summaryrefslogtreecommitdiff
path: root/src/audioin.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/audioin.cc')
-rw-r--r--src/audioin.cc443
1 files changed, 443 insertions, 0 deletions
diff --git a/src/audioin.cc b/src/audioin.cc
new file mode 100644
index 0000000..8ed8eac
--- /dev/null
+++ b/src/audioin.cc
@@ -0,0 +1,443 @@
+/* -*- 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 <asoundlib.h>
+#include <exception>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string>
+
+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(&params);
+
+ // 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 <test.h>
+#include "test_audio.h"
+#include <string.h>
+
+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*/