|
|
@@ -45,6 +45,7 @@ void logFailure (HRESULT hr) |
|
|
|
{
|
|
|
|
case E_POINTER: m = "E_POINTER"; break;
|
|
|
|
case E_INVALIDARG: m = "E_INVALIDARG"; break;
|
|
|
|
case E_NOINTERFACE: m = "E_NOINTERFACE"; break;
|
|
|
|
|
|
|
|
#define JUCE_WASAPI_ERR(desc, n) \
|
|
|
|
case MAKE_HRESULT(1, 0x889, n): m = #desc; break;
|
|
|
@@ -126,7 +127,11 @@ enum EDataFlow |
|
|
|
eAll = (eCapture + 1)
|
|
|
|
};
|
|
|
|
|
|
|
|
enum { DEVICE_STATE_ACTIVE = 1 };
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
DEVICE_STATE_ACTIVE = 1,
|
|
|
|
AUDCLNT_BUFFERFLAGS_SILENT = 2
|
|
|
|
};
|
|
|
|
|
|
|
|
JUCE_IUNKNOWNCLASS (IPropertyStore, "886d8eeb-8cf2-4446-8d02-cdba1dbdcf99")
|
|
|
|
{
|
|
|
@@ -353,6 +358,9 @@ public: |
|
|
|
defaultBufferSize (0),
|
|
|
|
latencySamples (0),
|
|
|
|
useExclusiveMode (exclusiveMode),
|
|
|
|
actualBufferSize (0),
|
|
|
|
bytesPerSample (0),
|
|
|
|
bytesPerFrame (0),
|
|
|
|
sampleRateHasChanged (false)
|
|
|
|
{
|
|
|
|
clientEvent = CreateEvent (nullptr, false, false, nullptr);
|
|
|
@@ -382,11 +390,7 @@ public: |
|
|
|
rates.addUsingDefaultSort (defaultSampleRate);
|
|
|
|
|
|
|
|
if (useExclusiveMode
|
|
|
|
&& (findSupportedFormat (tempClient, defaultSampleRate, true, 4, 4, format.dwChannelMask, format)
|
|
|
|
|| findSupportedFormat (tempClient, defaultSampleRate, false, 4, 4, format.dwChannelMask, format)
|
|
|
|
|| findSupportedFormat (tempClient, defaultSampleRate, false, 3, 4, format.dwChannelMask, format)
|
|
|
|
|| findSupportedFormat (tempClient, defaultSampleRate, false, 3, 3, format.dwChannelMask, format)
|
|
|
|
|| findSupportedFormat (tempClient, defaultSampleRate, false, 2, 2, format.dwChannelMask, format)))
|
|
|
|
&& findSupportedFormat (tempClient, defaultSampleRate, format.dwChannelMask, format))
|
|
|
|
{
|
|
|
|
// Got a format that is supported by the device so we can ask what sample rates are supported (in whatever format)
|
|
|
|
}
|
|
|
@@ -430,11 +434,7 @@ public: |
|
|
|
client = createClient();
|
|
|
|
|
|
|
|
if (client != nullptr
|
|
|
|
&& (tryInitialisingWithFormat (true, 4, 4, bufferSizeSamples)
|
|
|
|
|| tryInitialisingWithFormat (false, 4, 4, bufferSizeSamples)
|
|
|
|
|| tryInitialisingWithFormat (false, 3, 4, bufferSizeSamples)
|
|
|
|
|| tryInitialisingWithFormat (false, 3, 3, bufferSizeSamples)
|
|
|
|
|| tryInitialisingWithFormat (false, 2, 2, bufferSizeSamples)))
|
|
|
|
&& tryInitialisingWithBufferSize (bufferSizeSamples))
|
|
|
|
{
|
|
|
|
sampleRateHasChanged = false;
|
|
|
|
|
|
|
@@ -485,7 +485,7 @@ public: |
|
|
|
BigInteger channels;
|
|
|
|
Array<int> channelMaps;
|
|
|
|
UINT32 actualBufferSize;
|
|
|
|
int bytesPerSample;
|
|
|
|
int bytesPerSample, bytesPerFrame;
|
|
|
|
bool sampleRateHasChanged;
|
|
|
|
|
|
|
|
virtual void updateFormat (bool isFloat) = 0;
|
|
|
@@ -556,11 +556,19 @@ private: |
|
|
|
return client;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool findSupportedFormat (IAudioClient* clientToUse, double sampleRate, bool useFloat,
|
|
|
|
int bytesPerSampleToTry, int bytesPerSampleContainer,
|
|
|
|
DWORD mixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const
|
|
|
|
struct AudioSampleFormat
|
|
|
|
{
|
|
|
|
if (numChannels <= 2 && bytesPerSampleToTry <= 2)
|
|
|
|
bool useFloat;
|
|
|
|
int bitsPerSampleToTry;
|
|
|
|
int bytesPerSampleContainer;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool tryFormat (const AudioSampleFormat sampleFormat, IAudioClient* clientToUse, double sampleRate,
|
|
|
|
DWORD mixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const
|
|
|
|
{
|
|
|
|
zerostruct (format);
|
|
|
|
|
|
|
|
if (numChannels <= 2 && sampleFormat.bitsPerSampleToTry <= 16)
|
|
|
|
{
|
|
|
|
format.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
|
|
}
|
|
|
@@ -572,11 +580,11 @@ private: |
|
|
|
|
|
|
|
format.Format.nSamplesPerSec = (DWORD) sampleRate;
|
|
|
|
format.Format.nChannels = (WORD) numChannels;
|
|
|
|
format.Format.wBitsPerSample = (WORD) (8 * bytesPerSampleContainer);
|
|
|
|
format.Samples.wValidBitsPerSample = (WORD) (8 * bytesPerSampleToTry);
|
|
|
|
format.Format.wBitsPerSample = (WORD) (8 * sampleFormat.bytesPerSampleContainer);
|
|
|
|
format.Samples.wValidBitsPerSample = (WORD) (sampleFormat.bitsPerSampleToTry);
|
|
|
|
format.Format.nBlockAlign = (WORD) (format.Format.nChannels * format.Format.wBitsPerSample / 8);
|
|
|
|
format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign);
|
|
|
|
format.SubFormat = useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
|
|
|
|
format.SubFormat = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
|
|
|
|
format.dwChannelMask = mixFormatChannelMask;
|
|
|
|
|
|
|
|
WAVEFORMATEXTENSIBLE* nearestFormat = nullptr;
|
|
|
@@ -597,36 +605,53 @@ private: |
|
|
|
return check (hr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool tryInitialisingWithFormat (const bool useFloat, const int bytesPerSampleToTry,
|
|
|
|
const int bytesPerSampleContainer, const int bufferSizeSamples)
|
|
|
|
bool findSupportedFormat (IAudioClient* clientToUse, double sampleRate,
|
|
|
|
DWORD mixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const
|
|
|
|
{
|
|
|
|
static const AudioSampleFormat formats[] =
|
|
|
|
{
|
|
|
|
{ true, 32, 4 },
|
|
|
|
{ false, 32, 4 },
|
|
|
|
{ false, 24, 4 },
|
|
|
|
{ false, 24, 3 },
|
|
|
|
{ false, 20, 4 },
|
|
|
|
{ false, 20, 3 },
|
|
|
|
{ false, 16, 2 }
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int i = 0; i < numElementsInArray (formats); ++i)
|
|
|
|
if (tryFormat (formats[i], clientToUse, sampleRate, mixFormatChannelMask, format))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool tryInitialisingWithBufferSize (const int bufferSizeSamples)
|
|
|
|
{
|
|
|
|
WAVEFORMATEXTENSIBLE format;
|
|
|
|
zerostruct (format);
|
|
|
|
|
|
|
|
if (findSupportedFormat (client, sampleRate, useFloat, bytesPerSampleToTry,
|
|
|
|
bytesPerSampleContainer, mixFormatChannelMask, format))
|
|
|
|
if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format))
|
|
|
|
{
|
|
|
|
REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
|
|
|
|
|
|
|
|
if (useExclusiveMode)
|
|
|
|
{
|
|
|
|
check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
|
|
|
|
check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
|
|
|
|
|
|
|
|
if (bufferSizeSamples > 0)
|
|
|
|
defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec));
|
|
|
|
}
|
|
|
|
if (useExclusiveMode && bufferSizeSamples > 0)
|
|
|
|
defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec));
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
GUID session;
|
|
|
|
HRESULT hr = client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
|
|
|
|
0x40000 /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/,
|
|
|
|
defaultPeriod, defaultPeriod, (WAVEFORMATEX*) &format, nullptr);
|
|
|
|
defaultPeriod, useExclusiveMode ? defaultPeriod : 0, (WAVEFORMATEX*) &format, &session);
|
|
|
|
|
|
|
|
if (check (hr))
|
|
|
|
{
|
|
|
|
actualNumChannels = format.Format.nChannels;
|
|
|
|
actualNumChannels = format.Format.nChannels;
|
|
|
|
const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
|
|
|
bytesPerSample = format.Format.wBitsPerSample / 8;
|
|
|
|
bytesPerSample = format.Format.wBitsPerSample / 8;
|
|
|
|
bytesPerFrame = format.Format.nBlockAlign;
|
|
|
|
|
|
|
|
updateFormat (isFloat);
|
|
|
|
return true;
|
|
|
@@ -671,10 +696,6 @@ public: |
|
|
|
|
|
|
|
bool open (const double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
|
|
|
|
{
|
|
|
|
reservoirSize = 0;
|
|
|
|
reservoirCapacity = 16384;
|
|
|
|
reservoir.setSize (actualNumChannels * reservoirCapacity * sizeof (float));
|
|
|
|
|
|
|
|
return openClient (newSampleRate, newChannels, bufferSizeSamples)
|
|
|
|
&& (numChannels == 0 || check (client->GetService (__uuidof (IAudioCaptureClient),
|
|
|
|
(void**) captureClient.resetAndGetPointerAddress())));
|
|
|
@@ -685,89 +706,128 @@ public: |
|
|
|
closeClient();
|
|
|
|
captureClient = nullptr;
|
|
|
|
reservoir.reset();
|
|
|
|
reservoirReadPos = reservoirWritePos = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<class SourceType>
|
|
|
|
void updateFormatWithType (SourceType*)
|
|
|
|
void updateFormatWithType (SourceType*) noexcept
|
|
|
|
{
|
|
|
|
typedef AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> NativeType;
|
|
|
|
converter = new AudioData::ConverterInstance<AudioData::Pointer<SourceType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const>, NativeType> (actualNumChannels, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateFormat (bool isFloat)
|
|
|
|
void updateFormat (bool isFloat) override
|
|
|
|
{
|
|
|
|
if (isFloat) updateFormatWithType ((AudioData::Float32*) nullptr);
|
|
|
|
else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) nullptr);
|
|
|
|
else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) nullptr);
|
|
|
|
else updateFormatWithType ((AudioData::Int16*) nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool start (const int userBufferSize)
|
|
|
|
{
|
|
|
|
if (isFloat) updateFormatWithType ((AudioData::Float32*) 0);
|
|
|
|
else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) 0);
|
|
|
|
else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) 0);
|
|
|
|
else updateFormatWithType ((AudioData::Int16*) 0);
|
|
|
|
reservoirSize = actualBufferSize + userBufferSize;
|
|
|
|
reservoirMask = nextPowerOfTwo (reservoirSize) - 1;
|
|
|
|
reservoir.setSize ((reservoirMask + 1) * bytesPerFrame, true);
|
|
|
|
reservoirReadPos = reservoirWritePos = 0;
|
|
|
|
|
|
|
|
if (! check (client->Start()))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
purgeInputBuffers();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void copyBuffers (float** destBuffers, int numDestBuffers, int bufferSize, Thread& thread)
|
|
|
|
void purgeInputBuffers()
|
|
|
|
{
|
|
|
|
uint8* inputData;
|
|
|
|
UINT32 numSamplesAvailable;
|
|
|
|
DWORD flags;
|
|
|
|
|
|
|
|
while (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)
|
|
|
|
!= MAKE_HRESULT (0, 0x889, 0x1) /* AUDCLNT_S_BUFFER_EMPTY */)
|
|
|
|
captureClient->ReleaseBuffer (numSamplesAvailable);
|
|
|
|
}
|
|
|
|
|
|
|
|
int getNumSamplesInReservoir() const noexcept { return reservoirWritePos - reservoirReadPos; }
|
|
|
|
|
|
|
|
void handleDeviceBuffer()
|
|
|
|
{
|
|
|
|
if (numChannels <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int offset = 0;
|
|
|
|
uint8* inputData;
|
|
|
|
UINT32 numSamplesAvailable;
|
|
|
|
DWORD flags;
|
|
|
|
|
|
|
|
while (bufferSize > 0)
|
|
|
|
while (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)) && numSamplesAvailable > 0)
|
|
|
|
{
|
|
|
|
if (reservoirSize > 0) // There's stuff in the reservoir, so use that...
|
|
|
|
int samplesLeft = (int) numSamplesAvailable;
|
|
|
|
|
|
|
|
while (samplesLeft > 0)
|
|
|
|
{
|
|
|
|
const int samplesToDo = jmin (bufferSize, (int) reservoirSize);
|
|
|
|
const int localWrite = reservoirWritePos & reservoirMask;
|
|
|
|
const int samplesToDo = jmin (samplesLeft, reservoirMask + 1 - localWrite);
|
|
|
|
const int samplesToDoBytes = samplesToDo * bytesPerFrame;
|
|
|
|
|
|
|
|
void* reservoirPtr = addBytesToPointer (reservoir.getData(), localWrite * bytesPerFrame);
|
|
|
|
|
|
|
|
for (int i = 0; i < numDestBuffers; ++i)
|
|
|
|
converter->convertSamples (destBuffers[i] + offset, 0, reservoir.getData(), channelMaps.getUnchecked(i), samplesToDo);
|
|
|
|
if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) != 0)
|
|
|
|
zeromem (reservoirPtr, samplesToDoBytes);
|
|
|
|
else
|
|
|
|
memcpy (reservoirPtr, inputData, samplesToDoBytes);
|
|
|
|
|
|
|
|
bufferSize -= samplesToDo;
|
|
|
|
offset += samplesToDo;
|
|
|
|
reservoirSize = 0;
|
|
|
|
reservoirWritePos += samplesToDo;
|
|
|
|
inputData += samplesToDoBytes;
|
|
|
|
samplesLeft -= samplesToDo;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
UINT32 packetLength = 0;
|
|
|
|
if (! (useExclusiveMode || check (captureClient->GetNextPacketSize (&packetLength))))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (packetLength == 0)
|
|
|
|
{
|
|
|
|
if (thread.threadShouldExit()
|
|
|
|
|| WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
|
|
|
|
break;
|
|
|
|
if (getNumSamplesInReservoir() > reservoirSize)
|
|
|
|
reservoirReadPos = reservoirWritePos - reservoirSize;
|
|
|
|
|
|
|
|
if (! useExclusiveMode)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
captureClient->ReleaseBuffer (numSamplesAvailable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8* inputData;
|
|
|
|
UINT32 numSamplesAvailable;
|
|
|
|
DWORD flags;
|
|
|
|
void copyBuffersFromReservoir (float** destBuffers, int numDestBuffers, int bufferSize)
|
|
|
|
{
|
|
|
|
if (numChannels <= 0 && bufferSize == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, 0, 0)))
|
|
|
|
{
|
|
|
|
const int samplesToDo = jmin (bufferSize, (int) numSamplesAvailable);
|
|
|
|
int offset = jmax (0, bufferSize - getNumSamplesInReservoir());
|
|
|
|
|
|
|
|
for (int i = 0; i < numDestBuffers; ++i)
|
|
|
|
converter->convertSamples (destBuffers[i] + offset, 0, inputData, channelMaps.getUnchecked(i), samplesToDo);
|
|
|
|
if (offset > 0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < numDestBuffers; ++i)
|
|
|
|
zeromem (destBuffers[i], offset * sizeof (float));
|
|
|
|
|
|
|
|
bufferSize -= samplesToDo;
|
|
|
|
offset += samplesToDo;
|
|
|
|
bufferSize -= offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (samplesToDo < (int) numSamplesAvailable)
|
|
|
|
{
|
|
|
|
reservoirSize = jmin ((int) (numSamplesAvailable - samplesToDo), reservoirCapacity);
|
|
|
|
memcpy ((uint8*) reservoir.getData(), inputData + bytesPerSample * actualNumChannels * samplesToDo,
|
|
|
|
(size_t) (bytesPerSample * actualNumChannels * reservoirSize));
|
|
|
|
}
|
|
|
|
while (bufferSize > 0)
|
|
|
|
{
|
|
|
|
const int localRead = reservoirReadPos & reservoirMask;
|
|
|
|
|
|
|
|
captureClient->ReleaseBuffer (numSamplesAvailable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const int samplesToDo = jmin (bufferSize, getNumSamplesInReservoir(), reservoirMask + 1 - localRead);
|
|
|
|
if (samplesToDo <= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
const int reservoirOffset = localRead * bytesPerFrame;
|
|
|
|
|
|
|
|
for (int i = 0; i < numDestBuffers; ++i)
|
|
|
|
converter->convertSamples (destBuffers[i] + offset, 0, addBytesToPointer (reservoir.getData(), reservoirOffset), channelMaps.getUnchecked(i), samplesToDo);
|
|
|
|
|
|
|
|
bufferSize -= samplesToDo;
|
|
|
|
offset += samplesToDo;
|
|
|
|
reservoirReadPos += samplesToDo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ComSmartPtr<IAudioCaptureClient> captureClient;
|
|
|
|
MemoryBlock reservoir;
|
|
|
|
int reservoirSize, reservoirCapacity;
|
|
|
|
int reservoirSize, reservoirMask;
|
|
|
|
volatile int reservoirReadPos, reservoirWritePos;
|
|
|
|
|
|
|
|
ScopedPointer<AudioData::Converter> converter;
|
|
|
|
|
|
|
|
private:
|
|
|
@@ -791,8 +851,8 @@ public: |
|
|
|
bool open (const double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
|
|
|
|
{
|
|
|
|
return openClient (newSampleRate, newChannels, bufferSizeSamples)
|
|
|
|
&& (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient),
|
|
|
|
(void**) renderClient.resetAndGetPointerAddress())));
|
|
|
|
&& (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient),
|
|
|
|
(void**) renderClient.resetAndGetPointerAddress())));
|
|
|
|
}
|
|
|
|
|
|
|
|
void close()
|
|
|
@@ -808,7 +868,7 @@ public: |
|
|
|
converter = new AudioData::ConverterInstance<NativeType, AudioData::Pointer<DestType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> > (1, actualNumChannels);
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateFormat (bool isFloat)
|
|
|
|
void updateFormat (bool isFloat) override
|
|
|
|
{
|
|
|
|
if (isFloat) updateFormatWithType ((AudioData::Float32*) nullptr);
|
|
|
|
else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) nullptr);
|
|
|
@@ -816,7 +876,34 @@ public: |
|
|
|
else updateFormatWithType ((AudioData::Int16*) nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void copyBuffers (const float** const srcBuffers, const int numSrcBuffers, int bufferSize, Thread& thread)
|
|
|
|
bool start()
|
|
|
|
{
|
|
|
|
int samplesToDo = getNumSamplesAvailableToCopy();
|
|
|
|
uint8* outputData;
|
|
|
|
|
|
|
|
if (check (renderClient->GetBuffer (samplesToDo, &outputData)))
|
|
|
|
renderClient->ReleaseBuffer (samplesToDo, AUDCLNT_BUFFERFLAGS_SILENT);
|
|
|
|
|
|
|
|
return check (client->Start());
|
|
|
|
}
|
|
|
|
|
|
|
|
int getNumSamplesAvailableToCopy() const
|
|
|
|
{
|
|
|
|
if (numChannels <= 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (! useExclusiveMode)
|
|
|
|
{
|
|
|
|
UINT32 padding = 0;
|
|
|
|
if (check (client->GetCurrentPadding (&padding)))
|
|
|
|
return actualBufferSize - (int) padding;
|
|
|
|
}
|
|
|
|
|
|
|
|
return actualBufferSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
void copyBuffers (const float** const srcBuffers, const int numSrcBuffers, int bufferSize,
|
|
|
|
WASAPIInputDevice* inputDevice, Thread& thread)
|
|
|
|
{
|
|
|
|
if (numChannels <= 0)
|
|
|
|
return;
|
|
|
@@ -825,26 +912,25 @@ public: |
|
|
|
|
|
|
|
while (bufferSize > 0)
|
|
|
|
{
|
|
|
|
// In exclusive mode, GetCurrentPadding ALWAYS returns the buffer size, so we need
|
|
|
|
// to wait for the event before getting the buffer.
|
|
|
|
if (useExclusiveMode && (thread.threadShouldExit() || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT))
|
|
|
|
break;
|
|
|
|
// This is needed in order not to drop any input data if the output device endpoint buffer was full
|
|
|
|
if ((! useExclusiveMode) && inputDevice != nullptr
|
|
|
|
&& WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
|
|
|
|
inputDevice->handleDeviceBuffer();
|
|
|
|
|
|
|
|
UINT32 padding = 0;
|
|
|
|
if (! check (client->GetCurrentPadding (&padding)))
|
|
|
|
return;
|
|
|
|
int samplesToDo = jmin (getNumSamplesAvailableToCopy(), bufferSize);
|
|
|
|
|
|
|
|
int samplesToDo = useExclusiveMode ? actualBufferSize
|
|
|
|
: jmin ((int) (actualBufferSize - padding), bufferSize);
|
|
|
|
|
|
|
|
if (samplesToDo <= 0)
|
|
|
|
if (samplesToDo == 0)
|
|
|
|
{
|
|
|
|
if (thread.threadShouldExit() || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
|
|
|
|
break;
|
|
|
|
// This can ONLY occur in non-exclusive mode
|
|
|
|
if (! thread.threadShouldExit() && WaitForSingleObject (clientEvent, 1000) == WAIT_OBJECT_0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (useExclusiveMode && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
|
|
|
|
break;
|
|
|
|
|
|
|
|
uint8* outputData = nullptr;
|
|
|
|
if (check (renderClient->GetBuffer ((UINT32) samplesToDo, &outputData)))
|
|
|
|
{
|
|
|
@@ -852,10 +938,10 @@ public: |
|
|
|
converter->convertSamples (outputData, channelMaps.getUnchecked(i), srcBuffers[i] + offset, 0, samplesToDo);
|
|
|
|
|
|
|
|
renderClient->ReleaseBuffer ((UINT32) samplesToDo, 0);
|
|
|
|
|
|
|
|
offset += samplesToDo;
|
|
|
|
bufferSize -= samplesToDo;
|
|
|
|
}
|
|
|
|
|
|
|
|
bufferSize -= samplesToDo;
|
|
|
|
offset += samplesToDo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
@@ -1031,7 +1117,7 @@ public: |
|
|
|
{
|
|
|
|
latencyIn = (int) (inputDevice->latencySamples + currentBufferSizeSamples);
|
|
|
|
|
|
|
|
if (! check (inputDevice->client->Start()))
|
|
|
|
if (! inputDevice->start (currentBufferSizeSamples))
|
|
|
|
{
|
|
|
|
close();
|
|
|
|
lastError = TRANS("Couldn't start the input device!");
|
|
|
@@ -1043,7 +1129,7 @@ public: |
|
|
|
{
|
|
|
|
latencyOut = (int) (outputDevice->latencySamples + currentBufferSizeSamples);
|
|
|
|
|
|
|
|
if (! check (outputDevice->client->Start()))
|
|
|
|
if (! outputDevice->start())
|
|
|
|
{
|
|
|
|
close();
|
|
|
|
lastError = TRANS("Couldn't start the output device!");
|
|
|
@@ -1132,37 +1218,48 @@ public: |
|
|
|
const int bufferSize = currentBufferSizeSamples;
|
|
|
|
const int numInputBuffers = getActiveInputChannels().countNumberOfSetBits();
|
|
|
|
const int numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits();
|
|
|
|
bool sampleRateChanged = false;
|
|
|
|
bool sampleRateHasChanged = false;
|
|
|
|
|
|
|
|
AudioSampleBuffer ins (jmax (1, numInputBuffers), bufferSize + 32);
|
|
|
|
AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32);
|
|
|
|
float** const inputBuffers = ins.getArrayOfWritePointers();
|
|
|
|
float** const outputBuffers = outs.getArrayOfWritePointers();
|
|
|
|
ins.clear();
|
|
|
|
|
|
|
|
if (outputDevice != nullptr)
|
|
|
|
SetEvent (outputDevice->clientEvent); // (Equivalent to preloading the output buffer)
|
|
|
|
outs.clear();
|
|
|
|
|
|
|
|
while (! threadShouldExit())
|
|
|
|
{
|
|
|
|
if (inputDevice != nullptr)
|
|
|
|
{
|
|
|
|
inputDevice->copyBuffers (inputBuffers, numInputBuffers, bufferSize, *this);
|
|
|
|
if (outputDevice == nullptr)
|
|
|
|
{
|
|
|
|
if (WaitForSingleObject (inputDevice->clientEvent, 1000) == WAIT_TIMEOUT)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (threadShouldExit())
|
|
|
|
break;
|
|
|
|
inputDevice->handleDeviceBuffer();
|
|
|
|
|
|
|
|
if (inputDevice->getNumSamplesInReservoir() < bufferSize)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (useExclusiveMode && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
|
|
|
|
inputDevice->handleDeviceBuffer();
|
|
|
|
}
|
|
|
|
|
|
|
|
inputDevice->copyBuffersFromReservoir (inputBuffers, numInputBuffers, bufferSize);
|
|
|
|
|
|
|
|
if (inputDevice->sampleRateHasChanged)
|
|
|
|
{
|
|
|
|
sampleRateChanged = true;
|
|
|
|
sampleRateHasChanged = true;
|
|
|
|
sampleRateChangedByOutput = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const ScopedLock sl (startStopLock);
|
|
|
|
const ScopedTryLock sl (startStopLock);
|
|
|
|
|
|
|
|
if (isStarted)
|
|
|
|
if (sl.isLocked() && isStarted)
|
|
|
|
callback->audioDeviceIOCallback (const_cast<const float**> (inputBuffers), numInputBuffers,
|
|
|
|
outputBuffers, numOutputBuffers, bufferSize);
|
|
|
|
else
|
|
|
@@ -1171,16 +1268,18 @@ public: |
|
|
|
|
|
|
|
if (outputDevice != nullptr)
|
|
|
|
{
|
|
|
|
outputDevice->copyBuffers (const_cast<const float**> (outputBuffers), numOutputBuffers, bufferSize, *this);
|
|
|
|
// Note that this function is handed the input device so it can check for the event and make sure
|
|
|
|
// the input reservoir is filled up correctly even when bufferSize > device actualBufferSize
|
|
|
|
outputDevice->copyBuffers (const_cast<const float**> (outputBuffers), numOutputBuffers, bufferSize, inputDevice, *this);
|
|
|
|
|
|
|
|
if (outputDevice->sampleRateHasChanged)
|
|
|
|
{
|
|
|
|
sampleRateChanged = true;
|
|
|
|
sampleRateHasChanged = true;
|
|
|
|
sampleRateChangedByOutput = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sampleRateChanged)
|
|
|
|
if (sampleRateHasChanged)
|
|
|
|
{
|
|
|
|
triggerAsyncUpdate();
|
|
|
|
break; // Quit the thread... will restart it later!
|
|
|
|