Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/sounddeviceportaudio.cpp

Go to the documentation of this file.
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines