X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=RtAudio.cpp;h=64b15774c8642423fffb6492eac264469b930d60;hb=9e8043f492c5c2b9456f771b8e5b2b4fdf29ed41;hp=91e4f2c31b7b0942ca49eab0610ff7f0aa9d40c9;hpb=4e5d52a99bdf324c30ea8818b74696c45dac502d;p=rtaudio.git diff --git a/RtAudio.cpp b/RtAudio.cpp index 91e4f2c..64b1577 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -47,6 +47,7 @@ #include #include #include +#include // Static variable definitions. const unsigned int RtApi::MAX_SAMPLE_RATES = 14; @@ -465,7 +466,7 @@ double RtApi :: getStreamTime( void ) struct timeval then; struct timeval now; - if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 ) + if ( stream_.state != STREAM_RUNNING || (stream_.lastTickTimestamp.tv_sec == 0 && stream_.lastTickTimestamp.tv_usec == 0) ) return stream_.streamTime; gettimeofday( &now, NULL ); @@ -496,6 +497,14 @@ unsigned int RtApi :: getStreamSampleRate( void ) return stream_.sampleRate; } +void RtApi :: startStream( void ) +{ +#if defined( HAVE_GETTIMEOFDAY ) + stream_.lastTickTimestamp.tv_sec = 0; + stream_.lastTickTimestamp.tv_usec = 0; +#endif +} + // *************************************************** // // @@ -1535,12 +1544,17 @@ void RtApiCore :: closeStream( void ) void RtApiCore :: startStream( void ) { verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiCore::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { @@ -2493,12 +2507,17 @@ void RtApiJack :: closeStream( void ) void RtApiJack :: startStream( void ) { verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiJack::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { @@ -3372,12 +3391,17 @@ bool stopThreadCalled = false; void RtApiAsio :: startStream() { verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAsio::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { @@ -3755,7 +3779,7 @@ static const char* getAsioErrorString( ASIOError result ) #include #include #include -#include +#include #ifndef MF_E_TRANSFORM_NEED_MORE_INPUT #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) @@ -3829,8 +3853,9 @@ public: relOutIndex += bufferSize_; } - // "in" index can end on the "out" index but cannot begin at it - if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) { + // the "IN" index CAN BEGIN at the "OUT" index + // the "IN" index CANNOT END at the "OUT" index + if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) { return false; // not enough space between "in" index and "out" index } @@ -3890,8 +3915,9 @@ public: relInIndex += bufferSize_; } - // "out" index can begin at and end on the "in" index - if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) { + // the "OUT" index CANNOT BEGIN at the "IN" index + // the "OUT" index CAN END at the "IN" index + if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) { return false; // not enough space between "out" index and "in" index } @@ -4544,6 +4570,7 @@ void RtApiWasapi::closeStream( void ) void RtApiWasapi::startStream( void ) { verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiWasapi::startStream: The stream is already running."; @@ -4551,6 +4578,10 @@ void RtApiWasapi::startStream( void ) return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + // update stream state stream_.state = STREAM_RUNNING; @@ -4590,26 +4621,6 @@ void RtApiWasapi::stopStream( void ) // Wait for the last buffer to play before stopping. Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); - // stop capture client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - - // stop render client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; @@ -4640,26 +4651,6 @@ void RtApiWasapi::abortStream( void ) Sleep( 1 ); } - // stop capture client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - - // stop render client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; @@ -4804,7 +4795,8 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; if ( renderAudioClient ) { - return SUCCESS; + methodResult = SUCCESS; + goto Exit; } hr = renderDevices->Item( device, &devicePtr ); @@ -4973,14 +4965,15 @@ void RtApiWasapi::wasapiThread() unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; - errorText_.clear(); + std::string errorText; RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; - TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = + ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); FreeLibrary( AvrtDll ); } @@ -4989,7 +4982,7 @@ void RtApiWasapi::wasapiThread() if ( captureAudioClient ) { hr = captureAudioClient->GetMixFormat( &captureFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } @@ -5008,14 +5001,14 @@ void RtApiWasapi::wasapiThread() captureFormat, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), ( void** ) &captureClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; goto Exit; } @@ -5026,13 +5019,13 @@ void RtApiWasapi::wasapiThread() captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !captureEvent ) { errorType = RtAudioError::SYSTEM_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to create capture event."; + errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; goto Exit; } hr = captureAudioClient->SetEventHandle( captureEvent ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; goto Exit; } @@ -5040,12 +5033,26 @@ void RtApiWasapi::wasapiThread() } ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; + + // reset the capture stream + hr = captureAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; + goto Exit; + } + + // start the capture stream + hr = captureAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start capture stream."; + goto Exit; + } } unsigned int inBufferSize = 0; hr = captureAudioClient->GetBufferSize( &inBufferSize ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; goto Exit; } @@ -5055,27 +5062,13 @@ void RtApiWasapi::wasapiThread() // set captureBuffer size captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); - - // reset the capture stream - hr = captureAudioClient->Reset(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; - goto Exit; - } - - // start the capture stream - hr = captureAudioClient->Start(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to start capture stream."; - goto Exit; - } } // start render stream if applicable if ( renderAudioClient ) { hr = renderAudioClient->GetMixFormat( &renderFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } @@ -5094,14 +5087,14 @@ void RtApiWasapi::wasapiThread() renderFormat, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &renderClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; goto Exit; } @@ -5109,24 +5102,38 @@ void RtApiWasapi::wasapiThread() renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { errorType = RtAudioError::SYSTEM_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to create render event."; + errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } hr = renderAudioClient->SetEventHandle( renderEvent ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to set render event handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to set render event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; + + // reset the render stream + hr = renderAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to reset render stream."; + goto Exit; + } + + // start the render stream + hr = renderAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start render stream."; + goto Exit; + } } unsigned int outBufferSize = 0; hr = renderAudioClient->GetBufferSize( &outBufferSize ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; goto Exit; } @@ -5136,20 +5143,6 @@ void RtApiWasapi::wasapiThread() // set renderBuffer size renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); - - // reset the render stream - hr = renderAudioClient->Reset(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to reset render stream."; - goto Exit; - } - - // start the render stream - hr = renderAudioClient->Start(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to start render stream."; - goto Exit; - } } // malloc buffer memory @@ -5173,11 +5166,11 @@ void RtApiWasapi::wasapiThread() } convBuffSize *= 2; // allow overflow for *SrRatio remainders - convBuffer = ( char* ) malloc( convBuffSize ); - stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); + convBuffer = ( char* ) calloc( convBuffSize, 1 ); + stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; + errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } @@ -5261,18 +5254,21 @@ void RtApiWasapi::wasapiThread() captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, stream_.callbackInfo.userData ); + // tick stream time + RtApi::tickStreamTime(); + // Handle return value from callback if ( callbackResult == 1 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } @@ -5283,12 +5279,12 @@ void RtApiWasapi::wasapiThread() HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } @@ -5316,6 +5312,12 @@ void RtApiWasapi::wasapiThread() stream_.convertInfo[OUTPUT] ); } + else { + // no further conversion, simple copy userBuffer to deviceBuffer + memcpy( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.bufferSize * stream_.nUserChannels[OUTPUT] * formatBytes( stream_.userFormat ) ); + } // Convert callback buffer to stream sample rate renderResampler->Convert( convBuffer, @@ -5351,7 +5353,7 @@ void RtApiWasapi::wasapiThread() &bufferFrameCount, &captureFlags, NULL, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; goto Exit; } @@ -5364,7 +5366,7 @@ void RtApiWasapi::wasapiThread() // Release capture buffer hr = captureClient->ReleaseBuffer( bufferFrameCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5373,7 +5375,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5383,7 +5385,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5405,13 +5407,13 @@ void RtApiWasapi::wasapiThread() // Get render buffer from stream hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; goto Exit; } hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; goto Exit; } @@ -5420,7 +5422,7 @@ void RtApiWasapi::wasapiThread() if ( bufferFrameCount != 0 ) { hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; goto Exit; } @@ -5433,7 +5435,7 @@ void RtApiWasapi::wasapiThread() // Release render buffer hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5442,7 +5444,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5452,7 +5454,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5463,9 +5465,6 @@ void RtApiWasapi::wasapiThread() // unsetting the callbackPulled flag lets the stream know that // the audio device is ready for another callback output buffer. callbackPulled = false; - - // tick stream time - RtApi::tickStreamTime(); } } @@ -5481,11 +5480,14 @@ Exit: CoUninitialize(); - if ( !errorText_.empty() ) - error( errorType ); - // update stream state stream_.state = STREAM_STOPPED; + + if ( !errorText.empty() ) + { + errorText_ = errorText; + error( errorType ); + } } //******************** End of __WINDOWS_WASAPI__ *********************// @@ -6376,12 +6378,17 @@ void RtApiDs :: closeStream() void RtApiDs :: startStream() { verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiDs::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of @@ -6432,6 +6439,7 @@ void RtApiDs :: startStream() void RtApiDs :: stopStream() { verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); @@ -7149,7 +7157,7 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) unsigned nDevices = 0; int result, subdevice, card; char name[64]; - snd_ctl_t *handle; + snd_ctl_t *handle = 0; // Count cards and devices card = -1; @@ -7158,6 +7166,7 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) sprintf( name, "hw:%d", card ); result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { + handle = 0; errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); @@ -7177,7 +7186,8 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) nDevices++; } nextcard: - snd_ctl_close( handle ); + if ( handle ) + snd_ctl_close( handle ); snd_card_next( &card ); } @@ -7198,7 +7208,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) unsigned nDevices = 0; int result, subdevice, card; char name[64]; - snd_ctl_t *chandle; + snd_ctl_t *chandle = 0; // Count cards and devices card = -1; @@ -7208,6 +7218,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { + chandle = 0; errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); @@ -7230,7 +7241,8 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) nDevices++; } nextcard: - snd_ctl_close( chandle ); + if ( chandle ) + snd_ctl_close( chandle ); snd_card_next( &card ); } @@ -8066,6 +8078,7 @@ void RtApiAlsa :: startStream() // This method calls snd_pcm_prepare if the device isn't already in that state. verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); @@ -8074,6 +8087,10 @@ void RtApiAlsa :: startStream() MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + int result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; @@ -8419,6 +8436,7 @@ static void *alsaCallbackHandler( void *ptr ) #include #include +#include #include static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, @@ -8455,8 +8473,33 @@ unsigned int RtApiPulse::getDeviceCount( void ) return 1; } +void RtApiPulse::sinkInfoCallback(pa_context*, const pa_sink_info* info, int, void* arg) +{ + RtApiPulse* api = (RtApiPulse *) arg; + if (info) { + api->channels_ = info->sample_spec.channels; + } + pa_threaded_mainloop_signal(api->mainloop_, 0); +} + +void RtApiPulse::contextStateCallback(pa_context* c, void* arg) +{ + pa_threaded_mainloop* mainloop = (pa_threaded_mainloop*) arg; + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(mainloop, 0); + break; + default: + break; + } +} + RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) { + /* Set up some defaults in case we crash and burn */ RtAudio::DeviceInfo info; info.probed = true; info.name = "PulseAudio"; @@ -8472,6 +8515,72 @@ RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) info.preferredSampleRate = 48000; info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; + /* Get the number of output channels from pulseaudio. A simple task, you say? + "What is your mainloop?" */ + mainloop_ = pa_threaded_mainloop_new(); + if (!mainloop_) { + return info; + } + + pa_threaded_mainloop_start(mainloop_); + pa_threaded_mainloop_lock(mainloop_); + + /* "And what is your context?" */ + pa_context* context = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), "RtAudio"); + if (!context) { + pa_threaded_mainloop_unlock(mainloop_); + pa_threaded_mainloop_stop(mainloop_); + pa_threaded_mainloop_free(mainloop_); + mainloop_ = 0; + return info; + } + + pa_context_set_state_callback(context, contextStateCallback, mainloop_); + + pa_context_connect(context, 0, (pa_context_flags_t) 0, 0); + + /* "And what is your favourite colour?" */ + int connected = 0; + pa_context_state_t state = pa_context_get_state(context); + for (; !connected; state = pa_context_get_state(context)) { + switch (state) { + case PA_CONTEXT_READY: + connected = 1; + continue; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + /* Blue! No, I mean red! */ + pa_threaded_mainloop_unlock(mainloop_); + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_stop(mainloop_); + pa_threaded_mainloop_free(mainloop_); + mainloop_ = 0; + return info; + default: + pa_threaded_mainloop_wait(mainloop_); + break; + } + } + + pa_operation* op = pa_context_get_sink_info_by_index(context, 0, sinkInfoCallback, this); + + if (op) { + pa_operation_unref(op); + } + + pa_threaded_mainloop_wait(mainloop_); + pa_threaded_mainloop_unlock(mainloop_); + + pa_context_disconnect(context); + pa_context_unref(context); + + pa_threaded_mainloop_stop(mainloop_); + pa_threaded_mainloop_free(mainloop_); + mainloop_ = 0; + + info.outputChannels = channels_; + return info; } @@ -8624,12 +8733,21 @@ void RtApiPulse::callbackEvent( void ) MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); + if (pah->s_play) { + int e = 0; + pa_usec_t const lat = pa_simple_get_latency(pah->s_play, &e); + if (e == 0) { + stream_.latency[0] = lat * stream_.sampleRate / 1000000; + } + } + if ( doStopStream == 1 ) stopStream(); } void RtApiPulse::startStream( void ) { + RtApi::startStream(); PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { @@ -8645,6 +8763,10 @@ void RtApiPulse::startStream( void ) MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + stream_.state = STREAM_RUNNING; pah->runnable = true; @@ -8668,6 +8790,7 @@ void RtApiPulse::stopStream( void ) } stream_.state = STREAM_STOPPED; + pah->runnable = false; MUTEX_LOCK( &stream_.mutex ); if ( pah && pah->s_play ) { @@ -8702,6 +8825,7 @@ void RtApiPulse::abortStream( void ) } stream_.state = STREAM_STOPPED; + pah->runnable = false; MUTEX_LOCK( &stream_.mutex ); if ( pah && pah->s_play ) { @@ -8731,10 +8855,6 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, if ( device != 0 ) return false; if ( mode != INPUT && mode != OUTPUT ) return false; - if ( channels != 1 && channels != 2 ) { - errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; - return false; - } ss.channels = channels; if ( firstChannel != 0 ) return false; @@ -8854,7 +8974,38 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, } break; case OUTPUT: - pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + /* XXX: hard-coded for DCP-o-matic */ + pa_channel_map map; + pa_channel_map_init(&map); + /* XXX: need to check 7.1 */ + map.channels = channels; + + if (channels > 0) { + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + } + if (channels > 1) { + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + } + if (channels > 2) { + map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + } + if (channels > 3) { + map.map[3] = PA_CHANNEL_POSITION_LFE; + } + if (channels > 4) { + map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; + } + if (channels > 5) { + map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; + } + if (channels > 6) { + map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + } + if (channels > 7) { + map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + } + + pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, &map, NULL, &error ); if ( !pah->s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; @@ -9621,6 +9772,7 @@ void RtApiOss :: closeStream() void RtApiOss :: startStream() { verifyStream(); + RtApi::startStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiOss::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); @@ -9629,6 +9781,10 @@ void RtApiOss :: startStream() MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + stream_.state = STREAM_RUNNING; // No need to do anything else here ... OSS automatically starts