ffmpeg 从mp4上提取H264的nalu

来源:互联网 发布:qq伪装地理位置软件 编辑:程序博客网 时间:2024/06/06 07:32

原文链接:http://blog.csdn.net/gavinr/article/details/7183499

1.获取数据
ffmpeg读取mp4中的H264数据,并不能直接得到NALU,文件中也没有储存0x00000001的分隔符。下面这张图为packet.data中的数据


从图中可以发现,packet中的数据起始处没有分隔符(0x00000001), 也不是0x65、0x67、0x68、0x41等字节,所以可以肯定这不是标准的nalu。

其实,前4个字0x000032ce表示的是nalu的长度,从第5个字节开始才是nalu的数据。所以直接将前4个字节替换为0x00000001即可得到标准的nalu数据。


2.获取pps及sps

pps及sps不能从packet获得,而是保存在AVCodecContext的extradata数据域中。如下:




如何从extradata中解析出sps及pps呢?ffmpeg中提供了一个流过滤器"h264_mp4toannexb"完成这项工作,关键代码如下:

static int h264_extradata_to_annexb(H264BSFContext *ctx, AVCodecContext *avctx, const int padding){    uint16_t unit_size;    uint64_t total_size                 = 0;    uint8_t *out                        = NULL, unit_nb, sps_done = 0,             sps_seen                   = 0, pps_seen = 0;    const uint8_t *extradata            = avctx->extradata + 4;    static const uint8_t nalu_header[4] = { 0, 0, 0, 1 };    int length_size = (*extradata++ & 0x3) + 1; // retrieve length coded size    ctx->sps_offset = ctx->pps_offset = -1;    /* retrieve sps and pps unit(s) */    unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */    if (!unit_nb) {        goto pps;    } else {        ctx->sps_offset = 0;        sps_seen = 1;    }    while (unit_nb--) {        int err;        unit_size   = AV_RB16(extradata);        total_size += unit_size + 4;        if (total_size > INT_MAX - padding) {            av_log(avctx, AV_LOG_ERROR,                   "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");            av_free(out);            return AVERROR(EINVAL);        }        if (extradata + 2 + unit_size > avctx->extradata + avctx->extradata_size) {            av_log(avctx, AV_LOG_ERROR, "Packet header is not contained in global extradata, "                   "corrupted stream or invalid MP4/AVCC bitstream\n");            av_free(out);            return AVERROR(EINVAL);        }        if ((err = av_reallocp(&out, total_size + padding)) < 0)            return err;        memcpy(out + total_size - unit_size - 4, nalu_header, 4);        memcpy(out + total_size - unit_size, extradata + 2, unit_size);        extradata += 2 + unit_size;pps:        if (!unit_nb && !sps_done++) {            unit_nb = *extradata++; /* number of pps unit(s) */            if (unit_nb) {                ctx->pps_offset = total_size;                pps_seen = 1;            }        }    }    if (out)        memset(out + total_size, 0, padding);    if (!sps_seen)        av_log(avctx, AV_LOG_WARNING,               "Warning: SPS NALU missing or invalid. "               "The resulting stream may not play.\n");    if (!pps_seen)        av_log(avctx, AV_LOG_WARNING,               "Warning: PPS NALU missing or invalid. "               "The resulting stream may not play.\n");    if (!ctx->private_spspps) {        av_free(avctx->extradata);        avctx->extradata      = out;        avctx->extradata_size = total_size;    }    ctx->spspps_buf  = out;    ctx->spspps_size = total_size;    return length_size;}static int h264_mp4toannexb_filter(AVBitStreamFilterContext *bsfc,                                   AVCodecContext *avctx, const char *args,                                   uint8_t **poutbuf, int *poutbuf_size,                                   const uint8_t *buf, int buf_size,                                   int keyframe){    H264BSFContext *ctx = bsfc->priv_data;    int i;    uint8_t unit_type;    int32_t nal_size;    uint32_t cumul_size    = 0;    const uint8_t *buf_end = buf + buf_size;    int ret = 0;    /* nothing to filter */    if (!avctx->extradata || avctx->extradata_size < 6) {        *poutbuf      = (uint8_t *)buf;        *poutbuf_size = buf_size;        return 0;    }    /* retrieve sps and pps NAL units from extradata */    if (!ctx->extradata_parsed) {        if (args && strstr(args, "private_spspps_buf"))            ctx->private_spspps = 1;        ret = h264_extradata_to_annexb(ctx, avctx, AV_INPUT_BUFFER_PADDING_SIZE);        if (ret < 0)            return ret;        ctx->length_size      = ret;        ctx->new_idr          = 1;        ctx->idr_sps_seen     = 0;        ctx->idr_pps_seen     = 0;        ctx->extradata_parsed = 1;    }    *poutbuf_size = 0;    *poutbuf      = NULL;    do {        ret= AVERROR(EINVAL);        if (buf + ctx->length_size > buf_end)            goto fail;        for (nal_size = 0, i = 0; i<ctx->length_size; i++)            nal_size = (nal_size << 8) | buf[i];        buf      += ctx->length_size;        unit_type = *buf & 0x1f;        if (nal_size > buf_end - buf || nal_size < 0)            goto fail;        if (unit_type == 7)            ctx->idr_sps_seen = ctx->new_idr = 1;        else if (unit_type == 8) {            ctx->idr_pps_seen = ctx->new_idr = 1;            /* if SPS has not been seen yet, prepend the AVCC one to PPS */            if (!ctx->idr_sps_seen) {                if (ctx->sps_offset == -1)                    av_log(avctx, AV_LOG_WARNING, "SPS not present in the stream, nor in AVCC, stream may be unreadable\n");                else {                    if ((ret = alloc_and_copy(poutbuf, poutbuf_size,                                         ctx->spspps_buf + ctx->sps_offset,                                         ctx->pps_offset != -1 ? ctx->pps_offset : ctx->spspps_size - ctx->sps_offset,                                         buf, nal_size)) < 0)                        goto fail;                    ctx->idr_sps_seen = 1;                    goto next_nal;                }            }        }        /* if this is a new IDR picture following an IDR picture, reset the idr flag.         * Just check first_mb_in_slice to be 0 as this is the simplest solution.         * This could be checking idr_pic_id instead, but would complexify the parsing. */        if (!ctx->new_idr && unit_type == 5 && (buf[1] & 0x80))            ctx->new_idr = 1;        /* prepend only to the first type 5 NAL unit of an IDR picture, if no sps/pps are already present */        if (ctx->new_idr && unit_type == 5 && !ctx->idr_sps_seen && !ctx->idr_pps_seen) {            if ((ret=alloc_and_copy(poutbuf, poutbuf_size,                               ctx->spspps_buf, ctx->spspps_size,                               buf, nal_size)) < 0)                goto fail;            ctx->new_idr = 0;        /* if only SPS has been seen, also insert PPS */        } else if (ctx->new_idr && unit_type == 5 && ctx->idr_sps_seen && !ctx->idr_pps_seen) {            if (ctx->pps_offset == -1) {                av_log(avctx, AV_LOG_WARNING, "PPS not present in the stream, nor in AVCC, stream may be unreadable\n");                if ((ret = alloc_and_copy(poutbuf, poutbuf_size,                                     NULL, 0, buf, nal_size)) < 0)                    goto fail;            } else if ((ret = alloc_and_copy(poutbuf, poutbuf_size,                                        ctx->spspps_buf + ctx->pps_offset, ctx->spspps_size - ctx->pps_offset,                                        buf, nal_size)) < 0)                goto fail;        } else {            if ((ret=alloc_and_copy(poutbuf, poutbuf_size,                               NULL, 0, buf, nal_size)) < 0)                goto fail;            if (!ctx->new_idr && unit_type == 1) {                ctx->new_idr = 1;                ctx->idr_sps_seen = 0;                ctx->idr_pps_seen = 0;            }        }next_nal:        buf        += nal_size;        cumul_size += nal_size + ctx->length_size;    } while (cumul_size < buf_size);    return 1;fail:    av_freep(poutbuf);    *poutbuf_size = 0;    return ret;}




一般情况下,extradata中包含一个sps、一个pps 的nalu, 从上面的代码中容易看出extradata的数据格式。分析后的sps及pps依然储存在extradata域中,并添加了起始符。从代码中还可以看出,上面的函数会将sps、pps及packet中的数据,都copy到poutbuf指示的内存中,如果不需要copy到指定内存,直接给buf参数传入空值即可。




3.使用ffmpeg的流过滤器获取sps及pps

流过滤器"h264_mp4toannexb", 在av_register_all()函数中会被注册。用法示例如下:

int ParseH264ExtraDataInMp4(int stream_id)  {      uint8_t *dummy = NULL;      int dummy_size;      AVBitStreamFilterContext* bsfc =  av_bitstream_filter_init("h264_mp4toannexb");          if(bsfc == NULL)      {          return -1;      }          av_bitstream_filter_filter(              bsfc, format_ctx_->streams[stream_id]->codec, NULL, &dummy, &dummy_size, NULL, 0, 0);      av_bitstream_filter_close(bsfc);          return 0;  }  


原创粉丝点击