zhuizhu il y a 6 mois
Parent
commit
75148e7eab

+ 1 - 1
libs/Recorder/Recorder.cpp

@@ -165,7 +165,7 @@ void capture_screen()
                     }
                 }
 
-                _sleep(50);
+                // 移除固定睡眠以降低延迟,依靠事件/时间戳节奏
             }
 
             av_free_packet(packet);

+ 7 - 41
libs/Recorder/encoder_aac.cpp

@@ -381,9 +381,10 @@ void encoder_aac::encode_loop()
 
     while (_running) {
         std::unique_lock<std::mutex> lock(_mutex);
-        // 实时推流优化:减少等待时间,提高处理频率
-        while (!_cond_notify && _running)
-            _cond_var.wait_for(lock, std::chrono::milliseconds(50));  // 从300ms减少到50ms
+        // 改为基于条件的等待,避免固定50ms超时带来的延迟抖动
+        _cond_var.wait(lock, [this] { return _cond_notify || !_running; });
+        if (!_running)
+            break;
 
         while ((len = _ring_buffer->get(_buff, _buff_size, pcm_frame))) {
             // 验证获取的音频数据
@@ -419,47 +420,12 @@ void encoder_aac::encode_loop()
                 al_warn("Frame samples mismatch: frame->nb_samples=%d, encoder->frame_size=%d", 
                         _frame->nb_samples, _encoder_ctx->frame_size);
             }
-            
-            // 重新填充音频frame数据,确保数据正确
-            int ret = avcodec_fill_audio_frame(_frame, 
-                                               ffmpeg_get_frame_channels(_frame), 
-                                               (AVSampleFormat)_frame->format, 
-                                               _buff, 
-                                               len, 
-                                               0);
-            if (ret < 0) {
-                al_warn("Failed to fill audio frame: %d", ret);
-                continue;
-            }
-            
-            // 验证填充后的帧数据
-            if (!_frame->data[0]) {
-                al_warn("Frame data is null after filling");
-                continue;
-            }
-            
-            // 计算期望的数据大小并验证
-            int expectedSize = _frame->nb_samples * ffmpeg_get_frame_channels(_frame) * 
-                              av_get_bytes_per_sample((AVSampleFormat)_frame->format);
-            if (len != expectedSize) {
-                al_warn("Data size validation failed: actual=%d, expected=%d", len, expectedSize);
-            }
 
-            if ((error = encode(_frame, packet)) != AE_NO) {
-                al_error("AAC encoding failed: error=%d, frame_pts=%lld, frame_size=%d", 
-                        error, _frame->pts, _frame->nb_samples);
-                
-                // 尝试恢复:跳过当前帧,继续处理下一帧
-                if (error == AE_FFMPEG_ENCODE_FRAME_FAILED || error == AE_FFMPEG_READ_PACKET_FAILED) {
-                    al_warn("Attempting to recover from encoding error, skipping current frame");
-                    continue;
-                }
-                
-                // 严重错误,通知上层并退出
+            int error = encode(_frame, packet);
+            if (error != AE_NO) {
                 if (_on_error)
                     _on_error(error);
-
-                al_fatal("Critical AAC encoding error:%d, stopping encoder", error);
+                al_fatal("encode aac packet failed:%d", error);
                 break;
             }
         }

+ 7 - 0
libs/Recorder/encoder_aac.h

@@ -45,6 +45,13 @@ public:
 
     AVCodecID get_codec_id();
 
+    // Ring buffer stats helpers
+    inline size_t rb_dropped_frames() const { return _ring_buffer ? _ring_buffer->dropped_frames() : 0; }
+    inline size_t rb_pending_frames() const { return _ring_buffer ? _ring_buffer->get_pending_frames() : 0; }
+    inline size_t rb_max_frames() const { return _ring_buffer ? _ring_buffer->max_frames() : 0; }
+    inline void rb_set_max_frames(size_t max_frames) { if (_ring_buffer) _ring_buffer->set_max_frames(max_frames); }
+    inline void rb_reset_dropped() { if (_ring_buffer) _ring_buffer->reset_dropped_frames(); }
+
 private:
     int encode(AVFrame *frame, AVPacket *packet);
 

+ 7 - 0
libs/Recorder/encoder_video.h

@@ -45,6 +45,13 @@ public:
 
     virtual AVCodecID get_codec_id() = 0;
 
+    // Ring buffer stats/config helpers
+    inline size_t rb_dropped_frames() const { return _ring_buffer ? _ring_buffer->dropped_frames() : 0; }
+    inline size_t rb_pending_frames() const { return _ring_buffer ? _ring_buffer->get_pending_frames() : 0; }
+    inline size_t rb_max_frames() const { return _ring_buffer ? _ring_buffer->max_frames() : 0; }
+    inline void rb_set_max_frames(size_t max_frames) { if (_ring_buffer) _ring_buffer->set_max_frames(max_frames); }
+    inline void rb_reset_dropped() { if (_ring_buffer) _ring_buffer->reset_dropped_frames(); }
+
 protected:
     virtual void cleanup() = 0;
     virtual void encode_loop() = 0;

+ 8 - 4
libs/Recorder/encoder_video_nvenc.cpp

@@ -50,7 +50,7 @@ int encoder_video_nvenc::init(
 
         const char *rate_control = "cbr"; // cbr | cqp | vbr | lossless
         const char *profile = "baseline"; // baseline | main | high |high444p
-        const char *preset = "default";   // default | slow | medium | fast |
+        const char *preset = "ultrafast";   // default | slow | medium | fast |
                                           // hp | hq | bd | 11 | 11hq | 11hp | lossless | losslesshp
 
 #if 0 //USE_CBR
@@ -73,6 +73,9 @@ int encoder_video_nvenc::init(
 
         av_opt_set(_encoder_ctx->priv_data, "profile", profile, 0);
         av_opt_set(_encoder_ctx->priv_data, "preset", preset, 0);
++        // use NVENC low-latency tune and disable lookahead for minimum latency
++        av_opt_set(_encoder_ctx->priv_data, "tune", "ll", 0);
++        av_opt_set_int(_encoder_ctx->priv_data, "rc-lookahead", 0, 0);
 
         av_opt_set(_encoder_ctx->priv_data, "level", "auto", 0);
         av_opt_set_int(_encoder_ctx->priv_data, "2pass", false, 0);
@@ -188,9 +191,10 @@ void encoder_video_nvenc::encode_loop()
 
     while (_running) {
         std::unique_lock<std::mutex> lock(_mutex);
-        // 实时推流优化:减少等待时间,提高处理频率
-        while (!_cond_notify && _running)
-            _cond_var.wait_for(lock, std::chrono::milliseconds(50));  // 从300ms减少到50ms
+        // 改为基于条件的等待,避免固定50ms超时带来的延迟抖动
+        _cond_var.wait(lock, [this] { return _cond_notify || !_running; });
+        if (!_running)
+            break;
 
         while (_ring_buffer->get(_buff, _buff_size, yuv_frame)) {
             if (yuv_frame.pts != AV_NOPTS_VALUE) {

+ 4 - 3
libs/Recorder/encoder_video_x264.cpp

@@ -207,9 +207,10 @@ void encoder_video_x264::encode_loop()
 
     while (_running) {
         std::unique_lock<std::mutex> lock(_mutex);
-        // 实时推流优化:减少等待时间,提高处理频率
-        while (!_cond_notify && _running)
-            _cond_var.wait_for(lock, std::chrono::milliseconds(50));  // 从300ms减少到50ms
+        // 改为基于条件的等待,避免固定50ms超时带来的延迟抖动
+        _cond_var.wait(lock, [this] { return _cond_notify || !_running; });
+        if (!_running)
+            break;
 
         while (_ring_buffer->get(_buff, _buff_size, yuv_frame)) {
             // Normalize incoming frame timestamps to encoder time_base (1/fps)

+ 97 - 0
libs/Recorder/export.cpp

@@ -2,6 +2,8 @@
 
 #include "device_audios.h"
 #include "encoder_video_define.h"
+#include "encoder_video.h"
+#include "encoder_aac.h"
 
 #include "record_audio_factory.h"
 #include "record_desktop_factory.h"
@@ -55,6 +57,10 @@ public:
 
     void set_preview_enabled(bool enable);
 
+    // Expose underlying encoders for diagnostics
+    encoder_video* get_video_encoder();
+    encoder_aac*   get_audio_encoder();
+
 private:
     void on_preview_yuv(const uint8_t *data, int size, int width, int height, int type);
     void get_valid_out_resolution(int src_width, int src_height, int *out_width, int *out_height);
@@ -327,6 +333,26 @@ void recorder::set_preview_enabled(bool enable)
     _muxer->set_preview_enabled(enable);
 }
 
+encoder_video* recorder::get_video_encoder()
+{
+    lock_guard lock(_mutex);
+    if (!_inited || _muxer == nullptr)
+        return nullptr;
+    auto mf = dynamic_cast<muxer_ffmpeg*>(_muxer);
+    if (!mf) return nullptr;
+    return mf->get_video_encoder();
+}
+
+encoder_aac* recorder::get_audio_encoder()
+{
+    lock_guard lock(_mutex);
+    if (!_inited || _muxer == nullptr)
+        return nullptr;
+    auto mf = dynamic_cast<muxer_ffmpeg*>(_muxer);
+    if (!mf) return nullptr;
+    return mf->get_audio_encoder();
+}
+
 void recorder::on_preview_yuv(const uint8_t *data, int size, int width, int height, int type)
 {
     if (_callbacks.func_preview_yuv != NULL)
@@ -513,3 +539,74 @@ AMRECORDER_API void recorder_set_logpath(const char *path)
 {
     AMLog *log = AMLog::get(path);
 }
+
+// ================= Ring buffer diagnostics =================
+AMRECORDER_API uint64_t recorder_get_video_rb_dropped()
+{
+    auto enc = am::recorder::instance()->get_video_encoder();
+    if (!enc) return 0;
+    return enc->rb_dropped_frames();
+}
+
+AMRECORDER_API int recorder_get_video_rb_pending()
+{
+    auto enc = am::recorder::instance()->get_video_encoder();
+    if (!enc) return 0;
+    return enc->rb_pending_frames();
+}
+
+AMRECORDER_API int recorder_get_video_rb_max()
+{
+    auto enc = am::recorder::instance()->get_video_encoder();
+    if (!enc) return 0;
+    return enc->rb_max_frames();
+}
+
+AMRECORDER_API void recorder_set_video_rb_max(int max_frames)
+{
+    auto enc = am::recorder::instance()->get_video_encoder();
+    if (!enc) return;
+    enc->rb_set_max_frames(max_frames);
+}
+
+AMRECORDER_API void recorder_reset_video_rb_dropped()
+{
+    auto enc = am::recorder::instance()->get_video_encoder();
+    if (!enc) return;
+    enc->rb_reset_dropped();
+}
+
+AMRECORDER_API uint64_t recorder_get_audio_rb_dropped()
+{
+    auto enc = am::recorder::instance()->get_audio_encoder();
+    if (!enc) return 0;
+    return enc->rb_dropped_frames();
+}
+
+AMRECORDER_API int recorder_get_audio_rb_pending()
+{
+    auto enc = am::recorder::instance()->get_audio_encoder();
+    if (!enc) return 0;
+    return enc->rb_pending_frames();
+}
+
+AMRECORDER_API int recorder_get_audio_rb_max()
+{
+    auto enc = am::recorder::instance()->get_audio_encoder();
+    if (!enc) return 0;
+    return enc->rb_max_frames();
+}
+
+AMRECORDER_API void recorder_set_audio_rb_max(int max_frames)
+{
+    auto enc = am::recorder::instance()->get_audio_encoder();
+    if (!enc) return;
+    enc->rb_set_max_frames(max_frames);
+}
+
+AMRECORDER_API void recorder_reset_audio_rb_dropped()
+{
+    auto enc = am::recorder::instance()->get_audio_encoder();
+    if (!enc) return;
+    enc->rb_reset_dropped();
+}

+ 24 - 15
libs/Recorder/export.h

@@ -202,7 +202,7 @@ typedef struct {
 AMRECORDER_API const char * recorder_err2str(int error);
 
 /**
-* Initialize recorder with specified seetings、speaker、mic、encoder...
+* Initialize recorder with specified seetings锟斤拷speaker锟斤拷mic锟斤拷encoder...
 * @return 0 if succeed,error code otherwise
 */
 AMRECORDER_API int recorder_init(const AMRECORDER_SETTING &setting, const AMRECORDER_CALLBACK &callbacks);
@@ -258,23 +258,16 @@ AMRECORDER_API int recorder_get_cameras(AMRECORDER_DEVICE **devices);
 
 /**
 * Get valid encoders
-* @param[in] encoders a pointer to a encoder array,should call recorder_free_array to free memory
-* @return count of encoders
 */
 AMRECORDER_API int recorder_get_vencoders(AMRECORDER_ENCODERS **encoders);
 
 /**
-* Free memory allocate by recorder
-* @param[in] array_address the pointer of array buffer
+* Free array memory allocated by recorder APIs
 */
 AMRECORDER_API void recorder_free_array(void *array_address);
 
 /**
-* Recorder create a remux job
-* @param[in] src    source file path
-* @param[in] dst   0 for unremuxing,1 for remuxing
-* @param[in] func_progress   0 for succed,otherwhise error code
-* @param[in] func_state   0 for succed,otherwhise error code
+* Remux a file
 */
 AMRECORDER_API int recorder_remux(
 	const char *src, const char *dst,
@@ -282,15 +275,31 @@ AMRECORDER_API int recorder_remux(
 	AMRECORDER_FUNC_REMUX_STATE func_state);
 
 /**
-* Enable or disable preview include video and audio
-* @param[in] enable 1 for enable,0 for disable
+* Enable/Disable preview callback
 */
 AMRECORDER_API void recorder_set_preview_enabled(int enable);
 
-
 /**
-* Set log path
-* @param[in] log file path
+* Set log file path
 */
 AMRECORDER_API void recorder_set_logpath(const char* path);
+
+/**
+* Ring buffer diagnostics (video)
+*/
+AMRECORDER_API uint64_t recorder_get_video_rb_dropped();
+AMRECORDER_API int      recorder_get_video_rb_pending();
+AMRECORDER_API int      recorder_get_video_rb_max();
+AMRECORDER_API void     recorder_set_video_rb_max(int max_frames);
+AMRECORDER_API void     recorder_reset_video_rb_dropped();
+
+/**
+* Ring buffer diagnostics (audio)
+*/
+AMRECORDER_API uint64_t recorder_get_audio_rb_dropped();
+AMRECORDER_API int      recorder_get_audio_rb_pending();
+AMRECORDER_API int      recorder_get_audio_rb_max();
+AMRECORDER_API void     recorder_set_audio_rb_max(int max_frames);
+AMRECORDER_API void     recorder_reset_audio_rb_dropped();
+
 #endif

+ 4 - 3
libs/Recorder/filter_amix.cpp

@@ -271,14 +271,15 @@ void filter_amix::filter_loop()
     int ret = 0;
     while (_running) {
         std::unique_lock<std::mutex> lock(_mutex);
-        while (!_cond_notify && _running)
-            _cond_var.wait_for(lock, std::chrono::milliseconds(300));
+        // 改为基于条件的等待,避免固定300ms超时导致的缓冲延迟
+        _cond_var.wait(lock, [this] { return _cond_notify || !_running; });
+        if (!_running)
+            break;
 
         while (_running && _cond_notify) {
             ret = av_buffersink_get_frame(_ctx_out.ctx, frame);
             if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                 break;
-                ;
             }
 
             if (ret < 0) {

+ 4 - 3
libs/Recorder/filter_aresample.cpp

@@ -226,14 +226,15 @@ void filter_aresample::filter_loop()
     int ret = 0;
     while (_running) {
         std::unique_lock<std::mutex> lock(_mutex);
-        while (!_cond_notify && _running)
-            _cond_var.wait_for(lock, std::chrono::milliseconds(300));
+        // 改为基于条件的等待,避免固定300ms超时导致的缓冲延迟
+        _cond_var.wait(lock, [this] { return _cond_notify || !_running; });
+        if (!_running)
+            break;
 
         while (_running && _cond_notify) {
             ret = av_buffersink_get_frame(_ctx_out.ctx, frame);
             if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                 break;
-                ;
             }
 
             if (ret < 0) {

+ 11 - 0
libs/Recorder/muxer_ffmpeg.cpp

@@ -196,6 +196,17 @@ int muxer_ffmpeg::resume()
     return 0;
 }
 
+// Exposed getters for encoder instances
+encoder_video* muxer_ffmpeg::get_video_encoder() const
+{
+    return _v_stream ? _v_stream->v_enc : nullptr;
+}
+
+encoder_aac* muxer_ffmpeg::get_audio_encoder() const
+{
+    return _a_stream ? _a_stream->a_enc : nullptr;
+}
+
 void muxer_ffmpeg::on_desktop_data(AVFrame *frame)
 {
     if (_running == false || _paused == true || !_v_stream || !_v_stream->v_enc

+ 8 - 0
libs/Recorder/muxer_ffmpeg.h

@@ -12,6 +12,10 @@
 #include "headers_ffmpeg.h"
 
 namespace am {
+// Forward declarations for encoder classes to expose getters without including headers here
+class encoder_video;
+class encoder_aac;
+
 class muxer_ffmpeg : public muxer_file
 {
 public:
@@ -30,6 +34,10 @@ public:
     int pause();
     int resume();
 
+    // Expose encoder instances for diagnostics/metrics
+    encoder_video* get_video_encoder() const;
+    encoder_aac* get_audio_encoder() const;
+
 private:
     void on_desktop_data(AVFrame *frame);
 

+ 32 - 13
libs/Recorder/record_desktop_duplication.cpp

@@ -485,8 +485,13 @@ int record_desktop_duplication::get_desktop_image(DXGI_OUTDUPL_FRAME_INFO *frame
 {
     IDXGIResource *dxgi_res = nullptr;
 
-    // Get new frame
-    HRESULT hr = _duplication->AcquireNextFrame(500, frame_info, &dxgi_res);
+    // Get new frame (adaptive low-latency timeout)
+    int timeout_ms = 10;
+    if (_fps > 0) {
+        timeout_ms = 1000 / _fps / 2;
+        if (timeout_ms < 1) timeout_ms = 1;
+    }
+    HRESULT hr = _duplication->AcquireNextFrame(timeout_ms, frame_info, &dxgi_res);
 
     // Timeout will return when desktop has no chane
     if (hr == DXGI_ERROR_WAIT_TIMEOUT)
@@ -813,24 +818,38 @@ void record_desktop_duplication::record_func()
 		}
 #endif
 
-    //Should init after desktop attatched
-    //error = init_duplication();
-    //if (error != AE_NO) {
-    //	al_fatal("duplication initialize failed %s,last error :%s", err2str(error),
-    // system_error::error2str(GetLastError()).c_str());
-    //	if (_on_error) _on_error(error);
-    //	return;
-    //}
-
     DXGI_OUTDUPL_FRAME_INFO frame_info;
     while (_running) {
         //Timeout is no new picture,no need to update
-        if ((error = get_desktop_image(&frame_info)) == AE_TIMEOUT)
+        if ((error = get_desktop_image(&frame_info)) == AE_TIMEOUT) {
+            // Push duplicate frame to keep steady cadence and avoid buffer growth
+            frame->pts = av_gettime_relative();
+            frame->pkt_dts = frame->pts;
+            frame->width = _width;
+            frame->height = _height;
+            frame->format = AV_PIX_FMT_BGRA;
+            frame->pict_type = AV_PICTURE_TYPE_NONE;
+            frame->pkt_size = _width * _height * 4;
+
+            av_image_fill_arrays(frame->data,
+                                 frame->linesize,
+                                 _buffer,
+                                 AV_PIX_FMT_BGRA,
+                                 _width,
+                                 _height,
+                                 1);
+
+            if (_on_data)
+                _on_data(frame);
+
+            do_sleep(dur, pre_pts, frame->pts);
+            pre_pts = frame->pts;
             continue;
+        }
 
         if (error != AE_NO) {
             while (_running) {
-                Sleep(300);
+                Sleep(50);
                 clean_duplication();
                 if ((error = init_duplication()) != AE_NO) {
                     if (_on_error)

+ 24 - 7
libs/Recorder/ring_buffer.h

@@ -6,6 +6,7 @@
 
 #include <mutex>
 #include <queue>
+#include <atomic>
 
 #include "error_define.h"
 #include "log_helper.h"
@@ -22,10 +23,12 @@ template<typename T>
 class ring_buffer
 {
 public:
-    ring_buffer(unsigned int size = 1920 * 1080 * 4 * 2)  // 从10帧减少到2帧,减少积压
+    ring_buffer(unsigned int size = 1920 * 1080 * 4 * 2, size_t max_frames = 3)  // 从10帧减少到2帧,减少积压
     {
         _size = size;
         _head = _tail = 0;
+        _max_frames = max_frames;
+        _dropped_frames.store(0, std::memory_order_relaxed);
 
         _buf = new uint8_t[size];
     }
@@ -40,15 +43,15 @@ public:
         std::lock_guard<std::mutex> locker(_lock);
 
         // 实时推流优化:当缓冲区积压超过阈值时,丢弃最旧的帧
-        const size_t MAX_FRAMES = 3;  // 最大允许3帧积压
-        while (_frames.size() >= MAX_FRAMES) {
+        while (_frames.size() >= _max_frames) {
             // 丢弃最旧的帧
             if (!_frames.empty()) {
                 ring_frame<T> old_frame = _frames.front();
                 _frames.pop();
-                
+
                 // 更新tail指针,跳过被丢弃的数据
                 _tail = (_tail + old_frame.len) % _size;
+                _dropped_frames.fetch_add(1, std::memory_order_relaxed);
             }
         }
 
@@ -126,17 +129,31 @@ public:
     bool is_buffer_full(float threshold = 0.8f) const 
     {
         std::lock_guard<std::mutex> locker(_lock);
-        const size_t MAX_FRAMES = 3;
-        return _frames.size() >= (MAX_FRAMES * threshold);
+        return _frames.size() >= (static_cast<size_t>(_max_frames * threshold));
+    }
+
+    // 配置与统计接口
+    void set_max_frames(size_t max_frames)
+    {
+        std::lock_guard<std::mutex> locker(_lock);
+        _max_frames = max_frames;
     }
 
+    size_t max_frames() const { return _max_frames; }
+
+    size_t dropped_frames() const { return _dropped_frames.load(std::memory_order_relaxed); }
+
+    void reset_dropped_frames() { _dropped_frames.store(0, std::memory_order_relaxed); }
+
 private:
     std::queue<ring_frame<T>> _frames;
     unsigned int _size, _head, _tail;
 
     uint8_t *_buf;
 
-    std::mutex _lock;
+    mutable std::mutex _lock;
+    std::atomic<size_t> _dropped_frames{0};
+    size_t _max_frames{3};
 };
 } // namespace am
 #endif