blob: 7d6aa8b4e11ee2020082fac7a89ca23e07f0332a [file] [log] [blame]
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEOBOE_NATIVEAUDIOCONTEXT_H
#define NATIVEOBOE_NATIVEAUDIOCONTEXT_H
#include <dlfcn.h>
#include <jni.h>
#include <thread>
#include <unordered_map>
#include <vector>
#include "common/OboeDebug.h"
#include "oboe/Oboe.h"
#include "AudioStreamGateway.h"
#include "flowunits/ImpulseOscillator.h"
#include "flowgraph/ManyToMultiConverter.h"
#include "flowgraph/MonoToMultiConverter.h"
#include "flowgraph/SinkFloat.h"
#include "flowgraph/SinkI16.h"
#include "flowunits/ExponentialShape.h"
#include "flowunits/LinearShape.h"
#include "flowunits/SineOscillator.h"
#include "flowunits/SawtoothOscillator.h"
#include "FullDuplexEcho.h"
#include "FullDuplexGlitches.h"
#include "FullDuplexLatency.h"
#include "FullDuplexStream.h"
#include "InputStreamCallbackAnalyzer.h"
#include "MultiChannelRecording.h"
#include "OboeStreamCallbackProxy.h"
#include "PlayRecordingCallback.h"
#include "SawPingGenerator.h"
#include "flowunits/TriangleOscillator.h"
// These must match order in strings.xml and in StreamConfiguration.java
#define NATIVE_MODE_UNSPECIFIED 0
#define NATIVE_MODE_OPENSLES 1
#define NATIVE_MODE_AAUDIO 2
#define MAX_SINE_OSCILLATORS 8
#define AMPLITUDE_SINE 1.0
#define AMPLITUDE_SAWTOOTH 0.5
#define FREQUENCY_SAW_PING 800.0
#define AMPLITUDE_SAW_PING 0.8
#define AMPLITUDE_IMPULSE 0.7
#define NANOS_PER_MICROSECOND ((int64_t) 1000)
#define NANOS_PER_MILLISECOND (1000 * NANOS_PER_MICROSECOND)
#define NANOS_PER_SECOND (1000 * NANOS_PER_MILLISECOND)
#define LIB_AAUDIO_NAME "libaaudio.so"
#define FUNCTION_IS_MMAP "AAudioStream_isMMapUsed"
#define FUNCTION_SET_MMAP_POLICY "AAudio_setMMapPolicy"
#define FUNCTION_GET_MMAP_POLICY "AAudio_getMMapPolicy"
#define SECONDS_TO_RECORD 10
typedef struct AAudioStreamStruct AAudioStream;
/**
* Call some AAudio test routines that are not part of the normal API.
*/
class AAudioExtensions {
public:
static AAudioExtensions &getInstance() {
static AAudioExtensions instance;
return instance;
}
bool isMMapUsed(oboe::AudioStream *oboeStream) {
if (!loadLibrary()) return false;
AAudioStream *aaudioStream = (AAudioStream *) oboeStream->getUnderlyingStream();
return mAAudioStream_isMMap(aaudioStream);
}
bool setMMapEnabled(bool enabled) {
if (!loadLibrary()) return false;
return mAAudio_setMMapPolicy(enabled ? AAUDIO_POLICY_AUTO : AAUDIO_POLICY_NEVER);
}
bool isMMapEnabled() {
if (!loadLibrary()) return false;
return mAAudio_getMMapPolicy() != AAUDIO_POLICY_NEVER;
}
bool isMMapSupported() {
if (!loadLibrary()) return false;
return mMMapSupported;
}
private:
enum {
AAUDIO_POLICY_NEVER = 1,
AAUDIO_POLICY_AUTO,
AAUDIO_POLICY_ALWAYS
};
typedef int32_t aaudio_policy_t;
// return true if it succeeds
bool loadLibrary() {
if (mFirstTime) {
mFirstTime = false;
mLibHandle = dlopen(LIB_AAUDIO_NAME, 0);
if (mLibHandle == nullptr) {
LOGI("%s() could not find "
LIB_AAUDIO_NAME, __func__);
return false;
}
mAAudioStream_isMMap = (bool (*)(AAudioStream *stream))
dlsym(mLibHandle, FUNCTION_IS_MMAP);
if (mAAudioStream_isMMap == nullptr) {
LOGI("%s() could not find "
FUNCTION_IS_MMAP, __func__);
return false;
}
mAAudio_setMMapPolicy = (int32_t (*)(aaudio_policy_t policy))
dlsym(mLibHandle, FUNCTION_SET_MMAP_POLICY);
if (mAAudio_setMMapPolicy == nullptr) {
LOGI("%s() could not find "
FUNCTION_SET_MMAP_POLICY, __func__);
return false;
}
mAAudio_getMMapPolicy = (aaudio_policy_t (*)())
dlsym(mLibHandle, FUNCTION_GET_MMAP_POLICY);
if (mAAudio_getMMapPolicy == nullptr) {
LOGI("%s() could not find "
FUNCTION_GET_MMAP_POLICY, __func__);
return false;
}
mMMapSupported = isMMapEnabled(); // still supported even if disabled later
}
return true;
}
bool mFirstTime = true;
void *mLibHandle = nullptr;
bool (*mAAudioStream_isMMap)(AAudioStream *stream) = nullptr;
int32_t (*mAAudio_setMMapPolicy)(aaudio_policy_t policy) = nullptr;
aaudio_policy_t (*mAAudio_getMMapPolicy)() = nullptr;
bool mMMapSupported = false;
};
/**
* Abstract base class that corresponds to a test at the Java level.
*/
class ActivityContext {
public:
ActivityContext() {}
virtual ~ActivityContext() = default;
oboe::AudioStream *getStream(int32_t streamIndex) {
auto it = mOboeStreams.find(streamIndex);
if (it != mOboeStreams.end()) {
return it->second;
} else {
return nullptr;
}
}
virtual void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder);
int open(jint nativeApi,
jint sampleRate,
jint channelCount,
jint format,
jint sharingMode,
jint performanceMode,
jint deviceId,
jint sessionId,
jint framesPerBurst,
jboolean channelConversionAllowed,
jboolean formatConversionAllowed,
jint rateConversionQuality,
jboolean isMMap,
jboolean isInput);
virtual void close(int32_t streamIndex);
void printScheduler() {
#if OBOE_ENABLE_LOGGING
int scheduler = audioStreamGateway.getScheduler();
#endif
LOGI("scheduler = 0x%08x, SCHED_FIFO = 0x%08X\n", scheduler, SCHED_FIFO);
}
virtual void configureForStart() {}
oboe::Result start();
oboe::Result pause();
oboe::Result stopAllStreams();
virtual oboe::Result stop() {
return stopAllStreams();
}
virtual void setAmplitude(double amplitude) {}
virtual oboe::Result startPlayback() {
return oboe::Result::OK;
}
virtual oboe::Result stopPlayback() {
return oboe::Result::OK;
}
virtual void runBlockingIO() {};
static void threadCallback(ActivityContext *context) {
LOGD("%s: called", __func__);
context->runBlockingIO();
LOGD("%s: exiting", __func__);
}
void stopBlockingIOThread() {
if (dataThread != nullptr) {
// stop a thread that runs in place of the callback
threadEnabled.store(false); // ask thread to exit its loop
dataThread->join();
dataThread = nullptr;
}
}
virtual double getPeakLevel(int index) {
return 0.0;
}
virtual void setEnabled(bool enabled) {
}
bool isMMapUsed(int32_t streamIndex);
int32_t getFramesPerBlock() {
return (callbackSize == 0) ? mFramesPerBurst : callbackSize;
}
int64_t getCallbackCount() {
return oboeCallbackProxy.getCallbackCount();
}
virtual void setChannelEnabled(int channelIndex, bool enabled) {}
virtual void setSignalType(int signalType) {}
virtual int32_t saveWaveFile(const char *filename);
virtual void setMinimumFramesBeforeRead(int32_t numFrames) {}
static bool mUseCallback;
static int callbackSize;
protected:
oboe::AudioStream *getInputStream();
oboe::AudioStream *getOutputStream();
int32_t allocateStreamIndex();
void freeStreamIndex(int32_t streamIndex);
virtual void createRecording() {
mRecording = std::make_unique<MultiChannelRecording>(mChannelCount,
SECONDS_TO_RECORD * mSampleRate);
}
virtual void finishOpen(bool isInput, oboe::AudioStream *oboeStream) {}
virtual oboe::Result startStreams() = 0;
std::unique_ptr<float []> dataBuffer{};
AudioStreamGateway audioStreamGateway;
OboeStreamCallbackProxy oboeCallbackProxy;
std::unique_ptr<MultiChannelRecording> mRecording{};
int32_t mNextStreamHandle = 0;
std::unordered_map<int32_t, oboe::AudioStream *> mOboeStreams;
int32_t mFramesPerBurst = 0; // TODO per stream
int32_t mChannelCount = 0; // TODO per stream
int32_t mSampleRate = 0; // TODO per stream
std::atomic<bool> threadEnabled{false};
std::thread *dataThread = nullptr;
private:
};
/**
* Test a single input stream.
*/
class ActivityTestInput : public ActivityContext {
public:
ActivityTestInput() {}
virtual ~ActivityTestInput() = default;
void configureForStart() override;
double getPeakLevel(int index) override {
return mInputAnalyzer.getPeakLevel(index);
}
void runBlockingIO() override;
InputStreamCallbackAnalyzer mInputAnalyzer;
void setMinimumFramesBeforeRead(int32_t numFrames) override {
mInputAnalyzer.setMinimumFramesBeforeRead(numFrames);
mMinimumFramesBeforeRead = numFrames;
}
int32_t getMinimumFramesBeforeRead() const {
return mMinimumFramesBeforeRead;
}
protected:
oboe::Result startStreams() override {
return getInputStream()->requestStart();
}
int32_t mMinimumFramesBeforeRead = 0;
};
/**
* Record a configured input stream and play it back some simple way.
*/
class ActivityRecording : public ActivityTestInput {
public:
ActivityRecording() {}
virtual ~ActivityRecording() = default;
oboe::Result stop() override {
oboe::Result resultStopPlayback = stopPlayback();
oboe::Result resultStopAudio = ActivityContext::stop();
return (resultStopPlayback != oboe::Result::OK) ? resultStopPlayback : resultStopAudio;
}
oboe::Result startPlayback() override;
oboe::Result stopPlayback() override;
PlayRecordingCallback mPlayRecordingCallback;
oboe::AudioStream *playbackStream = nullptr;
};
/**
* Test a single output stream.
*/
class ActivityTestOutput : public ActivityContext {
public:
ActivityTestOutput()
: sineOscillators(MAX_SINE_OSCILLATORS)
, sawtoothOscillators(MAX_SINE_OSCILLATORS) {}
virtual ~ActivityTestOutput() = default;
void close(int32_t streamIndex) override;
oboe::Result startStreams() override {
return getOutputStream()->start();
}
void configureForStart() override;
virtual void configureStreamGateway();
void runBlockingIO() override;
void setAmplitude(double amplitude) override {
LOGD("%s(%f)", __func__, amplitude);
for (int i = 0; i < mChannelCount; i++) {
sineOscillators[i].amplitude.setValue(amplitude);
sawtoothOscillators[i].amplitude.setValue(amplitude);
}
impulseGenerator.amplitude.setValue(amplitude);
}
void setChannelEnabled(int channelIndex, bool enabled) override;
// WARNING - must match order in strings.xml and OboeAudioOutputStream.java
enum SignalType {
Sine = 0,
Sawtooth = 1,
FreqSweep = 2,
PitchSweep = 3,
WhiteNoise = 4
};
void setSignalType(int signalType) override {
mSignalType = (SignalType) signalType;
}
protected:
SignalType mSignalType = SignalType::Sine;
std::vector<SineOscillator> sineOscillators;
std::vector<SawtoothOscillator> sawtoothOscillators;
ImpulseOscillator impulseGenerator;
static constexpr float kSweepPeriod = 10.0; // for triangle up and down
// A triangle LFO is shaped into either a linear or an exponential range.
TriangleOscillator mTriangleOscillator;
LinearShape mLinearShape;
ExponentialShape mExponentialShape;
std::unique_ptr<ManyToMultiConverter> manyToMulti;
std::unique_ptr<MonoToMultiConverter> monoToMulti;
std::shared_ptr<flowgraph::SinkFloat> mSinkFloat;
std::shared_ptr<flowgraph::SinkI16> mSinkI16;
};
/**
* Generate a short beep with a very short attack.
* This is used by Java to measure output latency.
*/
class ActivityTapToTone : public ActivityTestOutput {
public:
ActivityTapToTone() {}
virtual ~ActivityTapToTone() = default;
void configureForStart() override;
void setAmplitude(double amplitude) override {
LOGD("%s(%f)", __func__, amplitude);
ActivityTestOutput::setAmplitude(amplitude);
sawPingGenerator.amplitude.setValue(amplitude);
}
virtual void setEnabled(bool enabled) override {
sawPingGenerator.setEnabled(enabled);
}
SawPingGenerator sawPingGenerator;
};
/**
* Activity that uses synchronized input/output streams.
*/
class ActivityFullDuplex : public ActivityContext {
public:
void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
virtual int32_t getState() { return -1; }
virtual int32_t getResult() { return -1; }
virtual bool isAnalyzerDone() { return false; }
void setMinimumFramesBeforeRead(int32_t numFrames) override {
getFullDuplexAnalyzer()->setMinimumFramesBeforeRead(numFrames);
}
virtual FullDuplexAnalyzer *getFullDuplexAnalyzer() = 0;
int32_t getResetCount() {
return getFullDuplexAnalyzer()->getLoopbackProcessor()->getResetCount();
}
protected:
void createRecording() override {
mRecording = std::make_unique<MultiChannelRecording>(2, // output and input
SECONDS_TO_RECORD * mSampleRate);
}
};
/**
* Echo input to output through a delay line.
*/
class ActivityEcho : public ActivityFullDuplex {
public:
oboe::Result startStreams() override {
return mFullDuplexEcho->start();
}
void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
void setDelayTime(double delayTimeSeconds) {
if (mFullDuplexEcho) {
mFullDuplexEcho->setDelayTime(delayTimeSeconds);
}
}
virtual FullDuplexAnalyzer *getFullDuplexAnalyzer() override {
return (FullDuplexAnalyzer *) mFullDuplexEcho.get();
}
protected:
void finishOpen(bool isInput, oboe::AudioStream *oboeStream) override;
private:
std::unique_ptr<FullDuplexEcho> mFullDuplexEcho{};
};
/**
* Measure Round Trip Latency
*/
class ActivityRoundTripLatency : public ActivityFullDuplex {
public:
oboe::Result startStreams() override {
return mFullDuplexLatency->start();
}
void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
LatencyAnalyzer *getLatencyAnalyzer() {
return mFullDuplexLatency->getLatencyAnalyzer();
}
int32_t getState() override {
return getLatencyAnalyzer()->getState();
}
int32_t getResult() override {
return getLatencyAnalyzer()->getState();
}
bool isAnalyzerDone() override {
return mFullDuplexLatency->isDone();
}
FullDuplexAnalyzer *getFullDuplexAnalyzer() override {
return (FullDuplexAnalyzer *) mFullDuplexLatency.get();
}
protected:
void finishOpen(bool isInput, oboe::AudioStream *oboeStream) override;
private:
std::unique_ptr<FullDuplexLatency> mFullDuplexLatency{};
};
/**
* Measure Glitches
*/
class ActivityGlitches : public ActivityFullDuplex {
public:
oboe::Result startStreams() override {
return mFullDuplexGlitches->start();
}
void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
GlitchAnalyzer *getGlitchAnalyzer() {
return mFullDuplexGlitches->getGlitchAnalyzer();
}
int32_t getState() override {
return getGlitchAnalyzer()->getState();
}
int32_t getResult() override {
return getGlitchAnalyzer()->getResult();
}
bool isAnalyzerDone() override {
return mFullDuplexGlitches->isDone();
}
FullDuplexAnalyzer *getFullDuplexAnalyzer() override {
return (FullDuplexAnalyzer *) mFullDuplexGlitches.get();
}
protected:
void finishOpen(bool isInput, oboe::AudioStream *oboeStream) override;
private:
std::unique_ptr<FullDuplexGlitches> mFullDuplexGlitches{};
};
/**
* Switch between various
*/
class NativeAudioContext {
public:
ActivityContext *getCurrentActivity() {
return currentActivity;
};
void setActivityType(int activityType) {
LOGD("%s(%d)", __func__, activityType);
mActivityType = (ActivityType) activityType;
switch(mActivityType) {
default:
case ActivityType::Undefined:
case ActivityType::TestOutput:
currentActivity = &mActivityTestOutput;
break;
case ActivityType::TestInput:
currentActivity = &mActivityTestInput;
break;
case ActivityType::TapToTone:
currentActivity = &mActivityTapToTone;
break;
case ActivityType::RecordPlay:
currentActivity = &mActivityRecording;
break;
case ActivityType::Echo:
currentActivity = &mActivityEcho;
break;
case ActivityType::RoundTripLatency:
currentActivity = &mActivityRoundTripLatency;
break;
case ActivityType::Glitches:
currentActivity = &mActivityGlitches;
break;
}
}
void setDelayTime(double delayTimeMillis) {
mActivityEcho.setDelayTime(delayTimeMillis);
}
ActivityTestOutput mActivityTestOutput;
ActivityTestInput mActivityTestInput;
ActivityTapToTone mActivityTapToTone;
ActivityRecording mActivityRecording;
ActivityEcho mActivityEcho;
ActivityRoundTripLatency mActivityRoundTripLatency;
ActivityGlitches mActivityGlitches;
private:
// WARNING - must match definitions in TestAudioActivity.java
enum ActivityType {
Undefined = -1,
TestOutput = 0,
TestInput = 1,
TapToTone = 2,
RecordPlay = 3,
Echo = 4,
RoundTripLatency = 5,
Glitches = 6,
};
ActivityType mActivityType = ActivityType::Undefined;
ActivityContext *currentActivity = &mActivityTestOutput;
};
#endif //NATIVEOBOE_NATIVEAUDIOCONTEXT_H