![]() |
Mixxx
|
00001 /*************************************************************************** 00002 sounddeviceportaudio.cpp 00003 ------------------- 00004 begin : Sun Aug 15, 2007 (Stardate -315378.5417935057) 00005 copyright : (C) 2007 Albert Santoni 00006 email : gamegod \a\t users.sf.net 00007 ***************************************************************************/ 00008 00009 /*************************************************************************** 00010 * * 00011 * This program is free software; you can redistribute it and/or modify * 00012 * it under the terms of the GNU General Public License as published by * 00013 * the Free Software Foundation; either version 2 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 ***************************************************************************/ 00017 00018 #include <QtDebug> 00019 #include <QtCore> 00020 #include <portaudio.h> 00021 #include <assert.h> 00022 #include "controlobjectthreadmain.h" 00023 #include "soundmanager.h" 00024 #include "sounddevice.h" 00025 #include "sounddeviceportaudio.h" 00026 #include "soundmanagerutil.h" 00027 #include "controlobject.h" 00028 00029 SoundDevicePortAudio::SoundDevicePortAudio(ConfigObject<ConfigValue> *config, SoundManager *sm, 00030 const PaDeviceInfo *deviceInfo, unsigned int devIndex) 00031 : SoundDevice(config, sm), 00032 m_bSetThreadPriority(false) 00033 { 00034 //qDebug() << "SoundDevicePortAudio::SoundDevicePortAudio()"; 00035 m_deviceInfo = deviceInfo; 00036 m_devId = devIndex; 00037 m_hostAPI = Pa_GetHostApiInfo(deviceInfo->hostApi)->name; 00038 m_dSampleRate = deviceInfo->defaultSampleRate; 00039 m_strInternalName = QString("%1, %2").arg(QString::number(m_devId)).arg(deviceInfo->name); 00040 m_strDisplayName = QString(deviceInfo->name); 00041 00042 m_pStream = 0; 00043 //m_devId = -1; 00044 m_iNumberOfBuffers = 2; 00045 m_iNumInputChannels = m_deviceInfo->maxInputChannels; 00046 m_iNumOutputChannels = m_deviceInfo->maxOutputChannels; 00047 } 00048 00049 SoundDevicePortAudio::~SoundDevicePortAudio() 00050 { 00051 00052 } 00053 00054 int SoundDevicePortAudio::open() 00055 { 00056 qDebug() << "SoundDevicePortAudio::open()" << this->getInternalName(); 00057 PaError err; 00058 00059 if (m_audioOutputs.empty() && m_audioInputs.empty()) { 00060 m_lastError = QString::fromAscii("No inputs or outputs in SDPA::open() " 00061 "(THIS IS A BUG, this should be filtered by SM::setupDevices)"); 00062 return ERR; 00063 } 00064 00065 memset(&m_outputParams, 0, sizeof(m_outputParams)); 00066 memset(&m_inputParams, 0, sizeof(m_inputParams)); 00067 PaStreamParameters * pOutputParams = &m_outputParams; 00068 PaStreamParameters * pInputParams = &m_inputParams; 00069 00070 // Look at how many audio outputs we have, 00071 // so we can figure out how many output channels we need to open. 00072 if (m_audioOutputs.empty()) { 00073 m_outputParams.channelCount = 0; 00074 pOutputParams = NULL; 00075 } else { 00076 foreach (AudioOutput out, m_audioOutputs) { 00077 ChannelGroup channelGroup = out.getChannelGroup(); 00078 int highChannel = channelGroup.getChannelBase() 00079 + channelGroup.getChannelCount(); 00080 if (m_outputParams.channelCount <= highChannel) { 00081 m_outputParams.channelCount = highChannel; 00082 } 00083 } 00084 } 00085 00086 // Look at how many audio inputs we have, 00087 // so we can figure out how many input channels we need to open. 00088 if (m_audioInputs.empty()) { 00089 m_inputParams.channelCount = 0; 00090 pInputParams = NULL; 00091 } else { 00092 foreach (AudioInput in, m_audioInputs) { 00093 ChannelGroup channelGroup = in.getChannelGroup(); 00094 int highChannel = channelGroup.getChannelBase() 00095 + channelGroup.getChannelCount(); 00096 if (m_inputParams.channelCount <= highChannel) { 00097 m_inputParams.channelCount = highChannel; 00098 } 00099 } 00100 } 00101 00102 //Sample rate 00103 if (m_dSampleRate <= 0) { 00104 m_dSampleRate = 44100.0f; 00105 } 00106 00107 //Get latency in milleseconds 00108 qDebug() << "framesPerBuffer:" << m_framesPerBuffer; 00109 double latencyMSec = m_framesPerBuffer / m_dSampleRate * 1000; 00110 qDebug() << "Requested sample rate: " << m_dSampleRate << "Hz, latency:" << latencyMSec << "ms"; 00111 00112 qDebug() << "Output channels:" << m_outputParams.channelCount << "| Input channels:" 00113 << m_inputParams.channelCount; 00114 00115 /* 00116 //Calculate the latency in samples 00117 //Max channels opened for input or output 00118 int iMaxChannels = math_max(m_outputParams.channelCount, m_inputParams.channelCount); 00119 00120 int iLatencySamples = (int)((float)(m_dSampleRate*iMaxChannels)/1000.f*(float)iLatencyMSec); 00121 00122 //Round to the nearest multiple of 4. 00123 if (iLatencySamples % 4 != 0) { 00124 iLatencySamples -= (iLatencySamples % 4); 00125 iLatencySamples += 4; 00126 } 00127 00128 qDebug() << "iLatencySamples:" << iLatencySamples; 00129 00130 int iNumberOfBuffers = 2; 00131 //Apply simple rule to determine number of buffers 00132 if (iLatencySamples / MIXXXPA_MAX_FRAME_SIZE < 2) 00133 iNumberOfBuffers = 2; 00134 else 00135 iNumberOfBuffers = iLatencySamples / MIXXXPA_MAX_FRAME_SIZE; 00136 00137 //Frame size... 00138 unsigned int iFramesPerBuffer = iLatencySamples/m_iNumberOfBuffers; 00139 */ 00140 00141 //PortAudio's JACK backend also only properly supports paFramesPerBufferUnspecified in non-blocking mode 00142 //because the latency comes from the JACK daemon. (PA should give an error or something though, but it doesn't.) 00143 if (m_hostAPI == MIXXX_PORTAUDIO_JACK_STRING) { 00144 m_framesPerBuffer = paFramesPerBufferUnspecified; 00145 } 00146 00147 //Fill out the rest of the info. 00148 m_outputParams.device = m_devId; 00149 m_outputParams.sampleFormat = paFloat32; 00150 m_outputParams.suggestedLatency = latencyMSec / 1000.0; 00151 m_outputParams.hostApiSpecificStreamInfo = NULL; 00152 00153 m_inputParams.device = m_devId; 00154 m_inputParams.sampleFormat = paInt16; //This is how our vinyl control stuff like samples. 00155 m_inputParams.suggestedLatency = latencyMSec / 1000.0; 00156 m_inputParams.hostApiSpecificStreamInfo = NULL; 00157 00158 qDebug() << "Opening stream with id" << m_devId; 00159 00160 //Create the callback function pointer. 00161 PaStreamCallback *callback = paV19Callback; 00162 00163 // Try open device using iChannelMax 00164 err = Pa_OpenStream(&m_pStream, 00165 pInputParams, // Input parameters 00166 pOutputParams, // Output parameters 00167 m_dSampleRate, // Sample rate 00168 m_framesPerBuffer, // Frames per buffer 00169 paClipOff, // Stream flags 00170 callback, // Stream callback 00171 (void*) this); // pointer passed to the callback function 00172 00173 if (err != paNoError) 00174 { 00175 qWarning() << "Error opening stream:" << Pa_GetErrorText(err); 00176 m_lastError = QString::fromUtf8(Pa_GetErrorText(err)); 00177 m_pStream = 0; 00178 return ERR; 00179 } 00180 else 00181 { 00182 qDebug() << "Opened PortAudio stream successfully... starting"; 00183 } 00184 00185 #ifdef __LINUX__ 00186 //Attempt to dynamically load and resolve stuff in the PortAudio library 00187 //in order to enable RT priority with ALSA. 00188 QLibrary portaudio("libportaudio.so.2"); 00189 if (!portaudio.load()) 00190 qWarning() << "Failed to dynamically load PortAudio library"; 00191 else 00192 qDebug() << "Dynamically loaded PortAudio library"; 00193 00194 EnableAlsaRT enableRealtime = (EnableAlsaRT) portaudio.resolve("PaAlsa_EnableRealtimeScheduling"); 00195 if (enableRealtime) 00196 { 00197 enableRealtime(m_pStream, 1); 00198 } 00199 portaudio.unload(); 00200 #endif 00201 00202 // Start stream 00203 err = Pa_StartStream(m_pStream); 00204 if (err != paNoError) 00205 { 00206 qWarning() << "PortAudio: Start stream error:" << Pa_GetErrorText(err); 00207 m_lastError = QString::fromUtf8(Pa_GetErrorText(err)); 00208 m_pStream = 0; 00209 return ERR; 00210 } 00211 else 00212 qDebug() << "PortAudio: Started stream successfully"; 00213 00214 // Get the actual details of the stream & update Mixxx's data 00215 const PaStreamInfo* streamDetails = Pa_GetStreamInfo(m_pStream); 00216 m_dSampleRate = streamDetails->sampleRate; 00217 latencyMSec = streamDetails->outputLatency*1000; 00218 qDebug() << " Actual sample rate: " << m_dSampleRate << "Hz, latency:" << latencyMSec << "ms"; 00219 00220 //Update the samplerate and latency ControlObjects, which allow the waveform view to properly correct 00221 //for the latency. 00222 00223 ControlObjectThreadMain* pControlObjectSampleRate = 00224 new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Master]","samplerate"))); 00225 ControlObjectThreadMain* pControlObjectLatency = 00226 new ControlObjectThreadMain(ControlObject::getControl(ConfigKey("[Master]","latency"))); 00227 00228 pControlObjectLatency->slotSet(latencyMSec); 00229 pControlObjectSampleRate->slotSet(m_dSampleRate); 00230 00231 //qDebug() << "SampleRate" << pControlObjectSampleRate->get(); 00232 //qDebug() << "Latency" << pControlObjectLatency->get(); 00233 00234 delete pControlObjectLatency; 00235 delete pControlObjectSampleRate; 00236 return OK; 00237 } 00238 00239 int SoundDevicePortAudio::close() 00240 { 00241 //qDebug() << "SoundDevicePortAudio::close()" << this->getInternalName(); 00242 if (m_pStream) 00243 { 00244 //Make sure the stream is not stopped before we try stopping it. 00245 PaError err = Pa_IsStreamStopped(m_pStream); 00246 if (err == 1) //1 means the stream is stopped. 0 means active. 00247 { 00248 qDebug() << "PortAudio: Stream already stopped, but no error."; 00249 return 1; 00250 } 00251 if (err < 0) //Real PaErrors are always negative. 00252 { 00253 qWarning() << "PortAudio: Stream already stopped:" << Pa_GetErrorText(err) << getInternalName(); 00254 return 1; 00255 } 00256 00257 //Stop the stream. 00258 err = Pa_StopStream(m_pStream); 00259 //PaError err = Pa_AbortStream(m_pStream); //Trying Pa_AbortStream instead, because StopStream seems to wait 00260 //until all the buffers have been flushed, which can take a 00261 //few (annoying) seconds when you're doing soundcard input. 00262 //(it flushes the input buffer, and then some, or something) 00263 //BIG FAT WARNING: Pa_AbortStream() will kill threads while they're 00264 //waiting on a mutex, which will leave the mutex in an screwy 00265 //state. Don't use it! 00266 00267 if( err != paNoError ) 00268 { 00269 qWarning() << "PortAudio: Stop stream error:" << Pa_GetErrorText(err) << getInternalName(); 00270 return 1; 00271 } 00272 00273 // Close stream 00274 err = Pa_CloseStream(m_pStream); 00275 if( err != paNoError ) 00276 { 00277 qWarning() << "PortAudio: Close stream error:" << Pa_GetErrorText(err) << getInternalName(); 00278 return 1; 00279 } 00280 } 00281 00282 m_pStream = 0; 00283 00284 return 0; 00285 } 00286 00287 QString SoundDevicePortAudio::getError() const { 00288 return m_lastError; 00289 } 00290 00296 int SoundDevicePortAudio::callbackProcess(unsigned long framesPerBuffer, float *output, short *in) 00297 { 00298 //qDebug() << "SoundDevicePortAudio::callbackProcess:" << getInternalName(); 00299 00300 static ControlObject* pControlObjectVinylControlGain = 00301 ControlObject::getControl(ConfigKey("[VinylControl]", "gain")); 00302 static const float SHRT_CONVERSION_FACTOR = 1.0f/SHRT_MAX; 00303 int iFrameSize = m_outputParams.channelCount; 00304 int iVCGain = 1; 00305 00306 // Turn on TimeCritical priority for the callback thread. If we are running 00307 // in Linux userland, for example, this will have no effect. 00308 if (!m_bSetThreadPriority) { 00309 QThread::currentThread()->setPriority(QThread::TimeCriticalPriority); 00310 m_bSetThreadPriority = true; 00311 } 00312 00313 //Send audio from the soundcard's input off to the SoundManager... 00314 if (in && framesPerBuffer > 0) 00315 { 00316 //Note: Input is processed first so that any ControlObject changes made in response to input 00317 // is processed as soon as possible (that is, when m_pSoundManager->requestBuffer() is 00318 // called below.) 00319 00320 //Apply software preamp 00321 //Super big warning: Need to use channel_count here instead of iFrameSize because iFrameSize is 00322 //only for output buffers... 00323 // TODO(bkgood) move this to vcproxy or something, once we have other 00324 // inputs we don't want every input getting the vc gain 00325 iVCGain = pControlObjectVinylControlGain->get(); 00326 for (unsigned int i = 0; i < framesPerBuffer * m_inputParams.channelCount; ++i) 00327 in[i] *= iVCGain; 00328 00329 //qDebug() << in[0]; 00330 00331 // TODO(bkgood) deinterlace here and send a hashmap of buffers to 00332 // soundmanager so we have all our deinterlacing in one place and 00333 // soundmanager gets simplified to boot 00334 00335 m_pSoundManager->pushBuffer(m_audioInputs, in, framesPerBuffer, 00336 m_inputParams.channelCount); 00337 } 00338 00339 if (output && framesPerBuffer > 0) 00340 { 00341 assert(iFrameSize > 0); 00342 QHash<AudioOutput, const CSAMPLE*> outputAudio 00343 = m_pSoundManager->requestBuffer(m_audioOutputs, framesPerBuffer, 00344 this, Pa_GetStreamTime(m_pStream)); 00345 00346 // Reset sample for each open channel 00347 memset(output, 0, framesPerBuffer * iFrameSize * sizeof(*output)); 00348 00349 // Interlace Audio data onto portaudio buffer. We iterate through the 00350 // source list to find out what goes in the buffer data is interlaced in 00351 // the order of the list 00352 00353 for (QList<AudioOutput>::const_iterator i = m_audioOutputs.begin(), 00354 e = m_audioOutputs.end(); i != e; ++i) { 00355 const AudioOutput &out = *i; 00356 const CSAMPLE* input = outputAudio[out]; 00357 const ChannelGroup outChans = out.getChannelGroup(); 00358 const int iChannelCount = outChans.getChannelCount(); 00359 const int iChannelBase = outChans.getChannelBase(); 00360 00361 for (unsigned int iFrameNo=0; iFrameNo < framesPerBuffer; ++iFrameNo) { 00362 // this will make sure a sample from each channel is copied 00363 for (int iChannel = 0; iChannel < iChannelCount; ++iChannel) { 00364 // iFrameBase is the "base sample" in a frame (ie. the first 00365 // sample in a frame) 00366 unsigned int iFrameBase = iFrameNo * iFrameSize; 00367 unsigned int iLocalFrameBase = iFrameNo * iChannelCount; 00368 00369 // note that if QHash gets request for a value with a key it 00370 // doesn't know, it will return a default value (NULL is the 00371 // likely choice here), but the old system would've done 00372 // something similar (it would have gone over the bounds of 00373 // the array) 00374 00375 output[iFrameBase + iChannelBase + iChannel] += 00376 input[iLocalFrameBase + iChannel] * SHRT_CONVERSION_FACTOR; 00377 00378 //Input audio pass-through (useful for debugging) 00379 //if (in) 00380 // output[iFrameBase + src.channelBase + iChannel] += 00381 // in[iFrameBase + src.channelBase + iChannel] * SHRT_CONVERSION_FACTOR; 00382 } 00383 } 00384 } 00385 } 00386 00387 return paContinue; 00388 } 00389 00390 /* -------- ------------------------------------------------------ 00391 Purpose: Wrapper function to call processing loop function, 00392 implemented as a method in a class. Used in PortAudio, 00393 which knows nothing about C++. 00394 Input: . 00395 Output: - 00396 -------- ------------------------------------------------------ */ 00397 int paV19Callback(const void *inputBuffer, void *outputBuffer, 00398 unsigned long framesPerBuffer, 00399 const PaStreamCallbackTimeInfo *timeInfo, 00400 PaStreamCallbackFlags statusFlags, 00401 void *soundDevice) 00402 { 00403 /* 00404 //Variables that are used in the human-readable form of function call from hell (below). 00405 static PlayerPortAudio* _player; 00406 static int devIndex; 00407 _player = ((PAPlayerCallbackStuff*)_callbackStuff)->player; 00408 devIndex = ((PAPlayerCallbackStuff*)_callbackStuff)->devIndex; 00409 */ 00410 // these two are unused for now, suppressing compiler warnings -bkgood 00411 Q_UNUSED(timeInfo); 00412 Q_UNUSED(statusFlags); 00413 00414 //Human-readable form of the function call from hell: 00415 //return _player->callbackProcess(framesPerBuffer, (float *)outputBuffer, devIndex); 00416 00417 return ((SoundDevicePortAudio*) soundDevice)->callbackProcess(framesPerBuffer, 00418 (float*) outputBuffer, (short*) inputBuffer); 00419 // return ((SoundDevicePortAudio*)_callbackStuff)->callbackProcess(framesPerBuffer, (float*) outputBuffer, (short*) inputBuffer); 00420 } 00421