ffmpeg 从mp4上提取H264的nalu

转载 2017年07月07日 15:08:00

原文链接: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;  
}  


H.264流媒体协议格式中的Annex B格式和AVCC格式深度解析

本文需要读者对H.264流有一定的了解才可以理解2种格式差异。          首先要理解的是没有标准的H.264基本流格式。文档中的确包含了一个Annex,特别是描述了一种可能的格式Annex B...
  • Romantic_Energy
  • Romantic_Energy
  • 2016年01月13日 09:44
  • 5938

H264 获取SPS与PPS(附源码)

在用Android手机进行h264硬编码的时候如果要进行视频流的实时传输与播放,就需要知道视频流的Sequence Parameter Sets (SPS) 和Picture Parameter Se...
  • xiangjai
  • xiangjai
  • 2013年10月21日 12:54
  • 2547

H264码流的两种形式:Annex B和AVCC——非常详细的翻译

http://blog.csdn.net/romantic_energy/article/details/50508332   H.264流媒体协议格式中的Annex B格...
  • STN_LCD
  • STN_LCD
  • 2017年07月06日 10:04
  • 813

FFmpeg 获取H264流中的sps pps

H.264的SPS和PPS串,包含了初始化H.264解码器所需要的信息参数,包括编码所用的profile,level,图像的宽和高,deblock滤波器等。 (1)avcC的数据结构: aligned...
  • ren65432
  • ren65432
  • 2015年02月03日 13:45
  • 5819

使用FFMPEG从MP4封装中提取视频流到H264文件

http://www.360doc.com/content/13/0925/17/13084517_317034684.shtml 命令行: ffmpeg -i 20130312_1333...
  • mengzhengjie
  • mengzhengjie
  • 2016年01月17日 18:58
  • 4236

MP4中提取H.264码流

 1.获取数据 ffmpeg读取mp4中的H264数据,并不能直接得到NALU,文件中也没有储存0x00000001的分隔符。下面这张图为packet.data中的数据 从图中可以发...
  • xiaojun111111
  • xiaojun111111
  • 2016年08月06日 11:26
  • 1422

关于FFMPEG里读取的h264的AVPACKET存储nalu成为h264问题

转载http://blog.csdn.net/leixiaohua1020/article/details/11800877经过试验确实是这样       在使用FFMPEG的类库进行编程的过程...
  • u010289908
  • u010289908
  • 2015年06月01日 18:29
  • 2821

H264文件解析出nalu数据,送给ffmpeg解码,opencv显示

本博客主要是H264的视频码流有ffmpeg 解码后,有opencv先,这里贴出全部代码,你只需自己建个工程,配置一下ffmpeg库和opencv3.0库就好了。 头文件 H264.h#includ...
  • baidu_31872269
  • baidu_31872269
  • 2017年08月15日 22:20
  • 421

ffmpeg 提取mp4 文件h264 数据

  • sky88866
  • sky88866
  • 2016年04月22日 12:51
  • 425

使用FFMPEG从MP4封装中提取视频流到.264文件

命令行: ffmpeg -i 20130312_133313.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 20130312_133313.264 说明...
  • cmsbupt
  • cmsbupt
  • 2013年03月24日 21:51
  • 3218
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ffmpeg 从mp4上提取H264的nalu
举报原因:
原因补充:

(最多只允许输入30个字)