Refactor hello-oboe sample to use floats in signal path
diff --git a/samples/hello-oboe/CMakeLists.txt b/samples/hello-oboe/CMakeLists.txt
index b194577..5541dce 100644
--- a/samples/hello-oboe/CMakeLists.txt
+++ b/samples/hello-oboe/CMakeLists.txt
@@ -25,8 +25,8 @@
# specify a binary directory
add_subdirectory(${OBOE_DIR} ./oboe-bin)
-# Include the Oboe headers
-include_directories(${OBOE_DIR}/include)
+# Include the Oboe headers and shared sample code
+include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples)
### END OBOE INCLUDE SECTION ###
@@ -40,7 +40,7 @@
file (GLOB_RECURSE APP_SOURCES
${APP_DIR}/jni_bridge.cpp
${APP_DIR}/PlayAudioEngine.cpp
- ${APP_DIR}/SineGenerator.cpp
+ ${APP_DIR}/SoundGenerator.cpp
)
# Build the libhello-oboe library
diff --git a/samples/hello-oboe/build.gradle b/samples/hello-oboe/build.gradle
index 4c0161a..cdd975d 100644
--- a/samples/hello-oboe/build.gradle
+++ b/samples/hello-oboe/build.gradle
@@ -11,8 +11,8 @@
versionName '1.0'
externalNativeBuild {
cmake {
+ cppFlags "-std=c++14"
arguments '-DANDROID_STL=c++_static'
-
// armeabi and mips are deprecated in NDK r16 so we don't want to build for them
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
index df404d2..a23b5b9 100644
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
+++ b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
@@ -16,9 +16,13 @@
#include <trace.h>
#include <inttypes.h>
+#include <memory>
+
+#include "shared/Oscillator.h"
#include "PlayAudioEngine.h"
#include "logging_macros.h"
+#include "SoundGenerator.h"
constexpr int64_t kNanosPerMillisecond = 1000000; // Use int64_t to avoid overflows in calculations
constexpr int32_t kDefaultChannelCount = 2; // Stereo
@@ -80,10 +84,22 @@
// TODO: Implement Oboe_convertStreamToText
// PrintAudioStreamInfo(mPlayStream);
- prepareOscillators();
+ if (mPlayStream->getFormat() == oboe::AudioFormat::I16){
+ // create a buffer of floats which we can render our audio data into
+ int conversionBufferSamples = mPlayStream->getBufferCapacityInFrames() * channelCount;
+ LOGD("Stream format is 16-bit integers, creating a temporary buffer of %d samples"
+ " for float->int16 conversion", conversionBufferSamples);
+ mConversionBuffer = new float[conversionBufferSamples];
+ }
+
+ mSoundGenerator = std::make_unique<SoundGenerator>(
+ mPlayStream->getSampleRate(),
+ mPlayStream->getBufferCapacityInFrames(),
+ mPlayStream->getChannelCount()
+ );
// Create a latency tuner which will automatically tune our buffer size.
- mLatencyTuner = std::unique_ptr<oboe::LatencyTuner>(new oboe::LatencyTuner(*mPlayStream));
+ mLatencyTuner = std::make_unique<oboe::LatencyTuner>(*mPlayStream);
// Start the stream - the dataCallback function will start being called
result = mPlayStream->requestStart();
if (result != oboe::Result::OK) {
@@ -98,18 +114,6 @@
}
}
-void PlayAudioEngine::prepareOscillators() {
-
- double frequency = 440.0;
- constexpr double interval = 110.0;
- constexpr float amplitude = 0.25;
-
- for (SineGenerator &osc : mOscillators){
- osc.setup(frequency, mSampleRate, amplitude);
- frequency += interval;
- }
-}
-
/**
* Sets the stream parameters which are specific to playback, including device id and the
* callback class, which must be set for low latency playback.
@@ -140,10 +144,15 @@
LOGE("Error closing output stream. %s", oboe::convertToText(result));
}
}
+
+ if (mConversionBuffer != nullptr) {
+ delete[] mConversionBuffer;
+ mConversionBuffer = nullptr;
+ }
}
void PlayAudioEngine::setToneOn(bool isToneOn) {
- mIsToneOn = isToneOn;
+ mSoundGenerator->setTonesOn(isToneOn);
}
/**
@@ -178,27 +187,18 @@
Trace::beginSection("numFrames %d, Underruns %d, buffer size %d",
numFrames, underrunCountResult.value(), bufferSize);
+ bool is16BitFormat = (audioStream->getFormat() == oboe::AudioFormat::I16);
int32_t channelCount = audioStream->getChannelCount();
- // If the tone is on we need to use our synthesizer to render the audio data for the sine waves
- if (audioStream->getFormat() == oboe::AudioFormat::Float) {
- if (mIsToneOn) {
- for (int i = 0; i < channelCount; ++i) {
- mOscillators[i].render(static_cast<float *>(audioData) + i, channelCount, numFrames);
- }
- } else {
- memset(static_cast<uint8_t *>(audioData), 0,
- sizeof(float) * channelCount * numFrames);
- }
- } else {
- if (mIsToneOn) {
- for (int i = 0; i < channelCount; ++i) {
- mOscillators[i].render(static_cast<int16_t *>(audioData) + i, channelCount, numFrames);
- }
- } else {
- memset(static_cast<uint8_t *>(audioData), 0,
- sizeof(int16_t) * channelCount * numFrames);
- }
+ // If the stream is 16-bit render into a float buffer then convert that buffer to 16-bit ints
+ float *outputBuffer = (is16BitFormat) ? mConversionBuffer : static_cast<float *>(audioData);
+
+ mSoundGenerator->renderAudio(outputBuffer, numFrames);
+
+ if (is16BitFormat){
+ oboe::convertFloatToPcm16(mConversionBuffer,
+ static_cast<int16_t *>(audioData),
+ numFrames * channelCount);
}
if (mIsLatencyDetectionSupported) {
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
index ff4bce7..a45209b 100644
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
+++ b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.h
@@ -20,10 +20,13 @@
#include <thread>
#include <array>
#include <oboe/Oboe.h>
+
+#include "shared/Mixer.h"
+
#include "SineGenerator.h"
+#include "SoundGenerator.h"
constexpr int32_t kBufferSizeAutomatic = 0;
-constexpr int32_t kMaximumChannelCount = 8;
class PlayAudioEngine : oboe::AudioStreamCallback {
@@ -65,9 +68,11 @@
oboe::AudioStream *mPlayStream;
std::unique_ptr<oboe::LatencyTuner> mLatencyTuner;
std::mutex mRestartingLock;
+ std::unique_ptr<SoundGenerator> mSoundGenerator;
+ float *mConversionBuffer = nullptr;
// The SineGenerators generate audio data, feel free to replace with your own audio generators
- std::array<SineGenerator, kMaximumChannelCount> mOscillators;
+ //std::array<SineGenerator, kMaximumChannelCount> mOscillators;
void createPlaybackStream();
@@ -77,7 +82,7 @@
void setupPlaybackStreamParameters(oboe::AudioStreamBuilder *builder);
- void prepareOscillators();
+ void prepareOscillators(oboe::AudioFormat format, int32_t channelCount);
oboe::Result calculateCurrentOutputLatencyMillis(oboe::AudioStream *stream, double *latencyMillis);
diff --git a/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp b/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp
new file mode 100644
index 0000000..690bf37
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/SoundGenerator.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 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 <memory>
+#include "SoundGenerator.h"
+
+SoundGenerator::SoundGenerator(int32_t sampleRate, int32_t maxFrames, int32_t channelCount)
+ : mSampleRate(sampleRate)
+ , mChannelCount(channelCount)
+ , mOscillators(std::make_unique<Oscillator[]>(channelCount))
+ , mBuffer(std::make_unique<float[]>(maxFrames)){
+
+ double frequency = 440.0;
+ constexpr double interval = 110.0;
+ constexpr float amplitude = 1.0;
+
+ // Set up the oscillators
+ for (int i = 0; i < mChannelCount; ++i) {
+ mOscillators[i].setFrequency(frequency);
+ mOscillators[i].setSampleRate(mSampleRate);
+ mOscillators[i].setAmplitude(amplitude);
+ frequency += interval;
+ }
+}
+
+void SoundGenerator::renderAudio(float *audioData, int32_t numFrames) {
+
+ // Render each oscillator into its own channel
+ for (int i = 0; i < mChannelCount; ++i) {
+
+ mOscillators[i].renderAudio(mBuffer.get(), numFrames);
+ for (int j = 0; j < numFrames; ++j) {
+ audioData[(j*mChannelCount)+i] = mBuffer[j];
+ }
+ }
+}
+
+void SoundGenerator::setTonesOn(bool isOn) {
+ for (int i = 0; i < mChannelCount; ++i) {
+ mOscillators[i].setWaveOn(isOn);
+ }
+}
diff --git a/samples/hello-oboe/src/main/cpp/SoundGenerator.h b/samples/hello-oboe/src/main/cpp/SoundGenerator.h
new file mode 100644
index 0000000..7577cdc
--- /dev/null
+++ b/samples/hello-oboe/src/main/cpp/SoundGenerator.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 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 SAMPLES_SOUNDGENERATOR_H
+#define SAMPLES_SOUNDGENERATOR_H
+
+
+#include <shared/IRenderableAudio.h>
+#include <shared/Oscillator.h>
+
+/**
+ * Generates a fixed frequency tone for each channel.
+ */
+class SoundGenerator : public IRenderableAudio {
+public:
+ /**
+ * Create a new SoundGenerator object.
+ *
+ * @param sampleRate - The output sample rate.
+ * @param maxFrames - The maximum number of audio frames which will be rendered, this is used to
+ * calculate this object's internal buffer size.
+ * @param channelCount - The number of channels in the output, one tone will be created for each
+ * channel, the output will be interlaced.
+ *
+ */
+ SoundGenerator(int32_t sampleRate, int32_t maxFrames, int32_t channelCount);
+ ~SoundGenerator() = default;
+
+ // Switch the tones on
+ void setTonesOn(bool isOn);
+
+ // From IRenderableAudio
+ void renderAudio(float *audioData, int32_t numFrames) override;
+
+private:
+ const int32_t mSampleRate;
+ const int32_t mChannelCount;
+ const std::unique_ptr<Oscillator[]> mOscillators;
+ const std::unique_ptr<float[]> mBuffer;
+};
+
+
+#endif //SAMPLES_SOUNDGENERATOR_H
diff --git a/samples/shared/RenderableAudio.h b/samples/shared/IRenderableAudio.h
similarity index 83%
rename from samples/shared/RenderableAudio.h
rename to samples/shared/IRenderableAudio.h
index 756ed72..eee9872 100644
--- a/samples/shared/RenderableAudio.h
+++ b/samples/shared/IRenderableAudio.h
@@ -17,15 +17,14 @@
#ifndef MEGADRONE_RENDERABLEAUDIO_H
#define MEGADRONE_RENDERABLEAUDIO_H
-
#include <cstdint>
+#include <string>
-template <typename T>
-class RenderableAudio {
+class IRenderableAudio {
public:
- virtual ~RenderableAudio() = default;
- virtual void renderAudio(T *audioData, int32_t numFrames) = 0;
+ virtual ~IRenderableAudio() = default;
+ virtual void renderAudio(float *audioData, int32_t numFrames) = 0;
};
diff --git a/samples/shared/Mixer.h b/samples/shared/Mixer.h
index 6969832..d58d192 100644
--- a/samples/shared/Mixer.h
+++ b/samples/shared/Mixer.h
@@ -17,19 +17,19 @@
#ifndef RHYTHMGAME_MIXER_H
#define RHYTHMGAME_MIXER_H
-#include "RenderableAudio.h"
+#include <array>
+#include "IRenderableAudio.h"
constexpr int32_t kBufferSize = 192*10; // Temporary buffer is used for mixing
constexpr uint8_t kMaxTracks = 100;
-template <typename T>
-class Mixer : public RenderableAudio<T> {
+class Mixer : public IRenderableAudio {
public:
- void renderAudio(T *audioData, int32_t numFrames) {
+ void renderAudio(float *audioData, int32_t numFrames) {
// Zero out the incoming container array
- memset(audioData, 0, sizeof(T) * numFrames);
+ memset(audioData, 0, sizeof(float) * numFrames);
for (int i = 0; i < mNextFreeTrackIndex; ++i) {
mTracks[i]->renderAudio(mixingBuffer, numFrames);
@@ -40,13 +40,13 @@
}
}
- void addTrack(std::shared_ptr<RenderableAudio<T>> renderer){
+ void addTrack(std::shared_ptr<IRenderableAudio> renderer){
mTracks[mNextFreeTrackIndex++] = renderer;
}
private:
- T mixingBuffer[kBufferSize];
- std::array<std::shared_ptr<RenderableAudio<T>>, kMaxTracks> mTracks;
+ float mixingBuffer[kBufferSize];
+ std::array<std::shared_ptr<IRenderableAudio>, kMaxTracks> mTracks;
uint8_t mNextFreeTrackIndex = 0;
};
diff --git a/samples/shared/MonoToStereo.h b/samples/shared/MonoToStereo.h
index 80ff9a3..97ba349 100644
--- a/samples/shared/MonoToStereo.h
+++ b/samples/shared/MonoToStereo.h
@@ -14,19 +14,21 @@
* limitations under the License.
*/
-#ifndef MEGADRONE_MONOTOSTEREO_H
-#define MEGADRONE_MONOTOSTEREO_H
+#ifndef SHARED_MONOTOSTEREO_H
+#define SHARED_MONOTOSTEREO_H
-#include "RenderableAudio.h"
+#include "IRenderableAudio.h"
-template <typename T>
-class MonoToStereo : public RenderableAudio<T> {
+
+class MonoToStereo : public IRenderableAudio {
public:
- MonoToStereo(RenderableAudio<T> *input) : mInput(input){};
+ MonoToStereo(IRenderableAudio *input) : mInput(input){};
- void renderAudio(T *audioData, int32_t numFrames) override {
+ void renderAudio(float *audioData, int32_t numFrames) override {
+
+ constexpr int kChannelCountStereo = 2;
mInput->renderAudio(audioData, numFrames);
@@ -35,13 +37,13 @@
// e.g. 123 => 112233
for (int i = numFrames - 1; i >= 0; --i) {
- audioData[i * oboe::ChannelCount::Stereo] = audioData[i];
- audioData[i * oboe::ChannelCount::Stereo + 1] = audioData[i];
+ audioData[i * kChannelCountStereo] = audioData[i];
+ audioData[i * kChannelCountStereo + 1] = audioData[i];
}
}
- RenderableAudio<T> *mInput;
+ IRenderableAudio *mInput;
};
-#endif //MEGADRONE_MONOTOSTEREO_H
+#endif //SHARED_MONOTOSTEREO_H
diff --git a/samples/shared/Oscillator.cpp b/samples/shared/Oscillator.cpp
index fe590de..cdd77b2 100644
--- a/samples/shared/Oscillator.cpp
+++ b/samples/shared/Oscillator.cpp
@@ -17,7 +17,7 @@
#include "Oscillator.h"
// We need to calculate the amplitude value differently for each supported output format
-template<>
+/*template<>
void Oscillator<float>::setAmplitude(float amplitude){
mAmplitude = amplitude;
};
@@ -25,4 +25,4 @@
template<>
void Oscillator<int16_t>::setAmplitude(float amplitude){
mAmplitude = amplitude * INT16_MAX;
-};
\ No newline at end of file
+};*/
\ No newline at end of file
diff --git a/samples/shared/Oscillator.h b/samples/shared/Oscillator.h
index 33b2772..4847274 100644
--- a/samples/shared/Oscillator.h
+++ b/samples/shared/Oscillator.h
@@ -15,31 +15,32 @@
*/
-#ifndef MEGADRONE_OSCILLATOR_H
-#define MEGADRONE_OSCILLATOR_H
+#ifndef SHARED_OSCILLATOR_H
+#define SHARED_OSCILLATOR_H
#include <cstdint>
#include <atomic>
#include <math.h>
#include <memory>
-#include "RenderableAudio.h"
+#include "IRenderableAudio.h"
constexpr double kDefaultFrequency = 440.0;
constexpr int32_t kDefaultSampleRate = 48000;
constexpr double kPi = M_PI;
constexpr double kTwoPi = kPi * 2;
-template <typename T>
-class Oscillator : public RenderableAudio<T> {
+class Oscillator : public IRenderableAudio {
public:
- void setWaveOn(bool isWaveOn){
+ ~Oscillator() = default;
+
+ void setWaveOn(bool isWaveOn) {
mIsWaveOn.store(isWaveOn);
};
- void setSampleRate(int32_t sampleRate){
+ void setSampleRate(int32_t sampleRate) {
mSampleRate = sampleRate;
updatePhaseIncrement();
};
@@ -49,16 +50,18 @@
updatePhaseIncrement();
};
- void setAmplitude(float amplitude);
+ inline void setAmplitude(float amplitude) {
+ mAmplitude = amplitude;
+ };
- // From RenderableAudio<T>
- void renderAudio(T *audioData, int32_t numFrames) {
+ // From IRenderableAudio
+ void renderAudio(float *audioData, int32_t numFrames) override {
if (mIsWaveOn){
for (int i = 0; i < numFrames; ++i) {
// Sine wave (sinf)
- //audioData[i*kChannelCount] = sinf(mPhase) * mAmplitude;
+ //data[i*kChannelCount] = sinf(mPhase) * mAmplitude;
// Square wave
if (mPhase <= kPi){
@@ -71,14 +74,14 @@
if (mPhase > kTwoPi) mPhase -= kTwoPi;
}
} else {
- memset(audioData, 0, sizeof(T) * numFrames);
+ memset(audioData, 0, sizeof(float) * numFrames);
}
};
private:
std::atomic<bool> mIsWaveOn { false };
float mPhase = 0.0;
- std::atomic<T> mAmplitude { 0 };
+ std::atomic<float> mAmplitude { 0 };
std::atomic<double> mPhaseIncrement { 0.0 };
double mFrequency = kDefaultFrequency;
int32_t mSampleRate = kDefaultSampleRate;
@@ -88,4 +91,4 @@
};
};
-#endif //MEGADRONE_OSCILLATOR_H
+#endif //SHARED_OSCILLATOR_H