#include #include #include #include #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 "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; } }