mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
Optimise and refactor protocol, better logging, optimise rendering, double buffering.
This commit is contained in:
+38
-33
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 null‑terminated, so strlen + 1 to include the '\0'
|
||||
uint32_t fn_len = strlen(filename) + 1;
|
||||
|
||||
// Total buffer size: 4 (fn_len) + fn_len + 4 (content_len) + content_len
|
||||
std::unique_ptr<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 32‑bit 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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};
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 null‑terminated, so strlen + 1 to include the '\0'
|
||||
uint32_t fn_len = strlen(filename) + 1;
|
||||
|
||||
// Total buffer size: 4 (fn_len) + fn_len + 4 (content_len) + content_len
|
||||
std::unique_ptr<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 32‑bit 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 */
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user