FFMPEG源码之ffmpeg.c解析

main()

int main(int argc, char **argv)
{
    int ret;
    BenchmarkTimeStamps ti;

    /* 初始化动态加载 */
    init_dynload();

    /* 注册退出回调函数 */
    register_exit(ffmpeg_cleanup);

    /* 设置stderr的缓冲模式(win32运行时需要) */
    setvbuf(stderr, NULL, _IONBF, 0);

    /* 设置日志打印选项 */
    av_log_set_flags(AV_LOG_SKIP_REPEATED);
    parse_loglevel(argc, argv, options);

#if CONFIG_AVDEVICE
    /* 注册音视频设备 */
    avdevice_register_all();
#endif

    /* 初始化网络模块 */
    avformat_network_init();

    /* 显示ffmpeg的banner信息 */
    show_banner(argc, argv, options);

    /* 解析命令行选项并打开所有的输入/输出文件 */
    ret = ffmpeg_parse_options(argc, argv);
    if (ret < 0)
        exit_program(1);

    /* 检查是否没有指定输出文件并且没有输入文件 */
    if (nb_output_files <= 0 && nb_input_files == 0) {
        show_usage();
        av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);
        exit_program(1);
    }

    /* 检查是否至少指定一个输出文件 */
    if (nb_output_files <= 0) {
        av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
        exit_program(1);
    }

    current_time = ti = get_benchmark_time_stamps();

    /* 文件转码或抓取 */
    if (transcode() < 0)
        exit_program(1);

    if (do_benchmark) {
        int64_t utime, stime, rtime;
        current_time = get_benchmark_time_stamps();
        utime = current_time.user_usec - ti.user_usec;
        stime = current_time.sys_usec  - ti.sys_usec;
        rtime = current_time.real_usec - ti.real_usec;
        av_log(NULL, AV_LOG_INFO,
               "bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n",
               utime / 1000000.0, stime / 1000000.0, rtime / 1000000.0);
    }

    av_log(NULL, AV_LOG_DEBUG,
           "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",
           decode_error_stat[0], decode_error_stat[1]);

    /* 检查解码错误是否超过了指定的错误率 */
    if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
        exit_program(69);

    /* 根据是否接收到信号确定程序的返回码,并终止程序 */
    exit_program(received_nb_signals ? 255 : main_return_code);

    return main_return_code;
}

下面是对每个步骤的功能的详细解释:

  1. 初始化动态加载。
    • 调用init_dynload函数,用于初始化动态加载库的相关资源,以便在需要时加载需要的库。
  2. 注册退出回调函数。
    • 调用register_exit函数,将ffmpeg_cleanup函数注册为在程序退出时被调用的回调函数。
  3. 设置stderr的缓冲模式。
    • 调用setvbuf函数,将stderr的缓冲模式设置为无缓冲模式,以确保错误信息可以立即显示在终端上。
  4. 设置日志打印选项。
    • 调用av_log_set_flags函数,设置日志打印选项。在这里设置
      AV_LOG_SKIP_REPEATED选项,表示日志会跳过重复的消息。
  5. 解析命令行参数中的日志级别选项。
    • 调用parse_loglevel函数,解析命令行参数中的日志级别选项,并将其应用到日志系统中。
  6. 注册音视频设备。
    • 调用avdevice_register_all函数,用于注册所有的音视频设备。
  7. 初始化网络模块。
    • 调用avformat_network_init函数,初始化网络模块,以便进行网络相关的操作,如打开网络流。
  8. 显示ffmpeg的banner信息。
    • 调用show_banner函数,根据命令行参数、选项和程序信息,打印ffmpeg的banner信息。
  9. 解析命令行选项并打开所有的输入/输出文件。
    • 调用ffmpeg_parse_options函数,解析命令行选项,并根据选项打开所有的输入/输出文件。
  10. 检查是否没有指定输出文件并且没有输入文件。
    • 检查nb_output_files和nb_input_files的值。
    • 若满足条件,则打印用法信息和警告,并终止程序。
  11. 检查是否至少指定一个输出文件。
    • 检查nb_output_files的值。
    • 若不满足条件,则打印致命错误信息,并终止程序。
  12. 进行文件转码或抓取。
    • 调用transcode函数,进行文件转码或抓取。
    • transcode函数返回值小于0表示出错,并通过调用exit_program函数终止程序。
  13. 如果启用了性能评测,输出性能数据。
    • 如果do_benchmark为真,计算从开始到结束的用户时间、系统时间和真实时间,并打印出来。
  14. 打印解码帧数和解码错误数。
    • 调用av_log函数,打印成功解码的帧数和解码错误数。
  15. 检查解码错误是否超过了指定的错误率。
    • 检查解码错误数是否超过了最大错误率。
    • 若满足条件,则通过调用exit_program终止程序。
  16. 根据是否接收到信号确定程序的返回码,并终止程序。
  17. 返回main_return_code作为main函数的返回值。

针对main函数中几个重要的函数,下面将逐步解析他们的具体实现

ffmpeg_parse_options()

// 解析命令行参数并设置选项
int ffmpeg_parse_options(int argc, char **argv)
{
    // 定义选项解析上下文和错误信息
    OptionParseContext octx;
    uint8_t error[128];
    int ret;

    memset(&octx, 0, sizeof(octx));

    /* split the commandline into an internal representation */
    // 将命令行参数拆分为内部表示形式
    ret = split_commandline(&octx, argc, argv, options, groups,
                            FF_ARRAY_ELEMS(groups));
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error splitting the argument list: ");
        goto fail;
    }

    /* apply global options */
    // 应用全局选项
    ret = parse_optgroup(NULL, &octx.global_opts);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error parsing global options: ");
        goto fail;
    }

    /* configure terminal and setup signal handlers */
    // 配置终端并设置信号处理程序
    term_init();

    /* open input files */
    // 打开输入文件
    ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error opening input files: ");
        goto fail;
    }

    // 应用同步偏移量
    apply_sync_offsets();

    /* create the complex filtergraphs */
    // 创建复杂的滤波器图
    ret = init_complex_filters();
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n");
        goto fail;
    }

    /* open output files */
    // 打开输出文件
    ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error opening output files: ");
        goto fail;
    }

    // 检查滤波器的输出
    check_filter_outputs();

fail:
    // 反初始化选项解析上下文
    uninit_parse_context(&octx);
    if (ret < 0) {
        // 如果有错误,将错误信息输出到日志
        av_strerror(ret, error, sizeof(error));
        av_log(NULL, AV_LOG_FATAL, "%s\n", error);
    }
    return ret;
}

函数功能:该函数用于解析命令行参数并进行相应处理,包括拆分命令行参数为内部表示形式、应用全局选项、配置终端和设置信号处理程序、打开输入文件、应用同步偏移量、创建复杂的滤波器图、打开输出文件、检查滤波器的输出。如果出现错误,会将错误信息输出到日志。


transcode()

static int transcode(void)
{
    int ret, i;
    OutputStream *ost;
    InputStream *ist;
    int64_t timer_start;
    int64_t total_packets_written = 0;
	
	//执行转码初始化操作,并将返回值赋给变量ret。
    ret = transcode_init();
    if (ret < 0)
        goto fail;
	
	//检查是否启用了从标准输入进行交互的选项。
    if (stdin_interaction) {
        av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n");
    }

	// 记录转码开始的时间,用于计算整个过程的耗时。
    timer_start = av_gettime_relative();

	//执行输入线程的初始化,启动输入线程来读取输入文件。
    if ((ret = init_input_threads()) < 0)
        goto fail;

	//持续循环进行转码操作,直到接收到停止信号。
    while (!received_sigterm) {
        int64_t cur_time= av_gettime_relative();	//记录当前时间,用于计算转码过程中的耗时。

        /* if 'q' pressed, exits */
        /* 检查是否启用了从标准输入进行交互的选项。如果启用了与用户交互的选项,
           将会检查是否需要检查键盘交互来停止转码。*/
        if (stdin_interaction)
            if (check_keyboard_interaction(cur_time) < 0)
                break;

        /* check if there's any stream where output is still needed */
        // 检查是否还需要写入输出流
        if (!need_output()) {
            av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n");
            break;
        }
		
		// 执行转码的一步操作,比如从输入文件中读取数据,应用滤镜,写入输出文件等等。
        ret = transcode_step();
        if (ret < 0 && ret != AVERROR_EOF) {
            av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret));
            break;
        }

        /* dump report by using the output first video and audio streams */
        // 根据输出流的第一个视频和音频流打印转码报告。显示转码的进度和统计信息。
        print_report(0, timer_start, cur_time);
    }
    free_input_threads();	//释放输入线程的资源。

    /* at the end of stream, we must flush the decoder buffers */
    //循环处理每个输入流的解码器缓冲区,以确保在流结束时进行刷新。
    for (i = 0; i < nb_input_streams; i++) {
        ist = input_streams[i];
        if (!input_files[ist->file_index]->eof_reached) {
            process_input_packet(ist, NULL, 0);
        }
    }
    flush_encoders();	// 刷新编码器的缓冲区,将剩余的数据编码为输出帧。

    term_exit(); // 执行终结操作,例如释放资源和关闭设备。

    /* write the trailer if needed */
    // 循环写入每个输出文件的尾部。
    for (i = 0; i < nb_output_files; i++) {
        ret = of_write_trailer(output_files[i]); 
        if (ret < 0 && exit_on_error)
            exit_program(1);
    }

    /* dump report by using the first video and audio streams */
    // 根据第一个视频和音频流打印转码报告,显示转码的最终进度和统计信息。
    print_report(1, timer_start, av_gettime_relative());

    /* close each encoder */
    // 循环关闭每个输出流的编码器。
    for (i = 0; i < nb_output_streams; i++) {
        uint64_t packets_written;
        ost = output_streams[i];
        packets_written = atomic_load(&ost->packets_written);
        total_packets_written += packets_written;
        if (!packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) {
            av_log(NULL, AV_LOG_FATAL, "Empty output on stream %d.\n", i);
            exit_program(1);
        }
    }
	
	// 如果输出为空且设置了对空输出流终止转码的标志,将会退出程序。
    if (!total_packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT)) {
        av_log(NULL, AV_LOG_FATAL, "Empty output\n");
        exit_program(1);
    }

    /* close each decoder */
    // 循环关闭每个输入流的解码器。
    for (i = 0; i < nb_input_streams; i++) {
        ist = input_streams[i];
        if (ist->decoding_needed) {
            avcodec_close(ist->dec_ctx);
        }
    }
	
	// 释放所有的硬件设备。
    hw_device_free_all();

    /* finished ! */
    ret = 0;

 fail:
    free_input_threads();	// 释放输入线程所占用的资源。
	
	// 如果存在输出流,将会释放输出流相关的资源。
    if (output_streams) {
        for (i = 0; i < nb_output_streams; i++) {
            ost = output_streams[i];
            if (ost) {
                if (ost->logfile) {
                    if (fclose(ost->logfile))
                        av_log(NULL, AV_LOG_ERROR,
                               "Error closing logfile, loss of information possible: %s\n",
                               av_err2str(AVERROR(errno)));
                    ost->logfile = NULL;
                }
                av_freep(&ost->forced_kf_pts);
                av_freep(&ost->apad);
                av_freep(&ost->disposition);
                av_dict_free(&ost->encoder_opts);
                av_dict_free(&ost->sws_dict);
                av_dict_free(&ost->swr_opts);
            }
        }
    }
    return ret;
}


transcode()函数中每个流程的详细操作步骤及功能,并与上下文的关系的说明:
  1. 初始化转码:调用transcode_init函数初始化转码过程。进行一些初始化操作,包括打开输入和输出文件,初始化输入和输出流等。该步骤在整个流程中只进行一次。
  2. 控制台交互提示:如果启用stdin_interaction参数,打印控制台交互提示信息。在控制台输出交互提示信息,指示用户如何操作。根据参数stdin_interaction的设置,判断是否需要进行控制台交互提示。
  3. 初始化输入线程:调用init_input_threads函数初始化输入线程,准备进行输入数据的读取和处理。在转码过程中,可能需要使用多线程来进行输入数据的读取和处理。调用此函数来初始化输入线程。
  4. 循环处理输入数据:在循环处理输入数据的过程中,调用transcode_step函数来处理输入数据。该函数通过选择输出流、处理滤镜、处理输入流等步骤,将输入数据进行转码处理,并写入到输出流中。
  5. 打印报告:在转码过程中,定期调用print_report函数来打印转码报告,展示转码的进度和所消耗的时间。
  6. 释放输入线程::调用free_input_threads函数释放输入线程。在转码结束后,释放输入线程的资源,表示输入数据的读取和处理已经完成。
  7. 刷新编码器:调用flush_encoders函数刷新编码器。在所有的输入数据处理完毕后,需要将编码器中剩余的数据进行编码和写入输出流。调用flush_encoders函数来完成此操作。
  8. 退出程序:在转码流程结束后,进行一些资源的释放和清理工作,然后调用term_exit函数退出程序。
  9. 写入输出文件尾:在转码结束后,需要将输出文件的文件尾写入。调用of_write_trailer函数完成此操作。
  10. 打印最终报告:在转码结束后,调用print_report函数来打印最终的转码报告,展示转码的总体进度和所消耗的时间。
  11. 关闭编码器和解码器:在转码结束后,需要关闭每个输出流中的编码器,以及每个输入流中的解码器,释放相应的资源。

通过以上的流程,transcode()函数实现了将输入数据进行转码处理,并将结果写入输出流中,最终完成转码操作。每个流程均有特定的功能,按照一定的顺序进行处理,达到转码的目的。每个流程的操作都与上下文关系密切,根据上一个流程的处理结果和状态来决定下一个流程的操作和流程逻辑。整个转码过程在循环中进行,直到所有的输入数据处理完毕、输出流刷新完毕,并将输出文件的文件尾写入,最后退出程序。

transcode_init()

static int transcode_init(void)
{
    int ret = 0, i, j, k;
    OutputStream *ost;
    InputStream *ist;
    char error[1024] = {0};
	
	// 遍历 nb_filtergraphs 列表中的 FilterGraph 结构体。
    for (i = 0; i < nb_filtergraphs; i++) {
        FilterGraph *fg = filtergraphs[i];
        for (j = 0; j < fg->nb_outputs; j++) {
            OutputFilter *ofilter = fg->outputs[j];
            if (!ofilter->ost || ofilter->ost->source_index >= 0) // 如果输出的 ost 为空或 source_index 大于等于0,则跳过
                continue;
            if (fg->nb_inputs != 1)	// 如果 FilterGraph 的输入数不等于1,则跳过。
                continue;
            for (k = nb_input_streams-1; k >= 0 ; k--) // 从后往前遍历输入流列表,找到与 FilterGraph 的第一个输入匹配的输入流。
                if (fg->inputs[0]->ist == input_streams[k])
                    break;
            ofilter->ost->source_index = k; // 将找到的输入流的索引赋值给 ost 的 source_index 属性
        }
    }

    /* init framerate emulation */
    // 初始化帧率模拟
    for (i = 0; i < nb_input_files; i++) {	// 遍历输入文件列表。
        InputFile *ifile = input_files[i];
        if (ifile->readrate || ifile->rate_emu) // 如果输入文件的 readrate 或 rate_emu 不为0,则对其所有流进行帧率模拟的初始化。
            for (j = 0; j < ifile->nb_streams; j++)
                input_streams[j + ifile->ist_index]->start = av_gettime_relative(); // 初始化方法是设置输入流的 start 属性为当前系统时间。
    }

    /* init input streams */
    // 初始化输入流
    for (i = 0; i < nb_input_streams; i++) // 遍历输入流列表。
        if ((ret = init_input_stream(i, error, sizeof(error))) < 0) // 调用 init_input_stream() 对输入流进行初始化。
            goto dump_format;

    /*
     * initialize stream copy and subtitle/data streams.
     * Encoded AVFrame based streams will get initialized as follows:
     * - when the first AVFrame is received in do_video_out
     * - just before the first AVFrame is received in either transcode_step
     *   or reap_filters due to us requiring the filter chain buffer sink
     *   to be configured with the correct audio frame size, which is only
     *   known after the encoder is initialized.
     */
     //初始化流复制和字幕/数据流:
    for (i = 0; i < nb_output_streams; i++) { // 遍历输出流列表。
    	// 对于每个输出流,检查其是否为编码的 AVFrame 流。如果是,则跳过初始化。
        if (output_streams[i]->enc_ctx &&
            (output_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
             output_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
            continue;

        ret = init_output_stream_wrapper(output_streams[i], NULL, 0); // 初始化输出流。
        if (ret < 0)
            goto dump_format;
    }

    /* discard unused programs */
    // 丢弃未使用的程序:
    for (i = 0; i < nb_input_files; i++) { // 遍历输入文件列表。
        InputFile *ifile = input_files[i];
        for (j = 0; j < ifile->ctx->nb_programs; j++) { // 对于每个输入文件,遍历其所有程序。
            AVProgram *p = ifile->ctx->programs[j];
            int discard  = AVDISCARD_ALL;
			// 检查每个程序关联的流是否需要丢弃。
            for (k = 0; k < p->nb_stream_indexes; k++)
                if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) {
                    discard = AVDISCARD_DEFAULT;	// 如果有未被丢弃的流,则将程序的丢弃标志设置为默认丢弃。
                    break;
                }
            p->discard = discard;
        }
    }

 dump_format:
    /* dump the stream mapping */
    // 打印流映射信息:
    av_log(NULL, AV_LOG_INFO, "Stream mapping:\n");
    for (i = 0; i < nb_input_streams; i++) {
        ist = input_streams[i];

        for (j = 0; j < ist->nb_filters; j++) {
        	// 对于每个输入流的滤镜图,如果不是简单滤镜图,则打印其相关信息。
            if (!filtergraph_is_simple(ist->filters[j]->graph)) {
                av_log(NULL, AV_LOG_INFO, "  Stream #%d:%d (%s) -> %s",
                       ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?",
                       ist->filters[j]->name);	
                if (nb_filtergraphs > 1)
                    av_log(NULL, AV_LOG_INFO, " (graph %d)", ist->filters[j]->graph->index);
                av_log(NULL, AV_LOG_INFO, "\n");
            }
        }
    }

	// 打印输出流映射信息
    for (i = 0; i < nb_output_streams; i++) { // 遍历输出流列表。
        ost = output_streams[i];

		// 如果输出流是附加的文件流,则打印其文件名和关联的流信息。
        if (ost->attachment_filename) {	
            /* an attached file */
            av_log(NULL, AV_LOG_INFO, "  File %s -> Stream #%d:%d\n",
                   ost->attachment_filename, ost->file_index, ost->index);
            continue;
        }

		// 如果输出流是来自复杂滤镜图的输出流,则打印滤镜图和关联的流信息。
        if (ost->filter && !filtergraph_is_simple(ost->filter->graph)) {
            /* output from a complex graph */
            av_log(NULL, AV_LOG_INFO, "  %s", ost->filter->name);
            if (nb_filtergraphs > 1)
                av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index);

            av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)\n", ost->file_index,
                   ost->index, ost->enc ? ost->enc->name : "?");
            continue;
        }
		
		// 否则,打印输入流和输出流的对应关系。
        av_log(NULL, AV_LOG_INFO, "  Stream #%d:%d -> #%d:%d",
               input_streams[ost->source_index]->file_index,
               input_streams[ost->source_index]->st->index,
               ost->file_index,
               ost->index);
        if (ost->enc_ctx) {
            const AVCodec *in_codec    = input_streams[ost->source_index]->dec;
            const AVCodec *out_codec   = ost->enc;
            const char *decoder_name   = "?";
            const char *in_codec_name  = "?";
            const char *encoder_name   = "?";
            const char *out_codec_name = "?";
            const AVCodecDescriptor *desc;

            if (in_codec) {
                decoder_name  = in_codec->name;
                desc = avcodec_descriptor_get(in_codec->id);
                if (desc)
                    in_codec_name = desc->name;
                if (!strcmp(decoder_name, in_codec_name))
                    decoder_name = "native";
            }

            if (out_codec) {
                encoder_name   = out_codec->name;
                desc = avcodec_descriptor_get(out_codec->id);
                if (desc)
                    out_codec_name = desc->name;
                if (!strcmp(encoder_name, out_codec_name))
                    encoder_name = "native";
            }

            av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))",
                   in_codec_name, decoder_name,
                   out_codec_name, encoder_name);
        } else
            av_log(NULL, AV_LOG_INFO, " (copy)");
        av_log(NULL, AV_LOG_INFO, "\n");
    }

    if (ret) {
        av_log(NULL, AV_LOG_ERROR, "%s\n", error);
        return ret;
    }

    atomic_store(&transcode_init_done, 1);

    return 0;
}


该函数是一个用于初始化转码器的函数。下面是每个步骤的详细操作步骤及功能,并与上下文的关系进行说明:
  1. 初始化source_index:该步骤的目的是为每个输出流设置源输入流的索引(source_index)。这个索引用于指示输出流的源是哪个输入流。根据转码器的逻辑,只有当输出流没有源或者它的源索引为负值时,才需要设置源索引。如果过滤图的输入数目不为1,则跳过该输出流。然后,遍历所有的输入流,找到与当前过滤图的输入对应的输入流索引并将其设置为源索引。
  2. 初始化帧率仿真:这一步骤的目的是为了在处理视频帧时模拟输入文件的特定帧速率。对于每个输入文件,如果设置了readrate或rate_emu参数,则遍历该文件的每个流,设置其开始时间为相对于系统启动时间的时间戳。
  3. 初始化输入流:对于每个输入流,调用init_input_stream函数进行初始化。如果初始化失败,则跳转到dump_format标签,进行格式转换。
  4. 初始化流复制和字幕/数据流:这一步骤的目的是初始化流的复制、字幕和数据流。对于每个输出流,如果其对应的编码上下文存在且流的类型是视频或音频,则跳过该输出流。否则,调用init_output_stream_wrapper函数进行初始化。
  5. 丢弃未使用的节目:这一步骤的目的是丢弃未使用的节目,即那些没有被任何输入流参考的节目。对于每个输入文件,遍历其所有的节目,并检查每个节目中的流索引。如果找到至少一个流不是丢弃的,则将该节目的discard设置为AVDISCARD_DEFAULT,否则设置为AVDISCARD_ALL。
  6. 打印流映射:输出日志以打印流映射关系。依次遍历每个输入流,并对于每个输入流中的滤镜依次输出相关信息。这一步骤的作用是在日志中显示输入流的滤镜链。
  7. 打印输出流映射:输出日志以打印输出流映射关系。依次遍历每个输出流,并输出与该输出流相关的信息,如附加文件信息、复杂图结构信息等。
  8. 检查初始化结果:如果初始化过程中出现错误,则返回错误码并输出错误信息。否则,设置transcode_init_done标志为1,表示转码器已经初始化完成,并返回0表示成功初始化。

transcode_step()

static int transcode_step(void)
{
    OutputStream *ost;         // 输出流指针
    InputStream  *ist = NULL;  // 输入流指针
    int ret;

    // 选择输出流
    ost = choose_output();
    if (!ost) {
        // 如果没有输出流
        if (got_eagain()) {  // 检查是否发生 EAGAIN 错误
            reset_eagain();
            av_usleep(10000);  // 延时等待一段时间
            return 0;
        }
        av_log(NULL, AV_LOG_VERBOSE, "No more inputs to read from, finishing.\n");
        return AVERROR_EOF;
    }

    if (ost->filter && !ost->filter->graph->graph) {
        // 如果输出流有滤镜,并且滤镜图没有构建,则进行滤镜图的配置
        if (ifilter_has_all_input_formats(ost->filter->graph)) {
            ret = configure_filtergraph(ost->filter->graph);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n");
                return ret;
            }
        }
    }

    if (ost->filter && ost->filter->graph->graph) {
        // 如果输出流有滤镜,并且滤镜图已经构建,则进行相关操作
        if (av_buffersink_get_type(ost->filter->filter) == AVMEDIA_TYPE_AUDIO)
            init_output_stream_wrapper(ost, NULL, 1);  // 音频特殊处理,进行输出流的初始化

        ret = transcode_from_filter(ost->filter->graph, &ist);  // 从滤镜开始转码
        if (ret < 0)
            return ret;
        if (!ist)
            return 0;
    } else if (ost->filter) {
        // 如果输出流有滤镜,但滤镜图没有构建,则尝试找到输入流
        int i;
        for (i = 0; i < ost->filter->graph->nb_inputs; i++) {
            InputFilter *ifilter = ost->filter->graph->inputs[i];
            if (!ifilter->ist->got_output && !input_files[ifilter->ist->file_index]->eof_reached) {
                ist = ifilter->ist;
                break;
            }
        }
        if (!ist) {
            ost->inputs_done = 1;  // 输入流处理完毕
            return 0;
        }
    } else {
        // 如果输出流没有滤镜,则直接使用输入流
        av_assert0(ost->source_index >= 0);
        ist = input_streams[ost->source_index];
    }

    ret = process_input(ist->file_index);  // 处理输入
    if (ret == AVERROR(EAGAIN)) {
        // 如果返回 EAGAIN 错误
        if (input_files[ist->file_index]->eagain)
            ost->unavailable = 1;
        return 0;
    }

    if (ret < 0)
        return ret == AVERROR_EOF ? 0 : ret;

    return reap_filters(0);  // 回收滤镜
}

transcode_step函数实现了对输入流的处理和转码操作。具体的处理方式和操作根据输出流的关联情况和滤镜图的配置状态进行调整,以达到转码的功能。
  1. 选择输出流:调用choose_output函数选择一个输出流进行处理。如果没有可用的输出流,则检查是否收到EAGAIN错误,如果是则等待一段时间后返回0,否则输出日志并返回AVERROR_EOF表示已经没有更多的输入需要读取。
  2. 配置滤镜图:对输出流关联的滤镜图进行配置。如果输出流关联了滤镜图,并且滤镜图尚未被配置,则调用configure_filtergraph函数对滤镜图进行配置。
  3. 处理带滤镜的输出流:对关联了滤镜图的输出流进行处理。如果输出流关联了滤镜图,并且滤镜图已经被配置,就进行处理。这个步骤包含了对特殊情况的处理,主要是针对音频的初始化。然后调用transcode_from_filter函数对滤镜图进行处理,并通过传入的指针返回输入流。
  4. 处理无滤镜的输出流:对没有关联滤镜图的输出流进行处理。如果输出流没有关联滤镜图,但关联了输出流的源输入流,则遍历滤镜图的输入,找到第一个没有输出的输入流,并将其赋值给ist指针。如果所有的输入流都已经输出完毕(eof_reached标志为真),则将输出流的inputs_done标志置为1表示输入流处理完毕,并返回0。
  5. 处理输入流:调用process_input函数处理输入流。具体操作是读取输入帧,解码并进行相应的处理。返回值可能为EAGAIN表示需要更多的输入,所以直接返回0。
  6. 处理输入错误:如果处理输入流发生错误,则根据错误码判断是否为AVERROR_EOF,如果是则返回0表示输入流处理完毕,否则返回错误码。
  7. 回收滤镜:调用reap_filters函数回收滤镜。这个函数会检查滤镜图中的每个滤镜的输出是否准备好,如果准备好则进行相关操作。返回0表示滤镜回收完毕,继续进行下一轮的处理。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值