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-osx | |
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-osx')
-rw-r--r-- | gr-audio-osx/AUTHORS | 1 | ||||
-rw-r--r-- | gr-audio-osx/ChangeLog | 25 | ||||
-rw-r--r-- | gr-audio-osx/Makefile.am | 25 | ||||
-rw-r--r-- | gr-audio-osx/README_OSX | 61 | ||||
-rw-r--r-- | gr-audio-osx/TODO | 51 | ||||
-rw-r--r-- | gr-audio-osx/src/Makefile.am | 88 | ||||
-rw-r--r-- | gr-audio-osx/src/audio_osx.h | 49 | ||||
-rw-r--r-- | gr-audio-osx/src/audio_osx.i | 95 | ||||
-rw-r--r-- | gr-audio-osx/src/audio_osx_sink.cc | 398 | ||||
-rw-r--r-- | gr-audio-osx/src/audio_osx_sink.h | 96 | ||||
-rw-r--r-- | gr-audio-osx/src/audio_osx_source.cc | 978 | ||||
-rw-r--r-- | gr-audio-osx/src/audio_osx_source.h | 132 | ||||
-rw-r--r-- | gr-audio-osx/src/circular_buffer.h | 326 | ||||
-rw-r--r-- | gr-audio-osx/src/mld_threads.h | 255 | ||||
-rwxr-xr-x | gr-audio-osx/src/qa_osx.py | 40 | ||||
-rw-r--r-- | gr-audio-osx/src/run_tests.in | 47 | ||||
-rwxr-xr-x | gr-audio-osx/src/test_audio_loop.py | 65 |
17 files changed, 2732 insertions, 0 deletions
diff --git a/gr-audio-osx/AUTHORS b/gr-audio-osx/AUTHORS new file mode 100644 index 000000000..ecc9577a4 --- /dev/null +++ b/gr-audio-osx/AUTHORS @@ -0,0 +1 @@ +Michael Dickens <mdickens@nd.edu> NCIP Lab, University of Notre Dame diff --git a/gr-audio-osx/ChangeLog b/gr-audio-osx/ChangeLog new file mode 100644 index 000000000..0ae5acf78 --- /dev/null +++ b/gr-audio-osx/ChangeLog @@ -0,0 +1,25 @@ +2006-04-22 Michael Dickens <mdickens@nd.edu> + NCIP Lab, University of Notre Dame + + Everything is new. + +# +# 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. +# diff --git a/gr-audio-osx/Makefile.am b/gr-audio-osx/Makefile.am new file mode 100644 index 000000000..077d2aa1f --- /dev/null +++ b/gr-audio-osx/Makefile.am @@ -0,0 +1,25 @@ +# +# 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. +# + +include $(top_srcdir)/Makefile.common + +EXTRA_DIST = README_OSX +SUBDIRS = src diff --git a/gr-audio-osx/README_OSX b/gr-audio-osx/README_OSX new file mode 100644 index 000000000..2a98e96bb --- /dev/null +++ b/gr-audio-osx/README_OSX @@ -0,0 +1,61 @@ +Michael Dickens +2006-Apr-30 + +0) This module should compile and install in the same manner as the +other GNURadio modules (e.g. gnuradio-core), with the possible +exception that GNU libtool 1.5.20 or newer should be installed and +used (if not first in the path) via "make LIBTOOL=/..." and so forth. +Version 1.5.10 has failed making, and while picking version 1.5.20 is +somewhat arbitrary, the newer version compiles and installs easily +under OSX. + +1) This module should be automatically loaded by the Python command +"from gr import audio". The audio import script will automatically +select gr.audio_osx if it is available (though it will try to import +ALSA first, then OSS, then OSX, and finally WINDOWS audio modules, in +that order). If that import command doesn't work, try reinstalling +gnuradio-core from scratch followed by gr-audio-osx. + +2) Instantiation arguments for either source or sink are: + +* sample_rate : integer : default == 44100 + OSX converts the integer sample rate to a double internally; it + would be nice to have this input as a double natively, but that + doesn't work with other audio devices. + +* device_name : string : default == "2" + For OSX, the device name should be an integer string. This value is + the maximum number of channels to allocate (for input or output). + In the "source" case (input), the actual number of channels will be + whatever is available on that current system input device. In the + "sink" case (output), OSX will convert the provided channels into + whatever format is required by the current system output device. + For example, "3" would try to setup for 3 input or output channels. + NOTE that this is a very different use than that for other audio + modules (though they can interpret the number of channels from this + argument). + +* do_block : boolean : default == true + If the data transfer buffer between OSX internals and GNURadio gets + full, either block (true) or overwrite (false) depending on this + variable. + +The following are currently non-standard arguments: + +* channel_config : integer : default == -1 + An enum (internally) describing the channel configuration. Not + currently used, but rather reserved for future expansion. + +* max_sample_count : integer : default == -1 + The maximum number of samples to buffer between OSX internals and + GNURadio. The value -1 is mapped to 1 second's worth of data. + +3) When the buffer is full and do_block is false and new data comes +in, the oldest data will be overwritten. The source will print out +"iX" each time this happens; the sink will print out "oX". + +4) In the "src" directory is a python script "test_audio_loop" which +connects the default audio input device to the default audio output +device. This script is very useful in testing that audio is correctly +installed and both the source and sink are functional. This script is +not run by "make check". diff --git a/gr-audio-osx/TODO b/gr-audio-osx/TODO new file mode 100644 index 000000000..e36ed2b25 --- /dev/null +++ b/gr-audio-osx/TODO @@ -0,0 +1,51 @@ +List as of 2006-Apr-22, by Michael Dickens, primary author + +* Change buffering to use gr_buffer's and necessary related classes. + Currently uses a circular_buffer I wrote myself (in + ./src/circular_buffer.h ), which hides the "circular" part from the + user but otherwise is very fast as well as thread safe. + +* A current limitation of this implementation is that the user cannot + dynamically switch audio devices (input or output) during playback + and use the new device without stopping the current executing GR and + restarting the process. I would like to figure out how to get a + CoreAudio "listener" for when the default hardware input / output + device changes (e.g. when switched by the user in the "Sound" system + preference pane). The code in ./src/audio-osx-source.cc creates + listeners for the AudioUnit being used as well as the Hardware + device, but these for some reason don't do the trick. It's possible + that the Python framework doesn't allow for one to do this. + +* In both the source and sink, move the code which defines the "Audio + Stream Basic Description" (ASBD) to a routine to do this as needed + as start-up as well as in a callback ("listener") if the default + device changes. + +* Tweak the mutex (d_internal) to only where it is truly needed + (around class-specific variables used across all threads) in order + to improve performance. Currently the mutex is used at the + start and end of any function requiring access to the class variables. + +* Change the instantiation arguments ... once those arguments are + finalized. Right now gr.audio.source () takes the sample rate (int + - should be double), the device name (std::string), a boolean for + blocking or not, and 2 completely non-standard ones for channel + configuration and the maximum sample count to buffer. These are + reasonable for OSX, but might not be for other OSs. Nothing to do + right now but wait and discuss. + +* Once the previous issue has been resolved, then the current method + of determining the number of channels needs to be updated. + Currently the "device_name" for OSX should contain a numeric string + for the maximum number of channels to use (e.g. "3" means 3 + channels, generally mapped to 2 channel stereo by OSX). The + "device_name" is generally input via "-O" in Python scripts, so "-O + 3" would allow for 3 incoming output channels (or fewer). In theory + the "channel_config" argument would make more sense for determining + channel usage. Example config strings might be "2.1" for stereo w/ + subwoofer (3 channels), "5.1" or "6.1" for various surround w/ + subwoofer (6 & 7 channels, respectively). OSX can handle all sorts + of channel configurations, but the names for these will be different + than those use for OSS or ALMA or Windows ... thus the need for a + common naming scheme for all cared-about configurations, possibly + with options for other OS-specific options. diff --git a/gr-audio-osx/src/Makefile.am b/gr-audio-osx/src/Makefile.am new file mode 100644 index 000000000..54cc32720 --- /dev/null +++ b/gr-audio-osx/src/Makefile.am @@ -0,0 +1,88 @@ +# +# 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. +# + +include $(top_srcdir)/Makefile.common + +# Install this stuff so that it ends up as the gnuradio.audio_osx 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_osx.i + +NON_LOCAL_IFILES = \ + $(top_srcdir)/gnuradio-core/src/lib/swig/gnuradio.i + +ALL_IFILES = \ + $(LOCAL_IFILES) \ + $(NON_LOCAL_IFILES) + +BUILT_SOURCES = \ + audio_osx.cc \ + audio_osx.py + +ourpython_PYTHON = \ + audio_osx.py + +INCLUDES = $(STD_DEFINES_AND_INCLUDES) $(PYTHON_CPPFLAGS) + +SWIGCPPPYTHONARGS = -python $(PYTHON_CPPFLAGS) $(STD_DEFINES_AND_INCLUDES) + +ourlib_LTLIBRARIES = _audio_osx.la + +_audio_osx_la_SOURCES = \ + audio_osx.cc \ + audio_osx_sink.cc \ + audio_osx_source.cc + +noinst_HEADERS = \ + audio_osx.h \ + circular_buffer.h \ + mld_threads.h + +grinclude_HEADERS = \ + audio_osx_sink.h \ + audio_osx_source.h + +swiginclude_HEADERS = \ + $(LOCAL_IFILES) + +_audio_osx_la_LIBADD = \ + $(PYTHON_LDFLAGS) \ + $(GNURADIO_CORE_LIBS) \ + -lstdc++ + +_audio_osx_la_LDFLAGS = $(NO_UNDEFINED) -module -avoid-version -framework AudioUnit -framework CoreAudio -framework AudioToolbox + +audio_osx.cc audio_osx.py: audio_osx.i + $(SWIG) $(SWIGCPPPYTHONARGS) -module audio_osx -o audio_osx.cc $< + +noinst_PYTHON = qa_osx.py test_audio_loop.py + +MOSTLYCLEANFILES = $(BUILT_SOURCES) *~ *.pyc run_tests *.loT + +CONFIG_CLEAN_FILES = Makefile.in run_tests *.loT diff --git a/gr-audio-osx/src/audio_osx.h b/gr-audio-osx/src/audio_osx.h new file mode 100644 index 000000000..f92ca3fcd --- /dev/null +++ b/gr-audio-osx/src/audio_osx.h @@ -0,0 +1,49 @@ +/* -*- 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_OSX_H +#define INCLUDED_AUDIO_OSX_H + +#define CheckErrorAndThrow(err,what,throw_str) \ +if (err) { \ + OSStatus error = static_cast<OSStatus>(err); \ + fprintf (stderr, "%s\n Error# %ld ('%4s')\n %s:%d\n", \ + what, error, (char*)(&err), __FILE__, __LINE__); \ + fflush (stdout); \ + throw std::runtime_error (throw_str); \ +} + +#define CheckError(err,what) \ +if (err) { \ + OSStatus error = static_cast<OSStatus>(err); \ + fprintf (stderr, "%s\n Error# %ld ('%4s')\n %s:%d\n", \ + what, error, (char*)(&err), __FILE__, __LINE__); \ + fflush (stdout); \ +} + +#ifdef WORDS_BIGENDIAN +#define GR_PCM_ENDIANNESS kLinearPCMFormatFlagIsBigEndian +#else +#define GR_PCM_ENDIANNESS 0 +#endif + +#endif /* INCLUDED_AUDIO_OSX_H */ diff --git a/gr-audio-osx/src/audio_osx.i b/gr-audio-osx/src/audio_osx.i new file mode 100644 index 000000000..9ced419fa --- /dev/null +++ b/gr-audio-osx/src/audio_osx.i @@ -0,0 +1,95 @@ +/* -*- 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. + */ + +%feature("autodoc","1"); + +%include "exception.i" +%import "gnuradio.i" // the common stuff + +%{ +#include "gnuradio_swig_bug_workaround.h" // mandatory bug fix +#include "audio_osx_sink.h" +#include "audio_osx_source.h" +#include <stdexcept> +%} + +// ---------------------------------------------------------------- + +GR_SWIG_BLOCK_MAGIC(audio_osx,sink) + +audio_osx_sink_sptr +audio_osx_make_sink (int sample_rate = 44100, + const std::string device_name = "2", + bool do_block = TRUE, + int channel_config = -1, + int max_sample_count = -1 + ) throw (std::runtime_error); + +class audio_osx_sink : public gr_sync_block { + protected: + audio_osx_sink (int sample_rate = 44100, + const std::string device_name = "2", + bool do_block = TRUE, + int channel_config = -1, + int max_sample_count = -1); + + public: + ~audio_osx_sink (); + + bool start (); + bool stop (); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +}; + +// ---------------------------------------------------------------- + +GR_SWIG_BLOCK_MAGIC(audio_osx,source) + +audio_osx_source_sptr +audio_osx_make_source (int sample_rate = 44100, + const std::string device_name = "2", + bool do_block = TRUE, + int channel_config = -1, + int max_sample_count = -1 + ) throw (std::runtime_error); + +class audio_osx_source : public gr_sync_block { + protected: + audio_osx_source (int sample_rate = 44100, + const std::string device_name = "2", + bool do_block = TRUE, + int channel_config = -1, + int max_sample_count = -1); + + public: + ~audio_osx_source (); + + bool start (); + bool stop (); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +}; diff --git a/gr-audio-osx/src/audio_osx_sink.cc b/gr-audio-osx/src/audio_osx_sink.cc new file mode 100644 index 000000000..14b4a5130 --- /dev/null +++ b/gr-audio-osx/src/audio_osx_sink.cc @@ -0,0 +1,398 @@ +/* -*- 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 + +#define _USE_OMNI_THREADS_ + +#include <audio_osx_sink.h> +#include <gr_io_signature.h> +#include <stdexcept> +#include <audio_osx.h> + +#define _OSX_AU_DEBUG_ 0 + +audio_osx_sink::audio_osx_sink (int sample_rate, + const std::string device_name, + bool do_block, + int channel_config, + int max_sample_count) + : gr_sync_block ("audio_osx_sink", + gr_make_io_signature (0, 0, 0), + gr_make_io_signature (0, 0, 0)), + d_sample_rate (0.0), d_channel_config (0), d_n_channels (0), + d_queueSampleCount (0), d_max_sample_count (0), + d_do_block (do_block), d_internal (0), d_cond_data (0), + d_OutputAU (0) +{ + if (sample_rate <= 0) { + fprintf (stderr, "Invalid Sample Rate: %d\n", sample_rate); + throw std::invalid_argument ("audio_osx_sink::audio_osx_sink"); + } else + d_sample_rate = (Float64) sample_rate; + + if (channel_config <= 0 & channel_config != -1) { + fprintf (stderr, "Invalid Channel Config: %d\n", channel_config); + throw std::invalid_argument ("audio_osx_sink::audio_osx_sink"); + } else if (channel_config == -1) { +// no user input; try "device name" instead + int l_n_channels = (int) strtol (device_name.data(), (char **)NULL, 10); + if (l_n_channels == 0 & errno) { + fprintf (stderr, "Error Converting Device Name: %d\n", errno); + throw std::invalid_argument ("audio_osx_sink::audio_osx_sink"); + } + if (l_n_channels <= 0) + channel_config = 2; + else + channel_config = l_n_channels; + } + + d_n_channels = d_channel_config = channel_config; + +// set the input signature + + set_input_signature (gr_make_io_signature (1, d_n_channels, sizeof (float))); + +// check that the max # of samples to store is valid + + if (max_sample_count == -1) + max_sample_count = sample_rate; + else if (max_sample_count <= 0) { + fprintf (stderr, "Invalid Max Sample Count: %d\n", max_sample_count); + throw std::invalid_argument ("audio_osx_sink::audio_osx_sink"); + } + + d_max_sample_count = max_sample_count; + +// allocate the output circular buffer(s), one per channel + + d_buffers = (circular_buffer<float>**) new + circular_buffer<float>* [d_n_channels]; + UInt32 n_alloc = (UInt32) ceil ((double) d_max_sample_count); + for (UInt32 n = 0; n < d_n_channels; n++) { + d_buffers[n] = new circular_buffer<float> (n_alloc, false, false); + } + +// create the default AudioUnit for output + OSStatus err = noErr; + +// Open the default output unit + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent (NULL, &desc); + if (comp == NULL) { + fprintf (stderr, "FindNextComponent Error\n"); + throw std::runtime_error ("audio_osx_sink::audio_osx_sink"); + } + + err = OpenAComponent (comp, &d_OutputAU); + CheckErrorAndThrow (err, "OpenAComponent", "audio_osx_sink::audio_osx_sink"); + +// Set up a callback function to generate output to the output unit + + AURenderCallbackStruct input; + input.inputProc = (AURenderCallback)(audio_osx_sink::AUOutputCallback); + input.inputProcRefCon = this; + + err = AudioUnitSetProperty (d_OutputAU, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof (input)); + CheckErrorAndThrow (err, "AudioUnitSetProperty Render Callback", "audio_osx_sink::audio_osx_sink"); + +// tell the Output Unit what format data will be supplied to it +// so that it handles any format conversions + + AudioStreamBasicDescription streamFormat; + streamFormat.mSampleRate = (Float64)(sample_rate); + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = (kLinearPCMFormatFlagIsFloat | + GR_PCM_ENDIANNESS | + kLinearPCMFormatFlagIsPacked | + kAudioFormatFlagIsNonInterleaved); + streamFormat.mBytesPerPacket = 4; + streamFormat.mFramesPerPacket = 1; + streamFormat.mBytesPerFrame = 4; + streamFormat.mChannelsPerFrame = d_n_channels; + streamFormat.mBitsPerChannel = 32; + + err = AudioUnitSetProperty (d_OutputAU, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof (AudioStreamBasicDescription)); + CheckErrorAndThrow (err, "AudioUnitSetProperty StreamFormat", "audio_osx_sink::audio_osx_sink"); + +// create the stuff to regulate I/O + + d_internal = new mld_mutex (); + if (d_internal == NULL) + CheckErrorAndThrow (errno, "new mld_mutex (internal)", + "audio_osx_source::audio_osx_source"); + + d_cond_data = new mld_condition (); + if (d_cond_data == NULL) + CheckErrorAndThrow (errno, "new mld_condition (data)", + "audio_osx_source::audio_osx_source"); + +// initialize the AU for output + + err = AudioUnitInitialize (d_OutputAU); + CheckErrorAndThrow (err, "AudioUnitInitialize", + "audio_osx_sink::audio_osx_sink"); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "audio_osx_sink Parameters:\n"); + fprintf (stderr, " Sample Rate is %g\n", d_sample_rate); + fprintf (stderr, " Number of Channels is %ld\n", d_n_channels); + fprintf (stderr, " Max # samples to store per channel is %ld", + d_max_sample_count); +#endif +} + +bool audio_osx_sink::IsRunning () +{ + UInt32 AURunning = 0, AUSize = sizeof (UInt32); + + OSStatus err = AudioUnitGetProperty (d_OutputAU, + kAudioOutputUnitProperty_IsRunning, + kAudioUnitScope_Global, + 0, + &AURunning, + &AUSize); + CheckErrorAndThrow (err, "AudioUnitGetProperty IsRunning", + "audio_osx_sink::IsRunning"); + + return (AURunning); +} + +bool audio_osx_sink::start () +{ + if (! IsRunning ()) { + OSStatus err = AudioOutputUnitStart (d_OutputAU); + CheckErrorAndThrow (err, "AudioOutputUnitStart", "audio_osx_sink::start"); + } + + return (true); +} + +bool audio_osx_sink::stop () +{ + if (IsRunning ()) { + OSStatus err = AudioOutputUnitStop (d_OutputAU); + CheckErrorAndThrow (err, "AudioOutputUnitStop", "audio_osx_sink::stop"); + + for (UInt32 n = 0; n < d_n_channels; n++) { + d_buffers[n]->abort (); + } + } + + return (true); +} + +audio_osx_sink::~audio_osx_sink () +{ +// stop and close the AudioUnit + stop (); + AudioUnitUninitialize (d_OutputAU); + CloseComponent (d_OutputAU); + +// empty and delete the queues + for (UInt32 n = 0; n < d_n_channels; n++) { + delete d_buffers[n]; + d_buffers[n] = 0; + } + delete [] d_buffers; + d_buffers = 0; + +// close and delete control stuff + delete d_internal; + delete d_cond_data; +} + +audio_osx_sink_sptr +audio_osx_make_sink (int sampling_freq, + const std::string dev, + bool do_block, + int channel_config, + int max_sample_count) +{ + return audio_osx_sink_sptr (new audio_osx_sink (sampling_freq, + dev, + do_block, + channel_config, + max_sample_count)); +} + +int +audio_osx_sink::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + d_internal->wait (); + + /* take the input data, copy it, and push it to the bottom of the queue + mono input are pushed onto queue[0]; + stereo input are pushed onto queue[1]. + Start the AudioUnit if necessary. */ + + UInt32 l_max_count; + int diff_count = d_max_sample_count - noutput_items; + if (diff_count < 0) + l_max_count = 0; + else + l_max_count = (UInt32) diff_count; + +#if 0 + if (l_max_count < d_queueItemLength->back()) { +// allow 2 buffers at a time, regardless of length + l_max_count = d_queueItemLength->back(); + } +#endif + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "work1: qSC = %ld, lMC = %ld, dmSC = %ld, nOI = %d\n", + d_queueSampleCount, l_max_count, d_max_sample_count, noutput_items); +#endif + + if (d_queueSampleCount > l_max_count) { +// data coming in too fast; do_block decides what to do + if (d_do_block == true) { +// block until there is data to return + while (d_queueSampleCount > l_max_count) { +// release control so-as to allow data to be retrieved + d_internal->post (); +// block until there is data to return + d_cond_data->wait (); +// the condition's signal() was called; acquire control +// to keep thread safe + d_internal->wait (); + } + } + } +// not blocking case and overflow is handled by the circular buffer + +// add the input frames to the buffers' queue, checking for overflow + + UInt32 l_counter; + int res = 0; + float* inBuffer = (float*) input_items[0]; + const UInt32 l_size = input_items.size(); + for (l_counter = 0; l_counter < l_size; l_counter++) { + inBuffer = (float*) input_items[l_counter]; + int l_res = d_buffers[l_counter]->enqueue (inBuffer, + noutput_items); + if (l_res == -1) + res = -1; + } + while (l_counter < d_n_channels) { +// for extra channels, copy the last input's data + int l_res = d_buffers[l_counter++]->enqueue (inBuffer, + noutput_items); + if (l_res == -1) + res = -1; + } + + if (res == -1) { +// data coming in too fast +// drop oldest buffer + fputs ("oX", stderr); + fflush (stderr); +// set the local number of samples available to the max + d_queueSampleCount = d_buffers[0]->buffer_length_items (); + } else { +// keep up the local sample count + d_queueSampleCount += noutput_items; + } + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "work2: #OI = %4d, #Cnt = %4ld, mSC = %ld\n", + noutput_items, d_queueSampleCount, d_max_sample_count); +#endif + +// release control to allow for other processing parts to run + d_internal->post (); + + return (noutput_items); +} + +OSStatus audio_osx_sink::AUOutputCallback +(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + audio_osx_sink* This = (audio_osx_sink*) inRefCon; + OSStatus err = noErr; + + This->d_internal->wait (); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "cb_in: SC = %4ld, in#F = %4ld\n", + This->d_queueSampleCount, inNumberFrames); +#endif + + if (This->d_queueSampleCount < inNumberFrames) { +// not enough data to fill request + err = -1; + } else { +// enough data; remove data from our buffers into the AU's buffers + int l_counter = This->d_n_channels; + + while (--l_counter >= 0) { + UInt32 t_n_output_items = inNumberFrames; + float* outBuffer = (float*) ioData->mBuffers[l_counter].mData; + This->d_buffers[l_counter]->dequeue (outBuffer, &t_n_output_items); + if (t_n_output_items != inNumberFrames) { + throw std::runtime_error ("audio_osx_sink::AUOutputCallback(): " + "number of available items changing " + "unexpectedly.\n"); + } + } + + This->d_queueSampleCount -= inNumberFrames; + } + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "cb_out: SC = %4ld\n", This->d_queueSampleCount); +#endif + +// signal that data is available + This->d_cond_data->signal (); + +// release control to allow for other processing parts to run + This->d_internal->post (); + + return (err); +} diff --git a/gr-audio-osx/src/audio_osx_sink.h b/gr-audio-osx/src/audio_osx_sink.h new file mode 100644 index 000000000..8cd3be0a1 --- /dev/null +++ b/gr-audio-osx/src/audio_osx_sink.h @@ -0,0 +1,96 @@ +/* -*- 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_OSX_SINK_H +#define INCLUDED_AUDIO_OSX_SINK_H + +#include <gr_sync_block.h> +#include <string> +#include <list> +#include <AudioUnit/AudioUnit.h> +#include <circular_buffer.h> + +class audio_osx_sink; +typedef boost::shared_ptr<audio_osx_sink> audio_osx_sink_sptr; + +audio_osx_sink_sptr +audio_osx_make_sink (int sample_rate = 44100, + const std::string device_name = "2", + bool do_block = true, + int channel_config = -1, + int max_sample_count = -1); + +/*! + * \brief audio sink using OSX + * + * input signature is one or two streams of floats. + * Input samples must be in the range [-1,1]. + */ + +class audio_osx_sink : public gr_sync_block { + friend audio_osx_sink_sptr + audio_osx_make_sink (int sample_rate, + const std::string device_name, + bool do_block, + int channel_config, + int max_sample_count); + + Float64 d_sample_rate; + int d_channel_config; + UInt32 d_n_channels; + UInt32 d_queueSampleCount, d_max_sample_count; + bool d_do_block; + mld_mutex_ptr d_internal; + mld_condition_ptr d_cond_data; + circular_buffer<float>** d_buffers; + +// AudioUnits and Such + AudioUnit d_OutputAU; + +protected: + audio_osx_sink (int sample_rate = 44100, + const std::string device_name = "2", + bool do_block = true, + int channel_config = -1, + int max_sample_count = -1); + +public: + ~audio_osx_sink (); + + bool IsRunning (); + bool start (); + bool stop (); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + +private: + static OSStatus AUOutputCallback (void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); +}; + +#endif /* INCLUDED_AUDIO_OSX_SINK_H */ diff --git a/gr-audio-osx/src/audio_osx_source.cc b/gr-audio-osx/src/audio_osx_source.cc new file mode 100644 index 000000000..2abf1c2a7 --- /dev/null +++ b/gr-audio-osx/src/audio_osx_source.cc @@ -0,0 +1,978 @@ +/* -*- 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 + +#define _USE_OMNI_THREADS_ + +#include <audio_osx_source.h> +#include <gr_io_signature.h> +#include <stdexcept> +#include <audio_osx.h> + +#define _OSX_AU_DEBUG_ 0 +#define _OSX_DO_LISTENERS_ 0 + +void PrintStreamDesc (AudioStreamBasicDescription *inDesc) +{ + if (inDesc == NULL) { + fprintf (stderr, "PrintStreamDesc: Can't print a NULL desc!\n"); + return; + } + + fprintf (stderr, " Sample Rate : %g\n", inDesc->mSampleRate); + fprintf (stderr, " Format ID : %4s\n", (char*)&inDesc->mFormatID); + fprintf (stderr, " Format Flags : %lX\n", inDesc->mFormatFlags); + fprintf (stderr, " Bytes per Packet : %ld\n", inDesc->mBytesPerPacket); + fprintf (stderr, " Frames per Packet : %ld\n", inDesc->mFramesPerPacket); + fprintf (stderr, " Bytes per Frame : %ld\n", inDesc->mBytesPerFrame); + fprintf (stderr, " Channels per Frame : %ld\n", inDesc->mChannelsPerFrame); + fprintf (stderr, " Bits per Channel : %ld\n", inDesc->mBitsPerChannel); +} + +// FIXME these should query some kind of user preference + +audio_osx_source::audio_osx_source (int sample_rate, + const std::string device_name, + bool do_block, + int channel_config, + int max_sample_count) + : gr_sync_block ("audio_osx_source", + gr_make_io_signature (0, 0, 0), + gr_make_io_signature (0, 0, 0)), + d_deviceSampleRate (0.0), d_outputSampleRate (0.0), + d_channel_config (0), + d_inputBufferSizeFrames (0), d_inputBufferSizeBytes (0), + d_outputBufferSizeFrames (0), d_outputBufferSizeBytes (0), + d_deviceBufferSizeFrames (0), d_deviceBufferSizeBytes (0), + d_leadSizeFrames (0), d_leadSizeBytes (0), + d_trailSizeFrames (0), d_trailSizeBytes (0), + d_extraBufferSizeFrames (0), d_extraBufferSizeBytes (0), + d_queueSampleCount (0), d_max_sample_count (0), + d_n_AvailableInputFrames (0), d_n_ActualInputFrames (0), + d_n_user_channels (0), d_n_max_channels (0), d_n_deviceChannels (0), + d_do_block (do_block), d_passThrough (false), + d_internal (0), d_cond_data (0), + d_buffers (0), + d_InputAU (0), d_InputBuffer (0), d_OutputBuffer (0), + d_AudioConverter (0) +{ + if (sample_rate <= 0) { + fprintf (stderr, "Invalid Sample Rate: %d\n", sample_rate); + throw std::invalid_argument ("audio_osx_source::audio_osx_source"); + } else + d_outputSampleRate = (Float64) sample_rate; + + if (channel_config <= 0 & channel_config != -1) { + fprintf (stderr, "Invalid Channel Config: %d\n", channel_config); + throw std::invalid_argument ("audio_osx_source::audio_osx_source"); + } else if (channel_config == -1) { +// no user input; try "device name" instead + int l_n_channels = (int) strtol (device_name.data(), (char **)NULL, 10); + if (l_n_channels == 0 & errno) { + fprintf (stderr, "Error Converting Device Name: %d\n", errno); + throw std::invalid_argument ("audio_osx_source::audio_osx_source"); + } + if (l_n_channels <= 0) + channel_config = 2; + else + channel_config = l_n_channels; + } + + d_channel_config = channel_config; + +// check that the max # of samples to store is valid + + if (max_sample_count == -1) + max_sample_count = sample_rate; + else if (max_sample_count <= 0) { + fprintf (stderr, "Invalid Max Sample Count: %d\n", max_sample_count); + throw std::invalid_argument ("audio_osx_source::audio_osx_source"); + } + + d_max_sample_count = max_sample_count; + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "source(): max # samples = %ld", d_max_sample_count); +#endif + + OSStatus err = noErr; + +// create the default AudioUnit for input + +// Open the default input unit + ComponentDescription InputDesc; + + InputDesc.componentType = kAudioUnitType_Output; + InputDesc.componentSubType = kAudioUnitSubType_HALOutput; + InputDesc.componentManufacturer = kAudioUnitManufacturer_Apple; + InputDesc.componentFlags = 0; + InputDesc.componentFlagsMask = 0; + + Component comp = FindNextComponent (NULL, &InputDesc); + if (comp == NULL) { + fprintf (stderr, "FindNextComponent Error\n"); + throw std::runtime_error ("audio_osx_source::audio_osx_source"); + } + + err = OpenAComponent (comp, &d_InputAU); + CheckErrorAndThrow (err, "OpenAComponent", + "audio_osx_source::audio_osx_source"); + + UInt32 enableIO; + +// must enable the AUHAL for input and disable output +// before setting the AUHAL's current device + +// Enable input on the AUHAL + enableIO = 1; + err = AudioUnitSetProperty (d_InputAU, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + 1, // input element + &enableIO, + sizeof (UInt32)); + CheckErrorAndThrow (err, "AudioUnitSetProperty Input Enable", + "audio_osx_source::audio_osx_source"); + +// Disable output on the AUHAL + enableIO = 0; + err = AudioUnitSetProperty (d_InputAU, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + 0, // output element + &enableIO, + sizeof (UInt32)); + CheckErrorAndThrow (err, "AudioUnitSetProperty Output Disable", + "audio_osx_source::audio_osx_source"); + +// set the default input device for our input AU + + SetDefaultInputDeviceAsCurrent (); + +#if _OSX_DO_LISTENERS_ +// set up a listener if default hardware input device changes + + err = AudioHardwareAddPropertyListener + (kAudioHardwarePropertyDefaultInputDevice, + (AudioHardwarePropertyListenerProc) HardwareListener, + this); + + CheckErrorAndThrow (err, "AudioHardwareAddPropertyListener", + "audio_osx_source::audio_osx_source"); + +// Add a listener for any changes in the input AU's output stream +// the function "UnitListener" will be called if the stream format +// changes for whatever reason + + err = AudioUnitAddPropertyListener + (d_InputAU, + kAudioUnitProperty_StreamFormat, + (AudioUnitPropertyListenerProc) UnitListener, + this); + CheckErrorAndThrow (err, "Adding Unit Property Listener", + "audio_osx_source::audio_osx_source"); +#endif + +// Now find out if it actually can do input. + + UInt32 hasInput = 0; + UInt32 dataSize = sizeof (hasInput); + err = AudioUnitGetProperty (d_InputAU, + kAudioOutputUnitProperty_HasIO, + kAudioUnitScope_Input, + 1, + &hasInput, + &dataSize); + CheckErrorAndThrow (err, "AudioUnitGetProperty HasIO", + "audio_osx_source::audio_osx_source"); + if (hasInput == 0) { + fprintf (stderr, "Selected Audio Device does not support Input.\n"); + throw std::runtime_error ("audio_osx_source::audio_osx_source"); + } + +// Set up a callback function to retrieve input from the Audio Device + + AURenderCallbackStruct AUCallBack; + + AUCallBack.inputProc = (AURenderCallback)(audio_osx_source::AUInputCallback); + AUCallBack.inputProcRefCon = this; + + err = AudioUnitSetProperty (d_InputAU, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + 0, + &AUCallBack, + sizeof (AURenderCallbackStruct)); + CheckErrorAndThrow (err, "AudioUnitSetProperty Input Callback", + "audio_osx_source::audio_osx_source"); + + UInt32 propertySize; + AudioStreamBasicDescription asbd_device, asbd_client, asbd_user; + +// asbd_device: ASBD of the device that is creating the input data stream +// asbd_client: ASBD of the client size (output) of the hardware device +// asbd_user: ASBD of the user's arguments + +// Get the Stream Format (device side) + + propertySize = sizeof (asbd_device); + err = AudioUnitGetProperty (d_InputAU, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 1, + &asbd_device, + &propertySize); + CheckErrorAndThrow (err, "AudioUnitGetProperty Device Input Stream Format", + "audio_osx_source::audio_osx_source"); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "---- Device Stream Format ----\n" ); + PrintStreamDesc (&asbd_device); +#endif + +// Get the Stream Format (client side) + propertySize = sizeof (asbd_client); + err = AudioUnitGetProperty (d_InputAU, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 1, + &asbd_client, + &propertySize); + CheckErrorAndThrow (err, "AudioUnitGetProperty Device Ouput Stream Format", + "audio_osx_source::audio_osx_source"); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "---- Client Stream Format ----\n"); + PrintStreamDesc (&asbd_client); +#endif + +// Set the format of all the AUs to the input/output devices channel count + +// get the max number of input (& thus output) channels supported by +// this device + d_n_max_channels = asbd_client.mChannelsPerFrame; + +// create the output io signature; +// no input siganture to set (source is hardware) + set_output_signature (gr_make_io_signature (1, + d_n_max_channels, + sizeof (float))); + +// allocate the output circular buffer(s), one per channel + d_buffers = (circular_buffer<float>**) new + circular_buffer<float>* [d_n_max_channels]; + UInt32 n_alloc = (UInt32) ceil ((double) d_max_sample_count); + for (UInt32 n = 0; n < d_n_max_channels; n++) { + d_buffers[n] = new circular_buffer<float> (n_alloc, false, false); + } + + d_deviceSampleRate = asbd_device.mSampleRate; + d_n_deviceChannels = asbd_device.mChannelsPerFrame; + +// create an ASBD for the user's wants + + asbd_user.mSampleRate = d_outputSampleRate; + asbd_user.mFormatID = kAudioFormatLinearPCM; + asbd_user.mFormatFlags = (kLinearPCMFormatFlagIsFloat | + GR_PCM_ENDIANNESS | + kLinearPCMFormatFlagIsPacked | + kAudioFormatFlagIsNonInterleaved); + asbd_user.mBytesPerPacket = 4; + asbd_user.mFramesPerPacket = 1; + asbd_user.mBytesPerFrame = 4; + asbd_user.mChannelsPerFrame = d_n_max_channels; + asbd_user.mBitsPerChannel = 32; + + if (d_deviceSampleRate == d_outputSampleRate) { +// no need to do conversion if asbd_client matches user wants + d_passThrough = true; + d_leadSizeFrames = d_trailSizeFrames = 0L; + } else { + d_passThrough = false; +// Create the audio converter + + err = AudioConverterNew (&asbd_client, &asbd_user, &d_AudioConverter); + CheckErrorAndThrow (err, "AudioConverterNew", + "audio_osx_source::audio_osx_source"); + +// Set the audio converter sample rate quality to "max" ... +// requires more samples, but should sound nicer + + UInt32 ACQuality = kAudioConverterQuality_Max; + propertySize = sizeof (ACQuality); + err = AudioConverterSetProperty (d_AudioConverter, + kAudioConverterSampleRateConverterQuality, + propertySize, + &ACQuality); + CheckErrorAndThrow (err, "AudioConverterSetProperty " + "SampleRateConverterQuality", + "audio_osx_source::audio_osx_source"); + +// set the audio converter's prime method to "pre", +// which uses both leading and trailing frames +// from the "current input". All of this is handled +// internally by the AudioConverter; we just supply +// the frames for conversion. + +// UInt32 ACPrimeMethod = kConverterPrimeMethod_None; + UInt32 ACPrimeMethod = kConverterPrimeMethod_Pre; + propertySize = sizeof (ACPrimeMethod); + err = AudioConverterSetProperty (d_AudioConverter, + kAudioConverterPrimeMethod, + propertySize, + &ACPrimeMethod); + CheckErrorAndThrow (err, "AudioConverterSetProperty PrimeMethod", + "audio_osx_source::audio_osx_source"); + +// Get the size of the I/O buffer(s) to allow for pre-allocated buffers + +// lead frame info (trail frame info is ignored) + + AudioConverterPrimeInfo ACPrimeInfo = {0, 0}; + propertySize = sizeof (ACPrimeInfo); + err = AudioConverterGetProperty (d_AudioConverter, + kAudioConverterPrimeInfo, + &propertySize, + &ACPrimeInfo); + CheckErrorAndThrow (err, "AudioConverterGetProperty PrimeInfo", + "audio_osx_source::audio_osx_source"); + + switch (ACPrimeMethod) { + case (kConverterPrimeMethod_None): + d_leadSizeFrames = + d_trailSizeFrames = 0L; + break; + case (kConverterPrimeMethod_Normal): + d_leadSizeFrames = 0L; + d_trailSizeFrames = ACPrimeInfo.trailingFrames; + break; + default: + d_leadSizeFrames = ACPrimeInfo.leadingFrames; + d_trailSizeFrames = ACPrimeInfo.trailingFrames; + } + } + d_leadSizeBytes = d_leadSizeFrames * sizeof (Float32); + d_trailSizeBytes = d_trailSizeFrames * sizeof (Float32); + + propertySize = sizeof (d_deviceBufferSizeFrames); + err = AudioUnitGetProperty (d_InputAU, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Global, + 0, + &d_deviceBufferSizeFrames, + &propertySize); + CheckErrorAndThrow (err, "AudioUnitGetProperty Buffer Frame Size", + "audio_osx_source::audio_osx_source"); + + d_deviceBufferSizeBytes = d_deviceBufferSizeFrames * sizeof (Float32); + d_inputBufferSizeBytes = d_deviceBufferSizeBytes + d_leadSizeBytes; + d_inputBufferSizeFrames = d_deviceBufferSizeFrames + d_leadSizeFrames; + +// outBufSizeBytes = floor (inBufSizeBytes * rate_out / rate_in) +// since this is rarely exact, we need another buffer to hold +// "extra" samples not processed at any given sampling period +// this buffer must be at least 4 floats in size, but generally +// follows the rule that +// extraBufSize = ceil (rate_in / rate_out)*sizeof(float) + + d_extraBufferSizeFrames = ((UInt32) ceil (d_deviceSampleRate + / d_outputSampleRate) + * sizeof (float)); + if (d_extraBufferSizeFrames < 4) + d_extraBufferSizeFrames = 4; + d_extraBufferSizeBytes = d_extraBufferSizeFrames * sizeof (float); + + d_outputBufferSizeFrames = (UInt32) ceil (((Float64) d_inputBufferSizeFrames) + * d_outputSampleRate + / d_deviceSampleRate); + d_outputBufferSizeBytes = d_outputBufferSizeFrames * sizeof (float); + d_inputBufferSizeFrames += d_extraBufferSizeFrames; + +// pre-alloc all buffers + + AllocAudioBufferList (&d_InputBuffer, d_n_deviceChannels, + d_inputBufferSizeBytes); + if (d_passThrough == false) { + AllocAudioBufferList (&d_OutputBuffer, d_n_max_channels, + d_outputBufferSizeBytes); + } else { + d_OutputBuffer = d_InputBuffer; + } + +// create the stuff to regulate I/O + + d_internal = new mld_mutex (); + if (d_internal == NULL) + CheckErrorAndThrow (errno, "new mld_mutex (internal)", + "audio_osx_source::audio_osx_source"); + + d_cond_data = new mld_condition (); + if (d_cond_data == NULL) + CheckErrorAndThrow (errno, "new mld_condition (data)", + "audio_osx_source::audio_osx_source"); + +// initialize the AU for input + + err = AudioUnitInitialize (d_InputAU); + CheckErrorAndThrow (err, "AudioUnitInitialize", + "audio_osx_source::audio_osx_source"); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "audio_osx_source Parameters:\n"); + fprintf (stderr, " Device Sample Rate is %g\n", d_deviceSampleRate); + fprintf (stderr, " User Sample Rate is %g\n", d_outputSampleRate); + fprintf (stderr, " Max Sample Count is %ld\n", d_max_sample_count); + fprintf (stderr, " # Device Channels is %ld\n", d_n_deviceChannels); + fprintf (stderr, " # Max Channels is %ld\n", d_n_max_channels); + fprintf (stderr, " Device Buffer Size is Frames = %ld\n", + d_deviceBufferSizeFrames); + fprintf (stderr, " Lead Size is Frames = %ld\n", + d_leadSizeFrames); + fprintf (stderr, " Trail Size is Frames = %ld\n", + d_trailSizeFrames); + fprintf (stderr, " Input Buffer Size is Frames = %ld\n", + d_inputBufferSizeFrames); + fprintf (stderr, " Output Buffer Size is Frames = %ld\n", + d_outputBufferSizeFrames); +#endif +} + +void +audio_osx_source::AllocAudioBufferList (AudioBufferList** t_ABL, + UInt32 n_channels, + UInt32 bufferSizeBytes) +{ + FreeAudioBufferList (t_ABL); + UInt32 propertySize = (offsetof (AudioBufferList, mBuffers[0]) + + (sizeof (AudioBuffer) * n_channels)); + *t_ABL = (AudioBufferList*) calloc (1, propertySize); + (*t_ABL)->mNumberBuffers = n_channels; + + int counter = n_channels; + + while (--counter >= 0) { + (*t_ABL)->mBuffers[counter].mNumberChannels = 1; + (*t_ABL)->mBuffers[counter].mDataByteSize = bufferSizeBytes; + (*t_ABL)->mBuffers[counter].mData = calloc (1, bufferSizeBytes); + } +} + +void +audio_osx_source::FreeAudioBufferList (AudioBufferList** t_ABL) +{ +// free pre-allocated audio buffer, if it exists + if (*t_ABL != NULL) { + int counter = (*t_ABL)->mNumberBuffers; + while (--counter >= 0) + free ((*t_ABL)->mBuffers[counter].mData); + free (*t_ABL); + (*t_ABL) = 0; + } +} + +bool audio_osx_source::IsRunning () +{ + UInt32 AURunning = 0, AUSize = sizeof (UInt32); + + OSStatus err = AudioUnitGetProperty (d_InputAU, + kAudioOutputUnitProperty_IsRunning, + kAudioUnitScope_Global, + 0, + &AURunning, + &AUSize); + CheckErrorAndThrow (err, "AudioUnitGetProperty IsRunning", + "audio_osx_source::IsRunning"); + + return (AURunning); +} + +bool audio_osx_source::start () +{ + if (! IsRunning ()) { + OSStatus err = AudioOutputUnitStart (d_InputAU); + CheckErrorAndThrow (err, "AudioOutputUnitStart", + "audio_osx_source::start"); + } + + return (true); +} + +bool audio_osx_source::stop () +{ + if (IsRunning ()) { + OSStatus err = AudioOutputUnitStop (d_InputAU); + CheckErrorAndThrow (err, "AudioOutputUnitStart", + "audio_osx_source::stop"); + for (UInt32 n = 0; n < d_n_user_channels; n++) { + d_buffers[n]->abort (); + } + } + + return (true); +} + +audio_osx_source::~audio_osx_source () +{ + OSStatus err = noErr; + +// stop the AudioUnit + stop(); + +#if _OSX_DO_LISTENERS_ +// remove the listeners + + err = AudioUnitRemovePropertyListener + (d_InputAU, + kAudioUnitProperty_StreamFormat, + (AudioUnitPropertyListenerProc) UnitListener); + CheckError (err, "~audio_osx_source: AudioUnitRemovePropertyListener"); + + err = AudioHardwareRemovePropertyListener + (kAudioHardwarePropertyDefaultInputDevice, + (AudioHardwarePropertyListenerProc) HardwareListener); + CheckError (err, "~audio_osx_source: AudioHardwareRemovePropertyListener"); +#endif + +// free pre-allocated audio buffers + FreeAudioBufferList (&d_InputBuffer); + + if (d_passThrough == false) { + err = AudioConverterDispose (d_AudioConverter); + CheckError (err, "~audio_osx_source: AudioConverterDispose"); + FreeAudioBufferList (&d_OutputBuffer); + } + +// remove the audio unit + err = AudioUnitUninitialize (d_InputAU); + CheckError (err, "~audio_osx_source: AudioUnitUninitialize"); + + err = CloseComponent (d_InputAU); + CheckError (err, "~audio_osx_source: CloseComponent"); + +// empty and delete the queues + for (UInt32 n = 0; n < d_n_max_channels; n++) { + delete d_buffers[n]; + d_buffers[n] = 0; + } + delete [] d_buffers; + d_buffers = 0; + +// close and delete the control stuff + delete d_internal; + delete d_cond_data; +} + +audio_osx_source_sptr +audio_osx_make_source (int sampling_freq, + const std::string device_name, + bool do_block, + int channel_config, + int max_sample_count) +{ + return audio_osx_source_sptr (new audio_osx_source (sampling_freq, + device_name, + do_block, + channel_config, + max_sample_count)); +} + +bool +audio_osx_source::check_topology (int ninputs, int noutputs) +{ +// check # inputs to make sure it's valid + if (ninputs != 0) { + fprintf (stderr, "audio_osx_source::check_topology(): " + "number of input streams provided (%d) should be 0.\n", + ninputs); + throw std::runtime_error ("audio_osx_source::check_topology()"); + } + +// check # outputs to make sure it's valid + if ((noutputs < 1) | (noutputs > (int) d_n_max_channels)) { + fprintf (stderr, "audio_osx_source::check_topology(): " + "number of output streams provided (%d) should be in " + "[1,%ld] for the selected audio device.\n", + noutputs, d_n_max_channels); + throw std::runtime_error ("audio_osx_source::check_topology()"); + } + +// save the actual number of output (user) channels + d_n_user_channels = noutputs; + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "chk_topo: Actual # user output channels = %d\n", + noutputs); +#endif + + return (true); +} + +int +audio_osx_source::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ +// acquire control to do processing here only + d_internal->wait (); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "work1: SC = %4ld, #OI = %4d, #Chan = %ld\n", + d_queueSampleCount, noutput_items, output_items.size()); +#endif + +// ?: always block until there is something to output from the source +// or return anything that is available, even if it's less than desired? + + UInt32 actual_noutput_items = noutput_items; + + if (d_queueSampleCount < actual_noutput_items) { + if (d_queueSampleCount == 0) { +// no data; do_block decides what to do + if (d_do_block == true) { + while (d_queueSampleCount == 0) { +// release control so-as to allow data to be retrieved + d_internal->post (); +// block until there is data to return + d_cond_data->wait (); +// the condition's signal() was called; acquire control +// to keep thread safe + d_internal->wait (); + } + } else { +// not enough data & not blocking; return nothing + return (0); + } + } + actual_noutput_items = d_queueSampleCount; + } + + int l_counter = (int) output_items.size(); + +// get the items from the circular buffers + while (--l_counter >= 0) { + UInt32 t_n_output_items = actual_noutput_items; + d_buffers[l_counter]->dequeue ((float*) output_items[l_counter], + &t_n_output_items); + if (t_n_output_items != actual_noutput_items) { + fprintf (stderr, "audio_osx_source::work(): " + "number of available items changing " + "unexpectedly; expecting %ld, got %ld.\n", + actual_noutput_items, t_n_output_items); + throw std::runtime_error ("audio_osx_source::work()"); + } + } + + d_queueSampleCount -= actual_noutput_items; + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "work2: SC = %4ld, act#OI = %4ld\n", + d_queueSampleCount, actual_noutput_items); +#endif + +// release control to allow for other processing parts to run + d_internal->post (); + + return (actual_noutput_items); +} + +OSStatus +audio_osx_source::ConverterCallback (AudioConverterRef inAudioConverter, + UInt32* ioNumberDataPackets, + AudioBufferList* ioData, + AudioStreamPacketDescription** ioASPD, + void* inUserData) +{ +// take current device buffers and copy them to the tail of the input buffers +// the lead buffer is already there in the first d_leadSizeFrames slots + + audio_osx_source* This = static_cast<audio_osx_source*>(inUserData); + AudioBufferList* l_inputABL = This->d_InputBuffer; + UInt32 totalInputBufferSizeBytes = ((*ioNumberDataPackets) * sizeof (float)); + int counter = This->d_n_deviceChannels; + ioData->mNumberBuffers = This->d_n_deviceChannels; + This->d_n_ActualInputFrames = (*ioNumberDataPackets); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "cc1: io#DP = %ld, TIBSB = %ld, #C = %d\n", + *ioNumberDataPackets, totalInputBufferSizeBytes, counter); +#endif + + while (--counter >= 0) { + AudioBuffer* l_ioD_AB = &(ioData->mBuffers[counter]); + l_ioD_AB->mNumberChannels = 1; + l_ioD_AB->mData = (float*)(l_inputABL->mBuffers[counter].mData); + l_ioD_AB->mDataByteSize = totalInputBufferSizeBytes; + } + + return (noErr); +} + +OSStatus +audio_osx_source::AUInputCallback (void* inRefCon, + AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData) +{ + OSStatus err = noErr; + audio_osx_source* This = static_cast<audio_osx_source*>(inRefCon); + + This->d_internal->wait (); + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "cb0: in#F = %4ld, inBN = %ld, SC = %4ld\n", + inNumberFrames, inBusNumber, This->d_queueSampleCount); +#endif + +// Get the new audio data from the input device + + err = AudioUnitRender (This->d_InputAU, + ioActionFlags, + inTimeStamp, + 1, //inBusNumber, + inNumberFrames, + This->d_InputBuffer); + CheckErrorAndThrow (err, "AudioUnitRender", + "audio_osx_source::AUInputCallback"); + + UInt32 AvailableInputFrames = inNumberFrames; + This->d_n_AvailableInputFrames = inNumberFrames; + +// get the number of actual output frames, +// either via converting the buffer or not + + UInt32 ActualOutputFrames; + + if (This->d_passThrough == true) { + ActualOutputFrames = AvailableInputFrames; + } else { + UInt32 AvailableInputBytes = AvailableInputFrames * sizeof (float); + UInt32 AvailableOutputBytes = AvailableInputBytes; + UInt32 AvailableOutputFrames = AvailableOutputBytes / sizeof (float); + UInt32 propertySize = sizeof (AvailableOutputBytes); + err = AudioConverterGetProperty (This->d_AudioConverter, + kAudioConverterPropertyCalculateOutputBufferSize, + &propertySize, + &AvailableOutputBytes); + CheckErrorAndThrow (err, "AudioConverterGetProperty CalculateOutputBufferSize", "audio_osx_source::audio_osx_source"); + + AvailableOutputFrames = AvailableOutputBytes / sizeof (float); + +#if 0 +// when decimating too much, the output sounds warbly due to +// fluctuating # of output frames +// This should not be a surprise, but there's probably some +// clever programming that could lessed the effect ... +// like finding the "ideal" # of output frames, and keeping +// that number constant no matter the # of input frames + UInt32 l_InputBytes = AvailableOutputBytes; + propertySize = sizeof (AvailableOutputBytes); + err = AudioConverterGetProperty (This->d_AudioConverter, + kAudioConverterPropertyCalculateInputBufferSize, + &propertySize, + &l_InputBytes); + CheckErrorAndThrow (err, "AudioConverterGetProperty CalculateInputBufferSize", "audio_osx_source::audio_osx_source"); + + if (l_InputBytes < AvailableInputBytes) { +// OK to zero pad the input a little + AvailableOutputFrames += 1; + AvailableOutputBytes = AvailableOutputFrames * sizeof (float); + } +#endif + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "cb1: avail: #IF = %ld, #OF = %ld\n", + AvailableInputFrames, AvailableOutputFrames); +#endif + ActualOutputFrames = AvailableOutputFrames; + +// convert the data to the correct rate +// on input, ActualOutputFrames is the number of available output frames + + err = AudioConverterFillComplexBuffer (This->d_AudioConverter, + (AudioConverterComplexInputDataProc)(This->ConverterCallback), + inRefCon, + &ActualOutputFrames, + This->d_OutputBuffer, + NULL); + CheckErrorAndThrow (err, "AudioConverterFillComplexBuffer", + "audio_osx_source::AUInputCallback"); + +// on output, ActualOutputFrames is the actual number of output frames + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "cb2: actual: #IF = %ld, #OF = %ld\n", + This->d_n_ActualInputFrames, AvailableOutputFrames); + if (This->d_n_ActualInputFrames != AvailableInputFrames) + fprintf (stderr, "cb2.1: avail#IF = %ld, actual#IF = %ld\n", + AvailableInputFrames, This->d_n_ActualInputFrames); +#endif + } + +// add the output frames to the buffers' queue, checking for overflow + + int l_counter = This->d_n_user_channels; + int res = 0; + + while (--l_counter >= 0) { + float* inBuffer = (float*) This->d_OutputBuffer->mBuffers[l_counter].mData; + int l_res = This->d_buffers[l_counter]->enqueue (inBuffer, ActualOutputFrames); + if (l_res == -1) + res = -1; + } + + if (res == -1) { +// data coming in too fast +// drop oldest buffer + fputs ("aO", stderr); + fflush (stderr); +// set the local number of samples available to the max + This->d_queueSampleCount = This->d_buffers[0]->buffer_length_items (); + } else { +// keep up the local sample count + This->d_queueSampleCount += ActualOutputFrames; + } + +#if _OSX_AU_DEBUG_ + fprintf (stderr, "cb5: #OI = %4ld, #Cnt = %4ld, mSC = %ld, \n", + ActualOutputFrames, This->d_queueSampleCount, + This->d_max_sample_count); +#endif + +// signal that data is available, if appropraite + This->d_cond_data->signal (); + +// release control to allow for other processing parts to run + This->d_internal->post (); + + return (err); +} + +void +audio_osx_source::SetDefaultInputDeviceAsCurrent +() +{ +// set the default input device + AudioDeviceID deviceID; + UInt32 dataSize = sizeof (AudioDeviceID); + AudioHardwareGetProperty (kAudioHardwarePropertyDefaultInputDevice, + &dataSize, + &deviceID); + OSStatus err = AudioUnitSetProperty (d_InputAU, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &deviceID, + sizeof (AudioDeviceID)); + CheckErrorAndThrow (err, "AudioUnitSetProperty Current Device", + "audio_osx_source::SetDefaultInputDeviceAsCurrent"); +} + +#if _OSX_DO_LISTENERS_ +OSStatus +audio_osx_source::HardwareListener +(AudioHardwarePropertyID inPropertyID, + void *inClientData) +{ + OSStatus err = noErr; + audio_osx_source* This = static_cast<audio_osx_source*>(inClientData); + + fprintf (stderr, "a_o_s::HardwareListener\n"); + +// set the new default hardware input device for use by our AU + + This->SetDefaultInputDeviceAsCurrent (); + +// reset the converter to tell it that the stream has changed + + err = AudioConverterReset (This->d_AudioConverter); + CheckErrorAndThrow (err, "AudioConverterReset", + "audio_osx_source::UnitListener"); + + return (err); +} + +OSStatus +audio_osx_source::UnitListener +(void *inRefCon, + AudioUnit ci, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement) +{ + OSStatus err = noErr; + audio_osx_source* This = static_cast<audio_osx_source*>(inRefCon); + AudioStreamBasicDescription asbd; + + fprintf (stderr, "a_o_s::UnitListener\n"); + +// get the converter's input ASBD (for printing) + + UInt32 propertySize = sizeof (asbd); + err = AudioConverterGetProperty (This->d_AudioConverter, + kAudioConverterCurrentInputStreamDescription, + &propertySize, + &asbd); + CheckErrorAndThrow (err, "AudioConverterGetProperty " + "CurrentInputStreamDescription", + "audio_osx_source::UnitListener"); + + fprintf (stderr, "UnitListener: Input Source changed.\n" + "Old Source Output Info:\n"); + PrintStreamDesc (&asbd); + +// get the new input unit's output ASBD + + propertySize = sizeof (asbd); + err = AudioUnitGetProperty (This->d_InputAU, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, 1, + &asbd, &propertySize); + CheckErrorAndThrow (err, "AudioUnitGetProperty StreamFormat", + "audio_osx_source::UnitListener"); + + fprintf (stderr, "New Source Output Info:\n"); + PrintStreamDesc (&asbd); + +// set the converter's input ASBD to this + + err = AudioConverterSetProperty (This->d_AudioConverter, + kAudioConverterCurrentInputStreamDescription, + propertySize, + &asbd); + CheckErrorAndThrow (err, "AudioConverterSetProperty " + "CurrentInputStreamDescription", + "audio_osx_source::UnitListener"); + +// reset the converter to tell it that the stream has changed + + err = AudioConverterReset (This->d_AudioConverter); + CheckErrorAndThrow (err, "AudioConverterReset", + "audio_osx_source::UnitListener"); + + return (err); +} +#endif diff --git a/gr-audio-osx/src/audio_osx_source.h b/gr-audio-osx/src/audio_osx_source.h new file mode 100644 index 000000000..1799588ef --- /dev/null +++ b/gr-audio-osx/src/audio_osx_source.h @@ -0,0 +1,132 @@ +/* -*- 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_OSX_SOURCE_H +#define INCLUDED_AUDIO_OSX_SOURCE_H + +#include <gr_sync_block.h> +#include <string> +#include <AudioToolbox/AudioToolbox.h> +#include <AudioUnit/AudioUnit.h> +#include <circular_buffer.h> + +class audio_osx_source; +typedef boost::shared_ptr<audio_osx_source> audio_osx_source_sptr; + +audio_osx_source_sptr +audio_osx_make_source (int sample_rate = 44100, + const std::string device_name = "", + bool do_block = true, + int channel_config = -1, + int max_sample_count = -1); + +/*! + * \brief audio source using OSX + * + * Input signature is one or two streams of floats. + * Samples must be in the range [-1,1]. + */ + +class audio_osx_source : public gr_sync_block { + friend audio_osx_source_sptr + audio_osx_make_source (int sample_rate, + const std::string device_name, + bool do_block, + int channel_config, + int max_sample_count); + + Float64 d_deviceSampleRate, d_outputSampleRate; + int d_channel_config; + UInt32 d_inputBufferSizeFrames, d_inputBufferSizeBytes; + UInt32 d_outputBufferSizeFrames, d_outputBufferSizeBytes; + UInt32 d_deviceBufferSizeFrames, d_deviceBufferSizeBytes; + UInt32 d_leadSizeFrames, d_leadSizeBytes; + UInt32 d_trailSizeFrames, d_trailSizeBytes; + UInt32 d_extraBufferSizeFrames, d_extraBufferSizeBytes; + UInt32 d_queueSampleCount, d_max_sample_count; + UInt32 d_n_AvailableInputFrames, d_n_ActualInputFrames; + UInt32 d_n_user_channels, d_n_max_channels, d_n_deviceChannels; + bool d_do_block, d_passThrough, d_waiting_for_data; + mld_mutex_ptr d_internal; + mld_condition_ptr d_cond_data; + circular_buffer<float>** d_buffers; + +// AudioUnits and Such + AudioUnit d_InputAU; + AudioBufferList* d_InputBuffer; + AudioBufferList* d_OutputBuffer; + AudioConverterRef d_AudioConverter; + +protected: + audio_osx_source (int sample_rate = 44100, + const std::string device_name = "", + bool do_block = true, + int channel_config = -1, + int max_sample_count = -1); + +public: + ~audio_osx_source (); + + bool start (); + bool stop (); + bool IsRunning (); + + 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); + +private: + void SetDefaultInputDeviceAsCurrent (); + + void AllocAudioBufferList (AudioBufferList** t_ABL, + UInt32 n_channels, + UInt32 inputBufferSizeBytes); + + void FreeAudioBufferList (AudioBufferList** t_ABL); + + static OSStatus ConverterCallback (AudioConverterRef inAudioConverter, + UInt32* ioNumberDataPackets, + AudioBufferList* ioData, + AudioStreamPacketDescription** outASPD, + void* inUserData); + + static OSStatus AUInputCallback (void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); +#if _OSX_DO_LISTENERS_ + static OSStatus UnitListener (void *inRefCon, + AudioUnit ci, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement); + + static OSStatus HardwareListener (AudioHardwarePropertyID inPropertyID, + void *inClientData); +#endif +}; + +#endif /* INCLUDED_AUDIO_OSX_SOURCE_H */ diff --git a/gr-audio-osx/src/circular_buffer.h b/gr-audio-osx/src/circular_buffer.h new file mode 100644 index 000000000..f644e7d02 --- /dev/null +++ b/gr-audio-osx/src/circular_buffer.h @@ -0,0 +1,326 @@ +/* -*- 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 _CIRCULAR_BUFFER_H_ +#define _CIRCULAR_BUFFER_H_ + +#include "mld_threads.h" +#include <stdexcept> + +#define DO_DEBUG 0 + +template <class T> class circular_buffer +{ +private: +// the buffer to use + T* d_buffer; + +// the following are in Items (type T) + UInt32 d_bufLen_I, d_readNdx_I, d_writeNdx_I; + UInt32 d_n_avail_write_I, d_n_avail_read_I; + +// stuff to control access to class internals + mld_mutex_ptr d_internal; + mld_condition_ptr d_readBlock, d_writeBlock; + +// booleans to decide how to control reading, writing, and aborting + bool d_doWriteBlock, d_doFullRead, d_doAbort; + + void delete_mutex_cond () { + if (d_internal) { + delete d_internal; + d_internal = NULL; + } + if (d_readBlock) { + delete d_readBlock; + d_readBlock = NULL; + } + if (d_writeBlock) { + delete d_writeBlock; + d_writeBlock = NULL; + } + }; + +public: + circular_buffer (UInt32 bufLen_I, + bool doWriteBlock = true, bool doFullRead = false) { + if (bufLen_I == 0) + throw std::runtime_error ("circular_buffer(): " + "Number of items to buffer must be > 0.\n"); + d_bufLen_I = bufLen_I; + d_buffer = (T*) new T[d_bufLen_I]; + d_doWriteBlock = doWriteBlock; + d_doFullRead = doFullRead; + d_internal = NULL; + d_readBlock = d_writeBlock = NULL; + reset (); +#if DO_DEBUG + fprintf (stderr, "c_b(): buf len (items) = %ld, " + "doWriteBlock = %s, doFullRead = %s\n", d_bufLen_I, + (d_doWriteBlock ? "true" : "false"), + (d_doFullRead ? "true" : "false")); +#endif + }; + + ~circular_buffer () { + delete_mutex_cond (); + delete [] d_buffer; + }; + + inline UInt32 n_avail_write_items () { + d_internal->lock (); + UInt32 retVal = d_n_avail_write_I; + d_internal->unlock (); + return (retVal); + }; + + inline UInt32 n_avail_read_items () { + d_internal->lock (); + UInt32 retVal = d_n_avail_read_I; + d_internal->unlock (); + return (retVal); + }; + + inline UInt32 buffer_length_items () {return (d_bufLen_I);}; + inline bool do_write_block () {return (d_doWriteBlock);}; + inline bool do_full_read () {return (d_doFullRead);}; + + void reset () { + d_doAbort = false; + bzero (d_buffer, d_bufLen_I * sizeof (T)); + d_readNdx_I = d_writeNdx_I = d_n_avail_read_I = 0; + d_n_avail_write_I = d_bufLen_I; + delete_mutex_cond (); + d_internal = new mld_mutex (); + d_readBlock = new mld_condition (); + d_writeBlock = new mld_condition (); + }; + +/* + * enqueue: add the given buffer of item-length to the queue, + * first-in-first-out (FIFO). + * + * inputs: + * buf: a pointer to the buffer holding the data + * + * bufLen_I: the buffer length in items (of the instantiated type) + * + * returns: + * -1: on overflow (write is not blocking, and data is being + * written faster than it is being read) + * 0: if nothing to do (0 length buffer) + * 1: if success + * 2: in the process of aborting, do doing nothing + * + * will throw runtime errors if inputs are improper: + * buffer pointer is NULL + * buffer length is larger than the instantiated buffer length + */ + + int enqueue (T* buf, UInt32 bufLen_I) { +#if DO_DEBUG + fprintf (stderr, "enqueue: buf = %X, bufLen = %ld, #av_wr = %ld, " + "#av_rd = %ld.\n", (unsigned int)buf, bufLen_I, + d_n_avail_write_I, d_n_avail_read_I); +#endif + if (bufLen_I > d_bufLen_I) { + fprintf (stderr, "cannot add buffer longer (%ld" + ") than instantiated length (%ld" + ").\n", bufLen_I, d_bufLen_I); + throw std::runtime_error ("circular_buffer::enqueue()"); + } + + if (bufLen_I == 0) + return (0); + if (!buf) + throw std::runtime_error ("circular_buffer::enqueue(): " + "input buffer is NULL.\n"); + d_internal->lock (); + if (d_doAbort) { + d_internal->unlock (); + return (2); + } + if (bufLen_I > d_n_avail_write_I) { + if (d_doWriteBlock) { + while (bufLen_I > d_n_avail_write_I) { +#if DO_DEBUG + fprintf (stderr, "enqueue: #len > #a, waiting.\n"); +#endif + d_internal->unlock (); + d_writeBlock->wait (); + d_internal->lock (); + if (d_doAbort) { + d_internal->unlock (); +#if DO_DEBUG + fprintf (stderr, "enqueue: #len > #a, aborting.\n"); +#endif + return (2); + } +#if DO_DEBUG + fprintf (stderr, "enqueue: #len > #a, done waiting.\n"); +#endif + } + } else { + d_n_avail_read_I = d_bufLen_I - bufLen_I; + d_n_avail_write_I = bufLen_I; +#if DO_DEBUG + fprintf (stderr, "circular_buffer::enqueue: overflow\n"); +#endif + return (-1); + } + } + UInt32 n_now_I = d_bufLen_I - d_writeNdx_I, n_start_I = 0; + if (n_now_I > bufLen_I) + n_now_I = bufLen_I; + else if (n_now_I < bufLen_I) + n_start_I = bufLen_I - n_now_I; + bcopy (buf, &(d_buffer[d_writeNdx_I]), n_now_I * sizeof (T)); + if (n_start_I) { + bcopy (&(buf[n_now_I]), d_buffer, n_start_I * sizeof (T)); + d_writeNdx_I = n_start_I; + } else + d_writeNdx_I += n_now_I; + d_n_avail_read_I += bufLen_I; + d_n_avail_write_I -= bufLen_I; + d_readBlock->signal (); + d_internal->unlock (); + return (1); + }; + +/* + * dequeue: removes from the queue the number of items requested, or + * available, into the given buffer on a FIFO basis. + * + * inputs: + * buf: a pointer to the buffer into which to copy the data + * + * bufLen_I: pointer to the number of items to remove in items + * (of the instantiated type) + * + * returns: + * 0: if nothing to do (0 length buffer) + * 1: if success + * 2: in the process of aborting, do doing nothing + * + * will throw runtime errors if inputs are improper: + * buffer pointer is NULL + * buffer length pointer is NULL + * buffer length is larger than the instantiated buffer length + */ + + int dequeue (T* buf, UInt32* bufLen_I) { +#if DO_DEBUG + fprintf (stderr, "dequeue: buf = %X, *bufLen = %ld, #av_wr = %ld, " + "#av_rd = %ld.\n", (unsigned int)buf, *bufLen_I, + d_n_avail_write_I, d_n_avail_read_I); +#endif + if (!bufLen_I) + throw std::runtime_error ("circular_buffer::dequeue(): " + "input bufLen pointer is NULL.\n"); + if (!buf) + throw std::runtime_error ("circular_buffer::dequeue(): " + "input buffer pointer is NULL.\n"); + UInt32 l_bufLen_I = *bufLen_I; + if (l_bufLen_I == 0) + return (0); + if (l_bufLen_I > d_bufLen_I) { + fprintf (stderr, "cannot remove buffer longer (%ld" + ") than instantiated length (%ld" + ").\n", l_bufLen_I, d_bufLen_I); + throw std::runtime_error ("circular_buffer::dequeue()"); + } + + d_internal->lock (); + if (d_doAbort) { + d_internal->unlock (); + return (2); + } + if (d_doFullRead) { + while (d_n_avail_read_I < l_bufLen_I) { +#if DO_DEBUG + fprintf (stderr, "dequeue: #a < #len, waiting.\n"); +#endif + d_internal->unlock (); + d_readBlock->wait (); + d_internal->lock (); + if (d_doAbort) { + d_internal->unlock (); +#if DO_DEBUG + fprintf (stderr, "dequeue: #a < #len, aborting.\n"); +#endif + return (2); + } +#if DO_DEBUG + fprintf (stderr, "dequeue: #a < #len, done waiting.\n"); +#endif + } + } else { + while (d_n_avail_read_I == 0) { +#if DO_DEBUG + fprintf (stderr, "dequeue: #a == 0, waiting.\n"); +#endif + d_internal->unlock (); + d_readBlock->wait (); + d_internal->lock (); + if (d_doAbort) { + d_internal->unlock (); +#if DO_DEBUG + fprintf (stderr, "dequeue: #a == 0, aborting.\n"); +#endif + return (2); + } +#if DO_DEBUG + fprintf (stderr, "dequeue: #a == 0, done waiting.\n"); +#endif + } + } + if (l_bufLen_I > d_n_avail_read_I) + l_bufLen_I = d_n_avail_read_I; + UInt32 n_now_I = d_bufLen_I - d_readNdx_I, n_start_I = 0; + if (n_now_I > l_bufLen_I) + n_now_I = l_bufLen_I; + else if (n_now_I < l_bufLen_I) + n_start_I = l_bufLen_I - n_now_I; + bcopy (&(d_buffer[d_readNdx_I]), buf, n_now_I * sizeof (T)); + if (n_start_I) { + bcopy (d_buffer, &(buf[n_now_I]), n_start_I * sizeof (T)); + d_readNdx_I = n_start_I; + } else + d_readNdx_I += n_now_I; + *bufLen_I = l_bufLen_I; + d_n_avail_read_I -= l_bufLen_I; + d_n_avail_write_I += l_bufLen_I; + d_writeBlock->signal (); + d_internal->unlock (); + return (1); + }; + + void abort () { + d_internal->lock (); + d_doAbort = true; + d_writeBlock->signal (); + d_readBlock->signal (); + d_internal->unlock (); + }; +}; + +#endif /* _CIRCULAR_BUFFER_H_ */ diff --git a/gr-audio-osx/src/mld_threads.h b/gr-audio-osx/src/mld_threads.h new file mode 100644 index 000000000..12adc23ee --- /dev/null +++ b/gr-audio-osx/src/mld_threads.h @@ -0,0 +1,255 @@ +/* -*- 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_MLD_THREADS_H_ +#define _INCLUDED_MLD_THREADS_H_ + +/* classes which allow for either pthreads or omni_threads */ + +#ifdef _USE_OMNI_THREADS_ +#include <gnuradio/omnithread.h> +#else +#include <pthread.h> +#endif + +#include <stdexcept> + +#define __INLINE__ inline + +class mld_condition_t; + +class mld_mutex_t { +#ifdef _USE_OMNI_THREADS_ + typedef omni_mutex l_mutex, *l_mutex_ptr; +#else + typedef pthread_mutex_t l_mutex, *l_mutex_ptr; +#endif + + friend class mld_condition_t; + +private: + l_mutex_ptr d_mutex; + +protected: + inline l_mutex_ptr mutex () { return (d_mutex); }; + +public: + __INLINE__ mld_mutex_t () { +#ifdef _USE_OMNI_THREADS_ + d_mutex = new omni_mutex (); +#else + d_mutex = (l_mutex_ptr) new l_mutex; + int l_ret = pthread_mutex_init (d_mutex, NULL); + if (l_ret != 0) { + fprintf (stderr, "Error %d creating mutex.\n", l_ret); + throw std::runtime_error ("mld_mutex_t::mld_mutex_t()\n"); + } +#endif + }; + + __INLINE__ ~mld_mutex_t () { + unlock (); +#ifndef _USE_OMNI_THREADS_ + int l_ret = pthread_mutex_destroy (d_mutex); + if (l_ret != 0) { + fprintf (stderr, "mld_mutex_t::~mld_mutex_t(): " + "Error %d destroying mutex.\n", l_ret); + } +#endif + delete d_mutex; + d_mutex = NULL; + }; + + __INLINE__ void lock () { +#ifdef _USE_OMNI_THREADS_ + d_mutex->lock (); +#else + int l_ret = pthread_mutex_lock (d_mutex); + if (l_ret != 0) { + fprintf (stderr, "mld_mutex_t::lock(): " + "Error %d locking mutex.\n", l_ret); + } +#endif + }; + + __INLINE__ void unlock () { +#ifdef _USE_OMNI_THREADS_ + d_mutex->unlock (); +#else + int l_ret = pthread_mutex_unlock (d_mutex); + if (l_ret != 0) { + fprintf (stderr, "mld_mutex_t::unlock(): " + "Error %d locking mutex.\n", l_ret); + } +#endif + }; + + __INLINE__ bool trylock () { +#ifdef _USE_OMNI_THREADS_ + int l_ret = d_mutex->trylock (); +#else + int l_ret = pthread_mutex_unlock (d_mutex); +#endif + return (l_ret == 0 ? true : false); + }; + + inline void acquire () { lock(); }; + inline void release () { unlock(); }; + inline void wait () { lock(); }; + inline void post () { unlock(); }; +}; + +typedef mld_mutex_t mld_mutex, *mld_mutex_ptr; + +class mld_condition_t { +#ifdef _USE_OMNI_THREADS_ + typedef omni_condition l_condition, *l_condition_ptr; +#else + typedef pthread_cond_t l_condition, *l_condition_ptr; +#endif + +private: + l_condition_ptr d_condition; + mld_mutex_ptr d_mutex; + bool d_waiting; + +public: + __INLINE__ mld_condition_t () { + d_waiting = false; + d_mutex = new mld_mutex (); +#ifdef _USE_OMNI_THREADS_ + d_condition = new omni_condition (d_mutex->mutex ()); +#else + d_condition = (l_condition_ptr) new l_condition; + int l_ret = pthread_cond_init (d_condition, NULL); + if (l_ret != 0) { + fprintf (stderr, "Error %d creating condition.\n", l_ret); + throw std::runtime_error ("mld_condition_t::mld_condition_t()\n"); + } +#endif + }; + + __INLINE__ ~mld_condition_t () { + signal (); +#ifndef _USE_OMNI_THREADS_ + int l_ret = pthread_cond_destroy (d_condition); + if (l_ret != 0) { + fprintf (stderr, "mld_condition_t::mld_condition_t(): " + "Error %d destroying condition.\n", l_ret); + } +#endif + delete d_condition; + d_condition = NULL; + delete d_mutex; + d_mutex = NULL; + }; + + __INLINE__ void signal () { + if (d_waiting == true) { +#ifdef _USE_OMNI_THREADS_ + d_condition->signal (); +#else + int l_ret = pthread_cond_signal (d_condition); + if (l_ret != 0) { + fprintf (stderr, "mld_condition_t::signal(): " + "Error %d.\n", l_ret); + } +#endif + d_waiting = false; + } + }; + + __INLINE__ void wait () { + if (d_waiting == false) { + d_waiting = true; +#ifdef _USE_OMNI_THREADS_ + d_condition->wait (); +#else + int l_ret = pthread_cond_wait (d_condition, d_mutex->mutex ()); + if (l_ret != 0) { + fprintf (stderr, "mld_condition_t::wait(): " + "Error %d.\n", l_ret); + } +#endif + } + }; +}; + +typedef mld_condition_t mld_condition, *mld_condition_ptr; + +class mld_thread_t { +#ifdef _USE_OMNI_THREADS_ + typedef omni_thread l_thread, *l_thread_ptr; +#else + typedef pthread_t l_thread, *l_thread_ptr; +#endif + +private: +#ifndef _USE_OMNI_THREADS_ + l_thread d_thread; + void (*d_start_routine)(void*); + void *d_arg; +#else + l_thread_ptr d_thread; +#endif + +#ifndef _USE_OMNI_THREADS_ + static void* local_start_routine (void *arg) { + mld_thread_t* This = (mld_thread_t*) arg; + (*(This->d_start_routine))(This->d_arg); + return (NULL); + }; +#endif + +public: + __INLINE__ mld_thread_t (void (*start_routine)(void *), void *arg) { +#ifdef _USE_OMNI_THREADS_ + d_thread = new omni_thread (start_routine, arg); + d_thread->start (); +#else + d_start_routine = start_routine; + d_arg = arg; + int l_ret = pthread_create (&d_thread, NULL, local_start_routine, this); + if (l_ret != 0) { + fprintf (stderr, "Error %d creating thread.\n", l_ret); + throw std::runtime_error ("mld_thread_t::mld_thread_t()\n"); + } +#endif + }; + + __INLINE__ ~mld_thread_t () { +#ifdef _USE_OMNI_THREADS_ +// delete d_thread; + d_thread = NULL; +#else + int l_ret = pthread_detach (d_thread); + if (l_ret != 0) { + fprintf (stderr, "Error %d detaching thread.\n", l_ret); + throw std::runtime_error ("mld_thread_t::~mld_thread_t()\n"); + } +#endif + }; +}; + +typedef mld_thread_t mld_thread, *mld_thread_ptr; + +#endif /* _INCLUDED_MLD_THREADS_H_ */ diff --git a/gr-audio-osx/src/qa_osx.py b/gr-audio-osx/src/qa_osx.py new file mode 100755 index 000000000..1e85c1581 --- /dev/null +++ b/gr-audio-osx/src/qa_osx.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# +# 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. +# + +from gnuradio import gr, gr_unittest +import audio_osx + +class qa_osx (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 OSX drivers, etc. Don't try to run anything""" + pass + +if __name__ == '__main__': + gr_unittest.main () diff --git a/gr-audio-osx/src/run_tests.in b/gr-audio-osx/src/run_tests.in new file mode 100644 index 000000000..52de4dd13 --- /dev/null +++ b/gr-audio-osx/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-osx/src +# Where to look in the src tree for swig generated python code +libsrc=@abs_top_srcdir@/gr-audio-osx/src +# Where to look in the src tree for hand written python code +py=@abs_top_srcdir@/gr-audio-osx/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 diff --git a/gr-audio-osx/src/test_audio_loop.py b/gr-audio-osx/src/test_audio_loop.py new file mode 100755 index 000000000..825908f57 --- /dev/null +++ b/gr-audio-osx/src/test_audio_loop.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# +# 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. +# + +from gnuradio import gr +from gnuradio import audio +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +class my_graph(gr.flow_graph): + + def __init__(self): + gr.flow_graph.__init__(self) + + parser = OptionParser(option_class=eng_option) + parser.add_option("-O", "--audio-output", + type="string", + default="", + help="audio output device name. E.g., hw:0,0 or /dev/dsp") + parser.add_option("-I", "--audio-input", + type="string", + default="", + help="audio input device name. E.g., hw:0,0 or /dev/dsp") + parser.add_option("-r", "--sample-rate", + type="eng_float", + default=48000, + help="set sample rate to RATE (48000)") + (options, args) = parser.parse_args () + if len(args) != 0: + parser.print_help() + raise SystemExit, 1 + + sample_rate = int(options.sample_rate) + src = audio.source (sample_rate, options.audio_output) + dst = audio.sink (sample_rate, options.audio_output) + + max_chan = max (src.output_signature().max_streams(), + dst.output_signature().max_streams()) + + for i in range (max_chan): + self.connect ((src, i), (dst, i)) + +if __name__ == '__main__': + try: + my_graph().run() + except KeyboardInterrupt: + pass |