diff --git a/settings.txt b/settings.txt index c6117ef..2407afe 100644 --- a/settings.txt +++ b/settings.txt @@ -20,8 +20,11 @@ # source images and drawing times #fps = 50 -# Application starts in full screen -#fullscreen = true +# Application window mode +# 0 - windowed +# 1 - fullscreen +# 2 - headless linux (in case of direct output to screen without window manager) +#window-mode = 0 # Show mouse pointer #cursor = false @@ -100,7 +103,8 @@ # Request extra frames onevery key press. Usefull if you do not see last updates after key press # Enable for RPI hardware decoding and other systems where hardware decoder tends to buffer frames -#force-redraw = false +# 0 - disabled, enter number of frames to request (find minimal value that get all screen updates) +#force-redraw = 0 # Corrects aspect of UI #aspect-correction = 1 @@ -152,24 +156,41 @@ #on-connect-script = #on-disconnect-script = -# Map extra keys for control with there codes +############################################################################## +# 4. Key mapping +############################################################################## + +# Map keys to control # If you set logging true you and press keys you will see there codes in logs -#key-left = 0 -#key-right = 0 -#key-enter = 0 -#key-back = 0 -#key-home = 0 +#key-siri = 115 # S +#key-nightmode-on = 122 # Z +#key-nightmode-off = 120 # X +#key-left = 1073741903 # Left arrow +#key-right = 1073741904 # Right arrow +#key-enter = 13 # Enter +# For pipe to simulate key up after key-enter press +#key-enterup = 0 # unmapped by default +#key-back = 8 # Backspace +#key-up = 1073741906 # Up arrow +#key-down = 1073741905 # Down arrow +#key-home = 104 # H +#key-play = 93 # P +#key-pause = 91 # [ +#key-play-toggle = 112 # ] +#key-next = 46 # > +#key-previous = 44 # < +#key-call-accept = 97 # A +#key-call-reject = 115 # S +#key-video-focus = 118 # V +#key-video-release = 98 # B +#key-nav-focus = 110 # N +#key-nav-release = 109 # M # Path for named pipe that keys are going to be listen on to. # If empty pipe listening is disables. # Example /tmp/fastcarplay_pipe -# Keys are bytes and defined directly as in protocol, examples: -# BTN_LEFT 100 -# BTN_RIGHT 101 -# BTN_SELECT_DOWN 104 -# BTN_SELECT_UP 105 -# BTN_BACK 106 -# BTN_HOME 200 +# Keys are byte value [1..255] +# They are mapped to keys defined above #key-pipe-path = ############################################################################## diff --git a/src/application.cpp b/src/application.cpp new file mode 100644 index 0000000..39ccc97 --- /dev/null +++ b/src/application.cpp @@ -0,0 +1,389 @@ +#include "application.h" + +#include + +#include "struct/video_buffer.h" + +#include "settings.h" +#include "interface.h" +#include "decoder.h" +#include "pcm_audio.h" +#include "pipe_listener.h" + +#define EVT_STATUS_OFFSET 0 +#define EVT_PHONE_OFFSET 1 + +static KeySetting *keyMap[] = { + &Settings::keySiri, + &Settings::keyNightOn, + &Settings::keyNightOff, + &Settings::keyLeft, + &Settings::keyRight, + &Settings::keyEnter, + &Settings::keyEnterUp, + &Settings::keyBack, + &Settings::keyUp, + &Settings::keyDown, + &Settings::keyHome, + &Settings::keyPlay, + &Settings::keyPause, + &Settings::keyPlayPause, + &Settings::keyNext, + &Settings::keyPrev, + &Settings::keyAccept, + &Settings::keyReject, + &Settings::keyVideoFocus, + &Settings::keyVideoRelease, + &Settings::keyNavFocus}; + +static constexpr size_t keyMapSize = sizeof(keyMap) / sizeof(keyMap[0]); + +Application::Application(/* args */) : _window(nullptr), + _renderer(nullptr), + _active(true) +{ + std::cout << "[App] Creating" << std::endl; + + if (!setAudioDriver()) + throw std::runtime_error("Unsupported audio driver " + std::string(Settings::audioDriver.value)); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) != 0) + throw std::runtime_error(std::string("SDL initialisation failed > ") + SDL_GetError()); + + if (TTF_Init() != 0) + { + SDL_Quit(); + throw std::runtime_error(std::string("TTF initialisation failed > ") + TTF_GetError()); + } + + if (SDL_GetCurrentDisplayMode(0, &_displayMode) != 0) + { + TTF_Quit(); + SDL_Quit(); + throw std::runtime_error(std::string("SDL get display mode failed > ") + SDL_GetError()); + } + + std::cout << "[App] SDL screen: " + << _displayMode.w << "x" << _displayMode.h << "@" << _displayMode.refresh_rate + << ", audio: " << SDL_GetCurrentAudioDriver() << std::endl; +} + +Application::~Application() +{ + std::cout << "[App] Destroying" << std::endl; + if (_renderer != nullptr) + SDL_DestroyRenderer(_renderer); + if (_window != nullptr) + SDL_DestroyWindow(_window); + TTF_Quit(); + SDL_Quit(); + std::cout << "[App] Finished" << std::endl; +} + +void Application::start(const char *title) +{ + std::cout << "[App] Initialising" << std::endl; + + // 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"); + + // Prepare window, show it in headless to avoid blinking, otherwise hidden untill iniailised + bool fullsize = Settings::isFullscreen() || Settings::isHeadless(); + _width = fullsize ? _displayMode.w : Settings::width; + _height = fullsize ? _displayMode.h : Settings::height; + _window = SDL_CreateWindow(title, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + _width, + _height, + SDL_WINDOW_RESIZABLE | (Settings::isHeadless() ? 0 : SDL_WINDOW_HIDDEN)); + + if (!_window) + throw std::runtime_error(std::string("SDL can't create window > ") + SDL_GetError()); + + if (!Settings::cursor) + SDL_ShowCursor(SDL_DISABLE); + + // Create accelerated renderer for the window + Uint32 flags = SDL_RENDERER_ACCELERATED; + if (Settings::vsync) + flags |= SDL_RENDERER_PRESENTVSYNC; + _renderer = SDL_CreateRenderer(_window, -1, flags); + if (!_renderer) + throw std::runtime_error(std::string("SDL can't create renderer > ") + SDL_GetError()); + + // Register additional events + _evtBase = SDL_RegisterEvents(2); + if (_evtBase == (Uint32)-1) + throw std::runtime_error(std::string("Can't register custom events > ") + SDL_GetError()); + + std::cout << "[App] Starting" << std::endl; + loop(); + std::cout << "[App] Stopped" << std::endl; +} + +bool Application::setAudioDriver() +{ + if (Settings::audioDriver.value.length() < 2) + return true; + + for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i) + { + if (SDL_GetAudioDriver(i) == Settings::audioDriver.value) + { + SDL_setenv("SDL_AUDIODRIVER", Settings::audioDriver.value.c_str(), 1); + return true; + } + } + return false; +} + +int Application::processKey(SDL_Keysym key) +{ + for (uint8_t i = 0; i < keyMapSize; i++) + { + if (keyMap[i]->value == key.sym) + { + return keyMap[i]->key; + } + } + std::cout << "[App] Unmapped key " << key.sym << std::endl; + return 0; +} + +bool Application::processSystemEvent(const SDL_Event &e) +{ + if (e.type == SDL_QUIT) + { + _active = false; + return true; + } + + if (e.type == SDL_WINDOWEVENT) + { + if (e.window.event == SDL_WINDOWEVENT_RESIZED) + { + SDL_GetWindowSize(_window, &_width, &_height); + _state.dirty = true; + } + return true; + } + + if (e.type == SDL_KEYDOWN) + { + switch (e.key.keysym.sym) + { + case SDLK_f: + { + if (Settings::isHeadless()) + return true; + _state.fullscreen = !_state.fullscreen; // Toggle fullscreen mode + SDL_SetWindowFullscreen(_window, _state.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + SDL_SetWindowBordered(_window, _state.fullscreen ? SDL_FALSE : SDL_TRUE); + return true; + } + case SDLK_q: + { + _active = false; + return true; + } + } + } + + if (e.type == (_evtBase + EVT_STATUS_OFFSET)) + { + _state.deviceStatus = e.user.code; + return true; + } + + if (e.type == (_evtBase + EVT_PHONE_OFFSET)) + { + _state.connected = e.user.code != 0; + _state.frameRendered = false; + _state.dirty = true; + _state.requestFrame = -1; + _state.flushBuffers = _state.connected; + return true; + } + + return false; +} + +bool Application::processFrameEvents(Protocol &protocol, Renderer &renderer) +{ + bool result = false; + 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)) + { + if (processSystemEvent(e)) + continue; + + switch (e.type) + { + + case SDL_MOUSEBUTTONDOWN: + { + _state.mouseDown = true; + downX = e.button.x; + downY = e.button.y; + break; + } + + case SDL_MOUSEBUTTONUP: + { + _state.mouseDown = false; + upX = e.button.x; + upY = e.button.y; + result = true; + break; + } + case SDL_MOUSEMOTION: + { + if (!_state.mouseDown) + break; + motionX = e.motion.x; + motionY = e.motion.y; + break; + } + case SDL_KEYDOWN: + { + int key = processKey(e.key.keysym); + if (key > 0) + { + protocol.sendKey(key); + result = true; + } + break; + } + case SDL_KEYUP: + { + if (e.key.keysym.sym == Settings::keyEnter) + { + protocol.sendKey(Settings::keyEnterUp.key); + result = true; + } + break; + } + } + } + + if (_state.frameRendered && (downX >= 0 || upX >= 0 || motionX >= 0)) + { + if (downX >= 0) + protocol.sendClick(renderer.xScale * downX / _width, renderer.yScale * downY / _height, true); + if (motionX >= 0) + protocol.sendMove(renderer.xScale * motionX / _width, renderer.yScale * motionY / _height); + if (upX >= 0) + protocol.sendClick(renderer.xScale * upX / _width, renderer.yScale * upY / _height, false); + } + + return result; +} + +void Application::loop() +{ + // Prepare home screen + Interface interface(_renderer); + interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN); + + // Process full screen, do not do this in headless to avoid blinking + if (Settings::isFullscreen()) + { + _state.fullscreen = true; + SDL_SetWindowFullscreen(_window, SDL_WINDOW_FULLSCREEN); + SDL_SetWindowBordered(_window, SDL_FALSE); + } + + // Show window, do not do this in headless to avoid blinking + if (!Settings::isHeadless()) + SDL_ShowWindow(_window); + interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN); + + 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(_evtBase + EVT_STATUS_OFFSET, _evtBase + EVT_PHONE_OFFSET); + + std::cout << "[App] Loop" << std::endl; + Uint32 frameStart = SDL_GetTicks(); + AVFrame *frame = nullptr; + uint32_t frameid = 0; + uint32_t latestFrameid = 0; + uint32_t frameTargetTime = 1000 / Settings::fps; + int frameDelay = 0; + while (_active) + { + if (_state.connected && _state.showVideo) + { + if (videoBuffer.latest(&frame, &frameid) && frame && (frameid != latestFrameid || _state.dirty)) + { + if (interface.render(frame)) + { + _state.frameRendered = true; + if (!_state.dirty && (frameid != latestFrameid + 1)) + std::cout << "[App] Frame drop " << frameid - latestFrameid - 1 << " on " << frameid << std::endl; + latestFrameid = frameid; + _state.dirty = false; + if (_state.requestFrame >= 0) + _state.requestFrame++; + } + videoBuffer.consume(); + } + + if (_state.requestFrame > 0 && Settings::forceRedraw > 0) + { + if (_state.requestFrame <= Settings::forceRedraw) + protocol.requestKeyframe(); + else + _state.requestFrame = -1; + } + } + + if (!_state.frameRendered || !_state.showVideo) + { + interface.drawHome(_state.dirty, _state.connected ? PROTOCOL_STATUS_CONNECTED : _state.deviceStatus); + _state.dirty = false; + SDL_Event e; + while (SDL_PollEvent(&e)) + processSystemEvent(e); + } + else + { + if (processFrameEvents(protocol, interface) && Settings::forceRedraw > 0) + _state.requestFrame = 1; + } + + if (_state.flushBuffers) + { + _state.flushBuffers = false; + decoder.flush(); + videoBuffer.reset(); + } + + if (_active) + { + Uint32 frameEnd = SDL_GetTicks(); + frameDelay = frameTargetTime - (frameEnd - frameStart); + SDL_Delay(frameDelay > 0 ? frameDelay : 1); + frameStart += frameTargetTime; + } + } + + if (!Settings::isHeadless()) + SDL_HideWindow(_window); +} \ No newline at end of file diff --git a/src/application.h b/src/application.h new file mode 100644 index 0000000..946aefe --- /dev/null +++ b/src/application.h @@ -0,0 +1,51 @@ +#ifndef SRC_APPLICATION +#define SRC_APPLICATION + +#include +#include + +#include "helper/protocol_const.h" + +#include "protocol.h" +#include "renderer.h" + +class Application +{ +public: + Application(/* args */); + ~Application(); + + void start(const char *title); + +private: + struct State + { + bool connected = false; + bool dirty = false; + bool frameRendered = false; + int requestFrame = 0; + bool showVideo = true; + bool fullscreen = false; + bool mouseDown = false; + int8_t deviceStatus = PROTOCOL_STATUS_INITIALISING; + bool flushBuffers = false; + }; + + bool setAudioDriver(); + int processKey(SDL_Keysym key); + bool processSystemEvent(const SDL_Event &e); + bool processFrameEvents(Protocol &protocol, Renderer &renderer); + + void loop(); + + SDL_Window *_window; + SDL_Renderer *_renderer; + bool _active; + Uint32 _evtBase; + SDL_DisplayMode _displayMode; + State _state; + int _width; + int _height; +}; + +#endif /* SRC_APPLICATION */ diff --git a/src/connector.cpp b/src/connector.cpp index 833ad15..59bf080 100644 --- a/src/connector.cpp +++ b/src/connector.cpp @@ -315,8 +315,6 @@ void Connector::printMessage(uint32_t cmd, uint32_t length, uint8_t *data, bool void Connector::readLoop() { - std::mutex mtx; - std::condition_variable cv; Header header; int transferred = 0; uint8_t *data = nullptr; diff --git a/src/decoder.cpp b/src/decoder.cpp index a94ce50..79c863d 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -37,7 +37,7 @@ void Decoder::stop() void Decoder::flush() { - if(_context) + if (_context) avcodec_flush_buffers(_context); } @@ -63,9 +63,9 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) break; } - if(Settings::codecLowDelay) + if (Settings::codecLowDelay) result->flags |= AV_CODEC_FLAG_LOW_DELAY; - if(Settings::codecFast) + if (Settings::codecFast) result->flags2 |= AV_CODEC_FLAG2_FAST; int ret = avcodec_open2(result, codec, nullptr); diff --git a/src/helper/protocol_const.h b/src/helper/protocol_const.h index 32c02fb..b9d8d73 100644 --- a/src/helper/protocol_const.h +++ b/src/helper/protocol_const.h @@ -1,6 +1,7 @@ #ifndef SRC_HELPER_PROTOCOL_CONST #define SRC_HELPER_PROTOCOL_CONST +#define PROTOCOL_STATUS_UNKNOWN -1 // Manual > 0 #define PROTOCOL_STATUS_INITIALISING 0 // Initialised > 1 #define PROTOCOL_STATUS_NO_DEVICE 1 // Start linking > 3 #define PROTOCOL_STATUS_LINKING 2 // Linked > 4, Failed in sequence > 3 diff --git a/src/helper/settings_base.h b/src/helper/settings_base.h index 2c40cce..d7280ae 100644 --- a/src/helper/settings_base.h +++ b/src/helper/settings_base.h @@ -12,6 +12,7 @@ class ISetting { public: std::string name; + ISetting(std::string name_) : name(std::move(name_)) {} virtual void parse(std::string &str) = 0; virtual std::string asString() const = 0; }; @@ -30,9 +31,8 @@ class Setting : public ISetting public: T value; Setting(std::string name_, T default_) - : value(default_) + : ISetting(std::move(name_)), value(default_) { - name = std::move(name_); _settings().push_back(this); } @@ -57,7 +57,7 @@ public: else if (str == "0" || str == "false") value = false; else - throw new std::runtime_error("Can't convert to boolean."); + throw std::runtime_error("Can't convert to boolean."); } else if constexpr (std::is_integral_v && !std::is_same_v) { @@ -90,4 +90,16 @@ public: } }; +template +class KeySetting : public Setting +{ +public: + int key; + + KeySetting(std::string name_, T default_, int key_) + : Setting(std::move(name_), default_), key(key_) + { + } +}; + #endif /* SRC_HELPER_SETTINGS_BASE */ diff --git a/src/interface.cpp b/src/interface.cpp index 5ddbf71..c335a8f 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -28,7 +28,6 @@ bool Interface::drawHome(bool force, int state) _state = state; int width, height; SDL_GetRendererOutputSize(_renderer, &width, &height); - SDL_RenderClear(_renderer); _mainImage.draw(_renderer, width, height); if (state == PROTOCOL_STATUS_ERROR) diff --git a/src/main.cpp b/src/main.cpp index d06830c..f43be22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,444 +1,26 @@ -#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" +#include "application.h" #include "pipe_listener.h" +#include "settings.h" -#define FRAME_DELAY_INACTIVE 200 -#define MAX_EVENTS_PER_FRAME 5 +static const char *title = "Fast Car Play v0.7"; -static const char *title = "Fast Car Play v0.6"; -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; -}; - -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); - return; - - case SDLK_q: - active = false; - return; - - case SDLK_r: - protocol.sendKey(BTN_SCREEN_REFRESH); - params.dirty = true; - return; - - case SDLK_h: - protocol.sendKey(BTN_HOME); - return; - - case SDLK_s: - protocol.sendKey(BTN_SIRI); - return; - - case SDLK_m: - protocol.sendKey(BTN_MICROPHONE); - return; - - case SDLK_LEFT: - protocol.sendKey(BTN_LEFT); - return; - - case SDLK_RIGHT: - protocol.sendKey(BTN_RIGHT); - return; - - case SDLK_DOWN: - protocol.sendKey(BTN_DOWN); - return; - - case SDLK_SPACE: - protocol.sendKey(BTN_PLAY); - return; - - case SDLK_p: - protocol.sendKey(BTN_PAUSE); - return; - - case SDLK_MINUS: - protocol.sendKey(BTN_PREVIOUS_TRACK); - return; - - case SDLK_EQUALS: - protocol.sendKey(BTN_NEXT_TRACK); - return; - - case SDLK_RETURN: - protocol.sendKey(BTN_SELECT_DOWN); - protocol.sendKey(BTN_SELECT_UP); - return; - - case SDLK_BACKSPACE: - protocol.sendKey(BTN_BACK); - return; - - case 0: - return; - } - - if (key.sym == Settings::keyLeft) - { - protocol.sendKey(BTN_LEFT); - } - else if (key.sym == Settings::keyRight) - { - protocol.sendKey(BTN_RIGHT); - } - else if (key.sym == Settings::keyEnter) - { - protocol.sendKey(BTN_SELECT_DOWN); - protocol.sendKey(BTN_SELECT_UP); - } - else if (key.sym == Settings::keyBack) - { - protocol.sendKey(BTN_BACK); - } - else if (key.sym == Settings::keyHome) - { - protocol.sendKey(BTN_HOME); - } - else - { - std::cout << "[Key] Unmapped key " << key.sym << std::endl; - } -} - -bool processEvents(Protocol &protocol, RunParams ¶ms, Renderer &renderer) -{ - bool result = false; - SDL_Event e; - int motionX = -1; - int motionY = -1; - int downX = -1; - int downY = -1; - int upX = -1; - int upY = -1; - int processed = 0; - - while (SDL_PollEvent(&e) && processed++ < MAX_EVENTS_PER_FRAME) - { - 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; - params.videoPrepaired = false; - result = true; - } - 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(renderer.xScale * downX / window_width, renderer.yScale * downY / window_height, true); - if (motionX >= 0) - protocol.sendMove(renderer.xScale * motionX / window_width, renderer.yScale * motionY / window_height); - if (upX >= 0) - protocol.sendClick(renderer.xScale * upX / window_width, renderer.yScale * upY / window_height, false); - } - - return result; -} - -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; - - Interface interface(renderer); - if (p.fullscreen) - { - SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); - SDL_SetWindowBordered(window, SDL_FALSE); - } - SDL_ShowWindow(window); - interface.drawHome(true, 0); - - 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); - - PipeListener pipeListener(protocol, (Settings::keyPipe.value.length() > 2) ? Settings::keyPipe.value.c_str() : nullptr); - - std::cout << "[Main] Loop" << std::endl; - uint32_t latestid = 0; - Uint32 frameStart = SDL_GetTicks(); - uint32_t FrameRequest = 0; - while (active) - { - if (processEvents(protocol, p, interface)) - { - if (p.connected) - { - decoder.flush(); - videoBuffer.reset(); - } - } - - if (p.connected) - { - AVFrame *frame = nullptr; - uint32_t frameid = 0; - if (videoBuffer.latest(&frame, &frameid) && (frameid != latestid || p.dirty) && frame) - { - 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(); - FrameRequest = 0; - } - else - { - if (Settings::forceRedraw) - { - if (protocol.checkKey()) - FrameRequest = 3; - else if (FrameRequest-- > 0) - protocol.requestKeyframe(); - } - } - } - - if (!p.videoRendered) - { - interface.drawHome(p.dirty, p.connected ? PROTOCOL_STATUS_CONNECTED : p.deviceStatus); - p.dirty = false; - } - - if (active) - { - Uint32 frameEnd = SDL_GetTicks(); - Uint32 frameTime = frameEnd - frameStart; - if (frameTime < p.frameDelay) - { - SDL_Delay(p.frameDelay - frameTime); - frameStart += p.frameDelay; - } - else - { - SDL_Delay(1); - frameStart += 1; - } - } - } - - std::cout << "[Main] Stopping" << std::endl; - SDL_HideWindow(window); -} - -bool setAudioDriver() -{ - if (Settings::audioDriver.value.length() < 2) - return true; - - int num = SDL_GetNumAudioDrivers(); - for (int i = 0; i < num; ++i) - { - if (SDL_GetAudioDriver(i) == Settings::audioDriver.value) - { - SDL_setenv("SDL_AUDIODRIVER", SDL_GetAudioDriver(i), 1); - return true; - } - } - return false; -} - -int start() +void start() { if (!Settings::logging) disable_cout(); else Settings::print(); - if (!setAudioDriver()) - std::cerr << "[Main] Not supported audio driver " << Settings::audioDriver.value << std::endl; + if (Settings::keyPipe.value.length() > 2) + PipeListener pipeListener(Settings::keyPipe.value.c_str()); - // 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; - } - std::cout << "[Main] SDL audio driver: " << SDL_GetCurrentAudioDriver() << std::endl; - - 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; - } - - if (!Settings::cursor) - SDL_ShowCursor(SDL_DISABLE); - - // 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; + Application app; + app.start(title); } int main(int argc, char **argv) @@ -451,13 +33,15 @@ int main(int argc, char **argv) } try { - if (argc == 2) - Settings::load(argv[1]); - return start(); + if (argc == 2 && !Settings::load(argv[1])) + return 1; + + start(); } catch (const std::exception &e) { - std::cerr << e.what() << std::endl; + std::cerr << "[Main] Error > " << e.what() << std::endl; return 1; } + return 0; } \ No newline at end of file diff --git a/src/pipe_listener.cpp b/src/pipe_listener.cpp index d96eefa..90f461f 100644 --- a/src/pipe_listener.cpp +++ b/src/pipe_listener.cpp @@ -1,23 +1,21 @@ #include "pipe_listener.h" +#include + #include #include #include #include #include -PipeListener::PipeListener(Protocol &protocol, const char *path) - : _protocol(protocol), _path(path), _active(false) +PipeListener::PipeListener(const char *path) + : _path(path), _active(false) { - if(path == nullptr) + if (path == nullptr) return; unlink(_path); - if (mkfifo(_path, 0666) == -1) - if (errno != EEXIST) - { - std::cout << "[Pipe] Failed to create FIFO " << _path << ": " << std::strerror(errno) << std::endl; - return; - } + if (mkfifo(_path, 0666) == -1 && errno != EEXIST) + throw std::runtime_error(std::string("[Pipe] Failed to create FIFO ") + _path + ": " + std::strerror(errno)); _active = true; _thread = std::thread(&PipeListener::loop, this); @@ -57,7 +55,15 @@ void PipeListener::loop() { std::cout << "[Pipe] Received: " << (int)value << std::endl; if (value != 0) - _protocol.sendKey(value); + { + SDL_Event e{}; + e.type = SDL_KEYDOWN; + e.key.state = SDL_RELEASED; + e.key.repeat = 0; + e.key.keysym.sym = static_cast(value); + e.key.keysym.scancode = SDL_GetScancodeFromKey(e.key.keysym.sym); + SDL_PushEvent(&e); + } } if (fd >= 0) diff --git a/src/pipe_listener.h b/src/pipe_listener.h index 2111400..66783d4 100644 --- a/src/pipe_listener.h +++ b/src/pipe_listener.h @@ -1,5 +1,5 @@ -#ifndef SRC_NAMED_PIPE_H -#define SRC_NAMED_PIPE_H +#ifndef SRC_PIPE_LISTENER +#define SRC_PIPE_LISTENER #include #include @@ -7,16 +7,15 @@ class PipeListener { public: - PipeListener(Protocol &protocol, const char *path); + PipeListener(const char *path); ~PipeListener(); private: void loop(); - Protocol &_protocol; const char *_path; bool _active; std::thread _thread; }; -#endif // SRC_NAMED_PIPE_H +#endif /* SRC_PIPE_LISTENER */ diff --git a/src/protocol.cpp b/src/protocol.cpp index 015639b..e4eacc4 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -249,7 +249,7 @@ void Protocol::sendEncryption() { if (!_cipher) { - std::cout << "[Protocol] Can't enable encryption: cypher is not initalised"; + std::cout << "[Protocol] Can't enable encryption: cypher is not initalised" << std::endl; return; } uint8_t buf[4]; @@ -259,6 +259,7 @@ void Protocol::sendEncryption() void Protocol::onStatus(uint8_t status) { + std::cout << "[Protocol] Status " << (int)status << std::endl; pushEvent(_evtStatusId, status); } diff --git a/src/settings.cpp b/src/settings.cpp index 855a31d..9f0ea82 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -3,13 +3,13 @@ #include #include -void Settings::load(const std::string &filename) +bool Settings::load(const std::string &filename) { std::ifstream file(filename); if (!file.is_open()) { std::cerr << "[Settings] Cannot open “" << filename << "”" << std::endl; - return; + return false; } std::string line; @@ -48,6 +48,7 @@ void Settings::load(const std::string &filename) if (!found) std::cerr << "[Settings] Unknown key “" << key << "”" << std::endl; } + return true; } void Settings::print() diff --git a/src/settings.h b/src/settings.h index cc27e05..6cb85cb 100644 --- a/src/settings.h +++ b/src/settings.h @@ -3,6 +3,10 @@ #include "helper/settings_base.h" +#define SCREEN_MODE_WINDOW 0 +#define SCREEN_MODE_FULLSCREEN 1 +#define SCREEN_MODE_HEADLESS 2 + // The singleton “Settings” namespace class Settings { @@ -14,7 +18,7 @@ public: 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 screenMode{"window-mode", 0}; static inline Setting cursor{"cursor", false}; static inline Setting logging{"logging", false}; @@ -35,7 +39,7 @@ public: static inline Setting fontSize{"font-size", 30}; static inline Setting vsync{"vsync", false}; static inline Setting hwDecode{"hw-decode", true}; - static inline Setting forceRedraw{"force-redraw", false}; + static inline Setting forceRedraw{"force-redraw", 0}; static inline Setting aspectCorrection{"aspect-correction", 1}; static inline Setting fastScale{"fast-render-scale", false}; static inline Setting videoQueue{"video-buffer-size", 32}; @@ -43,30 +47,47 @@ public: static inline Setting audioDelay{"audio-buffer-wait", 2}; static inline Setting audioDelayCall{"audio-buffer-wait-call", 8}; static inline Setting audioFade{"audio-fade", 0.3}; - static inline Setting audioBuffer{"audio-buffer-samples", 2048}; + static inline Setting audioBuffer{"audio-buffer-samples", 2048}; static inline Setting audioDriver{"audio-driver", ""}; static inline Setting onConnect{"on-connect-script", ""}; static inline Setting onDisconnect{"on-disconnect-script", ""}; - static inline Setting keyLeft{"key-left", 0}; - static inline Setting keyRight{"key-right", 0}; - static inline Setting keyEnter{"key-enter", 0}; - static inline Setting keyBack{"key-back", 0}; - static inline Setting keyHome{"key-home", 0}; - static inline Setting keyPipe{"key-pipe-path", ""}; + // Key mapping section + static inline KeySetting keySiri{"key-siri", 115, 5}; + static inline KeySetting keyNightOn{"key-nightmode-on", 122, 16}; + static inline KeySetting keyNightOff{"key-nightmode-off", 120, 17}; + static inline KeySetting keyLeft{"key-left", 1073741903, 100}; + static inline KeySetting keyRight{"key-right", 1073741904, 101}; + static inline KeySetting keyEnter{"key-enter", 13, 104}; + static inline KeySetting keyEnterUp{"key-enterup", 0, 105}; + static inline KeySetting keyBack{"key-back", 8, 106}; + static inline KeySetting keyUp{"key-up", 1073741906, 113}; + static inline KeySetting keyDown{"key-down", 1073741905, 114}; + static inline KeySetting keyHome{"key-home", 104, 200}; + static inline KeySetting keyPlay{"key-play", 93, 201}; + static inline KeySetting keyPause{"key-pause", 91, 202}; + static inline KeySetting keyPlayPause{"key-play-toggle", 112, 203}; + static inline KeySetting keyNext{"key-next", 46, 204}; + static inline KeySetting keyPrev{"key-previous", 44, 205}; + static inline KeySetting keyAccept{"key-call-accept", 97, 300}; + static inline KeySetting keyReject{"key-call-reject", 115, 301}; + static inline KeySetting keyVideoFocus{"key-video-focus", 118, 500}; + static inline KeySetting keyVideoRelease{"key-video-release", 98, 501}; + static inline KeySetting keyNavFocus{"key-nav-focus", 110, 508}; + static inline KeySetting keyNavRelease{"key-nav-release", 109, 509}; + static inline Setting keyPipe{"key-pipe-path", ""}; // Debug section static inline Setting protocolDebug{"protocol-debug", 0}; static inline Setting codecLowDelay{"decode-low-delay", true}; static inline Setting codecFast{"decode-fast", false}; - - - - - static void load(const std::string &filename); + static bool load(const std::string &filename); static void print(); + static inline bool isFullscreen() { return screenMode == SCREEN_MODE_FULLSCREEN; }; + static inline bool isHeadless() { return screenMode == SCREEN_MODE_HEADLESS; }; + private: static void trim(std::string &s); };