- statistics for Android Oboe backend
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "../helper/HL_String.h"
|
#include "../helper/HL_String.h"
|
||||||
#include "../helper/HL_Time.h"
|
#include "../helper/HL_Time.h"
|
||||||
@@ -141,6 +142,9 @@ AndroidOutputDevice::AndroidOutputDevice(int devId)
|
|||||||
AndroidOutputDevice::~AndroidOutputDevice()
|
AndroidOutputDevice::~AndroidOutputDevice()
|
||||||
{
|
{
|
||||||
ICELogDebug(<< "Deleting AndroidOutputDevice.");
|
ICELogDebug(<< "Deleting AndroidOutputDevice.");
|
||||||
|
// Mark shutdown before closing so a disconnect callback racing with teardown
|
||||||
|
// does not resurrect the stream via onErrorAfterClose()'s restart.
|
||||||
|
mInShutdown = true;
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +155,7 @@ bool AndroidOutputDevice::open()
|
|||||||
if (mActive)
|
if (mActive)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
mInShutdown = false;
|
||||||
mRequestedFrames = 0;
|
mRequestedFrames = 0;
|
||||||
mStartTime = 0.0;
|
mStartTime = 0.0;
|
||||||
mEndTime = 0.0;
|
mEndTime = 0.0;
|
||||||
@@ -161,6 +166,10 @@ bool AndroidOutputDevice::open()
|
|||||||
builder.setSharingMode(oboe::SharingMode::Exclusive);
|
builder.setSharingMode(oboe::SharingMode::Exclusive);
|
||||||
builder.setFormat(oboe::AudioFormat::I16);
|
builder.setFormat(oboe::AudioFormat::I16);
|
||||||
builder.setChannelCount(oboe::ChannelCount::Mono);
|
builder.setChannelCount(oboe::ChannelCount::Mono);
|
||||||
|
// Route through the platform voice-call path: correct device selection/volume
|
||||||
|
// and platform voice tuning for a softphone.
|
||||||
|
builder.setUsage(oboe::Usage::VoiceCommunication);
|
||||||
|
builder.setContentType(oboe::ContentType::Speech);
|
||||||
// builder.setDataCallback(this);
|
// builder.setDataCallback(this);
|
||||||
builder.setCallback(this);
|
builder.setCallback(this);
|
||||||
//builder.setErrorCallback(this)
|
//builder.setErrorCallback(this)
|
||||||
@@ -170,7 +179,7 @@ bool AndroidOutputDevice::open()
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
mDeviceRate = mPlayingStream->getSampleRate();
|
mDeviceRate = mPlayingStream->getSampleRate();
|
||||||
ICELogInfo(<< "Input Opened with rate " << mDeviceRate);
|
ICELogInfo(<< "Output opened with rate " << mDeviceRate);
|
||||||
mActive = true;
|
mActive = true;
|
||||||
|
|
||||||
rescode = mPlayingStream->requestStart();
|
rescode = mPlayingStream->requestStart();
|
||||||
@@ -178,7 +187,18 @@ bool AndroidOutputDevice::open()
|
|||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
mActive = false;
|
mActive = false;
|
||||||
|
return mActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Latch the burst size and start from a two-burst buffer. onAudioReady() grows
|
||||||
|
// this on XRuns (up to a cap) so we keep low latency when the device can sustain
|
||||||
|
// it and trade a little latency for glitch-free playback when it can't.
|
||||||
|
mBurstFrames = mPlayingStream->getFramesPerBurst();
|
||||||
|
mXRunLast = 0;
|
||||||
|
mHeartbeatLast = 0.0f;
|
||||||
|
if (mBurstFrames > 0)
|
||||||
|
mPlayingStream->setBufferSizeInFrames(mBurstFrames * 2);
|
||||||
|
|
||||||
return mActive;
|
return mActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,14 +252,50 @@ oboe::DataCallbackResult AndroidOutputDevice::onAudioReady(oboe::AudioStream *au
|
|||||||
}
|
}
|
||||||
mRequestedFrames += numFrames;
|
mRequestedFrames += numFrames;
|
||||||
|
|
||||||
|
// Adaptive buffer sizing: on new XRuns (device-side underruns, i.e. we missed a
|
||||||
|
// callback deadline), grow the buffer one burst at a time up to a cap. Both calls
|
||||||
|
// are documented as safe from within the data callback.
|
||||||
|
auto xrun = audioStream->getXRunCount();
|
||||||
|
if (xrun && xrun.value() > mXRunLast)
|
||||||
|
{
|
||||||
|
mXRunLast = xrun.value();
|
||||||
|
if (mBurstFrames > 0)
|
||||||
|
{
|
||||||
|
int32_t cap = mBurstFrames * 8;
|
||||||
|
int32_t cur = audioStream->getBufferSizeInFrames();
|
||||||
|
int32_t next = std::min(cur + mBurstFrames, cap);
|
||||||
|
if (next > cur)
|
||||||
|
audioStream->setBufferSizeInFrames(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device-side heartbeat (Step 0): surfaces XRuns/buffer growth so device glitches
|
||||||
|
// can be told apart from network/jitter impairments.
|
||||||
|
float t = now_ms();
|
||||||
|
if (mHeartbeatLast == 0.0f)
|
||||||
|
mHeartbeatLast = t;
|
||||||
|
else if (t - mHeartbeatLast >= 5000.0f)
|
||||||
|
{
|
||||||
|
ICELogInfo(<< "[spk-heartbeat] xruns=" << mXRunLast
|
||||||
|
<< " bufFrames=" << audioStream->getBufferSizeInFrames()
|
||||||
|
<< " burst=" << mBurstFrames
|
||||||
|
<< " rate=" << mDeviceRate);
|
||||||
|
mHeartbeatLast = t;
|
||||||
|
}
|
||||||
|
|
||||||
return oboe::DataCallbackResult::Continue;
|
return oboe::DataCallbackResult::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - special case https://github.com/google/oboe/blob/master/docs/notes/disconnect.md
|
// Disconnect recovery: on a route change (headset/BT plug/unplug) AAudio tears the
|
||||||
|
// stream down and calls this on its own thread after the stream is closed. Rebuild
|
||||||
|
// on the new default route so audio doesn't silently die mid-call.
|
||||||
|
// See https://github.com/google/oboe/blob/master/docs/notes/disconnect.md
|
||||||
void AndroidOutputDevice::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result) {
|
void AndroidOutputDevice::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result) {
|
||||||
if (result == oboe::Result::ErrorDisconnected) {
|
if (result == oboe::Result::ErrorDisconnected && !mInShutdown) {
|
||||||
// LOGI("Restarting AudioStream after disconnect");
|
ICELogInfo(<< "Output stream disconnected; restarting on the new route");
|
||||||
// soundEngine.restart(); // please check oboe samples for soundEngine.restart(); call
|
// close() and open() each take mMutex internally; this callback holds none.
|
||||||
|
close();
|
||||||
|
open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // TARGET_ANDROID
|
#endif // TARGET_ANDROID
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ class AndroidOutputDevice: public OutputDevice, public oboe::AudioStreamCallback
|
|||||||
bool mInShutdown = false;
|
bool mInShutdown = false;
|
||||||
bool mActive = false;
|
bool mActive = false;
|
||||||
|
|
||||||
|
// Adaptive buffer sizing + device-side diagnostics (Step 0 / #5).
|
||||||
|
int32_t mBurstFrames = 0; // frames per burst, latched at open()
|
||||||
|
int32_t mXRunLast = 0; // last observed cumulative XRun count
|
||||||
|
float mHeartbeatLast = 0.0f; // now_ms() of last device heartbeat log
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
float mRequestedFrames = 0.0, mStartTime = 0.0, mEndTime = 0.0;
|
float mRequestedFrames = 0.0, mStartTime = 0.0, mEndTime = 0.0;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user