From 64f709072f33cb78242fd03090b1ee33556bbef0 Mon Sep 17 00:00:00 2001 From: Niellune Date: Tue, 20 May 2025 23:16:06 +0300 Subject: [PATCH] Optimise queues and rendering --- settings.txt | 26 ++--- src/connector.cpp | 2 +- src/decoder.cpp | 71 ++----------- src/decoder.h | 9 +- src/helper/functions.h | 2 +- src/helper/iprotocol.h | 6 +- src/{ux => helper}/ufont.h | 6 +- src/{ux => helper}/uimage.h | 6 +- src/main.cpp | 89 ++++++----------- src/pcm_audio.cpp | 89 ++++++----------- src/pcm_audio.h | 28 ++++-- src/protocol.cpp | 8 +- src/protocol.h | 13 +-- src/renderer.cpp | 183 ++++++++++++++++++++++++++++++++++ src/renderer.h | 53 ++++++++++ src/{helper => }/settings.cpp | 0 src/{helper => }/settings.h | 12 +-- src/struct/atomic_queue.h | 105 +++++++++++++++++++ src/struct/message.h | 43 ++++++++ src/struct/raw-queue.cpp | 69 ------------- src/struct/raw_queue.h | 46 --------- src/struct/video_buffer.cpp | 111 --------------------- src/struct/video_buffer.h | 84 ++++++++++++---- 23 files changed, 585 insertions(+), 476 deletions(-) rename src/{ux => helper}/ufont.h (94%) rename src/{ux => helper}/uimage.h (92%) create mode 100644 src/renderer.cpp create mode 100644 src/renderer.h rename src/{helper => }/settings.cpp (100%) rename src/{helper => }/settings.h (77%) create mode 100644 src/struct/atomic_queue.h create mode 100644 src/struct/message.h delete mode 100644 src/struct/raw-queue.cpp delete mode 100644 src/struct/raw_queue.h delete mode 100644 src/struct/video_buffer.cpp diff --git a/settings.txt b/settings.txt index ae5dce9..05cac83 100644 --- a/settings.txt +++ b/settings.txt @@ -1,20 +1,20 @@ # Dongle configuration # Note that vendor id and product id are in decimals, not hex here -vendor-id = 4884 # 0x1314 -product-id = 5408 # 0x1520 +#vendor-id = 4884 # 0x1314 +#product-id = 5408 # 0x1520 # Application starts in full screen -fullscreen = true +#fullscreen = true # Application drawing settings widthxheight -width = 720 -height = 576 -fps = 30 +#width = 720 +#height = 576 +#fps = 60 # Requested image from phone -source-width = 1080 -source-height = 864 -source-fps = 30 +#source-width = 720 +#source-height = 576 +#source-fps = 30 # Scaler algorithm if application drawing is differen from source image # It's recommended to keep application and source values same cause scaling @@ -31,13 +31,13 @@ source-fps = 30 # SWS_SINC 256 # SWS_LANCZOS 512 # SWS_SPLINE 1024 -scaler = 1024 +#scaler = 2 # Enable logging -logging = false +logging = true # Size of video and audio buffers. Increase if you see artifacts -queue-size = 32 +#queue-size = 32 # Font size for messgaes on screen -font-size = 30 \ No newline at end of file +#font-size = 30 \ No newline at end of file diff --git a/src/connector.cpp b/src/connector.cpp index 70a92de..9384464 100644 --- a/src/connector.cpp +++ b/src/connector.cpp @@ -5,7 +5,7 @@ #include #include "helper/functions.h" -#include "helper/settings.h" +#include "settings.h" Connector::Connector(uint16_t videoPadding) : _videoPadding(videoPadding) diff --git a/src/decoder.cpp b/src/decoder.cpp index b4337eb..4342549 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -2,7 +2,7 @@ #include #include "helper/functions.h" -#include "helper/settings.h" +#include "settings.h" Decoder::Decoder() { @@ -13,7 +13,7 @@ Decoder::~Decoder() stop(); } -void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId) +void Decoder::start(AtomicQueue *data, VideoBuffer *vb, AVCodecID codecId) { if (_active) stop(); @@ -22,7 +22,6 @@ void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId) _data = data; _codecId = codecId; _active = true; - _running = false; _thread = std::thread(&Decoder::runner, this); } void Decoder::stop() @@ -133,11 +132,7 @@ void Decoder::runner() void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame) { - SwsContext *sws = nullptr; - int sws_height = 0; - int sws_width = 0; uint32_t counter = 0; - _running = true; std::cout << "Video decoder loop started" << std::endl; @@ -145,20 +140,13 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack while (_active) { // Get raw data segment from queue - RawEntry segment = _data->wait(_active); + std::unique_ptr segment = _data->wait(_active); if (!_active) - { - if (segment.data) - { - free(segment.data); - segment.data = nullptr; - } continue; - } - uint8_t *data_ptr = segment.data + segment.offset; // Pointer to raw data buffer - int data_size = segment.size; // Size of raw data buffer + uint8_t *data_ptr = segment->data(); + int data_size = segment->length(); // Feed raw data into the parser and decoder while (_active && data_size > 0) @@ -201,52 +189,11 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack // Receive decoded frames while (avcodec_receive_frame(context, frame) == 0 && _active) { - if (!sws || sws_width != frame->width || sws_height != frame->height) - { - if (sws) - { - sws_freeContext(sws); - sws = nullptr; - } - - sws_width = frame->width; - sws_height = frame->height; - sws = sws_getContext(sws_width, sws_height, (AVPixelFormat)frame->format, - _vb->width(), _vb->height(), AV_PIX_FMT_YUV420P, - Settings::scaler, nullptr, nullptr, nullptr); - if (!sws) - { - std::cout << "Error creating sws context" << std::endl; - _active = false; - continue; - } - } - - // Write decoded frame into video buffer and get output frame pointer - const AVFrame *out = _vb->writeFrame(counter++); - // Scale frame to output format - sws_scale(sws, - frame->data, frame->linesize, - 0, sws_height, - out->data, - out->linesize); - // Commit frame to buffer - _vb->commitFrame(); + AVFrame* out = _vb->write(counter++); + av_frame_unref(out); + av_frame_move_ref(out, frame); + _vb->commit(); } } - - if (segment.data) - { - free(segment.data); - segment.data = nullptr; - } - } - - _running = false; - - if (sws) - { - sws_freeContext(sws); - sws = nullptr; } } diff --git a/src/decoder.h b/src/decoder.h index f929030..1cdfcdf 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -11,7 +11,9 @@ extern "C" #include #include "struct/video_buffer.h" -#include "struct/raw_queue.h" +#include "struct/atomic_queue.h" +#include "struct/message.h" +#include "helper/error.h" class Decoder { @@ -20,7 +22,7 @@ public: Decoder(); ~Decoder(); - void start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId); + void start(AtomicQueue *data, VideoBuffer *vb, AVCodecID codecId); void stop(); private: @@ -33,9 +35,8 @@ private: Error _status; std::atomic _active = false; - std::atomic _running = false; - RawQueue *_data = nullptr; + AtomicQueue *_data = nullptr; VideoBuffer *_vb = nullptr; }; diff --git a/src/helper/functions.h b/src/helper/functions.h index 1ae0a0c..98c3290 100644 --- a/src/helper/functions.h +++ b/src/helper/functions.h @@ -21,4 +21,4 @@ inline void disable_cout() std::cout.setstate(std::ios_base::failbit); } -#endif /* SRC_HELPER_FUNCTIONS */ \ No newline at end of file +#endif /* SRC_HELPER_FUNCTIONS */ diff --git a/src/helper/iprotocol.h b/src/helper/iprotocol.h index ec7eb87..0535772 100644 --- a/src/helper/iprotocol.h +++ b/src/helper/iprotocol.h @@ -1,5 +1,5 @@ -#ifndef SRC_PROTOCOL_ISENDER -#define SRC_PROTOCOL_ISENDER +#ifndef SRC_HELPER_IPROTOCOL +#define SRC_HELPER_IPROTOCOL #include #include @@ -13,4 +13,4 @@ public: virtual void onStatus(const char* status) = 0; virtual void onDevice(bool connected) = 0; }; -#endif /* SRC_PROTOCOL_ISENDER */ +#endif /* SRC_HELPER_IPROTOCOL */ diff --git a/src/ux/ufont.h b/src/helper/ufont.h similarity index 94% rename from src/ux/ufont.h rename to src/helper/ufont.h index a690065..1c6c4d6 100644 --- a/src/ux/ufont.h +++ b/src/helper/ufont.h @@ -1,5 +1,5 @@ -#ifndef SRC_UX_UFONT -#define SRC_UX_UFONT +#ifndef SRC_HELPER_UFONT +#define SRC_HELPER_UFONT #include #include @@ -59,4 +59,4 @@ private: TTF_Font *_font = nullptr; }; -#endif /* SRC_UX_UFONT */ +#endif /* SRC_HELPER_UFONT */ diff --git a/src/ux/uimage.h b/src/helper/uimage.h similarity index 92% rename from src/ux/uimage.h rename to src/helper/uimage.h index a39f232..975c357 100644 --- a/src/ux/uimage.h +++ b/src/helper/uimage.h @@ -1,5 +1,5 @@ -#ifndef SRC_UX_UIMAGE -#define SRC_UX_UIMAGE +#ifndef SRC_HELPER_UIMAGE +#define SRC_HELPER_UIMAGE #include #include @@ -49,4 +49,4 @@ private: SDL_Surface *_surface = nullptr; }; -#endif \ No newline at end of file +#endif /* SRC_HELPER_UIMAGE */ diff --git a/src/main.cpp b/src/main.cpp index cf55b50..0a7bfc1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,8 @@ -#include // Include SDL2 library headers for graphics and event handling +#include #include +#include +#include +#include extern "C" { @@ -9,33 +12,21 @@ extern "C" #include // FFmpeg utility functions for image handling } -#include // C++ atomic types for thread-safe variables -#include // C++ mutex for locking resources -#include // C++ condition variable for thread synchronization -#include // Math functions (not explicitly used here but included) -#include // Standard C I/O functions -#include // C++ dynamic array container -#include // C++ string type -#include -#include - #include "resource/background.h" #include "resource/font.h" -#include "helper/settings.h" #include "helper/functions.h" - -#include "ux/ufont.h" -#include "ux/uimage.h" +#include "helper/ufont.h" +#include "helper/uimage.h" #include "struct/video_buffer.h" + #include "protocol.h" #include "decoder.h" #include "pcm_audio.h" +#include "renderer.h" -static const char *title = "Fast Car Play v0.1"; -static int width = 0; -static int height = 0; +static const char *title = "Fast Car Play v0.2"; static SDL_Window *window = nullptr; static SDL_Renderer *renderer = nullptr; bool active = false; @@ -43,7 +34,6 @@ bool active = false; static SDL_Texture *textTexture = nullptr; static std::string textureText = ""; static SDL_Texture *imgTexture = nullptr; -static SDL_Texture *videoTexture = nullptr; static bool mouseDown = false; static bool fullscreen = false; @@ -196,10 +186,7 @@ void application() SDL_RenderClear(renderer); // Clear renderer to black SDL_RenderPresent(renderer); // Present initial blank frame - std::cout << " > Application started" << std::endl; - VideoBuffer videoBuffer; - videoBuffer.allocate(Settings::width, Settings::height).throwError(); Protocol protocol(Settings::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE); Decoder decoder; PcmAudio audio0, audio1, audio2; @@ -221,8 +208,10 @@ void application() SDL_RenderPresent(renderer); std::cout << " > Application loop" << std::endl; + Renderer videoRenderer(renderer); bool dirty = true; bool connected = false; + bool videoPrepared = false; const int activeDelay = 1000 / Settings::fps; const int inactiveDelay = 1000 / 5; // 5FPS uint32_t frameDelay = inactiveDelay; @@ -240,6 +229,7 @@ void application() DrawImage(image); SDL_RenderPresent(renderer); dirty = true; + videoPrepared = false; frameDelay = connected ? activeDelay : inactiveDelay; } @@ -247,18 +237,18 @@ void application() { AVFrame *frame = nullptr; uint32_t frameid = 0; - if (videoBuffer.getLatest(&frame, &frameid) && frameid != latestid) + if (videoBuffer.latest(&frame, &frameid) && frameid != latestid && frame) { - // Update SDL texture with YUV frame data - SDL_UpdateYUVTexture(videoTexture, nullptr, - frame->data[0], frame->linesize[0], - frame->data[1], frame->linesize[1], - frame->data[2], frame->linesize[2]); + if (!videoPrepared) + videoPrepared = videoRenderer.prepare(frame, Settings::width, Settings::height, Settings::scaler); + if (videoPrepared && videoRenderer.render(frame)) + { + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, videoRenderer.texture, nullptr, nullptr); + SDL_RenderPresent(renderer); + } latestid = frameid; - videoBuffer.consumeLatest(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, videoTexture, nullptr, nullptr); - SDL_RenderPresent(renderer); + videoBuffer.consume(); } } else @@ -287,6 +277,7 @@ void application() } } std::cout << " > Application stopping" << std::endl; + SDL_HideWindow(window); } int main(int argc, char **argv) @@ -331,23 +322,13 @@ int main(int argc, char **argv) return 1; } - if (Settings::fullscreen) - { - width = displayMode.w; - height = displayMode.h; - } - else - { - width = Settings::width; - height = Settings::height; - } - - // Create SDL window centered on screen, 800x600 size + // Create SDL window centered on screen + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - width, - height, + Settings::fullscreen ? displayMode.w : Settings::width, + Settings::fullscreen ? displayMode.h : Settings::height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN); if (!window) @@ -362,19 +343,9 @@ int main(int argc, char **argv) renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); if (renderer) { - videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, - SDL_TEXTUREACCESS_STREAMING, - Settings::width, Settings::height); - if (videoTexture) - { - application(); - SDL_DestroyTexture(videoTexture); - std::cout << " > Application finish" << std::endl; - } - else - { - std::cerr << "[Main] SDL can't create video texture: " << SDL_GetError() << std::endl; - } + std::cout << " > Application started" << std::endl; + application(); + std::cout << " > Application finish" << std::endl; SDL_DestroyRenderer(renderer); } else diff --git a/src/pcm_audio.cpp b/src/pcm_audio.cpp index 28f258c..f8f27ad 100644 --- a/src/pcm_audio.cpp +++ b/src/pcm_audio.cpp @@ -11,12 +11,12 @@ PcmAudio::~PcmAudio() void PcmAudio::setVolume(float vol) { - if(vol<0) + if (vol < 0) { _volume = 0; return; } - if(vol>1) + if (vol > 1) { _volume = 1; return; @@ -24,7 +24,7 @@ void PcmAudio::setVolume(float vol) _volume = vol; } -void PcmAudio::start(RawQueue *data) +void PcmAudio::start(AtomicQueue *data) { if (_active) stop(); @@ -43,6 +43,21 @@ void PcmAudio::stop() _thread.join(); } +static ChannelConfig configTable[] = { + {8000, 1}, // type = 3 + {48000, 2}, // type = 4 + {16000, 1}, // type = 5 + {24000, 1}, // type = 6 + {16000, 2}, // type = 7 +}; + +ChannelConfig PcmAudio::getConfig(int type) const +{ + if (type >= 3 && type <= 7) + return configTable[type - 3]; + return {44100, 2}; +} + void PcmAudio::runner() { setThreadName("audio"); @@ -52,63 +67,21 @@ void PcmAudio::runner() size_t bufferedBytes = 0; bool unpaused = false; size_t targetBytes = 0; - int rate = 0; - int channels = 0; + ChannelConfig config = {0, 0}; while (_active) { - RawEntry segment = _data->wait(_active); + unique_ptr segment = _data->wait(_active); if (!_active) - { - if (segment.data) - free(segment.data); break; - } - int config = 0; - memcpy(&config, segment.data, sizeof(int)); - - int newRate = 44100; - int newChannels = 2; - switch (config) - { - case 1: - newRate = 44100; - newChannels = 2; - break; - case 2: - newRate = 44100; - newChannels = 2; - break; - case 3: - newRate = 8000; - newChannels = 1; - break; - case 4: - newRate = 48000; - newChannels = 2; - break; - case 5: - newRate = 16000; - newChannels = 1; - break; - case 6: - newRate = 24000; - newChannels = 1; - break; - case 7: - newRate = 16000; - newChannels = 2; - break; - } + ChannelConfig segmentConfig = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)); // If settings changed, (re)open audio device - if (device == 0 || rate != newRate || channels != newChannels) + if (device == 0 || config != segmentConfig) { - rate = newRate; - channels = newChannels; + config = segmentConfig; - printf("PCM SETTING %d %d\n", rate, channels); // Close existing device if (device != 0) { @@ -117,9 +90,9 @@ void PcmAudio::runner() } // Configure new spec SDL_zero(spec); - spec.freq = rate; + spec.freq = config.rate; spec.format = AUDIO_S16SYS; - spec.channels = static_cast(channels); + spec.channels = config.channels; spec.samples = 4096; spec.callback = nullptr; @@ -127,11 +100,10 @@ void PcmAudio::runner() if (device == 0) { std::cerr << "Failed to open audio: " << SDL_GetError() << std::endl; - free(segment.data); continue; } // Calculate new buffer target: 0.5s - targetBytes = rate * channels * sizeof(int16_t) / 2; + targetBytes = config.rate * config.channels * sizeof(int16_t) / 2; bufferedBytes = 0; unpaused = false; // Start paused @@ -139,17 +111,16 @@ void PcmAudio::runner() } // Apply volume in-place - int16_t *samples = reinterpret_cast(segment.data + segment.offset); - size_t count = segment.size / sizeof(int16_t); + int16_t *samples = reinterpret_cast(segment->data()); + size_t count = segment->length() / sizeof(int16_t); for (size_t i = 0; i < count; ++i) { samples[i] = static_cast(samples[i] * _volume); } // Queue audio - SDL_QueueAudio(device, segment.data + segment.offset, segment.size); - bufferedBytes += segment.size; - free(segment.data); + SDL_QueueAudio(device, segment->data(), segment->length()); + bufferedBytes += segment->length(); // Unpause when enough buffered if (!unpaused && bufferedBytes >= targetBytes) diff --git a/src/pcm_audio.h b/src/pcm_audio.h index 70f84a8..6289945 100644 --- a/src/pcm_audio.h +++ b/src/pcm_audio.h @@ -7,28 +7,44 @@ #include -#include "struct/raw_queue.h" +#include "struct/atomic_queue.h" +#include "struct/message.h" #include "helper/error.h" +struct ChannelConfig +{ + int rate; + uint8_t channels; -class PcmAudio { + bool operator==(ChannelConfig const &other) const + { + return rate == other.rate && channels == other.channels; + } + + bool operator!=(ChannelConfig const &other) const + { + return !(*this == other); + } +}; + +class PcmAudio +{ public: PcmAudio(); ~PcmAudio(); // Start playing raw PCM data from queue - void start(RawQueue* data); + void start(AtomicQueue *data); void stop(); void setVolume(float vol); private: + ChannelConfig getConfig(int type) const; void runner(); void loop(SDL_AudioDeviceID device); - RawQueue* _data = nullptr; - int _sampleRate = 0; - int _channels = 0; + AtomicQueue *_data = nullptr; float _volume = 1.0f; diff --git a/src/protocol.cpp b/src/protocol.cpp index aa8e805..e9eafc7 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -178,7 +178,7 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) { if (length <= 20) break; - videoData.push(data, 20, length - 20); + videoData.pushDiscard( std::make_unique(data, length, 20)); dispose = false; break; } @@ -193,19 +193,19 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) memcpy(&channel, data + 8, sizeof(int)); if (channel == 0) { - audioStream0.push(data, 12, length - 12); + audioStream0.pushDiscard(std::make_unique(data, length, 12)); dispose = false; break; } if (channel == 1) { - audioStream1.push(data, 12, length - 12); + audioStream1.pushDiscard(std::make_unique(data, length, 12)); dispose = false; break; } if (channel == 2) { - audioStream2.push(data, 12, length - 12); + audioStream2.pushDiscard(std::make_unique(data, length, 12)); dispose = false; break; } diff --git a/src/protocol.h b/src/protocol.h index daaee76..3456359 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,9 +1,10 @@ #ifndef SRC_PROTOCOL #define SRC_PROTOCOL -#include "struct/raw_queue.h" +#include "struct/atomic_queue.h" +#include "struct/message.h" #include "helper/iprotocol.h" -#include "helper/settings.h" +#include "settings.h" #include "connector.h" #define MAGIC 0x55aa55aa @@ -40,10 +41,10 @@ public: void sendMove(float dx, float dy); Connector connector; - RawQueue videoData; - RawQueue audioStream0; - RawQueue audioStream1; - RawQueue audioStream2; + AtomicQueue videoData; + AtomicQueue audioStream0; + AtomicQueue audioStream1; + AtomicQueue audioStream2; bool phoneConnected; private: diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..8929511 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,183 @@ +#include "renderer.h" +#include "helper/error.h" +#include + +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) + : texture(nullptr), + textureWidth(0), + textureHeight(0), + _renderer(renderer), + _render(nullptr), + _sws(nullptr), + _swsWidth(0), + _swsHeight(0), + _frame(nullptr) +{ +} + +Renderer::~Renderer() +{ + if (texture) + { + SDL_DestroyTexture(texture); + texture = nullptr; + } + if (_sws) + { + sws_freeContext(_sws); + _sws = nullptr; + } + if (_frame) + { + av_frame_free(&_frame); + _frame = nullptr; + } +} + +bool Renderer::render(AVFrame *frame) +{ + if (_render == nullptr) + return false; + (this->*_render)(frame); + return true; +} + +bool Renderer::prepareTexture(uint32_t format, int width, int height) +{ + if (texture) + { + if (textureWidth == width && textureHeight == height) + return true; + SDL_DestroyTexture(texture); + texture = nullptr; + } + + texture = SDL_CreateTexture(_renderer, format, + SDL_TEXTUREACCESS_STREAMING, + width, height); + if (!texture) + { + std::cerr << "[Render] SDL can't create video texture: " << SDL_GetError() << std::endl; + return false; + } + return true; +} + +bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler) +{ + if (frame->width == targetWidth && frame->height == targetHeight) + { + AVPixelFormat fmt = static_cast(frame->format); + for (const FormatMapping &mapping : _mapping) + { + if (mapping.avFormat == fmt) + { + if (prepareTexture(mapping.sdlFormat, targetWidth, targetHeight)) + { + std::cout << "[Render] Direct rendering " << mapping.name << std::endl; + _render = mapping.function; + return true; + } + } + } + } + else + { + std::cout << "[Render] Scaling required from " << frame->width << "x" << frame->height << " to " << targetWidth << "x" << targetHeight << std::endl; + } + + if (!prepareTexture(SDL_PIXELFORMAT_IYUV, targetWidth, targetHeight)) + return false; + + if (!_sws || _swsWidth != frame->width || _swsHeight != frame->height) + { + if (_sws) + { + sws_freeContext(_sws); + _sws = nullptr; + } + + _sws = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format, + targetWidth, targetHeight, AV_PIX_FMT_YUV420P, + scaler, nullptr, nullptr, nullptr); + if (!_sws) + { + std::cerr << "[Render] Can't create sws context" << std::endl; + return false; + } + _swsWidth = frame->width; + _swsHeight = frame->height; + } + + if (!_frame) + { + _frame = av_frame_alloc(); + if (!_frame) + { + std::cerr << "[Render] Can't allocate AVFrame" << std::endl; + return false; + } + _frame->format = AV_PIX_FMT_YUV420P; + _frame->width = targetWidth; + _frame->height = targetHeight; + // Allocate data buffer with 32 byte allingment + int avRes = av_frame_get_buffer(_frame, 32); + if (avRes != 0) + { + std::cerr << "[Render] Can't allocate AVFrame buffer: " << Error::avErrorText(avRes) << std::endl; + return false; + } + } + + std::cout << "[Render] Scaling rendering source format " << frame->format << std::endl; + _render = &Renderer::scale; + return true; +} + +void Renderer::rgb(AVFrame *frame) +{ + SDL_UpdateTexture( + texture, + nullptr, + frame->data[0], frame->linesize[0]); +} + +void Renderer::nv(AVFrame *frame) +{ + SDL_UpdateNVTexture( + texture, + nullptr, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1]); +} +void Renderer::yuv(AVFrame *frame) +{ + SDL_UpdateYUVTexture( + texture, + nullptr, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2]); +} + +void Renderer::scale(AVFrame *frame) +{ + // Scale frame to output format + sws_scale(_sws, + frame->data, frame->linesize, + 0, _swsHeight, + _frame->data, + _frame->linesize); + + // Update SDL texture with YUV frame data + SDL_UpdateYUVTexture(texture, nullptr, + _frame->data[0], _frame->linesize[0], + _frame->data[1], _frame->linesize[1], + _frame->data[2], _frame->linesize[2]); +} diff --git a/src/renderer.h b/src/renderer.h new file mode 100644 index 0000000..6d0bf2a --- /dev/null +++ b/src/renderer.h @@ -0,0 +1,53 @@ +#ifndef SRC_RENDERER +#define SRC_RENDERER + +extern "C" +{ +#include // FFmpeg library for multimedia container format handling +#include // FFmpeg library for image scaling and pixel format conversion +} + +#include +#include + +class Renderer +{ +public: + Renderer(SDL_Renderer *renderer); + ~Renderer(); + + bool prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler); + bool render(AVFrame *frame); + + SDL_Texture *texture; + int textureWidth; + int textureHeight; + +private: + using DrawFuncType = void (Renderer::*)(AVFrame *); + + struct FormatMapping + { + AVPixelFormat avFormat; + SDL_PixelFormatEnum sdlFormat; + DrawFuncType function; + std::string name; + }; + + bool prepareTexture(uint32_t format, int width, int height); + + void rgb(AVFrame *frame); + void nv(AVFrame *frame); + void yuv(AVFrame *frame); + void scale(AVFrame *frame); + + SDL_Renderer *_renderer; + DrawFuncType _render; + SwsContext *_sws; + int _swsWidth; + int _swsHeight; + AVFrame *_frame; + static const FormatMapping _mapping[]; +}; + +#endif /* SRC_RENDERER */ diff --git a/src/helper/settings.cpp b/src/settings.cpp similarity index 100% rename from src/helper/settings.cpp rename to src/settings.cpp diff --git a/src/helper/settings.h b/src/settings.h similarity index 77% rename from src/helper/settings.h rename to src/settings.h index 111b509..e6467f9 100644 --- a/src/helper/settings.h +++ b/src/settings.h @@ -1,7 +1,7 @@ -#ifndef SRC_HELPER_SETTINGS -#define SRC_HELPER_SETTINGS +#ifndef SRC_SETTINGS +#define SRC_SETTINGS -#include "settings_base.h" +#include "helper/settings_base.h" // The singleton “Settings” namespace class Settings @@ -9,14 +9,14 @@ class Settings public: static inline Setting vendorid{"vendor-id", 4884}; static inline Setting productid{"product-id ", 5408}; - static inline Setting fullscreen{"fullscreen", false}; + static inline Setting fullscreen{"fullscreen", true}; static inline Setting width{"width", 720}; static inline Setting height{"height", 576}; static inline Setting fps{"fps", 60}; static inline Setting sourceWidth{"source-width", 720}; static inline Setting sourceHeight{"source-height", 576}; static inline Setting sourceFps{"source-fps", 30}; - static inline Setting logging{"logging", true}; + static inline Setting logging{"logging", false}; static inline Setting scaler{"scaler", 2}; static inline Setting queue{"queue-size", 32}; static inline Setting fontSize{"font-size", 30}; @@ -28,4 +28,4 @@ private: static void trim(std::string &s); }; -#endif /* SRC_HELPER_SETTINGS */ +#endif /* SRC_SETTINGS */ diff --git a/src/struct/atomic_queue.h b/src/struct/atomic_queue.h new file mode 100644 index 0000000..225bd2f --- /dev/null +++ b/src/struct/atomic_queue.h @@ -0,0 +1,105 @@ +#ifndef SRC_STRUCT_ATOMIC_QUEUE +#define SRC_STRUCT_ATOMIC_QUEUE + +#include +#include +#include +#include +#include + +using namespace std; + +template +class AtomicQueue +{ +public: + AtomicQueue(uint16_t size) + : _size(size), _data(new unique_ptr[size]), _first(0), _last(0), _count(0) + { + } + + AtomicQueue(const AtomicQueue &) = delete; + AtomicQueue &operator=(const AtomicQueue &) = delete; + + ~AtomicQueue() = default; + + bool pushDiscard(unique_ptr obj) + { + if (_count == _size) + return false; + + _first = (_first + 1) % _size; + _data[_first] = std::move(obj); + ++_count; + _lock.notify_one(); + return true; + } + + bool pushReplace(unique_ptr obj) + { + if (_count == _size) + { + _data[_first] = std::move(obj); + return false; + } + + _first = (_first + 1) % _size; + _data[_first] = std::move(obj); + ++_count; + _lock.notify_one(); + return true; + } + + unique_ptr pop() + { + if (_count == 0) + return nullptr; + + _last = (_last + 1) % _size; + auto item = std::move(_data[_last]); + --_count; + return item; + } + + unique_ptr wait(atomic &waitFlag) + { + unique_lock lock(_mtx); + + _lock.wait(lock, [&] + { return _count > 0 || !waitFlag; }); + + if (!waitFlag) + return nullptr; + + _last = (_last + 1) % _size; + auto item = std::move(_data[_last]); + --_count; + return item; + } + + void clear() + { + _data = std::make_unique[]>(_size); + _first = 0; + _last = 0; + _count = 0; + } + + void notify() + { + _lock.notify_all(); + } + + uint16_t count() { return _count; } + +private: + uint16_t _size; + unique_ptr[]> _data; + uint16_t _first; + uint16_t _last; + atomic _count; + mutex _mtx; + condition_variable _lock; +}; + +#endif /* SRC_STRUCT_ATOMIC_QUEUE */ diff --git a/src/struct/message.h b/src/struct/message.h new file mode 100644 index 0000000..dd8dd6e --- /dev/null +++ b/src/struct/message.h @@ -0,0 +1,43 @@ +#ifndef SRC_STRUCT_MESSAGE +#define SRC_STRUCT_MESSAGE + +#include +#include +#include + +#define OFFSET_AUDIO_FORMAT 0 + +class Message +{ +public: + Message(uint8_t *data, uint32_t data_length, uint32_t offset) : _data(data), _length(data_length), _offset(offset) + { + } + + ~Message() + { + if (_data) + { + free(_data); + _data = nullptr; + } + } + + int getInt(uint32_t offset) const + { + int result = 0; + if (_length - sizeof(int) >= offset) + memcpy(&result, _data + offset, sizeof(int)); + return result; + } + + uint8_t *data() const { return _data + _offset; } + uint32_t length() const { return _length - _offset; } + +private: + uint8_t *_data; + uint32_t _length; + uint32_t _offset; +}; + +#endif /* SRC_STRUCT_MESSAGE */ diff --git a/src/struct/raw-queue.cpp b/src/struct/raw-queue.cpp deleted file mode 100644 index 98978bc..0000000 --- a/src/struct/raw-queue.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "raw_queue.h" - -RawQueue::RawQueue(uint16_t capacity) - : _buffer(capacity), _head(0), _tail(0), _size(0), _capacity(capacity) -{ -} - -RawQueue::~RawQueue() -{ - clear(); -} - -bool RawQueue::push(uint8_t *data, int offset, int size) -{ - std::lock_guard lock(_mutex); - if (_size == _buffer.size()) - { - free(data); - return false; // queue full - } - - _buffer[_tail] = RawEntry{data, offset, size}; - _tail = (_tail + 1) % _capacity; - _size++; - - _condition.notify_one(); - return true; -} - -RawEntry RawQueue::wait(const std::atomic &reading) -{ - std::unique_lock lock(_mutex); - _condition.wait(lock, [&] - { return !reading.load() || _size > 0; }); - - if (!reading || _size == 0) - return RawEntry{nullptr, 0, 0}; - - RawEntry entry = _buffer[_head]; - _head = (_head + 1) % _capacity; - _size--; - return entry; -} - -void RawQueue::clear() -{ - std::lock_guard lock(_mutex); - - // Free any remaining buffers - while (_size > 0) - { - RawEntry &e = _buffer[_head]; - if (e.data) - { - free(e.data); - e.data = nullptr; - } - _head = (_head + 1) % _capacity; - _size--; - } - - // Reset indices - _head = _tail = 0; -} - -void RawQueue::notify() -{ - _condition.notify_all(); -} diff --git a/src/struct/raw_queue.h b/src/struct/raw_queue.h deleted file mode 100644 index b9b932d..0000000 --- a/src/struct/raw_queue.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef SRC_RAW_QUEUE -#define SRC_RAW_QUEUE - -#include -#include -#include -#include -#include - -struct RawEntry -{ - uint8_t *data; - int offset; - int size; -}; - -// Single entry: raw buffer pointer + metadata -class RawQueue -{ -public: - RawQueue(uint16_t capacity = 256); - ~RawQueue(); - - // Non-blocking push: returns false if full - bool push(uint8_t *data, int offset, int size); - - // Blocks until an entry is available or reader_active == false - RawEntry wait(const std::atomic &reading); - - // Clears the queue and frees any pending buffers - void clear(); - - // Unlock queus - void notify(); - -private: - std::vector _buffer; - uint16_t _head; - uint16_t _tail; - uint16_t _size; - uint16_t _capacity; - std::mutex _mutex; - std::condition_variable _condition; -}; - -#endif \ No newline at end of file diff --git a/src/struct/video_buffer.cpp b/src/struct/video_buffer.cpp deleted file mode 100644 index f8a7d51..0000000 --- a/src/struct/video_buffer.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "video_buffer.h" - -extern "C" -{ -#include -} - -#include -#include - -#include "error.h" - -VideoBuffer::VideoBuffer() - : _width(0), _height(0) -{ -} - -// Allocate two YUV420P frames for double buffering and initialize to black -Error VideoBuffer::allocate(uint16_t width, uint16_t height) -{ - _width = width; - _height = height; - - deallocate(); - reset(); - - Error e; - for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i) - { - // Allocate AVFrame - _frames[i] = av_frame_alloc(); - if (e.null(_frames[i], "Failed to allocate AVFrame")) - break; - _frames[i]->format = AV_PIX_FMT_YUV420P; - _frames[i]->width = width; - _frames[i]->height = height; - // Allocate data buffer with 32 byte allingment - if (e.avFail(av_frame_get_buffer(_frames[i], 32), "Failed to allocate AVFrame buffer")) - break; - // Set Y plane to black (0) - memset(_frames[i]->data[0], 0, _frames[i]->linesize[0] * height); - // Set U plane to 128 (neutral) - memset(_frames[i]->data[1], 128, _frames[i]->linesize[1] * (height / 2)); - // Set V plane to 128 (neutral) - memset(_frames[i]->data[2], 128, _frames[i]->linesize[2] * (height / 2)); - } - return e; -} - -VideoBuffer::~VideoBuffer() -{ - deallocate(); -} - -void VideoBuffer::deallocate() -{ - for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i) - { - if (_frames[i]) - { - // Free the frame itself - av_frame_free(&_frames[i]); - // Clear - _frames[i] = nullptr; - } - } -} - -bool VideoBuffer::getLatest(AVFrame **frame, uint32_t *id) -{ - _reading.store(_latest.load()); - int index = _reading.load(); - if (index < 0) - return false; - *frame = _frames[index]; - *id = _ids[index]; - return true; -} - -void VideoBuffer::consumeLatest() -{ - _reading.store(-1); -} - -const AVFrame *VideoBuffer::writeFrame(uint32_t id) -{ - int index = _writing.load(); - while (index == _reading.load() || index == _latest.load()) - { - index = (index + 1) % BUFFER_VIDEO_FRAMES; - } - _writing.store(index); - _ids[index] = id; - return _frames[index]; -} - -void VideoBuffer::commitFrame() -{ - _latest.store(_writing.load()); -} - -void VideoBuffer::reset() -{ - _writing.store(0); - _reading.store(-1); - _latest.store(-1); - for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; i++) - { - _ids[i] = 0; - } -} diff --git a/src/struct/video_buffer.h b/src/struct/video_buffer.h index e0c0e2f..bf88e93 100644 --- a/src/struct/video_buffer.h +++ b/src/struct/video_buffer.h @@ -1,5 +1,5 @@ -#ifndef SRC_VIDEO_BUFFER -#define SRC_VIDEO_BUFFER +#ifndef SRC_STRUCT_VIDEO_BUFFER +#define SRC_STRUCT_VIDEO_BUFFER extern "C" { @@ -7,31 +7,75 @@ extern "C" } #include - -#include "helper/error.h" +#include #define BUFFER_VIDEO_FRAMES 3 class VideoBuffer { public: - VideoBuffer(); - ~VideoBuffer(); - - Error allocate(uint16_t width, uint16_t height); - uint16_t width() const { return _width; }; - uint16_t height() const { return _height; }; - void reset(); - bool getLatest(AVFrame **frame, uint32_t *id); - void consumeLatest(); - const AVFrame *writeFrame(uint32_t id); - void commitFrame(); + VideoBuffer() + { + _writing.store(0); + _reading.store(-1); + _latest.store(-1); + for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i) + { + _ids[i] = 0; + _frames[i] = av_frame_alloc(); + if (!_frames[i]) + { + throw std::runtime_error("Failed to allocate AVFrame"); + } + } + } + + ~VideoBuffer() + { + for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i) + { + if (_frames[i]) + { + av_frame_free(&_frames[i]); + _frames[i] = nullptr; + } + } + } + + bool latest(AVFrame **frame, uint32_t *id) + { + _reading.store(_latest.load()); + int index = _reading.load(); + if (index < 0) + return false; + *frame = _frames[index]; + *id = _ids[index]; + return true; + } + + void consume() + { + _reading.store(-1); + } + + AVFrame *write(uint32_t id) + { + int index = _writing.load(); + while (index == _reading.load() || index == _latest.load()) + { + index = (index + 1) % BUFFER_VIDEO_FRAMES; + } + _writing.store(index); + _ids[index] = id; + return _frames[index]; + } + + void commit() + { + _latest.store(_writing.load()); + } private: - void deallocate(); - - uint16_t _width; - uint16_t _height; std::atomic _latest; std::atomic _reading; std::atomic _writing; @@ -39,4 +83,4 @@ private: uint32_t _ids[BUFFER_VIDEO_FRAMES]; }; -#endif /* SRC_VIDEO_BUFFER */ +#endif /* SRC_STRUCT_VIDEO_BUFFER */