Initial commit. Version 0.1

This commit is contained in:
Niellune
2025-05-20 16:05:26 +03:00
parent 9139cb4fd4
commit 7f3ea88b21
30 changed files with 2537 additions and 1 deletions
+278
View File
@@ -0,0 +1,278 @@
#include "connector.h"
#include <stdexcept>
#include <iostream>
#include <condition_variable>
#include "helper/functions.h"
#include "helper/settings.h"
Connector::Connector(uint16_t videoPadding)
: _videoPadding(videoPadding)
{
int result = libusb_init(&_context);
if (result < 0)
throw std::runtime_error(std::string("Can't initialise USB: ") + libusb_error_name(result));
}
Connector::~Connector()
{
stop();
if (_device)
{
libusb_release_interface(_device, 0);
libusb_close(_device);
_device = nullptr;
}
if (_context)
{
libusb_exit(_context);
_context = nullptr;
}
}
void Connector::start(IProtocol *protocol)
{
_protocol = protocol;
if (_active)
stop();
_active = true;
_write_thread = std::thread(&Connector::write_loop, this);
_read_thread = std::thread(&Connector::read_loop, this);
}
void Connector::stop()
{
if (!_active)
return;
_active = false;
if (_read_thread.joinable())
_read_thread.join();
if (_write_thread.joinable())
_write_thread.join();
}
bool Connector::connect(uint16_t vendor_id, uint16_t product_id)
{
status("Searching for device");
std::cout << "Creating device handle" << std::endl;
_device = libusb_open_device_with_vid_pid(_context, vendor_id, product_id);
if (!_device)
{
status("Can't find device");
return false;
}
if (link())
return true;
libusb_close(_device);
_device = nullptr;
return false;
}
bool Connector::link()
{
status("Linking device");
std::cout << "Reset device" << std::endl;
if (libusb_reset_device(_device) < 0)
return false;
std::cout << "Set configuration" << std::endl;
if (libusb_set_configuration(_device, 1) < 0)
return false;
std::cout << "Claim interface" << std::endl;
if (libusb_claim_interface(_device, 0) < 0)
return false;
std::cout << "Get config descriptor" << std::endl;
libusb_device *dev = libusb_get_device(_device);
struct libusb_config_descriptor *config = nullptr;
if (libusb_get_active_config_descriptor(dev, &config) < 0)
return false;
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;
std::cout << "Found input endpoint" << std::endl;
}
else
{
_endpoint_out = ep->bEndpointAddress;
std::cout << "Found output endpoint" << std::endl;
}
}
std::cout << "Free config descriptor" << std::endl;
libusb_free_config_descriptor(config);
return true;
}
void Connector::release()
{
if (_device)
{
libusb_release_interface(_device, 0);
libusb_close(_device);
_device = nullptr;
}
}
void Connector::status(const char *status)
{
std::cout << status << std::endl;
if (_protocol)
_protocol->onStatus(status);
}
void Connector::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;
}
int Connector::send(int cmd)
{
if (!_connected)
return 0;
int transferred;
uint8_t buffer[16];
write_uint32_le(&buffer[0], 0x55AA55AA);
write_uint32_le(&buffer[4], 0);
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);
return transferred;
}
int Connector::send(int cmd, uint8_t *data, uint32_t size)
{
if (!_connected)
return 0;
int transferred;
uint8_t buffer[16];
write_uint32_le(&buffer[0], 0x55AA55AA);
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);
libusb_bulk_transfer(_device, _endpoint_out, data, size, &transferred, 0);
return transferred;
}
void Connector::read_loop()
{
std::mutex mtx;
std::condition_variable cv;
Header header;
int transferred = 0;
uint8_t *data;
// Set thread name
setThreadName( "protocol-reader");
while (_active)
{
if (!_connected)
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait_for(lock, std::chrono::seconds(1), [&]()
{ return !_active.load(); });
continue;
}
int result = libusb_bulk_transfer(_device, _endpoint_in, reinterpret_cast<uint8_t *>(&header), sizeof(Header), &transferred, READ_TIMEOUT);
if (result == LIBUSB_ERROR_NO_DEVICE)
{
if (_protocol)
_protocol->onDevice(false);
_connected = false;
continue;
}
if (result != LIBUSB_SUCCESS || transferred != sizeof(Header))
{
continue;
}
int padding = 0;
if (header.type == 6)
padding = _videoPadding;
if (header.length > 0)
{
data = (uint8_t *)malloc(header.length + padding);
if (!data)
continue;
libusb_bulk_transfer(_device, _endpoint_in, data, header.length, &transferred, READ_TIMEOUT);
}
if (!_protocol)
free(data);
else
{
if (padding > 0)
std::fill(data + header.length, data + header.length + padding, 0);
_protocol->onData(header.type, header.length, data);
}
}
}
void Connector::write_loop()
{
std::mutex mtx;
std::condition_variable cv;
// Set thread name
setThreadName("protocol-writer");
while (_active)
{
_connected = connect(Settings::vendorid, Settings::productid);
if (_connected)
{
status("Starting device");
if (_protocol)
_protocol->onDevice(true);
status("Waiting for connecton");
while (_connected && _active)
{
send(170);
std::unique_lock<std::mutex> lock(mtx);
cv.wait_for(lock, std::chrono::seconds(2), [&]()
{ return !_active.load(); });
}
}
std::unique_lock<std::mutex> lock(mtx);
cv.wait_for(lock, std::chrono::seconds(1), [&]()
{ return !_active.load(); });
}
}
+65
View File
@@ -0,0 +1,65 @@
#ifndef SRC_CONNECTOR
#define SRC_CONNECTOR
#include <libusb-1.0/libusb.h>
#include <atomic>
#include <thread>
#include <mutex>
#include <string>
#include "helper/iprotocol.h"
#define READ_TIMEOUT 5000
#pragma pack(push, 1)
struct Header {
uint32_t magic;
uint32_t length;
uint32_t type;
uint32_t typecheck;
};
#pragma pack(pop)
class Connector
{
public:
Connector(uint16_t videoPadding);
~Connector();
void start(IProtocol *protocol);
void stop();
int send(int cmd, uint8_t *data, uint32_t size);
int send(int cmd);
static void write_uint32_le(uint8_t *dst, uint32_t value);
private:
void read_loop();
void write_loop();
bool connect(uint16_t vendor_id, uint16_t product_id);
bool link();
void release();
void status(const char* status);
libusb_context *_context = nullptr;
libusb_device_handle *_device = nullptr;
uint8_t _endpoint_in;
uint8_t _endpoint_out;
bool _connected;
std::thread _read_thread;
std::thread _write_thread;
std::mutex _write_mutex;
std::atomic<bool> _active = false;
u_int16_t _videoPadding;
IProtocol* _protocol = nullptr;
};
#endif /* SRC_CONNECTOR */
+252
View File
@@ -0,0 +1,252 @@
#include "decoder.h"
#include <iostream>
#include "helper/functions.h"
#include "helper/settings.h"
Decoder::Decoder()
{
}
Decoder::~Decoder()
{
stop();
}
void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId)
{
if (_active)
stop();
_vb = vb;
_data = data;
_codecId = codecId;
_active = true;
_running = false;
_thread = std::thread(&Decoder::runner, this);
}
void Decoder::stop()
{
if (!_active)
return;
_active = false;
_data->notify();
if (_thread.joinable())
_thread.join();
}
// Initialize and select the best decoder (try HW first, then SW)
AVCodecContext *Decoder::load_codec(AVCodecID codec_id)
{
void *iter = nullptr;
const AVCodec *codec = nullptr;
AVCodecContext *result = nullptr;
// Try hardware-accelerated decoders by iterating registered codecs
while ((codec = av_codec_iterate(&iter)))
{
if (!av_codec_is_decoder(codec) || codec->id != codec_id)
continue;
if (!(codec->capabilities & AV_CODEC_CAP_HARDWARE))
continue;
result = avcodec_alloc_context3(codec);
if (!result)
{
std::cout << "Can't load HW codec " << codec->name << ": out of memory" << std::endl;
break;
}
int ret = avcodec_open2(result, codec, nullptr);
if (ret == 0)
{
std::cout << "Using HW decoder: " << codec->name << std::endl;
return result;
}
std::cout << "Can't load HW codec " << codec->name << ": " << Error::avErrorText(ret) << std::endl;
avcodec_free_context(&result);
}
// Fallback to software decoder
codec = avcodec_find_decoder(codec_id);
if (!codec)
{
std::cout << "Decoder not found for codec id " << codec_id << std::endl;
return nullptr;
}
result = avcodec_alloc_context3(codec);
if (!result)
{
std::cout << "Failed to allocate context for codec id " << codec_id << std::endl;
return nullptr;
}
int ret = avcodec_open2(result, codec, nullptr);
if (ret < 0)
{
std::cout << "Failed to open software decoder " << codec->name << ": " << Error::avErrorText(ret) << std::endl;
avcodec_free_context(&result);
return nullptr;
}
std::cout << "Using SW decoder " << codec->name << std::endl;
return result;
}
void Decoder::runner()
{
// Set thread name
setThreadName( "video-decoding");
// Load codec context
AVCodecContext *context = load_codec(_codecId);
if (_status.null(context, ("Can't find decoder for codec " + _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))
{
// Allocate packet for decoding
AVPacket *packet = av_packet_alloc();
if (!_status.null(packet, "Can't allocate packet for codec " + codec))
{
// Allocate frame for decoded data
AVFrame *frame = av_frame_alloc();
if (!_status.null(frame, "Can't allocate frame for codec " + codec))
{
loop(context, parser, packet, frame); // Run decoding loop
av_frame_free(&frame);
}
av_packet_free(&packet);
}
av_parser_close(parser);
}
avcodec_free_context(&context);
if (_status.error())
std::cout << "Video decoder error: " << _status.message() << std::endl;
}
void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame)
{
SwsContext *sws = nullptr;
int sws_height = 0;
int sws_width = 0;
uint32_t counter = 0;
_running = true;
std::cout << "Video decoder loop started" << std::endl;
// Main decoding loop; runs until global_quit flag is set
while (_active)
{
// Get raw data segment from queue
RawEntry segment = _data->wait(_active);
if (!_active)
{
if (segment.data)
{
free(segment.data);
segment.data = nullptr;
}
continue;
}
uint8_t *data_ptr = segment.data + segment.offset; // Pointer to raw data buffer
int data_size = segment.size; // Size of raw data buffer
// Feed raw data into the parser and decoder
while (_active && data_size > 0)
{
uint8_t *paket_data;
int paket_size;
// printf("avparser offset %d size %d fullsize %d\n", data_ptr-segment.data, data_size, segment.size);
// Parse raw data into packets
int len = av_parser_parse2(parser, context,
&paket_data, &paket_size,
data_ptr, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
// Parsing error; break out
if (len < 0)
break;
// Move forward through segment
data_ptr += len;
data_size -= len;
if (paket_size <= 0)
continue;
// Load packet data
av_packet_unref(packet);
packet->data = paket_data;
packet->size = paket_size;
// Send packet to decoder
int send_ret = avcodec_send_packet(context, packet);
if (send_ret != 0)
{
std::cout << "Error decoding packet: " << Error::avErrorText(send_ret) << std::endl;
continue;
}
// Receive decoded frames
while (avcodec_receive_frame(context, frame) == 0 && _active)
{
if (!sws || sws_width != frame->width || sws_height != frame->height)
{
if (sws)
{
sws_freeContext(sws);
sws = nullptr;
}
sws_width = frame->width;
sws_height = frame->height;
sws = sws_getContext(sws_width, sws_height, (AVPixelFormat)frame->format,
_vb->width(), _vb->height(), AV_PIX_FMT_YUV420P,
Settings::scaler, nullptr, nullptr, nullptr);
if (!sws)
{
std::cout << "Error creating sws context" << std::endl;
_active = false;
continue;
}
}
// Write decoded frame into video buffer and get output frame pointer
const AVFrame *out = _vb->writeFrame(counter++);
// Scale frame to output format
sws_scale(sws,
frame->data, frame->linesize,
0, sws_height,
out->data,
out->linesize);
// Commit frame to buffer
_vb->commitFrame();
}
}
if (segment.data)
{
free(segment.data);
segment.data = nullptr;
}
}
_running = false;
if (sws)
{
sws_freeContext(sws);
sws = nullptr;
}
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef SRC_DECODER
#define SRC_DECODER
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}
#include <atomic>
#include <thread>
#include "struct/video_buffer.h"
#include "struct/raw_queue.h"
class Decoder
{
public:
Decoder();
~Decoder();
void start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId);
void stop();
private:
void runner();
void loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame);
static AVCodecContext *load_codec(AVCodecID codec_id);
std::thread _thread;
AVCodecID _codecId;
Error _status;
std::atomic<bool> _active = false;
std::atomic<bool> _running = false;
RawQueue *_data = nullptr;
VideoBuffer *_vb = nullptr;
};
#endif /* SRC_DECODER */
+88
View File
@@ -0,0 +1,88 @@
#ifndef SRC_ERROR
#define SRC_ERROR
#include <string>
#include <stdexcept>
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;
}
static 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";
}
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 */
+24
View File
@@ -0,0 +1,24 @@
#ifndef SRC_HELPER_FUNCTIONS
#define SRC_HELPER_FUNCTIONS
#if defined(__linux__) || defined(__APPLE__)
#include <pthread.h>
#endif
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);
}
#endif /* SRC_HELPER_FUNCTIONS */
+16
View File
@@ -0,0 +1,16 @@
#ifndef SRC_PROTOCOL_ISENDER
#define SRC_PROTOCOL_ISENDER
#include <cstdint>
#include <functional>
using StatusCallback = void (*)(const char* status);
class IProtocol
{
public:
virtual void onData(uint32_t cmd, uint32_t length, uint8_t* data) = 0;
virtual void onStatus(const char* status) = 0;
virtual void onDevice(bool connected) = 0;
};
#endif /* SRC_PROTOCOL_ISENDER */
+75
View File
@@ -0,0 +1,75 @@
#include "settings.h"
#include <iostream>
#include <fstream>
void Settings::load(const std::string &filename)
{
std::ifstream file(filename);
if (!file.is_open())
{
std::cerr << "[Settings] Cannot open “" << filename << "" << std::endl;
return;
}
std::string line;
while (std::getline(file, line))
{
// strip comments
std::size_t p = line.find('#');
if (p != std::string::npos)
line.erase(p);
// trim
trim(line);
if (line.empty())
continue;
// split on '='
std::size_t eq = line.find('=');
if (eq == std::string::npos)
continue;
std::string key = line.substr(0, eq);
std::string value = line.substr(eq + 1);
trim(key);
trim(value);
// lookup in registry
bool found = false;
for (ISetting *setting : _settings())
{
if (setting->name == key)
{
setting->parse(value);
found = true;
break;
}
}
if (!found)
std::cerr << "[Settings] Unknown key “" << key << "" << std::endl;
}
}
void Settings::print()
{
for (ISetting *setting : _settings())
{
std::cout << "[Settings] " << setting->name << " = " << setting->asString() << "\n";
}
}
void Settings::trim(std::string &s)
{
// Left trim
s.erase(s.begin(),
std::find_if(s.begin(), s.end(),
[](unsigned char c)
{ return !std::isspace(c); }));
// Right trim
s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char c)
{ return !std::isspace(c); })
.base(),
s.end());
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef SRC_HELPER_SETTINGS
#define SRC_HELPER_SETTINGS
#include "settings_base.h"
// The singleton “Settings” namespace
class Settings
{
public:
static inline Setting<int> vendorid{"vendor-id", 4884};
static inline Setting<int> productid{"product-id ", 5408};
static inline Setting<bool> fullscreen{"fullscreen", false};
static inline Setting<int> width{"width", 720};
static inline Setting<int> height{"height", 576};
static inline Setting<int> fps{"fps", 60};
static inline Setting<int> sourceWidth{"source-width", 720};
static inline Setting<int> sourceHeight{"source-height", 576};
static inline Setting<int> sourceFps{"source-fps", 30};
static inline Setting<bool> logging{"logging", true};
static inline Setting<int> scaler{"scaler", 2};
static inline Setting<int> queue{"queue-size", 32};
static inline Setting<int> fontSize{"font-size", 30};
static void load(const std::string &filename);
static void print();
private:
static void trim(std::string &s);
};
#endif /* SRC_HELPER_SETTINGS */
+93
View File
@@ -0,0 +1,93 @@
#ifndef SRC_HELPER_SETTINGS_BASE
#define SRC_HELPER_SETTINGS_BASE
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <stdexcept>
// Base interface for any one setting
class ISetting
{
public:
std::string name;
virtual void parse(std::string &str) = 0;
virtual std::string asString() const = 0;
};
// Holds the global registry
inline std::vector<ISetting *> &_settings()
{
static std::vector<ISetting *> settings;
return settings;
}
// A “typed” setting that autoregisters itself
template <typename T>
class Setting : public ISetting
{
public:
T value;
Setting(std::string name_, T default_)
: value(default_)
{
name = std::move(name_);
_settings().push_back(this);
}
// allow using this as if it were a T
operator T() const { return value; }
Setting &operator=(T newValue)
{
value = newValue;
return *this;
}
// parse a string into T
void parse(std::string &str) override
{
try
{
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
if constexpr (std::is_same_v<T, bool>)
{
if (str == "1" || str == "true")
value = true;
else if (str == "0" || str == "false")
value = false;
else
throw new std::runtime_error("Can't convert to boolean.");
}
else if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>)
{
value = static_cast<T>(std::stoll(str));
}
else if constexpr (std::is_floating_point_v<T>)
{
value = static_cast<T>(std::stold(str));
}
else if constexpr (std::is_same_v<T, std::string>)
{
value = str;
}
}
catch (const std::exception &e)
{
std::cerr << "[Settings] failed to parse \"" << str
<< "\" for key \"" << name << "\": " << e.what() << std::endl;
}
}
std::string asString() const override
{
if constexpr (std::is_same_v<T, bool>)
return value ? "true" : "false";
else if constexpr (std::is_same_v<T, std::string>)
return value;
else
return std::to_string(value);
}
};
#endif /* SRC_HELPER_SETTINGS_BASE */
+393
View File
@@ -0,0 +1,393 @@
#include <SDL2/SDL.h> // Include SDL2 library headers for graphics and event handling
#include <SDL2/SDL_ttf.h>
extern "C"
{
#include <libavformat/avformat.h> // FFmpeg library for multimedia container format handling
#include <libavcodec/avcodec.h> // FFmpeg library for encoding/decoding
#include <libswscale/swscale.h> // FFmpeg library for image scaling and pixel format conversion
#include <libavutil/imgutils.h> // FFmpeg utility functions for image handling
}
#include <atomic> // C++ atomic types for thread-safe variables
#include <mutex> // C++ mutex for locking resources
#include <condition_variable> // C++ condition variable for thread synchronization
#include <cmath> // Math functions (not explicitly used here but included)
#include <cstdio> // Standard C I/O functions
#include <vector> // C++ dynamic array container
#include <string> // C++ string type
#include <unistd.h>
#include <iostream>
#include "resource/background.h"
#include "resource/font.h"
#include "helper/settings.h"
#include "helper/functions.h"
#include "ux/ufont.h"
#include "ux/uimage.h"
#include "struct/video_buffer.h"
#include "protocol.h"
#include "decoder.h"
#include "pcm_audio.h"
static const char *title = "Fast Car Play v0.1";
static int width = 0;
static int height = 0;
static SDL_Window *window = nullptr;
static SDL_Renderer *renderer = nullptr;
bool active = false;
static SDL_Texture *textTexture = nullptr;
static std::string textureText = "";
static SDL_Texture *imgTexture = nullptr;
static SDL_Texture *videoTexture = nullptr;
static bool mouseDown = false;
static bool fullscreen = false;
std::mutex statusMutex;
std::string statusText;
void onStatus(const char *status)
{
std::lock_guard<std::mutex> lock(statusMutex);
statusText = status;
}
void DrawText(UFont &font, std::string text)
{
if (!textTexture || textureText.compare(text) != 0)
{
if (textTexture)
SDL_DestroyTexture(textTexture);
textTexture = font.GetText(renderer, text.c_str(), {255, 255, 255, 255});
textureText = text;
}
if (!textTexture)
return;
int textW, textH;
SDL_QueryTexture(textTexture, nullptr, nullptr, &textW, &textH);
int windowW, windowH;
SDL_GetRendererOutputSize(renderer, &windowW, &windowH);
// Center text
SDL_Rect dstRect = {
(windowW - textW) / 2,
(windowH - textH) * 9 / 10,
textW,
textH};
SDL_RenderCopy(renderer, textTexture, nullptr, &dstRect);
}
void DrawImage(UImage &img)
{
if (!imgTexture)
imgTexture = img.GetImage(renderer);
if (!imgTexture)
return;
int windowW, windowH;
SDL_GetRendererOutputSize(renderer, &windowW, &windowH);
// Compute destination rectangle to center image
SDL_Rect dst = {
(windowW - img.Width) / 2, // x: center horizontally
(windowH - img.Height) / 2, // y: center vertically
img.Width, // width: original image width
img.Height // height: original image height
};
SDL_RenderCopy(renderer, imgTexture, nullptr, &dst);
}
void processKey(Protocol &protocol, SDL_Keysym key)
{
switch (key.sym)
{
case SDLK_f:
fullscreen = !fullscreen; // Toggle fullscreen mode
SDL_SetWindowFullscreen(window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
SDL_SetWindowBordered(window, fullscreen ? SDL_FALSE : SDL_TRUE);
break;
case SDLK_LEFT:
protocol.sendKey(100);
break;
case SDLK_RIGHT:
protocol.sendKey(101);
break;
case SDLK_RETURN:
protocol.sendKey(104);
protocol.sendKey(105);
break;
case SDLK_BACKSPACE:
protocol.sendKey(106);
break;
}
}
void processEvents(Protocol &protocol)
{
SDL_Event e;
while (SDL_PollEvent(&e))
{
switch (e.type)
{
case SDL_QUIT:
active = false;
break;
case SDL_MOUSEBUTTONDOWN:
{
mouseDown = true;
int window_width, window_height;
SDL_GetWindowSize(window, &window_width, &window_height);
protocol.sendClick(1.0 * e.button.x / window_width, 1.0 * e.button.y / window_height, true);
break;
}
case SDL_MOUSEBUTTONUP:
{
mouseDown = false;
int window_width, window_height;
SDL_GetWindowSize(window, &window_width, &window_height);
protocol.sendClick(1.0 * e.button.x / window_width, 1.0 * e.button.y / window_height, false);
break;
}
case SDL_MOUSEMOTION:
{
if (!mouseDown)
break;
int window_width, window_height;
SDL_GetWindowSize(window, &window_width, &window_height);
protocol.sendMove(1.0 * e.motion.x / window_width, 1.0 * e.motion.y / window_height);
break;
}
case SDL_KEYDOWN:
{
processKey(protocol, e.key.keysym);
break;
}
}
}
}
void application()
{
fullscreen = Settings::fullscreen;
if (fullscreen)
{
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
SDL_SetWindowBordered(window, SDL_FALSE);
}
SDL_ShowWindow(window);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // Set draw color to black
SDL_RenderClear(renderer); // Clear renderer to black
SDL_RenderPresent(renderer); // Present initial blank frame
std::cout << " > Application started" << std::endl;
VideoBuffer videoBuffer;
videoBuffer.allocate(Settings::width, Settings::height).throwError();
Protocol protocol(Settings::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE);
Decoder decoder;
PcmAudio audio0, audio1, audio2;
decoder.start(&protocol.videoData, &videoBuffer, AV_CODEC_ID_H264);
audio0.start(&protocol.audioStream0);
audio1.start(&protocol.audioStream1);
audio2.start(&protocol.audioStream2);
protocol.start(onStatus);
UFont textFont(font, font_len, Settings::fontSize);
std::string status = "Initialising";
DrawText(textFont, status);
SDL_RenderPresent(renderer);
UImage image(background, background_len);
SDL_RenderClear(renderer);
DrawImage(image);
DrawText(textFont, status);
SDL_RenderPresent(renderer);
std::cout << " > Application loop" << std::endl;
bool dirty = true;
bool connected = false;
const int activeDelay = 1000 / Settings::fps;
const int inactiveDelay = 1000 / 5; // 5FPS
uint32_t frameDelay = inactiveDelay;
active = true;
uint32_t latestid = 0;
while (active)
{
Uint32 frameStart = SDL_GetTicks();
processEvents(protocol);
if (connected != protocol.phoneConnected)
{
connected = protocol.phoneConnected;
SDL_RenderClear(renderer);
DrawImage(image);
SDL_RenderPresent(renderer);
dirty = true;
frameDelay = connected ? activeDelay : inactiveDelay;
}
if (connected)
{
AVFrame *frame = nullptr;
uint32_t frameid = 0;
if (videoBuffer.getLatest(&frame, &frameid) && frameid != latestid)
{
// Update SDL texture with YUV frame data
SDL_UpdateYUVTexture(videoTexture, nullptr,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
latestid = frameid;
videoBuffer.consumeLatest();
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, videoTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
}
else
{
{
std::lock_guard<std::mutex> lock(statusMutex);
if (status != statusText)
{
status = statusText;
dirty = true;
}
}
if (dirty)
{
SDL_RenderClear(renderer);
DrawImage(image);
DrawText(textFont, status);
SDL_RenderPresent(renderer);
}
}
Uint32 frameTime = SDL_GetTicks() - frameStart;
if (frameTime < frameDelay)
{
SDL_Delay(frameDelay - frameTime); // Sleep only the remaining time
}
}
std::cout << " > Application stopping" << std::endl;
}
int main(int argc, char **argv)
{
std::cout << title << std::endl;
if (argc > 2)
{
std::cerr << " Usage: " << argv[0] << " [settings_file]" << std::endl;
return 1;
}
if (argc == 2)
{
Settings::load(argv[1]);
}
if (!Settings::logging)
disable_cout();
else
Settings::print();
// Initialize SDL video subsystem
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) != 0)
{
std::cerr << "[Main] SDL initialisation failed: " << SDL_GetError() << std::endl;
return 1;
}
if (TTF_Init() != 0)
{
std::cerr << "[Main] TTF initialisation failed: " << TTF_GetError() << std::endl;
SDL_Quit();
return 1;
}
SDL_DisplayMode displayMode;
if (SDL_GetCurrentDisplayMode(0, &displayMode) != 0)
{
std::cerr << "[Main] SDL get display mode failed: " << SDL_GetError() << std::endl;
TTF_Quit();
SDL_Quit();
return 1;
}
if (Settings::fullscreen)
{
width = displayMode.w;
height = displayMode.h;
}
else
{
width = Settings::width;
height = Settings::height;
}
// Create SDL window centered on screen, 800x600 size
window = SDL_CreateWindow(title,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
width,
height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
if (!window)
{
std::cerr << "[Main] SDL can't create window: " << SDL_GetError() << std::endl;
TTF_Quit();
SDL_Quit();
return 1;
}
// Create accelerated renderer for the window
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer)
{
videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING,
Settings::width, Settings::height);
if (videoTexture)
{
application();
SDL_DestroyTexture(videoTexture);
std::cout << " > Application finish" << std::endl;
}
else
{
std::cerr << "[Main] SDL can't create video texture: " << SDL_GetError() << std::endl;
}
SDL_DestroyRenderer(renderer);
}
else
{
std::cerr << "[Main] SDL can't create renderer: " << SDL_GetError() << std::endl;
}
if (textTexture)
SDL_DestroyTexture(textTexture);
if (imgTexture)
SDL_DestroyTexture(imgTexture);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 0;
}
+164
View File
@@ -0,0 +1,164 @@
#include "pcm_audio.h"
#include "helper/functions.h"
// Implementation
PcmAudio::PcmAudio() {}
PcmAudio::~PcmAudio()
{
stop();
}
void PcmAudio::setVolume(float vol)
{
if(vol<0)
{
_volume = 0;
return;
}
if(vol>1)
{
_volume = 1;
return;
}
_volume = vol;
}
void PcmAudio::start(RawQueue *data)
{
if (_active)
stop();
_data = data;
_active = true;
_thread = std::thread(&PcmAudio::runner, this);
}
void PcmAudio::stop()
{
if (!_active)
return;
_active = false;
_data->notify();
if (_thread.joinable())
_thread.join();
}
void PcmAudio::runner()
{
setThreadName("audio");
SDL_AudioDeviceID device = 0;
SDL_AudioSpec spec;
size_t bufferedBytes = 0;
bool unpaused = false;
size_t targetBytes = 0;
int rate = 0;
int channels = 0;
while (_active)
{
RawEntry segment = _data->wait(_active);
if (!_active)
{
if (segment.data)
free(segment.data);
break;
}
int config = 0;
memcpy(&config, segment.data, sizeof(int));
int newRate = 44100;
int newChannels = 2;
switch (config)
{
case 1:
newRate = 44100;
newChannels = 2;
break;
case 2:
newRate = 44100;
newChannels = 2;
break;
case 3:
newRate = 8000;
newChannels = 1;
break;
case 4:
newRate = 48000;
newChannels = 2;
break;
case 5:
newRate = 16000;
newChannels = 1;
break;
case 6:
newRate = 24000;
newChannels = 1;
break;
case 7:
newRate = 16000;
newChannels = 2;
break;
}
// If settings changed, (re)open audio device
if (device == 0 || rate != newRate || channels != newChannels)
{
rate = newRate;
channels = newChannels;
printf("PCM SETTING %d %d\n", rate, channels);
// Close existing device
if (device != 0)
{
SDL_CloseAudioDevice(device);
device = 0;
}
// Configure new spec
SDL_zero(spec);
spec.freq = rate;
spec.format = AUDIO_S16SYS;
spec.channels = static_cast<Uint8>(channels);
spec.samples = 4096;
spec.callback = nullptr;
device = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0);
if (device == 0)
{
std::cerr << "Failed to open audio: " << SDL_GetError() << std::endl;
free(segment.data);
continue;
}
// Calculate new buffer target: 0.5s
targetBytes = rate * channels * sizeof(int16_t) / 2;
bufferedBytes = 0;
unpaused = false;
// Start paused
SDL_PauseAudioDevice(device, 1);
}
// Apply volume in-place
int16_t *samples = reinterpret_cast<int16_t *>(segment.data + segment.offset);
size_t count = segment.size / sizeof(int16_t);
for (size_t i = 0; i < count; ++i)
{
samples[i] = static_cast<int16_t>(samples[i] * _volume);
}
// Queue audio
SDL_QueueAudio(device, segment.data + segment.offset, segment.size);
bufferedBytes += segment.size;
free(segment.data);
// Unpause when enough buffered
if (!unpaused && bufferedBytes >= targetBytes)
{
SDL_PauseAudioDevice(device, 0);
unpaused = true;
}
}
if (device)
SDL_CloseAudioDevice(device);
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef SRC_PCM_AUDIO
#define SRC_PCM_AUDIO
#include <iostream>
#include <thread>
#include <atomic>
#include <SDL2/SDL.h>
#include "struct/raw_queue.h"
#include "helper/error.h"
class PcmAudio {
public:
PcmAudio();
~PcmAudio();
// Start playing raw PCM data from queue
void start(RawQueue* data);
void stop();
void setVolume(float vol);
private:
void runner();
void loop(SDL_AudioDeviceID device);
RawQueue* _data = nullptr;
int _sampleRate = 0;
int _channels = 0;
float _volume = 1.0f;
std::thread _thread;
std::atomic<bool> _active{false};
};
#endif /* SRC_PCM_AUDIO */
+279
View File
@@ -0,0 +1,279 @@
#include "protocol.h"
#include <cstring>
Protocol::Protocol(uint16_t width, uint16_t height, uint16_t fps, uint16_t padding)
: connector(padding),
videoData(Settings::queue),
audioStream0(Settings::queue),
audioStream1(Settings::queue),
audioStream2(Settings::queue),
phoneConnected(false),
_width(width),
_height(height),
_fps(fps)
{
}
Protocol::~Protocol()
{
stop();
}
const char *Protocol::cmdString(int cmd)
{
switch (cmd)
{
case CMD_OPEN:
return "Open";
case CMD_PLUGGED:
return "Plugged";
case CMD_UNPLUGGED:
return "Unplugged";
case CMD_TOUCH:
return "Touch";
case CMD_VIDEO_DATA:
return "Video";
case CMD_AUDIO_DATA:
return "Audio";
case CMD_SEND_FILE:
return "File";
default:
return "Unknown";
}
}
void Protocol::start(StatusCallback onStatus)
{
_statusCallback = onStatus;
connector.start(this);
}
void Protocol::stop()
{
connector.stop();
}
void Protocol::sendInit(int width, int height, int fps)
{
uint8_t buf[28];
Connector::write_uint32_le(&buf[0], width);
Connector::write_uint32_le(&buf[4], height);
Connector::write_uint32_le(&buf[8], fps);
Connector::write_uint32_le(&buf[12], 5);
Connector::write_uint32_le(&buf[16], 49152);
Connector::write_uint32_le(&buf[20], 2);
Connector::write_uint32_le(&buf[24], 2);
connector.send(1, buf, 28);
}
void Protocol::sendKey(int key)
{
printf("Send key %d", key);
uint8_t buf[4];
Connector::write_uint32_le(&buf[0], key);
connector.send(8, buf, 4);
}
void Protocol::sendFile(const char *filename, const char *value)
{
uint32_t len = 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'
sendFile(filename,
reinterpret_cast<const uint8_t *>(value),
static_cast<uint32_t>(len));
}
// overload for a single 32bit integer
void Protocol::sendFile(const char *filename, int value)
{
uint8_t buf[4];
Connector::write_uint32_le(buf, value);
sendFile(filename, buf, 4);
}
void Protocol::sendClick(float x, float y, bool down)
{
uint8_t buf[16];
Connector::write_uint32_le(buf, down ? 14 : 16);
Connector::write_uint32_le(buf + 4, int(10000 * x));
Connector::write_uint32_le(buf + 8, int(10000 * y));
Connector::write_uint32_le(buf + 12, 0);
connector.send(5, buf, 16);
}
void Protocol::sendMove(float dx, float dy)
{
uint8_t buf[16];
Connector::write_uint32_le(buf, 15);
Connector::write_uint32_le(buf + 4, int(10000 * dx));
Connector::write_uint32_le(buf + 8, int(10000 * dy));
Connector::write_uint32_le(buf + 12, 0);
connector.send(5, buf, 16);
}
void Protocol::sendFile(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
uint32_t total = 4 + fn_len + 4 + length;
std::vector<uint8_t> result(total);
uint8_t *buf = result.data();
// 1) filename length (LE)
Connector::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)
Connector::write_uint32_le(buf, length);
buf += 4;
// 4) content bytes
std::memcpy(buf, data, length);
connector.send(CMD_SEND_FILE, result.data(), total);
}
void Protocol::onStatus(const char *status)
{
if (_statusCallback)
_statusCallback(status);
}
void Protocol::onDevice(bool connected)
{
if (connected)
{
sendInit(_width, _height, _fps);
sendFile("/tmp/night_mode", 0); // 0==day, 1==night
sendFile("/tmp/hand_drive_mode", 0); // 0==left, 1==right
sendFile("/tmp/charge_mode", 0);
sendFile("/etc/box_name", "CarPlay");
}
else
{
}
}
void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data)
{
bool dispose = true;
switch (cmd)
{
case CMD_VIDEO_DATA:
{
if (length <= 20)
break;
videoData.push(data, 20, length - 20);
dispose = false;
break;
}
case CMD_AUDIO_DATA:
{
if (length <= 13)
{
print_message(cmd, length, data);
break;
}
int channel = 0;
memcpy(&channel, data + 8, sizeof(int));
if (channel == 0)
{
audioStream0.push(data, 12, length - 12);
dispose = false;
break;
}
if (channel == 1)
{
audioStream1.push(data, 12, length - 12);
dispose = false;
break;
}
if (channel == 2)
{
audioStream2.push(data, 12, length - 12);
dispose = false;
break;
}
print_message(cmd, length, data);
break;
}
case CMD_PLUGGED:
{
phoneConnected = true;
break;
}
case CMD_UNPLUGGED:
{
phoneConnected = false;
break;
}
default:
print_message(cmd, length, data);
break;
}
if (dispose && length > 0 && data)
free(data);
}
void Protocol::print_ints(uint32_t length, uint8_t *data, uint16_t max)
{
if (data && length >= 4)
{
printf(" > ");
size_t count = length / 4;
for (size_t i = 0; (i < count) & (i < max); ++i)
{
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);
printf("%u ", val);
}
printf("\n");
}
}
void Protocol::print_bytes(uint32_t length, uint8_t *data, uint16_t max)
{
if (data && length >= 4)
{
printf(" > ");
for (size_t i = 0; (i < length) & (i < max); ++i)
{
printf("%d ", data[i]);
}
printf("\n");
}
}
void Protocol::print_message(uint32_t cmd, uint32_t length, uint8_t *data)
{
printf("Cmd: %-3u %-10s Size: %-6u > ", cmd, cmdString(cmd), length);
if (data && length > 0)
for (size_t i = 0; i < 40 && i < length; ++i)
{
char ch = static_cast<char>(data[i]);
printf("%c", isprint(ch) ? ch : '.');
}
// for (int i = 0; i < length && i < 10; i++)
// printf("%-4u", data[i]);
printf("\n");
}
+65
View File
@@ -0,0 +1,65 @@
#ifndef SRC_PROTOCOL
#define SRC_PROTOCOL
#include "struct/raw_queue.h"
#include "helper/iprotocol.h"
#include "helper/settings.h"
#include "connector.h"
#define MAGIC 0x55aa55aa
#define CMD_OPEN 1
#define CMD_PLUGGED 2
#define CMD_UNPLUGGED 4
#define CMD_TOUCH 5
#define CMD_VIDEO_DATA 6
#define CMD_AUDIO_DATA 7
#define CMD_SEND_FILE 153
class Protocol : public IProtocol
{
public:
Protocol(uint16_t width, uint16_t height, uint16_t fps, uint16_t padding);
~Protocol();
Protocol(const Protocol &) = delete;
Protocol &operator=(const Protocol &) = delete;
static const char *cmdString(int cmd);
void start(StatusCallback onStatus);
void stop();
void sendKey(int key);
void sendInit(int width, int height, int fps);
void sendFile(const char *filename, const uint8_t *data, uint32_t length);
void sendFile(const char *filename, const char *value);
void sendFile(const char *filename, int value);
void sendClick(float x, float y, bool down);
void sendMove(float dx, float dy);
Connector connector;
RawQueue videoData;
RawQueue audioStream0;
RawQueue audioStream1;
RawQueue audioStream2;
bool phoneConnected;
private:
void onStatus(const char *status) override;
void onDevice(bool connected) override;
void onData(uint32_t cmd, uint32_t length, uint8_t *data) override;
void print_message(uint32_t cmd, uint32_t length, uint8_t *data);
void print_ints(uint32_t length, uint8_t *data, uint16_t max);
void print_bytes(uint32_t length, uint8_t *data, uint16_t max);
uint16_t _width;
uint16_t _height;
uint16_t _fps;
StatusCallback _statusCallback = nullptr;
};
#endif /* SRC_PROTOCOL */
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

+7
View File
@@ -0,0 +1,7 @@
#ifndef SRC_IMG_BACKGROUND
#define SRC_IMG_BACKGROUND
extern unsigned char background[];
extern unsigned int background_len;
#endif /* SRC_IMG_BACKGROUND */
+7
View File
@@ -0,0 +1,7 @@
#ifndef SRC_RESOURCE_SST_REGULAR
#define SRC_RESOURCE_SST_REGULAR
extern unsigned char font[];
extern unsigned int font_len;
#endif /* SRC_RESOURCE_SST_REGULAR */
Binary file not shown.
+69
View File
@@ -0,0 +1,69 @@
#include "raw_queue.h"
RawQueue::RawQueue(uint16_t capacity)
: _buffer(capacity), _head(0), _tail(0), _size(0), _capacity(capacity)
{
}
RawQueue::~RawQueue()
{
clear();
}
bool RawQueue::push(uint8_t *data, int offset, int size)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_size == _buffer.size())
{
free(data);
return false; // queue full
}
_buffer[_tail] = RawEntry{data, offset, size};
_tail = (_tail + 1) % _capacity;
_size++;
_condition.notify_one();
return true;
}
RawEntry RawQueue::wait(const std::atomic<bool> &reading)
{
std::unique_lock<std::mutex> lock(_mutex);
_condition.wait(lock, [&]
{ return !reading.load() || _size > 0; });
if (!reading || _size == 0)
return RawEntry{nullptr, 0, 0};
RawEntry entry = _buffer[_head];
_head = (_head + 1) % _capacity;
_size--;
return entry;
}
void RawQueue::clear()
{
std::lock_guard<std::mutex> lock(_mutex);
// Free any remaining buffers
while (_size > 0)
{
RawEntry &e = _buffer[_head];
if (e.data)
{
free(e.data);
e.data = nullptr;
}
_head = (_head + 1) % _capacity;
_size--;
}
// Reset indices
_head = _tail = 0;
}
void RawQueue::notify()
{
_condition.notify_all();
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef SRC_RAW_QUEUE
#define SRC_RAW_QUEUE
#include <cstdint>
#include <vector>
#include <atomic>
#include <mutex>
#include <condition_variable>
struct RawEntry
{
uint8_t *data;
int offset;
int size;
};
// Single entry: raw buffer pointer + metadata
class RawQueue
{
public:
RawQueue(uint16_t capacity = 256);
~RawQueue();
// Non-blocking push: returns false if full
bool push(uint8_t *data, int offset, int size);
// Blocks until an entry is available or reader_active == false
RawEntry wait(const std::atomic<bool> &reading);
// Clears the queue and frees any pending buffers
void clear();
// Unlock queus
void notify();
private:
std::vector<RawEntry> _buffer;
uint16_t _head;
uint16_t _tail;
uint16_t _size;
uint16_t _capacity;
std::mutex _mutex;
std::condition_variable _condition;
};
#endif
+111
View File
@@ -0,0 +1,111 @@
#include "video_buffer.h"
extern "C"
{
#include <libavutil/imgutils.h>
}
#include <stdexcept>
#include <string>
#include "error.h"
VideoBuffer::VideoBuffer()
: _width(0), _height(0)
{
}
// Allocate two YUV420P frames for double buffering and initialize to black
Error VideoBuffer::allocate(uint16_t width, uint16_t height)
{
_width = width;
_height = height;
deallocate();
reset();
Error e;
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
{
// Allocate AVFrame
_frames[i] = av_frame_alloc();
if (e.null(_frames[i], "Failed to allocate AVFrame"))
break;
_frames[i]->format = AV_PIX_FMT_YUV420P;
_frames[i]->width = width;
_frames[i]->height = height;
// Allocate data buffer with 32 byte allingment
if (e.avFail(av_frame_get_buffer(_frames[i], 32), "Failed to allocate AVFrame buffer"))
break;
// Set Y plane to black (0)
memset(_frames[i]->data[0], 0, _frames[i]->linesize[0] * height);
// Set U plane to 128 (neutral)
memset(_frames[i]->data[1], 128, _frames[i]->linesize[1] * (height / 2));
// Set V plane to 128 (neutral)
memset(_frames[i]->data[2], 128, _frames[i]->linesize[2] * (height / 2));
}
return e;
}
VideoBuffer::~VideoBuffer()
{
deallocate();
}
void VideoBuffer::deallocate()
{
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
{
if (_frames[i])
{
// Free the frame itself
av_frame_free(&_frames[i]);
// Clear
_frames[i] = nullptr;
}
}
}
bool VideoBuffer::getLatest(AVFrame **frame, uint32_t *id)
{
_reading.store(_latest.load());
int index = _reading.load();
if (index < 0)
return false;
*frame = _frames[index];
*id = _ids[index];
return true;
}
void VideoBuffer::consumeLatest()
{
_reading.store(-1);
}
const AVFrame *VideoBuffer::writeFrame(uint32_t id)
{
int index = _writing.load();
while (index == _reading.load() || index == _latest.load())
{
index = (index + 1) % BUFFER_VIDEO_FRAMES;
}
_writing.store(index);
_ids[index] = id;
return _frames[index];
}
void VideoBuffer::commitFrame()
{
_latest.store(_writing.load());
}
void VideoBuffer::reset()
{
_writing.store(0);
_reading.store(-1);
_latest.store(-1);
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; i++)
{
_ids[i] = 0;
}
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef SRC_VIDEO_BUFFER
#define SRC_VIDEO_BUFFER
extern "C"
{
#include <libavutil/frame.h> // For AVFrame
}
#include <atomic>
#include "helper/error.h"
#define BUFFER_VIDEO_FRAMES 3
class VideoBuffer
{
public:
VideoBuffer();
~VideoBuffer();
Error allocate(uint16_t width, uint16_t height);
uint16_t width() const { return _width; };
uint16_t height() const { return _height; };
void reset();
bool getLatest(AVFrame **frame, uint32_t *id);
void consumeLatest();
const AVFrame *writeFrame(uint32_t id);
void commitFrame();
private:
void deallocate();
uint16_t _width;
uint16_t _height;
std::atomic<int8_t> _latest;
std::atomic<int8_t> _reading;
std::atomic<int8_t> _writing;
AVFrame *_frames[BUFFER_VIDEO_FRAMES] = {nullptr, nullptr, nullptr};
uint32_t _ids[BUFFER_VIDEO_FRAMES];
};
#endif /* SRC_VIDEO_BUFFER */
+62
View File
@@ -0,0 +1,62 @@
#ifndef SRC_UX_UFONT
#define SRC_UX_UFONT
#include <SDL2/SDL_ttf.h>
#include <iostream>
class UFont
{
public:
UFont(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;
return;
}
_font = TTF_OpenFontRW(font_rw, 1, ptsize);
if (!_font)
{
std::cerr << "[UX] SDL can't load font: " << TTF_GetError() << std::endl;
}
};
~UFont()
{
if (_font)
{
TTF_CloseFont(_font);
_font = nullptr;
}
};
SDL_Texture *GetText(SDL_Renderer *renderer, const char *text, SDL_Color color)
{
if (!_font)
return nullptr;
SDL_Surface *textSurface = TTF_RenderText_Blended(_font, text, color);
if (!textSurface)
{
std::cerr << "[UX] Failed to create text surface: " << TTF_GetError() << std::endl;
return nullptr;
}
SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
SDL_FreeSurface(textSurface);
if (!textTexture)
{
std::cerr << "[UX] Failed to create text texture: " << TTF_GetError() << std::endl;
return nullptr;
}
return textTexture;
}
private:
TTF_Font *_font = nullptr;
};
#endif /* SRC_UX_UFONT */
+52
View File
@@ -0,0 +1,52 @@
#ifndef SRC_UX_UIMAGE
#define SRC_UX_UIMAGE
#include <SDL2/SDL.h>
#include <iostream>
class UImage
{
public:
UImage(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;
return;
}
_surface = SDL_LoadBMP_RW(img_rw, 1);
if (!_surface)
{
std::cerr << "[UX] Failed to create image surface: " << SDL_GetError() << std::endl;
return;
}
Width = _surface->w;
Height = _surface->h;
};
~UImage()
{
if (_surface)
{
SDL_FreeSurface(_surface);
_surface = nullptr;
}
};
SDL_Texture *GetImage(SDL_Renderer *renderer)
{
return SDL_CreateTextureFromSurface(renderer, _surface);
}
int Width = 0;
int Height = 0;
private:
SDL_Surface *_surface = nullptr;
};
#endif