evsqiezi

攻城师 349383985

ffmpeg.c总体调用

本文主要以转码为例子,流程主要为解析命令,转码,转码又可以细分为:打开编解码器,开始转码,关闭编解码器。

FFmpeg的总体函数调用结构图如下图所示:

命令解析

ffmpeg_parse_options

命令解析主要在ret = ffmpeg_parse_options(argc, argv);

find_option

 po = find_option(options, opt);  //从全局const OptionDef options[]中查找opt,opt是字符串,对应OptionDef的第一列。

void opt_output_file(void *optctx, const char *filename)
{
    //略…
    err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);
 
    if (!oc) {
        print_error(filename, err);
        exit(1);
    }
    //略…
   new_video_stream();
   …
   new_audio_stream();
   …
   new_subtitle_stream ();
    //略…
}

avformat_find_stream_info

        avformat_find_stream_info()函数可以读取一部分视音频数据并且获得一些相关的信息。函数正常执行后返回值大于等于0。该函数上下文部分是调节起播延迟效果最明显的地方。函数分析流的时间主要由传入的AVFormatContext的probesize 和 max_analyze_duration两个属性决定,probesize是探测读入大小,默认值为32K;max_analyze_duration默认值为5S。在网络状况比较好的情况下可分别设置4K和1S,也可根据具体情况在起播画面效果、起播延迟、分析成功率等因素间取舍。这两个参数的设置在avformat_open_input()函数对结构体AVFormatContext处理完成后进行设置。


在ffmpeg_parse_options中,会调用libavfornat/utils.c里的avformat_find_stream_info,其中会会调用try_decode_frame(avcodec_open2,avcodec_decode_xxx,),avcodec_close;重要步骤为:
1.查找解码器:find_decoder()
2.打开解码器:avcodec_open2()
3.读取完整的一帧压缩编码的数据:read_frame_internal()
4.解码一些压缩编码数据:try_decode_frame()

choose_output()

用于选择一个OutputStream。比如有一个audio,一个video,那要根据pts策略,比如谁的pts比较小,就挑哪个OutputStream先干活。
transcode_frome_filter()函数用于选个一个InputStream,用于下一步的process_input()。
process_input()函数主要是解码,并把解码的buffer送往filter处理。

reap_filters()函数主要是,从filter的FIFO拿出buffer,并编码。

do_video_out(AVFormatContext*s...   //准备编码YUV数据。

static OutputStream *choose_output(void)
{
    int i;
    int64_t opts_min = INT64_MAX;
    OutputStream *ost_min = NULL;
    for (i = 0; i < nb_output_streams; i++) {
        OutputStream *ost = output_streams[i];
        int64_t opts = ost->st->cur_dts == AV_NOPTS_VALUE ? INT64_MIN :
                       av_rescale_q(ost->st->cur_dts, ost->st->time_base,
                                    AV_TIME_BASE_Q);
        if (ost->st->cur_dts == AV_NOPTS_VALUE)
            av_log(NULL, AV_LOG_DEBUG, "cur_dts is invalid (this is harmless if it occurs once at the start per stream)\n");

        if (!ost->finished && opts < opts_min) {
            opts_min = opts;
            ost_min  = ost->unavailable ? NULL : ost;
        }

    }

  //比如;输出是双码率,nb_output_streams就是4(一个视频,一个音频),从4个输出里找最小的,即为:opts_min。

    return ost_min;
}

update_stream_timings
start_time1 = av_rescale_q(st->start_time, st->time_base,AV_TIME_BASE_Q);
start_time = FFMIN(start_time, start_time1);
ic->start_time = start_time;  //90000基转成1000000基,修改了开始时间。如133200变成1480000。

try_decode_frame
从try_decode_frame()的定义可以看出,该函数首先判断视音频流的解码器是否已经打开,如果没有打开的话,先打开相应的解码器。接下来根据视音频流类型的不同,调用不同的解码函数进行解码:视频流调用avcodec_decode_video2(),音频流调用avcodec_decode_audio4(),字幕流调用avcodec_decode_subtitle2()。解码的循环会一直持续下去直到满足了while()的所有条件。while()语句的条件中有一个has_codec_parameters()函数,用于判断AVStream中的成员变量是否都已经设置完毕。该函数在avformat_find_stream_info()中的多个地方被使用过。

转码

转码的内容封装在transcode函数里,会重新打开解码器,编码器。

# 打开编解码器

如上图,打开编码器,到最后还是调用具体的编码器(X264编码器),X264初始化的函数为int X264_init(AVCodecContext *avctx),可以看出ffmpeg通过AVCodecContext结构体,将编码参数传递给x264的x264_param_t。

process_input

主要是解码,并把解码的buffer送往filter处理。

process_input_packet

该函数对帧数据进行解码并通过所有适用的过滤器进行处理。时间戳校准和字幕处理的工作也在这个函数中进行。最后,在函数返回之前,已解码的帧被复制到每个相关的输出流。

复制解码后的帧

for (i = 0; pkt && i < nb_output_streams; i++) {
  ...  // check constraints
  do_streamcopy(ist, ost, pkt);   
}

transcode_step()

transcode_step()调用了如下函数:

process_input():完成解码工作。

transcode_step()函数调用reap_filters()函数(第1424行)来循环遍历每个输出流。reap_filters()函数的for循环负责收集缓冲区中待处理的帧,并将这些帧进行编码,然后封装到一个输出文件中。

reap_filters()

reap_filters()主要完成了编码的工作。其函数调用结构如下图所示。

reap_filters()调用了如下函数
av_buffersink_get_buffer_ref():从AVFilterContext中取出一帧解码后的数据(结构为AVFilterBufferRef,可以转换为AVFrame)。
avfilter_copy_buf_props():AVFilterBufferRef转换为AVFrame。
do_audio_out():编码音频。
do_video_out():编码视频。
avfilter_unref_buffer():释放资源。
 
do_video_out()调用了如下函数
avcodec_encode_video2():编码一帧视频。
write_frame():写入编码后的视频压缩数据。
 
write_frame()调用了如下函数:
av_bitstream_filter_filter():使用AVBitStreamFilter的时候,会调用此函数进行处理。
av_interleaved_write_frame():写入压缩编码数据。
 
do_audio_out()调用的函数与do_video_out()基本上一样。唯一的不同在于视频编码函数avcodec_encode_video2()变成了音频编码函数avcodec_encode_audio2()。

copy怎么工作?
在main()->ffmpeg-parse_options()->open_files()->open_output_file()->new_video->stream()->new_output_stream()->choose_encoder()有:
if (!strcmp(codec_name, "copy"))
        ost->stream_copy = 1;

另一个地方会        ost->encoding_needed = !ost->stream_copy,表明是否需要解码。
在transcode_init中
 if (ost->stream_copy) {
    所有的参数都用解码后得到的参数。
}else{
   使用转码参数。
   调用init_simple_filtergraph();有这句ost->filter = fg->outputs[0];这样ost->filter就有了值,在reap_filters就会进行编码(会判断ost->filter)。
}


多线程问题

#要想使用多线程,需要编译的时候加上--enable-pthread。

#有些编解码不支持多线程,配置了线程数也不会起作用。

参考:http://blog.csdn.net/leixiaohua1020/article/details/39760711


dup!

打印位置:

static void do_video_out(AVFormatContext *s,
                         OutputStream *ost,
                         AVFrame *next_picture,
                         double sync_ipts)
{

    if (nb_frames > (nb0_frames && ost->last_dropped) + (nb_frames > nb0_frames)) {
        if (nb_frames > dts_error_threshold * 30) {
            av_log(NULL, AV_LOG_ERROR, "%d frame duplication too large, skipping\n", nb_frames - 1);
            nb_frames_drop++;
            return;
        }
        nb_frames_dup += nb_frames - (nb0_frames && ost->last_dropped) - (nb_frames > nb0_frames);
        av_log(NULL, AV_LOG_VERBOSE, "*** %d dup!\n", nb_frames - 1);
    }

}

"Drop" means it has dropped a frame during encoding.

"Dup" means it has duplicated a frame during encoding.

一些打印

static void print_report(intis_last_report, int64_t timer_start, int64_t cur_time){

  secs= FFABS(pts) / AV_TIME_BASE;

   us = FFABS(pts) % AV_TIME_BASE;

   mins = secs / 60;

   secs %= 60;

   hours = mins / 60;

   mins %= 60;

    //时分秒毫秒是由pts得到。

   bitrate = pts && total_size >= 0 ? total_size * 8 / (pts /1000.0) : -1;

    //比特率是总文件大小除以秒数。

   speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1;

         //总倍数是文件时长除以转码时长。

}

No more output streams to write to, finishing

依赖于need_output函数。

static int need_output(void)
{
    int i;
    for (i = 0; i < nb_output_streams; i++) {
        OutputStream *ost    = output_streams[i];
        OutputFile *of       = output_files[ost->file_index];
        AVFormatContext *os  = output_files[ost->file_index]->ctx;
        if (ost->finished ||
            (os->pb && avio_tell(os->pb) >= of->limit_filesize))

            continue;
        if (ost->frame_number >= ost->max_frames) {
            int j;
            for (j = 0; j < of->ctx->nb_streams; j++)
                close_output_stream(output_streams[of->ost_index + j]);
            continue;
        }

        return 1;
    }

    return 0;
}

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/evsqiezi/article/details/51564657
个人分类: ffmpeg--ffmpeg.c
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

ffmpeg.c总体调用

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭