鸽了许久,最近所做的项目算是有暂时喘口气的机会了,赶快吧FFmpeg解读捡起来,上次说到在第三次循环中,avformat_find_stream_info会对视频码流数据进行检查,以至于会进行一部分音视频数据的解码,来获取到stream中的真实数据。本文会进行第四次循环的分析,以及分析EOF情况的处理和编解码器codec的flush冲刷。
先看一下在第三次循环之后和第四次循环开始前进行的处理的代码,首先是进行了EOF(读取到了文件末尾)的情况的处理
if (eof_reached) {
int stream_index;
for (stream_index = 0; stream_index < ic->nb_streams; stream_index++) {
st = ic->streams[stream_index];
avctx = st->internal->avctx;
if (!has_codec_parameters(st, NULL)) {
const AVCodec *codec = find_probe_decoder(ic, st, st->codecpar->codec_id);
if (codec && !avctx->codec) {
AVDictionary *opts = NULL;
if (ic->codec_whitelist)
av_dict_set(&opts, "codec_whitelist", ic->codec_whitelist, 0);
if (avcodec_open2(avctx, codec, (options && stream_index < orig_nb_streams) ? &options[stream_index] : &opts) < 0)
av_log(ic, AV_LOG_WARNING,
"Failed to open codec in %s\n",__FUNCTION__);
av_dict_free(&opts);
}
}
// EOF already reached while reading the stream above.
// So continue with reoordering DTS with whatever delay we have.
if (ic->internal->packet_buffer && !has_decode_delay_been_guessed(st)) {
update_dts_from_pts(ic, stream_index, ic->internal->packet_buffer);
}
}
}
在第三次循环中,我们对用于探测的数据的大小进行了统计,而一旦这个数据量超过了整个音视频文件的数据量,处理就会出现EOF的情况,到了文件末尾,此时,就需要进行EOF的处理。在出现eof的处理中,对stream再次进行一次遍历,对没有设置编解码参数的stream,使用find_probe_decoder为它找一个codec,并设置好codec以及对应stream的avctx,使用avcodec_open2来补充相关的编解码信息参数,最后,根据pts的情况对dts信息进行更新。
在这之后,由于进行了编解码数据处理,所以按照惯例,需要把已经被动过的buffer进行flush冲刷,吃完饭就要洗碗,所以进入flush的情况处理
if (flush_codecs) {
AVPacket empty_pkt = { 0 };
int err = 0;
av_init_packet(&empty_pkt);
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
/* flush the decoders */
if (st->info->found_decoder == 1) {
do {
err = try_decode_frame(ic, st, &empty_pkt,
(options && i < orig_nb_streams)
? &options[i] : NULL);
} while (err > 0 && !has_codec_parameters(st, NULL));
if (err < 0) {
av_log(ic, AV_LOG_INFO,
"decoding for stream %d failed\n", st->index);
}
}
}
}
ff_rfps_calculate(ic);
整个解码过程就是往编解码器codec的buffer中不断塞数据、取数据的过程,但塞入一帧,往往取出的并不是刚刚塞入的这一帧,而是之前塞入的帧,所以,在编解码的buffer中可能还残留一些数据,所以这里设置了一个空的AVPacket,把这个空的pkt塞到解码器中,对于所有的stream中的编解码器进行这样一个处理,这样尚未解码出的数据就通过不停地try_decode_frame来冲刷出来了,直到被塞入的空pkt被冲刷出来,就代表buffer里面没有残留了。
最后,完成flush后再调用ff_rfps_calculate计算一下相关的fps数据。
第四次循环是一个关于stream类型判断和相关tag设置的循环,虽然比较长,但是功能并不复杂
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
avctx = st->internal->avctx;
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
if (avctx->codec_id == AV_CODEC_ID_RAWVIDEO && !avctx->codec_tag && !avctx->bits_per_coded_sample) {
uint32_t tag= avcodec_pix_fmt_to_codec_tag(avctx->pix_fmt);
if (avpriv_find_pix_fmt(avpriv_get_raw_pix_fmt_tags(), tag) == avctx->pix_fmt)
avctx->codec_tag= tag;
}
/* estimate average framerate if not set by demuxer */
if (st->info->codec_info_duration_fields &&
!st->avg_frame_rate.num &&
st->info->codec_info_duration) {
int best_fps = 0;
double best_error = 0.01;
AVRational codec_frame_rate = avctx->framerate;
if (st->info->codec_info_duration >= INT64_MAX / st->time_base.num / 2||
st->info->codec_info_duration_fields >= INT64_MAX / st->time_base.den ||
st->info->codec_info_duration < 0)
continue;
av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
st->info->codec_info_duration_fields * (int64_t) st->time_base.den,
st->info->codec_info_duration * 2 * (int64_t) st->time_base.num, 60000);
/* Round guessed framerate to a "standard" framerate if it's
* within 1% of the original estimate. */
for (j = 0; j < MAX_STD_TIMEBASES; j++) {
AVRational std_fps = { get_std_framerate(j), 12 * 1001 };
double error = fabs(av_q2d(st->avg_frame_rate) /
av_q2d(std_fps) - 1);
if (error < best_error) {
best_error = error;
best_fps = std_fps.num;
}
if (ic->internal->prefer_codec_framerate && codec_frame_rate.num > 0 && codec_frame_rate.den > 0) {
error = fabs(av_q2d(codec_frame_rate) /
av_q2d(std_fps) - 1);
if (error < best_error) {
best_error = error;
best_fps = std_fps.num;
}
}
}
if (best_fps)
av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
best_fps, 12 * 1001, INT_MAX);
}
if (!st->r_frame_rate.num) {
if ( avctx->time_base.den * (int64_t) st->time_base.num
<= avctx->time_base.num * avctx->ticks_per_frame * (uint64_t) st->time_base.den) {
av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den,
avctx->time_base.den, (int64_t)avctx->time_base.num * avctx->ticks_per_frame, INT_MAX);
} else {
st->r_frame_rate.num = st->time_base.den;
st->r_frame_rate.den = st->time_base.num;
}
}
if (st->display_aspect_ratio.num && st->display_aspect_ratio.den) {
AVRational hw_ratio = { avctx->height, avctx->width };
st->sample_aspect_ratio = av_mul_q(st->display_aspect_ratio,
hw_ratio);
}
} else if (avctx->codec_type == AVMEDIA_TYPE_AUDIO) {
if (!avctx->bits_per_coded_sample)
avctx->bits_per_coded_sample =
av_get_bits_per_sample(avctx->codec_id);
// set stream disposition based on audio service type
switch (avctx->audio_service_type) {
case AV_AUDIO_SERVICE_TYPE_EFFECTS:
st->disposition = AV_DISPOSITION_CLEAN_EFFECTS;
break;
case AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED:
st->disposition = AV_DISPOSITION_VISUAL_IMPAIRED;
break;
case AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED:
st->disposition = AV_DISPOSITION_HEARING_IMPAIRED;
break;
case AV_AUDIO_SERVICE_TYPE_COMMENTARY:
st->disposition = AV_DISPOSITION_COMMENT;
break;
case AV_AUDIO_SERVICE_TYPE_KARAOKE:
st->disposition = AV_DISPOSITION_KARAOKE;
break;
}
}
}
从代码中可以看出,这是对所有stream的再一次遍历,对stream,根据codec_type,即视频类型还是音频类型进行了区分。如果是视频类型,首先根据pix_fmt设置好tag,并且在用户没有手动设置好平均帧率average framerate的情况下,还需要进行平均帧率的计算估计,使用到了AVRational这个结构体来记录帧率,同时调用了av_reduce函数来计算帧率,整个平均帧率的计算过程也是比较复杂,代码讲解就不做过多计算的说明了
如果是音频类型,那么对stream中的disposition进行设置,设置好相关的属性。(触及到了我音频类型的知识盲区了)
概括一下内容:第四次循环开始前,find_stream函数首先收拾了第三次循环处理过程后可能留下的烂摊子,比如eof的数据,还有残存在codec解码器buffer中的剩余数据,接下来,是进行第四次循环,根据音视频流不同的类型,设置好相关参数,视频流是平均帧率的计算,音频流是disposition的设置。
下一篇文章,第五和第六次循环,以及find_stream的总结