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