diff --git a/packages/mediacenter/xbmc/patches/xbmc-999.03-PR3976-pulse.patch b/packages/mediacenter/xbmc/patches/xbmc-999.03-PR3976-pulse.patch new file mode 100644 index 0000000000..5d33db2ebe --- /dev/null +++ b/packages/mediacenter/xbmc/patches/xbmc-999.03-PR3976-pulse.patch @@ -0,0 +1,1166 @@ +From cf70010716d4e46ec1e1329f2028779cfa898d2c Mon Sep 17 00:00:00 2001 +From: Tobias Arrskog +Date: Thu, 2 Jan 2014 12:07:49 +0100 +Subject: [PATCH 1/6] AESinkPULSE: Initial Implementation + +--- + xbmc/cores/AudioEngine/AEFactory.cpp | 34 +- + xbmc/cores/AudioEngine/AEFactory.h | 1 - + xbmc/cores/AudioEngine/AESinkFactory.cpp | 17 + + xbmc/cores/AudioEngine/Makefile.in | 4 +- + xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 730 +++++++++++++++++++++++++++ + xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h | 72 +++ + 6 files changed, 822 insertions(+), 36 deletions(-) + create mode 100644 xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp + create mode 100644 xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h + +diff --git a/xbmc/cores/AudioEngine/AEFactory.cpp b/xbmc/cores/AudioEngine/AEFactory.cpp +index b76d304..464dd5a 100644 +--- a/xbmc/cores/AudioEngine/AEFactory.cpp ++++ b/xbmc/cores/AudioEngine/AEFactory.cpp +@@ -29,10 +29,6 @@ + #include "Engines/ActiveAE/ActiveAE.h" + #endif + +-#if defined(HAS_PULSEAUDIO) +- #include "Engines/PulseAE/PulseAE.h" +-#endif +- + #include "guilib/LocalizeStrings.h" + #include "settings/lib/Setting.h" + #include "settings/Settings.h" +@@ -53,32 +49,9 @@ bool CAEFactory::LoadEngine() + + #if defined(TARGET_DARWIN) + return CAEFactory::LoadEngine(AE_ENGINE_COREAUDIO); ++#else ++ return CAEFactory::LoadEngine(AE_ENGINE_ACTIVE); + #endif +- +- std::string engine; +- if (getenv("AE_ENGINE")) +- { +- engine = (std::string)getenv("AE_ENGINE"); +- std::transform(engine.begin(), engine.end(), engine.begin(), ::toupper); +- +- #if defined(HAS_PULSEAUDIO) +- if (!loaded && engine == "PULSE") +- loaded = CAEFactory::LoadEngine(AE_ENGINE_PULSE); +- #endif +- +- if (!loaded && engine == "ACTIVE") +- loaded = CAEFactory::LoadEngine(AE_ENGINE_ACTIVE); +- } +- +-#if defined(HAS_PULSEAUDIO) +- if (!loaded) +- loaded = CAEFactory::LoadEngine(AE_ENGINE_PULSE); +-#endif +- +- if (!loaded) +- loaded = CAEFactory::LoadEngine(AE_ENGINE_ACTIVE); +- +- return loaded; + } + + bool CAEFactory::LoadEngine(enum AEEngine engine) +@@ -95,9 +68,6 @@ bool CAEFactory::LoadEngine(enum AEEngine engine) + #else + case AE_ENGINE_ACTIVE : AE = new ActiveAE::CActiveAE(); break; + #endif +-#if defined(HAS_PULSEAUDIO) +- case AE_ENGINE_PULSE : AE = new CPulseAE(); break; +-#endif + default: + return false; + } +diff --git a/xbmc/cores/AudioEngine/AEFactory.h b/xbmc/cores/AudioEngine/AEFactory.h +index cb5317e..9a340cc 100644 +--- a/xbmc/cores/AudioEngine/AEFactory.h ++++ b/xbmc/cores/AudioEngine/AEFactory.h +@@ -30,7 +30,6 @@ enum AEEngine + { + AE_ENGINE_NULL, + AE_ENGINE_COREAUDIO, +- AE_ENGINE_PULSE, + AE_ENGINE_ACTIVE, + AE_ENGINE_PIAUDIO + }; +diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp +index 7dc504b..8999085 100644 +--- a/xbmc/cores/AudioEngine/AESinkFactory.cpp ++++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp +@@ -31,6 +31,9 @@ + #if defined(HAS_ALSA) + #include "Sinks/AESinkALSA.h" + #endif ++ #if defined(HAS_PULSEAUDIO) ++ #include "Sinks/AESinkPULSE.h" ++ #endif + #include "Sinks/AESinkOSS.h" + #else + #pragma message("NOTICE: No audio sink for target platform. Audio output will not be available.") +@@ -63,6 +66,7 @@ void CAESinkFactory::ParseDevice(std::string &device, std::string &driver) + #if defined(HAS_ALSA) + driver == "ALSA" || + #endif ++ driver == "PULSE" || + driver == "OSS" || + #endif + driver == "PROFILER") +@@ -119,6 +123,11 @@ IAESink *CAESinkFactory::Create(std::string &device, AEAudioFormat &desiredForma + TRY_SINK(Pi) + + #elif defined(TARGET_LINUX) || defined(TARGET_FREEBSD) ++ #if defined(HAS_PULSEAUDIO) ++ if (driver.empty() || driver == "PULSE") ++ TRY_SINK(PULSE) ++ #endif ++ + #if defined(HAS_ALSA) + if (driver.empty() || driver == "ALSA") + TRY_SINK(ALSA) +@@ -154,6 +163,14 @@ void CAESinkFactory::EnumerateEx(AESinkInfoList &list, bool force) + #elif defined(TARGET_RASPBERRY_PI) + ENUMERATE_SINK(Pi, force); + #elif defined(TARGET_LINUX) || defined(TARGET_FREEBSD) ++ #if defined(HAS_PULSEAUDIO) ++ ENUMERATE_SINK(PULSE, force); ++ #endif ++ ++ if (!list.empty()) { ++ return; ++ } ++ + #if defined(HAS_ALSA) + ENUMERATE_SINK(ALSA, force); + #endif +diff --git a/xbmc/cores/AudioEngine/Makefile.in b/xbmc/cores/AudioEngine/Makefile.in +index 7f87410..3815a16 100644 +--- a/xbmc/cores/AudioEngine/Makefile.in ++++ b/xbmc/cores/AudioEngine/Makefile.in +@@ -54,9 +54,7 @@ else + SRCS += Sinks/AESinkALSA.cpp + SRCS += Sinks/AESinkOSS.cpp + ifeq (@USE_PULSE@,1) +-SRCS += Engines/PulseAE/PulseAE.cpp +-SRCS += Engines/PulseAE/PulseAEStream.cpp +-SRCS += Engines/PulseAE/PulseAESound.cpp ++SRCS += Sinks/AESinkPULSE.cpp + endif + endif + endif +diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +new file mode 100644 +index 0000000..916aaf6 +--- /dev/null ++++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +@@ -0,0 +1,730 @@ ++/* ++ * 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 ++ * . ++ * ++ */ ++#include "system.h" ++#ifdef HAS_PULSEAUDIO ++#include "AESinkPULSE.h" ++#include "utils/log.h" ++#include "Util.h" ++#include "guilib/LocalizeStrings.h" ++ ++using namespace std; ++ ++static const char *ContextStateToString(pa_context_state s) ++{ ++ switch (s) ++ { ++ case PA_CONTEXT_UNCONNECTED: ++ return "unconnected"; ++ case PA_CONTEXT_CONNECTING: ++ return "connecting"; ++ case PA_CONTEXT_AUTHORIZING: ++ return "authorizing"; ++ case PA_CONTEXT_SETTING_NAME: ++ return "setting name"; ++ case PA_CONTEXT_READY: ++ return "ready"; ++ case PA_CONTEXT_FAILED: ++ return "failed"; ++ case PA_CONTEXT_TERMINATED: ++ return "terminated"; ++ default: ++ return "none"; ++ } ++} ++ ++static const char *StreamStateToString(pa_stream_state s) ++{ ++ switch(s) ++ { ++ case PA_STREAM_UNCONNECTED: ++ return "unconnected"; ++ case PA_STREAM_CREATING: ++ return "creating"; ++ case PA_STREAM_READY: ++ return "ready"; ++ case PA_STREAM_FAILED: ++ return "failed"; ++ case PA_STREAM_TERMINATED: ++ return "terminated"; ++ default: ++ return "none"; ++ } ++} ++ ++static pa_sample_format AEFormatToPulseFormat(AEDataFormat format) ++{ ++ switch (format) ++ { ++ case AE_FMT_U8 : return PA_SAMPLE_U8; ++ case AE_FMT_S16LE : return PA_SAMPLE_S16LE; ++ case AE_FMT_S16BE : return PA_SAMPLE_S16BE; ++ case AE_FMT_S16NE : return PA_SAMPLE_S16NE; ++ case AE_FMT_S24LE3: return PA_SAMPLE_S24LE; ++ case AE_FMT_S24BE3: return PA_SAMPLE_S24BE; ++ case AE_FMT_S24NE3: return PA_SAMPLE_S24NE; ++ case AE_FMT_S24LE4: return PA_SAMPLE_S24_32LE; ++ case AE_FMT_S24BE4: return PA_SAMPLE_S24_32BE; ++ case AE_FMT_S24NE4: return PA_SAMPLE_S24_32NE; ++ case AE_FMT_S32BE : return PA_SAMPLE_S32BE; ++ case AE_FMT_S32LE : return PA_SAMPLE_S32LE; ++ case AE_FMT_S32NE : return PA_SAMPLE_S32NE; ++ case AE_FMT_FLOAT : return PA_SAMPLE_FLOAT32; ++ ++ case AE_FMT_AC3: ++ case AE_FMT_DTS: ++ case AE_FMT_EAC3: ++ return PA_SAMPLE_S16NE; ++ ++ default: ++ return PA_SAMPLE_INVALID; ++ } ++} ++ ++static pa_encoding AEFormatToPulseEncoding(AEDataFormat format) ++{ ++ switch (format) ++ { ++ case AE_FMT_AC3 : return PA_ENCODING_AC3_IEC61937; ++ case AE_FMT_DTS : return PA_ENCODING_DTS_IEC61937; ++ case AE_FMT_EAC3 : return PA_ENCODING_EAC3_IEC61937; ++ ++ default: ++ return PA_ENCODING_PCM; ++ } ++} ++ ++static AEDataFormat defaultDataFormats[] = { ++ AE_FMT_U8, ++ AE_FMT_S16LE, ++ AE_FMT_S16BE, ++ AE_FMT_S16NE, ++ AE_FMT_S24LE3, ++ AE_FMT_S24BE3, ++ AE_FMT_S24NE3, ++ AE_FMT_S24LE4, ++ AE_FMT_S24BE4, ++ AE_FMT_S24NE4, ++ AE_FMT_S32BE, ++ AE_FMT_S32LE, ++ AE_FMT_S32NE, ++ AE_FMT_FLOAT ++}; ++ ++static unsigned int defaultSampleRates[] = { ++ 5512, ++ 8000, ++ 11025, ++ 16000, ++ 22050, ++ 32000, ++ 44100, ++ 48000, ++ 64000, ++ 88200, ++ 96000, ++ 176400, ++ 192000, ++ 384000 ++}; ++ ++/* Static callback functions */ ++ ++static void ContextStateCallback(pa_context *c, void *userdata) ++{ ++ pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata; ++ switch (pa_context_get_state(c)) ++ { ++ case PA_CONTEXT_READY: ++ case PA_CONTEXT_TERMINATED: ++ case PA_CONTEXT_UNCONNECTED: ++ case PA_CONTEXT_CONNECTING: ++ case PA_CONTEXT_AUTHORIZING: ++ case PA_CONTEXT_SETTING_NAME: ++ case PA_CONTEXT_FAILED: ++ pa_threaded_mainloop_signal(m, 0); ++ break; ++ } ++} ++ ++static void StreamStateCallback(pa_stream *s, void *userdata) ++{ ++ pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata; ++ switch (pa_stream_get_state(s)) ++ { ++ case PA_STREAM_UNCONNECTED: ++ case PA_STREAM_CREATING: ++ case PA_STREAM_READY: ++ case PA_STREAM_FAILED: ++ case PA_STREAM_TERMINATED: ++ pa_threaded_mainloop_signal(m, 0); ++ break; ++ } ++} ++ ++static void StreamRequestCallback(pa_stream *s, size_t length, void *userdata) ++{ ++ pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata; ++ pa_threaded_mainloop_signal(m, 0); ++} ++ ++static void StreamLatencyUpdateCallback(pa_stream *s, void *userdata) ++{ ++ pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata; ++ pa_threaded_mainloop_signal(m, 0); ++} ++struct SinkInfoStruct ++{ ++ AEDeviceInfoList *list; ++ bool isHWDevice; ++ bool error; ++ pa_threaded_mainloop *mainloop; ++}; ++ ++static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) ++{ ++ SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata; ++ if(eol) ++ { ++ sinkStruct->error = true; ++ pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); ++ return; ++ } ++ if (i && i->flags && (i->flags & PA_SINK_HARDWARE)) ++ sinkStruct->isHWDevice = true; ++ pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); ++} ++ ++static AEChannel PAChannelToAEChannel(pa_channel_position_t channel) ++{ ++ AEChannel ae_channel; ++ switch (channel) ++ { ++ case PA_CHANNEL_POSITION_FRONT_LEFT: ++ ae_channel = AE_CH_FL; ++ break; ++ case PA_CHANNEL_POSITION_FRONT_RIGHT: ++ ae_channel = AE_CH_FR; ++ break; ++ case PA_CHANNEL_POSITION_FRONT_CENTER: ++ ae_channel = AE_CH_FC; ++ break; ++ case PA_CHANNEL_POSITION_SIDE_LEFT: ++ ae_channel = AE_CH_SL; ++ break; ++ case PA_CHANNEL_POSITION_SIDE_RIGHT: ++ ae_channel = AE_CH_FL; ++ break; ++ case PA_CHANNEL_POSITION_REAR_LEFT: ++ ae_channel = AE_CH_BL; ++ break; ++ case PA_CHANNEL_POSITION_REAR_RIGHT: ++ ae_channel = AE_CH_BR; ++ break; ++ case PA_CHANNEL_POSITION_LFE: ++ ae_channel = AE_CH_LFE; ++ break; ++ default: ++ ae_channel = AE_CH_NULL; ++ break; ++ } ++ return ae_channel; ++} ++ ++static CAEChannelInfo PAChannelToAEChannelMap(pa_channel_map channels) ++{ ++ CAEChannelInfo info; ++ info.Reset(); ++ for (unsigned int i=0; ierror = true; ++ pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); ++ return; ++ } ++ ++ if(sinkStruct && sinkStruct->list->empty()) ++ { ++ //add a default device first ++ CAEDeviceInfo defaultDevice; ++ defaultDevice.m_deviceName = std::string("Default"); ++ defaultDevice.m_displayName = std::string("Default"); ++ defaultDevice.m_displayNameExtra = std::string("PULSE: (Default)"); ++ defaultDevice.m_dataFormats.insert(defaultDevice.m_dataFormats.end(), defaultDataFormats, defaultDataFormats + sizeof(defaultDataFormats) / sizeof(defaultDataFormats[0])); ++ defaultDevice.m_channels = CAEChannelInfo(AE_CH_LAYOUT_2_0); ++ defaultDevice.m_sampleRates.assign(defaultSampleRates, defaultSampleRates + sizeof(defaultSampleRates) / sizeof(defaultSampleRates[0])); ++ defaultDevice.m_deviceType = AE_DEVTYPE_PCM; ++ sinkStruct->list->push_back(defaultDevice); ++ } ++ ++ if (i && i->name) ++ { ++ CAEDeviceInfo device; ++ ++ device.m_deviceName = string(i->name); ++ device.m_displayName = string(i->description); ++ device.m_displayNameExtra = std::string("PULSE: ").append(i->description); ++ unsigned int device_type = AE_DEVTYPE_PCM; //0 ++ ++ device.m_channels = PAChannelToAEChannelMap(i->channel_map); ++ device.m_sampleRates.assign(defaultSampleRates, defaultSampleRates + sizeof(defaultSampleRates) / sizeof(defaultSampleRates[0])); ++ ++ for (unsigned int j = 0; j < i->n_formats; j++) ++ { ++ switch(i->formats[j]->encoding) ++ { ++ case PA_ENCODING_AC3_IEC61937: ++ device.m_dataFormats.push_back(AE_FMT_AC3); ++ device_type |= AE_DEVTYPE_IEC958; ++ break; ++ case PA_ENCODING_DTS_IEC61937: ++ device.m_dataFormats.push_back(AE_FMT_DTS); ++ device_type |= AE_DEVTYPE_IEC958; ++ break; ++ case PA_ENCODING_EAC3_IEC61937: ++ device.m_dataFormats.push_back(AE_FMT_EAC3); ++ device_type |= AE_DEVTYPE_IEC958; ++ break; ++ case PA_ENCODING_PCM: ++ device.m_dataFormats.insert(device.m_dataFormats.end(), defaultDataFormats, defaultDataFormats + sizeof(defaultDataFormats) / sizeof(defaultDataFormats[0])); ++ break; ++ default: ++ break; ++ } ++ } ++ // passthrough is only working when device has Stereo channel config ++ if (device_type > AE_DEVTYPE_PCM && device.m_channels.Count() == 2) ++ device.m_deviceType = AE_DEVTYPE_IEC958; ++ else ++ device.m_deviceType = AE_DEVTYPE_PCM; ++ ++ CLog::Log(LOGDEBUG, "PulseAudio: Found %s with devicestring %s", device.m_displayName.c_str(), device.m_deviceName.c_str()); ++ sinkStruct->list->push_back(device); ++ } ++ pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); ++} ++ ++/* PulseAudio class memberfunctions*/ ++ ++ ++CAESinkPULSE::CAESinkPULSE() ++{ ++ m_IsAllocated = false; ++ m_BytesPerSecond = 0; ++ m_FrameSize = 0; ++ m_BufferSize = 0; ++ m_Channels = 0; ++ m_Stream = NULL; ++ m_Context = NULL; ++} ++ ++CAESinkPULSE::~CAESinkPULSE() ++{ ++ Deinitialize(); ++} ++ ++bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) ++{ ++ m_IsAllocated = false; ++ m_BytesPerSecond = 0; ++ m_FrameSize = 0; ++ m_BufferSize = 0; ++ m_Channels = 0; ++ m_Stream = NULL; ++ m_Context = NULL; ++ ++ if (!SetupContext(NULL, &m_Context, &m_MainLoop)) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Failed to create context"); ++ Deinitialize(); ++ return false; ++ } ++ ++ pa_threaded_mainloop_lock(m_MainLoop); ++ ++ m_Channels = format.m_channelLayout.Count(); ++ ++ struct pa_channel_map map; ++ /* TODO Add proper mapping */ ++ pa_channel_map_init_auto(&map, m_Channels, PA_CHANNEL_MAP_ALSA); ++ ++ pa_cvolume_reset(&m_Volume, m_Channels); ++ ++ pa_format_info *info[1]; ++ info[0] = pa_format_info_new(); ++ info[0]->encoding = AEFormatToPulseEncoding(format.m_dataFormat); ++ pa_format_info_set_sample_format(info[0], AEFormatToPulseFormat(format.m_dataFormat)); ++ pa_format_info_set_channels(info[0], m_Channels); ++ unsigned int samplerate = AE_IS_RAW(format.m_dataFormat) ? format.m_encodedRate : format.m_sampleRate; ++ pa_format_info_set_rate(info[0], samplerate); ++ ++ if (!pa_format_info_valid(info[0])) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Invalid format info"); ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ Deinitialize(); ++ return false; ++ } ++ ++ pa_sample_spec spec; ++ pa_format_info_to_sample_spec(info[0], &spec, &map); ++ if (!pa_sample_spec_valid(&spec)) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Invalid sample spec"); ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ Deinitialize(); ++ return false; ++ } ++ ++ m_BytesPerSecond = pa_bytes_per_second(&spec); ++ m_FrameSize = pa_frame_size(&spec); ++ ++ m_Stream = pa_stream_new_extended(m_Context, "audio stream", info, 1, NULL); ++ pa_format_info_free(info[0]); ++ ++ if (m_Stream == NULL) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Could not create a stream"); ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ Deinitialize(); ++ return false; ++ } ++ ++ pa_stream_set_state_callback(m_Stream, StreamStateCallback, m_MainLoop); ++ pa_stream_set_write_callback(m_Stream, StreamRequestCallback, m_MainLoop); ++ pa_stream_set_latency_update_callback(m_Stream, StreamLatencyUpdateCallback, m_MainLoop); ++ ++ bool isDefaultDevice = (device == "Default"); ++ ++ pa_buffer_attr buffer_attr; ++ SinkInfoStruct sinkStruct; ++ sinkStruct.mainloop = m_MainLoop; ++ sinkStruct.isHWDevice = false; ++ sinkStruct.error = false; ++ if (!isDefaultDevice) ++ WaitForOperation(pa_context_get_sink_info_by_name(m_Context, device.c_str(),SinkInfoCallback, &sinkStruct), m_MainLoop, "Get Sink Info"); ++ ++ if(sinkStruct.error) ++ { ++ // device not found, fallback to default device ++ CLog::Log(LOGERROR, "PulseAudio: Failed to open %s output device - will use default device", device.c_str()); ++ sinkStruct.error = false; ++ isDefaultDevice = true; ++ } ++ // 200ms max latency ++ // 50ms min packet size ++ if(sinkStruct.isHWDevice || isDefaultDevice) ++ { ++ unsigned int latency = m_BytesPerSecond / 5; ++ unsigned int process_time = latency / 4; ++ memset(&buffer_attr, 0, sizeof(buffer_attr)); ++ buffer_attr.tlength = (uint32_t) latency; ++ buffer_attr.minreq = (uint32_t) process_time; ++ buffer_attr.maxlength = (uint32_t) -1; ++ buffer_attr.prebuf = (uint32_t) -1; ++ buffer_attr.fragsize = (uint32_t) latency; ++ } ++ ++ if (pa_stream_connect_playback(m_Stream, isDefaultDevice ? NULL : device.c_str(), sinkStruct.isHWDevice ? &buffer_attr : NULL, ((pa_stream_flags)(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY)), &m_Volume, NULL) < 0) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Failed to connect stream to output"); ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ Deinitialize(); ++ return false; ++ } ++ ++ /* Wait until the stream is ready */ ++ do ++ { ++ pa_threaded_mainloop_wait(m_MainLoop); ++ CLog::Log(LOGDEBUG, "PulseAudio: Stream %s", StreamStateToString(pa_stream_get_state(m_Stream))); ++ } ++ while (pa_stream_get_state(m_Stream) != PA_STREAM_READY && pa_stream_get_state(m_Stream) != PA_STREAM_FAILED); ++ ++ if (pa_stream_get_state(m_Stream) == PA_STREAM_FAILED) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Waited for the stream but it failed"); ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ Deinitialize(); ++ return false; ++ } ++ ++ const pa_buffer_attr *a; ++ ++ if (!(a = pa_stream_get_buffer_attr(m_Stream))) ++ CLog::Log(LOGERROR, "PulseAudio: %s", pa_strerror(pa_context_errno(m_Context))); ++ else ++ { ++ unsigned int packetSize = a->minreq; ++ m_BufferSize = a->tlength; ++ ++ format.m_frames = packetSize / m_FrameSize; ++ } ++ ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ ++ m_IsAllocated = true; ++ format.m_frameSize = m_FrameSize; ++ format.m_frameSamples = format.m_frames * format.m_channelLayout.Count(); ++ m_format = format; ++ format.m_dataFormat = AE_IS_RAW(format.m_dataFormat) ? AE_FMT_S16NE : format.m_dataFormat; ++ ++ SetVolume(1.0); ++ Cork(false); ++ ++ return true; ++} ++ ++void CAESinkPULSE::Deinitialize() ++{ ++ m_IsAllocated = false; ++ ++ if (m_Stream) ++ Drain(); ++ ++ if (m_MainLoop) ++ pa_threaded_mainloop_stop(m_MainLoop); ++ ++ if (m_Stream) ++ { ++ pa_stream_disconnect(m_Stream); ++ pa_stream_unref(m_Stream); ++ m_Stream = NULL; ++ } ++ ++ if (m_Context) ++ { ++ pa_context_disconnect(m_Context); ++ pa_context_unref(m_Context); ++ m_Context = NULL; ++ } ++ ++ if (m_MainLoop) ++ { ++ pa_threaded_mainloop_free(m_MainLoop); ++ m_MainLoop = NULL; ++ } ++} ++ ++double CAESinkPULSE::GetDelay() ++{ ++ if (!m_IsAllocated) ++ return 0; ++ ++ pa_usec_t latency = (pa_usec_t) -1; ++ pa_threaded_mainloop_lock(m_MainLoop); ++ while (pa_stream_get_latency(m_Stream, &latency, NULL) < 0) ++ { ++ if (pa_context_errno(m_Context) != PA_ERR_NODATA) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: pa_stream_get_latency() failed"); ++ break; ++ } ++ /* Wait until latency data is available again */ ++ pa_threaded_mainloop_wait(m_MainLoop); ++ } ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ return latency / 1000000.0; ++} ++ ++double CAESinkPULSE::GetCacheTotal() ++{ ++ return (float)m_BufferSize / (float)m_BytesPerSecond; ++} ++ ++unsigned int CAESinkPULSE::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking) ++{ ++ if (!m_IsAllocated) ++ return frames; ++ ++ pa_threaded_mainloop_lock(m_MainLoop); ++ ++ unsigned int available = frames * m_format.m_frameSize; ++ unsigned int length = std::min((unsigned int)pa_stream_writable_size(m_Stream), available); ++ int error = pa_stream_write(m_Stream, data, length, NULL, 0, PA_SEEK_RELATIVE); ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ ++ if (error) ++ { ++ CLog::Log(LOGERROR, "CPulseAudioDirectSound::AddPackets - pa_stream_write failed\n"); ++ return 0; ++ } ++ ++ return (unsigned int)(length / m_format.m_frameSize); ++} ++ ++void CAESinkPULSE::Drain() ++{ ++ if (!m_IsAllocated) ++ return; ++ ++ pa_threaded_mainloop_lock(m_MainLoop); ++ WaitForOperation(pa_stream_drain(m_Stream, NULL, NULL), m_MainLoop, "Drain"); ++ pa_threaded_mainloop_unlock(m_MainLoop); ++} ++ ++void CAESinkPULSE::SetVolume(float volume) ++{ ++ if (m_IsAllocated) ++ { ++ pa_threaded_mainloop_lock(m_MainLoop); ++ pa_volume_t pavolume = pa_sw_volume_from_linear(volume); ++ if ( pavolume <= 0 ) ++ pa_cvolume_mute(&m_Volume, m_Channels); ++ else ++ pa_cvolume_set(&m_Volume, m_Channels, pavolume); ++ pa_operation *op = pa_context_set_sink_input_volume(m_Context, pa_stream_get_index(m_Stream), &m_Volume, NULL, NULL); ++ if (op == NULL) ++ CLog::Log(LOGERROR, "PulseAudio: Failed to set volume"); ++ else ++ pa_operation_unref(op); ++ ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ } ++} ++ ++void CAESinkPULSE::EnumerateDevicesEx(AEDeviceInfoList &list, bool force) ++{ ++ pa_context *context; ++ pa_threaded_mainloop *mainloop; ++ ++ if (!SetupContext(NULL, &context, &mainloop)) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Failed to create context"); ++ return; ++ } ++ ++ pa_threaded_mainloop_lock(mainloop); ++ ++ SinkInfoStruct sinkStruct; ++ sinkStruct.mainloop = mainloop; ++ sinkStruct.list = &list; ++ WaitForOperation(pa_context_get_sink_info_list(context, SinkInfoRequestCallback, &sinkStruct), mainloop, "EnumerateAudioSinks"); ++ ++ pa_threaded_mainloop_unlock(mainloop); ++ ++ if (mainloop) ++ pa_threaded_mainloop_stop(mainloop); ++ ++ if (context) ++ { ++ pa_context_disconnect(context); ++ pa_context_unref(context); ++ context = NULL; ++ } ++ ++ if (mainloop) ++ { ++ pa_threaded_mainloop_free(mainloop); ++ mainloop = NULL; ++ } ++} ++ ++bool CAESinkPULSE::Cork(bool cork) ++{ ++ pa_threaded_mainloop_lock(m_MainLoop); ++ ++ if (!WaitForOperation(pa_stream_cork(m_Stream, cork ? 1 : 0, NULL, NULL), m_MainLoop, cork ? "Pause" : "Resume")) ++ cork = !cork; ++ ++ pa_threaded_mainloop_unlock(m_MainLoop); ++ ++ return cork; ++} ++ ++inline bool CAESinkPULSE::WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry = "") ++{ ++ if (op == NULL) ++ return false; ++ ++ bool sucess = true; ++ ++ while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) ++ pa_threaded_mainloop_wait(mainloop); ++ ++ if (pa_operation_get_state(op) != PA_OPERATION_DONE) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: %s Operation failed", LogEntry); ++ sucess = false; ++ } ++ ++ pa_operation_unref(op); ++ return sucess; ++} ++ ++bool CAESinkPULSE::SetupContext(const char *host, pa_context **context, pa_threaded_mainloop **mainloop) ++{ ++ if ((*mainloop = pa_threaded_mainloop_new()) == NULL) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop"); ++ return false; ++ } ++ ++ if (((*context) = pa_context_new(pa_threaded_mainloop_get_api(*mainloop), "XBMC")) == NULL) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context"); ++ return false; ++ } ++ ++ pa_context_set_state_callback(*context, ContextStateCallback, *mainloop); ++ ++ if (pa_context_connect(*context, host, (pa_context_flags_t)0, NULL) < 0) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Failed to connect context"); ++ return false; ++ } ++ pa_threaded_mainloop_lock(*mainloop); ++ ++ if (pa_threaded_mainloop_start(*mainloop) < 0) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop"); ++ pa_threaded_mainloop_unlock(*mainloop); ++ return false; ++ } ++ ++ /* Wait until the context is ready */ ++ do ++ { ++ pa_threaded_mainloop_wait(*mainloop); ++ CLog::Log(LOGDEBUG, "PulseAudio: Context %s", ContextStateToString(pa_context_get_state(*context))); ++ } ++ while (pa_context_get_state(*context) != PA_CONTEXT_READY && pa_context_get_state(*context) != PA_CONTEXT_FAILED); ++ ++ if (pa_context_get_state(*context) == PA_CONTEXT_FAILED) ++ { ++ CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed"); ++ pa_threaded_mainloop_unlock(*mainloop); ++ return false; ++ } ++ ++ pa_threaded_mainloop_unlock(*mainloop); ++ return true; ++} ++#endif +diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h +new file mode 100644 +index 0000000..a0fbbc5 +--- /dev/null ++++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h +@@ -0,0 +1,72 @@ ++#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 ++ * . ++ * ++ */ ++ ++#include "system.h" ++ ++#include "cores/AudioEngine/Interfaces/AESink.h" ++#include "Utils/AEDeviceInfo.h" ++#include "Utils/AEUtil.h" ++#include ++ ++class CAESinkPULSE : public IAESink ++{ ++public: ++ virtual const char *GetName() { return "PULSE"; } ++ ++ CAESinkPULSE(); ++ virtual ~CAESinkPULSE(); ++ ++ virtual bool Initialize(AEAudioFormat &format, std::string &device); ++ virtual void Deinitialize(); ++ ++ virtual double GetDelay (); ++ virtual double GetCacheTotal (); ++ virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false); ++ virtual void Drain (); ++ ++ virtual bool HasVolume() { return true; }; ++ virtual void SetVolume(float volume); ++ ++ static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false); ++private: ++ bool Cork(bool cork); ++ static inline bool WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry); ++ static bool SetupContext(const char *host, pa_context **context, pa_threaded_mainloop **mainloop); ++ ++ bool m_IsAllocated; ++ ++ AEAudioFormat m_format; ++ unsigned int m_BytesPerSecond; ++ unsigned int m_BufferSize; ++ unsigned int m_Channels; ++ unsigned int m_FrameSize; ++/* ++ unsigned int m_uiSamplesPerSec; ++ unsigned int m_uiBitsPerSample; ++ unsigned int m_uiDataChannels; ++ unsigned int m_uiChannels; ++*/ ++ pa_stream *m_Stream; ++ pa_cvolume m_Volume; ++ ++ pa_context *m_Context; ++ pa_threaded_mainloop *m_MainLoop; ++}; +-- +1.8.5.1 + + +From 7c4a3a0e9e2e1fb0424e18459268bad64991600f Mon Sep 17 00:00:00 2001 +From: fritsch +Date: Mon, 6 Jan 2014 21:33:31 +0100 +Subject: [PATCH 2/6] AESinkPULSE: Revert error handling - did not work as + expected + +--- + xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 26 ++------------------------ + 1 file changed, 2 insertions(+), 24 deletions(-) + +diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +index 916aaf6..dca5e57 100644 +--- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp ++++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +@@ -193,22 +193,15 @@ struct SinkInfoStruct + { + AEDeviceInfoList *list; + bool isHWDevice; +- bool error; + pa_threaded_mainloop *mainloop; + }; + + static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) + { + SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata; +- if(eol) +- { +- sinkStruct->error = true; +- pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); +- return; +- } + if (i && i->flags && (i->flags & PA_SINK_HARDWARE)) + sinkStruct->isHWDevice = true; +- pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); ++ pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); + } + + static AEChannel PAChannelToAEChannel(pa_channel_position_t channel) +@@ -262,13 +255,6 @@ static void SinkInfoRequestCallback(pa_context *c, const pa_sink_info *i, int eo + { + + SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata; +- +- if(eol) +- { +- sinkStruct->error = true; +- pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); +- return; +- } + + if(sinkStruct && sinkStruct->list->empty()) + { +@@ -427,17 +413,9 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) + SinkInfoStruct sinkStruct; + sinkStruct.mainloop = m_MainLoop; + sinkStruct.isHWDevice = false; +- sinkStruct.error = false; + if (!isDefaultDevice) + WaitForOperation(pa_context_get_sink_info_by_name(m_Context, device.c_str(),SinkInfoCallback, &sinkStruct), m_MainLoop, "Get Sink Info"); +- +- if(sinkStruct.error) +- { +- // device not found, fallback to default device +- CLog::Log(LOGERROR, "PulseAudio: Failed to open %s output device - will use default device", device.c_str()); +- sinkStruct.error = false; +- isDefaultDevice = true; +- } ++ + // 200ms max latency + // 50ms min packet size + if(sinkStruct.isHWDevice || isDefaultDevice) +-- +1.8.5.1 + + +From c86957a9e4fc196624aa612f8418070e83c29956 Mon Sep 17 00:00:00 2001 +From: fritsch +Date: Mon, 6 Jan 2014 22:01:28 +0100 +Subject: [PATCH 3/6] AESinkPULSE: Add missing #ifdef to not enumerate Pulse + later + +--- + xbmc/cores/AudioEngine/AESinkFactory.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp +index 8999085..8427cdf 100644 +--- a/xbmc/cores/AudioEngine/AESinkFactory.cpp ++++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp +@@ -66,7 +66,9 @@ void CAESinkFactory::ParseDevice(std::string &device, std::string &driver) + #if defined(HAS_ALSA) + driver == "ALSA" || + #endif ++ #if defined(HAS_PULSEAUDIO) + driver == "PULSE" || ++ #endif + driver == "OSS" || + #endif + driver == "PROFILER") +-- +1.8.5.1 + + +From 0478f5deb2b815b172db10f7984b35fe68e4c445 Mon Sep 17 00:00:00 2001 +From: fritsch +Date: Mon, 6 Jan 2014 23:16:14 +0100 +Subject: [PATCH 4/6] AESinkPULSE: Block until something is available + +--- + xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +index dca5e57..d700b2e 100644 +--- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp ++++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +@@ -545,7 +545,13 @@ unsigned int CAESinkPULSE::AddPackets(uint8_t *data, unsigned int frames, bool h + pa_threaded_mainloop_lock(m_MainLoop); + + unsigned int available = frames * m_format.m_frameSize; +- unsigned int length = std::min((unsigned int)pa_stream_writable_size(m_Stream), available); ++ unsigned int length = 0; ++ while ((length = pa_stream_writable_size(m_Stream)) == 0) ++ pa_threaded_mainloop_wait(m_MainLoop); ++ ++ length = std::min((unsigned int)length, available); ++ //CLog::Log(LOGDEBUG, "CPulseAudioDirectSound::AddPackets Length %u Available %u", length, available); ++ + int error = pa_stream_write(m_Stream, data, length, NULL, 0, PA_SEEK_RELATIVE); + pa_threaded_mainloop_unlock(m_MainLoop); + +-- +1.8.5.1 + + +From fa79f58a9befcde42080919c92d6e535a6c819d0 Mon Sep 17 00:00:00 2001 +From: fritsch +Date: Tue, 7 Jan 2014 08:25:04 +0100 +Subject: [PATCH 5/6] Squash me later (Remove Log) + +--- + xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +index d700b2e..35ad796 100644 +--- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp ++++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +@@ -546,11 +546,11 @@ unsigned int CAESinkPULSE::AddPackets(uint8_t *data, unsigned int frames, bool h + + unsigned int available = frames * m_format.m_frameSize; + unsigned int length = 0; ++ // revisit me after Gotham - should use a callback for the write function + while ((length = pa_stream_writable_size(m_Stream)) == 0) + pa_threaded_mainloop_wait(m_MainLoop); + + length = std::min((unsigned int)length, available); +- //CLog::Log(LOGDEBUG, "CPulseAudioDirectSound::AddPackets Length %u Available %u", length, available); + + int error = pa_stream_write(m_Stream, data, length, NULL, 0, PA_SEEK_RELATIVE); + pa_threaded_mainloop_unlock(m_MainLoop); +-- +1.8.5.1 + + +From 5ca9f9189247a340877d113d400b18bad135398d Mon Sep 17 00:00:00 2001 +From: fritsch +Date: Tue, 7 Jan 2014 11:43:32 +0100 +Subject: [PATCH 6/6] AESinkPULSE: Be compatible to version 1.0 + +--- + xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +index 35ad796..a7ed63d 100644 +--- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp ++++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +@@ -380,7 +380,13 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) + } + + pa_sample_spec spec; +- pa_format_info_to_sample_spec(info[0], &spec, &map); ++ #if PA_CHECK_VERSION(2,0,0) ++ pa_format_info_to_sample_spec(info[0], &spec, &map); ++ #else ++ spec.rate = (AEFormatToPulseEncoding(format.m_dataFormat) == PA_ENCODING_EAC3_IEC61937) ? 4 * samplerate : samplerate; ++ spec.format = AEFormatToPulseFormat(format.m_dataFormat); ++ spec.channels = m_Channels; ++ #endif + if (!pa_sample_spec_valid(&spec)) + { + CLog::Log(LOGERROR, "PulseAudio: Invalid sample spec"); +-- +1.8.5.1 +