fixed: PulseAE GUI sounds now work
[xbmc:xbmc-antiquated.git] / xbmc / cores / AudioEngine / Engines / PulseAESound.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 "PulseAESound.h"
26 #include "AEFactory.h"
27 #include "utils/log.h"
28 #include "MathUtils.h"
29 #include "StringUtils.h"
30
31 CPulseAESound::CPulseAESound(const CStdString &filename, pa_context *context, pa_threaded_mainloop *mainLoop) :
32   IAESound  (filename),
33   m_filename(filename),
34   m_dataSent(0       ),
35   m_context (context ),
36   m_mainLoop(mainLoop),
37   m_stream  (NULL    )
38 {
39   m_pulseName = StringUtils::CreateUUID();
40 }
41
42 CPulseAESound::~CPulseAESound()
43 {
44   DeInitialize();
45 }
46
47 bool CPulseAESound::Initialize()
48 {
49   /* we dont re-init the wav loader in PA as PA handles the samplerate */
50   if (!m_wavLoader.IsValid() && !m_wavLoader.Initialize(m_filename, 0))
51     return false;
52
53   m_sampleSpec.format   = PA_SAMPLE_FLOAT32NE;
54   m_sampleSpec.rate     = m_wavLoader.GetSampleRate();
55   m_sampleSpec.channels = m_wavLoader.GetChannelCount();
56
57   if (!pa_sample_spec_valid(&m_sampleSpec))
58   {
59     CLog::Log(LOGERROR, "CPulseAESound::Initialize - Invalid sample spec");
60     return false;
61   }
62
63   struct pa_channel_map map;
64   map.channels = m_sampleSpec.channels;
65   switch(map.channels) {
66     case 1:
67       map.map[0] = PA_CHANNEL_POSITION_MONO;
68       break;
69
70     case 2:
71       map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
72       map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
73       break;
74
75     default:
76       CLog::Log(LOGERROR, "CPulseAESound::Initialize - We do not yet support multichannel sounds");
77       return false;
78   }
79
80   m_maxVolume     = AE.GetVolume();
81   m_volume        = 1.0f;
82   float useVolume = m_volume * m_maxVolume;
83
84   pa_volume_t paVolume = MathUtils::round_int(useVolume * PA_VOLUME_NORM);
85   pa_cvolume_set(&m_chVolume, m_sampleSpec.channels, paVolume);
86
87   pa_threaded_mainloop_lock(m_mainLoop);
88   if ((m_stream = pa_stream_new(m_context, m_pulseName.c_str(), &m_sampleSpec, &map)) == NULL)
89   {
90     CLog::Log(LOGERROR, "CPulseAESound::Initialize - Could not create a stream");
91     pa_threaded_mainloop_unlock(m_mainLoop);
92     return false;
93   }
94
95   pa_stream_set_state_callback(m_stream, CPulseAESound::StreamStateCallback, this);
96   pa_stream_set_write_callback(m_stream, CPulseAESound::StreamWriteCallback, this);
97
98   if (pa_stream_connect_upload(m_stream, m_wavLoader.GetFrameCount() * pa_frame_size(&m_sampleSpec)) != 0) {
99     CLog::Log(LOGERROR, "CPulseAESound::Initialize - Could not initialize the stream");
100     pa_stream_disconnect(m_stream);
101     m_stream = NULL;
102     pa_threaded_mainloop_unlock(m_mainLoop);
103     return false;
104   }
105
106   /* Wait until the stream is ready */
107   while (pa_stream_get_state(m_stream) != PA_STREAM_READY && pa_stream_get_state(m_stream) != PA_STREAM_FAILED)
108     pa_threaded_mainloop_wait(m_mainLoop);
109
110   /* check if the stream failed */
111   if (pa_stream_get_state(m_stream) == PA_STREAM_FAILED)
112   {
113     CLog::Log(LOGERROR, "CPulseAESound::Initialize - Waited for the stream but it failed");
114     pa_stream_disconnect(m_stream);
115     m_stream = NULL;
116     pa_threaded_mainloop_unlock(m_mainLoop);
117     return false;
118   }
119
120   pa_threaded_mainloop_unlock(m_mainLoop);
121   return true;
122 }
123
124 void CPulseAESound::DeInitialize()
125 {
126   m_wavLoader.DeInitialize();
127   if (m_stream) {
128     pa_stream_disconnect(m_stream);
129     m_stream = NULL;
130   }
131 }
132
133 void CPulseAESound::Play()
134 {
135   pa_threaded_mainloop_lock(m_mainLoop);
136   pa_operation *o = pa_context_play_sample(m_context, m_pulseName.c_str(), NULL, PA_VOLUME_INVALID, NULL, NULL);
137   if (o)
138     pa_operation_unref(o);
139   pa_threaded_mainloop_unlock(m_mainLoop);
140 }
141
142 void CPulseAESound::Stop()
143 {
144 }
145
146 bool CPulseAESound::IsPlaying()
147 {
148   return false;
149 }
150
151 void CPulseAESound::SetVolume(float volume)
152 {
153 }
154
155 float CPulseAESound::GetVolume()
156 {
157   return 1.0f;
158 }
159
160 void CPulseAESound::StreamStateCallback(pa_stream *s, void *userdata)
161 {
162   CPulseAESound *sound = (CPulseAESound*)userdata;
163   pa_stream_state_t state = pa_stream_get_state(s);
164
165   switch (state)
166   {
167     case PA_STREAM_FAILED:
168       CLog::Log(LOGERROR, "CPulseAESound::StreamStateCallback - %s", pa_strerror(pa_context_errno(sound->m_context)));
169    
170     case PA_STREAM_UNCONNECTED:
171     case PA_STREAM_CREATING:
172     case PA_STREAM_READY:
173     case PA_STREAM_TERMINATED:
174       pa_threaded_mainloop_signal(sound->m_mainLoop, 0);
175       break;
176   }
177 }
178
179 void CPulseAESound::StreamWriteCallback(pa_stream *s, size_t length, void *userdata)
180 {
181   CPulseAESound *sound = (CPulseAESound*)userdata;
182   sound->Upload(length);
183 }
184
185 void CPulseAESound::Upload(size_t length)
186 {
187   float *samples = m_wavLoader.GetSamples();
188   size_t left    = (m_wavLoader.GetSampleCount() * sizeof(float)) - m_dataSent;
189   size_t send    = std::min(length, left);
190
191   if (pa_stream_write(m_stream, samples + m_dataSent, send, 0, 0, PA_SEEK_RELATIVE) == 0)
192     m_dataSent += send;
193
194   /* if we have no more data disable the callback */
195   if (left == send) {
196     pa_stream_set_write_callback(m_stream, NULL, NULL);
197     if (pa_stream_finish_upload(m_stream) != 0)
198     {
199       CLog::Log(LOGERROR, "CPulseAESound::Upload - Error occured");
200       /* FIXME: Better error handling */
201     }
202   }
203 }
204
205 #endif