audio-orchestra/airtaudio/api/Ds.cpp

1463 lines
52 KiB
C++

/** @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 <airtaudio/Interface.h>
#include <airtaudio/debug.h>
#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 <dsound.h>
#include <assert.h>
#include <algorithm>
#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<std11::thread> 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<DsDevice> 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<DsDevice>* 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; iii<m_private->dsDevices.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; i<m_private->dsDevices.size(); i++) {
if (m_private->dsDevices[i].found == false) {
indices.push_back(i);
}
}
uint32_t nErased = 0;
for (uint32_t i=0; i<indices.size(); i++) {
m_private->dsDevices.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<uint32_t> 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<rates.size(); i++) {
found = false;
for (uint32_t j=0; j<info.sampleRates.size(); j++) {
if (rates[i] == info.sampleRates[j]) {
found = true;
break;
}
}
if (found == false) info.sampleRates.push_back(rates[i]);
}
std::sort(info.sampleRates.begin(), info.sampleRates.end());
// If device opens for both playback and capture, we determine the channels.
if (info.outputChannels > 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<std11::thread> 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(&currentWritePointer, &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; iii<bufferBytes; ++iii) {
buffer[iii] = buffer[iii] + 128;
}
}
DWORD dsBufferSize = m_private->dsBufferSize[0];
nextWritePointer = m_private->bufferPointer[0];
DWORD endWrite, leadPointer;
while (true) {
// Find out where the read and "safe write" pointers are.
result = dsBuffer->GetCurrentPosition(&currentWritePointer, &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(&currentReadPointer, &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(&currentReadPointer, &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<bufferBytes; ++jjj) {
buffer[jjj] = (signed char) (buffer[jjj] - 128);
}
}
// Do buffer conversion if necessary.
if (m_doConvertBuffer[1]) {
convertBuffer(&m_userBuffer[1][0], m_deviceBuffer, m_convertInfo[1]);
}
}
unlock:
airtaudio::Api::tickStreamTime();
}
void airtaudio::api::Ds::dsCallbackEvent(void *_userData) {
etk::log::setThreadName("DS IO-" + m_name);
airtaudio::api::Ds* myClass = reinterpret_cast<airtaudio::api::Ds*>(_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<DsDevice>& 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<dsDevices.size(); i++) {
if (dsDevices[i].name == name) {
dsDevices[i].found = true;
if (probeInfo.isInput) {
dsDevices[i].id[1] = _lpguid;
dsDevices[i].validId[1] = true;
} else {
dsDevices[i].id[0] = _lpguid;
dsDevices[i].validId[0] = true;
}
return TRUE;
}
}
DsDevice device;
device.name = name;
device.found = true;
if (probeInfo.isInput) {
device.id[1] = _lpguid;
device.validId[1] = true;
} else {
device.id[0] = _lpguid;
device.validId[0] = true;
}
dsDevices.push_back(device);
}
return TRUE;
}
static const char* getErrorString(int32_t code) {
switch (code) {
case DSERR_ALLOCATED:
return "Already allocated";
case DSERR_CONTROLUNAVAIL:
return "Control unavailable";
case DSERR_INVALIDPARAM:
return "Invalid parameter";
case DSERR_INVALIDCALL:
return "Invalid call";
case DSERR_GENERIC:
return "Generic error";
case DSERR_PRIOLEVELNEEDED:
return "Priority level needed";
case DSERR_OUTOFMEMORY:
return "Out of memory";
case DSERR_BADFORMAT:
return "The sample rate or the channel format is not supported";
case DSERR_UNSUPPORTED:
return "Not supported";
case DSERR_NODRIVER:
return "No driver";
case DSERR_ALREADYINITIALIZED:
return "Already initialized";
case DSERR_NOAGGREGATION:
return "No aggregation";
case DSERR_BUFFERLOST:
return "Buffer lost";
case DSERR_OTHERAPPHASPRIO:
return "Another application already has priority";
case DSERR_UNINITIALIZED:
return "Uninitialized";
default:
return "DirectSound unknown error";
}
}
#endif