fixed: PulseAE GUI sounds now work
[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   CSingleLock lock(m_lock);
168   m_streams.push_back(st);
169   return st;
170 }
171
172 IAEStream *CPulseAE::AlterStream(IAEStream *stream, enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int channelCount, AEChLayout channelLayout, unsigned int options)
173 {
174   /* FIXME: Pulse can alter a stream I think */
175   ((CPulseAEStream*)stream)->SetFreeOnDrain();
176   stream->Drain();
177
178   return GetStream(dataFormat, sampleRate, channelCount, channelLayout, options);
179 }
180
181 IAESound *CPulseAE::GetSound(CStdString file)
182 {
183   /* see if we have a valid sound */
184   std::map<const CStdString, CPulseAESound*>::iterator itt = m_sounds.find(file);
185   if (itt != m_sounds.end())
186     return itt->second;
187
188   CPulseAESound *sound = new CPulseAESound(file, m_Context, m_MainLoop);
189   if (!sound->Initialize())
190   {
191     delete sound;
192     return NULL;
193   }
194
195   m_sounds[file] = sound;
196   return sound;
197 }
198
199 void CPulseAE::FreeSound(IAESound *sound)
200 {
201   //delete (CPulseAESound*)sound;
202 }
203
204 void CPulseAE::GarbageCollect()
205 {
206   CSingleLock lock(m_lock);
207   std::list<CPulseAEStream*>::iterator itt;
208   for(itt = m_streams.begin(); itt != m_streams.end();)
209   {
210     if ((*itt)->IsDestroyed())
211     {
212       delete (*itt);
213       itt = m_streams.erase(itt);
214       continue;
215     }
216     ++itt;
217   }
218 }
219
220 void CPulseAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
221 {
222 }
223
224 void CPulseAE::ContextStateCallback(pa_context *c, void *userdata)
225 {
226   pa_threaded_mainloop *m = (pa_threaded_mainloop *)userdata;
227   switch (pa_context_get_state(c))
228   {
229     case PA_CONTEXT_READY:
230     case PA_CONTEXT_TERMINATED:
231     case PA_CONTEXT_UNCONNECTED:
232     case PA_CONTEXT_CONNECTING:
233     case PA_CONTEXT_AUTHORIZING:
234     case PA_CONTEXT_SETTING_NAME:
235     case PA_CONTEXT_FAILED:
236       pa_threaded_mainloop_signal(m, 0);
237       break;
238   }
239 }
240
241 #endif