|
|
@@ -124,6 +124,12 @@ struct OboeAudioIODeviceBufferHelpers<float> |
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <typename Type> |
|
|
|
static String getOboeString (const Type& value) |
|
|
|
{ |
|
|
|
return String (oboe::convertToText (value)); |
|
|
|
} |
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
class OboeAudioIODevice : public AudioIODevice
|
|
|
@@ -438,8 +444,8 @@ private: |
|
|
|
|
|
|
|
~OboeStream()
|
|
|
|
{
|
|
|
|
// AudioStreamCallback can only be deleted when stream has been closed
|
|
|
|
close();
|
|
|
|
delete stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool openedOk() const noexcept
|
|
|
@@ -458,40 +464,47 @@ private: |
|
|
|
int64 timeoutNanos = 1000 * oboe::kNanosPerMillisecond;
|
|
|
|
|
|
|
|
auto startResult = stream->requestStart();
|
|
|
|
JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + String (oboe::convertToText (startResult)));
|
|
|
|
|
|
|
|
startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos);
|
|
|
|
JUCE_OBOE_LOG ("Starting Oboe stream with result: " + String (oboe::convertToText (startResult));
|
|
|
|
+ "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
|
|
|
|
+ "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
|
|
|
|
+ "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
|
|
|
|
+ "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
|
|
|
|
+ "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
|
|
|
|
+ "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
|
|
|
|
+ "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
|
|
|
|
+ "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
|
|
|
|
+ "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?"))
|
|
|
|
+ "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
|
|
|
|
+ "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
|
|
|
|
+ "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
|
|
|
|
+ "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency))
|
|
|
|
+ "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
|
|
|
|
JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + getOboeString (startResult));
|
|
|
|
|
|
|
|
startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos); |
|
|
|
|
|
|
|
JUCE_OBOE_LOG ("Starting Oboe stream with result: " + getOboeString (startResult);
|
|
|
|
+ "\nUses AAudio = " + String ((int) stream->usesAAudio())
|
|
|
|
+ "\nDirection = " + getOboeString (stream->getDirection())
|
|
|
|
+ "\nSharingMode = " + getOboeString (stream->getSharingMode())
|
|
|
|
+ "\nChannelCount = " + String (stream->getChannelCount())
|
|
|
|
+ "\nFormat = " + getOboeString (stream->getFormat())
|
|
|
|
+ "\nSampleRate = " + String (stream->getSampleRate())
|
|
|
|
+ "\nBufferSizeInFrames = " + String (stream->getBufferSizeInFrames())
|
|
|
|
+ "\nBufferCapacityInFrames = " + String (stream->getBufferCapacityInFrames())
|
|
|
|
+ "\nFramesPerBurst = " + String (stream->getFramesPerBurst())
|
|
|
|
+ "\nFramesPerCallback = " + String (stream->getFramesPerCallback())
|
|
|
|
+ "\nBytesPerFrame = " + String (stream->getBytesPerFrame())
|
|
|
|
+ "\nBytesPerSample = " + String (stream->getBytesPerSample())
|
|
|
|
+ "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency)
|
|
|
|
+ "\ngetDeviceId = " + String (stream->getDeviceId()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oboe::AudioStream* getNativeStream()
|
|
|
|
oboe::AudioStream* getNativeStream() const
|
|
|
|
{
|
|
|
|
jassert (openedOk());
|
|
|
|
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getXRunCount() const
|
|
|
|
{
|
|
|
|
if (auto xruns = stream->getXRunCount())
|
|
|
|
return xruns.value();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
if (stream != nullptr) |
|
|
|
{
|
|
|
|
auto count = stream->getXRunCount(); |
|
|
|
|
|
|
|
if (count)
|
|
|
|
return count.value(); |
|
|
|
|
|
|
|
JUCE_OBOE_LOG ("Failed to get Xrun count: " + getOboeString (count.error())); |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
@@ -501,39 +514,38 @@ private: |
|
|
|
int32 sampleRate, int32 bufferSize,
|
|
|
|
oboe::AudioStreamCallback* callback = nullptr)
|
|
|
|
{
|
|
|
|
oboe::DefaultStreamValues::FramesPerBurst = getDefaultFramesPerBurst();
|
|
|
|
|
|
|
|
oboe::AudioStreamBuilder builder;
|
|
|
|
|
|
|
|
if (deviceId != -1)
|
|
|
|
builder.setDeviceId (deviceId);
|
|
|
|
|
|
|
|
static int defaultFramesPerBurst = getDefaultFramesPerBurst();
|
|
|
|
|
|
|
|
// Note: Letting OS to choose the buffer capacity & frames per callback.
|
|
|
|
// Note: letting OS to choose the buffer capacity & frames per callback.
|
|
|
|
builder.setDirection (direction);
|
|
|
|
builder.setSharingMode (sharingMode);
|
|
|
|
builder.setChannelCount (channelCount);
|
|
|
|
builder.setFormat (format);
|
|
|
|
builder.setSampleRate (sampleRate);
|
|
|
|
builder.setFramesPerCallback ((int32) defaultFramesPerBurst);
|
|
|
|
builder.setPerformanceMode (oboe::PerformanceMode::LowLatency);
|
|
|
|
builder.setCallback (callback);
|
|
|
|
|
|
|
|
JUCE_OBOE_LOG (String ("Preparing Oboe stream with params:")
|
|
|
|
+ "\nAAudio supported = " + String (int (builder.isAAudioSupported()))
|
|
|
|
+ "\nAPI = " + String (oboe::convertToText (builder.getAudioApi()))
|
|
|
|
+ "\nAPI = " + getOboeString (builder.getAudioApi())
|
|
|
|
+ "\nDeviceId = " + String (deviceId)
|
|
|
|
+ "\nDirection = " + String (oboe::convertToText (direction))
|
|
|
|
+ "\nSharingMode = " + String (oboe::convertToText (sharingMode))
|
|
|
|
+ "\nDirection = " + getOboeString (direction)
|
|
|
|
+ "\nSharingMode = " + getOboeString (sharingMode)
|
|
|
|
+ "\nChannelCount = " + String (channelCount)
|
|
|
|
+ "\nFormat = " + String (oboe::convertToText (format))
|
|
|
|
+ "\nFormat = " + getOboeString (format)
|
|
|
|
+ "\nSampleRate = " + String (sampleRate)
|
|
|
|
+ "\nBufferSizeInFrames = " + String (bufferSize)
|
|
|
|
+ "\nFramesPerBurst = " + String (defaultFramesPerBurst)
|
|
|
|
+ "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency)));
|
|
|
|
+ "\nFramesPerBurst = " + String (oboe::DefaultStreamValues::FramesPerBurst)
|
|
|
|
+ "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency));
|
|
|
|
|
|
|
|
openResult = builder.openStream (&stream);
|
|
|
|
JUCE_OBOE_LOG ("Building Oboe stream with result: " + String (oboe::convertToText (openResult))
|
|
|
|
+ "\nStream state = " + (stream != nullptr ? String (oboe::convertToText (stream->getState())) : String ("?")));
|
|
|
|
JUCE_OBOE_LOG ("Building Oboe stream with result: " + getOboeString (openResult)
|
|
|
|
+ "\nStream state = " + (stream != nullptr ? getOboeString (stream->getState()) : String ("?")));
|
|
|
|
|
|
|
|
if (stream != nullptr)
|
|
|
|
stream->setBufferSizeInFrames (bufferSize);
|
|
|
@@ -541,10 +553,10 @@ private: |
|
|
|
JUCE_OBOE_LOG (String ("Stream details:")
|
|
|
|
+ "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
|
|
|
|
+ "\nDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?"))
|
|
|
|
+ "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
|
|
|
|
+ "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
|
|
|
|
+ "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?"))
|
|
|
|
+ "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
|
|
|
|
+ "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
|
|
|
|
+ "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
|
|
|
|
+ "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?"))
|
|
|
|
+ "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
|
|
|
|
+ "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
|
|
|
|
+ "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
|
|
|
@@ -552,7 +564,7 @@ private: |
|
|
|
+ "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
|
|
|
|
+ "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
|
|
|
|
+ "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
|
|
|
|
+ "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency)));
|
|
|
|
+ "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency));
|
|
|
|
}
|
|
|
|
|
|
|
|
void close()
|
|
|
@@ -560,27 +572,10 @@ private: |
|
|
|
if (stream != nullptr)
|
|
|
|
{
|
|
|
|
oboe::Result result = stream->close();
|
|
|
|
JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + String (oboe::convertToText (result)));
|
|
|
|
JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + getOboeString (result));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int getDefaultFramesPerBurst() const
|
|
|
|
{
|
|
|
|
// NB: this function only works for inbuilt speakers and headphones
|
|
|
|
auto* env = getEnv();
|
|
|
|
|
|
|
|
auto audioManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
|
|
|
|
JuceAppActivity.getSystemService,
|
|
|
|
javaString ("audio").get()));
|
|
|
|
|
|
|
|
auto propertyJavaString = javaString ("android.media.property.OUTPUT_FRAMES_PER_BUFFER");
|
|
|
|
|
|
|
|
auto framesPerBurstString = LocalRef<jstring> ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty,
|
|
|
|
propertyJavaString.get()));
|
|
|
|
|
|
|
|
return framesPerBurstString != 0 ? env->CallStaticIntMethod (JavaInteger, JavaInteger.parseInt, framesPerBurstString.get(), 10) : 192;
|
|
|
|
}
|
|
|
|
|
|
|
|
oboe::AudioStream* stream = nullptr;
|
|
|
|
oboe::Result openResult;
|
|
|
|
};
|
|
|
@@ -672,9 +667,7 @@ private: |
|
|
|
void checkStreamSetup (OboeStream* stream, int deviceId, int numChannels, int sampleRate,
|
|
|
|
int bufferSize, oboe::AudioFormat format)
|
|
|
|
{
|
|
|
|
auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr;
|
|
|
|
|
|
|
|
if (nativeStream != nullptr)
|
|
|
|
if (auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr)
|
|
|
|
{
|
|
|
|
ignoreUnused (deviceId, numChannels, sampleRate, bufferSize);
|
|
|
|
ignoreUnused (streamFormat, bitDepth);
|
|
|
@@ -739,7 +732,8 @@ private: |
|
|
|
|
|
|
|
outputStream->start();
|
|
|
|
|
|
|
|
checkIsOutputLatencyDetectionSupported();
|
|
|
|
isInputLatencyDetectionSupported = isLatencyDetectionSupported (inputStream.get());
|
|
|
|
isOutputLatencyDetectionSupported = isLatencyDetectionSupported (outputStream.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
void stop() override
|
|
|
@@ -754,19 +748,16 @@ private: |
|
|
|
}
|
|
|
|
|
|
|
|
int getOutputLatencyInSamples() override { return outputLatency; }
|
|
|
|
int getInputLatencyInSamples() override { return -1; }
|
|
|
|
int getInputLatencyInSamples() override { return inputLatency; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
void checkIsOutputLatencyDetectionSupported()
|
|
|
|
bool isLatencyDetectionSupported (OboeStream* stream)
|
|
|
|
{
|
|
|
|
if (! openedOk())
|
|
|
|
{
|
|
|
|
isOutputLatencyDetectionSupported = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (stream == nullptr || ! openedOk())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto result = outputStream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, 0, 0);
|
|
|
|
isOutputLatencyDetectionSupported = result != oboe::Result::ErrorUnimplemented;
|
|
|
|
auto result = stream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, 0, 0);
|
|
|
|
return result != oboe::Result::ErrorUnimplemented;
|
|
|
|
}
|
|
|
|
|
|
|
|
oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override
|
|
|
@@ -792,7 +783,7 @@ private: |
|
|
|
|
|
|
|
if (nativeInputStream->getFormat() != oboe::AudioFormat::I16 && nativeInputStream->getFormat() != oboe::AudioFormat::Float)
|
|
|
|
{
|
|
|
|
JUCE_OBOE_LOG ("Unsupported input stream audio format: " + String (oboe::convertToText (nativeInputStream->getFormat())));
|
|
|
|
JUCE_OBOE_LOG ("Unsupported input stream audio format: " + getOboeString (nativeInputStream->getFormat()));
|
|
|
|
jassertfalse;
|
|
|
|
return oboe::DataCallbackResult::Continue;
|
|
|
|
}
|
|
|
@@ -809,9 +800,11 @@ private: |
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Failed to read from input stream.
|
|
|
|
jassertfalse;
|
|
|
|
JUCE_OBOE_LOG ("Failed to read from input stream: " + getOboeString (result.error()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInputLatencyDetectionSupported)
|
|
|
|
inputLatency = getLatencyFor (*inputStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------
|
|
|
@@ -834,7 +827,7 @@ private: |
|
|
|
OboeAudioIODeviceBufferHelpers<SampleType>::convertToOboe (outputStreamSampleBuffer, static_cast<SampleType*> (audioData), numFrames);
|
|
|
|
|
|
|
|
if (isOutputLatencyDetectionSupported)
|
|
|
|
calculateOutputLatency();
|
|
|
|
outputLatency = getLatencyFor (*outputStream);
|
|
|
|
|
|
|
|
audioCallbackGuard.set (0);
|
|
|
|
}
|
|
|
@@ -847,10 +840,10 @@ private: |
|
|
|
ignoreUnused (stream);
|
|
|
|
|
|
|
|
JUCE_OBOE_LOG ("\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
|
|
|
|
+ "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
|
|
|
|
+ "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
|
|
|
|
+ "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?"))
|
|
|
|
+ "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
|
|
|
|
+ "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
|
|
|
|
+ "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
|
|
|
|
+ "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?"))
|
|
|
|
+ "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
|
|
|
|
+ "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
|
|
|
|
+ "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
|
|
|
@@ -858,34 +851,48 @@ private: |
|
|
|
+ "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
|
|
|
|
+ "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
|
|
|
|
+ "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
|
|
|
|
+ "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency))
|
|
|
|
+ "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency)
|
|
|
|
+ "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
|
|
|
|
}
|
|
|
|
|
|
|
|
void calculateOutputLatency()
|
|
|
|
int getLatencyFor (OboeStream& stream)
|
|
|
|
{
|
|
|
|
// Sadly, Oboe uses non-portable int64_t (a.k.a. long on LP64 and long long on LLP64)
|
|
|
|
int64_t lastWrittenAndPresentedFrameIndex = 0;
|
|
|
|
int64_t lastFramePresentationTimeNanos = 0;
|
|
|
|
auto& nativeStream = *stream.getNativeStream();
|
|
|
|
|
|
|
|
if (auto latency = nativeStream.calculateLatencyMillis())
|
|
|
|
return static_cast<int> ((latency.value() * sampleRate) / 1000);
|
|
|
|
|
|
|
|
// Get the time that a known audio frame was presented.
|
|
|
|
int64_t hardwareFrameIndex = 0;
|
|
|
|
int64_t hardwareFrameHardwareTime = 0;
|
|
|
|
|
|
|
|
auto result = outputStream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC,
|
|
|
|
&lastWrittenAndPresentedFrameIndex,
|
|
|
|
&lastFramePresentationTimeNanos);
|
|
|
|
auto result = nativeStream.getTimestamp (CLOCK_MONOTONIC,
|
|
|
|
&hardwareFrameIndex,
|
|
|
|
&hardwareFrameHardwareTime);
|
|
|
|
|
|
|
|
if (result != oboe::Result::OK)
|
|
|
|
return;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Get counter closest to the app.
|
|
|
|
const bool isOutput = nativeStream.getDirection() == oboe::Direction::Output;
|
|
|
|
const int64_t appFrameIndex = isOutput ? nativeStream.getFramesWritten() : nativeStream.getFramesRead();
|
|
|
|
|
|
|
|
int64_t currentNumFramesWritten = outputStream->getNativeStream()->getFramesWritten();
|
|
|
|
int64_t framesDelta = currentNumFramesWritten - lastWrittenAndPresentedFrameIndex;
|
|
|
|
int64_t timeDeltaNanos = framesDelta * oboe::kNanosPerSecond / sampleRate;
|
|
|
|
// Assume that the next frame will be processed at the current time
|
|
|
|
using namespace std::chrono;
|
|
|
|
int64_t appFrameAppTime = getCurrentTimeNanos();//duration_cast<nanoseconds> (steady_clock::now().time_since_epoch()).count();
|
|
|
|
int64_t appFrameAppTime2 = duration_cast<nanoseconds> (steady_clock::now().time_since_epoch()).count();
|
|
|
|
|
|
|
|
int64_t nextPresentationTimeNanos = lastFramePresentationTimeNanos + timeDeltaNanos;
|
|
|
|
int64_t nextFrameWriteTimeNanos = getCurrentTimeNanos();
|
|
|
|
// Calculate the number of frames between app and hardware
|
|
|
|
int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
|
|
|
|
|
|
|
|
if (nextFrameWriteTimeNanos < 0)
|
|
|
|
return;
|
|
|
|
// Calculate the time which the next frame will be or was presented
|
|
|
|
int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / sampleRate;
|
|
|
|
int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
|
|
|
|
|
|
|
|
outputLatency = (int) ((nextPresentationTimeNanos - nextFrameWriteTimeNanos) * sampleRate / oboe::kNanosPerSecond);
|
|
|
|
// Calculate latency as a difference in time between when the current frame is at the app
|
|
|
|
// and when it is at the hardware.
|
|
|
|
auto latencyNanos = isOutput ? (appFrameHardwareTime - appFrameAppTime) : (appFrameAppTime - appFrameHardwareTime);
|
|
|
|
return static_cast<int> ((latencyNanos * sampleRate) / oboe::kNanosPerSecond);
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t getCurrentTimeNanos()
|
|
|
@@ -900,19 +907,23 @@ private: |
|
|
|
|
|
|
|
void onErrorBeforeClose (oboe::AudioStream* stream, oboe::Result error) override
|
|
|
|
{
|
|
|
|
attachAndroidJNI();
|
|
|
|
|
|
|
|
// only output stream should be the master stream receiving callbacks
|
|
|
|
jassert (stream->getDirection() == oboe::Direction::Output);
|
|
|
|
|
|
|
|
JUCE_OBOE_LOG ("Oboe stream onErrorBeforeClose(): " + String (oboe::convertToText (error)));
|
|
|
|
JUCE_OBOE_LOG ("Oboe stream onErrorBeforeClose(): " + getOboeString (error));
|
|
|
|
printStreamDebugInfo (stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override
|
|
|
|
{
|
|
|
|
attachAndroidJNI();
|
|
|
|
|
|
|
|
// only output stream should be the master stream receiving callbacks
|
|
|
|
jassert (stream->getDirection() == oboe::Direction::Output);
|
|
|
|
|
|
|
|
JUCE_OBOE_LOG ("Oboe stream onErrorAfterClose(): " + String (oboe::convertToText (error)));
|
|
|
|
JUCE_OBOE_LOG ("Oboe stream onErrorAfterClose(): " + getOboeString (error));
|
|
|
|
|
|
|
|
if (error == oboe::Result::ErrorDisconnected)
|
|
|
|
{
|
|
|
@@ -948,7 +959,10 @@ private: |
|
|
|
Atomic<int> audioCallbackGuard { 0 },
|
|
|
|
streamRestartGuard { 0 };
|
|
|
|
|
|
|
|
bool isOutputLatencyDetectionSupported = true;
|
|
|
|
bool isInputLatencyDetectionSupported = false;
|
|
|
|
int inputLatency = -1;
|
|
|
|
|
|
|
|
bool isOutputLatencyDetectionSupported = false;
|
|
|
|
int outputLatency = -1;
|
|
|
|
};
|
|
|
|
|
|
|
@@ -1016,6 +1030,23 @@ private: |
|
|
|
return androidHasSystemFeature ("android.hardware.audio.pro");
|
|
|
|
}
|
|
|
|
|
|
|
|
static int getDefaultFramesPerBurst()
|
|
|
|
{
|
|
|
|
// NB: this function only works for inbuilt speakers and headphones
|
|
|
|
auto* env = getEnv();
|
|
|
|
|
|
|
|
auto audioManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
|
|
|
|
JuceAppActivity.getSystemService,
|
|
|
|
javaString ("audio").get()));
|
|
|
|
|
|
|
|
auto propertyJavaString = javaString ("android.media.property.OUTPUT_FRAMES_PER_BUFFER");
|
|
|
|
|
|
|
|
auto framesPerBurstString = LocalRef<jstring> ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty,
|
|
|
|
propertyJavaString.get()));
|
|
|
|
|
|
|
|
return framesPerBurstString != 0 ? env->CallStaticIntMethod (JavaInteger, JavaInteger.parseInt, framesPerBurstString.get(), 10) : 192;
|
|
|
|
}
|
|
|
|
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODevice)
|
|
|
|
};
|
|
|
|
|
|
|
@@ -1098,7 +1129,7 @@ public: |
|
|
|
forInput ? oboe::Direction::Input : oboe::Direction::Output,
|
|
|
|
oboe::SharingMode::Shared,
|
|
|
|
forInput ? 1 : 2,
|
|
|
|
getSdkVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16,
|
|
|
|
getSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16,
|
|
|
|
(int) OboeAudioIODevice::getNativeSampleRate(),
|
|
|
|
OboeAudioIODevice::getNativeBufferSize(),
|
|
|
|
nullptr);
|
|
|
@@ -1117,18 +1148,17 @@ public: |
|
|
|
|
|
|
|
int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
|
|
|
|
{
|
|
|
|
if (device == nullptr)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
auto* oboeDevice = static_cast<OboeAudioIODevice*> (device);
|
|
|
|
auto oboeDeviceId = asInput ? oboeDevice->inputDeviceId
|
|
|
|
: oboeDevice->outputDeviceId;
|
|
|
|
if (auto oboeDevice = static_cast<OboeAudioIODevice*> (device)) |
|
|
|
{
|
|
|
|
auto oboeDeviceId = asInput ? oboeDevice->inputDeviceId
|
|
|
|
: oboeDevice->outputDeviceId;
|
|
|
|
|
|
|
|
auto& devices = asInput ? inputDevices : outputDevices;
|
|
|
|
auto& devices = asInput ? inputDevices : outputDevices;
|
|
|
|
|
|
|
|
for (int i = 0; i < devices.size(); ++i)
|
|
|
|
if (devices.getReference (i).id == oboeDeviceId)
|
|
|
|
return i;
|
|
|
|
for (int i = 0; i < devices.size(); ++i)
|
|
|
|
if (devices.getReference (i).id == oboeDeviceId)
|
|
|
|
return i; |
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
@@ -1233,11 +1263,11 @@ public: |
|
|
|
|
|
|
|
bool supportsDevicesInfo() const
|
|
|
|
{
|
|
|
|
static auto result = getSdkVersion() >= 23;
|
|
|
|
static auto result = getSDKVersion() >= 23;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getSdkVersion() const
|
|
|
|
int getSDKVersion() const
|
|
|
|
{
|
|
|
|
static auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT);
|
|
|
|
return sdkVersion;
|
|
|
@@ -1399,7 +1429,7 @@ public: |
|
|
|
return testStream != nullptr && testStream->openedOk();
|
|
|
|
}
|
|
|
|
|
|
|
|
pthread_t startThread (void* (*entry) (void*), void* userPtr)
|
|
|
|
pthread_t startThread (void*(*entry)(void*), void* userPtr)
|
|
|
|
{
|
|
|
|
pthread_mutex_lock (&threadReadyMutex);
|
|
|
|
|
|
|
@@ -1439,26 +1469,16 @@ public: |
|
|
|
|
|
|
|
void onErrorBeforeClose (oboe::AudioStream*, oboe::Result error) override
|
|
|
|
{
|
|
|
|
JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + String (oboe::convertToText (error)));
|
|
|
|
JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + getOboeString (error));
|
|
|
|
ignoreUnused (error);
|
|
|
|
jassertfalse; // Should never get here! |
|
|
|
}
|
|
|
|
|
|
|
|
void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override
|
|
|
|
void onErrorAfterClose (oboe::AudioStream*, oboe::Result error) override
|
|
|
|
{
|
|
|
|
JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + String (oboe::convertToText (error)));
|
|
|
|
|
|
|
|
if (error == oboe::Result::ErrorDisconnected)
|
|
|
|
{
|
|
|
|
testStream.reset();
|
|
|
|
testStream.reset (new OboeStream (-1,
|
|
|
|
oboe::Direction::Output,
|
|
|
|
oboe::SharingMode::Exclusive,
|
|
|
|
1,
|
|
|
|
formatUsed,
|
|
|
|
(int) OboeAudioIODevice::getNativeSampleRate(),
|
|
|
|
OboeAudioIODevice::getNativeBufferSize(),
|
|
|
|
this));
|
|
|
|
testStream->start();
|
|
|
|
}
|
|
|
|
JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + getOboeString (error));
|
|
|
|
ignoreUnused (error);
|
|
|
|
jassertfalse; // Should never get here! |
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
@@ -1482,10 +1502,7 @@ pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr) |
|
|
|
return {};
|
|
|
|
|
|
|
|
auto threadID = thread->startThread (entry, userPtr);
|
|
|
|
|
|
|
|
// the thread will de-allocate itself
|
|
|
|
thread.release();
|
|
|
|
|
|
|
|
thread.release(); // the thread will de-allocate itself |
|
|
|
return threadID;
|
|
|
|
}
|
|
|
|
|
|
|
|