博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用FFMPEG类库分离出多媒体文件中的H.264码流
阅读量:6453 次
发布时间:2019-06-23

本文共 6516 字,大约阅读时间需要 21 分钟。

在使用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 } else110 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及pps134 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参数传入空值即可。

转载地址:http://skfzo.baihongyu.com/

你可能感兴趣的文章
变量的原理
查看>>
Poj 1017 / OpenJudge 1017 Packets/装箱问题
查看>>
python 文件处理
查看>>
有氧运动 && 无氧运动
查看>>
mysql时间查看以及定时器相关操作
查看>>
【最大点独立集】【poj1419】【Graph Coloring】
查看>>
php5魔术函数、魔术常量
查看>>
Pusher 消息推送测试工具
查看>>
html&css中的文字对齐问题
查看>>
Qt基本布局(QLayout)
查看>>
springboot搭建的2种方式
查看>>
多线程网络程序服务端架构
查看>>
github文件上传及github pages博客搭建教程
查看>>
左神算法进阶班1_5BFPRT算法
查看>>
Linux修改用户密码
查看>>
小程序里json字符串转json对象需注意的地方
查看>>
【java】struts2+hibernate写的一个学生管理系统
查看>>
EasyUI datagrid easyui datagrid +dialog 加载 可直接运行 七
查看>>
HTTP 与 Post
查看>>
iOS开发本地推送(iOS10)UNUserNotificationCenter
查看>>