summaryrefslogtreecommitdiffstats
path: root/xbmc/cores/paplayer
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/cores/paplayer
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/cores/paplayer')
-rw-r--r--xbmc/cores/paplayer/AudioDecoder.cpp401
-rw-r--r--xbmc/cores/paplayer/AudioDecoder.h91
-rw-r--r--xbmc/cores/paplayer/CMakeLists.txt13
-rw-r--r--xbmc/cores/paplayer/CachingCodec.h18
-rw-r--r--xbmc/cores/paplayer/CodecFactory.cpp112
-rw-r--r--xbmc/cores/paplayer/CodecFactory.h23
-rw-r--r--xbmc/cores/paplayer/ICodec.h82
-rw-r--r--xbmc/cores/paplayer/PAPlayer.cpp1207
-rw-r--r--xbmc/cores/paplayer/PAPlayer.h154
-rw-r--r--xbmc/cores/paplayer/VideoPlayerCodec.cpp533
-rw-r--r--xbmc/cores/paplayer/VideoPlayerCodec.h65
11 files changed, 2699 insertions, 0 deletions
diff --git a/xbmc/cores/paplayer/AudioDecoder.cpp b/xbmc/cores/paplayer/AudioDecoder.cpp
new file mode 100644
index 0000000..03dc907
--- /dev/null
+++ b/xbmc/cores/paplayer/AudioDecoder.cpp
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2005-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 "AudioDecoder.h"
+
+#include "CodecFactory.h"
+#include "FileItem.h"
+#include "ICodec.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include <cmath>
+#include <mutex>
+
+CAudioDecoder::CAudioDecoder()
+{
+ m_codec = NULL;
+ m_rawBuffer = nullptr;
+
+ m_eof = false;
+
+ m_status = STATUS_NO_FILE;
+ m_canPlay = false;
+
+ // output buffer (for transferring data from the Pcm Buffer to the rest of the audio chain)
+ memset(&m_outputBuffer, 0, OUTPUT_SAMPLES * sizeof(float));
+ memset(&m_pcmInputBuffer, 0, INPUT_SIZE * sizeof(unsigned char));
+ memset(&m_inputBuffer, 0, INPUT_SAMPLES * sizeof(float));
+
+ m_rawBufferSize = 0;
+}
+
+CAudioDecoder::~CAudioDecoder()
+{
+ Destroy();
+}
+
+void CAudioDecoder::Destroy()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_status = STATUS_NO_FILE;
+
+ m_pcmBuffer.Destroy();
+
+ if ( m_codec )
+ delete m_codec;
+ m_codec = NULL;
+
+ m_canPlay = false;
+}
+
+bool CAudioDecoder::Create(const CFileItem &file, int64_t seekOffset)
+{
+ Destroy();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // reset our playback timing variables
+ m_eof = false;
+
+ // get correct cache size
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ unsigned int filecache = settings->GetInt(CSettings::SETTING_CACHEAUDIO_INTERNET);
+ if ( file.IsHD() )
+ filecache = settings->GetInt(CSettings::SETTING_CACHE_HARDDISK);
+ else if ( file.IsOnDVD() )
+ filecache = settings->GetInt(CSettings::SETTING_CACHEAUDIO_DVDROM);
+ else if ( file.IsOnLAN() )
+ filecache = settings->GetInt(CSettings::SETTING_CACHEAUDIO_LAN);
+
+ // create our codec
+ m_codec=CodecFactory::CreateCodecDemux(file, filecache * 1024);
+
+ if (!m_codec || !m_codec->Init(file, filecache * 1024))
+ {
+ CLog::Log(LOGERROR, "CAudioDecoder: Unable to Init Codec while loading file {}",
+ file.GetDynPath());
+ Destroy();
+ return false;
+ }
+ unsigned int blockSize = (m_codec->m_bitsPerSample >> 3) * m_codec->m_format.m_channelLayout.Count();
+
+ if (blockSize == 0)
+ {
+ CLog::Log(LOGERROR, "CAudioDecoder: Codec provided invalid parameters ({}-bit, {} channels)",
+ m_codec->m_bitsPerSample, GetFormat().m_channelLayout.Count());
+ return false;
+ }
+
+ /* allocate the pcmBuffer for 2 seconds of audio */
+ m_pcmBuffer.Create(2 * blockSize * m_codec->m_format.m_sampleRate);
+
+ if (file.HasMusicInfoTag())
+ {
+ // set total time from the given tag
+ if (file.GetMusicInfoTag()->GetDuration())
+ m_codec->SetTotalTime(file.GetMusicInfoTag()->GetDuration());
+
+ // update ReplayGain from the given tag if it's better then original (cuesheet)
+ ReplayGain rgInfo = m_codec->m_tag.GetReplayGain();
+ bool anySet = false;
+ if (!rgInfo.Get(ReplayGain::ALBUM).Valid()
+ && file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::ALBUM).Valid())
+ {
+ rgInfo.Set(ReplayGain::ALBUM, file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::ALBUM));
+ anySet = true;
+ }
+ if (!rgInfo.Get(ReplayGain::TRACK).Valid()
+ && file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::TRACK).Valid())
+ {
+ rgInfo.Set(ReplayGain::TRACK, file.GetMusicInfoTag()->GetReplayGain().Get(ReplayGain::TRACK));
+ anySet = true;
+ }
+ if (anySet)
+ m_codec->m_tag.SetReplayGain(rgInfo);
+ }
+
+ if (seekOffset)
+ m_codec->Seek(seekOffset);
+
+ m_status = STATUS_QUEUING;
+
+ m_rawBufferSize = 0;
+
+ return true;
+}
+
+AEAudioFormat CAudioDecoder::GetFormat()
+{
+ AEAudioFormat format;
+ if (!m_codec)
+ return format;
+ return m_codec->m_format;
+}
+
+unsigned int CAudioDecoder::GetChannels()
+{
+ return GetFormat().m_channelLayout.Count();
+}
+
+int64_t CAudioDecoder::Seek(int64_t time)
+{
+ m_pcmBuffer.Clear();
+ m_rawBufferSize = 0;
+ if (!m_codec)
+ return 0;
+ if (time < 0) time = 0;
+ if (time > m_codec->m_TotalTime) time = m_codec->m_TotalTime;
+ return m_codec->Seek(time);
+}
+
+void CAudioDecoder::SetTotalTime(int64_t time)
+{
+ if (m_codec)
+ m_codec->m_TotalTime = time;
+}
+
+int64_t CAudioDecoder::TotalTime()
+{
+ if (m_codec)
+ return m_codec->m_TotalTime;
+ return 0;
+}
+
+unsigned int CAudioDecoder::GetDataSize(bool checkPktSize)
+{
+ if (m_status == STATUS_QUEUING || m_status == STATUS_NO_FILE)
+ return 0;
+
+ if (m_codec->m_format.m_dataFormat != AE_FMT_RAW)
+ {
+ // check for end of file and end of buffer
+ if (m_status == STATUS_ENDING)
+ {
+ if (m_pcmBuffer.getMaxReadSize() == 0)
+ m_status = STATUS_ENDED;
+ else if (checkPktSize && m_pcmBuffer.getMaxReadSize() < PACKET_SIZE)
+ m_status = STATUS_ENDED;
+ }
+ return std::min(m_pcmBuffer.getMaxReadSize() / (m_codec->m_bitsPerSample >> 3), (unsigned int)OUTPUT_SAMPLES);
+ }
+ else
+ {
+ if (m_status == STATUS_ENDING)
+ m_status = STATUS_ENDED;
+ return m_rawBufferSize;
+ }
+}
+
+void *CAudioDecoder::GetData(unsigned int samples)
+{
+ unsigned int size = samples * (m_codec->m_bitsPerSample >> 3);
+ if (size > sizeof(m_outputBuffer))
+ {
+ CLog::Log(LOGERROR, "CAudioDecoder::GetData - More data was requested then we have space to buffer!");
+ return NULL;
+ }
+
+ if (size > m_pcmBuffer.getMaxReadSize())
+ {
+ CLog::Log(
+ LOGWARNING,
+ "CAudioDecoder::GetData() more bytes/samples ({}) requested than we have to give ({})!",
+ size, m_pcmBuffer.getMaxReadSize());
+ size = m_pcmBuffer.getMaxReadSize();
+ }
+
+ if (m_pcmBuffer.ReadData((char *)m_outputBuffer, size))
+ {
+ if (m_status == STATUS_ENDING && m_pcmBuffer.getMaxReadSize() == 0)
+ m_status = STATUS_ENDED;
+
+ return m_outputBuffer;
+ }
+
+ CLog::Log(LOGERROR, "CAudioDecoder::GetData() ReadBinary failed with {} samples", samples);
+ return NULL;
+}
+
+uint8_t *CAudioDecoder::GetRawData(int &size)
+{
+ if (m_status == STATUS_ENDING)
+ m_status = STATUS_ENDED;
+
+ if (m_rawBufferSize)
+ {
+ size = m_rawBufferSize;
+ m_rawBufferSize = 0;
+ return m_rawBuffer;
+ }
+ return nullptr;
+}
+
+int CAudioDecoder::ReadSamples(int numsamples)
+{
+ if (m_status == STATUS_NO_FILE || m_status == STATUS_ENDING || m_status == STATUS_ENDED)
+ return RET_SLEEP; // nothing loaded yet
+
+ // start playing once we're fully queued and we're ready to go
+ if (m_status == STATUS_QUEUED && m_canPlay)
+ m_status = STATUS_PLAYING;
+
+ // grab a lock to ensure the codec is created at this point.
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_codec->m_format.m_dataFormat != AE_FMT_RAW)
+ {
+ // Read in more data
+ int maxsize = std::min<int>(INPUT_SAMPLES, m_pcmBuffer.getMaxWriteSize() / (m_codec->m_bitsPerSample >> 3));
+ numsamples = std::min<int>(numsamples, maxsize);
+ numsamples -= (numsamples % GetFormat().m_channelLayout.Count()); // make sure it's divisible by our number of channels
+ if (numsamples)
+ {
+ size_t readSize = 0;
+ int result = m_codec->ReadPCM(
+ m_pcmInputBuffer, static_cast<size_t>(numsamples * (m_codec->m_bitsPerSample >> 3)),
+ &readSize);
+
+ if (result != READ_ERROR && readSize)
+ {
+ // move it into our buffer
+ m_pcmBuffer.WriteData((char *)m_pcmInputBuffer, readSize);
+
+ // update status
+ if (m_status == STATUS_QUEUING && m_pcmBuffer.getMaxReadSize() > m_pcmBuffer.getSize() * 0.9)
+ {
+ CLog::Log(LOGINFO, "AudioDecoder: File is queued");
+ m_status = STATUS_QUEUED;
+ }
+
+ if (result == READ_EOF) // EOF reached
+ {
+ // setup ending if we're within set time of the end (currently just EOF)
+ m_eof = true;
+ if (m_status < STATUS_ENDING)
+ m_status = STATUS_ENDING;
+ }
+
+ return RET_SUCCESS;
+ }
+ if (result == READ_ERROR)
+ {
+ // error decoding, lets finish up and get out
+ CLog::Log(LOGERROR, "CAudioDecoder: Error while decoding {}", result);
+ return RET_ERROR;
+ }
+ if (result == READ_EOF)
+ {
+ m_eof = true;
+ // setup ending if we're within set time of the end (currently just EOF)
+ if (m_status < STATUS_ENDING)
+ m_status = STATUS_ENDING;
+ }
+ }
+ }
+ else
+ {
+ if (m_rawBufferSize == 0)
+ {
+ int result = m_codec->ReadRaw(&m_rawBuffer, &m_rawBufferSize);
+ if (result == READ_SUCCESS && m_rawBufferSize)
+ {
+ //! @todo trash this useless ringbuffer
+ if (m_status == STATUS_QUEUING)
+ {
+ m_status = STATUS_QUEUED;
+ }
+ return RET_SUCCESS;
+ }
+ else if (result == READ_ERROR)
+ {
+ // error decoding, lets finish up and get out
+ CLog::Log(LOGERROR, "CAudioDecoder: Error while decoding {}", result);
+ return RET_ERROR;
+ }
+ else if (result == READ_EOF)
+ {
+ m_eof = true;
+ // setup ending if we're within set time of the end (currently just EOF)
+ if (m_status < STATUS_ENDING)
+ m_status = STATUS_ENDING;
+ }
+ }
+ }
+ return RET_SLEEP; // nothing to do
+}
+
+bool CAudioDecoder::CanSeek()
+{
+ if (m_codec)
+ return m_codec->CanSeek();
+ else
+ return false;
+}
+
+float CAudioDecoder::GetReplayGain(float &peakVal)
+{
+#define REPLAY_GAIN_DEFAULT_LEVEL 89.0f
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+
+ const auto& replayGainSettings = appVolume->GetReplayGainSettings();
+ if (replayGainSettings.iType == ReplayGain::NONE)
+ return 1.0f;
+
+ // Compute amount of gain
+ float replaydB = (float)replayGainSettings.iNoGainPreAmp;
+ float peak = 1.0f;
+ const ReplayGain& rgInfo = m_codec->m_tag.GetReplayGain();
+ if (replayGainSettings.iType == ReplayGain::ALBUM)
+ {
+ if (rgInfo.Get(ReplayGain::ALBUM).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::ALBUM).Gain();
+ if (rgInfo.Get(ReplayGain::ALBUM).HasPeak())
+ peak = rgInfo.Get(ReplayGain::ALBUM).Peak();
+ }
+ else if (rgInfo.Get(ReplayGain::TRACK).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::TRACK).Gain();
+ if (rgInfo.Get(ReplayGain::TRACK).HasPeak())
+ peak = rgInfo.Get(ReplayGain::TRACK).Peak();
+ }
+ }
+ else if (replayGainSettings.iType == ReplayGain::TRACK)
+ {
+ if (rgInfo.Get(ReplayGain::TRACK).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::TRACK).Gain();
+ if (rgInfo.Get(ReplayGain::TRACK).HasPeak())
+ peak = rgInfo.Get(ReplayGain::TRACK).Peak();
+ }
+ else if (rgInfo.Get(ReplayGain::ALBUM).HasGain())
+ {
+ replaydB = (float)replayGainSettings.iPreAmp + rgInfo.Get(ReplayGain::ALBUM).Gain();
+ if (rgInfo.Get(ReplayGain::ALBUM).HasPeak())
+ peak = rgInfo.Get(ReplayGain::ALBUM).Peak();
+ }
+ }
+ // convert to a gain type
+ float replaygain = std::pow(10.0f, (replaydB - REPLAY_GAIN_DEFAULT_LEVEL) * 0.05f);
+
+ CLog::Log(LOGDEBUG,
+ "AudioDecoder::GetReplayGain - Final Replaygain applied: {:f}, Track/Album Gain {:f}, "
+ "Peak {:f}",
+ replaygain, replaydB, peak);
+
+ peakVal = peak;
+ return replaygain;
+}
+
diff --git a/xbmc/cores/paplayer/AudioDecoder.h b/xbmc/cores/paplayer/AudioDecoder.h
new file mode 100644
index 0000000..9d0eac6
--- /dev/null
+++ b/xbmc/cores/paplayer/AudioDecoder.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2005-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 "threads/CriticalSection.h"
+#include "utils/RingBuffer.h"
+
+struct AEAudioFormat;
+class CFileItem;
+class ICodec;
+
+#define PACKET_SIZE 3840 // audio packet size - we keep 1 in reserve for gapless playback
+ // using a multiple of 1, 2, 3, 4, 5, 6 to guarantee track alignment
+ // note that 7 or higher channels won't work too well.
+
+#define INPUT_SIZE PACKET_SIZE * 3 // input data size we read from the codecs at a time
+ // * 3 to allow 24 bit audio
+
+#define OUTPUT_SAMPLES PACKET_SIZE // max number of output samples
+#define INPUT_SAMPLES PACKET_SIZE // number of input samples (distributed over channels)
+
+#define STATUS_NO_FILE 0
+#define STATUS_QUEUING 1
+#define STATUS_QUEUED 2
+#define STATUS_PLAYING 3
+#define STATUS_ENDING 4
+#define STATUS_ENDED 5
+
+// return codes from decoders
+#define RET_ERROR -1
+#define RET_SUCCESS 0
+#define RET_SLEEP 1
+
+class CAudioDecoder
+{
+public:
+ CAudioDecoder();
+ ~CAudioDecoder();
+
+ bool Create(const CFileItem &file, int64_t seekOffset);
+ void Destroy();
+
+ int ReadSamples(int numsamples);
+
+ bool CanSeek();
+ int64_t Seek(int64_t time);
+ int64_t TotalTime();
+ void SetTotalTime(int64_t time);
+ void Start() { m_canPlay = true;}; // cause a pre-buffered stream to start.
+ int GetStatus() { return m_status; }
+ void SetStatus(int status) { m_status = status; }
+
+ AEAudioFormat GetFormat();
+ unsigned int GetChannels();
+ // Data management
+ unsigned int GetDataSize(bool checkPktSize);
+ void *GetData(unsigned int samples);
+ uint8_t* GetRawData(int &size);
+ ICodec *GetCodec() const { return m_codec; }
+ float GetReplayGain(float &peakVal);
+
+private:
+ // pcm buffer
+ CRingBuffer m_pcmBuffer;
+
+ // output buffer (for transferring data from the Pcm Buffer to the rest of the audio chain)
+ float m_outputBuffer[OUTPUT_SAMPLES];
+
+ // input buffer (for transferring data from the Codecs to our Pcm Ringbuffer
+ uint8_t m_pcmInputBuffer[INPUT_SIZE];
+ float m_inputBuffer[INPUT_SAMPLES];
+
+ uint8_t *m_rawBuffer;
+ int m_rawBufferSize;
+
+ // status
+ bool m_eof;
+ int m_status;
+ bool m_canPlay;
+
+ // the codec we're using
+ ICodec* m_codec;
+
+ CCriticalSection m_critSection;
+};
diff --git a/xbmc/cores/paplayer/CMakeLists.txt b/xbmc/cores/paplayer/CMakeLists.txt
new file mode 100644
index 0000000..8da2e7c
--- /dev/null
+++ b/xbmc/cores/paplayer/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES AudioDecoder.cpp
+ CodecFactory.cpp
+ PAPlayer.cpp
+ VideoPlayerCodec.cpp)
+
+set(HEADERS AudioDecoder.h
+ CachingCodec.h
+ CodecFactory.h
+ ICodec.h
+ PAPlayer.h
+ VideoPlayerCodec.h)
+
+core_add_library(paplayer)
diff --git a/xbmc/cores/paplayer/CachingCodec.h b/xbmc/cores/paplayer/CachingCodec.h
new file mode 100644
index 0000000..55ac36b
--- /dev/null
+++ b/xbmc/cores/paplayer/CachingCodec.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2005-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 "ICodec.h"
+
+class CachingCodec : public ICodec
+{
+public:
+ virtual ~CachingCodec() {}
+ virtual int GetCacheLevel() const { return -1; }
+};
diff --git a/xbmc/cores/paplayer/CodecFactory.cpp b/xbmc/cores/paplayer/CodecFactory.cpp
new file mode 100644
index 0000000..70684d8
--- /dev/null
+++ b/xbmc/cores/paplayer/CodecFactory.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2005-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 "CodecFactory.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "VideoPlayerCodec.h"
+#include "addons/AudioDecoder.h"
+#include "addons/ExtsMimeSupportList.h"
+#include "addons/addoninfo/AddonType.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI::ADDONS;
+
+ICodec* CodecFactory::CreateCodec(const CURL& urlFile)
+{
+ std::string fileType = urlFile.GetFileType();
+ StringUtils::ToLower(fileType);
+
+ auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetExtensionSupportedAddonInfos(
+ "." + fileType, CExtsMimeSupportList::FilterSelect::all);
+ for (const auto& addonInfo : addonInfos)
+ {
+ // Check asked and given extension is supported by only for here allowed audiodecoder addons.
+ if (addonInfo.first == ADDON::AddonType::AUDIODECODER)
+ {
+ std::unique_ptr<CAudioDecoder> result = std::make_unique<CAudioDecoder>(addonInfo.second);
+ if (!result->CreateDecoder())
+ continue;
+
+ return result.release();
+ }
+ }
+
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ return dvdcodec;
+}
+
+ICodec* CodecFactory::CreateCodecDemux(const CFileItem& file, unsigned int filecache)
+{
+ CURL urlFile(file.GetDynPath());
+ std::string content = file.GetMimeType();
+ StringUtils::ToLower(content);
+ if (!content.empty())
+ {
+ auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetMimetypeSupportedAddonInfos(
+ content, CExtsMimeSupportList::FilterSelect::all);
+ for (const auto& addonInfo : addonInfos)
+ {
+ // Check asked and given mime type is supported by only for here allowed audiodecoder addons.
+ if (addonInfo.first == ADDON::AddonType::AUDIODECODER)
+ {
+ std::unique_ptr<CAudioDecoder> result = std::make_unique<CAudioDecoder>(addonInfo.second);
+ if (!result->CreateDecoder() && result->SupportsFile(file.GetPath()))
+ continue;
+
+ return result.release();
+ }
+ }
+ }
+
+ if( content == "audio/mpeg" ||
+ content == "audio/mpeg3" ||
+ content == "audio/mp3" ||
+ content == "audio/aac" ||
+ content == "audio/aacp" ||
+ content == "audio/x-ms-wma" ||
+ content == "audio/x-ape" ||
+ content == "audio/ape" ||
+ content == "application/ogg" ||
+ content == "audio/ogg" ||
+ content == "audio/x-xbmc-pcm" ||
+ content == "audio/flac" ||
+ content == "audio/x-flac" ||
+ content == "application/x-flac"
+ )
+ {
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType(content);
+ return dvdcodec;
+ }
+ else if (urlFile.IsProtocol("shout"))
+ {
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType("audio/mp3");
+ return dvdcodec; // if we got this far with internet radio - content-type was wrong. gamble on mp3.
+ }
+ else if (urlFile.IsFileType("wav") ||
+ content == "audio/wav" ||
+ content == "audio/x-wav")
+ {
+ VideoPlayerCodec *dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType("audio/x-spdif-compressed");
+ if (dvdcodec->Init(file, filecache))
+ {
+ return dvdcodec;
+ }
+
+ dvdcodec = new VideoPlayerCodec();
+ dvdcodec->SetContentType(content);
+ return dvdcodec;
+ }
+ else
+ return CreateCodec(urlFile);
+}
+
diff --git a/xbmc/cores/paplayer/CodecFactory.h b/xbmc/cores/paplayer/CodecFactory.h
new file mode 100644
index 0000000..01a2614
--- /dev/null
+++ b/xbmc/cores/paplayer/CodecFactory.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-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 "ICodec.h"
+
+class CFileItem;
+
+class CodecFactory
+{
+public:
+ CodecFactory() = default;
+ virtual ~CodecFactory() = default;
+ static ICodec* CreateCodec(const CURL& urlFile);
+ static ICodec* CreateCodecDemux(const CFileItem& file, unsigned int filecache);
+};
+
diff --git a/xbmc/cores/paplayer/ICodec.h b/xbmc/cores/paplayer/ICodec.h
new file mode 100644
index 0000000..1b45280
--- /dev/null
+++ b/xbmc/cores/paplayer/ICodec.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005-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 "cores/AudioEngine/Utils/AEAudioFormat.h"
+#include "filesystem/File.h"
+#include "music/tags/MusicInfoTag.h"
+
+#include <string>
+
+#define READ_EOF -1
+#define READ_SUCCESS 0
+#define READ_ERROR 1
+
+class CFileItem;
+
+class ICodec
+{
+public:
+ ICodec()
+ {
+ m_TotalTime = 0;
+ m_bitRate = 0;
+ m_bitsPerSample = 0;
+ m_bitsPerCodedSample = 0;
+ };
+ virtual ~ICodec() = default;
+
+ // Virtual functions that all codecs should implement. Note that these may need
+ // enhancing and or refactoring at a later date. It works currently well for MP3 and
+ // APE codecs.
+
+ // Init(filename)
+ // This routine should handle any initialization necessary. At a minimum it needs to:
+ // 1. Load any dlls and make sure any buffers etc. are allocated.
+ // 2. Load the file (or at least attempt to load it)
+ // 3. Fill in the m_TotalTime, m_SampleRate, m_BitsPerSample and m_Channels parameters.
+ virtual bool Init(const CFileItem &file, unsigned int filecache)=0;
+
+ virtual bool CanSeek() {return true;}
+
+ // Seek()
+ // Should seek to the appropriate time (in ms) in the file, and return the
+ // time to which we managed to seek (in the case where seeking is problematic)
+ // This is used in FFwd/Rewd so can be called very often.
+ virtual bool Seek(int64_t iSeekTime)=0;
+
+ // ReadPCM()
+ // Decodes audio into pBuffer up to size bytes. The actual amount of returned data
+ // is given in actualsize. Returns READ_SUCCESS on success. Returns READ_EOF when
+ // the data has been exhausted, and READ_ERROR on error.
+ virtual int ReadPCM(uint8_t* pBuffer, size_t size, size_t* actualsize) = 0;
+
+ virtual int ReadRaw(uint8_t **pBuffer, int *bufferSize) { return READ_ERROR; }
+
+ // CanInit()
+ // Should return true if the codec can be initialized
+ // eg. check if a dll needed for the codec exists
+ virtual bool CanInit()=0;
+
+ // set the total time - useful when info comes from a preset tag
+ virtual void SetTotalTime(int64_t totaltime) {}
+
+ virtual bool IsCaching() const {return false;}
+ virtual int GetCacheLevel() const {return -1;}
+
+ int64_t m_TotalTime; // time in ms
+ int m_bitRate;
+ int m_bitsPerSample;
+ int m_bitsPerCodedSample;
+ std::string m_CodecName;
+ MUSIC_INFO::CMusicInfoTag m_tag;
+ XFILE::CFile m_file;
+ AEAudioFormat m_format;
+};
+
diff --git a/xbmc/cores/paplayer/PAPlayer.cpp b/xbmc/cores/paplayer/PAPlayer.cpp
new file mode 100644
index 0000000..05bf906
--- /dev/null
+++ b/xbmc/cores/paplayer/PAPlayer.cpp
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (C) 2005-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 "PAPlayer.h"
+
+#include "FileItem.h"
+#include "ICodec.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/AEStream.h"
+#include "cores/AudioEngine/Utils/AEStreamData.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/DataCacheCore.h"
+#include "cores/VideoPlayer/Process/ProcessInfo.h"
+#include "messaging/ApplicationMessenger.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SystemClock.h"
+#include "utils/JobManager.h"
+#include "utils/log.h"
+#include "video/Bookmark.h"
+
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+#define TIME_TO_CACHE_NEXT_FILE 5000 /* 5 seconds before end of song, start caching the next song */
+#define FAST_XFADE_TIME 80 /* 80 milliseconds */
+#define MAX_SKIP_XFADE_TIME 2000 /* max 2 seconds crossfade on track skip */
+
+// PAP: Psycho-acoustic Audio Player
+// Supporting all open audio codec standards.
+// First one being nullsoft's nsv audio decoder format
+
+PAPlayer::PAPlayer(IPlayerCallback& callback) :
+ IPlayer(callback),
+ CThread("PAPlayer"),
+ m_signalSpeedChange(false),
+ m_playbackSpeed(1 ),
+ m_isPlaying(false),
+ m_isPaused(false),
+ m_isFinished(false),
+ m_defaultCrossfadeMS (0),
+ m_upcomingCrossfadeMS(0),
+ m_audioCallback(NULL ),
+ m_jobCounter(0),
+ m_newForcedPlayerTime(-1),
+ m_newForcedTotalTime (-1)
+{
+ memset(&m_playerGUIData, 0, sizeof(m_playerGUIData));
+ m_processInfo.reset(CProcessInfo::CreateInstance());
+ m_processInfo->SetDataCache(&CServiceBroker::GetDataCacheCore());
+}
+
+PAPlayer::~PAPlayer()
+{
+ CloseFile();
+}
+
+void PAPlayer::SoftStart(bool wait/* = false */)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_fadeOutTriggered)
+ continue;
+
+ si->m_stream->Resume();
+ si->m_stream->FadeVolume(0.0f, 1.0f, FAST_XFADE_TIME);
+ }
+
+ if (wait)
+ {
+ /* wait for them to fade in */
+ lock.unlock();
+ CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME));
+ lock.lock();
+
+ /* be sure they have faded in */
+ while(wait)
+ {
+ wait = false;
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream->IsFading())
+ {
+ lock.unlock();
+ wait = true;
+ CThread::Sleep(1ms);
+ lock.lock();
+ break;
+ }
+ }
+ }
+ }
+}
+
+void PAPlayer::SoftStop(bool wait/* = false */, bool close/* = true */)
+{
+ /* fade all the streams out fast for a nice soft stop */
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream)
+ si->m_stream->FadeVolume(1.0f, 0.0f, FAST_XFADE_TIME);
+
+ if (close)
+ {
+ si->m_prepareTriggered = true;
+ si->m_playNextTriggered = true;
+ si->m_fadeOutTriggered = true;
+ }
+ }
+
+ /* if we are going to wait for them to finish fading */
+ if(wait)
+ {
+ // fail safe timer, do not wait longer than 1000ms
+ XbmcThreads::EndTime<> timer(1000ms);
+
+ /* wait for them to fade out */
+ lock.unlock();
+ CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME));
+ lock.lock();
+
+ /* be sure they have faded out */
+ while(wait && !CServiceBroker::GetActiveAE()->IsSuspended() && !timer.IsTimePast())
+ {
+ wait = false;
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream && si->m_stream->IsFading())
+ {
+ lock.unlock();
+ wait = true;
+ CThread::Sleep(1ms);
+ lock.lock();
+ break;
+ }
+ }
+ }
+
+ /* if we are not closing the streams, pause them */
+ if (!close)
+ {
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ si->m_stream->Pause();
+ }
+ }
+ }
+}
+
+void PAPlayer::CloseAllStreams(bool fade/* = true */)
+{
+ if (!fade)
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ while (!m_streams.empty())
+ {
+ StreamInfo* si = m_streams.front();
+ m_streams.pop_front();
+
+ if (si->m_stream)
+ {
+ CloseFileCB(*si);
+ si->m_stream.reset();
+ }
+
+ si->m_decoder.Destroy();
+ delete si;
+ }
+
+ while (!m_finishing.empty())
+ {
+ StreamInfo* si = m_finishing.front();
+ m_finishing.pop_front();
+
+ if (si->m_stream)
+ {
+ CloseFileCB(*si);
+ si->m_stream.reset();
+ }
+
+ si->m_decoder.Destroy();
+ delete si;
+ }
+ m_currentStream = nullptr;
+ }
+ else
+ {
+ SoftStop(false, true);
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_currentStream = NULL;
+ }
+}
+
+bool PAPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
+{
+ m_defaultCrossfadeMS = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE) * 1000;
+ m_fullScreen = options.fullscreen;
+
+ if (m_streams.size() > 1 || !m_defaultCrossfadeMS || m_isPaused)
+ {
+ CloseAllStreams(!m_isPaused);
+ StopThread();
+ m_isPaused = false; // Make sure to reset the pause state
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_jobCounter++;
+ }
+ CServiceBroker::GetJobManager()->Submit([=]() { QueueNextFileEx(file, false); }, this,
+ CJob::PRIORITY_NORMAL);
+
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (m_streams.size() == 2)
+ {
+ //do a short crossfade on trackskip, set to max 2 seconds for these prev/next transitions
+ m_upcomingCrossfadeMS = std::min(m_defaultCrossfadeMS, (unsigned int)MAX_SKIP_XFADE_TIME);
+
+ //start transition to next track
+ StreamInfo* si = m_streams.front();
+ si->m_playNextAtFrame = si->m_framesSent; //start next track at current frame
+ si->m_prepareTriggered = true; //next track is ready to go
+ }
+ lock.unlock();
+
+ if (!IsRunning())
+ Create();
+
+ /* trigger playback start */
+ m_isPlaying = true;
+ m_startEvent.Set();
+
+ // OnPlayBackStarted to be made only once. Callback processing may be slower than player process
+ // so clear signal flag first otherwise async stream processing could also make callback
+ m_signalStarted = false;
+ m_callback.OnPlayBackStarted(file);
+
+ return true;
+}
+
+void PAPlayer::UpdateCrossfadeTime(const CFileItem& file)
+{
+ // we explicitly disable crossfading for audio cds
+ if (file.IsCDDA())
+ m_upcomingCrossfadeMS = 0;
+ else
+ m_upcomingCrossfadeMS = m_defaultCrossfadeMS = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE) * 1000;
+
+ if (m_upcomingCrossfadeMS)
+ {
+ if (!m_currentStream || (file.HasMusicInfoTag() &&
+ !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_MUSICPLAYER_CROSSFADEALBUMTRACKS) &&
+ m_currentStream->m_fileItem->HasMusicInfoTag() &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetAlbum() != "") &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetAlbum() ==
+ file.GetMusicInfoTag()->GetAlbum()) &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetDiscNumber() ==
+ file.GetMusicInfoTag()->GetDiscNumber()) &&
+ (m_currentStream->m_fileItem->GetMusicInfoTag()->GetTrackNumber() ==
+ file.GetMusicInfoTag()->GetTrackNumber() - 1)))
+ {
+ //do not crossfade when playing consecutive albumtracks
+ m_upcomingCrossfadeMS = 0;
+ }
+ }
+}
+
+bool PAPlayer::QueueNextFile(const CFileItem &file)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_jobCounter++;
+ }
+ CServiceBroker::GetJobManager()->Submit([this, file]() { QueueNextFileEx(file, true); }, this,
+ CJob::PRIORITY_NORMAL);
+
+ return true;
+}
+
+bool PAPlayer::QueueNextFileEx(const CFileItem &file, bool fadeIn)
+{
+ if (m_currentStream)
+ {
+ // check if we advance a track of a CUE sheet
+ // if this is the case we don't need to open a new stream
+ std::string newURL = file.GetDynURL().GetFileName();
+ std::string oldURL = m_currentStream->m_fileItem->GetDynURL().GetFileName();
+ if (newURL.compare(oldURL) == 0 && file.GetStartOffset() &&
+ file.GetStartOffset() == m_currentStream->m_fileItem->GetEndOffset() && m_currentStream &&
+ m_currentStream->m_prepareTriggered)
+ {
+ m_currentStream->m_nextFileItem.reset(new CFileItem(file));
+ m_upcomingCrossfadeMS = 0;
+ return true;
+ }
+ m_currentStream->m_nextFileItem.reset();
+ }
+
+ StreamInfo *si = new StreamInfo();
+ si->m_fileItem = std::make_unique<CFileItem>(file);
+
+ // Start stream at zero offset
+ si->m_startOffset = 0;
+ //File item start offset defines where in song to resume
+ double starttime = CUtil::ConvertMilliSecsToSecs(si->m_fileItem->GetStartOffset());
+
+ // Music from cuesheet => "item_start" and offset match
+ // Start offset defines where this song starts in file of multiple songs
+ if (si->m_fileItem->HasProperty("item_start") &&
+ (si->m_fileItem->GetProperty("item_start").asInteger() == si->m_fileItem->GetStartOffset()))
+ {
+ // Start stream at offset from cuesheet
+ si->m_startOffset = si->m_fileItem->GetStartOffset();
+ starttime = 0; // No resume point
+ }
+
+ if (!si->m_decoder.Create(file, si->m_startOffset))
+ {
+ CLog::Log(LOGWARNING, "PAPlayer::QueueNextFileEx - Failed to create the decoder");
+
+ // advance playlist
+ AdvancePlaylistOnError(*si->m_fileItem);
+ m_callback.OnQueueNextItem();
+
+ delete si;
+ return false;
+ }
+
+ /* decode until there is data-available */
+ si->m_decoder.Start();
+ while (si->m_decoder.GetDataSize(true) == 0)
+ {
+ int status = si->m_decoder.GetStatus();
+ if (status == STATUS_ENDED ||
+ status == STATUS_NO_FILE ||
+ si->m_decoder.ReadSamples(PACKET_SIZE) == RET_ERROR)
+ {
+ CLog::Log(LOGINFO, "PAPlayer::QueueNextFileEx - Error reading samples");
+
+ si->m_decoder.Destroy();
+ // advance playlist
+ AdvancePlaylistOnError(*si->m_fileItem);
+ m_callback.OnQueueNextItem();
+ delete si;
+ return false;
+ }
+
+ /* yield our time so that the main PAP thread doesn't stall */
+ CThread::Sleep(1ms);
+ }
+
+ // set m_upcomingCrossfadeMS depending on type of file and user settings
+ UpdateCrossfadeTime(*si->m_fileItem);
+
+ /* init the streaminfo struct */
+ si->m_audioFormat = si->m_decoder.GetFormat();
+ // si->m_startOffset already initialized
+ si->m_endOffset = file.GetEndOffset();
+ si->m_bytesPerSample = CAEUtil::DataFormatToBits(si->m_audioFormat.m_dataFormat) >> 3;
+ si->m_bytesPerFrame = si->m_bytesPerSample * si->m_audioFormat.m_channelLayout.Count();
+ si->m_started = false;
+ si->m_finishing = false;
+ si->m_framesSent = 0;
+ si->m_seekNextAtFrame = 0;
+ si->m_seekFrame = -1;
+ si->m_stream = NULL;
+ si->m_volume = (fadeIn && m_upcomingCrossfadeMS) ? 0.0f : 1.0f;
+ si->m_fadeOutTriggered = false;
+ si->m_isSlaved = false;
+
+ si->m_decoderTotal = si->m_decoder.TotalTime();
+ int64_t streamTotalTime = si->m_decoderTotal;
+ if (si->m_endOffset)
+ streamTotalTime = si->m_endOffset - si->m_startOffset;
+
+ // Seek to a resume point
+ if (si->m_fileItem->HasProperty("StartPercent") &&
+ (si->m_fileItem->GetProperty("StartPercent").asDouble() > 0) &&
+ (si->m_fileItem->GetProperty("StartPercent").asDouble() <= 100))
+ {
+ si->m_seekFrame =
+ si->m_audioFormat.m_sampleRate *
+ CUtil::ConvertMilliSecsToSecs(static_cast<int>(+(static_cast<double>(
+ streamTotalTime * (si->m_fileItem->GetProperty("StartPercent").asDouble() / 100.0)))));
+ }
+ else if (starttime > 0)
+ si->m_seekFrame = si->m_audioFormat.m_sampleRate * starttime;
+ else if (si->m_fileItem->HasProperty("audiobook_bookmark"))
+ si->m_seekFrame = si->m_audioFormat.m_sampleRate *
+ CUtil::ConvertMilliSecsToSecs(
+ si->m_fileItem->GetProperty("audiobook_bookmark").asInteger());
+
+ si->m_prepareNextAtFrame = 0;
+ // cd drives don't really like it to be crossfaded or prepared
+ if (!file.IsCDDA())
+ {
+ if (streamTotalTime >= TIME_TO_CACHE_NEXT_FILE + m_defaultCrossfadeMS)
+ si->m_prepareNextAtFrame = (int)((streamTotalTime - TIME_TO_CACHE_NEXT_FILE - m_defaultCrossfadeMS) * si->m_audioFormat.m_sampleRate / 1000.0f);
+ }
+
+ if (m_currentStream && ((m_currentStream->m_audioFormat.m_dataFormat == AE_FMT_RAW) || (si->m_audioFormat.m_dataFormat == AE_FMT_RAW)))
+ {
+ m_currentStream->m_prepareTriggered = false;
+ m_currentStream->m_waitOnDrain = true;
+ m_currentStream->m_prepareNextAtFrame = 0;
+ si->m_decoder.Destroy();
+ delete si;
+ return false;
+ }
+
+ si->m_prepareTriggered = false;
+ si->m_playNextAtFrame = 0;
+ si->m_playNextTriggered = false;
+ si->m_waitOnDrain = false;
+
+ if (!PrepareStream(si))
+ {
+ CLog::Log(LOGINFO, "PAPlayer::QueueNextFileEx - Error preparing stream");
+
+ si->m_decoder.Destroy();
+ // advance playlist
+ AdvancePlaylistOnError(*si->m_fileItem);
+ m_callback.OnQueueNextItem();
+ delete si;
+ return false;
+ }
+
+ /* add the stream to the list */
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_streams.push_back(si);
+ //update the current stream to start playing the next track at the correct frame.
+ UpdateStreamInfoPlayNextAtFrame(m_currentStream, m_upcomingCrossfadeMS);
+
+ return true;
+}
+
+void PAPlayer::UpdateStreamInfoPlayNextAtFrame(StreamInfo *si, unsigned int crossFadingTime)
+{
+ // if no crossfading or cue sheet, wait for eof
+ if (si && (crossFadingTime || si->m_endOffset))
+ {
+ int64_t streamTotalTime = si->m_decoder.TotalTime();
+ if (si->m_endOffset)
+ streamTotalTime = si->m_endOffset - si->m_startOffset;
+ if (streamTotalTime < crossFadingTime)
+ si->m_playNextAtFrame = (int)((streamTotalTime / 2) * si->m_audioFormat.m_sampleRate / 1000.0f);
+ else
+ si->m_playNextAtFrame = (int)((streamTotalTime - crossFadingTime) * si->m_audioFormat.m_sampleRate / 1000.0f);
+ }
+}
+
+inline bool PAPlayer::PrepareStream(StreamInfo *si)
+{
+ /* if we have a stream we are already prepared */
+ if (si->m_stream)
+ return true;
+
+ /* get a paused stream */
+ AEAudioFormat format = si->m_audioFormat;
+ si->m_stream = CServiceBroker::GetActiveAE()->MakeStream(
+ format,
+ AESTREAM_PAUSED
+ );
+
+ if (!si->m_stream)
+ {
+ CLog::Log(LOGDEBUG, "PAPlayer::PrepareStream - Failed to get IAEStream");
+ return false;
+ }
+
+ si->m_stream->SetVolume(si->m_volume);
+ float peak = 1.0;
+ float gain = si->m_decoder.GetReplayGain(peak);
+ if (peak * gain <= 1.0f)
+ // No clipping protection needed
+ si->m_stream->SetReplayGain(gain);
+ else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING))
+ // Normalise volume reducing replaygain to avoid needing clipping protection, plays file at lower level
+ si->m_stream->SetReplayGain(1.0f / fabs(peak));
+ else
+ // Clipping protection (when enabled in AE) by audio limiting, applied just where needed
+ si->m_stream->SetAmplification(gain);
+
+ /* if its not the first stream and crossfade is not enabled */
+ if (m_currentStream && m_currentStream != si && !m_upcomingCrossfadeMS)
+ {
+ /* slave the stream for gapless */
+ si->m_isSlaved = true;
+ m_currentStream->m_stream->RegisterSlave(si->m_stream.get());
+ }
+
+ /* fill the stream's buffer */
+ while(si->m_stream->IsBuffering())
+ {
+ int status = si->m_decoder.GetStatus();
+ if (status == STATUS_ENDED ||
+ status == STATUS_NO_FILE ||
+ si->m_decoder.ReadSamples(PACKET_SIZE) == RET_ERROR)
+ {
+ CLog::Log(LOGINFO, "PAPlayer::PrepareStream - Stream Finished");
+ break;
+ }
+
+ if (!QueueData(si))
+ break;
+
+ /* yield our time so that the main PAP thread doesn't stall */
+ CThread::Sleep(1ms);
+ }
+
+ CLog::Log(LOGINFO, "PAPlayer::PrepareStream - Ready");
+
+ return true;
+}
+
+bool PAPlayer::CloseFile(bool reopen)
+{
+ if (reopen)
+ CServiceBroker::GetActiveAE()->KeepConfiguration(3000);
+
+ if (!m_isPaused)
+ SoftStop(true, true);
+ CloseAllStreams(false);
+
+ /* wait for the thread to terminate */
+ StopThread(true);//true - wait for end of thread
+
+ // wait for any pending jobs to complete
+ {
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ while (m_jobCounter > 0)
+ {
+ lock.unlock();
+ m_jobEvent.Wait(100ms);
+ lock.lock();
+ }
+ }
+
+ return true;
+}
+
+void PAPlayer::Process()
+{
+ if (!m_startEvent.Wait(100ms))
+ {
+ CLog::Log(LOGDEBUG, "PAPlayer::Process - Failed to receive start event");
+ return;
+ }
+
+ CLog::Log(LOGDEBUG, "PAPlayer::Process - Playback started");
+ while(m_isPlaying && !m_bStop)
+ {
+ /* this needs to happen outside of any locks to prevent deadlocks */
+ if (m_signalSpeedChange)
+ {
+ m_callback.OnPlayBackSpeedChanged(m_playbackSpeed);
+ m_signalSpeedChange = false;
+ }
+
+ double freeBufferTime = 0.0;
+ ProcessStreams(freeBufferTime);
+
+ // if none of our streams wants at least 10ms of data, we sleep
+ if (freeBufferTime < 0.01)
+ {
+ CThread::Sleep(10ms);
+ }
+
+ if (m_newForcedPlayerTime != -1)
+ {
+ if (SetTimeInternal(m_newForcedPlayerTime))
+ {
+ m_newForcedPlayerTime = -1;
+ }
+ }
+
+ if (m_newForcedTotalTime != -1)
+ {
+ if (SetTotalTimeInternal(m_newForcedTotalTime))
+ {
+ m_newForcedTotalTime = -1;
+ }
+ }
+
+ GetTimeInternal(); //update for GUI
+ }
+ m_isPlaying = false;
+}
+
+inline void PAPlayer::ProcessStreams(double &freeBufferTime)
+{
+ std::unique_lock<CCriticalSection> sharedLock(m_streamsLock);
+ if (m_isFinished && m_streams.empty() && m_finishing.empty())
+ {
+ m_isPlaying = false;
+ freeBufferTime = 1.0;
+ return;
+ }
+
+ /* destroy any drained streams */
+ for (auto itt = m_finishing.begin(); itt != m_finishing.end();)
+ {
+ StreamInfo* si = *itt;
+ if (si->m_stream->IsDrained())
+ {
+ itt = m_finishing.erase(itt);
+ CloseFileCB(*si);
+ delete si;
+ CLog::Log(LOGDEBUG, "PAPlayer::ProcessStreams - Stream Freed");
+ }
+ else
+ ++itt;
+ }
+
+ sharedLock.unlock();
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+
+ for(StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
+ {
+ StreamInfo* si = *itt;
+ if (!m_currentStream && !si->m_started)
+ {
+ m_currentStream = si;
+ UpdateGUIData(si); //update for GUI
+ }
+ /* if the stream is finishing */
+ if ((si->m_playNextTriggered && si->m_stream && !si->m_stream->IsFading()) || !ProcessStream(si, freeBufferTime))
+ {
+ if (!si->m_prepareTriggered)
+ {
+ if (si->m_waitOnDrain)
+ {
+ si->m_stream->Drain(true);
+ si->m_waitOnDrain = false;
+ }
+ si->m_prepareTriggered = true;
+ m_callback.OnQueueNextItem();
+ }
+
+ /* remove the stream */
+ itt = m_streams.erase(itt);
+ /* if its the current stream */
+ if (si == m_currentStream)
+ {
+ /* if it was the last stream */
+ if (itt == m_streams.end())
+ {
+ /* if it didn't trigger the next queue item */
+ if (!si->m_prepareTriggered)
+ {
+ if (si->m_waitOnDrain)
+ {
+ si->m_stream->Drain(true);
+ si->m_waitOnDrain = false;
+ }
+ m_callback.OnQueueNextItem();
+ si->m_prepareTriggered = true;
+ }
+ m_currentStream = NULL;
+ }
+ else
+ {
+ m_currentStream = *itt;
+ UpdateGUIData(*itt); //update for GUI
+ }
+ }
+
+ /* unregister the audio callback */
+ si->m_stream->UnRegisterAudioCallback();
+ si->m_decoder.Destroy();
+ si->m_stream->Drain(false);
+ m_finishing.push_back(si);
+ return;
+ }
+
+ if (!si->m_started)
+ continue;
+
+ // is it time to prepare the next stream?
+ if (si->m_prepareNextAtFrame > 0 && !si->m_prepareTriggered && si->m_framesSent >= si->m_prepareNextAtFrame)
+ {
+ si->m_prepareTriggered = true;
+ m_callback.OnQueueNextItem();
+ }
+
+ // it is time to start playing the next stream?
+ if (si->m_playNextAtFrame > 0 && !si->m_playNextTriggered && !si->m_nextFileItem && si->m_framesSent >= si->m_playNextAtFrame)
+ {
+ if (!si->m_prepareTriggered)
+ {
+ si->m_prepareTriggered = true;
+ m_callback.OnQueueNextItem();
+ }
+
+ if (!m_isFinished)
+ {
+ if (m_upcomingCrossfadeMS)
+ {
+ si->m_stream->FadeVolume(1.0f, 0.0f, m_upcomingCrossfadeMS);
+ si->m_fadeOutTriggered = true;
+ }
+ m_currentStream = NULL;
+
+ /* unregister the audio callback */
+ si->m_stream->UnRegisterAudioCallback();
+ }
+
+ si->m_playNextTriggered = true;
+ }
+ }
+}
+
+inline bool PAPlayer::ProcessStream(StreamInfo *si, double &freeBufferTime)
+{
+ /* if playback needs to start on this stream, do it */
+ if (si == m_currentStream && !si->m_started)
+ {
+ si->m_started = true;
+ si->m_stream->RegisterAudioCallback(m_audioCallback);
+ if (!si->m_isSlaved)
+ si->m_stream->Resume();
+ si->m_stream->FadeVolume(0.0f, 1.0f, m_upcomingCrossfadeMS);
+ if (m_signalStarted)
+ m_callback.OnPlayBackStarted(*si->m_fileItem);
+ m_signalStarted = true;
+ if (m_fullScreen)
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN);
+ m_fullScreen = false;
+ }
+ m_callback.OnAVStarted(*si->m_fileItem);
+ }
+
+ /* if we have not started yet and the stream has been primed */
+ unsigned int space = si->m_stream->GetSpace();
+ if (!si->m_started && !space)
+ return true;
+
+ if (!m_playbackSpeed)
+ return true;
+
+ /* see if it is time yet to FF/RW or a direct seek */
+ if (!si->m_playNextTriggered && ((m_playbackSpeed != 1 && si->m_framesSent >= si->m_seekNextAtFrame) || si->m_seekFrame > -1))
+ {
+ int64_t time = (int64_t)0;
+ /* if its a direct seek */
+ if (si->m_seekFrame > -1)
+ {
+ time = (int64_t)((float)si->m_seekFrame / (float)si->m_audioFormat.m_sampleRate * 1000.0f);
+ si->m_framesSent = (int)(si->m_seekFrame - ((float)si->m_startOffset * (float)si->m_audioFormat.m_sampleRate) / 1000.0f);
+ si->m_seekFrame = -1;
+ m_playerGUIData.m_time = time; //update for GUI
+ si->m_seekNextAtFrame = 0;
+ CDataCacheCore::GetInstance().SetPlayTimes(0, time, 0, m_playerGUIData.m_totalTime);
+ }
+ /* if its FF/RW */
+ else
+ {
+ si->m_framesSent += si->m_audioFormat.m_sampleRate * (m_playbackSpeed - 1);
+ si->m_seekNextAtFrame = si->m_framesSent + si->m_audioFormat.m_sampleRate / 2;
+ time = (int64_t)(((float)si->m_framesSent / (float)si->m_audioFormat.m_sampleRate * 1000.0f) + (float)si->m_startOffset);
+ }
+
+ /* if we are seeking back before the start of the track start normal playback */
+ if (time < si->m_startOffset || si->m_framesSent < 0)
+ {
+ time = si->m_startOffset;
+ si->m_framesSent = 0;
+ si->m_seekNextAtFrame = 0;
+ SetSpeed(1);
+ }
+
+ si->m_decoder.Seek(time);
+ }
+
+ int status = si->m_decoder.GetStatus();
+ if (status == STATUS_ENDED ||
+ status == STATUS_NO_FILE ||
+ si->m_decoder.ReadSamples(PACKET_SIZE) == RET_ERROR ||
+ ((si->m_endOffset) && (si->m_framesSent / si->m_audioFormat.m_sampleRate >= (si->m_endOffset - si->m_startOffset) / 1000)))
+ {
+ if (si == m_currentStream && si->m_nextFileItem)
+ {
+ CloseFileCB(*si);
+
+ // update current stream with info of next track
+ si->m_startOffset = si->m_nextFileItem->GetStartOffset();
+ if (si->m_nextFileItem->GetEndOffset())
+ si->m_endOffset = si->m_nextFileItem->GetEndOffset();
+ else
+ si->m_endOffset = 0;
+ si->m_framesSent = 0;
+
+ *si->m_fileItem = *si->m_nextFileItem;
+ si->m_nextFileItem.reset();
+
+ int64_t streamTotalTime = si->m_decoder.TotalTime() - si->m_startOffset;
+ if (si->m_endOffset)
+ streamTotalTime = si->m_endOffset - si->m_startOffset;
+
+ // calculate time when to prepare next stream
+ si->m_prepareNextAtFrame = 0;
+ if (streamTotalTime >= TIME_TO_CACHE_NEXT_FILE + m_defaultCrossfadeMS)
+ si->m_prepareNextAtFrame = (int)((streamTotalTime - TIME_TO_CACHE_NEXT_FILE - m_defaultCrossfadeMS) * si->m_audioFormat.m_sampleRate / 1000.0f);
+
+ si->m_prepareTriggered = false;
+ si->m_playNextAtFrame = 0;
+ si->m_playNextTriggered = false;
+ si->m_seekNextAtFrame = 0;
+
+ //update the current stream to start playing the next track at the correct frame.
+ UpdateStreamInfoPlayNextAtFrame(m_currentStream, m_upcomingCrossfadeMS);
+
+ UpdateGUIData(si);
+ if (m_signalStarted)
+ m_callback.OnPlayBackStarted(*si->m_fileItem);
+ m_signalStarted = true;
+ m_callback.OnAVStarted(*si->m_fileItem);
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "PAPlayer::ProcessStream - Stream Finished");
+ return false;
+ }
+ }
+
+ if (!QueueData(si))
+ return false;
+
+ /* update free buffer time if we are running */
+ if (si->m_started)
+ {
+ if (si->m_stream->IsBuffering())
+ freeBufferTime = 1.0;
+ else
+ {
+ double free_space;
+ if (si->m_audioFormat.m_dataFormat != AE_FMT_RAW)
+ free_space = (double)(si->m_stream->GetSpace() / si->m_bytesPerSample) / si->m_audioFormat.m_sampleRate;
+ else
+ {
+ if (si->m_audioFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD &&
+ !m_processInfo->WantsRawPassthrough())
+ {
+ free_space = static_cast<double>(si->m_stream->GetSpace()) *
+ si->m_audioFormat.m_streamInfo.GetDuration() / 1000 / 2;
+ }
+ else
+ {
+ free_space = static_cast<double>(si->m_stream->GetSpace()) *
+ si->m_audioFormat.m_streamInfo.GetDuration() / 1000;
+ }
+ }
+
+ freeBufferTime = std::max(freeBufferTime , free_space);
+ }
+ }
+
+ return true;
+}
+
+bool PAPlayer::QueueData(StreamInfo *si)
+{
+ unsigned int space = si->m_stream->GetSpace();
+
+ if (si->m_audioFormat.m_dataFormat != AE_FMT_RAW)
+ {
+ unsigned int samples = std::min(si->m_decoder.GetDataSize(false), space / si->m_bytesPerSample);
+ if (!samples)
+ return true;
+
+ // we want complete frames
+ samples -= samples % si->m_audioFormat.m_channelLayout.Count();
+
+ uint8_t* data = (uint8_t*)si->m_decoder.GetData(samples);
+ if (!data)
+ {
+ CLog::Log(LOGERROR, "PAPlayer::QueueData - Failed to get data from the decoder");
+ return false;
+ }
+
+ unsigned int frames = samples/si->m_audioFormat.m_channelLayout.Count();
+ unsigned int added = si->m_stream->AddData(&data, 0, frames, nullptr);
+ si->m_framesSent += added;
+ }
+ else
+ {
+ if (!space)
+ return true;
+
+ int size;
+ uint8_t *data = si->m_decoder.GetRawData(size);
+ if (data && size)
+ {
+ int added = si->m_stream->AddData(&data, 0, size, nullptr);
+ if (added != size)
+ {
+ CLog::Log(LOGERROR, "PAPlayer::QueueData - unknown error");
+ return false;
+ }
+ if (si->m_audioFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD &&
+ !m_processInfo->WantsRawPassthrough())
+ {
+ si->m_framesSent += si->m_audioFormat.m_streamInfo.GetDuration() / 1000 / 2 *
+ si->m_audioFormat.m_streamInfo.m_sampleRate;
+ }
+ else
+ {
+ si->m_framesSent += si->m_audioFormat.m_streamInfo.GetDuration() / 1000 *
+ si->m_audioFormat.m_streamInfo.m_sampleRate;
+ }
+ }
+ }
+
+ const ICodec* codec = si->m_decoder.GetCodec();
+ m_playerGUIData.m_cacheLevel = codec ? codec->GetCacheLevel() : 0; //update for GUI
+
+ return true;
+}
+
+void PAPlayer::OnExit()
+{
+ //@todo signal OnPlayBackError if there was an error on last stream
+ if (m_isFinished && !m_bStop)
+ m_callback.OnPlayBackEnded();
+ else
+ m_callback.OnPlayBackStopped();
+}
+
+void PAPlayer::OnNothingToQueueNotify()
+{
+ m_isFinished = true;
+}
+
+bool PAPlayer::IsPlaying() const
+{
+ return m_isPlaying;
+}
+
+void PAPlayer::Pause()
+{
+ if (m_isPaused)
+ {
+ SetSpeed(1);
+ }
+ else
+ {
+ SetSpeed(0);
+ }
+}
+
+void PAPlayer::SetVolume(float volume)
+{
+
+}
+
+void PAPlayer::SetDynamicRangeCompression(long drc)
+{
+
+}
+
+void PAPlayer::SetSpeed(float speed)
+{
+ m_playbackSpeed = static_cast<int>(speed);
+ CDataCacheCore::GetInstance().SetSpeed(1.0, speed);
+ if (m_playbackSpeed != 0 && m_isPaused)
+ {
+ m_isPaused = false;
+ SoftStart();
+ m_callback.OnPlayBackResumed();
+ }
+ else if (m_playbackSpeed == 0 && !m_isPaused)
+ {
+ m_isPaused = true;
+ SoftStop(true, false);
+ m_callback.OnPlayBackPaused();
+ }
+ m_signalSpeedChange = true;
+}
+
+int64_t PAPlayer::GetTimeInternal()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return 0;
+
+ double time = ((double)m_currentStream->m_framesSent / (double)m_currentStream->m_audioFormat.m_sampleRate);
+ if (m_currentStream->m_stream)
+ time -= m_currentStream->m_stream->GetDelay();
+ time = time * 1000.0;
+
+ m_playerGUIData.m_time = (int64_t)time; //update for GUI
+ CDataCacheCore::GetInstance().SetPlayTimes(0, time, 0, m_playerGUIData.m_totalTime);
+
+ return (int64_t)time;
+}
+
+bool PAPlayer::SetTotalTimeInternal(int64_t time)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ {
+ return false;
+ }
+
+ m_currentStream->m_decoder.SetTotalTime(time);
+ UpdateGUIData(m_currentStream);
+
+ return true;
+}
+
+bool PAPlayer::SetTimeInternal(int64_t time)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return false;
+
+ m_currentStream->m_framesSent = time / 1000 * m_currentStream->m_audioFormat.m_sampleRate;
+
+ if (m_currentStream->m_stream)
+ m_currentStream->m_framesSent += m_currentStream->m_stream->GetDelay() * m_currentStream->m_audioFormat.m_sampleRate;
+
+ return true;
+}
+
+void PAPlayer::SetTime(int64_t time)
+{
+ m_newForcedPlayerTime = time;
+}
+
+int64_t PAPlayer::GetTotalTime64()
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return 0;
+
+ int64_t total = m_currentStream->m_decoder.TotalTime();
+ if (m_currentStream->m_endOffset)
+ total = m_currentStream->m_endOffset;
+ total -= m_currentStream->m_startOffset;
+ return total;
+}
+
+void PAPlayer::SetTotalTime(int64_t time)
+{
+ m_newForcedTotalTime = time;
+}
+
+int PAPlayer::GetCacheLevel() const
+{
+ return m_playerGUIData.m_cacheLevel;
+}
+
+void PAPlayer::GetAudioStreamInfo(int index, AudioStreamInfo& info) const
+{
+ info.bitrate = m_playerGUIData.m_audioBitrate;
+ info.channels = m_playerGUIData.m_channelCount;
+ info.codecName = m_playerGUIData.m_codec;
+ info.samplerate = m_playerGUIData.m_sampleRate;
+ info.bitspersample = m_playerGUIData.m_bitsPerSample;
+}
+
+bool PAPlayer::CanSeek() const
+{
+ return m_playerGUIData.m_canSeek;
+}
+
+void PAPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
+{
+ if (!CanSeek()) return;
+
+ long long seek;
+ const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ if (advancedSettings->m_musicUseTimeSeeking && m_playerGUIData.m_totalTime > 2 * advancedSettings->m_musicTimeSeekForwardBig)
+ {
+ if (bLargeStep)
+ seek = bPlus ? advancedSettings->m_musicTimeSeekForwardBig : advancedSettings->m_musicTimeSeekBackwardBig;
+ else
+ seek = bPlus ? advancedSettings->m_musicTimeSeekForward : advancedSettings->m_musicTimeSeekBackward;
+ seek *= 1000;
+ seek += m_playerGUIData.m_time;
+ }
+ else
+ {
+ float percent;
+ if (bLargeStep)
+ percent = bPlus ? static_cast<float>(advancedSettings->m_musicPercentSeekForwardBig) : static_cast<float>(advancedSettings->m_musicPercentSeekBackwardBig);
+ else
+ percent = bPlus ? static_cast<float>(advancedSettings->m_musicPercentSeekForward) : static_cast<float>(advancedSettings->m_musicPercentSeekBackward);
+ seek = static_cast<long long>(GetTotalTime64() * (GetPercentage() + percent) / 100);
+ }
+
+ SeekTime(seek);
+}
+
+void PAPlayer::SeekTime(int64_t iTime /*=0*/)
+{
+ if (!CanSeek()) return;
+
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ if (!m_currentStream)
+ return;
+
+ int64_t seekOffset = iTime - GetTimeInternal();
+
+ if (m_playbackSpeed != 1)
+ SetSpeed(1);
+
+ m_currentStream->m_seekFrame = (int)((float)m_currentStream->m_audioFormat.m_sampleRate * ((float)iTime + (float)m_currentStream->m_startOffset) / 1000.0f);
+ m_callback.OnPlayBackSeek(iTime, seekOffset);
+}
+
+void PAPlayer::SeekPercentage(float fPercent /*=0*/)
+{
+ if (fPercent < 0.0f ) fPercent = 0.0f;
+ if (fPercent > 100.0f) fPercent = 100.0f;
+ SeekTime((int64_t)(fPercent * 0.01f * (float)GetTotalTime64()));
+}
+
+float PAPlayer::GetPercentage()
+{
+ if (m_playerGUIData.m_totalTime > 0)
+ return m_playerGUIData.m_time * 100.0f / m_playerGUIData.m_totalTime;
+
+ return 0.0f;
+}
+
+void PAPlayer::UpdateGUIData(StreamInfo *si)
+{
+ /* Store data need by external threads in member
+ * structure to prevent locking conflicts when
+ * data required by GUI and main application
+ */
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+
+ m_playerGUIData.m_sampleRate = si->m_audioFormat.m_sampleRate;
+ m_playerGUIData.m_channelCount = si->m_audioFormat.m_channelLayout.Count();
+ m_playerGUIData.m_canSeek = si->m_decoder.CanSeek();
+
+ const ICodec* codec = si->m_decoder.GetCodec();
+
+ m_playerGUIData.m_audioBitrate = codec ? codec->m_bitRate : 0;
+ strncpy(m_playerGUIData.m_codec,codec ? codec->m_CodecName.c_str() : "",20);
+ m_playerGUIData.m_cacheLevel = codec ? codec->GetCacheLevel() : 0;
+ m_playerGUIData.m_bitsPerSample = (codec && codec->m_bitsPerCodedSample) ? codec->m_bitsPerCodedSample : si->m_bytesPerSample << 3;
+
+ int64_t total = si->m_decoder.TotalTime();
+ if (si->m_endOffset)
+ total = m_currentStream->m_endOffset;
+ total -= m_currentStream->m_startOffset;
+ m_playerGUIData.m_totalTime = total;
+
+ CServiceBroker::GetDataCacheCore().SignalAudioInfoChange();
+}
+
+void PAPlayer::OnJobComplete(unsigned int jobID, bool success, CJob *job)
+{
+ std::unique_lock<CCriticalSection> lock(m_streamsLock);
+ m_jobCounter--;
+ m_jobEvent.Set();
+}
+
+void PAPlayer::CloseFileCB(StreamInfo &si)
+{
+ IPlayerCallback *cb = &m_callback;
+ CFileItem fileItem(*si.m_fileItem);
+ CBookmark bookmark;
+ double total = si.m_decoderTotal;
+ if (si.m_endOffset)
+ total = si.m_endOffset;
+ total -= si.m_startOffset;
+ bookmark.totalTimeInSeconds = total / 1000;
+ bookmark.timeInSeconds = (static_cast<double>(si.m_framesSent) /
+ static_cast<double>(si.m_audioFormat.m_sampleRate));
+ bookmark.timeInSeconds -= si.m_stream->GetDelay();
+ bookmark.player = m_name;
+ bookmark.playerState = GetPlayerState();
+ CServiceBroker::GetJobManager()->Submit([=]() { cb->OnPlayerCloseFile(fileItem, bookmark); },
+ CJob::PRIORITY_NORMAL);
+}
+
+void PAPlayer::AdvancePlaylistOnError(CFileItem &fileItem)
+{
+ if (m_signalStarted)
+ m_callback.OnPlayBackStarted(fileItem);
+ m_signalStarted = true;
+ m_callback.OnAVStarted(fileItem);
+}
diff --git a/xbmc/cores/paplayer/PAPlayer.h b/xbmc/cores/paplayer/PAPlayer.h
new file mode 100644
index 0000000..fe2489d
--- /dev/null
+++ b/xbmc/cores/paplayer/PAPlayer.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2005-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 "AudioDecoder.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Interfaces/IAudioCallback.h"
+#include "cores/IPlayer.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+#include "utils/Job.h"
+
+#include <atomic>
+#include <list>
+#include <vector>
+
+class IAEStream;
+class CFileItem;
+class CProcessInfo;
+
+class PAPlayer : public IPlayer, public CThread, public IJobCallback
+{
+friend class CQueueNextFileJob;
+public:
+ explicit PAPlayer(IPlayerCallback& callback);
+ ~PAPlayer() override;
+
+ bool OpenFile(const CFileItem& file, const CPlayerOptions &options) override;
+ bool QueueNextFile(const CFileItem &file) override;
+ void OnNothingToQueueNotify() override;
+ bool CloseFile(bool reopen = false) override;
+ bool IsPlaying() const override;
+ void Pause() override;
+ bool HasVideo() const override { return false; }
+ bool HasAudio() const override { return true; }
+ bool CanSeek() const override;
+ void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) override;
+ void SeekPercentage(float fPercent = 0.0f) override;
+ void SetVolume(float volume) override;
+ void SetDynamicRangeCompression(long drc) override;
+ void SetSpeed(float speed = 0) override;
+ int GetCacheLevel() const override;
+ void SetTotalTime(int64_t time) override;
+ void GetAudioStreamInfo(int index, AudioStreamInfo& info) const override;
+ void SetTime(int64_t time) override;
+ void SeekTime(int64_t iTime = 0) override;
+ void GetAudioCapabilities(std::vector<int>& audioCaps) const override {}
+
+ int GetAudioStreamCount() const override { return 1; }
+ int GetAudioStream() override { return 0; }
+
+ // implementation of IJobCallback
+ void OnJobComplete(unsigned int jobID, bool success, CJob *job) override;
+
+ struct
+ {
+ char m_codec[21];
+ int64_t m_time;
+ int64_t m_totalTime;
+ int m_channelCount;
+ int m_bitsPerSample;
+ int m_sampleRate;
+ int m_audioBitrate;
+ int m_cacheLevel;
+ bool m_canSeek;
+ } m_playerGUIData;
+
+protected:
+ // implementation of CThread
+ void OnStartup() override {}
+ void Process() override;
+ void OnExit() override;
+ float GetPercentage();
+
+private:
+ struct StreamInfo
+ {
+ std::unique_ptr<CFileItem> m_fileItem;
+ std::unique_ptr<CFileItem> m_nextFileItem;
+ CAudioDecoder m_decoder; /* the stream decoder */
+ int64_t m_startOffset; /* the stream start offset */
+ int64_t m_endOffset; /* the stream end offset */
+ int64_t m_decoderTotal = 0;
+ AEAudioFormat m_audioFormat;
+ unsigned int m_bytesPerSample; /* number of bytes per audio sample */
+ unsigned int m_bytesPerFrame; /* number of bytes per audio frame */
+
+ bool m_started; /* if playback of this stream has been started */
+ bool m_finishing; /* if this stream is finishing */
+ int m_framesSent; /* number of frames sent to the stream */
+ int m_prepareNextAtFrame; /* when to prepare the next stream */
+ bool m_prepareTriggered; /* if the next stream has been prepared */
+ int m_playNextAtFrame; /* when to start playing the next stream */
+ bool m_playNextTriggered; /* if this stream has started the next one */
+ bool m_fadeOutTriggered; /* if the stream has been told to fade out */
+ int m_seekNextAtFrame; /* the FF/RR sample to seek at */
+ int m_seekFrame; /* the exact position to seek too, -1 for none */
+
+ IAE::StreamPtr m_stream; /* the playback stream */
+ float m_volume; /* the initial volume level to set the stream to on creation */
+
+ bool m_isSlaved; /* true if the stream has been slaved to another */
+ bool m_waitOnDrain; /* wait for stream being drained in AE */
+ };
+
+ typedef std::list<StreamInfo*> StreamList;
+
+ bool m_signalSpeedChange; /* true if OnPlaybackSpeedChange needs to be called */
+ bool m_signalStarted = true;
+ std::atomic_int m_playbackSpeed; /* the playback speed (1 = normal) */
+ bool m_isPlaying;
+ bool m_isPaused;
+ bool m_isFinished; /* if there are no more songs in the queue */
+ bool m_fullScreen;
+ unsigned int m_defaultCrossfadeMS; /* how long the default crossfade is in ms */
+ unsigned int m_upcomingCrossfadeMS; /* how long the upcoming crossfade is in ms */
+ CEvent m_startEvent; /* event for playback start */
+ StreamInfo* m_currentStream = nullptr;
+ IAudioCallback* m_audioCallback; /* the viz audio callback */
+
+ CCriticalSection m_streamsLock; /* lock for the stream list */
+ StreamList m_streams; /* playing streams */
+ StreamList m_finishing; /* finishing streams */
+ int m_jobCounter;
+ CEvent m_jobEvent;
+ int64_t m_newForcedPlayerTime;
+ int64_t m_newForcedTotalTime;
+ std::unique_ptr<CProcessInfo> m_processInfo;
+
+ bool QueueNextFileEx(const CFileItem &file, bool fadeIn);
+ void SoftStart(bool wait = false);
+ void SoftStop(bool wait = false, bool close = true);
+ void CloseAllStreams(bool fade = true);
+ void ProcessStreams(double &freeBufferTime);
+ bool PrepareStream(StreamInfo *si);
+ bool ProcessStream(StreamInfo *si, double &freeBufferTime);
+ bool QueueData(StreamInfo *si);
+ int64_t GetTotalTime64();
+ void UpdateCrossfadeTime(const CFileItem& file);
+ void UpdateStreamInfoPlayNextAtFrame(StreamInfo *si, unsigned int crossFadingTime);
+ void UpdateGUIData(StreamInfo *si);
+ int64_t GetTimeInternal();
+ bool SetTimeInternal(int64_t time);
+ bool SetTotalTimeInternal(int64_t time);
+ void CloseFileCB(StreamInfo &si);
+ void AdvancePlaylistOnError(CFileItem &fileItem);
+};
+
diff --git a/xbmc/cores/paplayer/VideoPlayerCodec.cpp b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
new file mode 100644
index 0000000..a77a27d
--- /dev/null
+++ b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2005-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 "VideoPlayerCodec.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "cores/AudioEngine/AEResampleFactory.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDFactoryDemuxer.h"
+#include "cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.h"
+#include "cores/VideoPlayer/DVDStreamInfo.h"
+#include "music/tags/TagLoaderTagLib.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+VideoPlayerCodec::VideoPlayerCodec() : m_processInfo(CProcessInfo::CreateInstance())
+{
+ m_CodecName = "VideoPlayer";
+}
+
+VideoPlayerCodec::~VideoPlayerCodec()
+{
+ DeInit();
+}
+
+AEAudioFormat VideoPlayerCodec::GetFormat()
+{
+ AEAudioFormat format;
+ if (m_pAudioCodec)
+ {
+ format = m_pAudioCodec->GetFormat();
+ }
+ return format;
+}
+
+void VideoPlayerCodec::SetContentType(const std::string &strContent)
+{
+ m_strContentType = strContent;
+ StringUtils::ToLower(m_strContentType);
+}
+
+void VideoPlayerCodec::SetPassthroughStreamType(CAEStreamInfo::DataType streamType)
+{
+ m_srcFormat.m_streamInfo.m_type = streamType;
+}
+
+bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
+{
+ // take precaution if Init()ialized earlier
+ if (m_bInited)
+ {
+ // keep things as is if Init() was done with known strFile
+ if (m_strFileName == file.GetDynPath())
+ return true;
+
+ // got differing filename, so cleanup before starting over
+ DeInit();
+ }
+
+ m_nDecodedLen = 0;
+
+ CFileItem fileitem(file);
+ fileitem.SetMimeType(m_strContentType);
+ fileitem.SetMimeTypeForInternetFile();
+ m_pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, fileitem);
+ if (!m_pInputStream)
+ {
+ CLog::Log(LOGERROR, "{}: Error creating input stream for {}", __FUNCTION__, file.GetDynPath());
+ return false;
+ }
+
+ //! @todo
+ //! convey CFileItem::ContentLookup() into Open()
+ if (!m_pInputStream->Open())
+ {
+ CLog::Log(LOGERROR, "{}: Error opening file {}", __FUNCTION__, file.GetDynPath());
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ return false;
+ }
+
+ m_pDemuxer = NULL;
+
+ try
+ {
+ m_pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(m_pInputStream);
+ if (!m_pDemuxer)
+ {
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ CLog::Log(LOGERROR, "{}: Error creating demuxer", __FUNCTION__);
+ return false;
+ }
+ }
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "{}: Exception thrown when opening demuxer", __FUNCTION__);
+ if (m_pDemuxer)
+ {
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ }
+ return false;
+ }
+
+ CDemuxStream* pStream = NULL;
+ m_nAudioStream = -1;
+ int64_t demuxerId = -1;
+ for (auto stream : m_pDemuxer->GetStreams())
+ {
+ if (stream && stream->type == STREAM_AUDIO)
+ {
+ m_nAudioStream = stream->uniqueId;
+ demuxerId = stream->demuxerId;
+ pStream = stream;
+ break;
+ }
+ }
+
+ if (m_nAudioStream == -1)
+ {
+ CLog::Log(LOGERROR, "{}: Could not find audio stream", __FUNCTION__);
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ return false;
+ }
+
+ CDVDStreamInfo hint(*pStream, true);
+
+ CAEStreamInfo::DataType ptStreamTye =
+ GetPassthroughStreamType(hint.codec, hint.samplerate, hint.profile);
+ m_pAudioCodec = CDVDFactoryCodec::CreateAudioCodec(hint, *m_processInfo, true, true, ptStreamTye);
+ if (!m_pAudioCodec)
+ {
+ CLog::Log(LOGERROR, "{}: Could not create audio codec", __FUNCTION__);
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+ return false;
+ }
+
+ // Extract ReplayGain info
+ // tagLoaderTagLib.Load will try to determine tag type by file extension, so set fallback by contentType
+ std::string strFallbackFileExtension = "";
+ if (m_strContentType == "audio/aacp" ||
+ m_strContentType == "audio/aac")
+ strFallbackFileExtension = "m4a";
+ else if (m_strContentType == "audio/x-ms-wma")
+ strFallbackFileExtension = "wma";
+ else if (m_strContentType == "audio/x-ape" ||
+ m_strContentType == "audio/ape")
+ strFallbackFileExtension = "ape";
+ CTagLoaderTagLib tagLoaderTagLib;
+ tagLoaderTagLib.Load(file.GetDynPath(), m_tag, strFallbackFileExtension);
+
+ // we have to decode initial data in order to get channels/samplerate
+ // for sanity - we read no more than 10 packets
+ int nErrors = 0;
+ for (int nPacket = 0;
+ nPacket < 10 && (m_channels == 0 || m_format.m_sampleRate == 0 || m_format.m_frameSize == 0);
+ nPacket++)
+ {
+ uint8_t dummy[256];
+ size_t nSize = 256;
+ if (ReadPCM(dummy, nSize, &nSize) == READ_ERROR)
+ ++nErrors;
+
+ m_srcFormat = m_pAudioCodec->GetFormat();
+ m_format = m_srcFormat;
+ m_channels = m_srcFormat.m_channelLayout.Count();
+ m_bitsPerSample = CAEUtil::DataFormatToBits(m_srcFormat.m_dataFormat);
+ m_bitsPerCodedSample = static_cast<CDemuxStreamAudio*>(pStream)->iBitsPerSample;
+ }
+ if (nErrors >= 10)
+ {
+ CLog::Log(LOGDEBUG, "{}: Could not decode data", __FUNCTION__);
+ return false;
+ }
+
+ // test if seeking is supported
+ m_bCanSeek = false;
+ if (m_pInputStream->Seek(0, SEEK_POSSIBLE))
+ {
+ if (Seek(1))
+ {
+ // rewind stream to beginning
+ Seek(0);
+ m_bCanSeek = true;
+ }
+ else
+ {
+ m_pInputStream->Seek(0, SEEK_SET);
+ if (!m_pDemuxer->Reset())
+ return false;
+ }
+ }
+
+ if (m_channels == 0) // no data - just guess and hope for the best
+ {
+ m_srcFormat.m_channelLayout = CAEChannelInfo(AE_CH_LAYOUT_2_0);
+ m_channels = m_srcFormat.m_channelLayout.Count();
+ }
+
+ if (m_srcFormat.m_sampleRate == 0)
+ m_srcFormat.m_sampleRate = 44100;
+
+ m_TotalTime = m_pDemuxer->GetStreamLength();
+ m_bitRate = m_pAudioCodec->GetBitRate();
+ if (!m_bitRate && m_TotalTime)
+ {
+ m_bitRate = (int)(((m_pInputStream->GetLength()*1000) / m_TotalTime) * 8);
+ }
+ m_CodecName = m_pDemuxer->GetStreamCodecName(demuxerId, m_nAudioStream);
+
+ m_needConvert = false;
+ if (NeedConvert(m_srcFormat.m_dataFormat))
+ {
+ m_needConvert = true;
+ // if we don't know the framesize yet, we will fail when converting
+ if (m_srcFormat.m_frameSize == 0)
+ return false;
+
+ m_pResampler = ActiveAE::CAEResampleFactory::Create();
+
+ SampleConfig dstConfig, srcConfig;
+ dstConfig.channel_layout = CAEUtil::GetAVChannelLayout(m_srcFormat.m_channelLayout);
+ dstConfig.channels = m_channels;
+ dstConfig.sample_rate = m_srcFormat.m_sampleRate;
+ dstConfig.fmt = CAEUtil::GetAVSampleFormat(AE_FMT_FLOAT);
+ dstConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(AE_FMT_FLOAT);
+ dstConfig.dither_bits = CAEUtil::DataFormatToDitherBits(AE_FMT_FLOAT);
+
+ srcConfig.channel_layout = CAEUtil::GetAVChannelLayout(m_srcFormat.m_channelLayout);
+ srcConfig.channels = m_channels;
+ srcConfig.sample_rate = m_srcFormat.m_sampleRate;
+ srcConfig.fmt = CAEUtil::GetAVSampleFormat(m_srcFormat.m_dataFormat);
+ srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_srcFormat.m_dataFormat);
+ srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_srcFormat.m_dataFormat);
+
+ m_pResampler->Init(dstConfig, srcConfig,
+ false,
+ false,
+ M_SQRT1_2,
+ NULL,
+ AE_QUALITY_UNKNOWN,
+ false);
+
+ m_planes = AE_IS_PLANAR(m_srcFormat.m_dataFormat) ? m_channels : 1;
+ m_format = m_srcFormat;
+ m_format.m_dataFormat = AE_FMT_FLOAT;
+ m_bitsPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat);
+ }
+
+ m_strFileName = file.GetDynPath();
+ m_bInited = true;
+
+ return true;
+}
+
+void VideoPlayerCodec::DeInit()
+{
+ if (m_pDemuxer != NULL)
+ {
+ delete m_pDemuxer;
+ m_pDemuxer = NULL;
+ }
+
+ if (m_pInputStream.use_count() > 1)
+ throw std::runtime_error("m_pInputStream reference count is greater than 1");
+ m_pInputStream.reset();
+
+ m_pAudioCodec.reset();
+
+ delete m_pResampler;
+ m_pResampler = NULL;
+
+ // cleanup format information
+ m_TotalTime = 0;
+ m_bitsPerSample = 0;
+ m_bitRate = 0;
+ m_channels = 0;
+ m_format.m_dataFormat = AE_FMT_INVALID;
+
+ m_nDecodedLen = 0;
+
+ m_strFileName = "";
+ m_bInited = false;
+}
+
+bool VideoPlayerCodec::Seek(int64_t iSeekTime)
+{
+ // default to announce backwards seek if !m_pPacket to not make FFmpeg
+ // skip mpeg audio frames at playback start
+ bool seekback = true;
+
+ bool ret = m_pDemuxer->SeekTime((int)iSeekTime, seekback);
+ m_pAudioCodec->Reset();
+
+ m_nDecodedLen = 0;
+
+ return ret;
+}
+
+int VideoPlayerCodec::ReadPCM(uint8_t* pBuffer, size_t size, size_t* actualsize)
+{
+ if (m_nDecodedLen > 0)
+ {
+ size_t nLen = (size < m_nDecodedLen) ? size : m_nDecodedLen;
+ *actualsize = nLen;
+ if (m_needConvert)
+ {
+ int samples = *actualsize / (m_bitsPerSample>>3);
+ int frames = samples / m_channels;
+ m_pResampler->Resample(&pBuffer, frames, m_audioFrame.data, frames, 1.0);
+ for (int i=0; i<m_planes; i++)
+ {
+ m_audioFrame.data[i] += frames*m_srcFormat.m_frameSize/m_planes;
+ }
+ }
+ else
+ {
+ memcpy(pBuffer, m_audioFrame.data[0], *actualsize);
+ m_audioFrame.data[0] += (*actualsize);
+ }
+ m_nDecodedLen -= nLen;
+ return READ_SUCCESS;
+ }
+
+ m_nDecodedLen = 0;
+ m_pAudioCodec->GetData(m_audioFrame);
+ int bytes = m_audioFrame.nb_frames * m_audioFrame.framesize;
+
+ if (!bytes)
+ {
+ DemuxPacket* pPacket = nullptr;
+ do
+ {
+ if (pPacket)
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ pPacket = m_pDemuxer->Read();
+ } while (pPacket && pPacket->iStreamId != m_nAudioStream);
+
+ if (!pPacket)
+ {
+ return READ_EOF;
+ }
+
+ pPacket->pts = DVD_NOPTS_VALUE;
+ pPacket->dts = DVD_NOPTS_VALUE;
+
+ int ret = m_pAudioCodec->AddData(*pPacket);
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ if (ret < 0)
+ {
+ return READ_ERROR;
+ }
+
+ m_pAudioCodec->GetData(m_audioFrame);
+ bytes = m_audioFrame.nb_frames * m_audioFrame.framesize;
+ }
+
+ m_nDecodedLen = bytes;
+ // scale decoded bytes to destination format
+ if (m_needConvert)
+ m_nDecodedLen *= (m_bitsPerSample>>3) / (m_srcFormat.m_frameSize / m_channels);
+
+ *actualsize = (m_nDecodedLen <= size) ? m_nDecodedLen : size;
+ if (*actualsize > 0)
+ {
+ if (m_needConvert)
+ {
+ int samples = *actualsize / (m_bitsPerSample>>3);
+ int frames = samples / m_channels;
+ m_pResampler->Resample(&pBuffer, frames, m_audioFrame.data, frames, 1.0);
+ for (int i=0; i<m_planes; i++)
+ {
+ m_audioFrame.data[i] += frames*m_srcFormat.m_frameSize/m_planes;
+ }
+ }
+ else
+ {
+ memcpy(pBuffer, m_audioFrame.data[0], *actualsize);
+ m_audioFrame.data[0] += *actualsize;
+ }
+ m_nDecodedLen -= *actualsize;
+ }
+
+ return READ_SUCCESS;
+}
+
+int VideoPlayerCodec::ReadRaw(uint8_t **pBuffer, int *bufferSize)
+{
+ DemuxPacket* pPacket;
+
+ m_nDecodedLen = 0;
+ DVDAudioFrame audioframe;
+
+ m_pAudioCodec->GetData(audioframe);
+ if (audioframe.nb_frames)
+ {
+ return READ_SUCCESS;
+ }
+
+ do
+ {
+ pPacket = m_pDemuxer->Read();
+ } while (pPacket && pPacket->iStreamId != m_nAudioStream);
+
+ if (!pPacket)
+ {
+ return READ_EOF;
+ }
+ pPacket->pts = DVD_NOPTS_VALUE;
+ pPacket->dts = DVD_NOPTS_VALUE;
+ int ret = m_pAudioCodec->AddData(*pPacket);
+ CDVDDemuxUtils::FreeDemuxPacket(pPacket);
+ if (ret < 0)
+ {
+ return READ_ERROR;
+ }
+
+ m_pAudioCodec->GetData(audioframe);
+ if (audioframe.nb_frames)
+ {
+ *bufferSize = audioframe.nb_frames;
+ *pBuffer = audioframe.data[0];
+ }
+ else
+ {
+ *bufferSize = 0;
+ }
+
+ return READ_SUCCESS;
+}
+
+bool VideoPlayerCodec::CanInit()
+{
+ return true;
+}
+
+bool VideoPlayerCodec::CanSeek()
+{
+ return m_bCanSeek;
+}
+
+bool VideoPlayerCodec::NeedConvert(AEDataFormat fmt)
+{
+ if (fmt == AE_FMT_RAW)
+ return false;
+
+ switch(fmt)
+ {
+ case AE_FMT_U8:
+ case AE_FMT_S16NE:
+ case AE_FMT_S32NE:
+ case AE_FMT_FLOAT:
+ case AE_FMT_DOUBLE:
+ return false;
+ default:
+ return true;
+ }
+}
+
+CAEStreamInfo::DataType VideoPlayerCodec::GetPassthroughStreamType(AVCodecID codecId,
+ int samplerate,
+ int profile)
+{
+ AEAudioFormat format;
+ format.m_dataFormat = AE_FMT_RAW;
+ format.m_sampleRate = samplerate;
+ format.m_streamInfo.m_type = CAEStreamInfo::DataType::STREAM_TYPE_NULL;
+ switch (codecId)
+ {
+ case AV_CODEC_ID_AC3:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_AC3;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_EAC3:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_EAC3;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_DTS:
+ if (profile == FF_PROFILE_DTS_HD_HRA)
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD;
+ else if (profile == FF_PROFILE_DTS_HD_MA)
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_MA;
+ else
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_CORE;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ case AV_CODEC_ID_TRUEHD:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_TRUEHD;
+ format.m_streamInfo.m_sampleRate = samplerate;
+ break;
+
+ default:
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_NULL;
+ }
+
+ bool supports = CServiceBroker::GetActiveAE()->SupportsRaw(format);
+
+ if (!supports && codecId == AV_CODEC_ID_DTS &&
+ format.m_streamInfo.m_type != CAEStreamInfo::STREAM_TYPE_DTSHD_CORE &&
+ CServiceBroker::GetActiveAE()->UsesDtsCoreFallback())
+ {
+ format.m_streamInfo.m_type = CAEStreamInfo::STREAM_TYPE_DTSHD_CORE;
+ supports = CServiceBroker::GetActiveAE()->SupportsRaw(format);
+ }
+
+ if (supports)
+ return format.m_streamInfo.m_type;
+ else
+ return CAEStreamInfo::DataType::STREAM_TYPE_NULL;
+}
diff --git a/xbmc/cores/paplayer/VideoPlayerCodec.h b/xbmc/cores/paplayer/VideoPlayerCodec.h
new file mode 100644
index 0000000..08a9c2f
--- /dev/null
+++ b/xbmc/cores/paplayer/VideoPlayerCodec.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-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 "ICodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodec.h"
+#include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
+#include "cores/VideoPlayer/DVDInputStreams/DVDInputStream.h"
+
+namespace ActiveAE
+{
+ class IAEResample;
+};
+
+class VideoPlayerCodec : public ICodec
+{
+public:
+ VideoPlayerCodec();
+ ~VideoPlayerCodec() override;
+
+ bool Init(const CFileItem &file, unsigned int filecache) override;
+ bool Seek(int64_t iSeekTime) override;
+ int ReadPCM(uint8_t* pBuffer, size_t size, size_t* actualsize) override;
+ int ReadRaw(uint8_t **pBuffer, int *bufferSize) override;
+ bool CanInit() override;
+ bool CanSeek() override;
+
+ void DeInit();
+ AEAudioFormat GetFormat();
+ void SetContentType(const std::string &strContent);
+
+ bool NeedConvert(AEDataFormat fmt);
+ void SetPassthroughStreamType(CAEStreamInfo::DataType streamType);
+
+private:
+ CAEStreamInfo::DataType GetPassthroughStreamType(AVCodecID codecId, int samplerate, int profile);
+
+ CDVDDemux* m_pDemuxer{nullptr};
+ std::shared_ptr<CDVDInputStream> m_pInputStream;
+ std::unique_ptr<CDVDAudioCodec> m_pAudioCodec;
+
+ std::string m_strContentType;
+ std::string m_strFileName;
+ int m_nAudioStream{-1};
+ size_t m_nDecodedLen{0};
+
+ bool m_bInited{false};
+ bool m_bCanSeek{false};
+
+ ActiveAE::IAEResample* m_pResampler{nullptr};
+ DVDAudioFrame m_audioFrame{};
+ int m_planes{0};
+ bool m_needConvert{false};
+ AEAudioFormat m_srcFormat{};
+ int m_channels{0};
+
+ std::unique_ptr<CProcessInfo> m_processInfo;
+};
+