/* 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 "JackPlatformThread.h" #include "JackError.h" #include "jack.h" namespace Jack { //inline void* aligned_calloc(size_t nmemb, size_t size) { return (void*)((size_t)(calloc((nmemb * size) + 15, sizeof(char))) + 15 & ~15); } 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(int input, int output, jack_nframes_t buffer_size, jack_nframes_t sample_rate) : fCardName("hw:0"), fFrequency(sample_rate), fBuffering(buffer_size), fSoftInputs(input), fSoftOutputs(output) {} 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; } AudioParam& inputs(int n) { fSoftInputs = n; return *this; } AudioParam& outputs(int n) { fSoftOutputs = n; return *this; } }; /** * An ALSA audio interface */ class AudioInterface : public AudioParam { public: snd_pcm_t* fOutputDevice; snd_pcm_t* fInputDevice; snd_pcm_hw_params_t* fInputParams; snd_pcm_hw_params_t* fOutputParams; snd_pcm_format_t fSampleFormat; snd_pcm_access_t fSampleAccess; unsigned int fCardInputs; unsigned int fCardOutputs; unsigned int fChanInputs; unsigned int fChanOutputs; // 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: 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; } AudioInterface(int input, int output, jack_nframes_t buffer_size, jack_nframes_t sample_rate) : AudioParam(input, output, 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() { int err; // allocation d'un stream d'entree et d'un stream de sortie err = snd_pcm_open(&fInputDevice, fCardName, SND_PCM_STREAM_CAPTURE, 0); check_error(err) err = snd_pcm_open(&fOutputDevice, fCardName, SND_PCM_STREAM_PLAYBACK, 0); check_error(err) // recherche des parametres d'entree err = snd_pcm_hw_params_malloc(&fInputParams); check_error(err); setAudioParams(fInputDevice, fInputParams); snd_pcm_hw_params_get_channels(fInputParams, &fCardInputs); // recherche des parametres de sortie err = snd_pcm_hw_params_malloc(&fOutputParams); check_error(err) setAudioParams(fOutputDevice, fOutputParams); snd_pcm_hw_params_get_channels(fOutputParams, &fCardOutputs); jack_info("inputs : %ud, outputs : %ud", fCardInputs, fCardOutputs); // enregistrement des parametres d'entree-sortie err = snd_pcm_hw_params(fInputDevice, fInputParams); check_error (err); err = snd_pcm_hw_params(fOutputDevice, fOutputParams); check_error (err); // allocation of alsa 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); } } // allocation of floating point buffers needed by the dsp code fChanInputs = max(fSoftInputs, fCardInputs); assert (fChanInputs < 256); fChanOutputs = max(fSoftOutputs, fCardOutputs); assert (fChanOutputs < 256); for (unsigned int i = 0; i < fChanInputs; 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 < fChanOutputs; 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 < fChanInputs; i++) { if (fInputSoftChannels[i]) free(fInputSoftChannels[i]); } for (unsigned int i = 0; i < fChanOutputs; 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) { int err; // set params record with initial values err = snd_pcm_hw_params_any ( stream, params ); check_error_msg(err, "unable to init parameters") // set alsa access mode (and fSampleAccess field) either to non interleaved or interleaved err = snd_pcm_hw_params_set_access (stream, params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); if (err) { err = snd_pcm_hw_params_set_access (stream, params, SND_PCM_ACCESS_RW_INTERLEAVED ); check_error_msg(err, "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 err = snd_pcm_hw_params_set_format (stream, params, SND_PCM_FORMAT_S32); if (err) { err = snd_pcm_hw_params_set_format (stream, params, SND_PCM_FORMAT_S16); check_error_msg(err, "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) err = snd_pcm_hw_params_set_period_size (stream, params, fBuffering, 0); check_error_msg(err, "period size not available"); err = snd_pcm_hw_params_set_periods (stream, params, 2, 0); check_error_msg(err, "number of periods not available"); return 0; } ssize_t interleavedBufferSize(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); unsigned int channels; 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() { if (fSampleAccess == SND_PCM_ACCESS_RW_INTERLEAVED) { int count = snd_pcm_readi(fInputDevice, fInputCardBuffer, fBuffering); if (count<0) { display_error_msg(count, "reading samples"); int err = snd_pcm_prepare(fInputDevice); check_error_msg(err, "preparing input stream"); } if (fSampleFormat == SND_PCM_FORMAT_S16) { short* buffer16b = (short*) fInputCardBuffer; for (int s = 0; s < fBuffering; s++) { for (unsigned int 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 (int s = 0; s < fBuffering; s++) { for (unsigned int c = 0; c < fCardInputs; c++) { fInputSoftChannels[c][s] = float(buffer32b[c + s*fCardInputs])*(1.0/float(LONG_MAX)); } } } } else if (fSampleAccess == SND_PCM_ACCESS_RW_NONINTERLEAVED) { int count = snd_pcm_readn(fInputDevice, fInputCardChannels, fBuffering); if (count < 0) { display_error_msg(count, "reading samples"); int err = snd_pcm_prepare(fInputDevice); check_error_msg(err, "preparing input stream"); } if (fSampleFormat == SND_PCM_FORMAT_S16) { for (unsigned int c = 0; c < fCardInputs; c++) { short* chan16b = (short*) fInputCardChannels[c]; for (int s = 0; s < fBuffering; s++) { fInputSoftChannels[c][s] = float(chan16b[s])*(1.0/float(SHRT_MAX)); } } } else { // SND_PCM_FORMAT_S32 for (unsigned int c = 0; c < fCardInputs; c++) { long* chan32b = (long*) fInputCardChannels[c]; for (int s = 0; s < fBuffering; s++) { fInputSoftChannels[c][s] = float(chan32b[s])*(1.0/float(LONG_MAX)); } } } } else { check_error_msg(-10000, "unknow access mode"); } return 0; } /** * write the output soft channels to the audio card. Convert sample * format and interleaves buffers when needed */ int write() { recovery: if (fSampleAccess == SND_PCM_ACCESS_RW_INTERLEAVED) { if (fSampleFormat == SND_PCM_FORMAT_S16) { short* buffer16b = (short*) fOutputCardBuffer; for (int 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 (int 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)); } } } int 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; } } else if (fSampleAccess == SND_PCM_ACCESS_RW_NONINTERLEAVED) { if (fSampleFormat == SND_PCM_FORMAT_S16) { for (unsigned int c = 0; c < fCardOutputs; c++) { short* chan16b = (short*) fOutputCardChannels[c]; for (int f = 0; f < fBuffering; f++) { float x = fOutputSoftChannels[c][f]; chan16b[f] = short(max(min(x,1.0), -1.0) * float(SHRT_MAX)) ; } } } else { // SND_PCM_FORMAT_S32 for (unsigned int c = 0; c < fCardOutputs; c++) { long* chan32b = (long*) fOutputCardChannels[c]; for (int f = 0; f < fBuffering; f++) { float x = fOutputSoftChannels[c][f]; chan32b[f] = long(max(min(x,1.0),-1.0) * float(LONG_MAX)) ; } } } int 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; } } else { check_error_msg(-10000, "unknow access mode"); } 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() { int err; snd_ctl_card_info_t* card_info; snd_ctl_t* ctl_handle; jack_info("Audio Interface Description :"); jack_info("Sampling Frequency : %d, Sample Format : %s, buffering : %d", fFrequency, snd_pcm_format_name((_snd_pcm_format)fSampleFormat), fBuffering); jack_info("Software inputs : %2d, Software outputs : %2d", fSoftInputs, fSoftOutputs); jack_info("Hardware inputs : %2d, Hardware outputs : %2d", fCardInputs, fCardOutputs); jack_info("Channel inputs : %2d, Channel outputs : %2d", fChanInputs, fChanOutputs); // affichage des infos de la carte 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); printCardInfo(card_info); // affichage des infos liees aux streams d'entree-sortie 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)); jack_info("\tFormat = %s", snd_pcm_format_name((_snd_pcm_format)snd_pcm_hw_params_get_format(params))); jack_info("\tAccess = %s", snd_pcm_access_name((_snd_pcm_access)snd_pcm_hw_params_get_access(params))); jack_info("\tRate = %d", snd_pcm_hw_params_get_rate(params, NULL)); jack_info("\tPeriods = %d", snd_pcm_hw_params_get_periods(params, NULL)); jack_info("\tPeriod size = %d", (int)snd_pcm_hw_params_get_period_size(params, NULL)); jack_info("\tPeriod time = %d", snd_pcm_hw_params_get_period_time(params, NULL)); jack_info("\tBuffer size = %d", (int)snd_pcm_hw_params_get_buffer_size(params)); jack_info("\tBuffer time = %d", snd_pcm_hw_params_get_buffer_time(params, NULL)); #endif jack_info("--------------"); } }; class JackAlsaAdapter : public JackAudioAdapterInterface, public JackRunnableInterface { private: JackThread fThread; AudioInterface fAudioInterface; public: JackAlsaAdapter(int input, int output, jack_nframes_t buffer_size, jack_nframes_t sample_rate) :JackAudioAdapterInterface(input, output, buffer_size, sample_rate) ,fThread(this), fAudioInterface(input, output, buffer_size, sample_rate) {} ~JackAlsaAdapter() {} virtual int Open(); virtual int Close(); virtual int SetBufferSize(jack_nframes_t buffer_size); virtual bool Init(); virtual bool Execute(); }; } #endif