zoukankan      html  css  js  c++  java
  • 使用FFMPEG类库分离出多媒体文件中的H.264码流

    在使用FFMPEG的类库进行编程的过程中,可以直接输出解复用之后的的视频数据码流。只需要在每次调用av_read_frame()之后将得到的视频的AVPacket存为本地文件即可。

    经试验,在分离MPEG2码流的时候,直接存储AVPacket即可。

    在分离H.264码流的时候,直接存储AVPacket后的文件可能是不能播放的。

    如果视音频复用格式是TS(MPEG2 Transport Stream),直接存储后的文件是可以播放的。

    复用格式是FLV,MP4则不行。

    经过长时间资料搜索发现,FLV,MP4这些属于“特殊容器”,需要经过以下处理才能得到可播放的H.264码流:

    1.第一次存储AVPacket之前需要在前面加上H.264的SPS和PPS。这些信息存储在AVCodecContext的extradata里面。

    并且需要使用FFMPEG中的名为"h264_mp4toannexb"的bitstream filter 进行处理。

    然后将处理后的extradata存入文件

    具体代码如下:(源码见最后)

    FILE *fp=fopen("test.264","ab");
    AVCodecContext *pCodecCtx=...  
    
    unsigned char *dummy=NULL;   //输入的指针  
    int dummy_len;  
    AVBitStreamFilterContext* bsfc =  av_bitstream_filter_init("h264_mp4toannexb");    
    av_bitstream_filter_filter(bsfc, pCodecCtx, NULL, &dummy, &dummy_len, NULL, 0, 0);  
    fwrite(pCodecCtx->extradata,pCodecCtx-->extradata_size,1,fp);  
    av_bitstream_filter_close(bsfc);    
    free(dummy);  

    2.通过查看FFMPEG源代码我们发现,AVPacket中的数据起始处没有分隔符(0x00000001), 也不是0x65、0x67、0x68、0x41等字节,所以可以AVPacket肯定这不是标准的nalu。其实,AVPacket前4个字表示的是nalu的长度,从第5个字节开始才是nalu的数据。所以直接将AVPacket前4个字节替换为0x00000001即可得到标准的nalu数据。

    具体代码如下:

    char nal_start[]={0,0,0,1};  
    fwrite(nal_start,4,1,fp);  
    fwrite(pkt->data+4,pkt->size-4,1,fp);  
    fclose(fp);  

    经过以上两步处理之后,我们就得到了可以正常播放的H.264码流。

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

      1 //h264_mp4toannexb_bsf.c
      2 static int h264_mp4toannexb_filter(AVBitStreamFilterContext *bsfc,
      3                                    AVCodecContext *avctx, const char *args,
      4                                    uint8_t  **poutbuf, int *poutbuf_size,
      5                                    const uint8_t *buf, int      buf_size,
      6                                    int keyframe) {
      7     H264BSFContext *ctx = bsfc->priv_data;
      8     uint8_t unit_type;
      9     int32_t nal_size;
     10     uint32_t cumul_size = 0;
     11     const uint8_t *buf_end = buf + buf_size;
     12 
     13 
     14     /* nothing to filter */
     15     if (!avctx->extradata || avctx->extradata_size < 6) {
     16         *poutbuf = (uint8_t*) buf;
     17         *poutbuf_size = buf_size;
     18         return 0;
     19     }
     20     
     21     //
     22     //从extradata中分析出SPS、PPS
     23     //
     24     /* retrieve sps and pps NAL units from extradata */
     25     if (!ctx->extradata_parsed) {
     26         uint16_t unit_size;
     27         uint64_t total_size = 0;
     28         uint8_t *out = NULL, unit_nb, sps_done = 0, sps_seen = 0, pps_seen = 0;
     29         const uint8_t *extradata = avctx->extradata+4;  //跳过前4个字节
     30         static const uint8_t nalu_header[4] = {0, 0, 0, 1};
     31 
     32 
     33         /* retrieve length coded size */
     34         ctx->length_size = (*extradata++ & 0x3) + 1;    //用于指示表示编码数据长度所需字节数
     35         if (ctx->length_size == 3)
     36             return AVERROR(EINVAL);
     37 
     38 
     39         /* retrieve sps and pps unit(s) */
     40         unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */
     41         if (!unit_nb) {
     42             goto pps;
     43         } else {
     44             sps_seen = 1;
     45         }
     46 
     47 
     48         while (unit_nb--) {
     49             void *tmp;
     50 
     51 
     52             unit_size = AV_RB16(extradata);
     53             total_size += unit_size+4;
     54             if (total_size > INT_MAX - FF_INPUT_BUFFER_PADDING_SIZE ||
     55                 extradata+2+unit_size > avctx->extradata+avctx->extradata_size) {
     56                 av_free(out);
     57                 return AVERROR(EINVAL);
     58             }
     59             tmp = av_realloc(out, total_size + FF_INPUT_BUFFER_PADDING_SIZE);
     60             if (!tmp) {
     61                 av_free(out);
     62                 return AVERROR(ENOMEM);
     63             }
     64             out = tmp;
     65             memcpy(out+total_size-unit_size-4, nalu_header, 4);
     66             memcpy(out+total_size-unit_size,   extradata+2, unit_size);
     67             extradata += 2+unit_size;
     68 pps:
     69             if (!unit_nb && !sps_done++) {
     70                 unit_nb = *extradata++; /* number of pps unit(s) */
     71                 if (unit_nb)
     72                     pps_seen = 1;
     73             }
     74         }
     75 
     76 
     77         if(out)
     78             memset(out + total_size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
     79 
     80 
     81         if (!sps_seen)
     82             av_log(avctx, AV_LOG_WARNING, "Warning: SPS NALU missing or invalid. The resulting stream may not play.
    ");
     83         if (!pps_seen)
     84             av_log(avctx, AV_LOG_WARNING, "Warning: PPS NALU missing or invalid. The resulting stream may not play.
    ");
     85 
     86 
     87         av_free(avctx->extradata);
     88         avctx->extradata      = out;
     89         avctx->extradata_size = total_size;
     90         ctx->first_idr        = 1;
     91         ctx->extradata_parsed = 1;
     92     }
     93 
     94 
     95     *poutbuf_size = 0;
     96     *poutbuf = NULL;
     97     do {
     98         if (buf + ctx->length_size > buf_end)
     99             goto fail;  //buf为NULL时,以下代码将不再执行
    100 
    101 
    102         //
    103         //用于保存数据长度的字节数,是在分析原extradata计算出来的
    104         //
    105         if (ctx->length_size == 1) {
    106             nal_size = buf[0];
    107         } else if (ctx->length_size == 2) {
    108             nal_size = AV_RB16(buf);
    109         } else
    110             nal_size = AV_RB32(buf);
    111 
    112 
    113         buf += ctx->length_size;
    114         unit_type = *buf & 0x1f;
    115 
    116 
    117         if (buf + nal_size > buf_end || nal_size < 0)
    118             goto fail;
    119 
    120 
    121         /* prepend only to the first type 5 NAL unit of an IDR picture */
    122         if (ctx->first_idr && unit_type == 5) {
    123             //
    124             //copy IDR 帧时,需要将sps及pps一同拷贝
    125             //
    126             if (alloc_and_copy(poutbuf, poutbuf_size,
    127                                avctx->extradata, avctx->extradata_size,
    128                                buf, nal_size) < 0)
    129                 goto fail;
    130             ctx->first_idr = 0;
    131         } else {
    132             //
    133             //非IDR帧,没有sps及pps
    134             if (alloc_and_copy(poutbuf, poutbuf_size,
    135                                NULL, 0,
    136                                buf, nal_size) < 0)
    137                 goto fail;
    138             if (!ctx->first_idr && unit_type == 1)
    139                 ctx->first_idr = 1;
    140         }
    141 
    142 
    143         buf += nal_size;
    144         cumul_size += nal_size + ctx->length_size;
    145     } while (cumul_size < buf_size);
    146 
    147 
    148     return 1;
    149 
    150 
    151 fail:
    152     av_freep(poutbuf);
    153     *poutbuf_size = 0;
    154     return AVERROR(EINVAL);
    155 }

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

  • 相关阅读:
    自己实现的string的库函数
    单链表的面试题
    顺序表的实现
    指针数组与数组指针
    指针与数组
    sizeof 与 strlen
    HTML配色工具!在线配色工具
    [转载] python的sorted函数对字典按key排序和按value排序
    [转载]python脚本删除一定时间以外的文件
    python基础教程(四)
  • 原文地址:https://www.cnblogs.com/jiu0821/p/9101985.html
Copyright © 2011-2022 走看看