[v3,1/7] avio: Allow custom IO users to get labels for the output bytestream

Message ID 1463486568-8200-1-git-send-email-martin@martin.st
State Committed
Headers show

Commit Message

Martin Storsjö May 17, 2016, 12:02 p.m.
This allows callers with avio write callbacks to get the bytestream
positions that correspond to keyframes, suitable for live streaming.

In the simplest form, a caller could expect that a header is written
to the bytestream during the avformat_write_header, and the data
output to the avio context during e.g. av_write_frame corresponds
exactly to the current packet passed in.

When combined with av_interleaved_write_frame, and with muxers that
do buffering (most muxers that do some sort of fragmenting or
clustering), the mapping from input data to bytestream positions
is nontrivial.

This allows callers to get directly information about what part
of the bytestream is what, without having to resort to assumptions
about the muxer behaviour.

One keyframe/fragment/block can still be split into multiple (if
they are larger than the aviocontext buffer), which would call
the callback with e.g. AVIO_DATA_MARKER_SYNC_POINT, followed by
AVIO_DATA_MARKER_UNKNOWN for the second time it is called with
the following data.

---
Renamed the enums to AVIO_DATA_MARKER_*, as requested by Anton,
and approved by him with that change.
---
 doc/APIchanges        |  4 ++++
 libavformat/avio.h    | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/aviobuf.c | 52 ++++++++++++++++++++++++++++++++++++++--
 libavformat/mux.c     |  6 +++++
 libavformat/version.h |  4 ++--
 5 files changed, 128 insertions(+), 4 deletions(-)

Comments

Martin Storsjö May 17, 2016, 12:09 p.m. | #1
On Tue, 17 May 2016, Martin Storsjö wrote:

> This allows callers with avio write callbacks to get the bytestream
> positions that correspond to keyframes, suitable for live streaming.
>
> In the simplest form, a caller could expect that a header is written
> to the bytestream during the avformat_write_header, and the data
> output to the avio context during e.g. av_write_frame corresponds
> exactly to the current packet passed in.
>
> When combined with av_interleaved_write_frame, and with muxers that
> do buffering (most muxers that do some sort of fragmenting or
> clustering), the mapping from input data to bytestream positions
> is nontrivial.
>
> This allows callers to get directly information about what part
> of the bytestream is what, without having to resort to assumptions
> about the muxer behaviour.
>
> One keyframe/fragment/block can still be split into multiple (if
> they are larger than the aviocontext buffer), which would call
> the callback with e.g. AVIO_DATA_MARKER_SYNC_POINT, followed by
> AVIO_DATA_MARKER_UNKNOWN for the second time it is called with
> the following data.
>
> ---
> Renamed the enums to AVIO_DATA_MARKER_*, as requested by Anton,
> and approved by him with that change.
> ---
> doc/APIchanges        |  4 ++++
> libavformat/avio.h    | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++
> libavformat/aviobuf.c | 52 ++++++++++++++++++++++++++++++++++++++--
> libavformat/mux.c     |  6 +++++
> libavformat/version.h |  4 ++--
> 5 files changed, 128 insertions(+), 4 deletions(-)

The whole set has been approved now, so will push tomorrow unless there's 
something else still to change.

// Martin

Patch

diff --git a/doc/APIchanges b/doc/APIchanges
index b89303a..e4b0432 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -13,6 +13,10 @@  libavutil:     2015-08-28
 
 API changes, most recent first:
 
+2016-xx-xx - xxxxxxx - lavf 57.6.0 - avio.h
+  Add AVIODataMarkerType, write_data_type, ignore_boundary_point and
+  avio_write_marker.
+
 2016-xx-xx - xxxxxxx - lavu 55.11.0 - hwcontext_dxva2.h
   Add new installed header with DXVA2-specific hwcontext definitions.
 
diff --git a/libavformat/avio.h b/libavformat/avio.h
index d60a597..4bd5cb1 100644
--- a/libavformat/avio.h
+++ b/libavformat/avio.h
@@ -54,6 +54,42 @@  typedef struct AVIOInterruptCB {
 } AVIOInterruptCB;
 
 /**
+ * Different data types that can be returned via the AVIO
+ * write_data_type callback.
+ */
+enum AVIODataMarkerType {
+    /**
+     * Header data; this needs to be present for the stream to be decodeable.
+     */
+    AVIO_DATA_MARKER_HEADER,
+    /**
+     * A point in the output bytestream where a decoder can start decoding
+     * (i.e. a keyframe). A demuxer/decoder given the data flagged with
+     * AVIO_DATA_MARKER_HEADER, followed by any AVIO_DATA_MARKER_SYNC_POINT,
+     * should give decodeable results.
+     */
+    AVIO_DATA_MARKER_SYNC_POINT,
+    /**
+     * A point in the output bytestream where a demuxer can start parsing
+     * (for non self synchronizing bytestream formats). That is, any
+     * non-keyframe packet start point.
+     */
+    AVIO_DATA_MARKER_BOUNDARY_POINT,
+    /**
+     * This is any, unlabelled data. It can either be a muxer not marking
+     * any positions at all, it can be an actual boundary/sync point
+     * that the muxer chooses not to mark, or a later part of a packet/fragment
+     * that is cut into multiple write callbacks due to limited IO buffer size.
+     */
+    AVIO_DATA_MARKER_UNKNOWN,
+    /**
+     * Trailer data, which doesn't contain actual content, but only for
+     * finalizing the output file.
+     */
+    AVIO_DATA_MARKER_TRAILER
+};
+
+/**
  * Bytestream IO Context.
  * New fields can be added to the end with minor version bumps.
  * Removal, reordering and changes to existing fields require a major
@@ -115,6 +151,24 @@  typedef struct AVIOContext {
      * A combination of AVIO_SEEKABLE_ flags or 0 when the stream is not seekable.
      */
     int seekable;
+
+    /**
+     * A callback that is used instead of write_packet.
+     */
+    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
+                           enum AVIODataMarkerType type, int64_t time);
+    /**
+     * If set, don't call write_data_type separately for AVIO_DATA_MARKER_BOUNDARY_POINT,
+     * but ignore them and treat them as AVIO_DATA_MARKER_UNKNOWN (to avoid needlessly
+     * small chunks of data returned from the callback).
+     */
+    int ignore_boundary_point;
+
+    /**
+     * Internal, not meant to be used from outside of AVIOContext.
+     */
+    enum AVIODataMarkerType current_type;
+    int64_t last_time;
 } AVIOContext;
 
 /**
@@ -193,6 +247,18 @@  int avio_put_str16le(AVIOContext *s, const char *str);
 int avio_put_str16be(AVIOContext *s, const char *str);
 
 /**
+ * Mark the written bytestream as a specific type.
+ *
+ * Zero-length ranges are omitted from the output.
+ *
+ * @param time the stream time the current bytestream pos corresponds to
+ *             (in AV_TIME_BASE units), or AV_NOPTS_VALUE if unknown or not
+ *             applicable
+ * @param type the kind of data written starting at the current pos
+ */
+void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type);
+
+/**
  * ORing this as the "whence" parameter to a seek function causes it to
  * return the filesize without seeking anywhere. Supporting this is optional.
  * If it is not supported then the seek function will return <0.
diff --git a/libavformat/aviobuf.c b/libavformat/aviobuf.c
index 29fccbe..706cf5d 100644
--- a/libavformat/aviobuf.c
+++ b/libavformat/aviobuf.c
@@ -140,6 +140,11 @@  int ffio_init_context(AVIOContext *s,
     s->read_pause = NULL;
     s->read_seek  = NULL;
 
+    s->write_data_type       = NULL;
+    s->ignore_boundary_point = 0;
+    s->current_type          = AVIO_DATA_MARKER_UNKNOWN;
+    s->last_time             = AV_NOPTS_VALUE;
+
     return 0;
 }
 
@@ -163,13 +168,25 @@  AVIOContext *avio_alloc_context(
 static void flush_buffer(AVIOContext *s)
 {
     if (s->buf_ptr > s->buffer) {
-        if (s->write_packet && !s->error) {
-            int ret = s->write_packet(s->opaque, s->buffer,
+        if (!s->error) {
+            int ret = 0;
+            if (s->write_data_type)
+                ret = s->write_data_type(s->opaque, s->buffer,
+                                         s->buf_ptr - s->buffer,
+                                         s->current_type,
+                                         s->last_time);
+            else if (s->write_packet)
+                ret = s->write_packet(s->opaque, s->buffer,
                                       s->buf_ptr - s->buffer);
             if (ret < 0) {
                 s->error = ret;
             }
         }
+        if (s->current_type == AVIO_DATA_MARKER_SYNC_POINT ||
+            s->current_type == AVIO_DATA_MARKER_BOUNDARY_POINT) {
+            s->current_type = AVIO_DATA_MARKER_UNKNOWN;
+        }
+        s->last_time = AV_NOPTS_VALUE;
         if (s->update_checksum) {
             s->checksum     = s->update_checksum(s->checksum, s->checksum_ptr,
                                                  s->buf_ptr - s->checksum_ptr);
@@ -402,6 +419,37 @@  void avio_wb24(AVIOContext *s, unsigned int val)
     avio_w8(s, val);
 }
 
+void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type)
+{
+    if (!s->write_data_type)
+        return;
+    // If ignoring boundary points, just treat it as unknown
+    if (type == AVIO_DATA_MARKER_BOUNDARY_POINT && s->ignore_boundary_point)
+        type = AVIO_DATA_MARKER_UNKNOWN;
+    // Avoid unnecessary flushes if we are already in non-header/trailer
+    // data and setting the type to unknown
+    if (type == AVIO_DATA_MARKER_UNKNOWN &&
+        (s->current_type != AVIO_DATA_MARKER_HEADER &&
+         s->current_type != AVIO_DATA_MARKER_TRAILER))
+        return;
+
+    switch (type) {
+    case AVIO_DATA_MARKER_HEADER:
+    case AVIO_DATA_MARKER_TRAILER:
+        // For header/trailer, ignore a new marker of the same type;
+        // consecutive header/trailer markers can be merged.
+        if (type == s->current_type)
+            return;
+        break;
+    }
+
+    // If we've reached here, we have a new, noteworthy marker.
+    // Flush the previous data and mark the start of the new data.
+    avio_flush(s);
+    s->current_type = type;
+    s->last_time = time;
+}
+
 /* Input stream */
 
 static void fill_buffer(AVIOContext *s)
diff --git a/libavformat/mux.c b/libavformat/mux.c
index 0761a93..49fe65c 100644
--- a/libavformat/mux.c
+++ b/libavformat/mux.c
@@ -256,11 +256,15 @@  int avformat_write_header(AVFormatContext *s, AVDictionary **options)
     if (ret = init_muxer(s, options))
         return ret;
 
+    if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
+        avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_HEADER);
     if (s->oformat->write_header) {
         ret = s->oformat->write_header(s);
         if (ret < 0)
             return ret;
     }
+    if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
+        avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_UNKNOWN);
 
     if (s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO) {
         if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) {
@@ -704,6 +708,8 @@  int av_write_trailer(AVFormatContext *s)
             goto fail;
     }
 
+    if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
+        avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
     if (s->oformat->write_trailer)
         ret = s->oformat->write_trailer(s);
 
diff --git a/libavformat/version.h b/libavformat/version.h
index aae1e23..a50e443 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -30,8 +30,8 @@ 
 #include "libavutil/version.h"
 
 #define LIBAVFORMAT_VERSION_MAJOR 57
-#define LIBAVFORMAT_VERSION_MINOR  5
-#define LIBAVFORMAT_VERSION_MICRO  2
+#define LIBAVFORMAT_VERSION_MINOR  6
+#define LIBAVFORMAT_VERSION_MICRO  0
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \