android 音视频同步
2020-04-25 本文已影响0人
小明叔叔_乐
1、思路:
采用视频跟随音频的策略(人对音频敏感,对视频相对不敏感导致):音频通道正常播放,在每次获取音频frame对象放入opensl播放队列后,记录当前音频播放的时间戳;视频通道,获取当前视频frame对象,并在显示一帧画面后,获取视频frame对象的显示时间戳,从而可以得到两者的时间差,根据这个时间差调整视频的播放策略(如丢帧,减少帧间时间,延长帧间时间等)
2、上代码:
AudioChannel.cpp
/**
* 获取PCM数据
* @return PCM数据的有效长度
*/
int AudioChannel::getPcm() {
...
while (isPlaying) {
ret = frame_queue.get(frame);
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
... // 转成PCM数据,并放入opensl播放队列中
// 设置当前音频播放的时间戳(音视频同步时使用) --- pts 显示时间戳; dts 解码时间戳
colock = frame->pts * av_q2d(timeBase);
break;
}
// 转换成功后,释放frame对象
releaseFrame(frame);
return out_buffer_size;
}
VideoChannel.cpp
/**
* 从frame queue中获取frame对象,并传给window对象真正显示
*/
void VideoChannel::showFrame() {
... // 显示frame的前期准备
double delay = 0;
while (isPlaying) {
if (!isPlaying) {
break;
}
ret = frame_queue.get(frame);
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
... // 数据转换并显示
// 视频 与 音频 的时间戳 的差异, timeDif单位为 秒
videoClock = frame->pts * av_q2d(timeBase);
if (audioChannel) {
timeDif = videoClock - audioChannel->colock;
}
end = getCurrentTimeUs();
extra_delay =
frame->repeat_pict / (double) (2 * fps); // 如果没有重复帧,这个值为0;为重复帧时,导致的额外延时,单位 秒
// 统一转换为单位 秒 (end - start)是解码一帧消耗的时间
delay = (1.0f / fps) + extra_delay - (end - start) / 1000000.0f;
delay = delay < 0 ? 0 : delay;
// 根据timeDif来动态调整休眠时间 --- 音视频同步的精髓
adjustSleepTime(timeDif, delay);
// 释放 frame, RGBA数据都出来了,frame就没有用了
releaseFrame(frame);
}
releaseFrame(frame);
av_freep(&dst_data[0]);
isPlaying = false;
}
/**
* 根据timeDif,调整sleep时间
*
* @param timeDif 视频时钟 - 音频时钟 的时间差
* @param delay 视频当前帧应该delay的时间
*/
void VideoChannel::adjustSleepTime(double timeDif, double delay) const {
if (timeDif >= 0) {
// 如果视频超前超过1秒,视频增加一帧的播放时间,让音频可以追上来
if (timeDif > 1) {
// 延时时间翻倍
// 延时 ms --- av_usleep 入参单位是 微秒 us,
av_usleep(delay * 2 * 1000000);
} else {
delay = delay + timeDif;
delay = delay < 0 ? 0 : delay;
av_usleep(delay * 1000000);
};
}
// 视频落后
else {
// 如果落后超过1秒,就丢帧
if (timeDif < -1) {
// 丢帧处理 --- 丢下一帧
dropFrame(frame_queue);
} else {
delay = delay + timeDif;
delay = delay < 0 ? 0 : delay;
av_usleep(delay * 1000000);
}
}
}