ffplay源码解析3 读线程_2

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))))

检测队列总和是否已经有足够数据条件有三:

  1. 检查当前是否为无限缓冲状态.
  2. (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE):如果音频、视频和字幕队列的总和大小超过了预设的最大队列大小(MAX_QUEUE_SIZE),则不再读取数据。
  3. 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(真),表示当前流有足够的数据包。

  1. 如果 stream_id 小于 0,表示没有有效的流,因此返回值为真,表示不需要检查数据包的数量。
  2. 如果 queue->abort_request 为真,表明系统正在请求退出操作,也应返回真,无需继续检查数据。
  3. 判断是否是附加图片,(st->disposition & AV_DISPOSITION_ATTACHED_PIC)如果该流是附加图片,则返回真。附加图片通常用于封面图像等,是静态一次加载完成。
  4. 数据包计数:检查当前队列中的数据包数量是否大于 MIN_FRAMES(假设是 25)。
  5. 时间长度检查:首先检查 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)))// 或者视频播放完毕
  1. 没有视频流
  2. 视频解码器标记 viddec.finished 等于视频队列的序列号 (is->viddec.finished == is->videoq.serial)
  3. 视频队列中剩余的帧数为0 (frame_queue_nb_remaining(&is->pictq) == 0)

音频流同理。
如果视频流和音频流都满足以上条件,并且没有设置循环播放。
调用 stream_seek 函数进行流的重新定位。
start_time 或者默认从0开始。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值