ffmpeg源码简析(八)解码 av_read_frame(),avcodec_decode_video2(),avformat_close_input()
1.av_read_frame()
av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。
通过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()的声明位于libavformat\avformat.h,如下所示。
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
av_read_frame()使用方法在注释中写得很详细,用中文简单描述一下它的两个参数:
s:输入的AVFormatContextpkt:输出的AVPacket
如果返回0则说明读取正常。
av_read_frame()的定义位于libavformat\utils.c,如下所示:
//获取一个AVPacket /* * av_read_frame - 新版本的ffmpeg用的是av_read_frame,而老版本的是av_read_packet * 。区别是av_read_packet读出的是包,它可能是半帧或多帧,不保证帧的完整性。av_read_frame对 * av_read_packet进行了封装,使读出的数据总是完整的帧 */ int av_read_frame(AVFormatContext *s, AVPacket *pkt) { const int genpts = s->flags & AVFMT_FLAG_GENPTS; int eof = 0; if (!genpts) /** * This buffer is only needed when packets were already buffered but * not decoded, for example to get the codec parameters in MPEG * streams. * 一般情况下会调用read_frame_internal(s, pkt) * 直接返回 */ return s->packet_buffer ? read_from_packet_buffer(s, pkt) : read_frame_internal(s, pkt); for (;;) { int ret; AVPacketList *pktl = s->packet_buffer; if (pktl) { AVPacket *next_pkt = &pktl->pkt; if (next_pkt->dts != AV_NOPTS_VALUE) { int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits; while (pktl && next_pkt->pts == AV_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) && av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2LL << (wrap_bits - 1))) { //not b frame next_pkt->pts = pktl->pkt.dts; } pktl = pktl->next; } pktl = s->packet_buffer; } /* read packet from packet buffer, if there is data */ if (!(next_pkt->pts == AV_NOPTS_VALUE && next_pkt->dts != AV_NOPTS_VALUE && !eof)) return read_from_packet_buffer(s, pkt); } ret = read_frame_internal(s, pkt); if (ret < 0) { if (pktl && ret != AVERROR(EAGAIN)) { eof = 1; continue; } else return ret; } if (av_dup_packet(add_to_pktbuf(&s->packet_buffer, pkt, &s->packet_buffer_end)) < 0) return AVERROR(ENOMEM); } }
可以从源代码中看出,av_read_frame()调用了read_frame_internal()。
read_frame_internal()
read_frame_internal()代码比较长,这里只简单看一下它前面的部分。它前面部分有2步是十分关键的:
(1)调用了ff_read_packet()从相应的AVInputFormat读取数据。
(2)如果媒体频流需要使用AVCodecParser,则调用parse_packet()解析相应的AVPacket。
下面我们分成分别看一下ff_read_packet()和parse_packet()
ff_read_packet()
ff_read_packet()中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数。
flv_read_packet()
flv_read_packet()的定义位于libavformat\flvdec.c
它的主要功能就是根据(FLV)文件格式的规范,逐层解析(Tag)以及(TagData),获取Tag以及TagData中的信息。
parse_packet()
parse_packet()给需要AVCodecParser的媒体流提供解析AVPacket的功能
最终调用了相应AVCodecParser的av_parser_parse2()函数,解析出来AVPacket。此后根据解析的信息还进行了一系列的赋值工作
2.avcodec_decode_video2()
avcodec_decode_video2()的作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。该函数的声明位于libavcodec\avcodec.h
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);
源代码位于libavcodec\utils.c,如下所示:
int attribute_align_arg avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt) { AVCodecInternal *avci = avctx->internal; int ret; // copy to ensure we do not change avpkt AVPacket tmp = *avpkt; if (!avctx->codec) return AVERROR(EINVAL); //检查是不是视频(非音频) if (avctx->codec->type != AVMEDIA_TYPE_VIDEO) { av_log(avctx, AV_LOG_ERROR, "Invalid media type for video\n"); return AVERROR(EINVAL); } *got_picture_ptr = 0; //检查宽、高设置是否正确 if ((avctx->coded_width || avctx->coded_height) && av_image_check_size(avctx->coded_width, avctx->coded_height, 0, avctx)) return AVERROR(EINVAL); av_frame_unref(picture); if ((avctx->codec->capabilities & CODEC_CAP_DELAY) || avpkt->size || (avctx->active_thread_type & FF_THREAD_FRAME)) { int did_split = av_packet_split_side_data(&tmp); ret = apply_param_change(avctx, &tmp); if (ret < 0) { av_log(avctx, AV_LOG_ERROR, "Error applying parameter changes.\n"); if (avctx->err_recognition & AV_EF_EXPLODE) goto fail; } avctx->internal->pkt = &tmp; if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME) ret = ff_thread_decode_frame(avctx, picture, got_picture_ptr, &tmp); else { //最关键的解码函数 ret = avctx->codec->decode(avctx, picture, got_picture_ptr, &tmp); //设置pkt_dts字段的值 picture->pkt_dts = avpkt->dts; if(!avctx->has_b_frames){ av_frame_set_pkt_pos(picture, avpkt->pos); } //FIXME these should be under if(!avctx->has_b_frames) /* get_buffer is supposed to set frame parameters */ if (!(avctx->codec->capabilities & CODEC_CAP_DR1)) { //对一些字段进行赋值 if (!picture->sample_aspect_ratio.num) picture->sample_aspect_ratio = avctx->sample_aspect_ratio; if (!picture->width) picture->width = avctx->width; if (!picture->height) picture->height = avctx->height; if (picture->format == AV_PIX_FMT_NONE) picture->format = avctx->pix_fmt; } } add_metadata_from_side_data(avctx, picture); fail: emms_c(); //needed to avoid an emms_c() call before every return; avctx->internal->pkt = NULL; if (did_split) { av_packet_free_side_data(&tmp); if(ret == tmp.size) ret = avpkt->size; } if (*got_picture_ptr) { if (!avctx->refcounted_frames) { int err = unrefcount_frame(avci, picture); if (err < 0) return err; } avctx->frame_number++; av_frame_set_best_effort_timestamp(picture, guess_correct_pts(avctx, picture->pkt_pts, picture->pkt_dts)); } else av_frame_unref(picture); } else ret = 0; /* many decoders assign whole AVFrames, thus overwriting extended_data; * make sure it's set correctly */ av_assert0(!picture->extended_data || picture->extended_data == picture->data); #if FF_API_AVCTX_TIMEBASE if (avctx->framerate.num > 0 && avctx->framerate.den > 0) avctx->time_base = av_inv_q(av_mul_q(avctx->framerate, (AVRational){avctx->ticks_per_frame, 1})); #endif return ret; }
从代码中可以看出,avcodec_decode_video2()主要做了以下几个方面的工作:
(1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。
(2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。
(3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。
其中第二部是关键的一步,它调用了AVCodec的decode()方法完成了解码。AVCodec的decode()方法是一个函数指针,指向了具体解码器的解码函数。在这里我们以H.264解码器为例,看一下解码的实现过程。H.264解码器对应的AVCodec的定义位于libavcodec\h264.c
3.avformat_close_input()
avformat_close_input()函数。该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。
声明位于libavformat\avformat.h,如下所示
void avformat_close_input(AVFormatContext **s);
下面看一下avformat_close_input()的源代码,位于libavformat\utils.c文件中。
oid avformat_close_input(AVFormatContext **ps)
{ AVFormatContext *s; AVIOContext *pb; if (!ps || !*ps) return; s = *ps; pb = s->pb; if ((s->iformat && strcmp(s->iformat->name, "image2") && s->iformat->flags & AVFMT_NOFILE) || (s->flags & AVFMT_FLAG_CUSTOM_IO)) pb = NULL; flush_packet_queue(s); if (s->iformat) if (s->iformat->read_close) s->iformat->read_close(s); avformat_free_context(s); *ps = NULL; avio_close(pb);
}
从源代码中可以看出,avformat_close_input()主要做了以下几步工作:
(1)调用AVInputFormat的read_close()方法关闭输入流
(2)调用avformat_free_context()释放AVFormatContext
(3)调用avio_close()关闭并且释放AVIOContext
ffmpeg源码简析(八)解码 av_read_frame(),avcodec_decode_video2(),avformat_close_input()相关推荐
- ffmpeg源码简析(十二)FFMPEG中的主要结构体总结
FFMPEG中结构体很多.最关键的结构体可以分成以下几类: a) 解协议(http,rtsp,rtmp,mms) AVIOContext,URLProtocol,URLContext主要存储视音频使用 ...
- FFmpeg源码(三)解码前世今生——avcodec_decode_video2、avcodec_send_packet与avcodec_receive_frame
写在前面 本节主要讲AVPacket中的数据解码到AVFrame中的过程. 前置知识点 1.FFmpeg数据结构简介 AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了 ...
- ffmpeg源码简析(十)libswscale中的SwsContext,sws_scale()
libswscale是一个主要用于处理图片像素数据的类库.可以完成图片像素格式的转换,图片的拉伸等工作. libswscale常用的函数数量很少,一般情况下就3个: sws_getContext() ...
- ffmpeg源码简析(九)av_log(),AVClass,AVOption
1.av_log() av_log()是FFmpeg中输出日志的函数.随便打开一个FFmpeg的源代码文件,就会发现其中遍布着av_log()函数.一般情况下FFmpeg类库的源代码中是不允许使用pr ...
- ffmpeg源码简析(六)编码-av_write_frame(),av_write_trailer()
1.av_write_frame() av_write_frame()用于输出一帧视音频数据,它的声明位于libavformat\avformat.h,如下所示. int av_write_frame ...
- ffmpeg实战教程(十三)iJKPlayer源码简析
要使用封装优化ijk就必须先了解ffmpeg,然后看ijk对ffmpeg的C层封装! 这是我看ijk源码时候的笔记,比较散乱.不喜勿喷~ ijk源码简析: 1.ijkplayer_jni.c 封装的播 ...
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
- django源码简析——后台程序入口
django源码简析--后台程序入口 这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结.工作中主 ...
- (Ajax)axios源码简析(三)——请求与取消请求
传送门: axios源码简析(一)--axios入口文件 axios源码简析(二)--Axios类与拦截器 axios源码简析(三)--请求与取消请求 请求过程 在Axios.prototype.re ...
- java ArrayList 概述 与源码简析
ArrayList 概述 与源码简析 1 ArrayList 创建 ArrayList<String> list = new ArrayList<>(); //构造一个初始容量 ...
最新文章
- 【自动驾驶】26.【很清晰】旋转矩阵,欧拉角,四元数,旋转向量和齐次变换矩阵
- SAP and ABAP Memory总结
- 信息系统项目管理师论文考试汇总(2010~2021年)
- 系兄弟就来砍我 有向图单源最短路
- Java中快速处理集合_简洁又快速地处理集合——Java8 Stream(上)
- 男子吐槽:为什么那么多人不喜欢996,非要年纪轻轻进国企养老
- Anroid View事件响应机制和ViewGroup的事件响应分发机制
- mysql数据库kj_Python3.7和数据库MySQL 8.0.12 数据库SQLite3连接(三)
- 如何用深度学习 AI 美颜实现天天 P 图疯狂变脸算法? | 技术头条
- 占用51cto。记录自己
- 请勿在计算机室吃带果壳的食品英语,双语者如何在两种语言间切换?
- android 取色器
- 网络调试助手(模拟下位机收发数据)快速指南
- pscp新机器提示Store key in cache? 重装后提示Update cached key?的解决方案
- JavaScript函数isFinite()
- 华为云容器镜像服务 SWR 加速镜像的拉取和推送
- 百度18年兴衰背后:一部互联网流量变迁史
- error: resource style/Theme.AppCompat.Light.NoActionBar
- CocosCreator开源框架(不断更新)
- 1. 人工智能(AI)概述
热门文章
- 能破解百度网盘提取码,云盘万能钥匙宣布关闭!
- windows 无法加载DLL “***.dll”:找不到指定的模块
- WeChat Subscribers Lite - 微信公众订阅号自动回复WordPress插件
- Java+SpringBoot+vue+elementui垃圾分类网站系统mysql源码介绍
- 微信小程序生成海报库
- C语言图形化界面——含图形、按钮、鼠标、进度条等部件制作(带详细代码、讲解及注释)
- 离散数学及其应用(第七版黑书)笔记
- _beginthread 与 _endthread 函数分析 (ReactOS版)
- 微信小程序在线旅游信息管理+后台管理系统
- 网络规划设计师水平考试备考资料(1.前言及目录)