有了前面几篇文章介绍的ffmpeg抓帧与opengl显示,就可以播放视频了,本篇主要介绍视频帧的播放与调度。
在播发器的开发过程中,虽然ffmpeg抓出来的不管是音频还是视频,都是统一用avframe来存储数据,并且播放顺序上也是按顺序的,但如果直接用一个线程既播放音频又播放视频,效果会很不好,会出现明显的卡顿,所以需要分发,将音频和视频分别放在各自的队列中使用各自单独的线程进行播放,最后进行音视频同步。
对于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;
}
if (do_pause) {
// 毫秒
DELAY(100);
m_state = STATE_PAUSE;
continue;
}
stop
结束循环
seekTo
未完待续。。。