Files
FastCarPlay/src/main.cpp
T

385 lines
11 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 "resource/background.h"
#include "resource/font.h"
#include "helper/functions.h"
#include "helper/ufont.h"
#include "helper/uimage.h"
#include "struct/video_buffer.h"
#include "protocol.h"
#include "decoder.h"
#include "pcm_audio.h"
#include "renderer.h"
static const char *title = "Fast Car Play v0.2";
static SDL_Window *window = nullptr;
static SDL_Renderer *renderer = nullptr;
bool active = false;
static SDL_Texture *textTexture = nullptr;
static std::string textureText = "";
static SDL_Texture *imgTexture = nullptr;
static bool mouseDown = false;
static bool fullscreen = false;
std::mutex statusMutex;
std::string statusText;
void onStatus(const char *status)
{
std::lock_guard<std::mutex> lock(statusMutex);
statusText = status;
}
void DrawText(UFont &font, std::string text)
{
if (!textTexture || textureText.compare(text) != 0)
{
if (textTexture)
SDL_DestroyTexture(textTexture);
textTexture = font.GetText(renderer, text.c_str(), {255, 255, 255, 255});
textureText = text;
}
if (!textTexture)
return;
int textW, textH;
SDL_QueryTexture(textTexture, nullptr, nullptr, &textW, &textH);
int windowW, windowH;
SDL_GetRendererOutputSize(renderer, &windowW, &windowH);
// Center text
SDL_Rect dstRect = {
(windowW - textW) / 2,
(windowH - textH) * 9 / 10,
textW,
textH};
SDL_RenderCopy(renderer, textTexture, nullptr, &dstRect);
}
void DrawImage(UImage &img)
{
if (!imgTexture)
imgTexture = img.GetImage(renderer);
if (!imgTexture)
return;
int windowW, windowH;
SDL_GetRendererOutputSize(renderer, &windowW, &windowH);
// Compute destination rectangle to center image
SDL_Rect dst = {
(windowW - img.Width) / 2, // x: center horizontally
(windowH - img.Height) / 2, // y: center vertically
img.Width, // width: original image width
img.Height // height: original image height
};
SDL_RenderCopy(renderer, imgTexture, nullptr, &dst);
}
void processKey(Protocol &protocol, SDL_Keysym key)
{
switch (key.sym)
{
case SDLK_f:
fullscreen = !fullscreen; // Toggle fullscreen mode
SDL_SetWindowFullscreen(window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
SDL_SetWindowBordered(window, 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, bool processMouse)
{
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_MOUSEBUTTONDOWN:
{
mouseDown = true;
downX = e.button.x;
downY = e.button.y;
break;
}
case SDL_MOUSEBUTTONUP:
{
mouseDown = false;
upX = e.button.x;
upY = e.button.y;
break;
}
case SDL_MOUSEMOTION:
{
if (!mouseDown)
break;
motionX = e.motion.x;
motionY = e.motion.y;
break;
}
case SDL_KEYDOWN:
{
processKey(protocol, e.key.keysym);
break;
}
}
}
if(processMouse && (downX>=0 || upX>=0 || motionX>=0))
{
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);
if(motionX>=0)
protocol.sendMove(1.0 * motionX / window_width, 1.0 * motionY / window_height);
if(upX>=0)
protocol.sendClick(1.0 * upX / window_width, 1.0 * upY / window_height, false);
}
}
void application()
{
fullscreen = Settings::fullscreen;
if (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::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE);
Decoder decoder;
PcmAudio audio0, audio1, audio2;
decoder.start(&protocol.videoData, &videoBuffer, AV_CODEC_ID_H264);
audio0.start(&protocol.audioStream0);
audio1.start(&protocol.audioStream1);
audio2.start(&protocol.audioStream2);
protocol.start(onStatus);
UFont textFont(font, font_len, Settings::fontSize);
std::string status = "Initialising";
DrawText(textFont, status);
SDL_RenderPresent(renderer);
UImage image(background, background_len);
SDL_RenderClear(renderer);
DrawImage(image);
DrawText(textFont, status);
SDL_RenderPresent(renderer);
std::cout << "[Main] Loop" << std::endl;
Renderer videoRenderer(renderer);
bool dirty = true;
bool connected = false;
bool videoPrepared = false;
const int activeDelay = 1000 / Settings::fps;
const int inactiveDelay = 1000 / 5; // 5FPS
uint32_t frameDelay = inactiveDelay;
active = true;
uint32_t latestid = 0;
while (active)
{
Uint32 frameStart = SDL_GetTicks();
processEvents(protocol, videoPrepared);
if (connected != protocol.phoneConnected)
{
connected = protocol.phoneConnected;
SDL_RenderClear(renderer);
DrawImage(image);
SDL_RenderPresent(renderer);
dirty = true;
videoPrepared = false;
frameDelay = connected ? activeDelay : inactiveDelay;
}
if (connected)
{
AVFrame *frame = nullptr;
uint32_t frameid = 0;
if (videoBuffer.latest(&frame, &frameid) && frameid != latestid && frame)
{
if (!videoPrepared)
videoPrepared = videoRenderer.prepare(frame, Settings::width, Settings::height, Settings::scaler);
if (videoPrepared && videoRenderer.render(frame))
{
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, videoRenderer.texture, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
if(frameid!=latestid+1)
std::cout << "[Main] Fram drop from " << frameid - latestid - 1 << std::endl;
latestid = frameid;
videoBuffer.consume();
}
}
else
{
{
std::lock_guard<std::mutex> lock(statusMutex);
if (status != statusText)
{
status = statusText;
dirty = true;
}
}
if (dirty)
{
SDL_RenderClear(renderer);
DrawImage(image);
DrawText(textFont, status);
SDL_RenderPresent(renderer);
}
}
Uint32 frameTime = SDL_GetTicks() - frameStart;
if (frameTime < frameDelay)
{
SDL_Delay(frameDelay - frameTime); // Sleep only the remaining time
}
}
std::cout << "[Main] Stopping" << std::endl;
SDL_HideWindow(window);
}
int main(int argc, char **argv)
{
std::cout << title << std::endl;
if (argc > 2)
{
std::cerr << " Usage: " << argv[0] << " [settings_file]" << std::endl;
return 1;
}
if (argc == 2)
{
Settings::load(argv[1]);
}
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
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, SDL_RENDERER_ACCELERATED);
if (renderer)
{
std::cout << "[Main] Started" << std::endl;
application();
std::cout << "[Main] Finish" << std::endl;
SDL_DestroyRenderer(renderer);
}
else
{
std::cerr << "[Main] SDL can't create renderer: " << SDL_GetError() << std::endl;
}
if (textTexture)
SDL_DestroyTexture(textTexture);
if (imgTexture)
SDL_DestroyTexture(imgTexture);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 0;
}