zhuizhu преди 6 месеца
родител
ревизия
0aa748f3c2
променени са 4 файла, в които са добавени 131 реда и са изтрити 42 реда
  1. 2 1
      libs/AVPlayer/av_clock.h
  2. 84 30
      libs/AVPlayer/av_player.cpp
  3. 37 3
      libs/AVPlayer/av_player.h
  4. 8 8
      libs/AVPlayer/low_latency_config.h

+ 2 - 1
libs/AVPlayer/av_clock.h

@@ -111,7 +111,8 @@ private:
     {
         // 定期重置时间基准,防止长时间运行时的累积误差
         if (currentTimeUs - m_lastResetTimeUs > LowLatencyConfig::TIMER_RESET_INTERVAL_US) {
-            int64_t currentClockUs = getClockUs();
+            // 修复:避免在此处调用 getClockUs() 造成递归/重入
+            int64_t currentClockUs = m_driftUs + (currentTimeUs - m_baseTimeUs);
             m_baseTimeUs = currentTimeUs;
             m_driftUs = currentClockUs;
             m_lastResetTimeUs = currentTimeUs;

+ 84 - 30
libs/AVPlayer/av_player.cpp

@@ -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;
 }
 

+ 37 - 3
libs/AVPlayer/av_player.h

@@ -28,6 +28,8 @@ class AVPlayer : public QObject
 
 public:
     enum PlayState { AV_STOPPED, AV_PLAYING, AV_PAUSED };
+    // 新增:同步主时钟类型
+    enum SyncType { SYNC_AUDIO, SYNC_VIDEO, SYNC_EXTERNAL };
 
     AVPlayer();
     ~AVPlayer();
@@ -53,10 +55,13 @@ public:
         m_volume = (volumePer * SDL_MIX_MAXVOLUME / 100) % (SDL_MIX_MAXVOLUME + 1);
     }
 
-    // 修改:根据可用主时钟进行seek,使用微秒时间戳
+    // 新增:设置同步主时钟类型(默认外部时钟,适合直播/RTSP)
+    inline void setSyncType(SyncType type) { m_syncType = type; }
+
+    // 修改:根据选定主时钟进行seek,使用微秒时间戳
     inline void seekBy(int32_t time_s)
     {
-        int64_t baseUs = m_hasAudio ? m_audioClock.getClock() : m_videoClock.getClock();
+        int64_t baseUs = masterClockUs();
         int32_t baseSeconds = static_cast<int32_t>(baseUs / 1000000);
         seekTo(baseSeconds + time_s);
     }
@@ -67,7 +72,7 @@ public:
             return;
         if (time_s < 0)
             time_s = 0;
-        int64_t baseUs = m_hasAudio ? m_audioClock.getClock() : m_videoClock.getClock();
+        int64_t baseUs = masterClockUs();
         int32_t baseSeconds = static_cast<int32_t>(baseUs / 1000000);
         m_decoder->seekTo(time_s, time_s - baseSeconds);
     }
@@ -91,6 +96,20 @@ private:
     void displayImage(AVFrame* frame);
     void initAVClock();
 
+    // 新增:获取当前主时钟(微秒)
+    inline int64_t masterClockUs()
+    {
+        switch (m_syncType) {
+        case SYNC_AUDIO:
+            return m_audioClock.getClock();
+        case SYNC_VIDEO:
+            return m_videoClock.getClock();
+        case SYNC_EXTERNAL:
+        default:
+            return m_extClock.getClock();
+        }
+    }
+
 private:
     //解码器实例
     Decoder* m_decoder;
@@ -125,6 +144,13 @@ private:
     //视频播放时钟
     AVClock m_videoClock;
 
+    // 新增:外部时钟(以系统时钟为基准)
+    AVClock m_extClock;
+
+    // 新增:PTS基线(微秒),用于将首帧归零,避免绝对PTS导致的巨幅偏差
+    int64_t m_audioStartPtsUs = -1;
+    int64_t m_videoStartPtsUs = -1;
+
     int m_targetChannels;
     int m_targetFreq;
     int m_targetChannelLayout;
@@ -148,6 +174,11 @@ private:
     //记录暂停前的时间(微秒)
     int64_t m_pauseTimeUs;
 
+    //暂停时钟快照(微秒)
+    int64_t m_pauseAudClockUs = 0;
+    int64_t m_pauseVidClockUs = 0;
+    int64_t m_pauseExtClockUs = 0;
+
     //暂停标志
     std::atomic<int> m_pause;
 
@@ -173,6 +204,9 @@ private:
     int64_t m_frameTimerUs = 0;         // 高精度帧定时器(微秒)
     int m_performanceFrameCount = 0;    // 性能监控帧计数
     double m_lastDelayValue = 0.0;      // 上次延迟值,用于延迟累积检测
+
+    // 新增:同步主时钟类型(默认外部时钟,同步策略更接近ffplay -sync ext)
+    SyncType m_syncType = SYNC_EXTERNAL;
 };
 
 #endif

+ 8 - 8
libs/AVPlayer/low_latency_config.h

@@ -52,17 +52,17 @@ constexpr int64_t BALANCED_SYNC_THRESHOLD_MIN_US = 10000LL;     // 同步阈值
 constexpr int64_t BALANCED_SYNC_THRESHOLD_MAX_US = 40000LL;     // 同步阈值最大值(40ms = 40000微秒)
 constexpr int64_t BALANCED_SYNC_REJUDGE_THRESHOLD_US = 5000LL;  // 同步重判阈值(5ms = 5000微秒)
     
-    // 网络优化参数(更新为平衡配置
+    // 网络优化参数(更新为更激进的低延迟配置,匹配ffplay
     constexpr const char* RTSP_TRANSPORT = "tcp";
-    constexpr int PROBE_SIZE = 1048576;                // 1MB探测大小 - 增加以获得足够帧进行帧率计算
-    constexpr int ANALYZE_DURATION = 3000000;          // 3秒分析时长 - 增加以确保准确的流信息
-    constexpr int MAX_DELAY = 500000;                  // 500ms最大延迟
-    constexpr const char* FFLAGS = "nobuffer";         // 无缓冲模式
-    constexpr int BUFFER_SIZE = 131072;                // 128KB接收缓冲区 - 增加以配合更大的探测大小
+    constexpr int PROBE_SIZE = 32;                   // 极小探测大小,尽快开始解码
+    constexpr int ANALYZE_DURATION = 0;              // 关闭分析,直接解码
+    constexpr int MAX_DELAY = 500000;                // 500ms最大延迟
+    constexpr const char* FFLAGS = "nobuffer";       // 无缓冲模式
+    constexpr int BUFFER_SIZE = 131072;              // 接收缓冲区(保持)
     
     // 字符串版本的配置参数(用于av_dict_set)
-    constexpr const char* PROBE_SIZE_STR = "1048576";      // 1MB探测大小字符串版本
-    constexpr const char* ANALYZE_DURATION_STR = "3000000"; // 3秒分析时长字符串版本
+    constexpr const char* PROBE_SIZE_STR = "32";          // 极小探测大小
+    constexpr const char* ANALYZE_DURATION_STR = "0";      // 关闭分析
     constexpr const char* MAX_DELAY_STR = "500000";        // 500ms最大延迟字符串版本
     constexpr const char* BUFFER_SIZE_STR = "131072";      // 128KB接收缓冲区字符串版本