diff options
57 files changed, 6151 insertions, 108 deletions
diff --git a/language/English/strings.po b/language/English/strings.po index 55d187099c..6fbd2697dc 100644 --- a/language/English/strings.po +++ b/language/English/strings.po @@ -1762,7 +1762,10 @@ msgctxt "#420" msgid "HDMI" msgstr "" -#empty string with id 421 +#: system/settings/settings.xml +msgctxt "#421" +msgid "Stream silence when idle" +msgstr "" msgctxt "#422" msgid "Delete album info" @@ -12125,8 +12128,13 @@ msgctxt "#34110" msgid "7.1" msgstr "" -#empty strings from id 34111 to 34119 -#34111-34119 reserved for future use +#: system/settings/settings.xml +msgctxt "#34111" +msgid "When activated silence is output in order to keep alive receiver, otherwise sink is drained in idle state" +msgstr "" + +#empty strings from id 34112 to 34119 +#34112-34119 reserved for future use #: system/settings/settings.xml msgctxt "#34120" @@ -12890,7 +12898,7 @@ msgstr "" #: system/settings/settings.xml msgctxt "#36169" -msgid "No info available yet." +msgid "Resampling and other sound processing quality. Low quality is fast, higher quality will consume more CPU." msgstr "" #: system/settings/settings.xml diff --git a/lib/DllAvUtil.h b/lib/DllAvUtil.h index 1afee5e367..521263f7d9 100644 --- a/lib/DllAvUtil.h +++ b/lib/DllAvUtil.h @@ -82,6 +82,8 @@ public: virtual const AVCRC* av_crc_get_table(AVCRCId crc_id)=0; virtual uint32_t av_crc(const AVCRC *ctx, uint32_t crc, const uint8_t *buffer, size_t length)=0; virtual int av_opt_set(void *obj, const char *name, const char *val, int search_flags)=0; + virtual int av_opt_set_double(void *obj, const char *name, double val, int search_flags)=0; + virtual int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags)=0; virtual AVFifoBuffer *av_fifo_alloc(unsigned int size) = 0; virtual void av_fifo_free(AVFifoBuffer *f) = 0; virtual void av_fifo_reset(AVFifoBuffer *f) = 0; @@ -95,6 +97,9 @@ public: virtual void av_dict_free(AVDictionary **pm) = 0; virtual int av_samples_get_buffer_size (int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align) = 0; virtual int64_t av_get_default_channel_layout(int nb_channels)=0; + virtual int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align) = 0; + virtual int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt) = 0; + virtual int av_get_channel_layout_channel_index (uint64_t channel_layout, uint64_t channel) = 0; }; #if defined (USE_EXTERNAL_FFMPEG) || (defined TARGET_DARWIN) @@ -116,6 +121,8 @@ public: virtual const AVCRC* av_crc_get_table(AVCRCId crc_id) { return ::av_crc_get_table(crc_id); } virtual uint32_t av_crc(const AVCRC *ctx, uint32_t crc, const uint8_t *buffer, size_t length) { return ::av_crc(ctx, crc, buffer, length); } virtual int av_opt_set(void *obj, const char *name, const char *val, int search_flags) { return ::av_opt_set(obj, name, val, search_flags); } + virtual int av_opt_set_double(void *obj, const char *name, double val, int search_flags) { return ::av_opt_set_double(obj, name, val, search_flags); } + virtual int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags) { return ::av_opt_set_int(obj, name, val, search_flags); } virtual AVFifoBuffer *av_fifo_alloc(unsigned int size) {return ::av_fifo_alloc(size); } virtual void av_fifo_free(AVFifoBuffer *f) { ::av_fifo_free(f); } virtual void av_fifo_reset(AVFifoBuffer *f) { ::av_fifo_reset(f); } @@ -133,6 +140,10 @@ public: virtual int av_samples_get_buffer_size (int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align) { return ::av_samples_get_buffer_size(linesize, nb_channels, nb_samples, sample_fmt, align); } virtual int64_t av_get_default_channel_layout(int nb_channels) { return ::av_get_default_channel_layout(nb_channels); } + virtual int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align) + { return ::av_samples_alloc(audio_data, linesize, nb_channels, nb_samples, sample_fmt, align); } + virtual int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt) { return ::av_sample_fmt_is_planar(sample_fmt); } + virtual int av_get_channel_layout_channel_index (uint64_t channel_layout, uint64_t channel) { return ::av_get_channel_layout_channel_index(channel_layout, channel); } // DLL faking. virtual bool ResolveExports() { return true; } @@ -165,6 +176,8 @@ class DllAvUtilBase : public DllDynamic, DllAvUtilInterface DEFINE_METHOD5(int, av_crc_init, (AVCRC *p1, int p2, int p3, uint32_t p4, int p5)); DEFINE_METHOD4(uint32_t, av_crc, (const AVCRC *p1, uint32_t p2, const uint8_t *p3, size_t p4)); DEFINE_METHOD4(int, av_opt_set, (void *p1, const char *p2, const char *p3, int p4)); + DEFINE_METHOD4(int, av_opt_set_double, (void *p1, const char *p2, double p3, int p4)) + DEFINE_METHOD4(int, av_opt_set_int, (void *p1, const char *p2, int64_t p3, int p4)) DEFINE_METHOD1(AVFifoBuffer*, av_fifo_alloc, (unsigned int p1)) DEFINE_METHOD1(void, av_fifo_free, (AVFifoBuffer *p1)) DEFINE_METHOD1(void, av_fifo_reset, (AVFifoBuffer *p1)) @@ -178,6 +191,9 @@ class DllAvUtilBase : public DllDynamic, DllAvUtilInterface DEFINE_METHOD1(void, av_dict_free, (AVDictionary **p1)); DEFINE_METHOD5(int, av_samples_get_buffer_size, (int *p1, int p2, int p3, enum AVSampleFormat p4, int p5)) DEFINE_METHOD1(int64_t, av_get_default_channel_layout, (int p1)) + DEFINE_METHOD6(int, av_samples_alloc, (uint8_t **p1, int *p2, int p3, int p4, enum AVSampleFormat p5, int p6)) + DEFINE_METHOD1(int, av_sample_fmt_is_planar, (enum AVSampleFormat p1)) + DEFINE_METHOD2(int, av_get_channel_layout_channel_index, (uint64_t p1, uint64_t p2)) public: BEGIN_METHOD_RESOLVE() @@ -193,6 +209,8 @@ class DllAvUtilBase : public DllDynamic, DllAvUtilInterface RESOLVE_METHOD(av_crc_get_table) RESOLVE_METHOD(av_crc) RESOLVE_METHOD(av_opt_set) + RESOLVE_METHOD(av_opt_set_double) + RESOLVE_METHOD(av_opt_set_int) RESOLVE_METHOD(av_fifo_alloc) RESOLVE_METHOD(av_fifo_free) RESOLVE_METHOD(av_fifo_reset) @@ -206,6 +224,10 @@ class DllAvUtilBase : public DllDynamic, DllAvUtilInterface RESOLVE_METHOD(av_dict_free) RESOLVE_METHOD(av_samples_get_buffer_size) RESOLVE_METHOD(av_get_default_channel_layout) + RESOLVE_METHOD(av_samples_alloc) + RESOLVE_METHOD(av_sample_fmt_is_planar) + RESOLVE_METHOD(av_get_channel_layout_channel_index) + END_METHOD_RESOLVE() }; diff --git a/lib/DllSwResample.h b/lib/DllSwResample.h index e9613d35f4..57eeda133e 100644 --- a/lib/DllSwResample.h +++ b/lib/DllSwResample.h @@ -60,6 +60,9 @@ public: virtual int swr_init(struct SwrContext *s)=0; virtual void swr_free(struct SwrContext **s)=0; virtual int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in , int in_count)=0; + virtual int64_t swr_get_delay(struct SwrContext *s, int64_t base) = 0; + virtual int swr_set_channel_mapping(struct SwrContext *s, const int *channel_map) = 0; + virtual int swr_set_matrix(struct SwrContext *s, const double *matrix, int stride) = 0; }; #if (defined USE_EXTERNAL_FFMPEG) || (defined TARGET_DARWIN) @@ -84,6 +87,9 @@ public: virtual int swr_init(struct SwrContext *s) { return ::swr_init(s); } virtual void swr_free(struct SwrContext **s){ return ::swr_free(s); } virtual int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in , int in_count){ return ::swr_convert(s, out, out_count, in, in_count); } + virtual int64_t swr_get_delay(struct SwrContext *s, int64_t base) { return ::swr_get_delay(s, base); } + virtual int swr_set_channel_mapping (struct SwrContext *s, const int *channel_map) { return ::swr_set_channel_mapping(s, channel_map); } + virtual int swr_set_matrix(struct SwrContext *s, const double *matrix, int stride) { return ::swr_set_matrix(s, matrix, stride); } }; #else // Wrap the same API through libavresample. @@ -129,12 +135,18 @@ class DllSwResample : public DllDynamic, DllSwResampleInterface DEFINE_METHOD1(int, swr_init, (struct SwrContext *p1)) DEFINE_METHOD1(void, swr_free, (struct SwrContext **p1)) DEFINE_METHOD5(int, swr_convert, (struct SwrContext *p1, uint8_t **p2, int p3, const uint8_t **p4, int p5)) + DEFINE_METHOD2(int64_t, swr_get_delay, (struct SwrContext *p1, int64_t p2)) + DEFINE_METHOD2(int, swr_set_channel_mapping, (struct SwrContext *p1, const int *p2)) + DEFINE_METHOD3(int, swr_set_matrix, (struct SwrContext *p1, const double *p2, int p3)) BEGIN_METHOD_RESOLVE() RESOLVE_METHOD(swr_alloc_set_opts) RESOLVE_METHOD(swr_init) RESOLVE_METHOD(swr_free) RESOLVE_METHOD(swr_convert) + RESOLVE_METHOD(swr_get_delay) + RESOLVE_METHOD(swr_set_channel_mapping) + RESOLVE_METHOD(swr_set_matrix) END_METHOD_RESOLVE() /* dependencies of libavformat */ diff --git a/project/VS2010Express/XBMC.vcxproj b/project/VS2010Express/XBMC.vcxproj index 90200d46d9..e06fb25bdc 100644 --- a/project/VS2010Express/XBMC.vcxproj +++ b/project/VS2010Express/XBMC.vcxproj @@ -379,6 +379,12 @@ <ClCompile Include="..\..\xbmc\cores\AudioEngine\AEFactory.cpp" /> <ClCompile Include="..\..\xbmc\cores\AudioEngine\AESinkFactory.cpp" /> <ClCompile Include="..\..\xbmc\cores\AudioEngine\Encoders\AEEncoderFFmpeg.cpp" /> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAE.cpp" /> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEBuffer.cpp" /> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEResample.cpp" /> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESink.cpp" /> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESound.cpp" /> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEStream.cpp" /> <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\SoftAE\SoftAE.cpp" /> <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\SoftAE\SoftAESound.cpp" /> <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\SoftAE\SoftAEStream.cpp" /> @@ -1031,6 +1037,12 @@ <ClInclude Include="..\..\xbmc\cores\AudioEngine\AEFactory.h" /> <ClInclude Include="..\..\xbmc\cores\AudioEngine\AESinkFactory.h" /> <ClInclude Include="..\..\xbmc\cores\AudioEngine\Encoders\AEEncoderFFmpeg.h" /> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAE.h" /> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEBuffer.h" /> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEResample.h" /> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESink.h" /> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESound.h" /> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEStream.h" /> <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\SoftAE\SoftAE.h" /> <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\SoftAE\SoftAESound.h" /> <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\SoftAE\SoftAEStream.h" /> @@ -1177,6 +1189,7 @@ <ClInclude Include="..\..\xbmc\settings\windows\GUIWindowSettingsCategory.h" /> <ClInclude Include="..\..\xbmc\settings\windows\GUIWindowSettingsScreenCalibration.h" /> <ClInclude Include="..\..\xbmc\settings\windows\GUIWindowTestPattern.h" /> + <ClInclude Include="..\..\xbmc\utils\ActorProtocol.h" /> <ClInclude Include="..\..\xbmc\utils\BooleanLogic.h" /> <ClInclude Include="..\..\xbmc\utils\IRssObserver.h" /> <ClInclude Include="..\..\xbmc\utils\IXmlDeserializable.h" /> @@ -1319,6 +1332,7 @@ </ClInclude> <ClInclude Include="..\..\xbmc\interfaces\json-rpc\AddonsOperations.h" /> <ClCompile Include="..\..\xbmc\ThumbLoader.cpp" /> + <ClCompile Include="..\..\xbmc\utils\ActorProtocol.cpp" /> <ClCompile Include="..\..\xbmc\utils\BooleanLogic.cpp" /> <ClCompile Include="..\..\xbmc\utils\LegacyPathTranslation.cpp" /> <ClCompile Include="..\..\xbmc\utils\RssManager.cpp" /> @@ -2976,4 +2990,4 @@ </VisualStudio> </ProjectExtensions> <Import Project="$(SolutionDir)\$(ProjectFileName).targets.user" Condition="Exists('$(SolutionDir)\$(ProjectFileName).targets.user')" /> -</Project> +</Project>
\ No newline at end of file diff --git a/project/VS2010Express/XBMC.vcxproj.filters b/project/VS2010Express/XBMC.vcxproj.filters index f989167d0f..3b2c73da82 100644 --- a/project/VS2010Express/XBMC.vcxproj.filters +++ b/project/VS2010Express/XBMC.vcxproj.filters @@ -307,6 +307,9 @@ <Filter Include="interfaces\generic"> <UniqueIdentifier>{4286258a-45d7-45e8-9e56-ebf18fea53ec}</UniqueIdentifier> </Filter> + <Filter Include="cores\AudioEngine\Engines\ActiveAE"> + <UniqueIdentifier>{27f2c647-7b5f-4c49-b2e7-22bf360e58ab}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <ClCompile Include="..\..\xbmc\win32\pch.cpp"> @@ -3045,6 +3048,27 @@ <Filter>addons</Filter> </ClCompile> <ClCompile Include="..\..\xbmc\GitRevision.cpp" /> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAE.cpp"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClCompile> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEBuffer.cpp"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClCompile> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEResample.cpp"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClCompile> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESink.cpp"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClCompile> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESound.cpp"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClCompile> + <ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEStream.cpp"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClCompile> + <ClCompile Include="..\..\xbmc\utils\ActorProtocol.cpp"> + <Filter>utils</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="..\..\xbmc\win32\pch.h"> @@ -5972,6 +5996,27 @@ <Filter>addons</Filter> </ClInclude> <ClInclude Include="..\..\xbmc\win32\PlatformInclude.h" /> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAE.h"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClInclude> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEBuffer.h"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClInclude> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEResample.h"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClInclude> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESink.h"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClInclude> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAESound.h"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClInclude> + <ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAEStream.h"> + <Filter>cores\AudioEngine\Engines\ActiveAE</Filter> + </ClInclude> + <ClInclude Include="..\..\xbmc\utils\ActorProtocol.h"> + <Filter>utils</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ResourceCompile Include="..\..\xbmc\win32\XBMC_PC.rc"> @@ -6001,4 +6046,4 @@ <Filter>interfaces\swig</Filter> </None> </ItemGroup> -</Project> +</Project>
\ No newline at end of file diff --git a/system/settings/settings.xml b/system/settings/settings.xml index d5e05ac071..32573dcfd0 100644 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -1959,6 +1959,15 @@ <level>2</level> <default>true</default> </setting> + <setting id="audiooutput.processquality" type="integer" label="13505" help="36169"> + <visible>HAS_AE_QUALITY_LEVELS</visible> + <level>2</level> + <default>30</default> <!-- AE_QUALITY_MID --> + <constraints> + <options>aequalitylevels</options> + </constraints> + <control type="spinner" format="string" /> + </setting> <setting id="audiooutput.stereoupmix" type="boolean" label="252" help="36364"> <level>2</level> <default>false</default> @@ -2025,6 +2034,11 @@ </dependency> </dependencies> </setting> + <setting id="audiooutput.streamsilence" type="boolean" label="421" help="34111"> + <level>2</level> + <visible>audiosupportsdrain</visible> + <default>true</default> + </setting> </group> <group id="2"> <setting id="audiooutput.audiodevice" type="string" label="545" help="36371"> diff --git a/system/settings/win32.xml b/system/settings/win32.xml index 76bc2bf989..1e0a97bc80 100644 --- a/system/settings/win32.xml +++ b/system/settings/win32.xml @@ -39,5 +39,33 @@ </setting> </group> </category> + <category id="audiooutput" label="772" help="36360"> + <group id="2"> + <setting id="audiooutput.audiodevice" type="string" label="545" help="36371"> + <level>2</level> + <default>WASAPI:default</default> <!-- will be properly set on startup --> + <constraints> + <options>audiodevices</options> + </constraints> + <control type="spinner" format="string" /> + </setting> + <setting id="audiooutput.passthroughdevice" type="string" label="546" help="36372"> + <level>2</level> + <default>WASAPI:default</default> <!-- will be properly set on startup --> + <constraints> + <options>audiodevicespassthrough</options> + </constraints> + <dependencies> + <dependency type="enable"> + <or> + <condition setting="audiooutput.mode">1</condition> <!-- AUDIO_IEC958 --> + <condition setting="audiooutput.mode">2</condition> <!-- AUDIO_HDMI --> + </or> + </dependency> + </dependencies> + <control type="spinner" format="string" /> + </setting> + </group> + </category> </section> </settings> diff --git a/xbmc/addons/Visualisation.cpp b/xbmc/addons/Visualisation.cpp index 44c7bba548..8ac8ca9992 100644 --- a/xbmc/addons/Visualisation.cpp +++ b/xbmc/addons/Visualisation.cpp @@ -111,6 +111,7 @@ bool CVisualisation::Create(int x, int y, int w, int h, void *device) if (g_application.m_pPlayer) g_application.m_pPlayer->RegisterAudioCallback(this); + CAEFactory::RegisterAudioCallback(this); return true; } @@ -175,6 +176,7 @@ void CVisualisation::Render() void CVisualisation::Stop() { if (g_application.m_pPlayer) g_application.m_pPlayer->UnRegisterAudioCallback(); + CAEFactory::UnregisterAudioCallback(); if (Initialized()) { CAddonDll<DllVisualisation, Visualisation, VIS_PROPS>::Stop(); diff --git a/xbmc/cores/AudioEngine/AEAudioFormat.h b/xbmc/cores/AudioEngine/AEAudioFormat.h index 369811c038..64be36f794 100644 --- a/xbmc/cores/AudioEngine/AEAudioFormat.h +++ b/xbmc/cores/AudioEngine/AEAudioFormat.h @@ -31,26 +31,33 @@ enum AEDataFormat AE_FMT_INVALID = -1, AE_FMT_U8, + AE_FMT_U8P, AE_FMT_S8, AE_FMT_S16BE, AE_FMT_S16LE, AE_FMT_S16NE, + AE_FMT_S16NEP, AE_FMT_S32BE, AE_FMT_S32LE, AE_FMT_S32NE, + AE_FMT_S32NEP, AE_FMT_S24BE4, AE_FMT_S24LE4, AE_FMT_S24NE4, /* S24 in 4 bytes */ + AE_FMT_S24NE4P, AE_FMT_S24BE3, AE_FMT_S24LE3, AE_FMT_S24NE3, /* S24 in 3 bytes */ + AE_FMT_S24NE3P, AE_FMT_DOUBLE, + AE_FMT_DOUBLEP, AE_FMT_FLOAT, + AE_FMT_FLOATP, /* Bitstream formats */ AE_FMT_AAC, diff --git a/xbmc/cores/AudioEngine/AEFactory.cpp b/xbmc/cores/AudioEngine/AEFactory.cpp index 7141dd34d0..50a309c22c 100644 --- a/xbmc/cores/AudioEngine/AEFactory.cpp +++ b/xbmc/cores/AudioEngine/AEFactory.cpp @@ -27,6 +27,7 @@ #include "settings/SettingsManager.h" #else #include "Engines/SoftAE/SoftAE.h" + #include "Engines/ActiveAE/ActiveAE.h" #endif #if defined(HAS_PULSEAUDIO) @@ -67,6 +68,22 @@ bool CAEFactory::LoadEngine() #endif if (!loaded && engine == "SOFT" ) loaded = CAEFactory::LoadEngine(AE_ENGINE_SOFT); + if (!loaded && engine == "ACTIVE") + loaded = CAEFactory::LoadEngine(AE_ENGINE_ACTIVE); + } +#endif + +#if defined(TARGET_WINDOWS) + std::string engine; + if (getenv("AE_ENGINE")) + { + engine = (std::string)getenv("AE_ENGINE"); + std::transform(engine.begin(), engine.end(), engine.begin(), ::toupper); + + if (!loaded && engine == "SOFT" ) + loaded = CAEFactory::LoadEngine(AE_ENGINE_SOFT); + if (!loaded && engine == "ACTIVE") + loaded = CAEFactory::LoadEngine(AE_ENGINE_ACTIVE); } #endif @@ -99,6 +116,7 @@ bool CAEFactory::LoadEngine(enum AEEngine engine) case AE_ENGINE_COREAUDIO: AE = new CCoreAudioAE(); break; #else case AE_ENGINE_SOFT : AE = new CSoftAE(); break; + case AE_ENGINE_ACTIVE : AE = new ActiveAE::CActiveAE(); break; #endif #if defined(HAS_PULSEAUDIO) case AE_ENGINE_PULSE : AE = new CPulseAE(); break; @@ -243,6 +261,28 @@ bool CAEFactory::SupportsRaw() return false; } +bool CAEFactory::SupportsDrain() +{ + if(AE) + return AE->SupportsDrain(); + + return false; +} + +/** + * Returns true if current AudioEngine supports at lest two basic quality levels + * @return true if quality setting is supported, otherwise false + */ +bool CAEFactory::SupportsQualitySetting(void) +{ + if (!AE) + return false; + + return ((AE->SupportsQualityLevel(AE_QUALITY_LOW)? 1 : 0) + + (AE->SupportsQualityLevel(AE_QUALITY_MID)? 1 : 0) + + (AE->SupportsQualityLevel(AE_QUALITY_HIGH)? 1 : 0)) >= 2; +} + void CAEFactory::SetMute(const bool enabled) { if(AE) @@ -323,6 +363,21 @@ void CAEFactory::SettingOptionsAudioOutputModesFiller(const CSetting *setting, s list.push_back(std::make_pair(g_localizeStrings.Get(420), AUDIO_HDMI)); } +void CAEFactory::SettingOptionsAudioQualityLevelsFiller(const CSetting *setting, std::vector< std::pair<std::string, int> > &list, int ¤t) +{ + if (!AE) + return; + + if(AE->SupportsQualityLevel(AE_QUALITY_LOW)) + list.push_back(std::make_pair(g_localizeStrings.Get(13506), AE_QUALITY_LOW)); + if(AE->SupportsQualityLevel(AE_QUALITY_MID)) + list.push_back(std::make_pair(g_localizeStrings.Get(13507), AE_QUALITY_MID)); + if(AE->SupportsQualityLevel(AE_QUALITY_HIGH)) + list.push_back(std::make_pair(g_localizeStrings.Get(13508), AE_QUALITY_HIGH)); + if(AE->SupportsQualityLevel(AE_QUALITY_REALLYHIGH)) + list.push_back(std::make_pair(g_localizeStrings.Get(13509), AE_QUALITY_REALLYHIGH)); +} + void CAEFactory::SettingOptionsAudioDevicesFillerGeneral(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string ¤t, bool passthrough) { current = ((const CSettingString*)setting)->GetValue(); @@ -358,3 +413,15 @@ void CAEFactory::SettingOptionsAudioDevicesFillerGeneral(const CSetting *setting if (!foundValue) current = firstDevice; } + +void CAEFactory::RegisterAudioCallback(IAudioCallback* pCallback) +{ + if (AE) + AE->RegisterAudioCallback(pCallback); +} + +void CAEFactory::UnregisterAudioCallback() +{ + if (AE) + AE->UnregisterAudioCallback(); +} diff --git a/xbmc/cores/AudioEngine/AEFactory.h b/xbmc/cores/AudioEngine/AEFactory.h index 56a1e273f3..ed345b6140 100644 --- a/xbmc/cores/AudioEngine/AEFactory.h +++ b/xbmc/cores/AudioEngine/AEFactory.h @@ -31,7 +31,8 @@ enum AEEngine AE_ENGINE_NULL, AE_ENGINE_SOFT, AE_ENGINE_COREAUDIO, - AE_ENGINE_PULSE + AE_ENGINE_PULSE, + AE_ENGINE_ACTIVE }; class CAEFactory @@ -53,6 +54,13 @@ public: static void VerifyOutputDevice(std::string &device, bool passthrough); static std::string GetDefaultDevice(bool passthrough); static bool SupportsRaw(); + static bool SupportsDrain(); + + /** + * Returns true if current AudioEngine supports at lest two basic quality levels + * @return true if quality setting is supported, otherwise false + */ + static bool SupportsQualitySetting(void); static void SetMute(const bool enabled); static bool IsMuted(); static float GetVolume(); @@ -66,6 +74,10 @@ public: static void SettingOptionsAudioDevicesFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string ¤t); static void SettingOptionsAudioDevicesPassthroughFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string ¤t); static void SettingOptionsAudioOutputModesFiller(const CSetting *setting, std::vector< std::pair<std::string, int> > &list, int ¤t); + static void SettingOptionsAudioQualityLevelsFiller(const CSetting *setting, std::vector< std::pair<std::string, int> > &list, int ¤t); + + static void RegisterAudioCallback(IAudioCallback* pCallback); + static void UnregisterAudioCallback(); private: static bool LoadEngine(enum AEEngine engine); diff --git a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp index ba89868d64..2f305b12a9 100644 --- a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp +++ b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp @@ -98,7 +98,7 @@ unsigned int CAEEncoderFFmpeg::BuildChannelLayout(const int64_t ffmap, CAEChanne return layout.Count(); } -bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format) +bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format, bool allow_planar_input) { Reset(); @@ -150,6 +150,7 @@ bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format) bool hasS32 = false; bool hasS16 = false; bool hasU8 = false; + bool hasFloatP = false; bool hasUnknownFormat = false; for(int i = 0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) @@ -161,6 +162,12 @@ bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format) case AV_SAMPLE_FMT_S32: hasS32 = true; break; case AV_SAMPLE_FMT_S16: hasS16 = true; break; case AV_SAMPLE_FMT_U8 : hasU8 = true; break; + case AV_SAMPLE_FMT_FLTP: + if (allow_planar_input) + hasFloatP = true; + else + hasUnknownFormat = true; + break; case AV_SAMPLE_FMT_NONE: return false; default: hasUnknownFormat = true; break; } @@ -171,6 +178,11 @@ bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format) m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_FLT; format.m_dataFormat = AE_FMT_FLOAT; } + else if (hasFloatP) + { + m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP; + format.m_dataFormat = AE_FMT_FLOATP; + } else if (hasDouble) { m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_DBL; @@ -214,7 +226,6 @@ bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format) return false; } - format.m_dataFormat = AE_FMT_FLOAT; format.m_frames = m_CodecCtx->frame_size; format.m_frameSamples = m_CodecCtx->frame_size * m_CodecCtx->channels; format.m_frameSize = m_CodecCtx->channels * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3); @@ -351,6 +362,57 @@ int CAEEncoderFFmpeg::Encode(float *data, unsigned int frames) return m_NeededFrames; } +int CAEEncoderFFmpeg::Encode(uint8_t *in, int in_size, uint8_t *out, int out_size) +{ + int got_output; + AVFrame *frame; + + if (!m_CodecCtx) + return 0; + + /* allocate the input frame + * sadly, we have to alloc/dealloc it everytime since we have no guarantee the + * data argument will be constant over iterated calls and the frame needs to + * setup pointers inside data */ + frame = m_dllAvCodec.avcodec_alloc_frame(); + if (!frame) + return 0; + + frame->nb_samples = m_CodecCtx->frame_size; + frame->format = m_CodecCtx->sample_fmt; + frame->channel_layout = m_CodecCtx->channel_layout; + + m_dllAvCodec.avcodec_fill_audio_frame(frame, m_CodecCtx->channels, m_CodecCtx->sample_fmt, + in, in_size, 0); + + /* initialize the output packet */ + m_dllAvCodec.av_init_packet(&m_Pkt); + m_Pkt.size = out_size - IEC61937_DATA_OFFSET; + m_Pkt.data = out + IEC61937_DATA_OFFSET; + + /* encode it */ + int ret = m_dllAvCodec.avcodec_encode_audio2(m_CodecCtx, &m_Pkt, frame, &got_output); + + /* free temporary data */ + m_dllAvCodec.avcodec_free_frame(&frame); + + if (ret < 0 || !got_output) + { + CLog::Log(LOGERROR, "CAEEncoderFFmpeg::Encode - Encoding failed"); + return 0; + } + + /* pack it into an IEC958 frame */ + m_PackFunc(NULL, m_Pkt.size, out); + + /* free the packet */ + m_dllAvCodec.av_free_packet(&m_Pkt); + + /* return the number of frames used */ + return m_NeededFrames; +} + + int CAEEncoderFFmpeg::GetData(uint8_t **data) { int size; diff --git a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h index 4ecc9f2951..4fb67c4525 100644 --- a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h +++ b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h @@ -36,7 +36,7 @@ public: virtual ~CAEEncoderFFmpeg(); virtual bool IsCompatible(AEAudioFormat format); - virtual bool Initialize(AEAudioFormat &format); + virtual bool Initialize(AEAudioFormat &format, bool allow_planar_input = false); virtual void Reset(); virtual unsigned int GetBitRate (); @@ -44,6 +44,7 @@ public: virtual unsigned int GetFrames (); virtual int Encode (float *data, unsigned int frames); + virtual int Encode (uint8_t *in, int in_size, uint8_t *out, int out_size); virtual int GetData(uint8_t **data); virtual double GetDelay(unsigned int bufferSize); private: diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp new file mode 100644 index 0000000000..31aa3c0a53 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -0,0 +1,2311 @@ +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "ActiveAE.h" + +using namespace ActiveAE; +#include "ActiveAESound.h" +#include "ActiveAEStream.h" +#include "Utils/AEUtil.h" +#include "Encoders/AEEncoderFFmpeg.h" + +#include "settings/Settings.h" +#include "settings/AdvancedSettings.h" +#include "windowing/WindowingFactory.h" + +#define MAX_CACHE_LEVEL 0.5 // total cache time of stream in seconds +#define MAX_WATER_LEVEL 0.25 // buffered time after stream stages in seconds + +void CEngineStats::Reset(unsigned int sampleRate) +{ + CSingleLock lock(m_lock); + m_sinkUpdate = XbmcThreads::SystemClockMillis(); + m_sinkDelay = 0; + m_sinkSampleRate = sampleRate; + m_bufferedSamples = 0; + m_suspended = false; +} + +void CEngineStats::UpdateSinkDelay(double delay, int samples) +{ + CSingleLock lock(m_lock); + m_sinkUpdate = XbmcThreads::SystemClockMillis(); + m_sinkDelay = delay; + if (samples > m_bufferedSamples) + { + CLog::Log(LOGERROR, "CEngineStats::UpdateSinkDelay - inconsistency in buffer time"); + } + else + m_bufferedSamples -= samples; +} + +void CEngineStats::AddSamples(int samples, std::list<CActiveAEStream*> &streams) +{ + CSingleLock lock(m_lock); + m_bufferedSamples += samples; + + //update buffered time of streams + std::list<CActiveAEStream*>::iterator it; + for(it=streams.begin(); it!=streams.end(); ++it) + { + float delay = 0; + std::deque<CSampleBuffer*>::iterator itBuf; + for(itBuf=(*it)->m_processingSamples.begin(); itBuf!=(*it)->m_processingSamples.end(); ++itBuf) + { + delay += (float)(*itBuf)->pkt->nb_samples / (*itBuf)->pkt->config.sample_rate; + } + delay += (*it)->m_resampleBuffers->GetDelay(); + (*it)->m_bufferedTime = delay; + } +} + +float CEngineStats::GetDelay() +{ + CSingleLock lock(m_lock); + unsigned int now = XbmcThreads::SystemClockMillis(); + float delay = m_sinkDelay - (double)(now-m_sinkUpdate) / 1000; + delay += (float)m_bufferedSamples / m_sinkSampleRate; + + return delay; +} + +float CEngineStats::GetDelay(CActiveAEStream *stream) +{ + CSingleLock lock(m_lock); + unsigned int now = XbmcThreads::SystemClockMillis(); + float delay = m_sinkDelay - (double)(now-m_sinkUpdate) / 1000; + delay += (float)m_bufferedSamples / m_sinkSampleRate; + + delay += stream->m_bufferedTime; + return delay; +} + +float CEngineStats::GetCacheTime(CActiveAEStream *stream) +{ + CSingleLock lock(m_lock); + float delay = (float)m_bufferedSamples / m_sinkSampleRate; + + delay += stream->m_bufferedTime; + return delay; +} + +float CEngineStats::GetCacheTotal(CActiveAEStream *stream) +{ + return MAX_CACHE_LEVEL + m_sinkCacheTotal; +} + +float CEngineStats::GetWaterLevel() +{ + return (float)m_bufferedSamples / m_sinkSampleRate; +} + +void CEngineStats::SetSuspended(bool state) +{ + CSingleLock lock(m_lock); + m_suspended = state; +} + +bool CEngineStats::IsSuspended() +{ + CSingleLock lock(m_lock); + return m_suspended; +} + +CActiveAE::CActiveAE() : + CThread("ActiveAE"), + m_controlPort("OutputControlPort", &m_inMsgEvent, &m_outMsgEvent), + m_dataPort("OutputDataPort", &m_inMsgEvent, &m_outMsgEvent), + m_sink(&m_outMsgEvent) +{ + m_sinkBuffers = NULL; + m_silenceBuffers = NULL; + m_encoderBuffers = NULL; + m_vizBuffers = NULL; + m_volume = 1.0; + m_aeVolume = 1.0; + m_muted = false; + m_aeMuted = false; + m_mode = MODE_PCM; + m_encoder = NULL; + m_audioCallback = NULL; + m_vizInitialized = false; + m_sinkHasVolume = false; +} + +CActiveAE::~CActiveAE() +{ + Dispose(); +} + +void CActiveAE::Dispose() +{ +#if defined(HAS_GLX) || defined(TARGET_DARWIN_OSX) + g_Windowing.Unregister(this); +#endif + + m_bStop = true; + m_outMsgEvent.Set(); + StopThread(); + m_controlPort.Purge(); + m_dataPort.Purge(); + m_sink.Dispose(); + + m_dllAvFormat.Unload(); + m_dllAvCodec.Unload(); + m_dllAvUtil.Unload(); +} + +//----------------------------------------------------------------------------- +// Behavior +//----------------------------------------------------------------------------- + +enum AE_STATES +{ + AE_TOP = 0, // 0 + AE_TOP_ERROR, // 1 + AE_TOP_UNCONFIGURED, // 2 + AE_TOP_RECONFIGURING, // 3 + AE_TOP_CONFIGURED, // 4 + AE_TOP_CONFIGURED_SUSPEND, // 5 + AE_TOP_CONFIGURED_IDLE, // 6 + AE_TOP_CONFIGURED_PLAY, // 7 +}; + +int AE_parentStates[] = { + -1, + 0, //TOP_ERROR + 0, //TOP_UNCONFIGURED + 0, //TOP_CONFIGURED + 0, //TOP_RECONFIGURING + 4, //TOP_CONFIGURED_SUSPEND + 4, //TOP_CONFIGURED_IDLE + 4, //TOP_CONFIGURED_PLAY +}; + +void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) +{ + for (int state = m_state; ; state = AE_parentStates[state]) + { + switch (state) + { + case AE_TOP: // TOP + if (port == &m_controlPort) + { + switch (signal) + { + case CActiveAEControlProtocol::GETSTATE: + msg->Reply(CActiveAEControlProtocol::ACC, &m_state, sizeof(m_state)); + return; + case CActiveAEControlProtocol::SOUNDMODE: + m_soundMode = *(int*)msg->data; + return; + case CActiveAEControlProtocol::VOLUME: + m_volume = *(float*)msg->data; + if (m_sinkHasVolume) + m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::VOLUME, &m_volume, sizeof(float)); + return; + case CActiveAEControlProtocol::MUTE: + m_muted = *(bool*)msg->data; + return; + default: + break; + } + } + else if (port == &m_dataPort) + { + switch (signal) + { + case CActiveAEDataProtocol::NEWSOUND: + CActiveAESound *sound; + sound = *(CActiveAESound**)msg->data; + if (sound) + { + m_sounds.push_back(sound); + ResampleSounds(); + } + return; + case CActiveAEDataProtocol::FREESTREAM: + CActiveAEStream *stream; + stream = *(CActiveAEStream**)msg->data; + DiscardStream(stream); + return; + case CActiveAEDataProtocol::FREESOUND: + sound = *(CActiveAESound**)msg->data; + DiscardSound(sound); + return; + case CActiveAEDataProtocol::DRAINSTREAM: + stream = *(CActiveAEStream**)msg->data; + stream->m_drain = true; + stream->m_resampleBuffers->m_drain = true; + msg->Reply(CActiveAEDataProtocol::ACC); + stream->m_streamPort->SendInMessage(CActiveAEDataProtocol::STREAMDRAINED); + return; + default: + break; + } + } + else if (&m_sink.m_dataPort) + { + switch (signal) + { + case CSinkDataProtocol::RETURNSAMPLE: + CSampleBuffer **buffer; + buffer = (CSampleBuffer**)msg->data; + if (buffer) + { + (*buffer)->Return(); + } + return; + default: + break; + } + } + { + std::string portName = port == NULL ? "timer" : port->portName; + CLog::Log(LOGWARNING, "CActiveAE::%s - signal: %d from port: %s not handled for state: %d", __FUNCTION__, signal, portName.c_str(), m_state); + } + return; + + case AE_TOP_ERROR: + if (port == NULL) // timeout + { + switch (signal) + { + case CActiveAEControlProtocol::TIMEOUT: + m_extError = false; + LoadSettings(); + Configure(); + if (!m_extError) + { + m_state = AE_TOP_CONFIGURED_IDLE; + m_extTimeout = 0; + } + else + { + m_state = AE_TOP_ERROR; + m_extTimeout = 500; + } + return; + default: + break; + } + } + break; + + case AE_TOP_UNCONFIGURED: + if (port == &m_controlPort) + { + switch (signal) + { + case CActiveAEControlProtocol::INIT: + m_extError = false; + m_sink.EnumerateSinkList(); + LoadSettings(); + Configure(); + msg->Reply(CActiveAEControlProtocol::ACC); + if (!m_extError) + { + m_state = AE_TOP_CONFIGURED_IDLE; + m_extTimeout = 0; + } + else + { + m_state = AE_TOP_ERROR; + m_extTimeout = 500; + } + return; + + default: + break; + } + } + break; + + case AE_TOP_RECONFIGURING: + if (port == NULL) // timeout + { + switch (signal) + { + case CActiveAEControlProtocol::TIMEOUT: + // drain + if (RunStages()) + { + m_extTimeout = 0; + return; + } + if (!m_sinkBuffers->m_inputSamples.empty() || !m_sinkBuffers->m_outputSamples.empty()) + { + m_extTimeout = 100; + return; + } + if (NeedReconfigureSink()) + DrainSink(); + + if (!m_extError) + Configure(); + if (!m_extError) + { + m_state = AE_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + } + else + { + m_state = AE_TOP_ERROR; + m_extTimeout = 500; + } + m_extDeferData = false; + return; + default: + break; + } + } + break; + + case AE_TOP_CONFIGURED: + if (port == &m_controlPort) + { + switch (signal) + { + case CActiveAEControlProtocol::RECONFIGURE: + if (m_streams.empty()) + { + bool silence = false; + m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::SILENCEMODE, &silence, sizeof(bool)); + } + LoadSettings(); + ChangeResampleQuality(); + if (!NeedReconfigureBuffers() && !NeedReconfigureSink()) + return; + m_state = AE_TOP_RECONFIGURING; + m_extTimeout = 0; + // don't accept any data until we are reconfigured + m_extDeferData = true; + return; + case CActiveAEControlProtocol::SUSPEND: + UnconfigureSink(); + m_stats.SetSuspended(true); + m_state = AE_TOP_CONFIGURED_SUSPEND; + m_extDeferData = true; + return; + case CActiveAEControlProtocol::DISPLAYLOST: + if (m_settings.mode == AUDIO_HDMI) + { + UnconfigureSink(); + m_stats.SetSuspended(true); + m_state = AE_TOP_CONFIGURED_SUSPEND; + m_extDeferData = true; + } + return; + case CActiveAEControlProtocol::PAUSESTREAM: + CActiveAEStream *stream; + stream = *(CActiveAEStream**)msg->data; + stream->m_paused = true; + return; + case CActiveAEControlProtocol::RESUMESTREAM: + stream = *(CActiveAEStream**)msg->data; + stream->m_paused = false; + m_state = AE_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + return; + case CActiveAEControlProtocol::STREAMAMP: + MsgStreamParameter *par; + par = (MsgStreamParameter*)msg->data; + par->stream->m_limiter.SetAmplification(par->parameter.float_par); + return; + case CActiveAEControlProtocol::STREAMVOLUME: + par = (MsgStreamParameter*)msg->data; + par->stream->m_volume = par->parameter.float_par; + return; + case CActiveAEControlProtocol::STREAMRGAIN: + par = (MsgStreamParameter*)msg->data; + par->stream->m_rgain = par->parameter.float_par; + return; + case CActiveAEControlProtocol::STREAMRESAMPLERATIO: + par = (MsgStreamParameter*)msg->data; + if (par->stream->m_resampleBuffers) + { + if ((unsigned int)(par->stream->m_resampleBuffers->m_format.m_sampleRate * par->parameter.double_par) != par->stream->m_resampleBuffers->m_outSampleRate) + { + par->stream->m_resampleBuffers->m_resampleRatio = par->parameter.double_par; + par->stream->m_resampleBuffers->m_resampleQuality = AE_QUALITY_LOW; + par->stream->m_resampleBuffers->m_changeResampler = true; + } + } + return; + case CActiveAEControlProtocol::STREAMFADE: + MsgStreamFade *fade; + fade = (MsgStreamFade*)msg->data; + fade->stream->m_fadingBase = fade->from; + fade->stream->m_fadingTarget = fade->target; + fade->stream->m_fadingTime = fade->millis; + fade->stream->m_fadingSamples = -1; + return; + case CActiveAEControlProtocol::STOPSOUND: + CActiveAESound *sound; + sound = *(CActiveAESound**)msg->data; + SStopSound(sound); + return; + default: + break; + } + } + else if (port == &m_dataPort) + { + switch (signal) + { + case CActiveAEDataProtocol::PLAYSOUND: + CActiveAESound *sound; + sound = *(CActiveAESound**)msg->data; + if (m_soundMode == AE_SOUND_OFF || + (m_soundMode == AE_SOUND_IDLE && !m_streams.empty())) + return; + if (sound) + { + SoundState st = {sound, 0}; + m_sounds_playing.push_back(st); + m_extTimeout = 0; + m_state = AE_TOP_CONFIGURED_PLAY; + } + return; + case CActiveAEDataProtocol::NEWSTREAM: + MsgStreamNew *streamMsg; + CActiveAEStream *stream; + streamMsg = (MsgStreamNew*)msg->data; + stream = CreateStream(streamMsg); + if(stream) + { + msg->Reply(CActiveAEDataProtocol::ACC, &stream, sizeof(CActiveAEStream*)); + Configure(); + if (!m_extError) + { + m_state = AE_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + } + else + { + m_state = AE_TOP_ERROR; + m_extTimeout = 500; + } + } + else + msg->Reply(CActiveAEDataProtocol::ERR); + return; + case CActiveAEDataProtocol::STREAMSAMPLE: + MsgStreamSample *msgData; + CSampleBuffer *samples; + msgData = (MsgStreamSample*)msg->data; + samples = msgData->stream->m_processingSamples.front(); + msgData->stream->m_processingSamples.pop_front(); + if (samples != msgData->buffer) + CLog::Log(LOGERROR, "CActiveAE - inconsistency in stream sample message"); + if (msgData->buffer->pkt->nb_samples == 0) + msgData->buffer->Return(); + else + msgData->stream->m_resampleBuffers->m_inputSamples.push_back(msgData->buffer); + m_extTimeout = 0; + m_state = AE_TOP_CONFIGURED_PLAY; + return; + case CActiveAEDataProtocol::FREESTREAM: + stream = *(CActiveAEStream**)msg->data; + DiscardStream(stream); + if (m_streams.empty()) + { + m_extDrainTimer.Set(m_stats.GetDelay() * 1000); + m_extDrain = true; + } + m_extTimeout = 0; + m_state = AE_TOP_CONFIGURED_PLAY; + return; + case CActiveAEDataProtocol::DRAINSTREAM: + stream = *(CActiveAEStream**)msg->data; + stream->m_drain = true; + stream->m_resampleBuffers->m_drain = true; + m_extTimeout = 0; + m_state = AE_TOP_CONFIGURED_PLAY; + msg->Reply(CActiveAEDataProtocol::ACC); + return; + case CActiveAEDataProtocol::FLUSHSTREAM: + stream = *(CActiveAEStream**)msg->data; + SFlushStream(stream); + msg->Reply(CActiveAEDataProtocol::ACC); + return; + default: + break; + } + } + else if (&m_sink.m_dataPort) + { + switch (signal) + { + case CSinkDataProtocol::RETURNSAMPLE: + CSampleBuffer **buffer; + buffer = (CSampleBuffer**)msg->data; + if (buffer) + { + (*buffer)->Return(); + } + m_extTimeout = 0; + m_state = AE_TOP_CONFIGURED_PLAY; + return; + default: + break; + } + } + break; + + case AE_TOP_CONFIGURED_SUSPEND: + if (port == &m_controlPort) + { + bool displayReset = false; + switch (signal) + { + case CActiveAEControlProtocol::DISPLAYRESET: + displayReset = true; + case CActiveAEControlProtocol::INIT: + m_extError = false; + if (!displayReset) + { + m_sink.EnumerateSinkList(); + LoadSettings(); + } + Configure(); + if (!displayReset) + msg->Reply(CActiveAEControlProtocol::ACC); + if (!m_extError) + { + m_state = AE_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + } + else + { + m_state = AE_TOP_ERROR; + m_extTimeout = 500; + } + m_stats.SetSuspended(false); + m_extDeferData = false; + return; + default: + break; + } + } + break; + + case AE_TOP_CONFIGURED_IDLE: + if (port == NULL) // timeout + { + switch (signal) + { + case CActiveAEControlProtocol::TIMEOUT: + ResampleSounds(); + ClearDiscardedBuffers(); + if (m_extDrain) + { + if (m_extDrainTimer.IsTimePast()) + { + Configure(); + if (!m_extError) + { + m_state = AE_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + } + else + { + m_state = AE_TOP_ERROR; + m_extTimeout = 500; + } + } + else + m_extTimeout = m_extDrainTimer.MillisLeft(); + } + else + m_extTimeout = 5000; + return; + default: + break; + } + } + break; + + case AE_TOP_CONFIGURED_PLAY: + if (port == NULL) // timeout + { + switch (signal) + { + case CActiveAEControlProtocol::TIMEOUT: + if (RunStages()) + { + m_extTimeout = 0; + return; + } + if (!m_extDrain && HasWork()) + { + ResampleSounds(); + ClearDiscardedBuffers(); + m_extTimeout = 100; + return; + } + m_extTimeout = 0; + m_state = AE_TOP_CONFIGURED_IDLE; + return; + default: + break; + } + } + break; + + default: // we are in no state, should not happen + CLog::Log(LOGERROR, "CActiveAE::%s - no valid state: %d", __FUNCTION__, m_state); + return; + } + } // for +} + +void CActiveAE::Process() +{ + Message *msg = NULL; + Protocol *port = NULL; + bool gotMsg; + + m_state = AE_TOP_UNCONFIGURED; + m_extTimeout = 1000; + m_bStateMachineSelfTrigger = false; + m_extDrain = false; + m_extDeferData = false; + + // start sink + m_sink.Start(); + + while (!m_bStop) + { + gotMsg = false; + + if (m_bStateMachineSelfTrigger) + { + m_bStateMachineSelfTrigger = false; + // self trigger state machine + StateMachine(msg->signal, port, msg); + if (!m_bStateMachineSelfTrigger) + { + msg->Release(); + msg = NULL; + } + continue; + } + // check control port + else if (m_controlPort.ReceiveOutMessage(&msg)) + { + gotMsg = true; + port = &m_controlPort; + } + // check sink data port + else if (m_sink.m_dataPort.ReceiveInMessage(&msg)) + { + gotMsg = true; + port = &m_sink.m_dataPort; + } + else if (!m_extDeferData) + { + // check data port + if (m_dataPort.ReceiveOutMessage(&msg)) + { + gotMsg = true; + port = &m_dataPort; + } + // stream data ports + else + { + std::list<CActiveAEStream*>::iterator it; + for(it=m_streams.begin(); it!=m_streams.end(); ++it) + { + if((*it)->m_streamPort->ReceiveOutMessage(&msg)) + { + gotMsg = true; + port = &m_dataPort; + break; + } + } + } + } + + if (gotMsg) + { + StateMachine(msg->signal, port, msg); + if (!m_bStateMachineSelfTrigger) + { + msg->Release(); + msg = NULL; + } + continue; + } + + // wait for message + else if (m_outMsgEvent.WaitMSec(m_extTimeout)) + { + continue; + } + // time out + else + { + msg = m_controlPort.GetMessage(); + msg->signal = CActiveAEControlProtocol::TIMEOUT; + port = 0; + // signal timeout to state machine + StateMachine(msg->signal, port, msg); + if (!m_bStateMachineSelfTrigger) + { + msg->Release(); + msg = NULL; + } + } + } +} + +void CActiveAE::Configure(AEAudioFormat *desiredFmt) +{ + bool initSink = false; + AEAudioFormat sinkInputFormat, inputFormat; + m_mode = MODE_PCM; + + if (m_streams.empty()) + { + inputFormat.m_dataFormat = AE_FMT_FLOAT; + inputFormat.m_sampleRate = 44100; + inputFormat.m_encodedRate = 0; + inputFormat.m_channelLayout = AE_CH_LAYOUT_2_0; + inputFormat.m_frames = 0; + inputFormat.m_frameSamples = 0; + inputFormat.m_frameSize = 0; + } + // force input format after unpausing slave + else if (desiredFmt != NULL) + { + inputFormat = *desiredFmt; + } + // keep format when having multiple streams + else if (m_streams.size() > 1 && m_silenceBuffers == NULL) + { + inputFormat = m_sinkRequestFormat; + } + else + { + inputFormat = m_streams.front()->m_format; + } + + m_sinkRequestFormat = inputFormat; + ApplySettingsToFormat(m_sinkRequestFormat, m_settings, true); + std::string device = AE_IS_RAW(m_sinkRequestFormat.m_dataFormat) ? m_settings.passthoughdevice : m_settings.device; + std::string driver; + CAESinkFactory::ParseDevice(device, driver); + if (!m_sink.IsCompatible(m_sinkRequestFormat, device) || m_settings.driver.compare(driver) != 0) + { + if (!InitSink()) + return; + m_settings.driver = driver; + initSink = true; + m_stats.Reset(m_sinkFormat.m_sampleRate); + m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::VOLUME, &m_volume, sizeof(float)); + } + + if (m_silenceBuffers) + { + m_discardBufferPools.push_back(m_silenceBuffers); + m_silenceBuffers = NULL; + } + + // buffers for driving gui sounds if no streams are active + if (m_streams.empty()) + { + inputFormat = m_sinkFormat; + inputFormat.m_dataFormat = AE_FMT_FLOAT; + inputFormat.m_frameSize = inputFormat.m_channelLayout.Count() * + (CAEUtil::DataFormatToBits(inputFormat.m_dataFormat) >> 3); + m_silenceBuffers = new CActiveAEBufferPool(inputFormat); + m_silenceBuffers->Create(MAX_WATER_LEVEL*1000); + sinkInputFormat = inputFormat; + m_internalFormat = inputFormat; + + bool silence = false; + m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::SILENCEMODE, &silence, sizeof(bool)); + + delete m_encoder; + m_encoder = NULL; + + if (m_encoderBuffers) + { + m_discardBufferPools.push_back(m_encoderBuffers); + m_encoderBuffers = NULL; + } + if (m_vizBuffers) + { + m_discardBufferPools.push_back(m_vizBuffers); + m_vizBuffers = NULL; + } + } + // resample buffers for streams + else + { + bool silence = true; + m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::SILENCEMODE, &silence, sizeof(bool)); + + AEAudioFormat outputFormat; + if (m_mode == MODE_RAW) + { + outputFormat = inputFormat; + sinkInputFormat = m_sinkFormat; + } + // transcode everything with more than 2 channels + else if (m_mode == MODE_TRANSCODE) + { + outputFormat = inputFormat; + outputFormat.m_dataFormat = AE_FMT_FLOATP; + + if (g_advancedSettings.m_audioResample) + { + outputFormat.m_sampleRate = g_advancedSettings.m_audioResample; + CLog::Log(LOGINFO, "CActiveAE::Configure - Forcing samplerate to %d", inputFormat.m_sampleRate); + } + + // setup encoder + if (!m_encoder) + { + m_encoder = new CAEEncoderFFmpeg(); + m_encoder->Initialize(outputFormat, true); + m_encoderFormat = outputFormat; + } + else + outputFormat = m_encoderFormat; + + outputFormat.m_channelLayout = m_encoderFormat.m_channelLayout; + outputFormat.m_frames = m_encoderFormat.m_frames; + + // encoder buffer + if (m_encoder->GetCodecID() == CODEC_ID_AC3) + { + AEAudioFormat format; + format.m_channelLayout = AE_CH_LAYOUT_2_0; + format.m_dataFormat = AE_FMT_S16NE; + format.m_frameSize = 2* (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3); + format.m_frames = AC3_FRAME_SIZE; + format.m_sampleRate = 48000; + if (m_encoderBuffers && initSink) + { + m_discardBufferPools.push_back(m_encoderBuffers); + m_encoderBuffers = NULL; + } + if (!m_encoderBuffers) + { + m_encoderBuffers = new CActiveAEBufferPool(format); + m_encoderBuffers->Create(MAX_WATER_LEVEL*1000); + } + } + + sinkInputFormat = m_sinkFormat; + } + else + { + outputFormat = m_sinkFormat; + outputFormat.m_channelLayout = m_sinkRequestFormat.m_channelLayout; + outputFormat.m_channelLayout.ResolveChannels(m_sinkFormat.m_channelLayout); + outputFormat.m_dataFormat = AE_FMT_FLOAT; + outputFormat.m_frameSize = outputFormat.m_channelLayout.Count() * + (CAEUtil::DataFormatToBits(outputFormat.m_dataFormat) >> 3); + // TODO: adjust to decoder + sinkInputFormat = outputFormat; + } + m_internalFormat = outputFormat; + + std::list<CActiveAEStream*>::iterator it; + for(it=m_streams.begin(); it!=m_streams.end(); ++it) + { + // check if we support input format of stream + if (!AE_IS_RAW((*it)->m_format.m_dataFormat) && + CActiveAEResample::GetAVSampleFormat((*it)->m_format.m_dataFormat) == AV_SAMPLE_FMT_FLT && + (*it)->m_format.m_dataFormat != AE_FMT_FLOAT) + { + (*it)->m_convertFn = CAEConvert::ToFloat((*it)->m_format.m_dataFormat); + (*it)->m_format.m_dataFormat = AE_FMT_FLOAT; + } + + if (!(*it)->m_inputBuffers) + { + // align input buffers with period of sink or encoder + (*it)->m_format.m_frames = m_internalFormat.m_frames * ((float)(*it)->m_format.m_sampleRate / m_internalFormat.m_sampleRate); + + // create buffer pool + (*it)->m_inputBuffers = new CActiveAEBufferPool((*it)->m_format); + (*it)->m_inputBuffers->Create(MAX_CACHE_LEVEL*1000); + } + if (initSink && (*it)->m_resampleBuffers) + { + m_discardBufferPools.push_back((*it)->m_resampleBuffers); + (*it)->m_resampleBuffers = NULL; + } + if (!(*it)->m_resampleBuffers) + { + (*it)->m_resampleBuffers = new CActiveAEBufferPoolResample((*it)->m_inputBuffers->m_format, outputFormat, m_settings.resampleQuality); + (*it)->m_resampleBuffers->Create(MAX_CACHE_LEVEL*1000, false); + } + if (m_mode == MODE_TRANSCODE || m_streams.size() > 1) + (*it)->m_resampleBuffers->m_fillPackets = true; + } + + // buffers for viz + if (!AE_IS_RAW(inputFormat.m_dataFormat)) + { + if (initSink && m_vizBuffers) + { + m_discardBufferPools.push_back(m_vizBuffers); + m_vizBuffers = NULL; + } + if (!m_vizBuffers) + { + AEAudioFormat vizFormat = m_internalFormat; + vizFormat.m_channelLayout = AE_CH_LAYOUT_2_0; + vizFormat.m_dataFormat = AE_FMT_FLOAT; + m_vizBuffers = new CActiveAEBufferPoolResample(m_internalFormat, vizFormat, m_settings.resampleQuality); + // TODO use cache of sync + water level + m_vizBuffers->Create(2000, false); + m_vizInitialized = false; + } + } + } + + // resample buffers for sink + if (m_sinkBuffers && !m_sink.IsCompatible(m_sinkBuffers->m_format, device)) + { + m_discardBufferPools.push_back(m_sinkBuffers); + m_sinkBuffers = NULL; + } + if (!m_sinkBuffers) + { + m_sinkBuffers = new CActiveAEBufferPoolResample(sinkInputFormat, m_sinkFormat, m_settings.resampleQuality); + m_sinkBuffers->Create(MAX_WATER_LEVEL*1000, true); + } + + // reset gui sounds + std::vector<CActiveAESound*>::iterator it; + for (it = m_sounds.begin(); it != m_sounds.end(); ++it) + { + (*it)->SetConverted(false); + } + + ClearDiscardedBuffers(); + m_extDrain = false; +} + +CActiveAEStream* CActiveAE::CreateStream(MsgStreamNew *streamMsg) +{ + // we only can handle a single pass through stream + if (!m_streams.empty()) + { + if (AE_IS_RAW(m_streams.front()->m_format.m_dataFormat) || AE_IS_RAW(streamMsg->format.m_dataFormat)) + return NULL; + } + + // create the stream + CActiveAEStream *stream; + stream = new CActiveAEStream(&streamMsg->format); + stream->m_streamPort = new CActiveAEDataProtocol("stream", + &stream->m_inMsgEvent, &m_outMsgEvent); + + // create buffer pool + stream->m_inputBuffers = NULL; // create in Configure when we know the sink format + stream->m_resampleBuffers = NULL; // create in Configure when we know the sink format + stream->m_statsLock = m_stats.GetLock(); + stream->m_fadingSamples = 0; + stream->m_started = false; + + if (streamMsg->options & AESTREAM_PAUSED) + stream->m_paused = true; + + m_streams.push_back(stream); + + return stream; +} + +void CActiveAE::DiscardStream(CActiveAEStream *stream) +{ + std::list<CActiveAEStream*>::iterator it; + for (it=m_streams.begin(); it!=m_streams.end(); ) + { + if (stream == (*it)) + { + while (!(*it)->m_processingSamples.empty()) + { + (*it)->m_processingSamples.front()->Return(); + (*it)->m_processingSamples.pop_front(); + } + m_discardBufferPools.push_back((*it)->m_inputBuffers); + m_discardBufferPools.push_back((*it)->m_resampleBuffers); + CLog::Log(LOGDEBUG, "CActiveAE::DiscardStream - audio stream deleted"); + delete (*it)->m_streamPort; + delete (*it); + it = m_streams.erase(it); + } + else + ++it; + } + + ClearDiscardedBuffers(); +} + +void CActiveAE::SFlushStream(CActiveAEStream *stream) +{ + while (!stream->m_processingSamples.empty()) + { + stream->m_processingSamples.front()->Return(); + stream->m_processingSamples.pop_front(); + } + stream->m_resampleBuffers->Flush(); + stream->m_streamPort->Purge(); + stream->m_bufferedTime = 0.0; + stream->m_paused = true; +} + +void CActiveAE::ClearDiscardedBuffers() +{ + std::list<CActiveAEBufferPool*>::iterator it; + for (it=m_discardBufferPools.begin(); it!=m_discardBufferPools.end(); ++it) + { + CActiveAEBufferPoolResample *rbuf = dynamic_cast<CActiveAEBufferPoolResample*>(*it); + if (rbuf) + { + rbuf->Flush(); + } + // if all buffers have returned, we can delete the buffer pool + if ((*it)->m_allSamples.size() == (*it)->m_freeSamples.size()) + { + delete (*it); + CLog::Log(LOGDEBUG, "CActiveAE::ClearDiscardedBuffers - buffer pool deleted"); + m_discardBufferPools.erase(it); + return; + } + } +} + +void CActiveAE::SStopSound(CActiveAESound *sound) +{ + std::list<SoundState>::iterator it; + for (it=m_sounds_playing.begin(); it!=m_sounds_playing.end(); ++it) + { + if (it->sound == sound) + { + m_sounds_playing.erase(it); + return; + } + } +} + +void CActiveAE::DiscardSound(CActiveAESound *sound) +{ + SStopSound(sound); + + std::vector<CActiveAESound*>::iterator it; + for (it=m_sounds.begin(); it!=m_sounds.end(); ++it) + { + if ((*it) == sound) + { + m_sounds.erase(it); + return; + } + } +} + +float CActiveAE::CalcStreamAmplification(CActiveAEStream *stream, CSampleBuffer *buf) +{ + float amp = 1.0f; + int nb_floats = buf->pkt->nb_samples * buf->pkt->config.channels / buf->pkt->planes; + float tamp; + for(int i=0; i<buf->pkt->planes; i++) + { + tamp = stream->m_limiter.Run((float*)buf->pkt->data[i], nb_floats); + amp = std::min(amp, tamp); + } + return amp; +} + +void CActiveAE::ChangeResampleQuality() +{ + std::list<CActiveAEStream*>::iterator it; + for(it=m_streams.begin(); it!=m_streams.end(); ++it) + { + if ((*it)->m_resampleBuffers && (*it)->m_resampleBuffers->m_resampler && ((*it)->m_resampleBuffers->m_resampleQuality != m_settings.resampleQuality)) + (*it)->m_resampleBuffers->m_changeResampler = true; + (*it)->m_resampleBuffers->m_resampleQuality = m_settings.resampleQuality; + } +} + +void CActiveAE::ApplySettingsToFormat(AEAudioFormat &format, AudioSettings &settings, bool setmode) +{ + // raw pass through + if (m_settings.mode != AUDIO_ANALOG && AE_IS_RAW(format.m_dataFormat)) + { + if ((format.m_dataFormat == AE_FMT_AC3 && !settings.ac3passthrough) || + (format.m_dataFormat == AE_FMT_TRUEHD && !settings.truehdpassthrough) || + (format.m_dataFormat == AE_FMT_DTS && !settings.dtspassthrough) || + (format.m_dataFormat == AE_FMT_DTSHD && !settings.dtshdpassthrough)) + { + CLog::Log(LOGERROR, "CActiveAE::ApplySettingsToFormat - input audio format is wrong"); + } + if (setmode) + m_mode = MODE_RAW; + } + // transcode + else if (m_settings.mode != AUDIO_ANALOG && + settings.ac3passthrough && + (!settings.multichannellpcm || (m_settings.mode != AUDIO_HDMI)) && + !m_streams.empty() && + format.m_channelLayout.Count() > 2) + { + format.m_dataFormat = AE_FMT_AC3; + format.m_sampleRate = 48000; + if (setmode) + m_mode = MODE_TRANSCODE; + } + else + { + format.m_dataFormat = AE_FMT_FLOAT; + if ((format.m_channelLayout.Count() > 2) || settings.stereoupmix) + { + switch (settings.channels) + { + default: + case 0: format.m_channelLayout = AE_CH_LAYOUT_2_0; break; + case 1: format.m_channelLayout = AE_CH_LAYOUT_2_0; break; + case 2: format.m_channelLayout = AE_CH_LAYOUT_2_1; break; + case 3: format.m_channelLayout = AE_CH_LAYOUT_3_0; break; + case 4: format.m_channelLayout = AE_CH_LAYOUT_3_1; break; + case 5: format.m_channelLayout = AE_CH_LAYOUT_4_0; break; + case 6: format.m_channelLayout = AE_CH_LAYOUT_4_1; break; + case 7: format.m_channelLayout = AE_CH_LAYOUT_5_0; break; + case 8: format.m_channelLayout = AE_CH_LAYOUT_5_1; break; + case 9: format.m_channelLayout = AE_CH_LAYOUT_7_0; break; + case 10: format.m_channelLayout = AE_CH_LAYOUT_7_1; break; + } + } + + if (g_advancedSettings.m_audioResample) + { + format.m_sampleRate = g_advancedSettings.m_audioResample; + CLog::Log(LOGINFO, "CActiveAE::ApplySettings - Forcing samplerate to %d", format.m_sampleRate); + } + + // for IEC958 limit to 2 channels + if (m_settings.mode == AUDIO_IEC958) + { + format.m_channelLayout = AE_CH_LAYOUT_2_0; + } + + CAEChannelInfo stdLayout = format.m_channelLayout; + format.m_channelLayout.ResolveChannels(stdLayout); + } +} + +bool CActiveAE::NeedReconfigureBuffers() +{ + AEAudioFormat newFormat = m_sinkRequestFormat; + ApplySettingsToFormat(newFormat, m_settings); + + if (newFormat.m_dataFormat != m_sinkRequestFormat.m_dataFormat || + newFormat.m_channelLayout != m_sinkRequestFormat.m_channelLayout || + newFormat.m_sampleRate != m_sinkRequestFormat.m_sampleRate) + return true; + + return false; +} + +bool CActiveAE::NeedReconfigureSink() +{ + AEAudioFormat newFormat = m_sinkRequestFormat; + ApplySettingsToFormat(newFormat, m_settings); + + std::string device = AE_IS_RAW(newFormat.m_dataFormat) ? m_settings.passthoughdevice : m_settings.device; + std::string driver; + CAESinkFactory::ParseDevice(device, driver); + if (m_settings.driver.compare(driver) != 0) + return true; + + if (!m_sink.IsCompatible(newFormat, device)) + return true; + + return false; +} + +bool CActiveAE::InitSink() +{ + SinkConfig config; + config.format = m_sinkRequestFormat; + config.stats = &m_stats; + + // send message to sink + Message *reply; + if (m_sink.m_controlPort.SendOutMessageSync(CSinkControlProtocol::CONFIGURE, + &reply, + 5000, + &config, sizeof(config))) + { + bool success = reply->signal == CSinkControlProtocol::ACC ? true : false; + if (!success) + { + reply->Release(); + CLog::Log(LOGERROR, "ActiveAE::%s - returned error", __FUNCTION__); + m_extError = true; + return false; + } + AEAudioFormat *data; + data = (AEAudioFormat*)reply->data; + if (data) + { + m_sinkFormat = *data; + } + m_sinkHasVolume = m_sink.HasVolume(); + reply->Release(); + } + else + { + CLog::Log(LOGERROR, "ActiveAE::%s - failed to init", __FUNCTION__); + m_extError = true; + return false; + } + + m_inMsgEvent.Reset(); + return true; +} + +void CActiveAE::DrainSink() +{ + // send message to sink + Message *reply; + if (m_sink.m_dataPort.SendOutMessageSync(CSinkDataProtocol::DRAIN, + &reply, + 2000)) + { + bool success = reply->signal == CSinkDataProtocol::ACC ? true : false; + if (!success) + { + reply->Release(); + CLog::Log(LOGERROR, "ActiveAE::%s - returned error on drain", __FUNCTION__); + m_extError = true; + return; + } + reply->Release(); + } + else + { + CLog::Log(LOGERROR, "ActiveAE::%s - failed to drain", __FUNCTION__); + m_extError = true; + return; + } +} + +void CActiveAE::UnconfigureSink() +{ + // send message to sink + Message *reply; + if (m_sink.m_controlPort.SendOutMessageSync(CSinkControlProtocol::UNCONFIGURE, + &reply, + 2000)) + { + bool success = reply->signal == CSinkControlProtocol::ACC ? true : false; + if (!success) + { + CLog::Log(LOGERROR, "ActiveAE::%s - returned error", __FUNCTION__); + m_extError = true; + } + reply->Release(); + } + else + { + CLog::Log(LOGERROR, "ActiveAE::%s - failed to unconfigure", __FUNCTION__); + m_extError = true; + } + + m_inMsgEvent.Reset(); +} + + +bool CActiveAE::RunStages() +{ + bool busy = false; + + // serve input streams + std::list<CActiveAEStream*>::iterator it; + for (it = m_streams.begin(); it != m_streams.end(); ++it) + { + if ((*it)->m_resampleBuffers && !(*it)->m_paused) + busy = (*it)->m_resampleBuffers->ResampleBuffers(); + else if ((*it)->m_resampleBuffers && + ((*it)->m_resampleBuffers->m_inputSamples.size() > (*it)->m_resampleBuffers->m_allSamples.size() * 0.5)) + { + CSingleLock lock((*it)->m_streamLock); + (*it)->m_streamIsBuffering = false; + } + + // provide buffers to stream + float time = m_stats.GetCacheTime((*it)); + CSampleBuffer *buffer; + if (!(*it)->m_drain) + { + while (time < MAX_CACHE_LEVEL && !(*it)->m_inputBuffers->m_freeSamples.empty()) + { + buffer = (*it)->m_inputBuffers->GetFreeBuffer(); + (*it)->m_processingSamples.push_back(buffer); + (*it)->m_streamPort->SendInMessage(CActiveAEDataProtocol::STREAMBUFFER, &buffer, sizeof(CSampleBuffer*)); + (*it)->IncFreeBuffers(); + time += (float)buffer->pkt->max_nb_samples / buffer->pkt->config.sample_rate; + } + } + else + { + if ((*it)->m_inputBuffers->m_allSamples.size() == (*it)->m_inputBuffers->m_freeSamples.size()) + { + (*it)->m_streamPort->SendInMessage(CActiveAEDataProtocol::STREAMDRAINED); + (*it)->m_drain = false; + (*it)->m_resampleBuffers->m_drain = false; + (*it)->m_started = false; + + // set variables being polled via stream interface + CSingleLock lock((*it)->m_streamLock); + if ((*it)->m_streamSlave) + { + CActiveAEStream *slave = (CActiveAEStream*)((*it)->m_streamSlave); + slave->m_paused = false; + Configure(&slave->m_format); + (*it)->m_streamSlave = NULL; + } + (*it)->m_streamDrained = true; + (*it)->m_streamDraining = false; + } + } + } + + if (m_stats.GetWaterLevel() < MAX_WATER_LEVEL && + (m_mode != MODE_TRANSCODE || (m_encoderBuffers && !m_encoderBuffers->m_freeSamples.empty()))) + { + // mix streams and sounds sounds + if (m_mode != MODE_RAW) + { + CSampleBuffer *out = NULL; + if (!m_sounds_playing.empty() && m_streams.empty()) + { + if (m_silenceBuffers && !m_silenceBuffers->m_freeSamples.empty()) + { + out = m_silenceBuffers->GetFreeBuffer(); + for (int i=0; i<out->pkt->planes; i++) + { + memset(out->pkt->data[i], 0, out->pkt->linesize); + } + out->pkt->nb_samples = out->pkt->max_nb_samples; + } + } + + // mix streams + std::list<CActiveAEStream*>::iterator it; + + // if we deal with more than a single stream, all streams + // must provide samples for mixing + bool allStreamsReady = true; + for (it = m_streams.begin(); it != m_streams.end(); ++it) + { + if ((*it)->m_paused || !(*it)->m_started || !(*it)->m_resampleBuffers) + continue; + + if ((*it)->m_resampleBuffers->m_outputSamples.empty()) + allStreamsReady = false; + } + + for (it = m_streams.begin(); it != m_streams.end() && allStreamsReady; ++it) + { + if ((*it)->m_paused || !(*it)->m_resampleBuffers) + continue; + + if (!(*it)->m_resampleBuffers->m_outputSamples.empty()) + { + (*it)->m_started = true; + + if (!out) + { + out = (*it)->m_resampleBuffers->m_outputSamples.front(); + (*it)->m_resampleBuffers->m_outputSamples.pop_front(); + + // volume for stream + float amp = (*it)->m_rgain * CalcStreamAmplification((*it), out); + + int nb_floats = out->pkt->nb_samples * out->pkt->config.channels / out->pkt->planes; + int nb_loops = 1; + float fadingStep; + + // fading + if ((*it)->m_fadingSamples == -1) + { + (*it)->m_fadingSamples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f; + (*it)->m_volume = (*it)->m_fadingBase; + } + if ((*it)->m_fadingSamples > 0) + { + nb_floats = out->pkt->config.channels / out->pkt->planes; + nb_loops = out->pkt->nb_samples; + float delta = (*it)->m_fadingTarget - (*it)->m_fadingBase; + int samples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f; + fadingStep = delta / samples; + } + for(int i=0; i<nb_loops; i++) + { + if ((*it)->m_fadingSamples > 0) + { + (*it)->m_volume += fadingStep; + (*it)->m_fadingSamples--; + + if ((*it)->m_fadingSamples == 0) + { + // set variables being polled via stream interface + CSingleLock lock((*it)->m_streamLock); + (*it)->m_streamFading = false; + } + } + float volume = (*it)->m_volume * amp; + + for(int j=0; j<out->pkt->planes; j++) + { +#ifdef __SSE__ + CAEUtil::SSEMulArray((float*)out->pkt->data[j]+i*nb_floats, m_muted ? 0.0 : volume, nb_floats); +#else + float* fbuffer = (float*) out->pkt->data[j]+i*nb_floats; + for (int k = 0; k < nb_floats; ++k) + *fbuffer++ *= m_muted ? 0.0 : volume; +#endif + } + } + } + else + { + CSampleBuffer *mix = NULL; + mix = (*it)->m_resampleBuffers->m_outputSamples.front(); + (*it)->m_resampleBuffers->m_outputSamples.pop_front(); + + // volume for stream + float amp = (*it)->m_volume * (*it)->m_rgain * CalcStreamAmplification((*it), mix); + + int nb_floats = mix->pkt->nb_samples * mix->pkt->config.channels / mix->pkt->planes; + int nb_loops = 1; + float fadingStep; + + // fading + if ((*it)->m_fadingSamples == -1) + { + (*it)->m_fadingSamples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f; + (*it)->m_volume = (*it)->m_fadingBase; + } + if ((*it)->m_fadingSamples > 0) + { + nb_floats = mix->pkt->config.channels / mix->pkt->planes; + nb_loops = mix->pkt->nb_samples; + float delta = (*it)->m_fadingTarget - (*it)->m_fadingBase; + int samples = m_internalFormat.m_sampleRate * (float)(*it)->m_fadingTime / 1000.0f; + fadingStep = delta / samples; + } + for(int i=0; i<nb_loops; i++) + { + if ((*it)->m_fadingSamples > 0) + { + (*it)->m_volume += fadingStep; + (*it)->m_fadingSamples--; + + if ((*it)->m_fadingSamples == 0) + { + // set variables being polled via stream interface + CSingleLock lock((*it)->m_streamLock); + (*it)->m_streamFading = false; + } + } + float volume = (*it)->m_volume * amp; + + for(int j=0; j<out->pkt->planes && j<mix->pkt->planes; j++) + { + float *dst = (float*)out->pkt->data[j]+i*nb_floats; + float *src = (float*)mix->pkt->data[j]+i*nb_floats; +#ifdef __SSE__ + CAEUtil::SSEMulAddArray(dst, src, m_muted ? 0.0 : volume, nb_floats); +#else + for (int k = 0; k < nb_floats; ++k) + *dst++ += *src++ * m_muted ? 0.0 : volume; +#endif + } + } + mix->Return(); + } + busy = true; + } + } + + // process output buffer, gui sounds, encode, viz + if (out) + { + // mix gui sounds + MixSounds(*(out->pkt)); + if (!m_sinkHasVolume) + Deamplify(*(out->pkt)); + + // encode + if (m_mode == MODE_TRANSCODE && m_encoder) + { + CSampleBuffer *buf = m_encoderBuffers->GetFreeBuffer(); + int ret = m_encoder->Encode(out->pkt->data[0], out->pkt->planes*out->pkt->linesize, + buf->pkt->data[0], buf->pkt->planes*buf->pkt->linesize); + buf->pkt->nb_samples = buf->pkt->max_nb_samples; + out->Return(); + out = buf; + } + + // update stats + m_stats.AddSamples(out->pkt->nb_samples, m_streams); + m_sinkBuffers->m_inputSamples.push_back(out); + + busy = true; + } + + // viz + { + CSingleLock lock(m_vizLock); + if (m_audioCallback && m_vizBuffers && !m_streams.empty()) + { + if (!m_vizInitialized) + { + m_audioCallback->OnInitialize(2, m_vizBuffers->m_format.m_sampleRate, 32); + m_vizInitialized = true; + } + + // if viz has no free buffer, it won't return current buffer "out" + if (!m_vizBuffers->m_freeSamples.empty()) + { + if (out) + { + out->Acquire(); + m_vizBuffers->m_inputSamples.push_back(out); + } + } + else + CLog::Log(LOGWARNING,"ActiveAE::%s - viz ran out of free buffers", __FUNCTION__); + unsigned int now = XbmcThreads::SystemClockMillis(); + unsigned int timestamp = now + m_stats.GetDelay() * 1000; + busy |= m_vizBuffers->ResampleBuffers(timestamp); + while(!m_vizBuffers->m_outputSamples.empty()) + { + CSampleBuffer *buf = m_vizBuffers->m_outputSamples.front(); + if ((now - buf->timestamp) & 0x80000000) + break; + else + { + int submitted = 0; + int samples; + while(submitted < buf->pkt->nb_samples) + { + samples = std::min(512, buf->pkt->nb_samples-submitted); + m_audioCallback->OnAudioData((float*)(buf->pkt->data[0]+2*submitted), samples); + submitted += samples; + } + buf->Return(); + m_vizBuffers->m_outputSamples.pop_front(); + } + } + } + else if (m_vizBuffers) + m_vizBuffers->Flush(); + } + } + // pass through + else + { + std::list<CActiveAEStream*>::iterator it; + CSampleBuffer *buffer; + for (it = m_streams.begin(); it != m_streams.end(); ++it) + { + if (!(*it)->m_resampleBuffers->m_outputSamples.empty()) + { + buffer = (*it)->m_resampleBuffers->m_outputSamples.front(); + (*it)->m_resampleBuffers->m_outputSamples.pop_front(); + m_stats.AddSamples(buffer->pkt->nb_samples, m_streams); + m_sinkBuffers->m_inputSamples.push_back(buffer); + } + } + } + + // serve sink buffers + busy = m_sinkBuffers->ResampleBuffers(); + while(!m_sinkBuffers->m_outputSamples.empty()) + { + CSampleBuffer *out = NULL; + out = m_sinkBuffers->m_outputSamples.front(); + m_sinkBuffers->m_outputSamples.pop_front(); + m_sink.m_dataPort.SendOutMessage(CSinkDataProtocol::SAMPLE, + &out, sizeof(CSampleBuffer*)); + busy = true; + } + } + + return busy; +} + +bool CActiveAE::HasWork() +{ + if (!m_sounds_playing.empty()) + return true; + if (!m_sinkBuffers->m_inputSamples.empty()) + return true; + if (!m_sinkBuffers->m_outputSamples.empty()) + return true; + + std::list<CActiveAEStream*>::iterator it; + for (it = m_streams.begin(); it != m_streams.end(); ++it) + { + if (!(*it)->m_resampleBuffers->m_inputSamples.empty()) + return true; + if (!(*it)->m_resampleBuffers->m_outputSamples.empty()) + return true; + if (!(*it)->m_processingSamples.empty()) + return true; + } + + return false; +} + +void CActiveAE::MixSounds(CSoundPacket &dstSample) +{ + if (m_sounds_playing.empty()) + return; + + float volume; + float *out; + float *sample_buffer; + int max_samples = dstSample.nb_samples; + + std::list<SoundState>::iterator it; + for (it = m_sounds_playing.begin(); it != m_sounds_playing.end(); ) + { + if (!it->sound->IsConverted()) + ResampleSound(it->sound); + int available_samples = it->sound->GetSound(false)->nb_samples - it->samples_played; + int mix_samples = std::min(max_samples, available_samples); + int start = it->samples_played * + m_dllAvUtil.av_get_bytes_per_sample(it->sound->GetSound(false)->config.fmt) * + it->sound->GetSound(false)->config.channels / + it->sound->GetSound(false)->planes; + + for(int j=0; j<dstSample.planes; j++) + { + volume = it->sound->GetVolume(); + out = (float*)dstSample.data[j]; + sample_buffer = (float*)(it->sound->GetSound(false)->data[j]+start); + int nb_floats = mix_samples * dstSample.config.channels / dstSample.planes; +#ifdef __SSE__ + CAEUtil::SSEMulAddArray(out, sample_buffer, volume, nb_floats); +#else + for (int k = 0; k < nb_floats; ++k) + *out++ += *sample_buffer++ * volume; +#endif + } + + it->samples_played += mix_samples; + + // no more frames, so remove it from the list + if (it->samples_played >= it->sound->GetSound(false)->nb_samples) + { + it = m_sounds_playing.erase(it); + continue; + } + ++it; + } +} + +void CActiveAE::Deamplify(CSoundPacket &dstSample) +{ + if (m_volume < 1.0) + { + float *buffer; + int nb_floats = dstSample.nb_samples * dstSample.config.channels / dstSample.planes; + + for(int j=0; j<dstSample.planes; j++) + { + buffer = (float*)dstSample.data[j]; +#ifdef __SSE__ + CAEUtil::SSEMulArray(buffer, m_muted ? 0.0 : m_volume, nb_floats); +#else + float *fbuffer = buffer; + for (unsigned int i = 0; i < nb_floats; i++) + *fbuffer++ *= m_volume; +#endif + } + } +} + +//----------------------------------------------------------------------------- +// Configuration +//----------------------------------------------------------------------------- + +void CActiveAE::LoadSettings() +{ + m_settings.device = CSettings::Get().GetString("audiooutput.audiodevice"); + m_settings.passthoughdevice = CSettings::Get().GetString("audiooutput.passthroughdevice"); + + m_settings.mode = CSettings::Get().GetInt("audiooutput.mode"); + m_settings.channels = CSettings::Get().GetInt("audiooutput.channels"); + + m_settings.stereoupmix = CSettings::Get().GetBool("audiooutput.stereoupmix"); + m_settings.ac3passthrough = CSettings::Get().GetBool("audiooutput.ac3passthrough"); + m_settings.truehdpassthrough = CSettings::Get().GetBool("audiooutput.truehdpassthrough"); + m_settings.dtspassthrough = CSettings::Get().GetBool("audiooutput.dtspassthrough"); + m_settings.dtshdpassthrough = CSettings::Get().GetBool("audiooutput.dtshdpassthrough"); + m_settings.aacpassthrough = CSettings::Get().GetBool("audiooutput.passthroughaac"); + m_settings.multichannellpcm = CSettings::Get().GetBool("audiooutput.multichannellpcm"); + + m_settings.resampleQuality = static_cast<AEQuality>(CSettings::Get().GetInt("audiooutput.processquality")); +} + +bool CActiveAE::Initialize() +{ + if (!m_dllAvUtil.Load() || !m_dllAvCodec.Load() || !m_dllAvFormat.Load()) + { + CLog::Log(LOGERROR,"CActiveAE::Initialize - failed to load ffmpeg libraries"); + return false; + } + m_dllAvFormat.av_register_all(); + + Create(); + Message *reply; + if (m_controlPort.SendOutMessageSync(CActiveAEControlProtocol::INIT, + &reply, + 5000)) + { + bool success = reply->signal == CActiveAEControlProtocol::ACC ? true : false; + reply->Release(); + if (!success) + { + CLog::Log(LOGERROR, "ActiveAE::%s - returned error", __FUNCTION__); + Dispose(); + return false; + } + } + else + { + CLog::Log(LOGERROR, "ActiveAE::%s - failed to init", __FUNCTION__); + Dispose(); + return false; + } + + // hook into windowing for receiving display reset events +#if defined(HAS_GLX) || defined(TARGET_DARWIN_OSX) + g_Windowing.Register(this); +#endif + + m_inMsgEvent.Reset(); + return true; +} + +void CActiveAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough) +{ + m_sink.EnumerateOutputDevices(devices, passthrough); +} + +std::string CActiveAE::GetDefaultDevice(bool passthrough) +{ + return m_sink.GetDefaultDevice(passthrough); +} + +void CActiveAE::OnSettingsChange(const std::string& setting) +{ + if (setting == "audiooutput.passthroughdevice" || + setting == "audiooutput.audiodevice" || + setting == "audiooutput.mode" || + setting == "audiooutput.ac3passthrough" || + setting == "audiooutput.dtspassthrough" || + setting == "audiooutput.passthroughaac" || + setting == "audiooutput.truehdpassthrough" || + setting == "audiooutput.dtshdpassthrough" || + setting == "audiooutput.channels" || + setting == "audiooutput.multichannellpcm" || + setting == "audiooutput.stereoupmix" || + setting == "audiooutput.streamsilence" || + setting == "audiooutput.processquality") + { + m_controlPort.SendOutMessage(CActiveAEControlProtocol::RECONFIGURE); + } +} + +bool CActiveAE::SupportsRaw() +{ + return true; +} + +bool CActiveAE::SupportsDrain() +{ + return true; +} + +bool CActiveAE::SupportsQualityLevel(enum AEQuality level) +{ + if (level == AE_QUALITY_LOW || level == AE_QUALITY_MID || level == AE_QUALITY_HIGH) + return true; + + return false; +} + +void CActiveAE::Shutdown() +{ + Dispose(); +} + +bool CActiveAE::Suspend() +{ + return m_controlPort.SendOutMessage(CActiveAEControlProtocol::SUSPEND); +} + +bool CActiveAE::Resume() +{ + Message *reply; + if (m_controlPort.SendOutMessageSync(CActiveAEControlProtocol::INIT, + &reply, + 5000)) + { + bool success = reply->signal == CActiveAEControlProtocol::ACC ? true : false; + reply->Release(); + if (!success) + { + CLog::Log(LOGERROR, "ActiveAE::%s - returned error", __FUNCTION__); + return false; + } + } + else + { + CLog::Log(LOGERROR, "ActiveAE::%s - failed to init", __FUNCTION__); + return false; + } + + m_inMsgEvent.Reset(); + return true; +} + +bool CActiveAE::IsSuspended() +{ + return m_stats.IsSuspended(); +} + +float CActiveAE::GetVolume() +{ + return m_aeVolume; +} + +void CActiveAE::SetVolume(const float volume) +{ + m_aeVolume = std::max( 0.0f, std::min(1.0f, volume)); + m_controlPort.SendOutMessage(CActiveAEControlProtocol::VOLUME, &m_aeVolume, sizeof(float)); +} + +void CActiveAE::SetMute(const bool enabled) +{ + m_aeMuted = enabled; + m_controlPort.SendOutMessage(CActiveAEControlProtocol::MUTE, &m_aeMuted, sizeof(bool)); +} + +bool CActiveAE::IsMuted() +{ + return m_aeMuted; +} + +void CActiveAE::SetSoundMode(const int mode) +{ + int soundmode = mode; + m_controlPort.SendOutMessage(CActiveAEControlProtocol::SOUNDMODE, &soundmode, sizeof(int)); +} + + +void CActiveAE::OnLostDevice() +{ +// m_controlPort.SendOutMessage(CActiveAEControlProtocol::DISPLAYLOST); +} + +void CActiveAE::OnResetDevice() +{ +// m_controlPort.SendOutMessage(CActiveAEControlProtocol::DISPLAYRESET); +} + +//----------------------------------------------------------------------------- +// Utils +//----------------------------------------------------------------------------- + +uint8_t **CActiveAE::AllocSoundSample(SampleConfig &config, int &samples, int &bytes_per_sample, int &planes, int &linesize) +{ + uint8_t **buffer; + planes = m_dllAvUtil.av_sample_fmt_is_planar(config.fmt) ? config.channels : 1; + buffer = new uint8_t*[planes]; + m_dllAvUtil.av_samples_alloc(buffer, &linesize, config.channels, + samples, config.fmt, 0); + bytes_per_sample = m_dllAvUtil.av_get_bytes_per_sample(config.fmt); + return buffer; +} + +void CActiveAE::FreeSoundSample(uint8_t **data) +{ + m_dllAvUtil.av_freep(data); + delete [] data; +} + +//----------------------------------------------------------------------------- +// GUI Sounds +//----------------------------------------------------------------------------- + +/** + * load sound from an audio file and store original format + * register the sound in ActiveAE + * later when the engine is idle it will convert the sound to sink format + */ + +#define SOUNDBUFFER_SIZE 20480 + +IAESound *CActiveAE::MakeSound(const std::string& file) +{ + AVFormatContext *fmt_ctx = NULL; + AVCodecContext *dec_ctx = NULL; + AVIOContext *io_ctx; + AVInputFormat *io_fmt; + AVCodec *dec = NULL; + int bit_rate; + CActiveAESound *sound = NULL; + SampleConfig config; + + sound = new CActiveAESound(file); + if (!sound->Prepare()) + return NULL; + int fileSize = sound->GetFileSize(); + + fmt_ctx = m_dllAvFormat.avformat_alloc_context(); + unsigned char* buffer = (unsigned char*)m_dllAvUtil.av_malloc(SOUNDBUFFER_SIZE+FF_INPUT_BUFFER_PADDING_SIZE); + io_ctx = m_dllAvFormat.avio_alloc_context(buffer, SOUNDBUFFER_SIZE, 0, + sound, CActiveAESound::Read, NULL, CActiveAESound::Seek); + io_ctx->max_packet_size = sound->GetChunkSize(); + if(io_ctx->max_packet_size) + io_ctx->max_packet_size *= SOUNDBUFFER_SIZE / io_ctx->max_packet_size; + + if(!sound->IsSeekPosible()) + io_ctx->seekable = 0; + + fmt_ctx->pb = io_ctx; + + m_dllAvFormat.av_probe_input_buffer(io_ctx, &io_fmt, file.c_str(), NULL, 0, 0); + if (!io_fmt) + { + m_dllAvFormat.avformat_close_input(&fmt_ctx); + delete sound; + return NULL; + } + + // find decoder + if (m_dllAvFormat.avformat_open_input(&fmt_ctx, file.c_str(), NULL, NULL) == 0) + { + fmt_ctx->flags |= AVFMT_FLAG_NOPARSE; + if (m_dllAvFormat.avformat_find_stream_info(fmt_ctx, NULL) >= 0) + { + dec_ctx = fmt_ctx->streams[0]->codec; + dec = m_dllAvCodec.avcodec_find_decoder(dec_ctx->codec_id); + config.sample_rate = dec_ctx->sample_rate; + bit_rate = dec_ctx->bit_rate; + config.channels = dec_ctx->channels; + config.channel_layout = dec_ctx->channel_layout; + } + } + if (dec == NULL) + { + m_dllAvFormat.avformat_close_input(&fmt_ctx); + delete sound; + return NULL; + } + + dec_ctx = m_dllAvCodec.avcodec_alloc_context3(dec); + dec_ctx->sample_rate = config.sample_rate; + dec_ctx->channels = config.channels; + if (!config.channel_layout) + config.channel_layout = m_dllAvUtil.av_get_default_channel_layout(config.channels); + dec_ctx->channel_layout = config.channel_layout; + + AVPacket avpkt; + AVFrame *decoded_frame = NULL; + decoded_frame = m_dllAvCodec.avcodec_alloc_frame(); + + if (m_dllAvCodec.avcodec_open2(dec_ctx, dec, NULL) >= 0) + { + bool init = false; + + // decode until eof + m_dllAvCodec.av_init_packet(&avpkt); + int len; + while (m_dllAvFormat.av_read_frame(fmt_ctx, &avpkt) >= 0) + { + int got_frame = 0; + len = m_dllAvCodec.avcodec_decode_audio4(dec_ctx, decoded_frame, &got_frame, &avpkt); + if (len < 0) + { + m_dllAvCodec.avcodec_close(dec_ctx); + m_dllAvUtil.av_free(dec_ctx); + m_dllAvUtil.av_free(&decoded_frame); + m_dllAvFormat.avformat_close_input(&fmt_ctx); + delete sound; + return NULL; + } + if (got_frame) + { + if (!init) + { + int samples = fileSize / m_dllAvUtil.av_get_bytes_per_sample(dec_ctx->sample_fmt) / config.channels; + config.fmt = dec_ctx->sample_fmt; + sound->InitSound(true, config, samples); + init = true; + } + sound->StoreSound(true, decoded_frame->extended_data, + decoded_frame->nb_samples, decoded_frame->linesize[0]); + } + } + m_dllAvCodec.avcodec_close(dec_ctx); + } + + m_dllAvUtil.av_free(dec_ctx); + m_dllAvUtil.av_free(decoded_frame); + m_dllAvFormat.avformat_close_input(&fmt_ctx); + + sound->Finish(); + + // register sound + m_dataPort.SendOutMessage(CActiveAEDataProtocol::NEWSOUND, &sound, sizeof(CActiveAESound*)); + + return sound; +} + +void CActiveAE::FreeSound(IAESound *sound) +{ + m_dataPort.SendOutMessage(CActiveAEDataProtocol::FREESOUND, &sound, sizeof(CActiveAESound*)); +} + +void CActiveAE::PlaySound(CActiveAESound *sound) +{ + m_dataPort.SendOutMessage(CActiveAEDataProtocol::PLAYSOUND, &sound, sizeof(CActiveAESound*)); +} + +void CActiveAE::StopSound(CActiveAESound *sound) +{ + m_controlPort.SendOutMessage(CActiveAEControlProtocol::STOPSOUND, &sound, sizeof(CActiveAESound*)); +} + +/** + * resample sounds to destination format for mixing + * destination format is either format of stream or + * default sink format when no stream is playing + */ +void CActiveAE::ResampleSounds() +{ + std::vector<CActiveAESound*>::iterator it; + for (it = m_sounds.begin(); it != m_sounds.end(); ++it) + { + if (!(*it)->IsConverted()) + ResampleSound(*it); + } +} + +bool CActiveAE::ResampleSound(CActiveAESound *sound) +{ + SampleConfig orig_config, dst_config; + uint8_t **dst_buffer; + int dst_samples; + + if (m_mode == MODE_RAW || m_internalFormat.m_dataFormat == AE_FMT_INVALID) + return false; + + if (!sound->GetSound(true)) + return false; + + orig_config = sound->GetSound(true)->config; + + dst_config.channel_layout = CActiveAEResample::GetAVChannelLayout(m_internalFormat.m_channelLayout); + dst_config.channels = m_internalFormat.m_channelLayout.Count(); + dst_config.sample_rate = m_internalFormat.m_sampleRate; + dst_config.fmt = CActiveAEResample::GetAVSampleFormat(m_internalFormat.m_dataFormat); + + CActiveAEResample *resampler = new CActiveAEResample(); + resampler->Init(dst_config.channel_layout, + dst_config.channels, + dst_config.sample_rate, + dst_config.fmt, + orig_config.channel_layout, + orig_config.channels, + orig_config.sample_rate, + orig_config.fmt, + NULL, + AE_QUALITY_MID); + + dst_samples = resampler->CalcDstSampleCount(sound->GetSound(true)->nb_samples, + m_internalFormat.m_sampleRate, + orig_config.sample_rate); + + dst_buffer = sound->InitSound(false, dst_config, dst_samples); + if (!dst_buffer) + { + delete resampler; + return false; + } + int samples = resampler->Resample(dst_buffer, dst_samples, + sound->GetSound(true)->data, + sound->GetSound(true)->nb_samples); + + sound->GetSound(false)->nb_samples = samples; + + delete resampler; + sound->SetConverted(true); + return true; +} + +//----------------------------------------------------------------------------- +// Streams +//----------------------------------------------------------------------------- + +IAEStream *CActiveAE::MakeStream(enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int encodedSampleRate, CAEChannelInfo channelLayout, unsigned int options) +{ + //TODO: pass number of samples in audio packet + + AEAudioFormat format; + format.m_dataFormat = dataFormat; + format.m_sampleRate = sampleRate; + format.m_encodedRate = encodedSampleRate; + format.m_channelLayout = channelLayout; + format.m_frames = format.m_sampleRate / 10; + format.m_frameSize = format.m_channelLayout.Count() * + (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3); + + MsgStreamNew msg; + msg.format = format; + msg.options = options; + + Message *reply; + if (m_dataPort.SendOutMessageSync(CActiveAEDataProtocol::NEWSTREAM, + &reply,1000, + &msg, sizeof(MsgStreamNew))) + { + bool success = reply->signal == CActiveAEControlProtocol::ACC ? true : false; + if (success) + { + CActiveAEStream *stream = *(CActiveAEStream**)reply->data; + reply->Release(); + return stream; + } + reply->Release(); + } + + CLog::Log(LOGERROR, "ActiveAE::%s - could not create stream", __FUNCTION__); + return NULL; +} + +IAEStream *CActiveAE::FreeStream(IAEStream *stream) +{ + m_dataPort.SendOutMessage(CActiveAEDataProtocol::FREESTREAM, &stream, sizeof(IAEStream*)); + return NULL; +} + +void CActiveAE::FlushStream(CActiveAEStream *stream) +{ + Message *reply; + if (m_dataPort.SendOutMessageSync(CActiveAEDataProtocol::FLUSHSTREAM, + &reply,1000, + &stream, sizeof(CActiveAEStream*))) + { + bool success = reply->signal == CActiveAEDataProtocol::ACC ? true : false; + reply->Release(); + if (!success) + { + CLog::Log(LOGERROR, "CActiveAE::FlushStream - failed"); + } + } +} + +void CActiveAE::PauseStream(CActiveAEStream *stream, bool pause) +{ + // TODO pause sink, needs api change + if (pause) + m_controlPort.SendOutMessage(CActiveAEControlProtocol::PAUSESTREAM, + &stream, sizeof(CActiveAEStream*)); + else + m_controlPort.SendOutMessage(CActiveAEControlProtocol::RESUMESTREAM, + &stream, sizeof(CActiveAEStream*)); +} + +void CActiveAE::SetStreamAmplification(CActiveAEStream *stream, float amplify) +{ + MsgStreamParameter msg; + msg.stream = stream; + msg.parameter.float_par = amplify; + m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMAMP, + &msg, sizeof(MsgStreamParameter)); +} + +void CActiveAE::SetStreamReplaygain(CActiveAEStream *stream, float rgain) +{ + MsgStreamParameter msg; + msg.stream = stream; + msg.parameter.float_par = rgain; + m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMRGAIN, + &msg, sizeof(MsgStreamParameter)); +} + +void CActiveAE::SetStreamVolume(CActiveAEStream *stream, float volume) +{ + MsgStreamParameter msg; + msg.stream = stream; + msg.parameter.float_par = volume; + m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMVOLUME, + &msg, sizeof(MsgStreamParameter)); +} + +void CActiveAE::SetStreamResampleRatio(CActiveAEStream *stream, double ratio) +{ + MsgStreamParameter msg; + msg.stream = stream; + msg.parameter.double_par = ratio; + m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMRESAMPLERATIO, + &msg, sizeof(MsgStreamParameter)); +} + +void CActiveAE::SetStreamFade(CActiveAEStream *stream, float from, float target, unsigned int millis) +{ + MsgStreamFade msg; + msg.stream = stream; + msg.from = from; + msg.target = target; + msg.millis = millis; + m_controlPort.SendOutMessage(CActiveAEControlProtocol::STREAMFADE, + &msg, sizeof(MsgStreamFade)); +} + +void CActiveAE::RegisterAudioCallback(IAudioCallback* pCallback) +{ + CSingleLock lock(m_vizLock); + m_audioCallback = pCallback; + m_vizInitialized = false; +} + +void CActiveAE::UnregisterAudioCallback() +{ + CSingleLock lock(m_vizLock); + m_audioCallback = NULL; +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h new file mode 100644 index 0000000000..90c7f14c7d --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h @@ -0,0 +1,338 @@ +#pragma once +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "threads/Thread.h" + +#include "ActiveAESink.h" +#include "ActiveAEResample.h" +#include "Interfaces/AEStream.h" +#include "Interfaces/AESound.h" +#include "AEFactory.h" +#include "guilib/DispResource.h" + +// ffmpeg +#include "DllAvFormat.h" +#include "DllAvCodec.h" +#include "DllAvUtil.h" + +class IAESink; +class IAEEncoder; + +namespace ActiveAE +{ + +class CActiveAESound; +class CActiveAEStream; + +struct AudioSettings +{ + std::string device; + std::string driver; + std::string passthoughdevice; + int mode; + int channels; + bool ac3passthrough; + bool dtspassthrough; + bool aacpassthrough; + bool truehdpassthrough; + bool dtshdpassthrough; + bool multichannellpcm; + bool stereoupmix; + AEQuality resampleQuality; +}; + +class CActiveAEControlProtocol : public Protocol +{ +public: + CActiveAEControlProtocol(std::string name, CEvent* inEvent, CEvent *outEvent) : Protocol(name, inEvent, outEvent) {}; + enum OutSignal + { + INIT = 0, + RECONFIGURE, + SUSPEND, + MUTE, + VOLUME, + PAUSESTREAM, + RESUMESTREAM, + STREAMRGAIN, + STREAMVOLUME, + STREAMAMP, + STREAMRESAMPLERATIO, + STREAMFADE, + STOPSOUND, + SOUNDMODE, + GETSTATE, + DISPLAYLOST, + DISPLAYRESET, + TIMEOUT, + }; + enum InSignal + { + ACC, + ERR, + STATS, + }; +}; + +class CActiveAEDataProtocol : public Protocol +{ +public: + CActiveAEDataProtocol(std::string name, CEvent* inEvent, CEvent *outEvent) : Protocol(name, inEvent, outEvent) {}; + enum OutSignal + { + NEWSOUND = 0, + PLAYSOUND, + FREESOUND, + NEWSTREAM, + FREESTREAM, + STREAMSAMPLE, + DRAINSTREAM, + FLUSHSTREAM, + }; + enum InSignal + { + ACC, + ERR, + STREAMBUFFER, + STREAMDRAINED, + }; +}; + +struct MsgStreamNew +{ + AEAudioFormat format; + unsigned int options; +}; + +struct MsgStreamSample +{ + CSampleBuffer *buffer; + CActiveAEStream *stream; +}; + +struct MsgStreamParameter +{ + CActiveAEStream *stream; + union + { + float float_par; + double double_par; + } parameter; +}; + +struct MsgStreamFade +{ + CActiveAEStream *stream; + float from; + float target; + unsigned int millis; +}; + +class CEngineStats +{ +public: + void Reset(unsigned int sampleRate); + void UpdateSinkDelay(double delay, int samples); + void AddSamples(int samples, std::list<CActiveAEStream*> &streams); + float GetDelay(); + float GetDelay(CActiveAEStream *stream); + float GetCacheTime(CActiveAEStream *stream); + float GetCacheTotal(CActiveAEStream *stream); + float GetWaterLevel(); + void SetSuspended(bool state); + void SetSinkCacheTotal(float time) { m_sinkCacheTotal = time; } + bool IsSuspended(); + CCriticalSection *GetLock() { return &m_lock; } +protected: + float m_sinkDelay; + float m_sinkCacheTotal; + int m_bufferedSamples; + unsigned int m_sinkSampleRate; + unsigned int m_sinkUpdate; + bool m_suspended; + CCriticalSection m_lock; +}; + +#if defined(HAS_GLX) || defined(TARGET_DARWIN_OSX) +class CActiveAE : public IAE, public IDispResource, private CThread +#else +class CActiveAE : public IAE, private CThread +#endif +{ +protected: + friend class ::CAEFactory; + friend class CActiveAESound; + friend class CActiveAEStream; + friend class CSoundPacket; + friend class CActiveAEBufferPoolResample; + CActiveAE(); + virtual ~CActiveAE(); + virtual bool Initialize(); + +public: + virtual void Shutdown(); + virtual bool Suspend(); + virtual bool Resume(); + virtual bool IsSuspended(); + virtual void OnSettingsChange(const std::string& setting); + + virtual float GetVolume(); + virtual void SetVolume(const float volume); + virtual void SetMute(const bool enabled); + virtual bool IsMuted(); + virtual void SetSoundMode(const int mode); + + /* returns a new stream for data in the specified format */ + virtual IAEStream *MakeStream(enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int encodedSampleRate, CAEChannelInfo channelLayout, unsigned int options = 0); + virtual IAEStream *FreeStream(IAEStream *stream); + + /* returns a new sound object */ + virtual IAESound *MakeSound(const std::string& file); + virtual void FreeSound(IAESound *sound); + + virtual void GarbageCollect() {}; + + virtual void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough); + virtual std::string GetDefaultDevice(bool passthrough); + virtual bool SupportsRaw(); + virtual bool SupportsDrain(); + virtual bool SupportsQualityLevel(enum AEQuality level); + + virtual void RegisterAudioCallback(IAudioCallback* pCallback); + virtual void UnregisterAudioCallback(); + + virtual void OnLostDevice(); + virtual void OnResetDevice(); + +protected: + void PlaySound(CActiveAESound *sound); + uint8_t **AllocSoundSample(SampleConfig &config, int &samples, int &bytes_per_sample, int &planes, int &linesize); + void FreeSoundSample(uint8_t **data); + float GetDelay(CActiveAEStream *stream) { return m_stats.GetDelay(stream); } + float GetCacheTime(CActiveAEStream *stream) { return m_stats.GetCacheTime(stream); } + float GetCacheTotal(CActiveAEStream *stream) { return m_stats.GetCacheTotal(stream); } + void FlushStream(CActiveAEStream *stream); + void PauseStream(CActiveAEStream *stream, bool pause); + void StopSound(CActiveAESound *sound); + void SetStreamAmplification(CActiveAEStream *stream, float amplify); + void SetStreamReplaygain(CActiveAEStream *stream, float rgain); + void SetStreamVolume(CActiveAEStream *stream, float volume); + void SetStreamResampleRatio(CActiveAEStream *stream, double ratio); + void SetStreamFade(CActiveAEStream *stream, float from, float target, unsigned int millis); + +protected: + void Process(); + void StateMachine(int signal, Protocol *port, Message *msg); + bool InitSink(); + void DrainSink(); + void UnconfigureSink(); + void Start(); + void Dispose(); + void LoadSettings(); + bool NeedReconfigureBuffers(); + bool NeedReconfigureSink(); + void ApplySettingsToFormat(AEAudioFormat &format, AudioSettings &settings, bool setmode = false); + void Configure(AEAudioFormat *desiredFmt = NULL); + CActiveAEStream* CreateStream(MsgStreamNew *streamMsg); + void DiscardStream(CActiveAEStream *stream); + void SFlushStream(CActiveAEStream *stream); + void ClearDiscardedBuffers(); + void SStopSound(CActiveAESound *sound); + void DiscardSound(CActiveAESound *sound); + float CalcStreamAmplification(CActiveAEStream *stream, CSampleBuffer *buf); + void ChangeResampleQuality(); + + bool RunStages(); + bool HasWork(); + + void ResampleSounds(); + bool ResampleSound(CActiveAESound *sound); + void MixSounds(CSoundPacket &dstSample); + void Deamplify(CSoundPacket &dstSample); + + CEvent m_inMsgEvent; + CEvent m_outMsgEvent; + CActiveAEControlProtocol m_controlPort; + CActiveAEDataProtocol m_dataPort; + int m_state; + bool m_bStateMachineSelfTrigger; + int m_extTimeout; + bool m_extError; + bool m_extDrain; + XbmcThreads::EndTime m_extDrainTimer; + bool m_extDeferData; + + enum + { + MODE_RAW, + MODE_TRANSCODE, + MODE_PCM + }m_mode; + + CActiveAESink m_sink; + AEAudioFormat m_sinkFormat; + AEAudioFormat m_sinkRequestFormat; + AEAudioFormat m_encoderFormat; + AEAudioFormat m_internalFormat; + AudioSettings m_settings; + CEngineStats m_stats; + IAEEncoder *m_encoder; + + // buffers + CActiveAEBufferPoolResample *m_sinkBuffers; + CActiveAEBufferPoolResample *m_vizBuffers; + CActiveAEBufferPool *m_silenceBuffers; // needed to drive gui sounds if we have no streams + CActiveAEBufferPool *m_encoderBuffers; + + // streams + std::list<CActiveAEStream*> m_streams; + std::list<CActiveAEBufferPool*> m_discardBufferPools; + + // gui sounds + struct SoundState + { + CActiveAESound *sound; + int samples_played; + }; + std::list<SoundState> m_sounds_playing; + std::vector<CActiveAESound*> m_sounds; + int m_soundMode; + + float m_volume; + bool m_muted; + bool m_sinkHasVolume; + + // viz + IAudioCallback *m_audioCallback; + bool m_vizInitialized; + CCriticalSection m_vizLock; + + // ffmpeg + DllAvFormat m_dllAvFormat; + DllAvCodec m_dllAvCodec; + DllAvUtil m_dllAvUtil; + + // polled via the interface + float m_aeVolume; + bool m_aeMuted; +}; +}; diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp new file mode 100644 index 0000000000..03fb16f9f1 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2010-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "ActiveAEBuffer.h" +#include "AEFactory.h" +#include "ActiveAE.h" + +using namespace ActiveAE; + +/* typecast AE to CActiveAE */ +#define AE (*((CActiveAE*)CAEFactory::GetEngine())) + +CSoundPacket::CSoundPacket(SampleConfig conf, int samples) : config(conf) +{ + data = AE.AllocSoundSample(config, samples, bytes_per_sample, planes, linesize); + max_nb_samples = samples; + nb_samples = 0; +} + +CSoundPacket::~CSoundPacket() +{ + if (data) + AE.FreeSoundSample(data); +} + +CSampleBuffer::CSampleBuffer() : pkt(NULL), pool(NULL) +{ + refCount = 0; +} + +CSampleBuffer::~CSampleBuffer() +{ + delete pkt; +} + +CSampleBuffer* CSampleBuffer::Acquire() +{ + refCount++; + return this; +} + +void CSampleBuffer::Return() +{ + refCount--; + if (pool && refCount <= 0) + pool->ReturnBuffer(this); +} + +CActiveAEBufferPool::CActiveAEBufferPool(AEAudioFormat format) +{ + m_format = format; + if (AE_IS_RAW(m_format.m_dataFormat)) + m_format.m_dataFormat = AE_FMT_S16NE; +} + +CActiveAEBufferPool::~CActiveAEBufferPool() +{ + CSampleBuffer *buffer; + while(!m_allSamples.empty()) + { + buffer = m_allSamples.front(); + m_allSamples.pop_front(); + delete buffer; + } +} + +CSampleBuffer* CActiveAEBufferPool::GetFreeBuffer() +{ + CSampleBuffer* buf = NULL; + + if (!m_freeSamples.empty()) + { + buf = m_freeSamples.front(); + m_freeSamples.pop_front(); + buf->refCount = 1; + } + return buf; +} + +void CActiveAEBufferPool::ReturnBuffer(CSampleBuffer *buffer) +{ + buffer->pkt->nb_samples = 0; + m_freeSamples.push_back(buffer); +} + +bool CActiveAEBufferPool::Create(unsigned int totaltime) +{ + CSampleBuffer *buffer; + SampleConfig config; + config.fmt = CActiveAEResample::GetAVSampleFormat(m_format.m_dataFormat); + config.channels = m_format.m_channelLayout.Count(); + config.sample_rate = m_format.m_sampleRate; + config.channel_layout = CActiveAEResample::GetAVChannelLayout(m_format.m_channelLayout); + + unsigned int time = 0; + unsigned int buffertime = (m_format.m_frames*1000) / m_format.m_sampleRate; + unsigned int n = 0; + while (time < totaltime || n < 5) + { + buffer = new CSampleBuffer(); + buffer->pool = this; + buffer->pkt = new CSoundPacket(config, m_format.m_frames); + + m_allSamples.push_back(buffer); + m_freeSamples.push_back(buffer); + time += buffertime; + n++; + } + + return true; +} + +//----------------------------------------------------------------------------- + +CActiveAEBufferPoolResample::CActiveAEBufferPoolResample(AEAudioFormat inputFormat, AEAudioFormat outputFormat, AEQuality quality) + : CActiveAEBufferPool(outputFormat) +{ + m_inputFormat = inputFormat; + if (AE_IS_RAW(m_inputFormat.m_dataFormat)) + m_inputFormat.m_dataFormat = AE_FMT_S16NE; + m_resampler = NULL; + m_fillPackets = false; + m_drain = false; + m_empty = true; + m_procSample = NULL; + m_resampleRatio = 1.0; + m_resampleQuality = quality; + m_changeResampler = false; +} + +CActiveAEBufferPoolResample::~CActiveAEBufferPoolResample() +{ + delete m_resampler; +} + +bool CActiveAEBufferPoolResample::Create(unsigned int totaltime, bool remap) +{ + CActiveAEBufferPool::Create(totaltime); + + if (m_inputFormat.m_channelLayout != m_format.m_channelLayout || + m_inputFormat.m_sampleRate != m_format.m_sampleRate || + m_inputFormat.m_dataFormat != m_format.m_dataFormat) + { + m_resampler = new CActiveAEResample(); + m_resampler->Init(CActiveAEResample::GetAVChannelLayout(m_format.m_channelLayout), + m_format.m_channelLayout.Count(), + m_format.m_sampleRate, + CActiveAEResample::GetAVSampleFormat(m_format.m_dataFormat), + CActiveAEResample::GetAVChannelLayout(m_inputFormat.m_channelLayout), + m_inputFormat.m_channelLayout.Count(), + m_inputFormat.m_sampleRate, + CActiveAEResample::GetAVSampleFormat(m_inputFormat.m_dataFormat), + remap ? &m_format.m_channelLayout : NULL, + m_resampleQuality); + } + + // store output sampling rate, needed when ratio gets changed + m_outSampleRate = m_format.m_sampleRate; + + return true; +} + +void CActiveAEBufferPoolResample::ChangeResampler() +{ + m_outSampleRate = m_format.m_sampleRate * m_resampleRatio; + + delete m_resampler; + + m_resampler = new CActiveAEResample(); + m_resampler->Init(CActiveAEResample::GetAVChannelLayout(m_format.m_channelLayout), + m_format.m_channelLayout.Count(), + m_outSampleRate, + CActiveAEResample::GetAVSampleFormat(m_format.m_dataFormat), + CActiveAEResample::GetAVChannelLayout(m_inputFormat.m_channelLayout), + m_inputFormat.m_channelLayout.Count(), + m_inputFormat.m_sampleRate, + CActiveAEResample::GetAVSampleFormat(m_inputFormat.m_dataFormat), + NULL, + m_resampleQuality); + + m_changeResampler = false; +} + +bool CActiveAEBufferPoolResample::ResampleBuffers(unsigned int timestamp) +{ + bool busy = false; + CSampleBuffer *in; + + if (!m_resampler) + { + if (m_changeResampler) + { + ChangeResampler(); + return true; + } + while(!m_inputSamples.empty()) + { + in = m_inputSamples.front(); + m_inputSamples.pop_front(); + in->timestamp = timestamp; + m_outputSamples.push_back(in); + busy = true; + } + } + else if (m_procSample || !m_freeSamples.empty()) + { + // GetBufferedSamples is not accurate because of rounding errors + int out_samples = m_resampler->GetBufferedSamples(); + int free_samples; + if (m_procSample) + free_samples = m_procSample->pkt->max_nb_samples - m_procSample->pkt->nb_samples; + else + free_samples = m_format.m_frames; + + bool skipInput = false; + // avoid that ffmpeg resample buffer grows too large + if (out_samples > free_samples * 2 && !m_empty) + skipInput = true; + + bool hasInput = !m_inputSamples.empty(); + + if (hasInput || skipInput || m_drain || m_changeResampler) + { + if (!m_procSample) + { + m_procSample = GetFreeBuffer(); + } + + if (hasInput && !skipInput && !m_changeResampler) + { + in = m_inputSamples.front(); + m_inputSamples.pop_front(); + } + else + in = NULL; + + int start = m_procSample->pkt->nb_samples * + m_procSample->pkt->bytes_per_sample * + m_procSample->pkt->config.channels / + m_procSample->pkt->planes; + + for(int i=0; i<m_procSample->pkt->planes; i++) + { + m_planes[i] = m_procSample->pkt->data[i] + start; + } + + out_samples = m_resampler->Resample(m_planes, + m_procSample->pkt->max_nb_samples - m_procSample->pkt->nb_samples, + in ? in->pkt->data : NULL, + in ? in->pkt->nb_samples : 0); + m_procSample->pkt->nb_samples += out_samples; + busy = true; + m_empty = (out_samples == 0); + + if ((m_drain || m_changeResampler) && m_empty) + { + if (m_fillPackets && m_procSample->pkt->nb_samples != 0) + { + // pad with zero + start = m_procSample->pkt->nb_samples * + m_procSample->pkt->bytes_per_sample * + m_procSample->pkt->config.channels / + m_procSample->pkt->planes; + for(int i=0; i<m_procSample->pkt->planes; i++) + { + memset(m_procSample->pkt->data[i]+start, 0, m_procSample->pkt->linesize-start); + } + } + m_procSample->timestamp = timestamp; + + // check if draining is finished + if (m_drain && m_procSample->pkt->nb_samples == 0) + { + m_procSample->Return(); + busy = false; + } + else + m_outputSamples.push_back(m_procSample); + + m_procSample = NULL; + if (m_changeResampler) + ChangeResampler(); + } + // some methods like encode require completely filled packets + else if (!m_fillPackets || (m_procSample->pkt->nb_samples == m_procSample->pkt->max_nb_samples)) + { + m_procSample->timestamp = timestamp; + m_outputSamples.push_back(m_procSample); + m_procSample = NULL; + } + + if (in) + in->Return(); + } + } + return busy; +} + +float CActiveAEBufferPoolResample::GetDelay() +{ + float delay = 0; + std::deque<CSampleBuffer*>::iterator itBuf; + + if (m_procSample) + delay += m_procSample->pkt->nb_samples / m_procSample->pkt->config.sample_rate; + + for(itBuf=m_inputSamples.begin(); itBuf!=m_inputSamples.end(); ++itBuf) + { + delay += (float)(*itBuf)->pkt->nb_samples / (*itBuf)->pkt->config.sample_rate; + } + + for(itBuf=m_outputSamples.begin(); itBuf!=m_outputSamples.end(); ++itBuf) + { + delay += (float)(*itBuf)->pkt->nb_samples / (*itBuf)->pkt->config.sample_rate; + } + + if (m_resampler) + { + int samples = m_resampler->GetBufferedSamples(); + delay += (float)samples / m_outSampleRate; + } + + return delay; +} + +void CActiveAEBufferPoolResample::Flush() +{ + if (m_procSample) + { + m_procSample->Return(); + m_procSample = NULL; + } + while (!m_inputSamples.empty()) + { + m_inputSamples.front()->Return(); + m_inputSamples.pop_front(); + } + while (!m_outputSamples.empty()) + { + m_outputSamples.front()->Return(); + m_outputSamples.pop_front(); + } +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h new file mode 100644 index 0000000000..5b42168f7f --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h @@ -0,0 +1,112 @@ +#pragma once +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "DllAvUtil.h" +#include "DllSwResample.h" +#include "AEAudioFormat.h" +#include "Interfaces/AE.h" +#include <deque> + +namespace ActiveAE +{ + +struct SampleConfig +{ + AVSampleFormat fmt; + uint64_t channel_layout; + int channels; + int sample_rate; +}; + +/** + * the variables here follow ffmpeg naming + */ +class CSoundPacket +{ +public: + CSoundPacket(SampleConfig conf, int samples); + ~CSoundPacket(); + uint8_t **data; // array with pointers to planes of data + SampleConfig config; + AEDataFormat internal_format; // used when carrying pass through + int bytes_per_sample; // bytes per sample and per channel + int linesize; // see ffmpeg, required for planar formats + int planes; // 1 for non planar formats, #channels for planar + int nb_samples; // number of frames used + int max_nb_samples; // max number of frames this packet can hold +}; + +class CActiveAEBufferPool; + +class CSampleBuffer +{ +public: + CSampleBuffer(); + ~CSampleBuffer(); + CSampleBuffer *Acquire(); + void Return(); + CSoundPacket *pkt; + CActiveAEBufferPool *pool; + unsigned int timestamp; + int refCount; +}; + +class CActiveAEBufferPool +{ +public: + CActiveAEBufferPool(AEAudioFormat format); + virtual ~CActiveAEBufferPool(); + virtual bool Create(unsigned int totaltime); + CSampleBuffer *GetFreeBuffer(); + void ReturnBuffer(CSampleBuffer *buffer); + AEAudioFormat m_format; + std::deque<CSampleBuffer*> m_allSamples; + std::deque<CSampleBuffer*> m_freeSamples; +}; + +class CActiveAEResample; + +class CActiveAEBufferPoolResample : public CActiveAEBufferPool +{ +public: + CActiveAEBufferPoolResample(AEAudioFormat inputFormat, AEAudioFormat outputFormat, AEQuality quality); + virtual ~CActiveAEBufferPoolResample(); + virtual bool Create(unsigned int totaltime, bool remap); + void ChangeResampler(); + bool ResampleBuffers(unsigned int timestamp = 0); + float GetDelay(); + void Flush(); + AEAudioFormat m_inputFormat; + std::deque<CSampleBuffer*> m_inputSamples; + std::deque<CSampleBuffer*> m_outputSamples; + CSampleBuffer *m_procSample; + CActiveAEResample *m_resampler; + uint8_t *m_planes[16]; + bool m_fillPackets; + bool m_drain; + bool m_empty; + bool m_changeResampler; + double m_resampleRatio; + AEQuality m_resampleQuality; + unsigned int m_outSampleRate; +}; + +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResample.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResample.cpp new file mode 100644 index 0000000000..5678ebc606 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResample.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "ActiveAEResample.h" + +using namespace ActiveAE; + +CActiveAEResample::CActiveAEResample() +{ + m_pContext = NULL; +} + +CActiveAEResample::~CActiveAEResample() +{ + if (m_pContext) + m_dllSwResample.swr_free(&m_pContext); + + m_dllAvUtil.Unload(); + m_dllSwResample.Unload(); +} + +bool CActiveAEResample::Init(uint64_t dst_chan_layout, int dst_channels, int dst_rate, AVSampleFormat dst_fmt, uint64_t src_chan_layout, int src_channels, int src_rate, AVSampleFormat src_fmt, CAEChannelInfo *remapLayout, AEQuality quality) +{ + if (!m_dllAvUtil.Load() || !m_dllSwResample.Load()) + return false; + + m_dst_chan_layout = dst_chan_layout; + m_dst_channels = dst_channels; + m_dst_rate = dst_rate; + m_dst_fmt = dst_fmt; + m_src_chan_layout = src_chan_layout; + m_src_channels = src_channels; + m_src_rate = src_rate; + m_src_fmt = src_fmt; + + if (m_dst_chan_layout == 0) + m_dst_chan_layout = m_dllAvUtil.av_get_default_channel_layout(m_dst_channels); + if (m_src_chan_layout == 0) + m_src_chan_layout = m_dllAvUtil.av_get_default_channel_layout(m_src_channels); + + m_pContext = m_dllSwResample.swr_alloc_set_opts(NULL, m_dst_chan_layout, m_dst_fmt, m_dst_rate, + m_src_chan_layout, m_src_fmt, m_src_rate, + 0, NULL); + if(quality == AE_QUALITY_HIGH) + { + m_dllAvUtil.av_opt_set_double(m_pContext, "cutoff", 1.0, 0); + m_dllAvUtil.av_opt_set_int(m_pContext,"filter_size", 256, 0); + } + else if(quality == AE_QUALITY_MID) + { + // 0.97 is default cutoff so use (1.0 - 0.97) / 2.0 + 0.97 + m_dllAvUtil.av_opt_set_double(m_pContext, "cutoff", 0.985, 0); + m_dllAvUtil.av_opt_set_int(m_pContext,"filter_size", 64, 0); + } + else if(quality == AE_QUALITY_LOW) + { + m_dllAvUtil.av_opt_set_double(m_pContext, "cutoff", 0.97, 0); + m_dllAvUtil.av_opt_set_int(m_pContext,"filter_size", 32, 0); + } + + if(!m_pContext) + { + CLog::Log(LOGERROR, "CActiveAEResample::Init - create context failed"); + return false; + } + if (remapLayout) + { + // one-to-one mapping of channels + // remapLayout is the layout of the sink, if the channel is in our src layout + // the channel is mapped by setting coef 1.0 + memset(m_rematrix, 0, sizeof(m_rematrix)); + for (unsigned int out=0; out<remapLayout->Count(); out++) + { + int idx = GetAVChannelIndex((*remapLayout)[out], m_src_chan_layout); + if (idx >= 0) + { + m_rematrix[out][idx] = 1.0; + } + } + + if (m_dllSwResample.swr_set_matrix(m_pContext, (const double*)m_rematrix, AE_CH_MAX) < 0) + { + CLog::Log(LOGERROR, "CActiveAEResample::Init - setting channel matrix failed"); + return false; + } + } + if(m_dllSwResample.swr_init(m_pContext) < 0) + { + CLog::Log(LOGERROR, "CActiveAEResample::Init - init resampler failed"); + return false; + } + return true; +} + +int CActiveAEResample::Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples) +{ + int ret = m_dllSwResample.swr_convert(m_pContext, dst_buffer, dst_samples, (const uint8_t**)src_buffer, src_samples); + if (ret < 0) + { + CLog::Log(LOGERROR, "CActiveAEResample::Resample - resample failed"); + return 0; + } + return ret; +} + +int64_t CActiveAEResample::GetDelay(int64_t base) +{ + return m_dllSwResample.swr_get_delay(m_pContext, base); +} + +int CActiveAEResample::GetBufferedSamples() +{ + return m_dllAvUtil.av_rescale_rnd(m_dllSwResample.swr_get_delay(m_pContext, m_src_rate), + m_dst_rate, m_src_rate, AV_ROUND_UP); +} + +int CActiveAEResample::CalcDstSampleCount(int src_samples, int dst_rate, int src_rate) +{ + return m_dllAvUtil.av_rescale_rnd(src_samples, dst_rate, src_rate, AV_ROUND_UP); +} + +int CActiveAEResample::GetSrcBufferSize(int samples) +{ + return m_dllAvUtil.av_samples_get_buffer_size(NULL, m_src_channels, samples, m_src_fmt, 1); +} + +int CActiveAEResample::GetDstBufferSize(int samples) +{ + return m_dllAvUtil.av_samples_get_buffer_size(NULL, m_dst_channels, samples, m_dst_fmt, 1); +} + +uint64_t CActiveAEResample::GetAVChannelLayout(CAEChannelInfo &info) +{ + uint64_t channelLayout = 0; + if (info.HasChannel(AE_CH_FL)) channelLayout |= AV_CH_FRONT_LEFT; + if (info.HasChannel(AE_CH_FR)) channelLayout |= AV_CH_FRONT_RIGHT; + if (info.HasChannel(AE_CH_FC)) channelLayout |= AV_CH_FRONT_CENTER; + if (info.HasChannel(AE_CH_LFE)) channelLayout |= AV_CH_LOW_FREQUENCY; + if (info.HasChannel(AE_CH_BL)) channelLayout |= AV_CH_BACK_LEFT; + if (info.HasChannel(AE_CH_BR)) channelLayout |= AV_CH_BACK_RIGHT; + if (info.HasChannel(AE_CH_FLOC)) channelLayout |= AV_CH_FRONT_LEFT_OF_CENTER; + if (info.HasChannel(AE_CH_FROC)) channelLayout |= AV_CH_FRONT_RIGHT_OF_CENTER; + if (info.HasChannel(AE_CH_BC)) channelLayout |= AV_CH_BACK_CENTER; + if (info.HasChannel(AE_CH_SL)) channelLayout |= AV_CH_SIDE_LEFT; + if (info.HasChannel(AE_CH_SR)) channelLayout |= AV_CH_SIDE_RIGHT; + if (info.HasChannel(AE_CH_TC)) channelLayout |= AV_CH_TOP_CENTER; + if (info.HasChannel(AE_CH_TFL)) channelLayout |= AV_CH_TOP_FRONT_LEFT; + if (info.HasChannel(AE_CH_TFC)) channelLayout |= AV_CH_TOP_FRONT_CENTER; + if (info.HasChannel(AE_CH_TFR)) channelLayout |= AV_CH_TOP_FRONT_RIGHT; + if (info.HasChannel(AE_CH_TBL)) channelLayout |= AV_CH_TOP_BACK_LEFT; + if (info.HasChannel(AE_CH_TBC)) channelLayout |= AV_CH_TOP_BACK_CENTER; + if (info.HasChannel(AE_CH_TBR)) channelLayout |= AV_CH_TOP_BACK_RIGHT; + + return channelLayout; +} + +//CAEChannelInfo CActiveAEResample::GetAEChannelLayout(uint64_t layout) +//{ +// CAEChannelInfo channelLayout; +// channelLayout.Reset(); +// +// if (layout & AV_CH_FRONT_LEFT ) channelLayout += AE_CH_FL ; +// if (layout & AV_CH_FRONT_RIGHT ) channelLayout += AE_CH_FR ; +// if (layout & AV_CH_FRONT_CENTER ) channelLayout += AE_CH_FC ; +// if (layout & AV_CH_LOW_FREQUENCY ) channelLayout += AE_CH_LFE ; +// if (layout & AV_CH_BACK_LEFT ) channelLayout += AE_CH_BL ; +// if (layout & AV_CH_BACK_RIGHT ) channelLayout += AE_CH_BR ; +// if (layout & AV_CH_FRONT_LEFT_OF_CENTER ) channelLayout += AE_CH_FLOC; +// if (layout & AV_CH_FRONT_RIGHT_OF_CENTER) channelLayout += AE_CH_FROC; +// if (layout & AV_CH_BACK_CENTER ) channelLayout += AE_CH_BC ; +// if (layout & AV_CH_SIDE_LEFT ) channelLayout += AE_CH_SL ; +// if (layout & AV_CH_SIDE_RIGHT ) channelLayout += AE_CH_SR ; +// if (layout & AV_CH_TOP_CENTER ) channelLayout += AE_CH_TC ; +// if (layout & AV_CH_TOP_FRONT_LEFT ) channelLayout += AE_CH_TFL ; +// if (layout & AV_CH_TOP_FRONT_CENTER ) channelLayout += AE_CH_TFC ; +// if (layout & AV_CH_TOP_FRONT_RIGHT ) channelLayout += AE_CH_TFR ; +// if (layout & AV_CH_TOP_BACK_LEFT ) channelLayout += AE_CH_BL ; +// if (layout & AV_CH_TOP_BACK_CENTER ) channelLayout += AE_CH_BC ; +// if (layout & AV_CH_TOP_BACK_RIGHT ) channelLayout += AE_CH_BR ; +// +// return channelLayout; +//} + +AVSampleFormat CActiveAEResample::GetAVSampleFormat(AEDataFormat format) +{ + if (format == AE_FMT_U8) return AV_SAMPLE_FMT_U8; + else if (format == AE_FMT_S16NE) return AV_SAMPLE_FMT_S16; + else if (format == AE_FMT_S32NE) return AV_SAMPLE_FMT_S32; + else if (format == AE_FMT_FLOAT) return AV_SAMPLE_FMT_FLT; + else if (format == AE_FMT_DOUBLE) return AV_SAMPLE_FMT_DBL; + + else if (format == AE_FMT_U8P) return AV_SAMPLE_FMT_U8P; + else if (format == AE_FMT_S16NEP) return AV_SAMPLE_FMT_S16P; + else if (format == AE_FMT_S32NEP) return AV_SAMPLE_FMT_S32P; + else if (format == AE_FMT_FLOATP) return AV_SAMPLE_FMT_FLTP; + else if (format == AE_FMT_DOUBLEP) return AV_SAMPLE_FMT_DBLP; + + return AV_SAMPLE_FMT_FLT; +} + +AEDataFormat CActiveAEResample::GetAESampleFormat(AVSampleFormat format) +{ + if (format == AV_SAMPLE_FMT_U8) return AE_FMT_U8; + else if (format == AV_SAMPLE_FMT_S16) return AE_FMT_S16NE; + else if (format == AV_SAMPLE_FMT_S32) return AE_FMT_S32NE; + else if (format == AV_SAMPLE_FMT_FLT) return AE_FMT_FLOAT; + else if (format == AV_SAMPLE_FMT_DBL) return AE_FMT_DOUBLE; + + else if (format == AV_SAMPLE_FMT_U8P) return AE_FMT_U8P; + else if (format == AV_SAMPLE_FMT_S16P) return AE_FMT_S16NEP; + else if (format == AV_SAMPLE_FMT_S32P) return AE_FMT_S32NEP; + else if (format == AV_SAMPLE_FMT_FLTP) return AE_FMT_FLOATP; + else if (format == AV_SAMPLE_FMT_DBLP) return AE_FMT_DOUBLEP; + + CLog::Log(LOGERROR, "CActiveAEResample::GetAESampleFormat - format not supported"); + return AE_FMT_INVALID; +} + +uint64_t CActiveAEResample::GetAVChannel(enum AEChannel aechannel) +{ + switch (aechannel) + { + case AE_CH_FL: return AV_CH_FRONT_LEFT; + case AE_CH_FR: return AV_CH_FRONT_RIGHT; + case AE_CH_FC: return AV_CH_FRONT_CENTER; + case AE_CH_LFE: return AV_CH_LOW_FREQUENCY; + case AE_CH_BL: return AV_CH_BACK_LEFT; + case AE_CH_BR: return AV_CH_BACK_RIGHT; + case AE_CH_FLOC: return AV_CH_FRONT_LEFT_OF_CENTER; + case AE_CH_FROC: return AV_CH_FRONT_RIGHT_OF_CENTER; + case AE_CH_BC: return AV_CH_BACK_CENTER; + case AE_CH_SL: return AV_CH_SIDE_LEFT; + case AE_CH_SR: return AV_CH_SIDE_RIGHT; + case AE_CH_TC: return AV_CH_TOP_CENTER; + case AE_CH_TFL: return AV_CH_TOP_FRONT_LEFT; + case AE_CH_TFC: return AV_CH_TOP_FRONT_CENTER; + case AE_CH_TFR: return AV_CH_TOP_FRONT_RIGHT; + case AE_CH_TBL: return AV_CH_TOP_BACK_LEFT; + case AE_CH_TBC: return AV_CH_TOP_BACK_CENTER; + case AE_CH_TBR: return AV_CH_TOP_BACK_RIGHT; + default: + return 0; + } +} + +int CActiveAEResample::GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout) +{ + return m_dllAvUtil.av_get_channel_layout_channel_index(layout, GetAVChannel(aechannel)); +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResample.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResample.h new file mode 100644 index 0000000000..2c5aff1d44 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResample.h @@ -0,0 +1,62 @@ +#pragma once +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "DllAvUtil.h" +#include "DllSwResample.h" +#include "Utils/AEChannelInfo.h" +#include "AEAudioFormat.h" +#include "ActiveAEBuffer.h" +#include "Interfaces/AE.h" + +namespace ActiveAE +{ + +class CActiveAEResample +{ +public: + CActiveAEResample(); + virtual ~CActiveAEResample(); + bool Init(uint64_t dst_chan_layout, int dst_channels, int dst_rate, AVSampleFormat dst_fmt, uint64_t src_chan_layout, int src_channels, int src_rate, AVSampleFormat src_fmt, CAEChannelInfo *remapLayout, AEQuality quality); + int Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples); + int64_t GetDelay(int64_t base); + int GetBufferedSamples(); + int CalcDstSampleCount(int src_samples, int dst_rate, int src_rate); + int GetSrcBufferSize(int samples); + int GetDstBufferSize(int samples); + static uint64_t GetAVChannelLayout(CAEChannelInfo &info); +// static CAEChannelInfo GetAEChannelLayout(uint64_t layout); + static AVSampleFormat GetAVSampleFormat(AEDataFormat format); + static AEDataFormat GetAESampleFormat(AVSampleFormat format); + static uint64_t GetAVChannel(enum AEChannel aechannel); + int GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout); + +protected: + DllAvUtil m_dllAvUtil; + DllSwResample m_dllSwResample; + uint64_t m_src_chan_layout, m_dst_chan_layout; + int m_src_rate, m_dst_rate; + int m_src_channels, m_dst_channels; + AVSampleFormat m_src_fmt, m_dst_fmt; + SwrContext *m_pContext; + double m_rematrix[AE_CH_MAX][AE_CH_MAX]; +}; + +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp new file mode 100644 index 0000000000..9af241d764 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -0,0 +1,863 @@ +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <sstream> + +#include "ActiveAESink.h" +#include "Utils/AEUtil.h" +#include "utils/EndianSwap.h" +#include "ActiveAE.h" + +#include "settings/Settings.h" + +using namespace ActiveAE; + +CActiveAESink::CActiveAESink(CEvent *inMsgEvent) : + CThread("AESink"), + m_controlPort("SinkControlPort", inMsgEvent, &m_outMsgEvent), + m_dataPort("SinkDataPort", inMsgEvent, &m_outMsgEvent) +{ + m_inMsgEvent = inMsgEvent; + m_sink = NULL; + m_stats = NULL; + m_convertBuffer = NULL; + m_volume = 0.0; +} + +void CActiveAESink::Start() +{ + if (!IsRunning()) + { + Create(); + SetPriority(THREAD_PRIORITY_ABOVE_NORMAL); + } +} + +void CActiveAESink::Dispose() +{ + m_bStop = true; + m_outMsgEvent.Set(); + StopThread(); + m_controlPort.Purge(); + m_dataPort.Purge(); + + if (m_sink) + { + m_sink->Drain(); + m_sink->Deinitialize(); + delete m_sink; + m_sink = NULL; + } + + delete m_sampleOfSilence.pkt; + m_sampleOfSilence.pkt = NULL; + + delete m_sampleOfNoise.pkt; + m_sampleOfNoise.pkt = NULL; + + if (m_convertBuffer) + { + _aligned_free(m_convertBuffer); + m_convertBuffer = NULL; + } +} + +bool CActiveAESink::IsCompatible(const AEAudioFormat format, const std::string &device) +{ + if (!m_sink) + return false; + return m_sink->IsCompatible(format, device); +} + +bool CActiveAESink::HasVolume() +{ + if (!m_sink) + return false; + return m_sink->HasVolume(); +} + +enum SINK_STATES +{ + S_TOP = 0, // 0 + S_TOP_UNCONFIGURED, // 1 + S_TOP_CONFIGURED, // 2 + S_TOP_CONFIGURED_SUSPEND, // 3 + S_TOP_CONFIGURED_IDLE, // 4 + S_TOP_CONFIGURED_PLAY, // 5 + S_TOP_CONFIGURED_SILENCE, // 6 + S_TOP_CONFIGURED_WARMUP, // 7 +}; + +int SINK_parentStates[] = { + -1, + 0, //TOP_UNCONFIGURED + 0, //TOP_CONFIGURED + 2, //TOP_CONFIGURED_SUSPEND + 2, //TOP_CONFIGURED_IDLE + 2, //TOP_CONFIGURED_PLAY + 2, //TOP_CONFIGURED_SILENCE + 2, //TOP_CONFIGURED_WARMUP +}; + +void CActiveAESink::StateMachine(int signal, Protocol *port, Message *msg) +{ + for (int state = m_state; ; state = SINK_parentStates[state]) + { + switch (state) + { + case S_TOP: // TOP + if (port == &m_controlPort) + { + switch (signal) + { + case CSinkControlProtocol::CONFIGURE: + SinkConfig *data; + data = (SinkConfig*)msg->data; + if (data) + { + m_requestedFormat = data->format; + m_stats = data->stats; + } + m_extError = false; + m_extSilence = false; + ReturnBuffers(); + OpenSink(); + + if (!m_extError) + { + m_stats->SetSinkCacheTotal(m_sink->GetCacheTotal()); + m_state = S_TOP_CONFIGURED_IDLE; + m_extTimeout = 10000; + msg->Reply(CSinkControlProtocol::ACC, &m_sinkFormat, sizeof(AEAudioFormat)); + } + else + { + m_state = S_TOP_UNCONFIGURED; + msg->Reply(CSinkControlProtocol::ERR); + } + return; + + case CSinkControlProtocol::UNCONFIGURE: + ReturnBuffers(); + if (m_sink) + { + m_sink->Drain(); + m_sink->Deinitialize(); + delete m_sink; + m_sink = NULL; + } + m_state = S_TOP_UNCONFIGURED; + msg->Reply(CSinkControlProtocol::ACC); + return; + + default: + break; + } + } + else if (port == &m_dataPort) + { + switch (signal) + { + case CSinkDataProtocol::DRAIN: + msg->Reply(CSinkDataProtocol::ACC); + m_state = S_TOP_UNCONFIGURED; + m_extTimeout = 0; + return; + default: + break; + } + } + { + std::string portName = port == NULL ? "timer" : port->portName; + CLog::Log(LOGWARNING, "CActiveAESink::%s - signal: %d form port: %s not handled for state: %d", __FUNCTION__, signal, portName.c_str(), m_state); + } + return; + + case S_TOP_UNCONFIGURED: + if (port == NULL) // timeout + { + switch (signal) + { + case CSinkControlProtocol::TIMEOUT: + m_extTimeout = 1000; + return; + default: + break; + } + } + else if (port == &m_dataPort) + { + switch (signal) + { + case CSinkDataProtocol::SAMPLE: + CSampleBuffer *samples; + int timeout; + samples = *((CSampleBuffer**)msg->data); + timeout = 1000*samples->pkt->nb_samples/samples->pkt->config.sample_rate; + Sleep(timeout); + msg->Reply(CSinkDataProtocol::RETURNSAMPLE, &samples, sizeof(CSampleBuffer*)); + m_extTimeout = 0; + return; + default: + break; + } + } + break; + + case S_TOP_CONFIGURED: + if (port == &m_controlPort) + { + switch (signal) + { + case CSinkControlProtocol::SILENCEMODE: + m_extSilence = *(bool*)msg->data; + if (CSettings::Get().GetBool("audiooutput.streamsilence")) + m_extSilence = true; + if (m_extSilence) + { + m_extCycleCounter = 5; + m_state = S_TOP_CONFIGURED_WARMUP; + m_extTimeout = 0; + } + return; + case CSinkControlProtocol::VOLUME: + m_volume = *(float*)msg->data; + m_sink->SetVolume(m_volume); + return; + default: + break; + } + } + else if (port == &m_dataPort) + { + switch (signal) + { + case CSinkDataProtocol::DRAIN: + m_sink->Drain(); + msg->Reply(CSinkDataProtocol::ACC); + m_state = S_TOP_CONFIGURED_IDLE; + m_extTimeout = 10000; + return; + case CSinkDataProtocol::SAMPLE: + CSampleBuffer *samples; + unsigned int delay; + samples = *((CSampleBuffer**)msg->data); + delay = OutputSamples(samples); + msg->Reply(CSinkDataProtocol::RETURNSAMPLE, &samples, sizeof(CSampleBuffer*)); + if (m_extError) + { + m_sink->Deinitialize(); + delete m_sink; + m_sink = NULL; + m_state = S_TOP_CONFIGURED_SUSPEND; + m_extTimeout = 0; + } + else + { + m_state = S_TOP_CONFIGURED_PLAY; + m_extTimeout = delay / 2; + } + return; + default: + break; + } + } + break; + + case S_TOP_CONFIGURED_SUSPEND: + if (port == &m_controlPort) + { + switch (signal) + { + case CSinkControlProtocol::SILENCEMODE: + return; + case CSinkControlProtocol::VOLUME: + m_volume = *(float*)msg->data; + return; + default: + break; + } + } + else if (port == &m_dataPort) + { + switch (signal) + { + case CSinkDataProtocol::SAMPLE: + m_extError = false; + OpenSink(); + OutputSamples(&m_sampleOfNoise); + m_state = S_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + m_bStateMachineSelfTrigger = true; + return; + case CSinkDataProtocol::DRAIN: + msg->Reply(CSinkDataProtocol::ACC); + return; + default: + break; + } + } + else if (port == NULL) // timeout + { + switch (signal) + { + case CSinkControlProtocol::TIMEOUT: + m_extTimeout = 10000; + return; + default: + break; + } + } + break; + + case S_TOP_CONFIGURED_IDLE: + if (port == &m_dataPort) + { + switch (signal) + { + case CSinkDataProtocol::SAMPLE: + OutputSamples(&m_sampleOfNoise); + m_state = S_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + m_bStateMachineSelfTrigger = true; + return; + default: + break; + } + } + else if (port == NULL) // timeout + { + switch (signal) + { + case CSinkControlProtocol::TIMEOUT: + m_sink->Deinitialize(); + delete m_sink; + m_sink = NULL; + m_state = S_TOP_CONFIGURED_SUSPEND; + m_extTimeout = 10000; + return; + default: + break; + } + } + break; + + case S_TOP_CONFIGURED_PLAY: + if (port == NULL) // timeout + { + switch (signal) + { + case CSinkControlProtocol::TIMEOUT: + if (m_extSilence) + { + m_state = S_TOP_CONFIGURED_SILENCE; + m_extTimeout = 0; + } + else + { + m_sink->Drain(); + m_state = S_TOP_CONFIGURED_IDLE; + m_extTimeout = 10000; + } + return; + default: + break; + } + } + break; + + case S_TOP_CONFIGURED_SILENCE: + if (port == NULL) // timeout + { + switch (signal) + { + case CSinkControlProtocol::TIMEOUT: + unsigned int delay; + delay = OutputSamples(&m_sampleOfSilence); + m_extCycleCounter--; + if (m_extError) + { + m_sink->Deinitialize(); + delete m_sink; + m_sink = NULL; + m_state = S_TOP_CONFIGURED_SUSPEND; + } + else if(m_extCycleCounter <= 0) + { + m_extCycleCounter = 2; + m_state = S_TOP_CONFIGURED_WARMUP; + } + m_extTimeout = 0; + return; + default: + break; + } + } + break; + + case S_TOP_CONFIGURED_WARMUP: + if (port == NULL) // timeout + { + switch (signal) + { + case CSinkControlProtocol::TIMEOUT: + unsigned int delay; + delay = OutputSamples(&m_sampleOfNoise); + m_extCycleCounter--; + if (m_extError) + { + m_sink->Deinitialize(); + delete m_sink; + m_sink = NULL; + m_state = S_TOP_CONFIGURED_SUSPEND; + } + else if(m_extCycleCounter <= 0) + { + m_extCycleCounter = 20; + m_state = S_TOP_CONFIGURED_SILENCE; + } + m_extTimeout = 0; + return; + default: + break; + } + } + break; + + default: // we are in no state, should not happen + CLog::Log(LOGERROR, "CActiveSink::%s - no valid state: %d", __FUNCTION__, m_state); + return; + } + } // for +} + +void CActiveAESink::Process() +{ + Message *msg = NULL; + Protocol *port = NULL; + bool gotMsg; + + m_state = S_TOP_UNCONFIGURED; + m_extTimeout = 1000; + m_bStateMachineSelfTrigger = false; + + while (!m_bStop) + { + gotMsg = false; + + if (m_bStateMachineSelfTrigger) + { + m_bStateMachineSelfTrigger = false; + // self trigger state machine + StateMachine(msg->signal, port, msg); + if (!m_bStateMachineSelfTrigger) + { + msg->Release(); + msg = NULL; + } + continue; + } + // check control port + else if (m_controlPort.ReceiveOutMessage(&msg)) + { + gotMsg = true; + port = &m_controlPort; + } + // check data port + else if (m_dataPort.ReceiveOutMessage(&msg)) + { + gotMsg = true; + port = &m_dataPort; + } + + if (gotMsg) + { + StateMachine(msg->signal, port, msg); + if (!m_bStateMachineSelfTrigger) + { + msg->Release(); + msg = NULL; + } + continue; + } + + // wait for message + else if (m_outMsgEvent.WaitMSec(m_extTimeout)) + { + continue; + } + // time out + else + { + msg = m_controlPort.GetMessage(); + msg->signal = CSinkControlProtocol::TIMEOUT; + port = 0; + // signal timeout to state machine + StateMachine(msg->signal, port, msg); + if (!m_bStateMachineSelfTrigger) + { + msg->Release(); + msg = NULL; + } + } + } +} + +void CActiveAESink::EnumerateSinkList() +{ + unsigned int c_retry = 5; + m_sinkInfoList.clear(); + CAESinkFactory::EnumerateEx(m_sinkInfoList); + while(m_sinkInfoList.size() == 0 && c_retry > 0) + { + CLog::Log(LOGNOTICE, "No Devices found - retry: %d", c_retry); + Sleep(2000); + c_retry--; + // retry the enumeration + CAESinkFactory::EnumerateEx(m_sinkInfoList, true); + } + CLog::Log(LOGNOTICE, "Found %lu Lists of Devices", m_sinkInfoList.size()); + PrintSinks(); +} + +void CActiveAESink::PrintSinks() +{ + for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) + { + CLog::Log(LOGNOTICE, "Enumerated %s devices:", itt->m_sinkName.c_str()); + int count = 0; + for (AEDeviceInfoList::iterator itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) + { + CLog::Log(LOGNOTICE, " Device %d", ++count); + CAEDeviceInfo& info = *itt2; + std::stringstream ss((std::string)info); + std::string line; + while(std::getline(ss, line, '\n')) + CLog::Log(LOGNOTICE, " %s", line.c_str()); + } + } +} + +void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough) +{ + for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) + { + AESinkInfo sinkInfo = *itt; + for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2) + { + CAEDeviceInfo devInfo = *itt2; + if (passthrough && devInfo.m_deviceType == AE_DEVTYPE_PCM) + continue; + + std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName; + + std::stringstream ss; + + /* add the sink name if we have more then one sink type */ + if (m_sinkInfoList.size() > 1) + ss << sinkInfo.m_sinkName << ": "; + + ss << devInfo.m_displayName; + if (!devInfo.m_displayNameExtra.empty()) + ss << ", " << devInfo.m_displayNameExtra; + + devices.push_back(AEDevice(ss.str(), device)); + } + } +} + +std::string CActiveAESink::GetDefaultDevice(bool passthrough) +{ + for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) + { + AESinkInfo sinkInfo = *itt; + for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2) + { + CAEDeviceInfo devInfo = *itt2; + if (passthrough && devInfo.m_deviceType == AE_DEVTYPE_PCM) + continue; + + std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName; + return device; + } + } + return "default"; +} + +void CActiveAESink::GetDeviceFriendlyName(std::string &device) +{ + m_deviceFriendlyName = "Device not found"; + /* Match the device and find its friendly name */ + for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) + { + AESinkInfo sinkInfo = *itt; + for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2) + { + CAEDeviceInfo& devInfo = *itt2; + if (devInfo.m_deviceName == device) + { + m_deviceFriendlyName = devInfo.m_displayName; + break; + } + } + } + return; +} + +void CActiveAESink::OpenSink() +{ + std::string device, driver; + bool passthrough = AE_IS_RAW(m_requestedFormat.m_dataFormat); + if (passthrough) + device = CSettings::Get().GetString("audiooutput.passthroughdevice"); + else + device = CSettings::Get().GetString("audiooutput.audiodevice"); + + CAESinkFactory::ParseDevice(device, driver); + if (driver.empty() && m_sink) + driver = m_sink->GetName(); + + std::string sinkName; + if (m_sink) + { + sinkName = m_sink->GetName(); + std::transform(sinkName.begin(), sinkName.end(), sinkName.begin(), ::toupper); + } + + if (!m_sink || sinkName != driver || !m_sink->IsCompatible(m_requestedFormat, device)) + { + CLog::Log(LOGINFO, "CActiveAE::OpenSink - sink incompatible, re-starting"); + + if (m_sink) + { + m_sink->Drain(); + m_sink->Deinitialize(); + delete m_sink; + m_sink = NULL; + } + + // get the display name of the device + GetDeviceFriendlyName(device); + + // if we already have a driver, prepend it to the device string + if (!driver.empty()) + device = driver + ":" + device; + + // WARNING: this changes format and does not use passthrough + m_sinkFormat = m_requestedFormat; + m_sink = CAESinkFactory::Create(device, m_sinkFormat, passthrough); + + if (!m_sink) + { + m_extError = true; + return; + } + + m_sink->SetVolume(m_volume); + +#ifdef WORDS_BIGENDIAN + if (m_sinkFormat.m_dataFormat == AE_FMT_S16BE) + m_sinkFormat.m_dataFormat = AE_FMT_S16NE; + else if (m_sinkFormat.m_dataFormat == AE_FMT_S32BE) + m_sinkFormat.m_dataFormat = AE_FMT_S32NE; +#else + if (m_sinkFormat.m_dataFormat == AE_FMT_S16LE) + m_sinkFormat.m_dataFormat = AE_FMT_S16NE; + else if (m_sinkFormat.m_dataFormat == AE_FMT_S32LE) + m_sinkFormat.m_dataFormat = AE_FMT_S32NE; +#endif + + CLog::Log(LOGDEBUG, "CActiveAE::OpenSink - %s Initialized:", m_sink->GetName()); + CLog::Log(LOGDEBUG, " Output Device : %s", m_deviceFriendlyName.c_str()); + CLog::Log(LOGDEBUG, " Sample Rate : %d", m_sinkFormat.m_sampleRate); + CLog::Log(LOGDEBUG, " Sample Format : %s", CAEUtil::DataFormatToStr(m_sinkFormat.m_dataFormat)); + CLog::Log(LOGDEBUG, " Channel Count : %d", m_sinkFormat.m_channelLayout.Count()); + CLog::Log(LOGDEBUG, " Channel Layout: %s", ((std::string)m_sinkFormat.m_channelLayout).c_str()); + CLog::Log(LOGDEBUG, " Frames : %d", m_sinkFormat.m_frames); + CLog::Log(LOGDEBUG, " Frame Samples : %d", m_sinkFormat.m_frameSamples); + CLog::Log(LOGDEBUG, " Frame Size : %d", m_sinkFormat.m_frameSize); + } + else + CLog::Log(LOGINFO, "CActiveAE::OpenSink - keeping old sink with : %s, %s, %dhz", + CAEUtil::DataFormatToStr(m_sinkFormat.m_dataFormat), + ((std::string)m_sinkFormat.m_channelLayout).c_str(), + m_sinkFormat.m_sampleRate); + + // init sample of silence + SampleConfig config; + config.fmt = CActiveAEResample::GetAVSampleFormat(m_sinkFormat.m_dataFormat); + config.channel_layout = CActiveAEResample::GetAVChannelLayout(m_sinkFormat.m_channelLayout); + config.channels = m_sinkFormat.m_channelLayout.Count(); + config.sample_rate = m_sinkFormat.m_sampleRate; + delete m_sampleOfSilence.pkt; + m_sampleOfSilence.pkt = new CSoundPacket(config, m_sinkFormat.m_frames); + m_sampleOfSilence.pkt->nb_samples = m_sampleOfSilence.pkt->max_nb_samples; + + // init sample of noise + delete m_sampleOfNoise.pkt; + m_sampleOfNoise.pkt = new CSoundPacket(config, m_sinkFormat.m_frames); + m_sampleOfNoise.pkt->nb_samples = m_sampleOfNoise.pkt->max_nb_samples; + if (!passthrough) + GenerateNoise(); + + if (m_convertBuffer) + { + _aligned_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_convertFn = NULL; + m_convertState = CHECK_CONVERT; +} + +void CActiveAESink::ReturnBuffers() +{ + Message *msg = NULL; + CSampleBuffer *samples; + while (m_dataPort.ReceiveOutMessage(&msg)) + { + if (msg->signal == CSinkDataProtocol::SAMPLE) + { + samples = *((CSampleBuffer**)msg->data); + msg->Reply(CSinkDataProtocol::RETURNSAMPLE, &samples, sizeof(CSampleBuffer*)); + } + } +} + +unsigned int CActiveAESink::OutputSamples(CSampleBuffer* samples) +{ + uint8_t *buffer = samples->pkt->data[0]; + unsigned int frames = samples->pkt->nb_samples; + unsigned int maxFrames; + int retry = 0; + int written = 0; + double sinkDelay = 0.0; + + switch(m_convertState) + { + case SKIP_CONVERT: + break; + case NEED_CONVERT: + EnsureConvertBuffer(samples); + buffer = Convert(samples); + break; + case NEED_BYTESWAP: + Endian_Swap16_buf((uint16_t *)buffer, (uint16_t *)buffer, frames * samples->pkt->config.channels); + break; + case CHECK_CONVERT: + ConvertInit(samples); + if (m_convertState == NEED_CONVERT) + buffer = Convert(samples); + else if (m_convertState == NEED_BYTESWAP) + Endian_Swap16_buf((uint16_t *)buffer, (uint16_t *)buffer, frames * samples->pkt->config.channels); + break; + default: + break; + } + + while(frames > 0) + { + maxFrames = std::min(frames, m_sinkFormat.m_frames); + written = m_sink->AddPackets(buffer, maxFrames, true, true); + if (written == 0) + { + Sleep(500*m_sinkFormat.m_frames/m_sinkFormat.m_sampleRate); + retry++; + if (retry > 4) + { + m_extError = true; + CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - failed"); + return 0; + } + else + continue; + } + frames -= written; + buffer += written*m_sinkFormat.m_frameSize; + sinkDelay = m_sink->GetDelay(); + m_stats->UpdateSinkDelay(sinkDelay, samples->pool ? written : 0); + } + return sinkDelay*1000; +} + +void CActiveAESink::ConvertInit(CSampleBuffer* samples) +{ + if (CActiveAEResample::GetAESampleFormat(samples->pkt->config.fmt) != m_sinkFormat.m_dataFormat) + { + m_convertFn = CAEConvert::FrFloat(m_sinkFormat.m_dataFormat); + if (m_convertBuffer) + _aligned_free(m_convertBuffer); + m_convertBufferSampleSize = samples->pkt->max_nb_samples; + m_convertBuffer = (uint8_t*)_aligned_malloc(samples->pkt->max_nb_samples * m_sinkFormat.m_channelLayout.Count() * m_sinkFormat.m_frameSize, 16); + memset(m_convertBuffer, 0, samples->pkt->max_nb_samples * m_sinkFormat.m_channelLayout.Count() * m_sinkFormat.m_frameSize); + m_convertState = NEED_CONVERT; + } + else if (AE_IS_RAW(m_requestedFormat.m_dataFormat) && CAEUtil::S16NeedsByteSwap(AE_FMT_S16NE, m_sinkFormat.m_dataFormat)) + { + m_convertState = NEED_BYTESWAP; + } + else + m_convertState = SKIP_CONVERT; +} + +void CActiveAESink::EnsureConvertBuffer(CSampleBuffer* samples) +{ + if (!m_convertBuffer) + return; + + if (samples->pkt->max_nb_samples <= m_convertBufferSampleSize) + return; + + _aligned_free(m_convertBuffer); + m_convertBufferSampleSize = samples->pkt->max_nb_samples; + m_convertBuffer = (uint8_t*)_aligned_malloc(samples->pkt->max_nb_samples * m_sinkFormat.m_channelLayout.Count() * m_sinkFormat.m_frameSize, 16); + memset(m_convertBuffer, 0, samples->pkt->max_nb_samples * m_sinkFormat.m_channelLayout.Count() * m_sinkFormat.m_frameSize); +} + +uint8_t* CActiveAESink::Convert(CSampleBuffer* samples) +{ + unsigned int nb_samples = m_convertFn((float*)samples->pkt->data[0], samples->pkt->nb_samples * samples->pkt->config.channels, m_convertBuffer); + return m_convertBuffer; +} + +#define PI 3.1415926536f + +void CActiveAESink::GenerateNoise() +{ + int nb_floats = m_sinkFormat.m_frames*m_sinkFormat.m_channelLayout.Count(); + float *noise = new float[nb_floats]; + + float R1, R2; + for(int i=0; i<nb_floats;i++) + { + do + { + R1 = (float) rand() / (float) RAND_MAX; + R2 = (float) rand() / (float) RAND_MAX; + } + while(R1 == 0.0f); + + noise[i] = (float) sqrt( -2.0f * log( R1 )) * cos( 2.0f * PI * R2 ) * 0.00001; + } + + AEDataFormat fmt = CActiveAEResample::GetAESampleFormat(m_sampleOfNoise.pkt->config.fmt); + CAEConvert::AEConvertFrFn convertFn = CAEConvert::FrFloat(fmt); + convertFn(noise, nb_floats, m_sampleOfNoise.pkt->data[0]); + delete [] noise; +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h new file mode 100644 index 0000000000..559179ca6e --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h @@ -0,0 +1,138 @@ +#pragma once +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/Event.h" +#include "threads/Thread.h" +#include "utils/ActorProtocol.h" +#include "Interfaces/AE.h" +#include "Interfaces/AESink.h" +#include "AESinkFactory.h" +#include "ActiveAEResample.h" +#include "Utils/AEConvert.h" + +namespace ActiveAE +{ +using namespace Actor; + +class CEngineStats; + +struct SinkConfig +{ + AEAudioFormat format; + CEngineStats *stats; +}; + +class CSinkControlProtocol : public Protocol +{ +public: + CSinkControlProtocol(std::string name, CEvent* inEvent, CEvent *outEvent) : Protocol(name, inEvent, outEvent) {}; + enum OutSignal + { + CONFIGURE, + UNCONFIGURE, + SILENCEMODE, + VOLUME, + TIMEOUT, + }; + enum InSignal + { + ACC, + ERR, + STATS, + }; +}; + +class CSinkDataProtocol : public Protocol +{ +public: + CSinkDataProtocol(std::string name, CEvent* inEvent, CEvent *outEvent) : Protocol(name, inEvent, outEvent) {}; + enum OutSignal + { + SAMPLE = 0, + DRAIN, + }; + enum InSignal + { + RETURNSAMPLE, + ACC, + }; +}; + +class CActiveAESink : private CThread +{ +public: + CActiveAESink(CEvent *inMsgEvent); + void EnumerateSinkList(); + void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough); + std::string GetDefaultDevice(bool passthrough); + void Start(); + void Dispose(); + bool IsCompatible(const AEAudioFormat format, const std::string &device); + bool HasVolume(); + CSinkControlProtocol m_controlPort; + CSinkDataProtocol m_dataPort; + +protected: + void Process(); + void StateMachine(int signal, Protocol *port, Message *msg); + void PrintSinks(); + void GetDeviceFriendlyName(std::string &device); + void OpenSink(); + void ReturnBuffers(); + + unsigned int OutputSamples(CSampleBuffer* samples); + void ConvertInit(CSampleBuffer* samples); + inline void EnsureConvertBuffer(CSampleBuffer* samples); + inline uint8_t* Convert(CSampleBuffer* samples); + + void GenerateNoise(); + + CEvent m_outMsgEvent; + CEvent *m_inMsgEvent; + int m_state; + bool m_bStateMachineSelfTrigger; + int m_extTimeout; + bool m_extError; + bool m_extSilence; + int m_extCycleCounter; + + CSampleBuffer m_sampleOfSilence; + CSampleBuffer m_sampleOfNoise; + uint8_t *m_convertBuffer; + int m_convertBufferSampleSize; + CAEConvert::AEConvertFrFn m_convertFn; + enum + { + CHECK_CONVERT, + NEED_CONVERT, + NEED_BYTESWAP, + SKIP_CONVERT, + } m_convertState; + + std::string m_deviceFriendlyName; + AESinkInfoList m_sinkInfoList; + IAESink *m_sink; + AEAudioFormat m_sinkFormat, m_requestedFormat; + CEngineStats *m_stats; + float m_volume; +}; + +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.cpp new file mode 100644 index 0000000000..fb75ba92e1 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Interfaces/AESound.h" + +#include "AEFactory.h" +#include "AEAudioFormat.h" +#include "ActiveAE.h" +#include "ActiveAESound.h" +#include "utils/log.h" +#include "DllAvUtil.h" + +using namespace ActiveAE; +using namespace XFILE; + +/* typecast AE to CActiveAE */ +#define AE (*((CActiveAE*)CAEFactory::GetEngine())) + +CActiveAESound::CActiveAESound(const std::string &filename) : + IAESound (filename), + m_filename (filename), + m_volume (1.0f ) +{ + m_orig_sound = NULL; + m_dst_sound = NULL; + m_pFile = NULL; +} + +CActiveAESound::~CActiveAESound() +{ + delete m_orig_sound; + delete m_dst_sound; + Finish(); +} + +void CActiveAESound::Play() +{ + AE.PlaySound(this); +} + +void CActiveAESound::Stop() +{ + AE.StopSound(this); +} + +bool CActiveAESound::IsPlaying() +{ + // TODO + return false; +} + +uint8_t** CActiveAESound::InitSound(bool orig, SampleConfig config, int nb_samples) +{ + CSoundPacket **info; + if (orig) + info = &m_orig_sound; + else + info = &m_dst_sound; + + delete *info; + *info = new CSoundPacket(config, nb_samples); + + (*info)->nb_samples = 0; + m_isConverted = false; + return (*info)->data; +} + +bool CActiveAESound::StoreSound(bool orig, uint8_t **buffer, int samples, int linesize) +{ + CSoundPacket **info; + if (orig) + info = &m_orig_sound; + else + info = &m_dst_sound; + + if ((*info)->nb_samples + samples > (*info)->max_nb_samples) + { + CLog::Log(LOGERROR, "CActiveAESound::StoreSound - exceeded max samples"); + return false; + } + + int bytes_to_copy = samples * (*info)->bytes_per_sample * (*info)->config.channels; + bytes_to_copy /= (*info)->planes; + int start = (*info)->nb_samples * (*info)->bytes_per_sample * (*info)->config.channels; + start /= (*info)->planes; + + for (int i=0; i<(*info)->planes; i++) + { + memcpy((*info)->data[i]+start, buffer[i], bytes_to_copy); + } + (*info)->nb_samples += samples; + + return true; +} + +CSoundPacket *CActiveAESound::GetSound(bool orig) +{ + if (orig) + return m_orig_sound; + else + return m_dst_sound; +} + +bool CActiveAESound::Prepare() +{ + unsigned int flags = READ_TRUNCATED | READ_CHUNKED; + m_pFile = new CFile(); + + if (!m_pFile->Open(m_filename, flags)) + { + delete m_pFile; + m_pFile = NULL; + return false; + } + m_isSeekPosible = m_pFile->IoControl(IOCTRL_SEEK_POSSIBLE, NULL) != 0; + m_fileSize = m_pFile->GetLength(); + return true; +} + +void CActiveAESound::Finish() +{ + delete m_pFile; + m_pFile = NULL; +} + +int CActiveAESound::GetChunkSize() +{ + return m_pFile->GetChunkSize(); +} + +int CActiveAESound::Read(void *h, uint8_t* buf, int size) +{ + CFile *pFile = static_cast<CActiveAESound*>(h)->m_pFile; + return pFile->Read(buf, size); +} + +offset_t CActiveAESound::Seek(void *h, offset_t pos, int whence) +{ + CFile* pFile = static_cast<CActiveAESound*>(h)->m_pFile; + if(whence == AVSEEK_SIZE) + return pFile->GetLength(); + else + return pFile->Seek(pos, whence & ~AVSEEK_FORCE); +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.h new file mode 100644 index 0000000000..7cdf76cc1c --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESound.h @@ -0,0 +1,73 @@ +#pragma once +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/StdString.h" +#include "Interfaces/AESound.h" +#include "ActiveAEResample.h" +#include "filesystem/File.h" + +class DllAvUtil; + +namespace ActiveAE +{ + +class CActiveAESound : public IAESound +{ +public: + CActiveAESound (const std::string &filename); + virtual ~CActiveAESound(); + + virtual void Play(); + virtual void Stop(); + virtual bool IsPlaying(); + + virtual void SetVolume(float volume) { m_volume = std::max(0.0f, std::min(1.0f, volume)); } + virtual float GetVolume() { return m_volume; } + + uint8_t** InitSound(bool orig, SampleConfig config, int nb_samples); + bool StoreSound(bool orig, uint8_t **buffer, int samples, int linesize); + CSoundPacket *GetSound(bool orig); + + bool IsConverted() { return m_isConverted; } + void SetConverted(bool state) { m_isConverted = state; } + + bool Prepare(); + void Finish(); + int GetChunkSize(); + int GetFileSize() { return m_fileSize; } + bool IsSeekPosible() { return m_isSeekPosible; } + + static int Read(void *h, uint8_t* buf, int size); + static offset_t Seek(void *h, offset_t pos, int whence); + +protected: + std::string m_filename; + XFILE::CFile *m_pFile; + bool m_isSeekPosible; + int m_fileSize; + float m_volume; + + CSoundPacket *m_orig_sound; + CSoundPacket *m_dst_sound; + + bool m_isConverted; +}; +} diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp new file mode 100644 index 0000000000..66f62b70a3 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "threads/SingleLock.h" +#include "utils/log.h" +#include "utils/MathUtils.h" + +#include "AEFactory.h" +#include "Utils/AEUtil.h" + +#include "ActiveAE.h" +#include "ActiveAEStream.h" + +using namespace ActiveAE; + +/* typecast AE to CActiveAE */ +#define AE (*((CActiveAE*)CAEFactory::GetEngine())) + + +CActiveAEStream::CActiveAEStream(AEAudioFormat *format) +{ + m_format = *format; + m_bufferedTime = 0; + m_currentBuffer = NULL; + m_drain = false; + m_paused = false; + m_rgain = 1.0; + m_volume = 1.0; + m_streamSpace = m_format.m_frameSize * m_format.m_frames; + m_streamDraining = false; + m_streamDrained = false; + m_streamFading = false; + m_streamFreeBuffers = 0; + m_streamIsBuffering = true; + m_streamSlave = NULL; + m_convertFn = NULL; +} + +CActiveAEStream::~CActiveAEStream() +{ +} + +void CActiveAEStream::IncFreeBuffers() +{ + CSingleLock lock(m_streamLock); + m_streamFreeBuffers++; +} + +void CActiveAEStream::DecFreeBuffers() +{ + CSingleLock lock(m_streamLock); + m_streamFreeBuffers--; +} + +void CActiveAEStream::ResetFreeBuffers() +{ + CSingleLock lock(m_streamLock); + m_streamFreeBuffers = 0; +} + +unsigned int CActiveAEStream::GetSpace() +{ + CSingleLock lock(m_streamLock); + return m_streamFreeBuffers * m_streamSpace; +} + +unsigned int CActiveAEStream::AddData(void *data, unsigned int size) +{ + Message *msg; + unsigned int copied = 0; + int bytesToCopy = size; + while(copied < size) + { + if (m_currentBuffer) + { + int start = m_currentBuffer->pkt->nb_samples * + m_currentBuffer->pkt->bytes_per_sample * + m_currentBuffer->pkt->config.channels / + m_currentBuffer->pkt->planes; + + int freeSamples = m_currentBuffer->pkt->max_nb_samples - m_currentBuffer->pkt->nb_samples; + int availableSamples = bytesToCopy / m_format.m_frameSize; + int space = freeSamples * m_currentBuffer->pkt->bytes_per_sample * m_currentBuffer->pkt->config.channels; + int samples = std::min(freeSamples, availableSamples); + int bytes = samples * m_format.m_frameSize; + //TODO: handle planar formats + if (m_convertFn) + m_convertFn((uint8_t*)data+copied, samples*m_currentBuffer->pkt->config.channels, (float*)(m_currentBuffer->pkt->data[0] + start)); + else + memcpy(m_currentBuffer->pkt->data[0] + start, (uint8_t*)data+copied, bytes); + { + CSingleLock lock(*m_statsLock); + m_currentBuffer->pkt->nb_samples += samples; + m_bufferedTime += (double)samples / m_currentBuffer->pkt->config.sample_rate; + } + copied += bytes; + bytesToCopy -= bytes; + if (m_currentBuffer->pkt->nb_samples == m_currentBuffer->pkt->max_nb_samples) + { + MsgStreamSample msgData; + msgData.buffer = m_currentBuffer; + msgData.stream = this; + m_streamPort->SendOutMessage(CActiveAEDataProtocol::STREAMSAMPLE, &msgData, sizeof(MsgStreamSample)); + m_currentBuffer = NULL; + } + continue; + } + else if (m_streamPort->ReceiveInMessage(&msg)) + { + if (msg->signal == CActiveAEDataProtocol::STREAMBUFFER) + { + m_currentBuffer = *((CSampleBuffer**)msg->data); + msg->Release(); + DecFreeBuffers(); + continue; + } + else + { + CLog::Log(LOGERROR, "CActiveAEStream::AddData - unknown signal"); + msg->Release(); + break; + } + } + if (!m_inMsgEvent.WaitMSec(200)) + break; + } + return copied; +} + +double CActiveAEStream::GetDelay() +{ + return AE.GetDelay(this); +} + +bool CActiveAEStream::IsBuffering() +{ + CSingleLock lock(m_streamLock); + return m_streamIsBuffering; +} + +double CActiveAEStream::GetCacheTime() +{ + return AE.GetCacheTime(this); +} + +double CActiveAEStream::GetCacheTotal() +{ + return AE.GetCacheTotal(this); +} + +void CActiveAEStream::Pause() +{ + AE.PauseStream(this, true); +} + +void CActiveAEStream::Resume() +{ + AE.PauseStream(this, false); +} + +void CActiveAEStream::Drain(bool wait) +{ + Message *msg; + CActiveAEStream *stream = this; + + m_streamDraining = true; + m_streamDrained = false; + + Message *reply; + if (m_streamPort->SendOutMessageSync(CActiveAEDataProtocol::DRAINSTREAM, + &reply,2000, + &stream, sizeof(CActiveAEStream*))) + { + bool success = reply->signal == CActiveAEDataProtocol::ACC ? true : false; + reply->Release(); + if (!success) + { + CLog::Log(LOGERROR, "CActiveAEStream::Drain - no acc"); + } + } + + if (m_currentBuffer) + { + MsgStreamSample msgData; + msgData.buffer = m_currentBuffer; + msgData.stream = this; + m_streamPort->SendOutMessage(CActiveAEDataProtocol::STREAMSAMPLE, &msgData, sizeof(MsgStreamSample)); + m_currentBuffer = NULL; + } + + XbmcThreads::EndTime timer(2000); + while (!timer.IsTimePast()) + { + if (m_streamPort->ReceiveInMessage(&msg)) + { + if (msg->signal == CActiveAEDataProtocol::STREAMBUFFER) + { + MsgStreamSample msgData; + msgData.stream = this; + msgData.buffer = *((CSampleBuffer**)msg->data); + msg->Reply(CActiveAEDataProtocol::STREAMSAMPLE, &msgData, sizeof(MsgStreamSample)); + DecFreeBuffers(); + continue; + } + else if (msg->signal == CActiveAEDataProtocol::STREAMDRAINED) + { + msg->Release(); + return; + } + } + else if (!wait) + return; + + m_inMsgEvent.WaitMSec(timer.MillisLeft()); + } + CLog::Log(LOGERROR, "CActiveAEStream::Drain - timeout out"); +} + +bool CActiveAEStream::IsDraining() +{ + CSingleLock lock(m_streamLock); + return m_streamDraining; +} + +bool CActiveAEStream::IsDrained() +{ + CSingleLock lock(m_streamLock); + return m_streamDrained; +} + +void CActiveAEStream::Flush() +{ + if (m_currentBuffer) + { + MsgStreamSample msgData; + m_currentBuffer->pkt->nb_samples = 0; + msgData.buffer = m_currentBuffer; + msgData.stream = this; + m_streamPort->SendOutMessage(CActiveAEDataProtocol::STREAMSAMPLE, &msgData, sizeof(MsgStreamSample)); + m_currentBuffer = NULL; + } + AE.FlushStream(this); + ResetFreeBuffers(); +} + +float CActiveAEStream::GetAmplification() +{ + return m_streamAmplify; +} + +void CActiveAEStream::SetAmplification(float amplify) +{ + m_streamAmplify = amplify; + AE.SetStreamAmplification(this, m_streamAmplify); +} + +float CActiveAEStream::GetReplayGain() +{ + return m_streamRgain; +} + +void CActiveAEStream::SetReplayGain(float factor) +{ + m_streamRgain = std::max( 0.0f, factor); + AE.SetStreamReplaygain(this, m_streamRgain); +} + +float CActiveAEStream::GetVolume() +{ + return m_streamVolume; +} + +void CActiveAEStream::SetVolume(float volume) +{ + m_streamVolume = std::max( 0.0f, std::min(1.0f, volume)); + AE.SetStreamVolume(this, m_streamVolume); +} + +double CActiveAEStream::GetResampleRatio() +{ + return m_streamResampleRatio; +} + +bool CActiveAEStream::SetResampleRatio(double ratio) +{ + m_streamResampleRatio = ratio; + AE.SetStreamResampleRatio(this, m_streamResampleRatio); + return true; +} + +void CActiveAEStream::FadeVolume(float from, float target, unsigned int time) +{ + if (time == 0) + return; + + m_streamFading = true; + AE.SetStreamFade(this, from, target, time); +} + +bool CActiveAEStream::IsFading() +{ + CSingleLock lock(m_streamLock); + return m_streamFading; +} + +const unsigned int CActiveAEStream::GetFrameSize() const +{ + return m_format.m_frameSize; +} + +const unsigned int CActiveAEStream::GetChannelCount() const +{ + return m_format.m_channelLayout.Count(); +} + +const unsigned int CActiveAEStream::GetSampleRate() const +{ + return m_format.m_sampleRate; +} + +const unsigned int CActiveAEStream::GetEncodedSampleRate() const +{ + return m_format.m_encodedRate; +} + +const enum AEDataFormat CActiveAEStream::GetDataFormat() const +{ + return m_format.m_dataFormat; +} + +void CActiveAEStream::RegisterAudioCallback(IAudioCallback* pCallback) +{ +} + +void CActiveAEStream::UnRegisterAudioCallback() +{ +} + +void CActiveAEStream::RegisterSlave(IAEStream *slave) +{ + CSingleLock lock(m_streamLock); + m_streamSlave = slave; +} + diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h new file mode 100644 index 0000000000..bddf1a37b1 --- /dev/null +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h @@ -0,0 +1,117 @@ +#pragma once +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "AEAudioFormat.h" +#include "Interfaces/AEStream.h" +#include "Utils/AELimiter.h" +#include "Utils/AEConvert.h" + +namespace ActiveAE +{ + +class CActiveAEStream : public IAEStream +{ +protected: + friend class CActiveAE; + friend class CEngineStats; + CActiveAEStream(AEAudioFormat *format); + virtual ~CActiveAEStream(); + void FadingFinished(); + void IncFreeBuffers(); + void DecFreeBuffers(); + void ResetFreeBuffers(); + +public: + virtual unsigned int GetSpace(); + virtual unsigned int AddData(void *data, unsigned int size); + virtual double GetDelay(); + virtual bool IsBuffering(); + virtual double GetCacheTime(); + virtual double GetCacheTotal(); + + virtual void Pause(); + virtual void Resume(); + virtual void Drain(bool wait); + virtual bool IsDraining(); + virtual bool IsDrained(); + virtual void Flush(); + + virtual float GetVolume(); + virtual float GetReplayGain(); + virtual float GetAmplification(); + virtual void SetVolume(float volume); + virtual void SetReplayGain(float factor); + virtual void SetAmplification(float amplify); + + virtual const unsigned int GetFrameSize() const; + virtual const unsigned int GetChannelCount() const; + + virtual const unsigned int GetSampleRate() const ; + virtual const unsigned int GetEncodedSampleRate() const; + virtual const enum AEDataFormat GetDataFormat() const; + + virtual double GetResampleRatio(); + virtual bool SetResampleRatio(double ratio); + virtual void RegisterAudioCallback(IAudioCallback* pCallback); + virtual void UnRegisterAudioCallback(); + virtual void FadeVolume(float from, float to, unsigned int time); + virtual bool IsFading(); + virtual void RegisterSlave(IAEStream *stream); + +protected: + + AEAudioFormat m_format; + float m_streamVolume; + float m_streamRgain; + float m_streamAmplify; + double m_streamResampleRatio; + unsigned int m_streamSpace; + bool m_streamDraining; + bool m_streamDrained; + bool m_streamFading; + int m_streamFreeBuffers; + bool m_streamIsBuffering; + IAEStream *m_streamSlave; + CAEConvert::AEConvertToFn m_convertFn; + CCriticalSection m_streamLock; + + // only accessed by engine + CActiveAEBufferPool *m_inputBuffers; + CActiveAEBufferPoolResample *m_resampleBuffers; + std::deque<CSampleBuffer*> m_processingSamples; + CSampleBuffer *m_currentBuffer; + CActiveAEDataProtocol *m_streamPort; + CEvent m_inMsgEvent; + CCriticalSection *m_statsLock; + bool m_drain; + bool m_paused; + bool m_started; + CAELimiter m_limiter; + float m_volume; + float m_rgain; + float m_bufferedTime; + int m_fadingSamples; + float m_fadingBase; + float m_fadingTarget; + int m_fadingTime; +}; +} + diff --git a/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.cpp b/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.cpp index a9422f779d..073769a2e9 100644 --- a/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.cpp +++ b/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.cpp @@ -603,7 +603,7 @@ void CCoreAudioAEStream::Resume() m_paused = false; } -void CCoreAudioAEStream::Drain() +void CCoreAudioAEStream::Drain(bool wait) { m_draining = true; } diff --git a/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.h b/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.h index 18c52eda1e..1366808132 100644 --- a/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.h +++ b/xbmc/cores/AudioEngine/Engines/CoreAudio/CoreAudioAEStream.h @@ -73,7 +73,7 @@ public: virtual void Pause(); virtual void Resume(); - virtual void Drain(); + virtual void Drain(bool wait); virtual void Flush(); virtual float GetVolume(); diff --git a/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.cpp b/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.cpp index 443c844be5..e1e19033e3 100644 --- a/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.cpp +++ b/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.cpp @@ -386,7 +386,7 @@ void CPulseAEStream::Resume() m_Paused = Cork(false); } -void CPulseAEStream::Drain() +void CPulseAEStream::Drain(bool wait) { if (!m_Initialized) return; diff --git a/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.h b/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.h index 51aeda853a..8d9435b15c 100644 --- a/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.h +++ b/xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.h @@ -48,7 +48,7 @@ public: virtual void Pause (); virtual void Resume (); - virtual void Drain (); + virtual void Drain (bool wait); virtual void Flush (); virtual float GetVolume (); diff --git a/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.cpp b/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.cpp index 05ee1b0bfb..b5c2da33ba 100644 --- a/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.cpp +++ b/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.cpp @@ -555,7 +555,7 @@ void CSoftAEStream::Resume() AE.ResumeStream(this); } -void CSoftAEStream::Drain() +void CSoftAEStream::Drain(bool wait) { CSingleLock lock(m_lock); m_draining = true; diff --git a/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.h b/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.h index f7410e3223..1475c836f8 100644 --- a/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.h +++ b/xbmc/cores/AudioEngine/Engines/SoftAE/SoftAEStream.h @@ -59,7 +59,7 @@ public: virtual void Pause (); virtual void Resume (); - virtual void Drain (); + virtual void Drain (bool wait); virtual bool IsDraining () { return m_draining; } virtual bool IsDrained (); virtual void Flush (); diff --git a/xbmc/cores/AudioEngine/Interfaces/AE.h b/xbmc/cores/AudioEngine/Interfaces/AE.h index cbfa8ed399..aa92e83226 100644 --- a/xbmc/cores/AudioEngine/Interfaces/AE.h +++ b/xbmc/cores/AudioEngine/Interfaces/AE.h @@ -34,12 +34,28 @@ typedef std::vector<AEDevice> AEDeviceList; class IAEStream; class IAESound; class IAEPacketizer; +class IAudioCallback; /* sound options */ #define AE_SOUND_OFF 0 /* disable sounds */ #define AE_SOUND_IDLE 1 /* only play sounds while no streams are running */ #define AE_SOUND_ALWAYS 2 /* always play sounds */ +enum AEQuality +{ + AE_QUALITY_UNKNOWN = -1, /* Unset, unknown or incorrect quality level */ + AE_QUALITY_DEFAULT = 0, /* Engine's default quality level */ + + /* Basic quality levels */ + AE_QUALITY_LOW = 20, /* Low quality level */ + AE_QUALITY_MID = 30, /* Standard quality level */ + AE_QUALITY_HIGH = 50, /* Best sound processing quality */ + + /* Optional quality levels */ + AE_QUALITY_REALLYHIGH = 100 /* Uncompromised optional quality level, + usually with unmeasurable and unnoticeable improvement */ +}; + /** * IAE Interface */ @@ -183,5 +199,21 @@ public: * @returns true if the AudioEngine is capable of RAW output */ virtual bool SupportsRaw() { return false; } + + /** + * Returns true if the AudioEngine supports drain mode which is not streaming silence when idle + * @returns true if the AudioEngine is capable of drain mode + */ + virtual bool SupportsDrain() { return false; } + + virtual void RegisterAudioCallback(IAudioCallback* pCallback) {} + + virtual void UnregisterAudioCallback() {} + + /** + * Returns true if AudioEngine supports specified quality level + * @return true if specified quality level is supported, otherwise false + */ + virtual bool SupportsQualityLevel(enum AEQuality level) { return false; } }; diff --git a/xbmc/cores/AudioEngine/Interfaces/AEEncoder.h b/xbmc/cores/AudioEngine/Interfaces/AEEncoder.h index 8c50fb1008..6e4c257592 100644 --- a/xbmc/cores/AudioEngine/Interfaces/AEEncoder.h +++ b/xbmc/cores/AudioEngine/Interfaces/AEEncoder.h @@ -48,9 +48,10 @@ public: /** * Called to setup the encoder to accept data in the specified format * @param format the desired audio format, may be changed to suit the encoder + * @param allow_planar_input allow engine to use with planar formats * @return true on success, false on failure */ - virtual bool Initialize(AEAudioFormat &format) = 0; + virtual bool Initialize(AEAudioFormat &format, bool allow_planar_input = false) = 0; /** * Reset the encoder for new data @@ -84,6 +85,16 @@ public: virtual int Encode(float *data, unsigned int frames) = 0; /** + * Encodes the supplied samples into a provided buffer + * @param in the PCM samples encoder requested format + * @param in_size input buffer size + * @param output buffer + * @param out_size output buffer size + * @return the number of samples consumed + */ + virtual int Encode (uint8_t *in, int in_size, uint8_t *out, int out_size) { return 0; }; + + /** * Get the encoded data * @param data return pointer to the buffer with the current encoded block * @return the size in bytes of *data diff --git a/xbmc/cores/AudioEngine/Interfaces/AESink.h b/xbmc/cores/AudioEngine/Interfaces/AESink.h index d4fc3b9a84..520d6efb25 100644 --- a/xbmc/cores/AudioEngine/Interfaces/AESink.h +++ b/xbmc/cores/AudioEngine/Interfaces/AESink.h @@ -71,7 +71,7 @@ public: /* Adds packets to be sent out, this routine MUST block or sleep. */ - virtual unsigned int AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) = 0; + virtual unsigned int AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false) = 0; /* Drain the sink diff --git a/xbmc/cores/AudioEngine/Interfaces/AEStream.h b/xbmc/cores/AudioEngine/Interfaces/AEStream.h index 67fa5e2a0f..92d5fef01e 100644 --- a/xbmc/cores/AudioEngine/Interfaces/AEStream.h +++ b/xbmc/cores/AudioEngine/Interfaces/AEStream.h @@ -97,7 +97,7 @@ public: * Start draining the stream * @note Once called AddData will not consume more data. */ - virtual void Drain() = 0; + virtual void Drain(bool wait) = 0; /** * Returns true if the is stream draining diff --git a/xbmc/cores/AudioEngine/Makefile.in b/xbmc/cores/AudioEngine/Makefile.in index 833dfabb70..2e1a83d49d 100644 --- a/xbmc/cores/AudioEngine/Makefile.in +++ b/xbmc/cores/AudioEngine/Makefile.in @@ -43,6 +43,13 @@ SRCS += Engines/SoftAE/SoftAE.cpp SRCS += Engines/SoftAE/SoftAEStream.cpp SRCS += Engines/SoftAE/SoftAESound.cpp +SRCS += Engines/ActiveAE/ActiveAE.cpp +SRCS += Engines/ActiveAE/ActiveAESink.cpp +SRCS += Engines/ActiveAE/ActiveAEStream.cpp +SRCS += Engines/ActiveAE/ActiveAESound.cpp +SRCS += Engines/ActiveAE/ActiveAEResample.cpp +SRCS += Engines/ActiveAE/ActiveAEBuffer.cpp + ifeq (@USE_ANDROID@,1) SRCS += Sinks/AESinkAUDIOTRACK.cpp else diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp index 45028d6cb4..4e0603155e 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp @@ -131,19 +131,20 @@ void CAESinkALSA::GetAESParams(AEAudioFormat format, std::string& params) bool CAESinkALSA::Initialize(AEAudioFormat &format, std::string &device) { + CAEChannelInfo channelLayout; m_initDevice = device; m_initFormat = format; /* if we are raw, correct the data format */ if (AE_IS_RAW(format.m_dataFormat)) { - m_channelLayout = GetChannelLayout(format); + channelLayout = GetChannelLayout(format); format.m_dataFormat = AE_FMT_S16NE; m_passthrough = true; } else { - m_channelLayout = GetChannelLayout(format); + channelLayout = GetChannelLayout(format); m_passthrough = false; } #if defined(HAS_AMLPLAYER) || defined(HAS_LIBAMCODEC) @@ -154,13 +155,13 @@ bool CAESinkALSA::Initialize(AEAudioFormat &format, std::string &device) } #endif - if (m_channelLayout.Count() == 0) + if (channelLayout.Count() == 0) { CLog::Log(LOGERROR, "CAESinkALSA::Initialize - Unable to open the requested channel layout"); return false; } - format.m_channelLayout = m_channelLayout; + format.m_channelLayout = channelLayout; AEDeviceType devType = AEDeviceTypeFromName(device); @@ -176,7 +177,7 @@ bool CAESinkALSA::Initialize(AEAudioFormat &format, std::string &device) snd_config_t *config; snd_config_copy(&config, snd_config); - if (!OpenPCMDevice(device, AESParams, m_channelLayout.Count(), &m_pcm, config)) + if (!OpenPCMDevice(device, AESParams, channelLayout.Count(), &m_pcm, config)) { CLog::Log(LOGERROR, "CAESinkALSA::Initialize - failed to initialize device \"%s\"", device.c_str()); snd_config_delete(config); @@ -435,10 +436,10 @@ bool CAESinkALSA::InitializeSW(AEAudioFormat &format) void CAESinkALSA::Deinitialize() { - Stop(); - if (m_pcm) { + snd_pcm_nonblock(m_pcm, 0); + Stop(); snd_pcm_close(m_pcm); m_pcm = NULL; } @@ -496,7 +497,7 @@ double CAESinkALSA::GetCacheTotal() return (double)m_bufferSize * m_formatSampleRateMul; } -unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) +unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) { if (!m_pcm) { @@ -517,7 +518,14 @@ unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames, bool ha } if ((unsigned int)ret < frames) - return 0; + if(blocking) + { + ret = snd_pcm_wait(m_pcm, m_timeout); + if (ret < 0) + HandleError("snd_pcm_wait", ret); + } + else + return 0; ret = snd_pcm_writei(m_pcm, (void*)data, frames); if (ret < 0) @@ -573,6 +581,7 @@ void CAESinkALSA::Drain() snd_pcm_nonblock(m_pcm, 0); snd_pcm_drain(m_pcm); + snd_pcm_prepare(m_pcm); snd_pcm_nonblock(m_pcm, 1); } diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.h b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.h index 39e3719b22..e43ea5e392 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.h @@ -47,7 +47,7 @@ public: virtual double GetDelay (); virtual double GetCacheTime (); virtual double GetCacheTotal (); - virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio); + virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); virtual void Drain (); virtual bool SoftSuspend(); virtual bool SoftResume(); @@ -64,7 +64,6 @@ private: unsigned int m_bufferSize; double m_formatSampleRateMul; bool m_passthrough; - CAEChannelInfo m_channelLayout; std::string m_device; snd_pcm_t *m_pcm; int m_timeout; diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp index 07a4bccdd5..48e488b6cf 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp @@ -212,7 +212,7 @@ double CAESinkAUDIOTRACK::GetCacheTotal() return m_sinkbuffer_sec + m_audiotrackbuffer_sec; } -unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) +unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) { // write as many frames of audio as we can fit into our internal buffer. @@ -243,6 +243,11 @@ unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t *data, unsigned int frames, b break; } } + // AddPackets runs under a non-idled AE thread we must block or sleep. + // Trying to calc the optimal sleep is tricky so just a minimal sleep. + if(blocking) + Sleep(10); + return hasAudio ? write_frames:frames; } diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h index 3d73d9fb0b..9600867951 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h @@ -40,7 +40,7 @@ public: virtual double GetDelay (); virtual double GetCacheTime (); virtual double GetCacheTotal (); - virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio); + virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); virtual void Drain (); virtual bool HasVolume (); virtual void SetVolume (float scale); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp index d46d551d5e..da98e1f78a 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp @@ -35,6 +35,7 @@ #include <Functiondiscoverykeys_devpkey.h> #include <Rpc.h> #include "cores/AudioEngine/Utils/AEUtil.h" +#include "utils/StringUtils.h" #pragma comment(lib, "Rpcrt4.lib") extern HWND g_hWnd; @@ -143,10 +144,14 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) LPGUID deviceGUID = NULL; RPC_CSTR wszUuid = NULL; HRESULT hr = E_FAIL; + std::string strDeviceGUID = device; std::list<DSDevice> DSDeviceList; std::string deviceFriendlyName; DirectSoundEnumerate(DSEnumCallback, &DSDeviceList); + if(StringUtils::EndsWith(device, std::string("default"))) + strDeviceGUID = GetDefaultDevice(); + for (std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); ++itt) { if ((*itt).lpGuid) @@ -154,23 +159,27 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) hr = (UuidToString((*itt).lpGuid, &wszUuid)); std::string sztmp = (char*)wszUuid; std::string szGUID = "{" + std::string(sztmp.begin(), sztmp.end()) + "}"; - if (strcasecmp(szGUID.c_str(), device.c_str()) == 0) + if (strcasecmp(szGUID.c_str(), strDeviceGUID.c_str()) == 0) { deviceGUID = (*itt).lpGuid; deviceFriendlyName = (*itt).name.c_str(); break; } } - if (hr == RPC_S_OK) RpcStringFree(&wszUuid); + if (hr == RPC_S_OK) RpcStringFree(&wszUuid); } hr = DirectSoundCreate(deviceGUID, &m_pDSound, NULL); if (FAILED(hr)) { - CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device."); - CLog::Log(LOGERROR, __FUNCTION__": DSErr: %s", dserr2str(hr)); - return false; + CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device %s with error %s, trying the default device.", deviceFriendlyName.c_str(), dserr2str(hr)); + hr = DirectSoundCreate(NULL, &m_pDSound, NULL); + if (FAILED(hr)) + { + CLog::Log(LOGERROR, __FUNCTION__": Failed to create the default DirectSound device with error %s.", dserr2str(hr)); + return false; + } } HWND tmp_hWnd; @@ -367,7 +376,7 @@ bool CAESinkDirectSound::IsCompatible(const AEAudioFormat format, const std::str return false; } -unsigned int CAESinkDirectSound::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) +unsigned int CAESinkDirectSound::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) { if (!m_initialized) return 0; @@ -386,10 +395,15 @@ unsigned int CAESinkDirectSound::AddPackets(uint8_t *data, unsigned int frames, while (GetSpace() < total) { - if (m_isDirtyDS) + if(m_isDirtyDS) return INT_MAX; else - return 0; + { + if(blocking) + Sleep(total * 1000 / m_AvgBytesPerSec); + else + return 0; + } } while (len) @@ -398,7 +412,8 @@ unsigned int CAESinkDirectSound::AddPackets(uint8_t *data, unsigned int frames, DWORD size = 0, sizeWrap = 0; if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually m_BufferOffset = 0; - HRESULT res = m_pBuffer->Lock(m_BufferOffset, m_dwChunkSize, &start, &size, &startWrap, &sizeWrap, 0); + DWORD dwWriteBytes = std::min((int)m_dwChunkSize, (int)len); + HRESULT res = m_pBuffer->Lock(m_BufferOffset, dwWriteBytes, &start, &size, &startWrap, &sizeWrap, 0); if (DS_OK != res) { CLog::Log(LOGERROR, __FUNCTION__ ": Unable to lock buffer at offset %u. HRESULT: 0x%08x", m_BufferOffset, res); @@ -436,6 +451,23 @@ void CAESinkDirectSound::Stop() m_pBuffer->Stop(); } +void CAESinkDirectSound::Drain() +{ + if (!m_initialized || m_isDirtyDS) + return; + + m_pBuffer->Stop(); + HRESULT res = m_pBuffer->SetCurrentPosition(0); + if (DS_OK != res) + { + CLog::Log(LOGERROR,__FUNCTION__ ": SetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x", res); + m_isDirtyDS = true; + return; + } + m_BufferOffset = 0; + UpdateCacheStatus(); +} + double CAESinkDirectSound::GetDelay() { if (!m_initialized) @@ -478,6 +510,8 @@ void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bo HRESULT hr; + std::string strDD = GetDefaultDevice(); + /* See if we are on Windows XP */ if (!g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionVista)) { @@ -512,6 +546,15 @@ void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bo deviceInfo.m_sampleRates.push_back((DWORD) 96000); deviceInfoList.push_back(deviceInfo); + + // add the default device with m_deviceName = default + if(strDD == deviceInfo.m_deviceName) + { + deviceInfo.m_deviceName = std::string("default"); + deviceInfo.m_displayName = std::string("default"); + deviceInfo.m_displayNameExtra = std::string(""); + deviceInfoList.push_back(deviceInfo); + } } RpcStringFree(&cszGUID); @@ -627,22 +670,14 @@ void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bo deviceInfo.m_deviceType = aeDeviceType; deviceInfoList.push_back(deviceInfo); - } - // since AE takes the first device in deviceInfoList as default audio device we need - // to sort it in order to use the real default device - if(deviceInfoList.size() > 1) - { - std::string strDD = GetDefaultDevice(); - for (AEDeviceInfoList::iterator itt = deviceInfoList.begin(); itt != deviceInfoList.end(); ++itt) + // add the default device with m_deviceName = default + if(strDD == strDevName) { - CAEDeviceInfo devInfo = *itt; - if(devInfo.m_deviceName == strDD) - { - deviceInfoList.erase(itt); - deviceInfoList.insert(deviceInfoList.begin(), devInfo); - break; - } + deviceInfo.m_deviceName = std::string("default"); + deviceInfo.m_displayName = std::string("default"); + deviceInfo.m_displayNameExtra = std::string(""); + deviceInfoList.push_back(deviceInfo); } } @@ -677,10 +712,6 @@ void CAESinkDirectSound::CheckPlayStatus() bool CAESinkDirectSound::UpdateCacheStatus() { CSingleLock lock (m_runLock); - // TODO: Check to see if we may have cycled around since last time - unsigned int time = XbmcThreads::SystemClockMillis(); - if (time == m_LastCacheCheck) - return true; // Don't recalc more frequently than once/ms (that is our max resolution anyway) DWORD playCursor = 0, writeCursor = 0; HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions @@ -691,7 +722,6 @@ bool CAESinkDirectSound::UpdateCacheStatus() return false; } - m_LastCacheCheck = time; // Check the state of the ring buffer (P->O->W == underrun) // These are the logical situations that can occur // O: CurrentOffset W: WriteCursor P: PlayCursor @@ -723,7 +753,8 @@ bool CAESinkDirectSound::UpdateCacheStatus() return false; } } - else m_BufferTimeouts = 0; + else + m_BufferTimeouts = 0; // Calculate available space in the ring buffer if (playCursor == m_BufferOffset && m_BufferOffset == writeCursor) // Playback is stopped and we are all at the same place @@ -910,4 +941,4 @@ bool CAESinkDirectSound::SoftResume() { /* Return false to force re-init by engine */ return false; -}
\ No newline at end of file +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h index b0149dd13b..a6f2912a33 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.h @@ -39,10 +39,11 @@ public: virtual bool IsCompatible(const AEAudioFormat format, const std::string &device); virtual void Stop (); + virtual void Drain (); virtual double GetDelay (); virtual double GetCacheTime (); virtual double GetCacheTotal (); - virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio); + virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); virtual bool SoftSuspend (); virtual bool SoftResume (); static std::string GetDefaultDevice (); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkNULL.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkNULL.cpp index 254b9c9d98..7d26017b75 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkNULL.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkNULL.cpp @@ -99,7 +99,7 @@ double CAESinkNULL::GetCacheTotal() return m_sinkbuffer_sec_per_byte * (double)m_sinkbuffer_size; } -unsigned int CAESinkNULL::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) +unsigned int CAESinkNULL::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) { unsigned int max_frames = (m_sinkbuffer_size - m_sinkbuffer_level) / m_sink_frameSize; if (frames > max_frames) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkNULL.h b/xbmc/cores/AudioEngine/Sinks/AESinkNULL.h index ef771196f1..1e4a3b9f49 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkNULL.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkNULL.h @@ -38,7 +38,7 @@ public: virtual double GetDelay (); virtual double GetCacheTime (); virtual double GetCacheTotal (); - virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio); + virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); virtual void Drain (); static void EnumerateDevices(AEDeviceList &devices, bool passthrough); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp index 281088dc57..b201b7b0bb 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.cpp @@ -408,7 +408,7 @@ double CAESinkOSS::GetDelay() return (double)delay / (m_format.m_frameSize * m_format.m_sampleRate); } -unsigned int CAESinkOSS::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) +unsigned int CAESinkOSS::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) { int size = frames * m_format.m_frameSize; if (m_fd == -1) @@ -420,7 +420,7 @@ unsigned int CAESinkOSS::AddPackets(uint8_t *data, unsigned int frames, bool has int wrote = write(m_fd, data, size); if (wrote < 0) { - if(errno == EAGAIN || errno == EWOULDBLOCK) + if(!blocking && (errno == EAGAIN || errno == EWOULDBLOCK)) return 0; CLog::Log(LOGERROR, "CAESinkOSS::AddPackets - Failed to write"); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkOSS.h b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.h index f50f713cda..9e558d3881 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkOSS.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkOSS.h @@ -41,7 +41,7 @@ public: virtual double GetDelay (); virtual double GetCacheTime () { return 0.0; } /* FIXME */ virtual double GetCacheTotal () { return 0.0; } /* FIXME */ - virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio); + virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); virtual void Drain (); static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false); private: diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.cpp index 2cff0ed20e..aeceb6b554 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.cpp @@ -71,7 +71,7 @@ double CAESinkProfiler::GetDelay() return 0.0f; } -unsigned int CAESinkProfiler::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) +unsigned int CAESinkProfiler::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) { int64_t ts = CurrentHostCounter(); CLog::Log(LOGDEBUG, "CAESinkProfiler::AddPackets - latency %f ms", (float)(ts - m_ts) / 1000000.0f); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.h b/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.h index d9423fb612..a0b955b3e6 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkProfiler.h @@ -39,7 +39,7 @@ public: virtual double GetDelay (); virtual double GetCacheTime () { return 0.0; } virtual double GetCacheTotal () { return 0.0; } - virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio); + virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); virtual void Drain (); static void EnumerateDevices(AEDeviceList &devices, bool passthrough); private: diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp index 9af3f2e52e..38065cd16a 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp @@ -34,6 +34,7 @@ #include "../Utils/AEDeviceInfo.h" #include <Mmreg.h> #include <mmdeviceapi.h> +#include "utils/StringUtils.h" #pragma comment(lib, "Avrt.lib") @@ -190,7 +191,10 @@ CAESinkWASAPI::CAESinkWASAPI() : m_isDirty(false), m_uiBufferLen(0), m_avgTimeWaiting(50), - m_sinkLatency(0.0) + m_sinkLatency(0.0), + m_pBuffer(NULL), + m_bufferPtr(0), + m_hnsRequestedDuration(0) { m_channelLayout.Reset(); } @@ -206,6 +210,7 @@ bool CAESinkWASAPI::Initialize(AEAudioFormat &format, std::string &device) return false; m_device = device; + bool bdefault = false; /* Save requested format */ /* Clear returned format */ @@ -227,41 +232,47 @@ bool CAESinkWASAPI::Initialize(AEAudioFormat &format, std::string &device) hr = pEnumDevices->GetCount(&uiCount); EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint count failed.") - for (UINT i = 0; i < uiCount; i++) + if(StringUtils::EndsWith(device, std::string("default"))) + bdefault = true; + + if(!bdefault) { - IPropertyStore *pProperty = NULL; - PROPVARIANT varName; + for (UINT i = 0; i < uiCount; i++) + { + IPropertyStore *pProperty = NULL; + PROPVARIANT varName; - hr = pEnumDevices->Item(i, &m_pDevice); - EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of WASAPI endpoint failed.") + hr = pEnumDevices->Item(i, &m_pDevice); + EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of WASAPI endpoint failed.") - hr = m_pDevice->OpenPropertyStore(STGM_READ, &pProperty); - EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of WASAPI endpoint properties failed.") + hr = m_pDevice->OpenPropertyStore(STGM_READ, &pProperty); + EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of WASAPI endpoint properties failed.") - hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName); - if (FAILED(hr)) - { - CLog::Log(LOGERROR, __FUNCTION__": Retrieval of WASAPI endpoint GUID failed."); - SAFE_RELEASE(pProperty); - goto failed; - } + hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName); + if (FAILED(hr)) + { + CLog::Log(LOGERROR, __FUNCTION__": Retrieval of WASAPI endpoint GUID failed."); + SAFE_RELEASE(pProperty); + goto failed; + } - std::string strDevName = localWideToUtf(varName.pwszVal); + std::string strDevName = localWideToUtf(varName.pwszVal); - if (device == strDevName) - i = uiCount; - else - SAFE_RELEASE(m_pDevice); + if (device == strDevName) + i = uiCount; + else + SAFE_RELEASE(m_pDevice); - PropVariantClear(&varName); - SAFE_RELEASE(pProperty); + PropVariantClear(&varName); + SAFE_RELEASE(pProperty); + } } - SAFE_RELEASE(pEnumDevices); if (!m_pDevice) { - CLog::Log(LOGINFO, __FUNCTION__": Could not locate the device named \"%s\" in the list of WASAPI endpoint devices. Trying the default device...", device.c_str()); + if(!bdefault) + CLog::Log(LOGINFO, __FUNCTION__": Could not locate the device named \"%s\" in the list of WASAPI endpoint devices. Trying the default device...", device.c_str()); hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_pDevice); EXIT_ON_FAILURE(hr, __FUNCTION__": Could not retrieve the default WASAPI audio endpoint.") @@ -307,6 +318,14 @@ bool CAESinkWASAPI::Initialize(AEAudioFormat &format, std::string &device) m_initialized = true; m_isDirty = false; + // allow feeding less samples than buffer size + // if the device is opened exclusive and event driven, provided samples must match buffersize + // ActiveAE tries to align provided samples with buffer size but cannot guarantee (e.g. transcoding) + // this can be avoided by dropping the event mode which has not much benefit; SoftAE polls anyway + delete [] m_pBuffer; + m_pBuffer = new uint8_t[format.m_frames * format.m_frameSize]; + m_bufferPtr = 0; + return true; failed: @@ -351,6 +370,9 @@ void CAESinkWASAPI::Deinitialize() SAFE_RELEASE(m_pDevice); m_initialized = false; + + delete [] m_pBuffer; + m_bufferPtr = 0; } bool CAESinkWASAPI::IsCompatible(const AEAudioFormat format, const std::string &device) @@ -423,7 +445,7 @@ double CAESinkWASAPI::GetCacheTotal() return m_sinkLatency; } -unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio) +unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) { if (!m_initialized) return 0; @@ -438,7 +460,15 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool LARGE_INTEGER timerFreq; #endif - unsigned int NumFramesRequested = frames; + unsigned int NumFramesRequested = m_format.m_frames; + unsigned int FramesToCopy = std::min(m_format.m_frames - m_bufferPtr, frames); + if (m_bufferPtr != 0 || frames != m_format.m_frames) + { + memcpy(m_pBuffer+m_bufferPtr*m_format.m_frameSize, data, FramesToCopy*m_format.m_frameSize); + m_bufferPtr += FramesToCopy; + if (frames != m_format.m_frames) + return frames; + } if (!m_running) //first time called, pre-fill buffer then start audio client { @@ -488,10 +518,33 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool #endif /* Wait for Audio Driver to tell us it's got a buffer available */ - DWORD eventAudioCallback = WaitForSingleObject(m_needDataEvent, 0); + DWORD eventAudioCallback; + if(!blocking) + eventAudioCallback = WaitForSingleObject(m_needDataEvent, 0); + else + eventAudioCallback = WaitForSingleObject(m_needDataEvent, 1100); + + if (!blocking) + { + if(eventAudioCallback != WAIT_OBJECT_0) + return 0; + } + else + { + if(eventAudioCallback != WAIT_OBJECT_0 || !&buf) + { + /* Event handle timed out - flag sink as dirty for re-initializing */ + CLog::Log(LOGERROR, __FUNCTION__": Endpoint Buffer timed out"); + if (g_advancedSettings.m_streamSilence) + { + m_isDirty = true; //flag new device or re-init needed + Deinitialize(); + m_running = false; + return INT_MAX; + } + } + } - if (eventAudioCallback != WAIT_OBJECT_0) - return 0; if (!m_running) return 0; @@ -516,7 +569,8 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool #endif return INT_MAX; } - memcpy(buf, data, NumFramesRequested * m_format.m_frameSize); //fill buffer + memcpy(buf, m_bufferPtr == 0 ? data : m_pBuffer, NumFramesRequested * m_format.m_frameSize); //fill buffer + m_bufferPtr = 0; hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver if (FAILED(hr)) { @@ -526,7 +580,13 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool return INT_MAX; } - return NumFramesRequested; + if (FramesToCopy != frames) + { + m_bufferPtr = frames-FramesToCopy; + memcpy(m_pBuffer, data+FramesToCopy*m_format.m_frameSize, m_bufferPtr*m_format.m_frameSize); + } + + return frames; } bool CAESinkWASAPI::SoftSuspend() @@ -554,8 +614,11 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo { IMMDeviceEnumerator* pEnumerator = NULL; IMMDeviceCollection* pEnumDevices = NULL; + IMMDevice* pDefaultDevice = NULL; CAEDeviceInfo deviceInfo; CAEChannelInfo deviceChannels; + LPWSTR pwszID = NULL; + std::wstring wstrDDID; WAVEFORMATEXTENSIBLE wfxex = {0}; HRESULT hr; @@ -565,6 +628,18 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo UINT uiCount = 0; + // get the default audio endpoint + if(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDefaultDevice) == S_OK) + { + if(pDefaultDevice->GetId(&pwszID) == S_OK) + { + wstrDDID = pwszID; + CoTaskMemFree(pwszID); + } + SAFE_RELEASE(pDefaultDevice); + } + + // enumerate over all audio endpoints hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pEnumDevices); EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint enumeration failed.") @@ -836,9 +911,6 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo CLog::Log(LOGDEBUG, __FUNCTION__": Failed to activate device for passthrough capability testing."); } - SAFE_RELEASE(pDevice); - SAFE_RELEASE(pProperty); - deviceInfo.m_deviceName = strDevName; deviceInfo.m_displayName = strWinDevType.append(strFriendlyName); deviceInfo.m_displayNameExtra = std::string("WASAPI: ").append(strFriendlyName); @@ -847,6 +919,21 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo /* Store the device info */ deviceInfoList.push_back(deviceInfo); + + if(pDevice->GetId(&pwszID) == S_OK) + { + if(wstrDDID.compare(pwszID) == 0) + { + deviceInfo.m_deviceName = std::string("default"); + deviceInfo.m_displayName = std::string("default"); + deviceInfo.m_displayNameExtra = std::string(""); + deviceInfoList.push_back(deviceInfo); + } + CoTaskMemFree(pwszID); + } + + SAFE_RELEASE(pDevice); + SAFE_RELEASE(pProperty); } return; @@ -1095,6 +1182,8 @@ initialize: hr = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, audioSinkBufferDurationMsec, audioSinkBufferDurationMsec, &wfxex.Format, NULL); + m_hnsRequestedDuration = audioSinkBufferDurationMsec; + if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { /* WASAPI requires aligned buffer */ @@ -1224,3 +1313,25 @@ const char *CAESinkWASAPI::WASAPIErrToStr(HRESULT err) } return NULL; } + +void CAESinkWASAPI::Drain() +{ + if(!m_pAudioClient) + return; + + Sleep( (DWORD)(m_hnsRequestedDuration / 10000)); + + if (m_running) + { + try + { + m_pAudioClient->Stop(); //stop the audio output + m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position + } + catch (...) + { + CLog::Log(LOGDEBUG, __FUNCTION__, "Invalidated AudioClient - Releasing"); + } + } + m_running = false; +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h index df16682a40..39d62d534b 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h @@ -42,9 +42,10 @@ public: virtual double GetDelay (); virtual double GetCacheTime (); virtual double GetCacheTotal (); - virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio); + virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); virtual bool SoftSuspend (); virtual bool SoftResume (); + virtual void Drain (); static void EnumerateDevicesEx (AEDeviceInfoList &deviceInfoList, bool force = false); private: bool InitializeExclusive(AEAudioFormat &format); @@ -78,4 +79,8 @@ private: unsigned int m_uiBufferLen; /* wasapi endpoint buffer size, in frames */ double m_avgTimeWaiting; /* time between next buffer of data from SoftAE and driver call for data */ double m_sinkLatency; /* time in seconds of total duration of the two WASAPI buffers */ + + uint8_t *m_pBuffer; + int m_bufferPtr; + REFERENCE_TIME m_hnsRequestedDuration; }; diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp index 2b6e0cd5bb..3a5c78f443 100644 --- a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp +++ b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp @@ -82,26 +82,33 @@ const unsigned int CAEUtil::DataFormatToBits(const enum AEDataFormat dataFormat) static const unsigned int formats[AE_FMT_MAX] = { 8, /* U8 */ + 8, /* U8P */ 8, /* S8 */ 16, /* S16BE */ 16, /* S16LE */ 16, /* S16NE */ + 16, /* S16NEP */ 32, /* S32BE */ 32, /* S32LE */ 32, /* S32NE */ + 32, /* S32NEP */ 32, /* S24BE */ 32, /* S24LE */ 32, /* S24NE */ + 32, /* S24NEP */ 24, /* S24BE3 */ 24, /* S24LE3 */ 24, /* S24NE3 */ + 24, /* S24NE3P*/ sizeof(double) << 3, /* DOUBLE */ + sizeof(double) << 3, /* DOUBLEP */ sizeof(float ) << 3, /* FLOAT */ + sizeof(float ) << 3, /* FLOATP */ 16, /* AAC */ 16, /* AC3 */ @@ -123,26 +130,33 @@ const char* CAEUtil::DataFormatToStr(const enum AEDataFormat dataFormat) static const char *formats[AE_FMT_MAX] = { "AE_FMT_U8", + "AE_FMT_U8P", "AE_FMT_S8", "AE_FMT_S16BE", "AE_FMT_S16LE", "AE_FMT_S16NE", + "AE_FMT_S16NEP", "AE_FMT_S32BE", "AE_FMT_S32LE", "AE_FMT_S32NE", + "AE_FMT_S32NEP", "AE_FMT_S24BE4", "AE_FMT_S24LE4", "AE_FMT_S24NE4", /* S24 in 4 bytes */ + "AE_FMT_S24NE4P", "AE_FMT_S24BE3", "AE_FMT_S24LE3", "AE_FMT_S24NE3", /* S24 in 3 bytes */ + "AE_FMT_S24NE3P", "AE_FMT_DOUBLE", + "AE_FMT_DOUBLEP", "AE_FMT_FLOAT", + "AE_FMT_FLOATP", /* for passthrough streams and the like */ "AE_FMT_AAC", diff --git a/xbmc/cores/dvdplayer/DVDAudio.cpp b/xbmc/cores/dvdplayer/DVDAudio.cpp index c37d4c51ea..64044338ee 100644 --- a/xbmc/cores/dvdplayer/DVDAudio.cpp +++ b/xbmc/cores/dvdplayer/DVDAudio.cpp @@ -298,7 +298,7 @@ void CDVDAudio::Drain() Finish(); CSingleLock lock (m_critSection); if (m_pAudioStream) - m_pAudioStream->Drain(); + m_pAudioStream->Drain(true); } void CDVDAudio::RegisterAudioCallback(IAudioCallback* pCallback) diff --git a/xbmc/cores/paplayer/PAPlayer.cpp b/xbmc/cores/paplayer/PAPlayer.cpp index e4ba6100df..94a339397b 100644 --- a/xbmc/cores/paplayer/PAPlayer.cpp +++ b/xbmc/cores/paplayer/PAPlayer.cpp @@ -610,7 +610,7 @@ inline void PAPlayer::ProcessStreams(double &delay, double &buffer) /* unregister the audio callback */ si->m_stream->UnRegisterAudioCallback(); si->m_decoder.Destroy(); - si->m_stream->Drain(); + si->m_stream->Drain(false); m_finishing.push_back(si); return; } diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index d1d5538a95..2a13509f44 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -362,6 +362,7 @@ void CSettings::Uninitialize() // unregister setting option fillers m_settingsManager->UnregisterSettingOptionsFiller("audiocdactions"); m_settingsManager->UnregisterSettingOptionsFiller("audiocdencoders"); + m_settingsManager->UnregisterSettingOptionsFiller("aequalitylevels"); m_settingsManager->UnregisterSettingOptionsFiller("audiodevices"); m_settingsManager->UnregisterSettingOptionsFiller("audiodevicespassthrough"); m_settingsManager->UnregisterSettingOptionsFiller("audiooutputmodes"); @@ -645,7 +646,7 @@ void CSettings::InitializeDefaults() ((CSettingString*)m_settingsManager->GetSetting("audiooutput.audiodevice"))->SetDefault(defaultAudioDeviceName); ((CSettingString*)m_settingsManager->GetSetting("audiooutput.passthroughdevice"))->SetDefault(defaultAudioDeviceName); #endif -#else +#elif !defined(TARGET_WINDOWS) ((CSettingString*)m_settingsManager->GetSetting("audiooutput.audiodevice"))->SetDefault(CAEFactory::GetDefaultDevice(false)); ((CSettingString*)m_settingsManager->GetSetting("audiooutput.passthroughdevice"))->SetDefault(CAEFactory::GetDefaultDevice(true)); #endif @@ -666,6 +667,7 @@ void CSettings::InitializeOptionFillers() m_settingsManager->RegisterSettingOptionsFiller("audiocdactions", MEDIA_DETECT::CAutorun::SettingOptionAudioCdActionsFiller); m_settingsManager->RegisterSettingOptionsFiller("audiocdencoders", MEDIA_DETECT::CAutorun::SettingOptionAudioCdEncodersFiller); #endif + m_settingsManager->RegisterSettingOptionsFiller("aequalitylevels", CAEFactory::SettingOptionsAudioQualityLevelsFiller); m_settingsManager->RegisterSettingOptionsFiller("audiodevices", CAEFactory::SettingOptionsAudioDevicesFiller); m_settingsManager->RegisterSettingOptionsFiller("audiodevicespassthrough", CAEFactory::SettingOptionsAudioDevicesPassthroughFiller); m_settingsManager->RegisterSettingOptionsFiller("audiooutputmodes", CAEFactory::SettingOptionsAudioOutputModesFiller); @@ -766,6 +768,12 @@ void CSettings::InitializeConditions() if (g_application.IsStandAlone()) m_settingsManager->AddCondition("isstandalone"); + if (CAEFactory::SupportsDrain()) + m_settingsManager->AddCondition("audiosupportsdrain"); + + if(CAEFactory::SupportsQualitySetting()) + m_settingsManager->AddCondition("has_ae_quality_levels"); + // add more complex conditions m_settingsManager->AddCondition("addonhassettings", AddonHasSettings); m_settingsManager->AddCondition("checkmasterlock", CheckMasterLock); @@ -846,8 +854,20 @@ void CSettings::InitializeISettingCallbacks() m_settingsManager->RegisterCallback(&CDisplaySettings::Get(), settingSet); settingSet.clear(); + settingSet.insert("audiooutput.mode"); settingSet.insert("audiooutput.channels"); + settingSet.insert("audiooutput.processquality"); settingSet.insert("audiooutput.guisoundmode"); + settingSet.insert("audiooutput.stereoupmix"); + settingSet.insert("audiooutput.ac3passthrough"); + settingSet.insert("audiooutput.dtspassthrough"); + settingSet.insert("audiooutput.passthroughaac"); + settingSet.insert("audiooutput.truehdpassthrough"); + settingSet.insert("audiooutput.dtshdpassthrough"); + settingSet.insert("audiooutput.multichannellpcm"); + settingSet.insert("audiooutput.audiodevice"); + settingSet.insert("audiooutput.passthroughdevice"); + settingSet.insert("audiooutput.streamsilence"); settingSet.insert("lookandfeel.skin"); settingSet.insert("lookandfeel.skinsettings"); settingSet.insert("lookandfeel.font"); diff --git a/xbmc/utils/ActorProtocol.cpp b/xbmc/utils/ActorProtocol.cpp new file mode 100644 index 0000000000..cf4c26f21d --- /dev/null +++ b/xbmc/utils/ActorProtocol.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "ActorProtocol.h" + +using namespace Actor; + +void Message::Release() +{ + bool skip; + origin->Lock(); + skip = isSync ? !isSyncFini : false; + isSyncFini = true; + origin->Unlock(); + + if (skip) + return; + + // free data buffer + if (data != buffer) + delete [] data; + + // delete event in case of sync message + if (event) + delete event; + + origin->ReturnMessage(this); +} + +bool Message::Reply(int sig, void *data /* = NULL*/, int size /* = 0 */) +{ + if (!isSync) + { + if (isOut) + return origin->SendInMessage(sig, data, size); + else + return origin->SendOutMessage(sig, data, size); + } + + origin->Lock(); + + if (!isSyncTimeout) + { + Message *msg = origin->GetMessage(); + msg->signal = sig; + msg->isOut = !isOut; + replyMessage = msg; + if (data) + { + if (size > MSG_INTERNAL_BUFFER_SIZE) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + } + + origin->Unlock(); + + if (event) + event->Set(); + + return true; +} + +Protocol::~Protocol() +{ + Message *msg; + Purge(); + while (!freeMessageQueue.empty()) + { + msg = freeMessageQueue.front(); + freeMessageQueue.pop(); + delete msg; + } +} + +Message *Protocol::GetMessage() +{ + Message *msg; + + CSingleLock lock(criticalSection); + + if (!freeMessageQueue.empty()) + { + msg = freeMessageQueue.front(); + freeMessageQueue.pop(); + } + else + msg = new Message(); + + msg->isSync = false; + msg->isSyncFini = false; + msg->isSyncTimeout = false; + msg->event = NULL; + msg->data = NULL; + msg->payloadSize = 0; + msg->replyMessage = NULL; + msg->origin = this; + + return msg; +} + +void Protocol::ReturnMessage(Message *msg) +{ + CSingleLock lock(criticalSection); + + freeMessageQueue.push(msg); +} + +bool Protocol::SendOutMessage(int signal, void *data /* = NULL */, int size /* = 0 */, Message *outMsg /* = NULL */) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = true; + + if (data) + { + if (size > MSG_INTERNAL_BUFFER_SIZE) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + + { CSingleLock lock(criticalSection); + outMessages.push(msg); + } + containerOutEvent->Set(); + + return true; +} + +bool Protocol::SendInMessage(int signal, void *data /* = NULL */, int size /* = 0 */, Message *outMsg /* = NULL */) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = false; + + if (data) + { + if (size > MSG_INTERNAL_BUFFER_SIZE) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + + { CSingleLock lock(criticalSection); + inMessages.push(msg); + } + containerInEvent->Set(); + + return true; +} + + +bool Protocol::SendOutMessageSync(int signal, Message **retMsg, int timeout, void *data /* = NULL */, int size /* = 0 */) +{ + Message *msg = GetMessage(); + msg->isOut = true; + msg->isSync = true; + msg->event = new CEvent; + msg->event->Reset(); + SendOutMessage(signal, data, size, msg); + + if (!msg->event->WaitMSec(timeout)) + { + msg->origin->Lock(); + if (msg->replyMessage) + *retMsg = msg->replyMessage; + else + { + *retMsg = NULL; + msg->isSyncTimeout = true; + } + msg->origin->Unlock(); + } + else + *retMsg = msg->replyMessage; + + msg->Release(); + + if (*retMsg) + return true; + else + return false; +} + +bool Protocol::ReceiveOutMessage(Message **msg) +{ + CSingleLock lock(criticalSection); + + if (outMessages.empty() || outDefered) + return false; + + *msg = outMessages.front(); + outMessages.pop(); + + return true; +} + +bool Protocol::ReceiveInMessage(Message **msg) +{ + CSingleLock lock(criticalSection); + + if (inMessages.empty() || inDefered) + return false; + + *msg = inMessages.front(); + inMessages.pop(); + + return true; +} + + +void Protocol::Purge() +{ + Message *msg; + + while (ReceiveInMessage(&msg)) + msg->Release(); + + while (ReceiveOutMessage(&msg)) + msg->Release(); +} diff --git a/xbmc/utils/ActorProtocol.h b/xbmc/utils/ActorProtocol.h new file mode 100644 index 0000000000..8ef3359698 --- /dev/null +++ b/xbmc/utils/ActorProtocol.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include "threads/Thread.h" +#include "utils/log.h" +#include <queue> +#include "memory.h" + +#define MSG_INTERNAL_BUFFER_SIZE 32 + +namespace Actor +{ + +class Protocol; + +class Message +{ + friend class Protocol; +public: + int signal; + bool isSync; + bool isSyncFini; + bool isOut; + bool isSyncTimeout; + int payloadSize; + uint8_t buffer[MSG_INTERNAL_BUFFER_SIZE]; + uint8_t *data; + Message *replyMessage; + Protocol *origin; + CEvent *event; + + void Release(); + bool Reply(int sig, void *data = NULL, int size = 0); + +private: + Message() {isSync = false; data = NULL; event = NULL; replyMessage = NULL;}; +}; + +class Protocol +{ +public: + Protocol(std::string name, CEvent* inEvent, CEvent *outEvent) + : portName(name), inDefered(false), outDefered(false) {containerInEvent = inEvent; containerOutEvent = outEvent;}; + virtual ~Protocol(); + Message *GetMessage(); + void ReturnMessage(Message *msg); + bool SendOutMessage(int signal, void *data = NULL, int size = 0, Message *outMsg = NULL); + bool SendInMessage(int signal, void *data = NULL, int size = 0, Message *outMsg = NULL); + bool SendOutMessageSync(int signal, Message **retMsg, int timeout, void *data = NULL, int size = 0); + bool ReceiveOutMessage(Message **msg); + bool ReceiveInMessage(Message **msg); + void Purge(); + void DeferIn(bool value) {inDefered = value;}; + void DeferOut(bool value) {outDefered = value;}; + void Lock() {criticalSection.lock();}; + void Unlock() {criticalSection.unlock();}; + std::string portName; + +protected: + CEvent *containerInEvent, *containerOutEvent; + CCriticalSection criticalSection; + std::queue<Message*> outMessages; + std::queue<Message*> inMessages; + std::queue<Message*> freeMessageQueue; + bool inDefered, outDefered; +}; + +} diff --git a/xbmc/utils/Makefile.in b/xbmc/utils/Makefile.in index 694a9bde84..ca49be25a3 100644 --- a/xbmc/utils/Makefile.in +++ b/xbmc/utils/Makefile.in @@ -70,6 +70,7 @@ SRCS += Vector.cpp SRCS += Weather.cpp SRCS += XBMCTinyXML.cpp SRCS += XMLUtils.cpp +SRCS += ActorProtocol.cpp ifeq (@USE_OPENGLES@,1) SRCS += AMLUtils.cpp |