diff options
Diffstat (limited to 'gr-audio-alsa/src')
-rw-r--r-- | gr-audio-alsa/src/Makefile.am | 91 | ||||
-rw-r--r-- | gr-audio-alsa/src/audio_alsa.i | 80 | ||||
-rw-r--r-- | gr-audio-alsa/src/audio_alsa_sink.cc | 538 | ||||
-rw-r--r-- | gr-audio-alsa/src/audio_alsa_sink.h | 124 | ||||
-rw-r--r-- | gr-audio-alsa/src/audio_alsa_source.cc | 506 | ||||
-rw-r--r-- | gr-audio-alsa/src/audio_alsa_source.h | 124 | ||||
-rw-r--r-- | gr-audio-alsa/src/gri_alsa.cc | 175 | ||||
-rw-r--r-- | gr-audio-alsa/src/gri_alsa.h | 44 | ||||
-rwxr-xr-x | gr-audio-alsa/src/qa_alsa.py | 40 | ||||
-rw-r--r-- | gr-audio-alsa/src/run_tests.in | 47 |
10 files changed, 1769 insertions, 0 deletions
diff --git a/gr-audio-alsa/src/Makefile.am b/gr-audio-alsa/src/Makefile.am new file mode 100644 index 000000000..e06fef9ae --- /dev/null +++ b/gr-audio-alsa/src/Makefile.am @@ -0,0 +1,91 @@ +# +# Copyright 2004 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +include $(top_srcdir)/Makefile.common + +# Install this stuff so that it ends up as the gnuradio.audio_alsa module +# This usually ends up at: +# ${prefix}/lib/python${python_version}/site-packages/gnuradio + +ourpythondir = $(grpythondir) +ourlibdir = $(grpyexecdir) + +LIBS += $(GNURADIO_CORE_LIBS) + +EXTRA_DIST = run_tests.in + +TESTS = run_tests + +LOCAL_IFILES = \ + audio_alsa.i + +NON_LOCAL_IFILES = \ + $(top_srcdir)/gnuradio-core/src/lib/swig/gnuradio.i + +ALL_IFILES = \ + $(LOCAL_IFILES) \ + $(NON_LOCAL_IFILES) + +BUILT_SOURCES = \ + audio_alsa.cc \ + audio_alsa.py + +ourpython_PYTHON = \ + audio_alsa.py + +INCLUDES = $(STD_DEFINES_AND_INCLUDES) $(PYTHON_CPPFLAGS) + +SWIGPYTHONARGS = $(SWIGPYTHONFLAGS) $(STD_DEFINES_AND_INCLUDES) + +ourlib_LTLIBRARIES = _audio_alsa.la + +_audio_alsa_la_SOURCES = \ + audio_alsa.cc \ + audio_alsa_sink.cc \ + audio_alsa_source.cc \ + gri_alsa.cc + +grinclude_HEADERS = \ + audio_alsa_sink.h \ + audio_alsa_source.h + +noinst_HEADERS = \ + gri_alsa.h + + +swiginclude_HEADERS = \ + $(LOCAL_IFILES) + +_audio_alsa_la_LIBADD = \ + $(PYTHON_LDFLAGS) \ + -lstdc++ + +_audio_alsa_la_LDFLAGS = $(NO_UNDEFINED) -module -avoid-version + +audio_alsa.cc audio_alsa.py: audio_alsa.i $(NON_LOCAL_IFILES) + $(SWIG) $(SWIGPYTHONARGS) -module audio_alsa -o audio_alsa.cc $< + + +noinst_PYTHON = \ + qa_alsa.py + +MOSTLYCLEANFILES = \ + $(BUILT_SOURCES) *~ *.pyc diff --git a/gr-audio-alsa/src/audio_alsa.i b/gr-audio-alsa/src/audio_alsa.i new file mode 100644 index 000000000..1cb13b838 --- /dev/null +++ b/gr-audio-alsa/src/audio_alsa.i @@ -0,0 +1,80 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004,2006 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +%feature("autodoc","1"); + +%include "exception.i" +%import "gnuradio.i" // the common stuff + +%{ +#include "gnuradio_swig_bug_workaround.h" // mandatory bug fix +#include "audio_alsa_sink.h" +#include "audio_alsa_source.h" +#include <stdexcept> +%} + +// ---------------------------------------------------------------- + +GR_SWIG_BLOCK_MAGIC(audio_alsa,source) + +audio_alsa_source_sptr +audio_alsa_make_source (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true + ) throw (std::runtime_error); + +class audio_alsa_source : public gr_sync_block { + + protected: + audio_alsa_source (int sampling_rate, + const std::string device_name, + bool ok_to_block = true + ) throw (std::runtime_error); + + public: + ~audio_alsa_source (); + + bool start(); + bool stop(); +}; + +// ---------------------------------------------------------------- + +GR_SWIG_BLOCK_MAGIC(audio_alsa,sink) + +audio_alsa_sink_sptr +audio_alsa_make_sink (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true + ) throw (std::runtime_error); + +class audio_alsa_sink : public gr_sync_block { + + protected: + audio_alsa_sink (int sampling_rate, + const std::string device_name, + bool ok_to_block + ) throw (std::runtime_error); + + public: + ~audio_alsa_sink (); +}; diff --git a/gr-audio-alsa/src/audio_alsa_sink.cc b/gr-audio-alsa/src/audio_alsa_sink.cc new file mode 100644 index 000000000..bdf9dbbed --- /dev/null +++ b/gr-audio-alsa/src/audio_alsa_sink.cc @@ -0,0 +1,538 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. 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 + +#include <audio_alsa_sink.h> +#include <gr_io_signature.h> +#include <gr_prefs.h> +#include <stdio.h> +#include <iostream> +#include <stdexcept> +#include <gri_alsa.h> + + +static bool CHATTY_DEBUG = false; + + +static snd_pcm_format_t acceptable_formats[] = { + // these are in our preferred order... + SND_PCM_FORMAT_S32, + SND_PCM_FORMAT_S16 +}; + +#define NELEMS(x) (sizeof(x)/sizeof(x[0])) + + +static std::string +default_device_name () +{ + return gr_prefs::singleton()->get_string("audio_alsa", "default_output_device", "hw:0,0"); +} + +static double +default_period_time () +{ + return std::max(0.001, gr_prefs::singleton()->get_double("audio_alsa", "period_time", 0.010)); +} + +static int +default_nperiods () +{ + return std::max(2L, gr_prefs::singleton()->get_long("audio_alsa", "nperiods", 4)); +} + +// ---------------------------------------------------------------- + +audio_alsa_sink_sptr +audio_alsa_make_sink (int sampling_rate, + const std::string dev, + bool ok_to_block) +{ + return audio_alsa_sink_sptr (new audio_alsa_sink (sampling_rate, dev, + ok_to_block)); +} + +audio_alsa_sink::audio_alsa_sink (int sampling_rate, + const std::string device_name, + bool ok_to_block) + : gr_sync_block ("audio_alsa_sink", + gr_make_io_signature (0, 0, 0), + gr_make_io_signature (0, 0, 0)), + d_sampling_rate (sampling_rate), + d_device_name (device_name.empty() ? default_device_name() : device_name), + d_pcm_handle (0), + d_hw_params ((snd_pcm_hw_params_t *)(new char[snd_pcm_hw_params_sizeof()])), + d_sw_params ((snd_pcm_sw_params_t *)(new char[snd_pcm_sw_params_sizeof()])), + d_nperiods (default_nperiods()), + d_period_time_us ((unsigned int) (default_period_time() * 1e6)), + d_period_size (0), + d_buffer_size_bytes (0), d_buffer (0), + d_worker (0), d_special_case_mono_to_stereo (false), + d_nunderuns (0), d_nsuspends (0) +{ + CHATTY_DEBUG = gr_prefs::singleton()->get_bool("audio_alsa", "verbose", false); + + int error; + int dir; + + // open the device for playback + error = snd_pcm_open(&d_pcm_handle, d_device_name.c_str (), + SND_PCM_STREAM_PLAYBACK, 0); + if (error < 0){ + fprintf (stderr, "audio_alsa_sink[%s]: %s\n", + d_device_name.c_str(), snd_strerror(error)); + throw std::runtime_error ("audio_alsa_sink"); + } + + // Fill params with a full configuration space for a PCM. + error = snd_pcm_hw_params_any(d_pcm_handle, d_hw_params); + if (error < 0) + bail ("broken configuration for playback", error); + + + if (CHATTY_DEBUG) + gri_alsa_dump_hw_params (d_pcm_handle, d_hw_params, stdout); + + + // now that we know how many channels the h/w can handle, set input signature + unsigned int umin_chan, umax_chan; + snd_pcm_hw_params_get_channels_min (d_hw_params, &umin_chan); + snd_pcm_hw_params_get_channels_max (d_hw_params, &umax_chan); + int min_chan = std::min (umin_chan, 1000U); + int max_chan = std::min (umax_chan, 1000U); + + // As a special case, if the hw's min_chan is two, we'll accept + // a single input and handle the duplication ourselves. + + if (min_chan == 2){ + min_chan = 1; + d_special_case_mono_to_stereo = true; + } + set_input_signature (gr_make_io_signature (min_chan, max_chan, + sizeof (float))); + + // fill in portions of the d_hw_params that we know now... + + // Specify the access methods we implement + // For now, we only handle RW_INTERLEAVED... + snd_pcm_access_mask_t *access_mask; + snd_pcm_access_mask_alloca (&access_mask); + snd_pcm_access_mask_none (access_mask); + snd_pcm_access_mask_set (access_mask, SND_PCM_ACCESS_RW_INTERLEAVED); + // snd_pcm_access_mask_set (access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED); + + if ((error = snd_pcm_hw_params_set_access_mask (d_pcm_handle, + d_hw_params, access_mask)) < 0) + bail ("failed to set access mask", error); + + + // set sample format + if (!gri_alsa_pick_acceptable_format (d_pcm_handle, d_hw_params, + acceptable_formats, + NELEMS (acceptable_formats), + &d_format, + "audio_alsa_sink", + CHATTY_DEBUG)) + throw std::runtime_error ("audio_alsa_sink"); + + + // sampling rate + unsigned int orig_sampling_rate = d_sampling_rate; + if ((error = snd_pcm_hw_params_set_rate_near (d_pcm_handle, d_hw_params, + &d_sampling_rate, 0)) < 0) + bail ("failed to set rate near", error); + + if (orig_sampling_rate != d_sampling_rate){ + fprintf (stderr, "audio_alsa_sink[%s]: unable to support sampling rate %d\n", + snd_pcm_name (d_pcm_handle), orig_sampling_rate); + fprintf (stderr, " card requested %d instead.\n", d_sampling_rate); + } + + /* + * ALSA transfers data in units of "periods". + * We indirectly determine the underlying buffersize by specifying + * the number of periods we want (typically 4) and the length of each + * period in units of time (typically 1ms). + */ + unsigned int min_nperiods, max_nperiods; + snd_pcm_hw_params_get_periods_min (d_hw_params, &min_nperiods, &dir); + snd_pcm_hw_params_get_periods_max (d_hw_params, &max_nperiods, &dir); + //fprintf (stderr, "alsa_sink: min_nperiods = %d, max_nperiods = %d\n", + // min_nperiods, max_nperiods); + + unsigned int orig_nperiods = d_nperiods; + d_nperiods = std::min (std::max (min_nperiods, d_nperiods), max_nperiods); + + // adjust period time so that total buffering remains more-or-less constant + d_period_time_us = (d_period_time_us * orig_nperiods) / d_nperiods; + + error = snd_pcm_hw_params_set_periods (d_pcm_handle, d_hw_params, + d_nperiods, 0); + if (error < 0) + bail ("set_periods failed", error); + + dir = 0; + error = snd_pcm_hw_params_set_period_time_near (d_pcm_handle, d_hw_params, + &d_period_time_us, &dir); + if (error < 0) + bail ("set_period_time_near failed", error); + + dir = 0; + error = snd_pcm_hw_params_get_period_size (d_hw_params, + &d_period_size, &dir); + if (error < 0) + bail ("get_period_size failed", error); + + set_output_multiple (d_period_size); +} + + +bool +audio_alsa_sink::check_topology (int ninputs, int noutputs) +{ + // ninputs is how many channels the user has connected. + // Now we can finish up setting up the hw params... + + int nchan = ninputs; + int err; + + // FIXME check_topology may be called more than once. + // Ensure that the pcm is in a state where we can still mess with the hw_params + + bool special_case = nchan == 1 && d_special_case_mono_to_stereo; + if (special_case) + nchan = 2; + + err = snd_pcm_hw_params_set_channels (d_pcm_handle, d_hw_params, nchan); + + if (err < 0){ + output_error_msg ("set_channels failed", err); + return false; + } + + // set the parameters into the driver... + err = snd_pcm_hw_params(d_pcm_handle, d_hw_params); + if (err < 0){ + output_error_msg ("snd_pcm_hw_params failed", err); + return false; + } + + // get current s/w params + err = snd_pcm_sw_params_current (d_pcm_handle, d_sw_params); + if (err < 0) + bail ("snd_pcm_sw_params_current", err); + + // Tell the PCM device to wait to start until we've filled + // it's buffers half way full. This helps avoid audio underruns. + + err = snd_pcm_sw_params_set_start_threshold(d_pcm_handle, + d_sw_params, + d_nperiods * d_period_size / 2); + if (err < 0) + bail ("snd_pcm_sw_params_set_start_threshold", err); + + // store the s/w params + err = snd_pcm_sw_params (d_pcm_handle, d_sw_params); + if (err < 0) + bail ("snd_pcm_sw_params", err); + + d_buffer_size_bytes = + d_period_size * nchan * snd_pcm_format_size (d_format, 1); + + d_buffer = new char [d_buffer_size_bytes]; + + if (CHATTY_DEBUG) + fprintf (stdout, "audio_alsa_sink[%s]: sample resolution = %d bits\n", + snd_pcm_name (d_pcm_handle), + snd_pcm_hw_params_get_sbits (d_hw_params)); + + switch (d_format){ + case SND_PCM_FORMAT_S16: + if (special_case) + d_worker = &audio_alsa_sink::work_s16_1x2; + else + d_worker = &audio_alsa_sink::work_s16; + break; + + case SND_PCM_FORMAT_S32: + if (special_case) + d_worker = &audio_alsa_sink::work_s32_1x2; + else + d_worker = &audio_alsa_sink::work_s32; + break; + + default: + assert (0); + } + + return true; +} + +audio_alsa_sink::~audio_alsa_sink () +{ + if (snd_pcm_state (d_pcm_handle) == SND_PCM_STATE_RUNNING) + snd_pcm_drop (d_pcm_handle); + + snd_pcm_close(d_pcm_handle); + delete [] ((char *) d_hw_params); + delete [] ((char *) d_sw_params); + delete [] d_buffer; +} + +int +audio_alsa_sink::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + assert ((noutput_items % d_period_size) == 0); + + // this is a call through a pointer to a method... + return (this->*d_worker)(noutput_items, input_items, output_items); +} + +/* + * Work function that deals with float to S16 conversion + */ +int +audio_alsa_sink::work_s16 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int16 sample_t; // the type of samples we're creating + static const int NBITS = 16; // # of bits in a sample + + unsigned int nchan = input_items.size (); + const float **in = (const float **) &input_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + int n; + + unsigned int sizeof_frame = nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + for (n = 0; n < noutput_items; n += d_period_size){ + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + for (unsigned int chan = 0; chan < nchan; chan++){ + buf[bi++] = (sample_t) (in[chan][i] * (float) ((1L << (NBITS-1)) - 1)); + } + } + + // update src pointers + for (unsigned int chan = 0; chan < nchan; chan++) + in[chan] += d_period_size; + + if (!write_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + } + + return n; +} + + +/* + * Work function that deals with float to S32 conversion + */ +int +audio_alsa_sink::work_s32 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int32 sample_t; // the type of samples we're creating + static const int NBITS = 32; // # of bits in a sample + + unsigned int nchan = input_items.size (); + const float **in = (const float **) &input_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + int n; + + unsigned int sizeof_frame = nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + for (n = 0; n < noutput_items; n += d_period_size){ + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + for (unsigned int chan = 0; chan < nchan; chan++){ + buf[bi++] = (sample_t) (in[chan][i] * (float) ((1L << (NBITS-1)) - 1)); + } + } + + // update src pointers + for (unsigned int chan = 0; chan < nchan; chan++) + in[chan] += d_period_size; + + if (!write_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + } + + return n; +} + +/* + * Work function that deals with float to S16 conversion and + * mono to stereo kludge. + */ +int +audio_alsa_sink::work_s16_1x2 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int16 sample_t; // the type of samples we're creating + static const int NBITS = 16; // # of bits in a sample + + assert (input_items.size () == 1); + static const unsigned int nchan = 2; + const float **in = (const float **) &input_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + int n; + + unsigned int sizeof_frame = nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + for (n = 0; n < noutput_items; n += d_period_size){ + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + sample_t t = (sample_t) (in[0][i] * (float) ((1L << (NBITS-1)) - 1)); + buf[bi++] = t; + buf[bi++] = t; + } + + // update src pointers + in[0] += d_period_size; + + if (!write_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + } + + return n; +} + +/* + * Work function that deals with float to S32 conversion and + * mono to stereo kludge. + */ +int +audio_alsa_sink::work_s32_1x2 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int32 sample_t; // the type of samples we're creating + static const int NBITS = 32; // # of bits in a sample + + assert (input_items.size () == 1); + static unsigned int nchan = 2; + const float **in = (const float **) &input_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + int n; + + unsigned int sizeof_frame = nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + for (n = 0; n < noutput_items; n += d_period_size){ + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + sample_t t = (sample_t) (in[0][i] * (float) ((1L << (NBITS-1)) - 1)); + buf[bi++] = t; + buf[bi++] = t; + } + + // update src pointers + in[0] += d_period_size; + + if (!write_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + } + + return n; +} + +bool +audio_alsa_sink::write_buffer (const void *vbuffer, + unsigned nframes, unsigned sizeof_frame) +{ + const unsigned char *buffer = (const unsigned char *) vbuffer; + + while (nframes > 0){ + int r = snd_pcm_writei (d_pcm_handle, buffer, nframes); + if (r == -EAGAIN) + continue; // try again + + else if (r == -EPIPE){ // underrun + d_nunderuns++; + fputs ("aU", stderr); + if ((r = snd_pcm_prepare (d_pcm_handle)) < 0){ + output_error_msg ("snd_pcm_prepare failed. Can't recover from underrun", r); + return false; + } + continue; // try again + } + + else if (r == -ESTRPIPE){ // h/w is suspended (whatever that means) + // This is apparently related to power management + d_nsuspends++; + if ((r = snd_pcm_resume (d_pcm_handle)) < 0){ + output_error_msg ("failed to resume from suspend", r); + return false; + } + continue; // try again + } + + else if (r < 0){ + output_error_msg ("snd_pcm_writei failed", r); + return false; + } + + nframes -= r; + buffer += r * sizeof_frame; + } + + return true; +} + + +void +audio_alsa_sink::output_error_msg (const char *msg, int err) +{ + fprintf (stderr, "audio_alsa_sink[%s]: %s: %s\n", + snd_pcm_name (d_pcm_handle), msg, snd_strerror (err)); +} + +void +audio_alsa_sink::bail (const char *msg, int err) throw (std::runtime_error) +{ + output_error_msg (msg, err); + throw std::runtime_error ("audio_alsa_sink"); +} diff --git a/gr-audio-alsa/src/audio_alsa_sink.h b/gr-audio-alsa/src/audio_alsa_sink.h new file mode 100644 index 000000000..bb61e49ab --- /dev/null +++ b/gr-audio-alsa/src/audio_alsa_sink.h @@ -0,0 +1,124 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef INCLUDED_AUDIO_ALSA_SINK_H +#define INCLUDED_AUDIO_ALSA_SINK_H + +// use new ALSA API +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +#include <gr_sync_block.h> +#include <string> +#include <alsa/asoundlib.h> +#include <stdexcept> + + +class audio_alsa_sink; +typedef boost::shared_ptr<audio_alsa_sink> audio_alsa_sink_sptr; + +/*! + * \brief make an alsa audio sink. + * + * \param sampling_rate sampling rate in Hz + * \param dev ALSA pcm device name, e.g., "hw:0,0" + * \param ok_to_block (currently ignored) + */ +audio_alsa_sink_sptr +audio_alsa_make_sink (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true); + +/*! + * \brief audio sink using ALSA + * + * The sink has N input streams of floats, where N depends + * on the hardware characteristics of the selected device. + * + * Input samples must be in the range [-1,1]. + */ +class audio_alsa_sink : public gr_sync_block { + friend audio_alsa_sink_sptr + audio_alsa_make_sink (int sampling_rate, const std::string device_name, + bool ok_to_block); + + // typedef for pointer to class work method + typedef int (audio_alsa_sink::*work_t)(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + unsigned int d_sampling_rate; + std::string d_device_name; + snd_pcm_t *d_pcm_handle; + snd_pcm_hw_params_t *d_hw_params; + snd_pcm_sw_params_t *d_sw_params; + snd_pcm_format_t d_format; + unsigned int d_nperiods; + unsigned int d_period_time_us; // microseconds + snd_pcm_uframes_t d_period_size; // in frames + unsigned int d_buffer_size_bytes; // sizeof of d_buffer + char *d_buffer; + work_t d_worker; // the work method to use + bool d_special_case_mono_to_stereo; + + // random stats + int d_nunderuns; // count of underruns + int d_nsuspends; // count of suspends + + void output_error_msg (const char *msg, int err); + void bail (const char *msg, int err) throw (std::runtime_error); + + protected: + audio_alsa_sink (int sampling_rate, const std::string device_name, + bool ok_to_block); + + public: + ~audio_alsa_sink (); + + bool check_topology (int ninputs, int noutputs); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + +protected: + bool write_buffer (const void *buffer, unsigned nframes, unsigned sizeof_frame); + + int work_s16 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + int work_s16_1x2 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + int work_s32 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + int work_s32_1x2 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +}; + +#endif /* INCLUDED_AUDIO_ALSA_SINK_H */ diff --git a/gr-audio-alsa/src/audio_alsa_source.cc b/gr-audio-alsa/src/audio_alsa_source.cc new file mode 100644 index 000000000..4e7ee94b6 --- /dev/null +++ b/gr-audio-alsa/src/audio_alsa_source.cc @@ -0,0 +1,506 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004,2006 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. 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 + +#include <audio_alsa_source.h> +#include <gr_io_signature.h> +#include <gr_prefs.h> +#include <stdio.h> +#include <iostream> +#include <stdexcept> +#include <gri_alsa.h> + + +static bool CHATTY_DEBUG = false; + +static snd_pcm_format_t acceptable_formats[] = { + // these are in our preferred order... + SND_PCM_FORMAT_S32, + SND_PCM_FORMAT_S16 +}; + +#define NELEMS(x) (sizeof(x)/sizeof(x[0])) + + +static std::string +default_device_name () +{ + return gr_prefs::singleton()->get_string("audio_alsa", "default_input_device", "hw:0,0"); +} + +static double +default_period_time () +{ + return std::max(0.001, gr_prefs::singleton()->get_double("audio_alsa", "period_time", 0.010)); +} + +static int +default_nperiods () +{ + return std::max(2L, gr_prefs::singleton()->get_long("audio_alsa", "nperiods", 4)); +} + +// ---------------------------------------------------------------- + +audio_alsa_source_sptr +audio_alsa_make_source (int sampling_rate, const std::string dev, + bool ok_to_block) +{ + return audio_alsa_source_sptr (new audio_alsa_source (sampling_rate, dev, + ok_to_block)); +} + +audio_alsa_source::audio_alsa_source (int sampling_rate, + const std::string device_name, + bool ok_to_block) + : gr_sync_block ("audio_alsa_source", + gr_make_io_signature (0, 0, 0), + gr_make_io_signature (0, 0, 0)), + d_sampling_rate (sampling_rate), + d_device_name (device_name.empty() ? default_device_name() : device_name), + d_pcm_handle (0), + d_hw_params ((snd_pcm_hw_params_t *)(new char[snd_pcm_hw_params_sizeof()])), + d_sw_params ((snd_pcm_sw_params_t *)(new char[snd_pcm_sw_params_sizeof()])), + d_nperiods (default_nperiods()), + d_period_time_us ((unsigned int) (default_period_time() * 1e6)), + d_period_size (0), + d_buffer_size_bytes (0), d_buffer (0), + d_worker (0), d_hw_nchan (0), + d_special_case_stereo_to_mono (false), + d_noverruns (0), d_nsuspends (0) +{ + + CHATTY_DEBUG = gr_prefs::singleton()->get_bool("audio_alsa", "verbose", false); + + int error; + int dir; + + // open the device for capture + error = snd_pcm_open(&d_pcm_handle, d_device_name.c_str (), + SND_PCM_STREAM_CAPTURE, 0); + if (error < 0){ + fprintf (stderr, "audio_alsa_source[%s]: %s\n", + d_device_name.c_str(), snd_strerror(error)); + throw std::runtime_error ("audio_alsa_source"); + } + + // Fill params with a full configuration space for a PCM. + error = snd_pcm_hw_params_any(d_pcm_handle, d_hw_params); + if (error < 0) + bail ("broken configuration for playback", error); + + if (CHATTY_DEBUG) + gri_alsa_dump_hw_params (d_pcm_handle, d_hw_params, stdout); + + // now that we know how many channels the h/w can handle, set output signature + unsigned int umax_chan; + unsigned int umin_chan; + snd_pcm_hw_params_get_channels_min (d_hw_params, &umin_chan); + snd_pcm_hw_params_get_channels_max (d_hw_params, &umax_chan); + int min_chan = std::min (umin_chan, 1000U); + int max_chan = std::min (umax_chan, 1000U); + + // As a special case, if the hw's min_chan is two, we'll accept + // a single output and handle the demux ourselves. + + if (min_chan == 2){ + min_chan = 1; + d_special_case_stereo_to_mono = true; + } + + set_output_signature (gr_make_io_signature (min_chan, max_chan, + sizeof (float))); + + // fill in portions of the d_hw_params that we know now... + + // Specify the access methods we implement + // For now, we only handle RW_INTERLEAVED... + snd_pcm_access_mask_t *access_mask; + snd_pcm_access_mask_alloca (&access_mask); + snd_pcm_access_mask_none (access_mask); + snd_pcm_access_mask_set (access_mask, SND_PCM_ACCESS_RW_INTERLEAVED); + // snd_pcm_access_mask_set (access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED); + + if ((error = snd_pcm_hw_params_set_access_mask (d_pcm_handle, + d_hw_params, access_mask)) < 0) + bail ("failed to set access mask", error); + + + // set sample format + if (!gri_alsa_pick_acceptable_format (d_pcm_handle, d_hw_params, + acceptable_formats, + NELEMS (acceptable_formats), + &d_format, + "audio_alsa_source", + CHATTY_DEBUG)) + throw std::runtime_error ("audio_alsa_source"); + + + // sampling rate + unsigned int orig_sampling_rate = d_sampling_rate; + if ((error = snd_pcm_hw_params_set_rate_near (d_pcm_handle, d_hw_params, + &d_sampling_rate, 0)) < 0) + bail ("failed to set rate near", error); + + if (orig_sampling_rate != d_sampling_rate){ + fprintf (stderr, "audio_alsa_source[%s]: unable to support sampling rate %d\n", + snd_pcm_name (d_pcm_handle), orig_sampling_rate); + fprintf (stderr, " card requested %d instead.\n", d_sampling_rate); + } + + /* + * ALSA transfers data in units of "periods". + * We indirectly determine the underlying buffersize by specifying + * the number of periods we want (typically 4) and the length of each + * period in units of time (typically 1ms). + */ + unsigned int min_nperiods, max_nperiods; + snd_pcm_hw_params_get_periods_min (d_hw_params, &min_nperiods, &dir); + snd_pcm_hw_params_get_periods_max (d_hw_params, &max_nperiods, &dir); + //fprintf (stderr, "alsa_source: min_nperiods = %d, max_nperiods = %d\n", + // min_nperiods, max_nperiods); + + + unsigned int orig_nperiods = d_nperiods; + d_nperiods = std::min (std::max (min_nperiods, d_nperiods), max_nperiods); + + // adjust period time so that total buffering remains more-or-less constant + d_period_time_us = (d_period_time_us * orig_nperiods) / d_nperiods; + + error = snd_pcm_hw_params_set_periods (d_pcm_handle, d_hw_params, + d_nperiods, 0); + if (error < 0) + bail ("set_periods failed", error); + + dir = 0; + error = snd_pcm_hw_params_set_period_time_near (d_pcm_handle, d_hw_params, + &d_period_time_us, &dir); + if (error < 0) + bail ("set_period_time_near failed", error); + + dir = 0; + error = snd_pcm_hw_params_get_period_size (d_hw_params, + &d_period_size, &dir); + if (error < 0) + bail ("get_period_size failed", error); + + set_output_multiple (d_period_size); +} + +bool +audio_alsa_source::check_topology (int ninputs, int noutputs) +{ + // noutputs is how many channels the user has connected. + // Now we can finish up setting up the hw params... + + unsigned int nchan = noutputs; + int err; + + // FIXME check_topology may be called more than once. + // Ensure that the pcm is in a state where we can still mess with the hw_params + + bool special_case = nchan == 1 && d_special_case_stereo_to_mono; + if (special_case) + nchan = 2; + + d_hw_nchan = nchan; + err = snd_pcm_hw_params_set_channels (d_pcm_handle, d_hw_params, d_hw_nchan); + if (err < 0){ + output_error_msg ("set_channels failed", err); + return false; + } + + // set the parameters into the driver... + err = snd_pcm_hw_params(d_pcm_handle, d_hw_params); + if (err < 0){ + output_error_msg ("snd_pcm_hw_params failed", err); + return false; + } + + d_buffer_size_bytes = + d_period_size * d_hw_nchan * snd_pcm_format_size (d_format, 1); + + d_buffer = new char [d_buffer_size_bytes]; + + if (CHATTY_DEBUG) + fprintf (stdout, "audio_alsa_source[%s]: sample resolution = %d bits\n", + snd_pcm_name (d_pcm_handle), + snd_pcm_hw_params_get_sbits (d_hw_params)); + + switch (d_format){ + case SND_PCM_FORMAT_S16: + if (special_case) + d_worker = &audio_alsa_source::work_s16_2x1; + else + d_worker = &audio_alsa_source::work_s16; + break; + + case SND_PCM_FORMAT_S32: + if (special_case) + d_worker = &audio_alsa_source::work_s32_2x1; + else + d_worker = &audio_alsa_source::work_s32; + break; + + default: + assert (0); + } + + return true; +} + +audio_alsa_source::~audio_alsa_source () +{ + if (snd_pcm_state (d_pcm_handle) == SND_PCM_STATE_RUNNING) + snd_pcm_drop (d_pcm_handle); + + snd_pcm_close(d_pcm_handle); + delete [] ((char *) d_hw_params); + delete [] ((char *) d_sw_params); + delete [] d_buffer; +} + +int +audio_alsa_source::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + assert ((noutput_items % d_period_size) == 0); + assert (noutput_items != 0); + + // this is a call through a pointer to a method... + return (this->*d_worker)(noutput_items, input_items, output_items); +} + +/* + * Work function that deals with float to S16 conversion + */ +int +audio_alsa_source::work_s16 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int16 sample_t; // the type of samples we're creating + static const int NBITS = 16; // # of bits in a sample + + unsigned int nchan = output_items.size (); + float **out = (float **) &output_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + + unsigned int sizeof_frame = d_hw_nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + // To minimize latency, return at most a single period's worth of samples. + // [We could also read the first one in a blocking mode and subsequent + // ones in non-blocking mode, but we'll leave that for later (or never).] + + if (!read_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + for (unsigned int chan = 0; chan < nchan; chan++){ + out[chan][i] = (float) buf[bi++] * (1.0 / (float) ((1L << (NBITS-1)) - 1)); + } + } + + return d_period_size; +} + +/* + * Work function that deals with float to S16 conversion + * and stereo to mono kludge... + */ +int +audio_alsa_source::work_s16_2x1 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int16 sample_t; // the type of samples we're creating + static const int NBITS = 16; // # of bits in a sample + + unsigned int nchan = output_items.size (); + float **out = (float **) &output_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + + assert (nchan == 1); + + unsigned int sizeof_frame = d_hw_nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + // To minimize latency, return at most a single period's worth of samples. + // [We could also read the first one in a blocking mode and subsequent + // ones in non-blocking mode, but we'll leave that for later (or never).] + + if (!read_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + int t = (buf[bi] + buf[bi+1]) / 2; + bi += 2; + out[0][i] = (float) t * (1.0 / (float) ((1L << (NBITS-1)) - 1)); + } + + return d_period_size; +} + +/* + * Work function that deals with float to S32 conversion + */ +int +audio_alsa_source::work_s32 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int32 sample_t; // the type of samples we're creating + static const int NBITS = 32; // # of bits in a sample + + unsigned int nchan = output_items.size (); + float **out = (float **) &output_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + + unsigned int sizeof_frame = d_hw_nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + // To minimize latency, return at most a single period's worth of samples. + // [We could also read the first one in a blocking mode and subsequent + // ones in non-blocking mode, but we'll leave that for later (or never).] + + if (!read_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + for (unsigned int chan = 0; chan < nchan; chan++){ + out[chan][i] = (float) buf[bi++] * (1.0 / (float) ((1L << (NBITS-1)) - 1)); + } + } + + return d_period_size; +} + +/* + * Work function that deals with float to S32 conversion + * and stereo to mono kludge... + */ +int +audio_alsa_source::work_s32_2x1 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + typedef gr_int32 sample_t; // the type of samples we're creating + static const int NBITS = 32; // # of bits in a sample + + unsigned int nchan = output_items.size (); + float **out = (float **) &output_items[0]; + sample_t *buf = (sample_t *) d_buffer; + int bi; + + assert (nchan == 1); + + unsigned int sizeof_frame = d_hw_nchan * sizeof (sample_t); + assert (d_buffer_size_bytes == d_period_size * sizeof_frame); + + // To minimize latency, return at most a single period's worth of samples. + // [We could also read the first one in a blocking mode and subsequent + // ones in non-blocking mode, but we'll leave that for later (or never).] + + if (!read_buffer (buf, d_period_size, sizeof_frame)) + return -1; // No fixing this problem. Say we're done. + + // process one period of data + bi = 0; + for (unsigned int i = 0; i < d_period_size; i++){ + int t = (buf[bi] + buf[bi+1]) / 2; + bi += 2; + out[0][i] = (float) t * (1.0 / (float) ((1L << (NBITS-1)) - 1)); + } + + return d_period_size; +} + +bool +audio_alsa_source::read_buffer (void *vbuffer, unsigned nframes, unsigned sizeof_frame) +{ + unsigned char *buffer = (unsigned char *) vbuffer; + + while (nframes > 0){ + int r = snd_pcm_readi (d_pcm_handle, buffer, nframes); + if (r == -EAGAIN) + continue; // try again + + else if (r == -EPIPE){ // overrun + d_noverruns++; + fputs ("aO", stderr); + if ((r = snd_pcm_prepare (d_pcm_handle)) < 0){ + output_error_msg ("snd_pcm_prepare failed. Can't recover from overrun", r); + return false; + } + continue; // try again + } + + else if (r == -ESTRPIPE){ // h/w is suspended (whatever that means) + // This is apparently related to power management + d_nsuspends++; + if ((r = snd_pcm_resume (d_pcm_handle)) < 0){ + output_error_msg ("failed to resume from suspend", r); + return false; + } + continue; // try again + } + + else if (r < 0){ + output_error_msg ("snd_pcm_readi failed", r); + return false; + } + + nframes -= r; + buffer += r * sizeof_frame; + } + + return true; +} + + +void +audio_alsa_source::output_error_msg (const char *msg, int err) +{ + fprintf (stderr, "audio_alsa_source[%s]: %s: %s\n", + snd_pcm_name (d_pcm_handle), msg, snd_strerror (err)); +} + +void +audio_alsa_source::bail (const char *msg, int err) throw (std::runtime_error) +{ + output_error_msg (msg, err); + throw std::runtime_error ("audio_alsa_source"); +} diff --git a/gr-audio-alsa/src/audio_alsa_source.h b/gr-audio-alsa/src/audio_alsa_source.h new file mode 100644 index 000000000..cc1ae1167 --- /dev/null +++ b/gr-audio-alsa/src/audio_alsa_source.h @@ -0,0 +1,124 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004,2006 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef INCLUDED_AUDIO_ALSA_SOURCE_H +#define INCLUDED_AUDIO_ALSA_SOURCE_H + +// use new ALSA API +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +#include <gr_sync_block.h> +#include <string> +#include <alsa/asoundlib.h> +#include <stdexcept> + +class audio_alsa_source; +typedef boost::shared_ptr<audio_alsa_source> audio_alsa_source_sptr; + +/*! + * \brief Make an ALSA audio source. + * + * \param sampling_rate sampling rate + * \param dev ALSA pcm device name, e.g., "hw:0,0" + * \param ok_to_block (currently ignored) + */ +audio_alsa_source_sptr +audio_alsa_make_source (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true); + +/*! + * \brief audio source using ALSA + * + * The source has between 1 and N input streams of floats, where N is + * depends on the hardware characteristics of the selected device. + * + * Output samples will be in the range [-1,1]. + */ +class audio_alsa_source : public gr_sync_block { + friend audio_alsa_source_sptr + audio_alsa_make_source (int sampling_rate, + const std::string device_name, + bool ok_to_block); + + // typedef for pointer to class work method + typedef int (audio_alsa_source::*work_t)(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + unsigned int d_sampling_rate; + std::string d_device_name; + snd_pcm_t *d_pcm_handle; + snd_pcm_hw_params_t *d_hw_params; + snd_pcm_sw_params_t *d_sw_params; + snd_pcm_format_t d_format; + unsigned int d_nperiods; + unsigned int d_period_time_us; // microseconds + snd_pcm_uframes_t d_period_size; // in frames + unsigned int d_buffer_size_bytes; // sizeof of d_buffer + char *d_buffer; + work_t d_worker; // the work method to use + unsigned int d_hw_nchan; // # of configured h/w channels + bool d_special_case_stereo_to_mono; + + // random stats + int d_noverruns; // count of overruns + int d_nsuspends; // count of suspends + + void output_error_msg (const char *msg, int err); + void bail (const char *msg, int err) throw (std::runtime_error); + + protected: + audio_alsa_source (int sampling_rate, const std::string device_name, + bool ok_to_block); + + public: + ~audio_alsa_source (); + + bool check_topology (int ninputs, int noutputs); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + +protected: + bool read_buffer (void *buffer, unsigned nframes, unsigned sizeof_frame); + + int work_s16 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + int work_s16_2x1 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + int work_s32 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + int work_s32_2x1 (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +}; + +#endif /* INCLUDED_AUDIO_ALSA_SOURCE_H */ diff --git a/gr-audio-alsa/src/gri_alsa.cc b/gr-audio-alsa/src/gri_alsa.cc new file mode 100644 index 000000000..25ff7f652 --- /dev/null +++ b/gr-audio-alsa/src/gri_alsa.cc @@ -0,0 +1,175 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. 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 + +#include <gri_alsa.h> +#include <algorithm> + +static snd_pcm_access_t access_types[] = { + SND_PCM_ACCESS_MMAP_INTERLEAVED, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + SND_PCM_ACCESS_MMAP_COMPLEX, + SND_PCM_ACCESS_RW_INTERLEAVED, + SND_PCM_ACCESS_RW_NONINTERLEAVED +}; + +static snd_pcm_format_t format_types[] = { + // SND_PCM_FORMAT_UNKNOWN, + SND_PCM_FORMAT_S8, + SND_PCM_FORMAT_U8, + SND_PCM_FORMAT_S16_LE, + SND_PCM_FORMAT_S16_BE, + SND_PCM_FORMAT_U16_LE, + SND_PCM_FORMAT_U16_BE, + SND_PCM_FORMAT_S24_LE, + SND_PCM_FORMAT_S24_BE, + SND_PCM_FORMAT_U24_LE, + SND_PCM_FORMAT_U24_BE, + SND_PCM_FORMAT_S32_LE, + SND_PCM_FORMAT_S32_BE, + SND_PCM_FORMAT_U32_LE, + SND_PCM_FORMAT_U32_BE, + SND_PCM_FORMAT_FLOAT_LE, + SND_PCM_FORMAT_FLOAT_BE, + SND_PCM_FORMAT_FLOAT64_LE, + SND_PCM_FORMAT_FLOAT64_BE, + SND_PCM_FORMAT_IEC958_SUBFRAME_LE, + SND_PCM_FORMAT_IEC958_SUBFRAME_BE, + SND_PCM_FORMAT_MU_LAW, + SND_PCM_FORMAT_A_LAW, + SND_PCM_FORMAT_IMA_ADPCM, + SND_PCM_FORMAT_MPEG, + SND_PCM_FORMAT_GSM, + SND_PCM_FORMAT_SPECIAL, + SND_PCM_FORMAT_S24_3LE, + SND_PCM_FORMAT_S24_3BE, + SND_PCM_FORMAT_U24_3LE, + SND_PCM_FORMAT_U24_3BE, + SND_PCM_FORMAT_S20_3LE, + SND_PCM_FORMAT_S20_3BE, + SND_PCM_FORMAT_U20_3LE, + SND_PCM_FORMAT_U20_3BE, + SND_PCM_FORMAT_S18_3LE, + SND_PCM_FORMAT_S18_3BE, + SND_PCM_FORMAT_U18_3LE, + SND_PCM_FORMAT_U18_3BE +}; + +static unsigned int test_rates[] = { + 8000, 16000, 22050, 32000, 44100, 48000, 96000, 192000 +}; + +#define NELEMS(x) (sizeof(x)/sizeof(x[0])) + +void +gri_alsa_dump_hw_params (snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, FILE *fp) +{ + fprintf (fp, "PCM name: %s\n", snd_pcm_name (pcm)); + + fprintf (fp, "Access types:\n"); + for (unsigned i = 0; i < NELEMS (access_types); i++){ + snd_pcm_access_t at = access_types[i]; + fprintf (fp, " %-20s %s\n", + snd_pcm_access_name (at), + snd_pcm_hw_params_test_access (pcm, hwparams, at) == 0 ? "YES" : "NO"); + } + + fprintf (fp, "Formats:\n"); + for (unsigned i = 0; i < NELEMS (format_types); i++){ + snd_pcm_format_t ft = format_types[i]; + if (0) + fprintf (fp, " %-20s %s\n", + snd_pcm_format_name (ft), + snd_pcm_hw_params_test_format (pcm, hwparams, ft) == 0 ? "YES" : "NO"); + else { + if (snd_pcm_hw_params_test_format (pcm, hwparams, ft) == 0) + fprintf (fp, " %-20s YES\n", snd_pcm_format_name (ft)); + } + } + + fprintf (fp, "Number of channels\n"); + unsigned int min_chan, max_chan; + snd_pcm_hw_params_get_channels_min (hwparams, &min_chan); + snd_pcm_hw_params_get_channels_max (hwparams, &max_chan); + fprintf (fp, " min channels: %d\n", min_chan); + fprintf (fp, " max channels: %d\n", max_chan); + unsigned int chan; + max_chan = std::min (max_chan, 16U); // truncate display... + for (chan = min_chan; chan <= max_chan; chan++){ + fprintf (fp, " %d channels\t%s\n", chan, + snd_pcm_hw_params_test_channels (pcm, hwparams, chan) == 0 ? "YES" : "NO"); + } + + fprintf (fp, "Sample Rates:\n"); + unsigned int min_rate, max_rate; + int min_dir, max_dir; + + snd_pcm_hw_params_get_rate_min (hwparams, &min_rate, &min_dir); + snd_pcm_hw_params_get_rate_max (hwparams, &max_rate, &max_dir); + fprintf (fp, " min rate: %7d (dir = %d)\n", min_rate, min_dir); + fprintf (fp, " max rate: %7d (dir = %d)\n", max_rate, max_dir); + for (unsigned i = 0; i < NELEMS (test_rates); i++){ + unsigned int rate = test_rates[i]; + fprintf (fp, " %6u %s\n", rate, + snd_pcm_hw_params_test_rate (pcm, hwparams, rate, 0) == 0 ? "YES" : "NO"); + } + + fflush (fp); +} + +bool +gri_alsa_pick_acceptable_format (snd_pcm_t *pcm, + snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t acceptable_formats[], + unsigned nacceptable_formats, + snd_pcm_format_t *selected_format, + const char *error_msg_tag, + bool verbose) +{ + int err; + + // pick a format that we like... + for (unsigned i = 0; i < nacceptable_formats; i++){ + if (snd_pcm_hw_params_test_format (pcm, hwparams, + acceptable_formats[i]) == 0){ + err = snd_pcm_hw_params_set_format (pcm, hwparams, acceptable_formats[i]); + if (err < 0){ + fprintf (stderr, "%s[%s]: failed to set format: %s\n", + error_msg_tag, snd_pcm_name (pcm), snd_strerror (err)); + return false; + } + if (verbose) + fprintf (stdout, "%s[%s]: using %s\n", + error_msg_tag, snd_pcm_name (pcm), + snd_pcm_format_name (acceptable_formats[i])); + *selected_format = acceptable_formats[i]; + return true; + } + } + + fprintf (stderr, "%s[%s]: failed to find acceptable format", + error_msg_tag, snd_pcm_name (pcm)); + return false; +} diff --git a/gr-audio-alsa/src/gri_alsa.h b/gr-audio-alsa/src/gri_alsa.h new file mode 100644 index 000000000..841c54d4b --- /dev/null +++ b/gr-audio-alsa/src/gri_alsa.h @@ -0,0 +1,44 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef INCLUDED_GRI_ALSA_H +#define INCLUDED_GRI_ALSA_H + +#include <stdio.h> +#include <alsa/asoundlib.h> + +void +gri_alsa_dump_hw_params (snd_pcm_t *pcm, + snd_pcm_hw_params_t *hwparams, + FILE *fp); + +bool +gri_alsa_pick_acceptable_format (snd_pcm_t *pcm, + snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t acceptable_formats[], + unsigned nacceptable_formats, + snd_pcm_format_t *selected_format, + const char *error_msg_tag, + bool verbose); + + +#endif /* INCLUDED_GRI_ALSA_H */ diff --git a/gr-audio-alsa/src/qa_alsa.py b/gr-audio-alsa/src/qa_alsa.py new file mode 100755 index 000000000..2994573d1 --- /dev/null +++ b/gr-audio-alsa/src/qa_alsa.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# +# Copyright 2005 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from gnuradio import gr, gr_unittest +import audio_alsa + +class qa_alsa (gr_unittest.TestCase): + + def setUp (self): + self.fg = gr.flow_graph () + + def tearDown (self): + self.fg = None + + def test_000_nop (self): + """Just see if we can import the module... + They may not have ALSA drivers, etc. Don't try to run anything""" + pass + +if __name__ == '__main__': + gr_unittest.main () diff --git a/gr-audio-alsa/src/run_tests.in b/gr-audio-alsa/src/run_tests.in new file mode 100644 index 000000000..4bfb076e8 --- /dev/null +++ b/gr-audio-alsa/src/run_tests.in @@ -0,0 +1,47 @@ +#!/bin/sh + +# All this strange PYTHONPATH manipulation is required to run our +# tests using our just built shared library and swig-generated python +# code prior to installation. + +# build tree == src tree unless you're doing a VPATH build. +# If you don't know what a VPATH build is, you're not doing one. Relax... + +prefix=@prefix@ +exec_prefix=@exec_prefix@ + +# Where to look in the build tree for our shared library +libbld=@abs_top_builddir@/gr-audio-alsa/src +# Where to look in the src tree for swig generated python code +libsrc=@abs_top_srcdir@/gr-audio-alsa/src +# Where to look in the src tree for hand written python code +py=@abs_top_srcdir@/gr-audio-alsa/src + +# Where to look for GNU Radio python modules in current build tree +# FIXME this is wrong on a distcheck. We really need to ask gnuradio-core +# where it put its python files. +grpythonbld=@abs_top_builddir@/gnuradio-core/src/python/:@abs_top_builddir@/gnuradio-core/src/lib/swig/:@abs_top_builddir@/gnuradio-core/src/lib/swig/.libs + +PYTHONPATH="$grpythonbld:$libbld:$libbld/.libs:$libsrc:$py:$PYTHONPATH" +export PYTHONPATH + +# +# This is the simple part... +# Run everything that matches qa_*.py and return the final result. +# + +ok=yes +for file in @srcdir@/qa_*.py +do + if ! $file + then + ok=no + fi +done + +if [ $ok = yes ] +then + exit 0 +else + exit 1 +fi |