ffmpeg.c(4.3.1)源码剖析(三)

简介: ffmpeg.c(4.3.1)源码剖析(三)

五、transcode 函数

transcode 用于实现媒体文件转码的函数之一。转码是指将一个媒体文件从一种编码格式转换为另一种编码格式的过程。这可以包括视频编解码器、音频编解码器、容器格式或其他媒体属性的更改。

其主要包括以下两个核心函数:

  • transcode_init()
  • 初始化,打开所有输出流的编码器,打开所有输入流的解码器,写入所有输出文件的文件头。
  • transcode_step()
  • 于实现 FFmpeg 转码过程中的一个步骤的函数

1、transcode_init 函数

初始化工作:

  • AVFormatContext *oc;//输出流的编解码器结构
  • OutputStream *ost;//输出流
  • InputStream *ist; //输入流
  • init_input_stream
  • init_output_stream
//transcode_init()函数是在转换前做准备工作的
static int transcode_init(void)
{
    int ret = 0, i, j, k;
    AVFormatContext *oc;//输出流的编解码器结构
    OutputStream *ost;  //输出流
    InputStream *ist; //输入流
    char error[1024] = {0};
    
    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)
                continue;
            if (fg->nb_inputs != 1)
                continue;
            for (k = nb_input_streams-1; k >= 0 ; k--)
                if (fg->inputs[0]->ist == input_streams[k])
                    break;
            ofilter->ost->source_index = k;
        }
    }
    /* init framerate emulation */
  //初始化帧率仿真(转换时是不按帧率来的,但如果要求帧率仿真,就可以做到)
    for (i = 0; i < nb_input_files; i++) {
        InputFile *ifile = input_files[i];
    //如果一个输入文件被要求帧率仿真(指的是即使是转换也像播放那样按照帧率来进行),则为这个文件中所有流记录下开始时间。
        if (ifile->rate_emu)
            for (j = 0; j < ifile->nb_streams; j++)
                input_streams[j + ifile->ist_index]->start = av_gettime_relative();
    }
    /* init input streams */
  //什么也没做,只是做了个判断而已。
    for (i = 0; i < nb_input_streams; i++)
        if ((ret = init_input_stream(i, error, sizeof(error))) < 0) {
            for (i = 0; i < nb_output_streams; i++) {
                ost = output_streams[i];
                avcodec_close(ost->enc_ctx);
            }
            goto dump_format;
        }
    /* open each encoder */
  //轮循所有输出流,打开每个输出流的编码器
    for (i = 0; i < nb_output_streams; i++) {
        // skip streams fed from filtergraphs until we have a frame for them
        if (output_streams[i]->filter)
            continue;
        ret = init_output_stream(output_streams[i], error, sizeof(error));
        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;
        }
    }
    /* write headers for files with no streams */
  //打开所有输出文件,写入媒体文件头
    for (i = 0; i < nb_output_files; i++) {
        oc = output_files[i]->ctx;
        if (oc->oformat->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) {
            ret = check_init_output_file(output_files[i], i);
            if (ret < 0)
                goto dump_format;
        }
    }
 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->sync_ist != input_streams[ost->source_index])
            av_log(NULL, AV_LOG_INFO, " [sync #%d:%d]",
                   ost->sync_ist->file_index,
                   ost->sync_ist->st->index);
    //如果只是复制一个流(不用解码后再编码),则把输入流的编码参数直接赋值给输出流  
    //此时是不需要解码也不需要编码,所以不需打开解码器和编码器 
        if (ost->stream_copy)
            av_log(NULL, AV_LOG_INFO, " (copy)");
        else {
            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);
        }
        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;
}
static int init_input_stream(int ist_index, char *error, int error_len)
{
    int ret;
    InputStream *ist = input_streams[ist_index];
    if (ist->decoding_needed) {
        AVCodec *codec = ist->dec;
        if (!codec) {
            snprintf(error, error_len, "Decoder (codec %s) not found for input stream #%d:%d",
                    avcodec_get_name(ist->dec_ctx->codec_id), ist->file_index, ist->st->index);
            return AVERROR(EINVAL);
        }
        ist->dec_ctx->opaque                = ist;
        ist->dec_ctx->get_format            = get_format;
        ist->dec_ctx->get_buffer2           = get_buffer;
        ist->dec_ctx->thread_safe_callbacks = 1;
        av_opt_set_int(ist->dec_ctx, "refcounted_frames", 1, 0);
        if (ist->dec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE &&
           (ist->decoding_needed & DECODING_FOR_OST)) {
            av_dict_set(&ist->decoder_opts, "compute_edt", "1", AV_DICT_DONT_OVERWRITE);
            if (ist->decoding_needed & DECODING_FOR_FILTER)
                av_log(NULL, AV_LOG_WARNING, "Warning using DVB subtitles for filtering and output at the same time is not fully supported, also see -compute_edt [0|1]\n");
        }
        av_dict_set(&ist->decoder_opts, "sub_text_format", "ass", AV_DICT_DONT_OVERWRITE);
        /* Useful for subtitles retiming by lavf (FIXME), skipping samples in
         * audio, and video decoders such as cuvid or mediacodec */
        ist->dec_ctx->pkt_timebase = ist->st->time_base;
        if (!av_dict_get(ist->decoder_opts, "threads", NULL, 0))
            av_dict_set(&ist->decoder_opts, "threads", "auto", 0);
        /* Attached pics are sparse, therefore we would not want to delay their decoding till EOF. */
        if (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)
            av_dict_set(&ist->decoder_opts, "threads", "1", 0);
        ret = hw_device_setup_for_decode(ist);
        if (ret < 0) {
            snprintf(error, error_len, "Device setup failed for "
                     "decoder on input stream #%d:%d : %s",
                     ist->file_index, ist->st->index, av_err2str(ret));
            return ret;
        }
    //打开解码器
        if ((ret = avcodec_open2(ist->dec_ctx, codec, &ist->decoder_opts)) < 0) {
            if (ret == AVERROR_EXPERIMENTAL)
                abort_codec_experimental(codec, 0);
            snprintf(error, error_len,
                     "Error while opening decoder for input stream "
                     "#%d:%d : %s",
                     ist->file_index, ist->st->index, av_err2str(ret));
            return ret;
        }
        assert_avoptions(ist->decoder_opts);
    }
    ist->next_pts = AV_NOPTS_VALUE;
    ist->next_dts = AV_NOPTS_VALUE;
    return 0;
}

2、transcode_step 函数

/**
 * Run a single step of transcoding.
 *
 * @return  0 for success, <0 for error
 */
 /*
 解码流程是:
 process_input() -> output_packet() -> decode_audio()/decode_video()/transcode_subtitles()
 而decode_audio() 是调用 avcodec_decode_audio4() 来完成工作的。
 decode_video() 则是通过调用 avcodec_decode_video2() 来完成的。
 编码流程是:
 reap_filters() -> do_video_out()或 do_audio_out() 
 -> avcodec_encode_video2() 或 avcodec_encode_audio2()。
 */
static int transcode_step(void)
{
    OutputStream *ost;
    InputStream  *ist = NULL;
    int ret;
  //选择一个有效的输出流进行处理
    ost = choose_output();
    if (!ost) {
        if (got_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 (!ost->initialized) {
            char error[1024] = {0};
            ret = init_output_stream(ost, error, sizeof(error));
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n",
                       ost->file_index, ost->index, error);
                exit_program(1);
            }
        }
        if ((ret = transcode_from_filter(ost->filter->graph, &ist)) < 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)) {
        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);
}
目录
相关文章
|
7月前
|
编解码 API
ffmpeg.c(4.3.1)源码剖析(一)
ffmpeg.c(4.3.1)源码剖析(一)
139 2
|
2月前
|
Ubuntu 应用服务中间件 nginx
Ubuntu安装笔记(三):ffmpeg(3.2.16)源码编译opencv(3.4.0)
本文是关于Ubuntu系统中使用ffmpeg 3.2.16源码编译OpenCV 3.4.0的安装笔记,包括安装ffmpeg、编译OpenCV、卸载OpenCV以及常见报错处理。
198 2
Ubuntu安装笔记(三):ffmpeg(3.2.16)源码编译opencv(3.4.0)
|
7月前
|
缓存 网络协议 Windows
FFmpeg开发笔记(六)如何访问Github下载FFmpeg源码
在国内访问GitHub不稳定时,可以采取三种解决方法。首先,通过网站(<https://ping.chinaz.com/github.com>)找到快速响应的GitHub IP,将其添加到本地hosts文件,然后刷新DNS缓存以正常访问。其次,使用代下载网站如(<https://d.serctl.com/>)下载GitHub上的压缩包。最后,可从国内镜像站点,如码云(<https://gitee.com/mirrors/ffmpeg>),下载FFmpeg等开源代码。这些方法有助于绕过访问限制,确保FFmpeg学习与开发的顺利进行。
159 3
FFmpeg开发笔记(六)如何访问Github下载FFmpeg源码
|
7月前
ffmpeg.c(4.3.1)源码剖析(二)
ffmpeg.c(4.3.1)源码剖析(二)
108 0
|
7月前
|
存储 编解码 缓存
FFmpeg之旅:深入解析FFplay源码
FFmpeg之旅:深入解析FFplay源码
658 0
|
7月前
|
编解码 网络协议 API
ffmpeg命令行工具源码之结构体分析1-命令行参数(未完结,持续更新)
ffmpeg作为多媒体文件转换工具,至少需要有一个要转换的输入文件信息(不仅仅是普通文件,还可以是摄像头设备,网络流等),和通常至少需要一个输出格式的文件(输出文件不仅仅指普通的文件,网络协议比如RTP协议,RTSP协议都可以理解为输出文件),ffmpeg的文件的转换过程主要由以下几个流程 (1)解封装 (2)解码 (3)过滤器 (4)编码 (5)封装 因此ffmpeg工具涉及的结构体主要就从这几个方面来说明这些结构体的含义。
80 0
|
7月前
|
Linux API C++
音视频windows安装ffmpeg6.0并使用vs调试源码笔记
音视频windows安装ffmpeg6.0并使用vs调试源码笔记
251 0
|
编译器 开发工具 C语言
FFmpeg开发笔记(一):ffmpeg介绍、windows开发环境搭建(mingw和msvc,无需源码编译)
FFmpeg开发笔记(一):ffmpeg介绍、windows开发环境搭建(mingw和msvc,无需源码编译)
FFmpeg开发笔记(一):ffmpeg介绍、windows开发环境搭建(mingw和msvc,无需源码编译)
|
C++ 编解码
把自定义的decoder加入ffmpeg源码
第一步: 在libavcodec目录下新建mkdecoder.c,并加入一下代码: [cpp] view plain copy   /*   *实现一个自己的decoder,编码工作其实就是把pkt的数据拷贝到frame  *作者:缪国凯(MK)   *821486004@qq.
1520 0
|
C++
把自定义的demuxer加入ffmpeg源码
.简介:把上一篇文章中的demuxer加入ffmpeg源码中去,使可以用命令行方式调用自定义的demuxer 第一步: 在libavformat目录下新建mkdemuxer.c和mkdemuxer.
1633 0