/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * audioin.cc * * Wed Sep 30 11:36:18 CEST 2009 * Copyright 2011 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of libaudioin. * * libaudioin is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * libaudioin 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with libaudioin; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "audioin.h" // Use the newer ALSA API #define ALSA_PCM_NEW_HW_PARAMS_API #include #include #include #include #include class AudioIn { public: class CouldNotOpenPCMDevice : public std::exception {}; class UnableToSetHWParams : public std::exception {}; class PcmBufferTooSmall : public std::exception {}; class OverRun : public std::exception {}; class ReadError : public std::exception {}; class ShortRead : public std::exception {}; class CouldNotInitialiseParams : public std::exception {}; class CouldNotSetAccessMode : public std::exception {}; class CouldNotSetFormat : public std::exception {}; class CouldNotSetChannelNumber : public std::exception {}; class UnableToSetSampleRate : public std::exception {}; class UnableToSetPeriodSize : public std::exception {}; class MixerInitilisationFailed : public std::exception {}; class MixerNotInitialised : public std::exception {}; class InvalidMixerLevel : public std::exception {}; class IllegalChannelNumber : public std::exception {}; class CouldNotSetMixerLevel : public std::exception {}; AudioIn(std::string device, std::string mixer, unsigned int srate, int ch); ~AudioIn(); /** * Reads a number of samples from the soundcard. * The returned signal is a 16bit mono pcm signal. */ size_t read(void *buf, size_t size); int set_level(unsigned int channel, float level); unsigned int get_samplerate(); void set_enable_noise_fix(bool fixit); private: bool noisefix; unsigned int samplerate; int channels; snd_pcm_t *handle; snd_pcm_hw_params_t *params; unsigned int val; int dir; snd_pcm_uframes_t frames; snd_mixer_t *mixhnd; snd_mixer_elem_t *elem; long lvl_min, lvl_max; }; AudioIn::AudioIn(std::string device, std::string mixer_interface, unsigned int srate, int ch) { int open_mode = 0; //open_mode |= SND_PCM_NONBLOCK; //open_mode |= SND_PCM_NO_AUTO_RESAMPLE; //open_mode |= SND_PCM_NO_AUTO_CHANNELS; //open_mode |= SND_PCM_NO_AUTO_FORMAT; //open_mode |= SND_PCM_NO_SOFTVOL; elem = NULL; mixhnd = NULL; noisefix = false; samplerate = 0; int rc; // Open PCM device for recording (capture). rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_CAPTURE, open_mode); if (rc < 0) throw CouldNotOpenPCMDevice(); // Allocate a hardware parameters object. snd_pcm_hw_params_alloca(¶ms); // Fill it in with default values. rc = snd_pcm_hw_params_any(handle, params); if (rc < 0) throw CouldNotInitialiseParams(); // Set the desired hardware parameters. // Interleaved mode rc = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); if (rc < 0) throw CouldNotSetAccessMode(); // Signed 16-bit little-endian format rc = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); if (rc < 0) throw CouldNotSetFormat(); // Set channels (stereo/mono) rc = snd_pcm_hw_params_set_channels(handle, params, ch); channels = ch; if (rc < 0) throw CouldNotSetChannelNumber(); // Set sampling rate samplerate = srate; rc = snd_pcm_hw_params_set_rate_near(handle, params, &samplerate, &dir); if(rc < 0) throw UnableToSetSampleRate(); //if(samplerate != srate) throw UnableToSetSampleRate(); // NOTE: Setting period size to 32 frames will force use of lowest possible value. frames = 512; rc = snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); if(rc < 0) throw UnableToSetPeriodSize(); // Write the parameters to the driver rc = snd_pcm_hw_params(handle, params); if (rc < 0) throw UnableToSetHWParams(); // // Set up mixer // snd_mixer_selem_id_t *mixer; // Open mixer device if(snd_mixer_open(&mixhnd, 0) != 0) throw MixerInitilisationFailed(); if(snd_mixer_attach(mixhnd, device.c_str()) != 0) throw MixerInitilisationFailed(); if(snd_mixer_selem_register(mixhnd, NULL, NULL) < 0) throw MixerInitilisationFailed(); if(snd_mixer_load(mixhnd) < 0) throw MixerInitilisationFailed(); // Obtain mixer element snd_mixer_selem_id_alloca(&mixer); if(mixer == NULL) throw MixerInitilisationFailed(); snd_mixer_selem_id_set_name(mixer, mixer_interface.c_str()); elem = snd_mixer_find_selem(mixhnd, mixer); if(elem == NULL) throw MixerInitilisationFailed(); if(snd_mixer_selem_get_capture_volume_range(elem, &lvl_min, &lvl_max) != 0) { throw MixerInitilisationFailed(); } if(snd_mixer_selem_set_capture_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, 1) != 0) { throw MixerInitilisationFailed(); } if(snd_mixer_selem_set_capture_switch(elem, SND_MIXER_SCHN_FRONT_RIGHT, 1) != 0) { throw MixerInitilisationFailed(); } // Obtain mixer enumeration element "Input Source" and set it to 2 (line). snd_mixer_selem_id_set_name(mixer, "Input Source"); snd_mixer_elem_t *iselem = snd_mixer_find_selem(mixhnd, mixer); if(iselem == NULL) return; if(snd_mixer_selem_is_enumerated(iselem)) { // Set to line-in for(int i = 0; i < 3; i++) { char name[16]; snd_mixer_selem_get_enum_item_name(iselem, i, sizeof(name), name); if(std::string(name) == "Line") { snd_mixer_selem_set_enum_item(iselem, SND_MIXER_SCHN_MONO, i); } } } } AudioIn::~AudioIn() { if(handle) { snd_pcm_drain(handle); snd_pcm_close(handle); } if(mixhnd) snd_mixer_close(mixhnd); } size_t AudioIn::read(void *buf, size_t size) { int rc; if(size < frames * sizeof(short) * channels) { throw PcmBufferTooSmall(); } rc = snd_pcm_readi(handle, buf, frames); if (rc == -EPIPE) { // EPIPE means overrun snd_pcm_prepare(handle); throw OverRun(); return 0; } else if (rc < 0) { throw ReadError(); } else if (rc != (int)frames) { throw ShortRead(); } return rc * sizeof(short) * channels; } unsigned int AudioIn::get_samplerate() { return samplerate; } int AudioIn::set_level(unsigned int channel, float level) { if(!elem) throw MixerNotInitialised(); if(level > 1.0 || level < 0.0) throw InvalidMixerLevel(); long lvl = ((lvl_max - lvl_min) * level) + lvl_min; snd_mixer_selem_channel_id_t ch; switch(channel) { case 0: ch = SND_MIXER_SCHN_FRONT_LEFT; break; case 1: ch = SND_MIXER_SCHN_FRONT_RIGHT; break; default: throw IllegalChannelNumber(); break; } if(snd_mixer_selem_set_capture_volume(elem, ch, lvl) != 0) { throw CouldNotSetMixerLevel(); } return 0; } #define MAGIC 0xdeadbeef struct ai_t { unsigned int magic; AudioIn *ai; }; struct ai_t *ai_init(int *err, const char *device, const char *mixer, unsigned int srate, unsigned int ch) { *err = NO_ERROR; struct ai_t *handle = new ai_t; handle->magic = MAGIC; if(handle == NULL) { *err = OUT_OF_MEMORY; return NULL; } try { handle->ai = new AudioIn(device, mixer, srate, ch); } catch(AudioIn::CouldNotOpenPCMDevice &e) { *err = COULD_NOT_OPEN_DEVICE; } catch(AudioIn::UnableToSetHWParams &e) { *err = COULD_NOT_SET_HW_PARAMS; } catch(AudioIn::CouldNotInitialiseParams &e) { *err = COULD_NOT_INIT_PARAMS; } catch(AudioIn::CouldNotSetAccessMode &e) { *err = COULD_NOT_SET_ACCESS_MODE; } catch(AudioIn::CouldNotSetFormat &e) { *err = COULD_NOT_SET_FORMAT; } catch(AudioIn::CouldNotSetChannelNumber &e) { *err = COULD_NOT_SET_CHANNELS; } catch(AudioIn::UnableToSetSampleRate &e) { *err = COULD_NOT_SET_SAMPLE_RATE; } catch(AudioIn::UnableToSetPeriodSize &e) { *err = COULD_NOT_SET_PERIOD_SIZE; } catch(AudioIn::MixerInitilisationFailed &e) { *err = MIXER_INIT_FAILED; } if(*err != NO_ERROR) { handle->magic = 0; delete handle; return NULL; } return handle; } int ai_read(int *err, struct ai_t *handle, void *pcm, unsigned int maxsize) { if(*err == 42) { // Magic debug function short *p = (short*)pcm; for(size_t i = 0; i < maxsize / sizeof(short); i++) { p[i] = i; } *err = NO_ERROR; return maxsize; } *err = NO_ERROR; if(handle == NULL || handle->magic != MAGIC || handle->ai == NULL) { *err = MISSING_HANDLE; return -1; } try { return handle->ai->read(pcm, maxsize); } catch(AudioIn::PcmBufferTooSmall &e) { *err = BUFFER_TOO_SMALL; } catch(AudioIn::OverRun &e) { *err = BUFFER_OVERRUN; } catch(AudioIn::ReadError &e) { *err = READ_ERROR; } catch(AudioIn::ShortRead &e) { *err = SHORT_READ; } return -1; } int ai_get_samplerate(int *err, struct ai_t *handle) { *err = NO_ERROR; if(handle == NULL || handle->magic != MAGIC || handle->ai == NULL) { *err = MISSING_HANDLE; return -1; } return (int)handle->ai->get_samplerate(); } int ai_set_mixer_level(int *err, struct ai_t *handle, unsigned int channel, float level) { *err = NO_ERROR; if(handle == NULL || handle->magic != MAGIC || handle->ai == NULL) { *err = MISSING_HANDLE; return -1; } try { return handle->ai->set_level(channel, level); } catch(AudioIn::MixerNotInitialised &e) { *err = MIXER_NOT_INITIALISED; } catch(AudioIn::InvalidMixerLevel &e) { *err = INVALID_MIXER_LEVEL; } catch(AudioIn::IllegalChannelNumber &e) { *err = INVALID_CHANNEL_NUMBER; } catch(AudioIn::CouldNotSetMixerLevel &e) { *err = COULD_NOT_SET_MIXER_LEVEL; } return -1; } void ai_close(int *err, struct ai_t *handle) { *err = NO_ERROR; if(handle == NULL || handle->magic != MAGIC || handle->ai == NULL) { *err = MISSING_HANDLE; return; } delete handle->ai; handle->ai = NULL; handle->magic = 0; delete handle; } #ifdef TEST_AUDIOIN //deps: //cflags: $(ALSA_CFLAGS) //libs: $(ALSA_LIBS) #include #include "test_audio.h" #include TEST_BEGIN; int err; struct ai_t *h; h = ai_init(&err, "default", "H/W Multi", 44100, 2); TEST_EQUAL_INT(err, 0, "Check for errors."); TEST_NOTEQUAL(h, NULL, "Check handle."); ai_close(&err, h); TEST_EQUAL_INT(err, 0, "Check for errors."); h = ai_init(&err, "default", "H/W Multi", 22050, 1); TEST_EQUAL_INT(err, 0, "Check for errors."); TEST_NOTEQUAL(h, NULL, "Check handle."); char pcm[1880]; // Hold exactly one frame. int r; r = ai_read(&err, h, pcm, 0); TEST_EQUAL_INT(r, -1, "We should read something."); TEST_EQUAL_INT(err, BUFFER_TOO_SMALL, "Check for errors."); for(int i = 0; i < 100; i++) { r = ai_read(&err, h, pcm, sizeof(pcm)); } TEST_EQUAL_INT(err, 0, "Check for errors."); TEST_NOTEQUAL_INT(r, 0, "We should read something."); char ref[sizeof(pcm)]; memset(ref, 0, sizeof(ref)); double diff = compareBuffers(r/sizeof(short), 1, pcm, ref); TEST_LESS_THAN_FLOAT(diff, 1000.0, "Compare buffers (with no mic on the soundcard)"); short p[1024]; err = 42; ai_read(&err, h, p, sizeof(p)); for(size_t i = 0; i < sizeof(p) / sizeof(short); i++) { TEST_EQUAL_INT(p[i], i, "Compare slide."); } ai_close(&err, h); TEST_END; #endif/*TEST_AUDIOIN*/