ffplay源码解析3 读线程_2
经过之前的处理已经找到相应的流信息,并创建相应的解码线程。
接下来使用for (;;)
无限循环来等待和处理事件。
1.检测是否退出
if (is->abort_request)
break;
如果收到退出请求(abort_request)。如果收到请求,循环会立即退出。
2. 检测是否暂停/继续
if (is->paused != is->last_paused)
如果 is->paused 的值与 is->last_paused 不同,说明状态发生了变化(暂停/继续)。
3. 检测是否seek
if (is->seek_req) { // 检查是否有seek请求
int64_t seek_target = is->seek_pos; // 目标位置
int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
is->seek_rel 大于 0,表示这是一个向前的寻求操作:
seek_min 计算为 seek_target - is->seek_rel + 2,这里的 +2 是为了处理精度问题。
然后在媒体流中执行寻求操作。
ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
ret返回值小于零表示寻求失败。
寻找媒体流成功之后执行以下操作:
如果有音频流,处理音频流:
if (is->audio_stream >= 0) { // 如果有音频流
packet_queue_flush(&is->audioq); // 清空packet队列数据
// 放入flush pkt, 用来开起新的一个播放序列, 解码器读取到flush_pkt也清空解码器
packet_queue_put(&is->audioq, &flush_pkt);
}
同样这样处理视频流和字幕流。
根据标志设置时钟
if (is->seek_flags & AVSEEK_FLAG_BYTE) {
set_clock(&is->extclk, NAN, 0);
} else {
set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0);
}
如果seek_flags有AVSEEK_FLAG_BYTE标志,说明是基于字节的,此时将时钟设置为 NAN(非数值)。
如果没有说明是基于时间戳的,将时钟设置为 seek_target 除以 AV_TIME_BASE 的结果。
播放控制
if (is->paused)
step_to_next_frame(is); // 播放seek后的第一帧
帮助播放器跳过到下一帧,以便在寻求后顺利继续播放。
4. 检测队列是否已经有足够数据
如果有足够的数据,对读线程进行一个阻塞
if (infinite_buffer<1 &&
(is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
|| (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) &&
stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) &&
stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq))))
检测队列总和是否已经有足够数据条件有三:
- 检查当前是否为无限缓冲状态.
- (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE):如果音频、视频和字幕队列的总和大小超过了预设的最大队列大小(MAX_QUEUE_SIZE),则不再读取数据。
- stream_has_enough_packets(…):这个函数用于检查每个流(音频、视频、字幕)中是否已经有足够的数据包,以满足播放需求。如果所有流都有足够的数据,则表示可以暂停读取。
接下来就逐个检查各个流的包队列:
static int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue) {
return stream_id < 0 || // 没有该流
queue->abort_request || // 请求退出
(st->disposition & AV_DISPOSITION_ATTACHED_PIC) || // 是 ATTACHED_PIC
queue->nb_packets > MIN_FRAMES // packet数 > 25
&& (!queue->duration || // 满足 PacketQueue 总时长为 0
av_q2d(st->time_base) * queue->duration > 1.0); // 或总时长超过 1s
}
以下条件满足任意一个,代表函数将返回 1(真),表示当前流有足够的数据包。
- 如果 stream_id 小于 0,表示没有有效的流,因此返回值为真,表示不需要检查数据包的数量。
- 如果 queue->abort_request 为真,表明系统正在请求退出操作,也应返回真,无需继续检查数据。
- 判断是否是附加图片,(st->disposition & AV_DISPOSITION_ATTACHED_PIC)如果该流是附加图片,则返回真。附加图片通常用于封面图像等,是静态一次加载完成。
- 数据包计数:检查当前队列中的数据包数量是否大于 MIN_FRAMES(假设是 25)。
- 时间长度检查:首先检查 queue->duration 是否为零。如果为零,说明队列中没有持续时间,这种情况下认为有足够的数据包。
如果 queue->duration 不为 0,则计算实际的总时长:av_q2d(st->time_base) * queue->duration。如果这个时长大于 1 秒,则也认为有足够的数据包。
如果判断整体队列已满,进入等待超时。
/* wait 10 ms */
SDL_LockMutex(wait_mutex);
// 如果没有唤醒则超时10ms退出,比如在seek操作时这里会被唤醒
SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
SDL_UnlockMutex(wait_mutex);
continue;
在循环中
-
锁定了一个名为 mutex 的互斥量(mutex)。
-
当前线程等待一个条件变量 is->continue_read_thread,它会在条件变量被唤醒或超时(在这里是 10毫秒)时返回。如果在 10 毫秒内没有收到唤醒信号,线程将继续执行。
-
SDL_UnlockMutex(wait_mutex); 这行代码解锁 wait_mutex,允许其他线程访问被保护的资源。
5. 检测码流是否已经播放结束
首先检测码流是否处于暂停状态,不是暂停状态的话执行下面操作
视频流检测:
(!is->video_st // 没有视频流
|| (is->viddec.finished == is->videoq.serial && frame_queue_nb_remaining(&is->pictq) == 0)))// 或者视频播放完毕
- 没有视频流
- 视频解码器标记 viddec.finished 等于视频队列的序列号 (is->viddec.finished == is->videoq.serial)
- 视频队列中剩余的帧数为0 (frame_queue_nb_remaining(&is->pictq) == 0)
音频流同理。
如果视频流和音频流都满足以上条件,并且没有设置循环播放。
调用 stream_seek 函数进行流的重新定位。
start_time 或者默认从0开始。