目录
ffplay源码解析2 读线程_1
在主线程中使用 SDL 函数 SDL_CreateThread 创建一个名为 “read_thread” 的线程
读函数
static int read_thread(void *arg)
1 定义变量
AVPacket pkt1, *pkt = &pkt1;
pkt1 是一个 AVPacket 结构体,用于存储解码前的媒体数据包(比如编码的音频帧或视频帧)。pkt 是指向 pkt1 的指针,方便传递数据。
int64_t stream_start_time;
stream_start_time 是一个 64 位整数,表示媒体流的开始时间。它通常用来计算播放的时间戳,进行时间同步。
int64_t pkt_ts;
pkt_ts 是一个 64 位整数,代表包的时间戳。它可能用于视频帧或音频帧的同步,确保包按照正确的时间顺序解码和播放。
int pkt_in_play_range = 0;
pkt_in_play_range 是一个布尔变量(0 或 1),用于判断当前解码的包是否在播放范围内。例如,在快进或播放特定区段时,用于判断是否需要处理当前包。
SDL_mutex *wait_mutex = SDL_CreateMutex();
wait_mutex 是一个 SDL 库的互斥锁(mutex)。SDL_CreateMutex() 函数创建了一个新的互斥锁,用于多线程同步,避免多线程访问共享数据时发生竞态条件。
2 初始化流状态
首先检查互斥锁创建是否成功if (!wait_mutex)
// 初始化为-1,如果一直为-1说明没相应steam
is->last_video_stream = is->video_stream = -1;
is->last_audio_stream = is->audio_stream = -1;
is->last_subtitle_stream = is->subtitle_stream = -1;
is->eof = 0; // =1是表明数据读取完毕
初始化三个流,初始化为 -1 表示还没有找到或设置相关的流
ic = avformat_alloc_context();
if (!ic) {
3 创建IC结构
ic 指向新分配的 AVFormatContext 结构体,然后判断是否成功分配
AVFormatContext是一个巨大的结构体(好几百行),是 FFmpeg 中处理多媒体文件的核心结构,管理文件格式、流信息和元数据等。
ic->interrupt_callback.callback = decode_interrupt_cb;
ic->interrupt_callback.opaque = is;
4 设置中断回调函数
设置IC(AVFormatContext)中中断回调函数(interrupt_callback)的两个成员变量:
callback:指向中断回调函数的指针
opaque:一个指向用户数据的指针,赋值成了is(AVPacket)
5 打开输入文件流
err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
avformat_open_input():这是 FFmpeg 中用于打开输入文件的函数。它会根据给定的文件名、输入格式和选项来初始化 AVFormatContext(ic)。
&ic:指向 AVFormatContext 的指针,用于存储文件的格式和流信息。
is->filename:输入文件的路径或 URL。
is->iformat:指定文件的格式。如果传入 NULL,FFmpeg 会自动探测文件格式。
&format_opts:字典选项,包含特定的选项,如我们之前看到的 scan_all_pmts
if (genpts)
ic->flags |= AVFMT_FLAG_GENPTS;
//#define AVFMT_FLAG_GENPTS 0x0001 ///< Generate missing pts even if it requires parsing future frames.
添加时间戳标志,将 AVFMT_FLAG_GENPTS 标志添加到 flags 字段中,表示会生成 PTS
if (find_stream_info) {
AVDictionary **opts = setup_find_stream_info_opts(ic, codec_opts);
int orig_nb_streams = ic->nb_streams;
find_stream_info为真时查找流信息,setup_find_stream_info_opts 函数的主要功能是为每个流分配内存并设置相应的编解码器选项,以便在查找流信息时使用。
err = avformat_find_stream_info(ic, opts);
探测媒体类型和流的信息。
if (ic->pb)
ic->pb->eof_reached = 0; // FIXME hack, ffplay maybe should not use
重置流状态:如果 ic->pb(指向 AVIOContext 的指针)存在,重置 eof_reached 标志为 0,表示尚未到达流的末尾。
6 跳转(seek)播放时间
if (start_time != AV_NOPTS_VALUE)
检查 start_time 是否有效,表示有一个指定的播放起始时间,即有指定seek。
int64_t timestamp;
timestamp = start_time;
timestamp作为指定的播放起始时间
if (ic->start_time != AV_NOPTS_VALUE)
timestamp += ic->start_time;
调整时间戳,ic->start_time是媒体流的实际开始时间 + 指定的偏移时间
ret = avformat_seek_file(ic, -1, INT64_MIN, timestamp, INT64_MAX, 0);
avformat_seek_file() 函数来进行媒体文件的跳转(seek)操作
ic: 输入的 AVFormatContext,表示当前打开的媒体文件或流。
-1: 表示不限制跳转到哪个流(stream_index = -1 表示对所有流进行跳转)。
INT64_MIN: 表示允许跳转的最小时间戳,即可以从最早的时间开始跳转。
timestamp: 目标跳转的时间戳(以微秒为单位)。这是你希望跳转到的媒体时间点,通常是用户或者程序指定的起始时间(可能已经调整过起始时间)。
INT64_MAX: 表示允许跳转的最大时间戳,即跳转时不设上限。
0: 这个参数表示没有额外的标志位(可以通过不同的标志控制跳转行为,比如 AVSEEK_FLAG_BACKWARD 允许向后跳转)
返回值:
ret: 如果跳转成功,avformat_seek_file() 会返回 0;如果跳转失败,则返回负数,表示错误。
7 查找AVStream
下面是根据用户指定来查找流,假设输入文件中有多个音频和视频流,用户只想选择特定语言的音频流以及某个分辨率的字幕流,那么 wanted_stream_spec 中会包含相关选择规则。代码略过
if (!video_disable)
st_index[AVMEDIA_TYPE_VIDEO] =
av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
if (!video_disable):
如果没有禁用视频流(video_disable 为 0)
使用av_find_best_stream
根据流的类型(如视频、音频、字幕等)找到最佳的流。
别的流同理
8 从待处理流中获取相关参数,设置显示窗口的宽度、高度及宽高比
它首先检查是否有有效的视频流,如果存在,则获取该流的宽度、高度和宽高比信息。
然后根据这些信息设置显示窗口的大小,使视频能够正确显示,保持适当的宽高比,避免失真。
// 设置显示窗口的大小和宽高比
set_default_window_size(codecpar->width, codecpar->height, sar);
9 打开视频、音频解码器。在此会打开相应解码器,并创建相应的解码线程
if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) { // 如果有音频流则打开音频流
stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
}
先检查是否存在有效的音频流。如果 st_index[AVMEDIA_TYPE_AUDIO] 大于或等于 0,则调用 stream_component_open 函数打开该音频流。
然后打开视频流,字幕流
最后,检查是否成功打开了音频或视频流。如果两者都没有成功(is->video_stream < 0 和 is->audio_stream < 0),则输出错误日志,说明打开文件或配置滤镜图失败,并将 ret 设置为 -1,跳转到错误处理逻辑。