[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / cores / AudioEngine / Engines / PulseAE / PulseAE.cpp
1 /*
2  *      Copyright (C) 2010-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "system.h"
22 #ifdef HAS_PULSEAUDIO
23
24 #include "PulseAE.h"
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"
35
36 /* Static helpers */
37 static const char *ContextStateToString(pa_context_state s)
38 {
39   switch (s)
40   {
41     case PA_CONTEXT_UNCONNECTED:
42       return "unconnected";
43     case PA_CONTEXT_CONNECTING:
44       return "connecting";
45     case PA_CONTEXT_AUTHORIZING:
46       return "authorizing";
47     case PA_CONTEXT_SETTING_NAME:
48       return "setting name";
49     case PA_CONTEXT_READY:
50       return "ready";
51     case PA_CONTEXT_FAILED:
52       return "failed";
53     case PA_CONTEXT_TERMINATED:
54       return "terminated";
55     default:
56       return "none";
57   }
58 }
59
60 #if 0
61 static const char *StreamStateToString(pa_stream_state s)
62 {
63   switch(s)
64   {
65     case PA_STREAM_UNCONNECTED:
66       return "unconnected";
67     case PA_STREAM_CREATING:
68       return "creating";
69     case PA_STREAM_READY:
70       return "ready";
71     case PA_STREAM_FAILED:
72       return "failed";
73     case PA_STREAM_TERMINATED:
74       return "terminated";
75     default:
76       return "none";
77   }
78 }
79 #endif
80
81 CPulseAE::CPulseAE()
82 {
83   m_Context = NULL;
84   m_MainLoop = NULL;
85   m_muted = false;
86   m_Volume = 0.0f;
87 }
88
89 CPulseAE::~CPulseAE()
90 {
91   if (m_Context)
92   {
93     pa_context_disconnect(m_Context);
94     pa_context_unref(m_Context);
95     m_Context = NULL;
96   }
97
98   if (m_MainLoop)
99   {
100     pa_threaded_mainloop_stop(m_MainLoop);
101     pa_threaded_mainloop_free(m_MainLoop);
102   }
103
104 }
105
106 bool CPulseAE::CanInit()
107 {
108   pa_simple *s;
109   pa_sample_spec ss;
110  
111   ss.format = PA_SAMPLE_S16NE;
112   ss.channels = 2;
113   ss.rate = 48000;
114  
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);
117  
118   if (s)
119   {
120     pa_simple_free(s);
121     return true;
122   }
123   else
124   {
125     return false;
126   }
127 }
128
129 bool CPulseAE::Initialize()
130 {
131   m_Volume = g_application.GetVolume(false);
132
133   if ((m_MainLoop = pa_threaded_mainloop_new()) == NULL)
134   {
135     CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop");
136     return false;
137   }
138
139   if ((m_Context = pa_context_new(pa_threaded_mainloop_get_api(m_MainLoop), "XBMC")) == NULL)
140   {
141     CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context");
142     return false;
143   }
144
145   pa_context_set_state_callback(m_Context, ContextStateCallback, m_MainLoop);
146
147   if (pa_context_connect(m_Context, NULL, (pa_context_flags_t)0, NULL) < 0)
148   {
149     CLog::Log(LOGERROR, "PulseAudio: Failed to connect context");
150     return false;
151   }
152
153   pa_threaded_mainloop_lock(m_MainLoop);
154   if (pa_threaded_mainloop_start(m_MainLoop) < 0)
155   {
156     CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop");
157     pa_threaded_mainloop_unlock(m_MainLoop);
158     return false;
159   }
160
161   /* Wait until the context is ready */
162   do
163   {
164     pa_threaded_mainloop_wait(m_MainLoop);
165     CLog::Log(LOGDEBUG, "PulseAudio: Context %s", ContextStateToString(pa_context_get_state(m_Context)));
166   }
167   while (pa_context_get_state(m_Context) != PA_CONTEXT_READY && pa_context_get_state(m_Context) != PA_CONTEXT_FAILED);
168
169   if (pa_context_get_state(m_Context) == PA_CONTEXT_FAILED)
170   {
171     CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed");
172     pa_threaded_mainloop_unlock(m_MainLoop);
173     return false;
174   }
175
176   pa_threaded_mainloop_unlock(m_MainLoop);
177   return true;
178 }
179
180 bool CPulseAE::Suspend()
181 {
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  */
186
187   return false;
188 }
189
190 bool CPulseAE::IsSuspended()
191 {
192   return false;
193 }
194
195 bool CPulseAE::Resume()
196 {
197   /* TODO: see comments in Suspend() above */
198
199   return false;
200 }
201
202 void CPulseAE::OnSettingsChange(const std::string& setting)
203 {
204 }
205
206 float CPulseAE::GetVolume()
207 {
208   return m_Volume;
209 }
210
211 void CPulseAE::SetVolume(float volume)
212 {
213   CSingleLock lock(m_lock);
214   m_Volume = volume;
215   std::list<CPulseAEStream*>::iterator itt;
216   for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
217     (*itt)->UpdateVolume(volume);
218 }
219
220 IAEStream *CPulseAE::MakeStream(enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int encodedSampleRate,CAEChannelInfo channelLayout, unsigned int options)
221 {
222   CPulseAEStream *st = new CPulseAEStream(m_Context, m_MainLoop, dataFormat, sampleRate, channelLayout, options);
223
224   CSingleLock lock(m_lock);
225   m_streams.push_back(st);
226   return st;
227 }
228
229 void CPulseAE::RemoveStream(IAEStream *stream)
230 {
231   CSingleLock lock(m_lock);
232   std::list<CPulseAEStream*>::iterator itt;
233
234   m_streams.remove((CPulseAEStream *)stream);
235
236   for (itt = m_streams.begin(); itt != m_streams.end(); ++itt)
237   {
238     if (*itt == stream)
239     {
240       m_streams.erase(itt);
241       return;
242     }
243   }
244 }
245
246 IAEStream *CPulseAE::FreeStream(IAEStream *stream)
247 {
248   RemoveStream(stream);
249
250   CPulseAEStream *istream = (CPulseAEStream *)stream;
251
252   delete istream;
253
254   return NULL;
255 }
256
257 IAESound *CPulseAE::MakeSound(const std::string& file)
258 {
259   CSingleLock lock(m_lock);
260
261   CPulseAESound *sound = new CPulseAESound(file, m_Context, m_MainLoop);
262   if (!sound->Initialize())
263   {
264     delete sound;
265     return NULL;
266   }
267
268   m_sounds.push_back(sound);
269   return sound;
270 }
271
272 void CPulseAE::FreeSound(IAESound *sound)
273 {
274   if (!sound)
275     return;
276
277   sound->Stop();
278   CSingleLock lock(m_lock);
279   for (std::list<CPulseAESound*>::iterator itt = m_sounds.begin(); itt != m_sounds.end(); ++itt)
280     if (*itt == sound)
281     {
282       m_sounds.erase(itt);
283       break;
284     }
285
286   delete (CPulseAESound*)sound;
287 }
288
289 void CPulseAE::GarbageCollect()
290 {
291   CSingleLock lock(m_lock);
292   std::list<CPulseAEStream*>::iterator itt;
293   for (itt = m_streams.begin(); itt != m_streams.end();)
294   {
295     if ((*itt)->IsDestroyed())
296     {
297       delete (*itt);
298       itt = m_streams.erase(itt);
299       continue;
300     }
301     ++itt;
302   }
303 }
304
305 struct SinkInfoStruct
306 {
307   bool passthrough;
308   AEDeviceList *list;
309   pa_threaded_mainloop *mainloop;
310 };
311
312 static bool WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry = "")
313 {
314   if (op == NULL)
315     return false;
316
317   bool sucess = true;
318
319   while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
320     pa_threaded_mainloop_wait(mainloop);
321
322   if (pa_operation_get_state(op) != PA_OPERATION_DONE)
323   {
324     CLog::Log(LOGERROR, "PulseAudio: %s Operation failed", LogEntry);
325     sucess = false;
326   }
327
328   pa_operation_unref(op);
329   return sucess;
330 }
331
332 static void SinkInfo(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
333 {
334   SinkInfoStruct *sinkStruct = (SinkInfoStruct *)userdata;
335
336   if (i && i->name)
337   {
338     bool       add  = false;
339     if(sinkStruct->passthrough)
340     {
341 #if PA_CHECK_VERSION(1,0,0)
342       for(int idx = 0; idx < i->n_formats; ++idx)
343       {
344         if(!pa_format_info_is_pcm(i->formats[idx]))
345         {
346           add = true;
347           break;
348         }
349       }
350 #endif
351     }
352     else
353       add = true;
354
355     if (add)
356     {
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());
361     }
362   }
363
364   pa_threaded_mainloop_signal(sinkStruct->mainloop, 0);
365 }
366
367 void CPulseAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
368 {
369   if (!m_MainLoop || ! m_Context)
370     return;
371
372   pa_threaded_mainloop_lock(m_MainLoop);
373
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");
382
383   pa_threaded_mainloop_unlock(m_MainLoop);
384 }
385
386 void CPulseAE::ContextStateCallback(pa_context *c, void *userdata)
387 {
388   pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata;
389   switch (pa_context_get_state(c))
390   {
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);
399       break;
400   }
401 }
402
403 void CPulseAE::SetMute(const bool enabled)
404 {
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);
409
410   m_muted = enabled;
411 }
412
413 #endif