目录
- 1.mian函数的参数
- 2.初始化
- 2.1 FFmpeg初始化
- 2.2 对传递的参数进行初始化
- 2.3 SDL的初始化
- 2.4 创建窗口
- 2.5 开启read_thread读取线程
- 2.6 事件响应
- 附
- 附1 VideoState 结构体
ffplay源码解析1 主线程
播放器常用功能包括:播放、停止、暂停、拖动、快进、快退、变速播放。
1.mian函数的参数
int main(int argc, char **argv)
argc: 这是一个整型参数,表示命令行中传递给程序的参数数量。
argv: 这是一个字符指针数组,存储了命令行参数的具体字符串值。**argv相当于 *argv[]。
argc 提供参数的数量,而 argv 提供每个参数的内容,便于程序根据用户输入进行相应操作。
2.初始化
包括对FFmpeg的初始化,对传递的参数进行初始化,SDL的初始化
2.1 FFmpeg初始化
#if CONFIG_AVDEVICE //如果在编译时定义了 CONFIG_AVDEVICE,则条件为真,编译器将编译 #if 和 #endif 之间的代码;否则,将跳过这些代码。
avdevice_register_all(); //通过调用FFmpeg的API遍历所有可用的设备驱动程序,并将它们注册到 FFmpeg 中。
#endif
avformat_network_init(); //初始化网络
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
sigterm_handler 函数是一个信号处理器,专门用于处理接收到的信号。当程序接收到中断或终止信号时(如 SIGINT 或 SIGTERM),这个函数将被调用,程序将以状态码 123 终止。
show_banner(argc, argv, options);
show_banner 函数负责显示程序的启动信息,包括程序名称、版权和相关库的配置信息。
2.2 对传递的参数进行初始化
parse_options(NULL, argc, argv, options, opt_input_file);
首先parse_options对用户输入的参数进行解析
怎么知道解析出来的参数哪个是input_filename?
通常,-i 是一个选项,input.mp4是该选项的参数,表示输入文件名。解析时根据选项对应
对参数两个判断
- input_filename是否为空,空的话异常退出,exit(1);
- display_disable是否显示视频,显示的话audio_disable = 1
2.3 SDL的初始化
flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
其中
#define SDL_INIT_TIMER 0x00000001 // 二进制:0000 0001
#define SDL_INIT_AUDIO 0x00000010 // 二进制:0001 0000
#define SDL_INIT_VIDEO 0x00000020 // 二进制:0010 0000
所有可能的 flags 结果组合有8种
由于每个模块可以启用或关闭,这里是每种可能的组合:
无任何模块启用:
flags = 0(即所有位都为 0)
仅启用 SDL_INIT_TIMER:
flags = 0x00000001(0000 0001)
仅启用 SDL_INIT_AUDIO:
flags = 0x00000010(0001 0000)
仅启用 SDL_INIT_VIDEO:
flags = 0x00000020(0010 0000)
启用 SDL_INIT_TIMER 和 SDL_INIT_AUDIO:
flags = 0x00000011(0001 0001)
启用 SDL_INIT_TIMER 和 SDL_INIT_VIDEO:
flags = 0x00000021(0010 0001)
启用 SDL_INIT_AUDIO 和 SDL_INIT_VIDEO:
flags = 0x00000030(0011 0000)
启用 SDL_INIT_TIMER、SDL_INIT_AUDIO 和 SDL_INIT_VIDEO:
flags = 0x00000031(0011 0001)
如果禁用音频display_disable == true,那么把音频标志从flags中去掉
if (display_disable)
flags &= ~SDL_INIT_VIDEO;
如果禁用音频,那么把视频标志从flag中去掉
if (audio_disable)
flags &= ~SDL_INIT_AUDIO;
Flush Packet:在 FFmpeg 中,flush_pkt 常用于刷新缓冲区(flush),特别是在处理完一个流后,发送一个这样的包可以让解码器知道流结束了,并清除其内部缓存。
av_init_packet(&flush_pkt); // 初始化flush_packet
flush_pkt.data = (uint8_t *)&flush_pkt; // 初始化为数据指向自己本身
2.4 创建窗口
如果没有禁用视频if (!display_disable)才往下执行
判断窗口有没有边框,把标志加到flags里面
if (borderless)
flags |= SDL_WINDOW_BORDERLESS; //无边框,不能调整大小
else
flags |= SDL_WINDOW_RESIZABLE;
创建窗口
window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
参数解释:
program_name: 窗口的标题,通常是程序的名称。
SDL_WINDOWPOS_UNDEFINED:表示窗口的位置未定义,SDL 会自动选择一个位置。 default_width: 窗口的宽度,通常是一个预先定义的常量或变量。
default_height: 窗口的高度,通常是一个预先定义的常量或变量。
flags:用于指定窗口的属性(如边框、可调整大小等),这些标志在前面的代码中已根据 borderless 和其他选项设置。
设置渲染质量
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
如果窗口创建成功,使用SDL 库的一个函数,给窗口创建一个渲染器renderer
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
在 SDL (Simple DirectMedia Layer) 中,renderer 是一个用于渲染图形的对象,它负责将图像绘制到窗口上。具体来说,renderer 提供了一系列函数和功能,用于处理绘图、图像加载、纹理管理等。
2.5 开启read_thread读取线程
is = stream_open(input_filename, file_iformat);
stream_open 函数用于打开和初始化一个流(stream)
它接受两个参数:
input_filename:指定的输入文件名或路径。
file_iformat:文件格式,可能是一个指示要打开的文件类型的参数。
返回值 is 是一个指向 VideoState 结构体的指针,用于表示视频播放器的状态和控制。
VideoState 结构体是一个很大的结构体,在放在附1
stream_open函数做了很多初始化工作,例如初始化帧队列,初始化时钟,初始化音量,创建读线程
初始化结构体
VideoState *is;//生命结构体指针
is = av_mallocz(sizeof(VideoState)); /* 分配结构体并初始化 */
if (!is)
return NULL;
is->filename = av_strdup(filename);
if (!is->filename)
goto fail;
is->iformat = iformat; //成员变量赋值
is->ytop = 0;
is->xleft = 0;
初始化帧队列
init了音频,视频,字幕的包队列和帧队列,有一个创建不成功就返回fail
if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)//1:表示队列在初始化时会分配内存
goto fail;
if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
goto fail;
if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
goto fail;
if (packet_queue_init(&is->videoq) < 0 ||
packet_queue_init(&is->audioq) < 0 ||
packet_queue_init(&is->subtitleq) < 0)
goto fail;
初始化时钟
初始化音频时钟
init_clock(&is->audclk, &is->audioq.serial);
初始化音频
限制启动音量的范围:
startup_volume = av_clip(startup_volume, 0, 100);
将启动音量转换为 SDL 的音量范围:
startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
相关设置:
is->audio_volume 设置为经过调整后的启动音量值。
is->muted 设置为 0,表示未静音状态。
is->av_sync_type 设置为 av_sync_type,可能是与音视频同步相关的类型或设置。
创建读线程
is->read_tid = SDL_CreateThread(read_thread, "read_thread", is);
使用 SDL 函数 SDL_CreateThread 创建一个名为 “read_thread” 的线程,并将该线程的标识符保存在 is->read_tid 中。
read_thread 是一个函数指针,用于指定线程的入口函数。
2.6 事件响应
事件循环监控,响应相关的键盘事件(f键,p键,空格等),鼠标事件,窗口事件等
event_loop(is);
附
附1 VideoState 结构体
typedef struct VideoState {
SDL_Thread *read_tid; // 读线程句柄
AVInputFormat *iformat; // 指向demuxer
int abort_request; // =1时请求退出播放
int force_refresh; // =1时需要刷新画面,请求立即刷新画面的意思
int paused; // =1时暂停,=0时播放
int last_paused; // 暂存“暂停”/“播放”状态
int queue_attachments_req;
int seek_req; // 标识一次seek请求
int seek_flags; // seek标志,诸如AVSEEK_FLAG_BYTE等
int64_t seek_pos; // 请求seek的目标位置(当前位置+增量)
int64_t seek_rel; // 本次seek的位置增量
int read_pause_return;
AVFormatContext *ic; // iformat的上下文
int realtime; // =1为实时流
Clock audclk; // 音频时钟
Clock vidclk; // 视频时钟
Clock extclk; // 外部时钟
FrameQueue pictq; // 视频Frame队列
FrameQueue subpq; // 字幕Frame队列
FrameQueue sampq; // 采样Frame队列
Decoder auddec; // 音频解码器
Decoder viddec; // 视频解码器
Decoder subdec; // 字幕解码器
int audio_stream ; // 音频流索引
int av_sync_type; // 音视频同步类型, 默认audio master
double audio_clock; // 当前音频帧的PTS+当前帧Duration
int audio_clock_serial; // 播放序列,seek可改变此值
// 以下4个参数 非audio master同步方式使用
double audio_diff_cum; // used for AV difference average computation
double audio_diff_avg_coef;
double audio_diff_threshold;
int audio_diff_avg_count;
// end
AVStream *audio_st; // 音频流
PacketQueue audioq; // 音频packet队列
int audio_hw_buf_size; // SDL音频缓冲区的大小(字节为单位)
// 指向待播放的一帧音频数据,指向的数据区将被拷入SDL音频缓冲区。若经过重采样则指向audio_buf1,
// 否则指向frame中的音频
uint8_t *audio_buf; // 指向需要重采样的数据
uint8_t *audio_buf1; // 指向重采样后的数据
unsigned int audio_buf_size; // 待播放的一帧音频数据(audio_buf指向)的大小
unsigned int audio_buf1_size; // 申请到的音频缓冲区audio_buf1的实际尺寸
int audio_buf_index; // 更新拷贝位置 当前音频帧中已拷入SDL音频缓冲区
// 的位置索引(指向第一个待拷贝字节)
// 当前音频帧中尚未拷入SDL音频缓冲区的数据量:
// audio_buf_size = audio_buf_index + audio_write_buf_size
int audio_write_buf_size;
int audio_volume; // 音量
int muted; // =1静音,=0则正常
struct AudioParams audio_src; // 音频frame的参数
#if CONFIG_AVFILTER
struct AudioParams audio_filter_src;
#endif
struct AudioParams audio_tgt; // SDL支持的音频参数,重采样转换:audio_src->audio_tgt
struct SwrContext *swr_ctx; // 音频重采样context
int frame_drops_early; // 丢弃视频packet计数
int frame_drops_late; // 丢弃视频frame计数
enum ShowMode {
SHOW_MODE_NONE = -1, // 无显示
SHOW_MODE_VIDEO = 0, // 显示视频
SHOW_MODE_WAVES, // 显示波浪,音频
SHOW_MODE_RDFT, // 自适应滤波器
SHOW_MODE_NB
} show_mode;
// 音频波形显示使用
int16_t sample_array[SAMPLE_ARRAY_SIZE]; // 采样数组
int sample_array_index; // 采样索引
int last_i_start; // 上一开始
RDFTContext *rdft; // 自适应滤波器上下文
int rdft_bits; // 自使用比特率
FFTSample *rdft_data; // 快速傅里叶采样
int xpos;
double last_vis_time;
SDL_Texture *vis_texture; // 音频Texture
SDL_Texture *sub_texture; // 字幕显示
SDL_Texture *vid_texture; // 视频显示
int subtitle_stream; // 字幕流索引
AVStream *subtitle_st; // 字幕流
PacketQueue subtitleq; // 字幕packet队列
double frame_timer; // 记录最后一帧播放的时刻
double frame_last_returned_time; // 上一次返回时间
double frame_last_filter_delay; // 上一个过滤器延时
int video_stream; // 视频流索引
AVStream *video_st; // 视频流
PacketQueue videoq; // 视频队列
double max_frame_duration; // 一帧最大间隔. above this, we consider the jump a timestamp discontinuity
struct SwsContext *img_convert_ctx; // 视频尺寸格式变换
struct SwsContext *sub_convert_ctx; // 字幕尺寸格式变换
int eof; // 是否读取结束
char *filename; // 文件名
int width, height, xleft, ytop; // 宽、高,x起始坐标,y起始坐标
int step; // =1 步进播放模式, =0 其他模式
#if CONFIG_AVFILTER
int vfilter_idx;
AVFilterContext *in_video_filter; // the first filter in the video chain
AVFilterContext *out_video_filter; // the last filter in the video chain
AVFilterContext *in_audio_filter; // the first filter in the audio chain
AVFilterContext *out_audio_filter; // the last filter in the audio chain
AVFilterGraph *agraph; // audio filter graph
#endif
// 保留最近的相应audio、video、subtitle流的steam index
int last_video_stream, last_audio_stream, last_subtitle_stream;
SDL_cond *continue_read_thread; // 当读取数据队列满了后进入休眠时,可以通过该condition唤醒读线程
} VideoState;