diff options
Diffstat (limited to 'xbmc/cores/RetroPlayer/streams')
19 files changed, 1370 insertions, 0 deletions
diff --git a/xbmc/cores/RetroPlayer/streams/CMakeLists.txt b/xbmc/cores/RetroPlayer/streams/CMakeLists.txt new file mode 100644 index 0000000..b5acbc9 --- /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 0000000..dddf3ff --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/IRetroPlayerStream.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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; +}; + +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/IStreamManager.h b/xbmc/cores/RetroPlayer/streams/IStreamManager.h new file mode 100644 index 0000000..b490642 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/IStreamManager.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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; +}; + +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp b/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp new file mode 100644 index 0000000..2682d4d --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RPStreamManager.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RPStreamManager.h" + +#include "IRetroPlayerStream.h" +#include "RetroPlayerAudio.h" +#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: + case StreamType::SW_BUFFER: + { + return StreamPtr(new CRetroPlayerVideo(m_renderManager, m_processInfo)); + } + 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 0000000..3615329 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RPStreamManager.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp new file mode 100644 index 0000000..d4e5a05 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RetroPlayerAudio.h" + +#include "ServiceBroker.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 "utils/log.h" + +#include <cmath> + +using namespace KODI; +using namespace RETRO; + +const double MAX_DELAY = 0.3; // seconds + +CRetroPlayerAudio::CRetroPlayerAudio(CRPProcessInfo& processInfo) + : m_processInfo(processInfo), m_pAudioStream(nullptr), m_bAudioEnabled(true) +{ + CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Initializing audio"); +} + +CRetroPlayerAudio::~CRetroPlayerAudio() +{ + CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Deinitializing audio"); + + CloseStream(); +} + +bool CRetroPlayerAudio::OpenStream(const StreamProperties& properties) +{ + const AudioStreamProperties& audioProperties = + static_cast<const AudioStreamProperties&>(properties); + + const AEDataFormat pcmFormat = CAudioTranslator::TranslatePCMFormat(audioProperties.format); + if (pcmFormat == AE_FMT_INVALID) + { + CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Unknown PCM format: {}", + 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(); + + IAE* audioEngine = CServiceBroker::GetActiveAE(); + if (audioEngine == nullptr) + return false; + + CLog::Log( + LOGINFO, + "RetroPlayer[AUDIO]: Creating audio stream, format = {}, sample rate = {}, channels = {}", + CAEUtil::DataFormatToStr(pcmFormat), iSampleRate, channelLayout.Count()); + + AEAudioFormat audioFormat; + audioFormat.m_dataFormat = pcmFormat; + audioFormat.m_sampleRate = iSampleRate; + audioFormat.m_channelLayout = channelLayout; + m_pAudioStream = audioEngine->MakeStream(audioFormat); + + if (m_pAudioStream == nullptr) + { + CLog::Log(LOGERROR, "RetroPlayer[AUDIO]: Failed to create audio stream"); + return false; + } + + m_processInfo.SetAudioChannels(audioFormat.m_channelLayout); + m_processInfo.SetAudioSampleRate(audioFormat.m_sampleRate); + m_processInfo.SetAudioBitsPerSample(CAEUtil::DataFormatToUsedBits(audioFormat.m_dataFormat)); + + return true; +} + +void CRetroPlayerAudio::AddStreamData(const StreamPacket& packet) +{ + const AudioStreamPacket& audioPacket = static_cast<const AudioStreamPacket&>(packet); + + if (m_bAudioEnabled) + { + if (m_pAudioStream) + { + const double delaySecs = m_pAudioStream->GetDelay(); + + const size_t frameSize = m_pAudioStream->GetChannelCount() * + (CAEUtil::DataFormatToBits(m_pAudioStream->GetDataFormat()) >> 3); + + const unsigned int frameCount = static_cast<unsigned int>(audioPacket.size / frameSize); + + if (delaySecs > MAX_DELAY) + { + m_pAudioStream->Flush(); + CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Audio delay ({:0.2f} ms) is too high - flushing", + delaySecs * 1000); + } + + m_pAudioStream->AddData(&audioPacket.data, 0, frameCount, nullptr); + } + } +} + +void CRetroPlayerAudio::CloseStream() +{ + if (m_pAudioStream) + { + CLog::Log(LOGDEBUG, "RetroPlayer[AUDIO]: Closing audio stream"); + + m_pAudioStream.reset(); + } +} diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h new file mode 100644 index 0000000..184fd54 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerAudio.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IRetroPlayerStream.h" +#include "cores/AudioEngine/Interfaces/AE.h" + +#include <memory> + +class IAEStream; + +namespace KODI +{ +namespace RETRO +{ +class CRPProcessInfo; + +struct AudioStreamProperties : public StreamProperties +{ + AudioStreamProperties(PCMFormat format, double sampleRate, AudioChannelMap channelMap) + : format(format), sampleRate(sampleRate), channelMap(channelMap) + { + } + + PCMFormat format; + double sampleRate; + AudioChannelMap channelMap; +}; + +struct AudioStreamPacket : public StreamPacket +{ + AudioStreamPacket(const uint8_t* data, size_t size) : data(data), size(size) {} + + const uint8_t* data; + size_t size; +}; + +class CRetroPlayerAudio : public IRetroPlayerStream +{ +public: + explicit CRetroPlayerAudio(CRPProcessInfo& processInfo); + ~CRetroPlayerAudio() 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; + IAE::StreamPtr m_pAudioStream; + bool m_bAudioEnabled; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp new file mode 100644 index 0000000..7f7d0de --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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 0000000..aa5686a --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerStreamTypes.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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, +}; + +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp new file mode 100644 index 0000000..14c66a8 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RetroPlayerVideo.h" + +#include "cores/RetroPlayer/process/RPProcessInfo.h" +#include "cores/RetroPlayer/rendering/RPRenderManager.h" +#include "cores/RetroPlayer/rendering/RenderTranslator.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 = + static_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; + + CLog::Log(LOGDEBUG, + "RetroPlayer[VIDEO]: Creating video stream - format {}, nominal {}x{}, max {}x{}", + 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, + pixelAspectRatio)) + m_bOpen = true; + + return m_bOpen; +} + +bool CRetroPlayerVideo::GetStreamBuffer(unsigned int width, + unsigned int height, + StreamBuffer& buffer) +{ + VideoStreamBuffer& videoBuffer = static_cast<VideoStreamBuffer&>(buffer); + + if (m_bOpen) + return m_renderManager.GetVideoBuffer(width, height, videoBuffer); + + return false; +} + +void CRetroPlayerVideo::AddStreamData(const StreamPacket& packet) +{ + const VideoStreamPacket& videoPacket = static_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/streams/RetroPlayerVideo.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h new file mode 100644 index 0000000..8d153ab --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IRetroPlayerStream.h" +#include "cores/RetroPlayer/RetroPlayerTypes.h" + +extern "C" +{ +#include <libavutil/pixfmt.h> +} + +namespace KODI +{ +namespace RETRO +{ +class CRPProcessInfo; +class CRPRenderManager; + +struct VideoStreamProperties : public StreamProperties +{ + VideoStreamProperties(AVPixelFormat pixfmt, + unsigned int nominalWidth, + unsigned int nominalHeight, + unsigned int maxWidth, + unsigned int maxHeight, + float pixelAspectRatio) + : pixfmt(pixfmt), + nominalWidth(nominalWidth), + nominalHeight(nominalHeight), + maxWidth(maxWidth), + maxHeight(maxHeight), + pixelAspectRatio(pixelAspectRatio) + { + } + + AVPixelFormat pixfmt; + unsigned int nominalWidth; + unsigned int nominalHeight; + unsigned int maxWidth; + unsigned int maxHeight; + float pixelAspectRatio; +}; + +struct VideoStreamBuffer : public StreamBuffer +{ + VideoStreamBuffer() = default; + + VideoStreamBuffer( + AVPixelFormat pixfmt, uint8_t* data, size_t size, DataAccess access, DataAlignment alignment) + : pixfmt(pixfmt), data(data), size(size), access(access), alignment(alignment) + { + } + + AVPixelFormat pixfmt{AV_PIX_FMT_NONE}; + uint8_t* data{nullptr}; + size_t size{0}; + DataAccess access{DataAccess::READ_WRITE}; + DataAlignment alignment{DataAlignment::DATA_UNALIGNED}; +}; + +struct VideoStreamPacket : public StreamPacket +{ + VideoStreamPacket(unsigned int width, + unsigned int height, + VideoRotation rotation, + const uint8_t* data, + size_t size) + : width(width), height(height), rotation(rotation), data(data), size(size) + { + } + + 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 IRetroPlayerStream +{ +public: + CRetroPlayerVideo(CRPRenderManager& m_renderManager, CRPProcessInfo& m_processInfo); + ~CRetroPlayerVideo() override; + + // implementation of IRetroPlayerStream + bool OpenStream(const StreamProperties& properties) override; + bool GetStreamBuffer(unsigned int width, unsigned int height, StreamBuffer& buffer) override; + void AddStreamData(const StreamPacket& packet) override; + void CloseStream() override; + +private: + // Construction parameters + CRPRenderManager& m_renderManager; + CRPProcessInfo& m_processInfo; + + // Stream properties + bool m_bOpen = false; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.cpp b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.cpp new file mode 100644 index 0000000..6d52d3f --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "BasicMemoryStream.h" + +using namespace KODI; +using namespace RETRO; + +CBasicMemoryStream::CBasicMemoryStream() +{ + Reset(); +} + +void CBasicMemoryStream::Init(size_t frameSize, uint64_t maxFrameCount) +{ + Reset(); + + m_frameSize = frameSize; +} + +void CBasicMemoryStream::Reset() +{ + m_frameSize = 0; + m_frameBuffer.reset(); + m_bHasFrame = false; +} + +uint8_t* CBasicMemoryStream::BeginFrame() +{ + if (m_frameSize == 0) + return nullptr; + + if (!m_frameBuffer) + m_frameBuffer.reset(new uint8_t[m_frameSize]); + + m_bHasFrame = false; + + return m_frameBuffer.get(); +} + +void CBasicMemoryStream::SubmitFrame() +{ + if (m_frameBuffer) + m_bHasFrame = true; +} + +const uint8_t* CBasicMemoryStream::CurrentFrame() const +{ + return m_bHasFrame ? m_frameBuffer.get() : nullptr; +} diff --git a/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.h new file mode 100644 index 0000000..c3cc716 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/BasicMemoryStream.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IMemoryStream.h" + +#include <memory> + +namespace KODI +{ +namespace RETRO +{ +class CBasicMemoryStream : public IMemoryStream +{ +public: + CBasicMemoryStream(); + + ~CBasicMemoryStream() override = default; + + // implementation of IMemoryStream + void Init(size_t frameSize, uint64_t maxFrameCount) override; + void Reset() override; + size_t FrameSize() const override { return m_frameSize; } + uint64_t MaxFrameCount() const override { return 1; } + void SetMaxFrameCount(uint64_t maxFrameCount) override {} + uint8_t* BeginFrame() override; + void SubmitFrame() override; + const uint8_t* CurrentFrame() const override; + uint64_t FutureFramesAvailable() const override { return 0; } + uint64_t AdvanceFrames(uint64_t frameCount) override { return 0; } + uint64_t PastFramesAvailable() const override { return 0; } + uint64_t RewindFrames(uint64_t frameCount) override { return 0; } + uint64_t GetFrameCounter() const override { return 0; } + void SetFrameCounter(uint64_t frameCount) override{}; + +private: + size_t m_frameSize; + std::unique_ptr<uint8_t[]> m_frameBuffer; + bool m_bHasFrame; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/memory/CMakeLists.txt b/xbmc/cores/RetroPlayer/streams/memory/CMakeLists.txt new file mode 100644 index 0000000..7297bc6 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SOURCES BasicMemoryStream.cpp + DeltaPairMemoryStream.cpp + LinearMemoryStream.cpp +) + +set(HEADERS BasicMemoryStream.h + DeltaPairMemoryStream.h + IMemoryStream.h + LinearMemoryStream.h +) + +core_add_library(retroplayer_memory) diff --git a/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.cpp b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.cpp new file mode 100644 index 0000000..eae65cd --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DeltaPairMemoryStream.h" + +#include "utils/log.h" + +using namespace KODI; +using namespace RETRO; + +void CDeltaPairMemoryStream::Reset() +{ + CLinearMemoryStream::Reset(); + + m_rewindBuffer.clear(); +} + +void CDeltaPairMemoryStream::SubmitFrameInternal() +{ + m_rewindBuffer.emplace_back(); + MemoryFrame& frame = m_rewindBuffer.back(); + + // Record frame history + frame.frameHistoryCount = m_currentFrameHistory++; + + uint32_t* currentFrame = m_currentFrame.get(); + uint32_t* nextFrame = m_nextFrame.get(); + + for (size_t i = 0; i < m_paddedFrameSize; i++) + { + uint32_t xor_val = currentFrame[i] ^ nextFrame[i]; + if (xor_val) + { + DeltaPair pair = {i, xor_val}; + frame.buffer.push_back(pair); + } + } + + // Delta is generated, bring the new frame forward (m_nextFrame is now disposable) + std::swap(m_currentFrame, m_nextFrame); + + m_bHasNextFrame = false; + + if (PastFramesAvailable() + 1 > MaxFrameCount()) + CullPastFrames(1); +} + +uint64_t CDeltaPairMemoryStream::PastFramesAvailable() const +{ + return static_cast<uint64_t>(m_rewindBuffer.size()); +} + +uint64_t CDeltaPairMemoryStream::RewindFrames(uint64_t frameCount) +{ + uint64_t rewound; + + for (rewound = 0; rewound < frameCount; rewound++) + { + if (m_rewindBuffer.empty()) + break; + + const MemoryFrame& frame = m_rewindBuffer.back(); + const DeltaPair* buffer = frame.buffer.data(); + + size_t bufferSize = frame.buffer.size(); + + // buffer pointer redirection violates data-dependency requirements... + // no vectorization for us :( + for (size_t i = 0; i < bufferSize; i++) + m_currentFrame[buffer[i].pos] ^= buffer[i].delta; + + // Restore frame history + m_currentFrameHistory = frame.frameHistoryCount; + + m_rewindBuffer.pop_back(); + } + + return rewound; +} + +void CDeltaPairMemoryStream::CullPastFrames(uint64_t frameCount) +{ + for (uint64_t removedCount = 0; removedCount < frameCount; removedCount++) + { + if (m_rewindBuffer.empty()) + { + CLog::Log(LOGDEBUG, + "CDeltaPairMemoryStream: Tried to cull {} frames too many. Check your math!", + frameCount - removedCount); + break; + } + m_rewindBuffer.pop_front(); + } +} diff --git a/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h new file mode 100644 index 0000000..b2a7cc8 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/DeltaPairMemoryStream.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "LinearMemoryStream.h" + +#include <deque> +#include <vector> + +namespace KODI +{ +namespace RETRO +{ +/*! + * \brief Implementation of a linear memory stream using XOR deltas + */ +class CDeltaPairMemoryStream : public CLinearMemoryStream +{ +public: + CDeltaPairMemoryStream() = default; + + ~CDeltaPairMemoryStream() override = default; + + // implementation of IMemoryStream via CLinearMemoryStream + void Reset() override; + uint64_t PastFramesAvailable() const override; + uint64_t RewindFrames(uint64_t frameCount) override; + +protected: + // implementation of CLinearMemoryStream + void SubmitFrameInternal() override; + void CullPastFrames(uint64_t frameCount) override; + + /*! + * Rewinding is implemented by applying XOR deltas on the specific parts of + * the save state buffer which have changed. In practice, this is very fast + * and simple (linear scan) and allows deltas to be compressed down to 1-3% + * of original save state size depending on the system. The algorithm runs + * on 32 bits at a time for speed. + * + * Use std::deque here to achieve amortized O(1) on pop/push to front and + * back. + */ + struct DeltaPair + { + size_t pos; + uint32_t delta; + }; + + using DeltaPairVector = std::vector<DeltaPair>; + + struct MemoryFrame + { + DeltaPairVector buffer; + uint64_t frameHistoryCount; + }; + + std::deque<MemoryFrame> m_rewindBuffer; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/memory/IMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/IMemoryStream.h new file mode 100644 index 0000000..379a6a4 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/IMemoryStream.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> + +namespace KODI +{ +namespace RETRO +{ +/*! + * \brief Stream of serialized states from game clients + * + * A memory stream is composed of "frames" of memory representing serialized + * states of the game client. For each video frame run by the game loop, the + * game client's state is serialized into a buffer provided by this interface. + * + * Implementation of three types of memory streams are provided: + * + * - Basic memory stream: has only a current frame, and supports neither + * rewind nor forward seeking. + * + * \sa CBasicMemoryStream + * + * - Linear memory stream: can grow in one direction. It is possible to + * rewind, but not fast-forward. + * + * \sa CLinearMemoryStream + * + * - Nonlinear memory stream: can have frames both ahead of and behind + * the current frame. If a stream is rewound, it is possible to + * recover these frames by seeking forward again. + * + * \sa CNonlinearMemoryStream (TODO) + */ +class IMemoryStream +{ +public: + virtual ~IMemoryStream() = default; + + /*! + * \brief Initialize memory stream + * + * \param frameSize The size of the serialized memory state + * \param maxFrameCount The maximum number of frames this stream can hold + */ + virtual void Init(size_t frameSize, uint64_t maxFrameCount) = 0; + + /*! + * \brief Free any resources used by this stream + */ + virtual void Reset() = 0; + + /*! + * \brief Return the frame size passed to Init() + */ + virtual size_t FrameSize() const = 0; + + /*! + * \brief Return the current max frame count + */ + virtual uint64_t MaxFrameCount() const = 0; + + /*! + * \brief Update the max frame count + * + * Old frames may be deleted if the max frame count is reduced. + */ + virtual void SetMaxFrameCount(uint64_t maxFrameCount) = 0; + + /*! + * \ brief Get a pointer to which FrameSize() bytes can be written + * + * The buffer exposed by this function is passed to the game client, which + * fills it with a serialization of its current state. + */ + virtual uint8_t* BeginFrame() = 0; + + /*! + * \brief Indicate that a frame of size FrameSize() has been written to the + * location returned from BeginFrame() + */ + virtual void SubmitFrame() = 0; + + /*! + * \brief Get a pointer to the current frame + * + * This function must have no side effects. The pointer is valid until the + * stream is modified. + * + * \return A buffer of size FrameSize(), or nullptr if the stream is empty + */ + virtual const uint8_t* CurrentFrame() const = 0; + + /*! + * \brief Return the number of frames ahead of the current frame + * + * If the stream supports forward seeking, frames that are passed over + * during a "rewind" operation can be recovered again. + */ + virtual uint64_t FutureFramesAvailable() const = 0; + + /*! + * \brief Seek ahead the specified number of frames + * + * \return The number of frames advanced + */ + virtual uint64_t AdvanceFrames(uint64_t frameCount) = 0; + + /*! + * \brief Return the number of frames behind the current frame + */ + virtual uint64_t PastFramesAvailable() const = 0; + + /*! + * \brief Seek backwards the specified number of frames + * + * \return The number of frames rewound + */ + virtual uint64_t RewindFrames(uint64_t frameCount) = 0; + + /*! + * \brief Get the total number of frames played until the current frame + * + * \return The history of the current frame, or 0 for unknown + */ + virtual uint64_t GetFrameCounter() const = 0; + + /*! + * \brief Set the total number of frames played until the current frame + * + * \param frameCount The history of the current frame + */ + virtual void SetFrameCounter(uint64_t frameCount) = 0; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.cpp b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.cpp new file mode 100644 index 0000000..65a7959 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LinearMemoryStream.h" + +using namespace KODI; +using namespace RETRO; + +// Pad forward to nearest boundary of bytes +#define PAD_TO_CEIL(x, bytes) ((((x) + (bytes)-1) / (bytes)) * (bytes)) + +CLinearMemoryStream::CLinearMemoryStream() +{ + Reset(); +} + +void CLinearMemoryStream::Init(size_t frameSize, uint64_t maxFrameCount) +{ + Reset(); + + m_frameSize = frameSize; + m_paddedFrameSize = PAD_TO_CEIL(m_frameSize, sizeof(uint32_t)); + m_maxFrames = maxFrameCount; +} + +void CLinearMemoryStream::Reset() +{ + m_frameSize = 0; + m_paddedFrameSize = 0; + m_maxFrames = 0; + m_currentFrame.reset(); + m_nextFrame.reset(); + m_bHasCurrentFrame = false; + m_bHasNextFrame = false; + m_currentFrameHistory = 0; +} + +void CLinearMemoryStream::SetMaxFrameCount(uint64_t maxFrameCount) +{ + if (maxFrameCount == 0) + { + Reset(); + } + else + { + const uint64_t frameCount = BufferSize(); + if (maxFrameCount < frameCount) + CullPastFrames(frameCount - maxFrameCount); + } + + m_maxFrames = maxFrameCount; +} + +uint8_t* CLinearMemoryStream::BeginFrame() +{ + if (m_paddedFrameSize == 0) + return nullptr; + + if (!m_bHasCurrentFrame) + { + if (!m_currentFrame) + m_currentFrame.reset(new uint32_t[m_paddedFrameSize]); + return reinterpret_cast<uint8_t*>(m_currentFrame.get()); + } + + if (!m_nextFrame) + m_nextFrame.reset(new uint32_t[m_paddedFrameSize]); + return reinterpret_cast<uint8_t*>(m_nextFrame.get()); +} + +const uint8_t* CLinearMemoryStream::CurrentFrame() const +{ + if (m_bHasCurrentFrame) + return reinterpret_cast<const uint8_t*>(m_currentFrame.get()); + + return nullptr; +} + +void CLinearMemoryStream::SubmitFrame() +{ + if (!m_bHasCurrentFrame) + { + m_bHasCurrentFrame = true; + } + else if (!m_bHasNextFrame) + { + m_bHasNextFrame = true; + } + + if (m_bHasNextFrame) + { + SubmitFrameInternal(); + } +} + +uint64_t CLinearMemoryStream::BufferSize() const +{ + return PastFramesAvailable() + (m_bHasCurrentFrame ? 1 : 0); +} diff --git a/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.h b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.h new file mode 100644 index 0000000..f7b9465 --- /dev/null +++ b/xbmc/cores/RetroPlayer/streams/memory/LinearMemoryStream.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IMemoryStream.h" + +#include <memory> +#include <stdint.h> + +namespace KODI +{ +namespace RETRO +{ +class CLinearMemoryStream : public IMemoryStream +{ +public: + CLinearMemoryStream(); + + ~CLinearMemoryStream() override = default; + + // partial implementation of IMemoryStream + void Init(size_t frameSize, uint64_t maxFrameCount) override; + void Reset() override; + size_t FrameSize() const override { return m_frameSize; } + uint64_t MaxFrameCount() const override { return m_maxFrames; } + void SetMaxFrameCount(uint64_t maxFrameCount) override; + uint8_t* BeginFrame() override; + void SubmitFrame() override; + const uint8_t* CurrentFrame() const override; + uint64_t FutureFramesAvailable() const override { return 0; } + uint64_t AdvanceFrames(uint64_t frameCount) override { return 0; } + uint64_t PastFramesAvailable() const override = 0; + uint64_t RewindFrames(uint64_t frameCount) override = 0; + uint64_t GetFrameCounter() const override { return m_currentFrameHistory; } + void SetFrameCounter(uint64_t frameCount) override { m_currentFrameHistory = frameCount; } + +protected: + virtual void SubmitFrameInternal() = 0; + virtual void CullPastFrames(uint64_t frameCount) = 0; + + // Helper function + uint64_t BufferSize() const; + + size_t m_paddedFrameSize; + uint64_t m_maxFrames; + + /** + * Simple double-buffering. After XORing the two states, the next becomes + * the current, and the current becomes a buffer for the next call to + * CGameClient::Serialize(). + */ + std::unique_ptr<uint32_t[]> m_currentFrame; + std::unique_ptr<uint32_t[]> m_nextFrame; + bool m_bHasCurrentFrame; + bool m_bHasNextFrame; + + uint64_t m_currentFrameHistory; + +private: + size_t m_frameSize; +}; +} // namespace RETRO +} // namespace KODI |