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()相关推荐

  1. ffmpeg源码简析(十二)FFMPEG中的主要结构体总结

    FFMPEG中结构体很多.最关键的结构体可以分成以下几类: a) 解协议(http,rtsp,rtmp,mms) AVIOContext,URLProtocol,URLContext主要存储视音频使用 ...

  2. FFmpeg源码(三)解码前世今生——avcodec_decode_video2、avcodec_send_packet与avcodec_receive_frame

    写在前面 本节主要讲AVPacket中的数据解码到AVFrame中的过程. 前置知识点 1.FFmpeg数据结构简介 AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了 ...

  3. ffmpeg源码简析(十)libswscale中的SwsContext,sws_scale()

    libswscale是一个主要用于处理图片像素数据的类库.可以完成图片像素格式的转换,图片的拉伸等工作.  libswscale常用的函数数量很少,一般情况下就3个: sws_getContext() ...

  4. ffmpeg源码简析(九)av_log(),AVClass,AVOption

    1.av_log() av_log()是FFmpeg中输出日志的函数.随便打开一个FFmpeg的源代码文件,就会发现其中遍布着av_log()函数.一般情况下FFmpeg类库的源代码中是不允许使用pr ...

  5. ffmpeg源码简析(六)编码-av_write_frame(),av_write_trailer()

    1.av_write_frame() av_write_frame()用于输出一帧视音频数据,它的声明位于libavformat\avformat.h,如下所示. int av_write_frame ...

  6. ffmpeg实战教程(十三)iJKPlayer源码简析

    要使用封装优化ijk就必须先了解ffmpeg,然后看ijk对ffmpeg的C层封装! 这是我看ijk源码时候的笔记,比较散乱.不喜勿喷~ ijk源码简析: 1.ijkplayer_jni.c 封装的播 ...

  7. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  8. django源码简析——后台程序入口

    django源码简析--后台程序入口 这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结.工作中主 ...

  9. (Ajax)axios源码简析(三)——请求与取消请求

    传送门: axios源码简析(一)--axios入口文件 axios源码简析(二)--Axios类与拦截器 axios源码简析(三)--请求与取消请求 请求过程 在Axios.prototype.re ...

  10. java ArrayList 概述 与源码简析

    ArrayList 概述 与源码简析 1 ArrayList 创建 ArrayList<String> list = new ArrayList<>(); //构造一个初始容量 ...

最新文章

  1. 【自动驾驶】26.【很清晰】旋转矩阵,欧拉角,四元数,旋转向量和齐次变换矩阵
  2. SAP and ABAP Memory总结
  3. 信息系统项目管理师论文考试汇总(2010~2021年)
  4. 系兄弟就来砍我 有向图单源最短路
  5. Java中快速处理集合_简洁又快速地处理集合——Java8 Stream(上)
  6. 男子吐槽:为什么那么多人不喜欢996,非要年纪轻轻进国企养老
  7. Anroid View事件响应机制和ViewGroup的事件响应分发机制
  8. mysql数据库kj_Python3.7和数据库MySQL 8.0.12 数据库SQLite3连接(三)
  9. 如何用深度学习 AI 美颜实现天天 P 图疯狂变脸算法? | 技术头条
  10. 占用51cto。记录自己
  11. 请勿在计算机室吃带果壳的食品英语,双语者如何在两种语言间切换?
  12. android 取色器
  13. 网络调试助手(模拟下位机收发数据)快速指南
  14. pscp新机器提示Store key in cache? 重装后提示Update cached key?的解决方案
  15. JavaScript函数isFinite()
  16. 华为云容器镜像服务 SWR 加速镜像的拉取和推送
  17. 百度18年兴衰背后:一部互联网流量变迁史
  18. error: resource style/Theme.AppCompat.Light.NoActionBar
  19. CocosCreator开源框架(不断更新)
  20. 1. 人工智能(AI)概述

热门文章

  1. 能破解百度网盘提取码,云盘万能钥匙宣布关闭!
  2. windows 无法加载DLL “***.dll”:找不到指定的模块
  3. WeChat Subscribers Lite - 微信公众订阅号自动回复WordPress插件
  4. Java+SpringBoot+vue+elementui垃圾分类网站系统mysql源码介绍
  5. 微信小程序生成海报库
  6. C语言图形化界面——含图形、按钮、鼠标、进度条等部件制作(带详细代码、讲解及注释)
  7. 离散数学及其应用(第七版黑书)笔记
  8. _beginthread 与 _endthread 函数分析 (ReactOS版)
  9. 微信小程序在线旅游信息管理+后台管理系统
  10. 网络规划设计师水平考试备考资料(1.前言及目录)