summaryrefslogtreecommitdiffstats
path: root/flow/audioioalsa.cc
diff options
context:
space:
mode:
Diffstat (limited to 'flow/audioioalsa.cc')
-rw-r--r--flow/audioioalsa.cc561
1 files changed, 561 insertions, 0 deletions
diff --git a/flow/audioioalsa.cc b/flow/audioioalsa.cc
new file mode 100644
index 0000000..303db16
--- /dev/null
+++ b/flow/audioioalsa.cc
@@ -0,0 +1,561 @@
+ /*
+
+ Copyright (C) 2000,2001 Jozef Kosoru
+ jozef.kosoru@pobox.sk
+ (C) 2000,2001 Stefan Westerfeld
+ stefan@space.twc.de
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/**
+ * only compile 'alsa' AudioIO class if configure thinks it is a good idea
+ */
+#ifdef HAVE_LIBASOUND
+
+#ifdef HAVE_ALSA_ASOUNDLIB_H
+#include <alsa/asoundlib.h>
+#elif defined(HAVE_SYS_ASOUNDLIB_H)
+#include <sys/asoundlib.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h> // Needed on some systems.
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <iostream>
+#include <algorithm>
+
+#include "debug.h"
+#include "audioio.h"
+
+namespace Arts {
+
+class AudioIOALSA : public AudioIO {
+protected:
+ int audio_read_fd;
+ int audio_write_fd;
+ int requestedFragmentSize;
+ int requestedFragmentCount;
+
+ enum BufferMode{block, stream};
+ int m_card;
+ int m_device;
+ int m_format;
+ BufferMode m_bufferMode;
+
+ snd_pcm_t *m_pcm_handle;
+ snd_pcm_channel_info_t m_cinfo;
+ snd_pcm_format_t m_cformat;
+ snd_pcm_channel_params_t m_params;
+ snd_pcm_channel_setup_t m_setup;
+
+ int setPcmParams(const int channel);
+ void checkCapabilities();
+
+public:
+ AudioIOALSA();
+
+ void setParam(AudioParam param, int& value);
+ int getParam(AudioParam param);
+
+ bool open();
+ void close();
+ int read(void *buffer, int size);
+ int write(void *buffer, int size);
+};
+
+REGISTER_AUDIO_IO(AudioIOALSA,"alsa","Advanced Linux Sound Architecture");
+};
+
+using namespace std;
+using namespace Arts;
+
+AudioIOALSA::AudioIOALSA()
+{
+ param(samplingRate) = 44100;
+ paramStr(deviceName) = "/dev/dsp"; //!! alsa doesn't need this
+ requestedFragmentSize = param(fragmentSize) = 1024;
+ requestedFragmentCount = param(fragmentCount) = 7;
+ param(channels) = 2;
+ param(direction) = directionWrite;
+
+ /*
+ * default parameters
+ */
+ m_card = snd_defaults_pcm_card(); //!! need interface !!
+ m_device = snd_defaults_pcm_device(); //!!
+#ifdef WORDS_BIGENDIAN
+ m_format = SND_PCM_SFMT_S16_BE;
+#else
+ m_format = SND_PCM_SFMT_S16_LE;
+#endif
+ m_bufferMode = block; //block/stream (stream mode doesn't work yet)
+
+ if(m_card >= 0) {
+ char* cardname = 0;
+
+ if(snd_card_get_name(m_card, &cardname) == 0 && cardname != 0)
+ {
+ //!! thats not what devicename is intended to do
+ //!! devicename is an input information into
+ //!! the "driver", to select which card to use
+ //!! not an output information
+ paramStr(deviceName) = cardname;
+ free(cardname);
+ }
+ }
+}
+
+bool AudioIOALSA::open()
+{
+ string& _error = paramStr(lastError);
+ string& _deviceName = paramStr(deviceName);
+ int& _channels = param(channels);
+ int& _fragmentSize = param(fragmentSize);
+ int& _fragmentCount = param(fragmentCount);
+ int& _samplingRate = param(samplingRate);
+ int& _direction = param(direction);
+ int& _format = param(format);
+
+ /*
+ * initialize format - TODO: implement fallback (i.e. if no format given,
+ * it should try 16bit first, then fall back to 8bit)
+ */
+ switch(_format)
+ {
+ default: _format = 16;
+
+ case 16: // 16bit, signed little endian
+ m_format = SND_PCM_SFMT_S16_LE;
+ break;
+
+ case 17: // 16bit, signed big endian
+ m_format = SND_PCM_SFMT_S16_BE;
+ break;
+
+ case 8: // 8bit, unsigned
+ m_format = SND_PCM_SFMT_U8;
+ break;
+ }
+
+ /* open pcm device */
+ int mode = SND_PCM_OPEN_NONBLOCK;
+
+ if(_direction == directionReadWrite)
+ mode |= SND_PCM_OPEN_DUPLEX;
+ else if(_direction == directionWrite)
+ mode |= SND_PCM_OPEN_PLAYBACK;
+ else
+ {
+ _error = "invalid direction";
+ return false;
+ }
+
+ int err;
+ if((err = snd_pcm_open(&m_pcm_handle, m_card, m_device, mode)) < 0) {
+ _error = "device: ";
+ _error += _deviceName.c_str();
+ _error += " can't be opened (";
+ _error += snd_strerror(err);
+ _error += ")";
+ return false;
+ }
+ else {
+ artsdebug("ALSA driver: %s", _deviceName.c_str());
+ }
+
+ snd_pcm_nonblock_mode(m_pcm_handle, 0);
+
+ /* flush buffers */
+ (void)snd_pcm_capture_flush(m_pcm_handle);
+ if(_direction & directionRead)
+ (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE);
+ if(_direction & directionWrite)
+ (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
+
+ /* check device capabilities */
+ checkCapabilities();
+
+ /* set the fragment settings to what the user requested */
+ _fragmentSize = requestedFragmentSize;
+ _fragmentCount = requestedFragmentCount;
+
+ /* set PCM communication parameters */
+ if((_direction & directionRead) && setPcmParams(SND_PCM_CHANNEL_CAPTURE))
+ return false;
+ if((_direction & directionWrite) && setPcmParams(SND_PCM_CHANNEL_PLAYBACK))
+ return false;
+
+ /* prepare channel */
+ if((_direction & directionRead) &&
+ snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE) < 0)
+ {
+ _error = "Unable to prepare capture channel!";
+ return false;
+ }
+ if((_direction & directionWrite) &&
+ snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
+ {
+ _error = "Unable to prepare playback channel!";
+ return false;
+ }
+
+ /* obtain current PCM setup (may differ from requested one) */
+ (void)memset(&m_setup, 0, sizeof(m_setup));
+
+ m_setup.channel = SND_PCM_CHANNEL_PLAYBACK;
+ if(snd_pcm_channel_setup(m_pcm_handle, &m_setup) < 0) {
+ _error = "Unable to obtain channel setup!";
+ return false;
+ }
+
+ /* check samplerate */
+ const int tolerance = _samplingRate/10+1000;
+ if(abs(m_setup.format.rate-_samplingRate) > tolerance)
+ {
+ _error = "Can't set requested sampling rate!";
+ char details[80];
+ sprintf(details," (requested rate %d, got rate %d)",
+ _samplingRate, m_setup.format.rate);
+ _error += details;
+ return false;
+ }
+ _samplingRate = m_setup.format.rate;
+
+ /* check format */
+ if(m_setup.format.format != m_format) {
+ _error = "Can't set requested format:";
+ _error += snd_pcm_get_format_name(m_format);
+ return false;
+ }
+
+ /* check voices */
+ if(m_setup.format.voices != _channels) {
+ _error = "Audio device doesn't support number of requested channels!";
+ return false;
+ }
+
+ /* update fragment settings with what we got */
+ switch(m_bufferMode) {
+ case block:
+ _fragmentSize = m_setup.buf.block.frag_size;
+ _fragmentCount = m_setup.buf.block.frags_max-1;
+ break;
+ case stream:
+ _fragmentSize = m_setup.buf.stream.queue_size;
+ _fragmentCount = 1;
+ break;
+ }
+
+ artsdebug("buffering: %d fragments with %d bytes "
+ "(audio latency is %1.1f ms)", _fragmentCount, _fragmentSize,
+ (float)(_fragmentSize*_fragmentCount) /
+ (float)(2.0 * _samplingRate * _channels)*1000.0);
+
+ /* obtain PCM file descriptor(s) */
+ audio_read_fd = audio_write_fd = -1;
+
+ if(_direction & directionRead)
+ audio_read_fd = snd_pcm_file_descriptor(m_pcm_handle,
+ SND_PCM_CHANNEL_CAPTURE);
+ if(_direction & directionWrite)
+ audio_write_fd = snd_pcm_file_descriptor(m_pcm_handle,
+ SND_PCM_CHANNEL_PLAYBACK);
+
+ /* start recording */
+ if((_direction & directionRead) && snd_pcm_capture_go(m_pcm_handle)) {
+ _error = "Can't start recording!";
+ return false;
+ }
+
+ return true;
+}
+
+void AudioIOALSA::close()
+{
+ int& _direction = param(direction);
+ if(_direction & directionRead)
+ (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE);
+ if(_direction & directionWrite)
+ (void)snd_pcm_channel_flush(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
+ (void)snd_pcm_close(m_pcm_handle);
+}
+
+void AudioIOALSA::setParam(AudioParam p, int& value)
+{
+ switch(p)
+ {
+ case fragmentSize:
+ param(p) = requestedFragmentSize = value;
+ break;
+ case fragmentCount:
+ param(p) = requestedFragmentCount = value;
+ break;
+ default:
+ param(p) = value;
+ break;
+ }
+}
+
+int AudioIOALSA::getParam(AudioParam p)
+{
+ snd_pcm_channel_status_t status;
+ (void)memset(&status, 0, sizeof(status));
+
+ switch(p)
+ {
+ case canRead:
+ status.channel = SND_PCM_CHANNEL_CAPTURE;
+ if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) {
+ arts_warning("Capture channel status error!");
+ return -1;
+ }
+ return status.free;
+ break;
+
+ case canWrite:
+ status.channel = SND_PCM_CHANNEL_PLAYBACK;
+ if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) {
+ arts_warning("Playback channel status error!");
+ return -1;
+ }
+ return status.free;
+ break;
+
+ case selectReadFD:
+ return audio_read_fd;
+ break;
+
+ case selectWriteFD:
+ return audio_write_fd;
+ break;
+
+ case autoDetect:
+ /*
+ * that the ALSA driver could be compiled doesn't say anything
+ * about whether it will work (the user might be using an OSS
+ * kernel driver) so we'll use a value less than the OSS one
+ * here, because OSS will most certainly work (ALSA's OSS emu)
+ */
+ return 5;
+ break;
+
+ default:
+ return param(p);
+ break;
+ }
+}
+
+int AudioIOALSA::read(void *buffer, int size)
+{
+ int length;
+ do {
+ length = snd_pcm_read(m_pcm_handle, buffer, size);
+ } while (length == -EINTR);
+ if(length == -EPIPE) {
+ snd_pcm_channel_status_t status;
+ (void)memset(&status, 0, sizeof(status));
+ status.channel = SND_PCM_CHANNEL_CAPTURE;
+ if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) {
+ arts_info("Capture channel status error!");
+ return -1;
+ }
+ else if(status.status == SND_PCM_STATUS_RUNNING) {
+ length = 0;
+ }
+ else if(status.status == SND_PCM_STATUS_OVERRUN) {
+ artsdebug("Overrun at position: %d" ,status.scount);
+ if(snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_CAPTURE)<0)
+ {
+ arts_info("Overrun: capture prepare error!");
+ return -1;
+ }
+ length = 0;
+ }
+ else {
+ arts_info("Unknown capture error!");
+ return -1;
+ }
+ }
+ else if(length < 0) {
+ arts_info("Capture error: %s", snd_strerror(length));
+ return -1;
+ }
+ return length;
+}
+
+int AudioIOALSA::write(void *buffer, int size)
+{
+ int length;
+ while((length = snd_pcm_write(m_pcm_handle, buffer, size)) != size) {
+ if (length == -EINTR)
+ continue; // Try again
+ snd_pcm_channel_status_t status;
+ (void)memset(&status, 0, sizeof(status));
+ status.channel = SND_PCM_CHANNEL_PLAYBACK;
+
+ if(snd_pcm_channel_status(m_pcm_handle, &status) < 0) {
+ arts_warning("Playback channel status error!");
+ return -1;
+ }
+ else if(status.status == SND_PCM_STATUS_UNDERRUN) {
+ artsdebug("Underrun at position: %d", status.scount);
+ if(snd_pcm_channel_prepare(m_pcm_handle, SND_PCM_CHANNEL_PLAYBACK)
+ < 0) {
+ arts_warning("Underrun: playback prepare error!");
+ return -1;
+ }
+ }
+ else {
+ arts_warning("Unknown playback error!");
+ return -1;
+ }
+ }
+ return size;
+}
+
+int AudioIOALSA::setPcmParams(const int channel)
+{
+ int &_samplingRate = param(samplingRate);
+ int &_channels = param(channels);
+ int &_fragmentSize = param(fragmentSize);
+ int &_fragmentCount = param(fragmentCount);
+
+ (void)memset(&m_cformat, 0, sizeof(m_cformat));
+ m_cformat.interleave = 1;
+ m_cformat.format = m_format;
+ m_cformat.rate = _samplingRate;
+ m_cformat.voices = _channels;
+
+ (void)memset(&m_params, 0, sizeof(m_params));
+ switch(m_bufferMode){
+ case stream:
+ m_params.mode=SND_PCM_MODE_STREAM;
+ break;
+ case block:
+ m_params.mode=SND_PCM_MODE_BLOCK;
+ break;
+ }
+ m_params.channel=channel;
+ (void)memcpy(&m_params.format, &m_cformat, sizeof(m_cformat));
+ if(channel==SND_PCM_CHANNEL_CAPTURE){
+ m_params.start_mode=SND_PCM_START_GO;
+ m_params.stop_mode=SND_PCM_STOP_ROLLOVER;
+ }
+ else{ //SND_PCM_CHANNEL_PLAYBACK
+ m_params.start_mode= (m_bufferMode==block) ? SND_PCM_START_FULL : SND_PCM_START_DATA;
+ m_params.stop_mode=SND_PCM_STOP_ROLLOVER; // SND_PCM_STOP_STOP
+ //use this ^^^ if you want to track underruns
+ }
+
+ switch(m_bufferMode){
+ case stream:
+ m_params.buf.stream.queue_size=1024*1024; //_fragmentSize*_fragmentCount;
+ m_params.buf.stream.fill=SND_PCM_FILL_SILENCE_WHOLE;
+ m_params.buf.stream.max_fill=1024;
+ break;
+ case block:
+ m_params.buf.block.frag_size=_fragmentSize;
+ if(channel==SND_PCM_CHANNEL_CAPTURE){
+ m_params.buf.block.frags_max=1;
+ m_params.buf.block.frags_min=1;
+ }
+ else{ //SND_PCM_CHANNEL_PLAYBACK
+ m_params.buf.block.frags_max=_fragmentCount+1;
+ m_params.buf.block.frags_min=1;
+ }
+ }
+ if(snd_pcm_channel_params(m_pcm_handle, &m_params)<0){
+ paramStr(lastError) = "Unable to set channel params!";
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+void AudioIOALSA::checkCapabilities()
+{
+ snd_pcm_info_t info;
+ (void)memset(&info, 0, sizeof(info));
+ if(!snd_pcm_info(m_pcm_handle, &info)) {
+ string flags = "";
+ if(info.flags & SND_PCM_INFO_PLAYBACK) flags += "playback ";
+ if(info.flags & SND_PCM_INFO_CAPTURE) flags += "capture ";
+ if(info.flags & SND_PCM_INFO_DUPLEX) flags += "duplex ";
+ if(info.flags & SND_PCM_INFO_DUPLEX_RATE) flags += "duplex_rate ";
+ artsdebug(" type:%d id:%s\n"
+ " flags:%s\n"
+ " playback_subdevices:%d capture_subdevices:%d",
+ info.type, info.id,
+ flags.c_str(),
+ info.playback+1, info.capture+1);
+ }
+ else {
+ arts_warning("Can't get device info!"); //not fatal error
+ }
+
+ (void)memset(&m_cinfo, 0, sizeof(m_cinfo));
+ m_cinfo.channel = SND_PCM_CHANNEL_PLAYBACK;
+ if(!snd_pcm_channel_info(m_pcm_handle, &m_cinfo)) {
+ string flags = "";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_MMAP) flags += "mmap ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_STREAM) flags += "stream ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_BLOCK) flags += "block ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_BATCH) flags += "batch ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_INTERLEAVE) flags += "interleave ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_NONINTERLEAVE) flags += "noninterleave ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_BLOCK_TRANSFER) flags += "block_transfer ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_OVERRANGE) flags += "overrange ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_MMAP_VALID) flags += "mmap_valid ";
+ if(m_cinfo.flags & SND_PCM_CHNINFO_PAUSE) flags += "pause ";
+
+ artsdebug(" subdevice:%d\n"
+ " flags:%s\n"
+ " min_rate:%d max_rate:%d\n"
+ " buffer_size:%d min_fragment_size:%d max_fragment_size:%d\n"
+ " fragment_align:%d fifo_size:%d transfer_block_size:%d\n"
+ " mmap_size:%d",
+ m_cinfo.subdevice,
+ flags.c_str(),
+ m_cinfo.min_rate, m_cinfo.max_rate,
+ m_cinfo.buffer_size, m_cinfo.min_fragment_size, m_cinfo.max_fragment_size,
+ m_cinfo.fragment_align, m_cinfo.fifo_size, m_cinfo.transfer_block_size,
+ m_cinfo.mmap_size);
+ }
+ else {
+ arts_warning("Can't get channel info!"); //not fatal error
+ }
+}
+
+#endif /* HAVE_LIBASOUND */