From cef813bcd62745884683dd3f97b7dca829c5c7be Mon Sep 17 00:00:00 2001 From: Niellune Date: Thu, 12 Mar 2026 20:13:45 +0200 Subject: [PATCH] Minor fixes, Alternative rendering mode --- Makefile | 8 ++--- settings.txt | 13 ++++++-- src/application.cpp | 58 +++++++++++++++++++++++---------- src/decoder.cpp | 22 ++++++++++++- src/helper/settings_base.h | 1 + src/main.cpp | 1 + src/protocol.cpp | 1 + src/recorder.cpp | 2 +- src/recorder.h | 1 - src/renderer.cpp | 66 +++++++++++++++++++++++++++++++------- src/renderer.h | 8 ++++- src/settings.h | 10 +++--- src/struct/atomic_queue.h | 4 +-- src/struct/video_buffer.h | 8 +++++ 14 files changed, 157 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 4cbf1bf..4880988 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ all: debug LDOPTIONS := -lSDL2 -lSDL2_ttf -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 -lssl -lcrypto LDFLAGS := -CXXCOMMON := -Wall -Isrc +CXXCOMMON := -Wall -std=c++17 -Isrc debug: BUILD_TYPE := debug debug: CXXFLAGS := -g -O0 -DPROTOCOL_DEBUG -fsanitize=address -fno-omit-frame-pointer @@ -32,8 +32,8 @@ debug: TARGET := $(TARGET_NAME)-debug debug: prepare release: BUILD_TYPE := release -release: CXXFLAGS := -Ofast -march=native -fno-plt -fno-rtti -flto -fdata-sections -ffunction-sections -DNDEBUG -release: LDFLAGS += -Ofast -march=native -Wl,--gc-sections -flto +release: CXXFLAGS := -O3 -ffast-math -march=native -fno-plt -fno-rtti -flto -fdata-sections -ffunction-sections -DNDEBUG +release: LDFLAGS += -O3 -ffast-math -march=native -Wl, -flto release: TARGET := $(TARGET_NAME) release: prepare @@ -58,4 +58,4 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp clean: @rm -rf $(OUT_DIR) @rm -rf $(GEN_DIR) - @echo "Clean complete" \ No newline at end of file + @echo "Clean complete" diff --git a/settings.txt b/settings.txt index 2407afe..c420b94 100644 --- a/settings.txt +++ b/settings.txt @@ -139,6 +139,13 @@ # pipewire #audio-driver = +# Force SDL renderer backend, leave empty for automatic +# Examples: opengles2, opengl, software +#renderer-driver = + +# Use alternative video rendering method. May reduce CPU load and increase smoothness. +#alternative-rendering = false + # Run script or app on phone connected and disconnected. # This script/app should be fast, otherwise it will block system. # If you need to start application in background use scripts with fork @@ -165,8 +172,8 @@ #key-siri = 115 # S #key-nightmode-on = 122 # Z #key-nightmode-off = 120 # X -#key-left = 1073741903 # Left arrow -#key-right = 1073741904 # Right arrow +#key-left = 1073741904 # Left arrow +#key-right = 1073741903 # Right arrow #key-enter = 13 # Enter # For pipe to simulate key up after key-enter press #key-enterup = 0 # unmapped by default @@ -212,4 +219,4 @@ # Enable FFMPEG AV_CODEC_FLAG2_FAST for HW decoder. # Allow non spec compliant speedup tricks. -#decode-fast = false \ No newline at end of file +#decode-fast = false diff --git a/src/application.cpp b/src/application.cpp index 39ccc97..d63146a 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -34,7 +34,8 @@ static KeySetting *keyMap[] = { &Settings::keyReject, &Settings::keyVideoFocus, &Settings::keyVideoRelease, - &Settings::keyNavFocus}; + &Settings::keyNavFocus, + &Settings::keyNavRelease}; static constexpr size_t keyMapSize = sizeof(keyMap) / sizeof(keyMap[0]); @@ -85,10 +86,7 @@ void Application::start(const char *title) std::cout << "[App] Initialising" << std::endl; // Create SDL window centered on screen - if (Settings::fastScale) - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); - else - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, Settings::fastScale ? "nearest" : "best"); // Prepare window, show it in headless to avoid blinking, otherwise hidden untill iniailised bool fullsize = Settings::isFullscreen() || Settings::isHeadless(); @@ -111,10 +109,22 @@ void Application::start(const char *title) Uint32 flags = SDL_RENDERER_ACCELERATED; if (Settings::vsync) flags |= SDL_RENDERER_PRESENTVSYNC; + + SDL_SetHint(SDL_HINT_RENDER_DRIVER, Settings::renderDriver.value.c_str()); _renderer = SDL_CreateRenderer(_window, -1, flags); + if (!_renderer) throw std::runtime_error(std::string("SDL can't create renderer > ") + SDL_GetError()); + SDL_RendererInfo rendererInfo{}; + if (SDL_GetRendererInfo(_renderer, &rendererInfo) == 0) + { + std::cout << "[App] Renderer: " << rendererInfo.name + << " (" << ((rendererInfo.flags & SDL_RENDERER_ACCELERATED) ? "accelerated" : "software") + << ", " << ((rendererInfo.flags & SDL_RENDERER_PRESENTVSYNC) ? "vsync" : "no-vsync") + << ")" << std::endl; + } + // Register additional events _evtBase = SDL_RegisterEvents(2); if (_evtBase == (Uint32)-1) @@ -204,7 +214,7 @@ bool Application::processSystemEvent(const SDL_Event &e) _state.connected = e.user.code != 0; _state.frameRendered = false; _state.dirty = true; - _state.requestFrame = -1; + _state.requestFrame = 0; _state.flushBuffers = _state.connected; return true; } @@ -324,7 +334,8 @@ void Application::loop() AVFrame *frame = nullptr; uint32_t frameid = 0; uint32_t latestFrameid = 0; - uint32_t frameTargetTime = 1000 / Settings::fps; + uint32_t requestFrameId = 0; + uint32_t frameTargetTime = Settings::fps > 0 ? 1000 / Settings::fps : 1000; int frameDelay = 0; while (_active) { @@ -339,18 +350,20 @@ void Application::loop() std::cout << "[App] Frame drop " << frameid - latestFrameid - 1 << " on " << frameid << std::endl; latestFrameid = frameid; _state.dirty = false; - if (_state.requestFrame >= 0) - _state.requestFrame++; } videoBuffer.consume(); } - if (_state.requestFrame > 0 && Settings::forceRedraw > 0) + if (_state.requestFrame > 0 && Settings::forceRedraw > 0 && ++_state.requestFrame > Settings::forceRedraw) { - if (_state.requestFrame <= Settings::forceRedraw) + if (latestFrameid == requestFrameId) + { protocol.requestKeyframe(); + _state.requestFrame = 1; + requestFrameId = latestFrameid; + } else - _state.requestFrame = -1; + _state.requestFrame = 0; } } @@ -365,7 +378,10 @@ void Application::loop() else { if (processFrameEvents(protocol, interface) && Settings::forceRedraw > 0) + { _state.requestFrame = 1; + requestFrameId = latestFrameid; + } } if (_state.flushBuffers) @@ -375,15 +391,23 @@ void Application::loop() videoBuffer.reset(); } - if (_active) + if (_active && !Settings::vsync) { Uint32 frameEnd = SDL_GetTicks(); - frameDelay = frameTargetTime - (frameEnd - frameStart); - SDL_Delay(frameDelay > 0 ? frameDelay : 1); - frameStart += frameTargetTime; + frameDelay = frameTargetTime - (frameEnd - frameStart); + if(latestFrameid > 0 && latestFrameid != videoBuffer.latestId()) + { + SDL_Delay(1); + frameStart = frameEnd; + } + else + { + SDL_Delay(frameDelay > 0 ? frameDelay : 1); + frameStart += frameTargetTime; + } } } if (!Settings::isHeadless()) SDL_HideWindow(_window); -} \ No newline at end of file +} diff --git a/src/decoder.cpp b/src/decoder.cpp index 79c863d..7c785db 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -49,6 +49,13 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) AVCodecContext *result = nullptr; // Try hardware-accelerated decoders by iterating registered codecs + // NOTE: simply opening a codec with AV_CODEC_CAP_HARDWARE is not sufficient + // on platforms such as V4L2M2M. A proper hwdevice context and get_format + // callback must be setup so that AVFrames reference driver buffers instead + // of being converted to system memory. This implementation currently + // only picks a hardware-capable codec but still operates in software mode. + // Fixing this will eliminate an extra copy and allow true GPU‑accelerated + // decoding on the Pi. while ((codec = av_codec_iterate(&iter)) && Settings::hwDecode) { if (!av_codec_is_decoder(codec) || codec->id != codec_id) @@ -115,7 +122,7 @@ void Decoder::runner() // Load codec context _context = load_codec(_codecId); - if (_status.null(_context, ("Can't find decoder for codec " + _codecId))) + if (_status.null(_context, std::string("Can't find decoder for codec ") + avcodec_get_name(_codecId))) return; std::string codec = _context->codec->name; @@ -203,4 +210,17 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack } } } + + // push null packet to flush decoder and drain delayed frames + if (_context) + { + avcodec_send_packet(context, nullptr); + while (avcodec_receive_frame(context, frame) == 0) + { + AVFrame *out = _vb->write(counter++); + av_frame_unref(out); + av_frame_move_ref(out, frame); + _vb->commit(); + } + } } diff --git a/src/helper/settings_base.h b/src/helper/settings_base.h index d7280ae..a53b576 100644 --- a/src/helper/settings_base.h +++ b/src/helper/settings_base.h @@ -6,6 +6,7 @@ #include #include #include +#include // Base interface for any one setting class ISetting diff --git a/src/main.cpp b/src/main.cpp index f43be22..a641cd1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "helper/functions.h" diff --git a/src/protocol.cpp b/src/protocol.cpp index e4eacc4..bdb93ba 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -22,6 +22,7 @@ Protocol::Protocol(uint16_t width, uint16_t height, uint16_t fps, uint16_t paddi Protocol::~Protocol() { + stop(); } void Protocol::start(uint32_t evtStatus, uint32_t evtPhone) diff --git a/src/recorder.cpp b/src/recorder.cpp index 3ac4f4b..9c5ed6f 100644 --- a/src/recorder.cpp +++ b/src/recorder.cpp @@ -9,7 +9,7 @@ Recorder::Recorder(uint16_t buffSize) - : _sender(nullptr), _active(false), _device(0), _data(buffSize) + : _sender(nullptr), _active(false), _data(buffSize) { } diff --git a/src/recorder.h b/src/recorder.h index e842b6d..1dab9f1 100644 --- a/src/recorder.h +++ b/src/recorder.h @@ -46,7 +46,6 @@ private: IAudioSender *_sender; std::atomic _active; std::thread _thread; - SDL_AudioDeviceID _device; AtomicQueue _data; }; diff --git a/src/renderer.cpp b/src/renderer.cpp index 5444a05..4e4b01b 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,4 +1,5 @@ #include "renderer.h" +#include #include #include "settings.h" #include "helper/functions.h" @@ -140,12 +141,6 @@ SDL_Rect RendererImage::draw(SDL_Renderer *renderer, int w, int h) return dst; } -const Renderer::FormatMapping Renderer::_mapping[] = { - {AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24, &Renderer::rgb, "RGB24"}, - {AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUV420P"}, - {AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUVJ420P"}, - {AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12, &Renderer::nv, "NV12"}}; - Renderer::Renderer(SDL_Renderer *renderer) : xScale(0), yScale(0), @@ -158,6 +153,12 @@ Renderer::Renderer(SDL_Renderer *renderer) _sws(nullptr), _frame(nullptr) { + if (Settings::alternativeRendering) + { + _mapping[1].function = &Renderer::yuvAlternative; + _mapping[2].function = &Renderer::yuvAlternative; + _mapping[3].function = &Renderer::nvAlternative; + } } Renderer::~Renderer() @@ -298,6 +299,25 @@ void Renderer::nv(AVFrame *frame) frame->data[1], frame->linesize[1]); } +void Renderer::nvAlternative(AVFrame *frame) +{ + uint8_t *pixels = nullptr; + int pitch = 0; + if (SDL_LockTexture(_texture, nullptr, (void **)&pixels, &pitch) != 0) + return; + + // Y plane + for (int i = 0; i < frame->height; i++) + memcpy(pixels + i * pitch, frame->data[0] + i * frame->linesize[0], frame->width); + + // UV interleaved plane (half height, full width) + uint8_t *uv = pixels + pitch * frame->height; + for (int i = 0; i < frame->height / 2; i++) + memcpy(uv + i * pitch, frame->data[1] + i * frame->linesize[1], frame->width); + + SDL_UnlockTexture(_texture); +} + void Renderer::yuv(AVFrame *frame) { SDL_UpdateYUVTexture( @@ -308,6 +328,30 @@ void Renderer::yuv(AVFrame *frame) frame->data[2], frame->linesize[2]); } +void Renderer::yuvAlternative(AVFrame *frame) +{ + uint8_t *pixels = nullptr; + int pitch = 0; + if (SDL_LockTexture(_texture, nullptr, (void **)&pixels, &pitch) != 0) + return; + + // Y plane + for (int i = 0; i < frame->height; i++) + memcpy(pixels + i * pitch, frame->data[0] + i * frame->linesize[0], frame->width); + + // U plane + uint8_t *u = pixels + pitch * frame->height; + for (int i = 0; i < frame->height / 2; i++) + memcpy(u + i * (pitch / 2), frame->data[1] + i * frame->linesize[1], frame->width / 2); + + // V plane + uint8_t *v = u + (pitch / 2) * (frame->height / 2); + for (int i = 0; i < frame->height / 2; i++) + memcpy(v + i * (pitch / 2), frame->data[2] + i * frame->linesize[2], frame->width / 2); + + SDL_UnlockTexture(_texture); +} + void Renderer::scale(AVFrame *frame) { // Scale frame to output format @@ -317,10 +361,8 @@ void Renderer::scale(AVFrame *frame) _frame->data, _frame->linesize); - SDL_UpdateYUVTexture( - _texture, - nullptr, - _frame->data[0], _frame->linesize[0], - _frame->data[1], _frame->linesize[1], - _frame->data[2], _frame->linesize[2]); + if (Settings::alternativeRendering) + yuvAlternative(_frame); + else + yuv(_frame); } diff --git a/src/renderer.h b/src/renderer.h index d7b9517..630c123 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -75,7 +75,9 @@ private: void rgb(AVFrame *frame); void nv(AVFrame *frame); + void nvAlternative(AVFrame *frame); void yuv(AVFrame *frame); + void yuvAlternative(AVFrame *frame); void scale(AVFrame *frame); SDL_Texture *_texture; @@ -85,7 +87,11 @@ private: DrawFuncType _render; SwsContext *_sws; AVFrame *_frame; - static const FormatMapping _mapping[]; + FormatMapping _mapping[4] = { + {AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24, &Renderer::rgb, "RGB24"}, + {AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUV420P"}, + {AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUVJ420P"}, + {AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12, &Renderer::nv, "NV12"}}; }; #endif /* SRC_RENDERER */ diff --git a/src/settings.h b/src/settings.h index 6cb85cb..988227f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -42,6 +42,7 @@ public: static inline Setting forceRedraw{"force-redraw", 0}; static inline Setting aspectCorrection{"aspect-correction", 1}; static inline Setting fastScale{"fast-render-scale", false}; + static inline Setting alternativeRendering{"alternative-rendering", false}; static inline Setting videoQueue{"video-buffer-size", 32}; static inline Setting audioQueue{"audio-buffer-size", 16}; static inline Setting audioDelay{"audio-buffer-wait", 2}; @@ -49,6 +50,7 @@ public: static inline Setting audioFade{"audio-fade", 0.3}; static inline Setting audioBuffer{"audio-buffer-samples", 2048}; static inline Setting audioDriver{"audio-driver", ""}; + static inline Setting renderDriver{"renderer-driver", "auto"}; static inline Setting onConnect{"on-connect-script", ""}; static inline Setting onDisconnect{"on-disconnect-script", ""}; @@ -56,8 +58,8 @@ public: static inline KeySetting keySiri{"key-siri", 115, 5}; static inline KeySetting keyNightOn{"key-nightmode-on", 122, 16}; static inline KeySetting keyNightOff{"key-nightmode-off", 120, 17}; - static inline KeySetting keyLeft{"key-left", 1073741903, 100}; - static inline KeySetting keyRight{"key-right", 1073741904, 101}; + static inline KeySetting keyLeft{"key-left", 1073741904, 100}; + static inline KeySetting keyRight{"key-right", 1073741903, 101}; static inline KeySetting keyEnter{"key-enter", 13, 104}; static inline KeySetting keyEnterUp{"key-enterup", 0, 105}; static inline KeySetting keyBack{"key-back", 8, 106}; @@ -79,8 +81,8 @@ public: // Debug section static inline Setting protocolDebug{"protocol-debug", 0}; - static inline Setting codecLowDelay{"decode-low-delay", true}; - static inline Setting codecFast{"decode-fast", false}; + static inline Setting codecLowDelay{"decode-low-delay", true}; + static inline Setting codecFast{"decode-fast", false}; static bool load(const std::string &filename); static void print(); diff --git a/src/struct/atomic_queue.h b/src/struct/atomic_queue.h index 25f93a7..1b79df2 100644 --- a/src/struct/atomic_queue.h +++ b/src/struct/atomic_queue.h @@ -78,8 +78,8 @@ public: unique_lock lock(_mtx); _lock.wait(lock, [&] - { return _count > count || !waitFlag; }); - return waitFlag; + { return _count > count || !waitFlag.load(); }); + return waitFlag.load(); } void clear() diff --git a/src/struct/video_buffer.h b/src/struct/video_buffer.h index 76ba814..cc4411f 100644 --- a/src/struct/video_buffer.h +++ b/src/struct/video_buffer.h @@ -42,6 +42,14 @@ public: } } + uint32_t latestId() + { + int index = _latest.load(); + if (index < 0) + return 0; + return _ids[index]; + } + bool latest(AVFrame **frame, uint32_t *id) { _reading.store(_latest.load());