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

This commit is contained in:
Niellune
2026-03-23 07:00:36 +02:00
parent b9047f65b8
commit b7e131bc38
41 changed files with 1746 additions and 1748 deletions
-31
View File
@@ -1,31 +0,0 @@
#ifndef SRC_STRUCT_AUDIO_CHUNK
#define SRC_STRUCT_AUDIO_CHUNK
#include <cstdlib>
#include <cstddef>
#include <cstdint>
class AudioChunk
{
public:
AudioChunk(uint16_t size)
: data(nullptr), size(size)
{
if (size > 0)
data = static_cast<uint8_t *>(malloc(size));
}
~AudioChunk()
{
free(data);
}
// Deleted copy constructor/assignment
AudioChunk(const AudioChunk &) = delete;
AudioChunk &operator=(const AudioChunk &) = delete;
uint8_t *data;
uint16_t size;
};
#endif /* SRC_STRUCT_AUDIO_CHUNK */
-189
View File
@@ -1,189 +0,0 @@
#ifndef SRC_STRUCT_COMMAND
#define SRC_STRUCT_COMMAND
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <cstdio>
#include <memory>
#include "helper/functions.h"
#include "helper/protocol_const.h"
#include "struct/audio_chunk.h"
#include "struct/multitouch.h"
class Command
{
public:
Command(const Command &) = delete;
Command &operator=(const Command &) = delete;
Command(int cmd, bool encrypt = true, uint32_t size = 0)
: command(cmd), encryption(encrypt), length(size), data(nullptr)
{
if (size > 0)
data = static_cast<uint8_t *>(malloc(size));
}
Command(int cmd, uint32_t value, bool encrypt = true)
: Command(cmd, encrypt, 4)
{
write_uint32_le(data, value);
}
Command(int cmd, bool encrypt, uint8_t *buffer, uint32_t size)
: command(cmd), encryption(encrypt), length(size), data(buffer)
{
}
~Command()
{
if (data)
{
free(data);
data = nullptr;
}
}
static std::unique_ptr<Command> Init(int width, int height, int fps)
{
std::unique_ptr<Command> result(new Command(CMD_OPEN, true, 28));
write_uint32_le(result->data + 0, width);
write_uint32_le(result->data + 4, height);
write_uint32_le(result->data + 8, fps);
write_uint32_le(result->data + 12, 5);
write_uint32_le(result->data + 16, 49152);
write_uint32_le(result->data + 20, 2);
write_uint32_le(result->data + 24, 2);
return result;
}
static std::unique_ptr<Command> File(const char *filename, const uint8_t *data, uint32_t length)
{
// filename is assumed nullterminated, so strlen + 1 to include the '\0'
uint32_t fn_len = strlen(filename) + 1;
// Total buffer size: 4 (fn_len) + fn_len + 4 (content_len) + content_len
std::unique_ptr<Command> result(new Command(CMD_SEND_FILE, true, 4 + fn_len + 4 + length));
uint8_t *buf = result->data;
// 1) filename length (LE)
write_uint32_le(buf, fn_len);
buf += 4;
// 2) filename bytes (including the '\0')
std::memcpy(buf, filename, fn_len);
buf += fn_len;
// 3) content length (LE)
write_uint32_le(buf, length);
buf += 4;
// 4) content bytes
if (length > 0 && data)
std::memcpy(buf, data, length);
return result;
}
static std::unique_ptr<Command> File(const char *filename, const char *value)
{
uint32_t len = std::strlen(value);
if (len > 16)
throw std::invalid_argument("String too long (max 16 bytes)");
// note: we send only the ASCII bytes, no trailing '\0'
return File(filename, reinterpret_cast<const uint8_t *>(value), len);
}
// overload for a single 32bit integer
static std::unique_ptr<Command> File(const char *filename, int value)
{
uint8_t buffer[4];
write_uint32_le(buffer, value);
return File(filename, buffer, 4);
}
static std::unique_ptr<Command> Control(uint32_t value, bool encrypt = false)
{
return std::unique_ptr<Command>(new Command(CMD_CONTROL, value, encrypt));
}
static std::unique_ptr<Command> Encryption(uint32_t seed)
{
return std::unique_ptr<Command>(new Command(CMD_ENCRYPTION, seed, false));
}
static std::unique_ptr<Command> String(uint32_t cmd, const char *str, bool encrypt = true)
{
uint32_t length = std::strlen(str);
std::unique_ptr<Command> result(new Command(cmd, encrypt, length));
if (length > 0)
std::memcpy(result->data, str, length);
return result;
}
template <typename... Args>
static std::unique_ptr<Command> String(uint32_t cmd, const char *format, Args... args)
{
char buffer[512];
std::snprintf(buffer, sizeof(buffer), format, args...);
return String(cmd, buffer);
}
static std::unique_ptr<Command> Touch(uint32_t action, float x, float y)
{
std::unique_ptr<Command> result(new Command(CMD_TOUCH, false, 16));
write_uint32_le(result->data, action);
write_uint32_le(result->data + 4, int(10000 * x));
write_uint32_le(result->data + 8, int(10000 * y));
write_uint32_le(result->data + 12, 0);
return result;
}
static std::unique_ptr<Command> Click(float x, float y, bool down)
{
return Touch(down ? 14 : 16, x, y);
}
static std::unique_ptr<Command> Move(float x, float y)
{
return Touch(15, x, y);
}
static std::unique_ptr<Command> Audio(std::unique_ptr<AudioChunk> chunk)
{
std::unique_ptr<Command> result(new Command(CMD_AUDIO_DATA, false, chunk->data, chunk->size));
chunk->data = nullptr;
write_uint32_le(result->data, 5);
write_uint32_le(result->data + 4, 0);
write_uint32_le(result->data + 8, 3);
return result;
}
static std::unique_ptr<Command> MultiTouch(const Multitouch &touches)
{
int count = touches.size();
if (count == 0)
return nullptr;
std::unique_ptr<Command> result(new Command(CMD_MULTI_TOUCH, false, sizeof(Multitouch::Touch) * count));
uint8_t *buf = result->data;
for (int i = 0; i < count; ++i)
{
const Multitouch::Touch &t = touches[i];
write_float_le(buf + 0, t.x);
write_float_le(buf + 4, t.y);
write_uint32_le(buf + 8, static_cast<uint32_t>(t.state));
write_uint32_le(buf + 12, static_cast<uint32_t>(t.id));
buf += 16;
}
return result;
}
int command;
bool encryption;
uint32_t length;
uint8_t *data;
};
#endif /* SRC_STRUCT_COMMAND */
-152
View File
@@ -1,152 +0,0 @@
#ifndef SRC_STRUCT_MESSAGE
#define SRC_STRUCT_MESSAGE
#include "libavcodec/defs.h"
#include "helper/protocol_const.h"
#include <cstdint>
#include <cstring>
#include <memory>
#include <cstdlib>
#include "aes_cipher.h"
#pragma pack(push, 1)
struct Header
{
uint32_t magic;
int32_t length;
uint32_t type;
uint32_t typecheck;
};
#pragma pack(pop)
class Message
{
public:
Message() : _header({0, 0, 0, 0}), _data(nullptr), _offset(0), _headerLegth(0), _dataLength(0), _valid(false), _ready(false)
{
}
Message(uint8_t *data, uint32_t length, uint32_t offset)
: _header({0, static_cast<int32_t>(length), 0, 0}),
_data(data),
_offset(offset <= length ? offset : length),
_headerLegth(sizeof(Header)),
_dataLength(length),
_valid(true),
_ready(true)
{
}
~Message()
{
if (_data)
{
free(_data);
_data = nullptr;
}
}
uint32_t parse(uint8_t *data, uint32_t data_length)
{
uint32_t result = 0;
if (_headerLegth != sizeof(Header))
{
uint8_t copy = sizeof(Header) - _headerLegth;
if (copy > data_length)
copy = data_length;
memcpy(reinterpret_cast<uint8_t *>(&_header) + _headerLegth, data, copy);
_headerLegth += copy;
result += copy;
if (_headerLegth != sizeof(Header))
return result;
if ((_header.magic != MAGIC && _header.magic != MAGIC_ENC) || _header.length < 0)
{
_ready = true;
return 1;
}
if (_header.length == 0)
{
_ready = true;
_valid = true;
return result;
}
}
if (_data == nullptr)
{
uint32_t padding = _header.type == CMD_VIDEO_DATA ? AV_INPUT_BUFFER_PADDING_SIZE : 0;
_data = (uint8_t *)malloc(_header.length + padding);
if (_data)
{
_valid = true;
if (padding > 0)
std::fill(_data + _header.length, _data + _header.length + padding, 0);
}
}
uint32_t copy = _header.length - _dataLength;
if (copy > data_length - result)
copy = data_length - result;
if (_valid)
memcpy(_data + _dataLength, data + result, copy);
_dataLength += copy;
result += copy;
_ready = _dataLength >= static_cast<uint32_t>(_header.length);
return result;
}
int getInt(uint32_t offset) const
{
int result = 0;
if (_data && _dataLength - sizeof(int) >= offset)
memcpy(&result, _data + offset, sizeof(int));
return result;
}
bool ready() const { return _ready; }
bool valid() const { return _valid; }
bool setOffset(uint32_t offset)
{
if (offset >= _dataLength)
return false;
_offset = offset;
return true;
}
bool encrypted() const { return _header.magic == MAGIC_ENC; }
bool decrypt(AESCipher *cipher)
{
if (!cipher)
return false;
return cipher->Decrypt(_data, _dataLength);
}
uint8_t *data() const { return _data + _offset; }
uint32_t length() const { return _dataLength - _offset; }
uint32_t type() const { return _header.type; }
private:
bool hasHeader() const { return _headerLegth == sizeof(Header); }
Header _header;
uint8_t *_data;
uint32_t _offset;
u_int8_t _headerLegth;
uint32_t _dataLength;
bool _valid;
bool _ready;
};
#endif /* SRC_STRUCT_MESSAGE */
+153
View File
@@ -0,0 +1,153 @@
#include "struct/usb_buffer.h"
#include <cstdlib>
#include <cstring>
#include <stdexcept>
DataSlot::DataSlot()
: ready(false), offset(0), length(0), size(0), data(nullptr), _cv(nullptr)
{
}
DataSlot::~DataSlot()
{
size = 0;
if (data)
{
free(data);
data = nullptr;
}
}
void DataSlot::init(uint32_t slotSize, std::condition_variable *condition)
{
ready.store(false);
offset = 0;
length = 0;
size = slotSize;
data = static_cast<uint8_t *>(malloc(size));
_cv = condition;
}
void DataSlot::reset()
{
ready.store(false);
offset = 0;
length = 0;
}
void DataSlot::commit(size_t dataSize)
{
length = dataSize;
offset = 0;
ready.store(true);
if (_cv)
_cv->notify_one();
}
bool DataSlot::consume(size_t dataSize)
{
offset += dataSize;
if (offset < length)
return false;
ready.store(false);
return true;
}
size_t DataSlot::remain() const
{
return length > offset ? length - offset : 0;
}
UsbBuffer::UsbBuffer(uint16_t slotCount, uint32_t slotSize)
: _slots(nullptr), _size(slotCount), _writeSlot(0), _readSlot(0)
{
if (slotCount == 0 || slotSize == 0)
throw std::invalid_argument("Number of slots and slot size must be greater than 0");
_slots = new DataSlot[_size];
for (uint16_t i = 0; i < _size; i++)
{
_slots[i].init(slotSize, &_cv);
}
}
UsbBuffer::~UsbBuffer()
{
_cv.notify_all();
if (_slots)
{
delete[] _slots;
}
}
DataSlot *UsbBuffer::get()
{
if (_slots[_writeSlot].ready.load())
return nullptr;
DataSlot *slot = &(_slots[_writeSlot]);
_writeSlot++;
if (_writeSlot >= _size)
_writeSlot = 0;
return slot;
}
bool UsbBuffer::read(uint8_t *dst, uint32_t length, std::atomic<bool> &active)
{
if (length == 0)
return true;
size_t done = 0;
while (length > 0)
{
while (!_slots[_readSlot].ready.load())
{
std::unique_lock<std::mutex> lock(_mutex);
_cv.wait(lock, [&]()
{ return !active.load() || _slots[_readSlot].ready.load(); });
if (!active.load())
return false;
}
size_t copy = _slots[_readSlot].remain();
if (copy > length)
copy = length;
if (dst != nullptr)
std::memcpy(dst + done, _slots[_readSlot].data + _slots[_readSlot].offset, copy);
if (_slots[_readSlot].consume(copy))
{
_readSlot++;
if (_readSlot >= _size)
_readSlot = 0;
}
done += copy;
length -= copy;
}
return active.load();
}
void UsbBuffer::reset()
{
_readSlot = 0;
_writeSlot = 0;
for (uint16_t i = 0; i < _size; i++)
{
_slots[i].reset();
}
}
void UsbBuffer::notify()
{
_cv.notify_all();
}
int UsbBuffer::count() const
{
int result = _writeSlot - _readSlot;
if (result < 0)
result += _size;
return result;
}
+21 -160
View File
@@ -4,69 +4,22 @@
#include <cstddef>
#include <cstdint>
#include <atomic>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <stdexcept>
#include <cstring>
class DataSlot
{
public:
DataSlot()
: ready(false), offset(0), length(0), size(0), data(nullptr), _cv(nullptr)
{
}
DataSlot();
~DataSlot();
~DataSlot()
{
size = 0;
if (data)
{
free(data);
data = nullptr;
}
}
void init(uint32_t slotSize, std::condition_variable *condition);
void reset();
void commit(size_t dataSize);
bool consume(size_t dataSize);
size_t remain() const;
void init(uint32_t slotSize, std::condition_variable *condition)
{
ready = false;
offset = 0;
length = 0;
size = slotSize;
data = static_cast<uint8_t *>(malloc(size));
_cv = condition;
}
void reset()
{
ready = false;
offset = 0;
length = 0;
}
void commit(size_t dataSize)
{
length = dataSize;
offset = 0;
ready = true;
if (_cv)
_cv->notify_one();
}
bool consume(size_t dataSize)
{
offset += dataSize;
if (offset < length)
return false;
ready = false;
return true;
}
size_t remain() const { return length > offset ? length - offset : 0; }
bool ready;
std::atomic<bool> ready;
size_t offset;
size_t length;
size_t size;
@@ -79,119 +32,27 @@ private:
class UsbBuffer
{
public:
UsbBuffer(uint16_t slotCount, size_t slotSize)
: _active(true), _size(slotCount)
{
if (slotCount == 0 || slotSize == 0)
throw std::invalid_argument("[UsbBuffer] Number of slots and slot size must be greater than 0");
_slots = new DataSlot[_size];
for (uint16_t i = 0; i < _size; i++)
{
_slots[i].init(slotSize, &_cvReady);
}
}
UsbBuffer(uint16_t slotCount, uint32_t slotSize);
~UsbBuffer();
UsbBuffer(const UsbBuffer &) = delete;
UsbBuffer &operator=(const UsbBuffer &) = delete;
~UsbBuffer()
{
stop();
if (_slots)
{
delete[] _slots;
}
}
DataSlot *get();
bool read(uint8_t *dst, uint32_t length, std::atomic<bool> &active);
void start()
{
_readSlot = 0;
_writeSlot = 0;
for (uint16_t i = 0; i < _size; i++)
{
_slots[i].reset();
}
_active = true;
}
void stop()
{
_active = false;
std::lock_guard<std::mutex> lock(_mutex);
_cvReady.notify_all();
}
DataSlot *get()
{
if (!_active || _slots[_writeSlot].ready)
throw std::runtime_error("[UsbBuffer] No free slots available");
DataSlot *slot = &(_slots[_writeSlot]);
_writeSlot++;
if (_writeSlot >= _size)
_writeSlot = 0;
return slot;
}
bool read(uint8_t *dst, size_t length)
{
if (length == 0)
return true;
if (dst == nullptr)
throw std::invalid_argument("[UsbBuffer] Read destination is null");
size_t done = 0;
while (length > 0)
{
if (!_active)
return false;
while (!_slots[_readSlot].ready)
{
std::unique_lock<std::mutex> lock(_mutex);
_cvReady.wait(lock, [&]()
{ return !_active || _slots[_readSlot].ready; });
if (!_active)
return false;
}
size_t copy = _slots[_readSlot].remain();
if (copy > length)
copy = length;
std::memcpy(dst + done, _slots[_readSlot].data + _slots[_readSlot].offset, copy);
if (_slots[_readSlot].consume(copy))
{
_readSlot++;
if (_readSlot >= _size)
_readSlot = 0;
}
done += copy;
length -= copy;
}
return true;
}
int count() const {
int result = _writeSlot - _readSlot;
if(result<0)
result += _size;
return result;
}
void reset();
void notify();
int count() const;
private:
mutable std::mutex _mutex;
std::condition_variable _cvReady;
std::mutex _mutex;
std::condition_variable _cv;
std::atomic<bool> _active;
uint16_t _writeSlot = 0;
uint16_t _readSlot = 0;
DataSlot *_slots = nullptr;
uint16_t _size = 0;
DataSlot *_slots;
uint16_t _size;
uint16_t _writeSlot;
uint16_t _readSlot;
};
#endif /* SRC_STRUCT_USB_BUFFER */
+18 -7
View File
@@ -1,5 +1,5 @@
#ifndef SRC_STRUCT_VIDEO_BUFFER
#define SRC_STRUCT_VIDEO_BUFFER
#ifndef SRC_STRUCT_VIDER_BUFFER2
#define SRC_STRUCT_VIDER_BUFFER2
extern "C"
{
@@ -9,7 +9,7 @@ extern "C"
#include <atomic>
#include <stdexcept>
#define BUFFER_VIDEO_FRAMES 3
#define BUFFER_VIDEO_FRAMES 4
class VideoBuffer
{
@@ -17,6 +17,7 @@ public:
VideoBuffer()
{
_writing.store(0);
_oldest.store(-1);
_reading.store(-1);
_latest.store(-1);
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
@@ -52,10 +53,15 @@ public:
bool latest(AVFrame **frame, uint32_t *id)
{
_reading.store(_latest.load());
_reading.store(_oldest.load());
int index = _reading.load();
if (index < 0)
return false;
{
_reading.store(_latest.load());
index = _reading.load();
if (index < 0)
return false;
}
*frame = _frames[index];
*id = _ids[index];
return true;
@@ -63,13 +69,15 @@ public:
void consume()
{
if(_oldest.load() == _reading.load())
_oldest.store(-1);
_reading.store(-1);
}
AVFrame *write(uint32_t id)
{
int index = _writing.load();
while (index == _reading.load() || index == _latest.load())
while (index == _reading.load() || index == _latest.load() || index == _oldest.load())
{
index = (index + 1) % BUFFER_VIDEO_FRAMES;
}
@@ -80,17 +88,20 @@ public:
void commit()
{
_oldest.store(_latest.load());
_latest.store(_writing.load());
}
void reset()
{
_writing.store(0);
_oldest.store(-1);
_reading.store(-1);
_latest.store(-1);
}
private:
std::atomic<int8_t> _oldest;
std::atomic<int8_t> _latest;
std::atomic<int8_t> _reading;
std::atomic<int8_t> _writing;
@@ -98,4 +109,4 @@ private:
uint32_t _ids[BUFFER_VIDEO_FRAMES];
};
#endif /* SRC_STRUCT_VIDEO_BUFFER */
#endif /* SRC_STRUCT_VIDER_BUFFER2 */