用ffmpeg來做音視訊同步,個人認為這個是ffmpeg基礎處理中最難的一個,無數人就卡在這裡,怎麼也不準,本人也是嘗試過網上各種demo,基本上都是渣渣,要麼僅僅支援極其少量的視訊檔比如收到的封包是一幀視訊一幀音訊的,要麼根本沒法同步歪七八糟的,要麼進度跳過去直接蹦蹦蹦崩潰的,其實最完美的音視訊同步處理demo就是ffplay,我親測過幾十種各種各樣的音視訊本地檔案,數十種視訊流檔案,都是非常完美,當然啦這是親生的啦,不完美還玩個屁。
如果僅僅是播放視訊流(不帶音訊流),可能不需要音視訊同步,所以最開始只做rtsp視訊流播放的時候根本沒有考慮同步的問題,因為沒遇到也不需要,等到後期發現各種rtmp、http、m3u8這種視訊流的時候,問題大了去了,他是hls格式的視訊流檔案一次性過來的,一個個小視訊檔過來的,如果沒有同步的話,意味著突然之間刷刷刷的圖片過去很多,下一次來的又是刷刷的,這就需要自己計算同步了,上次接收到的封包放入佇列,到了需要顯示的時候就顯示。
常用的音視訊同步方法:
void FFmpegSync::run()
{
reset();
while (!stopped) {
//暫停狀態或者佇列中沒有幀則不處理
if (!thread->isPause && packets.count() > 0) {
mutex.lock();
AVPacket *packet = packets.first();
mutex.unlock();
//h264的裸流檔案同步有問題,獲取不到pts和dts,暫時用最蠢的辦法延時解決
if (thread->formatName == "h264") {
int sleepTime = (1000 / thread->videoFps) - 5;
msleep(sleepTime);
}
//計算當前幀顯示時間 外部時鐘同步
ptsTime = getPtsTime(thread->formatCtx, packet);
if (!this->checkPtsTime()) {
msleep(1);
continue;
}
//顯示當前的播放進度
checkShowTime();
//0-表示音訊 1-表示視訊
if (type == 0) {
thread->decodeAudio(packet);
} else if (type == 1) {
thread->decodeVideo(packet);
}
//釋放資源並移除
thread->free(packet);
mutex.lock();
packets.removeFirst();
mutex.unlock();
}
msleep(1);
}
clear();
stopped = false;
}
bool FFmpegSync::checkPtsTime()
{
bool ok = false;
if (ptsTime > 0) {
if (ptsTime > offsetTime + 100000) {
bufferTime = ptsTime - offsetTime + 100000;
}
int offset = (type == 0 ? 1000 : 5000);
offsetTime = av_gettime() - startTime + bufferTime;
if ((offsetTime <= ptsTime && ptsTime - offsetTime <= offset) || (offsetTime > ptsTime)) {
ok = true;
}
} else {
ok = true;
}
return ok;
}