Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #3976 from fritsch/pulseaudio-pr

AESinkPULSE: Initial Implementation
  • Loading branch information...
commit 8a426d6628469ec08efed5bc281a5fb19538f387 2 parents 394a216 + 5d351e9
@fritsch fritsch authored
View
36 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"
@@ -49,36 +45,11 @@ IAE *CAEFactory::GetEngine()
bool CAEFactory::LoadEngine()
{
- bool loaded = false;
-
#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 +66,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;
}
View
1  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
};
View
19 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,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")
@@ -119,6 +125,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 +165,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
View
413 xbmc/cores/AudioEngine/Engines/PulseAE/PulseAE.cpp
@@ -1,413 +0,0 @@
-/*
- * 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"
-#ifdef HAS_PULSEAUDIO
-
-#include "PulseAE.h"
-#include "PulseAEStream.h"
-#include "PulseAESound.h"
-#include "Application.h"
-#include "threads/SingleLock.h"
-#include "utils/log.h"
-#include "utils/StringUtils.h"
-#include "settings/Settings.h"
-#include <pulse/pulseaudio.h>
-#include <pulse/simple.h>
-#include "guilib/LocalizeStrings.h"
-
-/* Static helpers */
-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";
- }
-}
-
-#if 0
-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";
- }
-}
-#endif
-
-CPulseAE::CPulseAE()
-{
- m_Context = NULL;
- m_MainLoop = NULL;
- m_muted = false;
- m_Volume = 0.0f;
-}
-
-CPulseAE::~CPulseAE()
-{
- if (m_Context)
- {
- pa_context_disconnect(m_Context);
- pa_context_unref(m_Context);
- m_Context = NULL;
- }
-
- if (m_MainLoop)
- {
- pa_threaded_mainloop_stop(m_MainLoop);
- pa_threaded_mainloop_free(m_MainLoop);
- }
-
-}
-
-bool CPulseAE::CanInit()
-{
- pa_simple *s;
- pa_sample_spec ss;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.channels = 2;
- ss.rate = 48000;
-
- //create a pulse client, if this returns NULL, pulseaudio isn't running
- s = pa_simple_new(NULL, "XBMC-test", PA_STREAM_PLAYBACK, NULL,"test", &ss, NULL, NULL, NULL);
-
- if (s)
- {
- pa_simple_free(s);
- return true;
- }
- else
- {
- return false;
- }
-}
-
-bool CPulseAE::Initialize()
-{
- m_Volume = g_application.GetVolume(false);
-
- if ((m_MainLoop = pa_threaded_mainloop_new()) == NULL)
- {
- CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop");
- return false;
- }
-
- if ((m_Context = pa_context_new(pa_threaded_mainloop_get_api(m_MainLoop), "XBMC")) == NULL)
- {
- CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context");
- return false;
- }
-
- pa_context_set_state_callback(m_Context, ContextStateCallback, m_MainLoop);
-
- if (pa_context_connect(m_Context, NULL, (pa_context_flags_t)0, NULL) < 0)
- {
- CLog::Log(LOGERROR, "PulseAudio: Failed to connect context");
- return false;
- }
-
- pa_threaded_mainloop_lock(m_MainLoop);
- if (pa_threaded_mainloop_start(m_MainLoop) < 0)
- {
- CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop");
- pa_threaded_mainloop_unlock(m_MainLoop);
- return false;
- }
-
- /* Wait until the context is ready */
- do
- {
- pa_threaded_mainloop_wait(m_MainLoop);
- CLog::Log(LOGDEBUG, "PulseAudio: Context %s", ContextStateToString(pa_context_get_state(m_Context)));
- }
- while (pa_context_get_state(m_Context) != PA_CONTEXT_READY && pa_context_get_state(m_Context) != PA_CONTEXT_FAILED);
-
- if (pa_context_get_state(m_Context) == PA_CONTEXT_FAILED)
- {
- CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed");
- pa_threaded_mainloop_unlock(m_MainLoop);
- return false;
- }
-
- pa_threaded_mainloop_unlock(m_MainLoop);
- return true;
-}
-
-bool CPulseAE::Suspend()
-{
- /* TODO: add implementation here. See SoftAE for example. Code should */
- /* release exclusive or hog mode and sleep each time packets would */
- /* normally be written to sink if m_isSuspended = true. False return */
- /* here will simply generate a debug log entry in externalplayer.cpp */
-
- return false;
-}
-
-bool CPulseAE::IsSuspended()
-{
- return false;
-}
-
-bool CPulseAE::Resume()
-{
- /* TODO: see comments in Suspend() above */
-
- return false;
-}
-
-void CPulseAE::OnSettingsChange(const std::string& setting)
-{
-}
-
-float CPulseAE::GetVolume()
-{
- return m_Volume;
-}
-
-void CPulseAE::SetVolume(float volume)
-{
- CSingleLock lock(m_lock);
- m_Volume = volume;
- std::list<CPulseAEStream*>::iterator itt;
- for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
- (*itt)->UpdateVolume(volume);
-}
-
-IAEStream *CPulseAE::MakeStream(enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int encodedSampleRate,CAEChannelInfo channelLayout, unsigned int options)
-{
- CPulseAEStream *st = new CPulseAEStream(m_Context, m_MainLoop, dataFormat, sampleRate, channelLayout, options);
-
- CSingleLock lock(m_lock);
- m_streams.push_back(st);
- return st;
-}
-
-void CPulseAE::RemoveStream(IAEStream *stream)
-{
- CSingleLock lock(m_lock);
- std::list<CPulseAEStream*>::iterator itt;
-
- m_streams.remove((CPulseAEStream *)stream);
-
- for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
- {
- if (*itt == stream)
- {
- m_streams.erase(itt);
- return;
- }
- }
-}
-
-IAEStream *CPulseAE::FreeStream(IAEStream *stream)
-{
- RemoveStream(stream);
-
- CPulseAEStream *istream = (CPulseAEStream *)stream;
-
- delete istream;
-
- return NULL;
-}
-
-IAESound *CPulseAE::MakeSound(const std::string& file)
-{
- CSingleLock lock(m_lock);
-
- CPulseAESound *sound = new CPulseAESound(file, m_Context, m_MainLoop);
- if (!sound->Initialize())
- {
- delete sound;
- return NULL;
- }
-
- m_sounds.push_back(sound);
- return sound;
-}
-
-void CPulseAE::FreeSound(IAESound *sound)
-{
- if (!sound)
- return;
-
- sound->Stop();
- CSingleLock lock(m_lock);
- for (std::list<CPulseAESound*>::iterator itt = m_sounds.begin(); itt != m_sounds.end(); ++itt)
- if (*itt == sound)
- {
- m_sounds.erase(itt);
- break;
- }
-
- delete (CPulseAESound*)sound;
-}
-
-void CPulseAE::GarbageCollect()
-{
- CSingleLock lock(m_lock);
- std::list<CPulseAEStream*>::iterator itt;
- for (itt = m_streams.begin(); itt != m_streams.end();)
- {
- if ((*itt)->IsDestroyed())
- {
- delete (*itt);
- itt = m_streams.erase(itt);
- continue;
- }
- ++itt;
- }
-}
-
-struct SinkInfoStruct
-{
- bool passthrough;
- AEDeviceList *list;
- pa_threaded_mainloop *mainloop;
-};
-
-static bool 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;
-}
-
-static void SinkInfo(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
-{
- SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata;
-
- if (i && i->name)
- {
- bool add = false;
- if(sinkStruct->passthrough)
- {
-#if PA_CHECK_VERSION(1,0,0)
- for(int idx = 0; idx < i->n_formats; ++idx)
- {
- if(!pa_format_info_is_pcm(i->formats[idx]))
- {
- add = true;
- break;
- }
- }
-#endif
- }
- else
- add = true;
-
- if (add)
- {
- CStdString desc = StringUtils::Format("%s (PulseAudio)", i->description);
- CStdString sink = StringUtils::Format("pulse:%s@default", i->name);
- sinkStruct->list->push_back(AEDevice(desc, sink));
- CLog::Log(LOGDEBUG, "PulseAudio: Found %s with devicestring %s", desc.c_str(), sink.c_str());
- }
- }
-
- pa_threaded_mainloop_signal(sinkStruct->mainloop, 0);
-}
-
-void CPulseAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
-{
- if (!m_MainLoop || ! m_Context)
- return;
-
- pa_threaded_mainloop_lock(m_MainLoop);
-
- SinkInfoStruct sinkStruct;
- sinkStruct.passthrough = passthrough;
- sinkStruct.mainloop = m_MainLoop;
- sinkStruct.list = &devices;
- CStdString def = StringUtils::Format("%s (PulseAudio)",g_localizeStrings.Get(409).c_str());
- devices.push_back(AEDevice(def, "pulse:default@default"));
- WaitForOperation(pa_context_get_sink_info_list(m_Context,
- SinkInfo, &sinkStruct), m_MainLoop, "EnumerateAudioSinks");
-
- pa_threaded_mainloop_unlock(m_MainLoop);
-}
-
-void CPulseAE::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;
- }
-}
-
-void CPulseAE::SetMute(const bool enabled)
-{
- CSingleLock lock(m_lock);
- std::list<CPulseAEStream*>::iterator itt;
- for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
- (*itt)->SetMute(enabled);
-
- m_muted = enabled;
-}
-
-#endif
View
90 xbmc/cores/AudioEngine/Engines/PulseAE/PulseAE.h
@@ -1,90 +0,0 @@
-#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"
-#ifdef HAS_PULSEAUDIO
-
-#include "Interfaces/AE.h"
-#include "PulseAEStream.h"
-#include "PulseAESound.h"
-#include "threads/CriticalSection.h"
-#include <list>
-
-struct pa_context;
-struct pa_threaded_mainloop;
-struct pa_stream;
-
-class CPulseAEStream;
-class CPulseAESound;
-class CPulseAE : public IAE
-{
-protected:
- friend class CAEFactory;
- CPulseAE();
- virtual ~CPulseAE();
-
-public:
- virtual bool CanInit();
- virtual bool Initialize ();
- virtual void OnSettingsChange(const std::string& setting);
-
- virtual bool Suspend(); /* Suspend output and de-initialize exclusive sink for external players and power savings */
-
- virtual bool IsSuspended();
- virtual bool Resume(); /* Resume ouput and re-initialize sink after Suspend() above */
-
- virtual float GetVolume();
- virtual void SetVolume(float volume);
-
- /* 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);
- void RemoveStream(IAEStream *stream);
-
- /* returns a new sound object */
- virtual IAESound *MakeSound(const std::string& file);
- virtual void FreeSound(IAESound *sound);
-
- /* free's sounds that have expired */
- virtual void GarbageCollect();
-
- virtual void SetMute(const bool enabled);
- virtual bool IsMuted() { return m_muted; }
- virtual void SetSoundMode(const int mode) {}
-#if PA_CHECK_VERSION(1,0,0)
- virtual bool SupportsRaw() { return true; }
-#endif
-
- virtual void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough);
-private:
- CCriticalSection m_lock;
- std::list<CPulseAEStream*> m_streams;
- std::list<CPulseAESound* > m_sounds;
-
- static void ContextStateCallback(pa_context *c, void *userdata);
-
- pa_context *m_Context;
- pa_threaded_mainloop *m_MainLoop;
- float m_Volume;
- bool m_muted;
-};
-
-#endif
View
227 xbmc/cores/AudioEngine/Engines/PulseAE/PulseAESound.cpp
@@ -1,227 +0,0 @@
-/*
- * 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"
-#ifdef HAS_PULSEAUDIO
-
-#include "PulseAESound.h"
-#include "cores/AudioEngine/AEFactory.h"
-#include "utils/log.h"
-#include "MathUtils.h"
-#include "StringUtils.h"
-
-CPulseAESound::CPulseAESound(const std::string &filename, pa_context *context, pa_threaded_mainloop *mainLoop) :
- IAESound (filename),
- m_pulseName (StringUtils::CreateUUID()),
- m_filename (filename),
- m_dataSent (0 ),
- m_context (context ),
- m_mainLoop (mainLoop),
- m_stream (NULL ),
- m_op (NULL ),
- m_maxVolume (0.0f ),
- m_volume (0.0f )
-{
- m_wavLoader.Load(filename);
-}
-
-CPulseAESound::~CPulseAESound()
-{
- DeInitialize();
-}
-
-bool CPulseAESound::Initialize()
-{
- /* we dont re-init the wav loader in PA as PA handles the samplerate */
- if (!m_wavLoader.IsValid())
- return false;
-
- m_sampleSpec.format = PA_SAMPLE_FLOAT32NE;
- m_sampleSpec.rate = m_wavLoader.GetSampleRate();
- m_sampleSpec.channels = m_wavLoader.GetChannelLayout().Count();
-
- if (!pa_sample_spec_valid(&m_sampleSpec))
- {
- CLog::Log(LOGERROR, "CPulseAESound::Initialize - Invalid sample spec");
- return false;
- }
-
- struct pa_channel_map map;
- map.channels = m_sampleSpec.channels;
- switch (map.channels)
- {
- case 1:
- map.map[0] = PA_CHANNEL_POSITION_MONO;
- break;
-
- case 2:
- map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
- map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
- break;
-
- default:
- CLog::Log(LOGERROR, "CPulseAESound::Initialize - We do not yet support multichannel sounds");
- return false;
- }
-
- m_maxVolume = CAEFactory::GetEngine()->GetVolume();
- m_volume = 1.0f;
- pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_volume * m_maxVolume));
- pa_cvolume_set(&m_chVolume, m_sampleSpec.channels, paVolume);
-
- pa_threaded_mainloop_lock(m_mainLoop);
- if ((m_stream = pa_stream_new(m_context, m_pulseName.c_str(), &m_sampleSpec, &map)) == NULL)
- {
- CLog::Log(LOGERROR, "CPulseAESound::Initialize - Could not create a stream");
- pa_threaded_mainloop_unlock(m_mainLoop);
- return false;
- }
-
- pa_stream_set_state_callback(m_stream, CPulseAESound::StreamStateCallback, this);
- pa_stream_set_write_callback(m_stream, CPulseAESound::StreamWriteCallback, this);
-
- if (pa_stream_connect_upload(m_stream, m_wavLoader.GetFrameCount() * pa_frame_size(&m_sampleSpec)) != 0)
- {
- CLog::Log(LOGERROR, "CPulseAESound::Initialize - Could not initialize the stream");
- pa_stream_disconnect(m_stream);
- m_stream = NULL;
- pa_threaded_mainloop_unlock(m_mainLoop);
- return false;
- }
-
- /* check if the stream failed */
- if (pa_stream_get_state(m_stream) == PA_STREAM_FAILED)
- {
- CLog::Log(LOGERROR, "CPulseAESound::Initialize - Waited for the stream but it failed");
- pa_stream_disconnect(m_stream);
- m_stream = NULL;
- pa_threaded_mainloop_unlock(m_mainLoop);
- return false;
- }
-
- pa_threaded_mainloop_unlock(m_mainLoop);
- return true;
-}
-
-void CPulseAESound::DeInitialize()
-{
- pa_threaded_mainloop_lock(m_mainLoop);
- pa_operation *op = pa_context_remove_sample(m_context, m_pulseName.c_str(), NULL, NULL);
- if (op)
- pa_operation_unref(op);
- pa_threaded_mainloop_unlock(m_mainLoop);
-
- m_wavLoader.DeInitialize();
-}
-
-void CPulseAESound::Play()
-{
- pa_threaded_mainloop_lock(m_mainLoop);
- /* we only keep the most recent operation as it is the only one needed for IsPlaying to function */
- if (m_op)
- pa_operation_unref(m_op);
- m_op = pa_context_play_sample(m_context, m_pulseName.c_str(), NULL, PA_VOLUME_INVALID, NULL, NULL);
- pa_threaded_mainloop_unlock(m_mainLoop);
-}
-
-void CPulseAESound::Stop()
-{
- if (m_op)
- {
- pa_operation_cancel(m_op);
- pa_operation_unref(m_op);
- m_op = NULL;
- }
-}
-
-bool CPulseAESound::IsPlaying()
-{
- if (m_op)
- {
- if (pa_operation_get_state(m_op) == PA_OPERATION_RUNNING)
- return true;
-
- pa_operation_unref(m_op);
- m_op = NULL;
- }
-
- return false;
-}
-
-void CPulseAESound::SetVolume(float volume)
-{
-}
-
-float CPulseAESound::GetVolume()
-{
- return 1.0f;
-}
-
-void CPulseAESound::StreamStateCallback(pa_stream *s, void *userdata)
-{
- CPulseAESound *sound = (CPulseAESound*)userdata;
- pa_stream_state_t state = pa_stream_get_state(s);
-
- switch (state)
- {
- case PA_STREAM_FAILED:
- CLog::Log(LOGERROR, "CPulseAESound::StreamStateCallback - %s", pa_strerror(pa_context_errno(sound->m_context)));
-
- case PA_STREAM_UNCONNECTED:
- case PA_STREAM_CREATING:
- case PA_STREAM_READY:
- case PA_STREAM_TERMINATED:
- pa_threaded_mainloop_signal(sound->m_mainLoop, 0);
- break;
- }
-}
-
-void CPulseAESound::StreamWriteCallback(pa_stream *s, size_t length, void *userdata)
-{
- CPulseAESound *sound = (CPulseAESound*)userdata;
- sound->Upload(length);
-}
-
-void CPulseAESound::Upload(size_t length)
-{
- float *samples = m_wavLoader.GetSamples();
- size_t left = (m_wavLoader.GetSampleCount() * sizeof(float)) - m_dataSent;
- size_t send = std::min(length, left);
-
- if (pa_stream_write(m_stream, samples + m_dataSent, send, 0, 0, PA_SEEK_RELATIVE) == 0)
- m_dataSent += send;
-
- /* if we have no more data disable the callback */
- if (left == send)
- {
- pa_stream_set_write_callback(m_stream, NULL, NULL);
- if (pa_stream_finish_upload(m_stream) != 0)
- {
- CLog::Log(LOGERROR, "CPulseAESound::Upload - Error occured");
- /* FIXME: Better error handling */
- }
-
- /* disconnect the stream as we dont need it anymore */
- pa_stream_disconnect(m_stream);
- m_stream = NULL;
- }
-}
-
-#endif
View
65 xbmc/cores/AudioEngine/Engines/PulseAE/PulseAESound.h
@@ -1,65 +0,0 @@
-#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"
-#ifdef HAS_PULSEAUDIO
-
-#include "cores/AudioEngine/Interfaces/AESound.h"
-#include "cores/AudioEngine/Utils/AEWAVLoader.h"
-#include <pulse/pulseaudio.h>
-
-class CPulseAESound : public IAESound
-{
-public:
- /* this should NEVER be called directly, use AE.GetSound */
- CPulseAESound(const std::string &filename, pa_context *context, pa_threaded_mainloop *mainLoop);
- virtual ~CPulseAESound();
-
- virtual void DeInitialize();
- virtual bool Initialize();
-
- virtual void Play();
- virtual void Stop();
- virtual bool IsPlaying();
-
- virtual void SetVolume(float volume);
- virtual float GetVolume();
-private:
- static void StreamStateCallback(pa_stream *s, void *userdata);
- static void StreamWriteCallback(pa_stream *s, size_t length, void *userdata);
- void Upload(size_t length);
-
- std::string m_pulseName;
- std::string m_filename;
- CAEWAVLoader m_wavLoader;
- size_t m_dataSent;
-
- pa_context *m_context;
- pa_threaded_mainloop *m_mainLoop;
- pa_stream *m_stream;
- pa_sample_spec m_sampleSpec;
- pa_cvolume m_chVolume;
- pa_operation *m_op;
-
- float m_maxVolume, m_volume;
-};
-
-#endif
View
670 xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.cpp
@@ -1,670 +0,0 @@
-/*
- * 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"
-#ifdef HAS_PULSEAUDIO
-
-#include "PulseAEStream.h"
-#include "cores/AudioEngine/AEFactory.h"
-#include "cores/AudioEngine/Utils/AEUtil.h"
-#include "utils/log.h"
-#include "utils/MathUtils.h"
-#include "threads/SingleLock.h"
-
-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";
- }
-}
-
-CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLoop, enum AEDataFormat format, unsigned int sampleRate, CAEChannelInfo channelLayout, unsigned int options) : m_fader(this)
-{
- ASSERT(channelLayout.Count());
- m_Destroyed = false;
- m_Initialized = false;
- m_Paused = false;
- m_ResumeCallback = false;
-
- m_Stream = NULL;
- m_Context = context;
- m_MainLoop = mainLoop;
-
- m_format = format;
- m_sampleRate = sampleRate;
- m_channelLayout = channelLayout;
- m_options = options;
-
- m_DrainOperation = NULL;
- m_slave = NULL;
-
- pa_threaded_mainloop_lock(m_MainLoop);
-
- m_SampleSpec.channels = channelLayout.Count();
- m_SampleSpec.rate = m_sampleRate;
-
- switch (m_format)
- {
- case AE_FMT_U8 : m_SampleSpec.format = PA_SAMPLE_U8; break;
- case AE_FMT_S16NE : m_SampleSpec.format = PA_SAMPLE_S16NE; break;
- case AE_FMT_S16LE : m_SampleSpec.format = PA_SAMPLE_S16LE; break;
- case AE_FMT_S16BE : m_SampleSpec.format = PA_SAMPLE_S16BE; break;
- case AE_FMT_S24NE3: m_SampleSpec.format = PA_SAMPLE_S24NE; break;
- case AE_FMT_S24NE4: m_SampleSpec.format = PA_SAMPLE_S24_32NE; break;
- case AE_FMT_S32NE : m_SampleSpec.format = PA_SAMPLE_S32NE; break;
- case AE_FMT_S32LE : m_SampleSpec.format = PA_SAMPLE_S32LE; break;
- case AE_FMT_S32BE : m_SampleSpec.format = PA_SAMPLE_S32BE; break;
- case AE_FMT_FLOAT : m_SampleSpec.format = PA_SAMPLE_FLOAT32NE; break;
-#if PA_CHECK_VERSION(1,0,0)
- case AE_FMT_DTS :
- case AE_FMT_EAC3 :
- case AE_FMT_AC3 : m_SampleSpec.format = PA_SAMPLE_S16NE; break;
-#endif
-
- default:
- CLog::Log(LOGERROR, "PulseAudio: Invalid format %i", format);
- pa_threaded_mainloop_unlock(m_MainLoop);
- m_format = AE_FMT_INVALID;
- return;
- }
-
- if (!pa_sample_spec_valid(&m_SampleSpec))
- {
- CLog::Log(LOGERROR, "PulseAudio: Invalid sample spec");
- pa_threaded_mainloop_unlock(m_MainLoop);
- Destroy();
- return /*false*/;
- }
-
- m_frameSize = pa_frame_size(&m_SampleSpec);
-
- struct pa_channel_map map;
- map.channels = m_channelLayout.Count();
-
- for (unsigned int ch = 0; ch < m_channelLayout.Count(); ++ch)
- switch(m_channelLayout[ch])
- {
- case AE_CH_NULL: break;
- case AE_CH_MAX : break;
- case AE_CH_RAW : break;
- case AE_CH_FL : map.map[ch] = PA_CHANNEL_POSITION_FRONT_LEFT ; break;
- case AE_CH_FR : map.map[ch] = PA_CHANNEL_POSITION_FRONT_RIGHT ; break;
- case AE_CH_FC : map.map[ch] = PA_CHANNEL_POSITION_FRONT_CENTER ; break;
- case AE_CH_BC : map.map[ch] = PA_CHANNEL_POSITION_REAR_CENTER ; break;
- case AE_CH_BL : map.map[ch] = PA_CHANNEL_POSITION_REAR_LEFT ; break;
- case AE_CH_BR : map.map[ch] = PA_CHANNEL_POSITION_REAR_RIGHT ; break;
- case AE_CH_LFE : map.map[ch] = PA_CHANNEL_POSITION_LFE ; break;
- case AE_CH_FLOC: map.map[ch] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ; break;
- case AE_CH_FROC: map.map[ch] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; break;
- case AE_CH_SL : map.map[ch] = PA_CHANNEL_POSITION_SIDE_LEFT ; break;
- case AE_CH_SR : map.map[ch] = PA_CHANNEL_POSITION_SIDE_RIGHT ; break;
- case AE_CH_TC : map.map[ch] = PA_CHANNEL_POSITION_TOP_CENTER ; break;
- case AE_CH_TFL : map.map[ch] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT ; break;
- case AE_CH_TFR : map.map[ch] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ; break;
- case AE_CH_TFC : map.map[ch] = PA_CHANNEL_POSITION_TOP_CENTER ; break;
- case AE_CH_TBL : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_LEFT ; break;
- case AE_CH_TBR : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT ; break;
- case AE_CH_TBC : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_CENTER ; break;
- default: break;
- }
-
- m_MaxVolume = CAEFactory::GetEngine()->GetVolume();
- m_Volume = 1.0f;
- pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume));
- pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume);
-
-#if PA_CHECK_VERSION(1,0,0)
- pa_format_info *info[1];
- info[0] = pa_format_info_new();
- switch(m_format)
- {
- case AE_FMT_DTS : info[0]->encoding = PA_ENCODING_DTS_IEC61937 ; break;
- case AE_FMT_EAC3: info[0]->encoding = PA_ENCODING_EAC3_IEC61937; break;
- case AE_FMT_AC3 : info[0]->encoding = PA_ENCODING_AC3_IEC61937 ; break;
- default: info[0]->encoding = PA_ENCODING_PCM ; break;
- }
- pa_format_info_set_rate (info[0], m_SampleSpec.rate);
- pa_format_info_set_channels (info[0], m_SampleSpec.channels);
- pa_format_info_set_channel_map (info[0], &map);
- pa_format_info_set_sample_format(info[0], m_SampleSpec.format);
- m_Stream = pa_stream_new_extended(m_Context, "audio stream", info, 1, NULL);
- pa_format_info_free(info[0]);
-#else
- m_Stream = pa_stream_new(m_Context, "audio stream", &m_SampleSpec, &map);
-#endif
-
- if (m_Stream == NULL)
- {
- CLog::Log(LOGERROR, "PulseAudio: Could not create a stream");
- pa_threaded_mainloop_unlock(m_MainLoop);
- Destroy();
- return /*false*/;
- }
-
- pa_stream_set_state_callback(m_Stream, CPulseAEStream::StreamStateCallback, this);
- pa_stream_set_write_callback(m_Stream, CPulseAEStream::StreamRequestCallback, this);
- pa_stream_set_latency_update_callback(m_Stream, CPulseAEStream::StreamLatencyUpdateCallback, this);
- pa_stream_set_underflow_callback(m_Stream, CPulseAEStream::StreamUnderflowCallback, this);
-
- int flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
- if (options && AESTREAM_FORCE_RESAMPLE)
- flags |= PA_STREAM_VARIABLE_RATE;
-
- if (pa_stream_connect_playback(m_Stream, NULL, NULL, (pa_stream_flags)flags, &m_ChVolume, NULL) < 0)
- {
- CLog::Log(LOGERROR, "PulseAudio: Failed to connect stream to output");
- pa_threaded_mainloop_unlock(m_MainLoop);
- Destroy();
- 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);
- Destroy();
- return /*false*/;
- }
-
- const pa_buffer_attr *streamBuffer;
- streamBuffer = pa_stream_get_buffer_attr(m_Stream);
- m_cacheSize = streamBuffer->maxlength;
-
- pa_threaded_mainloop_unlock(m_MainLoop);
-
- m_Initialized = true;
-
- CLog::Log(LOGINFO, "PulseAEStream::Initialized");
- CLog::Log(LOGINFO, " Sample Rate : %d", m_sampleRate);
- CLog::Log(LOGINFO, " Sample Format : %s", CAEUtil::DataFormatToStr(m_format));
- CLog::Log(LOGINFO, " Channel Count : %d", m_channelLayout.Count());
- CLog::Log(LOGINFO, " Channel Layout: %s", ((std::string)m_channelLayout).c_str());
- CLog::Log(LOGINFO, " Frame Size : %d", m_frameSize);
- CLog::Log(LOGINFO, " Cache Size : %d", m_cacheSize);
-
- Resume();
-
- return /*true*/;
-}
-
-CPulseAEStream::~CPulseAEStream()
-{
- Destroy();
-}
-
-/*
- this method may be called inside the pulse main loop,
- so be VERY careful with locking
-*/
-void CPulseAEStream::Destroy()
-{
- if (!m_Initialized)
- return;
-
- if (m_Destroyed)
- return;
-
- m_fader.StopThread(true);
-
- pa_threaded_mainloop_lock(m_MainLoop);
-
- if (m_DrainOperation)
- {
- pa_operation_cancel(m_DrainOperation);
- pa_operation_unref(m_DrainOperation);
- m_DrainOperation = NULL;
- }
-
- if (m_Stream)
- {
- pa_stream_set_state_callback(m_Stream, NULL, NULL);
- pa_stream_set_write_callback(m_Stream, NULL, NULL);
- pa_stream_set_latency_update_callback(m_Stream, NULL, NULL);
- pa_stream_set_underflow_callback(m_Stream, NULL, NULL);
- pa_stream_disconnect(m_Stream);
- pa_stream_unref(m_Stream);
- m_Stream = NULL;
- }
-
- /* signal CPulseAE to free us */
- m_Destroyed = true;
- m_Initialized = false;
-
- pa_threaded_mainloop_unlock(m_MainLoop);
-}
-
-unsigned int CPulseAEStream::GetSpace()
-{
- if (!m_Initialized)
- return 0;
-
- pa_threaded_mainloop_lock(m_MainLoop);
- unsigned int size = pa_stream_writable_size(m_Stream);
- pa_threaded_mainloop_unlock(m_MainLoop);
-
- if(size > m_cacheSize)
- m_cacheSize = size;
-
- return size;
-}
-
-unsigned int CPulseAEStream::AddData(void *data, unsigned int size)
-{
- if (!m_Initialized)
- return size;
-
- pa_threaded_mainloop_lock(m_MainLoop);
-
- int length = std::min((int)pa_stream_writable_size(m_Stream), (int)size);
- if (length == 0)
- {
- pa_threaded_mainloop_unlock(m_MainLoop);
- return 0;
- }
-
- int written = pa_stream_write(m_Stream, data, length, NULL, 0, PA_SEEK_RELATIVE);
- pa_threaded_mainloop_unlock(m_MainLoop);
-
- if (written < 0)
- {
- CLog::Log(LOGERROR, "PulseAudio: AddPackets - pa_stream_write failed\n");
- return 0;
- }
-
- return length;
-}
-
-double CPulseAEStream::GetDelay()
-{
- if (!m_Initialized)
- return 0.0;
-
- pa_usec_t latency = 0;
- pa_threaded_mainloop_lock(m_MainLoop);
-
- if (pa_stream_get_latency(m_Stream, &latency, NULL) == PA_ERR_NODATA)
- CLog::Log(LOGERROR, "PulseAudio: pa_stream_get_latency() failed");
-
- pa_threaded_mainloop_unlock(m_MainLoop);
- return (double)((double)latency / 1000000.0);
-}
-
-double CPulseAEStream::GetCacheTime()
-{
- if (!m_Initialized)
- return 0.0;
-
- return (double)(m_cacheSize - GetSpace()) / (double)(m_sampleRate * m_frameSize);
-}
-
-double CPulseAEStream::GetCacheTotal()
-{
- if (!m_Initialized)
- return 0.0;
-
- return (double)m_cacheSize / (double)(m_sampleRate * m_frameSize);
-}
-
-bool CPulseAEStream::IsPaused()
-{
- return m_Paused;
-}
-
-bool CPulseAEStream::IsDraining()
-{
- if (m_DrainOperation)
- {
- if (pa_operation_get_state(m_DrainOperation) == PA_OPERATION_RUNNING)
- return true;
-
- pa_operation_unref(m_DrainOperation);
- m_DrainOperation = NULL;
- }
- ProcessCallbacks();
- return false;
-}
-
-bool CPulseAEStream::IsDrained()
-{
- bool ret = (m_DrainOperation == NULL);
- ProcessCallbacks();
-
- return ret;
-}
-
-bool CPulseAEStream::IsDestroyed()
-{
- return m_Destroyed;
-}
-
-void CPulseAEStream::Pause()
-{
- if (m_Initialized)
- m_Paused = Cork(true);
-}
-
-void CPulseAEStream::Resume()
-{
- if (m_Initialized)
- m_Paused = Cork(false);
-}
-
-void CPulseAEStream::Drain(bool wait)
-{
- if (!m_Initialized)
- return;
-
- if (m_DrainOperation)
- return;
-
- pa_threaded_mainloop_lock(m_MainLoop);
- m_DrainOperation = pa_stream_drain(m_Stream, CPulseAEStream::StreamDrainComplete, this);
- pa_threaded_mainloop_unlock(m_MainLoop);
-}
-
-void CPulseAEStream::Flush()
-{
- if (!m_Initialized)
- return;
-
- pa_threaded_mainloop_lock(m_MainLoop);
- pa_operation_unref(pa_stream_flush(m_Stream, NULL, NULL));
- pa_threaded_mainloop_unlock(m_MainLoop);
-}
-
-float CPulseAEStream::GetVolume()
-{
- return m_Volume;
-}
-
-float CPulseAEStream::GetReplayGain()
-{
- return 0.0f;
-}
-
-void CPulseAEStream::SetVolume(float volume)
-{
- if (!m_Initialized)
- return;
-
- if (!pa_threaded_mainloop_in_thread(m_MainLoop))
- pa_threaded_mainloop_lock(m_MainLoop);
-
- if (volume > 0.f)
- {
- m_Volume = volume;
- pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume));
-
- pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume);
- }
- else
- pa_cvolume_mute(&m_ChVolume,m_SampleSpec.channels);
-
- pa_operation *op = pa_context_set_sink_input_volume(m_Context, pa_stream_get_index(m_Stream), &m_ChVolume, NULL, NULL);
-
- if (op == NULL)
- CLog::Log(LOGERROR, "PulseAudio: Failed to set volume");
- else
- pa_operation_unref(op);
-
- if (!pa_threaded_mainloop_in_thread(m_MainLoop))
- pa_threaded_mainloop_unlock(m_MainLoop);
-}
-
-void CPulseAEStream::UpdateVolume(float max)
-{
- if (!m_Initialized)
- return;
-
- m_MaxVolume = max;
- SetVolume(m_Volume);
-}
-
-void CPulseAEStream::SetMute(const bool mute)
-{
- if (mute)
- SetVolume(-1.f);
- else
- SetVolume(m_Volume);
-}
-
-void CPulseAEStream::SetReplayGain(float factor)
-{
-}
-
-const unsigned int CPulseAEStream::GetFrameSize() const
-{
- return m_frameSize;
-}
-
-const unsigned int CPulseAEStream::GetChannelCount() const
-{
- return m_channelLayout.Count();
-}
-
-const unsigned int CPulseAEStream::GetSampleRate() const
-{
- return m_sampleRate;
-}
-
-const enum AEDataFormat CPulseAEStream::GetDataFormat() const
-{
- return m_format;
-}
-
-double CPulseAEStream::GetResampleRatio()
-{
- return 1.0;
-}
-
-bool CPulseAEStream::SetResampleRatio(double ratio)
-{
- return false;
-}
-
-void CPulseAEStream::RegisterAudioCallback(IAudioCallback* pCallback)
-{
- m_AudioCallback = pCallback;
-}
-
-void CPulseAEStream::UnRegisterAudioCallback()
-{
- m_AudioCallback = NULL;
-}
-
-void CPulseAEStream::FadeVolume(float from, float target, unsigned int time)
-{
- if (!m_Initialized)
- return;
-
- m_fader.SetupFader(from, target, time);
-}
-
-bool CPulseAEStream::IsFading()
-{
- return m_fader.IsRunning();
-}
-
-void CPulseAEStream::StreamRequestCallback(pa_stream *s, size_t length, void *userdata)
-{
- CPulseAEStream *stream = (CPulseAEStream *)userdata;
- pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
-}
-
-void CPulseAEStream::StreamLatencyUpdateCallback(pa_stream *s, void *userdata)
-{
- CPulseAEStream *stream = (CPulseAEStream *)userdata;
- pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
-}
-
-void CPulseAEStream::StreamStateCallback(pa_stream *s, void *userdata)
-{
- CPulseAEStream *stream = (CPulseAEStream *)userdata;
- pa_stream_state_t state = pa_stream_get_state(s);
-
- switch (state)
- {
- case PA_STREAM_UNCONNECTED:
- case PA_STREAM_CREATING:
- case PA_STREAM_READY:
- case PA_STREAM_FAILED:
- case PA_STREAM_TERMINATED:
- pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
- break;
- }
-}
-
-void CPulseAEStream::StreamUnderflowCallback(pa_stream *s, void *userdata)
-{
- CPulseAEStream *stream = (CPulseAEStream *)userdata;
- CLog::Log(LOGWARNING, "PulseAudio: Stream underflow");
- pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
-}
-
-void CPulseAEStream::StreamDrainComplete(pa_stream *s, int success, void *userdata)
-{
- CPulseAEStream *stream = (CPulseAEStream *)userdata;
- if(stream)
- {
- stream->SetDrained();
- pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
- }
-}
-
-void CPulseAEStream::ProcessCallbacks()
-{
- if(m_ResumeCallback && m_slave)
- m_slave->Resume();
-
- m_ResumeCallback = false;
-}
-
-inline bool CPulseAEStream::WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry = "")
-{
- if (op == NULL)
- return false;
-
- bool sucess = true;
- ASSERT(!pa_threaded_mainloop_in_thread(mainloop));
-
- 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 CPulseAEStream::Cork(bool cork)
-{
- pa_threaded_mainloop_lock(m_MainLoop);
-
- pa_operation *op = pa_stream_cork(m_Stream, cork ? 1 : 0, NULL, NULL);
- if (!WaitForOperation(op, m_MainLoop, cork ? "Pause" : "Resume"))
- cork = !cork;
-
- pa_threaded_mainloop_unlock(m_MainLoop);
- return cork;
-}
-
-void CPulseAEStream::RegisterSlave(IAEStream *stream)
-{
- m_slave = stream;
-}
-
-CPulseAEStream::CLinearFader::CLinearFader(IAEStream *stream) : CThread("AEStream"), m_stream(stream)
-{
- m_from = 0;
- m_target = 0;
- m_time = 0;
- m_isRunning = false;
-}
-
-void CPulseAEStream::CLinearFader::SetupFader(float from, float target, unsigned int time)
-{
- StopThread(true);
-
- m_from = from;
- m_target = target;
- m_time = time;
-
- if (m_time > 0)
- Create();
- else
- m_stream->SetVolume(m_target);
-}
-
-void CPulseAEStream::CLinearFader::Process()
-{
- if (m_stream == NULL)
- return;
-
- m_isRunning = true;
- m_stream->SetVolume(m_from);
- float k = m_target - m_from;
-
- unsigned int begin = XbmcThreads::SystemClockMillis();
- unsigned int end = begin + m_time;
- unsigned int current = begin;
- unsigned int step = std::max(1u, m_time / 100);
-
- do
- {
- float x = ((float)current - (float)begin) / (float)m_time;
-
- m_stream->SetVolume(m_from + k * x);
- usleep(step * 1000);
- current = XbmcThreads::SystemClockMillis();
- } while (current <= end && !m_bStop);
-
- m_stream->SetVolume(m_target);
- m_isRunning = false;
-}
-
-bool CPulseAEStream::CLinearFader::IsRunning()
-{
- return !m_isRunning;
-}
-#endif
View
143 xbmc/cores/AudioEngine/Engines/PulseAE/PulseAEStream.h
@@ -1,143 +0,0 @@
-#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/>.
- *
- */
-
-#ifdef HAS_PULSEAUDIO
-
-#include "cores/AudioEngine/Interfaces/AEStream.h"
-#include "threads/Thread.h"
-#include <pulse/pulseaudio.h>
-
-class CPulseAEStream : public IAEStream
-{
-public:
- /* this should NEVER be called directly, use AE.GetStream */
- CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLoop, enum AEDataFormat format, unsigned int sampleRate, CAEChannelInfo channelLayout, unsigned int options);
- virtual ~CPulseAEStream();
-
- virtual void Destroy();
-
- virtual unsigned int GetSpace();
- virtual unsigned int AddData(void *data, unsigned int size);
- virtual double GetDelay();
- virtual double GetCacheTime ();
- virtual double GetCacheTotal();
-
- virtual bool IsPaused ();
- virtual bool IsDraining ();
- virtual bool IsDrained ();
- virtual bool IsDestroyed ();
- virtual bool IsBuffering() { return false; }
-
- virtual void Pause ();
- virtual void Resume ();
- virtual void Drain (bool wait);
- virtual void Flush ();
-
- virtual float GetVolume ();
- virtual float GetReplayGain();
- virtual float GetAmplification() { return 1.0f; }
- virtual void SetVolume (float volume);
- virtual void SetReplayGain(float factor);
- virtual void SetAmplification(float amplify){}
- void SetMute(const bool muted);
-
- virtual const unsigned int GetFrameSize () const;
- virtual const unsigned int GetChannelCount() const;
- virtual const unsigned int GetSampleRate () const;
- virtual const enum AEDataFormat GetDataFormat () const;
- virtual const unsigned int GetEncodedSampleRate() const { return GetSampleRate(); }
-
- /* for dynamic sample rate changes (smoothvideo) */
- virtual double GetResampleRatio();
- virtual bool SetResampleRatio(double ratio);
-
- /* vizualization callback register function */
- virtual void RegisterAudioCallback(IAudioCallback* pCallback);
- virtual void UnRegisterAudioCallback();
-
- virtual void FadeVolume(float from, float target, unsigned int time);
- virtual bool IsFading();
-
- /* trigger the stream to update its volume relative to AE */
- void UpdateVolume(float max);
-
- /* used to prepare a stream for resume */
- void SetDrained() { m_ResumeCallback = true; };
-
- /* Process the Resume of streams */
- void ProcessCallbacks();
-
- virtual void RegisterSlave(IAEStream *stream);
-private:
- static void StreamRequestCallback(pa_stream *s, size_t length, void *userdata);
- static void StreamLatencyUpdateCallback(pa_stream *s, void *userdata);
- static void StreamStateCallback(pa_stream *s, void *userdata);
- static void StreamUnderflowCallback(pa_stream *s, void *userdata);
- static void StreamDrainComplete(pa_stream *s, int success, void *userdata);
-
- static inline bool WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry);
- bool Cork(bool cork);
-
- bool m_Destroyed;
- bool m_Initialized;
- bool m_Paused;
- bool m_ResumeCallback;
-
- pa_stream *m_Stream;
- pa_sample_spec m_SampleSpec;
-
- float m_MaxVolume;
- float m_Volume;
- pa_cvolume m_ChVolume;
-
- pa_context *m_Context;
- pa_threaded_mainloop *m_MainLoop;
-
- IAudioCallback* m_AudioCallback;
-
- enum AEDataFormat m_format;
- unsigned int m_sampleRate;
- CAEChannelInfo m_channelLayout;
- unsigned int m_options;
- unsigned int m_frameSize;
- unsigned int m_cacheSize;
-
- pa_operation *m_DrainOperation;
- IAEStream *m_slave;
-
- class CLinearFader : public CThread
- {
- public:
- CLinearFader(IAEStream *stream);
- void SetupFader(float from, float target, unsigned int time);
- bool IsRunning();
- protected:
- virtual void Process();
- private:
- IAEStream *m_stream;
- float m_from;
- float m_target;
- unsigned int m_time;
- volatile bool m_isRunning;
- } m_fader;
-};
-
-#endif
View
4 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
View
777 xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp
@@ -0,0 +1,777 @@
+/*
+ * 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"
+#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;
+ pa_threaded_mainloop *mainloop;
+};
+
+static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
+{
+ SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata;
+ 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_LFE: ae_channel = AE_CH_LFE; 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_FRONT_LEFT_OF_CENTER: ae_channel = AE_CH_FLOC; break;
+ case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: ae_channel = AE_CH_FROC; break;
+ case PA_CHANNEL_POSITION_REAR_CENTER: ae_channel = AE_CH_BC; break;
+ case PA_CHANNEL_POSITION_SIDE_LEFT: ae_channel = AE_CH_SL; break;
+ case PA_CHANNEL_POSITION_SIDE_RIGHT: ae_channel = AE_CH_SR; break;
+ case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: ae_channel = AE_CH_TFL; break;
+ case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: ae_channel = AE_CH_TFR; break;
+ case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: ae_channel = AE_CH_TFC; break;
+ case PA_CHANNEL_POSITION_TOP_CENTER: ae_channel = AE_CH_TC; break;
+ case PA_CHANNEL_POSITION_TOP_REAR_LEFT: ae_channel = AE_CH_TBL; break;
+ case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: ae_channel = AE_CH_TBR; break;
+ case PA_CHANNEL_POSITION_TOP_REAR_CENTER: ae_channel = AE_CH_TBC; break;
+ default: ae_channel = AE_CH_NULL; break;
+ }
+ return ae_channel;
+}
+
+static pa_channel_position_t AEChannelToPAChannel(AEChannel ae_channel)
+{
+ pa_channel_position_t pa_channel;
+ switch (ae_channel)
+ {
+ case AE_CH_FL: pa_channel = PA_CHANNEL_POSITION_FRONT_LEFT; break;
+ case AE_CH_FR: pa_channel = PA_CHANNEL_POSITION_FRONT_RIGHT; break;
+ case AE_CH_FC: pa_channel = PA_CHANNEL_POSITION_FRONT_CENTER; break;
+ case AE_CH_LFE: pa_channel = PA_CHANNEL_POSITION_LFE; break;
+ case AE_CH_BL: pa_channel = PA_CHANNEL_POSITION_REAR_LEFT; break;
+ case AE_CH_BR: pa_channel = PA_CHANNEL_POSITION_REAR_RIGHT; break;
+ case AE_CH_FLOC: pa_channel = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; break;
+ case AE_CH_FROC: pa_channel = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; break;
+ case AE_CH_BC: pa_channel = PA_CHANNEL_POSITION_REAR_CENTER; break;
+ case AE_CH_SL: pa_channel = PA_CHANNEL_POSITION_SIDE_LEFT; break;
+ case AE_CH_SR: pa_channel = PA_CHANNEL_POSITION_SIDE_RIGHT; break;
+ case AE_CH_TFL: pa_channel = PA_CHANNEL_POSITION_TOP_FRONT_LEFT; break;
+ case AE_CH_TFR: pa_channel = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; break;
+ case AE_CH_TFC: pa_channel = PA_CHANNEL_POSITION_TOP_FRONT_CENTER; break;
+ case AE_CH_TC: pa_channel = PA_CHANNEL_POSITION_TOP_CENTER; break;
+ case AE_CH_TBL: pa_channel = PA_CHANNEL_POSITION_TOP_REAR_LEFT; break;
+ case AE_CH_TBR: pa_channel = PA_CHANNEL_POSITION_TOP_REAR_RIGHT; break;
+ case AE_CH_TBC: pa_channel = PA_CHANNEL_POSITION_TOP_REAR_CENTER; break;
+ default: pa_channel = PA_CHANNEL_POSITION_INVALID; break;
+ }
+ return pa_channel;
+}
+
+static pa_channel_map AEChannelMapToPAChannel(CAEChannelInfo info)
+{
+ pa_channel_map map;
+ pa_channel_map_init(&map);
+ pa_channel_position_t pos;
+ for (unsigned int i = 0; i < info.Count(); ++i)
+ {
+ pos = AEChannelToPAChannel(info[i]);
+ if(pos != PA_CHANNEL_POSITION_INVALID)
+ map.channels++;
+ }
+ return map;
+}
+
+static CAEChannelInfo PAChannelToAEChannelMap(pa_channel_map channels)
+{
+ CAEChannelInfo info;
+ AEChannel ch;
+ info.Reset();
+ for (unsigned int i=0; i<channels.channels; i++)
+ {
+ ch = PAChannelToAEChannel(channels.map[i]);
+ if(ch != AE_CH_NULL)
+ info += ch;
+ }
+ return info;
+}
+
+static void SinkInfoRequestCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
+{
+
+ SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata;
+
+ 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("Default Output Device (PULSEAUDIO)");
+ 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);
+ if (i->active_port && i->active_port->description)
+ device.m_displayNameExtra = std::string((i->active_port->description)).append(" (PULSEAUDIO)");
+ else
+ device.m_displayNameExtra = std::string((i->description)).append(" (PULSEAUDIO)");
+ 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_MainLoop = NULL;
+ m_BytesPerSecond = 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_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);
+
+ struct pa_channel_map map;
+ pa_channel_map_init(&map);
+
+ bool passthrough = AE_IS_RAW(format.m_dataFormat);
+
+ if(passthrough)
+ {
+ map.channels = 2;
+ format.m_channelLayout = AE_CH_LAYOUT_2_0;
+ }
+ else
+ {
+ map = AEChannelMapToPAChannel(format.m_channelLayout);
+ // if count has changed we need to fit the AE Map
+ if(map.channels != format.m_channelLayout.Count())
+ format.m_channelLayout = PAChannelToAEChannelMap(map);
+ }
+ m_Channels = format.m_channelLayout.Count();
+
+ 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);
+ if(!passthrough)
+ pa_format_info_set_sample_format(info[0], AEFormatToPulseFormat(format.m_dataFormat));
+ pa_format_info_set_channels(info[0], m_Channels);
+ unsigned int samplerate = passthrough ? 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;
+ #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");
+ pa_threaded_mainloop_unlock(m_MainLoop);
+ Deinitialize();
+ return false;
+ }
+
+ m_BytesPerSecond = pa_bytes_per_second(&spec);
+ unsigned int 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;
+ if (!isDefaultDevice)
+ WaitForOperation(pa_context_get_sink_info_by_name(m_Context, device.c_str(),SinkInfoCallback, &sinkStruct), m_MainLoop, "Get Sink Info");
+
+ // 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 / frameSize;
+ }
+
+ pa_threaded_mainloop_unlock(m_MainLoop);
+
+ m_IsAllocated = true;
+ format.m_frameSize = frameSize;
+ format.m_frameSamples = format.m_frames * format.m_channelLayout.Count();
+ m_format = format;
+ format.m_dataFormat = passthrough ? AE_FMT_S16NE : format.m_dataFormat;
+
+ Pause(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;
+
+ int error = 0;
+ pa_usec_t latency = (pa_usec_t) -1;
+ pa_threaded_mainloop_lock(m_MainLoop);
+ if ((error = pa_stream_get_latency(m_Stream, &latency, NULL)) < 0)
+ {
+ if (error == -PA_ERR_NODATA)
+ {
+ WaitForOperation(pa_stream_update_timing_info(m_Stream, NULL,NULL), m_MainLoop, "Update Timing Information");
+ if ((error = pa_stream_get_latency(m_Stream, &latency, NULL)) < 0)
+ {
+ CLog::Log(LOGDEBUG, "GetDelay - Failed to get Latency %d", error);
+ }
+ }
+ }
+ if (error < 0 )
+ latency = (pa_usec_t) 0;
+
+ 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 = 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);
+
+ 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::Pause(bool pause)
+{
+ pa_threaded_mainloop_lock(m_MainLoop);
+
+ if (!WaitForOperation(pa_stream_cork(m_Stream, pause ? 1 : 0, NULL, NULL), m_MainLoop, pause ? "Pause" : "Resume"))
+ pause = !pause;
+
+ pa_threaded_mainloop_unlock(m_MainLoop);
+
+ return pause;
+}
+
+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
View
66 xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h
@@ -0,0 +1,66 @@
+#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.