mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added getXRunCount to AudioIODevice as a way to get Xrun counts from underlying hardware
This commit is contained in:
parent
0b6f02a29e
commit
8bb64a5ddc
12 changed files with 208 additions and 23 deletions
|
|
@ -33,6 +33,7 @@ AudioIODevice::~AudioIODevice() {}
|
|||
void AudioIODeviceCallback::audioDeviceError (const String&) {}
|
||||
bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; }
|
||||
bool AudioIODevice::hasControlPanel() const { return false; }
|
||||
int AudioIODevice::getXRunCount() const noexcept { return -1; }
|
||||
|
||||
bool AudioIODevice::showControlPanel()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -293,6 +293,19 @@ public:
|
|||
*/
|
||||
virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of under- or over runs reported by the OS since
|
||||
playback/recording has started.
|
||||
|
||||
This number may be different than determining the Xrun count manually (by
|
||||
measuring the time spent in the audio callback) as the OS may be doing
|
||||
some buffering internally - especially on mobile devices.
|
||||
|
||||
Returns -1 if playback/recording has not started yet or if getting the underrun
|
||||
count is not supported for this device (Android SDK 23 and lower).
|
||||
*/
|
||||
virtual int getXRunCount() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
protected:
|
||||
/** Creates a device, setting its name and type member variables. */
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ namespace juce
|
|||
METHOD (release, "release", "()V") \
|
||||
METHOD (flush, "flush", "()V") \
|
||||
METHOD (write, "write", "([SII)I") \
|
||||
METHOD (getUnderrunCount, "getUnderrunCount", "()I") \
|
||||
|
||||
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
|
@ -281,6 +282,14 @@ public:
|
|||
String getLastError() override { return lastError; }
|
||||
bool isPlaying() override { return isRunning && callback != 0; }
|
||||
|
||||
int getXRunCount() const noexcept override
|
||||
{
|
||||
if (outputDevice != nullptr)
|
||||
return getEnv()->CallIntMethod (outputDevice, AudioTrack.getUnderrunCount);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void start (AudioIODeviceCallback* newCallback) override
|
||||
{
|
||||
if (isRunning && callback != newCallback)
|
||||
|
|
|
|||
|
|
@ -291,12 +291,32 @@ public:
|
|||
nextBlock (0), numBlocksOut (0)
|
||||
{}
|
||||
|
||||
~OpenSLQueueRunner()
|
||||
{
|
||||
if (config != nullptr && javaProxy != nullptr)
|
||||
{
|
||||
javaProxy.clear();
|
||||
(*config)->ReleaseJavaProxy (config, SL_ANDROID_JAVA_PROXY_ROUTING);
|
||||
}
|
||||
}
|
||||
|
||||
bool init()
|
||||
{
|
||||
runner = crtp().createPlayerOrRecorder();
|
||||
if (runner == nullptr)
|
||||
return false;
|
||||
|
||||
// may return nullptr on some platforms - that's ok
|
||||
config = SlRef<SLAndroidConfigurationItf_>::cast (runner);
|
||||
if (config != nullptr)
|
||||
{
|
||||
jobject audioRoutingJni;
|
||||
auto status = (*config)->AcquireJavaProxy (config, SL_ANDROID_JAVA_PROXY_ROUTING, &audioRoutingJni);
|
||||
|
||||
if (status == SL_RESULT_SUCCESS && audioRoutingJni != 0)
|
||||
javaProxy = GlobalRef (audioRoutingJni);
|
||||
}
|
||||
|
||||
queue = SlRef<SLAndroidSimpleBufferQueueItf_>::cast (runner);
|
||||
if (queue == nullptr)
|
||||
return false;
|
||||
|
|
@ -347,6 +367,8 @@ public:
|
|||
|
||||
SlRef<RunnerObjectType> runner;
|
||||
SlRef<SLAndroidSimpleBufferQueueItf_> queue;
|
||||
SlRef<SLAndroidConfigurationItf_> config;
|
||||
GlobalRef javaProxy;
|
||||
|
||||
int numChannels;
|
||||
|
||||
|
|
@ -379,12 +401,12 @@ public:
|
|||
SLDataSource source = {&queueLocator, &dataFormat};
|
||||
SLDataSink sink = {&outputMix, nullptr};
|
||||
|
||||
SLInterfaceID queueInterfaces[] = { &IntfIID<SLAndroidSimpleBufferQueueItf_>::iid };
|
||||
SLboolean trueFlag = SL_BOOLEAN_TRUE;
|
||||
SLInterfaceID queueInterfaces[] = { &IntfIID<SLAndroidSimpleBufferQueueItf_>::iid, &IntfIID<SLAndroidConfigurationItf_>::iid };
|
||||
SLboolean interfaceRequired[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE};
|
||||
|
||||
SLObjectItf obj = nullptr;
|
||||
|
||||
SLresult status = (*Base::owner.engine)->CreateAudioPlayer (Base::owner.engine, &obj, &source, &sink, 1, queueInterfaces, &trueFlag);
|
||||
SLresult status = (*Base::owner.engine)->CreateAudioPlayer (Base::owner.engine, &obj, &source, &sink, 2, queueInterfaces, interfaceRequired);
|
||||
if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
|
||||
{
|
||||
if (obj != nullptr)
|
||||
|
|
@ -437,15 +459,12 @@ public:
|
|||
|
||||
SlRef<SLRecordItf_> recorder = SlRef<SLRecordItf_>::cast (SlObjectRef (obj));
|
||||
|
||||
// may return nullptr on some platforms - that's ok
|
||||
config = SlRef<SLAndroidConfigurationItf_>::cast (recorder);
|
||||
|
||||
return recorder;
|
||||
}
|
||||
|
||||
bool setAudioPreprocessingEnabled (bool shouldEnable)
|
||||
{
|
||||
if (config != nullptr)
|
||||
if (Base::config != nullptr)
|
||||
{
|
||||
const bool supportsUnprocessed = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 25);
|
||||
const SLuint32 recordingPresetValue
|
||||
|
|
@ -453,8 +472,8 @@ public:
|
|||
: (supportsUnprocessed ? SL_ANDROID_RECORDING_PRESET_UNPROCESSED
|
||||
: SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION));
|
||||
|
||||
SLresult status = (*config)->SetConfiguration (config, SL_ANDROID_KEY_RECORDING_PRESET,
|
||||
&recordingPresetValue, sizeof (recordingPresetValue));
|
||||
SLresult status = (*Base::config)->SetConfiguration (Base::config, SL_ANDROID_KEY_RECORDING_PRESET,
|
||||
&recordingPresetValue, sizeof (recordingPresetValue));
|
||||
|
||||
return (status == SL_RESULT_SUCCESS);
|
||||
}
|
||||
|
|
@ -463,8 +482,6 @@ public:
|
|||
}
|
||||
|
||||
void setState (bool running) { (*Base::runner)->SetRecordState (Base::runner, running ? SL_RECORDSTATE_RECORDING : SL_RECORDSTATE_STOPPED); }
|
||||
|
||||
SlRef<SLAndroidConfigurationItf_> config;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -521,6 +538,7 @@ public:
|
|||
virtual void stop() { running = false; }
|
||||
virtual bool setAudioPreprocessingEnabled (bool shouldEnable) = 0;
|
||||
virtual bool supportsFloatingPoint() const noexcept = 0;
|
||||
virtual int getXRunCount() const noexcept = 0;
|
||||
|
||||
void setCallback (AudioIODeviceCallback* callbackToUse)
|
||||
{
|
||||
|
|
@ -678,6 +696,14 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
int getXRunCount() const noexcept override
|
||||
{
|
||||
if (player != nullptr && player->javaProxy != nullptr)
|
||||
return getEnv()->CallIntMethod (player->javaProxy, AudioTrack.getUnderrunCount);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool supportsFloatingPoint() const noexcept override { return (BufferHelpers<T>::isFloatingPoint != 0); }
|
||||
|
||||
void doSomeWorkOnAudioThread()
|
||||
|
|
@ -892,6 +918,7 @@ public:
|
|||
BigInteger getActiveInputChannels() const override { return activeInputChans; }
|
||||
String getLastError() override { return lastError; }
|
||||
bool isPlaying() override { return callback != nullptr; }
|
||||
int getXRunCount() const noexcept override { return (session != nullptr ? session->getXRunCount() : -1); }
|
||||
|
||||
int getDefaultBufferSize() override
|
||||
{
|
||||
|
|
|
|||
|
|
@ -313,6 +313,9 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead,
|
|||
{
|
||||
close();
|
||||
|
||||
firstHostTime = true;
|
||||
lastNumFrames = 0;
|
||||
xrun = 0;
|
||||
lastError.clear();
|
||||
preferredBufferSize = bufferSize <= 0 ? defaultBufferSize : bufferSize;
|
||||
|
||||
|
|
@ -714,6 +717,8 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead,
|
|||
{
|
||||
OSStatus err = noErr;
|
||||
|
||||
recordXruns (time, numFrames);
|
||||
|
||||
if (audioInputIsAvailable && numInputChannels > 0)
|
||||
err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
|
||||
|
||||
|
|
@ -799,6 +804,26 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead,
|
|||
updateSampleRateAndAudioInput();
|
||||
}
|
||||
|
||||
void recordXruns (const AudioTimeStamp* time, UInt32 numFrames)
|
||||
{
|
||||
if (time != nullptr && (time->mFlags & kAudioTimeStampSampleTimeValid) != 0)
|
||||
{
|
||||
if (! firstHostTime)
|
||||
{
|
||||
if ((time->mSampleTime - lastSampleTime) != lastNumFrames)
|
||||
xrun++;
|
||||
}
|
||||
else
|
||||
firstHostTime = false;
|
||||
|
||||
lastSampleTime = time->mSampleTime;
|
||||
}
|
||||
else
|
||||
firstHostTime = true;
|
||||
|
||||
lastNumFrames = numFrames;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
|
||||
UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
|
||||
|
|
@ -1063,6 +1088,11 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead,
|
|||
float* outputChannels[3];
|
||||
bool monoInputChannelNumber, monoOutputChannelNumber;
|
||||
|
||||
bool firstHostTime;
|
||||
Float64 lastSampleTime;
|
||||
unsigned int lastNumFrames;
|
||||
int xrun;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Pimpl)
|
||||
};
|
||||
|
||||
|
|
@ -1108,6 +1138,7 @@ BigInteger iOSAudioIODevice::getActiveInputChannels() const { return pim
|
|||
|
||||
int iOSAudioIODevice::getOutputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].outputLatency); }
|
||||
int iOSAudioIODevice::getInputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].inputLatency); }
|
||||
int iOSAudioIODevice::getXRunCount() const noexcept { return pimpl->xrun; }
|
||||
|
||||
void iOSAudioIODevice::setMidiMessageCollector (MidiMessageCollector* collector) { pimpl->messageCollector = collector; }
|
||||
AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl; }
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ public:
|
|||
int getOutputLatencyInSamples() override;
|
||||
int getInputLatencyInSamples() override;
|
||||
|
||||
int getXRunCount() const noexcept override;
|
||||
|
||||
//==============================================================================
|
||||
void setMidiMessageCollector (MidiMessageCollector*);
|
||||
AudioPlayHead* getAudioPlayHead() const;
|
||||
|
|
|
|||
|
|
@ -333,8 +333,14 @@ public:
|
|||
numDone = snd_pcm_writen (handle, (void**) data, (snd_pcm_uframes_t) numSamples);
|
||||
}
|
||||
|
||||
if (numDone < 0 && JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) numDone, 1 /* silent */)))
|
||||
return false;
|
||||
if (numDone < 0)
|
||||
{
|
||||
if (numDone == -(EPIPE))
|
||||
underrunCount++;
|
||||
|
||||
if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) numDone, 1 /* silent */)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numDone < numSamples)
|
||||
JUCE_ALSA_LOG ("Did not write all samples: numDone: " << numDone << ", numSamples: " << numSamples);
|
||||
|
|
@ -354,8 +360,15 @@ public:
|
|||
|
||||
snd_pcm_sframes_t num = snd_pcm_readi (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples);
|
||||
|
||||
if (num < 0 && JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
|
||||
return false;
|
||||
if (num < 0)
|
||||
{
|
||||
if (num == -(EPIPE))
|
||||
overrunCount++;
|
||||
|
||||
if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (num < numSamples)
|
||||
JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples);
|
||||
|
|
@ -367,8 +380,14 @@ public:
|
|||
{
|
||||
snd_pcm_sframes_t num = snd_pcm_readn (handle, (void**) data, (snd_pcm_uframes_t) numSamples);
|
||||
|
||||
if (num < 0 && JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
|
||||
return false;
|
||||
if (num < 0)
|
||||
{
|
||||
if (num == -(EPIPE))
|
||||
overrunCount++;
|
||||
|
||||
if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num < numSamples)
|
||||
JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples);
|
||||
|
|
@ -384,6 +403,7 @@ public:
|
|||
snd_pcm_t* handle;
|
||||
String error;
|
||||
int bitDepth, numChannelsRunning, latency;
|
||||
int underrunCount = 0, overrunCount = 0;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
@ -749,6 +769,19 @@ public:
|
|||
return 16;
|
||||
}
|
||||
|
||||
int getXRunCount() const noexcept
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if (outputDevice != nullptr)
|
||||
result += outputDevice->underrunCount;
|
||||
|
||||
if (inputDevice != nullptr)
|
||||
result += inputDevice->overrunCount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String error;
|
||||
double sampleRate;
|
||||
|
|
@ -908,6 +941,8 @@ public:
|
|||
int getOutputLatencyInSamples() override { return internal.outputLatency; }
|
||||
int getInputLatencyInSamples() override { return internal.inputLatency; }
|
||||
|
||||
int getXRunCount() const noexcept override { return internal.getXRunCount(); }
|
||||
|
||||
void start (AudioIODeviceCallback* callback) override
|
||||
{
|
||||
if (! isOpen_)
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t*
|
|||
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id));
|
||||
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port));
|
||||
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name));
|
||||
JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg));
|
||||
|
||||
#if JUCE_DEBUG
|
||||
#define JACK_LOGGING_ENABLED 1
|
||||
|
|
@ -258,9 +259,11 @@ public:
|
|||
lastError.clear();
|
||||
close();
|
||||
|
||||
xruns = 0;
|
||||
juce::jack_set_process_callback (client, processCallback, this);
|
||||
juce::jack_set_port_connect_callback (client, portConnectCallback, this);
|
||||
juce::jack_on_shutdown (client, shutdownCallback, this);
|
||||
juce::jack_set_xrun_callback (client, xrunCallback, this);
|
||||
juce::jack_activate (client);
|
||||
deviceIsOpen = true;
|
||||
|
||||
|
|
@ -302,6 +305,8 @@ public:
|
|||
if (client != nullptr)
|
||||
{
|
||||
juce::jack_deactivate (client);
|
||||
|
||||
juce::jack_set_xrun_callback (client, xrunCallback, nullptr);
|
||||
juce::jack_set_process_callback (client, processCallback, nullptr);
|
||||
juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr);
|
||||
juce::jack_on_shutdown (client, shutdownCallback, nullptr);
|
||||
|
|
@ -338,6 +343,7 @@ public:
|
|||
bool isPlaying() override { return callback != nullptr; }
|
||||
int getCurrentBitDepth() override { return 32; }
|
||||
String getLastError() override { return lastError; }
|
||||
int getXRunCount() const noexcept override { return xruns; }
|
||||
|
||||
BigInteger getActiveOutputChannels() const override { return activeOutputChannels; }
|
||||
BigInteger getActiveInputChannels() const override { return activeInputChannels; }
|
||||
|
|
@ -408,6 +414,14 @@ private:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int xrunCallback (void* callbackArgument)
|
||||
{
|
||||
if (callbackArgument != nullptr)
|
||||
((JackAudioIODevice*) callbackArgument)->xruns++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void updateActivePorts()
|
||||
{
|
||||
BigInteger newOutputChannels, newInputChannels;
|
||||
|
|
@ -477,6 +491,8 @@ private:
|
|||
int totalNumberOfOutputChannels;
|
||||
Array<void*> inputPorts, outputPorts;
|
||||
BigInteger activeInputChannels, activeOutputChannels;
|
||||
|
||||
int xruns;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -796,6 +796,7 @@ public:
|
|||
int inputLatency = 0;
|
||||
int outputLatency = 0;
|
||||
int bitDepth = 32;
|
||||
int xruns = 0;
|
||||
BigInteger activeInputChans, activeOutputChans;
|
||||
StringArray inChanNames, outChanNames;
|
||||
Array<double> sampleRates;
|
||||
|
|
@ -837,6 +838,9 @@ private:
|
|||
|
||||
switch (pa->mSelector)
|
||||
{
|
||||
case kAudioDeviceProcessorOverload:
|
||||
intern->xruns++;
|
||||
break;
|
||||
case kAudioDevicePropertyBufferSize:
|
||||
case kAudioDevicePropertyBufferFrameSize:
|
||||
case kAudioDevicePropertyNominalSampleRate:
|
||||
|
|
@ -962,6 +966,7 @@ public:
|
|||
double getCurrentSampleRate() override { return internal->getSampleRate(); }
|
||||
int getCurrentBitDepth() override { return internal->bitDepth; }
|
||||
int getCurrentBufferSizeSamples() override { return internal->getBufferSize(); }
|
||||
int getXRunCount() const noexcept override { return internal->xruns; }
|
||||
|
||||
int getDefaultBufferSize() override
|
||||
{
|
||||
|
|
@ -982,6 +987,7 @@ public:
|
|||
{
|
||||
isOpen_ = true;
|
||||
|
||||
internal->xruns = 0;
|
||||
if (bufferSizeSamples <= 0)
|
||||
bufferSizeSamples = getDefaultBufferSize();
|
||||
|
||||
|
|
|
|||
|
|
@ -409,6 +409,8 @@ public:
|
|||
Array<int> getAvailableBufferSizes() override { return bufferSizes; }
|
||||
int getDefaultBufferSize() override { return preferredBufferSize; }
|
||||
|
||||
int getXRunCount() const noexcept override { return xruns; }
|
||||
|
||||
String open (const BigInteger& inputChannels,
|
||||
const BigInteger& outputChannels,
|
||||
double sr, int bufferSizeSamples) override
|
||||
|
|
@ -464,6 +466,9 @@ public:
|
|||
err = asioObject->getChannels (&totalNumInputChans, &totalNumOutputChans);
|
||||
jassert (err == ASE_OK);
|
||||
|
||||
if (asioObject->future (kAsioCanReportOverload, nullptr) != ASE_OK)
|
||||
xruns = -1;
|
||||
|
||||
inBuffers.calloc (totalNumInputChans + 8);
|
||||
outBuffers.calloc (totalNumOutputChans + 8);
|
||||
|
||||
|
|
@ -789,6 +794,7 @@ private:
|
|||
bool volatile littleEndian, postOutput, needToReset;
|
||||
bool volatile insideControlPanelModalLoop;
|
||||
bool volatile shouldUsePreferredSize;
|
||||
int xruns = 0;
|
||||
|
||||
//==============================================================================
|
||||
static String convertASIOString (char* const text, int length)
|
||||
|
|
@ -1180,6 +1186,7 @@ private:
|
|||
totalNumOutputChans = 0;
|
||||
numActiveInputChans = 0;
|
||||
numActiveOutputChans = 0;
|
||||
xruns = 0;
|
||||
currentCallback = nullptr;
|
||||
|
||||
error.clear();
|
||||
|
|
@ -1349,7 +1356,7 @@ private:
|
|||
{
|
||||
case kAsioSelectorSupported:
|
||||
if (value == kAsioResetRequest || value == kAsioEngineVersion || value == kAsioResyncRequest
|
||||
|| value == kAsioLatenciesChanged || value == kAsioSupportsInputMonitor)
|
||||
|| value == kAsioLatenciesChanged || value == kAsioSupportsInputMonitor || value == kAsioOverload)
|
||||
return 1;
|
||||
break;
|
||||
|
||||
|
|
@ -1360,7 +1367,8 @@ private:
|
|||
case kAsioEngineVersion: return 2;
|
||||
|
||||
case kAsioSupportsTimeInfo:
|
||||
case kAsioSupportsTimeCode: return 0;
|
||||
case kAsioSupportsTimeCode: return 0;
|
||||
case kAsioOverload: xruns++; return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -266,6 +266,10 @@ public:
|
|||
pDirectSound = nullptr;
|
||||
pOutputBuffer = nullptr;
|
||||
writeOffset = 0;
|
||||
xruns = 0;
|
||||
|
||||
firstPlayTime = true;
|
||||
lastPlayTime = 0;
|
||||
|
||||
String error;
|
||||
HRESULT hr = E_NOINTERFACE;
|
||||
|
|
@ -276,6 +280,7 @@ public:
|
|||
if (SUCCEEDED (hr))
|
||||
{
|
||||
bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15;
|
||||
ticksPerBuffer = bytesPerBuffer * Time::getHighResolutionTicksPerSecond() / (sampleRate * (bitDepth >> 2));
|
||||
totalBytesPerBuffer = (blocksPerOverallBuffer * bytesPerBuffer) & ~15;
|
||||
const int numChannels = 2;
|
||||
|
||||
|
|
@ -397,6 +402,18 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
auto currentPlayTime = Time::getHighResolutionTicks ();
|
||||
if (! firstPlayTime)
|
||||
{
|
||||
auto expectedBuffers = (currentPlayTime - lastPlayTime) / ticksPerBuffer;
|
||||
|
||||
playCursor += static_cast<DWORD> (expectedBuffers * bytesPerBuffer);
|
||||
}
|
||||
else
|
||||
firstPlayTime = false;
|
||||
|
||||
lastPlayTime = currentPlayTime;
|
||||
|
||||
int playWriteGap = (int) (writeCursor - playCursor);
|
||||
if (playWriteGap < 0)
|
||||
playWriteGap += totalBytesPerBuffer;
|
||||
|
|
@ -409,6 +426,9 @@ public:
|
|||
{
|
||||
writeOffset = writeCursor;
|
||||
bytesEmpty = totalBytesPerBuffer - playWriteGap;
|
||||
|
||||
// buffer underflow
|
||||
xruns++;
|
||||
}
|
||||
|
||||
if (bytesEmpty >= bytesPerBuffer)
|
||||
|
|
@ -480,7 +500,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
int bitDepth;
|
||||
int bitDepth, xruns;
|
||||
bool doneFlag;
|
||||
|
||||
private:
|
||||
|
|
@ -496,6 +516,9 @@ private:
|
|||
int totalBytesPerBuffer, bytesPerBuffer;
|
||||
unsigned int lastPlayCursor;
|
||||
|
||||
bool firstPlayTime;
|
||||
int64 lastPlayTime, ticksPerBuffer;
|
||||
|
||||
static inline int convertInputValues (const float l, const float r) noexcept
|
||||
{
|
||||
return jlimit (-32768, 32767, roundToInt (32767.0f * r)) << 16
|
||||
|
|
@ -854,6 +877,11 @@ public:
|
|||
bool isPlaying() override { return isStarted && isOpen_ && isThreadRunning(); }
|
||||
String getLastError() override { return lastError; }
|
||||
|
||||
int getXRunCount () const noexcept override
|
||||
{
|
||||
return (outChans[0] != nullptr ? outChans[0]->xruns : -1);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray inChannels, outChannels;
|
||||
int outputDeviceIndex, inputDeviceIndex;
|
||||
|
|
|
|||
|
|
@ -128,7 +128,12 @@ enum EDataFlow
|
|||
|
||||
enum
|
||||
{
|
||||
DEVICE_STATE_ACTIVE = 1,
|
||||
DEVICE_STATE_ACTIVE = 1
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 1,
|
||||
AUDCLNT_BUFFERFLAGS_SILENT = 2
|
||||
};
|
||||
|
||||
|
|
@ -742,6 +747,7 @@ public:
|
|||
reservoirMask = nextPowerOfTwo (reservoirSize) - 1;
|
||||
reservoir.setSize ((reservoirMask + 1) * bytesPerFrame, true);
|
||||
reservoirReadPos = reservoirWritePos = 0;
|
||||
xruns = 0;
|
||||
|
||||
if (! check (client->Start()))
|
||||
return false;
|
||||
|
|
@ -774,6 +780,9 @@ public:
|
|||
|
||||
while (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)) && numSamplesAvailable > 0)
|
||||
{
|
||||
if ((flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0)
|
||||
xruns++;
|
||||
|
||||
int samplesLeft = (int) numSamplesAvailable;
|
||||
|
||||
while (samplesLeft > 0)
|
||||
|
|
@ -838,7 +847,7 @@ public:
|
|||
|
||||
ComSmartPtr<IAudioCaptureClient> captureClient;
|
||||
MemoryBlock reservoir;
|
||||
int reservoirSize, reservoirMask;
|
||||
int reservoirSize, reservoirMask, xruns;
|
||||
volatile int reservoirReadPos, reservoirWritePos;
|
||||
|
||||
ScopedPointer<AudioData::Converter> converter;
|
||||
|
|
@ -1075,7 +1084,7 @@ public:
|
|||
BigInteger getActiveOutputChannels() const override { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); }
|
||||
BigInteger getActiveInputChannels() const override { return inputDevice != nullptr ? inputDevice->channels : BigInteger(); }
|
||||
String getLastError() override { return lastError; }
|
||||
|
||||
int getXRunCount () const noexcept override { return inputDevice != nullptr ? inputDevice->xruns : -1; }
|
||||
|
||||
String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
|
||||
double sampleRate, int bufferSizeSamples) override
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue