ffmpeg 源代码简单分析 :av_read_frame()
2013-11-18 16:08
771 查看
原帖地址:/article/1347416.html
ffmpeg中的av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。
对该函数源代码的分析是很久之前做的了,现在翻出来,用博客记录一下。
上代码之前,先参考了其他人对av_read_frame()的解释,在此做一个参考:
通过av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以ts流为例,是读取一个完整的PES包(一个完整pes包包含若干视频或音频es包),读取完毕后,通过av_parser_parse2(***)分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL,即再此进入av_parser_parse2(***)流程,而不是下面的av_read_packet(**)流程,这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用av_read_frame(***)N次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。
av_read_frame()的源代码如下:
1、新版本的ffmpeg用的是av_read_frame,而老版本的是av_read_packet。区别是av_read_packet读出的是包,它可能是半帧或多帧,不保证帧的完整性。av_read_frame对av_read_packet进行了封装,使读出的数据总是完整的帧。
2、一般情况下,av_read_frame会调用read_frame_internal(),其代码如下所示:
ffmpeg中的av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。
对该函数源代码的分析是很久之前做的了,现在翻出来,用博客记录一下。
上代码之前,先参考了其他人对av_read_frame()的解释,在此做一个参考:
通过av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以ts流为例,是读取一个完整的PES包(一个完整pes包包含若干视频或音频es包),读取完毕后,通过av_parser_parse2(***)分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL,即再此进入av_parser_parse2(***)流程,而不是下面的av_read_packet(**)流程,这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用av_read_frame(***)N次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。
av_read_frame()的源代码如下:
int av_read_frame(***FormatContext *s, ***Packet *pkt) { const int genpts = s->flags & ***FMT_FLAG_GENPTS; int eof = 0; int ret; ***Stream *st; if (!genpts) { ret = s->packet_buffer ? read_from_packet_buffer(&s->packet_buffer, &s->packet_buffer_end, pkt) : read_frame_internal(s, pkt); if (ret < 0) return ret; goto return_packet; } for (;;) { ***PacketList *pktl = s->packet_buffer; if (pktl) { ***Packet *next_pkt = &pktl->pkt; if (next_pkt->dts != ***_NOPTS_VALUE) { int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits; // last dts seen for this stream. if any of packets following // current one had no dts, we will set this to ***_NOPTS_VALUE. int64_t last_dts = next_pkt->dts; while (pktl && next_pkt->pts == ***_NOPTS_VALUE) { if (pktl->pkt.stream_index == next_pkt->stream_index && (av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2LL << (wrap_bits - 1)) < 0)) { if (av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2LL << (wrap_bits - 1))) { //not b frame next_pkt->pts = pktl->pkt.dts; } if (last_dts != ***_NOPTS_VALUE) { // Once last dts was set to ***_NOPTS_VALUE, we don't change it. last_dts = pktl->pkt.dts; } } pktl = pktl->next; } if (eof && next_pkt->pts == ***_NOPTS_VALUE && last_dts != ***_NOPTS_VALUE) { // Fixing the last reference frame had none pts issue (For MXF etc). // We only do this when // 1. eof. // 2. we are not able to resolve a pts value for current packet. // 3. the packets for this stream at the end of the files had valid dts. next_pkt->pts = last_dts + next_pkt->duration; } pktl = s->packet_buffer; } /* read packet from packet buffer, if there is data */ if (!(next_pkt->pts == ***_NOPTS_VALUE && next_pkt->dts != ***_NOPTS_VALUE && !eof)) { ret = read_from_packet_buffer(&s->packet_buffer, &s->packet_buffer_end, pkt); goto return_packet; } } ret = read_frame_internal(s, pkt); if (ret < 0) { if (pktl && ret != ***ERROR(EAGAIN)) { eof = 1; continue; } else return ret; } if (av_dup_packet(add_to_pktbuf(&s->packet_buffer, pkt, &s->packet_buffer_end)) < 0) return ***ERROR(ENOMEM); } return_packet: st = s->streams[pkt->stream_index]; if (st->skip_samples) { uint8_t *p = av_packet_new_side_data(pkt, ***_PKT_DATA_SKIP_SAMPLES, 10); if (p) { ***_WL32(p, st->skip_samples); av_log(s, ***_LOG_DEBUG, "demuxer injecting skip %d\n", st->skip_samples); } st->skip_samples = 0; } if ((s->iformat->flags & ***FMT_GENERIC_INDEX) && pkt->flags & ***_PKT_FLAG_KEY) { ff_reduce_index(s, st->index); av_add_index_entry(st, pkt->pos, pkt->dts, 0, 0, ***INDEX_KEYFRAME); } if (is_relative(pkt->dts)) pkt->dts -= RELATIVE_TS_BASE; if (is_relative(pkt->pts)) pkt->pts -= RELATIVE_TS_BASE; return ret; }一些说明:
1、新版本的ffmpeg用的是av_read_frame,而老版本的是av_read_packet。区别是av_read_packet读出的是包,它可能是半帧或多帧,不保证帧的完整性。av_read_frame对av_read_packet进行了封装,使读出的数据总是完整的帧。
2、一般情况下,av_read_frame会调用read_frame_internal(),其代码如下所示:
static int read_frame_internal(***FormatContext *s, ***Packet *pkt) { int ret = 0, i, got_packet = 0; av_init_packet(pkt); while (!got_packet && !s->parse_queue) { ***Stream *st; ***Packet cur_pkt; /* read next packet */ ret = ff_read_packet(s, &cur_pkt); if (ret < 0) { if (ret == ***ERROR(EAGAIN)) return ret; /* flush the parsers */ for(i = 0; i < s->nb_streams; i++) { st = s->streams[i]; if (st->parser && st->need_parsing) parse_packet(s, NULL, st->index); } /* all remaining packets are now in parse_queue => * really terminate parsing */ break; } ret = 0; st = s->streams[cur_pkt.stream_index]; if (cur_pkt.pts != ***_NOPTS_VALUE && cur_pkt.dts != ***_NOPTS_VALUE && cur_pkt.pts < cur_pkt.dts) { av_log(s, ***_LOG_WARNING, "Invalid timestamps stream=%d, pts=%s, dts=%s, size=%d\n", cur_pkt.stream_index, av_ts2str(cur_pkt.pts), av_ts2str(cur_pkt.dts), cur_pkt.size); } if (s->debug & FF_FDEBUG_TS) av_log(s, ***_LOG_DEBUG, "ff_read_packet stream=%d, pts=%s, dts=%s, size=%d, duration=%d, flags=%d\n", cur_pkt.stream_index, av_ts2str(cur_pkt.pts), av_ts2str(cur_pkt.dts), cur_pkt.size, cur_pkt.duration, cur_pkt.flags); if (st->need_parsing && !st->parser && !(s->flags & ***FMT_FLAG_NOPARSE)) { st->parser = av_parser_init(st->codec->codec_id); if (!st->parser) { av_log(s, ***_LOG_VERBOSE, "parser not found for codec " "%s, packets or times may be invalid.\n", avcodec_get_name(st->codec->codec_id)); /* no parser available: just output the raw packets */ st->need_parsing = ***STREAM_PARSE_NONE; } else if(st->need_parsing == ***STREAM_PARSE_HEADERS) { st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; } else if(st->need_parsing == ***STREAM_PARSE_FULL_ONCE) { st->parser->flags |= PARSER_FLAG_ONCE; } else if(st->need_parsing == ***STREAM_PARSE_FULL_RAW) { st->parser->flags |= PARSER_FLAG_USE_CODEC_TS; } } if (!st->need_parsing || !st->parser) { /* no parsing needed: we just output the packet as is */ *pkt = cur_pkt; compute_pkt_fields(s, st, NULL, pkt); if ((s->iformat->flags & ***FMT_GENERIC_INDEX) && (pkt->flags & ***_PKT_FLAG_KEY) && pkt->dts != ***_NOPTS_VALUE) { ff_reduce_index(s, st->index); av_add_index_entry(st, pkt->pos, pkt->dts, 0, 0, ***INDEX_KEYFRAME); } got_packet = 1; } else if (st->discard < ***DISCARD_ALL) { if ((ret = parse_packet(s, &cur_pkt, cur_pkt.stream_index)) < 0) return ret; } else { /* free packet */ av_free_packet(&cur_pkt); } if (pkt->flags & ***_PKT_FLAG_KEY) st->skip_to_keyframe = 0; if (st->skip_to_keyframe) { av_free_packet(&cur_pkt); if (got_packet) { *pkt = cur_pkt; } got_packet = 0; } } if (!got_packet && s->parse_queue) ret = read_from_packet_buffer(&s->parse_queue, &s->parse_queue_end, pkt); if(s->debug & FF_FDEBUG_TS) av_log(s, ***_LOG_DEBUG, "read_frame_internal stream=%d, pts=%s, dts=%s, size=%d, duration=%d, flags=%d\n", pkt->stream_index, av_ts2str(pkt->pts), av_ts2str(pkt->dts), pkt->size, pkt->duration, pkt->flags); return ret; }更新:ffmpeg 2.1中,原文中提到的av_paser_parse2()已经被舍弃,新采用的方法随着学习的深入慢慢分析。
相关文章推荐
- 在ASP.NET中配置 html文件的访问验证
- 如何设置使 IIS 应用程序池使用 ASP.Net 3.5 而不是 2.0
- 一个标准的JavaBean
- hdu1072 Nightmare(优先队列,BFS)
- Python自学4:Python的程序流程
- YII框架调用插件PHPExcel的时候出现路径导入问题
- C++:多态性 (三)
- eclipse Java Workflow Tooling
- JVM——java对象生命周期(引用类型 创建对象规则)
- poj&nbsp;2447&nbsp;代码改正
- 6个常见的 PHP 安全性攻击
- JAVA 学习笔记一
- 解决PHP在IE浏览器下载文件,中文文件名乱码问题
- Java多线程的学习
- Java环境变量配置
- C#读写二进制文件
- lua 取一个数字的整数部分
- GoAgent,真乃神器也
- 关闭输入法及格式化VBA代码工具
- Yii 学习笔记 (Model篇)