从学龄前开始解读FFMPEG代码 之 avformat_find_stream_info函数三

从学龄前开始解读FFMPEG代码 之 avformat_find_stream_info函数 三

    • 开始学习前想说的话
    • 函数实现4-EOF的情况和flush
    • 函数实现5-第四次循环
    • 结尾的话

开始学习前想说的话

鸽了许久,最近所做的项目算是有暂时喘口气的机会了,赶快吧FFmpeg解读捡起来,上次说到在第三次循环中,avformat_find_stream_info会对视频码流数据进行检查,以至于会进行一部分音视频数据的解码,来获取到stream中的真实数据。本文会进行第四次循环的分析,以及分析EOF情况的处理和编解码器codec的flush冲刷。

函数实现4-EOF的情况和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数据。

函数实现5-第四次循环

第四次循环是一个关于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的总结

你可能感兴趣的:(FFMPEG,音视频,视频处理,c语言)