Optimise and refactor protocol, better logging, optimise rendering, double buffering.

This commit is contained in:
Niellune
2026-03-23 07:00:36 +02:00
parent b9047f65b8
commit b7e131bc38
41 changed files with 1746 additions and 1748 deletions
+38 -33
View File
@@ -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
+61 -70
View File
@@ -3,15 +3,13 @@
#include <SDL2/SDL_ttf.h>
#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<int> *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<Message> &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);
+7 -7
View File
@@ -3,11 +3,13 @@
#include <SDL2/SDL.h>
#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<int8_t> 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<Message> &queue, Renderer &renderer);
void loop();
SDL_Window *_window;
SDL_Renderer *_renderer;
bool _active;
Uint32 _evtBase;
SDL_DisplayMode _displayMode;
State _state;
int _width;
+49
View File
@@ -0,0 +1,49 @@
#ifndef SRC_COMMON_FUNCTIONS
#define SRC_COMMON_FUNCTIONS
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>
#include <SDL2/SDL.h>
#include "common/threading.h"
extern "C"
{
#include <libavutil/error.h>
}
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 */
+110
View File
@@ -0,0 +1,110 @@
#include "logger.h"
#ifndef DISBALE_LOG
#include <cstdio>
#include <ctime>
Logger::Logger()
: _level(Level::None)
{
}
Logger &Logger::instance()
{
static Logger logger;
return logger;
}
void Logger::setLevel(int level)
{
if (level < static_cast<int>(Level::None))
{
_level = Level::None;
return;
}
if (level > static_cast<int>(Level::Verbose))
{
_level = Level::Verbose;
return;
}
_level = static_cast<Level>(level);
}
bool Logger::enabled(Level msgLevel) const
{
return static_cast<int>(msgLevel) <= static_cast<int>(_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
+114
View File
@@ -0,0 +1,114 @@
#ifndef SRC_HELPER_LOGGER
#define SRC_HELPER_LOGGER
#include <cstdarg>
#include <cstdint>
#include <mutex>
#include <string>
#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 */
+64
View File
@@ -0,0 +1,64 @@
#ifndef SRC_STRUCT_STATUS
#define SRC_STRUCT_STATUS
#include <cstdarg>
#include <cstdio>
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 */
+24
View File
@@ -0,0 +1,24 @@
#ifndef SRC_HELPER_THREADING
#define SRC_HELPER_THREADING
#if defined(__linux__) || defined(__APPLE__)
#include <pthread.h>
#endif
extern "C"
{
#include <libavutil/error.h>
}
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 */
+294 -375
View File
@@ -4,36 +4,45 @@
#include <iostream>
#include <iomanip>
#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<int8_t>* 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> 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<Command> packet)
void Connector::onMessage(std::unique_ptr<Message> 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<std::mutex> 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<unsigned>(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<char>(data[i]);
if (ch == '\n' || ch == '\r')
oss << '.';
else
oss << (std::isprint(static_cast<unsigned char>(ch)) ? ch : '.');
}
}
std::cout << oss.str() << std::endl;
}
void Connector::onUsbRead(libusb_transfer *transfer)
{
UsbContext *c = static_cast<UsbContext *>(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<uint8_t *>(&header), sizeof(Header)))
break;
int32_t payloadLength = static_cast<size_t>(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<uint8_t *>(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<Command> 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;
}
}
+35 -59
View File
@@ -8,89 +8,65 @@
#include <mutex>
#include <string>
#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<int8_t> *statusHandler);
void stop();
bool send(std::unique_ptr<Command> 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> message) { return writeQueue.pushDiscard(std::move(message)); }
void setEncryption(bool enabled);
AtomicQueue<Message> writeQueue;
AtomicQueue<Message> videoStream;
AtomicQueue<Message> audioStreamMain;
AtomicQueue<Message> 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> 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<bool> _connected = false;
std::atomic<bool> _ecnrypt = false;
Recorder _recorder;
AESCipher *_cipher;
ConnectionReader _reader;
libusb_context *_context;
std::thread _thread;
std::atomic<bool> _active;
std::atomic<bool> _connected;
std::atomic<bool> _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<bool> _active = false;
AtomicQueue<Command> _queue{COMMAND_QUEUE_SIZE};
UsbContext _usbContext[MAX_USB_REQUESTS] = {};
atomic<int8_t> *_statusHandler;
};
#endif /* SRC_CONNECTOR */
+25 -18
View File
@@ -1,7 +1,8 @@
#include "decoder.h"
#include <iostream>
#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;
}
+1 -3
View File
@@ -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<bool> _active = false;
-81
View File
@@ -1,81 +0,0 @@
#ifndef SRC_ERROR
#define SRC_ERROR
#include <string>
#include <stdexcept>
#include "helper/functions.h"
extern "C"
{
#include <libavutil/error.h>
}
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 */
-78
View File
@@ -1,78 +0,0 @@
#ifndef SRC_HELPER_FUNCTIONS
#define SRC_HELPER_FUNCTIONS
#include <iostream>
#include <cstring>
#include <SDL2/SDL.h>
#if defined(__linux__) || defined(__APPLE__)
#include <pthread.h>
#endif
extern "C"
{
#include <libavutil/error.h>
}
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 */
-15
View File
@@ -1,15 +0,0 @@
#ifndef SRC_HELPER_ISENDER
#define SRC_HELPER_ISENDER
#include <memory>
#include "struct/command.h"
class ISender
{
public:
virtual ~ISender() = default;
virtual bool send(std::unique_ptr<Command> packet) = 0;
};
#endif /* SRC_HELPER_ISENDER */
+1 -1
View File
@@ -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),
+6 -7
View File
@@ -2,20 +2,19 @@
#include <iostream>
#include <memory>
#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;
}
}
+9 -8
View File
@@ -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);
}
+1 -1
View File
@@ -8,7 +8,7 @@
#include <SDL2/SDL.h>
#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
+7 -5
View File
@@ -5,9 +5,11 @@
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <sys/stat.h>
#include <unistd.h>
#include <stdexcept>
#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<int>(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);
}
-266
View File
@@ -1,266 +0,0 @@
#include "protocol.h"
#include "helper/protocol_const.h"
#include "helper/functions.h"
#include "settings.h"
#include <algorithm>
#include <cstring>
#include <ctime>
#include <iostream>
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<Message>(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<Message>(data, length, 12));
dispose = false;
}
else if (channel == 2)
{
audioStreamAux.pushDiscard(std::make_unique<Message>(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<Message> 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;
}
}
-47
View File
@@ -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<Message> videoData;
AtomicQueue<Message> audioStreamMain;
AtomicQueue<Message> 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<Message> 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 */
@@ -1,11 +1,12 @@
#include "aes_cipher.h"
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <memory>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <stdexcept>
#include <algorithm>
#include <iostream>
#include <ctime>
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<uint8_t>(_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, decltype(&EVP_CIPHER_CTX_free)>(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<uint8_t[]> 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, decltype(&EVP_CIPHER_CTX_free)>(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<uint8_t[]> 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;
}
return Status::Success();
}
@@ -2,9 +2,11 @@
#define SRC_AES_CIPHER
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <memory>
#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; }
+217
View File
@@ -0,0 +1,217 @@
#include "protocol/connection_reader.h"
#include <algorithm>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <utility>
#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<Context *>(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> message = std::make_unique<Message>();
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");
}
+61
View File
@@ -0,0 +1,61 @@
#ifndef SRC_PROTOCOL_CONNECTION_READER
#define SRC_PROTOCOL_CONNECTION_READER
#include <libusb-1.0/libusb.h>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <thread>
#include <vector>
#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> 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<bool> _active;
UsbBuffer _buffer;
std::vector<Context> _transfers;
IMessageReceiver *_receiver;
std::thread _readThread;
std::thread _processThread;
libusb_context *_usbContext;
};
#endif /* SRC_PROTOCOL_CONNECTION_READER */
+15
View File
@@ -0,0 +1,15 @@
#ifndef SRC_HELPER_ISENDER
#define SRC_HELPER_ISENDER
#include <memory>
#include "protocol/message.h"
class IMessageSender
{
public:
virtual ~IMessageSender() = default;
virtual bool send(std::unique_ptr<Message> packet) = 0;
};
#endif /* SRC_HELPER_ISENDER */
+325
View File
@@ -0,0 +1,325 @@
#ifndef SRC_PROTOCOL_MESSAGE
#define SRC_PROTOCOL_MESSAGE
#include <algorithm>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#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<uint32_t>(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<uint8_t *>(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<Message> Init(int width, int height, int fps)
{
std::unique_ptr<Message> 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<Message> File(const char *filename, const uint8_t *data, uint32_t length)
{
// filename is assumed nullterminated, 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<Message> 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<Message> 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<const uint8_t *>(value), len);
}
// overload for a single 32bit integer
static std::unique_ptr<Message> File(const char *filename, int value)
{
uint8_t buffer[4];
write_uint32_le(buffer, value);
return File(filename, buffer, 4);
}
static std::unique_ptr<Message> Control(uint32_t value, bool encrypt = false)
{
return std::unique_ptr<Message>(new Message(CMD_CONTROL, value, encrypt));
}
static std::unique_ptr<Message> Encryption(uint32_t seed)
{
return std::unique_ptr<Message>(new Message(CMD_ENCRYPTION, seed, false));
}
static std::unique_ptr<Message> String(uint32_t cmd, const char *str, bool encrypt = true)
{
uint32_t length = std::strlen(str);
std::unique_ptr<Message> result(new Message(cmd, encrypt, length));
if (length > 0)
std::memcpy(result->_data, str, length);
return result;
}
template <typename... Args>
static std::unique_ptr<Message> 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<Message> Touch(uint32_t action, float x, float y)
{
std::unique_ptr<Message> 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<Message> Click(float x, float y, bool down)
{
return Touch(down ? 14 : 16, x, y);
}
static std::unique_ptr<Message> Move(float x, float y)
{
return Touch(15, x, y);
}
static std::unique_ptr<Message> Audio(int length)
{
std::unique_ptr<Message> 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<Message> MultiTouch(const Multitouch &touches)
{
int count = touches.size();
if (count == 0)
return nullptr;
std::unique_ptr<Message> 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<uint32_t>(t.state));
write_uint32_le(buf + 12, static_cast<uint32_t>(t.id));
buf += 16;
}
return result;
}
static std::unique_ptr<Message> HeartBeat()
{
return std::unique_ptr<Message>(new Message(CMD_HEARTBEAT, false));
}
bool allocated() const { return _header.length <= 0 || static_cast<uint32_t>(_header.length) <= _size; }
bool encrypted() const { return _header.magic == MAGIC_ENC; }
uint8_t *header() { return reinterpret_cast<uint8_t *>(&_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 */
@@ -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 */
+31 -50
View File
@@ -3,58 +3,30 @@
#include <iostream>
#include <cstring>
#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<Message> *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<Recorder *>(userdata);
std::unique_ptr<AudioChunk> 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<AudioChunk> 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<Recorder *>(userdata);
if (!self->_queue)
return;
std::unique_ptr<Message> message = Message::Audio(len);
if(!message->allocated())
return;
std::memcpy(message->data(), stream, len);
self->_queue->pushDiscard(std::move(message));
}
+4 -6
View File
@@ -6,9 +6,8 @@
#include <SDL2/SDL.h>
#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<Message> *queue);
void stop();
private:
static void AudioCallback(void *userdata, Uint8 *stream, int len);
void runner();
ISender *_sender;
AtomicQueue<Message> *_queue;
std::atomic<bool> _active;
std::thread _thread;
AtomicQueue<AudioChunk> _data;
SDL_AudioDeviceID _device;
};
#endif /* SRC_RECORDER */
+15 -15
View File
@@ -1,8 +1,8 @@
#include "renderer.h"
#include <cstring>
#include <iostream>
#include "settings.h"
#include "helper/functions.h"
#include "common/functions.h"
#include "common/logger.h"
#include <SDL2/SDL_ttf.h>
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<AVPixelFormat>(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;
}
+6 -4
View File
@@ -3,12 +3,14 @@
#include <iostream>
#include <fstream>
#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());
}
}
+7 -7
View File
@@ -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<int> fps{"fps", 50};
static inline Setting<int> screenMode{"window-mode", 0};
static inline Setting<bool> cursor{"cursor", false};
static inline Setting<bool> logging{"logging", false};
static inline Setting<int> loglevel{"log-level", 2};
// Device configurations section
static inline Setting<bool> encryption{"encryption", false};
@@ -32,7 +32,7 @@ public:
static inline Setting<bool> bluetoothAudio{"bluetooth-audio", false};
static inline Setting<int> micType{"mic-type", 1};
static inline Setting<int> dpi{"android-dpi", 120};
static inline Setting<int> androidMode{"android-resolution", 0};
static inline Setting<int> androidMode{"android-resolution", 1};
static inline Setting<int> mediaDelay{"android-media-delay", 300};
// Application configuration section
@@ -40,12 +40,14 @@ public:
static inline Setting<bool> vsync{"vsync", false};
static inline Setting<bool> hwDecode{"hw-decode", true};
static inline Setting<int> forceRedraw{"force-redraw", 0};
static inline Setting<int> eventsSkip{"draw-skip-events", 0};
static inline Setting<float> aspectCorrection{"aspect-correction", 1};
static inline Setting<std::string> renderDriver{"renderer-driver", ""};
static inline Setting<bool> alternativeRendering{"alternative-rendering", true};
static inline Setting<bool> fastScale{"fast-render-scale", false};
static inline Setting<int> usbQueue{"async-usb-calls", 8};
static inline Setting<int> usbBufferSize{"usb-buffer-size", 131072};
static inline Setting<int> usbQueue{"async-usb-calls", 16};
static inline Setting<int> usbTransferSize{"usb-buffer-size", 2048};
static inline Setting<int> usbBuffer{"usb-buffer", 64};
static inline Setting<int> videoQueue{"video-buffer-size", 64};
static inline Setting<int> audioQueue{"audio-buffer-size", 64};
static inline Setting<int> audioDelay{"audio-buffer-wait", 2};
@@ -53,7 +55,6 @@ public:
static inline Setting<float> audioFade{"audio-fade", 0.3};
static inline Setting<int> audioBuffer{"audio-buffer-samples", 512};
static inline Setting<std::string> audioDriver{"audio-driver", ""};
static inline Setting<std::string> onConnect{"on-connect-script", ""};
static inline Setting<std::string> onDisconnect{"on-disconnect-script", ""};
@@ -83,7 +84,6 @@ public:
static inline Setting<std::string> keyPipe{"key-pipe-path", ""};
// Debug section
static inline Setting<int> protocolDebug{"protocol-debug", 0};
static inline Setting<bool> codecLowDelay{"decode-low-delay", true};
static inline Setting<bool> codecFast{"decode-fast", true};
-31
View File
@@ -1,31 +0,0 @@
#ifndef SRC_STRUCT_AUDIO_CHUNK
#define SRC_STRUCT_AUDIO_CHUNK
#include <cstdlib>
#include <cstddef>
#include <cstdint>
class AudioChunk
{
public:
AudioChunk(uint16_t size)
: data(nullptr), size(size)
{
if (size > 0)
data = static_cast<uint8_t *>(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 */
-189
View File
@@ -1,189 +0,0 @@
#ifndef SRC_STRUCT_COMMAND
#define SRC_STRUCT_COMMAND
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <cstdio>
#include <memory>
#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<uint8_t *>(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<Command> Init(int width, int height, int fps)
{
std::unique_ptr<Command> 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<Command> File(const char *filename, const uint8_t *data, uint32_t length)
{
// filename is assumed nullterminated, 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<Command> 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<Command> 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<const uint8_t *>(value), len);
}
// overload for a single 32bit integer
static std::unique_ptr<Command> File(const char *filename, int value)
{
uint8_t buffer[4];
write_uint32_le(buffer, value);
return File(filename, buffer, 4);
}
static std::unique_ptr<Command> Control(uint32_t value, bool encrypt = false)
{
return std::unique_ptr<Command>(new Command(CMD_CONTROL, value, encrypt));
}
static std::unique_ptr<Command> Encryption(uint32_t seed)
{
return std::unique_ptr<Command>(new Command(CMD_ENCRYPTION, seed, false));
}
static std::unique_ptr<Command> String(uint32_t cmd, const char *str, bool encrypt = true)
{
uint32_t length = std::strlen(str);
std::unique_ptr<Command> result(new Command(cmd, encrypt, length));
if (length > 0)
std::memcpy(result->data, str, length);
return result;
}
template <typename... Args>
static std::unique_ptr<Command> 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<Command> Touch(uint32_t action, float x, float y)
{
std::unique_ptr<Command> 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<Command> Click(float x, float y, bool down)
{
return Touch(down ? 14 : 16, x, y);
}
static std::unique_ptr<Command> Move(float x, float y)
{
return Touch(15, x, y);
}
static std::unique_ptr<Command> Audio(std::unique_ptr<AudioChunk> chunk)
{
std::unique_ptr<Command> 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<Command> MultiTouch(const Multitouch &touches)
{
int count = touches.size();
if (count == 0)
return nullptr;
std::unique_ptr<Command> 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<uint32_t>(t.state));
write_uint32_le(buf + 12, static_cast<uint32_t>(t.id));
buf += 16;
}
return result;
}
int command;
bool encryption;
uint32_t length;
uint8_t *data;
};
#endif /* SRC_STRUCT_COMMAND */
-152
View File
@@ -1,152 +0,0 @@
#ifndef SRC_STRUCT_MESSAGE
#define SRC_STRUCT_MESSAGE
#include "libavcodec/defs.h"
#include "helper/protocol_const.h"
#include <cstdint>
#include <cstring>
#include <memory>
#include <cstdlib>
#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<int32_t>(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<uint8_t *>(&_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<uint32_t>(_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 */
+153
View File
@@ -0,0 +1,153 @@
#include "struct/usb_buffer.h"
#include <cstdlib>
#include <cstring>
#include <stdexcept>
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<uint8_t *>(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<bool> &active)
{
if (length == 0)
return true;
size_t done = 0;
while (length > 0)
{
while (!_slots[_readSlot].ready.load())
{
std::unique_lock<std::mutex> 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;
}
+21 -160
View File
@@ -4,69 +4,22 @@
#include <cstddef>
#include <cstdint>
#include <atomic>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <stdexcept>
#include <cstring>
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<uint8_t *>(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<bool> 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<bool> &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<std::mutex> 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<std::mutex> 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<bool> _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 */
+18 -7
View File
@@ -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 <atomic>
#include <stdexcept>
#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<int8_t> _oldest;
std::atomic<int8_t> _latest;
std::atomic<int8_t> _reading;
std::atomic<int8_t> _writing;
@@ -98,4 +109,4 @@ private:
uint32_t _ids[BUFFER_VIDEO_FRAMES];
};
#endif /* SRC_STRUCT_VIDEO_BUFFER */
#endif /* SRC_STRUCT_VIDER_BUFFER2 */