mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
350 lines
9.8 KiB
C++
350 lines
9.8 KiB
C++
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_ttf.h>
|
|
#include <string>
|
|
#include <unistd.h>
|
|
#include <iostream>
|
|
|
|
extern "C"
|
|
{
|
|
#include <libavformat/avformat.h> // FFmpeg library for multimedia container format handling
|
|
#include <libavcodec/avcodec.h> // FFmpeg library for encoding/decoding
|
|
#include <libswscale/swscale.h> // FFmpeg library for image scaling and pixel format conversion
|
|
#include <libavutil/imgutils.h> // FFmpeg utility functions for image handling
|
|
}
|
|
|
|
#include "helper/functions.h"
|
|
#include "helper/protocol_const.h"
|
|
#include "struct/video_buffer.h"
|
|
|
|
#include "protocol.h"
|
|
#include "decoder.h"
|
|
#include "pcm_audio.h"
|
|
#include "interface.h"
|
|
|
|
#define FRAME_DELAY_INACTIVE 200
|
|
|
|
static const char *title = "Fast Car Play v0.4";
|
|
static SDL_Window *window = nullptr;
|
|
static SDL_Renderer *renderer = nullptr;
|
|
Uint32 evtStatus = (Uint32)-1;
|
|
Uint32 evtConnected = (Uint32)-1;
|
|
bool active = true;
|
|
|
|
struct RunParams
|
|
{
|
|
bool connected;
|
|
bool videoPrepaired;
|
|
bool videoRendered;
|
|
bool dirty;
|
|
bool fullscreen;
|
|
bool mouseDown;
|
|
uint8_t deviceStatus;
|
|
uint32_t frameDelay;
|
|
int activeDelay;
|
|
float cropX;
|
|
float cropY;
|
|
};
|
|
|
|
void processKey(Protocol &protocol, SDL_Keysym key, RunParams ¶ms)
|
|
{
|
|
switch (key.sym)
|
|
{
|
|
case SDLK_f:
|
|
params.fullscreen = !params.fullscreen; // Toggle fullscreen mode
|
|
SDL_SetWindowFullscreen(window, params.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
|
SDL_SetWindowBordered(window, params.fullscreen ? SDL_FALSE : SDL_TRUE);
|
|
break;
|
|
|
|
case SDLK_q:
|
|
active = false;
|
|
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, RunParams ¶ms, VideoBuffer &vb)
|
|
{
|
|
SDL_Event e;
|
|
int motionX = -1;
|
|
int motionY = -1;
|
|
int downX = -1;
|
|
int downY = -1;
|
|
int upX = -1;
|
|
int upY = -1;
|
|
|
|
while (SDL_PollEvent(&e))
|
|
{
|
|
switch (e.type)
|
|
{
|
|
case SDL_QUIT:
|
|
active = false;
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT:
|
|
if (e.window.event == SDL_WINDOWEVENT_RESIZED)
|
|
{
|
|
params.dirty = true;
|
|
}
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
{
|
|
params.mouseDown = true;
|
|
downX = e.button.x;
|
|
downY = e.button.y;
|
|
break;
|
|
}
|
|
|
|
case SDL_MOUSEBUTTONUP:
|
|
{
|
|
params.mouseDown = false;
|
|
upX = e.button.x;
|
|
upY = e.button.y;
|
|
break;
|
|
}
|
|
case SDL_MOUSEMOTION:
|
|
{
|
|
if (!params.mouseDown)
|
|
break;
|
|
motionX = e.motion.x;
|
|
motionY = e.motion.y;
|
|
break;
|
|
}
|
|
case SDL_KEYDOWN:
|
|
{
|
|
processKey(protocol, e.key.keysym, params);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
if (e.type == evtConnected)
|
|
{
|
|
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)
|
|
{
|
|
params.deviceStatus = e.user.code;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (params.videoRendered && (downX >= 0 || upX >= 0 || motionX >= 0))
|
|
{
|
|
int window_width, window_height;
|
|
SDL_GetWindowSize(window, &window_width, &window_height);
|
|
if (downX >= 0)
|
|
protocol.sendClick(params.cropX * downX / window_width, params.cropY * downY / window_height, true);
|
|
if (motionX >= 0)
|
|
protocol.sendMove(params.cropX * motionX / window_width, params.cropY * motionY / window_height);
|
|
if (upX >= 0)
|
|
protocol.sendClick(params.cropX * upX / window_width, params.cropY * upY / window_height, false);
|
|
}
|
|
}
|
|
|
|
void application()
|
|
{
|
|
RunParams p;
|
|
p.activeDelay = 1000 / Settings::fps;
|
|
p.connected = false;
|
|
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)
|
|
{
|
|
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
|
|
|
|
VideoBuffer videoBuffer;
|
|
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);
|
|
audioMain.start(&protocol.audioStreamMain);
|
|
audioAux.start(&protocol.audioStreamAux, &audioMain);
|
|
protocol.start(evtStatus, evtConnected);
|
|
Interface interface(renderer);
|
|
|
|
std::cout << "[Main] Loop" << std::endl;
|
|
uint32_t latestid = 0;
|
|
Uint32 frameStart = SDL_GetTicks();
|
|
while (active)
|
|
{
|
|
processEvents(protocol, p, videoBuffer);
|
|
|
|
if (p.connected)
|
|
{
|
|
AVFrame *frame = nullptr;
|
|
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;
|
|
if (!p.dirty && (frameid != latestid + 1))
|
|
std::cout << "[Main] Frame drop " << frameid - latestid - 1 << " on " << frameid << std::endl;
|
|
latestid = frameid;
|
|
p.dirty = false;
|
|
}
|
|
videoBuffer.consume();
|
|
}
|
|
}
|
|
|
|
if (!p.videoRendered)
|
|
{
|
|
interface.drawHome(p.dirty, p.connected ? PROTOCOL_STATUS_CONNECTED : p.deviceStatus);
|
|
p.dirty = false;
|
|
}
|
|
|
|
Uint32 frameEnd = SDL_GetTicks();
|
|
Uint32 frameTime = frameEnd - frameStart;
|
|
if (active && frameTime < p.frameDelay)
|
|
{
|
|
SDL_Delay(p.frameDelay - frameTime); // Sleep only the remaining time
|
|
frameStart = frameStart + p.frameDelay;
|
|
}
|
|
else
|
|
frameStart = frameEnd;
|
|
}
|
|
std::cout << "[Main] Stopping" << std::endl;
|
|
SDL_HideWindow(window);
|
|
}
|
|
|
|
int start()
|
|
{
|
|
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;
|
|
}
|
|
|
|
// Create SDL window centered on screen
|
|
if (Settings::fastScale)
|
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
|
|
else
|
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
|
|
window = SDL_CreateWindow(title,
|
|
SDL_WINDOWPOS_CENTERED,
|
|
SDL_WINDOWPOS_CENTERED,
|
|
Settings::fullscreen ? displayMode.w : Settings::width,
|
|
Settings::fullscreen ? displayMode.h : Settings::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, (Settings::vsync ? (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC) : SDL_RENDERER_ACCELERATED));
|
|
if (renderer)
|
|
{
|
|
evtStatus = SDL_RegisterEvents(2);
|
|
if (evtStatus != (Uint32)-1)
|
|
{
|
|
evtConnected = evtStatus + 1;
|
|
std::cout << "[Main] Started" << std::endl;
|
|
application();
|
|
std::cout << "[Main] Finish" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "[Main] Can't register custom events" << std::endl;
|
|
}
|
|
|
|
SDL_DestroyRenderer(renderer);
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "[Main] SDL can't create renderer: " << SDL_GetError() << std::endl;
|
|
}
|
|
|
|
SDL_DestroyWindow(window);
|
|
TTF_Quit();
|
|
SDL_Quit();
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
std::cout << title << std::endl;
|
|
if (argc > 2)
|
|
{
|
|
std::cerr << " Usage: " << argv[0] << " [settings_file]" << std::endl;
|
|
return 0;
|
|
}
|
|
try
|
|
{
|
|
if (argc == 2)
|
|
Settings::load(argv[1]);
|
|
return start();
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::cerr << e.what() << std::endl;
|
|
return 1;
|
|
}
|
|
} |