FFMPEG源码之ffmpeg.c解析

这篇具有很好参考价值的文章主要介绍了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()函数实现了将输入数据进行转码处理,并将结果写入输出流中,最终完成转码操作。每个流程均有特定的功能,按照一定的顺序进行处理,达到转码的目的。每个流程的操作都与上下文关系密切,根据上一个流程的处理结果和状态来决定下一个流程的操作和流程逻辑。整个转码过程在循环中进行,直到所有的输入数据处理完毕、输出流刷新完毕,并将输出文件的文件尾写入,最后退出程序。

文章来源地址https://www.toymoban.com/news/detail-605499.html

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表示滤镜回收完毕,继续进行下一轮的处理。

到了这里,关于FFMPEG源码之ffmpeg.c解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FFmpeg5.0源码阅读——FFmpeg大体框架(以GIF转码为示例)

       摘要 :前一段时间熟悉了下FFmpeg主流程源码实现,对FFmpeg的整体框架有了个大概的认识,因此在此做一个笔记,希望以比较容易理解的文字描述FFmpeg本身的结构,加深对FFmpeg的框架进行梳理加深理解,如果文章中有纰漏或者错误欢迎指出。本文描述了FFmpeg编解码框架的

    2024年02月10日
    浏览(39)
  • [FFmpeg] 源码编译

    git clone https://git.ffmpeg.org/ffmpeg.git git checkout -b 5.1 remotes/origin/release/5.1 ./configure --prefix=./OUT --enable-shared --disable-static make make install 默认安装路径见: /usr/local/bin /usr/local/include /usr/local/lib /usr/local/main/man1 /usr/local/main/man3 有 --prefix 参数的安装路径: [prefix]/bin [prefix]/include [prefix

    2024年02月10日
    浏览(35)
  • 源码编译FFmpeg4.3

    FreeSWITCH的mod_av模块目前(1.10.11)暂不支持FFmpeg4.4(或者更高版本),但4.3就没问题 最近试了试源码编译FFmpeg4.3,记录如下(系统centos7.9): git clone GitHub - BtbN/FFmpeg-Builds 找到4.4.sh,改成这样: GIT_BRANCH=\\\"release/4.3\\\" ./build.sh linux64 gpl 4.4 cd ffbuild/ffmpeg ./configure --enable-shared make

    2024年02月01日
    浏览(38)
  • FFmpeg源码走读之内存管理模型

    数据包管理过程中当数据转移到新的数据包时存在两种操作一种是数据包之间相互独立,当新创建一份数据包时,需要将原来的数据重新申请一个数据空间并且将数据拷贝到新的数据包中,具体过程如下图所示。这种数据包的管理优势是在于数据之间相互独立,不会存在数据

    2023年04月24日
    浏览(34)
  • FFmpeg aresample_swr_opts的解析

    aresample_swr_opts 是AVFilterGraph中的option。 因为是option,所以就想能不能将这个option配置到graph里面,分析代码发现, AVFilterGraph::aresample_swr_opts 在graph解析的时候不能当做filter的option解析。 因为graph load的在解析 graph 文本的过程,option来自filter的option, aresample_swr_opts 是AVFilterGr

    2024年02月15日
    浏览(34)
  • 了解FFmpeg音频通道布局结构:AVChannelLayout结构体解析

    FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里包含了很多编解码器,这些编解码器

    2024年02月08日
    浏览(45)
  • ffmpeg.c(4.3.1)源码剖析

    本文对 ffmpeg.c 源码进行学习及剖析。 链接:ffmpeg整体流程.jpg 下面对上述图片进行介绍: 函数背景色 函数在图中以方框的形式表现出来。不同的背景色标志了该函数不同的作用: 粉红色背景函数:FFmpeg 的 API 函数。 白色背景的函数:FFmpeg 的内部函数。 黄色背景的函数:

    2024年02月03日
    浏览(30)
  • ffmpeg.c源码与函数关系分析

    FFmpeg 是一个可以处理音视频的软件,功能非常强大,主要包括,编解码转换,封装格式转换,滤镜特效。 FFmpeg支持各种网络协议,支持 RTMP ,RTSP,HLS 等高层协议的推拉流,也支持更底层的TCP/UDP 协议推拉流。 FFmpeg 可以在 Windows,Linux,Mac,iOS,Android等操作系统上运行。 F

    2024年02月14日
    浏览(34)
  • 深入浅出:FFmpeg 音频解码与处理AVFrame全解析

    FFmpeg 是一个开源的音视频处理软件,它包含了一系列的库和程序,用于处理音频、视频和其他多媒体数据。FFmpeg 的名字来源于 “Fast Forward MPEG”,其中 MPEG 是一种常见的音视频编码标准。 FFmpeg 项目于 2000 年由 Fabrice Bellard 启动,他是 QEMU(一种开源的计算机模拟器和虚拟机

    2024年02月04日
    浏览(109)
  • ffmpeg中filter_query_formats函数解析

    ffmpeg中filter_query_formats主要起一个pix fmt引用指定的功能。 下下结论: 先看几个结构体定义: 结构体: AVFilterFormatsConfig 再来看函数: 核心函数: ff_set_common_formats 看宏定义: 接着看ref 主要看关键的三行代码: 这就是最开始图片指示的互相引用。

    2024年02月16日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包