Browse Source

Workaround for CoreAudio devices that don't change sample rate correctly in 10.6; improvement to WASAPI error reporting

tags/2021-05-28
Julian Storer 15 years ago
parent
commit
b171ae8400
3 changed files with 125 additions and 175 deletions
  1. +62
    -87
      juce_amalgamated.cpp
  2. +57
    -87
      src/native/mac/juce_mac_CoreAudio.cpp
  3. +6
    -1
      src/native/windows/juce_win32_WASAPI.cpp

+ 62
- 87
juce_amalgamated.cpp View File

@@ -246871,7 +246871,6 @@ public:
defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize); defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize);
sampleRates = inputDevice->rates; sampleRates = inputDevice->rates;
sampleRates.removeValuesNotIn (outputDevice->rates); sampleRates.removeValuesNotIn (outputDevice->rates);
jassert (sampleRates.size() > 0); // in and out devices don't share any common sample rates!
} }
else else
{ {
@@ -246945,6 +246944,12 @@ public:
close(); close();
lastError = String::empty; lastError = String::empty;


if (sampleRates.size() == 0 && inputDevice != 0 && outputDevice != 0)
{
lastError = "The input and output devices don't share a common sample rate!";
return lastError;
}

currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize); currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize);
currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate; currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate;


@@ -263162,6 +263167,8 @@ public:
isSlaveDevice (false), isSlaveDevice (false),
deviceID (id), deviceID (id),
started (false), started (false),
sampleRate (0),
bufferSize (512),
audioBuffer (0), audioBuffer (0),
numInputChans (0), numInputChans (0),
numOutputChans (0), numOutputChans (0),
@@ -263173,24 +263180,16 @@ public:
inputChannelInfo (0), inputChannelInfo (0),
outputChannelInfo (0) outputChannelInfo (0)
{ {
sampleRate = 0;
bufferSize = 512;
jassert (deviceID != 0);


if (deviceID == 0)
{
error = TRANS("can't open device");
}
else
{
updateDetailsFromDevice();
updateDetailsFromDevice();


AudioObjectPropertyAddress pa;
pa.mSelector = kAudioObjectPropertySelectorWildcard;
pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementWildcard;
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioObjectPropertySelectorWildcard;
pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementWildcard;


AudioObjectAddPropertyListener (deviceID, &pa, deviceListenerProc, this);
}
AudioObjectAddPropertyListener (deviceID, &pa, deviceListenerProc, this);
} }


~CoreAudioInternal() ~CoreAudioInternal()
@@ -263203,13 +263202,13 @@ public:
AudioObjectRemovePropertyListener (deviceID, &pa, deviceListenerProc, this); AudioObjectRemovePropertyListener (deviceID, &pa, deviceListenerProc, this);


stop (false); stop (false);
delete inputDevice;


juce_free (audioBuffer); juce_free (audioBuffer);
juce_free (tempInputBuffers); juce_free (tempInputBuffers);
juce_free (tempOutputBuffers); juce_free (tempOutputBuffers);
juce_free (inputChannelInfo); juce_free (inputChannelInfo);
juce_free (outputChannelInfo); juce_free (outputChannelInfo);
delete inputDevice;
} }


void allocateTempBuffers() void allocateTempBuffers()
@@ -263551,7 +263550,7 @@ public:
double newSampleRate, double newSampleRate,
int bufferSizeSamples) int bufferSizeSamples)
{ {
error = String::empty;
String error;
log ("CoreAudio reopen"); log ("CoreAudio reopen");
callbacksAllowed = false; callbacksAllowed = false;
stopTimer(); stopTimer();
@@ -263559,12 +263558,11 @@ public:
stop (false); stop (false);


activeInputChans = inputChannels; activeInputChans = inputChannels;
activeOutputChans = outputChannels;

activeInputChans.setRange (inChanNames.size(), activeInputChans.setRange (inChanNames.size(),
activeInputChans.getHighestBit() + 1 - inChanNames.size(), activeInputChans.getHighestBit() + 1 - inChanNames.size(),
false); false);


activeOutputChans = outputChannels;
activeOutputChans.setRange (outChanNames.size(), activeOutputChans.setRange (outChanNames.size(),
activeOutputChans.getHighestBit() + 1 - outChanNames.size(), activeOutputChans.getHighestBit() + 1 - outChanNames.size(),
false); false);
@@ -263573,52 +263571,49 @@ public:
numOutputChans = activeOutputChans.countNumberOfSetBits(); numOutputChans = activeOutputChans.countNumberOfSetBits();


// set sample rate // set sample rate
Float64 sr = newSampleRate;
UInt32 size = sizeof (sr);

AudioObjectPropertyAddress pa; AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyNominalSampleRate; pa.mSelector = kAudioDevicePropertyNominalSampleRate;
pa.mScope = kAudioObjectPropertyScopeWildcard; pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementMaster; pa.mElement = kAudioObjectPropertyElementMaster;
Float64 sr = newSampleRate;


OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, size, &sr));

// change buffer size
UInt32 framesPerBuf = bufferSizeSamples;
size = sizeof (framesPerBuf);

pa.mSelector = kAudioDevicePropertyBufferFrameSize;
OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, size, &framesPerBuf));

// wait for the changes to happen (on some devices)
int i = 30;
while (--i >= 0)
if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (sr), &sr)))
{ {
updateDetailsFromDevice();

if (sampleRate == newSampleRate && bufferSizeSamples == bufferSize)
break;

Thread::sleep (100);
error = "Couldn't change sample rate";
} }
else
{
// change buffer size
UInt32 framesPerBuf = bufferSizeSamples;
pa.mSelector = kAudioDevicePropertyBufferFrameSize;


if (i < 0)
error = "Couldn't change sample rate/buffer size";

if (sampleRates.size() == 0)
error = "Device has no available sample-rates";

if (bufferSizes.size() == 0)
error = "Device has no available buffer-sizes";
if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (framesPerBuf), &framesPerBuf)))
{
error = "Couldn't change buffer size";
}
else
{
// Annoyingly, after changing the rate and buffer size, some devices fail to
// correctly report their new settings until some random time in the future, so
// after calling updateDetailsFromDevice, we need to manually bodge these values
// to make sure we're using the correct numbers..
updateDetailsFromDevice();
sampleRate = newSampleRate;
bufferSize = bufferSizeSamples;


if (inputDevice != 0 && error.isEmpty())
error = inputDevice->reopen (inputChannels,
outputChannels,
newSampleRate,
bufferSizeSamples);
if (sampleRates.size() == 0)
error = "Device has no available sample-rates";
else if (bufferSizes.size() == 0)
error = "Device has no available buffer-sizes";
else if (inputDevice != 0)
error = inputDevice->reopen (inputChannels,
outputChannels,
newSampleRate,
bufferSizeSamples);
}
}


callbacksAllowed = true; callbacksAllowed = true;

return error; return error;
} }


@@ -263869,16 +263864,13 @@ public:
{ {
result = new CoreAudioInternal (devs[i]); result = new CoreAudioInternal (devs[i]);


if (result->error.isEmpty())
{
const bool thisIsInput = inChanNames.size() > 0 && outChanNames.size() == 0;
const bool otherIsInput = result->inChanNames.size() > 0 && result->outChanNames.size() == 0;
const bool thisIsInput = inChanNames.size() > 0 && outChanNames.size() == 0;
const bool otherIsInput = result->inChanNames.size() > 0 && result->outChanNames.size() == 0;


if (thisIsInput != otherIsInput
|| (inChanNames.size() + outChanNames.size() == 0)
|| (result->inChanNames.size() + result->outChanNames.size()) == 0)
break;
}
if (thisIsInput != otherIsInput
|| (inChanNames.size() + outChanNames.size() == 0)
|| (result->inChanNames.size() + result->outChanNames.size()) == 0)
break;


deleteAndZero (result); deleteAndZero (result);
} }
@@ -263893,7 +263885,6 @@ public:


juce_UseDebuggingNewOperator juce_UseDebuggingNewOperator


String error;
int inputLatency, outputLatency; int inputLatency, outputLatency;
BitArray activeInputChans, activeOutputChans; BitArray activeInputChans, activeOutputChans;
StringArray inChanNames, outChanNames; StringArray inChanNames, outChanNames;
@@ -264024,34 +264015,17 @@ public:
jassert (inputDeviceId != 0); jassert (inputDeviceId != 0);


device = new CoreAudioInternal (inputDeviceId); device = new CoreAudioInternal (inputDeviceId);
lastError = device->error;

if (lastError.isNotEmpty())
deleteAndZero (device);
} }
else else
{ {
device = new CoreAudioInternal (outputDeviceId); device = new CoreAudioInternal (outputDeviceId);
lastError = device->error;


if (lastError.isNotEmpty())
{
deleteAndZero (device);
}
else if (inputDeviceId != 0)
if (inputDeviceId != 0)
{ {
CoreAudioInternal* secondDevice = new CoreAudioInternal (inputDeviceId); CoreAudioInternal* secondDevice = new CoreAudioInternal (inputDeviceId);
lastError = device->error;


if (lastError.isNotEmpty())
{
delete secondDevice;
}
else
{
device->inputDevice = secondDevice;
secondDevice->isSlaveDevice = true;
}
device->inputDevice = secondDevice;
secondDevice->isSlaveDevice = true;
} }
} }


@@ -264129,14 +264103,15 @@ public:
if (bufferSizeSamples <= 0) if (bufferSizeSamples <= 0)
bufferSizeSamples = getDefaultBufferSize(); bufferSizeSamples = getDefaultBufferSize();


internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples);
lastError = internal->error;
lastError = internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples);
isOpen_ = lastError.isEmpty();
return lastError; return lastError;
} }


void close() void close()
{ {
isOpen_ = false; isOpen_ = false;
internal->stop (false);
} }


bool isOpen() bool isOpen()


+ 57
- 87
src/native/mac/juce_mac_CoreAudio.cpp View File

@@ -75,6 +75,8 @@ public:
isSlaveDevice (false), isSlaveDevice (false),
deviceID (id), deviceID (id),
started (false), started (false),
sampleRate (0),
bufferSize (512),
audioBuffer (0), audioBuffer (0),
numInputChans (0), numInputChans (0),
numOutputChans (0), numOutputChans (0),
@@ -86,24 +88,16 @@ public:
inputChannelInfo (0), inputChannelInfo (0),
outputChannelInfo (0) outputChannelInfo (0)
{ {
sampleRate = 0;
bufferSize = 512;
jassert (deviceID != 0);


if (deviceID == 0)
{
error = TRANS("can't open device");
}
else
{
updateDetailsFromDevice();
updateDetailsFromDevice();


AudioObjectPropertyAddress pa;
pa.mSelector = kAudioObjectPropertySelectorWildcard;
pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementWildcard;
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioObjectPropertySelectorWildcard;
pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementWildcard;


AudioObjectAddPropertyListener (deviceID, &pa, deviceListenerProc, this);
}
AudioObjectAddPropertyListener (deviceID, &pa, deviceListenerProc, this);
} }


~CoreAudioInternal() ~CoreAudioInternal()
@@ -116,13 +110,13 @@ public:
AudioObjectRemovePropertyListener (deviceID, &pa, deviceListenerProc, this); AudioObjectRemovePropertyListener (deviceID, &pa, deviceListenerProc, this);


stop (false); stop (false);
delete inputDevice;


juce_free (audioBuffer); juce_free (audioBuffer);
juce_free (tempInputBuffers); juce_free (tempInputBuffers);
juce_free (tempOutputBuffers); juce_free (tempOutputBuffers);
juce_free (inputChannelInfo); juce_free (inputChannelInfo);
juce_free (outputChannelInfo); juce_free (outputChannelInfo);
delete inputDevice;
} }


void allocateTempBuffers() void allocateTempBuffers()
@@ -466,7 +460,7 @@ public:
double newSampleRate, double newSampleRate,
int bufferSizeSamples) int bufferSizeSamples)
{ {
error = String::empty;
String error;
log ("CoreAudio reopen"); log ("CoreAudio reopen");
callbacksAllowed = false; callbacksAllowed = false;
stopTimer(); stopTimer();
@@ -474,12 +468,11 @@ public:
stop (false); stop (false);


activeInputChans = inputChannels; activeInputChans = inputChannels;
activeOutputChans = outputChannels;

activeInputChans.setRange (inChanNames.size(), activeInputChans.setRange (inChanNames.size(),
activeInputChans.getHighestBit() + 1 - inChanNames.size(), activeInputChans.getHighestBit() + 1 - inChanNames.size(),
false); false);


activeOutputChans = outputChannels;
activeOutputChans.setRange (outChanNames.size(), activeOutputChans.setRange (outChanNames.size(),
activeOutputChans.getHighestBit() + 1 - outChanNames.size(), activeOutputChans.getHighestBit() + 1 - outChanNames.size(),
false); false);
@@ -488,52 +481,49 @@ public:
numOutputChans = activeOutputChans.countNumberOfSetBits(); numOutputChans = activeOutputChans.countNumberOfSetBits();


// set sample rate // set sample rate
Float64 sr = newSampleRate;
UInt32 size = sizeof (sr);

AudioObjectPropertyAddress pa; AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyNominalSampleRate; pa.mSelector = kAudioDevicePropertyNominalSampleRate;
pa.mScope = kAudioObjectPropertyScopeWildcard; pa.mScope = kAudioObjectPropertyScopeWildcard;
pa.mElement = kAudioObjectPropertyElementMaster; pa.mElement = kAudioObjectPropertyElementMaster;
Float64 sr = newSampleRate;


OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, size, &sr));

// change buffer size
UInt32 framesPerBuf = bufferSizeSamples;
size = sizeof (framesPerBuf);

pa.mSelector = kAudioDevicePropertyBufferFrameSize;
OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, size, &framesPerBuf));

// wait for the changes to happen (on some devices)
int i = 30;
while (--i >= 0)
if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (sr), &sr)))
{ {
updateDetailsFromDevice();

if (sampleRate == newSampleRate && bufferSizeSamples == bufferSize)
break;

Thread::sleep (100);
error = "Couldn't change sample rate";
} }
else
{
// change buffer size
UInt32 framesPerBuf = bufferSizeSamples;
pa.mSelector = kAudioDevicePropertyBufferFrameSize;


if (i < 0)
error = "Couldn't change sample rate/buffer size";

if (sampleRates.size() == 0)
error = "Device has no available sample-rates";

if (bufferSizes.size() == 0)
error = "Device has no available buffer-sizes";

if (inputDevice != 0 && error.isEmpty())
error = inputDevice->reopen (inputChannels,
outputChannels,
newSampleRate,
bufferSizeSamples);
if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (framesPerBuf), &framesPerBuf)))
{
error = "Couldn't change buffer size";
}
else
{
// Annoyingly, after changing the rate and buffer size, some devices fail to
// correctly report their new settings until some random time in the future, so
// after calling updateDetailsFromDevice, we need to manually bodge these values
// to make sure we're using the correct numbers..
updateDetailsFromDevice();
sampleRate = newSampleRate;
bufferSize = bufferSizeSamples;

if (sampleRates.size() == 0)
error = "Device has no available sample-rates";
else if (bufferSizes.size() == 0)
error = "Device has no available buffer-sizes";
else if (inputDevice != 0)
error = inputDevice->reopen (inputChannels,
outputChannels,
newSampleRate,
bufferSizeSamples);
}
}


callbacksAllowed = true; callbacksAllowed = true;

return error; return error;
} }


@@ -784,16 +774,13 @@ public:
{ {
result = new CoreAudioInternal (devs[i]); result = new CoreAudioInternal (devs[i]);


if (result->error.isEmpty())
{
const bool thisIsInput = inChanNames.size() > 0 && outChanNames.size() == 0;
const bool otherIsInput = result->inChanNames.size() > 0 && result->outChanNames.size() == 0;
const bool thisIsInput = inChanNames.size() > 0 && outChanNames.size() == 0;
const bool otherIsInput = result->inChanNames.size() > 0 && result->outChanNames.size() == 0;


if (thisIsInput != otherIsInput
|| (inChanNames.size() + outChanNames.size() == 0)
|| (result->inChanNames.size() + result->outChanNames.size()) == 0)
break;
}
if (thisIsInput != otherIsInput
|| (inChanNames.size() + outChanNames.size() == 0)
|| (result->inChanNames.size() + result->outChanNames.size()) == 0)
break;


deleteAndZero (result); deleteAndZero (result);
} }
@@ -809,7 +796,6 @@ public:
//============================================================================== //==============================================================================
juce_UseDebuggingNewOperator juce_UseDebuggingNewOperator


String error;
int inputLatency, outputLatency; int inputLatency, outputLatency;
BitArray activeInputChans, activeOutputChans; BitArray activeInputChans, activeOutputChans;
StringArray inChanNames, outChanNames; StringArray inChanNames, outChanNames;
@@ -944,34 +930,17 @@ public:
jassert (inputDeviceId != 0); jassert (inputDeviceId != 0);


device = new CoreAudioInternal (inputDeviceId); device = new CoreAudioInternal (inputDeviceId);
lastError = device->error;

if (lastError.isNotEmpty())
deleteAndZero (device);
} }
else else
{ {
device = new CoreAudioInternal (outputDeviceId); device = new CoreAudioInternal (outputDeviceId);
lastError = device->error;


if (lastError.isNotEmpty())
{
deleteAndZero (device);
}
else if (inputDeviceId != 0)
if (inputDeviceId != 0)
{ {
CoreAudioInternal* secondDevice = new CoreAudioInternal (inputDeviceId); CoreAudioInternal* secondDevice = new CoreAudioInternal (inputDeviceId);
lastError = device->error;


if (lastError.isNotEmpty())
{
delete secondDevice;
}
else
{
device->inputDevice = secondDevice;
secondDevice->isSlaveDevice = true;
}
device->inputDevice = secondDevice;
secondDevice->isSlaveDevice = true;
} }
} }


@@ -1049,14 +1018,15 @@ public:
if (bufferSizeSamples <= 0) if (bufferSizeSamples <= 0)
bufferSizeSamples = getDefaultBufferSize(); bufferSizeSamples = getDefaultBufferSize();


internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples);
lastError = internal->error;
lastError = internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples);
isOpen_ = lastError.isEmpty();
return lastError; return lastError;
} }


void close() void close()
{ {
isOpen_ = false; isOpen_ = false;
internal->stop (false);
} }


bool isOpen() bool isOpen()


+ 6
- 1
src/native/windows/juce_win32_WASAPI.cpp View File

@@ -599,7 +599,6 @@ public:
defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize); defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize);
sampleRates = inputDevice->rates; sampleRates = inputDevice->rates;
sampleRates.removeValuesNotIn (outputDevice->rates); sampleRates.removeValuesNotIn (outputDevice->rates);
jassert (sampleRates.size() > 0); // in and out devices don't share any common sample rates!
} }
else else
{ {
@@ -674,6 +673,12 @@ public:
close(); close();
lastError = String::empty; lastError = String::empty;
if (sampleRates.size() == 0 && inputDevice != 0 && outputDevice != 0)
{
lastError = "The input and output devices don't share a common sample rate!";
return lastError;
}
currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize); currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize);
currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate; currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate;


Loading…
Cancel
Save