diff options
author | jcorgan | 2006-08-03 04:51:51 +0000 |
---|---|---|
committer | jcorgan | 2006-08-03 04:51:51 +0000 |
commit | 5d69a524f81f234b3fbc41d49ba18d6f6886baba (patch) | |
tree | b71312bf7f1e8d10fef0f3ac6f28784065e73e72 /gr-audio-portaudio/src | |
download | gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.gz gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.bz2 gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.zip |
Houston, we have a trunk.
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3122 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'gr-audio-portaudio/src')
-rw-r--r-- | gr-audio-portaudio/src/Makefile.am | 92 | ||||
-rw-r--r-- | gr-audio-portaudio/src/audio_portaudio.i | 77 | ||||
-rw-r--r-- | gr-audio-portaudio/src/audio_portaudio_sink.cc | 340 | ||||
-rw-r--r-- | gr-audio-portaudio/src/audio_portaudio_sink.h | 102 | ||||
-rw-r--r-- | gr-audio-portaudio/src/audio_portaudio_source.cc | 355 | ||||
-rw-r--r-- | gr-audio-portaudio/src/audio_portaudio_source.h | 101 | ||||
-rw-r--r-- | gr-audio-portaudio/src/gri_portaudio.cc | 111 | ||||
-rw-r--r-- | gr-audio-portaudio/src/gri_portaudio.h | 32 | ||||
-rwxr-xr-x | gr-audio-portaudio/src/qa_portaudio.py | 40 | ||||
-rw-r--r-- | gr-audio-portaudio/src/run_tests.in | 47 |
10 files changed, 1297 insertions, 0 deletions
diff --git a/gr-audio-portaudio/src/Makefile.am b/gr-audio-portaudio/src/Makefile.am new file mode 100644 index 000000000..1b6d518e0 --- /dev/null +++ b/gr-audio-portaudio/src/Makefile.am @@ -0,0 +1,92 @@ +# +# 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 + +LIBS += $(GNURADIO_CORE_LIBS) + +# Install this stuff so that it ends up as the gnuradio.audio_portaudio module +# This usually ends up at: +# ${prefix}/lib/python${python_version}/site-packages/gnuradio + +ourpythondir = $(grpythondir) +ourlibdir = $(grpyexecdir) + +EXTRA_DIST = run_tests.in + +TESTS = run_tests + +LOCAL_IFILES = \ + audio_portaudio.i + +NON_LOCAL_IFILES = \ + $(top_srcdir)/gnuradio-core/src/lib/swig/gnuradio.i + +ALL_IFILES = \ + $(LOCAL_IFILES) \ + $(NON_LOCAL_IFILES) + +BUILT_SOURCES = \ + audio_portaudio.cc \ + audio_portaudio.py + +ourpython_PYTHON = \ + audio_portaudio.py + +INCLUDES = $(STD_DEFINES_AND_INCLUDES) $(PYTHON_CPPFLAGS) + +SWIGPYTHONARGS = $(SWIGPYTHONFLAGS) $(STD_DEFINES_AND_INCLUDES) -I/usr/include + +ourlib_LTLIBRARIES = _audio_portaudio.la + + +_audio_portaudio_la_SOURCES = \ + audio_portaudio.cc \ + audio_portaudio_sink.cc \ + audio_portaudio_source.cc \ + gri_portaudio.cc + +grinclude_HEADERS = \ + audio_portaudio_sink.h \ + audio_portaudio_source.h + +noinst_HEADERS = \ + gri_portaudio.h + + +swiginclude_HEADERS = \ + $(LOCAL_IFILES) + +_audio_portaudio_la_LIBADD = \ + $(PYTHON_LDFLAGS) \ + -lstdc++ + +_audio_portaudio_la_LDFLAGS = $(NO_UNDEFINED) -module -avoid-version + +audio_portaudio.cc audio_portaudio.py: $(ALL_IFILES) audio_portaudio_sink.h audio_portaudio_source.h + $(SWIG) $(SWIGPYTHONARGS) -module audio_portaudio -o audio_portaudio.cc $< + + +noinst_PYTHON = \ + qa_portaudio.py + +MOSTLYCLEANFILES = \ + $(BUILT_SOURCES) *~ *.pyc diff --git a/gr-audio-portaudio/src/audio_portaudio.i b/gr-audio-portaudio/src/audio_portaudio.i new file mode 100644 index 000000000..35fc5c633 --- /dev/null +++ b/gr-audio-portaudio/src/audio_portaudio.i @@ -0,0 +1,77 @@ +/* -*- 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. + */ + +%feature("autodoc","1"); + +%include "exception.i" +%import "gnuradio.i" // the common stuff + +%{ +#include "gnuradio_swig_bug_workaround.h" // mandatory bug fix +#include "audio_portaudio_sink.h" +#include "audio_portaudio_source.h" +#include <stdexcept> +%} + +// ---------------------------------------------------------------- + +GR_SWIG_BLOCK_MAGIC(audio_portaudio,source) + +audio_portaudio_source_sptr +audio_portaudio_make_source (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true + ) throw (std::runtime_error); + +class audio_portaudio_source : public gr_sync_block { + + protected: + audio_portaudio_source (int sampling_rate, + const std::string device_name, + bool ok_to_block + ) throw (std::runtime_error); + + public: + ~audio_portaudio_source (); +}; + +// ---------------------------------------------------------------- + +GR_SWIG_BLOCK_MAGIC(audio_portaudio,sink) + +audio_portaudio_sink_sptr +audio_portaudio_make_sink (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true + ) throw (std::runtime_error); + +class audio_portaudio_sink : public gr_sync_block { + + protected: + audio_portaudio_sink (int sampling_rate, + const std::string device_name, + bool ok_to_block + ) throw (std::runtime_error); + + public: + ~audio_portaudio_sink (); +}; diff --git a/gr-audio-portaudio/src/audio_portaudio_sink.cc b/gr-audio-portaudio/src/audio_portaudio_sink.cc new file mode 100644 index 000000000..06bcc957a --- /dev/null +++ b/gr-audio-portaudio/src/audio_portaudio_sink.cc @@ -0,0 +1,340 @@ +/* -*- c++ -*- */ +/* + * Copyright 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 he 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_portaudio_sink.h> +#include <gr_io_signature.h> +#include <gr_prefs.h> +#include <stdio.h> +#include <iostream> +#include <unistd.h> +#include <stdexcept> +#include <gri_portaudio.h> +#include <omnithread.h> + +#define LOGGING 0 // define to 0 or 1 + +#define SAMPLE_FORMAT paFloat32 +typedef float sample_t; + +// Number of portaudio buffers in the ringbuffer +static const unsigned int N_BUFFERS = 4; + +static std::string +default_device_name () +{ + return gr_prefs::singleton()->get_string("audio_portaudio", "default_output_device", ""); +} + +void +audio_portaudio_sink::create_ringbuffer(void) +{ + int bufsize_samples = d_portaudio_buffer_size_frames * d_output_parameters.channelCount; + + if (d_verbose) + fprintf(stderr,"ring buffer size = %d frames\n", + N_BUFFERS*bufsize_samples/d_output_parameters.channelCount); + + // FYI, the buffer indicies are in units of samples. + d_writer = gr_make_buffer(N_BUFFERS * bufsize_samples, sizeof(sample_t)); + d_reader = gr_buffer_add_reader(d_writer, 0); +} + +/* + * This routine will be called by the PortAudio engine when audio is needed. + * It may called at interrupt level on some machines so don't do anything + * that could mess up the system like calling malloc() or free(). + * + * Our job is to write framesPerBuffer frames into outputBuffer. + */ +int +portaudio_sink_callback (const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *arg) +{ + audio_portaudio_sink *self = (audio_portaudio_sink *)arg; + int nreqd_samples = + framesPerBuffer * self->d_output_parameters.channelCount; + + int navail_samples = self->d_reader->items_available(); + + if (nreqd_samples <= navail_samples){ // We've got enough data... + if (LOGGING) + self->d_log->printf("PAsink cb: f/b = %4ld\n", framesPerBuffer); + // copy from ringbuffer into output buffer + memcpy(outputBuffer, + self->d_reader->read_pointer(), + nreqd_samples * sizeof(sample_t)); + self->d_reader->update_read_pointer(nreqd_samples); + + // Tell the sink thread there is new room in the ringbuffer. + self->d_ringbuffer_ready.post(); + return paContinue; + } + + else { // underrun + if (LOGGING) + self->d_log->printf("PAsink cb: f/b = %4ld UNDERRUN\n", framesPerBuffer); + + self->d_nunderuns++; + ::write(2, "aU", 2); // FIXME change to non-blocking call + + // FIXME we should transfer what we've got and pad the rest + memset(outputBuffer, 0, nreqd_samples * sizeof(sample_t)); + + self->d_ringbuffer_ready.post(); // Tell the sink to get going! + return paContinue; + } +} + + +// ---------------------------------------------------------------- + +audio_portaudio_sink_sptr +audio_portaudio_make_sink (int sampling_rate, const std::string dev, bool ok_to_block) +{ + return audio_portaudio_sink_sptr (new audio_portaudio_sink (sampling_rate, + dev, ok_to_block)); +} + +audio_portaudio_sink::audio_portaudio_sink(int sampling_rate, + const std::string device_name, + bool ok_to_block) + : gr_sync_block ("audio_portaudio_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_ok_to_block(ok_to_block), + d_verbose(gr_prefs::singleton()->get_bool("audio_portaudio", "verbose", false)), + d_portaudio_buffer_size_frames(0), + d_stream(0), + d_ringbuffer_ready(1, 1), // binary semaphore + d_nunderuns(0) +{ + memset(&d_output_parameters, 0, sizeof(d_output_parameters)); + if (LOGGING) + d_log = gri_logger::singleton(); + + PaError err; + int i, numDevices; + PaDeviceIndex device = 0; + const PaDeviceInfo *deviceInfo = NULL; + + err = Pa_Initialize(); + if (err != paNoError) { + bail ("Initialize failed", err); + } + + if (d_verbose) + gri_print_devices(); + + numDevices = Pa_GetDeviceCount(); + if (numDevices < 0) + bail("Pa Device count failed", 0); + if (numDevices == 0) + bail("no devices available", 0); + + if (d_device_name.empty()) + { + // FIXME Get smarter about picking something + fprintf(stderr,"\nUsing Default Device\n"); + device = Pa_GetDefaultOutputDevice(); + deviceInfo = Pa_GetDeviceInfo(device); + fprintf(stderr,"%s is the chosen device using %s as the host\n", + deviceInfo->name, Pa_GetHostApiInfo(deviceInfo->hostApi)->name); + } + else + { + bool found = false; + fprintf(stderr,"\nTest Devices\n"); + for (i=0;i<numDevices;i++) { + deviceInfo = Pa_GetDeviceInfo( i ); + fprintf(stderr,"Testing device name: %s",deviceInfo->name); + if (strstr(deviceInfo->name, d_device_name.c_str())){ + fprintf(stderr," Chosen!\n"); + device = gri_pa_find_device_by_name(deviceInfo->name); + fprintf(stderr,"%s using %s as the host\n",d_device_name.c_str(), + Pa_GetHostApiInfo(deviceInfo->hostApi)->name), fflush(stderr); + found = true; + deviceInfo = Pa_GetDeviceInfo(device); + i = numDevices; // force loop exit + } + fprintf(stderr,"\n"),fflush(stderr); + } + + if (!found){ + bail("Failed to find specified device name", 0); + exit(1); + } + } + + + d_output_parameters.device = device; + d_output_parameters.channelCount = deviceInfo->maxOutputChannels; + d_output_parameters.sampleFormat = SAMPLE_FORMAT; + d_output_parameters.suggestedLatency = deviceInfo->defaultLowOutputLatency; + d_output_parameters.hostApiSpecificStreamInfo = NULL; + + // We fill in the real channelCount in check_topology when we know + // how many inputs are connected to us. + + // Now that we know the maximum number of channels (allegedly) + // supported by the h/w, we can compute a reasonable input + // signature. The portaudio specs say that they'll accept any + // number of channels from 1 to max. + set_input_signature(gr_make_io_signature(1, deviceInfo->maxOutputChannels, + sizeof (sample_t))); +} + + +bool +audio_portaudio_sink::check_topology (int ninputs, int noutputs) +{ + PaError err; + + if (Pa_IsStreamActive(d_stream)) + { + Pa_CloseStream(d_stream); + d_stream = 0; + d_reader.reset(); // boost::shared_ptr for d_reader = 0 + d_writer.reset(); // boost::shared_ptr for d_write = 0 + } + + d_output_parameters.channelCount = ninputs; // # of channels we're really using + +#if 1 + d_portaudio_buffer_size_frames = (int)(0.0213333333 * d_sampling_rate + 0.5); // Force 1024 frame buffers at 48000 + fprintf(stderr, "Latency = %8.5f, requested sampling_rate = %g\n", // Force latency to 21.3333333.. ms + 0.0213333333, (double)d_sampling_rate); +#endif + err = Pa_OpenStream(&d_stream, + NULL, // No input + &d_output_parameters, + d_sampling_rate, + d_portaudio_buffer_size_frames, + paClipOff, + &portaudio_sink_callback, + (void*)this); + + if (err != paNoError) { + output_error_msg ("OpenStream failed", err); + return false; + } + +#if 0 + const PaStreamInfo *psi = Pa_GetStreamInfo(d_stream); + + d_portaudio_buffer_size_frames = (int)(d_output_parameters.suggestedLatency * psi->sampleRate); + fprintf(stderr, "Latency = %7.4f, psi->sampleRate = %g\n", + d_output_parameters.suggestedLatency, psi->sampleRate); +#endif + + fprintf(stderr, "d_portaudio_buffer_size_frames = %d\n", d_portaudio_buffer_size_frames); + + assert(d_portaudio_buffer_size_frames != 0); + + create_ringbuffer(); + + err = Pa_StartStream(d_stream); + if (err != paNoError) { + output_error_msg ("StartStream failed", err); + return false; + } + + return true; +} + +audio_portaudio_sink::~audio_portaudio_sink () +{ + Pa_StopStream(d_stream); // wait for output to drain + Pa_CloseStream(d_stream); + Pa_Terminate(); +} + +/* + * This version consumes everything sent to it, blocking if required. + * I think this will allow us better control of the total buffering/latency + * in the audio path. + */ +int +audio_portaudio_sink::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + const float **in = (const float **) &input_items[0]; + const unsigned nchan = d_output_parameters.channelCount; // # of channels == samples/frame + + int k; + for (k = 0; k < noutput_items; ){ + + int nframes = d_writer->space_available() / nchan; // How much space in ringbuffer + if (nframes == 0){ // no room... + if (d_ok_to_block){ + d_ringbuffer_ready.wait(); // block here, then try again + continue; + } + else { + // There's no room and we're not allowed to block. + // (A USRP is most likely controlling the pacing through the pipeline.) + // We drop the samples on the ground, and say we processed them all ;) + // + // FIXME, there's probably room for a bit more finesse here. + return noutput_items; + } + } + + // We can write the smaller of the request and the room we've got + int nf = std::min(noutput_items - k, nframes); + + float *p = (float *) d_writer->write_pointer(); + for (int i = 0; i < nf; i++){ + for (unsigned int c = 0; c < nchan; c++){ + *p++ = in[c][k + i]; + } + } + d_writer->update_write_pointer(nf * nchan); + k += nf; + } + + return k; // tell how many we actually did +} + +void +audio_portaudio_sink::output_error_msg (const char *msg, int err) +{ + fprintf (stderr, "audio_portaudio_sink[%s]: %s: %s\n", + d_device_name.c_str (), msg, Pa_GetErrorText(err)); +} + +void +audio_portaudio_sink::bail (const char *msg, int err) throw (std::runtime_error) +{ + output_error_msg (msg, err); + throw std::runtime_error ("audio_portaudio_sink"); +} diff --git a/gr-audio-portaudio/src/audio_portaudio_sink.h b/gr-audio-portaudio/src/audio_portaudio_sink.h new file mode 100644 index 000000000..2069f7c8b --- /dev/null +++ b/gr-audio-portaudio/src/audio_portaudio_sink.h @@ -0,0 +1,102 @@ +/* -*- c++ -*- */ +/* + * Copyright 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_PORTAUDIO_SINK_H +#define INCLUDED_AUDIO_PORTAUDIO_SINK_H + +#include <gr_sync_block.h> +#include <gr_buffer.h> +#include <omnithread.h> +#include <string> +#include <portaudio.h> +#include <stdexcept> +#include <gri_logger.h> + +class audio_portaudio_sink; +typedef boost::shared_ptr<audio_portaudio_sink> audio_portaudio_sink_sptr; + +/*! + * \PORTAUDIO audio sink. + * \param sampling_rate sampling rate in Hz + * \param dev PORTAUDIO device name, e.g., "pa:" + * \param ok_to_block true if it's ok for us to block + */ +audio_portaudio_sink_sptr +audio_portaudio_make_sink (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true); + +PaStreamCallback portaudio_sink_callback; + + +/*! + * \ Audio sink using PORTAUDIO + * + * Input samples must be in the range [-1,1]. + */ +class audio_portaudio_sink : public gr_sync_block { + friend audio_portaudio_sink_sptr + audio_portaudio_make_sink (int sampling_rate, + const std::string device_name, + bool ok_to_block); + + friend PaStreamCallback portaudio_sink_callback; + + + unsigned int d_sampling_rate; + std::string d_device_name; + bool d_ok_to_block; + bool d_verbose; + + unsigned int d_portaudio_buffer_size_frames; // number of frames in a portaudio buffer + + PaStream *d_stream; + PaStreamParameters d_output_parameters; + + gr_buffer_sptr d_writer; // buffer used between work and callback + gr_buffer_reader_sptr d_reader; + omni_semaphore d_ringbuffer_ready; // binary semaphore + + + // random stats + int d_nunderuns; // count of underruns + gri_logger_sptr d_log; // handle to non-blocking logging instance + + void output_error_msg (const char *msg, int err); + void bail (const char *msg, int err) throw (std::runtime_error); + void create_ringbuffer(); + + + protected: + audio_portaudio_sink (int sampling_rate, const std::string device_name, + bool ok_to_block); + + public: + ~audio_portaudio_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); +}; + +#endif /* INCLUDED_AUDIO_PORTAUDIO_SINK_H */ diff --git a/gr-audio-portaudio/src/audio_portaudio_source.cc b/gr-audio-portaudio/src/audio_portaudio_source.cc new file mode 100644 index 000000000..3741e826c --- /dev/null +++ b/gr-audio-portaudio/src/audio_portaudio_source.cc @@ -0,0 +1,355 @@ +/* -*- c++ -*- */ +/* + * Copyright 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 he 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_portaudio_source.h> +#include <gr_io_signature.h> +#include <gr_prefs.h> +#include <stdio.h> +#include <iostream> +#include <unistd.h> +#include <stdexcept> +#include <gri_portaudio.h> +#include <omnithread.h> + +#define LOGGING 0 // define to 0 or 1 + +#define SAMPLE_FORMAT paFloat32 +typedef float sample_t; + +// Number of portaudio buffers in the ringbuffer +static const unsigned int N_BUFFERS = 4; + +static std::string +default_device_name () +{ + return gr_prefs::singleton()->get_string("audio_portaudio", "default_input_device", ""); +} + +void +audio_portaudio_source::create_ringbuffer(void) +{ + int bufsize_samples = d_portaudio_buffer_size_frames * d_input_parameters.channelCount; + + if (d_verbose) + fprintf(stderr, "ring buffer size = %d frames\n", + N_BUFFERS*bufsize_samples/d_input_parameters.channelCount); + + // FYI, the buffer indicies are in units of samples. + d_writer = gr_make_buffer(N_BUFFERS * bufsize_samples, sizeof(sample_t)); + d_reader = gr_buffer_add_reader(d_writer, 0); +} + +/* + * This routine will be called by the PortAudio engine when audio is needed. + * It may called at interrupt level on some machines so don't do anything + * that could mess up the system like calling malloc() or free(). + * + * Our job is to copy framesPerBuffer frames from inputBuffer. + */ +int +portaudio_source_callback (const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *arg) +{ + audio_portaudio_source *self = (audio_portaudio_source *)arg; + int nchan = self->d_input_parameters.channelCount; + int nframes_to_copy = framesPerBuffer; + int nframes_room = self->d_writer->space_available() / nchan; + + if (nframes_to_copy <= nframes_room){ // We've got room for the data .. + if (LOGGING) + self->d_log->printf("PAsrc cb: f/b = %4ld\n", framesPerBuffer); + + // copy from input buffer to ringbuffer + memcpy(self->d_writer->write_pointer(), + inputBuffer, + nframes_to_copy * nchan * sizeof(sample_t)); + self->d_writer->update_write_pointer(nframes_to_copy * nchan); + + // Tell the source thread there is new data in the ringbuffer. + self->d_ringbuffer_ready.post(); + return paContinue; + } + + else { // overrun + if (LOGGING) + self->d_log->printf("PAsrc cb: f/b = %4ld OVERRUN\n", framesPerBuffer); + + self->d_noverruns++; + ::write(2, "aU", 2); // FIXME change to non-blocking call + +#if 0 + // copy any frames that will fit + memcpy(self->d_writer->write_pointer(), + inputBuffer, + nframes_room * nchan * sizeof(sample_t)); + self->d_writer->update_write_pointer(nframes_room * nchan); +#endif + + self->d_ringbuffer_ready.post(); // Tell the sink to get going! + return paContinue; + } +} + + +// ---------------------------------------------------------------- + +audio_portaudio_source_sptr +audio_portaudio_make_source (int sampling_rate, const std::string dev, bool ok_to_block) +{ + return audio_portaudio_source_sptr (new audio_portaudio_source (sampling_rate, + dev, ok_to_block)); +} + +audio_portaudio_source::audio_portaudio_source(int sampling_rate, + const std::string device_name, + bool ok_to_block) + : gr_sync_block ("audio_portaudio_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_ok_to_block(ok_to_block), + d_verbose(gr_prefs::singleton()->get_bool("audio_portaudio", "verbose", false)), + d_portaudio_buffer_size_frames(0), + d_stream(0), + d_ringbuffer_ready(1, 1), // binary semaphore + d_noverruns(0) +{ + memset(&d_input_parameters, 0, sizeof(d_input_parameters)); + if (LOGGING) + d_log = gri_logger::singleton(); + + PaError err; + int i, numDevices; + PaDeviceIndex device = 0; + const PaDeviceInfo *deviceInfo = NULL; + + + err = Pa_Initialize(); + if (err != paNoError) { + bail ("Initialize failed", err); + } + + if (d_verbose) + gri_print_devices(); + + numDevices = Pa_GetDeviceCount(); + if (numDevices < 0) + bail("Pa Device count failed", 0); + if (numDevices == 0) + bail("no devices available", 0); + + if (d_device_name.empty()) + { + // FIXME Get smarter about picking something + device = Pa_GetDefaultInputDevice(); + deviceInfo = Pa_GetDeviceInfo(device); + fprintf(stderr,"%s is the chosen device using %s as the host\n", + deviceInfo->name, Pa_GetHostApiInfo(deviceInfo->hostApi)->name); + } + else + { + bool found = false; + + for (i=0;i<numDevices;i++) { + deviceInfo = Pa_GetDeviceInfo( i ); + fprintf(stderr,"Testing device name: %s",deviceInfo->name); + if (strstr(deviceInfo->name, d_device_name.c_str())){ + fprintf(stderr," Chosen!\n"); + device = gri_pa_find_device_by_name(deviceInfo->name); + fprintf(stderr,"%s using %s as the host\n",d_device_name.c_str(), + Pa_GetHostApiInfo(deviceInfo->hostApi)->name), fflush(stderr); + found = true; + deviceInfo = Pa_GetDeviceInfo(device); + i = numDevices; // force loop exit + } + fprintf(stderr,"\n"),fflush(stderr); + } + + if (!found){ + bail("Failed to find specified device name", 0); + } + } + + + d_input_parameters.device = device; + d_input_parameters.channelCount = deviceInfo->maxInputChannels; + d_input_parameters.sampleFormat = SAMPLE_FORMAT; + d_input_parameters.suggestedLatency = deviceInfo->defaultLowInputLatency; + d_input_parameters.hostApiSpecificStreamInfo = NULL; + + // We fill in the real channelCount in check_topology when we know + // how many inputs are connected to us. + + // Now that we know the maximum number of channels (allegedly) + // supported by the h/w, we can compute a reasonable output + // signature. The portaudio specs say that they'll accept any + // number of channels from 1 to max. + set_output_signature(gr_make_io_signature(1, deviceInfo->maxInputChannels, + sizeof (sample_t))); +} + + +bool +audio_portaudio_source::check_topology (int ninputs, int noutputs) +{ + PaError err; + + if (Pa_IsStreamActive(d_stream)) + { + Pa_CloseStream(d_stream); + d_stream = 0; + d_reader.reset(); // boost::shared_ptr for d_reader = 0 + d_writer.reset(); // boost::shared_ptr for d_write = 0 + } + + d_input_parameters.channelCount = noutputs; // # of channels we're really using + +#if 1 + d_portaudio_buffer_size_frames = (int)(0.0213333333 * d_sampling_rate + 0.5); // Force 512 frame buffers at 48000 + fprintf(stderr, "Latency = %8.5f, requested sampling_rate = %g\n", // Force latency to 21.3333333.. ms + 0.0213333333, (double)d_sampling_rate); +#endif + err = Pa_OpenStream(&d_stream, + &d_input_parameters, + NULL, // No output + d_sampling_rate, + d_portaudio_buffer_size_frames, + paClipOff, + &portaudio_source_callback, + (void*)this); + + if (err != paNoError) { + output_error_msg ("OpenStream failed", err); + return false; + } + +#if 0 + const PaStreamInfo *psi = Pa_GetStreamInfo(d_stream); + + d_portaudio_buffer_size_frames = (int)(d_input_parameters.suggestedLatency * psi->sampleRate); + fprintf(stderr, "Latency = %7.4f, psi->sampleRate = %g\n", + d_input_parameters.suggestedLatency, psi->sampleRate); +#endif + + fprintf(stderr, "d_portaudio_buffer_size_frames = %d\n", d_portaudio_buffer_size_frames); + + assert(d_portaudio_buffer_size_frames != 0); + + create_ringbuffer(); + + err = Pa_StartStream(d_stream); + if (err != paNoError) { + output_error_msg ("StartStream failed", err); + return false; + } + + return true; +} + +audio_portaudio_source::~audio_portaudio_source () +{ + Pa_StopStream(d_stream); // wait for output to drain + Pa_CloseStream(d_stream); + Pa_Terminate(); +} + +int +audio_portaudio_source::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + float **out = (float **) &output_items[0]; + const unsigned nchan = d_input_parameters.channelCount; // # of channels == samples/frame + + int k; + for (k = 0; k < noutput_items; ){ + + int nframes = d_reader->items_available() / nchan; // # of frames in ringbuffer + if (nframes == 0){ // no data right now... + if (k > 0) // If we've produced anything so far, return that + return k; + + if (d_ok_to_block){ + d_ringbuffer_ready.wait(); // block here, then try again + continue; + } + + assert(k == 0); + + // There's no data and we're not allowed to block. + // (A USRP is most likely controlling the pacing through the pipeline.) + // This is an underun. The scheduler wouldn't have called us if it + // had anything better to do. Thus we really need to produce some amount + // of "fill". + // + // There are lots of options for comfort noise, etc. + // FIXME We'll fill with zeros for now. Yes, it will "click"... + + // Fill with some frames of zeros + int nf = std::min(noutput_items - k, (int) d_portaudio_buffer_size_frames); + for (int i = 0; i < nf; i++){ + for (unsigned int c = 0; c < nchan; c++){ + out[c][k + i] = 0; + } + } + k += nf; + return k; + } + + // We can read the smaller of the request and what's in the buffer. + int nf = std::min(noutput_items - k, nframes); + + const float *p = (const float *) d_reader->read_pointer(); + for (int i = 0; i < nf; i++){ + for (unsigned int c = 0; c < nchan; c++){ + out[c][k + i] = *p++; + } + } + d_reader->update_read_pointer(nf * nchan); + k += nf; + } + + return k; // tell how many we actually did +} + +void +audio_portaudio_source::output_error_msg (const char *msg, int err) +{ + fprintf (stderr, "audio_portaudio_source[%s]: %s: %s\n", + d_device_name.c_str (), msg, Pa_GetErrorText(err)); +} + +void +audio_portaudio_source::bail (const char *msg, int err) throw (std::runtime_error) +{ + output_error_msg (msg, err); + throw std::runtime_error ("audio_portaudio_source"); +} diff --git a/gr-audio-portaudio/src/audio_portaudio_source.h b/gr-audio-portaudio/src/audio_portaudio_source.h new file mode 100644 index 000000000..25b0c4ba0 --- /dev/null +++ b/gr-audio-portaudio/src/audio_portaudio_source.h @@ -0,0 +1,101 @@ +/* -*- c++ -*- */ +/* + * Copyright 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_PORTAUDIO_SOURCE_H +#define INCLUDED_AUDIO_PORTAUDIO_SOURCE_H + +#include <gr_sync_block.h> +#include <gr_buffer.h> +#include <omnithread.h> +#include <string> +#include <portaudio.h> +#include <stdexcept> +#include <gri_logger.h> + +class audio_portaudio_source; +typedef boost::shared_ptr<audio_portaudio_source> audio_portaudio_source_sptr; + +/*! + * \PORTAUDIO audio source. + * \param sampling_rate sampling rate in Hz + * \param dev PORTAUDIO device name, e.g., "pa:" + * \param ok_to_block true if it's ok for us to block + */ +audio_portaudio_source_sptr +audio_portaudio_make_source (int sampling_rate, + const std::string dev = "", + bool ok_to_block = true); + +PaStreamCallback portaudio_source_callback; + + +/*! + * \ Audio source using PORTAUDIO + * + * Input samples must be in the range [-1,1]. + */ +class audio_portaudio_source : public gr_sync_block { + friend audio_portaudio_source_sptr + audio_portaudio_make_source (int sampling_rate, + const std::string device_name, + bool ok_to_block); + + friend PaStreamCallback portaudio_source_callback; + + + unsigned int d_sampling_rate; + std::string d_device_name; + bool d_ok_to_block; + bool d_verbose; + + unsigned int d_portaudio_buffer_size_frames; // number of frames in a portaudio buffer + + PaStream *d_stream; + PaStreamParameters d_input_parameters; + + gr_buffer_sptr d_writer; // buffer used between work and callback + gr_buffer_reader_sptr d_reader; + omni_semaphore d_ringbuffer_ready; // binary semaphore + + // random stats + int d_noverruns; // count of overruns + gri_logger_sptr d_log; // handle to non-blocking logging instance + + void output_error_msg (const char *msg, int err); + void bail (const char *msg, int err) throw (std::runtime_error); + void create_ringbuffer(); + + + protected: + audio_portaudio_source (int sampling_rate, const std::string device_name, + bool ok_to_block); + + public: + ~audio_portaudio_source (); + + bool check_topology (int ninputs, int noutputs); + + int work (int ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +}; + +#endif /* INCLUDED_AUDIO_PORTAUDIO_SOURCE_H */ diff --git a/gr-audio-portaudio/src/gri_portaudio.cc b/gr-audio-portaudio/src/gri_portaudio.cc new file mode 100644 index 000000000..a2b08afb3 --- /dev/null +++ b/gr-audio-portaudio/src/gri_portaudio.cc @@ -0,0 +1,111 @@ +/* -*- c++ -*- */ +/* + * Copyright 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 <gri_portaudio.h> +#include <portaudio.h> +#include <string.h> + + +PaDeviceIndex +gri_pa_find_device_by_name(const char *name) +{ + int i; + int numDevices; + const PaDeviceInfo *pdi; + int len = strlen( name ); + PaDeviceIndex result = paNoDevice; + numDevices = Pa_GetDeviceCount(); + for( i=0; i<numDevices; i++ ) + { + pdi = Pa_GetDeviceInfo( i ); + if( strncmp( name, pdi->name, len ) == 0 ) + { + result = i; + break; + } + } + return result; +} + + +void +gri_print_devices() +{ + int numDevices, defaultDisplayed, myDevice=0; + const PaDeviceInfo *deviceInfo; + + numDevices = Pa_GetDeviceCount(); + if (numDevices < 0) + return; + + printf("Number of devices found = %d\n", numDevices); + + for (int i=0; i < numDevices; i++ ) { + deviceInfo = Pa_GetDeviceInfo( i ); + printf( "--------------------------------------- device #%d\n", i ); + /* Mark global and API specific default devices */ + defaultDisplayed = 0; + if( i == Pa_GetDefaultInputDevice() ) + { + myDevice = i; + printf( "[ Default Input" ); + defaultDisplayed = 1; + } + else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultInputDevice ) + { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + printf( "[ Default %s Input", hostInfo->name ); + defaultDisplayed = 1; + } + + if( i == Pa_GetDefaultOutputDevice() ) + { + printf( (defaultDisplayed ? "," : "[") ); + printf( " Default Output" ); + defaultDisplayed = 1; + } + else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) + { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + printf( (defaultDisplayed ? "," : "[") ); + printf( " Default %s Output", hostInfo->name ); + defaultDisplayed = 1; + } + if( defaultDisplayed ) + printf( " ]\n" ); + + /* print device info fields */ + printf( "Name = %s\n", deviceInfo->name ); + printf( "Host API = %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name ); + printf( "Max inputs = %d", deviceInfo->maxInputChannels ); + printf( ", Max outputs = %d\n", deviceInfo->maxOutputChannels ); + + printf( "Default low input latency = %8.3f\n", deviceInfo->defaultLowInputLatency ); + printf( "Default low output latency = %8.3f\n", deviceInfo->defaultLowOutputLatency ); + printf( "Default high input latency = %8.3f\n", deviceInfo->defaultHighInputLatency ); + printf( "Default high output latency = %8.3f\n", deviceInfo->defaultHighOutputLatency ); + } +} diff --git a/gr-audio-portaudio/src/gri_portaudio.h b/gr-audio-portaudio/src/gri_portaudio.h new file mode 100644 index 000000000..ca84aec0b --- /dev/null +++ b/gr-audio-portaudio/src/gri_portaudio.h @@ -0,0 +1,32 @@ +/* -*- c++ -*- */ +/* + * Copyright 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_GRI_PORTAUDIO_H +#define INCLUDED_GRI_PORTAUDIO_H + +#include <stdio.h> +#include <portaudio.h> + +PaDeviceIndex gri_pa_find_device_by_name(const char *name); +void gri_print_devices(); + +#endif /* INCLUDED_GRI_PORTAUDIO_H */ diff --git a/gr-audio-portaudio/src/qa_portaudio.py b/gr-audio-portaudio/src/qa_portaudio.py new file mode 100755 index 000000000..535690454 --- /dev/null +++ b/gr-audio-portaudio/src/qa_portaudio.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_portaudio + +class qa_portaudio (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 PORTAUDIO drivers, etc. Don't try to run anything""" + pass + +if __name__ == '__main__': + gr_unittest.main () diff --git a/gr-audio-portaudio/src/run_tests.in b/gr-audio-portaudio/src/run_tests.in new file mode 100644 index 000000000..2f2f40e82 --- /dev/null +++ b/gr-audio-portaudio/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-portaudio/src +# Where to look in the src tree for swig generated python code +libsrc=@abs_top_srcdir@/gr-audio-portaudio/src +# Where to look in the src tree for hand written python code +py=@abs_top_srcdir@/gr-audio-portaudio/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 |