引言
在数字化时代,多媒体播放器已成为我们日常生活中不可或缺的一部分。无论是观看电影、听取音乐,抑或是进行视频会议,我们都依赖于功能强大的播放器来呈现丰富多彩的音视频内容。
而在众多播放器中,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源码分析(一)主函数
未完待续…