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
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.
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
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
,
100 unsigned numChannels
,
102 unsigned bitsPerSample
)
105 Open (device
, dir
, numChannels
, sampleRate
, bitsPerSample
);
109 void PSoundChannelALSA::Construct()
119 PSoundChannelALSA::~PSoundChannelALSA()
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
;
139 if (dir
== Recorder
) {
141 stream
= SND_PCM_STREAM_CAPTURE
;
142 capture_devices
= PStringToOrdinal ();
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 ();
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
);
170 snd_ctl_pcm_next_device (handle
, &dev
);
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
);
188 playback_devices
.SetAt (name
, card
);
196 snd_ctl_close(handle
);
197 snd_card_next (&card
);
200 PStringToOrdinal devices_dict
;
202 devices_dict
= capture_devices
;
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";
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
,
228 unsigned _numChannels
,
229 unsigned _sampleRate
,
230 unsigned _bitsPerSample
)
232 PString real_device_name
;
233 POrdinalKey
*i
= NULL
;
234 snd_pcm_stream_t stream
;
240 if (_dir
== Recorder
)
241 stream
= SND_PCM_STREAM_CAPTURE
;
243 stream
= SND_PCM_STREAM_PLAYBACK
;
245 /* Open in NONBLOCK mode */
246 if (_dir
!= Recorder
&& _device
== "DMIX Plugin") {
248 real_device_name
= "plug:dmix";
252 i
= (_dir
== Recorder
) ? capture_devices
.GetAt (_device
) : playback_devices
.GetAt (_device
);
256 real_device_name
= "plughw:" + PString (*i
);
261 PTRACE (1, "ALSA\tDevice not found");
266 if (snd_pcm_open (&os_handle
, real_device_name
, stream
, SND_PCM_NONBLOCK
) < 0) {
268 PTRACE (1, "ALSA\tOpen Failed");
272 snd_pcm_nonblock (os_handle
, 0);
274 /* save internal parameters */
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");
288 BOOL
PSoundChannelALSA::Setup()
290 snd_pcm_hw_params_t
*hw_params
= NULL
;
291 snd_pcm_uframes_t buffer_size
= 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");
306 PTRACE(6, "ALSA\tSkipping setup of " << device
<< " as instance already initialised");
311 #if PBYTE_ORDER == PLITTLE_ENDIAN
312 val
= (mBitsPerSample
== 16) ? SND_PCM_FORMAT_S16_LE
: SND_PCM_FORMAT_U8
;
314 val
= (mBitsPerSample
== 16) ? SND_PCM_FORMAT_S16_BE
: SND_PCM_FORMAT_U8
;
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 " <<
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 " <<
338 if ((err
= snd_pcm_hw_params_set_format (os_handle
, hw_params
, val
)) < 0) {
340 PTRACE (1, "ALSA\tCannot set sample format " <<
346 if ((err
= snd_pcm_hw_params_set_rate_near (os_handle
,
351 PTRACE (1, "ALSA\tCannot set sample rate " <<
357 if ((err
= snd_pcm_hw_params_set_channels (os_handle
, hw_params
,
358 mNumChannels
)) < 0) {
360 PTRACE (1, "ALSA\tCannot set channel count " <<
366 // Ignore errors here
367 if (periods
&& period_size
) {
369 if ((err
= snd_pcm_hw_params_set_period_size_near (os_handle
,
373 PTRACE (1, "ALSA\tCannot set period size " <<
376 if ((err
= snd_pcm_hw_params_set_periods_near (os_handle
,
380 PTRACE (1, "ALSA\tCannot set number of periods " <<
383 buffer_size
= periods
*period_size
/frame_bytes
;
385 if ((err
= (int) snd_pcm_hw_params_set_buffer_size_near (os_handle
,
389 PTRACE (1, "ALSA\tCannot set buffer size " <<
394 if ((err
= snd_pcm_hw_params (os_handle
, hw_params
)) < 0) {
396 PTRACE (1, "ALSA\tCannot set parameters " <<
402 isInitialised
= TRUE
;
408 BOOL
PSoundChannelALSA::Close()
410 PWaitAndSignal
m(device_mutex
);
412 /* if the channel isn't open, do nothing */
416 snd_pcm_close (os_handle
);
423 BOOL
PSoundChannelALSA::Write (const void *buf
, PINDEX len
)
426 char *buf2
= (char *) buf
;
427 int pos
= 0, max_try
= 0;
429 PWaitAndSignal
m(device_mutex
);
431 if (!isInitialised
&& !Setup() || !len
|| !os_handle
)
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
);
443 pos
+= r
* frame_bytes
;
444 len
-= r
* frame_bytes
;
448 if (r
== -EPIPE
) { /* under-run */
450 r
= snd_pcm_prepare (os_handle
);
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 */
460 snd_pcm_prepare (os_handle
);
463 PTRACE (1, "ALSA\tCould not write "
464 << max_try
<< " " << len
<< " " << r
);
468 } while (len
> 0 && max_try
< 5);
475 BOOL
PSoundChannelALSA::Read (void * buf
, PINDEX len
)
479 char *buf2
= (char *) buf
;
480 int pos
= 0, max_try
= 0;
484 PWaitAndSignal
m(device_mutex
);
486 if (!isInitialised
&& !Setup() || !len
|| !os_handle
)
489 memset ((char *) buf
, 0, len
);
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
);
499 pos
+= r
* frame_bytes
;
500 len
-= r
* frame_bytes
;
501 lastReadCount
+= r
* frame_bytes
;
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 */
515 snd_pcm_prepare (os_handle
);
518 PTRACE (1, "ALSA\tCould not read");
521 } while (len
> 0 && max_try
< 5);
526 memset ((char *) &buf2
[pos
], 0, len
);
527 lastReadCount
+= len
;
529 PTRACE (1, "ALSA\tRead Error, filling with zeros");
536 BOOL
PSoundChannelALSA::SetFormat (unsigned numChannels
,
538 unsigned bitsPerSample
)
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
;
558 unsigned PSoundChannelALSA::GetChannels() const
564 unsigned PSoundChannelALSA::GetSampleRate() const
570 unsigned PSoundChannelALSA::GetSampleSize() const
572 return mBitsPerSample
;
576 BOOL
PSoundChannelALSA::SetBuffers (PINDEX size
, PINDEX count
)
579 period_size
= size
/ (frame_bytes
? frame_bytes
: 2);
585 BOOL
PSoundChannelALSA::GetBuffers(PINDEX
& size
, PINDEX
& count
)
587 size
= period_size
* (frame_bytes
? frame_bytes
: 2);
594 BOOL
PSoundChannelALSA::PlaySound(const PSound
& sound
, BOOL wait
)
597 return SetErrorValues(NotOpen
, EBADF
);
599 if (!Write((const BYTE
*)sound
, sound
.GetSize()))
603 return WaitForPlayCompletion();
609 BOOL
PSoundChannelALSA::PlayFile(const PFilePath
& filename
, BOOL wait
)
614 return SetErrorValues(NotOpen
, EBADF
);
616 PFile
file (filename
, PFile::ReadOnly
);
623 if (!file
.Read (buffer
, 512))
626 PINDEX len
= file
.GetLastReadCount();
629 if (!Write(buffer
, len
))
636 return WaitForPlayCompletion();
642 BOOL
PSoundChannelALSA::HasPlayCompleted()
645 return SetErrorValues(NotOpen
, EBADF
);
647 return (snd_pcm_state (os_handle
) != SND_PCM_STATE_RUNNING
);
651 BOOL
PSoundChannelALSA::WaitForPlayCompletion()
654 return SetErrorValues(NotOpen
, EBADF
);
656 snd_pcm_drain (os_handle
);
662 BOOL
PSoundChannelALSA::RecordSound(PSound
& sound
)
668 BOOL
PSoundChannelALSA::RecordFile(const PFilePath
& filename
)
674 BOOL
PSoundChannelALSA::StartRecording()
680 BOOL
PSoundChannelALSA::IsRecordBufferFull()
686 BOOL
PSoundChannelALSA::AreAllRecordBuffersFull()
692 BOOL
PSoundChannelALSA::WaitForRecordBufferFull()
698 BOOL
PSoundChannelALSA::WaitForAllRecordBuffersFull()
704 BOOL
PSoundChannelALSA::Abort()
711 if ((r
= snd_pcm_drain (os_handle
)) < 0) {
713 PTRACE (1, "ALSA\tCannot abort" <<
723 BOOL
PSoundChannelALSA::SetVolume (unsigned newVal
)
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
)
747 snd_mixer_elem_t
*elem
;
748 snd_mixer_selem_id_t
*sid
;
750 const char *play_mix_name
= (direction
== Player
) ? "PCM": "Mic";
753 long pmin
= 0, pmax
= 0;
759 card_name
= "hw:" + PString (card_nr
);
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
));
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
);
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
);
792 err
= snd_mixer_load(handle
);
795 PTRACE (1, "alsa-control: mixer load error: " << snd_strerror(err
));
796 snd_mixer_close(handle
);
800 elem
= snd_mixer_find_selem (handle
, sid
);
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
);
811 snd_mixer_selem_get_playback_volume_range (elem
, &pmin
, &pmax
);
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
);
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
);