+ // Setup the secondary DS buffer description.\r
+ DSCBUFFERDESC bufferDescription;\r
+ ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) );\r
+ bufferDescription.dwSize = sizeof( DSCBUFFERDESC );\r
+ bufferDescription.dwFlags = 0;\r
+ bufferDescription.dwReserved = 0;\r
+ bufferDescription.dwBufferBytes = dsBufferSize;\r
+ bufferDescription.lpwfxFormat = &waveFormat;\r
+\r
+ // Create the capture buffer.\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer;\r
+ result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Get the buffer size ... might be different from what we specified.\r
+ DSCBCAPS dscbcaps;\r
+ dscbcaps.dwSize = sizeof( DSCBCAPS );\r
+ result = buffer->GetCaps( &dscbcaps );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ dsBufferSize = dscbcaps.dwBufferBytes;\r
+\r
+ // NOTE: We could have a problem here if this is a duplex stream\r
+ // and the play and capture hardware buffer sizes are different\r
+ // (I'm actually not sure if that is a problem or not).\r
+ // Currently, we are not verifying that.\r
+\r
+ // Lock the capture buffer\r
+ LPVOID audioPtr;\r
+ DWORD dataLen;\r
+ result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ // Zero the buffer\r
+ ZeroMemory( audioPtr, dataLen );\r
+\r
+ // Unlock the buffer\r
+ result = buffer->Unlock( audioPtr, dataLen, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ input->Release();\r
+ buffer->Release();\r
+ errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!";\r
+ errorText_ = errorStream_.str();\r
+ return FAILURE;\r
+ }\r
+\r
+ ohandle = (void *) input;\r
+ bhandle = (void *) buffer;\r
+ }\r
+\r
+ // Set various stream parameters\r
+ DsHandle *handle = 0;\r
+ stream_.nDeviceChannels[mode] = channels + firstChannel;\r
+ stream_.nUserChannels[mode] = channels;\r
+ stream_.bufferSize = *bufferSize;\r
+ stream_.channelOffset[mode] = firstChannel;\r
+ stream_.deviceInterleaved[mode] = true;\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false;\r
+ else stream_.userInterleaved = true;\r
+\r
+ // Set flag for buffer conversion\r
+ stream_.doConvertBuffer[mode] = false;\r
+ if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode])\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if (stream_.userFormat != stream_.deviceFormat[mode])\r
+ stream_.doConvertBuffer[mode] = true;\r
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&\r
+ stream_.nUserChannels[mode] > 1 )\r
+ stream_.doConvertBuffer[mode] = true;\r
+\r
+ // Allocate necessary internal buffers\r
+ long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( mode == INPUT ) {\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= (long) bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Allocate our DsHandle structures for the stream.\r
+ if ( stream_.apiHandle == 0 ) {\r
+ try {\r
+ handle = new DsHandle;\r
+ }\r
+ catch ( std::bad_alloc& ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory.";\r
+ goto error;\r
+ }\r
+\r
+ // Create a manual-reset event.\r
+ handle->condition = CreateEvent( NULL, // no security\r
+ TRUE, // manual-reset\r
+ FALSE, // non-signaled initially\r
+ NULL ); // unnamed\r
+ stream_.apiHandle = (void *) handle;\r
+ }\r
+ else\r
+ handle = (DsHandle *) stream_.apiHandle;\r
+ handle->id[mode] = ohandle;\r
+ handle->buffer[mode] = bhandle;\r
+ handle->dsBufferSize[mode] = dsBufferSize;\r
+ handle->dsPointerLeadTime[mode] = dsPointerLeadTime;\r
+\r
+ stream_.device[mode] = device;\r
+ stream_.state = STREAM_STOPPED;\r
+ if ( stream_.mode == OUTPUT && mode == INPUT )\r
+ // We had already set up an output stream.\r
+ stream_.mode = DUPLEX;\r
+ else\r
+ stream_.mode = mode;\r
+ stream_.nBuffers = nBuffers;\r
+ stream_.sampleRate = sampleRate;\r
+\r
+ // Setup the buffer conversion information structure.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );\r
+\r
+ // Setup the callback thread.\r
+ if ( stream_.callbackInfo.isRunning == false ) {\r
+ unsigned threadId;\r
+ stream_.callbackInfo.isRunning = true;\r
+ stream_.callbackInfo.object = (void *) this;\r
+ stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler,\r
+ &stream_.callbackInfo, 0, &threadId );\r
+ if ( stream_.callbackInfo.thread == 0 ) {\r
+ errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!";\r
+ goto error;\r
+ }\r
+\r
+ // Boost DS thread priority\r
+ SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST );\r
+ }\r
+ return SUCCESS;\r
+\r
+ error:\r
+ if ( handle ) {\r
+ if ( handle->buffer[0] ) { // the object pointer can be NULL and valid\r
+ LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0];\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ if ( buffer ) buffer->Release();\r
+ object->Release();\r
+ }\r
+ if ( handle->buffer[1] ) {\r
+ LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1];\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ if ( buffer ) buffer->Release();\r
+ object->Release();\r
+ }\r
+ CloseHandle( handle->condition );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.state = STREAM_CLOSED;\r
+ return FAILURE;\r
+}\r
+\r
+void RtApiDs :: closeStream()\r
+{\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiDs::closeStream(): no open stream to close!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ // Stop the callback thread.\r
+ stream_.callbackInfo.isRunning = false;\r
+ WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE );\r
+ CloseHandle( (HANDLE) stream_.callbackInfo.thread );\r
+\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+ if ( handle ) {\r
+ if ( handle->buffer[0] ) { // the object pointer can be NULL and valid\r
+ LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0];\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ if ( buffer ) {\r
+ buffer->Stop();\r
+ buffer->Release();\r
+ }\r
+ object->Release();\r
+ }\r
+ if ( handle->buffer[1] ) {\r
+ LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1];\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ if ( buffer ) {\r
+ buffer->Stop();\r
+ buffer->Release();\r
+ }\r
+ object->Release();\r
+ }\r
+ CloseHandle( handle->condition );\r
+ delete handle;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ stream_.mode = UNINITIALIZED;\r
+ stream_.state = STREAM_CLOSED;\r
+}\r
+\r
+void RtApiDs :: startStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiDs::startStream(): the stream is already running!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+\r
+ // Increase scheduler frequency on lesser windows (a side-effect of\r
+ // increasing timer accuracy). On greater windows (Win2K or later),\r
+ // this is already in effect.\r
+ timeBeginPeriod( 1 ); \r
+\r
+ buffersRolling = false;\r
+ duplexPrerollBytes = 0;\r
+\r
+ if ( stream_.mode == DUPLEX ) {\r
+ // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize.\r
+ duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] );\r
+ }\r
+\r
+ HRESULT result = 0;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ result = buffer->Play( 0, 0, DSBPLAY_LOOPING );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ result = buffer->Start( DSCBSTART_LOOPING );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+ }\r
+\r
+ handle->drainCounter = 0;\r
+ handle->internalDrain = false;\r
+ ResetEvent( handle->condition );\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ unlock:\r
+ if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiDs :: stopStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiDs::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ HRESULT result = 0;\r
+ LPVOID audioPtr;\r
+ DWORD dataLen;\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ if ( handle->drainCounter == 0 ) {\r
+ handle->drainCounter = 2;\r
+ WaitForSingleObject( handle->condition, INFINITE ); // block until signaled\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ // Stop the buffer and clear memory\r
+ LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ result = buffer->Stop();\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Lock the buffer and clear it so that if we start to play again,\r
+ // we won't have old data playing.\r
+ result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Zero the DS buffer\r
+ ZeroMemory( audioPtr, dataLen );\r
+\r
+ // Unlock the DS buffer\r
+ result = buffer->Unlock( audioPtr, dataLen, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // If we start playing again, we must begin at beginning of buffer.\r
+ handle->bufferPointer[0] = 0;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+ LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ audioPtr = NULL;\r
+ dataLen = 0;\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+\r
+ if ( stream_.mode != DUPLEX )\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ result = buffer->Stop();\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Lock the buffer and clear it so that if we start to play again,\r
+ // we won't have old data playing.\r
+ result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // Zero the DS buffer\r
+ ZeroMemory( audioPtr, dataLen );\r
+\r
+ // Unlock the DS buffer\r
+ result = buffer->Unlock( audioPtr, dataLen, NULL, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!";\r
+ errorText_ = errorStream_.str();\r
+ goto unlock;\r
+ }\r
+\r
+ // If we start recording again, we must begin at beginning of buffer.\r
+ handle->bufferPointer[1] = 0;\r
+ }\r
+\r
+ unlock:\r
+ timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows.\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR );\r
+}\r
+\r
+void RtApiDs :: abortStream()\r
+{\r
+ verifyStream();\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiDs::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+ handle->drainCounter = 2;\r
+\r
+ stopStream();\r
+}\r
+\r
+void RtApiDs :: callbackEvent()\r
+{\r
+ if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) {\r
+ Sleep( 50 ); // sleep 50 milliseconds\r
+ return;\r
+ }\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!";\r
+ error( RtAudioError::WARNING );\r
+ return;\r
+ }\r
+\r
+ CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo;\r
+ DsHandle *handle = (DsHandle *) stream_.apiHandle;\r
+\r
+ // Check if we were draining the stream and signal is finished.\r
+ if ( handle->drainCounter > stream_.nBuffers + 2 ) {\r
+\r
+ stream_.state = STREAM_STOPPING;\r
+ if ( handle->internalDrain == false )\r
+ SetEvent( handle->condition );\r
+ else\r
+ stopStream();\r
+ return;\r
+ }\r
+\r
+ // Invoke user callback to get fresh output data UNLESS we are\r
+ // draining stream.\r
+ if ( handle->drainCounter == 0 ) {\r
+ RtAudioCallback callback = (RtAudioCallback) info->callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ if ( stream_.mode != INPUT && handle->xrun[0] == true ) {\r
+ status |= RTAUDIO_OUTPUT_UNDERFLOW;\r
+ handle->xrun[0] = false;\r
+ }\r
+ if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) {\r
+ status |= RTAUDIO_INPUT_OVERFLOW;\r
+ handle->xrun[1] = false;\r
+ }\r
+ int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1],\r
+ stream_.bufferSize, streamTime, status, info->userData );\r
+ if ( cbReturnValue == 2 ) {\r
+ stream_.state = STREAM_STOPPING;\r
+ handle->drainCounter = 2;\r
+ abortStream();\r
+ return;\r
+ }\r
+ else if ( cbReturnValue == 1 ) {\r
+ handle->drainCounter = 1;\r
+ handle->internalDrain = true;\r
+ }\r
+ }\r
+\r
+ HRESULT result;\r
+ DWORD currentWritePointer, safeWritePointer;\r
+ DWORD currentReadPointer, safeReadPointer;\r
+ UINT nextWritePointer;\r
+\r
+ LPVOID buffer1 = NULL;\r
+ LPVOID buffer2 = NULL;\r
+ DWORD bufferSize1 = 0;\r
+ DWORD bufferSize2 = 0;\r
+\r
+ char *buffer;\r
+ long bufferBytes;\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+\r
+ if ( buffersRolling == false ) {\r
+ if ( stream_.mode == DUPLEX ) {\r
+ //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] );\r
+\r
+ // It takes a while for the devices to get rolling. As a result,\r
+ // there's no guarantee that the capture and write device pointers\r
+ // will move in lockstep. Wait here for both devices to start\r
+ // rolling, and then set our buffer pointers accordingly.\r
+ // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600\r
+ // bytes later than the write buffer.\r
+\r
+ // Stub: a serious risk of having a pre-emptive scheduling round\r
+ // take place between the two GetCurrentPosition calls... but I'm\r
+ // really not sure how to solve the problem. Temporarily boost to\r
+ // Realtime priority, maybe; but I'm not sure what priority the\r
+ // DirectSound service threads run at. We *should* be roughly\r
+ // within a ms or so of correct.\r
+\r
+ LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+\r
+ DWORD startSafeWritePointer, startSafeReadPointer;\r
+\r
+ result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ while ( true ) {\r
+ result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break;\r
+ Sleep( 1 );\r
+ }\r
+\r
+ //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] );\r
+\r
+ handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0];\r
+ if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0];\r
+ handle->bufferPointer[1] = safeReadPointer;\r
+ }\r
+ else if ( stream_.mode == OUTPUT ) {\r
+\r
+ // Set the proper nextWritePosition after initial startup.\r
+ LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+ result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0];\r
+ if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0];\r
+ }\r
+\r
+ buffersRolling = true;\r
+ }\r
+\r
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ \r
+ LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
+\r
+ if ( handle->drainCounter > 1 ) { // write zeros to the output stream\r
+ bufferBytes = stream_.bufferSize * stream_.nUserChannels[0];\r
+ bufferBytes *= formatBytes( stream_.userFormat );\r
+ memset( stream_.userBuffer[0], 0, bufferBytes );\r
+ }\r
+\r
+ // Setup parameters and do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[0] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] );\r
+ bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0];\r
+ bufferBytes *= formatBytes( stream_.deviceFormat[0] );\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[0];\r
+ bufferBytes = stream_.bufferSize * stream_.nUserChannels[0];\r
+ bufferBytes *= formatBytes( stream_.userFormat );\r
+ }\r
+\r
+ // No byte swapping necessary in DirectSound implementation.\r
+\r
+ // Ahhh ... windoze. 16-bit data is signed but 8-bit data is\r
+ // unsigned. So, we need to convert our signed 8-bit data here to\r
+ // unsigned.\r
+ if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 )\r
+ for ( int i=0; i<bufferBytes; i++ ) buffer[i] = (unsigned char) ( buffer[i] + 128 );\r
+\r
+ DWORD dsBufferSize = handle->dsBufferSize[0];\r
+ nextWritePointer = handle->bufferPointer[0];\r
+\r
+ DWORD endWrite, leadPointer;\r
+ while ( true ) {\r
+ // Find out where the read and "safe write" pointers are.\r
+ result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ // We will copy our output buffer into the region between\r
+ // safeWritePointer and leadPointer. If leadPointer is not\r
+ // beyond the next endWrite position, wait until it is.\r
+ leadPointer = safeWritePointer + handle->dsPointerLeadTime[0];\r
+ //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl;\r
+ if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize;\r
+ if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset\r
+ endWrite = nextWritePointer + bufferBytes;\r
+\r
+ // Check whether the entire write region is behind the play pointer.\r
+ if ( leadPointer >= endWrite ) break;\r
+\r
+ // If we are here, then we must wait until the leadPointer advances\r
+ // beyond the end of our next write region. We use the\r
+ // Sleep() function to suspend operation until that happens.\r
+ double millis = ( endWrite - leadPointer ) * 1000.0;\r
+ millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate);\r
+ if ( millis < 1.0 ) millis = 1.0;\r
+ Sleep( (DWORD) millis );\r
+ }\r
+\r
+ if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize )\r
+ || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { \r
+ // We've strayed into the forbidden zone ... resync the read pointer.\r
+ handle->xrun[0] = true;\r
+ nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes;\r
+ if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize;\r
+ handle->bufferPointer[0] = nextWritePointer;\r
+ endWrite = nextWritePointer + bufferBytes;\r
+ }\r
+\r
+ // Lock free space in the buffer\r
+ result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1,\r
+ &bufferSize1, &buffer2, &bufferSize2, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ // Copy our buffer into the DS buffer\r
+ CopyMemory( buffer1, buffer, bufferSize1 );\r
+ if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 );\r
+\r
+ // Update our buffer offset and unlock sound buffer\r
+ dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize;\r
+ handle->bufferPointer[0] = nextWritePointer;\r
+ }\r
+\r
+ // Don't bother draining input\r
+ if ( handle->drainCounter ) {\r
+ handle->drainCounter++;\r
+ goto unlock;\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) {\r
+\r
+ // Setup parameters.\r
+ if ( stream_.doConvertBuffer[1] ) {\r
+ buffer = stream_.deviceBuffer;\r
+ bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1];\r
+ bufferBytes *= formatBytes( stream_.deviceFormat[1] );\r
+ }\r
+ else {\r
+ buffer = stream_.userBuffer[1];\r
+ bufferBytes = stream_.bufferSize * stream_.nUserChannels[1];\r
+ bufferBytes *= formatBytes( stream_.userFormat );\r
+ }\r
+\r
+ LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];\r
+ long nextReadPointer = handle->bufferPointer[1];\r
+ DWORD dsBufferSize = handle->dsBufferSize[1];\r
+\r
+ // Find out where the write and "safe read" pointers are.\r
+ result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset\r
+ DWORD endRead = nextReadPointer + bufferBytes;\r
+\r
+ // Handling depends on whether we are INPUT or DUPLEX. \r
+ // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode,\r
+ // then a wait here will drag the write pointers into the forbidden zone.\r
+ // \r
+ // In DUPLEX mode, rather than wait, we will back off the read pointer until \r
+ // it's in a safe position. This causes dropouts, but it seems to be the only \r
+ // practical way to sync up the read and write pointers reliably, given the \r
+ // the very complex relationship between phase and increment of the read and write \r
+ // pointers.\r
+ //\r
+ // In order to minimize audible dropouts in DUPLEX mode, we will\r
+ // provide a pre-roll period of 0.5 seconds in which we return\r
+ // zeros from the read buffer while the pointers sync up.\r
+\r
+ if ( stream_.mode == DUPLEX ) {\r
+ if ( safeReadPointer < endRead ) {\r
+ if ( duplexPrerollBytes <= 0 ) {\r
+ // Pre-roll time over. Be more agressive.\r
+ int adjustment = endRead-safeReadPointer;\r
+\r
+ handle->xrun[1] = true;\r
+ // Two cases:\r
+ // - large adjustments: we've probably run out of CPU cycles, so just resync exactly,\r
+ // and perform fine adjustments later.\r
+ // - small adjustments: back off by twice as much.\r
+ if ( adjustment >= 2*bufferBytes )\r
+ nextReadPointer = safeReadPointer-2*bufferBytes;\r
+ else\r
+ nextReadPointer = safeReadPointer-bufferBytes-adjustment;\r
+\r
+ if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize;\r
+\r
+ }\r
+ else {\r
+ // In pre=roll time. Just do it.\r
+ nextReadPointer = safeReadPointer - bufferBytes;\r
+ while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize;\r
+ }\r
+ endRead = nextReadPointer + bufferBytes;\r
+ }\r
+ }\r
+ else { // mode == INPUT\r
+ while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) {\r
+ // See comments for playback.\r
+ double millis = (endRead - safeReadPointer) * 1000.0;\r
+ millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate);\r
+ if ( millis < 1.0 ) millis = 1.0;\r
+ Sleep( (DWORD) millis );\r
+\r
+ // Wake up and find out where we are now.\r
+ result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ \r
+ if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset\r
+ }\r
+ }\r
+\r
+ // Lock free space in the buffer\r
+ result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1,\r
+ &bufferSize1, &buffer2, &bufferSize2, 0 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+\r
+ if ( duplexPrerollBytes <= 0 ) {\r
+ // Copy our buffer into the DS buffer\r
+ CopyMemory( buffer, buffer1, bufferSize1 );\r
+ if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 );\r
+ }\r
+ else {\r
+ memset( buffer, 0, bufferSize1 );\r
+ if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 );\r
+ duplexPrerollBytes -= bufferSize1 + bufferSize2;\r
+ }\r
+\r
+ // Update our buffer offset and unlock sound buffer\r
+ nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize;\r
+ dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 );\r
+ if ( FAILED( result ) ) {\r
+ errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtAudioError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ handle->bufferPointer[1] = nextReadPointer;\r
+\r
+ // No byte swapping necessary in DirectSound implementation.\r
+\r
+ // If necessary, convert 8-bit data from unsigned to signed.\r
+ if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 )\r
+ for ( int j=0; j<bufferBytes; j++ ) buffer[j] = (signed char) ( buffer[j] - 128 );\r
+\r
+ // Do buffer conversion if necessary.\r
+ if ( stream_.doConvertBuffer[1] )\r
+ convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] );\r
+ }\r
+\r
+ unlock:\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ RtApi::tickStreamTime();\r
+}\r
+\r
+// Definitions for utility functions and callbacks\r
+// specific to the DirectSound implementation.\r
+\r
+static unsigned __stdcall callbackHandler( void *ptr )\r
+{\r
+ CallbackInfo *info = (CallbackInfo *) ptr;\r
+ RtApiDs *object = (RtApiDs *) info->object;\r
+ bool* isRunning = &info->isRunning;\r
+\r
+ while ( *isRunning == true ) {\r
+ object->callbackEvent();\r
+ }\r
+\r
+ _endthreadex( 0 );\r
+ return 0;\r
+}\r
+\r
+static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid,\r
+ LPCTSTR description,\r
+ LPCTSTR /*module*/,\r
+ LPVOID lpContext )\r
+{\r
+ struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext;\r
+ std::vector<struct DsDevice>& dsDevices = *probeInfo.dsDevices;\r
+\r
+ HRESULT hr;\r
+ bool validDevice = false;\r
+ if ( probeInfo.isInput == true ) {\r
+ DSCCAPS caps;\r
+ LPDIRECTSOUNDCAPTURE object;\r
+\r
+ hr = DirectSoundCaptureCreate( lpguid, &object, NULL );\r
+ if ( hr != DS_OK ) return TRUE;\r
+\r
+ caps.dwSize = sizeof(caps);\r
+ hr = object->GetCaps( &caps );\r
+ if ( hr == DS_OK ) {\r
+ if ( caps.dwChannels > 0 && caps.dwFormats > 0 )\r
+ validDevice = true;\r
+ }\r
+ object->Release();\r
+ }\r
+ else {\r
+ DSCAPS caps;\r
+ LPDIRECTSOUND object;\r
+ hr = DirectSoundCreate( lpguid, &object, NULL );\r
+ if ( hr != DS_OK ) return TRUE;\r
+\r
+ caps.dwSize = sizeof(caps);\r
+ hr = object->GetCaps( &caps );\r
+ if ( hr == DS_OK ) {\r
+ if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO )\r
+ validDevice = true;\r
+ }\r
+ object->Release();\r
+ }\r
+\r
+ // If good device, then save its name and guid.\r
+ std::string name = convertCharPointerToStdString( description );\r
+ //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" )\r
+ if ( lpguid == NULL )\r
+ name = "Default Device";\r
+ if ( validDevice ) {\r
+ for ( unsigned int i=0; i<dsDevices.size(); i++ ) {\r
+ if ( dsDevices[i].name == name ) {\r
+ dsDevices[i].found = true;\r
+ if ( probeInfo.isInput ) {\r
+ dsDevices[i].id[1] = lpguid;\r
+ dsDevices[i].validId[1] = true;\r
+ }\r
+ else {\r
+ dsDevices[i].id[0] = lpguid;\r
+ dsDevices[i].validId[0] = true;\r
+ }\r
+ return TRUE;\r
+ }\r
+ }\r
+\r
+ DsDevice device;\r
+ device.name = name;\r
+ device.found = true;\r
+ if ( probeInfo.isInput ) {\r
+ device.id[1] = lpguid;\r
+ device.validId[1] = true;\r
+ }\r
+ else {\r
+ device.id[0] = lpguid;\r
+ device.validId[0] = true;\r
+ }\r
+ dsDevices.push_back( device );\r
+ }\r
+\r
+ return TRUE;\r
+}\r
+\r
+static const char* getErrorString( int code )\r
+{\r
+ switch ( code ) {\r
+\r
+ case DSERR_ALLOCATED:\r
+ return "Already allocated";\r
+\r
+ case DSERR_CONTROLUNAVAIL:\r
+ return "Control unavailable";\r
+\r
+ case DSERR_INVALIDPARAM:\r
+ return "Invalid parameter";\r
+\r
+ case DSERR_INVALIDCALL:\r
+ return "Invalid call";\r
+\r
+ case DSERR_GENERIC:\r
+ return "Generic error";\r
+\r
+ case DSERR_PRIOLEVELNEEDED:\r
+ return "Priority level needed";\r
+\r
+ case DSERR_OUTOFMEMORY:\r
+ return "Out of memory";\r
+\r
+ case DSERR_BADFORMAT:\r
+ return "The sample rate or the channel format is not supported";\r
+\r
+ case DSERR_UNSUPPORTED:\r
+ return "Not supported";\r
+\r
+ case DSERR_NODRIVER:\r
+ return "No driver";\r
+\r
+ case DSERR_ALREADYINITIALIZED:\r
+ return "Already initialized";\r
+\r
+ case DSERR_NOAGGREGATION:\r
+ return "No aggregation";\r
+\r
+ case DSERR_BUFFERLOST:\r
+ return "Buffer lost";\r
+\r
+ case DSERR_OTHERAPPHASPRIO:\r
+ return "Another application already has priority";\r
+\r
+ case DSERR_UNINITIALIZED:\r
+ return "Uninitialized";\r
+\r
+ default:\r
+ return "DirectSound unknown error";\r
+ }\r
+}\r
+//******************** End of __WINDOWS_DS__ *********************//\r
+#endif\r
+\r
+\r
+#if defined(__LINUX_ALSA__)\r
+\r
+#include <alsa/asoundlib.h>\r
+#include <unistd.h>\r
+\r
+ // A structure to hold various information related to the ALSA API\r
+ // implementation.\r
+struct AlsaHandle {\r
+ snd_pcm_t *handles[2];\r
+ bool synchronized;\r
+ bool xrun[2];\r
+ pthread_cond_t runnable_cv;\r
+ bool runnable;\r
+\r
+ AlsaHandle()\r
+ :synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; }\r
+};\r
+\r
+static void *alsaCallbackHandler( void * ptr );\r
+\r
+RtApiAlsa :: RtApiAlsa()\r
+{\r
+ // Nothing to do here.\r
+}\r
+\r
+RtApiAlsa :: ~RtApiAlsa()\r