Added a parameter to semaphore constructor to avoid ambiguity
[pwlib.git] / plugins / sound_alsa / sound_alsa.cxx
blob34e86fe886587765e005c4d976e8d8477f6b4e17
1 /*
2 * sound_alsa.cxx
4 * Sound driver implementation.
6 * Portable Windows Library
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original ALSA Code is
23 * Damien Sandras <dsandras@seconix.com>
25 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
26 * All Rights Reserved.
28 * Contributor(s): /
30 * $Log$
31 * Revision 1.14 2004/02/12 09:07:57 csoutheren
32 * Fixed typo in ALSA driver, thanks to Julien Puydt
34 * Revision 1.13 2004/01/04 20:59:30 dsandras
35 * Use set_rate_near instead of set_rate.
37 * Revision 1.12 2003/12/28 15:10:35 dsandras
38 * Updated to the new PCM API.
40 * Revision 1.11 2003/12/18 11:16:41 dominance
41 * Removed the ALSA Abort completely upon Damien's request ;)
43 * Revision 1.10 2003/12/18 10:38:55 dominance
44 * Removed ALSA Abort as it segfaults in various circumstances.
45 * Fix proposed by Damien Sandras <dsandras@seconix.com>.
47 * Revision 1.9 2003/12/09 22:47:10 dsandras
48 * Use less aggressive Abort.
50 * Revision 1.8 2003/12/03 21:48:21 dsandras
51 * Better handling of buffer sizes. Removed unuseful code.
53 * Revision 1.7 2003/11/25 20:13:48 dsandras
54 * Added #pragma.
56 * Revision 1.6 2003/11/25 09:58:01 dsandras
57 * Removed Abort call from PlaySound ().
59 * Revision 1.5 2003/11/25 09:52:07 dsandras
60 * Modified WaitForPlayCompletion so that it uses snd_pcm_drain instead of active waiting.
62 * Revision 1.4 2003/11/23 22:09:57 dsandras
63 * Removed unuseful stuff and added implementation for functions permitting to play a file or a PSound.
65 * Revision 1.3 2003/11/14 05:28:47 csoutheren
66 * Updated for new plugin code thanks to Damien and Snark
70 #pragma implementation "sound_alsa.h"
72 #include "sound_alsa.h"
75 PCREATE_SOUND_PLUGIN(ALSA, PSoundChannelALSA)
78 static PStringArray playback_devices;
79 static PStringArray capture_devices;
81 ///////////////////////////////////////////////////////////////////////////////
83 PSoundChannelALSA::PSoundChannelALSA()
85 PSoundChannelALSA::Construct();
89 PSoundChannelALSA::PSoundChannelALSA (const PString &device,
90 Directions dir,
91 unsigned numChannels,
92 unsigned sampleRate,
93 unsigned bitsPerSample)
95 Construct();
96 Open (device, dir, numChannels, sampleRate, bitsPerSample);
100 void PSoundChannelALSA::Construct()
102 frame_bytes = 0;
103 period_size = 0;
104 periods = 0;
105 card_nr = 0;
106 os_handle = NULL;
110 PSoundChannelALSA::~PSoundChannelALSA()
112 Close();
116 PStringArray PSoundChannelALSA::GetDeviceNames (Directions dir)
118 int card = -1, dev = -1;
120 snd_ctl_t *handle = NULL;
121 snd_ctl_card_info_t *info = NULL;
122 snd_pcm_info_t *pcminfo = NULL;
123 snd_pcm_stream_t stream;
125 char *name = NULL;
126 char card_id [32];
128 if (dir == Recorder) {
130 stream = SND_PCM_STREAM_CAPTURE;
131 capture_devices = PStringArray ();
133 else {
135 stream = SND_PCM_STREAM_PLAYBACK;
136 playback_devices = PStringArray ();
139 snd_ctl_card_info_alloca (&info);
140 snd_pcm_info_alloca (&pcminfo);
142 /* No sound card found */
143 if (snd_card_next (&card) < 0 || card < 0) {
145 return PStringArray ();
149 while (card >= 0) {
151 snprintf (card_id, 32, "hw:%d", card);
153 snd_ctl_open (&handle, card_id, 0);
154 snd_ctl_card_info (handle, info);
156 while (1) {
158 snd_ctl_pcm_next_device (handle, &dev);
160 if (dev < 0)
161 break;
163 snd_pcm_info_set_device (pcminfo, dev);
164 snd_pcm_info_set_subdevice (pcminfo, 0);
165 snd_pcm_info_set_stream (pcminfo, stream);
167 if (snd_ctl_pcm_info (handle, pcminfo) >= 0) {
169 snd_card_get_name (card, &name);
170 if (dir == Recorder) {
172 if (capture_devices.GetStringsIndex (name) == P_MAX_INDEX)
173 capture_devices.AppendString (name);
175 else {
177 if (playback_devices.GetStringsIndex (name) == P_MAX_INDEX)
178 playback_devices.AppendString (name);
181 free (name);
186 snd_ctl_close(handle);
187 snd_card_next (&card);
191 if (dir == Recorder)
192 return capture_devices;
193 else
194 return playback_devices;
198 PString PSoundChannelALSA::GetDefaultDevice(Directions dir)
200 PStringArray devicenames;
201 devicenames = PSoundChannelALSA::GetDeviceNames (dir);
203 return devicenames[0];
207 BOOL PSoundChannelALSA::Open (const PString & _device,
208 Directions _dir,
209 unsigned _numChannels,
210 unsigned _sampleRate,
211 unsigned _bitsPerSample)
213 PString real_device_name;
214 PINDEX i = 0;
215 snd_pcm_stream_t stream;
217 Close();
219 os_handle = NULL;
221 if (_dir == Recorder)
222 stream = SND_PCM_STREAM_CAPTURE;
223 else
224 stream = SND_PCM_STREAM_PLAYBACK;
226 /* Open in NONBLOCK mode */
227 if ((i = (_dir == Recorder) ? capture_devices.GetStringsIndex (_device) : playback_devices.GetStringsIndex (_device)) != P_MAX_INDEX) {
229 real_device_name = "plughw:" + PString (i);
230 card_nr = i;
232 else {
234 PTRACE (1, "ALSA\tDevice unavailable");
235 return FALSE;
238 if (snd_pcm_open (&os_handle, real_device_name, stream, SND_PCM_NONBLOCK) < 0) {
240 PTRACE (1, "ALSA\tOpen Failed");
241 return FALSE;
243 else
244 snd_pcm_nonblock (os_handle, 0);
246 /* save internal parameters */
247 direction = _dir;
248 device = real_device_name;
249 mNumChannels = _numChannels;
250 mSampleRate = _sampleRate;
251 mBitsPerSample = _bitsPerSample;
252 isInitialised = FALSE;
254 PTRACE (1, "ALSA\tDevice " << real_device_name << " Opened");
256 return TRUE;
260 BOOL PSoundChannelALSA::Setup()
262 snd_pcm_hw_params_t *hw_params = NULL;
263 snd_pcm_uframes_t buffer_size = 0;
265 int err = 0;
266 enum _snd_pcm_format val = SND_PCM_FORMAT_UNKNOWN;
267 BOOL no_error = TRUE;
270 if (os_handle == NULL) {
272 PTRACE(6, "ALSA\tSkipping setup of " << device << " as not open");
273 return FALSE;
276 if (isInitialised) {
278 PTRACE(6, "ALSA\tSkipping setup of " << device << " as instance already initialised");
279 return TRUE;
283 #if PBYTE_ORDER == PLITTLE_ENDIAN
284 val = (mBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U8;
285 #else
286 val = (mBitsPerSample == 16) ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_U8;
287 #endif
289 frame_bytes = (mNumChannels * (snd_pcm_format_width (val) / 8));
291 snd_pcm_hw_params_alloca (&hw_params);
293 if ((err = snd_pcm_hw_params_any (os_handle, hw_params)) < 0) {
295 PTRACE (1, "ALSA\tCannot initialize hardware parameter structure " <<
296 snd_strerror (err));
297 no_error = FALSE;
301 if ((err = snd_pcm_hw_params_set_access (os_handle, hw_params,
302 SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
304 PTRACE (1, "ALSA\tCannot set access type " <<
305 snd_strerror (err));
306 no_error = FALSE;
310 if ((err = snd_pcm_hw_params_set_format (os_handle, hw_params, val)) < 0) {
312 PTRACE (1, "ALSA\tCannot set sample format " <<
313 snd_strerror (err));
314 no_error = FALSE;
318 if ((err = snd_pcm_hw_params_set_rate_near (os_handle,
319 hw_params,
320 &mSampleRate,
321 NULL)) < 0) {
323 PTRACE (1, "ALSA\tCannot set sample rate " <<
324 snd_strerror (err));
325 no_error = FALSE;
329 if ((err = snd_pcm_hw_params_set_channels (os_handle, hw_params,
330 mNumChannels)) < 0) {
332 PTRACE (1, "ALSA\tCannot set channel count " <<
333 snd_strerror (err));
334 no_error = FALSE;
338 // Ignore errors here
339 if (periods && period_size) {
341 if ((err = snd_pcm_hw_params_set_period_size_near (os_handle,
342 hw_params,
343 &period_size,
344 0)) < 0)
345 PTRACE (1, "ALSA\tCannot set period size " <<
346 snd_strerror (err));
348 if ((err = snd_pcm_hw_params_set_periods_near (os_handle,
349 hw_params,
350 &periods,
351 0)) < 0)
352 PTRACE (1, "ALSA\tCannot set number of periods " <<
353 snd_strerror (err));
355 buffer_size = periods*period_size/frame_bytes;
357 if ((err = (int) snd_pcm_hw_params_set_buffer_size_near (os_handle,
358 hw_params,
359 &buffer_size))
360 < 0)
361 PTRACE (1, "ALSA\tCannot set buffer size " <<
362 snd_strerror (err));
366 if ((err = snd_pcm_hw_params (os_handle, hw_params)) < 0) {
368 PTRACE (1, "ALSA\tCannot set parameters " <<
369 snd_strerror (err));
370 no_error = FALSE;
374 isInitialised = TRUE;
376 return no_error;
380 BOOL PSoundChannelALSA::Close()
382 PWaitAndSignal m(device_mutex);
384 /* if the channel isn't open, do nothing */
385 if (!os_handle)
386 return FALSE;
388 snd_pcm_close (os_handle);
389 os_handle = NULL;
391 return TRUE;
395 BOOL PSoundChannelALSA::Write (const void *buf, PINDEX len)
397 long r = 0;
398 char *buf2 = (char *) buf;
399 int pos = 0, max_try = 0;
401 PWaitAndSignal m(device_mutex);
403 if (!isInitialised && !Setup() || !len || !os_handle)
404 return FALSE;
406 do {
409 /* the number of frames to read is the buffer length
410 divided by the size of one frame */
411 r = snd_pcm_writei (os_handle, (char *) &buf2 [pos], len / frame_bytes);
413 if (r > 0) {
415 pos += r * frame_bytes;
416 len -= r * frame_bytes;
418 else {
420 if (r == -EPIPE) { /* under-run */
422 r = snd_pcm_prepare (os_handle);
423 if (r < 0)
424 PTRACE (1, "ALSA\tCould not prepare device: " << snd_strerror (r));
426 } else if (r == -ESTRPIPE) {
428 while ((r = snd_pcm_resume (os_handle)) == -EAGAIN)
429 sleep(1); /* wait until the suspend flag is released */
431 if (r < 0)
432 snd_pcm_prepare (os_handle);
435 PTRACE (1, "ALSA\tCould not write "
436 << max_try << " " << len << " " << r);
437 max_try++;
440 } while (len > 0 && max_try < 5);
443 return TRUE;
447 BOOL PSoundChannelALSA::Read (void * buf, PINDEX len)
449 long r = 0;
451 char *buf2 = (char *) buf;
452 int pos = 0, max_try = 0;
454 lastReadCount = 0;
456 PWaitAndSignal m(device_mutex);
458 if (!isInitialised && !Setup() || !len || !os_handle)
459 return FALSE;
461 memset ((char *) buf, 0, len);
463 do {
465 /* the number of frames to read is the buffer length
466 divided by the size of one frame */
467 r = snd_pcm_readi (os_handle, (char *) &buf2 [pos], len / frame_bytes);
469 if (r > 0) {
471 pos += r * frame_bytes;
472 len -= r * frame_bytes;
473 lastReadCount += r * frame_bytes;
475 else {
477 if (r == -EPIPE) { /* under-run */
479 snd_pcm_prepare (os_handle);
481 } else if (r == -ESTRPIPE) {
483 while ((r = snd_pcm_resume (os_handle)) == -EAGAIN)
484 sleep(1); /* wait until the suspend flag is released */
486 if (r < 0)
487 snd_pcm_prepare (os_handle);
490 PTRACE (1, "ALSA\tCould not read");
491 max_try++;
493 } while (len > 0 && max_try < 5);
496 if (len != 0) {
498 memset ((char *) &buf2 [pos], 0, len);
499 lastReadCount += len;
501 PTRACE (1, "ALSA\tRead Error, filling with zeros");
504 return TRUE;
508 BOOL PSoundChannelALSA::SetFormat (unsigned numChannels,
509 unsigned sampleRate,
510 unsigned bitsPerSample)
512 if (!os_handle)
513 return SetErrorValues(NotOpen, EBADF);
515 /* check parameters */
516 PAssert((bitsPerSample == 8) || (bitsPerSample == 16), PInvalidParameter);
517 PAssert(numChannels >= 1 && numChannels <= 2, PInvalidParameter);
519 mNumChannels = numChannels;
520 mSampleRate = sampleRate;
521 mBitsPerSample = bitsPerSample;
523 /* mark this channel as uninitialised */
524 isInitialised = FALSE;
526 return TRUE;
530 unsigned PSoundChannelALSA::GetChannels() const
532 return mNumChannels;
536 unsigned PSoundChannelALSA::GetSampleRate() const
538 return mSampleRate;
542 unsigned PSoundChannelALSA::GetSampleSize() const
544 return mBitsPerSample;
548 BOOL PSoundChannelALSA::SetBuffers (PINDEX size, PINDEX count)
550 periods = count;
551 period_size = size / (frame_bytes ? frame_bytes : 2);
553 return TRUE;
557 BOOL PSoundChannelALSA::GetBuffers(PINDEX & size, PINDEX & count)
559 size = period_size * (frame_bytes ? frame_bytes : 2);
560 count = periods;
562 return FALSE;
566 BOOL PSoundChannelALSA::PlaySound(const PSound & sound, BOOL wait)
568 if (!os_handle)
569 return SetErrorValues(NotOpen, EBADF);
571 if (!Write((const BYTE *)sound, sound.GetSize()))
572 return FALSE;
574 if (wait)
575 return WaitForPlayCompletion();
577 return TRUE;
581 BOOL PSoundChannelALSA::PlayFile(const PFilePath & filename, BOOL wait)
583 BYTE buffer [512];
585 if (!os_handle)
586 return SetErrorValues(NotOpen, EBADF);
588 PFile file (filename, PFile::ReadOnly);
590 if (!file.IsOpen())
591 return FALSE;
593 for (;;) {
595 if (!file.Read (buffer, 512))
596 break;
598 PINDEX len = file.GetLastReadCount();
599 if (len == 0)
600 break;
601 if (!Write(buffer, len))
602 break;
605 file.Close();
607 if (wait)
608 return WaitForPlayCompletion();
610 return TRUE;
614 BOOL PSoundChannelALSA::HasPlayCompleted()
616 if (!os_handle)
617 return SetErrorValues(NotOpen, EBADF);
619 return (snd_pcm_state (os_handle) != SND_PCM_STATE_RUNNING);
623 BOOL PSoundChannelALSA::WaitForPlayCompletion()
625 if (!os_handle)
626 return SetErrorValues(NotOpen, EBADF);
628 snd_pcm_drain (os_handle);
630 return TRUE;
634 BOOL PSoundChannelALSA::RecordSound(PSound & sound)
636 return FALSE;
640 BOOL PSoundChannelALSA::RecordFile(const PFilePath & filename)
642 return FALSE;
646 BOOL PSoundChannelALSA::StartRecording()
648 return FALSE;
652 BOOL PSoundChannelALSA::IsRecordBufferFull()
654 return TRUE;
658 BOOL PSoundChannelALSA::AreAllRecordBuffersFull()
660 return TRUE;
664 BOOL PSoundChannelALSA::WaitForRecordBufferFull()
666 return TRUE;
670 BOOL PSoundChannelALSA::WaitForAllRecordBuffersFull()
672 return FALSE;
676 BOOL PSoundChannelALSA::Abort()
678 int r = 0;
680 if (!os_handle)
681 return FALSE;
683 if ((r = snd_pcm_drain (os_handle)) < 0) {
685 PTRACE (1, "ALSA\tCannot abort" <<
686 snd_strerror (r));
687 return FALSE;
689 else
690 return TRUE;
695 BOOL PSoundChannelALSA::SetVolume (unsigned newVal)
697 unsigned i = 0;
699 return Volume (TRUE, newVal, i);
703 BOOL PSoundChannelALSA::GetVolume(unsigned &devVol)
705 return Volume (FALSE, 0, devVol);
709 BOOL PSoundChannelALSA::IsOpen () const
711 return (os_handle != NULL);
715 BOOL PSoundChannelALSA::Volume (BOOL set, unsigned set_vol, unsigned &get_vol)
717 int err = 0;
718 snd_mixer_t *handle;
719 snd_mixer_elem_t *elem;
720 snd_mixer_selem_id_t *sid;
722 const char *play_mix_name = (direction == Player) ? "PCM": "Mic";
723 PString card_name;
725 long pmin = 0, pmax = 0;
726 long int vol = 0;
728 if (!os_handle)
729 return FALSE;
731 card_name = "hw:" + PString (card_nr);
733 //allocate simple id
734 snd_mixer_selem_id_alloca (&sid);
736 //sets simple-mixer index and name
737 snd_mixer_selem_id_set_index (sid, 0);
738 snd_mixer_selem_id_set_name (sid, play_mix_name);
740 if ((err = snd_mixer_open (&handle, 0)) < 0) {
742 PTRACE (1, "alsa-control: mixer open error: " << snd_strerror (err));
743 return FALSE;
747 if ((err = snd_mixer_attach (handle, card_name)) < 0) {
749 PTRACE (1, "alsa-control: mixer attach " << card_name << " error: "
750 << snd_strerror(err));
751 snd_mixer_close(handle);
752 return FALSE;
756 if ((err = snd_mixer_selem_register (handle, NULL, NULL)) < 0) {
758 PTRACE (1, "alsa-control: mixer register error: " << snd_strerror(err));
759 snd_mixer_close(handle);
760 return FALSE;
764 err = snd_mixer_load(handle);
765 if (err < 0) {
767 PTRACE (1, "alsa-control: mixer load error: " << snd_strerror(err));
768 snd_mixer_close(handle);
769 return FALSE;
772 elem = snd_mixer_find_selem (handle, sid);
774 if (!elem) {
776 PTRACE (1, "alsa-control: unable to find simple control "
777 << snd_mixer_selem_id_get_name(sid) << ","
778 << snd_mixer_selem_id_get_index(sid));
779 snd_mixer_close(handle);
780 return FALSE;
783 snd_mixer_selem_get_playback_volume_range (elem, &pmin, &pmax);
785 if (set) {
787 vol = (set_vol * (pmax?pmax:31)) / 100;
788 snd_mixer_selem_set_playback_volume (elem,
789 SND_MIXER_SCHN_FRONT_LEFT, vol);
790 snd_mixer_selem_set_playback_volume (elem,
791 SND_MIXER_SCHN_FRONT_RIGHT, vol);
793 PTRACE (4, "Set volume to " << vol);
795 else {
797 snd_mixer_selem_get_playback_volume (elem,
798 SND_MIXER_SCHN_FRONT_LEFT, &vol);
799 get_vol = (vol * 100) / (pmax?pmax:31);
800 PTRACE (4, "Got volume " << vol);
803 snd_mixer_close(handle);
805 return TRUE;