与av_read_packet的区别是读出的是包,它可能是半帧或多帧,不保证帧的完整性。av_read_frame对av_read_packet进行了封装,使读出的数据总是完整的帧。
read_from_packet_buffer
read_from_packet_buffer函数比较简单,从AVPacketList里取出数据即可。若缓存中没有数据,av_read_frame函数则会调用read_frame_internal函数来获取数据。
read_frame_internal
read_frame_internal主要有以下两步,
(1)调用了ff_read_packet()从相应的AVInputFormat读取数据,如解析出TS的PES,一个PES包中可能有多个音频帧。
(2)如果媒体频流需要使用AVCodecParser,则调用parse_packet()解析相应的AVPacket,如调用h264_parser.c解析出一帧H264。
static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
int ret = 0, i, got_packet = 0;
AVDictionary *metadata = NULL;
av_init_packet(pkt);
while (!got_packet && !s->internal->parse_queue) {
//parse_packet成功后,s->internal->parse_queue就不为空。
AVStream *st;
AVPacket cur_pkt;
。。。
}
//退出循环后,调用read_from_packet_buffer,从parse_queue中取出数据。
if (!got_packet && s->internal->parse_queue)
ret = read_from_packet_buffer(&s->internal->parse_queue, &s->internal->parse_queue_end, pkt);
}
ff_read_packet
ff_read_packet函数也是会判断缓存是否有数据,若有则从缓存中取出,若没有,则调用demuxer的read_packet来读取数据。ff_read_packet()中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数,比如flv的flv_read_packet,flv_read_packet()的代码比较长,但是逻辑比较简单。它的主要功能就是根据FLV文件格式的规范,逐层解析Tag以及TagData,获取Tag以及TagData中的信息。
ff_read_packet的缓存和av_read_frame函数里的缓存是不一样的,ff_read_packet的缓存为AVFormatInternal::raw_packet_buffer,在av_read_frame函数里缓存则为
AVFormatInternal::packet_buffer,简单的理解为在codec被识别之前用raw_packet_buffer缓存,codec识别后用packet_buffer。
int ff_read_packet(AVFormatContext *s, AVPacket *pkt)
{
int ret, i, err;
AVStream *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 (;;) {
AVPacketList *pktl = s->raw_packet_buffer;
//首先判断s->raw_packet_buffer中是否有数据,一般开始的时候是没有的
if (pktl) {
*pkt = pktl->pkt;
st = s->streams[pkt->stream_index];
if (s->raw_packet_buffer_remaining_size <= 0)
if ((err = probe_codec(s, st, NULL)) < 0)
return err; //
if (st->request_probe <= 0) { //-1是probe完成,0是不要probe
s->raw_packet_buffer = pktl->next;
s->raw_packet_buffer_remaining_size += pkt->size;
av_free(pktl);
return 0;
}
}
pkt->data = NULL;
pkt->size = 0;
av_init_packet(pkt);
ret = s->iformat->read_packet(s, pkt);
//如果没有数据,调用s->iformat->read_packet(s,pkt),对于ts流来讲:s->iformat->read_packet调用的是mpegts_read_packet(读出一个PES包),0表示成功,小于0表示错误,pkt就在这里获取到。
if (ret < 0) {
continue;
}
//读取成功 ,判断是不是要丢掉
if ((s->flags & AVFMT_FLAG_DISCARD_CORRUPT) &&
(pkt->flags & AV_PKT_FLAG_CORRUPT)) {
av_log(s, AV_LOG_WARNING,
"Dropped corrupted packet (stream = %d)\n",
pkt->stream_index);
av_free_packet(pkt);
continue;
}
if (pkt->stream_index >= (unsigned)s->nb_streams) {
av_log(s, AV_LOG_ERROR, "Invalid stream index %d\n", pkt->stream_index);
continue;
}
st = s->streams[pkt->stream_index];
if (update_wrap_reference(s, st, pkt->stream_index, pkt) && st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET) {
// correct first time stamps to negative values
if (!is_relative(st->first_dts))
st->first_dts = wrap_timestamp(st, st->first_dts);
if (!is_relative(st->start_time))
st->start_time = wrap_timestamp(st, st->start_time);
if (!is_relative(st->cur_dts))
st->cur_dts = wrap_timestamp(st, st->cur_dts);
}
pkt->dts = wrap_timestamp(st, pkt->dts);
pkt->pts = wrap_timestamp(st, pkt->pts);
force_codec_ids(s, st);
/* TODO: audio: time filter; video: frame reordering (pts != dts) */
//音频要做时间过滤; 视频帧的重排序(当视频帧的dts和pts不等的时候)
if (s->use_wallclock_as_timestamps)
pkt->dts = pkt->pts = av_rescale_q(av_gettime(), AV_TIME_BASE_Q, st->time_base);
if (!pktl && st->request_probe <= 0)
return ret;
//最后调用add_to_pktbuf将packet加入s->raw_packet_buffer中。
add_to_pktbuf(&s->raw_packet_buffer, pkt, &s->raw_packet_buffer_end);//将packet加入到s->raw_packet_buffer
s->raw_packet_buffer_remaining_size -= pkt->size;
//raw buffer也是有大小限制的,就是raw_packet_buffer_remaining_size 这么大。
if ((err = probe_codec(s, st, pkt)) < 0)
return err;
}
}
probe_codec
probe_codec是用来探测codec的,probe_codec先是把pkt的数据打包进AVProbeData,然后调用set_codec_from_probe_data来进行探测的,set_codec_from_probe_data的基本思想是根据av_probe_input_format3函数返回的一个AVInputFormat格式来和fmt_id_type匹配得出的codec_id和type的。
parse_packet
parse_packet()给需要AVCodecParser的媒体流提供解析AVPacket的功能,最终调用了相应AVCodecParser的av_parser_parse2()函数,代码为s->parser->parser_parse,接着会调用具体的解析函数,如h264_parse(parse_nal_units),最终解析出来AVPacket。
h264解析
ff_h264_decode_seq_parameter_set 解析SPS。
ff_h264_decode_picture_parameter_set 解析PPS。
ff_h264_decode_sei 解析SEI。
以上代码在libavcdec/h264_parser.c。
AVClass与AVOption
AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的“桥梁”。AVClass要求必须声明为目标结构体的第一个变量。 AVClass中有一个option数组用于存储目标结构体的所有的AVOption。举个例子,AVFormatContext结构体,AVClass和AVOption之间的关系如下图所示。
图中AVFormatContext结构体的第一个变量为AVClass类型的指针av_class,它在AVFormatContext结构体初始化的时候,被赋值指向了全局静态变量av_format_context_class结构体(定义位于libavformat\options.c)。而AVClass类型的av_format_context_class结构体中的option变量指向了全局静态数组avformat_options(定义位于libavformat\options_table.h)。现在回到AVOption。其实除了可以对FFmpeg常用结构体AVFormatContext,AVCodecContext等进行赋值之外,还可以对它们的私有数据priv_data进行赋值。这个字段里通常存储了各种编码器特有的结构体。而这些结构体的定义在FFmpeg的SDK中是找不到的。例如使用libx264进行编码的时候,通过AVCodecContext的priv_data字段可以对X264Context结构体中的变量进行赋值,设置preset,profile等。使用libx265进行编码的时候,通过AVCodecContext的priv_data字段可以对libx265Context结构体中的变量进行赋值,设置preset,tune等。
AVProbeData
typedef struct AVProbeData {
const char *filename; //文件名
unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. // buf存储用于推测AVInputFormat的媒体数据,最后还有个
mime_type保存媒体的类型。其中buf可以为空,但是其后面无论如何都需要 填充AVPROBE_PADDING_SIZE个0(AVPROBE_PADDING_SIZE取值为32,即32个0)。 */
int buf_size; /**< Size of buf except extra allocated bytes */
const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
avformat_open_input(init_input(av_probe_input_format3), s->iformat->read_header());
init_ input
if ((ret = init_input(s, filename)) < 0)
goto fail;
//执行完此函数后,s->pb和s->iformat都已经指向了有效实例.pb是用于读写数据的,它把媒体数据当做流来读写,不管是什么媒体格式,而iformat把pb读出来的流按某种媒体格式进行分析,也就是说pb在底层,iformat在上层。
主要分两种情况
1 知道s->pb,从s->pb得到s->iformat。
2 不知道s->pb,打开文件(avio_open),探测文件格式(av_probe_input_buffer)。
av_probe_input_format3
av_probe_input_format3()根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中。该函数最主要的部分是一个循环。该循环调用av_iformat_next()遍历FFmpeg中所有的AVInputFormat(av_register_all时组建),并根据以下规则确定AVInputFormat和输入媒体数据的匹配分数(score,反应匹配程度):
(1) 如果AVInputFormat中包含read_probe(),就调用read_probe()函数获取匹配分数(这一方法如果结果匹配的话,一般会获 得AVPROBE_SCORE_MAX的分值,即100分)。如果不包含该函数,就使用av_match_ext()函数比较输入媒体的扩展名和 AVInputFormat的扩展名是否匹配,如果匹配的话,设定匹配分数为 AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值为50,即50分)。
(2)使用av_match_name()比较输入媒体的mime_type和AVInputFormat的mime_type,如果匹配的话,设定匹配分数为AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值为75,即75分)。
(3)如果该AVInputFormat的匹配分数大于此前的最大匹配分数,则记录当前的匹配分数为最大匹配分数,并且记录当前的AVInputFormat为最佳匹配的AVInputFormat。
ffmpeg.c
parse_option()
解析一个输入选项。具体的解析步骤不再赘述。parse_options()会循环调用parse_option()直到所有选项解析完毕。FFmpeg的每一个选项信息存储在一个OptionDef结构体中。
av_interleaved_write_frame与av_write_frame的区别?
av_interleaved_write_frame
/*
* Write apacket to an output media file ensuring correct interleaving.
* This function will buffer the packetsinternally as needed to make sure the
* packets in the output file are properlyinterleaved in the order of
* increasing dts. Callers doing their owninterleaving should call
* av_write_frame() instead of this function.
*/
int av_write_frame(AVFormatContext *s, AVPacket *pkt);
/*
*Write a packet to an output media file.
* Thisfunction passes the packet directly to the muxer, without any buffering
* orreordering. The caller is responsible for correctly interleaving the
*packets if the format requires it. Callers that want libavformat to handle
* theinterleaving should call av_interleaved_write_frame() instead of this
*function. */
av_interleaved_write_frame调用了interleave_packet,而av_write_frame没有调用,看看interleave_packet的代码:
static intinterleave_packet(AVFormatContext *s, AVPacket *out, AVPacket *in, intflush)
{
if(s->oformat->interleave_packet) {
int ret =s->oformat->interleave_packet(s, out, in, flush);
if (in)
av_free_packet(in);
return ret;
} else
returnff_interleave_packet_per_dts(s, out, in, flush);
}
代码非常简单,如果AVOutputFormat有interleave_packet函数指针,则调用,如果没有则调用ff_interleave_packet_per_dts,看看ff_interleave_packet_per_dts的代码:
intff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *out,
AVPacket *pkt,int flush)
{
AVPacketList *pktl;
int stream_count = 0;
int noninterleaved_count = 0;
int i, ret;
if (pkt) {//把pkt加入到AVPacketList中去
if ((ret = ff_interleave_add_packet(s,pkt, interleave_compare_dts)) < 0)
return ret;
}
for (i = 0; i < s->nb_streams; i++) {
if(s->streams[i]->last_in_packet_buffer) {
++stream_count;
} else if(s->streams[i]->codec->codec_type != AVMEDIA_TYPE_ATTACHMENT&&
s->streams[i]->codec->codec_id != AV_CODEC_ID_VP8 &&
s->streams[i]->codec->codec_id != AV_CODEC_ID_VP9) {
++noninterleaved_count;
}
}
if(s->internal->nb_interleaved_streams == stream_count)
flush = 1;
if (s->max_interleave_delta > 0&&
s->internal->packet_buffer&&
!flush &&
s->internal->nb_interleaved_streams ==stream_count+noninterleaved_count
) {
AVPacket *top_pkt =&s->internal->packet_buffer->pkt;
int64_t delta_dts = INT64_MIN;
int64_t top_dts =av_rescale_q(top_pkt->dts,
s->streams[top_pkt->stream_index]->time_base,
AV_TIME_BASE_Q);
for (i = 0; i < s->nb_streams;i++) {
int64_t last_dts;
const AVPacketList *last =s->streams[i]->last_in_packet_buffer;
if (!last)
continue;
last_dts =av_rescale_q(last->pkt.dts,
s->streams[i]->time_base,
AV_TIME_BASE_Q);
delta_dts = FFMAX(delta_dts,last_dts - top_dts);
// last_dts -top_dts为PTS差值。
}
/*dts间隔大于max_interleave_delta */
if (delta_dts >s->max_interleave_delta) {
av_log(s, AV_LOG_DEBUG,
"Delay between thefirst packet and last packet in the "
"muxing queue is%"PRId64" > %"PRId64": forcing output\n",
delta_dts,s->max_interleave_delta);
flush = 1;
}
}
if (stream_count && flush) {
AVStream *st;
pktl =s->internal->packet_buffer;
*out = pktl->pkt;
st = s->streams[out->stream_index];
s->internal->packet_buffer =pktl->next;
if (!s->internal->packet_buffer)
s->internal->packet_buffer_end = NULL;
if (st->last_in_packet_buffer ==pktl)
st->last_in_packet_buffer =NULL;
av_freep(&pktl);
return 1;
} else {
av_init_packet(out);
return 0;
}
}
从代码中看到,函数ff_interleave_packet_per_dts调用ff_interleave_add_packet把pkt加入缓存,然后再从缓存中取出第一个pkt返回。
函数ff_interleave_add_packet把pkt经过排序(如果缓存中有pkt的话)加入到缓存中,这个排序是根据ff_interleave_packet_per_dts函数传入的compare(其实就是函数interleave_compare_dts)指针做的。
interleave_compare_dts函数其实就是在比较传入的2个pkt的dts值,其实就是比那个的dts更早,如果有音频还有audio_preload(音频预加载时间)的话,还需要把音频包的预加载时间算进去。