blob: 7809206facbbf296eb46d4304dda58561f0c2435 [file] [log] [blame]
/*
* Copyright 2017 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.
*/
#include <fstream>
#include <iostream>
#include <vector>
#include "util/WaveFileWriter.h"
#include "NativeAudioContext.h"
using namespace oboe;
static oboe::AudioApi convertNativeApiToAudioApi(int nativeApi) {
switch (nativeApi) {
default:
case NATIVE_MODE_UNSPECIFIED:
return oboe::AudioApi::Unspecified;
case NATIVE_MODE_AAUDIO:
return oboe::AudioApi::AAudio;
case NATIVE_MODE_OPENSLES:
return oboe::AudioApi::OpenSLES;
}
}
class MyOboeOutputStream : public WaveFileOutputStream {
public:
void write(uint8_t b) override {
mData.push_back(b);
}
int32_t length() {
return (int32_t) mData.size();
}
uint8_t *getData() {
return mData.data();
}
private:
std::vector<uint8_t> mData;
};
bool ActivityContext::mUseCallback = true;
int ActivityContext::callbackSize = 0;
oboe::AudioStream * ActivityContext::getOutputStream() {
for (auto entry : mOboeStreams) {
oboe::AudioStream *oboeStream = entry.second.get();
if (oboeStream->getDirection() == oboe::Direction::Output) {
return oboeStream;
}
}
return nullptr;
}
oboe::AudioStream * ActivityContext::getInputStream() {
for (auto entry : mOboeStreams) {
oboe::AudioStream *oboeStream = entry.second.get();
if (oboeStream != nullptr) {
if (oboeStream->getDirection() == oboe::Direction::Input) {
return oboeStream;
}
}
}
return nullptr;
}
void ActivityContext::freeStreamIndex(int32_t streamIndex) {
mOboeStreams[streamIndex].reset();
mOboeStreams.erase(streamIndex);
}
int32_t ActivityContext::allocateStreamIndex() {
return mNextStreamHandle++;
}
void ActivityContext::close(int32_t streamIndex) {
stopBlockingIOThread();
oboe::AudioStream *oboeStream = getStream(streamIndex);
if (oboeStream != nullptr) {
oboeStream->close();
LOGD("ActivityContext::%s() delete stream %d ", __func__, streamIndex);
freeStreamIndex(streamIndex);
}
}
bool ActivityContext::isMMapUsed(int32_t streamIndex) {
oboe::AudioStream *oboeStream = getStream(streamIndex);
if (oboeStream == nullptr) return false;
if (oboeStream->getAudioApi() != AudioApi::AAudio) return false;
return AAudioExtensions::getInstance().isMMapUsed(oboeStream);
}
oboe::Result ActivityContext::pause() {
oboe::Result result = oboe::Result::OK;
stopBlockingIOThread();
for (auto entry : mOboeStreams) {
oboe::AudioStream *oboeStream = entry.second.get();
result = oboeStream->requestPause();
}
return result;
}
oboe::Result ActivityContext::stopAllStreams() {
oboe::Result result = oboe::Result::OK;
stopBlockingIOThread();
for (auto entry : mOboeStreams) {
oboe::AudioStream *oboeStream = entry.second.get();
result = oboeStream->requestStop();
}
return result;
}
void ActivityContext::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
// We needed the proxy because we did not know the channelCount when we setup the Builder.
if (mUseCallback) {
LOGD("ActivityContext::open() set callback to use oboeCallbackProxy, callback size = %d",
callbackSize);
builder.setDataCallback(&oboeCallbackProxy);
builder.setFramesPerCallback(callbackSize);
}
}
int ActivityContext::open(jint nativeApi,
jint sampleRate,
jint channelCount,
jint format,
jint sharingMode,
jint performanceMode,
jint inputPreset,
jint deviceId,
jint sessionId,
jint framesPerBurst,
jboolean channelConversionAllowed,
jboolean formatConversionAllowed,
jint rateConversionQuality,
jboolean isMMap,
jboolean isInput) {
oboe::AudioApi audioApi = oboe::AudioApi::Unspecified;
switch (nativeApi) {
case NATIVE_MODE_UNSPECIFIED:
case NATIVE_MODE_AAUDIO:
case NATIVE_MODE_OPENSLES:
audioApi = convertNativeApiToAudioApi(nativeApi);
break;
default:
return (jint) oboe::Result::ErrorOutOfRange;
}
int32_t streamIndex = allocateStreamIndex();
if (streamIndex < 0) {
LOGE("ActivityContext::open() stream array full");
return (jint) oboe::Result::ErrorNoFreeHandles;
}
if (channelCount < 0 || channelCount > 256) {
LOGE("ActivityContext::open() channels out of range");
return (jint) oboe::Result::ErrorOutOfRange;
}
// Create an audio stream.
oboe::AudioStreamBuilder builder;
builder.setChannelCount(channelCount)
->setDirection(isInput ? oboe::Direction::Input : oboe::Direction::Output)
->setSharingMode((oboe::SharingMode) sharingMode)
->setPerformanceMode((oboe::PerformanceMode) performanceMode)
->setInputPreset((oboe::InputPreset)inputPreset)
->setDeviceId(deviceId)
->setSessionId((oboe::SessionId) sessionId)
->setSampleRate(sampleRate)
->setFormat((oboe::AudioFormat) format)
->setChannelConversionAllowed(channelConversionAllowed)
->setFormatConversionAllowed(formatConversionAllowed)
->setSampleRateConversionQuality((oboe::SampleRateConversionQuality) rateConversionQuality)
;
configureBuilder(isInput, builder);
builder.setAudioApi(audioApi);
// Temporarily set the AAudio MMAP policy to disable MMAP or not.
bool oldMMapEnabled = AAudioExtensions::getInstance().isMMapEnabled();
AAudioExtensions::getInstance().setMMapEnabled(isMMap);
// Open a stream based on the builder settings.
std::shared_ptr<oboe::AudioStream> oboeStream;
Result result = builder.openStream(oboeStream);
AAudioExtensions::getInstance().setMMapEnabled(oldMMapEnabled);
if (result != Result::OK) {
freeStreamIndex(streamIndex);
streamIndex = -1;
} else {
mOboeStreams[streamIndex] = oboeStream; // save shared_ptr
mChannelCount = oboeStream->getChannelCount(); // FIXME store per stream
mFramesPerBurst = oboeStream->getFramesPerBurst();
mSampleRate = oboeStream->getSampleRate();
createRecording();
finishOpen(isInput, oboeStream.get());
}
if (!mUseCallback) {
int numSamples = getFramesPerBlock() * mChannelCount;
dataBuffer = std::make_unique<float[]>(numSamples);
}
return (result != Result::OK) ? (int)result : streamIndex;
}
oboe::Result ActivityContext::start() {
oboe::Result result = oboe::Result::OK;
oboe::AudioStream *inputStream = getInputStream();
oboe::AudioStream *outputStream = getOutputStream();
if (inputStream == nullptr && outputStream == nullptr) {
LOGD("%s() - no streams defined", __func__);
return oboe::Result::ErrorInvalidState; // not open
}
configureForStart();
audioStreamGateway.reset();
result = startStreams();
if (!mUseCallback && result == oboe::Result::OK) {
// Instead of using the callback, start a thread that writes the stream.
threadEnabled.store(true);
dataThread = new std::thread(threadCallback, this);
}
return result;
}
int32_t ActivityContext::saveWaveFile(const char *filename) {
if (mRecording == nullptr) {
LOGW("ActivityContext::saveWaveFile(%s) but no recording!", filename);
return -1;
}
if (mRecording->getSizeInFrames() == 0) {
LOGW("ActivityContext::saveWaveFile(%s) but no frames!", filename);
return -2;
}
MyOboeOutputStream outStream;
WaveFileWriter writer(&outStream);
writer.setFrameRate(mSampleRate);
writer.setSamplesPerFrame(mRecording->getChannelCount());
writer.setBitsPerSample(24);
float buffer[mRecording->getChannelCount()];
// Read samples from start to finish.
mRecording->rewind();
for (int32_t frameIndex = 0; frameIndex < mRecording->getSizeInFrames(); frameIndex++) {
mRecording->read(buffer, 1 /* numFrames */);
for (int32_t i = 0; i < mRecording->getChannelCount(); i++) {
writer.write(buffer[i]);
}
}
writer.close();
if (outStream.length() > 0) {
auto myfile = std::ofstream(filename, std::ios::out | std::ios::binary);
myfile.write((char *) outStream.getData(), outStream.length());
myfile.close();
}
return outStream.length();
}
// =================================================================== ActivityTestOutput
void ActivityTestOutput::close(int32_t streamIndex) {
ActivityContext::close(streamIndex);
manyToMulti.reset(nullptr);
monoToMulti.reset(nullptr);
mSinkFloat.reset();
mSinkI16.reset();
}
void ActivityTestOutput::setChannelEnabled(int channelIndex, bool enabled) {
if (manyToMulti == nullptr) {
return;
}
if (enabled) {
switch (mSignalType) {
case SignalType::Sine:
sineOscillators[channelIndex].frequency.disconnect();
sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
break;
case SignalType::Sawtooth:
sawtoothOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
break;
case SignalType::FreqSweep:
mLinearShape.output.connect(&sineOscillators[channelIndex].frequency);
sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
break;
case SignalType::PitchSweep:
mExponentialShape.output.connect(&sineOscillators[channelIndex].frequency);
sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
break;
default:
break;
}
} else {
manyToMulti->inputs[channelIndex]->disconnect();
}
}
void ActivityTestOutput::configureForStart() {
manyToMulti = std::make_unique<ManyToMultiConverter>(mChannelCount);
mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
oboe::AudioStream *outputStream = getOutputStream();
mTriangleOscillator.setSampleRate(outputStream->getSampleRate());
mTriangleOscillator.frequency.setValue(1.0/kSweepPeriod);
mTriangleOscillator.amplitude.setValue(1.0);
mTriangleOscillator.setPhase(-1.0);
mLinearShape.setMinimum(0.0);
mLinearShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
mExponentialShape.setMinimum(110.0);
mExponentialShape.setMaximum(outputStream->getSampleRate() * 0.5); // Nyquist
mTriangleOscillator.output.connect(&(mLinearShape.input));
mTriangleOscillator.output.connect(&(mExponentialShape.input));
{
double frequency = 330.0;
for (int i = 0; i < mChannelCount; i++) {
sineOscillators[i].setSampleRate(outputStream->getSampleRate());
sineOscillators[i].frequency.setValue(frequency);
frequency *= 4.0 / 3.0; // each sine is at a higher frequency
sineOscillators[i].amplitude.setValue(AMPLITUDE_SINE);
setChannelEnabled(i, true);
}
}
manyToMulti->output.connect(&(mSinkFloat.get()->input));
manyToMulti->output.connect(&(mSinkI16.get()->input));
mSinkFloat->pullReset();
mSinkI16->pullReset();
configureStreamGateway();
}
void ActivityTestOutput::configureStreamGateway() {
oboe::AudioStream *outputStream = getOutputStream();
if (outputStream->getFormat() == oboe::AudioFormat::I16) {
audioStreamGateway.setAudioSink(mSinkI16);
} else if (outputStream->getFormat() == oboe::AudioFormat::Float) {
audioStreamGateway.setAudioSink(mSinkFloat);
}
if (mUseCallback) {
oboeCallbackProxy.setCallback(&audioStreamGateway);
}
}
void ActivityTestOutput::runBlockingIO() {
int32_t framesPerBlock = getFramesPerBlock();
oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
oboe::AudioStream *oboeStream = getOutputStream();
if (oboeStream == nullptr) {
LOGE("%s() : no stream found\n", __func__);
return;
}
while (threadEnabled.load()
&& callbackResult == oboe::DataCallbackResult::Continue) {
// generate output by calling the callback
callbackResult = audioStreamGateway.onAudioReady(oboeStream,
dataBuffer.get(),
framesPerBlock);
auto result = oboeStream->write(dataBuffer.get(),
framesPerBlock,
NANOS_PER_SECOND);
if (!result) {
LOGE("%s() returned %s\n", __func__, convertToText(result.error()));
break;
}
int32_t framesWritten = result.value();
if (framesWritten < framesPerBlock) {
LOGE("%s() : write() wrote %d of %d\n", __func__, framesWritten, framesPerBlock);
break;
}
}
}
// ======================================================================= ActivityTestInput
void ActivityTestInput::configureForStart() {
mInputAnalyzer.reset();
if (mUseCallback) {
oboeCallbackProxy.setCallback(&mInputAnalyzer);
}
mInputAnalyzer.setRecording(mRecording.get());
}
void ActivityTestInput::runBlockingIO() {
int32_t framesPerBlock = getFramesPerBlock();
oboe::DataCallbackResult callbackResult = oboe::DataCallbackResult::Continue;
oboe::AudioStream *oboeStream = getInputStream();
if (oboeStream == nullptr) {
LOGE("%s() : no stream found\n", __func__);
return;
}
while (threadEnabled.load()
&& callbackResult == oboe::DataCallbackResult::Continue) {
// Avoid glitches by waiting until there is extra data in the FIFO.
auto err = oboeStream->waitForAvailableFrames(mMinimumFramesBeforeRead, kNanosPerSecond);
if (!err) break;
// read from input
auto result = oboeStream->read(dataBuffer.get(),
framesPerBlock,
NANOS_PER_SECOND);
if (!result) {
LOGE("%s() : read() returned %s\n", __func__, convertToText(result.error()));
break;
}
int32_t framesRead = result.value();
if (framesRead < framesPerBlock) { // timeout?
LOGE("%s() : read() read %d of %d\n", __func__, framesRead, framesPerBlock);
break;
}
// analyze input
callbackResult = mInputAnalyzer.onAudioReady(oboeStream,
dataBuffer.get(),
framesRead);
}
}
oboe::Result ActivityRecording::stopPlayback() {
oboe::Result result = oboe::Result::OK;
if (playbackStream != nullptr) {
result = playbackStream->requestStop();
playbackStream->close();
mPlayRecordingCallback.setRecording(nullptr);
delete playbackStream;
playbackStream = nullptr;
}
return result;
}
oboe::Result ActivityRecording::startPlayback() {
stop();
oboe::AudioStreamBuilder builder;
builder.setChannelCount(mChannelCount)
->setSampleRate(mSampleRate)
->setFormat(oboe::AudioFormat::Float)
->setCallback(&mPlayRecordingCallback)
->setAudioApi(oboe::AudioApi::OpenSLES);
oboe::Result result = builder.openStream(&playbackStream);
if (result != oboe::Result::OK) {
delete playbackStream;
playbackStream = nullptr;
} else if (playbackStream != nullptr) {
if (mRecording != nullptr) {
mRecording->rewind();
mPlayRecordingCallback.setRecording(mRecording.get());
result = playbackStream->requestStart();
}
}
return result;
}
// ======================================================================= ActivityTapToTone
void ActivityTapToTone::configureForStart() {
monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
oboe::AudioStream *outputStream = getOutputStream();
sawPingGenerator.setSampleRate(outputStream->getSampleRate());
sawPingGenerator.frequency.setValue(FREQUENCY_SAW_PING);
sawPingGenerator.amplitude.setValue(AMPLITUDE_SAW_PING);
sawPingGenerator.output.connect(&(monoToMulti->input));
monoToMulti->output.connect(&(mSinkFloat.get()->input));
monoToMulti->output.connect(&(mSinkI16.get()->input));
mSinkFloat->pullReset();
mSinkI16->pullReset();
configureStreamGateway();
}
// ======================================================================= ActivityRoundTripLatency
void ActivityFullDuplex::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
if (isInput) {
// Ideally the output streams should be opened first.
oboe::AudioStream *outputStream = getOutputStream();
if (outputStream != nullptr) {
// Make sure the capacity is bigger than two bursts.
int32_t burst = outputStream->getFramesPerBurst();
builder.setBufferCapacityInFrames(2 * burst);
}
}
}
// ======================================================================= ActivityEcho
void ActivityEcho::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
ActivityFullDuplex::configureBuilder(isInput, builder);
if (mFullDuplexEcho.get() == nullptr) {
mFullDuplexEcho = std::make_unique<FullDuplexEcho>();
}
// only output uses a callback, input is polled
if (!isInput) {
builder.setCallback(mFullDuplexEcho.get());
}
}
void ActivityEcho::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
if (isInput) {
mFullDuplexEcho->setInputStream(oboeStream);
} else {
mFullDuplexEcho->setOutputStream(oboeStream);
}
}
// ======================================================================= ActivityRoundTripLatency
void ActivityRoundTripLatency::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
ActivityFullDuplex::configureBuilder(isInput, builder);
if (mFullDuplexLatency.get() == nullptr) {
mFullDuplexLatency = std::make_unique<FullDuplexLatency>();
}
if (!isInput) {
// only output uses a callback, input is polled
builder.setCallback(mFullDuplexLatency.get());
}
}
void ActivityRoundTripLatency::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
if (isInput) {
mFullDuplexLatency->setInputStream(oboeStream);
mFullDuplexLatency->setRecording(mRecording.get());
} else {
mFullDuplexLatency->setOutputStream(oboeStream);
}
}
// ======================================================================= ActivityGlitches
void ActivityGlitches::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
ActivityFullDuplex::configureBuilder(isInput, builder);
if (mFullDuplexGlitches.get() == nullptr) {
mFullDuplexGlitches = std::make_unique<FullDuplexGlitches>();
}
if (!isInput) {
// only output uses a callback, input is polled
builder.setCallback(mFullDuplexGlitches.get());
}
}
void ActivityGlitches::finishOpen(bool isInput, oboe::AudioStream *oboeStream) {
if (isInput) {
mFullDuplexGlitches->setInputStream(oboeStream);
mFullDuplexGlitches->setRecording(mRecording.get());
} else {
mFullDuplexGlitches->setOutputStream(oboeStream);
}
}
// =================================================================== ActivityTestDisconnect
void ActivityTestDisconnect::close(int32_t streamIndex) {
ActivityContext::close(streamIndex);
mSinkFloat.reset();
}
void ActivityTestDisconnect::configureForStart() {
oboe::AudioStream *outputStream = getOutputStream();
oboe::AudioStream *inputStream = getInputStream();
if (outputStream) {
mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
sineOscillator = std::make_unique<SineOscillator>();
monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
sineOscillator->setSampleRate(outputStream->getSampleRate());
sineOscillator->frequency.setValue(440.0);
sineOscillator->amplitude.setValue(AMPLITUDE_SINE);
sineOscillator->output.connect(&(monoToMulti->input));
monoToMulti->output.connect(&(mSinkFloat->input));
mSinkFloat->pullReset();
audioStreamGateway.setAudioSink(mSinkFloat);
} else if (inputStream) {
audioStreamGateway.setAudioSink(nullptr);
}
oboeCallbackProxy.setCallback(&audioStreamGateway);
}