ffmpeg源码简析(八)解码 av_read_frame(),avcodec_decode_video2(),avformat_close_input()

来源:互联网 发布:淘宝商家入口 编辑:程序博客网 时间:2024/05/29 18:17

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
1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 黑魂3被入侵了怎么办 轮胎螺丝滑牙了怎么办 gta5ol寻宝任务退出了怎么办 gta5线上模式买房子之后怎么办 开摩托车忘记带安全头盔怎么办 gta不想要车了怎么办 空气滤芯进水会怎么办 车胎扎了个钉子怎么办 德罗索没导弹了怎么办 CF手雷包不能用怎么办 cfAK爆头碰到狙怎么办 玩cf网络延迟高怎么办 逆水寒装备分解错了怎么办 轴与孔间隙过大怎么办 小孩眼睛被打了怎么办 玩王者荣耀手机屏幕竖着怎么办 棉被被老鼠尿湿怎么办 打完子弹能下来怎么办 怪物猎人 弩子弹打完了怎么办 烤瓷牙龈发黑了怎么办 做彩超前喝水了怎么办 胸贴过敏红痒怎么办 芡粉里面有虫了怎么办 勾芡淀粉放多了怎么办 剁排骨没有好刀怎么办 硬币掉进档位里怎么办 魅族mx5手机太卡怎么办 华为m9手机声音小怎么办 放卡的地方堵住怎么办 美图m8蓝屏了怎么办 美图m8手机蓝屏怎么办 美图m6s手机白屏怎么办 魅蓝2开不开机怎么办 魅族手机主键没反应怎么办 魅族手机主键失灵怎么办 手机4g网络不稳定怎么办 华为m9收不到手机信息怎么办 华为手机wifi信号弱怎么办 手机连接wifi信号差怎么办 华华为p10信号不好怎么办 烟没拆封受潮了怎么办