avcodec/videotoolbox: use decode_params to propagate H264 PPS changes and restart...
authorAman Gupta <aman@tmm1.net>
Fri, 10 Nov 2017 20:25:39 +0000 (12:25 -0800)
committerAman Gupta <aman@tmm1.net>
Mon, 13 Nov 2017 22:32:48 +0000 (14:32 -0800)
This fixes decoding of H264 video samples with SPS and PPS changes.

See for example https://s3.amazonaws.com/tmm1/videotoolbox/spschange.ts,
which previously stalled the decoder and failed to produce any new frames
after the SPS change.

Also see https://s3.amazonaws.com/tmm1/videotoolbox/ppschange.ts, which
uses multiple PPS and would previously cause VT decode failures.

If the VideoToolbox session needs to be restarted, and
videotoolbox_start() fails for some reason (for instance, if the video
is interlaced and the decoder is running on iOS), avcodec will return
AVERROR_EXTERNAL. This can be used by the API user to switch to another
decoder.

Signed-off-by: Aman Gupta <aman@tmm1.net>
libavcodec/videotoolbox.c
libavcodec/vt_internal.h

index 4fe89a2..c24f5aa 100644 (file)
@@ -42,6 +42,9 @@ enum { kCMVideoCodecType_HEVC = 'hvc1' };
 
 #define VIDEOTOOLBOX_ESDS_EXTRADATA_PADDING  12
 
+static void videotoolbox_stop(AVCodecContext *avctx);
+static int videotoolbox_start(AVCodecContext *avctx);
+
 static void videotoolbox_buffer_release(void *opaque, uint8_t *data)
 {
     CVPixelBufferRef cv_buffer = (CVImageBufferRef)data;
@@ -307,6 +310,27 @@ int ff_videotoolbox_h264_start_frame(AVCodecContext *avctx,
     return 0;
 }
 
+static int videotoolbox_h264_decode_params(AVCodecContext *avctx,
+                                           int type,
+                                           const uint8_t *buffer,
+                                           uint32_t size)
+{
+    VTContext *vtctx = avctx->internal->hwaccel_priv_data;
+
+    if (type == H264_NAL_SPS) {
+        if (!vtctx->sps || vtctx->sps_len != size || memcmp(buffer, vtctx->sps, size) != 0) {
+            vtctx->sps = av_fast_realloc(vtctx->sps, &vtctx->sps_capa, size);
+            if (vtctx->sps)
+                memcpy(vtctx->sps, buffer, size);
+            vtctx->reconfig_needed = true;
+            vtctx->sps_len = size;
+        }
+    }
+
+    // pass-through new PPS to the decoder
+    return ff_videotoolbox_h264_decode_slice(avctx, buffer, size);
+}
+
 int ff_videotoolbox_h264_decode_slice(AVCodecContext *avctx,
                                       const uint8_t *buffer,
                                       uint32_t size)
@@ -339,6 +363,7 @@ int ff_videotoolbox_uninit(AVCodecContext *avctx)
     VTContext *vtctx = avctx->internal->hwaccel_priv_data;
     if (vtctx) {
         av_freep(&vtctx->bitstream);
+        av_freep(&vtctx->sps);
         if (vtctx->frame)
             CVPixelBufferRelease(vtctx->frame);
     }
@@ -591,17 +616,30 @@ static int videotoolbox_common_end_frame(AVCodecContext *avctx, AVFrame *frame)
     AVVideotoolboxContext *videotoolbox = videotoolbox_get_context(avctx);
     VTContext *vtctx = avctx->internal->hwaccel_priv_data;
 
-    if (!videotoolbox->session || !vtctx->bitstream)
+    if (vtctx->reconfig_needed == true) {
+        vtctx->reconfig_needed = false;
+        av_log(avctx, AV_LOG_VERBOSE, "VideoToolbox decoder needs reconfig, restarting..\n");
+        videotoolbox_stop(avctx);
+        if (videotoolbox_start(avctx) != 0) {
+            return AVERROR_EXTERNAL;
+        }
+    }
+
+    if (!videotoolbox->session || !vtctx->bitstream || !vtctx->bitstream_size)
         return AVERROR_INVALIDDATA;
 
     status = videotoolbox_session_decode_frame(avctx);
     if (status != noErr) {
+        if (status == kVTVideoDecoderMalfunctionErr || status == kVTInvalidSessionErr)
+            vtctx->reconfig_needed = true;
         av_log(avctx, AV_LOG_ERROR, "Failed to decode frame (%s, %d)\n", videotoolbox_error_string(status), (int)status);
         return AVERROR_UNKNOWN;
     }
 
-    if (!vtctx->frame)
+    if (!vtctx->frame) {
+        vtctx->reconfig_needed = true;
         return AVERROR_UNKNOWN;
+    }
 
     return videotoolbox_buffer_create(avctx, frame);
 }
@@ -1018,6 +1056,7 @@ AVHWAccel ff_h264_videotoolbox_hwaccel = {
     .alloc_frame    = ff_videotoolbox_alloc_frame,
     .start_frame    = ff_videotoolbox_h264_start_frame,
     .decode_slice   = ff_videotoolbox_h264_decode_slice,
+    .decode_params  = videotoolbox_h264_decode_params,
     .end_frame      = videotoolbox_h264_end_frame,
     .frame_params   = videotoolbox_frame_params,
     .init           = videotoolbox_common_init,
index 607dd7a..fc27dad 100644 (file)
@@ -38,6 +38,12 @@ typedef struct VTContext {
     // Non-NULL if the new hwaccel API is used. This is only a separate struct
     // to ease compatibility with the old API.
     struct AVVideotoolboxContext *vt_ctx;
+
+    // Current H264 parameters (used to trigger decoder restart on SPS changes).
+    uint8_t                     *sps;
+    uint32_t                    sps_len;
+    unsigned int                sps_capa;
+    bool                        reconfig_needed;
 } VTContext;
 
 int ff_videotoolbox_alloc_frame(AVCodecContext *avctx, AVFrame *frame);