Removed printf that was interpreted as a trigraph
[pwlib.git] / plugins / sound_alsa / sound_alsa.cxx
blobd096e10383a31329e9ed76b99400cee3f9986d2e
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.17 2004/04/03 10:33:45 dsandras
32 * Use PStringToOrdinal to store the detected devices, that fixes problems if there is a discontinuity in the succession of soundcard ID's. For example the user has card ID 1 and 3, but not 2.
34 * Revision 1.16 2004/03/13 12:36:14 dsandras
35 * Added support for DMIX plugin output.
37 * Revision 1.15 2004/03/04 13:36:13 dsandras
38 * Added check so that ALSA doesn't assert on broken installations.
40 * Revision 1.14 2004/02/12 09:07:57 csoutheren
41 * Fixed typo in ALSA driver, thanks to Julien Puydt
43 * Revision 1.13 2004/01/04 20:59:30 dsandras
44 * Use set_rate_near instead of set_rate.
46 * Revision 1.12 2003/12/28 15:10:35 dsandras
47 * Updated to the new PCM API.
49 * Revision 1.11 2003/12/18 11:16:41 dominance
50 * Removed the ALSA Abort completely upon Damien's request ;)
52 * Revision 1.10 2003/12/18 10:38:55 dominance
53 * Removed ALSA Abort as it segfaults in various circumstances.
54 * Fix proposed by Damien Sandras <dsandras@seconix.com>.
56 * Revision 1.9 2003/12/09 22:47:10 dsandras
57 * Use less aggressive Abort.
59 * Revision 1.8 2003/12/03 21:48:21 dsandras
60 * Better handling of buffer sizes. Removed unuseful code.
62 * Revision 1.7 2003/11/25 20:13:48 dsandras
63 * Added #pragma.
65 * Revision 1.6 2003/11/25 09:58:01 dsandras
66 * Removed Abort call from PlaySound ().
68 * Revision 1.5 2003/11/25 09:52:07 dsandras
69 * Modified WaitForPlayCompletion so that it uses snd_pcm_drain instead of active waiting.
71 * Revision 1.4 2003/11/23 22:09:57 dsandras
72 * Removed unuseful stuff and added implementation for functions permitting to play a file or a PSound.
74 * Revision 1.3 2003/11/14 05:28:47 csoutheren
75 * Updated for new plugin code thanks to Damien and Snark
79 #pragma implementation "sound_alsa.h"
81 #include "sound_alsa.h"
84 PCREATE_SOUND_PLUGIN(ALSA, PSoundChannelALSA)
87 static PStringToOrdinal playback_devices;
88 static PStringToOrdinal capture_devices;
90 ///////////////////////////////////////////////////////////////////////////////
92 PSoundChannelALSA::PSoundChannelALSA()
94 PSoundChannelALSA::Construct();
98 PSoundChannelALSA::PSoundChannelALSA (const PString &device,
99 Directions dir,
100 unsigned numChannels,
101 unsigned sampleRate,
102 unsigned bitsPerSample)
104 Construct();
105 Open (device, dir, numChannels, sampleRate, bitsPerSample);
109 void PSoundChannelALSA::Construct()
111 frame_bytes = 0;
112 period_size = 0;
113 periods = 0;
114 card_nr = 0;
115 os_handle = NULL;
119 PSoundChannelALSA::~PSoundChannelALSA()
121 Close();
125 PStringArray PSoundChannelALSA::GetDeviceNames (Directions dir)
127 PStringArray devices;
129 int card = -1, dev = -1;
131 snd_ctl_t *handle = NULL;
132 snd_ctl_card_info_t *info = NULL;
133 snd_pcm_info_t *pcminfo = NULL;
134 snd_pcm_stream_t stream;
136 char *name = NULL;
137 char card_id [32];
139 if (dir == Recorder) {
141 stream = SND_PCM_STREAM_CAPTURE;
142 capture_devices = PStringToOrdinal ();
144 else {
146 stream = SND_PCM_STREAM_PLAYBACK;
147 playback_devices = PStringToOrdinal ();
150 snd_ctl_card_info_alloca (&info);
151 snd_pcm_info_alloca (&pcminfo);
153 /* No sound card found */
154 if (snd_card_next (&card) < 0 || card < 0) {
156 return PStringArray ();
160 while (card >= 0) {
162 snprintf (card_id, 32, "hw:%d", card);
164 if (snd_ctl_open (&handle, card_id, 0) == 0) {
166 snd_ctl_card_info (handle, info);
168 while (1) {
170 snd_ctl_pcm_next_device (handle, &dev);
172 if (dev < 0)
173 break;
175 snd_pcm_info_set_device (pcminfo, dev);
176 snd_pcm_info_set_subdevice (pcminfo, 0);
177 snd_pcm_info_set_stream (pcminfo, stream);
179 if (snd_ctl_pcm_info (handle, pcminfo) >= 0) {
181 snd_card_get_name (card, &name);
182 if (dir == Recorder) {
184 capture_devices.SetAt (name, card);
186 else {
188 playback_devices.SetAt (name, card);
191 free (name);
196 snd_ctl_close(handle);
197 snd_card_next (&card);
200 PStringToOrdinal devices_dict;
201 if (dir == Recorder)
202 devices_dict = capture_devices;
203 else
204 devices_dict = playback_devices;
206 for (PINDEX j = 0 ; j < devices_dict.GetSize () ; j++)
207 devices += devices_dict.GetKeyAt (j);
210 if (dir != Recorder && devices.GetSize () > 0)
211 devices += "DMIX Plugin";
213 return devices;
217 PString PSoundChannelALSA::GetDefaultDevice(Directions dir)
219 PStringArray devicenames;
220 devicenames = PSoundChannelALSA::GetDeviceNames (dir);
222 return devicenames[0];
226 BOOL PSoundChannelALSA::Open (const PString & _device,
227 Directions _dir,
228 unsigned _numChannels,
229 unsigned _sampleRate,
230 unsigned _bitsPerSample)
232 PString real_device_name;
233 POrdinalKey *i = NULL;
234 snd_pcm_stream_t stream;
236 Close();
238 os_handle = NULL;
240 if (_dir == Recorder)
241 stream = SND_PCM_STREAM_CAPTURE;
242 else
243 stream = SND_PCM_STREAM_PLAYBACK;
245 /* Open in NONBLOCK mode */
246 if (_dir != Recorder && _device == "DMIX Plugin") {
248 real_device_name = "plug:dmix";
250 else {
252 i = (_dir == Recorder) ? capture_devices.GetAt (_device) : playback_devices.GetAt (_device);
254 if (i) {
256 real_device_name = "plughw:" + PString (*i);
257 card_nr = *i;
259 else {
261 PTRACE (1, "ALSA\tDevice not found");
262 return FALSE;
266 if (snd_pcm_open (&os_handle, real_device_name, stream, SND_PCM_NONBLOCK) < 0) {
268 PTRACE (1, "ALSA\tOpen Failed");
269 return FALSE;
271 else
272 snd_pcm_nonblock (os_handle, 0);
274 /* save internal parameters */
275 direction = _dir;
276 device = real_device_name;
277 mNumChannels = _numChannels;
278 mSampleRate = _sampleRate;
279 mBitsPerSample = _bitsPerSample;
280 isInitialised = FALSE;
282 PTRACE (1, "ALSA\tDevice " << real_device_name << " Opened");
284 return TRUE;
288 BOOL PSoundChannelALSA::Setup()
290 snd_pcm_hw_params_t *hw_params = NULL;
291 snd_pcm_uframes_t buffer_size = 0;
293 int err = 0;
294 enum _snd_pcm_format val = SND_PCM_FORMAT_UNKNOWN;
295 BOOL no_error = TRUE;
298 if (os_handle == NULL) {
300 PTRACE(6, "ALSA\tSkipping setup of " << device << " as not open");
301 return FALSE;
304 if (isInitialised) {
306 PTRACE(6, "ALSA\tSkipping setup of " << device << " as instance already initialised");
307 return TRUE;
311 #if PBYTE_ORDER == PLITTLE_ENDIAN
312 val = (mBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U8;
313 #else
314 val = (mBitsPerSample == 16) ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_U8;
315 #endif
317 frame_bytes = (mNumChannels * (snd_pcm_format_width (val) / 8));
319 snd_pcm_hw_params_alloca (&hw_params);
321 if ((err = snd_pcm_hw_params_any (os_handle, hw_params)) < 0) {
323 PTRACE (1, "ALSA\tCannot initialize hardware parameter structure " <<
324 snd_strerror (err));
325 no_error = FALSE;
329 if ((err = snd_pcm_hw_params_set_access (os_handle, hw_params,
330 SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
332 PTRACE (1, "ALSA\tCannot set access type " <<
333 snd_strerror (err));
334 no_error = FALSE;
338 if ((err = snd_pcm_hw_params_set_format (os_handle, hw_params, val)) < 0) {
340 PTRACE (1, "ALSA\tCannot set sample format " <<
341 snd_strerror (err));
342 no_error = FALSE;
346 if ((err = snd_pcm_hw_params_set_rate_near (os_handle,
347 hw_params,
348 &mSampleRate,
349 NULL)) < 0) {
351 PTRACE (1, "ALSA\tCannot set sample rate " <<
352 snd_strerror (err));
353 no_error = FALSE;
357 if ((err = snd_pcm_hw_params_set_channels (os_handle, hw_params,
358 mNumChannels)) < 0) {
360 PTRACE (1, "ALSA\tCannot set channel count " <<
361 snd_strerror (err));
362 no_error = FALSE;
366 // Ignore errors here
367 if (periods && period_size) {
369 if ((err = snd_pcm_hw_params_set_period_size_near (os_handle,
370 hw_params,
371 &period_size,
372 0)) < 0)
373 PTRACE (1, "ALSA\tCannot set period size " <<
374 snd_strerror (err));
376 if ((err = snd_pcm_hw_params_set_periods_near (os_handle,
377 hw_params,
378 &periods,
379 0)) < 0)
380 PTRACE (1, "ALSA\tCannot set number of periods " <<
381 snd_strerror (err));
383 buffer_size = periods*period_size/frame_bytes;
385 if ((err = (int) snd_pcm_hw_params_set_buffer_size_near (os_handle,
386 hw_params,
387 &buffer_size))
388 < 0)
389 PTRACE (1, "ALSA\tCannot set buffer size " <<
390 snd_strerror (err));
394 if ((err = snd_pcm_hw_params (os_handle, hw_params)) < 0) {
396 PTRACE (1, "ALSA\tCannot set parameters " <<
397 snd_strerror (err));
398 no_error = FALSE;
402 isInitialised = TRUE;
404 return no_error;
408 BOOL PSoundChannelALSA::Close()
410 PWaitAndSignal m(device_mutex);
412 /* if the channel isn't open, do nothing */
413 if (!os_handle)
414 return FALSE;
416 snd_pcm_close (os_handle);
417 os_handle = NULL;
419 return TRUE;
423 BOOL PSoundChannelALSA::Write (const void *buf, PINDEX len)
425 long r = 0;
426 char *buf2 = (char *) buf;
427 int pos = 0, max_try = 0;
429 PWaitAndSignal m(device_mutex);
431 if (!isInitialised && !Setup() || !len || !os_handle)
432 return FALSE;
434 do {
437 /* the number of frames to read is the buffer length
438 divided by the size of one frame */
439 r = snd_pcm_writei (os_handle, (char *) &buf2 [pos], len / frame_bytes);
441 if (r > 0) {
443 pos += r * frame_bytes;
444 len -= r * frame_bytes;
446 else {
448 if (r == -EPIPE) { /* under-run */
450 r = snd_pcm_prepare (os_handle);
451 if (r < 0)
452 PTRACE (1, "ALSA\tCould not prepare device: " << snd_strerror (r));
454 } else if (r == -ESTRPIPE) {
456 while ((r = snd_pcm_resume (os_handle)) == -EAGAIN)
457 sleep(1); /* wait until the suspend flag is released */
459 if (r < 0)
460 snd_pcm_prepare (os_handle);
463 PTRACE (1, "ALSA\tCould not write "
464 << max_try << " " << len << " " << r);
465 max_try++;
468 } while (len > 0 && max_try < 5);
471 return TRUE;
475 BOOL PSoundChannelALSA::Read (void * buf, PINDEX len)
477 long r = 0;
479 char *buf2 = (char *) buf;
480 int pos = 0, max_try = 0;
482 lastReadCount = 0;
484 PWaitAndSignal m(device_mutex);
486 if (!isInitialised && !Setup() || !len || !os_handle)
487 return FALSE;
489 memset ((char *) buf, 0, len);
491 do {
493 /* the number of frames to read is the buffer length
494 divided by the size of one frame */
495 r = snd_pcm_readi (os_handle, (char *) &buf2 [pos], len / frame_bytes);
497 if (r > 0) {
499 pos += r * frame_bytes;
500 len -= r * frame_bytes;
501 lastReadCount += r * frame_bytes;
503 else {
505 if (r == -EPIPE) { /* under-run */
507 snd_pcm_prepare (os_handle);
509 } else if (r == -ESTRPIPE) {
511 while ((r = snd_pcm_resume (os_handle)) == -EAGAIN)
512 sleep(1); /* wait until the suspend flag is released */
514 if (r < 0)
515 snd_pcm_prepare (os_handle);
518 PTRACE (1, "ALSA\tCould not read");
519 max_try++;
521 } while (len > 0 && max_try < 5);
524 if (len != 0) {
526 memset ((char *) &buf2 [pos], 0, len);
527 lastReadCount += len;
529 PTRACE (1, "ALSA\tRead Error, filling with zeros");
532 return TRUE;
536 BOOL PSoundChannelALSA::SetFormat (unsigned numChannels,
537 unsigned sampleRate,
538 unsigned bitsPerSample)
540 if (!os_handle)
541 return SetErrorValues(NotOpen, EBADF);
543 /* check parameters */
544 PAssert((bitsPerSample == 8) || (bitsPerSample == 16), PInvalidParameter);
545 PAssert(numChannels >= 1 && numChannels <= 2, PInvalidParameter);
547 mNumChannels = numChannels;
548 mSampleRate = sampleRate;
549 mBitsPerSample = bitsPerSample;
551 /* mark this channel as uninitialised */
552 isInitialised = FALSE;
554 return TRUE;
558 unsigned PSoundChannelALSA::GetChannels() const
560 return mNumChannels;
564 unsigned PSoundChannelALSA::GetSampleRate() const
566 return mSampleRate;
570 unsigned PSoundChannelALSA::GetSampleSize() const
572 return mBitsPerSample;
576 BOOL PSoundChannelALSA::SetBuffers (PINDEX size, PINDEX count)
578 periods = count;
579 period_size = size / (frame_bytes ? frame_bytes : 2);
581 return TRUE;
585 BOOL PSoundChannelALSA::GetBuffers(PINDEX & size, PINDEX & count)
587 size = period_size * (frame_bytes ? frame_bytes : 2);
588 count = periods;
590 return FALSE;
594 BOOL PSoundChannelALSA::PlaySound(const PSound & sound, BOOL wait)
596 if (!os_handle)
597 return SetErrorValues(NotOpen, EBADF);
599 if (!Write((const BYTE *)sound, sound.GetSize()))
600 return FALSE;
602 if (wait)
603 return WaitForPlayCompletion();
605 return TRUE;
609 BOOL PSoundChannelALSA::PlayFile(const PFilePath & filename, BOOL wait)
611 BYTE buffer [512];
613 if (!os_handle)
614 return SetErrorValues(NotOpen, EBADF);
616 PFile file (filename, PFile::ReadOnly);
618 if (!file.IsOpen())
619 return FALSE;
621 for (;;) {
623 if (!file.Read (buffer, 512))
624 break;
626 PINDEX len = file.GetLastReadCount();
627 if (len == 0)
628 break;
629 if (!Write(buffer, len))
630 break;
633 file.Close();
635 if (wait)
636 return WaitForPlayCompletion();
638 return TRUE;
642 BOOL PSoundChannelALSA::HasPlayCompleted()
644 if (!os_handle)
645 return SetErrorValues(NotOpen, EBADF);
647 return (snd_pcm_state (os_handle) != SND_PCM_STATE_RUNNING);
651 BOOL PSoundChannelALSA::WaitForPlayCompletion()
653 if (!os_handle)
654 return SetErrorValues(NotOpen, EBADF);
656 snd_pcm_drain (os_handle);
658 return TRUE;
662 BOOL PSoundChannelALSA::RecordSound(PSound & sound)
664 return FALSE;
668 BOOL PSoundChannelALSA::RecordFile(const PFilePath & filename)
670 return FALSE;
674 BOOL PSoundChannelALSA::StartRecording()
676 return FALSE;
680 BOOL PSoundChannelALSA::IsRecordBufferFull()
682 return TRUE;
686 BOOL PSoundChannelALSA::AreAllRecordBuffersFull()
688 return TRUE;
692 BOOL PSoundChannelALSA::WaitForRecordBufferFull()
694 return TRUE;
698 BOOL PSoundChannelALSA::WaitForAllRecordBuffersFull()
700 return FALSE;
704 BOOL PSoundChannelALSA::Abort()
706 int r = 0;
708 if (!os_handle)
709 return FALSE;
711 if ((r = snd_pcm_drain (os_handle)) < 0) {
713 PTRACE (1, "ALSA\tCannot abort" <<
714 snd_strerror (r));
715 return FALSE;
717 else
718 return TRUE;
723 BOOL PSoundChannelALSA::SetVolume (unsigned newVal)
725 unsigned i = 0;
727 return Volume (TRUE, newVal, i);
731 BOOL PSoundChannelALSA::GetVolume(unsigned &devVol)
733 return Volume (FALSE, 0, devVol);
737 BOOL PSoundChannelALSA::IsOpen () const
739 return (os_handle != NULL);
743 BOOL PSoundChannelALSA::Volume (BOOL set, unsigned set_vol, unsigned &get_vol)
745 int err = 0;
746 snd_mixer_t *handle;
747 snd_mixer_elem_t *elem;
748 snd_mixer_selem_id_t *sid;
750 const char *play_mix_name = (direction == Player) ? "PCM": "Mic";
751 PString card_name;
753 long pmin = 0, pmax = 0;
754 long int vol = 0;
756 if (!os_handle)
757 return FALSE;
759 card_name = "hw:" + PString (card_nr);
761 //allocate simple id
762 snd_mixer_selem_id_alloca (&sid);
764 //sets simple-mixer index and name
765 snd_mixer_selem_id_set_index (sid, 0);
766 snd_mixer_selem_id_set_name (sid, play_mix_name);
768 if ((err = snd_mixer_open (&handle, 0)) < 0) {
770 PTRACE (1, "alsa-control: mixer open error: " << snd_strerror (err));
771 return FALSE;
775 if ((err = snd_mixer_attach (handle, card_name)) < 0) {
777 PTRACE (1, "alsa-control: mixer attach " << card_name << " error: "
778 << snd_strerror(err));
779 snd_mixer_close(handle);
780 return FALSE;
784 if ((err = snd_mixer_selem_register (handle, NULL, NULL)) < 0) {
786 PTRACE (1, "alsa-control: mixer register error: " << snd_strerror(err));
787 snd_mixer_close(handle);
788 return FALSE;
792 err = snd_mixer_load(handle);
793 if (err < 0) {
795 PTRACE (1, "alsa-control: mixer load error: " << snd_strerror(err));
796 snd_mixer_close(handle);
797 return FALSE;
800 elem = snd_mixer_find_selem (handle, sid);
802 if (!elem) {
804 PTRACE (1, "alsa-control: unable to find simple control "
805 << snd_mixer_selem_id_get_name(sid) << ","
806 << snd_mixer_selem_id_get_index(sid));
807 snd_mixer_close(handle);
808 return FALSE;
811 snd_mixer_selem_get_playback_volume_range (elem, &pmin, &pmax);
813 if (set) {
815 vol = (set_vol * (pmax?pmax:31)) / 100;
816 snd_mixer_selem_set_playback_volume (elem,
817 SND_MIXER_SCHN_FRONT_LEFT, vol);
818 snd_mixer_selem_set_playback_volume (elem,
819 SND_MIXER_SCHN_FRONT_RIGHT, vol);
821 PTRACE (4, "Set volume to " << vol);
823 else {
825 snd_mixer_selem_get_playback_volume (elem,
826 SND_MIXER_SCHN_FRONT_LEFT, &vol);
827 get_vol = (vol * 100) / (pmax?pmax:31);
828 PTRACE (4, "Got volume " << vol);
831 snd_mixer_close(handle);
833 return TRUE;