mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
Initial commit. Version 0.1
This commit is contained in:
@@ -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(); });
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 auto‑registers 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
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 32‑bit 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 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
|
||||
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");
|
||||
}
|
||||
@@ -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 |
@@ -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 */
|
||||
@@ -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.
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user