diff options
author | Garrett Brown <themagnificentmrb@gmail.com> | 2018-06-06 23:00:29 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-06 23:00:29 -0700 |
commit | 95da876587c466691eb9fc31b6bfb0c298a8dd8e (patch) | |
tree | 17bd98447f2fa9940fc8cfe2c0c27908732afa78 | |
parent | 2ac986a3da535e10ff5bbdf8fce9130a3f23b259 (diff) | |
parent | 0f7960bf886c5edfbb8feb6b526b193a02dac013 (diff) |
Merge pull request #13976 from garbear/game-api-streams
RetroPlayer: Add stream abstraction
45 files changed, 1994 insertions, 708 deletions
diff --git a/cmake/treedata/common/games.txt b/cmake/treedata/common/games.txt index 7b63f6a538..4e7466c4a9 100644 --- a/cmake/treedata/common/games.txt +++ b/cmake/treedata/common/games.txt @@ -3,6 +3,7 @@ xbmc/games/addons games/addons xbmc/games/addons/input games/addons/input xbmc/games/addons/playback games/addons/playback xbmc/games/addons/savestates games/addons/savestates +xbmc/games/addons/streams games/addons/streams xbmc/games/controllers games/controllers xbmc/games/controllers/dialogs games/controllers/dialogs xbmc/games/controllers/guicontrols games/controllers/guicontrols diff --git a/cmake/treedata/common/retroplayer.txt b/cmake/treedata/common/retroplayer.txt index 64db57fcf7..50ecebcda5 100644 --- a/cmake/treedata/common/retroplayer.txt +++ b/cmake/treedata/common/retroplayer.txt @@ -1,4 +1,5 @@ xbmc/cores/RetroPlayer cores/RetroPlayer +xbmc/cores/RetroPlayer/audio cores/RetroPlayer/audio xbmc/cores/RetroPlayer/buffers cores/RetroPlayer/buffers xbmc/cores/RetroPlayer/buffers/video cores/RetroPlayer/buffers/video xbmc/cores/RetroPlayer/guibridge cores/RetroPlayer/guibridge @@ -8,3 +9,4 @@ xbmc/cores/RetroPlayer/process cores/RetroPlaye xbmc/cores/RetroPlayer/rendering cores/RetroPlayer/rendering xbmc/cores/RetroPlayer/rendering/VideoRenderers cores/RetroPlayer/rendering/VideoRenderers xbmc/cores/RetroPlayer/rendering/VideoShaders cores/RetroPlayer/rendering/VideoShaders +xbmc/cores/RetroPlayer/streams cores/RetroPlayer/streams diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h index b0639e3771..298456ad85 100644 --- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h @@ -69,13 +69,13 @@ GAME_ERROR LoadStandalone(void); GAME_ERROR UnloadGame(void); /*! - * \brief Get information about the loaded game + * \brief Get timing information about the loaded game * * \param info The info structure to fill * * \return the error, or GAME_ERROR_NO_ERROR if info was filled */ -GAME_ERROR GetGameInfo(game_system_av_info* info); +GAME_ERROR GetGameTiming(game_system_timing* timing_info); /*! * \brief Get region of the loaded game @@ -311,7 +311,7 @@ void __declspec(dllexport) get_addon(void* ptr) pClient->toAddon.LoadGameSpecial = LoadGameSpecial; pClient->toAddon.LoadStandalone = LoadStandalone; pClient->toAddon.UnloadGame = UnloadGame; - pClient->toAddon.GetGameInfo = GetGameInfo; + pClient->toAddon.GetGameTiming = GetGameTiming; pClient->toAddon.GetRegion = GetRegion; pClient->toAddon.RequiresGameLoop = RequiresGameLoop; pClient->toAddon.RunFrame = RunFrame; diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h index 758b0fae31..6c3a312a9f 100644 --- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h @@ -62,6 +62,8 @@ extern "C" { #endif +/// @name Add-on types +///{ /*! Game add-on error codes */ typedef enum GAME_ERROR { @@ -74,50 +76,16 @@ typedef enum GAME_ERROR GAME_ERROR_NOT_LOADED, // no game is loaded GAME_ERROR_RESTRICTED, // game requires restricted resources } GAME_ERROR; +///} -typedef enum GAME_STREAM_TYPE -{ - GAME_STREAM_UNKNOWN, - GAME_STREAM_AUDIO, - GAME_STREAM_VIDEO, -} GAME_STREAM_TYPE; - -typedef enum GAME_PIXEL_FORMAT -{ - GAME_PIXEL_FORMAT_UNKNOWN, - GAME_PIXEL_FORMAT_YUV420P, - GAME_PIXEL_FORMAT_0RGB8888, - GAME_PIXEL_FORMAT_RGB565, - GAME_PIXEL_FORMAT_0RGB1555, -} GAME_PIXEL_FORMAT; - -typedef enum GAME_VIDEO_CODEC -{ - GAME_VIDEO_CODEC_UNKNOWN, - GAME_VIDEO_CODEC_H264, - GAME_VIDEO_CODEC_THEORA, -} GAME_VIDEO_CODEC; - -typedef enum GAME_VIDEO_ROTATION // Counter-clockwise -{ - GAME_VIDEO_ROTATION_0, - GAME_VIDEO_ROTATION_90, - GAME_VIDEO_ROTATION_180, - GAME_VIDEO_ROTATION_270, -} GAME_VIDEO_ROTATION; - +/// @name Audio stream +///{ typedef enum GAME_PCM_FORMAT { GAME_PCM_FORMAT_UNKNOWN, GAME_PCM_FORMAT_S16NE, } GAME_PCM_FORMAT; -typedef enum GAME_AUDIO_CODEC -{ - GAME_AUDIO_CODEC_UNKNOWN, - GAME_AUDIO_CODEC_OPUS, -} GAME_AUDIO_CODEC; - typedef enum GAME_AUDIO_CHANNEL { GAME_CH_NULL, // Channel list terminator @@ -143,50 +111,234 @@ typedef enum GAME_AUDIO_CHANNEL GAME_CH_BROC, } GAME_AUDIO_CHANNEL; -// TODO -typedef enum GAME_HW_FRAME_BUFFER +typedef struct game_stream_audio_properties +{ + GAME_PCM_FORMAT format; + const GAME_AUDIO_CHANNEL* channel_map; +} ATTRIBUTE_PACKED game_stream_audio_properties; + +typedef struct game_stream_audio_packet { - GAME_HW_FRAME_BUFFER_VALID, // Pass this to game_video_refresh if rendering to hardware - GAME_HW_FRAME_BUFFER_DUPLICATE, // Passing NULL to game_video_refresh is still a frame dupe as normal - GAME_HW_FRAME_BUFFER_RENDER, -} GAME_HW_FRAME_BUFFER; + const uint8_t *data; + size_t size; +} ATTRIBUTE_PACKED game_stream_audio_packet; +///} +/// @name Video stream +///{ +typedef enum GAME_PIXEL_FORMAT +{ + GAME_PIXEL_FORMAT_UNKNOWN, + GAME_PIXEL_FORMAT_0RGB8888, + GAME_PIXEL_FORMAT_RGB565, + GAME_PIXEL_FORMAT_0RGB1555, +} GAME_PIXEL_FORMAT; + +typedef enum GAME_VIDEO_ROTATION +{ + GAME_VIDEO_ROTATION_0, + GAME_VIDEO_ROTATION_90_CCW, + GAME_VIDEO_ROTATION_180_CCW, + GAME_VIDEO_ROTATION_270_CCW, +} GAME_VIDEO_ROTATION; + +typedef struct game_stream_video_properties +{ + GAME_PIXEL_FORMAT format; + unsigned int nominal_width; + unsigned int nominal_height; + unsigned int max_width; + unsigned int max_height; + float aspect_ratio; // If aspect_ratio is <= 0.0, an aspect ratio of nominal_width / nominal_height is assumed +} ATTRIBUTE_PACKED game_stream_video_properties; + +typedef struct game_stream_video_packet +{ + unsigned int width; + unsigned int height; + GAME_VIDEO_ROTATION rotation; + const uint8_t *data; + size_t size; +} ATTRIBUTE_PACKED game_stream_video_packet; +///} + +/// @name Hardware framebuffer stream +///{ typedef enum GAME_HW_CONTEXT_TYPE { GAME_HW_CONTEXT_NONE, - GAME_HW_CONTEXT_OPENGL, // OpenGL 2.x. Latest version available before 3.x+. Driver can choose to use latest compatibility context - GAME_HW_CONTEXT_OPENGLES2, // GLES 2.0 - GAME_HW_CONTEXT_OPENGL_CORE, // Modern desktop core GL context. Use major/minor fields to set GL version - GAME_HW_CONTEXT_OPENGLES3, // GLES 3.0 + + // OpenGL 2.x. Driver can choose to use latest compatibility context + GAME_HW_CONTEXT_OPENGL, + + // OpenGL ES 2.0 + GAME_HW_CONTEXT_OPENGLES2, + + // Modern desktop core GL context. Use major/minor fields to set GL version + GAME_HW_CONTEXT_OPENGL_CORE, + + // OpenGL ES 3.0 + GAME_HW_CONTEXT_OPENGLES3, + + // OpenGL ES 3.1+. Set major/minor fields. + GAME_HW_CONTEXT_OPENGLES_VERSION, + + // Vulkan + GAME_HW_CONTEXT_VULKAN } GAME_HW_CONTEXT_TYPE; -typedef enum GAME_INPUT_EVENT_SOURCE +typedef struct game_stream_hw_framebuffer_properties { - GAME_INPUT_EVENT_DIGITAL_BUTTON, - GAME_INPUT_EVENT_ANALOG_BUTTON, - GAME_INPUT_EVENT_AXIS, - GAME_INPUT_EVENT_ANALOG_STICK, - GAME_INPUT_EVENT_ACCELEROMETER, - GAME_INPUT_EVENT_KEY, - GAME_INPUT_EVENT_RELATIVE_POINTER, - GAME_INPUT_EVENT_ABSOLUTE_POINTER, - GAME_INPUT_EVENT_MOTOR, -} GAME_INPUT_EVENT_SOURCE; + /*! + * The API to use. + */ + GAME_HW_CONTEXT_TYPE context_type; -typedef enum GAME_KEY_MOD + /*! + * Set if render buffers should have depth component attached. + * + * TODO: Obsolete + */ + bool depth; + + /*! + * Set if stencil buffers should be attached. If depth and stencil are true, + * a packed 24/8 buffer will be added. Only attaching stencil is invalid and + * will be ignored. + * + * TODO: Obsolete. + */ + bool stencil; + + /*! + * Use conventional bottom-left origin convention. If false, standard top-left + * origin semantics are used. + * + * TODO: Move to GL specific interface + */ + bool bottom_left_origin; + + /*! + * Major version number for core GL context or GLES 3.1+. + */ + unsigned int version_major; + + /*! + * Minor version number for core GL context or GLES 3.1+. + */ + unsigned int version_minor; + + /*! + * If this is true, the frontend will go very far to avoid resetting context + * in scenarios like toggling fullscreen, etc. + * + * TODO: Obsolete? Maybe frontend should just always assume this... + * + * The reset callback might still be called in extreme situations such as if + * the context is lost beyond recovery. + * + * For optimal stability, set this to false, and allow context to be reset at + * any time. + */ + bool cache_context; + + /*! + * Creates a debug context. + */ + bool debug_context; +} ATTRIBUTE_PACKED game_stream_hw_framebuffer_properties; + +typedef struct game_stream_hw_framebuffer_buffer { - GAME_KEY_MOD_NONE = 0x0000, + uintptr_t framebuffer; +} ATTRIBUTE_PACKED game_stream_hw_framebuffer_buffer; - GAME_KEY_MOD_SHIFT = 0x0001, - GAME_KEY_MOD_CTRL = 0x0002, - GAME_KEY_MOD_ALT = 0x0004, - GAME_KEY_MOD_META = 0x0008, - GAME_KEY_MOD_SUPER = 0x0010, +typedef struct game_stream_hw_framebuffer_packet +{ + uintptr_t framebuffer; +} ATTRIBUTE_PACKED game_stream_hw_framebuffer_packet; - GAME_KEY_MOD_NUMLOCK = 0x0100, - GAME_KEY_MOD_CAPSLOCK = 0x0200, - GAME_KEY_MOD_SCROLLOCK = 0x0400, -} GAME_KEY_MOD; +typedef void (*game_proc_address_t)(void); +///} + +/// @name Software framebuffer stream +///{ +typedef game_stream_video_properties game_stream_sw_framebuffer_properties; + +typedef struct game_stream_sw_framebuffer_buffer +{ + GAME_PIXEL_FORMAT format; + uint8_t *data; + size_t size; +} ATTRIBUTE_PACKED game_stream_sw_framebuffer_buffer; + +typedef game_stream_video_packet game_stream_sw_framebuffer_packet; +///} + +/// @name Stream types +///{ +typedef enum GAME_STREAM_TYPE +{ + GAME_STREAM_UNKNOWN, + GAME_STREAM_AUDIO, + GAME_STREAM_VIDEO, + GAME_STREAM_HW_FRAMEBUFFER, + GAME_STREAM_SW_FRAMEBUFFER, +} GAME_STREAM_TYPE; + +/*! + * \brief Immutable stream metadata + * + * This metadata is provided when the stream is opened. If any stream + * properties change, a new stream must be opened. + */ +typedef struct game_stream_properties +{ + GAME_STREAM_TYPE type; + union + { + game_stream_audio_properties audio; + game_stream_video_properties video; + game_stream_hw_framebuffer_properties hw_framebuffer; + game_stream_sw_framebuffer_properties sw_framebuffer; + }; +} ATTRIBUTE_PACKED game_stream_properties; + +/*! + * \brief Stream buffers for hardware rendering and zero-copy support + */ +typedef struct game_stream_buffer +{ + GAME_STREAM_TYPE type; + union + { + game_stream_hw_framebuffer_buffer hw_framebuffer; + game_stream_sw_framebuffer_buffer sw_framebuffer; + }; +} ATTRIBUTE_PACKED game_stream_buffer; + +/*! + * \brief Stream packet and ephemeral metadata + * + * This packet contains stream data and accompanying metadata. The metadata + * is ephemeral, meaning it only applies to the current packet and can change + * from packet to packet in the same stream. + */ +typedef struct game_stream_packet +{ + GAME_STREAM_TYPE type; + union + { + game_stream_audio_packet audio; + game_stream_video_packet video; + game_stream_hw_framebuffer_packet hw_framebuffer; + game_stream_sw_framebuffer_packet sw_framebuffer; + }; +} ATTRIBUTE_PACKED game_stream_packet; +///} + +/// @name Game types +///{ /*! Returned from game_get_region() */ typedef enum GAME_REGION @@ -262,14 +414,38 @@ typedef enum GAME_SIMD GAME_SIMD_AVX2 = (1 << 12), GAME_SIMD_VFPU = (1 << 13), } GAME_SIMD; +///} -typedef enum GAME_ROTATION +/// @name Input types +///{ + +typedef enum GAME_INPUT_EVENT_SOURCE { - GAME_ROTATION_0_CW, - GAME_ROTATION_90_CW, - GAME_ROTATION_180_CW, - GAME_ROTATION_270_CW, -} GAME_ROTATION; + GAME_INPUT_EVENT_DIGITAL_BUTTON, + GAME_INPUT_EVENT_ANALOG_BUTTON, + GAME_INPUT_EVENT_AXIS, + GAME_INPUT_EVENT_ANALOG_STICK, + GAME_INPUT_EVENT_ACCELEROMETER, + GAME_INPUT_EVENT_KEY, + GAME_INPUT_EVENT_RELATIVE_POINTER, + GAME_INPUT_EVENT_ABSOLUTE_POINTER, + GAME_INPUT_EVENT_MOTOR, +} GAME_INPUT_EVENT_SOURCE; + +typedef enum GAME_KEY_MOD +{ + GAME_KEY_MOD_NONE = 0x0000, + + GAME_KEY_MOD_SHIFT = 0x0001, + GAME_KEY_MOD_CTRL = 0x0002, + GAME_KEY_MOD_ALT = 0x0004, + GAME_KEY_MOD_META = 0x0008, + GAME_KEY_MOD_SUPER = 0x0010, + + GAME_KEY_MOD_NUMLOCK = 0x0100, + GAME_KEY_MOD_CAPSLOCK = 0x0200, + GAME_KEY_MOD_SCROLLOCK = 0x0400, +} GAME_KEY_MOD; /*! * \brief Type of port on the virtual game console @@ -418,46 +594,16 @@ typedef struct game_input_event struct game_motor_event motor; }; } ATTRIBUTE_PACKED game_input_event; +///} -struct game_geometry -{ - unsigned base_width; // Nominal video width of game - unsigned base_height; // Nominal video height of game - unsigned max_width; // Maximum possible width of game - unsigned max_height; // Maximum possible height of game - float aspect_ratio; // Nominal aspect ratio of game. If aspect_ratio is <= 0.0, - // an aspect ratio of base_width / base_height is assumed. - // A frontend could override this setting if desired. -}; - +/// @name Environment types +///{ struct game_system_timing { double fps; // FPS of video content. double sample_rate; // Sampling rate of audio. }; - -struct game_system_av_info -{ - struct game_geometry geometry; - struct game_system_timing timing; -}; - -typedef void (*game_proc_address_t)(void); - -struct game_hw_info -{ - GAME_HW_CONTEXT_TYPE context_type; // Which API to use. Set by game client - bool depth; // Set if render buffers should have depth component attached - bool stencil; // Set if stencil buffers should be attached - // If depth and stencil are true, a packed 24/8 buffer will be added. Only attaching stencil is invalid and will be ignored - bool bottom_left_origin; // Use conventional bottom-left origin convention. Is false, standard top-left origin semantics are used - unsigned version_major; // Major version number for core GL context - unsigned version_minor; // Minor version number for core GL context - bool cache_context; // If this is true, the frontend will go very far to avoid resetting context in scenarios like toggling fullscreen, etc. - // The reset callback might still be called in extreme situations such as if the context is lost beyond recovery - // For optimal stability, set this to false, and allow context to be reset at any time - bool debug_context; // Creates a debug context -}; +///} /*! Properties passed to the ADDON_Create() method of a game client */ typedef struct AddonProps_Game @@ -512,7 +658,7 @@ typedef struct AddonProps_Game } AddonProps_Game; typedef AddonProps_Game game_client_properties; - + /*! Structure to transfer the methods from kodi_game_dll.h to Kodi */ typedef struct AddonToKodiFuncTable_Game @@ -520,16 +666,12 @@ typedef struct AddonToKodiFuncTable_Game KODI_HANDLE kodiInstance; void (*CloseGame)(void* kodiInstance); - int (*OpenPixelStream)(void* kodiInstance, GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation); - int (*OpenVideoStream)(void* kodiInstance, GAME_VIDEO_CODEC codec); - int (*OpenPCMStream)(void* kodiInstance, GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map); - int(*OpenAudioStream)(void* kodiInstance, GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map); - void (*AddStreamData)(void* kodiInstance, GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size); - void (*CloseStream)(void* kodiInstance, GAME_STREAM_TYPE stream); - void (*EnableHardwareRendering)(void* kodiInstance, const game_hw_info* hw_info); - uintptr_t (*HwGetCurrentFramebuffer)(void* kodiInstance); + void* (*OpenStream)(void*, const game_stream_properties*); + bool (*GetStreamBuffer)(void*, void*, unsigned int, unsigned int, game_stream_buffer*); + void (*AddStreamData)(void*, void*, const game_stream_packet*); + void (*ReleaseStreamBuffer)(void*, void*, game_stream_buffer*); + void (*CloseStream)(void*, void*); game_proc_address_t (*HwGetProcAddress)(void* kodiInstance, const char* symbol); - void (*RenderFrame)(void* kodiInstance); bool (*InputEvent)(void* kodiInstance, const game_input_event* event); } AddonToKodiFuncTable_Game; @@ -540,7 +682,7 @@ typedef struct KodiToAddonFuncTable_Game GAME_ERROR (__cdecl* LoadGameSpecial)(SPECIAL_GAME_TYPE, const char**, size_t); GAME_ERROR (__cdecl* LoadStandalone)(void); GAME_ERROR (__cdecl* UnloadGame)(void); - GAME_ERROR (__cdecl* GetGameInfo)(game_system_av_info*); + GAME_ERROR (__cdecl* GetGameTiming)(game_system_timing*); GAME_REGION (__cdecl* GetRegion)(void); bool (__cdecl* RequiresGameLoop)(void); GAME_ERROR (__cdecl* RunFrame)(void); diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h index 2774afd567..b6224f39a3 100644 --- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h @@ -65,72 +65,58 @@ public: */ void CloseGame(void) { - return m_callbacks->toKodi.CloseGame(m_callbacks->toKodi.kodiInstance); + m_callbacks->toKodi.CloseGame(m_callbacks->toKodi.kodiInstance); } /*! - * \brief Create a video stream for pixel data + * \brief Create a stream for gameplay data * - * \param format The type of pixel data accepted by this stream - * \param width The frame width - * \param height The frame height - * \param rotation The rotation (counter-clockwise) of the video frames + * \param properties The stream properties * - * \return 0 on success or -1 if a video stream is already created + * \return A stream handle, or NULL on failure */ - bool OpenPixelStream(GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation) + void* OpenStream(const game_stream_properties &properties) { - return m_callbacks->toKodi.OpenPixelStream(m_callbacks->toKodi.kodiInstance, format, width, height, rotation) == 0; + return m_callbacks->toKodi.OpenStream(m_callbacks->toKodi.kodiInstance, &properties); } /*! - * \brief Create a video stream for encoded video data + * \brief Get a buffer for zero-copy stream data * - * \param codec The video format accepted by this stream + * \param stream The stream handle + * \param width The framebuffer width, or 0 for no width specified + * \param height The framebuffer height, or 0 for no height specified + * \param[out] buffer The buffer, or unmodified if false is returned * - * \return 0 on success or -1 if a video stream is already created + * If this returns true, buffer must be freed using ReleaseStreamBuffer(). + * + * \return True if buffer was set, false otherwise */ - bool OpenVideoStream(GAME_VIDEO_CODEC codec) + bool GetStreamBuffer(void *stream, unsigned int width, unsigned int height, game_stream_buffer &buffer) { - return m_callbacks->toKodi.OpenVideoStream(m_callbacks->toKodi.kodiInstance, codec) == 0; + return m_callbacks->toKodi.GetStreamBuffer(m_callbacks->toKodi.kodiInstance, stream, width, height, &buffer); } /*! - * \brief Create an audio stream for PCM audio data - * - * \param format The type of audio data accepted by this stream - * \param channel_map The channel layout terminated by GAME_CH_NULL + * \brief Add a data packet to a stream * - * \return 0 on success or -1 if an audio stream is already created + * \param stream The target stream + * \param packet The data packet */ - bool OpenPCMStream(GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map) - { - return m_callbacks->toKodi.OpenPCMStream(m_callbacks->toKodi.kodiInstance, format, channel_map) == 0; - } - - /*! - * \brief Create an audio stream for encoded audio data - * - * \param codec The audio format accepted by this stream - * \param channel_map The channel layout terminated by GAME_CH_NULL - * - * \return 0 on success or -1 if an audio stream is already created - */ - bool OpenAudioStream(GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map) + void AddStreamData(void *stream, const game_stream_packet &packet) { - return m_callbacks->toKodi.OpenAudioStream(m_callbacks->toKodi.kodiInstance, codec, channel_map) == 0; + m_callbacks->toKodi.AddStreamData(m_callbacks->toKodi.kodiInstance, stream, &packet); } /*! - * \brief Add a data packet to an audio or video stream + * \brief Free an allocated buffer * - * \param stream The target stream - * \param data The data packet - * \param size The size of the data + * \param stream The stream handle + * \param buffer The buffer returned from GetStreamBuffer() */ - void AddStreamData(GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size) + void ReleaseStreamBuffer(void *stream, game_stream_buffer &buffer) { - m_callbacks->toKodi.AddStreamData(m_callbacks->toKodi.kodiInstance, stream, data, size); + m_callbacks->toKodi.ReleaseStreamBuffer(m_callbacks->toKodi.kodiInstance, stream, &buffer); } /*! @@ -138,7 +124,7 @@ public: * * \param stream The stream to close */ - void CloseStream(GAME_STREAM_TYPE stream) + void CloseStream(void *stream) { m_callbacks->toKodi.CloseStream(m_callbacks->toKodi.kodiInstance, stream); } @@ -146,26 +132,6 @@ public: // -- Hardware rendering callbacks ------------------------------------------- /*! - * \brief Enable hardware rendering - * - * \param hw_info A struct of properties for the hardware rendering system - */ - void EnableHardwareRendering(const struct game_hw_info* hw_info) - { - return m_callbacks->toKodi.EnableHardwareRendering(m_callbacks->toKodi.kodiInstance, hw_info); - } - - /*! - * \brief Get the framebuffer for rendering - * - * \return The framebuffer - */ - uintptr_t HwGetCurrentFramebuffer(void) - { - return m_callbacks->toKodi.HwGetCurrentFramebuffer(m_callbacks->toKodi.kodiInstance); - } - - /*! * \brief Get a symbol from the hardware context * * \param symbol The symbol's name @@ -177,14 +143,6 @@ public: return m_callbacks->toKodi.HwGetProcAddress(m_callbacks->toKodi.kodiInstance, sym); } - /*! - * \brief Called when a frame is being rendered - */ - void RenderFrame() - { - return m_callbacks->toKodi.RenderFrame(m_callbacks->toKodi.kodiInstance); - } - // --- Input callbacks ------------------------------------------------------- /*! diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h index 3c42dbaf0a..042b1932af 100644 --- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h @@ -86,8 +86,8 @@ #define ADDON_INSTANCE_VERSION_AUDIOENCODER_XML_ID "kodi.binary.instance.audioencoder" #define ADDON_INSTANCE_VERSION_AUDIOENCODER_DEPENDS "addon-instance/AudioEncoder.h" -#define ADDON_INSTANCE_VERSION_GAME "1.0.36" -#define ADDON_INSTANCE_VERSION_GAME_MIN "1.0.36" +#define ADDON_INSTANCE_VERSION_GAME "1.0.38" +#define ADDON_INSTANCE_VERSION_GAME_MIN "1.0.38" #define ADDON_INSTANCE_VERSION_GAME_XML_ID "kodi.binary.instance.game" #define ADDON_INSTANCE_VERSION_GAME_DEPENDS "kodi_game_dll.h" \ "kodi_game_types.h" \ diff --git a/xbmc/cores/RetroPlayer/CMakeLists.txt b/xbmc/cores/RetroPlayer/CMakeLists.txt index 96feb17dab..3e78c544dc 100644 --- a/xbmc/cores/RetroPlayer/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/CMakeLists.txt @@ -1,18 +1,14 @@ set(SOURCES RetroPlayer.cpp - RetroPlayerAudio.cpp RetroPlayerAutoSave.cpp RetroPlayerInput.cpp RetroPlayerUtils.cpp - RetroPlayerVideo.cpp ) set(HEADERS RetroPlayer.h - RetroPlayerAudio.h RetroPlayerAutoSave.h RetroPlayerInput.h RetroPlayerTypes.h RetroPlayerUtils.h - RetroPlayerVideo.h ) core_add_library(retroplayer) diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.cpp b/xbmc/cores/RetroPlayer/RetroPlayer.cpp index 31dc97fafa..ad26263636 100644 --- a/xbmc/cores/RetroPlayer/RetroPlayer.cpp +++ b/xbmc/cores/RetroPlayer/RetroPlayer.cpp @@ -19,15 +19,14 @@ */ #include "RetroPlayer.h" -#include "RetroPlayerAudio.h" #include "RetroPlayerAutoSave.h" #include "RetroPlayerInput.h" -#include "RetroPlayerVideo.h" #include "addons/AddonManager.h" #include "cores/DataCacheCore.h" #include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" #include "cores/RetroPlayer/process/RPProcessInfo.h" #include "cores/RetroPlayer/rendering/RPRenderManager.h" +#include "cores/RetroPlayer/streams/RPStreamManager.h" #include "dialogs/GUIDialogYesNo.h" #include "filesystem/File.h" #include "games/addons/input/GameClientInput.h" @@ -125,20 +124,20 @@ bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options m_gameClient = std::static_pointer_cast<CGameClient>(addon); if (m_gameClient->Initialize()) { - m_audio.reset(new CRetroPlayerAudio(*m_processInfo)); - m_video.reset(new CRetroPlayerVideo(*m_renderManager, *m_processInfo)); + m_streamManager.reset(new CRPStreamManager(*m_renderManager, *m_processInfo)); + m_input.reset(new CRetroPlayerInput(CServiceBroker::GetPeripherals())); if (!bStandalone) { std::string redactedPath = CURL::GetRedacted(fileCopy.GetPath()); CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening: %s", redactedPath.c_str()); - bSuccess = m_gameClient->OpenFile(fileCopy, m_audio.get(), m_video.get(), m_input.get()); + bSuccess = m_gameClient->OpenFile(fileCopy, *m_streamManager, m_input.get()); } else { CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening standalone"); - bSuccess = m_gameClient->OpenStandalone(m_audio.get(), m_video.get(), m_input.get()); + bSuccess = m_gameClient->OpenStandalone(*m_streamManager, m_input.get()); } if (bSuccess) @@ -196,8 +195,7 @@ bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options { m_gameClient.reset(); m_input.reset(); - m_audio.reset(); - m_video.reset(); + m_streamManager.reset(); } return bSuccess; @@ -227,8 +225,7 @@ bool CRetroPlayer::CloseFile(bool reopen /* = false */) } m_input.reset(); - m_audio.reset(); - m_video.reset(); + m_streamManager.reset(); m_renderManager.reset(); m_processInfo.reset(); @@ -336,8 +333,8 @@ float CRetroPlayer::GetCachePercentage() void CRetroPlayer::SetMute(bool bOnOff) { - if (m_audio) - m_audio->Enable(!bOnOff); + if (m_streamManager) + m_streamManager->EnableAudio(!bOnOff); } void CRetroPlayer::SeekTime(int64_t iTime /* = 0 */) @@ -552,7 +549,7 @@ void CRetroPlayer::SetSpeedInternal(double speed) void CRetroPlayer::OnSpeedChange(double newSpeed) { - m_audio->Enable(newSpeed == 1.0); + m_streamManager->EnableAudio(newSpeed == 1.0); m_input->SetSpeed(newSpeed); m_renderManager->SetSpeed(newSpeed); m_processInfo->SetSpeed(static_cast<float>(newSpeed)); diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.h b/xbmc/cores/RetroPlayer/RetroPlayer.h index 73d3352bff..1f3c6c1389 100644 --- a/xbmc/cores/RetroPlayer/RetroPlayer.h +++ b/xbmc/cores/RetroPlayer/RetroPlayer.h @@ -35,12 +35,11 @@ namespace GAME namespace RETRO { - class CRetroPlayerAudio; class CRetroPlayerAutoSave; class CRetroPlayerInput; - class CRetroPlayerVideo; class CRPProcessInfo; class CRPRenderManager; + class CRPStreamManager; class CRetroPlayer : public IPlayer, public IRenderLoop { @@ -169,8 +168,7 @@ namespace RETRO double m_priorSpeed = 0.0f; // Speed of gameplay before entering OSD std::unique_ptr<CRPProcessInfo> m_processInfo; std::unique_ptr<CRPRenderManager> m_renderManager; - std::unique_ptr<CRetroPlayerAudio> m_audio; - std::unique_ptr<CRetroPlayerVideo> m_video; + std::unique_ptr<CRPStreamManager> m_streamManager; std::unique_ptr<CRetroPlayerInput> m_input; std::unique_ptr<CRetroPlayerAutoSave> m_autoSave; GAME::GameClientPtr m_gameClient; diff --git a/xbmc/cores/RetroPlayer/RetroPlayerVideo.cpp b/xbmc/cores/RetroPlayer/RetroPlayerVideo.cpp deleted file mode 100644 index 34a13e50fa..0000000000 --- a/xbmc/cores/RetroPlayer/RetroPlayerVideo.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2012-2017 Team Kodi - * http://kodi.tv - * - * This Program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This Program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this Program; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/>. - * - */ - -#include "RetroPlayerVideo.h" -#include "cores/RetroPlayer/process/RPProcessInfo.h" -#include "cores/RetroPlayer/rendering/RenderTranslator.h" -#include "cores/RetroPlayer/rendering/RPRenderManager.h" -#include "utils/log.h" - -using namespace KODI; -using namespace RETRO; - -CRetroPlayerVideo::CRetroPlayerVideo(CRPRenderManager& renderManager, CRPProcessInfo& processInfo) : - m_renderManager(renderManager), - m_processInfo(processInfo) -{ - CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Initializing video"); - - m_renderManager.Initialize(); -} - -CRetroPlayerVideo::~CRetroPlayerVideo() -{ - CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Deinitializing video"); - - CloseStream(); - m_renderManager.Deinitialize(); -} - -bool CRetroPlayerVideo::OpenPixelStream(AVPixelFormat pixfmt, unsigned int width, unsigned int height, unsigned int orientationDeg) -{ - CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Creating video stream - format %s, %ux%u, %u deg", - CRenderTranslator::TranslatePixelFormat(pixfmt), - width, - height, - orientationDeg); - - m_processInfo.SetVideoPixelFormat(pixfmt); - m_processInfo.SetVideoDimensions(width, height); - - return m_renderManager.Configure(pixfmt, width, height, orientationDeg); -} - -bool CRetroPlayerVideo::OpenEncodedStream(AVCodecID codec) -{ - CLog::Log(LOGERROR, "RetroPlayer[VIDEO]: Encoded video stream not supported"); - - return false; //! @todo -} - -void CRetroPlayerVideo::AddData(const uint8_t* data, size_t size) -{ - m_renderManager.AddFrame(data, size); -} - -void CRetroPlayerVideo::CloseStream() -{ - CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Closing video stream"); - - m_renderManager.Flush(); -} diff --git a/xbmc/cores/RetroPlayer/audio/AudioTranslator.cpp b/xbmc/cores/RetroPlayer/audio/AudioTranslator.cpp new file mode 100644 index 0000000000..e5fcf6207d --- /dev/null +++ b/xbmc/cores/RetroPlayer/audio/AudioTranslator.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "AudioTranslator.h" + +using namespace KODI; +using namespace RETRO; + +AEDataFormat CAudioTranslator::TranslatePCMFormat(PCMFormat format) +{ + switch (format) + { + case PCMFormat::FMT_S16NE: return AE_FMT_S16NE; + default: + break; + } + return AE_FMT_INVALID; +} + +AEChannel CAudioTranslator::TranslateAudioChannel(AudioChannel channel) +{ + switch (channel) + { + case AudioChannel::CH_FL: return AE_CH_FL; + case AudioChannel::CH_FR: return AE_CH_FR; + case AudioChannel::CH_FC: return AE_CH_FC; + case AudioChannel::CH_LFE: return AE_CH_LFE; + case AudioChannel::CH_BL: return AE_CH_BL; + case AudioChannel::CH_BR: return AE_CH_BR; + case AudioChannel::CH_FLOC: return AE_CH_FLOC; + case AudioChannel::CH_FROC: return AE_CH_FROC; + case AudioChannel::CH_BC: return AE_CH_BC; + case AudioChannel::CH_SL: return AE_CH_SL; + case AudioChannel::CH_SR: return AE_CH_SR; + case AudioChannel::CH_TFL: return AE_CH_TFL; + case AudioChannel::CH_TFR: return AE_CH_TFR; + case AudioChannel::CH_TFC: return AE_CH_TFC; + case AudioChannel::CH_TC: return AE_CH_TC; + case AudioChannel::CH_TBL: return AE_CH_TBL; + case AudioChannel::CH_TBR: return AE_CH_TBR; + case AudioChannel::CH_TBC: return AE_CH_TBC; + case AudioChannel::CH_BLOC: return AE_CH_BLOC; + case AudioChannel::CH_BROC: return AE_CH_BROC; + default: + break; + } + return AE_CH_NULL; +} diff --git a/xbmc/cores/RetroPlayer/audio/AudioTranslator.h b/xbmc/cores/RetroPlayer/audio/AudioTranslator.h new file mode 100644 index 0000000000..86f5f1e212 --- /dev/null +++ b/xbmc/cores/RetroPlayer/audio/AudioTranslator.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "cores/RetroPlayer/streams/RetroPlayerStreamTypes.h" +#include "cores/AudioEngine/Utils/AEChannelData.h" + +namespace KODI +{ +namespace RETRO +{ + class CAudioTranslator + { + public: + /*! + * \brief Translate audio PCM format (Game API to AudioEngine). + * \param format The audio PCM format to translate. + * \return Translated audio PCM format. + */ + static AEDataFormat TranslatePCMFormat(PCMFormat format); + + /*! + * \brief Translate audio channels (Game API to AudioEngine). + * \param format The audio channels to translate. + * \return Translated audio channels. + */ + static AEChannel TranslateAudioChannel(AudioChannel channel); + }; +} +} diff --git a/xbmc/cores/RetroPlayer/audio/CMakeLists.txt b/xbmc/cores/RetroPlayer/audio/CMakeLists.txt new file mode 100644 index 0000000000..1cbb113c42 --- /dev/null +++ b/xbmc/cores/RetroPlayer/audio/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES AudioTranslator.cpp +) + +set(HEADERS AudioTranslator.h +) + +core_add_library(rp_audio) diff --git a/xbmc/cores/RetroPlayer/process/RPProcessInfo.h b/xbmc/cores/RetroPlayer/process/RPProcessInfo.h index 89ebdffa4a..d0df1002a0 100644 --- a/xbmc/cores/RetroPlayer/process/RPProcessInfo.h +++ b/xbmc/cores/RetroPlayer/process/RPProcessInfo.h @@ -42,18 +42,46 @@ namespace RETRO class CRPProcessInfo; class IRenderBufferPool; + /*! + * \brief Process info factory + */ using CreateRPProcessControl = CRPProcessInfo* (*)(); + /*! + * \brief Rendering factory + */ class IRendererFactory { public: virtual ~IRendererFactory() = default; + /*! + * \brief Get a description name of the rendering system + */ virtual std::string RenderSystemName() const = 0; + + /*! + * \brief Create a renderer + * + * \param settings The renderer's initial settings + * \param context The rendering context + * \param bufferPool The buffer pool to which buffers are returned + */ virtual CRPBaseRenderer *CreateRenderer(const CRenderSettings &settings, CRenderContext &context, std::shared_ptr<IRenderBufferPool> bufferPool) = 0; + + /*! + * \brief Create buffer pools to manager buffers + * + * \param context The rendering context shared with the buffer pools + * + * \return The buffer pools supported by the rendering system + */ virtual RenderBufferPoolVector CreateBufferPools(CRenderContext &context) = 0; }; + /*! + * \brief Player process info + */ class CRPProcessInfo { public: @@ -63,37 +91,97 @@ namespace RETRO virtual ~CRPProcessInfo(); + /*! + * \brief Get the descriptive name of the platform + * + * \return The name of the platform as set by windowing + */ const std::string &GetPlatformName() const { return m_platformName; } + + /*! + * \brief Get the descriptive name of the rendering system + * + * \param renderBufferPool A pool belonging to the rendering system + * + * \return The name of the rendering system as set by windowing + */ std::string GetRenderSystemName(IRenderBufferPool *renderBufferPool) const; + /*! + * \brief Create a renderer + * + * \param renderBufferPool The buffer pool used to return render buffers + * \param renderSettings The settings for this renderer + * + * \return The renderer, or nullptr on failure + */ CRPBaseRenderer *CreateRenderer(IRenderBufferPool *renderBufferPool, const CRenderSettings &renderSettings); + /*! + * \brief Set data cache + */ void SetDataCache(CDataCacheCore *cache); + + /*! + * \brief Reset data cache info + */ void ResetInfo(); - // rendering info + /// @name Rendering functions + ///{ + + /*! + * \brief Get the context shared by the rendering system + */ CRenderContext &GetRenderContext() { return *m_renderContext; } + + /*! + * \brief Get the buffer manager that owns the buffer pools + */ CRenderBufferManager &GetBufferManager() { return *m_renderBufferManager; } + + /*! + * \brief Check if a buffer pool supports the given scaling method + */ bool HasScalingMethod(ESCALINGMETHOD scalingMethod) const; + + /*! + * \brief Get the default scaling method for this rendering system + */ ESCALINGMETHOD GetDefaultScalingMethod() const { return m_defaultScalingMethod; } + ///} - // player video + /// @name Player video info + ///{ void SetVideoPixelFormat(AVPixelFormat pixFormat); void SetVideoDimensions(int width, int height); void SetVideoFps(float fps); + ///} - // player audio info + /// @name Player audio info + ///{ void SetAudioChannels(const std::string &channels); void SetAudioSampleRate(int sampleRate); void SetAudioBitsPerSample(int bitsPerSample); + ///} - // player states + /// @name Player states + ///{ void SetSpeed(float speed); void SetPlayTimes(time_t start, int64_t current, int64_t min, int64_t max); + ///} protected: + /*! + * \brief Constructor + * + * \param platformName A descriptive name of the platform + */ CRPProcessInfo(std::string platformName); + /*! + * \brief Get all scaling methods available to the rendering system + */ static std::vector<ESCALINGMETHOD> GetScalingMethods(); // Static factories @@ -104,7 +192,7 @@ namespace RETRO // Construction parameters const std::string m_platformName; - // Process info parameters + // Info parameters CDataCacheCore *m_dataCache = nullptr; // Rendering parameters diff --git a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp index 163dece617..88c6188d47 100644 --- a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp +++ b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp @@ -80,18 +80,20 @@ void CRPRenderManager::Deinitialize() m_state = RENDER_STATE::UNCONFIGURED; } -bool CRPRenderManager::Configure(AVPixelFormat format, unsigned int width, unsigned int height, unsigned int orientation) +bool CRPRenderManager::Configure(AVPixelFormat format, unsigned int nominalWidth, unsigned int nominalHeight, unsigned int maxWidth, unsigned int maxHeight) { - m_format = format; - m_width = width; - m_height = height; - m_orientation = orientation; - - CLog::Log(LOGINFO, "RetroPlayer[RENDER]: Configuring format %s, %ux%u, %u deg", + CLog::Log(LOGINFO, "RetroPlayer[RENDER]: Configuring format %s, nominal %ux%u, max %ux%u", CRenderTranslator::TranslatePixelFormat(format), - width, - height, - orientation); + nominalWidth, + nominalHeight, + maxWidth, + maxHeight); + + m_format = format; + m_maxWidth = maxWidth; + m_maxHeight = maxHeight; + m_width = nominalWidth; //! @todo Allow dimension changes + m_height = nominalHeight; //! @todo Allow dimension changes CSingleLock lock(m_stateMutex); @@ -100,10 +102,14 @@ bool CRPRenderManager::Configure(AVPixelFormat format, unsigned int width, unsig return true; } -void CRPRenderManager::AddFrame(const uint8_t* data, size_t size) +void CRPRenderManager::AddFrame(const uint8_t* data, size_t size, unsigned int width, unsigned int height, unsigned int orientationDegCCW) { // Validate parameters - if (data == nullptr || size == 0) + if (data == nullptr || size == 0 || width == 0 || height == 0) + return; + + //! @todo Allow dimension changes + if (width != m_width || height != m_height) return; // Copy frame to buffers with visible renderers @@ -116,7 +122,7 @@ void CRPRenderManager::AddFrame(const uint8_t* data, size_t size) IRenderBuffer *renderBuffer = bufferPool->GetBuffer(size); if (renderBuffer != nullptr) { - CopyFrame(renderBuffer, data, size, m_format); + CopyFrame(renderBuffer, m_format, data, size, width, height); renderBuffers.emplace_back(renderBuffer); } } @@ -373,7 +379,7 @@ std::shared_ptr<CRPBaseRenderer> CRPRenderManager::GetRenderer(IRenderBufferPool m_processInfo.GetRenderSystemName(bufferPool).c_str()); renderer.reset(m_processInfo.CreateRenderer(bufferPool, renderSettings)); - if (renderer && renderer->Configure(m_format, m_width, m_height, m_orientation)) + if (renderer && renderer->Configure(m_format, m_width, m_height)) { // Ensure we have a render buffer for this renderer CreateRenderBuffer(renderer->GetBufferPool()); @@ -442,7 +448,7 @@ void CRPRenderManager::CreateRenderBuffer(IRenderBufferPool *bufferPool) { { CSingleExit exit(m_bufferMutex); - CopyFrame(renderBuffer, cachedFrame.data(), cachedFrame.size(), m_format); + CopyFrame(renderBuffer, m_format, cachedFrame.data(), cachedFrame.size(), m_width, m_height); } m_renderBuffers.emplace_back(renderBuffer); } @@ -474,14 +480,14 @@ void CRPRenderManager::UpdateResolution() */ } -void CRPRenderManager::CopyFrame(IRenderBuffer *renderBuffer, const uint8_t *data, size_t size, AVPixelFormat format) +void CRPRenderManager::CopyFrame(IRenderBuffer *renderBuffer, AVPixelFormat format, const uint8_t *data, size_t size, unsigned int width, unsigned int height) { const uint8_t *source = data; uint8_t *target = renderBuffer->GetMemory(); if (target != nullptr) { - const unsigned int sourceStride = static_cast<unsigned int>(size / m_height); + const unsigned int sourceStride = static_cast<unsigned int>(size / height); const unsigned int targetStride = static_cast<unsigned int>(renderBuffer->GetFrameSize() / renderBuffer->GetHeight()); if (m_format == renderBuffer->GetFormat()) @@ -490,10 +496,10 @@ void CRPRenderManager::CopyFrame(IRenderBuffer *renderBuffer, const uint8_t *dat std::memcpy(target, source, size); else { - const unsigned int widthBytes = CRenderTranslator::TranslateWidthToBytes(m_width, m_format); + const unsigned int widthBytes = CRenderTranslator::TranslateWidthToBytes(width, m_format); if (widthBytes > 0) { - for (unsigned int i = 0; i < m_height; i++) + for (unsigned int i = 0; i < height; i++) std::memcpy(target + targetStride * i, source + sourceStride * i, widthBytes); } } @@ -502,7 +508,7 @@ void CRPRenderManager::CopyFrame(IRenderBuffer *renderBuffer, const uint8_t *dat { SwsContext *&scalerContext = m_scalers[renderBuffer->GetFormat()]; scalerContext = sws_getCachedContext(scalerContext, - m_width, m_height, format, + width, height, format, renderBuffer->GetWidth(), renderBuffer->GetHeight(), renderBuffer->GetFormat(), SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); @@ -513,7 +519,7 @@ void CRPRenderManager::CopyFrame(IRenderBuffer *renderBuffer, const uint8_t *dat uint8_t *dst[] = { target, nullptr, nullptr, nullptr }; int dstStride[] = { static_cast<int>(targetStride), 0, 0, 0 }; - sws_scale(scalerContext, src, srcStride, 0, m_height, dst, dstStride); + sws_scale(scalerContext, src, srcStride, 0, height, dst, dstStride); } } } diff --git a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.h b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.h index 0dffc27365..71769699e5 100644 --- a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.h +++ b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.h @@ -86,8 +86,8 @@ namespace RETRO CGUIRenderTargetFactory *GetGUIRenderTargetFactory() { return m_renderControlFactory.get(); } // Functions called from game loop - bool Configure(AVPixelFormat format, unsigned int width, unsigned int height, unsigned int orientation); - void AddFrame(const uint8_t* data, size_t size); + bool Configure(AVPixelFormat format, unsigned int nominalWidth, unsigned int nominalHeight, unsigned int maxWidth, unsigned int maxHeight); + void AddFrame(const uint8_t* data, size_t size, unsigned int width, unsigned int height, unsigned int orientationDegCW); // Functions called from the player void SetSpeed(double speed); @@ -142,7 +142,7 @@ namespace RETRO /*! * \brief Utility function to copy a frame and rescale pixels if necessary */ - void CopyFrame(IRenderBuffer *renderBuffer, const uint8_t *data, size_t size, AVPixelFormat format); + void CopyFrame(IRenderBuffer *renderBuffer, AVPixelFormat format, const uint8_t *data, size_t size, unsigned int width, unsigned int height); CRenderVideoSettings GetEffectiveSettings(const IGUIRenderSettings *settings) const; @@ -152,9 +152,10 @@ namespace RETRO // Stream properties AVPixelFormat m_format = AV_PIX_FMT_NONE; - unsigned int m_width = 0; - unsigned int m_height = 0; - unsigned int m_orientation = 0; // Degrees counter-clockwise + unsigned int m_maxWidth = 0; + unsigned int m_maxHeight = 0; + unsigned int m_width = 0; //! @todo Remove me when dimension changing is implemented + unsigned int m_height = 0; //! @todo Remove me when dimension changing is implemented // Render properties enum class RENDER_STATE diff --git a/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h index f0249e7885..02e37d96a1 100644 --- a/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h +++ b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h @@ -25,6 +25,9 @@ namespace KODI { namespace RETRO { + /*! + * \brief Video settings provided by the rendering system + */ class CRenderVideoSettings { public: diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp index bb5fa5f94b..2bf2130d64 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.cpp @@ -68,13 +68,13 @@ bool CRPBaseRenderer::IsCompatible(const CRenderVideoSettings &settings) const return true; } -bool CRPBaseRenderer::Configure(AVPixelFormat format, unsigned int width, unsigned int height, unsigned int orientation) +bool CRPBaseRenderer::Configure(AVPixelFormat format, unsigned int width, unsigned int height) { m_format = format; m_sourceWidth = width; m_sourceHeight = height; m_sourceFrameRatio = static_cast<float>(width) / static_cast<float>(height); - m_renderOrientation = orientation; + m_renderOrientation = 0; //! @todo if (!m_bufferPool->IsConfigured()) { diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h index b7ae3baef0..7392fd779a 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPBaseRenderer.h @@ -49,7 +49,7 @@ namespace RETRO IRenderBufferPool *GetBufferPool() { return m_bufferPool.get(); } // Player functions - bool Configure(AVPixelFormat format, unsigned int width, unsigned int height, unsigned int orientation); + bool Configure(AVPixelFormat format, unsigned int width, unsigned int height); void FrameMove(); /*! * \brief Performs whatever necessary before rendering the frame diff --git a/xbmc/cores/RetroPlayer/streams/CMakeLists.txt b/xbmc/cores/RetroPlayer/streams/CMakeLists.txt new file mode 100644 index 0000000000..b5acbc9172 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SOURCES RetroPlayerAudio.cpp + RetroPlayerStreamTypes.cpp + RetroPlayerVideo.cpp + RPStreamManager.cpp +) + +set(HEADERS IRetroPlayerStream.h + IStreamManager.h + RetroPlayerAudio.h + RetroPlayerStreamTypes.h + RetroPlayerVideo.h + RPStreamManager.h +) + +core_add_library(retroplayer_streams) diff --git a/xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h b/xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h new file mode 100644 index 0000000000..1f1ef6cdb8 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "RetroPlayerStreamTypes.h" + +namespace KODI +{ +namespace RETRO +{ + +struct StreamProperties +{ +}; + +struct StreamBuffer +{ +}; + +struct StreamPacket +{ +}; + +class IRetroPlayerStream +{ +public: + virtual ~IRetroPlayerStream() = default; + + /*! + * \brief Open a stream + * + * \return True if the stream was opened, false otherwise + */ + virtual bool OpenStream(const StreamProperties& properties) = 0; + + /*! + * \brief Get a buffer for zero-copy stream data + * + * \param width The framebuffer width, or 0 for no width specified + * \param height The framebuffer height, or 0 for no height specified + * \param[out] buffer The buffer, or unmodified if false is returned + * + * \return True if a buffer was returned, false otherwise + */ + virtual bool GetStreamBuffer(unsigned int width, unsigned int height, StreamBuffer& buffer) = 0; + + /*! + * \brief Add a data packet to a stream + * + * \param packet The data packet + */ + virtual void AddStreamData(const StreamPacket& packet) = 0; + + /*! + * \brief Close the stream + */ + virtual void CloseStream() = 0; +}; + +} +} diff --git a/xbmc/cores/RetroPlayer/streams/IStreamManager.h b/xbmc/cores/RetroPlayer/streams/IStreamManager.h new file mode 100644 index 0000000000..c4302e806b --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/IStreamManager.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "RetroPlayerStreamTypes.h" + +namespace KODI +{ +namespace RETRO +{ + +class IStreamManager +{ +public: + virtual ~IStreamManager() = default; + + /*! + * \brief Create a stream for gameplay data + * + * \param streamType The stream type + * + * \return A stream handle, or empty on failure + */ + virtual StreamPtr CreateStream(StreamType streamType) = 0; + + /*! + * \brief Free the specified stream + * + * \param stream The stream to close + */ + virtual void CloseStream(StreamPtr stream) = 0; +}; + +} +} diff --git a/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp b/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp new file mode 100644 index 0000000000..41df4f92ad --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "RPStreamManager.h" +#include "IRetroPlayerStream.h" +#include "RetroPlayerAudio.h" +//#include "RetroPlayerHardwareBuffer.h" //! @todo +//#include "RetroPlayerSoftwareBuffer.h" //! @todo +#include "RetroPlayerVideo.h" + +using namespace KODI; +using namespace RETRO; + +CRPStreamManager::CRPStreamManager(CRPRenderManager& renderManager, CRPProcessInfo& processInfo) : + m_renderManager(renderManager), + m_processInfo(processInfo) +{ +} + +void CRPStreamManager::EnableAudio(bool bEnable) +{ + if (m_audioStream != nullptr) + m_audioStream->Enable(bEnable); +} + +StreamPtr CRPStreamManager::CreateStream(StreamType streamType) +{ + switch (streamType) + { + case StreamType::AUDIO: + { + // Save pointer to audio stream + m_audioStream = new CRetroPlayerAudio(m_processInfo); + + return StreamPtr(m_audioStream); + } + case StreamType::VIDEO: + { + return StreamPtr(new CRetroPlayerVideo(m_renderManager, m_processInfo)); + } + case StreamType::SW_BUFFER: + { + //return StreamPtr(new CRetroPlayerSoftware(m_renderManager, m_processInfo)); //! @todo + } + case StreamType::HW_BUFFER: + { + //return StreamPtr(new CRetroPlayerHardware(m_renderManager, m_processInfo)); //! @todo + } + default: + break; + } + + return StreamPtr(); +} + +void CRPStreamManager::CloseStream(StreamPtr stream) +{ + if (stream) + { + if (stream.get() == m_audioStream) + m_audioStream = nullptr; + + stream->CloseStream(); + } +} diff --git a/xbmc/cores/RetroPlayer/streams/RPStreamManager.h b/xbmc/cores/RetroPlayer/streams/RPStreamManager.h new file mode 100644 index 0000000000..9e75aab840 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RPStreamManager.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IStreamManager.h" + +namespace KODI +{ +namespace RETRO +{ + class CRetroPlayerAudio; + class CRPProcessInfo; + class CRPRenderManager; + + class CRPStreamManager : public IStreamManager + { + public: + CRPStreamManager(CRPRenderManager& renderManager, CRPProcessInfo& processInfo); + ~CRPStreamManager() override = default; + + void EnableAudio(bool bEnable); + + // Implementation of IStreamManager + StreamPtr CreateStream(StreamType streamType) override; + void CloseStream(StreamPtr stream) override; + + private: + // Construction parameters + CRPRenderManager& m_renderManager; + CRPProcessInfo& m_processInfo; + + // Stream parameters + CRetroPlayerAudio* m_audioStream = nullptr; + }; +} +} diff --git a/xbmc/cores/RetroPlayer/RetroPlayerAudio.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp index 80649fde15..8d616e4b42 100644 --- a/xbmc/cores/RetroPlayer/RetroPlayerAudio.cpp +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp @@ -18,15 +18,17 @@ * */ -#include "ServiceBroker.h" #include "RetroPlayerAudio.h" #include "cores/AudioEngine/Interfaces/AE.h" #include "cores/AudioEngine/Interfaces/AEStream.h" #include "cores/AudioEngine/Utils/AEChannelInfo.h" #include "cores/AudioEngine/Utils/AEUtil.h" +#include "cores/RetroPlayer/audio/AudioTranslator.h" #include "cores/RetroPlayer/process/RPProcessInfo.h" -#include "threads/Thread.h" #include "utils/log.h" +#include "ServiceBroker.h" + +#include <cmath> using namespace KODI; using namespace RETRO; @@ -46,20 +48,56 @@ CRetroPlayerAudio::~CRetroPlayerAudio() CloseStream(); } -bool CRetroPlayerAudio::OpenPCMStream(AEDataFormat format, unsigned int samplerate, const CAEChannelInfo& channelLayout) +bool CRetroPlayerAudio::OpenStream(const StreamProperties& properties) { + const AudioStreamProperties& audioProperties = reinterpret_cast<const AudioStreamProperties&>(properties); + + const AEDataFormat pcmFormat = CAudioTranslator::TranslatePCMFormat(audioProperties.format); + if (pcmFormat == AE_FMT_INVALID) + { + CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Unknown PCM format: %d", static_cast<int>(audioProperties.format)); + return false; + } + + unsigned int iSampleRate = static_cast<unsigned int>(std::round(audioProperties.sampleRate)); + if (iSampleRate == 0) + { + CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Invalid samplerate: %f", audioProperties.sampleRate); + return false; + } + + CAEChannelInfo channelLayout; + for (auto it = audioProperties.channelMap.begin(); it != audioProperties.channelMap.end(); ++it) + { + AEChannel channel = CAudioTranslator::TranslateAudioChannel(*it); + if (channel == AE_CH_NULL) + break; + + channelLayout += channel; + } + + if (!channelLayout.IsLayoutValid()) + { + CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Empty channel layout"); + return false; + } + if (m_pAudioStream != nullptr) CloseStream(); - CLog::Log(LOGINFO, "RetroPlayer[AUDIO]: Creating audio stream, sample rate = %d", samplerate); + CLog::Log(LOGINFO, "RetroPlayer[AUDIO]: Creating audio stream, sample rate = %d", iSampleRate); + + IAE* audioEngine = CServiceBroker::GetActiveAE(); + if (audioEngine == nullptr) + return false; AEAudioFormat audioFormat; - audioFormat.m_dataFormat = format; - audioFormat.m_sampleRate = samplerate; + audioFormat.m_dataFormat = pcmFormat; + audioFormat.m_sampleRate = iSampleRate; audioFormat.m_channelLayout = channelLayout; - m_pAudioStream = CServiceBroker::GetActiveAE()->MakeStream(audioFormat); + m_pAudioStream = audioEngine->MakeStream(audioFormat); - if (!m_pAudioStream) + if (m_pAudioStream == nullptr) { CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Failed to create audio stream"); return false; @@ -72,21 +110,16 @@ bool CRetroPlayerAudio::OpenPCMStream(AEDataFormat format, unsigned int samplera return true; } -bool CRetroPlayerAudio::OpenEncodedStream(AVCodecID codec, unsigned int samplerate, const CAEChannelInfo& channelLayout) +void CRetroPlayerAudio::AddStreamData(const StreamPacket &packet) { - CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Encoded audio stream not supported"); + const AudioStreamPacket& audioPacket = reinterpret_cast<const AudioStreamPacket&>(packet); - return true; //! @todo -} - -void CRetroPlayerAudio::AddData(const uint8_t* data, size_t size) -{ if (m_bAudioEnabled) { if (m_pAudioStream) { const size_t frameSize = m_pAudioStream->GetChannelCount() * (CAEUtil::DataFormatToBits(m_pAudioStream->GetDataFormat()) >> 3); - m_pAudioStream->AddData(&data, 0, static_cast<unsigned int>(size / frameSize)); + m_pAudioStream->AddData(&audioPacket.data, 0, static_cast<unsigned int>(audioPacket.size / frameSize)); } } } diff --git a/xbmc/cores/RetroPlayer/RetroPlayerAudio.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h index 3d6d4a911b..3183a54439 100644 --- a/xbmc/cores/RetroPlayer/RetroPlayerAudio.h +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h @@ -19,7 +19,7 @@ */ #pragma once -#include "games/addons/GameClientCallbacks.h" +#include "IRetroPlayerStream.h" #include <memory> @@ -31,24 +31,37 @@ namespace RETRO { class CRPProcessInfo; - class CRetroPlayerAudio : public GAME::IGameAudioCallback + struct AudioStreamProperties + { + PCMFormat format; + double sampleRate; + AudioChannelMap channelMap; + }; + + struct AudioStreamPacket + { + const uint8_t* data; + size_t size; + }; + + class CRetroPlayerAudio : public IRetroPlayerStream { public: explicit CRetroPlayerAudio(CRPProcessInfo& processInfo); ~CRetroPlayerAudio() override; - // implementation of IGameAudioCallback - bool OpenPCMStream(AEDataFormat format, unsigned int samplerate, const CAEChannelInfo& channelLayout) override; - bool OpenEncodedStream(AVCodecID codec, unsigned int samplerate, const CAEChannelInfo& channelLayout) override; - void AddData(const uint8_t* data, size_t size) override; - void CloseStream() override; - void Enable(bool bEnabled) { m_bAudioEnabled = bEnabled; } + // implementation of IRetroPlayerStream + bool OpenStream(const StreamProperties& properties) override; + bool GetStreamBuffer(unsigned int width, unsigned int height, StreamBuffer& buffer) override { return false; } + void AddStreamData(const StreamPacket& packet) override; + void CloseStream() override; + private: CRPProcessInfo& m_processInfo; IAEStream* m_pAudioStream; - bool m_bAudioEnabled; + bool m_bAudioEnabled; }; } } diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp new file mode 100644 index 0000000000..5967c37676 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "RetroPlayerStreamTypes.h" +#include "IRetroPlayerStream.h" + +using namespace KODI; +using namespace RETRO; + +void DeleteStream::operator()(IRetroPlayerStream* stream) +{ + delete stream; +} diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h new file mode 100644 index 0000000000..ea3c8e82fa --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <array> +#include <memory> + +namespace KODI +{ +namespace RETRO +{ +class IRetroPlayerStream; + +struct DeleteStream +{ + void operator()(IRetroPlayerStream* stream); +}; + +using StreamPtr = std::unique_ptr<IRetroPlayerStream, DeleteStream>; + +enum class StreamType +{ + AUDIO, + VIDEO, + SW_BUFFER, + HW_BUFFER, +}; + +enum class PCMFormat +{ + FMT_UNKNOWN, + FMT_S16NE, +}; + +enum class AudioChannel +{ + CH_NULL, // Channel list terminator + CH_FL, + CH_FR, + CH_FC, + CH_LFE, + CH_BL, + CH_BR, + CH_FLOC, + CH_FROC, + CH_BC, + CH_SL, + CH_SR, + CH_TFL, + CH_TFR, + CH_TFC, + CH_TC, + CH_TBL, + CH_TBR, + CH_TBC, + CH_BLOC, + CH_BROC, + CH_COUNT +}; + +using AudioChannelMap = std::array<AudioChannel, static_cast<unsigned int>(AudioChannel::CH_COUNT)>; + +enum class PixelFormat +{ + FMT_UNKNOWN, + FMT_0RGB8888, + FMT_RGB565, + FMT_0RGB1555, +}; + +enum class VideoRotation +{ + ROTATION_0, + ROTATION_90_CCW, + ROTATION_180_CCW, + ROTATION_270_CCW, +}; + +} +} diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp new file mode 100644 index 0000000000..85b384a496 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012-2017 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "RetroPlayerVideo.h" +#include "cores/RetroPlayer/process/RPProcessInfo.h" +#include "cores/RetroPlayer/rendering/RenderTranslator.h" +#include "cores/RetroPlayer/rendering/RPRenderManager.h" +#include "utils/log.h" + +using namespace KODI; +using namespace RETRO; + +CRetroPlayerVideo::CRetroPlayerVideo(CRPRenderManager& renderManager, CRPProcessInfo& processInfo) : + m_renderManager(renderManager), + m_processInfo(processInfo) +{ + CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Initializing video"); + + m_renderManager.Initialize(); +} + +CRetroPlayerVideo::~CRetroPlayerVideo() +{ + CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Deinitializing video"); + + CloseStream(); + m_renderManager.Deinitialize(); +} + +bool CRetroPlayerVideo::OpenStream(const StreamProperties& properties) +{ + const VideoStreamProperties& videoProperties = reinterpret_cast<const VideoStreamProperties&>(properties); + + if (m_bOpen) + { + CloseStream(); + m_bOpen = false; + } + + const AVPixelFormat pixfmt = videoProperties.pixfmt; + const unsigned int nominalWidth = videoProperties.nominalWidth; + const unsigned int nominalHeight = videoProperties.nominalHeight; + const unsigned int maxWidth = videoProperties.maxWidth; + const unsigned int maxHeight = videoProperties.maxHeight; + //const float pixelAspectRatio = videoProperties.pixelAspectRatio; //! @todo + + CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Creating video stream - format %s, nominal %ux%u, max %ux%u", + CRenderTranslator::TranslatePixelFormat(pixfmt), + nominalWidth, + nominalHeight, + maxWidth, + maxHeight); + + m_processInfo.SetVideoPixelFormat(pixfmt); + m_processInfo.SetVideoDimensions(nominalWidth, nominalHeight); // Report nominal height for now + + if (m_renderManager.Configure(pixfmt, nominalWidth, nominalHeight, maxWidth, maxHeight)) + m_bOpen = true; + + return m_bOpen; +} + +void CRetroPlayerVideo::AddStreamData(const StreamPacket &packet) +{ + const VideoStreamPacket& videoPacket = reinterpret_cast<const VideoStreamPacket&>(packet); + + if (m_bOpen) + { + unsigned int orientationDegCCW = 0; + switch (videoPacket.rotation) + { + case VideoRotation::ROTATION_90_CCW: + orientationDegCCW = 90; + break; + case VideoRotation::ROTATION_180_CCW: + orientationDegCCW = 180; + break; + case VideoRotation::ROTATION_270_CCW: + orientationDegCCW = 270; + break; + default: + break; + } + + m_renderManager.AddFrame(videoPacket.data, + videoPacket.size, + videoPacket.width, + videoPacket.height, + orientationDegCCW); + } +} + +void CRetroPlayerVideo::CloseStream() +{ + if (m_bOpen) + { + CLog::Log(LOGDEBUG, "RetroPlayer[VIDEO]: Closing video stream"); + + m_renderManager.Flush(); + m_bOpen = false; + } +} diff --git a/xbmc/cores/RetroPlayer/RetroPlayerVideo.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h index 2472291c91..6cad4f3024 100644 --- a/xbmc/cores/RetroPlayer/RetroPlayerVideo.h +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h @@ -19,9 +19,11 @@ */ #pragma once -#include "games/addons/GameClientCallbacks.h" +#include "IRetroPlayerStream.h" -class CPixelConverter; +extern "C" { +#include "libavutil/pixfmt.h" +} namespace KODI { @@ -30,28 +32,49 @@ namespace RETRO class CRPProcessInfo; class CRPRenderManager; + struct VideoStreamProperties + { + AVPixelFormat pixfmt; + unsigned int nominalWidth; + unsigned int nominalHeight; + unsigned int maxWidth; + unsigned int maxHeight; + float pixelAspectRatio; + }; + + struct VideoStreamPacket + { + unsigned int width; + unsigned int height; + VideoRotation rotation; + const uint8_t *data; + size_t size; + }; + /*! * \brief Renders video frames provided by the game loop * * \sa CRPRenderManager */ - class CRetroPlayerVideo : public GAME::IGameVideoCallback + class CRetroPlayerVideo : public IRetroPlayerStream { public: CRetroPlayerVideo(CRPRenderManager& m_renderManager, CRPProcessInfo& m_processInfo); - ~CRetroPlayerVideo() override; - // implementation of IGameVideoCallback - bool OpenPixelStream(AVPixelFormat pixfmt, unsigned int width, unsigned int height, unsigned int orientationDeg) override; - bool OpenEncodedStream(AVCodecID codec) override; - void AddData(const uint8_t* data, size_t size) override; + // implementation of IRetroPlayerStream + bool OpenStream(const StreamProperties& properties) override; + bool GetStreamBuffer(unsigned int width, unsigned int height, StreamBuffer& buffer) override { return false; } + void AddStreamData(const StreamPacket &packet) override; void CloseStream() override; private: // Construction parameters CRPRenderManager& m_renderManager; - CRPProcessInfo& m_processInfo; + CRPProcessInfo& m_processInfo; + + // Stream properties + bool m_bOpen = false; }; } } diff --git a/xbmc/games/addons/GameClient.cpp b/xbmc/games/addons/GameClient.cpp index 88cd818c6a..b090fc5995 100644 --- a/xbmc/games/addons/GameClient.cpp +++ b/xbmc/games/addons/GameClient.cpp @@ -32,6 +32,8 @@ #include "games/addons/input/GameClientInput.h" #include "games/addons/playback/GameClientRealtimePlayback.h" #include "games/addons/playback/GameClientReversiblePlayback.h" +#include "games/addons/streams/GameClientStreams.h" +#include "games/addons/streams/IGameClientStream.h" #include "games/GameServices.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" @@ -118,8 +120,6 @@ CGameClient::CGameClient(ADDON::CAddonInfo addonInfo) : m_bSupportsAllExtensions(false), m_bIsPlaying(false), m_serializeSize(0), - m_audio(nullptr), - m_video(nullptr), m_region(GAME_REGION_UNKNOWN) { const ADDON::InfoMap& extraInfo = m_addonInfo.ExtraInfo(); @@ -208,16 +208,12 @@ bool CGameClient::Initialize(void) m_struct.toKodi.kodiInstance = this; m_struct.toKodi.CloseGame = cb_close_game; - m_struct.toKodi.OpenPixelStream = cb_open_pixel_stream; - m_struct.toKodi.OpenVideoStream = cb_open_video_stream; - m_struct.toKodi.OpenPCMStream = cb_open_pcm_stream; - m_struct.toKodi.OpenAudioStream = cb_open_audio_stream; + m_struct.toKodi.OpenStream = cb_open_stream; + m_struct.toKodi.GetStreamBuffer = cb_get_stream_buffer; m_struct.toKodi.AddStreamData = cb_add_stream_data; + m_struct.toKodi.ReleaseStreamBuffer = cb_release_stream_buffer; m_struct.toKodi.CloseStream = cb_close_stream; - m_struct.toKodi.EnableHardwareRendering = cb_enable_hardware_rendering; - m_struct.toKodi.HwGetCurrentFramebuffer = cb_hw_get_current_framebuffer; m_struct.toKodi.HwGetProcAddress = cb_hw_get_proc_address; - m_struct.toKodi.RenderFrame = cb_render_frame; m_struct.toKodi.InputEvent = cb_input_event; if (Create(ADDON_INSTANCE_GAME, &m_struct, &m_struct.props) == ADDON_STATUS_OK) @@ -237,11 +233,8 @@ void CGameClient::Unload() Destroy(); } -bool CGameClient::OpenFile(const CFileItem& file, IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input) +bool CGameClient::OpenFile(const CFileItem& file, RETRO::IStreamManager& streamManager, IGameInputCallback *input) { - if (audio == nullptr || video == nullptr) - return false; - // Check if we should open in standalone mode if (file.GetPath().empty()) return false; @@ -287,13 +280,13 @@ bool CGameClient::OpenFile(const CFileItem& file, IGameAudioCallback* audio, IGa return false; } - if (!InitializeGameplay(file.GetPath(), audio, video, input)) + if (!InitializeGameplay(file.GetPath(), streamManager, input)) return false; return true; } -bool CGameClient::OpenStandalone(IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input) +bool CGameClient::OpenStandalone(RETRO::IStreamManager& streamManager, IGameInputCallback *input) { CLog::Log(LOGDEBUG, "GameClient: Loading %s in standalone mode", ID().c_str()); @@ -315,21 +308,21 @@ bool CGameClient::OpenStandalone(IGameAudioCallback* audio, IGameVideoCallback* return false; } - if (!InitializeGameplay(ID(), audio, video, input)) + if (!InitializeGameplay(ID(), streamManager, input)) return false; return true; } -bool CGameClient::InitializeGameplay(const std::string& gamePath, IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input) +bool CGameClient::InitializeGameplay(const std::string& gamePath, RETRO::IStreamManager& streamManager, IGameInputCallback *input) { if (LoadGameInfo()) { + Streams().Initialize(streamManager); + m_bIsPlaying = true; m_gamePath = gamePath; m_serializeSize = GetSerializeSize(); - m_audio = audio; - m_video = video; m_input = input; m_inGameSaves.reset(new CGameClientInGameSaves(this, &m_struct.toAddon)); @@ -346,34 +339,32 @@ bool CGameClient::InitializeGameplay(const std::string& gamePath, IGameAudioCall bool CGameClient::LoadGameInfo() { - // Get information about system audio/video timings and geometry + // Get information about system timings // Can be called only after retro_load_game() - game_system_av_info av_info = { }; + game_system_timing timingInfo = { }; bool bSuccess = false; - try { bSuccess = LogError(m_struct.toAddon.GetGameInfo(&av_info), "GetGameInfo()"); } - catch (...) { LogException("GetGameInfo()"); } + try { bSuccess = LogError(m_struct.toAddon.GetGameTiming(&timingInfo), "GetGameTiming()"); } + catch (...) { LogException("GetGameTiming()"); } if (!bSuccess) + { + CLog::Log(LOGERROR, "GameClient: Failed to get timing info"); return false; + } GAME_REGION region; try { region = m_struct.toAddon.GetRegion(); } catch (...) { LogException("GetRegion()"); return false; } CLog::Log(LOGINFO, "GAME: ---------------------------------------"); - CLog::Log(LOGINFO, "GAME: Base Width: %u", av_info.geometry.base_width); - CLog::Log(LOGINFO, "GAME: Base Height: %u", av_info.geometry.base_height); - CLog::Log(LOGINFO, "GAME: Max Width: %u", av_info.geometry.max_width); - CLog::Log(LOGINFO, "GAME: Max Height: %u", av_info.geometry.max_height); - CLog::Log(LOGINFO, "GAME: Aspect Ratio: %f", av_info.geometry.aspect_ratio); - CLog::Log(LOGINFO, "GAME: FPS: %f", av_info.timing.fps); - CLog::Log(LOGINFO, "GAME: Sample Rate: %f", av_info.timing.sample_rate); - CLog::Log(LOGINFO, "GAME: Region: %s", CGameClientTranslator::TranslateRegion(region)); + CLog::Log(LOGINFO, "GAME: FPS: %f", timingInfo.fps); + CLog::Log(LOGINFO, "GAME: Sample Rate: %f", timingInfo.sample_rate); + CLog::Log(LOGINFO, "GAME: Region: %s", CGameClientTranslator::TranslateRegion(region)); CLog::Log(LOGINFO, "GAME: ---------------------------------------"); - m_framerate = av_info.timing.fps; - m_samplerate = av_info.timing.sample_rate; + m_framerate = timingInfo.fps; + m_samplerate = timingInfo.sample_rate; m_region = region; return true; @@ -480,10 +471,9 @@ void CGameClient::CloseFile() m_bIsPlaying = false; m_gamePath.clear(); m_serializeSize = 0; - - m_audio = nullptr; - m_video = nullptr; m_input = nullptr; + + Streams().Deinitialize(); } void CGameClient::RunFrame() @@ -507,148 +497,6 @@ void CGameClient::RunFrame() } } -bool CGameClient::OpenPixelStream(GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation) -{ - if (!m_video) - return false; - - AVPixelFormat pixelFormat = CGameClientTranslator::TranslatePixelFormat(format); - if (pixelFormat == AV_PIX_FMT_NONE) - { - CLog::Log(LOGERROR, "GAME: Unknown pixel format: %d", format); - return false; - } - - unsigned int orientation = 0; - switch (rotation) - { - case GAME_VIDEO_ROTATION_90: - orientation = 360 - 90; - break; - case GAME_VIDEO_ROTATION_180: - orientation = 360 - 180; - break; - case GAME_VIDEO_ROTATION_270: - orientation = 360 - 270; - break; - default: - break; - } - - return m_video->OpenPixelStream(pixelFormat, width, height, orientation); -} - -bool CGameClient::OpenVideoStream(GAME_VIDEO_CODEC codec) -{ - if (!m_video) - return false; - - AVCodecID videoCodec = CGameClientTranslator::TranslateVideoCodec(codec); - if (videoCodec == AV_CODEC_ID_NONE) - { - CLog::Log(LOGERROR, "GAME: Unknown video format: %d", codec); - return false; - } - - return m_video->OpenEncodedStream(videoCodec); -} - -bool CGameClient::OpenPCMStream(GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channelMap) -{ - if (!m_audio || channelMap == nullptr) - return false; - - AEDataFormat pcmFormat = CGameClientTranslator::TranslatePCMFormat(format); - if (pcmFormat == AE_FMT_INVALID) - { - CLog::Log(LOGERROR, "GAME: Unknown PCM format: %d", format); - return false; - } - - CAEChannelInfo channelLayout; - for (const GAME_AUDIO_CHANNEL* channelPtr = channelMap; *channelPtr != GAME_CH_NULL; channelPtr++) - { - AEChannel channel = CGameClientTranslator::TranslateAudioChannel(*channelPtr); - if (channel == AE_CH_NULL) - { - CLog::Log(LOGERROR, "GAME: Unknown channel ID: %d", *channelPtr); - return false; - } - channelLayout += channel; - } - - return m_audio->OpenPCMStream(pcmFormat, static_cast<unsigned int>(m_samplerate), channelLayout); -} - -bool CGameClient::OpenAudioStream(GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channelMap) -{ - if (!m_audio) - return false; - - AVCodecID audioCodec = CGameClientTranslator::TranslateAudioCodec(codec); - if (audioCodec == AV_CODEC_ID_NONE) - { - CLog::Log(LOGERROR, "GAME: Unknown audio codec: %d", codec); - return false; - } - - CAEChannelInfo channelLayout; - for (const GAME_AUDIO_CHANNEL* channelPtr = channelMap; *channelPtr != GAME_CH_NULL; channelPtr++) - { - AEChannel channel = CGameClientTranslator::TranslateAudioChannel(*channelPtr); - if (channel == AE_CH_NULL) - { - CLog::Log(LOGERROR, "GAME: Unknown channel ID: %d", *channelPtr); - return false; - } - channelLayout += channel; - } - - return m_audio->OpenEncodedStream(audioCodec, static_cast<unsigned int>(m_samplerate), channelLayout); -} - -void CGameClient::AddStreamData(GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size) -{ - switch (stream) - { - case GAME_STREAM_AUDIO: - { - if (m_audio) - m_audio->AddData(data, size); - break; - } - case GAME_STREAM_VIDEO: - { - if (m_video) - m_video->AddData(data, size); - break; - } - default: - break; - } -} - -void CGameClient::CloseStream(GAME_STREAM_TYPE stream) -{ - switch (stream) - { - case GAME_STREAM_AUDIO: - { - if (m_audio) - m_audio->CloseStream(); - break; - } - case GAME_STREAM_VIDEO: - { - if (m_video) - m_video->CloseStream(); - break; - } - default: - break; - } -} - size_t CGameClient::GetSerializeSize() { CSingleLock lock(m_critSection); @@ -734,77 +582,65 @@ void CGameClient::cb_close_game(void* kodiInstance) CApplicationMessenger::GetInstance().PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_STOP))); } -int CGameClient::cb_open_pixel_stream(void* kodiInstance, GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation) +void* CGameClient::cb_open_stream(void* kodiInstance, const game_stream_properties *properties) { - CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) - return -1; - - return gameClient->OpenPixelStream(format, width, height, rotation) ? 0 : -1; -} + if (properties == nullptr) + return nullptr; -int CGameClient::cb_open_video_stream(void* kodiInstance, GAME_VIDEO_CODEC codec) -{ CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) - return -1; + if (gameClient == nullptr) + return nullptr; - return gameClient->OpenVideoStream(codec) ? 0 : -1; + return gameClient->Streams().OpenStream(*properties); } -int CGameClient::cb_open_pcm_stream(void* kodiInstance, GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map) +bool CGameClient::cb_get_stream_buffer(void* kodiInstance, void *stream, unsigned int width, unsigned int height, game_stream_buffer *buffer) { - CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) - return -1; - - return gameClient->OpenPCMStream(format, channel_map) ? 0 : -1; -} + if (buffer == nullptr) + return false; -int CGameClient::cb_open_audio_stream(void* kodiInstance, GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map) -{ - CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) - return -1; + IGameClientStream *gameClientStream = static_cast<IGameClientStream*>(stream); + if (gameClientStream == nullptr) + return false; - return gameClient->OpenAudioStream(codec, channel_map) ? 0 : -1; + return gameClientStream->GetBuffer(width, height, *buffer); } -void CGameClient::cb_add_stream_data(void* kodiInstance, GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size) +void CGameClient::cb_add_stream_data(void* kodiInstance, void *stream, const game_stream_packet *packet) { - CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) + if (packet == nullptr) return; - gameClient->AddStreamData(stream, data, size); -} - -void CGameClient::cb_close_stream(void* kodiInstance, GAME_STREAM_TYPE stream) -{ - CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) + IGameClientStream *gameClientStream = static_cast<IGameClientStream*>(stream); + if (gameClientStream == nullptr) return; - gameClient->CloseStream(stream); + gameClientStream->AddData(*packet); } -void CGameClient::cb_enable_hardware_rendering(void* kodiInstance, const game_hw_info *hw_info) +void CGameClient::cb_release_stream_buffer(void* kodiInstance, void *stream, game_stream_buffer *buffer) { - CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) + if (buffer == nullptr) return; - //! @todo + IGameClientStream *gameClientStream = static_cast<IGameClientStream*>(stream); + if (gameClientStream == nullptr) + return; + + gameClientStream->ReleaseBuffer(*buffer); } -uintptr_t CGameClient::cb_hw_get_current_framebuffer(void* kodiInstance) +void CGameClient::cb_close_stream(void* kodiInstance, void *stream) { CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) - return 0; + if (gameClient == nullptr) + return; - //! @todo - return 0; + IGameClientStream *gameClientStream = static_cast<IGameClientStream*>(stream); + if (gameClientStream == nullptr) + return; + + gameClient->Streams().CloseStream(gameClientStream); } game_proc_address_t CGameClient::cb_hw_get_proc_address(void* kodiInstance, const char *sym) @@ -817,15 +653,6 @@ game_proc_address_t CGameClient::cb_hw_get_proc_address(void* kodiInstance, cons return nullptr; } -void CGameClient::cb_render_frame(void* kodiInstance) -{ - CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); - if (!gameClient) - return; - - //! @todo -} - bool CGameClient::cb_input_event(void* kodiInstance, const game_input_event* event) { CGameClient *gameClient = static_cast<CGameClient*>(kodiInstance); diff --git a/xbmc/games/addons/GameClient.h b/xbmc/games/addons/GameClient.h index ab4ec26fc6..fc70397d99 100644 --- a/xbmc/games/addons/GameClient.h +++ b/xbmc/games/addons/GameClient.h @@ -30,22 +30,24 @@ #include <set> #include <stdint.h> #include <string> -#include <vector> class CFileItem; namespace KODI { +namespace RETRO +{ + class IStreamManager; +} + namespace GAME { class CGameClientInGameSaves; class CGameClientInput; class CGameClientProperties; -class IGameAudioCallback; class IGameClientPlayback; class IGameInputCallback; -class IGameVideoCallback; /*! * \ingroup games @@ -63,10 +65,12 @@ public: // Game subsystems (const) const CGameClientInput &Input() const { return *m_subsystems.Input; } const CGameClientProperties &AddonProperties() const { return *m_subsystems.AddonProperties; } + const CGameClientStreams &Streams() const { return *m_subsystems.Streams; } // Game subsystems (mutable) CGameClientInput &Input() { return *m_subsystems.Input; } CGameClientProperties &AddonProperties() { return *m_subsystems.AddonProperties; } + CGameClientStreams &Streams() { return *m_subsystems.Streams; } // Implementation of IAddon via CAddonDll virtual std::string LibPath() const override; @@ -83,8 +87,8 @@ public: // Start/stop gameplay bool Initialize(void); void Unload(); - bool OpenFile(const CFileItem& file, IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input); - bool OpenStandalone(IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input); + bool OpenFile(const CFileItem& file, RETRO::IStreamManager& streamManager, IGameInputCallback *input); + bool OpenStandalone(RETRO::IStreamManager& streamManager, IGameInputCallback *input); void Reset(); void CloseFile(); const std::string& GetGamePath() const { return m_gamePath; } @@ -93,16 +97,9 @@ public: bool IsPlaying() const { return m_bIsPlaying; } IGameClientPlayback* GetPlayback() { return m_playback.get(); } double GetFrameRate() const { return m_framerate; } + double GetSampleRate() const { return m_samplerate; } void RunFrame(); - // Audio/video callbacks - bool OpenPixelStream(GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation); - bool OpenVideoStream(GAME_VIDEO_CODEC codec); - bool OpenPCMStream(GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channelMap); - bool OpenAudioStream(GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channelMap); - void AddStreamData(GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size); - void CloseStream(GAME_STREAM_TYPE stream); - // Access memory size_t SerializeSize() const { return m_serializeSize; } bool Serialize(uint8_t* data, size_t size); @@ -121,7 +118,7 @@ public: private: // Private gameplay functions - bool InitializeGameplay(const std::string& gamePath, IGameAudioCallback* audio, IGameVideoCallback* video, IGameInputCallback *input); + bool InitializeGameplay(const std::string& gamePath, RETRO::IStreamManager& streamManager, IGameInputCallback *input); bool LoadGameInfo(); void NotifyError(GAME_ERROR error); std::string GetMissingResource(); @@ -139,16 +136,12 @@ private: */ //@{ static void cb_close_game(void* kodiInstance); - static int cb_open_pixel_stream(void* kodiInstance, GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation); - static int cb_open_video_stream(void* kodiInstance, GAME_VIDEO_CODEC codec); - static int cb_open_pcm_stream(void* kodiInstance, GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map); - static int cb_open_audio_stream(void* kodiInstance, GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map); - static void cb_add_stream_data(void* kodiInstance, GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size); - static void cb_close_stream(void* kodiInstance, GAME_STREAM_TYPE stream); - static void cb_enable_hardware_rendering(void* kodiInstance, const game_hw_info* hw_info); - static uintptr_t cb_hw_get_current_framebuffer(void* kodiInstance); + static void* cb_open_stream(void* kodiInstance, const game_stream_properties *properties); + static bool cb_get_stream_buffer(void* kodiInstance, void *stream, unsigned int width, unsigned int height, game_stream_buffer *buffer); + static void cb_add_stream_data(void* kodiInstance, void *stream, const game_stream_packet *packet); + static void cb_release_stream_buffer(void* kodiInstance, void *stream, game_stream_buffer *buffer); + static void cb_close_stream(void* kodiInstance, void *stream); static game_proc_address_t cb_hw_get_proc_address(void* kodiInstance, const char* sym); - static void cb_render_frame(void* kodiInstance); static bool cb_input_event(void* kodiInstance, const game_input_event* event); //@} @@ -166,8 +159,6 @@ private: std::atomic_bool m_bIsPlaying; // True between OpenFile() and CloseFile() std::string m_gamePath; size_t m_serializeSize; - IGameAudioCallback* m_audio; // The audio callback passed to OpenFile() - IGameVideoCallback* m_video; // The video callback passed to OpenFile() IGameInputCallback* m_input = nullptr; // The input callback passed to OpenFile() double m_framerate = 0.0; // Video frame rate (fps) double m_samplerate = 0.0; // Audio sample rate (Hz) diff --git a/xbmc/games/addons/GameClientCallbacks.h b/xbmc/games/addons/GameClientCallbacks.h index c2fb9e94d6..4287e16923 100644 --- a/xbmc/games/addons/GameClientCallbacks.h +++ b/xbmc/games/addons/GameClientCallbacks.h @@ -19,41 +19,10 @@ */ #pragma once -#include "cores/AudioEngine/Utils/AEChannelData.h" - -#include "libavcodec/avcodec.h" -#include "libavutil/pixfmt.h" - -#include <stdint.h> - -class CAEChannelInfo; - namespace KODI { namespace GAME { - class IGameAudioCallback - { - public: - virtual ~IGameAudioCallback() = default; - - virtual bool OpenPCMStream(AEDataFormat format, unsigned int samplerate, const CAEChannelInfo& channelLayout) = 0; - virtual bool OpenEncodedStream(AVCodecID codec, unsigned int samplerate, const CAEChannelInfo& channelLayout) = 0; - virtual void AddData(const uint8_t* data, size_t size) = 0; - virtual void CloseStream() = 0; - }; - - class IGameVideoCallback - { - public: - virtual ~IGameVideoCallback() = default; - - virtual bool OpenPixelStream(AVPixelFormat pixfmt, unsigned int width, unsigned int height, unsigned int orientationDeg) = 0; - virtual bool OpenEncodedStream(AVCodecID codec) = 0; - virtual void AddData(const uint8_t* data, size_t size) = 0; - virtual void CloseStream() = 0; - }; - class IGameInputCallback { public: diff --git a/xbmc/games/addons/GameClientSubsystem.cpp b/xbmc/games/addons/GameClientSubsystem.cpp index a063566bc6..5a6240c2de 100644 --- a/xbmc/games/addons/GameClientSubsystem.cpp +++ b/xbmc/games/addons/GameClientSubsystem.cpp @@ -23,6 +23,7 @@ #include "GameClientProperties.h" #include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" #include "games/addons/input/GameClientInput.h" +#include "games/addons/streams/GameClientStreams.h" using namespace KODI; using namespace GAME; @@ -44,6 +45,7 @@ GameClientSubsystems CGameClientSubsystem::CreateSubsystems(CGameClient &gameCli subsystems.Input.reset(new CGameClientInput(gameClient, gameStruct, clientAccess)); subsystems.AddonProperties.reset(new CGameClientProperties(gameClient, gameStruct.props)); + subsystems.Streams.reset(new CGameClientStreams(gameClient)); return subsystems; } @@ -52,6 +54,7 @@ void CGameClientSubsystem::DestroySubsystems(GameClientSubsystems &subsystems) { subsystems.Input.reset(); subsystems.AddonProperties.reset(); + subsystems.Streams.reset(); } CGameClientInput &CGameClientSubsystem::Input() const @@ -63,3 +66,8 @@ CGameClientProperties &CGameClientSubsystem::AddonProperties() const { return m_gameClient.AddonProperties(); } + +CGameClientStreams &CGameClientSubsystem::Streams() const +{ + return m_gameClient.Streams(); +} diff --git a/xbmc/games/addons/GameClientSubsystem.h b/xbmc/games/addons/GameClientSubsystem.h index dc11f0dd0a..28978bad55 100644 --- a/xbmc/games/addons/GameClientSubsystem.h +++ b/xbmc/games/addons/GameClientSubsystem.h @@ -31,11 +31,13 @@ namespace GAME class CGameClient; class CGameClientInput; class CGameClientProperties; + class CGameClientStreams; struct GameClientSubsystems { std::unique_ptr<CGameClientInput> Input; std::unique_ptr<CGameClientProperties> AddonProperties; + std::unique_ptr<CGameClientStreams> Streams; }; /*! @@ -73,6 +75,7 @@ namespace GAME // Subsystems CGameClientInput &Input() const; CGameClientProperties &AddonProperties() const; + CGameClientStreams &Streams() const; // Construction parameters CGameClient &m_gameClient; diff --git a/xbmc/games/addons/GameClientTranslator.cpp b/xbmc/games/addons/GameClientTranslator.cpp index 51afd5e65e..b0ab3e3ef1 100644 --- a/xbmc/games/addons/GameClientTranslator.cpp +++ b/xbmc/games/addons/GameClientTranslator.cpp @@ -59,81 +59,96 @@ const char* CGameClientTranslator::ToString(GAME_MEMORY memory) return "unknown memory"; } -AVPixelFormat CGameClientTranslator::TranslatePixelFormat(GAME_PIXEL_FORMAT format) +bool CGameClientTranslator::TranslateStreamType(GAME_STREAM_TYPE gameType, RETRO::StreamType &retroType) { - switch (format) + switch (gameType) { - case GAME_PIXEL_FORMAT_YUV420P: return AV_PIX_FMT_YUV420P; - case GAME_PIXEL_FORMAT_0RGB8888: return AV_PIX_FMT_0RGB32; - case GAME_PIXEL_FORMAT_RGB565: return AV_PIX_FMT_RGB565; - case GAME_PIXEL_FORMAT_0RGB1555: return AV_PIX_FMT_RGB555; + case GAME_STREAM_AUDIO: + retroType = RETRO::StreamType::AUDIO; + return true; + case GAME_STREAM_VIDEO: + retroType = RETRO::StreamType::VIDEO; + return true; + case GAME_STREAM_SW_FRAMEBUFFER: + retroType = RETRO::StreamType::SW_BUFFER; + return true; + case GAME_STREAM_HW_FRAMEBUFFER: + retroType = RETRO::StreamType::HW_BUFFER; + return true; default: break; } - return AV_PIX_FMT_NONE; + return false; } -AVCodecID CGameClientTranslator::TranslateVideoCodec(GAME_VIDEO_CODEC codec) +AVPixelFormat CGameClientTranslator::TranslatePixelFormat(GAME_PIXEL_FORMAT format) { - switch (codec) + switch (format) { - case GAME_VIDEO_CODEC_H264: return AV_CODEC_ID_H264; + case GAME_PIXEL_FORMAT_0RGB8888: return AV_PIX_FMT_0RGB32; + case GAME_PIXEL_FORMAT_RGB565: return AV_PIX_FMT_RGB565; + case GAME_PIXEL_FORMAT_0RGB1555: return AV_PIX_FMT_RGB555; default: break; } - return AV_CODEC_ID_NONE; + return AV_PIX_FMT_NONE; } -AEDataFormat CGameClientTranslator::TranslatePCMFormat(GAME_PCM_FORMAT format) +RETRO::PCMFormat CGameClientTranslator::TranslatePCMFormat(GAME_PCM_FORMAT format) { switch (format) { - case GAME_PCM_FORMAT_S16NE: return AE_FMT_S16NE; + case GAME_PCM_FORMAT_S16NE: return RETRO::PCMFormat::FMT_S16NE; default: break; } - return AE_FMT_INVALID; + return RETRO::PCMFormat::FMT_UNKNOWN; } -AEChannel CGameClientTranslator::TranslateAudioChannel(GAME_AUDIO_CHANNEL channel) +RETRO::AudioChannel CGameClientTranslator::TranslateAudioChannel(GAME_AUDIO_CHANNEL channel) { switch (channel) { - case GAME_CH_FL: return AE_CH_FL; - case GAME_CH_FR: return AE_CH_FR; - case GAME_CH_FC: return AE_CH_FC; - case GAME_CH_LFE: return AE_CH_LFE; - case GAME_CH_BL: return AE_CH_BL; - case GAME_CH_BR: return AE_CH_BR; - case GAME_CH_FLOC: return AE_CH_FLOC; - case GAME_CH_FROC: return AE_CH_FROC; - case GAME_CH_BC: return AE_CH_BC; - case GAME_CH_SL: return AE_CH_SL; - case GAME_CH_SR: return AE_CH_SR; - case GAME_CH_TFL: return AE_CH_TFL; - case GAME_CH_TFR: return AE_CH_TFR; - case GAME_CH_TFC: return AE_CH_TFC; - case GAME_CH_TC: return AE_CH_TC; - case GAME_CH_TBL: return AE_CH_TBL; - case GAME_CH_TBR: return AE_CH_TBR; - case GAME_CH_TBC: return AE_CH_TBC; - case GAME_CH_BLOC: return AE_CH_BLOC; - case GAME_CH_BROC: return AE_CH_BROC; + case GAME_CH_FL: return RETRO::AudioChannel::CH_FL; + case GAME_CH_FR: return RETRO::AudioChannel::CH_FR; + case GAME_CH_FC: return RETRO::AudioChannel::CH_FC; + case GAME_CH_LFE: return RETRO::AudioChannel::CH_LFE; + case GAME_CH_BL: return RETRO::AudioChannel::CH_BL; + case GAME_CH_BR: return RETRO::AudioChannel::CH_BR; + case GAME_CH_FLOC: return RETRO::AudioChannel::CH_FLOC; + case GAME_CH_FROC: return RETRO::AudioChannel::CH_FROC; + case GAME_CH_BC: return RETRO::AudioChannel::CH_BC; + case GAME_CH_SL: return RETRO::AudioChannel::CH_SL; + case GAME_CH_SR: return RETRO::AudioChannel::CH_SR; + case GAME_CH_TFL: return RETRO::AudioChannel::CH_TFL; + case GAME_CH_TFR: return RETRO::AudioChannel::CH_TFR; + case GAME_CH_TFC: return RETRO::AudioChannel::CH_TFC; + case GAME_CH_TC: return RETRO::AudioChannel::CH_TC; + case GAME_CH_TBL: return RETRO::AudioChannel::CH_TBL; + case GAME_CH_TBR: return RETRO::AudioChannel::CH_TBR; + case GAME_CH_TBC: return RETRO::AudioChannel::CH_TBC; + case GAME_CH_BLOC: return RETRO::AudioChannel::CH_BLOC; + case GAME_CH_BROC: return RETRO::AudioChannel::CH_BROC; default: break; } - return AE_CH_NULL; + return RETRO::AudioChannel::CH_NULL; } -AVCodecID CGameClientTranslator::TranslateAudioCodec(GAME_AUDIO_CODEC codec) +RETRO::VideoRotation CGameClientTranslator::TranslateRotation(GAME_VIDEO_ROTATION rotation) { - switch (codec) + switch (rotation) { - case GAME_AUDIO_CODEC_OPUS: return AV_CODEC_ID_OPUS; + case GAME_VIDEO_ROTATION_90_CCW: + return RETRO::VideoRotation::ROTATION_90_CCW; + case GAME_VIDEO_ROTATION_180_CCW: + return RETRO::VideoRotation::ROTATION_180_CCW; + case GAME_VIDEO_ROTATION_270_CCW: + return RETRO::VideoRotation::ROTATION_270_CCW; default: break; } - return AV_CODEC_ID_NONE; + return RETRO::VideoRotation::ROTATION_0; } GAME_KEY_MOD CGameClientTranslator::GetModifiers(KEYBOARD::Modifier modifier) diff --git a/xbmc/games/addons/GameClientTranslator.h b/xbmc/games/addons/GameClientTranslator.h index 154089f9cd..f47eaaa213 100644 --- a/xbmc/games/addons/GameClientTranslator.h +++ b/xbmc/games/addons/GameClientTranslator.h @@ -20,12 +20,13 @@ #pragma once #include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" -#include "cores/AudioEngine/Utils/AEChannelData.h" +#include "cores/RetroPlayer/streams/RetroPlayerStreamTypes.h" #include "games/controllers/ControllerTypes.h" #include "input/keyboard/KeyboardTypes.h" -#include "libavcodec/avcodec.h" +extern "C" { #include "libavutil/pixfmt.h" +} namespace KODI { @@ -57,39 +58,40 @@ namespace GAME static const char* ToString(GAME_MEMORY error); /*! - * \brief Translate pixel format (Game API to FFMPEG). - * \param format The pixel format to translate. - * \return Translated pixel format. + * \brief Translate stream type (Game API to RetroPlayer). + * \param gameType The stream type to translate. + * \param[out] retroType The translated stream type. + * \return True if the Game API type was translated to a valid RetroPlayer type */ - static AVPixelFormat TranslatePixelFormat(GAME_PIXEL_FORMAT format); + static bool TranslateStreamType(GAME_STREAM_TYPE gameType, RETRO::StreamType &retroType); /*! - * \brief Translate video codec (Game API to FFMPEG). - * \param format The video codec to translate. - * \return Translated video codec format. + * \brief Translate pixel format (Game API to RetroPlayer/FFMPEG). + * \param format The pixel format to translate. + * \return Translated pixel format. */ - static AVCodecID TranslateVideoCodec(GAME_VIDEO_CODEC codec); + static AVPixelFormat TranslatePixelFormat(GAME_PIXEL_FORMAT format); /*! - * \brief Translate audio PCM format (Game API to AudioEngine). + * \brief Translate audio PCM format (Game API to RetroPlayer). * \param format The audio PCM format to translate. * \return Translated audio PCM format. */ - static AEDataFormat TranslatePCMFormat(GAME_PCM_FORMAT format); + static RETRO::PCMFormat TranslatePCMFormat(GAME_PCM_FORMAT format); /*! - * \brief Translate audio channels (Game API to AudioEngine). + * \brief Translate audio channels (Game API to RetroPlayer). * \param format The audio channels to translate. * \return Translated audio channels. */ - static AEChannel TranslateAudioChannel(GAME_AUDIO_CHANNEL channel); + static RETRO::AudioChannel TranslateAudioChannel(GAME_AUDIO_CHANNEL channel); /*! - * \brief Translate audio codec (Game API to FFMPEG). - * \param format The audio codec to translate. - * \return Translated audio codec format. + * \brief Translate video rotation (Game API to RetroPlayer). + * \param rotation The video rotation to translate. + * \return Translated video rotation. */ - static AVCodecID TranslateAudioCodec(GAME_AUDIO_CODEC codec); + static RETRO::VideoRotation TranslateRotation(GAME_VIDEO_ROTATION rotation); /*! * \brief Translate key modifiers (Kodi to Game API). diff --git a/xbmc/games/addons/streams/CMakeLists.txt b/xbmc/games/addons/streams/CMakeLists.txt new file mode 100644 index 0000000000..bd821a14b7 --- /dev/null +++ b/xbmc/games/addons/streams/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SOURCES GameClientStreamAudio.cpp + GameClientStreams.cpp + GameClientStreamVideo.cpp +) + +set(HEADERS GameClientStreamAudio.h + GameClientStreams.h + GameClientStreamVideo.h + IGameClientStream.h +) + +core_add_library(game_addon_streams) diff --git a/xbmc/games/addons/streams/GameClientStreamAudio.cpp b/xbmc/games/addons/streams/GameClientStreamAudio.cpp new file mode 100644 index 0000000000..d227508ff3 --- /dev/null +++ b/xbmc/games/addons/streams/GameClientStreamAudio.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientStreamAudio.h" +#include "cores/RetroPlayer/streams/RetroPlayerAudio.h" +#include "games/addons/GameClientTranslator.h" +#include "utils/log.h" + +using namespace KODI; +using namespace GAME; + +CGameClientStreamAudio::CGameClientStreamAudio(double sampleRate) : + m_sampleRate(sampleRate) +{ +} + +bool CGameClientStreamAudio::OpenStream(RETRO::IRetroPlayerStream* stream, const game_stream_properties& properties) +{ + RETRO::CRetroPlayerAudio* audioStream = dynamic_cast<RETRO::CRetroPlayerAudio*>(stream); + if (audioStream == nullptr) + { + CLog::Log(LOGERROR, "GAME: RetroPlayer stream is not an audio stream"); + return false; + } + + std::unique_ptr<RETRO::AudioStreamProperties> audioProperties(TranslateProperties(properties.audio, m_sampleRate)); + if (audioProperties) + { + if (audioStream->OpenStream(reinterpret_cast<const RETRO::StreamProperties&>(*audioProperties))) + m_stream = stream; + } + + return m_stream != nullptr; +} + +void CGameClientStreamAudio::CloseStream() +{ + m_stream->CloseStream(); + m_stream = nullptr; +} + +void CGameClientStreamAudio::AddData(const game_stream_packet &packet) +{ + if (packet.type != GAME_STREAM_AUDIO) + return; + + if (m_stream != nullptr) + { + const game_stream_audio_packet &audio = packet.audio; + + RETRO::AudioStreamPacket audioPacket{ + audio.data, + audio.size + }; + + m_stream->AddStreamData(reinterpret_cast<RETRO::StreamPacket&>(audioPacket)); + } +} + +RETRO::AudioStreamProperties* CGameClientStreamAudio::TranslateProperties(const game_stream_audio_properties &properties, double sampleRate) +{ + const RETRO::PCMFormat pcmFormat = CGameClientTranslator::TranslatePCMFormat(properties.format); + if (pcmFormat == RETRO::PCMFormat::FMT_UNKNOWN) + { + CLog::Log(LOGERROR, "GAME: Unknown PCM format: %d", static_cast<int>(properties.format)); + return nullptr; + } + + RETRO::AudioChannelMap channelMap = { { RETRO::AudioChannel::CH_NULL } }; + unsigned int i = 0; + if (properties.channel_map != nullptr) + { + for (const GAME_AUDIO_CHANNEL* channelPtr = properties.channel_map; + *channelPtr != GAME_CH_NULL; + channelPtr++) + { + RETRO::AudioChannel channel = CGameClientTranslator::TranslateAudioChannel(*channelPtr); + if (channel == RETRO::AudioChannel::CH_NULL) + { + CLog::Log(LOGERROR, "GAME: Unknown channel ID: %d", static_cast<int>(*channelPtr)); + return nullptr; + } + + channelMap[i++] = channel; + if (i + 1 >= channelMap.size()) + break; + } + } + channelMap[i] = RETRO::AudioChannel::CH_NULL; + + if (channelMap[0] == RETRO::AudioChannel::CH_NULL) + { + CLog::Log(LOGERROR, "GAME: Empty channel layout"); + return nullptr; + } + + return new RETRO::AudioStreamProperties{ + pcmFormat, + sampleRate, + channelMap + }; +} diff --git a/xbmc/games/addons/streams/GameClientStreamAudio.h b/xbmc/games/addons/streams/GameClientStreamAudio.h new file mode 100644 index 0000000000..0b4725319c --- /dev/null +++ b/xbmc/games/addons/streams/GameClientStreamAudio.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IGameClientStream.h" +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" + +#include <vector> + +namespace KODI +{ +namespace RETRO +{ + class IRetroPlayerStream; + struct AudioStreamProperties; +} + +namespace GAME +{ + +class CGameClientStreamAudio : public IGameClientStream +{ +public: + CGameClientStreamAudio(double sampleRate); + ~CGameClientStreamAudio() override = default; + + // Implementation of IGameClientStream + bool OpenStream(RETRO::IRetroPlayerStream* stream, + const game_stream_properties& properties) override; + void CloseStream() override; + void AddData(const game_stream_packet &packet) override; + +private: + // Utility functions + static RETRO::AudioStreamProperties* TranslateProperties(const game_stream_audio_properties &properties, double sampleRate); + + // Construction parameters + double m_sampleRate; + + // Stream parameters + RETRO::IRetroPlayerStream* m_stream; +}; + +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/addons/streams/GameClientStreamVideo.cpp b/xbmc/games/addons/streams/GameClientStreamVideo.cpp new file mode 100644 index 0000000000..7b829c378f --- /dev/null +++ b/xbmc/games/addons/streams/GameClientStreamVideo.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientStreamVideo.h" +#include "cores/RetroPlayer/streams/RetroPlayerVideo.h" +#include "games/addons/GameClientTranslator.h" +#include "utils/log.h" + +using namespace KODI; +using namespace GAME; + +bool CGameClientStreamVideo::OpenStream(RETRO::IRetroPlayerStream* stream, const game_stream_properties& properties) +{ + RETRO::CRetroPlayerVideo* videoStream = dynamic_cast<RETRO::CRetroPlayerVideo*>(stream); + if (videoStream == nullptr) + { + CLog::Log(LOGERROR, "GAME: RetroPlayer stream is not a video stream"); + return false; + } + + std::unique_ptr<RETRO::VideoStreamProperties> videoProperties(TranslateProperties(properties.video)); + if (videoProperties) + { + if (videoStream->OpenStream(reinterpret_cast<const RETRO::StreamProperties&>(*videoProperties))) + m_stream = stream; + } + + return m_stream != nullptr; +} + +void CGameClientStreamVideo::CloseStream() +{ + m_stream->CloseStream(); + m_stream = nullptr; +} + +void CGameClientStreamVideo::AddData(const game_stream_packet& packet) +{ + if (packet.type != GAME_STREAM_VIDEO) + return; + + if (m_stream != nullptr) + { + const game_stream_video_packet& video = packet.video; + + RETRO::VideoRotation rotation = CGameClientTranslator::TranslateRotation(video.rotation); + + RETRO::VideoStreamPacket videoPacket{ + video.width, + video.height, + rotation, + video.data, + video.size, + }; + + m_stream->AddStreamData(reinterpret_cast<const RETRO::StreamPacket&>(videoPacket)); + } +} + +RETRO::VideoStreamProperties* CGameClientStreamVideo::TranslateProperties(const game_stream_video_properties &properties) +{ + const AVPixelFormat pixelFormat = CGameClientTranslator::TranslatePixelFormat(properties.format); + if (pixelFormat == AV_PIX_FMT_NONE) + { + CLog::Log(LOGERROR, "GAME: Unknown pixel format: %d", properties.format); + return nullptr; + } + + const unsigned int nominalWidth = properties.nominal_width; + const unsigned int nominalHeight = properties.nominal_height; + if (nominalWidth == 0 || nominalHeight == 0) + { + CLog::Log(LOGERROR, "GAME: Invalid nominal dimensions: %ux%u", nominalWidth, nominalHeight); + return nullptr; + } + + const unsigned int maxWidth = properties.max_width; + const unsigned int maxHeight = properties.max_height; + if (maxWidth == 0 || maxHeight == 0) + { + CLog::Log(LOGERROR, "GAME: Invalid max dimensions: %ux%u", maxWidth, maxHeight); + return nullptr; + } + + if (nominalWidth > maxWidth || nominalHeight > maxHeight) + CLog::Log(LOGERROR, "GAME: Nominal dimensions (%ux%u) bigger than max dimensions (%ux%u)", nominalWidth, nominalHeight, maxWidth, maxHeight); + + float pixelAspectRatio; + + // Game API: If aspect_ratio is <= 0.0, an aspect ratio of + // (nominal_width / nominal_height) is assumed + if (properties.aspect_ratio <= 0.0) + pixelAspectRatio = 1.0f; + else + pixelAspectRatio = properties.aspect_ratio * nominalHeight / nominalWidth; + + return new RETRO::VideoStreamProperties{ + pixelFormat, + nominalWidth, + nominalHeight, + maxWidth, + maxHeight, + pixelAspectRatio + }; +} diff --git a/xbmc/games/addons/streams/GameClientStreamVideo.h b/xbmc/games/addons/streams/GameClientStreamVideo.h new file mode 100644 index 0000000000..a5aa4b7e1a --- /dev/null +++ b/xbmc/games/addons/streams/GameClientStreamVideo.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IGameClientStream.h" + +struct game_stream_video_properties; + +namespace KODI +{ +namespace RETRO +{ + class IRetroPlayerStream; + struct VideoStreamProperties; +} + +namespace GAME +{ + +class CGameClientStreamVideo : public IGameClientStream +{ +public: + CGameClientStreamVideo() = default; + ~CGameClientStreamVideo() override = default; + + // Implementation of IGameClientStream + bool OpenStream(RETRO::IRetroPlayerStream* stream, + const game_stream_properties& properties) override; + void CloseStream() override; + void AddData(const game_stream_packet& packet) override; + +private: + // Utility functions + static RETRO::VideoStreamProperties* TranslateProperties(const game_stream_video_properties &properties); + + // Stream parameters + RETRO::IRetroPlayerStream* m_stream; +}; + +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/addons/streams/GameClientStreams.cpp b/xbmc/games/addons/streams/GameClientStreams.cpp new file mode 100644 index 0000000000..915e579b8b --- /dev/null +++ b/xbmc/games/addons/streams/GameClientStreams.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientStreams.h" +#include "GameClientStreamAudio.h" +#include "GameClientStreamVideo.h" +#include "cores/RetroPlayer/streams/IRetroPlayerStream.h" +#include "cores/RetroPlayer/streams/IStreamManager.h" +#include "cores/RetroPlayer/streams/RetroPlayerStreamTypes.h" +#include "games/addons/GameClient.h" +#include "games/addons/GameClientTranslator.h" +#include "utils/log.h" + +#include <memory> + +using namespace KODI; +using namespace GAME; + +CGameClientStreams::CGameClientStreams(CGameClient &gameClient) : + m_gameClient(gameClient) +{ +} + +void CGameClientStreams::Initialize(RETRO::IStreamManager& streamManager) +{ + m_streamManager = &streamManager; +} + +void CGameClientStreams::Deinitialize() +{ + m_streamManager = nullptr; +} + +IGameClientStream *CGameClientStreams::OpenStream(const game_stream_properties &properties) +{ + if (m_streamManager == nullptr) + return nullptr; + + RETRO::StreamType retroStreamType; + if (!CGameClientTranslator::TranslateStreamType(properties.type, retroStreamType)) + { + CLog::Log(LOGERROR, "GAME: Invalid stream type: %d", static_cast<int>(properties.type)); + return nullptr; + } + + std::unique_ptr<IGameClientStream> gameStream = CreateStream(properties.type); + if (!gameStream) + { + CLog::Log(LOGERROR, "GAME: No stream implementation for type: %d", static_cast<int>(properties.type)); + return nullptr; + } + + RETRO::StreamPtr retroStream = m_streamManager->CreateStream(retroStreamType); + if (!retroStream) + { + CLog::Log(LOGERROR, "GAME: Invalid RetroPlayer stream type: %$d", static_cast<int>(retroStreamType)); + return nullptr; + } + + if (!gameStream->OpenStream(retroStream.get(), properties)) + { + CLog::Log(LOGERROR, "GAME: Failed to open audio stream"); + return nullptr; + } + + m_streams[gameStream.get()] = std::move(retroStream); + + return gameStream.release(); +} + +void CGameClientStreams::CloseStream(IGameClientStream *stream) +{ + if (stream != nullptr) + { + std::unique_ptr<IGameClientStream> streamHolder(stream); + m_streamManager->CloseStream(std::move(m_streams[stream])); + m_streams.erase(stream); + } +} + +std::unique_ptr<IGameClientStream> CGameClientStreams::CreateStream(GAME_STREAM_TYPE streamType) const +{ + std::unique_ptr<IGameClientStream> gameStream; + + switch (streamType) + { + case GAME_STREAM_AUDIO: + { + gameStream.reset(new CGameClientStreamAudio(m_gameClient.GetSampleRate())); + break; + } + case GAME_STREAM_VIDEO: + { + gameStream.reset(new CGameClientStreamVideo); + break; + } + default: + break; + } + + return gameStream; +} diff --git a/xbmc/games/addons/streams/GameClientStreams.h b/xbmc/games/addons/streams/GameClientStreams.h new file mode 100644 index 0000000000..b0a8f819f1 --- /dev/null +++ b/xbmc/games/addons/streams/GameClientStreams.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" +#include "cores/RetroPlayer/streams/RetroPlayerStreamTypes.h" + +#include <map> + +namespace KODI +{ +namespace RETRO +{ + class IStreamManager; +} + +namespace GAME +{ + +class CGameClient; +class IGameClientStream; + +class CGameClientStreams +{ +public: + CGameClientStreams(CGameClient &gameClient); + + void Initialize(RETRO::IStreamManager& streamManager); + void Deinitialize(); + + IGameClientStream* OpenStream(const game_stream_properties &properties); + void CloseStream(IGameClientStream* stream); + +private: + // Utility functions + std::unique_ptr<IGameClientStream> CreateStream(GAME_STREAM_TYPE streamType) const; + + // Construction parameters + CGameClient& m_gameClient; + + // Initialization parameters + RETRO::IStreamManager* m_streamManager = nullptr; + + // Stream parameters + std::map<IGameClientStream*, RETRO::StreamPtr> m_streams; +}; + +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/addons/streams/IGameClientStream.h b/xbmc/games/addons/streams/IGameClientStream.h new file mode 100644 index 0000000000..1f5bc6771c --- /dev/null +++ b/xbmc/games/addons/streams/IGameClientStream.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +struct game_stream_buffer; +struct game_stream_packet; +struct game_stream_properties; + +namespace KODI +{ +namespace RETRO +{ + class IRetroPlayerStream; +} + +namespace GAME +{ + +class IGameClientStream +{ +public: + virtual ~IGameClientStream() = default; + + /*! + * \brief Open the stream + * + * \param stream The RetroPlayer resource to take ownership of + * + * \return True if the stream was opened, false otherwise + */ + virtual bool OpenStream(RETRO::IRetroPlayerStream* stream, + const game_stream_properties& properties) = 0; + + /*! + * \brief Release the RetroPlayer stream resource + */ + virtual void CloseStream() = 0; + + /*! + * \brief Get a buffer for zero-copy stream data + * + * \param width The framebuffer width, or 0 for no width specified + * \param height The framebuffer height, or 0 for no height specified + * \param[out] buffer The buffer, or unmodified if false is returned + * + * If this returns true, buffer must be freed using ReleaseBuffer(). + * + * \return True if buffer was set, false otherwise + */ + virtual bool GetBuffer(unsigned int width, unsigned int height, game_stream_buffer& buffer) { return false; } + + /*! + * \brief Free an allocated buffer + * + * \param buffer The buffer returned from GetStreamBuffer() + */ + virtual void ReleaseBuffer(game_stream_buffer& buffer) { } + + /*! + * \brief Add a data packet to a stream + * + * \param packet The data packet + */ + virtual void AddData(const game_stream_packet& packet) = 0; +}; + +} // namespace GAME +} // namespace KODI |