1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 *
20 * This code is based on examples in an ALSA lib tutorial given at:
21 * http://equalarea.com/paul/alsa-audio.html
22 * Thanks to Paul Davis (except for directing me to JACK at the bottom and getting me WAY off track with my goals! :) )
23 */
24
25 #include "CALSASoundPlayer.h"
26
27 #ifdef ENABLE_ALSA
28
29
30 #include <stdexcept>
31 #include <string>
32
33 #include <istring>
34 #include <TAutoBuffer.h>
35
36 #include "settings.h"
37
38 #warning use these values from the registry globals
39 #define PERIOD_SIZE_FRAMES 1024
40 #define PERIOD_COUNT 2
41
42
CALSASoundPlayer()43 CALSASoundPlayer::CALSASoundPlayer() :
44 ASoundPlayer(),
45
46 initialized(false),
47 playback_handle(NULL),
48
49 playThread(this)
50 {
51 }
52
~CALSASoundPlayer()53 CALSASoundPlayer::~CALSASoundPlayer()
54 {
55 deinitialize();
56 }
57
isInitialized() const58 bool CALSASoundPlayer::isInitialized() const
59 {
60 return initialized;
61 }
62
initialize()63 void CALSASoundPlayer::initialize()
64 {
65 if(!initialized)
66 {
67 try
68 {
69 snd_pcm_hw_params_t *hw_params;
70 snd_pcm_sw_params_t *sw_params;
71 int err;
72
73 if((err = snd_pcm_open(&playback_handle, gALSAOutputDevice.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0)
74 throw runtime_error(string(__func__)+" -- cannot open audio device: "+gALSAOutputDevice+" -- "+snd_strerror(err));
75
76 if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
77 throw runtime_error(string(__func__)+" -- cannot allocate hardware parameter structure -- "+snd_strerror(err));
78
79 if((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0)
80 throw runtime_error(string(__func__)+" -- cannot initialize hardware parameter structure -- "+snd_strerror(err));
81
82 // set sample rate
83 unsigned int sampleRate=gDesiredOutputSampleRate;
84 unsigned int outSampleRate=sampleRate;
85 if((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &outSampleRate, 0)) < 0)
86 throw runtime_error(string(__func__)+" -- cannot set sample rate -- "+snd_strerror(err));
87 if(sampleRate!=outSampleRate)
88 fprintf(stderr,"warning: ALSA used a different sample rate (%d) than what was asked for (%d); will have to do extra calculations to compensate\n", (int)outSampleRate, (int)sampleRate);
89 devices[0].sampleRate=outSampleRate; // make note of the sample rate for this device (??? which is only device zero for now)
90
91
92 // set number of channels
93 unsigned channelCount=gDesiredOutputChannelCount;
94 // might need to use the near function
95 if((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, channelCount)) < 0)
96 throw runtime_error(string(__func__)+" -- cannot set channel count -- "+snd_strerror(err));
97 devices[0].channelCount=channelCount; // make note of the number of channels for this device (??? which is only device zero for now)
98
99
100 // set to interleaved access
101 if((err = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
102 throw runtime_error(string(__func__)+" -- cannot set access type -- "+snd_strerror(err));
103
104
105 // set periods to integer sizes only
106 if((err = snd_pcm_hw_params_set_periods_integer(playback_handle, hw_params)) < 0)
107 throw runtime_error(string(__func__)+" -- cannot set periods to integer only -- "+snd_strerror(err));
108
109
110 // set period size
111 snd_pcm_uframes_t period_size=PERIOD_SIZE_FRAMES;
112 int dir=0;
113 if((err = snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params, &period_size, &dir)) < 0)
114 throw runtime_error(string(__func__)+" -- cannot set period size -- "+snd_strerror(err));
115
116
117 // set number of periods
118 unsigned int periods=PERIOD_COUNT;
119 if((err = snd_pcm_hw_params_set_periods(playback_handle, hw_params, periods, 0)) < 0)
120 throw runtime_error(string(__func__)+" -- cannot set periods -- "+snd_strerror(err));
121
122
123 // and then set the buffer size (what .. what's this difference between buffers and periods?)
124 if((err = snd_pcm_hw_params_set_buffer_size(playback_handle, hw_params, (PERIOD_COUNT*PERIOD_SIZE_FRAMES) )) < 0)
125 throw runtime_error(string(__func__)+" -- cannot set buffer size -- "+snd_strerror(err));
126
127
128 // set sample format
129 vector<snd_pcm_format_t> formatsToTry;
130 #ifndef WORDS_BIGENDIAN
131 #if defined(SAMPLE_TYPE_S16)
132 formatsToTry.push_back(SND_PCM_FORMAT_S16_LE);
133 formatsToTry.push_back(SND_PCM_FORMAT_S32_LE);
134 formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_LE);
135 #elif defined(SAMPLE_TYPE_FLOAT)
136 formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_LE);
137 formatsToTry.push_back(SND_PCM_FORMAT_S32_LE);
138 formatsToTry.push_back(SND_PCM_FORMAT_S16_LE);
139 #else
140 #error unhandled SAMPLE_TYPE_xxx define
141 #endif
142 #else
143 #if defined(SAMPLE_TYPE_S16)
144 formatsToTry.push_back(SND_PCM_FORMAT_S16_BE);
145 formatsToTry.push_back(SND_PCM_FORMAT_S32_BE);
146 formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_BE);
147 #elif defined(SAMPLE_TYPE_FLOAT)
148 formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_BE);
149 formatsToTry.push_back(SND_PCM_FORMAT_S32_BE);
150 formatsToTry.push_back(SND_PCM_FORMAT_S16_BE);
151 #else
152 #error unhandled SAMPLE_TYPE_xxx define
153 #endif
154 #endif
155 bool found=false;
156 for(size_t t=0;t<formatsToTry.size();t++)
157 {
158 if((err = snd_pcm_hw_params_set_format(playback_handle, hw_params, formatsToTry[t])) < 0)
159 continue; // try the next one
160 else
161 {
162 found=true;
163 playback_format=formatsToTry[t];
164 break;
165 }
166 }
167 if(!found)
168 throw runtime_error(string(__func__)+" -- cannot set sample format -- "+snd_strerror(err));
169
170
171 // set the hardware parameters
172 if((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0)
173 throw runtime_error(string(__func__)+" -- cannot set parameters -- "+snd_strerror(err));
174
175 snd_pcm_hw_params_free(hw_params);
176
177
178
179 /* tell ALSA to wake us up whenever PERIOD_SIZE_FRAMES or more frames of playback data can be delivered. Also, tell ALSA that we'll start the device ourselves. */
180
181 if((err = snd_pcm_sw_params_malloc(&sw_params)) < 0)
182 throw runtime_error(string(__func__)+" -- cannot allocate software parameters structure -- "+snd_strerror(err));
183
184 if((err = snd_pcm_sw_params_current(playback_handle, sw_params)) < 0)
185 throw runtime_error(string(__func__)+" -- cannot initialize software parameters structure -- "+snd_strerror(err));
186
187 if((err = snd_pcm_sw_params_set_avail_min(playback_handle, sw_params, PERIOD_SIZE_FRAMES)) < 0)
188 throw runtime_error(string(__func__)+" -- cannot set minimum available count -- "+snd_strerror(err));
189
190 if((err = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params, 0)) < 0)
191 throw runtime_error(string(__func__)+" -- cannot set start threshold -- "+snd_strerror(err));
192
193 // make xruns not interrupt anything
194 snd_pcm_uframes_t boundary;
195 if((err = snd_pcm_sw_params_get_boundary(sw_params, &boundary)) < 0)
196 throw runtime_error(string(__func__)+" -- cannot get boundary -- "+snd_strerror(err));
197 if((err = snd_pcm_sw_params_set_stop_threshold(playback_handle, sw_params, boundary)) < 0)
198 throw runtime_error(string(__func__)+" -- cannot set stop threshold -- "+snd_strerror(err));
199
200 if((err = snd_pcm_sw_params_set_silence_threshold(playback_handle, sw_params, 0)) < 0)
201 throw runtime_error(string(__func__)+" -- cannot set silence threshold -- "+snd_strerror(err));
202
203
204 if((err = snd_pcm_sw_params(playback_handle, sw_params)) < 0)
205 throw runtime_error(string(__func__)+" -- cannot set software parameters -- "+snd_strerror(err));
206
207 snd_pcm_sw_params_free(sw_params);
208
209
210 if((err = snd_pcm_prepare(playback_handle)) < 0)
211 throw runtime_error(string(__func__)+" -- cannot prepare audio interface for use -- "+snd_strerror(err));
212
213
214
215 // start play thread
216 playThread.kill=false;
217
218 ASoundPlayer::initialize();
219 initialized=true;
220 playThread.start();
221 fprintf(stderr, "ALSA player initialized\n");
222 }
223 catch(...)
224 {
225 if(playThread.isRunning())
226 {
227 playThread.kill=true;
228 playThread.wait();
229 }
230 if(playback_handle)
231 snd_pcm_close(playback_handle);
232 playback_handle=NULL;
233
234 ASoundPlayer::deinitialize();
235 throw;
236 }
237 }
238 else
239 throw runtime_error(string(__func__)+" -- already initialized");
240 }
241
deinitialize()242 void CALSASoundPlayer::deinitialize()
243 {
244 if(initialized)
245 {
246 ASoundPlayer::deinitialize();
247
248 // stop play thread
249 playThread.kill=true;
250 playThread.wait();
251
252 // close ALSA audio device
253 if(playback_handle!=NULL)
254 snd_pcm_close(playback_handle);
255 playback_handle=NULL;
256
257 initialized=false;
258 fprintf(stderr, "ALSA player deinitialized\n");
259 }
260 }
261
aboutToRecord()262 void CALSASoundPlayer::aboutToRecord()
263 {
264 }
265
doneRecording()266 void CALSASoundPlayer::doneRecording()
267 {
268 }
269
270
CPlayThread(CALSASoundPlayer * _parent)271 CALSASoundPlayer::CPlayThread::CPlayThread(CALSASoundPlayer *_parent) :
272 AThread(),
273
274 kill(false),
275 parent(_parent)
276 {
277 }
278
~CPlayThread()279 CALSASoundPlayer::CPlayThread::~CPlayThread()
280 {
281 }
282
main()283 void CALSASoundPlayer::CPlayThread::main()
284 {
285 try
286 {
287 int err;
288 TAutoBuffer<sample_t> buffer(PERIOD_SIZE_FRAMES*parent->devices[0].channelCount*2, true);
289 // these are possibly used if sample format conversion is required
290 TAutoBuffer<int16_t> buffer__int16_t(PERIOD_SIZE_FRAMES*parent->devices[0].channelCount, true);
291 TAutoBuffer<int32_t> buffer__int32_t(PERIOD_SIZE_FRAMES*parent->devices[0].channelCount, true);
292 TAutoBuffer<float> buffer__float(PERIOD_SIZE_FRAMES*parent->devices[0].channelCount, true);
293
294 snd_pcm_format_t format=parent->playback_format;
295
296 // must do conversion of sample_t type -> initialized format type if necessary
297 int conv=-1;
298 #ifndef WORDS_BIGENDIAN
299 #if defined(SAMPLE_TYPE_S16)
300 if(format==SND_PCM_FORMAT_S16_LE)
301 conv=0;
302 else if(format==SND_PCM_FORMAT_S32_LE)
303 conv=4;
304 else if(format==SND_PCM_FORMAT_FLOAT_LE)
305 conv=2;
306 #elif defined(SAMPLE_TYPE_FLOAT)
307 if(format==SND_PCM_FORMAT_S16_LE)
308 conv=1;
309 else if(format==SND_PCM_FORMAT_S32_LE)
310 conv=3;
311 else if(format==SND_PCM_FORMAT_FLOAT_LE)
312 conv=0;
313 #else
314 #error unhandled SAMPLE_TYPE_xxx define
315 #endif
316 #else
317 #if defined(SAMPLE_TYPE_S16)
318 if(format==SND_PCM_FORMAT_S16_BE)
319 conv=0;
320 else if(format==SND_PCM_FORMAT_S32_BE)
321 conv=4;
322 else if(format==SND_PCM_FORMAT_FLOAT_BE)
323 conv=2;
324 #elif defined(SAMPLE_TYPE_FLOAT)
325 if(format==SND_PCM_FORMAT_S16_BE)
326 conv=1;
327 else if(format==SND_PCM_FORMAT_S32_BE)
328 conv=3;
329 else if(format==SND_PCM_FORMAT_FLOAT_BE)
330 conv=0;
331 #else
332 #error unhandled SAMPLE_TYPE_xxx define
333 #endif
334 #endif
335
336 while(!kill)
337 {
338 // can mixChannels throw any exception???
339 parent->mixSoundPlayerChannels(parent->devices[0].channelCount,buffer,PERIOD_SIZE_FRAMES);
340
341 // wait till the interface is ready for data, or 1 second has elapsed.
342 if((err = snd_pcm_wait (parent->playback_handle, 1000)) < 0)
343 throw runtime_error(string(__func__)+" -- snd_pcm_wait failed -- "+snd_strerror(err));
344
345 // find out how much space is available for playback data
346 snd_pcm_sframes_t frames_to_deliver;
347 if((frames_to_deliver = snd_pcm_avail_update(parent->playback_handle)) < 0)
348 {
349 if (frames_to_deliver == -EPIPE)
350 {
351 fprintf (stderr, "an xrun occured\n");
352 }
353 else
354 throw runtime_error(string(__func__)+" -- unknown ALSA avail update return value: "+istring(frames_to_deliver));
355 }
356
357 if(frames_to_deliver<PERIOD_SIZE_FRAMES)
358 throw runtime_error(string(__func__)+" -- frames_to_deliver is less than PERIOD_SIZE_FRAMES: "+istring(frames_to_deliver)+"<"+istring(PERIOD_SIZE_FRAMES));
359
360 #warning probably should abstract this into a since it could be used several places
361 switch(conv)
362 {
363 case 0: // no conversion necessary
364 {
365 if((err=snd_pcm_writei(parent->playback_handle, buffer, PERIOD_SIZE_FRAMES)) < 0)
366 fprintf(stderr, "ALSA write failed (%s)\n", snd_strerror(err));
367 }
368 break;
369
370 case 1: // we need to convert float->int16_t
371 {
372 unsigned l=PERIOD_SIZE_FRAMES*parent->devices[0].channelCount;
373 for(unsigned t=0;t<l;t++)
374 buffer__int16_t[t]=convert_sample<float,int16_t>((float)buffer[t]);
375 if((err=snd_pcm_writei(parent->playback_handle, buffer__int16_t, PERIOD_SIZE_FRAMES)) < 0)
376 fprintf(stderr, "ALSA write failed (%s)\n", snd_strerror(err));
377 }
378 break;
379
380 case 2: // we need to convert int16_t->float
381 {
382 unsigned l=PERIOD_SIZE_FRAMES*parent->devices[0].channelCount;
383 for(unsigned t=0;t<l;t++)
384 buffer__float[t]=convert_sample<int16_t,float>((int16_t)buffer[t]);
385 if((err=snd_pcm_writei(parent->playback_handle, buffer__float, PERIOD_SIZE_FRAMES)) < 0)
386 fprintf(stderr, "ALSA write failed (%s)\n", snd_strerror(err));
387 }
388 break;
389
390 case 3: // we need to convert float->int32_t
391 {
392 unsigned l=PERIOD_SIZE_FRAMES*parent->devices[0].channelCount;
393 for(unsigned t=0;t<l;t++)
394 buffer__int32_t[t]=convert_sample<float,int32_t>((float)buffer[t]);
395 if((err=snd_pcm_writei(parent->playback_handle, buffer__int32_t, PERIOD_SIZE_FRAMES)) < 0)
396 fprintf(stderr, "ALSA write failed (%s)\n", snd_strerror(err));
397 }
398 break;
399
400 case 4: // we need to convert int16_t->int32_t
401 {
402 unsigned l=PERIOD_SIZE_FRAMES*parent->devices[0].channelCount;
403 for(unsigned t=0;t<l;t++)
404 buffer__int32_t[t]=convert_sample<int16_t,int32_t>((int16_t)buffer[t]);
405 if((err=snd_pcm_writei(parent->playback_handle, buffer__int32_t, PERIOD_SIZE_FRAMES)) < 0)
406 fprintf(stderr, "ALSA write failed (%s)\n", snd_strerror(err));
407 }
408 break;
409
410 default:
411 throw runtime_error(string(__func__)+" -- no conversion determined");
412 }
413
414 }
415 }
416 catch(exception &e)
417 {
418 fprintf(stderr,"exception caught in play thread: %s\n",e.what());
419 }
420 catch(...)
421 {
422 fprintf(stderr,"unknown exception caught in play thread\n");
423 }
424 }
425
426 #endif // ENABLE_ALSA
427