/* Copyright (C) 2008 Grame This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JackAlsaAdapter__ #define __JackAlsaAdapter__ #include #include #include #include #include "JackAudioAdapterInterface.h" #include "JackPlatformPlug.h" #include "JackError.h" #include "jack.h" #include "jslist.h" namespace Jack { inline void* aligned_calloc ( size_t nmemb, size_t size ) { return ( void* ) calloc ( nmemb, size ); } #define max(x,y) (((x)>(y)) ? (x) : (y)) #define min(x,y) (((x)<(y)) ? (x) : (y)) #define check_error(err) if (err) { jack_error("%s:%d, alsa error %d : %s", __FILE__, __LINE__, err, snd_strerror(err)); return err; } #define check_error_msg(err,msg) if (err) { jack_error("%s:%d, %s : %s(%d)", __FILE__, __LINE__, msg, snd_strerror(err), err); return err; } #define display_error_msg(err,msg) if (err) { jack_error("%s:%d, %s : %s(%d)", __FILE__, __LINE__, msg, snd_strerror(err), err); } /** * A convenient class to pass parameters to AudioInterface */ class AudioParam { public: const char* fCardName; unsigned int fFrequency; int fBuffering; unsigned int fSoftInputs; unsigned int fSoftOutputs; public: AudioParam() : fCardName ( "hw:0" ), fFrequency ( 44100 ), fBuffering ( 512 ), fSoftInputs ( 2 ), fSoftOutputs ( 2 ) {} AudioParam ( jack_nframes_t buffer_size, jack_nframes_t sample_rate ) : fCardName ( "hw:0" ), fFrequency ( sample_rate ), fBuffering ( buffer_size ), fSoftInputs ( 2 ), fSoftOutputs ( 2 ) {} AudioParam& cardName ( const char* n ) { fCardName = n; return *this; } AudioParam& frequency ( int f ) { fFrequency = f; return *this; } AudioParam& buffering ( int fpb ) { fBuffering = fpb; return *this; } void setInputs ( int inputs ) { fSoftInputs = inputs; } AudioParam& inputs ( int n ) { fSoftInputs = n; return *this; } void setOutputs ( int outputs ) { fSoftOutputs = outputs; } AudioParam& outputs ( int n ) { fSoftOutputs = n; return *this; } }; /** * An ALSA audio interface */ class AudioInterface : public AudioParam { public: //device info snd_pcm_t* fOutputDevice; snd_pcm_t* fInputDevice; snd_pcm_hw_params_t* fInputParams; snd_pcm_hw_params_t* fOutputParams; //samples info snd_pcm_format_t fSampleFormat; snd_pcm_access_t fSampleAccess; //channels unsigned int fCardInputs; unsigned int fCardOutputs; //stream parameters unsigned int fPeriod; //interleaved mode audiocard buffers void* fInputCardBuffer; void* fOutputCardBuffer; //non-interleaved mode audiocard buffers void* fInputCardChannels[256]; void* fOutputCardChannels[256]; //non-interleaved mod, floating point software buffers float* fInputSoftChannels[256]; float* fOutputSoftChannels[256]; //public methods --------------------------------------------------------- const char* cardName() { return fCardName; } int frequency() { return fFrequency; } int buffering() { return fBuffering; } float** inputSoftChannels() { return fInputSoftChannels; } float** outputSoftChannels() { return fOutputSoftChannels; } AudioInterface ( const AudioParam& ap = AudioParam() ) : AudioParam ( ap ) { fInputDevice = 0; fOutputDevice = 0; fInputParams = 0; fOutputParams = 0; fPeriod = 2; } AudioInterface ( jack_nframes_t buffer_size, jack_nframes_t sample_rate ) : AudioParam ( buffer_size, sample_rate ) { fInputCardBuffer = 0; fOutputCardBuffer = 0; for ( int i = 0; i < 256; i++ ) { fInputCardChannels[i] = 0; fOutputCardChannels[i] = 0; fInputSoftChannels[i] = 0; fOutputSoftChannels[i] = 0; } } /** * Open the audio interface */ int open() { //open input/output streams check_error ( snd_pcm_open ( &fInputDevice, fCardName, SND_PCM_STREAM_CAPTURE, 0 ) ); check_error ( snd_pcm_open ( &fOutputDevice, fCardName, SND_PCM_STREAM_PLAYBACK, 0 ) ); //get hardware input parameters check_error ( snd_pcm_hw_params_malloc ( &fInputParams ) ); setAudioParams ( fInputDevice, fInputParams ); snd_pcm_hw_params_get_channels ( fInputParams, &fCardInputs ); //get hardware output parameters check_error ( snd_pcm_hw_params_malloc ( &fOutputParams ) ) setAudioParams ( fOutputDevice, fOutputParams ); snd_pcm_hw_params_get_channels ( fOutputParams, &fCardOutputs ); //set input/output param check_error ( snd_pcm_hw_params ( fInputDevice, fInputParams ) ); check_error ( snd_pcm_hw_params ( fOutputDevice, fOutputParams ) ); //set hardware buffers if ( fSampleAccess == SND_PCM_ACCESS_RW_INTERLEAVED ) { fInputCardBuffer = aligned_calloc ( interleavedBufferSize ( fInputParams ), 1 ); fOutputCardBuffer = aligned_calloc ( interleavedBufferSize ( fOutputParams ), 1 ); } else { for ( unsigned int i = 0; i < fCardInputs; i++ ) fInputCardChannels[i] = aligned_calloc ( noninterleavedBufferSize ( fInputParams ), 1 ); for ( unsigned int i = 0; i < fCardOutputs; i++ ) fOutputCardChannels[i] = aligned_calloc ( noninterleavedBufferSize ( fOutputParams ), 1 ); } //set floating point buffers needed by the dsp code fSoftInputs = max ( fSoftInputs, fCardInputs ); assert ( fSoftInputs < 256 ); fSoftOutputs = max ( fSoftOutputs, fCardOutputs ); assert ( fSoftOutputs < 256 ); for ( unsigned int i = 0; i < fSoftInputs; i++ ) { fInputSoftChannels[i] = ( float* ) aligned_calloc ( fBuffering, sizeof ( float ) ); for ( int j = 0; j < fBuffering; j++ ) fInputSoftChannels[i][j] = 0.0; } for ( unsigned int i = 0; i < fSoftOutputs; i++ ) { fOutputSoftChannels[i] = ( float* ) aligned_calloc ( fBuffering, sizeof ( float ) ); for ( int j = 0; j < fBuffering; j++ ) fOutputSoftChannels[i][j] = 0.0; } return 0; } int close() { snd_pcm_hw_params_free ( fInputParams ); snd_pcm_hw_params_free ( fOutputParams ); snd_pcm_close ( fInputDevice ); snd_pcm_close ( fOutputDevice ); for ( unsigned int i = 0; i < fSoftInputs; i++ ) if ( fInputSoftChannels[i] ) free ( fInputSoftChannels[i] ); for ( unsigned int i = 0; i < fSoftOutputs; i++ ) if ( fOutputSoftChannels[i] ) free ( fOutputSoftChannels[i] ); for ( unsigned int i = 0; i < fCardInputs; i++ ) if ( fInputCardChannels[i] ) free ( fInputCardChannels[i] ); for ( unsigned int i = 0; i < fCardOutputs; i++ ) if ( fOutputCardChannels[i] ) free ( fOutputCardChannels[i] ); if ( fInputCardBuffer ) free ( fInputCardBuffer ); if ( fOutputCardBuffer ) free ( fOutputCardBuffer ); return 0; } int setAudioParams ( snd_pcm_t* stream, snd_pcm_hw_params_t* params ) { //set params record with initial values check_error_msg ( snd_pcm_hw_params_any ( stream, params ), "unable to init parameters" ) //set alsa access mode (and fSampleAccess field) either to non interleaved or interleaved if ( snd_pcm_hw_params_set_access ( stream, params, SND_PCM_ACCESS_RW_NONINTERLEAVED ) ) check_error_msg ( snd_pcm_hw_params_set_access ( stream, params, SND_PCM_ACCESS_RW_INTERLEAVED ), "unable to set access mode neither to non-interleaved or to interleaved" ); snd_pcm_hw_params_get_access ( params, &fSampleAccess ); //search for 32-bits or 16-bits format if ( snd_pcm_hw_params_set_format ( stream, params, SND_PCM_FORMAT_S32 ) ) check_error_msg ( snd_pcm_hw_params_set_format ( stream, params, SND_PCM_FORMAT_S16 ), "unable to set format to either 32-bits or 16-bits" ); snd_pcm_hw_params_get_format ( params, &fSampleFormat ); //set sample frequency snd_pcm_hw_params_set_rate_near ( stream, params, &fFrequency, 0 ); //set period and period size (buffering) check_error_msg ( snd_pcm_hw_params_set_period_size ( stream, params, fBuffering, 0 ), "period size not available" ); check_error_msg ( snd_pcm_hw_params_set_periods ( stream, params, fPeriod, 0 ), "number of periods not available" ); return 0; } ssize_t interleavedBufferSize ( snd_pcm_hw_params_t* params ) { _snd_pcm_format format; unsigned int channels; snd_pcm_hw_params_get_format ( params, &format ); snd_pcm_uframes_t psize; snd_pcm_hw_params_get_period_size ( params, &psize, NULL ); snd_pcm_hw_params_get_channels ( params, &channels ); ssize_t bsize = snd_pcm_format_size ( format, psize * channels ); return bsize; } ssize_t noninterleavedBufferSize ( snd_pcm_hw_params_t* params ) { _snd_pcm_format format; snd_pcm_hw_params_get_format ( params, &format ); snd_pcm_uframes_t psize; snd_pcm_hw_params_get_period_size ( params, &psize, NULL ); ssize_t bsize = snd_pcm_format_size ( format, psize ); return bsize; } /** * Read audio samples from the audio card. Convert samples to floats and take * care of interleaved buffers */ int read() { int count, s; unsigned int c; switch ( fSampleAccess ) { case SND_PCM_ACCESS_RW_INTERLEAVED : count = snd_pcm_readi ( fInputDevice, fInputCardBuffer, fBuffering ); if ( count < 0 ) { display_error_msg ( count, "reading samples" ); check_error_msg ( snd_pcm_prepare ( fInputDevice ), "preparing input stream" ); } if ( fSampleFormat == SND_PCM_FORMAT_S16 ) { short* buffer16b = ( short* ) fInputCardBuffer; for ( s = 0; s < fBuffering; s++ ) for ( c = 0; c < fCardInputs; c++ ) fInputSoftChannels[c][s] = float ( buffer16b[c + s*fCardInputs] ) * ( 1.0/float ( SHRT_MAX ) ); } else // SND_PCM_FORMAT_S32 { long* buffer32b = ( long* ) fInputCardBuffer; for ( s = 0; s < fBuffering; s++ ) for ( c = 0; c < fCardInputs; c++ ) fInputSoftChannels[c][s] = float ( buffer32b[c + s*fCardInputs] ) * ( 1.0/float ( LONG_MAX ) ); } break; case SND_PCM_ACCESS_RW_NONINTERLEAVED : count = snd_pcm_readn ( fInputDevice, fInputCardChannels, fBuffering ); if ( count < 0 ) { display_error_msg ( count, "reading samples" ); check_error_msg ( snd_pcm_prepare ( fInputDevice ), "preparing input stream" ); } if ( fSampleFormat == SND_PCM_FORMAT_S16 ) { short* chan16b; for ( c = 0; c < fCardInputs; c++ ) { chan16b = ( short* ) fInputCardChannels[c]; for ( s = 0; s < fBuffering; s++ ) fInputSoftChannels[c][s] = float ( chan16b[s] ) * ( 1.0/float ( SHRT_MAX ) ); } } else // SND_PCM_FORMAT_S32 { long* chan32b; for ( c = 0; c < fCardInputs; c++ ) { chan32b = ( long* ) fInputCardChannels[c]; for ( s = 0; s < fBuffering; s++ ) fInputSoftChannels[c][s] = float ( chan32b[s] ) * ( 1.0/float ( LONG_MAX ) ); } } break; default : check_error_msg ( -10000, "unknow access mode" ); break; } return 0; } /** * write the output soft channels to the audio card. Convert sample * format and interleaves buffers when needed */ int write() { int count, f; unsigned int c; recovery: switch ( fSampleAccess ) { case SND_PCM_ACCESS_RW_INTERLEAVED : if ( fSampleFormat == SND_PCM_FORMAT_S16 ) { short* buffer16b = ( short* ) fOutputCardBuffer; for ( f = 0; f < fBuffering; f++ ) { for ( unsigned int c = 0; c < fCardOutputs; c++ ) { float x = fOutputSoftChannels[c][f]; buffer16b[c + f * fCardOutputs] = short ( max ( min ( x, 1.0 ), -1.0 ) * float ( SHRT_MAX ) ); } } } else // SND_PCM_FORMAT_S32 { long* buffer32b = ( long* ) fOutputCardBuffer; for ( f = 0; f < fBuffering; f++ ) { for ( unsigned int c = 0; c < fCardOutputs; c++ ) { float x = fOutputSoftChannels[c][f]; buffer32b[c + f * fCardOutputs] = long ( max ( min ( x, 1.0 ), -1.0 ) * float ( LONG_MAX ) ); } } } count = snd_pcm_writei ( fOutputDevice, fOutputCardBuffer, fBuffering ); if ( count < 0 ) { display_error_msg ( count, "w3" ); int err = snd_pcm_prepare ( fOutputDevice ); check_error_msg ( err, "preparing output stream" ); goto recovery; } break; case SND_PCM_ACCESS_RW_NONINTERLEAVED : if ( fSampleFormat == SND_PCM_FORMAT_S16 ) { for ( c = 0; c < fCardOutputs; c++ ) { short* chan16b = ( short* ) fOutputCardChannels[c]; for ( f = 0; f < fBuffering; f++ ) { float x = fOutputSoftChannels[c][f]; chan16b[f] = short ( max ( min ( x,1.0 ), -1.0 ) * float ( SHRT_MAX ) ) ; } } } else { for ( c = 0; c < fCardOutputs; c++ ) { long* chan32b = ( long* ) fOutputCardChannels[c]; for ( f = 0; f < fBuffering; f++ ) { float x = fOutputSoftChannels[c][f]; chan32b[f] = long ( max ( min ( x,1.0 ),-1.0 ) * float ( LONG_MAX ) ) ; } } } count = snd_pcm_writen ( fOutputDevice, fOutputCardChannels, fBuffering ); if ( count<0 ) { display_error_msg ( count, "w3" ); int err = snd_pcm_prepare ( fOutputDevice ); check_error_msg ( err, "preparing output stream" ); goto recovery; } break; default : check_error_msg ( -10000, "unknow access mode" ); break; } return 0; } /** * print short information on the audio device */ int shortinfo() { int err; snd_ctl_card_info_t* card_info; snd_ctl_t* ctl_handle; err = snd_ctl_open ( &ctl_handle, fCardName, 0 ); check_error ( err ); snd_ctl_card_info_alloca ( &card_info ); err = snd_ctl_card_info ( ctl_handle, card_info ); check_error ( err ); jack_info ( "%s|%d|%d|%d|%d|%s", snd_ctl_card_info_get_driver ( card_info ), fCardInputs, fCardOutputs, fFrequency, fBuffering, snd_pcm_format_name ( ( _snd_pcm_format ) fSampleFormat ) ); } /** * print more detailled information on the audio device */ int longinfo() { snd_ctl_card_info_t* card_info; snd_ctl_t* ctl_handle; //display info jack_info ( "Audio Interface Description :" ); jack_info ( "Sampling Frequency : %d, Sample Format : %s, buffering : %d, nperiod : %d", fFrequency, snd_pcm_format_name ( ( _snd_pcm_format ) fSampleFormat ), fBuffering, fPeriod ); jack_info ( "Software inputs : %2d, Software outputs : %2d", fSoftInputs, fSoftOutputs ); jack_info ( "Hardware inputs : %2d, Hardware outputs : %2d", fCardInputs, fCardOutputs ); //get audio card info and display check_error ( snd_ctl_open ( &ctl_handle, fCardName, 0 ) ); snd_ctl_card_info_alloca ( &card_info ); check_error ( snd_ctl_card_info ( ctl_handle, card_info ) ); printCardInfo ( card_info ); //display input/output streams info if ( fSoftInputs > 0 ) printHWParams ( fInputParams ); if ( fSoftOutputs > 0 ) printHWParams ( fOutputParams ); return 0; } void printCardInfo ( snd_ctl_card_info_t* ci ) { jack_info ( "Card info (address : %p)", ci ); jack_info ( "\tID = %s", snd_ctl_card_info_get_id ( ci ) ); jack_info ( "\tDriver = %s", snd_ctl_card_info_get_driver ( ci ) ); jack_info ( "\tName = %s", snd_ctl_card_info_get_name ( ci ) ); jack_info ( "\tLongName = %s", snd_ctl_card_info_get_longname ( ci ) ); jack_info ( "\tMixerName = %s", snd_ctl_card_info_get_mixername ( ci ) ); jack_info ( "\tComponents = %s", snd_ctl_card_info_get_components ( ci ) ); jack_info ( "--------------" ); } void printHWParams ( snd_pcm_hw_params_t* params ) { jack_info ( "HW Params info (address : %p)\n", params ); #if 0 jack_info ( "\tChannels = %d", snd_pcm_hw_params_get_channels ( params, NULL ) ); jack_info ( "\tFormat = %s", snd_pcm_format_name ( ( _snd_pcm_format ) snd_pcm_hw_params_get_format ( params, NULL ) ) ); jack_info ( "\tAccess = %s", snd_pcm_access_name ( ( _snd_pcm_access ) snd_pcm_hw_params_get_access ( params, NULL ) ) ); jack_info ( "\tRate = %d", snd_pcm_hw_params_get_rate ( params, NULL, NULL ) ); jack_info ( "\tPeriods = %d", snd_pcm_hw_params_get_periods ( params, NULL, NULL ) ); jack_info ( "\tPeriod size = %d", ( int ) snd_pcm_hw_params_get_period_size ( params, NULL, NULL ) ); jack_info ( "\tPeriod time = %d", snd_pcm_hw_params_get_period_time ( params, NULL, NULL ) ); jack_info ( "\tBuffer size = %d", ( int ) snd_pcm_hw_params_get_buffer_size ( params, NULL ) ); jack_info ( "\tBuffer time = %d", snd_pcm_hw_params_get_buffer_time ( params, NULL, NULL ) ); #endif jack_info ( "--------------" ); } }; /*! \brief Audio adapter using ALSA API. */ class JackAlsaAdapter : public JackAudioAdapterInterface, public JackRunnableInterface { private: JackThread fThread; AudioInterface fAudioInterface; public: JackAlsaAdapter ( jack_nframes_t buffer_size, jack_nframes_t sample_rate, const JSList* params ); ~JackAlsaAdapter() {} virtual int Open(); virtual int Close(); virtual int SetSampleRate ( jack_nframes_t sample_rate ); virtual int SetBufferSize ( jack_nframes_t buffer_size ); virtual bool Init(); virtual bool Execute(); }; } #ifdef __cplusplus extern "C" { #endif #include "JackCompilerDeps.h" #include "driver_interface.h" EXPORT jack_driver_desc_t* jack_get_descriptor(); #ifdef __cplusplus } #endif #endif