From 7f3ea88b21b286d37d51ebf9d1d56e7ae1022c6e Mon Sep 17 00:00:00 2001 From: Niellune Date: Tue, 20 May 2025 16:05:26 +0300 Subject: [PATCH] Initial commit. Version 0.1 --- .gitignore | 8 + FONT_LICENSE | 40 ++++ Makefile | 60 ++++++ README.md | 87 +++++++- settings.txt | 43 ++++ src/connector.cpp | 278 +++++++++++++++++++++++++ src/connector.h | 65 ++++++ src/decoder.cpp | 252 +++++++++++++++++++++++ src/decoder.h | 42 ++++ src/helper/error.h | 88 ++++++++ src/helper/functions.h | 24 +++ src/helper/iprotocol.h | 16 ++ src/helper/settings.cpp | 75 +++++++ src/helper/settings.h | 31 +++ src/helper/settings_base.h | 93 +++++++++ src/main.cpp | 393 ++++++++++++++++++++++++++++++++++++ src/pcm_audio.cpp | 164 +++++++++++++++ src/pcm_audio.h | 39 ++++ src/protocol.cpp | 279 +++++++++++++++++++++++++ src/protocol.h | 65 ++++++ src/resource/background.bmp | Bin 0 -> 92876 bytes src/resource/background.h | 7 + src/resource/font.h | 7 + src/resource/font.ttf | Bin 0 -> 130832 bytes src/struct/raw-queue.cpp | 69 +++++++ src/struct/raw_queue.h | 46 +++++ src/struct/video_buffer.cpp | 111 ++++++++++ src/struct/video_buffer.h | 42 ++++ src/ux/ufont.h | 62 ++++++ src/ux/uimage.h | 52 +++++ 30 files changed, 2537 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 FONT_LICENSE create mode 100644 Makefile create mode 100644 settings.txt create mode 100644 src/connector.cpp create mode 100644 src/connector.h create mode 100644 src/decoder.cpp create mode 100644 src/decoder.h create mode 100644 src/helper/error.h create mode 100644 src/helper/functions.h create mode 100644 src/helper/iprotocol.h create mode 100644 src/helper/settings.cpp create mode 100644 src/helper/settings.h create mode 100644 src/helper/settings_base.h create mode 100644 src/main.cpp create mode 100644 src/pcm_audio.cpp create mode 100644 src/pcm_audio.h create mode 100644 src/protocol.cpp create mode 100644 src/protocol.h create mode 100644 src/resource/background.bmp create mode 100644 src/resource/background.h create mode 100644 src/resource/font.h create mode 100644 src/resource/font.ttf create mode 100644 src/struct/raw-queue.cpp create mode 100644 src/struct/raw_queue.h create mode 100644 src/struct/video_buffer.cpp create mode 100644 src/struct/video_buffer.h create mode 100644 src/ux/ufont.h create mode 100644 src/ux/uimage.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53af0a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Generated files +out/ +headers/ +src/autogen + +# General files +.*/ +.DS* \ No newline at end of file diff --git a/FONT_LICENSE b/FONT_LICENSE new file mode 100644 index 0000000..db2eb29 --- /dev/null +++ b/FONT_LICENSE @@ -0,0 +1,40 @@ +Open Sans + +Designed by Steve Matteson +License +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1 . This license is copied below, and is also available with a FAQ at: https://openfontlicense.org + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + + Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b673c6e --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +# Compiler +CXX := g++ +SRC_DIR := ./src +OUT_DIR := ./out +RES_DIR := $(SRC_DIR)/resource +GEN_DIR := $(SRC_DIR)/autogen +BUILD_DIR := $(OUT_DIR)/$(BUILD_TYPE) + +# File lists +SRCS := $(shell find $(SRC_DIR) -type f -name '*.cpp') +OBJS=$(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS)) + +RES := $(shell find $(RES_DIR) -type f ! -name '*.h' -name '*.*') +RES_SRC := $(patsubst $(RES_DIR)/%,$(GEN_DIR)/%.cpp,$(RES)) + +# Targets +TARGET_NAME := app + +# Build types +.PHONY: all debug release clean build + +all: debug + +LDOPTIONS := -lSDL2 -lSDL2_ttf -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 +LDFLAGS := +CXXCOMMON := -Wall -Isrc + +debug: BUILD_TYPE := debug +debug: CXXFLAGS := -g -O0 -fsanitize=address -fno-omit-frame-pointer +debug: LDFLAGS += -fsanitize=address -fno-omit-frame-pointer +debug: TARGET := $(TARGET_NAME)-debug +debug: prepare + +release: BUILD_TYPE := release +release: CXXFLAGS := -O2 +release: TARGET := $(TARGET_NAME) +release: prepare + +prepare: $(RES_SRC) + $(MAKE) BUILD_TYPE=$(BUILD_TYPE) TARGET=$(OUT_DIR)/$(TARGET) CXXFLAGS="$(CXXFLAGS)" LDFLAGS="$(LDFLAGS)" build + +build: $(TARGET) + +$(GEN_DIR)/%.cpp: $(RES_DIR)/% + @mkdir -p $(GEN_DIR) + xxd -i -n $(basename $(notdir $<)) $< > $@ + +$(TARGET): $(OBJS) + @mkdir -p $(OUT_DIR) + $(CXX) $(LDFLAGS) $(OBJS) -o $(TARGET) $(LDOPTIONS) + @echo "Build complete: $(TARGET)" + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(dir $@) + $(CXX) $(CXXCOMMON) $(CXXFLAGS) -c $< -o $@ + +clean: + @rm -rf $(OUT_DIR) + @rm -rf $(GEN_DIR) + @echo "Clean complete" \ No newline at end of file diff --git a/README.md b/README.md index a9f7805..f61090a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,87 @@ # FastCarPlay -Car Play Receiver for dongles +This is C++ implementation of carplay receiver for "Autobox" dongles. +The purpose of the project was to make application lightweight to run on Raspberry PI Zero 2W and use hardware decoding. + +# Dongles +The dongles are readily available from Amazon or Aliexpress labeled by !["Carlinkit"](https://www.carlinkit.com/). +Devices might have different vendor and product id's. Check your with lsusb and update settings if necessary. + +# Setup +## Dependencies +The project is based on SDL2, FFMPEG, LIBUSB. It use XXD for resource embedding. +``` +sudo apt install build-essential xxd libsdl2-dev libsdl2-ttf-dev libavformat-dev libavcodec-dev libavutil-dev libswscale-dev libusb-1.0-0-dev +``` +To run the application you also need to install runtime +``` +sudo apt install ffmpeg libsdl2-2.0-0 libsdl2-ttf-2.0-0 libavformat59 libavcodec61 libavutil57 libswscale7 libusb-1.0-0 +``` + +## Build and run +The application can be started with settings file. Sample of the settings file can be found in settings.txt +The project is using make. From the repository root run following +``` +make clean +make release +./out/app ./settings.txt +``` + +## Customisation +You can change font and background images by replacing files in ./src/resource +- background.bmp for background image. Use BMP format only. +- font.ttf for font. Use TTF fdrmat only + +The names of the file need to be exactly same. Resources are embedded in executable, remake the project to regenerate resources +``` +make clean +make release +``` + +## Keys +The following keys have been mapped: +- Left - navigate left +- Right - navigate right +- Enter - select active item +- Backspace - Go back +- f - toggle fullscreen mode + +# Status +What is working: +- Video +- Audio (multiple channels) +- Key navigation +- Simple touch + +What is not working: +- Multi touch - i have no means to test it +- Microphone - that's next step for me to figure out how to feed sound +- Telephone - the listening part will work, but because there is no mic implementation you can't speak + +## Notes +Regardless the resolution there are 2 types of settings +- source-width source-height source-fps - defines what video parameters will be requested from device +- width height fps - defines video drawing resolution and fps + +The SDL will anyway scale the image to your screen/window size using internal HW scaling, so ideally you should use same values for width and height. +If the source parameters will be different the scaling will be applied, however that scaling is not hardware accelerated and can consume a lot of CPU. +You can set the 'scaler' in setting to define scaling algorithm. On my Raspbery Pi Zero 2W even easiest algorithm loads CPU to 100% and cause fram drop. + +Increasing FPS above Source-FPS will cause app to run UI loop with less delays and do more event polling. This can increase responsivenes of the system, but also will make X11 to use more resources. + +## Next plans +- Implement direct buffer transfer from video decoder to renderer (should reduce amount of memory copies and CPU load) +- Control audio buffers better (now system use 3 decoding threads but in reality only 2 required) +- Reduce music volume when there is navigation messages +- Add abilities to run script on device connect and device disconnect + +# Acknowledgement +The project is inspired and based on great work done by other developers: +- ![pycarplay by electric-monk](https://github.com/electric-monk/pycarplay) +- ![carplay-receiver by harrylepotter](https://github.com/harrylepotter/carplay-receiver) +- ![react-carplay by rhysmorgan134](https://github.com/rhysmorgan134/react-carplay) + +The project is licenced under GPL-3 licence. See LICENCE for details. +The project is using ![Open Sans](https://fonts.google.com/specimen/Open+Sans) font. See FONT_LICENCE for details. + +# Finally +If you have any questions, suggestions or you find problems running this feel free to open issue. diff --git a/settings.txt b/settings.txt new file mode 100644 index 0000000..ae5dce9 --- /dev/null +++ b/settings.txt @@ -0,0 +1,43 @@ +# Dongle configuration +# Note that vendor id and product id are in decimals, not hex here +vendor-id = 4884 # 0x1314 +product-id = 5408 # 0x1520 + +# Application starts in full screen +fullscreen = true + +# Application drawing settings widthxheight +width = 720 +height = 576 +fps = 30 + +# Requested image from phone +source-width = 1080 +source-height = 864 +source-fps = 30 + +# Scaler algorithm if application drawing is differen from source image +# It's recommended to keep application and source values same cause scaling +# takes a lot of CPU and can cause artifacts on slow devices +# options +# SWS_FAST_BILINEAR 1 +# SWS_BILINEAR 2 +# SWS_BICUBIC 4 +# SWS_X 8 +# SWS_POINT 16 +# SWS_AREA 32 +# SWS_BICUBLIN 64 +# SWS_GAUSS 128 +# SWS_SINC 256 +# SWS_LANCZOS 512 +# SWS_SPLINE 1024 +scaler = 1024 + +# Enable logging +logging = false + +# Size of video and audio buffers. Increase if you see artifacts +queue-size = 32 + +# Font size for messgaes on screen +font-size = 30 \ No newline at end of file diff --git a/src/connector.cpp b/src/connector.cpp new file mode 100644 index 0000000..70a92de --- /dev/null +++ b/src/connector.cpp @@ -0,0 +1,278 @@ +#include "connector.h" + +#include +#include +#include + +#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 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 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 lock(mtx); + cv.wait_for(lock, std::chrono::seconds(1), [&]() + { return !_active.load(); }); + continue; + } + + int result = libusb_bulk_transfer(_device, _endpoint_in, reinterpret_cast(&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 lock(mtx); + cv.wait_for(lock, std::chrono::seconds(2), [&]() + { return !_active.load(); }); + } + } + std::unique_lock lock(mtx); + cv.wait_for(lock, std::chrono::seconds(1), [&]() + { return !_active.load(); }); + } +} \ No newline at end of file diff --git a/src/connector.h b/src/connector.h new file mode 100644 index 0000000..ab7817b --- /dev/null +++ b/src/connector.h @@ -0,0 +1,65 @@ +#ifndef SRC_CONNECTOR +#define SRC_CONNECTOR + +#include + +#include +#include +#include +#include + +#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 _active = false; + + u_int16_t _videoPadding; + + IProtocol* _protocol = nullptr; +}; + +#endif /* SRC_CONNECTOR */ diff --git a/src/decoder.cpp b/src/decoder.cpp new file mode 100644 index 0000000..b4337eb --- /dev/null +++ b/src/decoder.cpp @@ -0,0 +1,252 @@ +#include "decoder.h" + +#include +#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; + } +} diff --git a/src/decoder.h b/src/decoder.h new file mode 100644 index 0000000..f929030 --- /dev/null +++ b/src/decoder.h @@ -0,0 +1,42 @@ +#ifndef SRC_DECODER +#define SRC_DECODER + +extern "C" +{ +#include +#include +} + +#include +#include + +#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 _active = false; + std::atomic _running = false; + + RawQueue *_data = nullptr; + VideoBuffer *_vb = nullptr; +}; + +#endif /* SRC_DECODER */ diff --git a/src/helper/error.h b/src/helper/error.h new file mode 100644 index 0000000..879e42b --- /dev/null +++ b/src/helper/error.h @@ -0,0 +1,88 @@ +#ifndef SRC_ERROR +#define SRC_ERROR + +#include +#include + +extern "C" +{ +#include +} + +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 */ diff --git a/src/helper/functions.h b/src/helper/functions.h new file mode 100644 index 0000000..1ae0a0c --- /dev/null +++ b/src/helper/functions.h @@ -0,0 +1,24 @@ +#ifndef SRC_HELPER_FUNCTIONS +#define SRC_HELPER_FUNCTIONS + +#if defined(__linux__) || defined(__APPLE__) +#include +#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 */ \ No newline at end of file diff --git a/src/helper/iprotocol.h b/src/helper/iprotocol.h new file mode 100644 index 0000000..ec7eb87 --- /dev/null +++ b/src/helper/iprotocol.h @@ -0,0 +1,16 @@ +#ifndef SRC_PROTOCOL_ISENDER +#define SRC_PROTOCOL_ISENDER + +#include +#include + +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 */ diff --git a/src/helper/settings.cpp b/src/helper/settings.cpp new file mode 100644 index 0000000..855a31d --- /dev/null +++ b/src/helper/settings.cpp @@ -0,0 +1,75 @@ +#include "settings.h" + +#include +#include + +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()); +} \ No newline at end of file diff --git a/src/helper/settings.h b/src/helper/settings.h new file mode 100644 index 0000000..111b509 --- /dev/null +++ b/src/helper/settings.h @@ -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 vendorid{"vendor-id", 4884}; + static inline Setting productid{"product-id ", 5408}; + static inline Setting fullscreen{"fullscreen", false}; + static inline Setting width{"width", 720}; + static inline Setting height{"height", 576}; + static inline Setting fps{"fps", 60}; + static inline Setting sourceWidth{"source-width", 720}; + static inline Setting sourceHeight{"source-height", 576}; + static inline Setting sourceFps{"source-fps", 30}; + static inline Setting logging{"logging", true}; + static inline Setting scaler{"scaler", 2}; + static inline Setting queue{"queue-size", 32}; + static inline Setting 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 */ diff --git a/src/helper/settings_base.h b/src/helper/settings_base.h new file mode 100644 index 0000000..a9a4431 --- /dev/null +++ b/src/helper/settings_base.h @@ -0,0 +1,93 @@ +#ifndef SRC_HELPER_SETTINGS_BASE +#define SRC_HELPER_SETTINGS_BASE + +#include +#include +#include +#include +#include + +// 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 &_settings() +{ + static std::vector settings; + return settings; +} + +// A “typed” setting that auto‑registers itself +template +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) + { + 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 && !std::is_same_v) + { + value = static_cast(std::stoll(str)); + } + else if constexpr (std::is_floating_point_v) + { + value = static_cast(std::stold(str)); + } + else if constexpr (std::is_same_v) + { + 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) + return value ? "true" : "false"; + else if constexpr (std::is_same_v) + return value; + else + return std::to_string(value); + } +}; + +#endif /* SRC_HELPER_SETTINGS_BASE */ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cf55b50 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,393 @@ +#include // Include SDL2 library headers for graphics and event handling +#include + +extern "C" +{ +#include // FFmpeg library for multimedia container format handling +#include // FFmpeg library for encoding/decoding +#include // FFmpeg library for image scaling and pixel format conversion +#include // FFmpeg utility functions for image handling +} + +#include // C++ atomic types for thread-safe variables +#include // C++ mutex for locking resources +#include // C++ condition variable for thread synchronization +#include // Math functions (not explicitly used here but included) +#include // Standard C I/O functions +#include // C++ dynamic array container +#include // C++ string type +#include +#include + +#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 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 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; +} \ No newline at end of file diff --git a/src/pcm_audio.cpp b/src/pcm_audio.cpp new file mode 100644 index 0000000..28f258c --- /dev/null +++ b/src/pcm_audio.cpp @@ -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(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(segment.data + segment.offset); + size_t count = segment.size / sizeof(int16_t); + for (size_t i = 0; i < count; ++i) + { + samples[i] = static_cast(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); +} diff --git a/src/pcm_audio.h b/src/pcm_audio.h new file mode 100644 index 0000000..70f84a8 --- /dev/null +++ b/src/pcm_audio.h @@ -0,0 +1,39 @@ +#ifndef SRC_PCM_AUDIO +#define SRC_PCM_AUDIO + +#include +#include +#include + +#include + +#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 _active{false}; +}; + +#endif /* SRC_PCM_AUDIO */ diff --git a/src/protocol.cpp b/src/protocol.cpp new file mode 100644 index 0000000..aa8e805 --- /dev/null +++ b/src/protocol.cpp @@ -0,0 +1,279 @@ +#include "protocol.h" + +#include + +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(value), + static_cast(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 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(data[i]); + printf("%c", isprint(ch) ? ch : '.'); + } + // for (int i = 0; i < length && i < 10; i++) + // printf("%-4u", data[i]); + printf("\n"); +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..daaee76 --- /dev/null +++ b/src/protocol.h @@ -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 */ diff --git a/src/resource/background.bmp b/src/resource/background.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a7207730304a2010ed5a330f9f68a989ef258349 GIT binary patch literal 92876 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 AV*mgE literal 0 HcmV?d00001 diff --git a/src/resource/background.h b/src/resource/background.h new file mode 100644 index 0000000..0885c0b --- /dev/null +++ b/src/resource/background.h @@ -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 */ diff --git a/src/resource/font.h b/src/resource/font.h new file mode 100644 index 0000000..be63215 --- /dev/null +++ b/src/resource/font.h @@ -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 */ diff --git a/src/resource/font.ttf b/src/resource/font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..67803bb642742643c9525b6d25b8ade89383232c GIT binary patch literal 130832 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 literal 0 HcmV?d00001 diff --git a/src/struct/raw-queue.cpp b/src/struct/raw-queue.cpp new file mode 100644 index 0000000..98978bc --- /dev/null +++ b/src/struct/raw-queue.cpp @@ -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 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 &reading) +{ + std::unique_lock 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 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(); +} diff --git a/src/struct/raw_queue.h b/src/struct/raw_queue.h new file mode 100644 index 0000000..b9b932d --- /dev/null +++ b/src/struct/raw_queue.h @@ -0,0 +1,46 @@ +#ifndef SRC_RAW_QUEUE +#define SRC_RAW_QUEUE + +#include +#include +#include +#include +#include + +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 &reading); + + // Clears the queue and frees any pending buffers + void clear(); + + // Unlock queus + void notify(); + +private: + std::vector _buffer; + uint16_t _head; + uint16_t _tail; + uint16_t _size; + uint16_t _capacity; + std::mutex _mutex; + std::condition_variable _condition; +}; + +#endif \ No newline at end of file diff --git a/src/struct/video_buffer.cpp b/src/struct/video_buffer.cpp new file mode 100644 index 0000000..f8a7d51 --- /dev/null +++ b/src/struct/video_buffer.cpp @@ -0,0 +1,111 @@ +#include "video_buffer.h" + +extern "C" +{ +#include +} + +#include +#include + +#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; + } +} diff --git a/src/struct/video_buffer.h b/src/struct/video_buffer.h new file mode 100644 index 0000000..e0c0e2f --- /dev/null +++ b/src/struct/video_buffer.h @@ -0,0 +1,42 @@ +#ifndef SRC_VIDEO_BUFFER +#define SRC_VIDEO_BUFFER + +extern "C" +{ +#include // For AVFrame +} + +#include + +#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 _latest; + std::atomic _reading; + std::atomic _writing; + AVFrame *_frames[BUFFER_VIDEO_FRAMES] = {nullptr, nullptr, nullptr}; + uint32_t _ids[BUFFER_VIDEO_FRAMES]; +}; + +#endif /* SRC_VIDEO_BUFFER */ diff --git a/src/ux/ufont.h b/src/ux/ufont.h new file mode 100644 index 0000000..a690065 --- /dev/null +++ b/src/ux/ufont.h @@ -0,0 +1,62 @@ +#ifndef SRC_UX_UFONT +#define SRC_UX_UFONT + +#include +#include + +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 */ diff --git a/src/ux/uimage.h b/src/ux/uimage.h new file mode 100644 index 0000000..a39f232 --- /dev/null +++ b/src/ux/uimage.h @@ -0,0 +1,52 @@ +#ifndef SRC_UX_UIMAGE +#define SRC_UX_UIMAGE + +#include +#include + +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 \ No newline at end of file