ffplay源码分析-main函数入口分析

ffplay源码分析-main函数入口分析

基于ffmpeg6.0源码分析。

流程

使用ffplay播放视频文件,会触发main函数的调用。main函数中会进行以下操作:

  1. 从命令行中解析日志级别、日志是否需要落文件、是否要输出banner信息。banner信息包含版权、库的版本。
  2. 注册解码器、复用器、协议。
  • avdevice_register_all会将输出formats和输入formats注册到allformats.c的变量中。
    allformat.c
void avpriv_register_devices(const FFOutputFormat * const o[], const AVInputFormat * const i[])
{
    
    
    // 还是个原子操作赋值。
    atomic_store_explicit(&outdev_list_intptr, (uintptr_t)o, memory_order_relaxed);
    atomic_store_explicit(&indev_list_intptr,  (uintptr_t)i, memory_order_relaxed);
}

alldevices.c

void avdevice_register_all(void)
{
    
    
    avpriv_register_devices(outdev_list, indev_list);
}

其中outdev_list来自outdev_list.c文件,idev_list来自indev_list.c文件。这2个文件是自动生成的。所以我猜它是根据我们不同的配置来生成不同的内容。
我的indev_list.c

static const AVInputFormat * const indev_list[] = {
    
    
&ff_avfoundation_demuxer,
&ff_lavfi_demuxer,
NULL };

我的outdev_list.c

static const FFOutputFormat * const outdev_list[] = {
    
    
&ff_audiotoolbox_muxer,
&ff_sdl2_muxer,
NULL };
  1. 初始化网络,会根据使用的是openssl还是guntls进行初始化,对于window,有可能需要调用一下WSAStartup,ms-socket
void ff_tls_deinit(void)
{
    
    
#if CONFIG_TLS_PROTOCOL
#if CONFIG_OPENSSL
    ff_openssl_deinit();
#endif
#if CONFIG_GNUTLS
    ff_gnutls_deinit();
#endif
#endif
}

int ff_network_init(void)
{
    
    
#if HAVE_WINSOCK2_H
    WSADATA wsaData;
    // Windows
    if (WSAStartup(MAKEWORD(1,1), &wsaData))
        return 0;
#endif
    return 1;
}
  1. 监听程序退出信号,这一步是为了能响应用户关闭程序的操作。比如在播放过程中,在命令行中按ctrl-c就会退出程序。
static void sigterm_handler(int sig)
{
    
    
    exit(123);
}
  1. 显示banner信息,banner信息包含版本、配置等。
void show_banner(int argc, char **argv, const OptionDef *options)
{
    
    
    int idx = locate_option(argc, argv, options, "version");
    if (hide_banner || idx)
        return;

    // 打印ffmpeg的版本,版权信息、配置信息
    print_program_info (INDENT|SHOW_COPYRIGHT, AV_LOG_INFO);
    // 打印子库的配置信息
    print_all_libs_info(INDENT|SHOW_CONFIG,  AV_LOG_INFO);
    // 打印字库的版本信息
    print_all_libs_info(INDENT|SHOW_VERSION, AV_LOG_INFO);
}
  1. 解析命令行参数,得到需要播放的文件,解析成功之后全局变量input_filename存储的就是输入的文件。
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
                   void (*parse_arg_function)(void *, const char*))
{
    
    
    const char *opt;
    int optindex, handleoptions = 1, ret;
    prepare_app_arguments(&argc, &argv); // 空的,不用管
    optindex = 1;
    while (optindex < argc) {
    
    
        opt = argv[optindex++];

        if (handleoptions && opt[0] == '-' && opt[1] != '\0') {
    
    
            if (opt[1] == '-' && opt[2] == '\0') {
    
    
                handleoptions = 0; // 如果参数是"--" ,即没有拼参数名,则跳过。
                continue;
            }
            opt++;
            // 如果小于0,说明解析参数发生了错误。
            if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)
                exit_program(1); // 退出程序。
            optindex += ret;
        } else {
    
    
            if (parse_arg_function)  // 如果上一个参数是跳过的(handleoptiongs是0),或者参数不是"-"开始的一个字符串,不能只是一个"-",则执行这个函数。
                parse_arg_function(optctx, opt); // 这种情况opt可能是要处理文件。
        }
    }
}
// parse_arg_function 就指向这个函数:
static void opt_input_file(void *optctx, const char *filename)
{
    
    
    if (input_filename) {
    
    
        av_log(NULL, AV_LOG_FATAL,
               "Argument '%s' provided as input filename, but '%s' was already specified.\n",
                filename, input_filename);
        exit(1);
    }
    if (!strcmp(filename, "-"))
        filename = "fd:";
    input_filename = filename; // 要播放的输入文件。
}
  1. 如果没解析到要播放的文件,输出帮助信息,并退出程序。
  2. 初始化SDL库,ffplay播放音频、视频都用到SDL库。
  3. 根据用户的配置、SDL的版本、来设置SDL显示窗口的配置,比如是否显示窗口边界。使用默认大小来创建一个SDL窗口。
 int flags = SDL_WINDOW_HIDDEN;
        if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)
            flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
            av_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endif
        if (borderless) // 命令参数可以控制
            flags |= SDL_WINDOW_BORDERLESS; // 控制SDL显示的窗口是否需要边界
        else
            flags |= SDL_WINDOW_RESIZABLE;

#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
        SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
        // 使用默认大小、窗口配置flags创建窗口。
        window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); // https://wiki.libsdl.org/SDL2/SDL_HINT_RENDER_SCALE_QUALITY
  1. 创建renderer,renderer是SDK绘制图形必须要用到的东西。以及是否有可用的texture。
 if (window) {
    
    
            // 创建renderer, 可以用这个画图形。
            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)) // 获取renderer的信息。
                    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);
        }
        ... 
  1. 打开输入文件。
  2. 进入循环,监听键盘、鼠标事件,循环渲染视频。

代码

int main(int argc, char **argv)
{
    
    
    int flags;
    VideoState *is; // 用来保存全局状态

    init_dynload(); // 仅window会有用,安全问题,会将当前目录从DLL库搜索目录中删除。

    av_log_set_flags(AV_LOG_SKIP_REPEATED); // 设置日志如果是重复文案是否跳过
    parse_loglevel(argc, argv, options); // 从参数中解析日志级别、日志是否要落文件、是否要输出banner信息。

    /* register all codecs, demux and protocols */
#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    avformat_network_init();
    // 处理用户退出程序操作。 https://zh.m.wikipedia.org/wiki/Unix%E4%BF%A1%E5%8F%B7#SIGTERM
    signal(SIGINT , sigterm_handler); /* Interrupt (ANSI).  Ctrl-C   */
    signal(SIGTERM, sigterm_handler); /* Termination (ANSI). when user terminate  */
    // 显示banner, 就是打印动态库的版权、版本等信息。
    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;
    }
    flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER; // 设置SDL初始化时的flags。包含了SDL音频子系统、SDL视频子系统、SDL时间子系统、事件子系统。
    if (audio_disable) // // 用户参数可以控制这个开关,默认关闭。
        flags &= ~SDL_INIT_AUDIO;
    else {
    
    
        /* Try to work around an occasional ALSA buffer underflow issue when the
         * period size is NPOT due to ALSA resampling by forcing the buffer size. */
        if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE")) // 奇怪的fix.
            SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);
    }
    if (display_disable)
        flags &= ~SDL_INIT_VIDEO;
    if (SDL_Init (flags)) {
    
     // 初始化SDL,https://wiki.libsdl.org/SDL2/SDL_Init
        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);
    }
    // 不监听系统事件和用户自定义事件。https://wiki.libsdl.org/SDL2/SDL_Event
    SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);
    SDL_EventState(SDL_USEREVENT, SDL_IGNORE);

    if (!display_disable) {
    
    
        int flags = SDL_WINDOW_HIDDEN;
        if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)
            flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
            av_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endif
        if (borderless)
            flags |= SDL_WINDOW_BORDERLESS; // 控制SDL显示的窗口是否需要边界
        else
            flags |= SDL_WINDOW_RESIZABLE;

#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
        SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
        // 使用默认大小、窗口配置flags创建窗口。
        window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); // https://wiki.libsdl.org/SDL2/SDL_HINT_RENDER_SCALE_QUALITY
        if (window) {
    
    
            // 创建renderer, 可以用这个画图形。
            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)) // 获取renderer的信息。
                    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);
        }
    }
    // 打开输入文件, 如果用户强制设置了fmt file_iformat则不为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;
}

猜你喜欢

转载自blog.csdn.net/a992036795/article/details/129768110