1
0
Fork 0
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:
hogliux 2017-09-21 15:49:02 +01:00
parent 0b6f02a29e
commit 8bb64a5ddc
12 changed files with 208 additions and 23 deletions

View file

@ -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()
{

View file

@ -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. */

View file

@ -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)

View file

@ -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
{

View file

@ -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; }

View file

@ -62,6 +62,8 @@ public:
int getOutputLatencyInSamples() override;
int getInputLatencyInSamples() override;
int getXRunCount() const noexcept override;
//==============================================================================
void setMidiMessageCollector (MidiMessageCollector*);
AudioPlayHead* getAudioPlayHead() const;

View file

@ -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_)

View file

@ -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;
};

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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