BPlay1.0系列(7:音频播放) 实现了音频数据的播放,但是实际上拿媒体数据测试时会发现视频播放的速度要远快与音频播放速度,这样就导致视频播放完成之后音频还在播放,这就是视音频不同步现象。本文将修复这个Bug,让音视频数据播放同步起来。
1、音视频同步原理
首先需要介绍一下音视频同步的相关原理:对于一个媒体文件,我们在解封装后会将流数据存到AVPacket里面,此时的数据为编码的流数据,AVPacket里面有一个变量pts,表示的时这一包编码前播放的时间(FFmpeg里有dts(Decoding Time Stamp)和pts(Presentation Time Stamp)两种时间戳,dts是解码的时间,pts是播放的时间),我们做简单音视频同步直接拿pts即可。此外音视频流AVStream结构里保存着一个变量time_base(时间基),pts和时间基的乘积就是这帧数据实际的播放时间。
2、音视频同步实现
根据上述原理,我们只要通过pts来控制视频帧和音频帧播放的相对速率即可实现音视频同步,在每次音频解码后记录音频pts:
/********************************
* void BAudio::run()
* 功能:音频解码/播放线程
* *****************************/
void Baudio::run()
{
char out[20000] = { 0 };
while (audiorun)
{
AVPacket pkt;
memset(&pkt , 0, sizeof(pkt));
if ((Bffmpeg::GetInstance()->GetAudioQue().que.size() == 0) || (output->bytesFree() < output->periodSize())) {
msleep(1);
continue;
} else {
/* 音频流编码数据出队列 */
QMutexLocker Locker(&Bffmpeg::GetInstance()->GetAudioQue().mtx);
pkt = Bffmpeg::GetInstance()->GetAudioQue().que.front();
Bffmpeg::GetInstance()->GetAudioQue().que.pop_front();
/* 更新音频播放时间 */
Bffmpeg::GetInstance()->SetAudioPts(pkt.pts *
av_q2d(Bffmpeg::GetInstance()->GetFormatContext()->streams[Bffmpeg::GetInstance()->GetAudioIndex()]->time_base));
BLOG("Bffmpeg::GetInstance()->GetAudioPts() %f", Bffmpeg::GetInstance()->GetAudioPts());
}
/* 解码 */
AVFrame* frame = Bffmpeg::GetInstance()->Decode(&pkt);
av_packet_unref(&pkt);
if (frame == NULL) {
continue;
}
/* 音频重采样 */
AVFormatContext *FormatContext = Bffmpeg::GetInstance()->GetFormatContext();
AVCodecContext *CodecContext = FormatContext->streams[Bffmpeg::GetInstance()->GetAudioIndex()]->codec;
uint8_t *data[1];
data[0] = (uint8_t *)out;
int len = swr_convert(Bffmpeg::GetInstance()->GetSwrContext(), data, sizeof(out), (const uint8_t **)frame->data, frame->nb_samples);
if (len <= 0)
{
return;
}
int datasize = av_samples_get_buffer_size(NULL, CodecContext->channels, frame->nb_samples, AV_SAMPLE_FMT_S16, 0);
/* 播放 */
io->write(out, datasize);
}
return;
}
视频播放线程里通过音频的pts来控制释放持续解码:
/********************************
* void Bvideo::run()
* 功能:视频解码线程
* *****************************/
void Bvideo::run()
{
while (videorun)
{
AVPacket pkt = Bffmpeg::GetInstance()->GetVideoQue().que.front();
if ((Bffmpeg::GetInstance()->GetVideoQue().que.size() == 0) ||
(pkt.pts * av_q2d(Bffmpeg::GetInstance()->GetFormatContext()->streams[Bffmpeg::GetInstance()->GetVideoIndex()]->time_base) > Bffmpeg::GetInstance()->GetAudioPts())) {
msleep(1);
continue;
} else {
/* 视频流编码数据出队列 */
QMutexLocker Locker(&Bffmpeg::GetInstance()->GetVideoQue().mtx);
Bffmpeg::GetInstance()->GetVideoQue().que.pop_front();
}
/* 解码 */
AVFrame* frame = Bffmpeg::GetInstance()->Decode(&pkt);
av_packet_unref(&pkt);
if (frame == NULL) {
continue;
}
/* 解码帧数据入RGB队列 */
QMutexLocker FrameLocker(&frameque.mtx);
}
return;
}
最终效果还是不错的,呵呵~~