diff --git a/settings.txt b/settings.txt index fee4fc4..04f5f75 100644 --- a/settings.txt +++ b/settings.txt @@ -16,9 +16,9 @@ # Application drawing target framerate. This can responsiveness. # If the setting is lower than source-fps the framse will be dropped -# If the setting is not multiple of source-fps can still cause frame drops cause out-of-sync +# If the setting is not multiple of source-fps can still cause frame drops cause out-of-sync # source images and drawing times -#fps = 50 +#fps = 50 # Application window mode # 0 - windowed @@ -29,8 +29,15 @@ # Show mouse pointer #cursor = false -# Enable generic console logging. Can reduce performace -#logging = false +# Set log level. Can reduce performace +# 0 - No logging +# 1 - Errors +# 2 - Warnings +# 3 - Information +# 4 - Debug +# 5 - Verbose +# 6 - Protocol dump +#log-level = 2 ############################################################################## # 2.Device configurations @@ -66,7 +73,7 @@ # 1 - Car # 2 - Box # 3 - Phone -#mic-type = 1 +#mic-type = 1 # Requested image DPI (Android auto scale) #android-dpi = 120 @@ -80,7 +87,7 @@ # to go to "Developer settings". Tap "Video Resolution" and select "Allow to car and phone to negotiate" # 1 - 480p => 800x480 # 2 - 720p => 1280x720 -# 3 - 1080p => 1920x1080 +# 3 - 1080p => 1920x1080 #android-resolution = 1 # Media delay for android auto @@ -93,7 +100,7 @@ # Font size for messgaes on screen. Set to 0 is you do not want any #font-size = 30 -# Enable vsync. This reduce tearing but can dramatically affect performance on low end systems +# Enable vsync. This reduce tearing but can dramatically affect performance on low end systems #vsync = false # Prefer HW decoding. @@ -106,6 +113,9 @@ # 0 - disabled, enter number of frames to request (find minimal value that get all screen updates) #force-redraw = 0 +# Skip several frames on processing events if FPS drops. Can increase responsivenes and reduce lag. +#draw-skip-events = 0 + # Corrects aspect of UI #aspect-correction = 1 @@ -119,16 +129,20 @@ # Select faster method of scaling image to window size (nearest) or better quality (linear) #fast-render-scale = false -# USB reading number of async requests and buffer size -#async-usb-calls = 8 -#usb-buffer-size = 131072 +# USB read pipeline tuning, number of libusb bulk transfers kept in flight and size of each USB transfer +# Increase amount of async-usb-calls if you have issues with audio and video lagging behind +#async-usb-calls = 16 +#usb-buffer-size = 2048 + +# Number of buffered USB data slots queued for processing +#usb-buffer = 64 # Size of video and audio buffers. Increase if you see artifacts #video-buffer-size = 64 #audio-buffer-size = 64 -# Audio delay for music and calls. Fill the buffer to this value before start playing. -# Increase if you hear audio artifacts or tearing. Should be less than audio-buffer-size +# Audio delay for music and calls. Fill the buffer to this value before start playing. +# Increase if you hear audio artifacts or tearing. Should be less than audio-buffer-size #audio-buffer-wait = 2 #audio-buffer-wait-call = 6 @@ -139,7 +153,7 @@ # Basic number of audio sample frames in the output buffer that is scaled by sample rate. # This controls the size of the audio buffer used by the audio device. -# The buffer time is ~ 1/10 buffer size in ms, depending on sample rate e.g. 2048 = ~200ms +# The buffer time is ~ 1/10 buffer size in ms, depending on sample rate e.g. 2048 = ~200ms # Preferably use power of 2 for the values or check your audio driver documentation #audio-buffer-samples = 512 @@ -150,10 +164,10 @@ # pipewire #audio-driver = -# 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 -# +# 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 +# # #!/bin/bash # MyApp & # echo $! > app.pid @@ -164,15 +178,15 @@ # rm app.pid # fi # -#on-connect-script = -#on-disconnect-script = +#on-connect-script = +#on-disconnect-script = ############################################################################## # 4. Key mapping ############################################################################## # Map keys to control -# If you set logging true you and press keys you will see there codes in logs +# If you set log-level and press keys you will see their codes in logs #key-siri = 115 # S #key-nightmode-on = 122 # Z #key-nightmode-off = 120 # X @@ -200,23 +214,14 @@ # Path for named pipe that keys are going to be listen on to. # If empty pipe listening is disables. # Example /tmp/fastcarplay_pipe -# Keys are byte value [1..255] -# They are mapped to keys defined above -#key-pipe-path = +# Keys are byte value [1..255] +# They are mapped to keys defined above +#key-pipe-path = ############################################################################## -# 4.Debug +# 5.Debug ############################################################################## -# Protocol debug level. Works only on builds only with PROTOCOL_DEBUG flag set. -# Add -DPROTOCOL_DEBUG to CXXCOMMON to enable protocol debugging and rebuild. -# 0 - nothing -# 1 - unknown commands -# 2 - all commands except data streams -# 3 - include outgoing commands -# 4 - log everything -# protocol-debug = 0 - # Enable FFMPEG AV_CODEC_FLAG_LOW_DELAY for HW decoder. # Force low delay. #decode-low-delay = true diff --git a/src/application.cpp b/src/application.cpp index d482a51..62467d6 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -3,15 +3,13 @@ #include #include "struct/video_buffer.h" +#include "common/logger.h" #include "settings.h" #include "interface.h" #include "decoder.h" #include "pcm_audio.h" -#define EVT_STATUS_OFFSET 0 -#define EVT_PHONE_OFFSET 1 - static KeySetting *keyMap[] = { &Settings::keySiri, &Settings::keyNightOn, @@ -42,7 +40,7 @@ Application::Application(/* args */) : _window(nullptr), _renderer(nullptr), _active(true) { - std::cout << "[App] Creating" << std::endl; + log_v("Creating"); if (!setAudioDriver()) throw std::runtime_error("Unsupported audio driver " + std::string(Settings::audioDriver.value)); @@ -63,26 +61,24 @@ Application::Application(/* args */) : _window(nullptr), throw std::runtime_error(std::string("SDL get display mode failed > ") + SDL_GetError()); } - std::cout << "[App] SDL screen: " - << _displayMode.w << "x" << _displayMode.h << "@" << _displayMode.refresh_rate - << ", audio: " << SDL_GetCurrentAudioDriver() << std::endl; + log_i("SDL screen %dx%d@%d, audio driver %s", _displayMode.w, _displayMode.h, _displayMode.refresh_rate, SDL_GetCurrentAudioDriver()); } Application::~Application() { - std::cout << "[App] Destroying" << std::endl; + log_v("Destroying"); if (_renderer != nullptr) SDL_DestroyRenderer(_renderer); if (_window != nullptr) SDL_DestroyWindow(_window); TTF_Quit(); SDL_Quit(); - std::cout << "[App] Finished" << std::endl; + log_d("Finished"); } void Application::start(const char *title) { - std::cout << "[App] Initialising" << std::endl; + log_d("Initialising"); // Create SDL window centered on screen SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, Settings::fastScale ? "nearest" : "best"); @@ -118,20 +114,14 @@ void Application::start(const char *title) 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; + log_i("Renderer %s (%s, %s)", rendererInfo.name, + ((rendererInfo.flags & SDL_RENDERER_ACCELERATED) ? "accelerated" : "software"), + ((rendererInfo.flags & SDL_RENDERER_PRESENTVSYNC) ? "vsync" : "no-vsync")); } - // Register additional events - _evtBase = SDL_RegisterEvents(2); - if (_evtBase == (Uint32)-1) - throw std::runtime_error(std::string("Can't register custom events > ") + SDL_GetError()); - - std::cout << "[App] Starting" << std::endl; + log_v("Starting"); loop(); - std::cout << "[App] Stopped" << std::endl; + log_v("Stopped"); } bool Application::setAudioDriver() @@ -159,7 +149,7 @@ int Application::processKey(SDL_Keysym key) return keyMap[i]->key; } } - std::cout << "[App] Unmapped key " << key.sym << std::endl; + log_w("Unmapped key %d", key.sym); return 0; } @@ -202,31 +192,16 @@ bool Application::processSystemEvent(const SDL_Event &e) } } - if (e.type == (_evtBase + EVT_STATUS_OFFSET)) - { - _state.deviceStatus = e.user.code; - return true; - } - - if (e.type == (_evtBase + EVT_PHONE_OFFSET)) - { - _state.connected = e.user.code != 0; - _state.frameRendered = false; - _state.dirty = true; - _state.requestFrame = 0; - _state.flushBuffers = _state.connected; - return true; - } - return false; } -bool Application::processFrameEvents(Protocol &protocol, Renderer &renderer) +bool Application::processFrameEvents(AtomicQueue &queue, Renderer &renderer) { bool result = false; SDL_Event e; - int motionX = -1; - int motionY = -1; + bool motion = false; + int motionX = 0; + int motionY = 0; int downX = -1; int downY = -1; int upX = -1; @@ -262,6 +237,7 @@ bool Application::processFrameEvents(Protocol &protocol, Renderer &renderer) break; motionX = e.motion.x; motionY = e.motion.y; + motion = true; break; } case SDL_KEYDOWN: @@ -269,7 +245,7 @@ bool Application::processFrameEvents(Protocol &protocol, Renderer &renderer) int key = processKey(e.key.keysym); if (key > 0) { - protocol.send(Command::Control(key)); + queue.pushDiscard(Message::Control(key)); result = true; } break; @@ -278,7 +254,7 @@ bool Application::processFrameEvents(Protocol &protocol, Renderer &renderer) { if (e.key.keysym.sym == Settings::keyEnter) { - protocol.send(Command::Control(Settings::keyEnterUp.key)); + queue.pushDiscard(Message::Control(Settings::keyEnterUp.key)); result = true; } break; @@ -286,14 +262,14 @@ bool Application::processFrameEvents(Protocol &protocol, Renderer &renderer) } } - if (_state.frameRendered && (downX >= 0 || upX >= 0 || motionX >= 0)) + if (_state.frameRendered && (downX >= 0 || upX >= 0 || motion)) { if (downX >= 0) - protocol.send(Command::Click(renderer.xScale * downX / _width, renderer.yScale * downY / _height, true)); - if (motionX >= 0) - protocol.send(Command::Move(renderer.xScale * motionX / _width, renderer.yScale * motionY / _height)); + queue.pushDiscard(Message::Click(renderer.xScale * downX / _width, renderer.yScale * downY / _height, true)); + if (motion) + queue.pushDiscard(Message::Move(renderer.xScale * motionX / _width, renderer.yScale * motionY / _height)); if (upX >= 0) - protocol.send(Command::Click(renderer.xScale * upX / _width, renderer.yScale * upY / _height, false)); + queue.pushDiscard(Message::Click(renderer.xScale * upX / _width, renderer.yScale * upY / _height, false)); } return result; @@ -319,25 +295,43 @@ void Application::loop() interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN); VideoBuffer videoBuffer; - Protocol protocol(Settings::width, Settings::height, Settings::sourceFps); + Connector protocol; Decoder decoder; - PcmAudio audioMain("Main"), audioAux("Aux"); + PcmAudio audioMain("main"), audioAux("aux"); - decoder.start(&protocol.videoData, &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(_evtBase + EVT_STATUS_OFFSET, _evtBase + EVT_PHONE_OFFSET); + protocol.start(&_state.deviceStatus); - std::cout << "[App] Loop" << std::endl; + log_v("Loop"); Uint32 frameStart = SDL_GetTicks(); AVFrame *frame = nullptr; uint32_t frameid = 0; uint32_t latestFrameid = 0; uint32_t frameTargetTime = Settings::fps > 0 ? 1000 / Settings::fps : 1000; - int frameDelay = 0; + int skipEvents = 0; while (_active) { - if (_state.connected && _state.showVideo) + if (_state.deviceStatus != _state.previousdeviceStatus) + { + // On connect/disconnect + if (_state.previousdeviceStatus == PROTOCOL_STATUS_CONNECTED || _state.deviceStatus == PROTOCOL_STATUS_CONNECTED) + { + _state.frameRendered = false; + _state.dirty = true; + _state.requestFrame = 0; + } + // On connect + if (_state.deviceStatus == PROTOCOL_STATUS_CONNECTED) + { + decoder.flush(); + videoBuffer.reset(); + } + _state.previousdeviceStatus = _state.deviceStatus; + } + + if (_state.deviceStatus == PROTOCOL_STATUS_CONNECTED && _state.showVideo) { if (videoBuffer.latest(&frame, &frameid) && frame && (frameid != latestFrameid || _state.dirty)) { @@ -345,7 +339,7 @@ void Application::loop() { _state.frameRendered = true; if (!_state.dirty && (frameid != latestFrameid + 1)) - std::cout << "[App] Frame drop " << frameid - latestFrameid - 1 << " on " << frameid << std::endl; + log_d("Frame drop %d on %d", frameid - latestFrameid - 1, frameid); latestFrameid = frameid; _state.dirty = false; } @@ -356,9 +350,9 @@ void Application::loop() { if (++_state.requestFrame % Settings::forceRedraw == 0) { - std::cout << "[App] Request screen update" << std::endl; - protocol.send(Command::Control(BTN_SCREEN_REFRESH)); - if (_state.requestFrame >= Settings::forceRedraw * 3) + log_d("Request screen update"); + protocol.send(Message::Control(BTN_SCREEN_REFRESH)); + if (_state.requestFrame >= Settings::forceRedraw * REDRAW_REQUEST) _state.requestFrame = 0; } } @@ -366,7 +360,7 @@ void Application::loop() if (!_state.frameRendered || !_state.showVideo) { - interface.drawHome(_state.dirty, _state.connected ? PROTOCOL_STATUS_CONNECTED : _state.deviceStatus); + interface.drawHome(_state.dirty, _state.deviceStatus); _state.dirty = false; SDL_Event e; while (SDL_PollEvent(&e)) @@ -374,23 +368,20 @@ void Application::loop() } else { - if (processFrameEvents(protocol, interface) && Settings::forceRedraw > 0) + if (latestFrameid < 0 || latestFrameid == videoBuffer.latestId() || ++skipEvents > Settings::eventsSkip) { - _state.requestFrame = 1; + skipEvents = 0; + if (processFrameEvents(protocol.writeQueue, interface) && Settings::forceRedraw > 0) + { + _state.requestFrame = 1; + } } } - if (_state.flushBuffers) - { - _state.flushBuffers = false; - decoder.flush(); - videoBuffer.reset(); - } - if (_active && !Settings::vsync) { Uint32 frameEnd = SDL_GetTicks(); - frameDelay = frameTargetTime - (frameEnd - frameStart); + int frameDelay = frameTargetTime - (frameEnd - frameStart); if (latestFrameid > 0 && latestFrameid != videoBuffer.latestId()) { SDL_Delay(1); diff --git a/src/application.h b/src/application.h index 8127350..60a819b 100644 --- a/src/application.h +++ b/src/application.h @@ -3,11 +3,13 @@ #include -#include "helper/protocol_const.h" +#include "protocol/protocol_const.h" -#include "protocol.h" +#include "connector.h" #include "renderer.h" +#define REDRAW_REQUEST 5 + class Application { public: @@ -19,28 +21,26 @@ public: private: struct State { - bool connected = false; bool dirty = false; bool frameRendered = false; int requestFrame = 0; bool showVideo = true; bool fullscreen = false; bool mouseDown = false; - int8_t deviceStatus = PROTOCOL_STATUS_INITIALISING; - bool flushBuffers = false; + int8_t previousdeviceStatus = PROTOCOL_STATUS_INITIALISING; + atomic deviceStatus = PROTOCOL_STATUS_INITIALISING; }; bool setAudioDriver(); int processKey(SDL_Keysym key); bool processSystemEvent(const SDL_Event &e); - bool processFrameEvents(Protocol &protocol, Renderer &renderer); + bool processFrameEvents(AtomicQueue &queue, Renderer &renderer); void loop(); SDL_Window *_window; SDL_Renderer *_renderer; bool _active; - Uint32 _evtBase; SDL_DisplayMode _displayMode; State _state; int _width; diff --git a/src/common/functions.h b/src/common/functions.h new file mode 100644 index 0000000..142c37f --- /dev/null +++ b/src/common/functions.h @@ -0,0 +1,49 @@ +#ifndef SRC_COMMON_FUNCTIONS +#define SRC_COMMON_FUNCTIONS + +#include +#include +#include +#include +#include + +#include + +#include "common/threading.h" + +extern "C" +{ +#include +} + +inline void execute(const char *path) +{ + if (!path || *path == '\0') + { + throw std::invalid_argument("Program path cannot be empty"); + } + + std::system(path); +} + +inline const std::string avErrorText(int code) +{ + char buf[AV_ERROR_MAX_STRING_SIZE] = {0}; + if (av_strerror(code, buf, sizeof(buf)) == 0) + return buf; + return "Unknown error"; +} + +inline void pushEvent(Uint32 evt, int code) +{ + if (evt == (Uint32)-1) + return; + SDL_Event event; + SDL_memset(&event, 0, sizeof(event)); + event.type = evt; + event.user.type = evt; + event.user.code = code; + SDL_PushEvent(&event); +} + +#endif /* SRC_COMMON_FUNCTIONS */ diff --git a/src/common/logger.cpp b/src/common/logger.cpp new file mode 100644 index 0000000..d08532d --- /dev/null +++ b/src/common/logger.cpp @@ -0,0 +1,110 @@ +#include "logger.h" + +#ifndef DISBALE_LOG + +#include +#include + +Logger::Logger() + : _level(Level::None) +{ +} + +Logger &Logger::instance() +{ + static Logger logger; + return logger; +} + +void Logger::setLevel(int level) +{ + if (level < static_cast(Level::None)) + { + _level = Level::None; + return; + } + + if (level > static_cast(Level::Verbose)) + { + _level = Level::Verbose; + return; + } + + _level = static_cast(level); +} + +bool Logger::enabled(Level msgLevel) const +{ + return static_cast(msgLevel) <= static_cast(_level); +} + +void Logger::log(Level level, const char *context, const char *fmt, ...) +{ + std::va_list args; + va_start(args, fmt); + vlog(level, context, fmt, args); + va_end(args); +} + +void Logger::vlog(Level level, const char *context, const char *fmt, std::va_list args) +{ + char timebuf[32] = "--:--:--"; + const std::time_t now = std::time(nullptr); + const std::tm *tmv = std::localtime(&now); + if (tmv != nullptr) + std::strftime(timebuf, sizeof(timebuf), "%H:%M:%S", tmv); + + char message[LOGGER_MESSAGE_SIZE]; + std::vsnprintf(message, LOGGER_MESSAGE_SIZE, fmt, args); + + std::fprintf( + (level <= Level::Warning) ? stderr : stdout, + "%s%s %s[%s] %s%s\n", + LOGGER_COLOR_GRAY, + timebuf, + levelColor(level), + className(context).c_str(), + message, + LOGGER_COLOR_RESET); +} + +const char *Logger::levelColor(Level level) +{ + switch (level) + { + case Level::Error: + return LOGGER_COLOR_RED; + case Level::Warning: + return LOGGER_COLOR_YELLOW; + case Level::Info: + return LOGGER_COLOR_CYAN; + case Level::Debug: + return LOGGER_COLOR_RESET; + case Level::Verbose: + return LOGGER_COLOR_GRAY; + case Level::Protocol: + return LOGGER_COLOR_GRAY; + default: + return LOGGER_COLOR_RESET; + } +} + +std::string Logger::className(const char *context) +{ + if (!context || *context == '\0') + return "Global"; + + std::string signature(context); + const std::size_t scopePos = signature.rfind("::"); + if (scopePos == std::string::npos) + return "Global"; + + std::string scope = signature.substr(0, scopePos); + const std::size_t spacePos = scope.find_last_of(" \t"); + if (spacePos != std::string::npos) + scope = scope.substr(spacePos + 1); + + return scope.empty() ? "Global" : scope; +} + +#endif diff --git a/src/common/logger.h b/src/common/logger.h new file mode 100644 index 0000000..1068a1f --- /dev/null +++ b/src/common/logger.h @@ -0,0 +1,114 @@ +#ifndef SRC_HELPER_LOGGER +#define SRC_HELPER_LOGGER + +#include +#include +#include +#include + +#define LOGGER_COLOR_RESET "\033[0m" +#define LOGGER_COLOR_GRAY "\033[90m" +#define LOGGER_COLOR_RED "\033[31m" +#define LOGGER_COLOR_YELLOW "\033[33m" +#define LOGGER_COLOR_CYAN "\033[36m" +#define LOGGER_COLOR_WHITE "\033[37m" + +#define LOGGER_MESSAGE_SIZE 2048 + +#ifdef DISBALE_LOG + +#define set_log_level(...) (false) +#define log_e(...) do { } while (0) +#define log_w(...) do { } while (0) +#define log_i(...) do { } while (0) +#define log_d(...) do { } while (0) +#define log_v(...) do { } while (0) +#define log_p(...) do { } while (0) + +#else + +#if defined(_MSC_VER) +#define LOGGER_CONTEXT __FUNCSIG__ +#elif defined(__GNUC__) || defined(__clang__) +#define LOGGER_CONTEXT __PRETTY_FUNCTION__ +#else +#define LOGGER_CONTEXT __func__ +#endif + +#define set_log_level(levelValue) Logger::instance().setLevel(levelValue) + +#define log_e(...) \ + do { \ + if (Logger::instance().enabled(Logger::Level::Error)) \ + Logger::instance().log(Logger::Level::Error, LOGGER_CONTEXT, __VA_ARGS__); \ + } while (0) + +#define log_w(...) \ + do { \ + if (Logger::instance().enabled(Logger::Level::Warning)) \ + Logger::instance().log(Logger::Level::Warning, LOGGER_CONTEXT, __VA_ARGS__); \ + } while (0) + +#define log_i(...) \ + do { \ + if (Logger::instance().enabled(Logger::Level::Info)) \ + Logger::instance().log(Logger::Level::Info, LOGGER_CONTEXT, __VA_ARGS__); \ + } while (0) + +#define log_d(...) \ + do { \ + if (Logger::instance().enabled(Logger::Level::Debug)) \ + Logger::instance().log(Logger::Level::Debug, LOGGER_CONTEXT, __VA_ARGS__); \ + } while (0) + +#define log_v(...) \ + do { \ + if (Logger::instance().enabled(Logger::Level::Verbose)) \ + Logger::instance().log(Logger::Level::Verbose, LOGGER_CONTEXT, __VA_ARGS__); \ + } while (0) + +#define log_p(...) \ + do { \ + if (Logger::instance().enabled(Logger::Level::Protocol)) \ + Logger::instance().log(Logger::Level::Verbose, LOGGER_CONTEXT, __VA_ARGS__); \ + } while (0) + +class Logger +{ +public: + enum class Level : uint8_t + { + None = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4, + Verbose = 5, + Protocol = 6, + }; + + static Logger& instance(); + + void setLevel(int level); + bool enabled(Level msgLevel) const; + + void log(Level level, const char* context, const char* fmt, ...); + +private: + Logger(); + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + + void vlog(Level level, const char* context, const char* fmt, std::va_list args); + + static const char* levelColor(Level level); + static std::string className(const char* context); + +private: + mutable std::mutex _mutex; + Level _level; +}; + +#endif + +#endif /* SRC_HELPER_LOGGER */ diff --git a/src/helper/settings_base.h b/src/common/settings_base.h similarity index 100% rename from src/helper/settings_base.h rename to src/common/settings_base.h diff --git a/src/common/status.h b/src/common/status.h new file mode 100644 index 0000000..3e42f77 --- /dev/null +++ b/src/common/status.h @@ -0,0 +1,64 @@ +#ifndef SRC_STRUCT_STATUS +#define SRC_STRUCT_STATUS + +#include +#include + +class Status +{ +public: + static Status Success() + { + return Status(); + } + + static Status Error(const char* format, ...) + { + std::va_list args; + va_start(args, format); + Status status(format, args); + va_end(args); + + return status; + } + + bool succeed() const + { + return _result; + } + + bool failed() const + { + return !_result; + } + + explicit operator bool() const + { + return _result; + } + + const char* error() const + { + return _message; + } + +private: + static constexpr int MESSAGE_SIZE = 256; + + Status() + : _result(true) + { + _message[0] = '\0'; + } + + Status(const char* format, std::va_list args) + : _result(false) + { + std::vsnprintf(_message, sizeof(_message), format ? format : "", args); + } + + bool _result; + char _message[MESSAGE_SIZE]; +}; + +#endif /* SRC_STRUCT_STATUS */ diff --git a/src/common/threading.h b/src/common/threading.h new file mode 100644 index 0000000..fa9d7de --- /dev/null +++ b/src/common/threading.h @@ -0,0 +1,24 @@ +#ifndef SRC_HELPER_THREADING +#define SRC_HELPER_THREADING + +#if defined(__linux__) || defined(__APPLE__) +#include +#endif + +extern "C" +{ +#include +} + +inline void setThreadName(const char *name) +{ +#if defined(__linux__) + pthread_setname_np(pthread_self(), name); // Linux: OK (limit 16 chars including null) +#elif defined(__APPLE__) + pthread_setname_np(name); // macOS: only current thread, OK +#else + (void)name; // suppress unused warning +#endif +} + +#endif /* SRC_HELPER_THREADING */ diff --git a/src/connector.cpp b/src/connector.cpp index afcc6d1..4fa8708 100644 --- a/src/connector.cpp +++ b/src/connector.cpp @@ -4,36 +4,45 @@ #include #include -#include "struct/message.h" -#include "helper/protocol_const.h" -#include "helper/functions.h" +#include "protocol/message.h" +#include "common/logger.h" +#include "protocol/protocol_const.h" +#include "common/functions.h" #include "settings.h" - Connector::Connector() - : _usbBuffer(Settings::usbQueue * 2, Settings::usbBufferSize) + : writeQueue(WRITE_QUEUE_SIZE), + videoStream(Settings::videoQueue), + audioStreamMain(Settings::audioQueue), + audioStreamAux(Settings::audioQueue), + _recorder(Settings::audioQueue), + _cipher(nullptr), + _context(nullptr), + _active(false), + _connected(false), + _ecnrypt(false), + _state(PROTOCOL_STATUS_INITIALISING), + _failCount(0), + _nodeviceCount(0), + _statusHandler(nullptr) { - try - { - _cipher = new AESCipher(ENCRYPTION_BASE); - } - catch (...) - { - _cipher = nullptr; - } - - _state = PROTOCOL_STATUS_INITIALISING; int result = libusb_init(&_context); if (result < 0) throw std::runtime_error(std::string("Can't initialise USB: ") + libusb_error_name(result)); - _usbTransfers = Settings::usbQueue; - if (_usbTransfers > MAX_USB_REQUESTS) - _usbTransfers = MAX_USB_REQUESTS; - - for (int i = 0; i < _usbTransfers; i++) + try { - _usbContext[i].transfer = libusb_alloc_transfer(0); + _cipher = new AESCipher(ENCRYPTION_BASE); + } + catch (const std::exception &e) + { + _cipher = nullptr; + log_w("Can't initialise cypher for encryption > %s", e.what()); + } + catch (...) + { + _cipher = nullptr; + log_w("Can't initialise cypher for encryption > Unknown error"); } } @@ -47,32 +56,6 @@ Connector::~Connector() _cipher = nullptr; } - for (int i = 0; i < _usbTransfers; i++) - { - if (_usbContext[i].transfer) - { - timeval timeout{0, 100000}; - libusb_cancel_transfer(_usbContext[i].transfer); - libusb_handle_events_timeout_completed(_context, &timeout, nullptr); - } - } - - for (int i = 0; i < _usbTransfers; i++) - { - if (_usbContext[i].transfer) - { - libusb_free_transfer(_usbContext[i].transfer); - _usbContext[i].transfer = nullptr; - } - } - - if (_device) - { - libusb_release_interface(_device, 0); - libusb_close(_device); - _device = nullptr; - } - if (_context) { libusb_exit(_context); @@ -80,74 +63,176 @@ Connector::~Connector() } } -void Connector::start() +void Connector::start(atomic* statusHandler) { + _statusHandler = statusHandler; + if (_active) - stop(); + return; _active = true; - _write_thread = std::thread(&Connector::writeLoop, this); + _thread = std::thread(&Connector::mainLoop, this); } void Connector::stop() { if (!_active) return; + _active = false; - _queue.notify(); + writeQueue.notify(); state(PROTOCOL_STATUS_INITIALISING); - if (_write_thread.joinable()) - _write_thread.join(); + if (_thread.joinable()) + _thread.join(); + + _statusHandler = nullptr; } -bool Connector::connect(uint16_t vendor_id, uint16_t product_id) +void Connector::mainLoop() { - _device = libusb_open_device_with_vid_pid(_context, vendor_id, product_id); - if (!_device) + // Set thread name + setThreadName("usb-write"); + state(PROTOCOL_STATUS_LINKING); + + while (_active) { - std::cout << "[Connection] Failed to create device handle - no device" << std::endl; + libusb_device_handle *handler = libusb_open_device_with_vid_pid(_context, Settings::vendorid, Settings::productid); + if (handler) + { + uint8_t epIn = 0; + uint8_t epOut = 0; + int retry = 0; + libusb_device *device = nullptr; + _ecnrypt = false; + writeQueue.clear(); + + while (!device && retry++ < LINK_RETRY) + { + device = link(handler, &epIn, &epOut); + writeQueue.waitFor(_active, LINK_RETRY_TIMEOUT); + } + + if (device) + { + _connected = true; + state(PROTOCOL_STATUS_ONLINE); + log_i("Device connected %d:%d speed: %d", libusb_get_bus_number(device), libusb_get_device_address(device), libusb_get_device_speed(device)); + _reader.start(_context, handler, epIn, this); + onConnect(); + + writeLoop(handler, epOut); + + _connected = false; + onDisconnect(); + _reader.stop(); + } + + libusb_release_interface(handler, 0); + libusb_close(handler); + } state(PROTOCOL_STATUS_NO_DEVICE); - return false; + writeQueue.waitFor(_active, RECONNECT_TIMEOUT); } - - if (link()) - return true; - - libusb_close(_device); - _device = nullptr; - - return false; } -bool Connector::link() +void Connector::writeLoop(libusb_device_handle *handler, uint8_t ep) +{ + while (_active && _reader.active()) + { + std::unique_ptr message = writeQueue.pop(); + if (!message) + { + if (!writeQueue.waitFor(_active, PROTOCOL_HEARTBEAT_DELAY)) + break; + message = writeQueue.pop(); + } + + if (!message) + message = Message::HeartBeat(); + + if (!_active || !_reader.active()) + break; + + if (!message->allocated()) + continue; + + while (message->isMotion() && writeQueue.peek() && writeQueue.peek()->isMotion()) + { + message = writeQueue.pop(); + } + + if (_ecnrypt) + { + Status s = message->encrypt(_cipher); + if (s.failed()) + { + log_w("Message encryption failed > %s", s.error()); + continue; + } + } + + int transferred; + libusb_bulk_transfer(handler, ep, message->header(), message->headerSize(), &transferred, 0); + if (message->length() > 0) + libusb_bulk_transfer(handler, ep, message->data(), message->length(), &transferred, 0); + } +} + +libusb_device *Connector::link(libusb_device_handle *handler, uint8_t *epIn, uint8_t *epOut) { state(PROTOCOL_STATUS_LINKING); - if (linkFail(libusb_reset_device(_device), " Can't reset device")) - return false; + if (fail(libusb_reset_device(handler), " Can't reset device")) + return nullptr; - if (linkFail(libusb_set_configuration(_device, 1), "Can't set configuration")) - return false; + if (fail(libusb_set_configuration(handler, 1), "Can't set configuration")) + return nullptr; - if (linkFail(libusb_claim_interface(_device, 0), "Can't claim interface")) - return false; + if (fail(libusb_claim_interface(handler, 0), "Can't claim interface")) + return nullptr; - libusb_device *dev = libusb_get_device(_device); + libusb_device *device = libusb_get_device(handler); struct libusb_config_descriptor *config = nullptr; - if (linkFail(libusb_get_active_config_descriptor(dev, &config), "Can't get config")) - return false; + if (fail(libusb_get_active_config_descriptor(device, &config), "Can't get config")) + return nullptr; for (int i = 0; i < config->interface[0].altsetting[0].bNumEndpoints; i++) { const struct libusb_endpoint_descriptor *ep = &config->interface[0].altsetting[0].endpoint[i]; if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) - _endpoint_in = ep->bEndpointAddress; + *epIn = ep->bEndpointAddress; else - _endpoint_out = ep->bEndpointAddress; + *epOut = ep->bEndpointAddress; } libusb_free_config_descriptor(config); + return device; +} + +void Connector::setEncryption(bool enabled) +{ + if (!enabled) + { + _ecnrypt = false; + return; + } + + if (!_cipher) + { + log_w("Can't enable encryption > Cipher is not initialised"); + } + + log_i("Encryption enabled"); + _ecnrypt = true; +} + +bool Connector::fail(int status, const char *msg) +{ + if (status == 0) + return false; + log_w("%s > %s", msg, libusb_error_name(status)); + state(PROTOCOL_STATUS_ERROR); return true; } @@ -166,7 +251,8 @@ bool Connector::state(u_int8_t state) _nodeviceCount = 0; _failCount = 0; _state = state; - onStatus(state); + if (_statusHandler) + *_statusHandler = state; return true; } @@ -174,329 +260,162 @@ bool Connector::state(u_int8_t state) { _failCount = 0; _state = state; - onStatus(state); + if (_statusHandler) + *_statusHandler = state; + return true; + } + + if (state == PROTOCOL_STATUS_ONLINE && _state == PROTOCOL_STATUS_CONNECTED) + { + _state = state; + if (_statusHandler) + *_statusHandler = state; return true; } return false; } -bool Connector::linkFail(int status, const char *msg) +void Connector::onConnect() { - if (status == 0) - return false; - std::cout << "[Connection] " << msg << ": " << libusb_error_name(status) << std::endl; - state(PROTOCOL_STATUS_ERROR); - return true; + int syncTime = std::time(nullptr); + int drivePosition = Settings::leftDrive ? 0 : 1; // 0==left, 1==right + int nightMode = Settings::nightMode; // 0==day, 1==night, 2==auto + if (nightMode < 0 || nightMode > 2) + nightMode = 2; + int mic = 7; + if (Settings::micType == 2) + mic = 15; + if (Settings::micType == 3) + mic = 21; + + int width; + int height; + switch (Settings::androidMode) + { + default: + width = 800; + height = 480; + break; + case 2: + width = 1280; + height = 720; + break; + case 3: + width = 1920; + height = 1080; + break; + } + + if (Settings::width < Settings::height) + std::swap(width, height); + + float scale = std::min((float)width / Settings::width, (float)height / Settings::height); + width = Settings::width * scale; + height = Settings::height * scale; + + log_i("Requesting carplay %dx%d@%d, android auto %dx%d", Settings::width.value, Settings::height.value, Settings::fps.value, width, height); + + if (Settings::encryption) + { + if (_cipher) + send(Message::Encryption(_cipher->Seed())); + else + log_w("Can't request encryption > Cypher is not initalised"); + } + + if (Settings::dpi > 0) + send(Message::File("/tmp/screen_dpi", Settings::dpi)); + send(Message::File("/etc/android_work_mode", 1)); + send(Message::Init(Settings::width, Settings::height, Settings::fps)); + send(Message::String( + CMD_JSON_CONTROL, + "{\"syncTime\":%d,\"mediaDelay\":%d,\"drivePosition\":%d," + "\"androidAutoSizeW\":%d,\"androidAutoSizeH\":%d,\"HiCarConnectMode\":0," + "\"GNSSCapability\":7,\"DashboardInfo\":1,\"UseBTPhone\":0}", + syncTime, Settings::mediaDelay.value, drivePosition, width, height)); + + send(Message::String(CMD_DAYNIGHT, "{\"DayNightMode\":%d}", nightMode)); + + send(Message::File("/tmp/night_mode", nightMode)); + send(Message::File("/tmp/charge_mode", Settings::weakCharge ? 0 : 2)); // Weak charge 0, other 2 + send(Message::File("/etc/box_name", "CarPlay")); + send(Message::File("/tmp/hand_drive_mode", drivePosition)); + + send(Message::Control(mic)); + send(Message::Control(Settings::wifi5 ? 25 : 24)); + send(Message::Control(Settings::bluetoothAudio ? 22 : 23)); + if (Settings::autoconnect) + send(Message::Control(1002)); + + if (Settings::onConnect.value.length() > 1) + execute(Settings::onConnect.value.c_str()); } void Connector::onDisconnect() { - if (!_connected) - return; - std::cout << "[Connection] Device disconnected" << std::endl; - _connected = false; + _recorder.stop(); + if (Settings::onDisconnect.value.length() > 1) + execute(Settings::onDisconnect.value.c_str()); } -bool Connector::send(std::unique_ptr packet) +void Connector::onMessage(std::unique_ptr message) { - if (!_connected || !packet) - return false; - - return _queue.pushDiscard(std::move(packet)); -} - -int Connector::write(int cmd, bool encrypt, uint8_t *data, uint32_t size) -{ - if (!_connected) - return 0; - - int transferred; - uint8_t buffer[16]; - uint32_t magic = MAGIC; - encrypt = encrypt && _ecnrypt; - - if (encrypt && data && size > 0) + Status s = message->decrypt(_cipher); + if (s.failed()) { - if (_cipher->Encrypt(data, size)) - magic = MAGIC_ENC; - } - - write_uint32_le(&buffer[0], magic); - write_uint32_le(&buffer[4], size); - write_uint32_le(&buffer[8], cmd); - write_uint32_le(&buffer[12], ~cmd); - - std::unique_lock lock(_write_mutex); - libusb_bulk_transfer(_device, _endpoint_out, buffer, 16, &transferred, 0); - if (data && size > 0) - libusb_bulk_transfer(_device, _endpoint_out, data, size, &transferred, 0); - -#ifdef PROTOCOL_DEBUG - printMessage(cmd, size, data, magic == MAGIC_ENC, true); -#endif - - return transferred; -} - -void Connector::setEncryption(bool enabled) -{ - if (!enabled) - { - _ecnrypt = false; - return; - } - if (!_cipher) - { - std::cout << "[Connection] Can't enable encryption: cypher initialisation failed" << std::endl; + log_w("Can't decrypt message %d > %s", message->type(), s.error()); return; } - std::cout << "[Connection] Encryption enabled" << std::endl; - _ecnrypt = true; -} - -void Connector::printInts(uint8_t *data, uint32_t length, uint16_t max) -{ - if (data && length >= 4) + switch (message->type()) { - std::cout << " > "; - size_t count = length / 4; - for (size_t i = 0; (i < count) & (i < max); ++i) + + case CMD_CONTROL: + if (message->length() == 4) { - uint32_t val = - ((uint32_t)data[i * 4 + 0]) | - ((uint32_t)data[i * 4 + 1] << 8) | - ((uint32_t)data[i * 4 + 2] << 16) | - ((uint32_t)data[i * 4 + 3] << 24); - std::cout << val << " "; - } - std::cout << std::endl; - } -} - -void Connector::printBytes(uint8_t *data, uint32_t length, uint16_t max) -{ - if (data && length >= 4) - { - std::cout << " > "; - for (size_t i = 0; (i < length) & (i < max); ++i) - { - std::cout << std::setw(4) << (uint32_t)(data[i]); - } - std::cout << std::endl; - } -} - -const char *Connector::cmdString(int cmd) -{ - for (const ProtocolCmdEntry &entry : protocolCmdList) - { - if (entry.cmd == cmd) - return entry.name; - } - return nullptr; -} - -void Connector::printMessage(uint32_t cmd, uint32_t length, uint8_t *data, bool encrypted, bool out) -{ - if (Settings::protocolDebug <= PROTOCOL_DEBUG_NONE) - return; - - const char *cmds = cmdString(cmd); - - if (Settings::protocolDebug <= PROTOCOL_DEBUG_UNKNOWN && cmds) - return; - - if (Settings::protocolDebug < PROTOCOL_DEBUG_OUT && out) - return; - - bool stream = (cmd == CMD_AUDIO_DATA || cmd == CMD_VIDEO_DATA) && length > 150; - stream = stream || cmd == CMD_HEARTBEAT; - if (Settings::protocolDebug < PROTOCOL_DEBUG_ALL && stream) - return; - - std::ostringstream oss; - - oss << (out ? "<" : ">") << (encrypted ? "*" : " ") - << std::setw(3) << std::right << static_cast(cmd) - << std::setw(8) << std::left << ("[" + std::to_string(length) + "]") - << std::setw(15) << std::left << (cmds ? cmds : "Unknown"); - - if (data && length > 0) - { - for (size_t i = 0; i < 50 && i < length; ++i) - { - char ch = static_cast(data[i]); - if (ch == '\n' || ch == '\r') - oss << '.'; - else - oss << (std::isprint(static_cast(ch)) ? ch : '.'); - } - } - - std::cout << oss.str() << std::endl; -} - -void Connector::onUsbRead(libusb_transfer *transfer) -{ - UsbContext *c = static_cast(transfer->user_data); - - if (!c->owner->_active) - return; - - c->slot->commit(transfer->actual_length); - try - { - c->slot = c->owner->_usbBuffer.get(); - } - catch (const std::exception &e) - { - std::cout << "[Connection] USB buffer unavailable: " << e.what() << std::endl; - c->owner->onDisconnect(); - return; - } - libusb_fill_bulk_transfer(c->transfer, c->owner->_device, c->owner->_endpoint_in, c->slot->data, c->slot->size, Connector::onUsbRead, c, 0); - if (c->owner->_active && (libusb_submit_transfer(c->transfer) != LIBUSB_SUCCESS)) - { - std::cout << "[Connection] USB transfer re-submit failed" << std::endl; - c->owner->onDisconnect(); - } -} - -void Connector::readLoop() -{ - setThreadName("protocol-reader"); - timeval timeout{0, 100000}; - - for (int i = 0; i < _usbTransfers; i++) - { - _usbContext[i].slot = _usbBuffer.get(); - _usbContext[i].owner = this; - libusb_fill_bulk_transfer(_usbContext[i].transfer, _device, _endpoint_in, _usbContext[i].slot->data, _usbContext[i].slot->size, Connector::onUsbRead, &_usbContext[i], 0); - int status = libusb_submit_transfer(_usbContext[i].transfer); - if (status != LIBUSB_SUCCESS) - { - std::cout << "[Connection] USB transfer submit " << i << " failed: " << status << std::endl; - onDisconnect(); - return; - } - } - - while (_active && _connected) - { - libusb_handle_events_timeout_completed(_context, &timeout, nullptr); - } -} - -void Connector::bufferReadLoop() -{ - setThreadName("protocol-log"); - - while (_active && _connected) - { - Header header{0, 0, 0, 0}; - uint8_t *data = nullptr; - - if (!_usbBuffer.read(reinterpret_cast(&header), sizeof(Header))) - break; - - int32_t payloadLength = static_cast(header.length); - int32_t padding = header.type == CMD_VIDEO_DATA ? AV_INPUT_BUFFER_PADDING_SIZE : 0; - - //std::cout << "[Connection] Chunk: cmd " << header.type << " len " << header.length << " magic " << header.magic << " queue state " << _usbBuffer.count() << std::endl; - - if (payloadLength > 0) - { - data = static_cast(malloc(payloadLength + padding)); - - if (!_usbBuffer.read(data, payloadLength)) + switch (message->getInt(0)) { - free(data); + case 1: + _recorder.start(&writeQueue); + break; + + case 2: + _recorder.stop(); break; } } + break; - if (header.magic == MAGIC_ENC && payloadLength > 0) - { - if (!_cipher) - { - std::cout << "[Connection] Received encrypted buffered command " << header.type - << " but cipher is not initialised" << std::endl; - free(data); - continue; - } + case CMD_PLUGGED: + state(PROTOCOL_STATUS_CONNECTED); + break; - if (!_cipher->Decrypt(data, payloadLength)) - { - std::cout << "[Connection] Can't decrypt buffered command " << header.type << std::endl; - free(data); - continue; - } - } + case CMD_UNPLUGGED: + state(PROTOCOL_STATUS_ONLINE); + break; -#ifdef PROTOCOL_DEBUG - printMessage(header.type, payloadLength, data, header.magic == MAGIC_ENC, false); -#endif - - if (padding > 0 && data) - std::fill(data + payloadLength, data + payloadLength + padding, 0); - - onData(header.type, payloadLength, data); - } -} - -void Connector::writeLoop() -{ - // Set thread name - setThreadName("protocol-writer"); - state(PROTOCOL_STATUS_LINKING); - - while (_active) + case CMD_VIDEO_DATA: { - _connected = connect(Settings::vendorid, Settings::productid); - if (_connected) - { - std::cout << "[Connection] Device connected" << std::endl; - - _usbBuffer.start(); - _read_thread = std::thread(&Connector::readLoop, this); - _buffer_thread = std::thread(&Connector::bufferReadLoop, this); - - onDevice(true); - state(PROTOCOL_STATUS_ONLINE); - - while (_connected && _active) - { - std::unique_ptr message = _queue.pop(); - if (!message) - { - if (!_queue.waitFor(_active, 2000)) - break; - message = _queue.pop(); - } - - if (message) - write(message->command, message->encryption, message->data, message->length); - else - write(CMD_HEARTBEAT, false, nullptr, 0); - } - - if (_active) - { - state(PROTOCOL_STATUS_NO_DEVICE); - onDevice(false); - } - - _queue.clear(); - _usbBuffer.stop(); - - if (_read_thread.joinable()) - _read_thread.join(); - - if (_buffer_thread.joinable()) - _buffer_thread.join(); - } - _queue.waitFor(_active, 100); + if (message->setOffset(20)) + videoStream.pushDiscard(std::move(message)); + break; + } + case CMD_AUDIO_DATA: + { + if (message->length() <= 16) + break; + int channel = message->getInt(8); + message->setOffset(12); + if (channel == 1) + audioStreamMain.pushDiscard(std::move(message)); + if (channel == 2) + audioStreamAux.pushDiscard(std::move(message)); + break; + } + case CMD_ENCRYPTION: + if (message->length() == 0) + setEncryption(true); + break; } } diff --git a/src/connector.h b/src/connector.h index 414c727..0b87edf 100644 --- a/src/connector.h +++ b/src/connector.h @@ -8,89 +8,65 @@ #include #include -#include "helper/isender.h" +#include "protocol/imessage_sender.h" #include "struct/atomic_queue.h" -#include "struct/command.h" #include "struct/usb_buffer.h" -#include "aes_cipher.h" +#include "protocol/aes_cipher.h" +#include "protocol/connection_reader.h" +#include "recorder.h" -#define MAX_USB_REQUESTS 128 -#define COMMAND_QUEUE_SIZE 256 +#define LINK_RETRY 5 +#define LINK_RETRY_TIMEOUT 100 +#define RECONNECT_TIMEOUT 100 +#define PROTOCOL_HEARTBEAT_DELAY 3000 + +#define WRITE_QUEUE_SIZE 256 #define ENCRYPTION_BASE "SkBRDy3gmrw1ieH0" -#define PROTOCOL_DEBUG_NONE 0 -#define PROTOCOL_DEBUG_UNKNOWN 1 -#define PROTOCOL_DEBUG_NOSTREAM 2 -#define PROTOCOL_DEBUG_OUT 3 -#define PROTOCOL_DEBUG_ALL 4 - -class Connector; - -struct UsbContext { - Connector* owner; - DataSlot* slot; - libusb_transfer* transfer; -}; - -class Connector : public ISender +class Connector : public IMessageReceiver { public: Connector(); virtual ~Connector(); - void start(); + void start(atomic *statusHandler); void stop(); - bool send(std::unique_ptr packet) override; -protected: - virtual void onData(uint32_t cmd, uint32_t length, uint8_t *data) = 0; - virtual void onStatus(u_int8_t status) = 0; - virtual void onDevice(bool connected) = 0; + bool inline send(std::unique_ptr message) { return writeQueue.pushDiscard(std::move(message)); } - void setEncryption(bool enabled); + AtomicQueue writeQueue; + AtomicQueue videoStream; + AtomicQueue audioStreamMain; + AtomicQueue audioStreamAux; - static void printMessage(uint32_t cmd, uint32_t length, uint8_t *data, bool encrypted, bool out); - static void printInts(uint8_t *data, uint32_t length, uint16_t max); - static void printBytes(uint8_t *data, uint32_t length, uint16_t max); - static const char *cmdString(int cmd); - - AESCipher *_cipher = nullptr; - UsbBuffer _usbBuffer; + virtual void onMessage(std::unique_ptr message) override; private: - static void onUsbRead(libusb_transfer *transfer); - - void readLoop(); - void bufferReadLoop(); - void writeLoop(); - void onDisconnect(); - bool connect(uint16_t vendor_id, uint16_t product_id); - bool link(); - + void mainLoop(); + void writeLoop(libusb_device_handle *handler, uint8_t ep); + libusb_device *link(libusb_device_handle *handler, uint8_t *epIn, uint8_t *epOut); + void setEncryption(bool enabled); + bool fail(int status, const char *msg); bool state(u_int8_t state); - bool linkFail(int status, const char *msg); - int write(int cmd, bool encrypt, uint8_t *data, uint32_t size); + void onConnect(); + void onDisconnect(); - libusb_context *_context = nullptr; - libusb_device_handle *_device = nullptr; - uint8_t _endpoint_in; - uint8_t _endpoint_out; - uint8_t _usbTransfers; - std::atomic _connected = false; - std::atomic _ecnrypt = false; + Recorder _recorder; + AESCipher *_cipher; + ConnectionReader _reader; + libusb_context *_context; + std::thread _thread; + + std::atomic _active; + std::atomic _connected; + std::atomic _ecnrypt; uint8_t _state; uint8_t _failCount; uint8_t _nodeviceCount; - std::thread _read_thread; - std::thread _buffer_thread; - std::thread _write_thread; - std::mutex _write_mutex; - std::atomic _active = false; - AtomicQueue _queue{COMMAND_QUEUE_SIZE}; - UsbContext _usbContext[MAX_USB_REQUESTS] = {}; + atomic *_statusHandler; }; #endif /* SRC_CONNECTOR */ diff --git a/src/decoder.cpp b/src/decoder.cpp index 7c785db..61bcd8e 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -1,7 +1,8 @@ #include "decoder.h" #include -#include "helper/functions.h" +#include "common/logger.h" +#include "common/functions.h" #include "settings.h" Decoder::Decoder() @@ -66,7 +67,7 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) result = avcodec_alloc_context3(codec); if (!result) { - std::cout << "[Video] Can't load HW codec " << codec->name << ": out of memory" << std::endl; + log_w("Can't load HW codec %s > out of memory", codec->name); break; } @@ -78,13 +79,13 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) int ret = avcodec_open2(result, codec, nullptr); if (ret == 0) { - std::cout << "[Video] Using HW decoder: " << codec->name << std::endl; + log_i("HW decoder %s", codec->name); if (result->codec->capabilities & AV_CODEC_CAP_DELAY) - std::cout << "[Video] Codec has AV_CODEC_CAP_DELAY and can introduce lags, consider use SW decoding" << std::endl; + log_w("Codec %s has AV_CODEC_CAP_DELAY and can introduce lags, consider use SW decoding", codec->name); return result; } - std::cout << "[Video] Can't load HW decoder " << codec->name << ": " << avErrorText(ret) << std::endl; + log_w("Can't load HW decoder %s > %s ", codec->name, avErrorText(ret).c_str()); avcodec_free_context(&result); } @@ -92,51 +93,60 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) codec = avcodec_find_decoder(codec_id); if (!codec) { - std::cout << "[Video] HW decoder not found for codec id " << codec_id << std::endl; + log_w("[Video] HW decoder not found for codec id %d", codec_id); return nullptr; } result = avcodec_alloc_context3(codec); if (!result) { - std::cout << "[Video] Failed to allocate context for codec id " << codec_id << std::endl; + log_w("Failed to allocate context for codec id %d", codec_id); return nullptr; } int ret = avcodec_open2(result, codec, nullptr); if (ret < 0) { - std::cout << "[Video] Failed to open SW decoder " << codec->name << ": " << avErrorText(ret) << std::endl; + log_w("Failed to open SW decoder %s > %s", codec->name, avErrorText(ret).c_str()); avcodec_free_context(&result); return nullptr; } - std::cout << "[Video] Using SW decoder " << codec->name << std::endl; + log_i("SW decoder %s", codec->name); return result; } void Decoder::runner() { // Set thread name - setThreadName("video-decoding"); + setThreadName("video-decoder"); // Load codec context _context = load_codec(_codecId); - if (_status.null(_context, std::string("Can't find decoder for codec ") + avcodec_get_name(_codecId))) + if (!_context) + { + log_e("Can't find decoder for codec %s", avcodec_get_name(_codecId)); return; + } std::string codec = _context->codec->name; // Initialize parser for the codec AVCodecParserContext *parser = av_parser_init(_codecId); - if (!_status.null(parser, "Can't initilise parser for codec " + codec)) + if (!parser) + log_e("Can't initilise parser for codec %s", codec.c_str()); + else { // Allocate packet for decoding AVPacket *packet = av_packet_alloc(); - if (!_status.null(packet, "Can't allocate packet for codec " + codec)) + if (!packet) + log_e("Can't allocate packet for codec %s", codec.c_str()); + else { // Allocate frame for decoded data AVFrame *frame = av_frame_alloc(); - if (!_status.null(frame, "Can't allocate frame for codec " + codec)) + if (!frame) + log_e("Can't allocate frame for codec %s", codec.c_str()); + else { loop(_context, parser, packet, frame); // Run decoding loop av_frame_free(&frame); @@ -147,9 +157,6 @@ void Decoder::runner() } avcodec_free_context(&_context); _context = nullptr; - - if (_status.error()) - std::cout << "[Video] Decoder error: " << _status.message() << std::endl; } void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame) @@ -196,7 +203,7 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack int send_ret = avcodec_send_packet(context, packet); if (send_ret != 0) { - std::cout << "[Video] Can't decode packet: " << avErrorText(send_ret) << std::endl; + log_w("Can't decode packet > %s", avErrorText(send_ret).c_str()); continue; } diff --git a/src/decoder.h b/src/decoder.h index aba985b..780468e 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -12,8 +12,7 @@ extern "C" #include "struct/video_buffer.h" #include "struct/atomic_queue.h" -#include "struct/message.h" -#include "helper/error.h" +#include "protocol/message.h" class Decoder { @@ -34,7 +33,6 @@ private: std::thread _thread; AVCodecContext* _context; AVCodecID _codecId; - Error _status; std::atomic _active = false; diff --git a/src/helper/error.h b/src/helper/error.h deleted file mode 100644 index 7882ac3..0000000 --- a/src/helper/error.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef SRC_ERROR -#define SRC_ERROR - -#include -#include -#include "helper/functions.h" - -extern "C" -{ -#include -} - -class Error -{ -public: - Error() - : _error(false), _text("") - { - } - - void set(const std::string &error) - { - _error = true; - _text = error; - } - - bool null(const void *ptr, const std::string &message = "") - { - if (!ptr) - { - _text = message; - _error = true; - return true; - } - - return false; - } - - bool zero(u_int32_t id, const std::string &message = "") - { - if (id==0) - { - _text = message; - _error = true; - return true; - } - - return false; - } - - bool avFail(int code, const std::string &message = "") - { - if (code == 0) - return false; - _text = message + avErrorText(code); - _error = true; - return true; - } - - bool error() const - { - return _error; - } - - const std::string &message() const - { - return _text; - } - - void throwError() const - { - if (_error) - throw std::runtime_error(_text); - } - -private: - bool _error; - std::string _text; -}; - -#endif /* SRC_ERROR */ diff --git a/src/helper/functions.h b/src/helper/functions.h deleted file mode 100644 index ed7de18..0000000 --- a/src/helper/functions.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef SRC_HELPER_FUNCTIONS -#define SRC_HELPER_FUNCTIONS - -#include -#include -#include - -#if defined(__linux__) || defined(__APPLE__) -#include -#endif - -extern "C" -{ -#include -} - -inline void setThreadName(const char *name) -{ -#if defined(__linux__) - pthread_setname_np(pthread_self(), name); // Linux: OK (limit 16 chars including null) -#elif defined(__APPLE__) - pthread_setname_np(name); // macOS: only current thread, OK -#else - (void)name; // suppress unused warning -#endif -} - -inline void disable_cout() -{ - std::cout.setstate(std::ios_base::failbit); -} - -inline void write_uint32_le(uint8_t *dst, uint32_t value) -{ - dst[0] = value & 0xFF; - dst[1] = (value >> 8) & 0xFF; - dst[2] = (value >> 16) & 0xFF; - dst[3] = (value >> 24) & 0xFF; -} - -inline void write_float_le(uint8_t* dst, float value) -{ - uint32_t bits; - std::memcpy(&bits, &value, sizeof(bits)); - write_uint32_le(dst, bits); -} - -inline void execute(const char *path) -{ - if (!path || *path == '\0') - { - throw std::invalid_argument("Program path cannot be empty"); - } - - std::system(path); -} - -inline const std::string avErrorText(int code) -{ - char buf[AV_ERROR_MAX_STRING_SIZE] = {0}; - if (av_strerror(code, buf, sizeof(buf)) == 0) - return buf; - return "Unknown error"; -} - -inline void pushEvent(Uint32 evt, int code) -{ - if (evt == (Uint32)-1) - return; - SDL_Event event; - SDL_memset(&event, 0, sizeof(event)); - event.type = evt; - event.user.type = evt; - event.user.code = code; - SDL_PushEvent(&event); -} - -#endif /* SRC_HELPER_FUNCTIONS */ diff --git a/src/helper/isender.h b/src/helper/isender.h deleted file mode 100644 index 1a8a5fb..0000000 --- a/src/helper/isender.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SRC_HELPER_ISENDER -#define SRC_HELPER_ISENDER - -#include - -#include "struct/command.h" - -class ISender -{ -public: - virtual ~ISender() = default; - virtual bool send(std::unique_ptr packet) = 0; -}; - -#endif /* SRC_HELPER_ISENDER */ diff --git a/src/interface.cpp b/src/interface.cpp index 4b3223a..489838c 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -3,7 +3,7 @@ #include "resource/font.h" #include "resource/colours.h" #include "settings.h" -#include "helper/protocol_const.h" +#include "protocol/protocol_const.h" Interface::Interface(SDL_Renderer *renderer) : Renderer(renderer), _state(0), diff --git a/src/main.cpp b/src/main.cpp index a641cd1..8cc2124 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,20 +2,19 @@ #include #include -#include "helper/functions.h" +#include "common/functions.h" +#include "common/logger.h" #include "application.h" #include "pipe_listener.h" #include "settings.h" -static const char *title = "Fast Car Play v0.7"; +static const char *title = "Fast Car Play v0.8"; void start() { - if (!Settings::logging) - disable_cout(); - else - Settings::print(); + set_log_level(Settings::loglevel); + Settings::print(); if (Settings::keyPipe.value.length() > 2) PipeListener pipeListener(Settings::keyPipe.value.c_str()); @@ -45,4 +44,4 @@ int main(int argc, char **argv) return 1; } return 0; -} \ No newline at end of file +} diff --git a/src/pcm_audio.cpp b/src/pcm_audio.cpp index 93b62d1..959fa7b 100644 --- a/src/pcm_audio.cpp +++ b/src/pcm_audio.cpp @@ -1,7 +1,8 @@ #include "pcm_audio.h" -#include "helper/functions.h" -#include "helper/protocol_const.h" +#include "common/functions.h" +#include "protocol/protocol_const.h" #include "settings.h" +#include "common/logger.h" // Add sample size (buffer size in samples) to ChannelConfig ChannelConfig PcmAudio::_configTable[] = { @@ -20,8 +21,7 @@ PcmAudio::PcmAudio(const char *name) _name = "[Audio]"; return; } - _name = "[Audio "; - _name = _name + name + "]"; + _name = name; _fade = false; _faded = false; _volume = 1.0; @@ -171,7 +171,8 @@ void PcmAudio::stop() void PcmAudio::runner() { - setThreadName("audio"); + std::string threadName = "audio-" + _name; + setThreadName(threadName.c_str()); SDL_AudioDeviceID device = 0; SDL_AudioSpec spec; @@ -204,7 +205,7 @@ void PcmAudio::runner() device = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0); if (device == 0) { - std::cerr << _name << " Failed to open audio: " << SDL_GetError() << std::endl; + log_w("Failed to open audio %s > %s", _name.c_str(), SDL_GetError()); SDL_Delay(100); continue; } @@ -219,7 +220,7 @@ void PcmAudio::runner() _prefill = spec.channels == 1 ? Settings::audioDelayCall : Settings::audioDelay; SDL_PauseAudioDevice(device, 0); - std::cout << _name << " Start playing " << _config.rate << "kHz " << (_config.channels == 2 ? "stereo" : "mono") << std::endl; + log_i("Start playing %s %dkHz %s", _name.c_str(), _config.rate, (_config.channels == 2 ? "stereo" : "mono")); if (_fader) _fader->Fade(true); @@ -228,7 +229,7 @@ void PcmAudio::runner() { return _paused.load() || !_active.load(); }); SDL_PauseAudioDevice(device, 1); - std::cout << _name << " Stop playing" << std::endl; + log_i("Stop playing %s", _name.c_str()); if (_fader) _fader->Fade(false); } diff --git a/src/pcm_audio.h b/src/pcm_audio.h index 8795d71..80da1ba 100644 --- a/src/pcm_audio.h +++ b/src/pcm_audio.h @@ -8,7 +8,7 @@ #include #include "struct/atomic_queue.h" -#include "struct/message.h" +#include "protocol/message.h" #define FADE_IN_SPEED 0.00001 #define FADE_OUT_SPEED 0.0001 diff --git a/src/pipe_listener.cpp b/src/pipe_listener.cpp index 39ac23c..134fe4a 100644 --- a/src/pipe_listener.cpp +++ b/src/pipe_listener.cpp @@ -5,9 +5,11 @@ #include #include #include -#include #include #include +#include + +#include "common/logger.h" PipeListener::PipeListener(const char *path) : _path(path), _active(false) @@ -41,20 +43,20 @@ PipeListener::~PipeListener() void PipeListener::loop() { - std::cout << "[Pipe] Listening on " << _path << std::endl; + log_i("Listening on %s", _path); while (_active) { int fd = open(_path, O_RDONLY); if (fd == -1) { - std::cout << "[Pipe] Failed to open " << _path << ": " << std::strerror(errno) << std::endl; + log_e("Failed to open %s: %s", _path, std::strerror(errno)); return; } char value; while (_active && read(fd, &value, 1) > 0) { - std::cout << "[Pipe] Received: " << (int)value << std::endl; + log_d("Received: %d", static_cast(value)); if (value != 0) { SDL_Event e{}; @@ -70,5 +72,5 @@ void PipeListener::loop() if (fd >= 0) close(fd); } - std::cout << "[Pipe] Finished on " << _path << std::endl; + log_v("Finished on %s", _path); } diff --git a/src/protocol.cpp b/src/protocol.cpp deleted file mode 100644 index af772ec..0000000 --- a/src/protocol.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include "protocol.h" -#include "helper/protocol_const.h" -#include "helper/functions.h" -#include "settings.h" - -#include -#include -#include -#include - -Protocol::Protocol(uint16_t width, uint16_t height, uint16_t fps) - : videoData(Settings::videoQueue), - audioStreamMain(Settings::audioQueue), - audioStreamAux(Settings::audioQueue), - _recorder(Settings::audioQueue), - _width(width), - _height(height), - _fps(fps), - _phoneConnected(false) -{ -} - -Protocol::~Protocol() -{ - stop(); -} - -void Protocol::start(uint32_t evtStatus, uint32_t evtPhone) -{ - _evtStatusId = evtStatus; - _evtPhoneId = evtPhone; - Connector::start(); -} - -void Protocol::stop() -{ - Connector::stop(); -} - -void Protocol::sendConfig() -{ - int syncTime = std::time(nullptr); - int drivePosition = Settings::leftDrive ? 0 : 1; // 0==left, 1==right - int nightMode = Settings::nightMode; // 0==day, 1==night, 2==auto - if (nightMode < 0 || nightMode > 2) - nightMode = 2; - int mic = 7; - if (Settings::micType == 2) - mic = 15; - if (Settings::micType == 3) - mic = 21; - - int width; - int height; - switch (Settings::androidMode) - { - default: - width = 800; - height = 480; - break; - case 2: - width = 1280; - height = 720; - break; - case 3: - width = 1920; - height = 1080; - break; - } - - if (_width < _height) - std::swap(width, height); - - float scale = std::min((float)width / _width, (float)height / _height); - width = _width * scale; - height = _height * scale; - - std::cout << "[Protocol] Request android image " << width << "x" << height << std::endl; - - send(Command::String( - CMD_JSON_CONTROL, - "{\"syncTime\":%d,\"mediaDelay\":%d,\"drivePosition\":%d," - "\"androidAutoSizeW\":%d,\"androidAutoSizeH\":%d,\"HiCarConnectMode\":0," - "\"GNSSCapability\":7,\"DashboardInfo\":1,\"UseBTPhone\":0}", - syncTime, Settings::mediaDelay.value, drivePosition, width, height)); - - send(Command::String(CMD_DAYNIGHT, "{\"DayNightMode\":%d}", nightMode)); - - send(Command::File("/tmp/night_mode", nightMode)); - send(Command::File("/tmp/charge_mode", Settings::weakCharge ? 0 : 2)); // Weak charge 0, other 2 - send(Command::File("/etc/box_name", "CarPlay")); - send(Command::File("/tmp/hand_drive_mode", drivePosition)); - - send(Command::Control(mic)); - send(Command::Control(Settings::wifi5 ? 25 : 24)); - send(Command::Control(Settings::bluetoothAudio ? 22 : 23)); - if (Settings::autoconnect) - send(Command::Control(1002)); -} - -void Protocol::onStatus(uint8_t status) -{ - std::cout << "[Protocol] Status " << (int)status << std::endl; - pushEvent(_evtStatusId, status); -} - -void Protocol::onDevice(bool connected) -{ - if (connected) - { - if (Settings::encryption) - { - if (_cipher) - send(Command::Encryption(_cipher->Seed())); - else - std::cout << "[Protocol] Can't enable encryption: cypher is not initalised" << std::endl; - } - if (Settings::dpi > 0) - send(Command::File("/tmp/screen_dpi", Settings::dpi)); - send(Command::File("/etc/android_work_mode", 1)); - send(Command::Init(_width, _height, _fps)); - sendConfig(); - } - else - { - onPhone(false); - setEncryption(false); - } -} - -void Protocol::onPhone(bool connected) -{ - if (connected == _phoneConnected) - return; - _phoneConnected = connected; - - std::cout << (connected ? "[Protocol] Phone connected" : "[Protocol] Phone disconnected") << std::endl; - - if (!connected) - _recorder.stop(); - - pushEvent(_evtPhoneId, connected ? 1 : 0); - - if (connected && Settings::onConnect.value.length() > 1) - execute(Settings::onConnect.value.c_str()); - - if (!connected && Settings::onDisconnect.value.length() > 1) - execute(Settings::onDisconnect.value.c_str()); -} - -void Protocol::onControl(int cmd) -{ - switch (cmd) - { - case 1: - _recorder.start(this); - break; - - case 2: - _recorder.stop(); - break; - } -} - -void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) -{ - bool dispose = true; - - switch (cmd) - { - case CMD_CONTROL: - if (length == 4) - { - int value = 0; - memcpy(&value, data, sizeof(int)); - onControl(value); - } - break; - - case CMD_PLUGGED: - onPhone(true); - break; - - case CMD_UNPLUGGED: - onPhone(false); - break; - - case CMD_VIDEO_DATA: - if (length > 20) - { - videoData.pushDiscard(std::make_unique(data, length, 20)); - dispose = false; - } - break; - - case CMD_AUDIO_DATA: - if (length > 16) - { - int channel = 0; - memcpy(&channel, data + 8, sizeof(int)); - - if (channel == 1) - { - audioStreamMain.pushDiscard(std::make_unique(data, length, 12)); - dispose = false; - } - else if (channel == 2) - { - audioStreamAux.pushDiscard(std::make_unique(data, length, 12)); - dispose = false; - } - } - break; - - case CMD_ENCRYPTION: - if (length == 0) - setEncryption(true); - break; - } - - if (dispose && data && length > 0) - free(data); -} - -void Protocol::dispatch(std::unique_ptr msg) -{ - switch (msg->type()) - { - - case CMD_CONTROL: - if (msg->length() == 4) - onControl(msg->getInt(0)); - break; - - case CMD_PLUGGED: - onPhone(true); - break; - - case CMD_UNPLUGGED: - onPhone(false); - break; - - case CMD_VIDEO_DATA: - { - if (msg->setOffset(20)) - videoData.pushDiscard(std::move(msg)); - break; - } - case CMD_AUDIO_DATA: - { - if (msg->length() <= 16) - break; - int channel = msg->getInt(8); - msg->setOffset(12); - if (channel == 1) - audioStreamMain.pushDiscard(std::move(msg)); - if (channel == 2) - audioStreamAux.pushDiscard(std::move(msg)); - break; - } - case CMD_ENCRYPTION: - if (msg->length() == 0) - setEncryption(true); - break; - } -} diff --git a/src/protocol.h b/src/protocol.h deleted file mode 100644 index 87f134c..0000000 --- a/src/protocol.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef SRC_PROTOCOL -#define SRC_PROTOCOL - -#include "struct/atomic_queue.h" -#include "struct/message.h" -#include "connector.h" -#include "recorder.h" - -class Protocol : public Connector -{ - -public: - Protocol(uint16_t width, uint16_t height, uint16_t fps); - ~Protocol() override; - - Protocol(const Protocol &) = delete; - Protocol &operator=(const Protocol &) = delete; - - void start(uint32_t evtStatus, uint32_t evtPhone); - void stop(); - - AtomicQueue videoData; - AtomicQueue audioStreamMain; - AtomicQueue audioStreamAux; - -private: - void sendConfig(); - - void onStatus(uint8_t status) override; - void onDevice(bool connected) override; - void onData(uint32_t cmd, uint32_t length, uint8_t *data) override; - - void dispatch(std::unique_ptr msg); - void onControl(int cmd); - void onPhone(bool connected); - - Recorder _recorder; - uint16_t _width; - uint16_t _height; - uint16_t _fps; - bool _phoneConnected; - - uint32_t _evtStatusId = (uint32_t)-1; - uint32_t _evtPhoneId = (uint32_t)-1; -}; - -#endif /* SRC_PROTOCOL */ diff --git a/src/aes_cipher.cpp b/src/protocol/aes_cipher.cpp similarity index 61% rename from src/aes_cipher.cpp rename to src/protocol/aes_cipher.cpp index b0c7b68..50cfb92 100644 --- a/src/aes_cipher.cpp +++ b/src/protocol/aes_cipher.cpp @@ -1,11 +1,12 @@ #include "aes_cipher.h" +#include +#include +#include +#include #include #include #include -#include -#include -#include AESCipher::AESCipher(const std::string &baseKey) : _baseKey(baseKey) @@ -15,7 +16,6 @@ AESCipher::AESCipher(const std::string &baseKey) if (_baseKey.size() != keyLength) { - std::cerr << "[Cypher] Error: base key must be exactly 16 bytes" << std::endl; throw std::invalid_argument("Base key must be exactly 16 bytes"); } @@ -31,80 +31,52 @@ AESCipher::AESCipher(const std::string &baseKey) _initVec[12] = static_cast(_seed >> 24); } -bool AESCipher::Encrypt(uint8_t *data, uint16_t length) const +Status AESCipher::Encrypt(uint8_t *data, uint32_t length) const { if (!data || length == 0) - { - return false; - } + return Status::Error("Empty data"); auto ctx = std::unique_ptr(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); if (!ctx) - { - std::cerr << "[Cypher] Failed to create cipher context" << std::endl; - return false; - } + return Status::Error("Failed to create cipher context"); if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cfb(), nullptr, _encKey.data(), _initVec.data()) != 1) - { - std::cerr << "[Cypher] Encryption initialization failed" << std::endl; - return false; - } + return Status::Error("Encryption initialization failed"); std::unique_ptr temp(new uint8_t[length + AES_BLOCK_SIZE]); int out_len = 0; if (EVP_EncryptUpdate(ctx.get(), temp.get(), &out_len, data, length) != 1) - { - std::cerr << "[Cypher] Encryption failed during update" << std::endl; - return false; - } + return Status::Error("Encryption failed during update"); int final_len = 0; if (EVP_EncryptFinal_ex(ctx.get(), temp.get() + out_len, &final_len) != 1) - { - std::cerr << "[Cypher] Encryption failed during final" << std::endl; - return false; - } + return Status::Error("Encryption failed during final"); std::copy_n(temp.get(), length, data); - return true; + return Status::Success(); } -bool AESCipher::Decrypt(uint8_t *data, uint16_t length) const +Status AESCipher::Decrypt(uint8_t *data, uint32_t length) const { if (!data || length == 0) - { - return false; - } + return Status::Error("Empty data"); auto ctx = std::unique_ptr(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); if (!ctx) - { - std::cerr << "[Cypher] Failed to create cipher context" << std::endl; - return false; - } + return Status::Error("Failed to create cipher context"); if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_cfb(), nullptr, _encKey.data(), _initVec.data()) != 1) - { - std::cerr << "[Cypher] Decryption initialization failed" << std::endl; - return false; - } + return Status::Error(" Decryption initialization failed"); std::unique_ptr temp(new uint8_t[length + AES_BLOCK_SIZE]); int out_len = 0; if (EVP_DecryptUpdate(ctx.get(), temp.get(), &out_len, data, length) != 1) - { - std::cerr << "[Cypher] Decryption failed during update" << std::endl; - return false; - } + return Status::Error("Decryption failed during update"); int final_len = 0; if (EVP_DecryptFinal_ex(ctx.get(), temp.get() + out_len, &final_len) != 1) - { - std::cerr << "[Cypher] Decryption failed during final" << std::endl; - return false; - } + return Status::Error("Decryption failed during final"); std::copy_n(temp.get(), length, data); - return true; -} \ No newline at end of file + return Status::Success(); +} diff --git a/src/aes_cipher.h b/src/protocol/aes_cipher.h similarity index 76% rename from src/aes_cipher.h rename to src/protocol/aes_cipher.h index 9d8cdc9..9bc6218 100644 --- a/src/aes_cipher.h +++ b/src/protocol/aes_cipher.h @@ -2,9 +2,11 @@ #define SRC_AES_CIPHER #include +#include #include #include -#include + +#include "common/status.h" class AESCipher { @@ -14,8 +16,8 @@ public: AESCipher(const std::string &base_key); ~AESCipher() = default; - bool Encrypt(uint8_t *data, uint16_t length) const; - bool Decrypt(uint8_t *data, uint16_t length) const; + Status Encrypt(uint8_t *data, uint32_t length) const; + Status Decrypt(uint8_t *data, uint32_t length) const; uint32_t Seed() const { return _seed; } const std::string& Key() const { return _baseKey; } diff --git a/src/protocol/connection_reader.cpp b/src/protocol/connection_reader.cpp new file mode 100644 index 0000000..8d910f2 --- /dev/null +++ b/src/protocol/connection_reader.cpp @@ -0,0 +1,217 @@ +#include "protocol/connection_reader.h" + +#include +#include +#include +#include +#include +#include + +#include "settings.h" +#include "common/logger.h" +#include "common/threading.h" +#include "protocol/protocol_const.h" +#include "libavcodec/defs.h" + +ConnectionReader::ConnectionReader() + : _active(false), + _buffer(Settings::usbBuffer, Settings::usbTransferSize), + _transfers(Settings::usbQueue), + _receiver(nullptr), + _usbContext(nullptr) +{ + log_v("Created"); +} + +ConnectionReader::~ConnectionReader() +{ + log_v("Destroying"); + + stop(); + + for (Context &context : _transfers) + { + if (context.transfer) + { + libusb_free_transfer(context.transfer); + context.transfer = nullptr; + } + } + log_v("Destroyed"); +} + +bool ConnectionReader::start(libusb_context *context, libusb_device_handle *device, uint8_t endpoint, IMessageReceiver *receiver) +{ + if (_active || !context || !device) + return false; + + _receiver = receiver; + _usbContext = context; + + log_i("Starting to read endpoint %d with %d requests", endpoint, _transfers.size()); + + // Prepare usb transfers + for (Context &context : _transfers) + { + context.owner = this; + if (!context.transfer) + { + context.transfer = libusb_alloc_transfer(0); + if (context.transfer == nullptr) + { + log_e("Can't allocate usb transfer"); + return false; + } + } + } + + // Start processing thread + _buffer.reset(); + _active = true; + _processThread = std::thread(&ConnectionReader::processLoop, this); + _readThread = std::thread(&ConnectionReader::readLoop, this); + + // Start usb reading thread + for (Context &context : _transfers) + { + context.slot = _buffer.get(); + if (context.slot == nullptr) + { + log_e("Can't allocate data slot for usb transfer"); + return false; + } + context.owner = this; + libusb_fill_bulk_transfer(context.transfer, device, endpoint, context.slot->data, context.slot->size, ConnectionReader::onUsbRead, &context, 0); + int status = libusb_submit_transfer(context.transfer); + if (status != LIBUSB_SUCCESS) + { + log_w("USB transfer submit failed with code %d", status); + return false; + } + } + + return true; +} + +void ConnectionReader::stop() +{ + log_v("Stopping"); + + _active = false; + + if (_usbContext) + { + for (Context &context : _transfers) + { + if (context.transfer) + libusb_cancel_transfer(context.transfer); + } + + timeval timeout{0, 100000}; + libusb_handle_events_timeout_completed(_usbContext, &timeout, nullptr); + + log_v("Events canceled"); + } + + _buffer.notify(); + + if (_readThread.joinable()) + _readThread.join(); + + if (_processThread.joinable()) + _processThread.join(); + + _usbContext = nullptr; + + log_v("Threads stopped"); +} + +void ConnectionReader::onUsbRead(libusb_transfer *transfer) +{ + if (!transfer || !transfer->user_data) + return; + + Context *c = static_cast(transfer->user_data); + if (!c->owner->_active) + return; + + log_p("USB read > status %d length %d", transfer->status, transfer->actual_length); + + if (transfer->status == LIBUSB_TRANSFER_CANCELLED) + return; + + if (transfer->status != LIBUSB_TRANSFER_COMPLETED) + { + log_w("USB read failed with status %d", transfer->status); + c->owner->_active = false; + return; + } + + c->slot->commit(transfer->actual_length); + + c->slot = c->owner->_buffer.get(); + if (!c->slot) + { + log_e("Can't allocate data slot for next usb transfer"); + c->owner->_active = false; + return; + } + c->transfer->buffer = c->slot->data; + + if (!c->owner->_active) + return; + + int status = libusb_submit_transfer(c->transfer); + if (status != LIBUSB_SUCCESS) + { + log_w("USB transfer re-submit failed with status %d", status); + c->owner->_active = false; + } +} + +void ConnectionReader::readLoop() +{ + setThreadName("usb-read"); + timeval timeout{0, 100000}; + + log_d("USB reading thread started"); + + while (_active) + { + libusb_handle_events_timeout_completed(_usbContext, &timeout, nullptr); + } + + log_v("USB reading thread stopped"); +} + +void ConnectionReader::processLoop() +{ + setThreadName("usb-process"); + log_d("USB processing thread started"); + + while (_active) + { + std::unique_ptr message = std::make_unique(); + + if (!_buffer.read(message->header(), message->headerSize(), _active)) + break; + + if (!message->valid()) + { + log_w("Received mallformed message"); + continue; + } + + if (message->length() >= 0) + { + uint8_t *buff = message->allocate(message->type() == CMD_VIDEO_DATA ? AV_INPUT_BUFFER_PADDING_SIZE : 0); + if (!_buffer.read(buff, message->length(), _active)) + break; + } + + if (_receiver && message->allocated()) + _receiver->onMessage(std::move(message)); + } + + log_v("USB processing thread stopped"); +} diff --git a/src/protocol/connection_reader.h b/src/protocol/connection_reader.h new file mode 100644 index 0000000..8258ac4 --- /dev/null +++ b/src/protocol/connection_reader.h @@ -0,0 +1,61 @@ +#ifndef SRC_PROTOCOL_CONNECTION_READER +#define SRC_PROTOCOL_CONNECTION_READER + +#include + +#include +#include +#include +#include +#include + +#include "struct/usb_buffer.h" +#include "protocol/aes_cipher.h" +#include "protocol/message.h" + +class IMessageReceiver +{ +public: + virtual ~IMessageReceiver() = default; + virtual void onMessage(std::unique_ptr message) = 0; +}; + +class ConnectionReader +{ +public: + ConnectionReader(); + ~ConnectionReader(); + + ConnectionReader(const ConnectionReader &) = delete; + ConnectionReader &operator=(const ConnectionReader &) = delete; + + bool start(libusb_context *context, libusb_device_handle *device, uint8_t endpoint, IMessageReceiver *receiver); + void stop(); + + int bufferCount() const { return _buffer.count(); } + bool active() const { return _active; } + +private: + struct Context + { + ConnectionReader *owner = nullptr; + DataSlot *slot = nullptr; + libusb_transfer *transfer = nullptr; + }; + + static void onUsbRead(libusb_transfer *transfer); + void readLoop(); + void processLoop(); + + void cancelTransfers(); + + std::atomic _active; + UsbBuffer _buffer; + std::vector _transfers; + IMessageReceiver *_receiver; + std::thread _readThread; + std::thread _processThread; + libusb_context *_usbContext; +}; + +#endif /* SRC_PROTOCOL_CONNECTION_READER */ diff --git a/src/protocol/imessage_sender.h b/src/protocol/imessage_sender.h new file mode 100644 index 0000000..617e882 --- /dev/null +++ b/src/protocol/imessage_sender.h @@ -0,0 +1,15 @@ +#ifndef SRC_HELPER_ISENDER +#define SRC_HELPER_ISENDER + +#include + +#include "protocol/message.h" + +class IMessageSender +{ +public: + virtual ~IMessageSender() = default; + virtual bool send(std::unique_ptr packet) = 0; +}; + +#endif /* SRC_HELPER_ISENDER */ diff --git a/src/protocol/message.h b/src/protocol/message.h new file mode 100644 index 0000000..74207ae --- /dev/null +++ b/src/protocol/message.h @@ -0,0 +1,325 @@ +#ifndef SRC_PROTOCOL_MESSAGE +#define SRC_PROTOCOL_MESSAGE + +#include +#include +#include +#include +#include + +#include "protocol/aes_cipher.h" +#include "protocol/protocol_const.h" +#include "struct/multitouch.h" + +#define MESSAGE_MAX_PAYLOAD_SIZE (2 * 1024 * 1024) + +#pragma pack(push, 1) +struct Header +{ + uint32_t magic; + int32_t length; + uint32_t type; + uint32_t typecheck; +}; +#pragma pack(pop) + +class Message +{ +public: + Message() + : _header({0, 0, 0, 0}), _data(nullptr), _offset(0), _size(0), _encrypt(false) + { + } + + Message(uint32_t cmd, bool encrypt = true, int32_t size = 0, uint8_t *buffer = nullptr) + : _header({static_cast(MAGIC), size, cmd, ~cmd}), + _data(nullptr), + _offset(0), + _size(0), + _encrypt(encrypt) + { + if (size <= 0) + return; + + if (!buffer) + { + allocate(); + return; + } + + _data = buffer; + _size = size; + } + + Message(uint32_t cmd, uint32_t value, bool encrypt = true) + : Message(cmd, encrypt, 4) + { + write_uint32_le(_data, value); + } + + Message(const Message &) = delete; + Message &operator=(const Message &) = delete; + + ~Message() + { + if (_data) + { + free(_data); + _data = nullptr; + } + } + + uint8_t *allocate(uint32_t padding = 0) + { + if (_data != nullptr || _header.length <= 0) + return nullptr; + + _size = _header.length + padding; + _data = static_cast(malloc(_size)); + if (!_data) + _size = 0; + else + std::fill(_data + _header.length, _data + _header.length + padding, 0); + return _data; + } + + int getInt(uint32_t offset) const + { + int result = 0; + if (_data && offset + sizeof(int) <= _size) + memcpy(&result, _data + offset, sizeof(int)); + return result; + } + + bool setOffset(uint32_t offset) + { + if (offset >= _size) + return false; + _offset = offset; + return true; + } + + bool valid() const + { + if (_header.magic != MAGIC_ENC && _header.magic != MAGIC) + return false; + if (_header.typecheck != ~_header.type) + return false; + if (_header.length < 0 || _header.length > MESSAGE_MAX_PAYLOAD_SIZE) + return false; + return true; + } + + Status encrypt(AESCipher *cipher) + { + if (!_encrypt) + return Status::Success(); + + if (_header.magic == MAGIC_ENC) + return Status::Success(); + + if (!cipher) + return Status::Error("Cipher is not initialised"); + + if (!allocated()) + return Status::Error("Message data is not allocated"); + + Status result = cipher->Encrypt(_data, _header.length); + + if (result.succeed()) + _header.magic = MAGIC_ENC; + + return result; + } + + Status decrypt(AESCipher *cipher) + { + if (_header.magic != MAGIC_ENC) + return Status::Success(); + + if (!cipher) + return Status::Error("Cipher is not initialised"); + + if (!allocated()) + return Status::Error("Message data is not allocated"); + + return cipher->Decrypt(_data, _header.length); + } + + bool isMotion() const + { + return _header.type == CMD_TOUCH && getInt(0) == 15; + } + + static std::unique_ptr Init(int width, int height, int fps) + { + std::unique_ptr result(new Message(CMD_OPEN, true, 28)); + write_uint32_le(result->_data + 0, width); + write_uint32_le(result->_data + 4, height); + write_uint32_le(result->_data + 8, fps); + write_uint32_le(result->_data + 12, 5); + write_uint32_le(result->_data + 16, 49152); + write_uint32_le(result->_data + 20, 2); + write_uint32_le(result->_data + 24, 2); + return result; + } + + static std::unique_ptr File(const char *filename, const uint8_t *data, uint32_t length) + { + // filename is assumed null‑terminated, so strlen + 1 to include the '\0' + uint32_t fn_len = strlen(filename) + 1; + + // Total buffer size: 4 (fn_len) + fn_len + 4 (content_len) + content_len + std::unique_ptr result(new Message(CMD_SEND_FILE, true, 4 + fn_len + 4 + length)); + uint8_t *buf = result->_data; + + // 1) filename length (LE) + write_uint32_le(buf, fn_len); + buf += 4; + + // 2) filename bytes (including the '\0') + std::memcpy(buf, filename, fn_len); + buf += fn_len; + + // 3) content length (LE) + write_uint32_le(buf, length); + buf += 4; + + // 4) content bytes + if (length > 0 && data) + std::memcpy(buf, data, length); + + return result; + } + + static std::unique_ptr File(const char *filename, const char *value) + { + uint32_t len = std::strlen(value); + if (len > 16) + throw std::invalid_argument("String too long (max 16 bytes)"); + // note: we send only the ASCII bytes, no trailing '\0' + return File(filename, reinterpret_cast(value), len); + } + + // overload for a single 32‑bit integer + static std::unique_ptr File(const char *filename, int value) + { + uint8_t buffer[4]; + write_uint32_le(buffer, value); + return File(filename, buffer, 4); + } + + static std::unique_ptr Control(uint32_t value, bool encrypt = false) + { + return std::unique_ptr(new Message(CMD_CONTROL, value, encrypt)); + } + + static std::unique_ptr Encryption(uint32_t seed) + { + return std::unique_ptr(new Message(CMD_ENCRYPTION, seed, false)); + } + + static std::unique_ptr String(uint32_t cmd, const char *str, bool encrypt = true) + { + uint32_t length = std::strlen(str); + std::unique_ptr result(new Message(cmd, encrypt, length)); + if (length > 0) + std::memcpy(result->_data, str, length); + return result; + } + + template + static std::unique_ptr String(uint32_t cmd, const char *format, Args... args) + { + char buffer[512]; + std::snprintf(buffer, sizeof(buffer), format, args...); + return String(cmd, buffer); + } + + static std::unique_ptr Touch(uint32_t action, float x, float y) + { + std::unique_ptr result(new Message(CMD_TOUCH, false, 16)); + write_uint32_le(result->_data, action); + write_uint32_le(result->_data + 4, int(10000 * x)); + write_uint32_le(result->_data + 8, int(10000 * y)); + write_uint32_le(result->_data + 12, 0); + return result; + } + + static std::unique_ptr Click(float x, float y, bool down) + { + return Touch(down ? 14 : 16, x, y); + } + + static std::unique_ptr Move(float x, float y) + { + return Touch(15, x, y); + } + + static std::unique_ptr Audio(int length) + { + std::unique_ptr result(new Message(CMD_AUDIO_DATA, false, length + AUDIO_BUFFER_OFFSET)); + write_uint32_le(result->_data, 5); + write_uint32_le(result->_data + 4, 0); + write_uint32_le(result->_data + 8, 3); + result->setOffset(AUDIO_BUFFER_OFFSET); + return result; + } + + static std::unique_ptr MultiTouch(const Multitouch &touches) + { + int count = touches.size(); + if (count == 0) + return nullptr; + + std::unique_ptr result(new Message(CMD_MULTI_TOUCH, false, sizeof(Multitouch::Touch) * count)); + uint8_t *buf = result->_data; + for (int i = 0; i < count; ++i) + { + const Multitouch::Touch &t = touches[i]; + write_float_le(buf + 0, t.x); + write_float_le(buf + 4, t.y); + write_uint32_le(buf + 8, static_cast(t.state)); + write_uint32_le(buf + 12, static_cast(t.id)); + buf += 16; + } + return result; + } + + static std::unique_ptr HeartBeat() + { + return std::unique_ptr(new Message(CMD_HEARTBEAT, false)); + } + + bool allocated() const { return _header.length <= 0 || static_cast(_header.length) <= _size; } + bool encrypted() const { return _header.magic == MAGIC_ENC; } + uint8_t *header() { return reinterpret_cast(&_header); } + uint32_t headerSize() const { return sizeof(Header); } + uint32_t type() const { return _header.type; } + int32_t length() const { return _header.length - _offset; } + uint8_t *data() const { return _data ? _data + _offset : nullptr; } + +private: + static inline void write_uint32_le(uint8_t *dst, uint32_t value) + { + dst[0] = value & 0xFF; + dst[1] = (value >> 8) & 0xFF; + dst[2] = (value >> 16) & 0xFF; + dst[3] = (value >> 24) & 0xFF; + } + + static inline void write_float_le(uint8_t *dst, float value) + { + uint32_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + write_uint32_le(dst, bits); + } + + Header _header; + uint8_t *_data; + uint32_t _offset; + uint32_t _size; + bool _encrypt; +}; + +#endif /* SRC_PROTOCOL_MESSAGE */ diff --git a/src/helper/protocol_const.h b/src/protocol/protocol_const.h similarity index 95% rename from src/helper/protocol_const.h rename to src/protocol/protocol_const.h index 8817b04..349d86c 100644 --- a/src/helper/protocol_const.h +++ b/src/protocol/protocol_const.h @@ -1,5 +1,5 @@ -#ifndef SRC_HELPER_PROTOCOL_CONST -#define SRC_HELPER_PROTOCOL_CONST +#ifndef SRC_PROTOCOL_PROTOCOL_CONST +#define SRC_PROTOCOL_PROTOCOL_CONST #define PROTOCOL_STATUS_UNKNOWN -1 // Manual > 0 #define PROTOCOL_STATUS_INITIALISING 0 // Initialised > 1 @@ -93,4 +93,4 @@ const ProtocolCmdEntry protocolCmdList[] = { {CMD_ENCRYPTION, "Encryption"}}; -#endif /* SRC_HELPER_PROTOCOL_CONST */ +#endif /* SRC_PROTOCOL_PROTOCOL_CONST */ diff --git a/src/recorder.cpp b/src/recorder.cpp index bf405d6..e48fea7 100644 --- a/src/recorder.cpp +++ b/src/recorder.cpp @@ -3,58 +3,30 @@ #include #include -#include "helper/protocol_const.h" -#include "helper/functions.h" +#include "protocol/protocol_const.h" +#include "common/functions.h" #include "settings.h" -#include "struct/command.h" +#include "protocol/message.h" Recorder::Recorder(uint16_t buffSize) - : _sender(nullptr), _active(false), _data(buffSize) + : _queue(nullptr), _active(false), _device(0) { } Recorder::~Recorder() { stop(); - if (_thread.joinable()) - _thread.join(); } -void Recorder::start(ISender *sender) +void Recorder::start(AtomicQueue *queue) { if (_active) return; - if (_thread.joinable()) - _thread.join(); - - _sender = sender; + _queue = queue; _active = true; - _thread = std::thread(&Recorder::runner, this); -} -void Recorder::stop() -{ - if (!_active) - return; - _active = false; - _data.notify(); -} - -void Recorder::AudioCallback(void *userdata, Uint8 *stream, int len) -{ - Recorder *self = static_cast(userdata); - std::unique_ptr frame(new AudioChunk(AUDIO_BUFFER_OFFSET + len)); - std::memcpy(frame.get()->data + AUDIO_BUFFER_OFFSET, stream, len); - self->_data.pushDiscard(std::move(frame)); -} - -void Recorder::runner() -{ - setThreadName("recorder"); - - SDL_AudioDeviceID device = 0; SDL_AudioSpec spec; SDL_zero(spec); @@ -65,26 +37,35 @@ void Recorder::runner() spec.callback = AudioCallback; spec.userdata = this; - device = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &spec, nullptr, 0); - if (device == 0) + _device = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &spec, nullptr, 0); + if (_device == 0) { std::cerr << "[Recording] Failed to open audio: " << SDL_GetError() << std::endl; _active = false; return; } - SDL_PauseAudioDevice(device, 0); - - while (_active) - { - std::unique_ptr buffer = _data.pop(); - if (buffer && _sender) - _sender->send(Command::Audio(std::move(buffer))); - else if (_active) - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - - } - - SDL_PauseAudioDevice(device, 1); - SDL_CloseAudioDevice(device); + SDL_PauseAudioDevice(_device, 0); +} + +void Recorder::stop() +{ + if (!_active) + return; + _active = false; + + SDL_PauseAudioDevice(_device, 1); + SDL_CloseAudioDevice(_device); +} + +void Recorder::AudioCallback(void *userdata, Uint8 *stream, int len) +{ + Recorder *self = static_cast(userdata); + if (!self->_queue) + return; + std::unique_ptr message = Message::Audio(len); + if(!message->allocated()) + return; + std::memcpy(message->data(), stream, len); + self->_queue->pushDiscard(std::move(message)); } diff --git a/src/recorder.h b/src/recorder.h index 4ed5d2d..3869c4c 100644 --- a/src/recorder.h +++ b/src/recorder.h @@ -6,9 +6,8 @@ #include -#include "helper/isender.h" -#include "struct/audio_chunk.h" #include "struct/atomic_queue.h" +#include "protocol/message.h" class Recorder { @@ -16,17 +15,16 @@ public: Recorder(uint16_t buffSize); ~Recorder(); - void start(ISender *sender); + void start(AtomicQueue *queue); void stop(); private: static void AudioCallback(void *userdata, Uint8 *stream, int len); void runner(); - ISender *_sender; + AtomicQueue *_queue; std::atomic _active; - std::thread _thread; - AtomicQueue _data; + SDL_AudioDeviceID _device; }; #endif /* SRC_RECORDER */ diff --git a/src/renderer.cpp b/src/renderer.cpp index 8fdb841..f6b1b63 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,8 +1,8 @@ #include "renderer.h" #include -#include #include "settings.h" -#include "helper/functions.h" +#include "common/functions.h" +#include "common/logger.h" #include RendererText::RendererText(const void *font_data, int data_size, int ptsize) @@ -19,14 +19,14 @@ RendererText::RendererText(const void *font_data, int data_size, int ptsize) SDL_RWops *font_rw = SDL_RWFromConstMem(font_data, data_size); if (!font_rw) { - std::cerr << "[UX] SDL can't open font: " << SDL_GetError() << std::endl; + log_e("SDL can't open font: %s", SDL_GetError()); return; } _font = TTF_OpenFontRW(font_rw, 1, ptsize); if (!_font) { - std::cerr << "[UX] SDL can't load font: " << TTF_GetError() << std::endl; + log_e("SDL can't load font: %s", TTF_GetError()); } }; @@ -77,7 +77,7 @@ SDL_Texture *RendererText::getText(SDL_Renderer *renderer, const char *text, SDL SDL_Surface *textSurface = TTF_RenderText_Blended(_font, text, color); if (!textSurface) { - std::cerr << "[UX] Failed to create text surface: " << TTF_GetError() << std::endl; + log_e("Failed to create text surface: %s", TTF_GetError()); return nullptr; } @@ -85,7 +85,7 @@ SDL_Texture *RendererText::getText(SDL_Renderer *renderer, const char *text, SDL SDL_FreeSurface(textSurface); if (!textTexture) { - std::cerr << "[UX] Failed to create text texture: " << TTF_GetError() << std::endl; + log_e("Failed to create text texture: %s", TTF_GetError()); return nullptr; } @@ -98,14 +98,14 @@ RendererImage::RendererImage(const void *img_data, int img_size) SDL_RWops *img_rw = SDL_RWFromConstMem(img_data, img_size); if (!img_rw) { - std::cerr << "[UX] SDL can't open image: " << SDL_GetError() << std::endl; + log_e("SDL can't open image: %s", SDL_GetError()); return; } _surface = SDL_LoadBMP_RW(img_rw, 1); if (!_surface) { - std::cerr << "[UX] Failed to create image surface: " << SDL_GetError() << std::endl; + log_e("Failed to create image surface: %s", SDL_GetError()); return; } @@ -186,7 +186,7 @@ bool Renderer::prepareTexture(uint32_t format, int width, int height) width, height); if (!_texture) { - std::cerr << "[UX] SDL can't create video texture: " << SDL_GetError() << std::endl; + log_e("SDL can't create video texture: %s", SDL_GetError()); return false; } @@ -230,7 +230,7 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight) xScale = (float)width / frame->width; yScale = (float)height / frame->height; - std::cout << "[UX] Prepare renderer " << width << "x" << height << " for source " << frame->width << "x" << frame->height << " target " << targetWidth << "x" << targetHeight << std::endl; + log_i("Prepare renderer %dx%d for source %dx%d target %dx%d", width, height, frame->width, frame->height, targetWidth, targetHeight); AVPixelFormat fmt = static_cast(frame->format); for (const FormatMapping &mapping : _mapping) @@ -239,7 +239,7 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight) { if (prepareTexture(mapping.sdlFormat, frame->width, frame->height)) { - std::cout << "[UX] Direct rendering " << mapping.name << std::endl; + log_i("Direct rendering %s", mapping.name.c_str()); _render = mapping.function; return true; } @@ -255,14 +255,14 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight) swsFlags, nullptr, nullptr, nullptr); if (!_sws) { - std::cerr << "[UX] Can't create sws context" << std::endl; + log_e("Can't create sws context"); return false; } _frame = av_frame_alloc(); if (!_frame) { - std::cerr << "[UX] Can't allocate AVFrame" << std::endl; + log_e("Can't allocate AVFrame"); return false; } _frame->format = AV_PIX_FMT_YUV420P; @@ -272,11 +272,11 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight) int avRes = av_frame_get_buffer(_frame, 32); if (avRes != 0) { - std::cerr << "[UX] Can't allocate AVFrame buffer: " << avErrorText(avRes) << std::endl; + log_e("Can't allocate AVFrame buffer: %s", avErrorText(avRes).c_str()); return false; } - std::cout << "[UX] Scaling rendering source format " << frame->format << std::endl; + log_i("Scaling rendering source format %d", frame->format); _render = &Renderer::scale; return true; } diff --git a/src/settings.cpp b/src/settings.cpp index 672913d..8257203 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -3,12 +3,14 @@ #include #include +#include "common/logger.h" + bool Settings::load(const std::string &filename) { std::ifstream file(filename); if (!file.is_open()) { - std::cerr << "[Settings] Cannot open file: " << filename << std::endl; + log_e( "Cannot open file > %s", filename.c_str()); return false; } @@ -46,7 +48,7 @@ bool Settings::load(const std::string &filename) } } if (!found) - std::cerr << "[Settings] Unknown key “" << key << "”" << std::endl; + log_w("Unknown key > %s", key.c_str()); } return true; } @@ -55,7 +57,7 @@ void Settings::print() { for (ISetting *setting : _settings()) { - std::cout << "[Settings] " << setting->name << " = " << setting->asString() << "\n"; + log_d("%s = %s",setting->name.c_str(), setting->asString().c_str()); } } @@ -73,4 +75,4 @@ void Settings::trim(std::string &s) { return !std::isspace(c); }) .base(), s.end()); -} \ No newline at end of file +} diff --git a/src/settings.h b/src/settings.h index 5320c8e..f18f8b4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,7 +1,7 @@ #ifndef SRC_SETTINGS #define SRC_SETTINGS -#include "helper/settings_base.h" +#include "common/settings_base.h" #define SCREEN_MODE_WINDOW 0 #define SCREEN_MODE_FULLSCREEN 1 @@ -20,7 +20,7 @@ public: static inline Setting fps{"fps", 50}; static inline Setting screenMode{"window-mode", 0}; static inline Setting cursor{"cursor", false}; - static inline Setting logging{"logging", false}; + static inline Setting loglevel{"log-level", 2}; // Device configurations section static inline Setting encryption{"encryption", false}; @@ -32,7 +32,7 @@ public: static inline Setting bluetoothAudio{"bluetooth-audio", false}; static inline Setting micType{"mic-type", 1}; static inline Setting dpi{"android-dpi", 120}; - static inline Setting androidMode{"android-resolution", 0}; + static inline Setting androidMode{"android-resolution", 1}; static inline Setting mediaDelay{"android-media-delay", 300}; // Application configuration section @@ -40,12 +40,14 @@ public: static inline Setting vsync{"vsync", false}; static inline Setting hwDecode{"hw-decode", true}; static inline Setting forceRedraw{"force-redraw", 0}; + static inline Setting eventsSkip{"draw-skip-events", 0}; static inline Setting aspectCorrection{"aspect-correction", 1}; static inline Setting renderDriver{"renderer-driver", ""}; static inline Setting alternativeRendering{"alternative-rendering", true}; static inline Setting fastScale{"fast-render-scale", false}; - static inline Setting usbQueue{"async-usb-calls", 8}; - static inline Setting usbBufferSize{"usb-buffer-size", 131072}; + static inline Setting usbQueue{"async-usb-calls", 16}; + static inline Setting usbTransferSize{"usb-buffer-size", 2048}; + static inline Setting usbBuffer{"usb-buffer", 64}; static inline Setting videoQueue{"video-buffer-size", 64}; static inline Setting audioQueue{"audio-buffer-size", 64}; static inline Setting audioDelay{"audio-buffer-wait", 2}; @@ -53,7 +55,6 @@ public: static inline Setting audioFade{"audio-fade", 0.3}; static inline Setting audioBuffer{"audio-buffer-samples", 512}; static inline Setting audioDriver{"audio-driver", ""}; - static inline Setting onConnect{"on-connect-script", ""}; static inline Setting onDisconnect{"on-disconnect-script", ""}; @@ -83,7 +84,6 @@ public: static inline Setting keyPipe{"key-pipe-path", ""}; // Debug section - static inline Setting protocolDebug{"protocol-debug", 0}; static inline Setting codecLowDelay{"decode-low-delay", true}; static inline Setting codecFast{"decode-fast", true}; diff --git a/src/struct/audio_chunk.h b/src/struct/audio_chunk.h deleted file mode 100644 index f8107bb..0000000 --- a/src/struct/audio_chunk.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef SRC_STRUCT_AUDIO_CHUNK -#define SRC_STRUCT_AUDIO_CHUNK - -#include -#include -#include - -class AudioChunk -{ -public: - AudioChunk(uint16_t size) - : data(nullptr), size(size) - { - if (size > 0) - data = static_cast(malloc(size)); - } - - ~AudioChunk() - { - free(data); - } - - // Deleted copy constructor/assignment - AudioChunk(const AudioChunk &) = delete; - AudioChunk &operator=(const AudioChunk &) = delete; - - uint8_t *data; - uint16_t size; -}; - -#endif /* SRC_STRUCT_AUDIO_CHUNK */ \ No newline at end of file diff --git a/src/struct/command.h b/src/struct/command.h deleted file mode 100644 index 347d8f5..0000000 --- a/src/struct/command.h +++ /dev/null @@ -1,189 +0,0 @@ -#ifndef SRC_STRUCT_COMMAND -#define SRC_STRUCT_COMMAND - -#include -#include -#include -#include -#include - -#include "helper/functions.h" -#include "helper/protocol_const.h" -#include "struct/audio_chunk.h" -#include "struct/multitouch.h" - -class Command -{ -public: - Command(const Command &) = delete; - Command &operator=(const Command &) = delete; - - Command(int cmd, bool encrypt = true, uint32_t size = 0) - : command(cmd), encryption(encrypt), length(size), data(nullptr) - { - if (size > 0) - data = static_cast(malloc(size)); - } - - Command(int cmd, uint32_t value, bool encrypt = true) - : Command(cmd, encrypt, 4) - { - write_uint32_le(data, value); - } - - Command(int cmd, bool encrypt, uint8_t *buffer, uint32_t size) - : command(cmd), encryption(encrypt), length(size), data(buffer) - { - } - - ~Command() - { - if (data) - { - free(data); - data = nullptr; - } - } - - static std::unique_ptr Init(int width, int height, int fps) - { - std::unique_ptr result(new Command(CMD_OPEN, true, 28)); - write_uint32_le(result->data + 0, width); - write_uint32_le(result->data + 4, height); - write_uint32_le(result->data + 8, fps); - write_uint32_le(result->data + 12, 5); - write_uint32_le(result->data + 16, 49152); - write_uint32_le(result->data + 20, 2); - write_uint32_le(result->data + 24, 2); - return result; - } - - static std::unique_ptr File(const char *filename, const uint8_t *data, uint32_t length) - { - // filename is assumed null‑terminated, so strlen + 1 to include the '\0' - uint32_t fn_len = strlen(filename) + 1; - - // Total buffer size: 4 (fn_len) + fn_len + 4 (content_len) + content_len - std::unique_ptr result(new Command(CMD_SEND_FILE, true, 4 + fn_len + 4 + length)); - uint8_t *buf = result->data; - - // 1) filename length (LE) - write_uint32_le(buf, fn_len); - buf += 4; - - // 2) filename bytes (including the '\0') - std::memcpy(buf, filename, fn_len); - buf += fn_len; - - // 3) content length (LE) - write_uint32_le(buf, length); - buf += 4; - - // 4) content bytes - if (length > 0 && data) - std::memcpy(buf, data, length); - - return result; - } - - static std::unique_ptr File(const char *filename, const char *value) - { - uint32_t len = std::strlen(value); - if (len > 16) - throw std::invalid_argument("String too long (max 16 bytes)"); - // note: we send only the ASCII bytes, no trailing '\0' - return File(filename, reinterpret_cast(value), len); - } - - // overload for a single 32‑bit integer - static std::unique_ptr File(const char *filename, int value) - { - uint8_t buffer[4]; - write_uint32_le(buffer, value); - return File(filename, buffer, 4); - } - - static std::unique_ptr Control(uint32_t value, bool encrypt = false) - { - return std::unique_ptr(new Command(CMD_CONTROL, value, encrypt)); - } - - static std::unique_ptr Encryption(uint32_t seed) - { - return std::unique_ptr(new Command(CMD_ENCRYPTION, seed, false)); - } - - static std::unique_ptr String(uint32_t cmd, const char *str, bool encrypt = true) - { - uint32_t length = std::strlen(str); - std::unique_ptr result(new Command(cmd, encrypt, length)); - if (length > 0) - std::memcpy(result->data, str, length); - return result; - } - - template - static std::unique_ptr String(uint32_t cmd, const char *format, Args... args) - { - char buffer[512]; - std::snprintf(buffer, sizeof(buffer), format, args...); - return String(cmd, buffer); - } - - static std::unique_ptr Touch(uint32_t action, float x, float y) - { - std::unique_ptr result(new Command(CMD_TOUCH, false, 16)); - write_uint32_le(result->data, action); - write_uint32_le(result->data + 4, int(10000 * x)); - write_uint32_le(result->data + 8, int(10000 * y)); - write_uint32_le(result->data + 12, 0); - return result; - } - - static std::unique_ptr Click(float x, float y, bool down) - { - return Touch(down ? 14 : 16, x, y); - } - - static std::unique_ptr Move(float x, float y) - { - return Touch(15, x, y); - } - - static std::unique_ptr Audio(std::unique_ptr chunk) - { - std::unique_ptr result(new Command(CMD_AUDIO_DATA, false, chunk->data, chunk->size)); - chunk->data = nullptr; - write_uint32_le(result->data, 5); - write_uint32_le(result->data + 4, 0); - write_uint32_le(result->data + 8, 3); - return result; - } - - static std::unique_ptr MultiTouch(const Multitouch &touches) - { - int count = touches.size(); - if (count == 0) - return nullptr; - - std::unique_ptr result(new Command(CMD_MULTI_TOUCH, false, sizeof(Multitouch::Touch) * count)); - uint8_t *buf = result->data; - for (int i = 0; i < count; ++i) - { - const Multitouch::Touch &t = touches[i]; - write_float_le(buf + 0, t.x); - write_float_le(buf + 4, t.y); - write_uint32_le(buf + 8, static_cast(t.state)); - write_uint32_le(buf + 12, static_cast(t.id)); - buf += 16; - } - return result; - } - - int command; - bool encryption; - uint32_t length; - uint8_t *data; -}; - -#endif /* SRC_STRUCT_COMMAND */ diff --git a/src/struct/message.h b/src/struct/message.h deleted file mode 100644 index 34fca75..0000000 --- a/src/struct/message.h +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef SRC_STRUCT_MESSAGE -#define SRC_STRUCT_MESSAGE - -#include "libavcodec/defs.h" -#include "helper/protocol_const.h" - -#include -#include -#include -#include - -#include "aes_cipher.h" - -#pragma pack(push, 1) -struct Header -{ - uint32_t magic; - int32_t length; - uint32_t type; - uint32_t typecheck; -}; -#pragma pack(pop) - -class Message -{ -public: - Message() : _header({0, 0, 0, 0}), _data(nullptr), _offset(0), _headerLegth(0), _dataLength(0), _valid(false), _ready(false) - { - } - - Message(uint8_t *data, uint32_t length, uint32_t offset) - : _header({0, static_cast(length), 0, 0}), - _data(data), - _offset(offset <= length ? offset : length), - _headerLegth(sizeof(Header)), - _dataLength(length), - _valid(true), - _ready(true) - { - } - - ~Message() - { - if (_data) - { - free(_data); - _data = nullptr; - } - } - - uint32_t parse(uint8_t *data, uint32_t data_length) - { - uint32_t result = 0; - - if (_headerLegth != sizeof(Header)) - { - uint8_t copy = sizeof(Header) - _headerLegth; - if (copy > data_length) - copy = data_length; - memcpy(reinterpret_cast(&_header) + _headerLegth, data, copy); - _headerLegth += copy; - result += copy; - - if (_headerLegth != sizeof(Header)) - return result; - - if ((_header.magic != MAGIC && _header.magic != MAGIC_ENC) || _header.length < 0) - { - _ready = true; - return 1; - } - - if (_header.length == 0) - { - _ready = true; - _valid = true; - return result; - } - } - - if (_data == nullptr) - { - uint32_t padding = _header.type == CMD_VIDEO_DATA ? AV_INPUT_BUFFER_PADDING_SIZE : 0; - _data = (uint8_t *)malloc(_header.length + padding); - if (_data) - { - _valid = true; - if (padding > 0) - std::fill(_data + _header.length, _data + _header.length + padding, 0); - } - } - - uint32_t copy = _header.length - _dataLength; - if (copy > data_length - result) - copy = data_length - result; - if (_valid) - memcpy(_data + _dataLength, data + result, copy); - _dataLength += copy; - result += copy; - - _ready = _dataLength >= static_cast(_header.length); - - return result; - } - - int getInt(uint32_t offset) const - { - int result = 0; - if (_data && _dataLength - sizeof(int) >= offset) - memcpy(&result, _data + offset, sizeof(int)); - return result; - } - - bool ready() const { return _ready; } - - bool valid() const { return _valid; } - - bool setOffset(uint32_t offset) - { - if (offset >= _dataLength) - return false; - _offset = offset; - return true; - } - - bool encrypted() const { return _header.magic == MAGIC_ENC; } - - bool decrypt(AESCipher *cipher) - { - if (!cipher) - return false; - return cipher->Decrypt(_data, _dataLength); - } - - uint8_t *data() const { return _data + _offset; } - uint32_t length() const { return _dataLength - _offset; } - uint32_t type() const { return _header.type; } - -private: - bool hasHeader() const { return _headerLegth == sizeof(Header); } - - Header _header; - uint8_t *_data; - uint32_t _offset; - - u_int8_t _headerLegth; - uint32_t _dataLength; - bool _valid; - bool _ready; -}; - -#endif /* SRC_STRUCT_MESSAGE */ diff --git a/src/struct/usb_buffer.cpp b/src/struct/usb_buffer.cpp new file mode 100644 index 0000000..7602ed2 --- /dev/null +++ b/src/struct/usb_buffer.cpp @@ -0,0 +1,153 @@ +#include "struct/usb_buffer.h" + +#include +#include +#include + +DataSlot::DataSlot() + : ready(false), offset(0), length(0), size(0), data(nullptr), _cv(nullptr) +{ +} + +DataSlot::~DataSlot() +{ + size = 0; + if (data) + { + free(data); + data = nullptr; + } +} + +void DataSlot::init(uint32_t slotSize, std::condition_variable *condition) +{ + ready.store(false); + offset = 0; + length = 0; + size = slotSize; + data = static_cast(malloc(size)); + _cv = condition; +} + +void DataSlot::reset() +{ + ready.store(false); + offset = 0; + length = 0; +} + +void DataSlot::commit(size_t dataSize) +{ + length = dataSize; + offset = 0; + ready.store(true); + + if (_cv) + _cv->notify_one(); +} + +bool DataSlot::consume(size_t dataSize) +{ + offset += dataSize; + if (offset < length) + return false; + ready.store(false); + return true; +} + +size_t DataSlot::remain() const +{ + return length > offset ? length - offset : 0; +} + +UsbBuffer::UsbBuffer(uint16_t slotCount, uint32_t slotSize) + : _slots(nullptr), _size(slotCount), _writeSlot(0), _readSlot(0) +{ + if (slotCount == 0 || slotSize == 0) + throw std::invalid_argument("Number of slots and slot size must be greater than 0"); + + _slots = new DataSlot[_size]; + + for (uint16_t i = 0; i < _size; i++) + { + _slots[i].init(slotSize, &_cv); + } +} + +UsbBuffer::~UsbBuffer() +{ + _cv.notify_all(); + if (_slots) + { + delete[] _slots; + } +} + +DataSlot *UsbBuffer::get() +{ + if (_slots[_writeSlot].ready.load()) + return nullptr; + DataSlot *slot = &(_slots[_writeSlot]); + _writeSlot++; + if (_writeSlot >= _size) + _writeSlot = 0; + return slot; +} + +bool UsbBuffer::read(uint8_t *dst, uint32_t length, std::atomic &active) +{ + if (length == 0) + return true; + + size_t done = 0; + while (length > 0) + { + while (!_slots[_readSlot].ready.load()) + { + std::unique_lock lock(_mutex); + _cv.wait(lock, [&]() + { return !active.load() || _slots[_readSlot].ready.load(); }); + if (!active.load()) + return false; + } + + size_t copy = _slots[_readSlot].remain(); + if (copy > length) + copy = length; + if (dst != nullptr) + std::memcpy(dst + done, _slots[_readSlot].data + _slots[_readSlot].offset, copy); + if (_slots[_readSlot].consume(copy)) + { + _readSlot++; + if (_readSlot >= _size) + _readSlot = 0; + } + done += copy; + length -= copy; + } + + return active.load(); +} + +void UsbBuffer::reset() +{ + _readSlot = 0; + _writeSlot = 0; + for (uint16_t i = 0; i < _size; i++) + { + _slots[i].reset(); + } +} + +void UsbBuffer::notify() +{ + _cv.notify_all(); +} + +int UsbBuffer::count() const +{ + int result = _writeSlot - _readSlot; + if (result < 0) + result += _size; + return result; +} diff --git a/src/struct/usb_buffer.h b/src/struct/usb_buffer.h index 3e7c421..b50db93 100644 --- a/src/struct/usb_buffer.h +++ b/src/struct/usb_buffer.h @@ -4,69 +4,22 @@ #include #include #include -#include #include #include -#include -#include class DataSlot { public: - DataSlot() - : ready(false), offset(0), length(0), size(0), data(nullptr), _cv(nullptr) - { - } + DataSlot(); + ~DataSlot(); - ~DataSlot() - { - size = 0; - if (data) - { - free(data); - data = nullptr; - } - } + void init(uint32_t slotSize, std::condition_variable *condition); + void reset(); + void commit(size_t dataSize); + bool consume(size_t dataSize); + size_t remain() const; - void init(uint32_t slotSize, std::condition_variable *condition) - { - ready = false; - offset = 0; - length = 0; - size = slotSize; - data = static_cast(malloc(size)); - _cv = condition; - } - - void reset() - { - ready = false; - offset = 0; - length = 0; - } - - void commit(size_t dataSize) - { - length = dataSize; - offset = 0; - ready = true; - - if (_cv) - _cv->notify_one(); - } - - bool consume(size_t dataSize) - { - offset += dataSize; - if (offset < length) - return false; - ready = false; - return true; - } - - size_t remain() const { return length > offset ? length - offset : 0; } - - bool ready; + std::atomic ready; size_t offset; size_t length; size_t size; @@ -79,119 +32,27 @@ private: class UsbBuffer { public: - UsbBuffer(uint16_t slotCount, size_t slotSize) - : _active(true), _size(slotCount) - { - if (slotCount == 0 || slotSize == 0) - throw std::invalid_argument("[UsbBuffer] Number of slots and slot size must be greater than 0"); - - _slots = new DataSlot[_size]; - - for (uint16_t i = 0; i < _size; i++) - { - _slots[i].init(slotSize, &_cvReady); - } - } + UsbBuffer(uint16_t slotCount, uint32_t slotSize); + ~UsbBuffer(); UsbBuffer(const UsbBuffer &) = delete; UsbBuffer &operator=(const UsbBuffer &) = delete; - ~UsbBuffer() - { - stop(); - if (_slots) - { - delete[] _slots; - } - } + DataSlot *get(); + bool read(uint8_t *dst, uint32_t length, std::atomic &active); - void start() - { - _readSlot = 0; - _writeSlot = 0; - for (uint16_t i = 0; i < _size; i++) - { - _slots[i].reset(); - } - _active = true; - } - - void stop() - { - _active = false; - std::lock_guard lock(_mutex); - _cvReady.notify_all(); - } - - DataSlot *get() - { - if (!_active || _slots[_writeSlot].ready) - throw std::runtime_error("[UsbBuffer] No free slots available"); - DataSlot *slot = &(_slots[_writeSlot]); - _writeSlot++; - if (_writeSlot >= _size) - _writeSlot = 0; - return slot; - } - - bool read(uint8_t *dst, size_t length) - { - if (length == 0) - return true; - - if (dst == nullptr) - throw std::invalid_argument("[UsbBuffer] Read destination is null"); - - size_t done = 0; - while (length > 0) - { - if (!_active) - return false; - - while (!_slots[_readSlot].ready) - { - std::unique_lock lock(_mutex); - _cvReady.wait(lock, [&]() - { return !_active || _slots[_readSlot].ready; }); - if (!_active) - return false; - } - - size_t copy = _slots[_readSlot].remain(); - if (copy > length) - copy = length; - std::memcpy(dst + done, _slots[_readSlot].data + _slots[_readSlot].offset, copy); - if (_slots[_readSlot].consume(copy)) - { - _readSlot++; - if (_readSlot >= _size) - _readSlot = 0; - } - done += copy; - length -= copy; - } - - return true; - } - - int count() const { - int result = _writeSlot - _readSlot; - if(result<0) - result += _size; - return result; - } + void reset(); + void notify(); + int count() const; private: - mutable std::mutex _mutex; - std::condition_variable _cvReady; + std::mutex _mutex; + std::condition_variable _cv; - std::atomic _active; - - uint16_t _writeSlot = 0; - uint16_t _readSlot = 0; - - DataSlot *_slots = nullptr; - uint16_t _size = 0; + DataSlot *_slots; + uint16_t _size; + uint16_t _writeSlot; + uint16_t _readSlot; }; #endif /* SRC_STRUCT_USB_BUFFER */ diff --git a/src/struct/video_buffer.h b/src/struct/video_buffer.h index cc4411f..52faabc 100644 --- a/src/struct/video_buffer.h +++ b/src/struct/video_buffer.h @@ -1,5 +1,5 @@ -#ifndef SRC_STRUCT_VIDEO_BUFFER -#define SRC_STRUCT_VIDEO_BUFFER +#ifndef SRC_STRUCT_VIDER_BUFFER2 +#define SRC_STRUCT_VIDER_BUFFER2 extern "C" { @@ -9,7 +9,7 @@ extern "C" #include #include -#define BUFFER_VIDEO_FRAMES 3 +#define BUFFER_VIDEO_FRAMES 4 class VideoBuffer { @@ -17,6 +17,7 @@ public: VideoBuffer() { _writing.store(0); + _oldest.store(-1); _reading.store(-1); _latest.store(-1); for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i) @@ -52,10 +53,15 @@ public: bool latest(AVFrame **frame, uint32_t *id) { - _reading.store(_latest.load()); + _reading.store(_oldest.load()); int index = _reading.load(); if (index < 0) - return false; + { + _reading.store(_latest.load()); + index = _reading.load(); + if (index < 0) + return false; + } *frame = _frames[index]; *id = _ids[index]; return true; @@ -63,13 +69,15 @@ public: void consume() { + if(_oldest.load() == _reading.load()) + _oldest.store(-1); _reading.store(-1); } AVFrame *write(uint32_t id) { int index = _writing.load(); - while (index == _reading.load() || index == _latest.load()) + while (index == _reading.load() || index == _latest.load() || index == _oldest.load()) { index = (index + 1) % BUFFER_VIDEO_FRAMES; } @@ -80,17 +88,20 @@ public: void commit() { + _oldest.store(_latest.load()); _latest.store(_writing.load()); } void reset() { _writing.store(0); + _oldest.store(-1); _reading.store(-1); _latest.store(-1); } private: + std::atomic _oldest; std::atomic _latest; std::atomic _reading; std::atomic _writing; @@ -98,4 +109,4 @@ private: uint32_t _ids[BUFFER_VIDEO_FRAMES]; }; -#endif /* SRC_STRUCT_VIDEO_BUFFER */ +#endif /* SRC_STRUCT_VIDER_BUFFER2 */