From 2d3eb8b4685c8fe2847e5037176437c37bae42ed Mon Sep 17 00:00:00 2001 From: Niellune Date: Mon, 23 Mar 2026 13:17:30 +0200 Subject: [PATCH] Single and Double buffered drawing --- settings.txt | 3 + src/application.cpp | 12 ++- src/application.h | 1 + src/decoder.cpp | 29 ++++-- src/decoder.h | 10 +- src/settings.h | 1 + src/struct/video_buffer.h | 186 +++++++++++++++++++++++++++++--------- 7 files changed, 181 insertions(+), 61 deletions(-) diff --git a/settings.txt b/settings.txt index 04f5f75..7f3c851 100644 --- a/settings.txt +++ b/settings.txt @@ -108,6 +108,9 @@ # This is happening or Raspberry Pi Zero 2W. Disable this to use SW decoding for that case. #hw-decode = true +# Use double buffering for drawing video. May smooth rendering but increase CPU usage +#double-buffered = true; + # Request extra frames onevery key press. Usefull if you do not see last updates after key press # Enable for RPI hardware decoding and other systems where hardware decoder tends to buffer frames # 0 - disabled, enter number of frames to request (find minimal value that get all screen updates) diff --git a/src/application.cpp b/src/application.cpp index 62467d6..90d4cad 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -120,7 +120,10 @@ void Application::start(const char *title) } log_v("Starting"); - loop(); + if (Settings::doubleBuffer) + loop(); + else + loop(); log_v("Stopped"); } @@ -275,6 +278,7 @@ bool Application::processFrameEvents(AtomicQueue &queue, Renderer &rend return result; } +template void Application::loop() { // Prepare home screen @@ -294,12 +298,12 @@ void Application::loop() SDL_ShowWindow(_window); interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN); - VideoBuffer videoBuffer; + Buffer videoBuffer; Connector protocol; - Decoder decoder; + Decoder decoder; PcmAudio audioMain("main"), audioAux("aux"); - decoder.start(&protocol.videoStream, &videoBuffer, AV_CODEC_ID_H264); + decoder.start(&protocol.videoStream, videoBuffer, AV_CODEC_ID_H264); audioMain.start(&protocol.audioStreamMain); audioAux.start(&protocol.audioStreamAux, &audioMain); protocol.start(&_state.deviceStatus); diff --git a/src/application.h b/src/application.h index 60a819b..c774f8a 100644 --- a/src/application.h +++ b/src/application.h @@ -36,6 +36,7 @@ private: bool processSystemEvent(const SDL_Event &e); bool processFrameEvents(AtomicQueue &queue, Renderer &renderer); + template void loop(); SDL_Window *_window; diff --git a/src/decoder.cpp b/src/decoder.cpp index 61bcd8e..009fd8b 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -5,28 +5,32 @@ #include "common/functions.h" #include "settings.h" -Decoder::Decoder() +template +Decoder::Decoder() : _context(nullptr) { } -Decoder::~Decoder() +template +Decoder::~Decoder() { stop(); } -void Decoder::start(AtomicQueue *data, VideoBuffer *vb, AVCodecID codecId) +template +void Decoder::start(AtomicQueue *data, Buffer &vb, AVCodecID codecId) { if (_active) stop(); - _vb = vb; + _vb = &vb; _data = data; _codecId = codecId; _active = true; _thread = std::thread(&Decoder::runner, this); } -void Decoder::stop() +template +void Decoder::stop() { if (!_active) return; @@ -36,14 +40,16 @@ void Decoder::stop() _thread.join(); } -void Decoder::flush() +template +void Decoder::flush() { if (_context) avcodec_flush_buffers(_context); } // Initialize and select the best decoder (try HW first, then SW) -AVCodecContext *Decoder::load_codec(AVCodecID codec_id) +template +AVCodecContext *Decoder::load_codec(AVCodecID codec_id) { void *iter = nullptr; const AVCodec *codec = nullptr; @@ -116,7 +122,8 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) return result; } -void Decoder::runner() +template +void Decoder::runner() { // Set thread name setThreadName("video-decoder"); @@ -159,7 +166,8 @@ void Decoder::runner() _context = nullptr; } -void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame) +template +void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame) { uint32_t counter = 0; @@ -231,3 +239,6 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack } } } + +template class Decoder; +template class Decoder; diff --git a/src/decoder.h b/src/decoder.h index 780468e..bdbeb64 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -14,6 +14,7 @@ extern "C" #include "struct/atomic_queue.h" #include "protocol/message.h" +template class Decoder { @@ -21,7 +22,7 @@ public: Decoder(); ~Decoder(); - void start(AtomicQueue *data, VideoBuffer *vb, AVCodecID codecId); + void start(AtomicQueue *data, Buffer &vb, AVCodecID codecId); void stop(); void flush(); @@ -31,13 +32,16 @@ private: static AVCodecContext *load_codec(AVCodecID codec_id); std::thread _thread; - AVCodecContext* _context; + AVCodecContext* _context; AVCodecID _codecId; std::atomic _active = false; AtomicQueue *_data = nullptr; - VideoBuffer *_vb = nullptr; + Buffer *_vb = nullptr; }; +extern template class Decoder; +extern template class Decoder; + #endif /* SRC_DECODER */ diff --git a/src/settings.h b/src/settings.h index f18f8b4..d3bc43c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -39,6 +39,7 @@ public: static inline Setting fontSize{"font-size", 30}; static inline Setting vsync{"vsync", false}; static inline Setting hwDecode{"hw-decode", true}; + static inline Setting doubleBuffer{"double-buffered", true}; static inline Setting forceRedraw{"force-redraw", 0}; static inline Setting eventsSkip{"draw-skip-events", 0}; static inline Setting aspectCorrection{"aspect-correction", 1}; diff --git a/src/struct/video_buffer.h b/src/struct/video_buffer.h index 52faabc..b9194c2 100644 --- a/src/struct/video_buffer.h +++ b/src/struct/video_buffer.h @@ -1,5 +1,5 @@ -#ifndef SRC_STRUCT_VIDER_BUFFER2 -#define SRC_STRUCT_VIDER_BUFFER2 +#ifndef SRC_STRUCT_VIDEO_BUFFER +#define SRC_STRUCT_VIDEO_BUFFER extern "C" { @@ -7,20 +7,18 @@ extern "C" } #include +#include #include -#define BUFFER_VIDEO_FRAMES 4 - class VideoBuffer { public: VideoBuffer() { - _writing.store(0); - _oldest.store(-1); + _writing = 0; _reading.store(-1); _latest.store(-1); - for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i) + for (uint8_t i = 0; i < 3; ++i) { _ids[i] = 0; _frames[i] = av_frame_alloc(); @@ -31,9 +29,9 @@ public: } } - ~VideoBuffer() + ~VideoBuffer() noexcept { - for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i) + for (uint8_t i = 0; i < 3; ++i) { if (_frames[i]) { @@ -43,58 +41,156 @@ public: } } - uint32_t latestId() + uint32_t latestId() const noexcept { - int index = _latest.load(); - if (index < 0) + const int8_t index = _latest.load(std::memory_order_acquire); + if (index == -1) return 0; - return _ids[index]; + return _ids[static_cast(index)]; } - bool latest(AVFrame **frame, uint32_t *id) + bool latest(AVFrame **frame, uint32_t *id) noexcept { - _reading.store(_oldest.load()); - int index = _reading.load(); - if (index < 0) - { - _reading.store(_latest.load()); - index = _reading.load(); - if (index < 0) - return false; - } - *frame = _frames[index]; - *id = _ids[index]; + const int8_t index = _latest.load(std::memory_order_acquire); + _reading.store(index, std::memory_order_seq_cst); + if (index == -1) + return false; + const uint8_t slot = static_cast(index); + *frame = _frames[slot]; + *id = _ids[slot]; return true; } - void consume() + void consume() noexcept { - if(_oldest.load() == _reading.load()) - _oldest.store(-1); - _reading.store(-1); + _reading.store(-1, std::memory_order_seq_cst); } - AVFrame *write(uint32_t id) + AVFrame *write(uint32_t id) noexcept { - int index = _writing.load(); - while (index == _reading.load() || index == _latest.load() || index == _oldest.load()) + int8_t index = _writing; + while (index == _reading.load(std::memory_order_seq_cst) || + index == _latest.load(std::memory_order_relaxed)) { - index = (index + 1) % BUFFER_VIDEO_FRAMES; + ++index; + if (index == 3) + index = 0; } - _writing.store(index); - _ids[index] = id; - return _frames[index]; + _writing = index; + const uint8_t slot = static_cast(index); + _ids[slot] = id; + return _frames[slot]; } - void commit() + void commit() noexcept { - _oldest.store(_latest.load()); - _latest.store(_writing.load()); + _latest.store(_writing, std::memory_order_release); } - void reset() + void reset() noexcept + { + _reading.store(-1); + _latest.store(-1); + } + +private: + std::atomic _latest; + std::atomic _reading; + int8_t _writing; + AVFrame *_frames[3]; + uint32_t _ids[3]; +}; + +class VideoBufferDouble +{ +public: + VideoBufferDouble() + { + _writing = 0; + _oldest.store(-1); + _reading.store(-1); + _latest.store(-1); + for (uint8_t i = 0; i < 4; ++i) + { + _ids[i] = 0; + _frames[i] = av_frame_alloc(); + if (!_frames[i]) + { + throw std::runtime_error("Failed to allocate AVFrame"); + } + } + } + + ~VideoBufferDouble() noexcept + { + for (uint8_t i = 0; i < 4; ++i) + { + if (_frames[i]) + { + av_frame_free(&_frames[i]); + _frames[i] = nullptr; + } + } + } + + uint32_t latestId() const noexcept + { + const int8_t index = _latest.load(std::memory_order_acquire); + if (index == -1) + return 0; + return _ids[static_cast(index)]; + } + + bool latest(AVFrame **frame, uint32_t *id) noexcept + { + int8_t index = _oldest.load(std::memory_order_acquire); + _reading.store(index, std::memory_order_seq_cst); + if (index == -1) + { + index = _latest.load(std::memory_order_acquire); + _reading.store(index, std::memory_order_seq_cst); + if (index == -1) + return false; + } + const uint8_t slot = static_cast(index); + *frame = _frames[slot]; + *id = _ids[slot]; + return true; + } + + void consume() noexcept + { + const int8_t reading = _reading.load(std::memory_order_seq_cst); + if (_oldest.load(std::memory_order_relaxed) == reading) + _oldest.store(-1, std::memory_order_relaxed); + _reading.store(-1, std::memory_order_seq_cst); + } + + AVFrame *write(uint32_t id) noexcept + { + int8_t index = _writing; + while (index == _reading.load(std::memory_order_seq_cst) || + index == _latest.load(std::memory_order_relaxed) || + index == _oldest.load(std::memory_order_relaxed)) + { + ++index; + if (index == 4) + index = 0; + } + _writing = index; + const uint8_t slot = static_cast(index); + _ids[slot] = id; + return _frames[slot]; + } + + void commit() noexcept + { + _oldest.store(_latest.load(std::memory_order_relaxed), std::memory_order_release); + _latest.store(_writing, std::memory_order_release); + } + + void reset() noexcept { - _writing.store(0); _oldest.store(-1); _reading.store(-1); _latest.store(-1); @@ -104,9 +200,9 @@ private: std::atomic _oldest; std::atomic _latest; std::atomic _reading; - std::atomic _writing; - AVFrame *_frames[BUFFER_VIDEO_FRAMES] = {nullptr, nullptr, nullptr}; - uint32_t _ids[BUFFER_VIDEO_FRAMES]; + int8_t _writing; + AVFrame *_frames[4]; + uint32_t _ids[4]; }; -#endif /* SRC_STRUCT_VIDER_BUFFER2 */ +#endif /* SRC_STRUCT_VIDEO_BUFFER */