From 907247467d0049746d4b73ffd22030f58f7659fe Mon Sep 17 00:00:00 2001 From: Niellun Date: Sun, 1 Jun 2025 06:19:22 +0300 Subject: [PATCH] Support for Android Auto --- README.md | 5 +- settings.txt | 30 ++++--- src/connector.cpp | 5 +- src/main.cpp | 22 +++-- src/protocol.cpp | 138 +++++++++++++++++++++++++------ src/protocol.h | 12 ++- src/renderer.cpp | 203 ++++++++++++++++++++++++++++++---------------- src/renderer.h | 15 +++- src/settings.h | 23 +++--- 9 files changed, 319 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index faf0e72..be445fc 100644 --- a/README.md +++ b/README.md @@ -98,11 +98,12 @@ Done - ✓ 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 +- ✓ Protocol debugging option + python script to decode usb dumps +- ✓ Android Auto (have not tested much, but seems to work for me) Next - Add microphone support (Calls, Siri) -- Support Android Auto (message me if you have idea how it suppose to work with this dongle) +- CAR menu with status, settings and options to run custom scripts ## Acknowledgement diff --git a/settings.txt b/settings.txt index 29b4063..39a644a 100644 --- a/settings.txt +++ b/settings.txt @@ -7,15 +7,13 @@ #vendor-id = 4884 # 0x1314 #product-id = 5408 # 0x1520 -# Requested image from phone -#source-width = 720 -#source-height = 576 -#source-fps = 50 - -# Application drawing settings widthxheight +# Requested image resolution #width = 720 #height = 576 +# Requested image refresh rate +#source-fps = 50 + # Application drawing target framerate. This can responsiveness. # If the setting is lower than source-fps the framse will be dropped # If the setting is not multiple of source-fps can still cause frame drops cause out-of-sync @@ -64,8 +62,20 @@ # 3 - Phone #mic-type = 1 -# Target DPI reported to device. Set 0 for default. Not sure if it affects anything -#dpi = 0 +# Requested image DPI (Android auto scale) +#android-dpi = 120 + +# Android auto resolution mode. +# Android is always streaming one of fixed resolutions +# If you are using default android auto make sure that your widh and height +# set up according to resolution. Or you can enable any resolution by developer menu. +# For developer menu go to Android Auto app info -> Additional settings in the app +# 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" +# 1 - 480p => 800x480 +# 2 - 720p => 1280x720 +# 3 - 1080p => 1920x1080 +#android-resolution = 1 ############################################################################## # 3.Application configuration @@ -81,10 +91,6 @@ #aspect-correction = 1 # 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 -# The image will still be scaled to window size using ginternal GL scaling. -# See fast-render-scale for that # options # SWS_FAST_BILINEAR 1 # SWS_BILINEAR 2 diff --git a/src/connector.cpp b/src/connector.cpp index d92874e..6cd68bd 100644 --- a/src/connector.cpp +++ b/src/connector.cpp @@ -251,7 +251,7 @@ void Connector::printInts(uint8_t *data, uint32_t length, uint16_t max) ((uint32_t)data[i * 4 + 1] << 8) | ((uint32_t)data[i * 4 + 2] << 16) | ((uint32_t)data[i * 4 + 3] << 24); - std::cout << val; + std::cout << val << " "; } std::cout << std::endl; } @@ -293,7 +293,8 @@ void Connector::printMessage(uint32_t cmd, uint32_t length, uint8_t *data, bool if (Settings::protocolDebug < PROTOCOL_DEBUG_OUT && out) return; - bool stream = (cmd == CMD_AUDIO_DATA || cmd == CMD_VIDEO_DATA) && length > 50; + bool stream = (cmd == CMD_AUDIO_DATA || cmd == CMD_VIDEO_DATA) && length > 150; + stream = stream || cmd == CMD_HEARTBEAT; if (Settings::protocolDebug < PROTOCOL_DEBUG_ALL && stream) return; diff --git a/src/main.cpp b/src/main.cpp index dbcca54..405ecdb 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.3"; +static const char *title = "Fast Car Play v0.4"; static SDL_Window *window = nullptr; static SDL_Renderer *renderer = nullptr; Uint32 evtStatus = (Uint32)-1; @@ -33,6 +33,7 @@ bool active = true; struct RunParams { bool connected; + bool videoPrepaired; bool videoRendered; bool dirty; bool fullscreen; @@ -40,6 +41,8 @@ struct RunParams uint8_t deviceStatus; uint32_t frameDelay; int activeDelay; + float cropX; + float cropY; }; void processKey(Protocol &protocol, SDL_Keysym key, RunParams ¶ms) @@ -132,13 +135,15 @@ void processEvents(Protocol &protocol, RunParams ¶ms, VideoBuffer &vb) { if (e.type == evtConnected) { - printf("\nEvt connected %d\n", e.user.code); params.connected = e.user.code != 0; params.dirty = true; params.videoRendered = false; params.frameDelay = params.connected ? params.activeDelay : FRAME_DELAY_INACTIVE; if (!params.connected) + { vb.reset(); + params.videoPrepaired = false; + } } else if (e.type == evtStatus) { @@ -153,11 +158,11 @@ void processEvents(Protocol &protocol, RunParams ¶ms, VideoBuffer &vb) int window_width, window_height; SDL_GetWindowSize(window, &window_width, &window_height); if (downX >= 0) - protocol.sendClick(1.0 * downX / window_width, 1.0 * downY / window_height, true); + protocol.sendClick(params.cropX * downX / window_width, params.cropY * downY / window_height, true); if (motionX >= 0) - protocol.sendMove(1.0 * motionX / window_width, 1.0 * motionY / window_height); + protocol.sendMove(params.cropX * motionX / window_width, params.cropY * motionY / window_height); if (upX >= 0) - protocol.sendClick(1.0 * upX / window_width, 1.0 * upY / window_height, false); + protocol.sendClick(params.cropX * upX / window_width, params.cropY * upY / window_height, false); } } @@ -169,9 +174,12 @@ void application() p.deviceStatus = PROTOCOL_STATUS_INITIALISING; p.dirty = false; p.frameDelay = FRAME_DELAY_INACTIVE; + p.videoPrepaired = false; p.videoRendered = false; p.fullscreen = Settings::fullscreen; p.mouseDown = false; + p.cropX = 1; + p.cropY = 1; if (p.fullscreen) { @@ -184,7 +192,7 @@ void application() SDL_RenderPresent(renderer); // Present initial blank frame VideoBuffer videoBuffer; - Protocol protocol(Settings::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE); + Protocol protocol(Settings::width, Settings::height, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE); Decoder decoder; PcmAudio audioMain("Main"), audioAux("Aux"); decoder.start(&protocol.videoData, &videoBuffer, AV_CODEC_ID_H264); @@ -206,6 +214,8 @@ void application() uint32_t frameid = 0; if (videoBuffer.latest(&frame, &frameid) && (frameid != latestid || p.dirty) && frame) { + if (!p.videoPrepaired) + p.videoPrepaired = interface.prepare(frame, Settings::width, Settings::height, Settings::scaler, protocol.phoneAndroid, &p.cropX, &p.cropY); if (interface.render(frame)) { p.videoRendered = true; diff --git a/src/protocol.cpp b/src/protocol.cpp index 1bd4fbd..7ec3902 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -12,9 +12,11 @@ Protocol::Protocol(uint16_t width, uint16_t height, uint16_t fps, uint16_t paddi audioStreamMain(Settings::audioQueue), audioStreamAux(Settings::audioQueue), phoneConnected(false), + phoneAndroid(false), _width(width), _height(height), - _fps(fps) + _fps(fps), + _phoneInfo(false) { } @@ -49,6 +51,57 @@ void Protocol::sendInit(int width, int height, int fps) connector.send(1, true, buf, 28); } +void Protocol::sendConfig() +{ + int syncTime = std::time(nullptr); + int mediaDelay = 300; + int drivePosition = Settings::leftDrive ? 0 : 1; // 0==left, 1==right + int nightMode = Settings::nightMode; // 0==day, 1==night, 2==auto + if (nightMode < 0 || nightMode > 2) + nightMode = 2; + int mic = 7; + if (Settings::micType == 2) + mic = 15; + if (Settings::micType == 3) + mic = 21; + + float aspect = (float)_width / _height; + int height = 480; + if (Settings::androidMode == 2) + height = 1280; + if (Settings::androidMode == 3) + height = 1920; + int width = aspect*height; + if (aspect < 1) + { + width = height; + height = height/aspect; + } + + char buffer[512]; + snprintf(buffer, sizeof(buffer), + "{\"syncTime\":%d,\"mediaDelay\":%d,\"drivePosition\":%d," + "\"androidAutoSizeW\":%d,\"androidAutoSizeH\":%d,\"HiCarConnectMode\":0," + "\"GNSSCapability\":7,\"DashboardInfo\":1,\"UseBTPhone\":0}", + syncTime, mediaDelay, drivePosition, width, height); + + sendString(CMD_JSON_CONTROL, buffer); + + snprintf(buffer, sizeof(buffer), "{\"DayNightMode\":%d}", nightMode); + sendString(CMD_DAYNIGHT, buffer); + + sendFile("/tmp/night_mode", nightMode); + sendFile("/tmp/charge_mode", Settings::weakCharge ? 0 : 2); // Weak charge 0, other 2 + sendFile("/etc/box_name", "CarPlay"); + sendFile("/tmp/hand_drive_mode", drivePosition); + + sendInt(CMD_CONTROL, mic); + sendInt(CMD_CONTROL, Settings::wifi5 ? 25 : 24); + sendInt(CMD_CONTROL, Settings::bluetoothAudio ? 22 : 23); + if (Settings::autoconnect) + sendInt(CMD_CONTROL, 1002); +} + void Protocol::sendKey(int key) { uint8_t buf[4]; @@ -134,6 +187,12 @@ void Protocol::sendInt(uint32_t cmd, uint32_t value, bool encryption) connector.send(cmd, encryption, buf, 4); } +void Protocol::sendString(uint32_t cmd, char *str, bool encryption) +{ + uint32_t total = strlen(str); + connector.send(cmd, true, (uint8_t *)str, total); +} + void Protocol::sendEncryption() { AESCipher *cypher = connector.Cypher(); @@ -147,6 +206,42 @@ void Protocol::sendEncryption() connector.send(CMD_ENCRYPTION, false, buf, 4); } +bool Protocol::jsonFind(const char *json, uint16_t length, const char *key, char *value, uint16_t size) const +{ + size_t key_len = std::strlen(key); + const char *end = json + length; + const char *p = json; + + while (p < end - key_len - 3) + { + if (*p == '"' && *(p + 1 + key_len) == '"' && std::memcmp(p + 1, key, key_len) == 0) + { + const char *colon = p + 1 + key_len + 1; + while (colon < end && *colon == ' ') + ++colon; + if (colon >= end || *colon != ':') + return false; + + const char *val_start = colon + 1; + while (val_start < end && (*val_start == ' ' || *val_start == '"')) + ++val_start; + + const char *val_end = val_start; + while (val_end < end && *val_end != ',' && *val_end != '}' && *val_end != '"') + ++val_end; + + uint16_t val_len = val_end - val_start; + if (val_len + 1 > size) + return false; + std::memcpy(value, val_start, val_len); + value[val_len] = '\0'; + return true; + } + ++p; + } + return false; +} + void Protocol::onStatus(uint8_t status) { pushEvent(_evtStatusId, status); @@ -158,30 +253,11 @@ void Protocol::onDevice(bool connected) { if (Settings::encryption) sendEncryption(); - sendInit(_width, _height, _fps); if (Settings::dpi > 0) sendFile("/tmp/screen_dpi", Settings::dpi); sendFile("/etc/android_work_mode", 1); - if (Settings::nightMode < 0 || Settings::nightMode > 2) // 0==day, 1==night, 2==auto - sendFile("/tmp/night_mode", 2); - else - sendFile("/tmp/night_mode", Settings::nightMode); - sendFile("/tmp/hand_drive_mode", Settings::leftDrive ? 0 : 1); // 0==left, 1==right - sendFile("/tmp/charge_mode", Settings::weakCharge ? 0 : 2); // Weak charge 0, other 2 - sendFile("/etc/box_name", "CarPlay"); - if (Settings::autoconnect) - sendInt(CMD_CONTROL, 1002); - sendInt(CMD_CONTROL, Settings::bluetoothAudio ? 22 : 23); - sendInt(CMD_CONTROL, Settings::wifi5 ? 25 : 24); - int mic = 7; - if (Settings::micType == 2) - mic = 15; - if (Settings::micType == 3) - mic = 21; - sendInt(CMD_CONTROL, mic); - - if (Settings::encryption) - sendEncryption(); + sendInit(_width, _height, _fps); + sendConfig(); } else { @@ -212,12 +288,26 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) bool dispose = true; switch (cmd) { + case CMD_JSON_CONTROL: + if (!_phoneInfo) + { + char res[32]; + if (jsonFind((char *)data, length, "MDLinkType", res, 32)) + { + _phoneInfo = true; + phoneAndroid = strncmp(res, "AndroidAuto", 11) == 0; + } + } + break; + case CMD_PLUGGED: onPhone(true); break; case CMD_UNPLUGGED: onPhone(false); + _phoneInfo = false; + phoneAndroid = false; break; case CMD_VIDEO_DATA: @@ -250,10 +340,6 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) } break; } - case CMD_CONTROL: - Connector::printBytes(data, length, 20); - break; - case CMD_ENCRYPTION: if (length == 0) connector.setEncryption(true); diff --git a/src/protocol.h b/src/protocol.h index 4ebe573..3b42965 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -23,7 +23,6 @@ public: 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); @@ -35,10 +34,14 @@ public: AtomicQueue audioStreamMain; AtomicQueue audioStreamAux; bool phoneConnected; + bool phoneAndroid; private: void sendInt(uint32_t cmd, uint32_t value, bool encryption = true); + void sendString(uint32_t cmd, char *str, bool encryption = true); void sendEncryption(); + void sendInit(int width, int height, int fps); + void sendConfig(); void onStatus(uint8_t status) override; void onDevice(bool connected) override; @@ -46,12 +49,15 @@ private: void onPhone(bool connected); + bool jsonFind(const char *json, uint16_t length, const char *key, char *value, uint16_t size) const; + uint16_t _width; uint16_t _height; uint16_t _fps; + bool _phoneInfo; - uint32_t _evtStatusId = (uint32_t) -1; - uint32_t _evtPhoneId = (uint32_t) -1; + uint32_t _evtStatusId = (uint32_t)-1; + uint32_t _evtPhoneId = (uint32_t)-1; }; #endif /* SRC_PROTOCOL */ diff --git a/src/renderer.cpp b/src/renderer.cpp index 68c31dc..eba7332 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -141,48 +141,35 @@ SDL_Rect RendererImage::draw(SDL_Renderer *renderer, int w, int h) } const Renderer::FormatMapping Renderer::_mapping[] = { - {AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24, &Renderer::rgb, "RGB24"}, - {AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUV420P"}, - {AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUVJ420P"}, - {AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12, &Renderer::nv, "NV12"}}; + {AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24, &Renderer::rgb, &Renderer::crgb, "RGB24", 3}, + {AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, &Renderer::cyuv, "YUV420P", 0}, + {AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, &Renderer::cyuv, "YUVJ420P", 0}, + {AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12, &Renderer::nv, &Renderer::cnv, "NV12", 0}}; Renderer::Renderer(SDL_Renderer *renderer) : _renderer(renderer), _texture(nullptr), _textureWidth(0), _textureHeight(0), + _cropX(0), + _cropY(0), + _crop(false), + _bytesPerPixel(0), _render(nullptr), _sws(nullptr), - _swsWidth(0), - _swsHeight(0), _frame(nullptr) { } Renderer::~Renderer() { - if (_texture) - { - SDL_DestroyTexture(_texture); - _texture = nullptr; - } - if (_sws) - { - sws_freeContext(_sws); - _sws = nullptr; - } - if (_frame) - { - av_frame_free(&_frame); - _frame = nullptr; - } + clear(); } bool Renderer::render(AVFrame *frame) { - if (_render == nullptr) - if (!prepare(frame, Settings::width, Settings::height, Settings::scaler)) - return false; + if (_render == nullptr || _texture == nullptr) + return false; (this->*_render)(frame); SDL_RenderClear(_renderer); SDL_RenderCopy(_renderer, _texture, nullptr, nullptr); @@ -211,19 +198,65 @@ bool Renderer::prepareTexture(uint32_t format, int width, int height) return true; } -bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler) +void Renderer::clear() { - if (frame->width == targetWidth && frame->height == targetHeight) + if (_texture) + { + SDL_DestroyTexture(_texture); + _texture = nullptr; + } + if (_sws) + { + sws_freeContext(_sws); + _sws = nullptr; + } + if (_frame) + { + av_frame_free(&_frame); + _frame = nullptr; + } +} + +bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler, bool android, float *cropX, float *cropY) +{ + clear(); + std::cout << "[UX] Prepare renderer " << targetWidth << "x" << targetHeight << " for source " << frame->width << "x" << frame->height << " " << (android ? "android auto" : "carplay") << std::endl; + if (targetWidth == 0 || targetHeight == 0) + return false; + + bool scaled = frame->height * targetWidth == targetHeight * frame->width; + int width = frame->width; + int height = frame->height; + + if (android && !scaled) + { + float scale = (float)frame->width / targetWidth; + float scale2 = (float)frame->height / targetHeight; + if (scale > scale2) + scale = scale2; + width = targetWidth * scale; + height = targetHeight * scale; + } + + bool cropW = android && width != frame->width; + bool cropH = android && height != frame->height; + _cropX = cropW ? (frame->width - width) / 2 : 0; + _cropY = cropH ? (frame->height - height) / 2 : 0; + *cropX = cropW ? (float)width / frame->width : 1; + *cropY = cropH ? (float)height / frame->height : 1; + + if (scaled || cropW || cropH) { AVPixelFormat fmt = static_cast(frame->format); for (const FormatMapping &mapping : _mapping) { if (mapping.avFormat == fmt) { - if (prepareTexture(mapping.sdlFormat, targetWidth, targetHeight)) + if (prepareTexture(mapping.sdlFormat, width, height)) { std::cout << "[UX] Direct rendering " << mapping.name << std::endl; - _render = mapping.function; + _render = (cropW || cropH) ? mapping.functionCrop : mapping.function; + _bytesPerPixel = mapping.bpp; return true; } } @@ -234,47 +267,33 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32 std::cout << "[UX] Scaling required from " << frame->width << "x" << frame->height << " to " << targetWidth << "x" << targetHeight << std::endl; } - if (!prepareTexture(SDL_PIXELFORMAT_IYUV, targetWidth, targetHeight)) + if (!prepareTexture(SDL_PIXELFORMAT_IYUV, width, height)) return false; - if (!_sws || _swsWidth != frame->width || _swsHeight != frame->height) + _sws = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format, + width, height, AV_PIX_FMT_YUV420P, + scaler, nullptr, nullptr, nullptr); + if (!_sws) { - if (_sws) - { - sws_freeContext(_sws); - _sws = nullptr; - } - - _sws = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format, - targetWidth, targetHeight, AV_PIX_FMT_YUV420P, - scaler, nullptr, nullptr, nullptr); - if (!_sws) - { - std::cerr << "[UX] Can't create sws context" << std::endl; - return false; - } - _swsWidth = frame->width; - _swsHeight = frame->height; + std::cerr << "[UX] Can't create sws context" << std::endl; + return false; } + _frame = av_frame_alloc(); if (!_frame) { - _frame = av_frame_alloc(); - if (!_frame) - { - std::cerr << "[UX] Can't allocate AVFrame" << std::endl; - return false; - } - _frame->format = AV_PIX_FMT_YUV420P; - _frame->width = targetWidth; - _frame->height = targetHeight; - // Allocate data buffer with 32 byte allingment - int avRes = av_frame_get_buffer(_frame, 32); - if (avRes != 0) - { - std::cerr << "[UX] Can't allocate AVFrame buffer: " << avErrorText(avRes) << std::endl; - return false; - } + std::cerr << "[UX] Can't allocate AVFrame" << std::endl; + return false; + } + _frame->format = AV_PIX_FMT_YUV420P; + _frame->width = width; + _frame->height = height; + // Allocate data buffer with 32 byte allingment + int avRes = av_frame_get_buffer(_frame, 32); + if (avRes != 0) + { + std::cerr << "[UX] Can't allocate AVFrame buffer: " << avErrorText(avRes) << std::endl; + return false; } std::cout << "[UX] Scaling rendering source format " << frame->format << std::endl; @@ -290,6 +309,15 @@ void Renderer::rgb(AVFrame *frame) frame->data[0], frame->linesize[0]); } +void Renderer::crgb(AVFrame *frame) +{ + uint8_t *rgb_data = frame->data[0] + _cropY * frame->linesize[0] + _cropX * _bytesPerPixel; + SDL_UpdateTexture( + _texture, + nullptr, + rgb_data, frame->linesize[0]); +} + void Renderer::nv(AVFrame *frame) { SDL_UpdateNVTexture( @@ -298,6 +326,21 @@ void Renderer::nv(AVFrame *frame) frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1]); } + +void Renderer::cnv(AVFrame *frame) +{ + uint8_t *y_plane = frame->data[0] + _cropY * frame->linesize[0] + _cropX; + + // UV plane (subsampled by 2) + uint8_t *uv_plane = frame->data[1] + (_cropY / 2) * frame->linesize[1] + 2 * (_cropX / 2); + + SDL_UpdateNVTexture( + _texture, + nullptr, + y_plane, frame->linesize[0], + uv_plane, frame->linesize[1]); +} + void Renderer::yuv(AVFrame *frame) { SDL_UpdateYUVTexture( @@ -308,18 +351,42 @@ void Renderer::yuv(AVFrame *frame) frame->data[2], frame->linesize[2]); } +void Renderer::cyuv(AVFrame *frame) +{ + + int crop_x_chroma = _cropX / 2; + int crop_y_chroma = _cropY / 2; + uint8_t *y_plane = frame->data[0] + _cropY * frame->linesize[0] + _cropX; + uint8_t *u_plane = frame->data[1] + crop_y_chroma * frame->linesize[1] + crop_x_chroma; + uint8_t *v_plane = frame->data[2] + crop_y_chroma * frame->linesize[2] + crop_x_chroma; + SDL_UpdateYUVTexture( + _texture, + nullptr, + y_plane, frame->linesize[0], + u_plane, frame->linesize[1], + v_plane, frame->linesize[2]); +} + void Renderer::scale(AVFrame *frame) { // Scale frame to output format sws_scale(_sws, frame->data, frame->linesize, - 0, _swsHeight, + 0, _textureHeight, _frame->data, _frame->linesize); - // Update SDL texture with YUV frame data - SDL_UpdateYUVTexture(_texture, nullptr, - _frame->data[0], _frame->linesize[0], - _frame->data[1], _frame->linesize[1], - _frame->data[2], _frame->linesize[2]); + yuv(_frame); } + +void Renderer::cscale(AVFrame *frame) +{ + // Scale frame to output format + sws_scale(_sws, + frame->data, frame->linesize, + 0, _textureHeight, + _frame->data, + _frame->linesize); + + cyuv(_frame); +} \ No newline at end of file diff --git a/src/renderer.h b/src/renderer.h index e88da5b..5740f0a 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -51,6 +51,7 @@ public: Renderer(SDL_Renderer *renderer); ~Renderer(); + bool prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler, bool android, float *cropX, float *cropY); bool render(AVFrame *frame); protected: @@ -64,25 +65,33 @@ private: AVPixelFormat avFormat; SDL_PixelFormatEnum sdlFormat; DrawFuncType function; + DrawFuncType functionCrop; std::string name; + uint8_t bpp; }; - bool prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler); + void clear(); bool prepareTexture(uint32_t format, int width, int height); void rgb(AVFrame *frame); void nv(AVFrame *frame); void yuv(AVFrame *frame); + void crgb(AVFrame *frame); + void cnv(AVFrame *frame); + void cyuv(AVFrame *frame); + void cscale(AVFrame *frame); void scale(AVFrame *frame); SDL_Texture *_texture; int _textureWidth; int _textureHeight; + int _cropX; + int _cropY; + bool _crop; + uint8_t _bytesPerPixel; DrawFuncType _render; SwsContext *_sws; - int _swsWidth; - int _swsHeight; AVFrame *_frame; static const FormatMapping _mapping[]; }; diff --git a/src/settings.h b/src/settings.h index 01878c3..87b9db1 100644 --- a/src/settings.h +++ b/src/settings.h @@ -9,12 +9,10 @@ class Settings public: // General section static inline Setting vendorid{"vendor-id", 4884}; - static inline Setting productid{"product-id ", 5408}; - static inline Setting sourceWidth{"source-width", 720}; - static inline Setting sourceHeight{"source-height", 576}; - static inline Setting sourceFps{"source-fps", 50}; + static inline Setting productid{"product-id", 5408}; static inline Setting width{"width", 720}; static inline Setting height{"height", 576}; + static inline Setting sourceFps{"source-fps", 50}; static inline Setting fps{"fps", 50}; static inline Setting fullscreen{"fullscreen", true}; static inline Setting logging{"logging", false}; @@ -24,16 +22,17 @@ public: static inline Setting autoconnect{"autoconnect", true}; static inline Setting weakCharge{"weak-charge", true}; static inline Setting leftDrive{"left-hand-drive", true}; - static inline Setting nightMode{"night-mode", 2}; - static inline Setting wifi5{"wifi-5", true}; - static inline Setting bluetoothAudio{"bluetooth-audio", false}; - static inline Setting micType{"mic-type", 1}; - static inline Setting dpi{"dpi", 0}; + static inline Setting nightMode{"night-mode", 2}; + static inline Setting wifi5{"wifi-5", true}; + static inline Setting bluetoothAudio{"bluetooth-audio", false}; + static inline Setting micType{"mic-type", 1}; + static inline Setting dpi{"android-dpi", 120}; + static inline Setting androidMode{"android-resolution", 0}; - // Application configuration section - static inline Setting fontSize{"font-size", 30}; + // Application configuration section + static inline Setting fontSize{"font-size", 30}; static inline Setting vsync{"vsync", false}; - static inline Setting aspectCorrection{"aspect-correction", 1}; + static inline Setting aspectCorrection{"aspect-correction", 1}; static inline Setting scaler{"scaler", 2}; static inline Setting fastScale{"fast-render-scale", false}; static inline Setting videoQueue{"video-buffer-size", 32};