From 5e97501c0ca23221284d8e562031db6589754dc2 Mon Sep 17 00:00:00 2001 From: Niellun Date: Mon, 2 Jun 2025 17:53:50 +0300 Subject: [PATCH] Audio input, Audio optimisation --- README.md | 68 ++++++++++---------- docs/images/screenshot.png | Bin 0 -> 59707 bytes settings.txt | 7 ++- src/connector.cpp | 5 +- src/helper/iaudio_sender.h | 12 ++++ src/helper/protocol_const.h | 3 + src/interface.cpp | 2 +- src/main.cpp | 2 +- src/pcm_audio.cpp | 73 ++++++++++++++-------- src/pcm_audio.h | 2 + src/protocol.cpp | 38 +++++++++++- src/protocol.h | 12 ++-- src/recorder.cpp | 89 +++++++++++++++++++++++++++ src/recorder.h | 53 ++++++++++++++++ src/resource/{colors.h => colours.h} | 0 src/settings.h | 1 + src/struct/atomic_queue.h | 5 ++ 17 files changed, 304 insertions(+), 68 deletions(-) create mode 100644 docs/images/screenshot.png create mode 100644 src/helper/iaudio_sender.h create mode 100644 src/recorder.cpp create mode 100644 src/recorder.h rename src/resource/{colors.h => colours.h} (100%) diff --git a/README.md b/README.md index 164de1c..c262c7c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # FastCarPlay -This is C++ implementation of carplay receiver for "Autobox" dongles. +This is C++ implementation of Carplay receiver and Android Auto receiver for "Autobox" dongles. The purpose of the project is to make application lightweight to run on Raspberry PI Zero 2W using hardware decoding. +![Logo](docs/images/screenshot.png) + ## Dongles The dongles are readily available from Amazon or Aliexpress labeled by Carlinkit. They also seems to have official web site https://www.carlinkit.com/. Devices might have different vendor and product id's. Check your with lsusb and update settings if necessary. @@ -18,7 +20,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 libsdl2-ttf-2.0-0 libusb-1.0-0 libssl3 ``` ### USB Permissions and device id -On linux app may not have permisiions to read USB device. You need to create udev rule to grant persmissions for dongle. +On linux app may not have permissions to read USB device. You need to create udev rule to grant permissions for dongle. First you need to figure out your idVendor and idProduct. ``` lsusb @@ -52,7 +54,8 @@ cp ./settings.txt /conf ### 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 +- font.ttf for font. Use TTF format only +- you can edit colours.h to modify text colours in RGBA format The names of the file need to be exactly same. Resources are embedded in executable, remake the project to regenerate resources ``` @@ -67,55 +70,56 @@ The following keys have been mapped: - Enter - select active item - Backspace - Go back - f - toggle fullscreen mode +- q - exit +- r - force refresh ## Status What is working: +- Carplay and Android Auto - Video - Audio (multiple channels) - Key navigation - Simple touch +- Microphone (calls, siri) 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 +- Multi touch - i have no means to test it and no idea how it should work +- Some android keys are not mapped +- No methods to edit autoconnect list or switch wireless devices ### 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. +- Do not try to run debug builds if you do not need to debug application. They consume a lot of memory for address sanitising thats grow over time. +- 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. +- For multichannel audion (driving guidance over music) you need to have multichannel driver in the system (puslsaudio, pipewire). If you only have ALSA backend the second channel will not work +- Android has it's own video resolution system which is fixed for 480p 720p 1080p. So set up resolution that is closest to what you want and adjust DPI for UI scale. If you do not have full screen Android Auto, you might need to enable resolution negotiation. Go to Android Auto app info and search for "Additional settings in the app" option. Scrol down and tap fast 5 times on version. Now you can use top right three dots menu to go to "Developer settings". Tap "Video Resolution" and select "Allow to car and phone to negotiate". ### Progress and plans Done -- ✓ 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 -- ✓ Add encrypted USB communication option with magic code 0x55bb55bb for new firmware -- ✓ Improve touch responsiveness -- ✓ Protocol debugging option + python script to decode usb dumps -- ✓ Android Auto (have not tested much, but seems to work for me) +- [x] Implement direct buffer transfer from video decoder to renderer (should reduce amount of memory copies and CPU load) +- [x] Control audio buffers better (now system use 3 decoding threads but in reality only 2 required) +- [x] Reduce music volume when there is navigation messages +- [x] Add abilities to run script on device connect and device disconnect +- [x] Add encrypted USB communication option with magic code 0x55bb55bb for new firmware +- [x] Improve touch responsiveness +- [x] Protocol debugging option + python script to decode usb dumps +- [x] Android Auto (have not tested much, but seems to work for me) +- [x] Microphone support (Calls, Siri) Next -- Add microphone support (Calls, Siri) -- CAR menu with status, settings and options to run custom scripts - +- [ ] Car menu with status, settings and options to run custom scripts +- [ ] Canbus script communication or some sort of side key press handling +- [ ] Better android navigation +- [ ] Switch between wireless devices ## 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) -- ![carplay-client by rayphee](https://github.com/rayphee/carplay-client) +- [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) +- [carplay-client by rayphee](https://github.com/rayphee/carplay-client) The project is licenced under GPL-3 licence. See LICENCE for details. -The project is using Open Sans font (https://fonts.google.com/specimen/Open+Sans). See FONT_LICENCE for details. +The project is using [Open Sans font](https://fonts.google.com/specimen/Open+Sans). See FONT_LICENCE for details. ## Finally -If you have any questions, suggestions or you find problems running this feel free to open issue. +If you have any questions, suggestions or you find problems running this feel free to open issue. \ No newline at end of file diff --git a/docs/images/screenshot.png b/docs/images/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..fe89db0cc9757b90bcc5bf09d5384ff66770b7fa GIT binary patch literal 59707 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�iVwq>BiD2LR%?P^r|0sv_QDEIwe=DeP`h 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 LNkvXXu0mjff* + +class IAudioSender { +public: + virtual ~IAudioSender() = default; + virtual void sendAudio(uint8_t* data, uint32_t length) = 0; +}; + +#endif /* SRC_HELPER_IAUDIO_SENDER */ diff --git a/src/helper/protocol_const.h b/src/helper/protocol_const.h index d882ca1..998bbe0 100644 --- a/src/helper/protocol_const.h +++ b/src/helper/protocol_const.h @@ -35,6 +35,9 @@ #define CMD_VERSION 204 #define CMD_ENCRYPTION 240 +#define AUDIO_BUFFER_SIZE 2560 +#define AUDIO_BUFFER_OFFSET 12 + struct ProtocolCmdEntry { int cmd; diff --git a/src/interface.cpp b/src/interface.cpp index a52065c..5ddbf71 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -1,7 +1,7 @@ #include "interface.h" #include "resource/background.h" #include "resource/font.h" -#include "resource/colors.h" +#include "resource/colours.h" #include "settings.h" #include "helper/protocol_const.h" #include diff --git a/src/main.cpp b/src/main.cpp index 52841fc..df98f84 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,7 +23,7 @@ extern "C" #define FRAME_DELAY_INACTIVE 200 -static const char *title = "Fast Car Play v0.4"; +static const char *title = "Fast Car Play v0.5"; static SDL_Window *window = nullptr; static SDL_Renderer *renderer = nullptr; Uint32 evtStatus = (Uint32)-1; diff --git a/src/pcm_audio.cpp b/src/pcm_audio.cpp index b38f564..181031b 100644 --- a/src/pcm_audio.cpp +++ b/src/pcm_audio.cpp @@ -24,6 +24,8 @@ PcmAudio::PcmAudio(const char *name) _faded = false; _volume = 1.0; _fadeVolume = Settings::audioFade; + _config.channels = 0; + _config.rate = 0; if (_fadeVolume < 0) _fadeVolume = 0; if (_fadeVolume > 1) @@ -64,17 +66,31 @@ void PcmAudio::callback(void *userdata, Uint8 *stream, int len) PcmAudio *self = static_cast(userdata); const Message *segment = self->_data->peek(); if (segment && self->_offset == 0 && getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)) != self->_config) - { self->_reconfig = true; - self->_cv.notify_one(); + + bool underflow; + if (self->_data->has(self->_underflowSize)) + { + underflow = false; + self->_underflowCount = 0; } + else + { + underflow = true; + } + while (len > 0) { - if (segment == nullptr || self->_reconfig) + if (segment == nullptr || self->_reconfig || underflow) { std::fill_n(stream, len, 0); self->_faded = self->_fade; self->_volume = self->_faded ? Settings::audioFade : 1; + if (underflow && self->_underflowCount++ < 20) + { + self->_data->clear(); + return; + } self->_reconfig = true; self->_cv.notify_one(); return; @@ -155,33 +171,40 @@ void PcmAudio::runner() if (!segment) continue; - _config = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)); - if (device != 0) + ChannelConfig config = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)); + if (_config != config) { - SDL_PauseAudioDevice(device, 1); - SDL_CloseAudioDevice(device); - device = 0; + if (device != 0) + { + SDL_PauseAudioDevice(device, 1); + SDL_CloseAudioDevice(device); + device = 0; + } + + // Configure new spec + SDL_zero(spec); + spec.freq = config.rate; + spec.format = AUDIO_S16SYS; + spec.channels = config.channels; + spec.samples = 4096; + spec.callback = callback; + spec.userdata = this; + + device = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0); + if (device == 0) + { + std::cerr << _name << " Failed to open audio: " << SDL_GetError() << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + _config = config; } - // Configure new spec - SDL_zero(spec); - spec.freq = _config.rate; - spec.format = AUDIO_S16SYS; - spec.channels = _config.channels; - spec.samples = 4096; - spec.callback = callback; - spec.userdata = this; - - _reconfig = false; _offset = 0; + _reconfig = false; + _underflowCount = 0; + _underflowSize = spec.channels == 1 ? Settings::audioDelayCall : Settings::audioDelay; - device = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0); - if (device == 0) - { - std::cerr << _name << " Failed to open audio: " << SDL_GetError() << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - continue; - } SDL_PauseAudioDevice(device, 0); std::cout << _name << " Start playing " << _config.rate << "kHz " << (_config.channels == 2 ? "stereo" : "mono") << std::endl; if (_fader) diff --git a/src/pcm_audio.h b/src/pcm_audio.h index 03ce003..35c9b34 100644 --- a/src/pcm_audio.h +++ b/src/pcm_audio.h @@ -58,6 +58,8 @@ private: bool _faded; float _volume; float _fadeVolume; + int _underflowCount; + int _underflowSize; std::thread _thread; std::mutex _mtx; diff --git a/src/protocol.cpp b/src/protocol.cpp index b07e9fe..caa0e04 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -11,11 +11,11 @@ Protocol::Protocol(uint16_t width, uint16_t height, uint16_t fps, uint16_t paddi videoData(Settings::videoQueue), audioStreamMain(Settings::audioQueue), audioStreamAux(Settings::audioQueue), + _recorder(Settings::audioQueue), _width(width), _height(height), _fps(fps), _phoneConnected(false) - { } @@ -163,6 +163,14 @@ void Protocol::sendMove(float dx, float dy) send(CMD_TOUCH, false, buf, 16); } +void Protocol::sendAudio(uint8_t *data, uint32_t length) +{ + write_uint32_le(data, 5); + write_uint32_le(data + 4, 0); + write_uint32_le(data + 8, 3); + send(CMD_AUDIO_DATA, false, data, length); +} + 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' @@ -248,6 +256,9 @@ void Protocol::onPhone(bool connected) std::cout << (connected ? "[Protocol] Phone connected" : "[Protocol] Phone disconnected") << std::endl; + if (!connected) + _recorder.stop(); + pushEvent(_evtPhoneId, connected ? 1 : 0); if (connected && Settings::onConnect.value.length() > 1) @@ -257,11 +268,36 @@ void Protocol::onPhone(bool connected) execute(Settings::onDisconnect.value.c_str()); } +void Protocol::onControl(int cmd) +{ + switch (cmd) + { + case 1: + _recorder.start(this); + break; + + case 2: + _recorder.stop(); + break; + } +} + void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) { bool dispose = true; switch (cmd) { + + case CMD_CONTROL: + if (length == 4) + { + int cmd = 0; + memcpy(&cmd, data, sizeof(int)); + onControl(cmd); + } + + break; + case CMD_PLUGGED: onPhone(true); break; diff --git a/src/protocol.h b/src/protocol.h index 1199d7d..22672f7 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -3,11 +3,12 @@ #include "struct/atomic_queue.h" #include "struct/message.h" - +#include "helper/iaudio_sender.h" #include "settings.h" #include "connector.h" +#include "recorder.h" -class Protocol : private Connector +class Protocol : private Connector, public IAudioSender { public: @@ -26,6 +27,7 @@ public: void sendFile(const char *filename, int value); void sendClick(float x, float y, bool down); void sendMove(float dx, float dy); + void sendAudio(uint8_t *data, uint32_t length) override; AtomicQueue videoData; AtomicQueue audioStreamMain; @@ -40,15 +42,17 @@ private: void onStatus(uint8_t status) override; void onDevice(bool connected) override; + void onControl(int cmd); void onData(uint32_t cmd, uint32_t length, uint8_t *data) override; void onPhone(bool connected); - static const char *cmdString(int cmd); + static const char *cmdString(int cmd); + Recorder _recorder; uint16_t _width; uint16_t _height; uint16_t _fps; - bool _phoneConnected; + bool _phoneConnected; uint32_t _evtStatusId = (uint32_t)-1; uint32_t _evtPhoneId = (uint32_t)-1; diff --git a/src/recorder.cpp b/src/recorder.cpp new file mode 100644 index 0000000..3ac4f4b --- /dev/null +++ b/src/recorder.cpp @@ -0,0 +1,89 @@ +#include "recorder.h" + +#include +#include + +#include "helper/protocol_const.h" +#include "helper/functions.h" +#include "settings.h" + + +Recorder::Recorder(uint16_t buffSize) + : _sender(nullptr), _active(false), _device(0), _data(buffSize) +{ +} + +Recorder::~Recorder() +{ + stop(); + if (_thread.joinable()) + _thread.join(); +} + +void Recorder::start(IAudioSender *sender) +{ + if (_active) + return; + + if (_thread.joinable()) + _thread.join(); + + _sender = sender; + _active = true; + _thread = std::thread(&Recorder::runner, this); +} + +void Recorder::stop() +{ + if (!_active) + return; + _active = false; + _data.notify(); +} + +void Recorder::AudioCallback(void *userdata, Uint8 *stream, int len) +{ + Recorder *self = static_cast(userdata); + std::unique_ptr frame(new AudioChunk(AUDIO_BUFFER_OFFSET + len)); + std::memcpy(frame.get()->data + AUDIO_BUFFER_OFFSET, stream, len); + self->_data.pushDiscard(std::move(frame)); +} + +void Recorder::runner() +{ + setThreadName("recorder"); + + SDL_AudioDeviceID device = 0; + SDL_AudioSpec spec; + + SDL_zero(spec); + spec.freq = 16000; + spec.format = AUDIO_S16LSB; + spec.channels = 1; + spec.samples = AUDIO_BUFFER_SIZE / 2; // = 2560 bytes (1280 samples * 2 bytes) + spec.callback = AudioCallback; + spec.userdata = this; + + device = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &spec, nullptr, 0); + if (device == 0) + { + std::cerr << "[Recording] Failed to open audio: " << SDL_GetError() << std::endl; + _active = false; + return; + } + + SDL_PauseAudioDevice(device, 0); + + while (_active) + { + std::unique_ptr buffer = _data.pop(); + if (buffer && _sender) + _sender->sendAudio(buffer.get()->data, buffer.get()->size); + else if (_active) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + } + + SDL_PauseAudioDevice(device, 1); + SDL_CloseAudioDevice(device); +} diff --git a/src/recorder.h b/src/recorder.h new file mode 100644 index 0000000..e842b6d --- /dev/null +++ b/src/recorder.h @@ -0,0 +1,53 @@ +#ifndef SRC_RECORDER +#define SRC_RECORDER + +#include +#include + +#include + +#include "helper/iaudio_sender.h" +#include "struct/atomic_queue.h" + +class AudioChunk +{ +public: + AudioChunk(uint16_t size) + : data(new uint8_t[size]), size(size) + { + } + + ~AudioChunk() + { + delete[] data; + } + + // Deleted copy constructor/assignment + AudioChunk(const AudioChunk &) = delete; + AudioChunk &operator=(const AudioChunk &) = delete; + + uint8_t *data; + size_t size; +}; + +class Recorder +{ +public: + Recorder(uint16_t buffSize); + ~Recorder(); + + void start(IAudioSender *sender); + void stop(); + +private: + static void AudioCallback(void *userdata, Uint8 *stream, int len); + void runner(); + + IAudioSender *_sender; + std::atomic _active; + std::thread _thread; + SDL_AudioDeviceID _device; + AtomicQueue _data; +}; + +#endif /* SRC_RECORDER */ diff --git a/src/resource/colors.h b/src/resource/colours.h similarity index 100% rename from src/resource/colors.h rename to src/resource/colours.h diff --git a/src/settings.h b/src/settings.h index d323d9b..ca75e8a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -40,6 +40,7 @@ public: static inline Setting videoQueue{"video-buffer-size", 32}; static inline Setting audioQueue{"audio-buffer-size", 16}; static inline Setting audioDelay{"audio-buffer-wait", 2}; + static inline Setting audioDelayCall{"audio-buffer-wait-call", 8}; static inline Setting audioFade{"audio-fade", 0.3}; static inline Setting audioDriver{"audio-driver", ""}; static inline Setting onConnect{"on-connect-script", ""}; diff --git a/src/struct/atomic_queue.h b/src/struct/atomic_queue.h index 6ea3d8f..25f93a7 100644 --- a/src/struct/atomic_queue.h +++ b/src/struct/atomic_queue.h @@ -68,6 +68,11 @@ public: return item; } + bool has(uint8_t count) + { + return _count >= count; + } + bool wait(atomic &waitFlag, uint8_t count = 0) { unique_lock lock(_mtx);