fixed: deadlock in CPulseAE::GetSound
[xbmc:xbmc-antiquated.git] / xbmc / cores / AudioEngine / Engines / PulseAE.cpp
1 /*
2  *      Copyright (C) 2005-2010 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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "system.h"
23 #ifdef HAS_PULSEAUDIO
24
25 #include "PulseAE.h"
26 #include "PulseAEStream.h"
27 #include "PulseAESound.h"
28 #include "utils/SingleLock.h"
29 #include "log.h"
30 #include "Settings.h"
31 #include <pulse/pulseaudio.h>
32
33 /* Static helpers */
34 static const char *ContextStateToString(pa_context_state s)
35 {
36   switch (s)
37   {
38     case PA_CONTEXT_UNCONNECTED:
39       return "unconnected";
40     case PA_CONTEXT_CONNECTING:
41       return "connecting";
42     case PA_CONTEXT_AUTHORIZING:
43       return "authorizing";
44     case PA_CONTEXT_SETTING_NAME:
45       return "setting name";
46     case PA_CONTEXT_READY:
47       return "ready";
48     case PA_CONTEXT_FAILED:
49       return "failed";
50     case PA_CONTEXT_TERMINATED:
51       return "terminated";
52     default:
53       return "none";
54   }
55 }
56
57 static const char *StreamStateToString(pa_stream_state s)
58 {
59   switch(s)
60   {
61     case PA_STREAM_UNCONNECTED:
62       return "unconnected";
63     case PA_STREAM_CREATING:
64       return "creating";
65     case PA_STREAM_READY:
66       return "ready";
67     case PA_STREAM_FAILED:
68       return "failed";
69     case PA_STREAM_TERMINATED:
70       return "terminated";
71     default:
72       return "none";
73   }
74 }
75
76 CPulseAE::CPulseAE()
77 {
78   m_Context = NULL;
79   m_MainLoop = NULL;
80 }
81
82 CPulseAE::~CPulseAE()
83 {
84   if (m_MainLoop)
85     pa_threaded_mainloop_stop(m_MainLoop);
86
87   if (m_Context)
88   {
89     pa_context_disconnect(m_Context);
90     pa_context_unref(m_Context);
91     m_Context = NULL;
92   }
93 }
94
95 bool CPulseAE::Initialize()
96 {
97   m_Volume = g_settings.m_fVolumeLevel;
98
99   if ((m_MainLoop = pa_threaded_mainloop_new()) == NULL)
100   {
101     CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop");
102     return false;
103   }
104
105   if ((m_Context = pa_context_new(pa_threaded_mainloop_get_api(m_MainLoop), "XBMC")) == NULL)
106   {
107     CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context");
108     return false;
109   }
110
111   pa_context_set_state_callback(m_Context, ContextStateCallback, m_MainLoop);
112
113   if (pa_context_connect(m_Context, NULL, (pa_context_flags_t)0, NULL) < 0)
114   {
115     CLog::Log(LOGERROR, "PulseAudio: Failed to connect context");
116     return false;
117   }
118
119   pa_threaded_mainloop_lock(m_MainLoop);
120   if (pa_threaded_mainloop_start(m_MainLoop) < 0)
121   {
122     CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop");
123     pa_threaded_mainloop_unlock(m_MainLoop);
124     return false;
125   }
126
127   /* Wait until the context is ready */
128   do
129   {
130     pa_threaded_mainloop_wait(m_MainLoop);
131     CLog::Log(LOGDEBUG, "PulseAudio: Context %s", ContextStateToString(pa_context_get_state(m_Context)));
132   }
133   while (pa_context_get_state(m_Context) != PA_CONTEXT_READY && pa_context_get_state(m_Context) != PA_CONTEXT_FAILED);
134
135   if (pa_context_get_state(m_Context) == PA_CONTEXT_FAILED)
136   {
137     CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed");
138     pa_threaded_mainloop_unlock(m_MainLoop);
139     return false;
140   }
141
142   pa_threaded_mainloop_unlock(m_MainLoop);
143   return true;
144 }
145
146 void CPulseAE::OnSettingsChange(CStdString setting)
147 {
148 }
149
150 float CPulseAE::GetVolume()
151 {
152   return m_Volume;
153 }
154
155 void CPulseAE::SetVolume(float volume)
156 {
157   CSingleLock lock(m_lock);
158   m_Volume = volume;
159   std::list<CPulseAEStream*>::iterator itt;
160   for(itt = m_streams.begin(); itt != m_streams.end(); ++itt)
161     (*itt)->UpdateVolume(volume);
162 }
163
164 IAEStream *CPulseAE::GetStream(enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int channelCount, AEChLayout channelLayout, unsigned int options)
165 {
166   CPulseAEStream *st = new CPulseAEStream(m_Context, m_MainLoop, dataFormat, sampleRate, channelCount, channelLayout, options);
167
168   CSingleLock lock(m_lock);
169   m_streams.push_back(st);
170   return st;
171 }
172
173 IAEStream *CPulseAE::AlterStream(IAEStream *stream, enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int channelCount, AEChLayout channelLayout, unsigned int options)
174 {
175   /* FIXME: Pulse can alter a stream I think */
176   ((CPulseAEStream*)stream)->SetFreeOnDrain();
177   stream->Drain();
178
179   return GetStream(dataFormat, sampleRate, channelCount, channelLayout, options);
180 }
181
182 IAESound *CPulseAE::GetSound(CStdString file)
183 {
184   CSingleLock lock(m_lock);
185
186   /* see if we have a valid sound */
187   std::map<const CStdString, CPulseAESound*>::iterator itt = m_sounds.find(file);
188   if (itt != m_sounds.end())
189     return itt->second;
190
191   CPulseAESound *sound = new CPulseAESound(file, m_Context, m_MainLoop);
192   if (!sound->Initialize())
193   {
194     delete sound;
195     return NULL;
196   }
197
198   m_sounds[file] = sound;
199   return sound;
200 }
201
202 void CPulseAE::FreeSound(IAESound *sound)
203 {
204   //delete (CPulseAESound*)sound;
205 }
206
207 void CPulseAE::GarbageCollect()
208 {
209   CSingleLock lock(m_lock);
210   std::list<CPulseAEStream*>::iterator itt;
211   for(itt = m_streams.begin(); itt != m_streams.end();)
212   {
213     if ((*itt)->IsDestroyed())
214     {
215       delete (*itt);
216       itt = m_streams.erase(itt);
217       continue;
218     }
219     ++itt;
220   }
221 }
222
223 void CPulseAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
224 {
225 }
226
227 void CPulseAE::ContextStateCallback(pa_context *c, void *userdata)
228 {
229   pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata;
230   switch (pa_context_get_state(c))
231   {
232     case PA_CONTEXT_READY:
233     case PA_CONTEXT_TERMINATED:
234     case PA_CONTEXT_UNCONNECTED:
235     case PA_CONTEXT_CONNECTING:
236     case PA_CONTEXT_AUTHORIZING:
237     case PA_CONTEXT_SETTING_NAME:
238     case PA_CONTEXT_FAILED:
239       pa_threaded_mainloop_signal(m, 0);
240       break;
241   }
242 }
243
244 #endif