Mercurial > hg > svapp
comparison audioio/AudioPulseAudioTarget.cpp @ 117:2bc8bf6d016c
* Provisional PulseAudio output driver. No latency handling yet, and
some other things missing. The very basic basics work.
author | Chris Cannam |
---|---|
date | Wed, 21 May 2008 16:54:24 +0000 |
parents | |
children | c41e340dfe8d |
comparison
equal
deleted
inserted
replaced
116:9554c19c42fd | 117:2bc8bf6d016c |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 This file copyright 2008 QMUL. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #ifdef HAVE_LIBPULSE | |
17 | |
18 #include "AudioPulseAudioTarget.h" | |
19 #include "AudioCallbackPlaySource.h" | |
20 | |
21 #include <QMutexLocker> | |
22 | |
23 #include <iostream> | |
24 #include <cassert> | |
25 #include <cmath> | |
26 | |
27 #define DEBUG_AUDIO_PULSE_AUDIO_TARGET 1 | |
28 | |
29 AudioPulseAudioTarget::AudioPulseAudioTarget(AudioCallbackPlaySource *source) : | |
30 AudioCallbackPlayTarget(source), | |
31 m_mutex(QMutex::Recursive), | |
32 m_loop(0), | |
33 m_api(0), | |
34 m_context(0), | |
35 m_stream(0), | |
36 m_loopThread(0), | |
37 m_bufferSize(0), | |
38 m_sampleRate(0), | |
39 m_latency(0), | |
40 m_done(false) | |
41 { | |
42 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET | |
43 std::cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << std::endl; | |
44 #endif | |
45 | |
46 m_loop = pa_mainloop_new(); | |
47 if (!m_loop) { | |
48 std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << std::endl; | |
49 return; | |
50 } | |
51 | |
52 m_api = pa_mainloop_get_api(m_loop); | |
53 | |
54 //!!! handle signals how? | |
55 | |
56 m_bufferSize = 2048; | |
57 m_sampleRate = 44100; | |
58 if (m_source && (m_source->getSourceSampleRate() != 0)) { | |
59 m_sampleRate = m_source->getSourceSampleRate(); | |
60 } | |
61 m_spec.rate = m_sampleRate; | |
62 m_spec.channels = 2; | |
63 m_spec.format = PA_SAMPLE_FLOAT32NE; | |
64 | |
65 m_context = pa_context_new(m_api, source->getClientName().toLocal8Bit().data()); | |
66 if (!m_context) { | |
67 std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << std::endl; | |
68 return; | |
69 } | |
70 | |
71 pa_context_set_state_callback(m_context, contextStateChangedStatic, this); | |
72 | |
73 pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0); // default server | |
74 | |
75 m_loopThread = new MainLoopThread(m_loop); | |
76 m_loopThread->start(); | |
77 | |
78 #ifdef DEBUG_PULSE_AUDIO_TARGET | |
79 std::cerr << "AudioPulseAudioTarget: initialised OK" << std::endl; | |
80 #endif | |
81 } | |
82 | |
83 AudioPulseAudioTarget::~AudioPulseAudioTarget() | |
84 { | |
85 std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << std::endl; | |
86 | |
87 if (m_source) { | |
88 m_source->setTarget(0, m_bufferSize); | |
89 } | |
90 | |
91 shutdown(); | |
92 | |
93 QMutexLocker locker(&m_mutex); | |
94 | |
95 if (m_stream) pa_stream_unref(m_stream); | |
96 | |
97 if (m_context) pa_context_unref(m_context); | |
98 | |
99 if (m_loop) { | |
100 pa_signal_done(); | |
101 pa_mainloop_free(m_loop); | |
102 } | |
103 | |
104 m_stream = 0; | |
105 m_context = 0; | |
106 m_loop = 0; | |
107 | |
108 std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << std::endl; | |
109 } | |
110 | |
111 void | |
112 AudioPulseAudioTarget::shutdown() | |
113 { | |
114 m_done = true; | |
115 } | |
116 | |
117 bool | |
118 AudioPulseAudioTarget::isOK() const | |
119 { | |
120 return (m_context != 0); | |
121 } | |
122 | |
123 double | |
124 AudioPulseAudioTarget::getCurrentTime() const | |
125 { | |
126 if (!m_stream) return 0.0; | |
127 //!!! else return Pa_GetStreamTime(m_stream); | |
128 | |
129 return 0.0;//!!! | |
130 } | |
131 | |
132 void | |
133 AudioPulseAudioTarget::sourceModelReplaced() | |
134 { | |
135 m_source->setTargetSampleRate(m_sampleRate); | |
136 } | |
137 | |
138 void | |
139 AudioPulseAudioTarget::streamWriteStatic(pa_stream *stream, | |
140 size_t length, | |
141 void *data) | |
142 { | |
143 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; | |
144 | |
145 assert(stream == target->m_stream); | |
146 | |
147 target->streamWrite(length); | |
148 } | |
149 | |
150 void | |
151 AudioPulseAudioTarget::streamWrite(size_t nframes) | |
152 { | |
153 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET | |
154 std::cout << "AudioPulseAudioTarget::streamWrite(" << nframes << ")" << std::endl; | |
155 #endif | |
156 if (m_done) return; | |
157 | |
158 QMutexLocker locker(&m_mutex); | |
159 | |
160 if (nframes > m_bufferSize) { | |
161 std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl; | |
162 } | |
163 | |
164 static float *output = 0; | |
165 static float **tmpbuf = 0; | |
166 static size_t tmpbufch = 0; | |
167 static size_t tmpbufsz = 0; | |
168 | |
169 size_t sourceChannels = m_source->getSourceChannelCount(); | |
170 | |
171 // Because we offer pan, we always want at least 2 channels | |
172 if (sourceChannels < 2) sourceChannels = 2; | |
173 | |
174 if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) { | |
175 | |
176 if (tmpbuf) { | |
177 for (size_t i = 0; i < tmpbufch; ++i) { | |
178 delete[] tmpbuf[i]; | |
179 } | |
180 delete[] tmpbuf; | |
181 } | |
182 | |
183 if (output) { | |
184 delete[] output; | |
185 } | |
186 | |
187 tmpbufch = sourceChannels; | |
188 tmpbufsz = nframes; | |
189 tmpbuf = new float *[tmpbufch]; | |
190 | |
191 for (size_t i = 0; i < tmpbufch; ++i) { | |
192 tmpbuf[i] = new float[tmpbufsz]; | |
193 } | |
194 | |
195 output = new float[tmpbufsz * tmpbufch]; | |
196 } | |
197 | |
198 size_t received = m_source->getSourceSamples(nframes, tmpbuf); | |
199 | |
200 float peakLeft = 0.0, peakRight = 0.0; | |
201 | |
202 for (size_t ch = 0; ch < 2; ++ch) { | |
203 | |
204 float peak = 0.0; | |
205 | |
206 if (ch < sourceChannels) { | |
207 | |
208 // PulseAudio samples are interleaved | |
209 for (size_t i = 0; i < nframes; ++i) { | |
210 if (i < received) { | |
211 output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; | |
212 float sample = fabsf(output[i * 2 + ch]); | |
213 if (sample > peak) peak = sample; | |
214 } else { | |
215 output[i * 2 + ch] = 0; | |
216 } | |
217 } | |
218 | |
219 } else if (ch == 1 && sourceChannels == 1) { | |
220 | |
221 for (size_t i = 0; i < nframes; ++i) { | |
222 if (i < received) { | |
223 output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain; | |
224 float sample = fabsf(output[i * 2 + ch]); | |
225 if (sample > peak) peak = sample; | |
226 } else { | |
227 output[i * 2 + ch] = 0; | |
228 } | |
229 } | |
230 | |
231 } else { | |
232 for (size_t i = 0; i < nframes; ++i) { | |
233 output[i * 2 + ch] = 0; | |
234 } | |
235 } | |
236 | |
237 if (ch == 0) peakLeft = peak; | |
238 if (ch > 0 || sourceChannels == 1) peakRight = peak; | |
239 } | |
240 | |
241 pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float), | |
242 0, 0, PA_SEEK_RELATIVE); | |
243 | |
244 m_source->setOutputLevels(peakLeft, peakRight); | |
245 | |
246 return; | |
247 } | |
248 | |
249 void | |
250 AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *stream, | |
251 void *data) | |
252 { | |
253 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; | |
254 | |
255 assert(stream == target->m_stream); | |
256 | |
257 target->streamStateChanged(); | |
258 } | |
259 | |
260 void | |
261 AudioPulseAudioTarget::streamStateChanged() | |
262 { | |
263 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET | |
264 std::cerr << "AudioPulseAudioTarget::streamStateChanged" << std::endl; | |
265 #endif | |
266 QMutexLocker locker(&m_mutex); | |
267 | |
268 switch (pa_stream_get_state(m_stream)) { | |
269 | |
270 case PA_STREAM_CREATING: | |
271 case PA_STREAM_TERMINATED: | |
272 break; | |
273 | |
274 case PA_STREAM_READY: | |
275 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Ready" << std::endl; | |
276 break; | |
277 | |
278 case PA_STREAM_FAILED: | |
279 default: | |
280 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Error: " | |
281 << pa_strerror(pa_context_errno(m_context)) << std::endl; | |
282 //!!! do something... | |
283 break; | |
284 } | |
285 } | |
286 | |
287 void | |
288 AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context, | |
289 void *data) | |
290 { | |
291 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; | |
292 | |
293 assert(context == target->m_context); | |
294 | |
295 target->contextStateChanged(); | |
296 } | |
297 | |
298 void | |
299 AudioPulseAudioTarget::contextStateChanged() | |
300 { | |
301 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET | |
302 std::cerr << "AudioPulseAudioTarget::contextStateChanged" << std::endl; | |
303 #endif | |
304 QMutexLocker locker(&m_mutex); | |
305 | |
306 switch (pa_context_get_state(m_context)) { | |
307 | |
308 case PA_CONTEXT_CONNECTING: | |
309 case PA_CONTEXT_AUTHORIZING: | |
310 case PA_CONTEXT_SETTING_NAME: | |
311 break; | |
312 | |
313 case PA_CONTEXT_READY: | |
314 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Ready" | |
315 << std::endl; | |
316 | |
317 m_stream = pa_stream_new(m_context, "stream", &m_spec, 0); | |
318 assert(m_stream); //!!! | |
319 | |
320 pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this); | |
321 pa_stream_set_write_callback(m_stream, streamWriteStatic, this); | |
322 | |
323 if (!pa_stream_connect_playback(m_stream, 0, 0, pa_stream_flags_t(0), 0, 0)) { | |
324 std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl; | |
325 break; | |
326 } | |
327 | |
328 const pa_buffer_attr *attr; | |
329 if (!(attr = pa_stream_get_buffer_attr(m_stream))) { | |
330 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Cannot query stream buffer attributes" << std::endl; | |
331 m_source->setTarget(this, 4096); | |
332 m_source->setTargetSampleRate(m_sampleRate); | |
333 m_source->setTargetPlayLatency(4096); | |
334 } else { | |
335 std::cerr << "AudioPulseAudioTarget::contextStateChanged: stream max length = " << attr->maxlength << std::endl; | |
336 int latency = attr->tlength; | |
337 std::cerr << "latency = " << latency << std::endl; | |
338 m_source->setTarget(this, attr->maxlength); | |
339 m_source->setTargetSampleRate(m_sampleRate); | |
340 m_source->setTargetPlayLatency(latency); | |
341 } | |
342 | |
343 break; | |
344 | |
345 case PA_CONTEXT_TERMINATED: | |
346 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Terminated" << std::endl; | |
347 //!!! do something... | |
348 break; | |
349 | |
350 case PA_CONTEXT_FAILED: | |
351 default: | |
352 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Error: " | |
353 << pa_strerror(pa_context_errno(m_context)) << std::endl; | |
354 //!!! do something... | |
355 break; | |
356 } | |
357 } | |
358 | |
359 #endif /* HAVE_PULSEAUDIO */ | |
360 |