/* * 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 "AudioSinkAE.h" #include "DVDClock.h" #include "DVDCodecs/Audio/DVDAudioCodec.h" #include "ServiceBroker.h" #include "cores/AudioEngine/Interfaces/AE.h" #include "cores/AudioEngine/Utils/AEAudioFormat.h" #include "cores/AudioEngine/Utils/AEStreamData.h" #include "utils/XTimeUtils.h" #include "utils/log.h" #include using namespace std::chrono_literals; CAudioSinkAE::CAudioSinkAE(CDVDClock *clock) : m_pClock(clock) { m_bPassthrough = false; m_iBitsPerSample = 0; m_sampleRate = 0; m_bPaused = true; m_playingPts = DVD_NOPTS_VALUE; //silence coverity uninitialized warning, is set elsewhere m_timeOfPts = 0.0; //silence coverity uninitialized warning, is set elsewhere m_syncError = 0.0; m_syncErrorTime = 0; } CAudioSinkAE::~CAudioSinkAE() { std::unique_lock lock(m_critSection); } bool CAudioSinkAE::Create(const DVDAudioFrame &audioframe, AVCodecID codec, bool needresampler) { CLog::Log(LOGINFO, "Creating audio stream (codec id: {}, channels: {}, sample rate: {}, {})", codec, audioframe.format.m_channelLayout.Count(), audioframe.format.m_sampleRate, audioframe.passthrough ? "pass-through" : "no pass-through"); // if passthrough isset do something else std::unique_lock lock(m_critSection); unsigned int options = needresampler && !audioframe.passthrough ? AESTREAM_FORCE_RESAMPLE : 0; options |= AESTREAM_PAUSED; AEAudioFormat format = audioframe.format; m_pAudioStream = CServiceBroker::GetActiveAE()->MakeStream( format, options, this ); if (!m_pAudioStream) return false; m_dataFormat = audioframe.format.m_dataFormat; m_sampleRate = audioframe.format.m_sampleRate; m_iBitsPerSample = audioframe.bits_per_sample; m_bPassthrough = audioframe.passthrough; m_channelLayout = audioframe.format.m_channelLayout; m_dataType = audioframe.format.m_streamInfo.m_type; return true; } void CAudioSinkAE::Destroy(bool finish) { std::unique_lock lock(m_critSection); if (m_pAudioStream) { m_pAudioStream.get_deleter().setFinish(finish); m_pAudioStream.reset(); } m_pAudioStream = NULL; m_sampleRate = 0; m_iBitsPerSample = 0; m_bPassthrough = false; m_bPaused = true; m_playingPts = DVD_NOPTS_VALUE; } unsigned int CAudioSinkAE::AddPackets(const DVDAudioFrame &audioframe) { m_bAbort = false; std::unique_lock lock(m_critSection); if (!m_pAudioStream) return 0; CAESyncInfo info = m_pAudioStream->GetSyncInfo(); if (info.state == CAESyncInfo::SYNC_INSYNC) { unsigned int newTime = info.errortime; if (newTime != m_syncErrorTime) { m_syncErrorTime = info.errortime; m_syncError = info.error / 1000 * DVD_TIME_BASE; m_resampleRatio = info.rr; } } else { m_syncErrorTime = 0; m_syncError = 0.0; } //Calculate a timeout when this definitely should be done double timeout; timeout = DVD_SEC_TO_TIME(m_pAudioStream->GetDelay()) + audioframe.duration; timeout += DVD_SEC_TO_TIME(1.0); timeout += m_pClock->GetAbsoluteClock(); unsigned int total = audioframe.nb_frames - audioframe.framesOut; unsigned int frames = total; unsigned int offset = audioframe.framesOut; do { IAEStream::ExtData ext; if (offset == 0) { ext.pts = audioframe.pts / DVD_TIME_BASE * 1000; } if (audioframe.hasDownmix) { ext.hasDownmix = true; ext.centerMixLevel = audioframe.centerMixLevel; } unsigned int copied = m_pAudioStream->AddData(audioframe.data, offset, frames, &ext); offset += copied; frames -= copied; if (frames <= 0) break; if (copied == 0 && timeout < m_pClock->GetAbsoluteClock()) { CLog::Log(LOGERROR, "CDVDAudio::AddPacketsRenderer - timeout adding data to renderer"); break; } lock.unlock(); KODI::TIME::Sleep(1ms); lock.lock(); } while (!m_bAbort); m_playingPts = audioframe.pts + audioframe.duration - GetDelay(); m_timeOfPts = m_pClock->GetAbsoluteClock(); return total - frames; } void CAudioSinkAE::Drain() { std::unique_lock lock(m_critSection); if (m_pAudioStream) m_pAudioStream->Drain(true); } void CAudioSinkAE::SetVolume(float volume) { std::unique_lock lock(m_critSection); if (m_pAudioStream) m_pAudioStream->SetVolume(volume); } void CAudioSinkAE::SetDynamicRangeCompression(long drc) { std::unique_lock lock(m_critSection); if (m_pAudioStream) m_pAudioStream->SetAmplification(powf(10.0f, (float)drc / 2000.0f)); } void CAudioSinkAE::Pause() { std::unique_lock lock(m_critSection); if (m_pAudioStream) m_pAudioStream->Pause(); CLog::Log(LOGDEBUG,"CDVDAudio::Pause - pausing audio stream"); m_playingPts = DVD_NOPTS_VALUE; } void CAudioSinkAE::Resume() { std::unique_lock lock(m_critSection); if (m_pAudioStream) m_pAudioStream->Resume(); CLog::Log(LOGDEBUG,"CDVDAudio::Resume - resume audio stream"); } double CAudioSinkAE::GetDelay() { std::unique_lock lock(m_critSection); double delay = 0.3; if(m_pAudioStream) delay = m_pAudioStream->GetDelay(); return delay * DVD_TIME_BASE; } void CAudioSinkAE::Flush() { m_bAbort = true; std::unique_lock lock(m_critSection); if (m_pAudioStream) { m_pAudioStream->Flush(); CLog::Log(LOGDEBUG,"CDVDAudio::Flush - flush audio stream"); } m_playingPts = DVD_NOPTS_VALUE; m_syncError = 0.0; m_syncErrorTime = 0; } void CAudioSinkAE::AbortAddPackets() { m_bAbort = true; } bool CAudioSinkAE::IsValidFormat(const DVDAudioFrame &audioframe) { if (!m_pAudioStream) return false; if (audioframe.passthrough != m_bPassthrough) return false; if (m_dataFormat != audioframe.format.m_dataFormat || m_sampleRate != audioframe.format.m_sampleRate || m_iBitsPerSample != audioframe.bits_per_sample || m_channelLayout != audioframe.format.m_channelLayout) return false; if (m_bPassthrough && m_dataType != audioframe.format.m_streamInfo.m_type) return false; return true; } double CAudioSinkAE::GetCacheTime() { std::unique_lock lock(m_critSection); if (!m_pAudioStream) return 0.0; return m_pAudioStream->GetCacheTime(); } double CAudioSinkAE::GetCacheTotal() { std::unique_lock lock(m_critSection); if (!m_pAudioStream) return 0.0; return m_pAudioStream->GetCacheTotal(); } double CAudioSinkAE::GetMaxDelay() { std::unique_lock lock(m_critSection); if (!m_pAudioStream) return 0.0; return m_pAudioStream->GetMaxDelay(); } double CAudioSinkAE::GetPlayingPts() { if (m_playingPts == DVD_NOPTS_VALUE) return 0.0; double now = m_pClock->GetAbsoluteClock(); double diff = now - m_timeOfPts; double cache = GetCacheTime(); double played = 0.0; if (diff < cache) played = diff; else played = cache; m_timeOfPts = now; m_playingPts += played; return m_playingPts; } double CAudioSinkAE::GetSyncError() { return m_syncError; } void CAudioSinkAE::SetSyncErrorCorrection(double correction) { m_syncError += correction; } double CAudioSinkAE::GetResampleRatio() { return m_resampleRatio; } void CAudioSinkAE::SetResampleMode(int mode) { std::unique_lock lock(m_critSection); if(m_pAudioStream) { m_pAudioStream->SetResampleMode(mode); } } double CAudioSinkAE::GetClock() { if (m_pClock) return (m_pClock->GetClock() + m_pClock->GetVsyncAdjust()) / DVD_TIME_BASE * 1000; else return 0.0; } double CAudioSinkAE::GetClockSpeed() { if (m_pClock) return m_pClock->GetClockSpeed(); else return 1.0; } CAEStreamInfo::DataType CAudioSinkAE::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; }