/** @file * @author Edouard DUPIN * @copyright 2011, Edouard DUPIN, all right reserved * @license APACHE v2.0 (see license file) * @fork from RTAudio */ // Windows DirectSound API #if defined(__WINDOWS_DS__) #include #include #undef __class__ #define __class__ "api::Ds" airtaudio::Api* airtaudio::api::Ds::Create() { return new airtaudio::api::Ds(); } // Modified by Robin Davies, October 2005 // - Improvements to DirectX pointer chasing. // - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. // - Auto-call CoInitialize for DSOUND and ASIO platforms. // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 #include #include #include #if defined(__MINGW32__) // missing from latest mingw winapi #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ #define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ #endif #define MINIMUM_DEVICE_BUFFER_SIZE 32768 #ifdef _MSC_VER // if Microsoft Visual C++ #pragma comment(lib, "winmm.lib") // then, auto-link winmm.lib. Otherwise, it has to be added manually. #endif static inline DWORD dsPointerBetween(DWORD _pointer, DWORD _laterPointer, DWORD _earlierPointer, DWORD _bufferSize) { if (_pointer > _bufferSize) { _pointer -= _bufferSize; } if (_laterPointer < _earlierPointer) { _laterPointer += _bufferSize; } if (_pointer < _earlierPointer) { _pointer += _bufferSize; } return _pointer >= _earlierPointer && _pointer < _laterPointer; } class DsDevice { public: LPGUID id[2]; bool validId[2]; bool found; std::string name; DsDevice() : found(false) { validId[0] = false; validId[1] = false; } }; namespace airtaudio { namespace api { class DsPrivate { public: std11::shared_ptr thread; bool threadRunning; uint32_t drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. void *id[2]; void *buffer[2]; bool xrun[2]; UINT bufferPointer[2]; DWORD dsBufferSize[2]; DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. HANDLE condition; std::vector dsDevices; DsPrivate() : threadRunning(false), drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } }; } } // Declarations for utility functions, callbacks, and structures // specific to the DirectSound implementation. static BOOL CALLBACK deviceQueryCallback(LPGUID _lpguid, LPCTSTR _description, LPCTSTR _module, LPVOID _lpContext); static const char* getErrorString(int32_t _code); struct DsProbeData { bool isInput; std::vector* dsDevices; }; airtaudio::api::Ds::Ds() : m_private(new airtaudio::api::DsPrivate()) { // Dsound will run both-threaded. If CoInitialize fails, then just // accept whatever the mainline chose for a threading model. m_coInitialized = false; HRESULT hr = CoInitialize(nullptr); if (!FAILED(hr)) { m_coInitialized = true; } } airtaudio::api::Ds::~Ds() { if (m_coInitialized) { CoUninitialize(); // balanced call. } if (m_state != airtaudio::state_closed) { closeStream(); } } // The DirectSound default output is always the first device. uint32_t airtaudio::api::Ds::getDefaultOutputDevice() { return 0; } // The DirectSound default input is always the first input device, // which is the first capture device enumerated. uint32_t airtaudio::api::Ds::getDefaultInputDevice() { return 0; } uint32_t airtaudio::api::Ds::getDeviceCount() { // Set query flag for previously found devices to false, so that we // can check for any devices that have disappeared. for (size_t iii=0; iiidsDevices.size(); ++iii) { m_private->dsDevices[iii].found = false; } // Query DirectSound devices. struct DsProbeData probeInfo; probeInfo.isInput = false; probeInfo.dsDevices = &m_private->dsDevices; HRESULT result = DirectSoundEnumerate((LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") enumerating output devices!"); return 0; } // Query DirectSoundCapture devices. probeInfo.isInput = true; result = DirectSoundCaptureEnumerate((LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") enumerating input devices!"); return 0; } // Clean out any devices that may have disappeared. std::vector< int32_t > indices; for (uint32_t i=0; idsDevices.size(); i++) { if (m_private->dsDevices[i].found == false) { indices.push_back(i); } } uint32_t nErased = 0; for (uint32_t i=0; idsDevices.erase(m_private->dsDevices.begin()-nErased++); } return m_private->dsDevices.size(); } airtaudio::DeviceInfo airtaudio::api::Ds::getDeviceInfo(uint32_t _device) { airtaudio::DeviceInfo info; info.probed = false; if (m_private->dsDevices.size() == 0) { // Force a query of all devices getDeviceCount(); if (m_private->dsDevices.size() == 0) { ATA_ERROR("no devices found!"); return info; } } if (_device >= m_private->dsDevices.size()) { ATA_ERROR("device ID is invalid!"); return info; } HRESULT result; if (m_private->dsDevices[ _device ].validId[0] == false) { goto probeInput; } LPDIRECTSOUND output; DSCAPS outCaps; result = DirectSoundCreate(m_private->dsDevices[ _device ].id[0], &output, nullptr); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") opening output device (" << m_private->dsDevices[ _device ].name << ")!"); goto probeInput; } outCaps.dwSize = sizeof(outCaps); result = output->GetCaps(&outCaps); if (FAILED(result)) { output->Release(); ATA_ERROR("error (" << getErrorString(result) << ") getting capabilities!"); goto probeInput; } // Get output channel information. info.outputChannels = (outCaps.dwFlags & DSCAPS_PRIMARYSTEREO) ? 2 : 1; // Get sample rate information. info.sampleRates.clear(); for (auto &it : airtaudio::genericSampleRate()) { if ( it >= outCaps.dwMinSecondarySampleRate && it <= outCaps.dwMaxSecondarySampleRate) { info.sampleRates.push_back(it); } } // Get format information. if (outCaps.dwFlags & DSCAPS_PRIMARY16BIT) { info.nativeFormats.push_back(audio::format_int16); } if (outCaps.dwFlags & DSCAPS_PRIMARY8BIT) { info.nativeFormats.push_back(audio::format_int8); } output->Release(); if (getDefaultOutputDevice() == _device) { info.isDefaultOutput = true; } if (m_private->dsDevices[ _device ].validId[1] == false) { info.name = m_private->dsDevices[ _device ].name; info.probed = true; return info; } probeInput: LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate(m_private->dsDevices[ _device ].id[1], &input, nullptr); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") opening input device (" << m_private->dsDevices[ _device ].name << ")!"); return info; } DSCCAPS inCaps; inCaps.dwSize = sizeof(inCaps); result = input->GetCaps(&inCaps); if (FAILED(result)) { input->Release(); ATA_ERROR("error (" << getErrorString(result) << ") getting object capabilities (" << m_private->dsDevices[ _device ].name << ")!"); return info; } // Get input channel information. info.inputChannels = inCaps.dwChannels; // Get sample rate and format information. std::vector rates; if (inCaps.dwChannels >= 2) { if ( (inCaps.dwFormats & WAVE_FORMAT_1S16) || (inCaps.dwFormats & WAVE_FORMAT_2S16) || (inCaps.dwFormats & WAVE_FORMAT_4S16) || (inCaps.dwFormats & WAVE_FORMAT_96S16) ) { info.nativeFormats.push_back(audio::format_int16); } if ( (inCaps.dwFormats & WAVE_FORMAT_1S08) || (inCaps.dwFormats & WAVE_FORMAT_2S08) || (inCaps.dwFormats & WAVE_FORMAT_4S08) || (inCaps.dwFormats & WAVE_FORMAT_96S08) ) { info.nativeFormats.push_back(audio::format_int8); } if ( (inCaps.dwFormats & WAVE_FORMAT_1S16) || (inCaps.dwFormats & WAVE_FORMAT_1S08) ){ rates.push_back(11025); } if ( (inCaps.dwFormats & WAVE_FORMAT_2S16) || (inCaps.dwFormats & WAVE_FORMAT_2S08) ){ rates.push_back(22050); } if ( (inCaps.dwFormats & WAVE_FORMAT_4S16) || (inCaps.dwFormats & WAVE_FORMAT_4S08) ){ rates.push_back(44100); } if ( (inCaps.dwFormats & WAVE_FORMAT_96S16) || (inCaps.dwFormats & WAVE_FORMAT_96S08) ){ rates.push_back(96000); } } else if (inCaps.dwChannels == 1) { if ( (inCaps.dwFormats & WAVE_FORMAT_1M16) || (inCaps.dwFormats & WAVE_FORMAT_2M16) || (inCaps.dwFormats & WAVE_FORMAT_4M16) || (inCaps.dwFormats & WAVE_FORMAT_96M16) ) { info.nativeFormats.push_back(audio::format_int16); } if ( (inCaps.dwFormats & WAVE_FORMAT_1M08) || (inCaps.dwFormats & WAVE_FORMAT_2M08) || (inCaps.dwFormats & WAVE_FORMAT_4M08) || (inCaps.dwFormats & WAVE_FORMAT_96M08) ) { info.nativeFormats.push_back(audio::format_int8); } if ( (inCaps.dwFormats & WAVE_FORMAT_1M16) || (inCaps.dwFormats & WAVE_FORMAT_1M08) ){ rates.push_back(11025); } if ( (inCaps.dwFormats & WAVE_FORMAT_2M16) || (inCaps.dwFormats & WAVE_FORMAT_2M08) ){ rates.push_back(22050); } if ( (inCaps.dwFormats & WAVE_FORMAT_4M16) || (inCaps.dwFormats & WAVE_FORMAT_4M08) ){ rates.push_back(44100); } if ( (inCaps.dwFormats & WAVE_FORMAT_96M16) || (inCaps.dwFormats & WAVE_FORMAT_96M08) ){ rates.push_back(96000); } } else { // technically, this would be an error info.inputChannels = 0; } input->Release(); if (info.inputChannels == 0) { return info; } // Copy the supported rates to the info structure but avoid duplication. bool found; for (uint32_t i=0; i 0 && info.inputChannels > 0) { info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; } if (_device == 0) { info.isDefaultInput = true; } // Copy name and return. info.name = m_private->dsDevices[ _device ].name; info.probed = true; return info; } bool airtaudio::api::Ds::probeDeviceOpen(uint32_t _device, enum airtaudio::mode _mode, uint32_t _channels, uint32_t _firstChannel, uint32_t _sampleRate, enum audio::format _format, uint32_t *_bufferSize, const airtaudio.::StreamOptions& _options) { if (_channels + _firstChannel > 2) { ATA_ERROR("DirectSound does not support more than 2 channels per device."); return false; } uint32_t nDevices = m_private->dsDevices.size(); if (nDevices == 0) { // This should not happen because a check is made before this function is called. ATA_ERROR("no devices found!"); return false; } if (_device >= nDevices) { // This should not happen because a check is made before this function is called. ATA_ERROR("device ID is invalid!"); return false; } if (_mode == airtaudio::mode_output) { if (m_private->dsDevices[ _device ].validId[0] == false) { ATA_ERROR("device (" << _device << ") does not support output!"); return false; } } else { // _mode == airtaudio::mode_input if (m_private->dsDevices[ _device ].validId[1] == false) { ATA_ERROR("device (" << _device << ") does not support input!"); return false; } } // According to a note in PortAudio, using GetDesktopWindow() // instead of GetForegroundWindow() is supposed to avoid problems // that occur when the application's window is not the foreground // window. Also, if the application window closes before the // DirectSound buffer, DirectSound can crash. In the past, I had // problems when using GetDesktopWindow() but it seems fine now // (January 2010). I'll leave it commented here. // HWND hWnd = GetForegroundWindow(); HWND hWnd = GetDesktopWindow(); // Check the numberOfBuffers parameter and limit the lowest value to // two. This is a judgement call and a value of two is probably too // low for capture, but it should work for playback. int32_t nBuffers = 0; /* nBuffers = _options.numberOfBuffers; */ if (_options.flags.m_minimizeLatency == true) { nBuffers = 2; } if (nBuffers < 2) { nBuffers = 3; } // Check the lower range of the user-specified buffer size and set // (arbitrarily) to a lower bound of 32. if (*_bufferSize < 32) { *_bufferSize = 32; } // Create the wave format structure. The data format setting will // be determined later. WAVEFORMATEX waveFormat; ZeroMemory(&waveFormat, sizeof(WAVEFORMATEX)); waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nChannels = _channels + _firstChannel; waveFormat.nSamplesPerSec = (uint64_t) _sampleRate; // Determine the device buffer size. By default, we'll use the value // defined above (32K), but we will grow it to make allowances for // very large software buffer sizes. DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; DWORD dsPointerLeadTime = 0; void *ohandle = 0, *bhandle = 0; HRESULT result; if (_mode == airtaudio::mode_output) { LPDIRECTSOUND output; result = DirectSoundCreate(m_private->dsDevices[ _device ].id[0], &output, nullptr); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") opening output device (" << m_private->dsDevices[ _device ].name << ")!"); return false; } DSCAPS outCaps; outCaps.dwSize = sizeof(outCaps); result = output->GetCaps(&outCaps); if (FAILED(result)) { output->Release(); ATA_ERROR("error (" << getErrorString(result) << ") getting capabilities (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Check channel information. if (_channels + _firstChannel == 2 && !(outCaps.dwFlags & DSCAPS_PRIMARYSTEREO)) { ATA_ERROR("the output device (" << m_private->dsDevices[ _device ].name << ") does not support stereo playback."); return false; } // Check format information. Use 16-bit format unless not // supported or user requests 8-bit. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && !( _format == audio::format_int8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT)) { waveFormat.wBitsPerSample = 16; m_deviceFormat[modeToIdTable(_mode)] = audio::format_int16; } else { waveFormat.wBitsPerSample = 8; m_deviceFormat[modeToIdTable(_mode)] = audio::format_int8; } m_userFormat = _format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*_bufferSize) * (waveFormat.wBitsPerSample / 8) * _channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while (dsPointerLeadTime * 2U > dsBufferSize) { dsBufferSize *= 2; } // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. // result = output->SetCooperativeLevel(hWnd, DSSCL_EXCLUSIVE); // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. result = output->SetCooperativeLevel(hWnd, DSSCL_PRIORITY); if (FAILED(result)) { output->Release(); ATA_ERROR("error (" << getErrorString(result) << ") setting cooperative level (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Even though we will write to the secondary buffer, we need to // access the primary buffer to set the correct output format // (since the default is 8-bit, 22 kHz!). Setup the DS primary // buffer description. DSBUFFERDESC bufferDescription; ZeroMemory(&bufferDescription, sizeof(DSBUFFERDESC)); bufferDescription.dwSize = sizeof(DSBUFFERDESC); bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; // Obtain the primary buffer LPDIRECTSOUNDBUFFER buffer; result = output->CreateSoundBuffer(&bufferDescription, &buffer, nullptr); if (FAILED(result)) { output->Release(); ATA_ERROR("error (" << getErrorString(result) << ") accessing primary buffer (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Set the primary DS buffer sound format. result = buffer->SetFormat(&waveFormat); if (FAILED(result)) { output->Release(); ATA_ERROR("error (" << getErrorString(result) << ") setting primary buffer format (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Setup the secondary DS buffer description. ZeroMemory(&bufferDescription, sizeof(DSBUFFERDESC)); bufferDescription.dwSize = sizeof(DSBUFFERDESC); bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCHARDWARE); // Force hardware mixing bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Try to create the secondary DS buffer. If that doesn't work, // try to use software mixing. Otherwise, there's a problem. result = output->CreateSoundBuffer(&bufferDescription, &buffer, nullptr); if (FAILED(result)) { bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE); // Force software mixing result = output->CreateSoundBuffer(&bufferDescription, &buffer, nullptr); if (FAILED(result)) { output->Release(); ATA_ERROR("error (" << getErrorString(result) << ") creating secondary buffer (" << m_private->dsDevices[ _device ].name << ")!"); return false; } } // Get the buffer size ... might be different from what we specified. DSBCAPS dsbcaps; dsbcaps.dwSize = sizeof(DSBCAPS); result = buffer->GetCaps(&dsbcaps); if (FAILED(result)) { output->Release(); buffer->Release(); ATA_ERROR("error (" << getErrorString(result) << ") getting buffer settings (" << m_private->dsDevices[ _device ].name << ")!"); return false; } dsBufferSize = dsbcaps.dwBufferBytes; // Lock the DS buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock(0, dsBufferSize, &audioPtr, &dataLen, nullptr, nullptr, 0); if (FAILED(result)) { output->Release(); buffer->Release(); ATA_ERROR("error (" << getErrorString(result) << ") locking buffer (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Zero the DS buffer ZeroMemory(audioPtr, dataLen); // Unlock the DS buffer result = buffer->Unlock(audioPtr, dataLen, nullptr, 0); if (FAILED(result)) { output->Release(); buffer->Release(); ATA_ERROR("error (" << getErrorString(result) << ") unlocking buffer (" << m_private->dsDevices[ _device ].name << ")!"); return false; } ohandle = (void *) output; bhandle = (void *) buffer; } if (_mode == airtaudio::mode_input) { LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate(m_private->dsDevices[ _device ].id[1], &input, nullptr); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") opening input device (" << m_private->dsDevices[ _device ].name << ")!"); return false; } DSCCAPS inCaps; inCaps.dwSize = sizeof(inCaps); result = input->GetCaps(&inCaps); if (FAILED(result)) { input->Release(); ATA_ERROR("error (" << getErrorString(result) << ") getting input capabilities (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Check channel information. if (inCaps.dwChannels < _channels + _firstChannel) { ATA_ERROR("the input device does not support requested input channels."); return false; } // Check format information. Use 16-bit format unless user // requests 8-bit. DWORD deviceFormats; if (_channels + _firstChannel == 2) { deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; if (_format == audio::format_int8 && inCaps.dwFormats & deviceFormats) { waveFormat.wBitsPerSample = 8; m_deviceFormat[modeToIdTable(_mode)] = audio::format_int8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; m_deviceFormat[modeToIdTable(_mode)] = audio::format_int16; } } else { // channel == 1 deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; if (_format == audio::format_int8 && inCaps.dwFormats & deviceFormats) { waveFormat.wBitsPerSample = 8; m_deviceFormat[modeToIdTable(_mode)] = audio::format_int8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; m_deviceFormat[modeToIdTable(_mode)] = audio::format_int16; } } m_userFormat = _format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*_bufferSize) * (waveFormat.wBitsPerSample / 8) * _channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while (dsPointerLeadTime * 2U > dsBufferSize) { dsBufferSize *= 2; } // Setup the secondary DS buffer description. DSCBUFFERDESC bufferDescription; ZeroMemory(&bufferDescription, sizeof(DSCBUFFERDESC)); bufferDescription.dwSize = sizeof(DSCBUFFERDESC); bufferDescription.dwFlags = 0; bufferDescription.dwReserved = 0; bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Create the capture buffer. LPDIRECTSOUNDCAPTUREBUFFER buffer; result = input->CreateCaptureBuffer(&bufferDescription, &buffer, nullptr); if (FAILED(result)) { input->Release(); ATA_ERROR("error (" << getErrorString(result) << ") creating input buffer (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Get the buffer size ... might be different from what we specified. DSCBCAPS dscbcaps; dscbcaps.dwSize = sizeof(DSCBCAPS); result = buffer->GetCaps(&dscbcaps); if (FAILED(result)) { input->Release(); buffer->Release(); ATA_ERROR("error (" << getErrorString(result) << ") getting buffer settings (" << m_private->dsDevices[ _device ].name << ")!"); return false; } dsBufferSize = dscbcaps.dwBufferBytes; // NOTE: We could have a problem here if this is a duplex stream // and the play and capture hardware buffer sizes are different // (I'm actually not sure if that is a problem or not). // Currently, we are not verifying that. // Lock the capture buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock(0, dsBufferSize, &audioPtr, &dataLen, nullptr, nullptr, 0); if (FAILED(result)) { input->Release(); buffer->Release(); ATA_ERROR("error (" << getErrorString(result) << ") locking input buffer (" << m_private->dsDevices[ _device ].name << ")!"); return false; } // Zero the buffer ZeroMemory(audioPtr, dataLen); // Unlock the buffer result = buffer->Unlock(audioPtr, dataLen, nullptr, 0); if (FAILED(result)) { input->Release(); buffer->Release(); ATA_ERROR("error (" << getErrorString(result) << ") unlocking input buffer (" << m_private->dsDevices[ _device ].name << ")!"); return false; } ohandle = (void *) input; bhandle = (void *) buffer; } // Set various stream parameters m_nDeviceChannels[modeToIdTable(_mode)] = _channels + _firstChannel; m_nUserChannels[modeToIdTable(_mode)] = _channels; m_bufferSize = *_bufferSize; m_channelOffset[modeToIdTable(_mode)] = _firstChannel; m_deviceInterleaved[modeToIdTable(_mode)] = true; // Set flag for buffer conversion m_doConvertBuffer[modeToIdTable(_mode)] = false; if (m_nUserChannels[modeToIdTable(_mode)] != m_nDeviceChannels[modeToIdTable(_mode)]) { m_doConvertBuffer[modeToIdTable(_mode)] = true; } if (m_userFormat != m_deviceFormat[modeToIdTable(_mode)]) { m_doConvertBuffer[modeToIdTable(_mode)] = true; } if ( m_deviceInterleaved[modeToIdTable(_mode)] == false && m_nUserChannels[modeToIdTable(_mode)] > 1) { m_doConvertBuffer[modeToIdTable(_mode)] = true; } // Allocate necessary internal buffers long bufferBytes = m_nUserChannels[modeToIdTable(_mode)] * *_bufferSize * audio::getFormatBytes(m_userFormat); m_userBuffer[modeToIdTable(_mode)].resize(bufferBytes, 0); if (m_userBuffer[modeToIdTable(_mode)].size() == 0) { ATA_ERROR("error allocating user buffer memory."); goto error; } if (m_doConvertBuffer[modeToIdTable(_mode)]) { bool makeBuffer = true; bufferBytes = m_nDeviceChannels[modeToIdTable(_mode)] * audio::getFormatBytes(m_deviceFormat[modeToIdTable(_mode)]); if (_mode == airtaudio::mode_input) { if (m_mode == airtaudio::mode_output && m_deviceBuffer) { uint64_t bytesOut = m_nDeviceChannels[0] * audio::getFormatBytes(m_deviceFormat[0]); if (bufferBytes <= (long) bytesOut) { makeBuffer = false; } } } if (makeBuffer) { bufferBytes *= *_bufferSize; if (m_deviceBuffer) { free(m_deviceBuffer); } m_deviceBuffer = (char *) calloc(bufferBytes, 1); if (m_deviceBuffer == nullptr) { ATA_ERROR("error allocating device buffer memory."); goto error; } } } // Create a manual-reset event. m_private->condition = CreateEvent(nullptr, // no security TRUE, // manual-reset FALSE, // non-signaled initially nullptr); // unnamed m_private->id[modeToIdTable(_mode)] = ohandle; m_private->buffer[modeToIdTable(_mode)] = bhandle; m_private->dsBufferSize[modeToIdTable(_mode)] = dsBufferSize; m_private->dsPointerLeadTime[modeToIdTable(_mode)] = dsPointerLeadTime; m_device[modeToIdTable(_mode)] = _device; m_state = airtaudio::state_stopped; if ( m_mode == airtaudio::mode_output && _mode == airtaudio::mode_input) { // We had already set up an output stream. m_mode = airtaudio::mode_duplex; } else { m_mode = _mode; } m_nBuffers = nBuffers; m_sampleRate = _sampleRate; // Setup the buffer conversion information structure. if (m_doConvertBuffer[modeToIdTable(_mode)]) { setConvertInfo(_mode, _firstChannel); } // Setup the callback thread. if (m_private->threadRunning == false) { m_private->threadRunning = true; std11::shared_ptr tmpThread(new std11::thread(&airtaudio::api::Ds::dsCallbackEvent, this)); m_private->thread = std::move(tmpThread); if (m_private->thread == nullptr) { ATA_ERROR("error creating callback thread!"); goto error; } // Boost DS thread priority SetThreadPriority((HANDLE)m_private->thread, THREAD_PRIORITY_HIGHEST); } return true; error: if (m_private->buffer[0]) { // the object pointer can be nullptr and valid LPDIRECTSOUND object = (LPDIRECTSOUND) m_private->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0]; if (buffer) { buffer->Release(); } object->Release(); } if (m_private->buffer[1]) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) m_private->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1]; if (buffer != nullptr) { buffer->Release(); } } CloseHandle(m_private->condition); for (size_t iii=0; iii<2; ++iii) { m_userBuffer[iii].clear(); } if (m_deviceBuffer) { free(m_deviceBuffer); m_deviceBuffer = 0; } m_state = airtaudio::state_closed; return false; } enum airtaudio::error airtaudio::api::Ds::closeStream() { if (m_state == airtaudio::state_closed) { ATA_ERROR("no open stream to close!"); return airtaudio::error_warning; } // Stop the callback thread. m_private->threadRunning = false; WaitForSingleObject((HANDLE) m_private->thread, INFINITE); CloseHandle((HANDLE) m_private->thread); if (m_private->buffer[0]) { // the object pointer can be nullptr and valid LPDIRECTSOUND object = (LPDIRECTSOUND) m_private->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0]; if (buffer) { buffer->Stop(); buffer->Release(); } object->Release(); } if (m_private->buffer[1]) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) m_private->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1]; if (buffer) { buffer->Stop(); buffer->Release(); } object->Release(); } CloseHandle(m_private->condition); for (size_t iii=0; iii<2; ++iii) { m_userBuffer[iii].clear(); } if (m_deviceBuffer) { free(m_deviceBuffer); m_deviceBuffer = 0; } m_mode = airtaudio::mode_unknow; m_state = airtaudio::state_closed; } enum airtaudio::error airtaudio::api::Ds::startStream() { // TODO : Check return ... airtaudio::Api::startStream(); if (verifyStream() != airtaudio::error_none) { return airtaudio::error_fail; } if (m_state == airtaudio::state_running) { ATA_ERROR("the stream is already running!"); return airtaudio::error_warning; } // Increase scheduler frequency on lesser windows (a side-effect of // increasing timer accuracy). On greater windows (Win2K or later), // this is already in effect. timeBeginPeriod(1); m_buffersRolling = false; m_duplexPrerollBytes = 0; if (m_mode == airtaudio::mode_duplex) { // 0.5 seconds of silence in airtaudio::mode_duplex mode while the devices spin up and synchronize. m_duplexPrerollBytes = (int) (0.5 * m_sampleRate * audio::getFormatBytes(m_deviceFormat[1]) * m_nDeviceChannels[1]); } HRESULT result = 0; if ( m_mode == airtaudio::mode_output || m_mode == airtaudio::mode_duplex) { LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0]; result = buffer->Play(0, 0, DSBPLAY_LOOPING); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") starting output buffer!"); goto unlock; } } if ( m_mode == airtaudio::mode_input || m_mode == airtaudio::mode_duplex) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1]; result = buffer->Start(DSCBSTART_LOOPING); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") starting input buffer!"); goto unlock; } } m_private->drainCounter = 0; m_private->internalDrain = false; ResetEvent(m_private->condition); m_state = airtaudio::state_running; unlock: if (FAILED(result)) { return airtaudio::error_systemError; } return airtaudio::error_none; } enum airtaudio::error airtaudio::api::Ds::stopStream() { if (verifyStream() != airtaudio::error_none) { return airtaudio::error_fail; } if (m_state == airtaudio::state_stopped) { ATA_ERROR("the stream is already stopped!"); return airtaudio::error_warning; } HRESULT result = 0; LPVOID audioPtr; DWORD dataLen; if ( m_mode == airtaudio::mode_output || m_mode == airtaudio::mode_duplex) { if (m_private->drainCounter == 0) { m_private->drainCounter = 2; WaitForSingleObject(m_private->condition, INFINITE); // block until signaled } m_state = airtaudio::state_stopped; // Stop the buffer and clear memory LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0]; result = buffer->Stop(); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") stopping output buffer!"); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock(0, m_private->dsBufferSize[0], &audioPtr, &dataLen, nullptr, nullptr, 0); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") locking output buffer!"); goto unlock; } // Zero the DS buffer ZeroMemory(audioPtr, dataLen); // Unlock the DS buffer result = buffer->Unlock(audioPtr, dataLen, nullptr, 0); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") unlocking output buffer!"); goto unlock; } // If we start playing again, we must begin at beginning of buffer. m_private->bufferPointer[0] = 0; } if ( m_mode == airtaudio::mode_input || m_mode == airtaudio::mode_duplex) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1]; audioPtr = nullptr; dataLen = 0; m_state = airtaudio::state_stopped; result = buffer->Stop(); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") stopping input buffer!"); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock(0, m_private->dsBufferSize[1], &audioPtr, &dataLen, nullptr, nullptr, 0); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") locking input buffer!"); goto unlock; } // Zero the DS buffer ZeroMemory(audioPtr, dataLen); // Unlock the DS buffer result = buffer->Unlock(audioPtr, dataLen, nullptr, 0); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") unlocking input buffer!"); goto unlock; } // If we start recording again, we must begin at beginning of buffer. m_private->bufferPointer[1] = 0; } unlock: timeEndPeriod(1); // revert to normal scheduler frequency on lesser windows. if (FAILED(result)) { return airtaudio::error_systemError; } return airtaudio::error_none; } enum airtaudio::error airtaudio::api::Ds::abortStream() { if (verifyStream() != airtaudio::error_none) { return airtaudio::error_fail; } if (m_state == airtaudio::state_stopped) { ATA_ERROR("the stream is already stopped!"); return airtaudio::error_warning; } m_private->drainCounter = 2; return stopStream(); } void airtaudio::api::Ds::callbackEvent() { if (m_state == airtaudio::state_stopped || m_state == airtaudio::state_stopping) { Sleep(50); // sleep 50 milliseconds return; } if (m_state == airtaudio::state_closed) { ATA_ERROR("the stream is closed ... this shouldn't happen!"); return; } // Check if we were draining the stream and signal is finished. if (m_private->drainCounter > m_nBuffers + 2) { m_state = airtaudio::state_stopping; if (m_private->internalDrain == false) { SetEvent(m_private->condition); } else { stopStream(); } return; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if (m_private->drainCounter == 0) { std11::chrono::system_clock::time_point streamTime = getStreamTime(); airtaudio::status status = airtaudio::status_ok; if ( m_mode != airtaudio::mode_input && m_private->xrun[0] == true) { status = airtaudio::status_underflow; m_private->xrun[0] = false; } if ( m_mode != airtaudio::mode_output && m_private->xrun[1] == true) { status = airtaudio::status_overflow; m_private->xrun[1] = false; } int32_t cbReturnValue = info->callback(&m_userBuffer[1][0], streamTime, &m_userBuffer[0][0], streamTime, m_bufferSize, status); if (cbReturnValue == 2) { m_state = airtaudio::state_stopping; m_private->drainCounter = 2; abortStream(); return; } else if (cbReturnValue == 1) { m_private->drainCounter = 1; m_private->internalDrain = true; } } HRESULT result; DWORD currentWritePointer, safeWritePointer; DWORD currentReadPointer, safeReadPointer; UINT nextWritePointer; LPVOID buffer1 = nullptr; LPVOID buffer2 = nullptr; DWORD bufferSize1 = 0; DWORD bufferSize2 = 0; char *buffer; long bufferBytes; if (m_buffersRolling == false) { if (m_mode == airtaudio::mode_duplex) { //assert(m_private->dsBufferSize[0] == m_private->dsBufferSize[1]); // It takes a while for the devices to get rolling. As a result, // there's no guarantee that the capture and write device pointers // will move in lockstep. Wait here for both devices to start // rolling, and then set our buffer pointers accordingly. // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 // bytes later than the write buffer. // Stub: a serious risk of having a pre-emptive scheduling round // take place between the two GetCurrentPosition calls... but I'm // really not sure how to solve the problem. Temporarily boost to // Realtime priority, maybe; but I'm not sure what priority the // DirectSound service threads run at. We *should* be roughly // within a ms or so of correct. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0]; LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1]; DWORD startSafeWritePointer, startSafeReadPointer; result = dsWriteBuffer->GetCurrentPosition(nullptr, &startSafeWritePointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current write position!"); return; } result = dsCaptureBuffer->GetCurrentPosition(nullptr, &startSafeReadPointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current read position!"); return; } while (true) { result = dsWriteBuffer->GetCurrentPosition(nullptr, &safeWritePointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current write position!"); return; } result = dsCaptureBuffer->GetCurrentPosition(nullptr, &safeReadPointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current read position!"); return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer) { break; } Sleep(1); } //assert(m_private->dsBufferSize[0] == m_private->dsBufferSize[1]); m_private->bufferPointer[0] = safeWritePointer + m_private->dsPointerLeadTime[0]; if (m_private->bufferPointer[0] >= m_private->dsBufferSize[0]) { m_private->bufferPointer[0] -= m_private->dsBufferSize[0]; } m_private->bufferPointer[1] = safeReadPointer; } else if (m_mode == airtaudio::mode_output) { // Set the proper nextWritePosition after initial startup. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0]; result = dsWriteBuffer->GetCurrentPosition(¤tWritePointer, &safeWritePointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current write position!"); return; } m_private->bufferPointer[0] = safeWritePointer + m_private->dsPointerLeadTime[0]; if (m_private->bufferPointer[0] >= m_private->dsBufferSize[0]) { m_private->bufferPointer[0] -= m_private->dsBufferSize[0]; } } m_buffersRolling = true; } if ( m_mode == airtaudio::mode_output || m_mode == airtaudio::mode_duplex) { LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0]; if (m_private->drainCounter > 1) { // write zeros to the output stream bufferBytes = m_bufferSize * m_nUserChannels[0]; bufferBytes *= audio::getFormatBytes(m_userFormat); memset(&m_userBuffer[0][0], 0, bufferBytes); } // Setup parameters and do buffer conversion if necessary. if (m_doConvertBuffer[0]) { buffer = m_deviceBuffer; convertBuffer(buffer, &m_userBuffer[0][0], m_convertInfo[0]); bufferBytes = m_bufferSize * m_nDeviceChannels[0]; bufferBytes *= audio::getFormatBytes(m_deviceFormat[0]); } else { buffer = &m_userBuffer[0][0]; bufferBytes = m_bufferSize * m_nUserChannels[0]; bufferBytes *= audio::getFormatBytes(m_userFormat); } // No byte swapping necessary in DirectSound implementation. // Ahhh ... windoze. 16-bit data is signed but 8-bit data is // unsigned. So, we need to convert our signed 8-bit data here to // unsigned. if (m_deviceFormat[0] == audio::format_int8) { for (size_t iii=0; iiidsBufferSize[0]; nextWritePointer = m_private->bufferPointer[0]; DWORD endWrite, leadPointer; while (true) { // Find out where the read and "safe write" pointers are. result = dsBuffer->GetCurrentPosition(¤tWritePointer, &safeWritePointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current write position!"); return; } // We will copy our output buffer into the region between // safeWritePointer and leadPointer. If leadPointer is not // beyond the next endWrite position, wait until it is. leadPointer = safeWritePointer + m_private->dsPointerLeadTime[0]; //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; if (leadPointer > dsBufferSize) { leadPointer -= dsBufferSize; } if (leadPointer < nextWritePointer) { leadPointer += dsBufferSize; // unwrap offset } endWrite = nextWritePointer + bufferBytes; // Check whether the entire write region is behind the play pointer. if (leadPointer >= endWrite) { break; } // If we are here, then we must wait until the leadPointer advances // beyond the end of our next write region. We use the // Sleep() function to suspend operation until that happens. double millis = (endWrite - leadPointer) * 1000.0; millis /= (audio::getFormatBytes(m_deviceFormat[0]) * m_nDeviceChannels[0] * m_sampleRate); if (millis < 1.0) { millis = 1.0; } Sleep((DWORD) millis); } if ( dsPointerBetween(nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize) || dsPointerBetween(endWrite, safeWritePointer, currentWritePointer, dsBufferSize)) { // We've strayed into the forbidden zone ... resync the read pointer. m_private->xrun[0] = true; nextWritePointer = safeWritePointer + m_private->dsPointerLeadTime[0] - bufferBytes; if (nextWritePointer >= dsBufferSize) { nextWritePointer -= dsBufferSize; } m_private->bufferPointer[0] = nextWritePointer; endWrite = nextWritePointer + bufferBytes; } // Lock free space in the buffer result = dsBuffer->Lock(nextWritePointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") locking buffer during playback!"); return; } // Copy our buffer into the DS buffer CopyMemory(buffer1, buffer, bufferSize1); if (buffer2 != nullptr) { CopyMemory(buffer2, buffer+bufferSize1, bufferSize2); } // Update our buffer offset and unlock sound buffer dsBuffer->Unlock(buffer1, bufferSize1, buffer2, bufferSize2); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") unlocking buffer during playback!"); return; } nextWritePointer = (nextWritePointer + bufferSize1 + bufferSize2) % dsBufferSize; m_private->bufferPointer[0] = nextWritePointer; if (m_private->drainCounter) { m_private->drainCounter++; goto unlock; } } if ( m_mode == airtaudio::mode_input || m_mode == airtaudio::mode_duplex) { // Setup parameters. if (m_doConvertBuffer[1]) { buffer = m_deviceBuffer; bufferBytes = m_bufferSize * m_nDeviceChannels[1]; bufferBytes *= audio::getFormatBytes(m_deviceFormat[1]); } else { buffer = &m_userBuffer[1][0]; bufferBytes = m_bufferSize * m_nUserChannels[1]; bufferBytes *= audio::getFormatBytes(m_userFormat); } LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1]; long nextReadPointer = m_private->bufferPointer[1]; DWORD dsBufferSize = m_private->dsBufferSize[1]; // Find out where the write and "safe read" pointers are. result = dsBuffer->GetCurrentPosition(¤tReadPointer, &safeReadPointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current read position!"); return; } if (safeReadPointer < (DWORD)nextReadPointer) { safeReadPointer += dsBufferSize; // unwrap offset } DWORD endRead = nextReadPointer + bufferBytes; // Handling depends on whether we are airtaudio::mode_input or airtaudio::mode_duplex. // If we're in airtaudio::mode_input mode then waiting is a good thing. If we're in airtaudio::mode_duplex mode, // then a wait here will drag the write pointers into the forbidden zone. // // In airtaudio::mode_duplex mode, rather than wait, we will back off the read pointer until // it's in a safe position. This causes dropouts, but it seems to be the only // practical way to sync up the read and write pointers reliably, given the // the very complex relationship between phase and increment of the read and write // pointers. // // In order to minimize audible dropouts in airtaudio::mode_duplex mode, we will // provide a pre-roll period of 0.5 seconds in which we return // zeros from the read buffer while the pointers sync up. if (m_mode == airtaudio::mode_duplex) { if (safeReadPointer < endRead) { if (m_duplexPrerollBytes <= 0) { // Pre-roll time over. Be more agressive. int32_t adjustment = endRead-safeReadPointer; m_private->xrun[1] = true; // Two cases: // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, // and perform fine adjustments later. // - small adjustments: back off by twice as much. if (adjustment >= 2*bufferBytes) { nextReadPointer = safeReadPointer-2*bufferBytes; } else { nextReadPointer = safeReadPointer-bufferBytes-adjustment; } if (nextReadPointer < 0) { nextReadPointer += dsBufferSize; } } else { // In pre=roll time. Just do it. nextReadPointer = safeReadPointer - bufferBytes; while (nextReadPointer < 0) { nextReadPointer += dsBufferSize; } } endRead = nextReadPointer + bufferBytes; } } else { // _mode == airtaudio::mode_input while ( safeReadPointer < endRead && m_private->threadRunning) { // See comments for playback. double millis = (endRead - safeReadPointer) * 1000.0; millis /= (audio::getFormatBytes(m_deviceFormat[1]) * m_nDeviceChannels[1] * m_sampleRate); if (millis < 1.0) { millis = 1.0; } Sleep((DWORD) millis); // Wake up and find out where we are now. result = dsBuffer->GetCurrentPosition(¤tReadPointer, &safeReadPointer); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") getting current read position!"); return; } if (safeReadPointer < (DWORD)nextReadPointer) { // unwrap offset safeReadPointer += dsBufferSize; } } } // Lock free space in the buffer result = dsBuffer->Lock(nextReadPointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") locking capture buffer!"); return; } if (m_duplexPrerollBytes <= 0) { // Copy our buffer into the DS buffer CopyMemory(buffer, buffer1, bufferSize1); if (buffer2 != nullptr) { CopyMemory(buffer+bufferSize1, buffer2, bufferSize2); } } else { memset(buffer, 0, bufferSize1); if (buffer2 != nullptr) { memset(buffer + bufferSize1, 0, bufferSize2); } m_duplexPrerollBytes -= bufferSize1 + bufferSize2; } // Update our buffer offset and unlock sound buffer nextReadPointer = (nextReadPointer + bufferSize1 + bufferSize2) % dsBufferSize; dsBuffer->Unlock(buffer1, bufferSize1, buffer2, bufferSize2); if (FAILED(result)) { ATA_ERROR("error (" << getErrorString(result) << ") unlocking capture buffer!"); return; } m_private->bufferPointer[1] = nextReadPointer; // No byte swapping necessary in DirectSound implementation. // If necessary, convert 8-bit data from unsigned to signed. if (m_deviceFormat[1] == audio::format_int8) { for (size_t jjj=0; jjj(_userData); while (myClass->m_private->threadRunning == true) { myClass->callbackEvent(); } } #include "tchar.h" static std::string convertTChar(LPCTSTR _name) { #if defined(UNICODE) || defined(_UNICODE) int32_t length = WideCharToMultiByte(CP_UTF8, 0, _name, -1, nullptr, 0, nullptr, nullptr); std::string s(length-1, '\0'); WideCharToMultiByte(CP_UTF8, 0, _name, -1, &s[0], length, nullptr, nullptr); #else std::string s(_name); #endif return s; } static BOOL CALLBACK deviceQueryCallback(LPGUID _lpguid, LPCTSTR _description, LPCTSTR _module, LPVOID _lpContext) { struct DsProbeData& probeInfo = *(struct DsProbeData*) _lpContext; std::vector& dsDevices = *probeInfo.dsDevices; HRESULT hr; bool validDevice = false; if (probeInfo.isInput == true) { DSCCAPS caps; LPDIRECTSOUNDCAPTURE object; hr = DirectSoundCaptureCreate(_lpguid, &object, nullptr); if (hr != DS_OK) { return TRUE; } caps.dwSize = sizeof(caps); hr = object->GetCaps(&caps); if (hr == DS_OK) { if (caps.dwChannels > 0 && caps.dwFormats > 0) { validDevice = true; } } object->Release(); } else { DSCAPS caps; LPDIRECTSOUND object; hr = DirectSoundCreate(_lpguid, &object, nullptr); if (hr != DS_OK) { return TRUE; } caps.dwSize = sizeof(caps); hr = object->GetCaps(&caps); if (hr == DS_OK) { if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO) { validDevice = true; } } object->Release(); } // If good device, then save its name and guid. std::string name = convertTChar(_description); //if (name == "Primary Sound Driver" || name == "Primary Sound Capture Driver") if (_lpguid == nullptr) { name = "Default Device"; } if (validDevice) { for (size_t i=0; i