您的位置:首页 > 其它

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

2017-04-24 20:42 429 查看

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:输入的AVFormatContext

pkt:输出的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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: