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.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
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
,
93 unsigned bitsPerSample
)
96 Open (device
, dir
, numChannels
, sampleRate
, bitsPerSample
);
100 void PSoundChannelALSA::Construct()
110 PSoundChannelALSA::~PSoundChannelALSA()
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
;
128 if (dir
== Recorder
) {
130 stream
= SND_PCM_STREAM_CAPTURE
;
131 capture_devices
= PStringArray ();
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 ();
151 snprintf (card_id
, 32, "hw:%d", card
);
153 snd_ctl_open (&handle
, card_id
, 0);
154 snd_ctl_card_info (handle
, info
);
158 snd_ctl_pcm_next_device (handle
, &dev
);
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
);
177 if (playback_devices
.GetStringsIndex (name
) == P_MAX_INDEX
)
178 playback_devices
.AppendString (name
);
186 snd_ctl_close(handle
);
187 snd_card_next (&card
);
192 return capture_devices
;
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
,
209 unsigned _numChannels
,
210 unsigned _sampleRate
,
211 unsigned _bitsPerSample
)
213 PString real_device_name
;
215 snd_pcm_stream_t stream
;
221 if (_dir
== Recorder
)
222 stream
= SND_PCM_STREAM_CAPTURE
;
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
);
234 PTRACE (1, "ALSA\tDevice unavailable");
238 if (snd_pcm_open (&os_handle
, real_device_name
, stream
, SND_PCM_NONBLOCK
) < 0) {
240 PTRACE (1, "ALSA\tOpen Failed");
244 snd_pcm_nonblock (os_handle
, 0);
246 /* save internal parameters */
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");
260 BOOL
PSoundChannelALSA::Setup()
262 snd_pcm_hw_params_t
*hw_params
= NULL
;
263 snd_pcm_uframes_t buffer_size
= 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");
278 PTRACE(6, "ALSA\tSkipping setup of " << device
<< " as instance already initialised");
283 #if PBYTE_ORDER == PLITTLE_ENDIAN
284 val
= (mBitsPerSample
== 16) ? SND_PCM_FORMAT_S16_LE
: SND_PCM_FORMAT_U8
;
286 val
= (mBitsPerSample
== 16) ? SND_PCM_FORMAT_S16_BE
: SND_PCM_FORMAT_U8
;
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 " <<
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 " <<
310 if ((err
= snd_pcm_hw_params_set_format (os_handle
, hw_params
, val
)) < 0) {
312 PTRACE (1, "ALSA\tCannot set sample format " <<
318 if ((err
= snd_pcm_hw_params_set_rate_near (os_handle
,
323 PTRACE (1, "ALSA\tCannot set sample rate " <<
329 if ((err
= snd_pcm_hw_params_set_channels (os_handle
, hw_params
,
330 mNumChannels
)) < 0) {
332 PTRACE (1, "ALSA\tCannot set channel count " <<
338 // Ignore errors here
339 if (periods
&& period_size
) {
341 if ((err
= snd_pcm_hw_params_set_period_size_near (os_handle
,
345 PTRACE (1, "ALSA\tCannot set period size " <<
348 if ((err
= snd_pcm_hw_params_set_periods_near (os_handle
,
352 PTRACE (1, "ALSA\tCannot set number of periods " <<
355 buffer_size
= periods
*period_size
/frame_bytes
;
357 if ((err
= (int) snd_pcm_hw_params_set_buffer_size_near (os_handle
,
361 PTRACE (1, "ALSA\tCannot set buffer size " <<
366 if ((err
= snd_pcm_hw_params (os_handle
, hw_params
)) < 0) {
368 PTRACE (1, "ALSA\tCannot set parameters " <<
374 isInitialised
= TRUE
;
380 BOOL
PSoundChannelALSA::Close()
382 PWaitAndSignal
m(device_mutex
);
384 /* if the channel isn't open, do nothing */
388 snd_pcm_close (os_handle
);
395 BOOL
PSoundChannelALSA::Write (const void *buf
, PINDEX len
)
398 char *buf2
= (char *) buf
;
399 int pos
= 0, max_try
= 0;
401 PWaitAndSignal
m(device_mutex
);
403 if (!isInitialised
&& !Setup() || !len
|| !os_handle
)
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
);
415 pos
+= r
* frame_bytes
;
416 len
-= r
* frame_bytes
;
420 if (r
== -EPIPE
) { /* under-run */
422 r
= snd_pcm_prepare (os_handle
);
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 */
432 snd_pcm_prepare (os_handle
);
435 PTRACE (1, "ALSA\tCould not write "
436 << max_try
<< " " << len
<< " " << r
);
440 } while (len
> 0 && max_try
< 5);
447 BOOL
PSoundChannelALSA::Read (void * buf
, PINDEX len
)
451 char *buf2
= (char *) buf
;
452 int pos
= 0, max_try
= 0;
456 PWaitAndSignal
m(device_mutex
);
458 if (!isInitialised
&& !Setup() || !len
|| !os_handle
)
461 memset ((char *) buf
, 0, len
);
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
);
471 pos
+= r
* frame_bytes
;
472 len
-= r
* frame_bytes
;
473 lastReadCount
+= r
* frame_bytes
;
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 */
487 snd_pcm_prepare (os_handle
);
490 PTRACE (1, "ALSA\tCould not read");
493 } while (len
> 0 && max_try
< 5);
498 memset ((char *) &buf2
[pos
], 0, len
);
499 lastReadCount
+= len
;
501 PTRACE (1, "ALSA\tRead Error, filling with zeros");
508 BOOL
PSoundChannelALSA::SetFormat (unsigned numChannels
,
510 unsigned bitsPerSample
)
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
;
530 unsigned PSoundChannelALSA::GetChannels() const
536 unsigned PSoundChannelALSA::GetSampleRate() const
542 unsigned PSoundChannelALSA::GetSampleSize() const
544 return mBitsPerSample
;
548 BOOL
PSoundChannelALSA::SetBuffers (PINDEX size
, PINDEX count
)
551 period_size
= size
/ (frame_bytes
? frame_bytes
: 2);
557 BOOL
PSoundChannelALSA::GetBuffers(PINDEX
& size
, PINDEX
& count
)
559 size
= period_size
* (frame_bytes
? frame_bytes
: 2);
566 BOOL
PSoundChannelALSA::PlaySound(const PSound
& sound
, BOOL wait
)
569 return SetErrorValues(NotOpen
, EBADF
);
571 if (!Write((const BYTE
*)sound
, sound
.GetSize()))
575 return WaitForPlayCompletion();
581 BOOL
PSoundChannelALSA::PlayFile(const PFilePath
& filename
, BOOL wait
)
586 return SetErrorValues(NotOpen
, EBADF
);
588 PFile
file (filename
, PFile::ReadOnly
);
595 if (!file
.Read (buffer
, 512))
598 PINDEX len
= file
.GetLastReadCount();
601 if (!Write(buffer
, len
))
608 return WaitForPlayCompletion();
614 BOOL
PSoundChannelALSA::HasPlayCompleted()
617 return SetErrorValues(NotOpen
, EBADF
);
619 return (snd_pcm_state (os_handle
) != SND_PCM_STATE_RUNNING
);
623 BOOL
PSoundChannelALSA::WaitForPlayCompletion()
626 return SetErrorValues(NotOpen
, EBADF
);
628 snd_pcm_drain (os_handle
);
634 BOOL
PSoundChannelALSA::RecordSound(PSound
& sound
)
640 BOOL
PSoundChannelALSA::RecordFile(const PFilePath
& filename
)
646 BOOL
PSoundChannelALSA::StartRecording()
652 BOOL
PSoundChannelALSA::IsRecordBufferFull()
658 BOOL
PSoundChannelALSA::AreAllRecordBuffersFull()
664 BOOL
PSoundChannelALSA::WaitForRecordBufferFull()
670 BOOL
PSoundChannelALSA::WaitForAllRecordBuffersFull()
676 BOOL
PSoundChannelALSA::Abort()
683 if ((r
= snd_pcm_drain (os_handle
)) < 0) {
685 PTRACE (1, "ALSA\tCannot abort" <<
695 BOOL
PSoundChannelALSA::SetVolume (unsigned newVal
)
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
)
719 snd_mixer_elem_t
*elem
;
720 snd_mixer_selem_id_t
*sid
;
722 const char *play_mix_name
= (direction
== Player
) ? "PCM": "Mic";
725 long pmin
= 0, pmax
= 0;
731 card_name
= "hw:" + PString (card_nr
);
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
));
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
);
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
);
764 err
= snd_mixer_load(handle
);
767 PTRACE (1, "alsa-control: mixer load error: " << snd_strerror(err
));
768 snd_mixer_close(handle
);
772 elem
= snd_mixer_find_selem (handle
, sid
);
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
);
783 snd_mixer_selem_get_playback_volume_range (elem
, &pmin
, &pmax
);
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
);
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
);