当前位置: 首页 > 工具软件 > Bistoury > 使用案例 >

视频手术刀 video bistoury(四)--VideoPlayer

穆智刚
2023-12-01

有了前面几篇文章介绍的ffmpeg抓帧与opengl显示,就可以播放视频了,本篇主要介绍视频帧的播放与调度。

帧缓存队列

在播发器的开发过程中,虽然ffmpeg抓出来的不管是音频还是视频,都是统一用avframe来存储数据,并且播放顺序上也是按顺序的,但如果直接用一个线程既播放音频又播放视频,效果会很不好,会出现明显的卡顿,所以需要分发,将音频和视频分别放在各自的队列中使用各自单独的线程进行播放,最后进行音视频同步。

Created with Raphaël 2.1.0 video ffmpeg capturer (thread) frame video or audio video frame queue video runnable thread delay per frame opengl render (thread) audio frame queue audio runnable thread audio player yes no

对于audioplayer,每个平台都有自己的播放接口,比如QT中使用QIODevice+QAudioOutput,android中使用AudioTrack;音频播放就是像这些设备API中写入数据,提前设置好参数就行,包括samplerate、channels、采样精度(一般是short)

音视频同步

音视频同步一般以音频时间为基准,将视频帧同步到音频时间轴上

比较音频帧与视频帧的pts,当差值超过阈值时微调视频帧pts,实现类似“你追我赶”的效果

在对音频帧队列和视频帧队列操作是需要加锁(std::mutex)

void VideoPlayer::video_runnable() {
    double delay = 0;//second
    FFrame *copyFrame = new FFrame();
    while (m_state != STATE_STOP) {
        if (video_frame_queue->size() > 0) {
            v_mutex.lock();
            auto frame = video_frame_queue->front();
            copyFrame->copy(frame);
            delete frame;
            video_frame_queue->pop();
            v_mutex.unlock();

            delay = copyFrame->pts - video_state.frame_last_pts;
            if (delay <= 0 || delay >= 1.0) {
                delay = video_state.frame_last_delay;
            }
            video_state.frame_last_delay = delay;
            video_state.frame_last_pts = copyFrame->pts;

            //同步视频到音频
            double ref_clock = get_audio_clock(&video_state);
            double diff = copyFrame->pts - ref_clock;
            double threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_NOSYNC_THRESHOLD;

            if (fabs(diff) < AV_NOSYNC_THRESHOLD) {
                if (diff <= -threshold) {// video slow
                    delay = 0;
                }
                if (diff >= threshold) {//video quick
                    delay *= 2;
                }
                video_state.frame_timer += delay;
            }
            DELAY((int64_t) (delay * 1000));

            //发射阻塞信号,在 slot 里释放
            emit displayFrame(copyFrame);
        } else {
            DELAY(100);
            continue;
        }
    }
    delete copyFrame;
}

获取音频时钟

音频跟视频不同,音频在时域上是有效应的,audio frame 中的pts标记的是这段音频开始播放的时间;在音视频同步时一段音频帧可能正处在播放状态中,这时使用audio_frame->pts 来同步视频帧可能并不准确,这里可根据音频数据的时域特性近似的估计下当前时钟,使同步更加准确。

double VideoPlayer::get_audio_clock(VideoState *vs) {
    double pts;
    int hw_buf_size;//一块音频中尚未播放的缓存大小
    pts = vs->audio_clock;
    hw_buf_size = vs->audio_buf_size / 2;
    if (vs->bytes_per_sec) {
        pts -= (double) hw_buf_size / vs->bytes_per_sec;
    }
    return pts;
}

播放器状态调度

  • pause
    在抓包的while循环中continue,跳过抓帧的操作,但是对循环保活;注意DRLAY操作,防止程序挂掉
        if (do_pause) {
//            毫秒
            DELAY(100);
            m_state = STATE_PAUSE;
            continue;
        }
  • stop
    结束循环

  • seekTo
    未完待续。。。

 类似资料: