|
|
@@ -69,6 +69,12 @@ int AVPlayer::play(const QString& url)
|
|
|
m_pause = 0;
|
|
|
m_clockInitFlag = -1;
|
|
|
|
|
|
+ // 播放开始前重置高精度时间基准与PTS归一化基线
|
|
|
+ m_baseTimeUs = av_gettime_relative();
|
|
|
+ m_frameTimerUs = 0;
|
|
|
+ m_audioStartPtsUs = -1;
|
|
|
+ m_videoStartPtsUs = -1;
|
|
|
+
|
|
|
// 判断是否存在音/视频流
|
|
|
m_audioIndex = m_decoder->audioIndex();
|
|
|
m_videoIndex = m_decoder->videoIndex();
|
|
|
@@ -212,11 +218,14 @@ void fillAStreamCallback(void* userdata, uint8_t* stream, int len)
|
|
|
is->m_audioBufIndex += len1;
|
|
|
stream += len1;
|
|
|
}
|
|
|
- //记录音频时钟,转换为微秒时间戳
|
|
|
+ //记录音频时钟,转换为微秒时间戳并做归一化(首帧为0)
|
|
|
int64_t audioPtsUs = static_cast<int64_t>(audioPts * 1000000.0);
|
|
|
- is->m_audioClock.setClock(audioPtsUs);
|
|
|
- //发送时间戳变化信号,因为进度以整数秒单位变化展示,
|
|
|
- //所以大于一秒才发送,避免过于频繁的信号槽通信消耗性能
|
|
|
+ if (is->m_audioStartPtsUs < 0) {
|
|
|
+ is->m_audioStartPtsUs = audioPtsUs;
|
|
|
+ }
|
|
|
+ int64_t normAudioPtsUs = audioPtsUs - is->m_audioStartPtsUs;
|
|
|
+ is->m_audioClock.setClock(normAudioPtsUs);
|
|
|
+ //发送时间戳变化信号(秒)
|
|
|
uint32_t _pts = (uint32_t) audioPts;
|
|
|
if (is->m_lastAudPts != _pts) {
|
|
|
emit is->AVPtsChanged(_pts);
|
|
|
@@ -317,18 +326,25 @@ void AVPlayer::pause(bool isPause)
|
|
|
if (isPause) {
|
|
|
if (SDL_GetAudioStatus() == SDL_AUDIO_PLAYING) {
|
|
|
SDL_PauseAudio(1);
|
|
|
- // 优化:直接使用高精度时间戳,避免除法运算
|
|
|
+ // 记录暂停开始时间与各时钟快照
|
|
|
int64_t pauseTimeUs = av_gettime_relative();
|
|
|
m_pauseTimeUs = pauseTimeUs - (m_baseTimeUs ? m_baseTimeUs : pauseTimeUs);
|
|
|
+ m_pauseAudClockUs = m_audioClock.getClock();
|
|
|
+ m_pauseVidClockUs = m_videoClock.getClock();
|
|
|
+ m_pauseExtClockUs = m_extClock.getClock();
|
|
|
m_pause = 1;
|
|
|
}
|
|
|
} else {
|
|
|
if (SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) {
|
|
|
SDL_PauseAudio(0);
|
|
|
- // 优化:直接使用高精度时间戳计算暂停时长,避免除法运算
|
|
|
+ // 恢复:排除暂停时长,并将各时钟锚定到暂停前快照,避免进度跳变
|
|
|
int64_t resumeTimeUs = av_gettime_relative();
|
|
|
int64_t resumeElapsedUs = resumeTimeUs - (m_baseTimeUs ? m_baseTimeUs : resumeTimeUs);
|
|
|
m_frameTimerUs += resumeElapsedUs - m_pauseTimeUs;
|
|
|
+ // 重新锚定各时钟(微秒),排除暂停期间的墙钟时间
|
|
|
+ m_audioClock.setClock(m_pauseAudClockUs);
|
|
|
+ m_videoClock.setClock(m_pauseVidClockUs);
|
|
|
+ m_extClock.setClock(m_pauseExtClockUs);
|
|
|
m_pause = 0;
|
|
|
}
|
|
|
}
|
|
|
@@ -336,17 +352,21 @@ void AVPlayer::pause(bool isPause)
|
|
|
// 仅视频:通过标志控制回放线程
|
|
|
if (isPause) {
|
|
|
if (!m_pause) {
|
|
|
- // 优化:直接使用高精度时间戳,避免除法运算
|
|
|
int64_t pauseTimeUs = av_gettime_relative();
|
|
|
m_pauseTimeUs = pauseTimeUs - (m_baseTimeUs ? m_baseTimeUs : pauseTimeUs);
|
|
|
+ // 记录暂停快照
|
|
|
+ m_pauseVidClockUs = m_videoClock.getClock();
|
|
|
+ m_pauseExtClockUs = m_extClock.getClock();
|
|
|
m_pause = 1;
|
|
|
}
|
|
|
} else {
|
|
|
if (m_pause) {
|
|
|
- // 优化:直接使用高精度时间戳计算暂停时长,避免除法运算
|
|
|
int64_t resumeTimeUs = av_gettime_relative();
|
|
|
int64_t resumeElapsedUs = resumeTimeUs - (m_baseTimeUs ? m_baseTimeUs : resumeTimeUs);
|
|
|
m_frameTimerUs += resumeElapsedUs - m_pauseTimeUs;
|
|
|
+ // 重新锚定视频与外部时钟
|
|
|
+ m_videoClock.setClock(m_pauseVidClockUs);
|
|
|
+ m_extClock.setClock(m_pauseExtClockUs);
|
|
|
m_pause = 0;
|
|
|
}
|
|
|
}
|
|
|
@@ -415,6 +435,11 @@ void AVPlayer::initAVClock()
|
|
|
{
|
|
|
m_audioClock.setClock(0);
|
|
|
m_videoClock.setClock(0);
|
|
|
+ // 新增:初始化外部时钟为0,对齐ffplay -sync ext 行为
|
|
|
+ m_extClock.setClock(0);
|
|
|
+ // 新增:重置PTS基线,确保每次开始都从0归一化
|
|
|
+ m_audioStartPtsUs = -1;
|
|
|
+ m_videoStartPtsUs = -1;
|
|
|
m_clockInitFlag = 1;
|
|
|
}
|
|
|
|
|
|
@@ -482,10 +507,14 @@ void AVPlayer::displayImage(AVFrame* frame)
|
|
|
frame->linesize));
|
|
|
}
|
|
|
|
|
|
- //记录视频时钟,转换为微秒时间戳
|
|
|
+ //记录视频时钟,转换为微秒时间戳并做归一化(首帧为0)
|
|
|
double videoPtsSeconds = frame->pts * av_q2d(m_fmtCtx->streams[m_videoIndex]->time_base);
|
|
|
int64_t videoPtsUs = static_cast<int64_t>(videoPtsSeconds * 1000000.0);
|
|
|
- m_videoClock.setClock(videoPtsUs);
|
|
|
+ if (m_videoStartPtsUs < 0) {
|
|
|
+ m_videoStartPtsUs = videoPtsUs;
|
|
|
+ }
|
|
|
+ int64_t normVideoPtsUs = videoPtsUs - m_videoStartPtsUs;
|
|
|
+ m_videoClock.setClock(normVideoPtsUs);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -594,7 +623,7 @@ void AVPlayer::videoCallback(std::shared_ptr<void> par)
|
|
|
|
|
|
// 无音频时,基于视频时钟更新对外进度
|
|
|
if (!m_hasAudio) {
|
|
|
- uint32_t _pts = (uint32_t) m_videoClock.getClock();
|
|
|
+ uint32_t _pts = (uint32_t) (m_videoClock.getClock() / 1000000);
|
|
|
if (m_lastAudPts != _pts) {
|
|
|
emit AVPtsChanged(_pts);
|
|
|
m_lastAudPts = _pts;
|
|
|
@@ -615,29 +644,54 @@ void AVPlayer::videoCallback(std::shared_ptr<void> par)
|
|
|
|
|
|
double AVPlayer::computeTargetDelay(double delay)
|
|
|
{
|
|
|
- // 无音频流时,不进行音视频同步,直接按视频节奏播放
|
|
|
- if (!m_hasAudio)
|
|
|
+ // 当选择视频为主时钟时,直接按视频节奏播放(不做同步调整)
|
|
|
+ if (m_syncType == SYNC_VIDEO) {
|
|
|
return delay;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一使用微秒单位进行计算
|
|
|
+ const int64_t videoUs = m_videoClock.getClock();
|
|
|
|
|
|
- //视频当前显示帧与当前播放音频帧时间戳差值
|
|
|
- double diff = m_videoClock.getClock() - m_audioClock.getClock();
|
|
|
-
|
|
|
- //计算同步阈值
|
|
|
- double sync = FFMAX(LowLatencyConfig::BALANCED_SYNC_THRESHOLD_MIN, FFMIN(LowLatencyConfig::BALANCED_SYNC_THRESHOLD_MAX, delay));
|
|
|
-
|
|
|
- //不同步时间超过阈值直接放弃同步
|
|
|
- if (!isnan(diff) && fabs(diff) < LowLatencyConfig::BALANCED_NOSYNC_THRESHOLD) {
|
|
|
- if (diff
|
|
|
- <= -sync) { //视频已落后音频大于一帧的显示时长,delay值应为0,立马将当前帧显示追赶音频
|
|
|
- delay = FFMAX(0, diff + delay);
|
|
|
- } else if (diff >= sync
|
|
|
- && delay
|
|
|
- > LowLatencyConfig::BALANCED_SYNC_FRAMEDUP_THRESHOLD) { //视频超前音频大于一个视频帧的时间,延时一个视频帧时间+已超时时间,下次判定将至少被延时到下个将要显示的视频帧pts
|
|
|
- delay = diff + delay;
|
|
|
- } else if (diff >= sync) { //高帧率视频直接延时两个视频帧时间;;;;
|
|
|
- delay = 2 * delay;
|
|
|
+ // 选择主时钟:音频/外部(二选一,若缺失则回退)
|
|
|
+ int64_t masterUs = 0;
|
|
|
+ if (m_syncType == SYNC_AUDIO) {
|
|
|
+ if (m_hasAudio) {
|
|
|
+ masterUs = m_audioClock.getClock();
|
|
|
+ } else {
|
|
|
+ // 无音频时回退到外部时钟
|
|
|
+ masterUs = m_extClock.getClock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // SYNC_EXTERNAL 或其他情况,统一用外部时钟
|
|
|
+ masterUs = m_extClock.getClock();
|
|
|
+ }
|
|
|
+
|
|
|
+ const int64_t diffUs = videoUs - masterUs; // 正值:视频超前主时钟;负值:视频落后
|
|
|
+
|
|
|
+ // 将单帧延时(秒)转为微秒,并在最小/最大阈值之间夹紧
|
|
|
+ int64_t delayUs = static_cast<int64_t>(delay * 1000000.0);
|
|
|
+ int64_t syncUs = FFMAX(LowLatencyConfig::BALANCED_SYNC_THRESHOLD_MIN_US,
|
|
|
+ FFMIN(LowLatencyConfig::BALANCED_SYNC_THRESHOLD_MAX_US, delayUs));
|
|
|
+
|
|
|
+ // 放弃同步的阈值(2s)改为微秒单位进行判断
|
|
|
+ const int64_t noSyncUs = static_cast<int64_t>(LowLatencyConfig::BALANCED_NOSYNC_THRESHOLD * 1000000.0);
|
|
|
+ const int64_t frameDupUs = static_cast<int64_t>(LowLatencyConfig::BALANCED_SYNC_FRAMEDUP_THRESHOLD * 1000000.0);
|
|
|
+
|
|
|
+ if (qAbs(diffUs) < noSyncUs) {
|
|
|
+ if (diffUs <= -syncUs) {
|
|
|
+ // 视频落后主时钟:尽快追赶
|
|
|
+ const int64_t newDelayUs = FFMAX(0LL, diffUs + delayUs);
|
|
|
+ delay = static_cast<double>(newDelayUs) / 1000000.0;
|
|
|
+ } else if (diffUs >= syncUs && delayUs > frameDupUs) {
|
|
|
+ // 视频超前主时钟且帧时长较长:延时一个帧时长 + 超前量
|
|
|
+ const int64_t newDelayUs = diffUs + delayUs;
|
|
|
+ delay = static_cast<double>(newDelayUs) / 1000000.0;
|
|
|
+ } else if (diffUs >= syncUs) {
|
|
|
+ // 高帧率场景:延时两个视频帧时长
|
|
|
+ delay = 2.0 * delay;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
return delay;
|
|
|
}
|
|
|
|