• 04-流媒体-ffmpeg.c源码分析


    ffmpeg.c是一个使用ffmpeg库的参考代码,实现了视频格式转换的功能,类似于我们常用的格式工产,源代码的的目录是:
    ffmpeg-4.2.2/fftools/ffmpeg.c
    和前面的ffplay一样,我们分析其源代码,主要只是为了让读者了解ffmpeg.c此文件的大概流程,并且熟悉常用的ffmpeg库的API。
    下面我们首先从主函数开始分析,如下

    int main(int argc, char **argv)
    {
        ......
        avdevice_register_all();
    	......
    
        show_banner(argc, argv, options);
    
       ......
        ret = ffmpeg_parse_options(argc, argv);
        ......
       
        if (transcode() < 0)
         ......
    
        exit_program(received_nb_signals ? 255 : main_return_code);
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    主函数有如下主要函数组成,每一个函数的功能如下
    avdevice_register_all()//注册所有编码器和解码器
    show_banner()//打印输出ffmpeg的版本信息、编译时间、编译选项、类库信息
    ffmpeg_parse_options()//解析输入的命令
    transcode()//转码
    exit_program()//退出和清理资源

    从上面分析可以看出,主核心的函数是转码函数transcode(),其内容如下:
    
    • 1
    static int transcode(void)
    {
        ......
        ret = transcode_init();
       ......
                if (check_keyboard_interaction(cur_time) < 0)
        ......
            ret = transcode_step();
     ......
            print_report(0, timer_start, cur_time);
    ......
        flush_encoders();
    ......
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    转码函数包括如下一些主要函数。
    transcode_init();//转码初始化
    check_keyboard_interaction()//检测键盘的操作
    ret = transcode_step()//进行转码
    print_report()//打印转码的信息
    flush_encoders()//输出编码器剩余的帧
    其中transcode_step()、print_report()、flush_encoders()循环执行,下面对transcode_step()分析

    static int transcode_step(void)
    {
    ......
    
        ret = process_input(ist->file_index);
       ......
        return reap_filters(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    转码的实际就是先解码,再编码
    process_input();//完成解码工作
    reap_filters();//完成编码工作

    1.先分析解码: process_input();//完成解码工作

    static int process_input(int file_index)
    {
        ......
        ret = get_input_packet(ifile, &pkt);
    
        ......
    
        process_input_packet(ist, &pkt, 0);
    
    ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    get_input_packet()->av_read_frame();//获取一个压缩数据帧,对应AVPacket
    
    • 1

    分析: process_input_packet();//解码

    	static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof)
    {
     ......
                ret = decode_audio    (ist, repeating ? NULL : &avpkt, &got_output,
                                       &decode_failed);
    ......
                ret = decode_video    (ist, repeating ? NULL : &avpkt, &got_output, &duration_pts, !pkt,
                                       &decode_failed);
                ......
    
            do_streamcopy(ist, ost, pkt);
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
        decode_audio();//音频解码
        decode_video(); //视频解码
        do_streamcopy();//不需要重新编码时使用
    
    • 1
    • 2
    • 3

    分析:decode_video(); //视频解码

    static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_t *duration_pts, int eof,
                            int *decode_failed)
    {
     ......
        ret = decode(ist->dec_ctx, decoded_frame, got_output, pkt ? &avpkt : NULL);
       .......
    
        err = send_frame_to_filters(ist, decoded_frame);
    ......
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    decode(); //解码
    ->avcodec_send_packet()
    ->avcodec_receive_frame();
    
    • 1
    • 2
    • 3

    send_frame_to_filters();//解码数据送到AVFilterContext

    2.再分析编码: reap_filters();//完成编码工作

    	static int reap_filters(int flush)
    {
    ......
    		   ret = av_buffersink_get_frame_flags(filter, filtered_frame,
                                                   AV_BUFFERSINK_FLAG_NO_REQUEST);
                 ......
                    do_video_out(of, ost, filtered_frame, float_pts);
                    .......
                    do_audio_out(of, ost, filtered_frame);
                .......
                av_frame_unref(filtered_frame);
       .......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    av_buffersink_get_frame_flags();//取一帧用于编码
    do_video_out();视频编码
    do_audio_out();音频编码
    av_frame_unref();	资源释放
    
    分析:do_video_out();视频编码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    static void do_video_out(OutputFile *of,
                             OutputStream *ost,
                             AVFrame *next_picture,
                             double sync_ipts)
    {
        ......
    
            ret = avcodec_send_frame(enc, in_picture);
            ......
            while (1) {
                ret = avcodec_receive_packet(enc, &pkt);
                ......
                output_packet(of, &pkt, ost, 0);
    
                .......
            
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    avcodec_send_frame():将AVFrame⾮压缩数据给编码器。
    avcodec_receive_packet():获取到编码后的AVPacket数据,收到的packet需要自己释放内存。
    output_packet()//按要求写封装

    3.总结
    从上面分析看出,其实ffmpeg.c主要实现的功能就是一个媒体文件格式(如.mp4)向另一种媒体格式如(.avi)的转换。有以下两种场景
    (1)读媒体文件->解封装->解码->编码->写封装->输出文件
    (2)读媒体文件->解封装->拷贝->写封装->输出文件
    用哪种场景是依赖于输入媒体文件与输出媒体文件所使用的压缩帧对应AVPacket是否一致所致。
    说明AVPacket用于存放视频h265、h264或音频g711a、aac等数据

  • 相关阅读:
    Android 10 如何在通知栏下拉状态栏会暂停第三方应用播放视频
    消息队列一|从秒杀活动开始聊起消息队列
    java防锁屏实现
    vue3发送验证码倒计时 (防止连点、封装复用)
    函数定义方式3种
    Java BufferedWriter.write()具有什么功能呢?
    六级备考24天|CET-6|翻译技巧1&2|理解背诵|11:00~12:00+14:20~15:30
    【微服务】Nacos服务端完成微服务注册以及健康检查流程
    PicGo配置阿里云oss
    力扣-240.搜索二维矩阵(2)
  • 原文地址:https://blog.csdn.net/sishen4199/article/details/133850757