[FFmpeg-devel] [PATCH v2 8/8] lavc: add async decoding/encoding API

wm4 nfxjfg at googlemail.com
Wed Mar 23 14:02:15 CET 2016


This needs to be explicitly supported by the AVCodec. Async mode can
be enabled for AVCodecs without explicit support too, which is treated
as if the codec performs all work instantly.
---
 libavcodec/avcodec.h | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 libavcodec/utils.c   |  30 ++++++++++++++
 2 files changed, 143 insertions(+), 1 deletion(-)

diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index f654bd2..437b473 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -160,6 +160,39 @@
  * @}
  */
 
+
+/**
+ * @ingroup libavc
+ * @defgroup lavc_async send/receive asynchronous mode API overview
+ * @{
+ *
+ * Asynchronous mode can be enabled by setting AV_CODEC_FLAG2_ASYNC_MODE in
+ * AVCodecContext.flags2 before opening the decoder. You must set the
+ * AVCodecContext.async_notification callback as well.
+ *
+ * Decoding and encoding work like in synchronous mode, except that:
+ * - avcodec_send_packet()/avcodec_send_frame()/avcodec_receive_packet()/
+ *   avcodec_receive_frame() potentially never block, and may return
+ *   AVERROR(EAGAIN) without making any real progress. This breaks the
+ *   guarantee that if e.g. avcodec_send_packet() return AVERROR(EAGAIN),
+ *   that avcodec_receive_frame() will definitely return a frame.
+ * - If these functions all return AVERROR(EAGAIN), then the codec is doing
+ *   decoding or encoding on a worker thread, and the API user has to wait
+ *   until data is returned. The async_notification callback will be invoked
+ *   by the decoder as soon as there is a change to be expected.
+ * - To avoid race conditions, the function avcodec_check_async_progress()
+ *   exists. Before going to sleep until async_notification is called, this
+ *   function must be called to determine whether there is actually more
+ *   required input or available output.
+ *
+ * Not all codecs support true asynchronous operation. Those which do are
+ * marked with AV_CODEC_CAP_ASYNC. While other codecs will likely just block
+ * the caller and do work on the caller's thread, the asynchronous mode API
+ * will (strictly speaking) still work and fulfill the guarantees given by
+ * it.
+ * @}
+ */
+
 /**
  * @defgroup lavc_core Core functions/structures.
  * @ingroup libavc
@@ -910,6 +943,12 @@ typedef struct RcOverride{
  * Discard cropping information from SPS.
  */
 #define AV_CODEC_FLAG2_IGNORE_CROP    (1 << 16)
+/**
+ * Enable asynchronous encoding/decoding mode. This depends on codec support,
+ * and will be effectively ignored in most cases. See also AV_CODEC_CAP_ASYNC.
+ * See asynchronous mode section for details.
+ */
+#define AV_CODEC_FLAG2_ASYNC_MODE     (1 << 17)
 
 /**
  * Show all frames before the first keyframe
@@ -1025,6 +1064,17 @@ typedef struct RcOverride{
  */
 #define AV_CODEC_CAP_VARIABLE_FRAME_SIZE (1 << 16)
 /**
+ * This codec has full support for AV_CODEC_FLAG2_ASYNC_MODE. This means it will
+ * never block the decoder (opening/flushing/closing it might still block, but
+ * not sending/receiving packets or frames). See asynchronous mode section for
+ * details.
+ */
+#define AV_CODEC_CAP_ASYNC               (1 << 17)
+/**
+ * This codec requires AV_CODEC_FLAG2_ASYNC_MODE to be set.
+ */
+#define AV_CODEC_CAP_ASYNC_ONLY          (1 << 18)
+/**
  * Codec is intra only.
  */
 #define AV_CODEC_CAP_INTRA_ONLY       0x40000000
@@ -1033,7 +1083,6 @@ typedef struct RcOverride{
  */
 #define AV_CODEC_CAP_LOSSLESS         0x80000000
 
-
 #if FF_API_WITHOUT_PREFIX
 /**
  * Allow decoders to produce frames with data planes that are not aligned
@@ -3491,6 +3540,15 @@ typedef struct AVCodecContext {
 #define FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS 1
 #endif
 
+    /**
+     * Notification callback for asynchronous decoding mode. Must be set if
+     * asynchronous mode is enabled. See asynchronous mode section for details.
+     *
+     * The callback can access the avcodec_send_/_receive_* functions, as long
+     * as the caller guarantees that only one thread accesses the AVCodecContext
+     * concurrently. Other accesses must be done outside of the callback.
+     */
+    int (*async_notification)(struct AVCodecContext *s);
 } AVCodecContext;
 
 AVRational av_codec_get_pkt_timebase         (const AVCodecContext *avctx);
@@ -3626,6 +3684,10 @@ typedef struct AVCodec {
     int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
     int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);
     /**
+     * Required for AV_CODEC_CAP_ASYNC. Behaves like avcodec_check_async_progress().
+     */
+    int (*check_async_progress)(AVCodecContext *avctx);
+    /**
      * Flush buffers.
      * Will be called when seeking
      */
@@ -4676,6 +4738,56 @@ int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
  */
 int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
 
+/**
+ * Determine whether there is still work to do (sending input or receiving
+ * output). See asynchronous mode section for an overview.
+ *
+ * If this returns 0, the caller thread can go to sleep (or do something else)
+ * while the codec is processing previously sent input data on a worker thread.
+ * It is guaranteed that AVCodecContext.async_notification will be called once
+ * the API user can do more progress by calling the usual functions for sending/
+ * receiving input/output. Strictly speaking, the callback could be invoked
+ * _while_ the function call returns to the caller.
+ *
+ * Here is an example that should avoid missed wakeups due to race conditions:
+ *
+ *      sem_t semaphore;
+ *      void user_async_notification(AVCodecContext *ctx) {
+ *              // unblock the decoding loop
+ *              sem_post(&semaphore);
+ *      }
+ *      void decode() {
+ *              AVCodecContext *ctx = ...alloc and setup context...;
+ *              ctx->flags2 |= AV_CODEC_FLAG2_ASYNC_MODE;
+ *              ctx->async_notification = user_async_notification;
+ *              sem_init(&semaphore, 0, 0);
+ *              avcodec_open2(ctx, codec, NULL);
+ *              // decoding loop
+ *              while (1) {
+ *                      // exchanges packets/frames; either or both of these
+ *                      // might return AVERROR(EAGAIN)
+ *                      avcodec_send_packet(ctx, ...);
+ *                      avcodec_receive_frame(ctx, ...);
+ *                      // reset the wait count to avoid unnecessary wakeups
+ *                      while (!sem_try_wait(&semaphore));
+ *                      // the important part is that user_async_notification
+ *                      // could be called starting from here (_during_ and not
+ *                      // _after_ the avcodec_check_async_progress call)
+ *                      if (avcodec_check_async_progress(ctx) == 0)
+ *                              sem_wait(&semaphore);
+ *              }
+ *              ...
+ *      }
+ *
+ * If asynchronous mode is not enabled with AV_CODEC_FLAG2_ASYNC_MODE, this
+ * function will always return 1, and async_notification will never be called.
+ *
+ * @return 0 if caller can go to sleep (and wait for the async_notification
+ *         callback),
+ *         >0 if caller has to send new input or receive output,
+ *         negative error code on error.
+ */
+int avcodec_check_async_progress(AVCodecContext *avctx);
 
 /**
  * @defgroup lavc_parsing Frame parsing
diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 3ba7412..ff15cc0 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -1232,6 +1232,25 @@ int attribute_align_arg avcodec_open2(AVCodecContext *avctx, const AVCodec *code
     if (ret < 0)
         return ret;
 
+    if (codec->capabilities & AV_CODEC_CAP_ASYNC)
+        av_assert0(codec->check_async_progress);
+
+    if (avctx->flags2 & AV_CODEC_FLAG2_ASYNC_MODE) {
+        if (!avctx->async_notification) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "AV_CODEC_FLAG2_ASYNC_MODE set, but no "
+                   "AVCodecContext.async_notification callback provided.\n");
+            ret = AVERROR(EINVAL);
+            goto end;
+        }
+    } else if (codec->capabilities & AV_CODEC_CAP_ASYNC_ONLY) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Decoder is marked as AV_CODEC_CAP_ASYNC_ONLY, but "
+               "AV_CODEC_FLAG2_ASYNC_MODE not set.\n");
+        ret = AVERROR(EINVAL);
+        goto end;
+    }
+
     avctx->internal = av_mallocz(sizeof(AVCodecInternal));
     if (!avctx->internal) {
         ret = AVERROR(ENOMEM);
@@ -2926,6 +2945,17 @@ int attribute_align_arg avcodec_receive_packet(AVCodecContext *avctx, AVPacket *
     return 0;
 }
 
+int attribute_align_arg avcodec_check_async_progress(AVCodecContext *avctx)
+{
+    if (!avcodec_is_open(avctx))
+        return AVERROR(EINVAL);
+
+    if (!(avctx->flags2 & AV_CODEC_FLAG2_ASYNC_MODE))
+        return 1;
+
+    return avctx->codec->check_async_progress(avctx);
+}
+
 av_cold int avcodec_close(AVCodecContext *avctx)
 {
     int i;
-- 
2.8.0.rc3



More information about the ffmpeg-devel mailing list