ffplay源码分析(一)主函数

引言

在数字化时代,多媒体播放器已成为我们日常生活中不可或缺的一部分。无论是观看电影、听取音乐,抑或是进行视频会议,我们都依赖于功能强大的播放器来呈现丰富多彩的音视频内容。

而在众多播放器中,FFplay以其简洁而强大的特性脱颖而出。作为FFmpeg多媒体框架的一部分,FFplay不仅提供了丰富的音视频格式支持,更以其开源、跨平台的特性备受开发者青睐。

在音视频相关的面试中,ffplay中的一些代码甚至被认为是标准的解决方案。

本文将深入探讨ffplay的源码结构和实现细节,帮助读者更好地理解这一多媒体播放器的工作原理和内部机制。我们将介绍ffplay的基本特点和功能,分析其源码结构,并重点解析其主要功能模块,包括音视频解码、音视频同步、渲染和事件处理等。通过对这些关键部分的分析,读者将能够更深入地理解ffplay的工作原理,并能够应用这些知识来解决实际的多媒体处理和播放任务。

从main函数开始

下面这段代码是一个简单的视频播放器的初始化和主循环部分,其中包含了一些SDL(Simple DirectMedia Layer)和FFmpeg库的函数调用。

逐步解释一下代码的主要部分:

初始化操作

  • 调用 init_dynload() 函数来设置动态加载的库路径,这在Windows下避免了DLL库的依赖问题。
  • 调用 av_log_set_flags() 来设置日志级别并跳过重复日志。
  • 解析命令行参数,包括日志等级等。
  • 注册设备(如果可用)并初始化网络相关功能。
  • 设置中断和终止信号的处理函数。

SDL初始化

  • 初始化SDL音频、视频、定时器等子系统。

  • 根据参数设置是否禁用音频和视频。

  • 设置SDL环境变量,尝试解决可能的ALSA缓冲区下溢问题。

  • 创建SDL窗口和渲染器,设置窗口的属性(如窗口是否可调整大小、是否无边框等)。

  • 尝试使用硬件加速和垂直同步方式创建渲染器,如果失败则退而求其次,创建一个普通的渲染器。

打开流

  • 调用 stream_open() 打开指定的输入流,并返回一个 VideoState 结构体。VideoState是ffplay的主要结构体。

事件循环

  • 进入事件循环 event_loop(),等待事件的发生,如键盘输入、鼠标事件等,然后作出相应的响应,比如播放、暂停等。
/* Called from the main */
int main(int argc, char **argv)
{
    int flags;
    VideoState *is;
    
    //如果是windows,指定dll目录为当前路径,避免出现dll库依赖的问题
    init_dynload();

    //设置日志级别,跳过重复的日志
    av_log_set_flags(AV_LOG_SKIP_REPEATED);
    
    //解析参数中的log等级
    parse_loglevel(argc, argv, options);

   
#if CONFIG_AVDEVICE
    //注册所有的设备(若有)
    avdevice_register_all();
#endif
    //加载socket库以及网络加密协议相关的库,为后续使用网络相关提供支持,如windows下的WSAStartup以及openssl和gnutls等
    avformat_network_init();

    // 设置中断和终止信号的处理函数
    signal(SIGINT , sigterm_handler); /* Interrupt (ANSI).    */
    signal(SIGTERM, sigterm_handler); /* Termination (ANSI).  */

    // 显示版本信息,解析用户选项
    show_banner(argc, argv, options);
    parse_options(NULL, argc, argv, options, opt_input_file);

    //如果没有指定输入,退出
    if (!input_filename) {
        show_usage();
        av_log(NULL, AV_LOG_FATAL, "An input file must be specified\n");
        av_log(NULL, AV_LOG_FATAL,
               "Use -h to get full help or, even better, run 'man %s'\n", program_name);
        exit(1);
    }

    // 如果设置了不显示,关闭视频
    if (display_disable) {
        video_disable = 1;
    }
  
    //初始化SDL的音频、视频、定时器
    flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
    if (audio_disable)
        flags &= ~SDL_INIT_AUDIO;
    else {
         /*
        尝试解决偶尔出现的ALSA缓冲区下溢问题,问题的原因可能是ALSA重新采样导致周期大小非2的幂
        通过强制设置缓冲区大小来解决
        */
        if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"))
            SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);
    }
    if (display_disable)
        flags &= ~SDL_INIT_VIDEO;
  
    //初始化SDL
    if (SDL_Init (flags)) {
        av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());
        av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n");
        exit(1);
    }

    // 指定事件被忽略
    SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);
    SDL_EventState(SDL_USEREVENT, SDL_IGNORE);

    //创建SDL渲染器
    if (!display_disable) {
        // 设定窗口初始状态为隐藏
        int flags = SDL_WINDOW_HIDDEN;
        if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)
           // 如果SDL的版本支持并且alwaysontop设置为true,创建的窗口总在最前面
            flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
           // 如果SDL的版本不支持,打印警告
            av_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endif
        // 如果borderless为true,创建无边界窗口
        if (borderless)
            flags |= SDL_WINDOW_BORDERLESS;
        else
        // 否则,创建可改变尺寸的窗口
            flags |= SDL_WINDOW_RESIZABLE;

#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
        // 在Linux X11环境下,设置窗口不绕过窗口管理器的compositor,这个选项可以防止窗口以全屏模式打开
        SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
        // 创建SDL窗口
        window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
        
        // 设置渲染器的缩放的插值方式为线性插值
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
        if (window) {
            // 当窗口成功创建后,尝试使用硬件加速并启用垂直同步的方式创建渲染器
            renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
            if (!renderer) {
                av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError());
                renderer = SDL_CreateRenderer(window, -1, 0);
            }
            if (renderer) {
              // 如果渲染器创建成功,打印渲染器的信息
                if (!SDL_GetRendererInfo(renderer, &renderer_info))
                    av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", renderer_info.name);
            }
        }  
        // 如果窗口或渲染器没有成功创建,或者渲染器不支持任何纹理格式,打印一个错误并退出程序
        if (!window || !renderer || !renderer_info.num_texture_formats) {
            av_log(NULL, AV_LOG_FATAL, "Failed to create window or renderer: %s", SDL_GetError());
            do_exit(NULL);
        }
    }
    //打开流
    is = stream_open(input_filename, file_iformat);
    if (!is) {
        av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");
        do_exit(NULL);
    }
    //事件循环
    event_loop(is);

    /* never returns */

    return 0;

系列文章目录

ffplay源码分析(一)主函数
未完待续…

  • 19
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值