mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
Support for Android Auto
This commit is contained in:
@@ -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
|
||||
|
||||
+18
-12
@@ -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
|
||||
|
||||
+3
-2
@@ -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;
|
||||
|
||||
|
||||
+16
-6
@@ -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;
|
||||
|
||||
+112
-26
@@ -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);
|
||||
|
||||
+9
-3
@@ -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<Message> audioStreamMain;
|
||||
AtomicQueue<Message> 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 */
|
||||
|
||||
+135
-68
@@ -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<AVPixelFormat>(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);
|
||||
}
|
||||
+12
-3
@@ -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[];
|
||||
};
|
||||
|
||||
+11
-12
@@ -9,12 +9,10 @@ class Settings
|
||||
public:
|
||||
// General section
|
||||
static inline Setting<int> vendorid{"vendor-id", 4884};
|
||||
static inline Setting<int> productid{"product-id ", 5408};
|
||||
static inline Setting<int> sourceWidth{"source-width", 720};
|
||||
static inline Setting<int> sourceHeight{"source-height", 576};
|
||||
static inline Setting<int> sourceFps{"source-fps", 50};
|
||||
static inline Setting<int> productid{"product-id", 5408};
|
||||
static inline Setting<int> width{"width", 720};
|
||||
static inline Setting<int> height{"height", 576};
|
||||
static inline Setting<int> sourceFps{"source-fps", 50};
|
||||
static inline Setting<int> fps{"fps", 50};
|
||||
static inline Setting<bool> fullscreen{"fullscreen", true};
|
||||
static inline Setting<bool> logging{"logging", false};
|
||||
@@ -24,16 +22,17 @@ public:
|
||||
static inline Setting<bool> autoconnect{"autoconnect", true};
|
||||
static inline Setting<bool> weakCharge{"weak-charge", true};
|
||||
static inline Setting<bool> leftDrive{"left-hand-drive", true};
|
||||
static inline Setting<int> nightMode{"night-mode", 2};
|
||||
static inline Setting<bool> wifi5{"wifi-5", true};
|
||||
static inline Setting<bool> bluetoothAudio{"bluetooth-audio", false};
|
||||
static inline Setting<int> micType{"mic-type", 1};
|
||||
static inline Setting<int> dpi{"dpi", 0};
|
||||
static inline Setting<int> nightMode{"night-mode", 2};
|
||||
static inline Setting<bool> wifi5{"wifi-5", true};
|
||||
static inline Setting<bool> bluetoothAudio{"bluetooth-audio", false};
|
||||
static inline Setting<int> micType{"mic-type", 1};
|
||||
static inline Setting<int> dpi{"android-dpi", 120};
|
||||
static inline Setting<int> androidMode{"android-resolution", 0};
|
||||
|
||||
// Application configuration section
|
||||
static inline Setting<int> fontSize{"font-size", 30};
|
||||
// Application configuration section
|
||||
static inline Setting<int> fontSize{"font-size", 30};
|
||||
static inline Setting<bool> vsync{"vsync", false};
|
||||
static inline Setting<float> aspectCorrection{"aspect-correction", 1};
|
||||
static inline Setting<float> aspectCorrection{"aspect-correction", 1};
|
||||
static inline Setting<int> scaler{"scaler", 2};
|
||||
static inline Setting<bool> fastScale{"fast-render-scale", false};
|
||||
static inline Setting<int> videoQueue{"video-buffer-size", 32};
|
||||
|
||||
Reference in New Issue
Block a user