2 * Copyright (C) 2010-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
25 #include "PulseAEStream.h"
26 #include "PulseAESound.h"
27 #include "Application.h"
28 #include "threads/SingleLock.h"
29 #include "utils/log.h"
30 #include "utils/StringUtils.h"
31 #include "settings/Settings.h"
32 #include <pulse/pulseaudio.h>
33 #include <pulse/simple.h>
34 #include "guilib/LocalizeStrings.h"
37 static const char *ContextStateToString(pa_context_state s)
41 case PA_CONTEXT_UNCONNECTED:
43 case PA_CONTEXT_CONNECTING:
45 case PA_CONTEXT_AUTHORIZING:
47 case PA_CONTEXT_SETTING_NAME:
48 return "setting name";
49 case PA_CONTEXT_READY:
51 case PA_CONTEXT_FAILED:
53 case PA_CONTEXT_TERMINATED:
61 static const char *StreamStateToString(pa_stream_state s)
65 case PA_STREAM_UNCONNECTED:
67 case PA_STREAM_CREATING:
71 case PA_STREAM_FAILED:
73 case PA_STREAM_TERMINATED:
93 pa_context_disconnect(m_Context);
94 pa_context_unref(m_Context);
100 pa_threaded_mainloop_stop(m_MainLoop);
101 pa_threaded_mainloop_free(m_MainLoop);
106 bool CPulseAE::CanInit()
111 ss.format = PA_SAMPLE_S16NE;
115 //create a pulse client, if this returns NULL, pulseaudio isn't running
116 s = pa_simple_new(NULL, "XBMC-test", PA_STREAM_PLAYBACK, NULL,"test", &ss, NULL, NULL, NULL);
129 bool CPulseAE::Initialize()
131 m_Volume = g_application.GetVolume(false);
133 if ((m_MainLoop = pa_threaded_mainloop_new()) == NULL)
135 CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop");
139 if ((m_Context = pa_context_new(pa_threaded_mainloop_get_api(m_MainLoop), "XBMC")) == NULL)
141 CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context");
145 pa_context_set_state_callback(m_Context, ContextStateCallback, m_MainLoop);
147 if (pa_context_connect(m_Context, NULL, (pa_context_flags_t)0, NULL) < 0)
149 CLog::Log(LOGERROR, "PulseAudio: Failed to connect context");
153 pa_threaded_mainloop_lock(m_MainLoop);
154 if (pa_threaded_mainloop_start(m_MainLoop) < 0)
156 CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop");
157 pa_threaded_mainloop_unlock(m_MainLoop);
161 /* Wait until the context is ready */
164 pa_threaded_mainloop_wait(m_MainLoop);
165 CLog::Log(LOGDEBUG, "PulseAudio: Context %s", ContextStateToString(pa_context_get_state(m_Context)));
167 while (pa_context_get_state(m_Context) != PA_CONTEXT_READY && pa_context_get_state(m_Context) != PA_CONTEXT_FAILED);
169 if (pa_context_get_state(m_Context) == PA_CONTEXT_FAILED)
171 CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed");
172 pa_threaded_mainloop_unlock(m_MainLoop);
176 pa_threaded_mainloop_unlock(m_MainLoop);
180 bool CPulseAE::Suspend()
182 /* TODO: add implementation here. See SoftAE for example. Code should */
183 /* release exclusive or hog mode and sleep each time packets would */
184 /* normally be written to sink if m_isSuspended = true. False return */
185 /* here will simply generate a debug log entry in externalplayer.cpp */
190 bool CPulseAE::IsSuspended()
195 bool CPulseAE::Resume()
197 /* TODO: see comments in Suspend() above */
202 void CPulseAE::OnSettingsChange(const std::string& setting)
206 float CPulseAE::GetVolume()
211 void CPulseAE::SetVolume(float volume)
213 CSingleLock lock(m_lock);
215 std::list<CPulseAEStream*>::iterator itt;
216 for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
217 (*itt)->UpdateVolume(volume);
220 IAEStream *CPulseAE::MakeStream(enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int encodedSampleRate,CAEChannelInfo channelLayout, unsigned int options)
222 CPulseAEStream *st = new CPulseAEStream(m_Context, m_MainLoop, dataFormat, sampleRate, channelLayout, options);
224 CSingleLock lock(m_lock);
225 m_streams.push_back(st);
229 void CPulseAE::RemoveStream(IAEStream *stream)
231 CSingleLock lock(m_lock);
232 std::list<CPulseAEStream*>::iterator itt;
234 m_streams.remove((CPulseAEStream *)stream);
236 for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
240 m_streams.erase(itt);
246 IAEStream *CPulseAE::FreeStream(IAEStream *stream)
248 RemoveStream(stream);
250 CPulseAEStream *istream = (CPulseAEStream *)stream;
257 IAESound *CPulseAE::MakeSound(const std::string& file)
259 CSingleLock lock(m_lock);
261 CPulseAESound *sound = new CPulseAESound(file, m_Context, m_MainLoop);
262 if (!sound->Initialize())
268 m_sounds.push_back(sound);
272 void CPulseAE::FreeSound(IAESound *sound)
278 CSingleLock lock(m_lock);
279 for (std::list<CPulseAESound*>::iterator itt = m_sounds.begin(); itt != m_sounds.end(); ++itt)
286 delete (CPulseAESound*)sound;
289 void CPulseAE::GarbageCollect()
291 CSingleLock lock(m_lock);
292 std::list<CPulseAEStream*>::iterator itt;
293 for (itt = m_streams.begin(); itt != m_streams.end();)
295 if ((*itt)->IsDestroyed())
298 itt = m_streams.erase(itt);
305 struct SinkInfoStruct
309 pa_threaded_mainloop *mainloop;
312 static bool WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry = "")
319 while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
320 pa_threaded_mainloop_wait(mainloop);
322 if (pa_operation_get_state(op) != PA_OPERATION_DONE)
324 CLog::Log(LOGERROR, "PulseAudio: %s Operation failed", LogEntry);
328 pa_operation_unref(op);
332 static void SinkInfo(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
334 SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata;
339 if(sinkStruct->passthrough)
341 #if PA_CHECK_VERSION(1,0,0)
342 for(int idx = 0; idx < i->n_formats; ++idx)
344 if(!pa_format_info_is_pcm(i->formats[idx]))
357 CStdString desc = StringUtils::Format("%s (PulseAudio)", i->description);
358 CStdString sink = StringUtils::Format("pulse:%s@default", i->name);
359 sinkStruct->list->push_back(AEDevice(desc, sink));
360 CLog::Log(LOGDEBUG, "PulseAudio: Found %s with devicestring %s", desc.c_str(), sink.c_str());
364 pa_threaded_mainloop_signal(sinkStruct->mainloop, 0);
367 void CPulseAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
369 if (!m_MainLoop || ! m_Context)
372 pa_threaded_mainloop_lock(m_MainLoop);
374 SinkInfoStruct sinkStruct;
375 sinkStruct.passthrough = passthrough;
376 sinkStruct.mainloop = m_MainLoop;
377 sinkStruct.list = &devices;
378 CStdString def = StringUtils::Format("%s (PulseAudio)",g_localizeStrings.Get(409).c_str());
379 devices.push_back(AEDevice(def, "pulse:default@default"));
380 WaitForOperation(pa_context_get_sink_info_list(m_Context,
381 SinkInfo, &sinkStruct), m_MainLoop, "EnumerateAudioSinks");
383 pa_threaded_mainloop_unlock(m_MainLoop);
386 void CPulseAE::ContextStateCallback(pa_context *c, void *userdata)
388 pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata;
389 switch (pa_context_get_state(c))
391 case PA_CONTEXT_READY:
392 case PA_CONTEXT_TERMINATED:
393 case PA_CONTEXT_UNCONNECTED:
394 case PA_CONTEXT_CONNECTING:
395 case PA_CONTEXT_AUTHORIZING:
396 case PA_CONTEXT_SETTING_NAME:
397 case PA_CONTEXT_FAILED:
398 pa_threaded_mainloop_signal(m, 0);
403 void CPulseAE::SetMute(const bool enabled)
405 CSingleLock lock(m_lock);
406 std::list<CPulseAEStream*>::iterator itt;
407 for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
408 (*itt)->SetMute(enabled);