使用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.\n");
 83         if (!pps_seen)
 84             av_log(avctx, AV_LOG_WARNING, "Warning: PPS NALU missing or invalid. The resulting stream may not play.\n");
 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参数传入空值即可。

posted @ 2018-05-28 20:01  jiu~  阅读(991)  评论(0编辑  收藏  举报