/* * Copyright (C) 2002 - David W. Durham * * This file is part of ReZound, an audio editing application. * * ReZound 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. * * ReZound 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "CALSASoundRecorder.h" #ifdef ENABLE_ALSA #include #include #include #include #include "settings.h" #include "AStatusComm.h" // ??? edit this to be able to detect necessary parameters from the typeof sample_t // or I need to convert to 16bit // needs to match for what is above and type of sample_t ??? #ifdef WORDS_BIGENDIAN #define ALSA_PCM_FORMAT SND_PCM_FORMAT_S16_BE #else #define ALSA_PCM_FORMAT SND_PCM_FORMAT_S16_LE #endif // ??? as the sample rate is lower these need to be lower so that onData is called more often and the view meters on the record dialog don't seem to lag #define PERIOD_SIZE_FRAMES 8192 #define PERIOD_COUNT 2 CALSASoundRecorder::CALSASoundRecorder() : ASoundRecorder(), initialized(false), capture_handle(NULL), recordThread(this) { } CALSASoundRecorder::~CALSASoundRecorder() { deinitialize(); } void CALSASoundRecorder::initialize(CSound *sound) { if(!initialized) { ASoundRecorder::initialize(sound); try { snd_pcm_hw_params_t *hw_params; snd_pcm_sw_params_t *sw_params; int err; if((err = snd_pcm_open(&capture_handle, gALSAInputDevice.c_str(), SND_PCM_STREAM_CAPTURE, 0)) < 0) throw runtime_error(string(__func__)+" -- cannot open audio device: "+gALSAInputDevice+" -- "+snd_strerror(err)); if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) throw runtime_error(string(__func__)+" -- cannot allocate hardware parameter structure -- "+snd_strerror(err)); if((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) throw runtime_error(string(__func__)+" -- cannot initialize hardware parameter structure -- "+snd_strerror(err)); // set sample rate unsigned int sampleRate=sound->getSampleRate(); unsigned int inSampleRate=sampleRate; if((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &inSampleRate, 0)) < 0) throw runtime_error(string(__func__)+" -- cannot set sample rate -- "+snd_strerror(err)); if(sampleRate!=inSampleRate) { // ??? need to translate Message("ALSA used a different sample rate ("+istring(inSampleRate)+") than what was asked for ("+istring(sound->getSampleRate())+")"); sound->setSampleRate(sampleRate); } // set number of channels unsigned channelCount=sound->getChannelCount(); // might need to use the near function if((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channelCount)) < 0) throw runtime_error(string(__func__)+" -- cannot set channel count -- "+snd_strerror(err)); // set to interleaved access if((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) throw runtime_error(string(__func__)+" -- cannot set access type -- "+snd_strerror(err)); // set periods to integer sizes only if((err = snd_pcm_hw_params_set_periods_integer(capture_handle, hw_params)) < 0) throw runtime_error(string(__func__)+" -- cannot set periods to integer only -- "+snd_strerror(err)); // set period size snd_pcm_uframes_t period_size=PERIOD_SIZE_FRAMES; int dir=0; if((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &period_size, &dir)) < 0) throw runtime_error(string(__func__)+" -- cannot set period size -- "+snd_strerror(err)); // set number of periods unsigned int periods=PERIOD_COUNT; if((err = snd_pcm_hw_params_set_periods(capture_handle, hw_params, periods, 0)) < 0) throw runtime_error(string(__func__)+" -- cannot set periods -- "+snd_strerror(err)); // and then set the buffer size (what .. what's this difference between buffers and periods?) if((err = snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params, (PERIOD_COUNT*PERIOD_SIZE_FRAMES) )) < 0) throw runtime_error(string(__func__)+" -- cannot set buffer size -- "+snd_strerror(err)); // set sample format vector formatsToTry; #ifndef WORDS_BIGENDIAN #if defined(SAMPLE_TYPE_S16) formatsToTry.push_back(SND_PCM_FORMAT_S16_LE); formatsToTry.push_back(SND_PCM_FORMAT_S32_LE); formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_LE); #elif defined(SAMPLE_TYPE_FLOAT) formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_LE); formatsToTry.push_back(SND_PCM_FORMAT_S32_LE); formatsToTry.push_back(SND_PCM_FORMAT_S16_LE); #else #error unhandled SAMPLE_TYPE_xxx define #endif #else #if defined(SAMPLE_TYPE_S16) formatsToTry.push_back(SND_PCM_FORMAT_S16_BE); formatsToTry.push_back(SND_PCM_FORMAT_S32_BE); formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_BE); #elif defined(SAMPLE_TYPE_FLOAT) formatsToTry.push_back(SND_PCM_FORMAT_FLOAT_BE); formatsToTry.push_back(SND_PCM_FORMAT_S32_BE); formatsToTry.push_back(SND_PCM_FORMAT_S16_BE); #else #error unhandled SAMPLE_TYPE_xxx define #endif #endif bool found=false; for(size_t t=0;tgetSound()->getChannelCount(); int err; TAutoBuffer buffer(PERIOD_SIZE_FRAMES*channelCount*2); // these are possibly used if sample format conversion is required TAutoBuffer buffer__int16_t(PERIOD_SIZE_FRAMES*channelCount); TAutoBuffer buffer__int32_t(PERIOD_SIZE_FRAMES*channelCount); TAutoBuffer buffer__float(PERIOD_SIZE_FRAMES*channelCount); snd_pcm_format_t format=parent->capture_format; // must do conversion of initialized format -> sample_t type type if necessary int conv=-1; #if defined(SAMPLE_TYPE_S16) if(format==SND_PCM_FORMAT_S16_LE || format==SND_PCM_FORMAT_S16_BE) conv=0; else if(format==SND_PCM_FORMAT_S32_LE || format==SND_PCM_FORMAT_S32_BE) conv=2; else if(format==SND_PCM_FORMAT_FLOAT_LE || format==SND_PCM_FORMAT_FLOAT_BE) conv=3; #elif defined(SAMPLE_TYPE_FLOAT) if(format==SND_PCM_FORMAT_S16_LE || format==SND_PCM_FORMAT_S16_BE) conv=1; else if(format==SND_PCM_FORMAT_S32_LE || format==SND_PCM_FORMAT_S32_BE) conv=2; else if(format==SND_PCM_FORMAT_FLOAT_LE || format==SND_PCM_FORMAT_FLOAT_BE) conv=0; #else #error unhandled SAMPLE_TYPE_xxx define #endif if((err = snd_pcm_start(parent->capture_handle)) < 0) throw runtime_error(string(__func__)+" -- snd_pcm_start failed -- "+snd_strerror(err)); while(!kill) { // wait till the interface has data ready, or 1 second has elapsed. if((err = snd_pcm_wait(parent->capture_handle, 1000)) < 0) throw runtime_error(string(__func__)+" -- snd_pcm_wait failed -- "+snd_strerror(err)); // find out how much data is available to read snd_pcm_sframes_t frames_ready; if((frames_ready = snd_pcm_avail_update(parent->capture_handle)) < 0) { if (frames_ready == -EPIPE) { fprintf (stderr, "an xrun occured\n"); } else throw runtime_error(string(__func__)+" -- unknown ALSA avail update return value: "+istring(frames_ready)); } // not absolutely necessary to wait for the exact amount I don't believe ??? but I don't think it hurts.. but code below assumes it's PERIOD_SIZE frames that are ready to read if(frames_readyredoMutex); */ snd_pcm_sframes_t len; #warning probably should abstract this into a since it could be used several places switch(conv) { case 0: // no conversion necessary { if((len=snd_pcm_readi(parent->capture_handle, buffer, PERIOD_SIZE_FRAMES)) < 0) fprintf(stderr, "ALSA read failed (%s)\n", snd_strerror(len)); } break; case 1: // we need to convert int16_t->sample_t { unsigned l=PERIOD_SIZE_FRAMES*channelCount; if((err=snd_pcm_readi(parent->capture_handle, buffer__int16_t, PERIOD_SIZE_FRAMES)) < 0) fprintf(stderr, "ALSA read failed (%s)\n", snd_strerror(err)); for(unsigned t=0;t(buffer__int16_t[t]); } break; case 2: // we need to convert int32_t->sample_t { unsigned l=PERIOD_SIZE_FRAMES*channelCount; if((err=snd_pcm_readi(parent->capture_handle, buffer__int32_t, PERIOD_SIZE_FRAMES)) < 0) fprintf(stderr, "ALSA read failed (%s)\n", snd_strerror(err)); for(unsigned t=0;t(buffer__int32_t[t]); } break; case 3: // we need to convert float->sample_t { unsigned l=PERIOD_SIZE_FRAMES*channelCount; if((err=snd_pcm_readi(parent->capture_handle, buffer__float, PERIOD_SIZE_FRAMES)) < 0) fprintf(stderr, "ALSA read failed (%s)\n", snd_strerror(err)); for(unsigned t=0;t(buffer__float[t]); } break; default: throw runtime_error(string(__func__)+" -- no conversion determined"); } parent->onData(buffer,PERIOD_SIZE_FRAMES); } } catch(exception &e) { fprintf(stderr,"exception caught in record thread: %s\n",e.what()); } catch(...) { fprintf(stderr,"unknown exception caught in record thread\n"); } } #endif // ENABLE_ALSA