[FFmpeg-devel] [PATCH] [WIP] avcodec/videotoolbox: add Annex B support

wm4 nfxjfg at googlemail.com
Wed Sep 30 21:22:50 CEST 2015


This adds support for decoding .ts files demuxed by libavformat and
such.

The problem is that the VideoToolbox API wants mp4-style data for input,
but the extradata used to initialize the decoder was still in Annex B
format. It has to be reformatted to mp4-style data.

Fortunately, libavformat already has code for doing this in
libavformat/avc.c (used for mp4 muxing). Unfortunately, this can't be
used directly from libavcodec, because libavcodec can't use libavformat
symbols at all. This patch copies part of avc.c into videotoolbox.c, and
replaces AVIOContext usage (used for getting an appendable memory
buffer). The required function is ff_isom_write_avcc().

Possible solutions:
a) move ff_isom_write_avcc() to libavcodec, and use it as avpriv_
   from libavformat
b) somehow add it as public libavcodec API
c) keep it duplicated in videotoolbox.c

Further, this actually depends on libavformat extracting SPS/PPS into
extradata. It's probably not such a good idea to rely on this. It's
fragile and burdends API users which do their own demuxing.

Possible solutions:
a) reconstruct SPS/PPS from H264Context (this is probably extremely
   complex, if possible at all)
b) add code to the h264 decoder to "collect" the currently needed
   SPS/PPS NALs
c) just rely on the libavformat hack

Suggestions welcome. Also, is there no appendable byte stream thing in
lavc? This is pretty painful.
---
 libavcodec/videotoolbox.c | 188 ++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 175 insertions(+), 13 deletions(-)

diff --git a/libavcodec/videotoolbox.c b/libavcodec/videotoolbox.c
index ce939ca..eead217 100644
--- a/libavcodec/videotoolbox.c
+++ b/libavcodec/videotoolbox.c
@@ -77,28 +77,190 @@ int ff_videotoolbox_alloc_frame(AVCodecContext *avctx, AVFrame *frame)
     return 0;
 }
 
-CFDataRef ff_videotoolbox_avcc_extradata_create(AVCodecContext *avctx)
+static const uint8_t *ff_avc_find_startcode_internal(const uint8_t *p, const uint8_t *end)
 {
-    CFDataRef data = NULL;
+    const uint8_t *a = p + 4 - ((intptr_t)p & 3);
 
-    /* Each VCL NAL in the bitstream sent to the decoder
-     * is preceded by a 4 bytes length header.
-     * Change the avcC atom header if needed, to signal headers of 4 bytes. */
-    if (avctx->extradata_size >= 4 && (avctx->extradata[4] & 0x03) != 0x03) {
-        uint8_t *rw_extradata = av_memdup(avctx->extradata, avctx->extradata_size);
+    for (end -= 3; p < a && p < end; p++) {
+        if (p[0] == 0 && p[1] == 0 && p[2] == 1)
+            return p;
+    }
 
-        if (!rw_extradata)
-            return NULL;
+    for (end -= 3; p < end; p += 4) {
+        uint32_t x = *(const uint32_t*)p;
+//      if ((x - 0x01000100) & (~x) & 0x80008000) // little endian
+//      if ((x - 0x00010001) & (~x) & 0x00800080) // big endian
+        if ((x - 0x01010101) & (~x) & 0x80808080) { // generic
+            if (p[1] == 0) {
+                if (p[0] == 0 && p[2] == 1)
+                    return p;
+                if (p[2] == 0 && p[3] == 1)
+                    return p+1;
+            }
+            if (p[3] == 0) {
+                if (p[2] == 0 && p[4] == 1)
+                    return p+2;
+                if (p[4] == 0 && p[5] == 1)
+                    return p+3;
+            }
+        }
+    }
+
+    for (end += 3; p < end; p++) {
+        if (p[0] == 0 && p[1] == 0 && p[2] == 1)
+            return p;
+    }
+
+    return end + 3;
+}
+
+static const uint8_t *ff_avc_find_startcode(const uint8_t *p, const uint8_t *end){
+    const uint8_t *out= ff_avc_find_startcode_internal(p, end);
+    if(p<out && out<end && !out[-1]) out--;
+    return out;
+}
+
+static int ff_avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size)
+{
+    const uint8_t *p = buf_in;
+    const uint8_t *end = p + *size;
+    const uint8_t *nal_start, *nal_end;
+
+    *buf = NULL;
+    *size = 0;
+    nal_start = ff_avc_find_startcode(p, end);
+    for (;;) {
+        int append_len;
+        uint8_t *p;
+        int ret;
+
+        while (nal_start < end && !*(nal_start++));
+        if (nal_start == end)
+            break;
+        nal_end = ff_avc_find_startcode(nal_start, end);
+
+        append_len = 4 + (nal_end - nal_start);
 
-        rw_extradata[4] |= 0x03;
+        if ((ret = av_reallocp(buf, *size + append_len)) < 0)
+            return ret;
+        p = *buf + *size;
 
-        data = CFDataCreate(kCFAllocatorDefault, rw_extradata, avctx->extradata_size);
+        AV_WB32(p, nal_end - nal_start);
+        memcpy(p + 4, nal_start, nal_end - nal_start);
+        *size += append_len;
+        nal_start = nal_end;
+    }
+
+    return 0;
+}
+
+#define AV_W8(p, v) *(p) = (v)
+
+static int ff_isom_write_avcc(uint8_t **out, const uint8_t *data, int len)
+{
+    int outlen = 0;
+    *out = NULL;
+
+    if (len > 6) {
+        /* check for h264 start code */
+        if (AV_RB32(data) == 0x00000001 ||
+            AV_RB24(data) == 0x000001) {
+            uint8_t *buf=NULL, *end, *start;
+            uint32_t sps_size=0, pps_size=0;
+            uint8_t *sps=0, *pps=0;
+            int total_len;
+            uint8_t *p;
+
+            int ret = ff_avc_parse_nal_units_buf(data, &buf, &len);
+            if (ret < 0)
+                return ret;
+            start = buf;
+            end = buf + len;
+
+            /* look for sps and pps */
+            while (end - buf > 4) {
+                uint32_t size;
+                uint8_t nal_type;
+                size = FFMIN(AV_RB32(buf), end - buf - 4);
+                buf += 4;
+                nal_type = buf[0] & 0x1f;
+
+                if (nal_type == 7) { /* SPS */
+                    sps = buf;
+                    sps_size = size;
+                } else if (nal_type == 8) { /* PPS */
+                    pps = buf;
+                    pps_size = size;
+                }
+
+                buf += size;
+            }
+
+            if (!sps || !pps || sps_size < 4 || sps_size > UINT16_MAX || pps_size > UINT16_MAX)
+                return AVERROR_INVALIDDATA;
+
+            // Number of bytes to be appended in the code below
+            total_len = 11 + sps_size + pps_size;
+
+            if ((ret = av_reallocp(out, outlen + total_len)) < 0)
+                return ret;
+
+            p = *out + outlen;
+            outlen += total_len;
+
+            AV_W8(p + 0, 1); /* version */
+            AV_W8(p + 1, sps[1]); /* profile */
+            AV_W8(p + 2, sps[2]); /* profile compat */
+            AV_W8(p + 3, sps[3]); /* level */
+            AV_W8(p + 4, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */
+            AV_W8(p + 5, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */
+
+            AV_WB16(p + 6, sps_size);
+            memcpy(p + 8, sps, sps_size);
+            AV_W8(p + 8 + sps_size, 1); /* number of pps */
+            AV_WB16(p + 9 + sps_size, pps_size);
+            memcpy(p + 11 + sps_size, pps, pps_size);
+            av_free(start);
+        } else {
+            int ret;
+            if ((ret = av_reallocp(out, outlen + len)) < 0)
+                return ret;
+            memcpy(*out + outlen, data, len);
+            outlen += len;
+        }
+    }
+    return outlen;
+}
 
-        av_freep(&rw_extradata);
+CFDataRef ff_videotoolbox_avcc_extradata_create(AVCodecContext *avctx)
+{
+    CFDataRef data = NULL;
+    uint8_t *vt_extradata = NULL;
+    int vt_extradata_size = 0;
+
+    if (avctx->extradata_size && avctx->extradata[0] != 1) {
+        // Convert Annex B to mp4-style extradata.
+        // This relies on libavformat extracting SPS/PPS.
+        vt_extradata_size = ff_isom_write_avcc(&vt_extradata,
+                                               avctx->extradata,
+                                               avctx->extradata_size);
+        if (vt_extradata_size < 0)
+            return NULL;
     } else {
-        data = CFDataCreate(kCFAllocatorDefault, avctx->extradata, avctx->extradata_size);
+        vt_extradata = av_memdup(avctx->extradata, avctx->extradata_size);
+        if (!vt_extradata && avctx->extradata_size)
+            return NULL;
+        vt_extradata_size = avctx->extradata_size;
     }
 
+    /* Each VCL NAL in the bitstream sent to the decoder
+     * is preceded by a 4 bytes length header.
+     * Change the avcC atom header if needed, to signal headers of 4 bytes. */
+    if (vt_extradata_size >= 4)
+        vt_extradata[4] |= 0x03;
+
+    data = CFDataCreate(kCFAllocatorDefault, vt_extradata, vt_extradata_size);
+    av_free(vt_extradata);
     return data;
 }
 
-- 
2.5.1



More information about the ffmpeg-devel mailing list