Skip to content

Commit

Permalink
Added resample on sample load to avoid the resampling penalty at play
Browse files Browse the repository at this point in the history
time.
  • Loading branch information
android-usb committed Aug 31, 2020
1 parent c5dcad3 commit 19143c3
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 18 deletions.
12 changes: 7 additions & 5 deletions samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ static SimpleMultiPlayer sDTPlayer;
* Native (JNI) implementation of DrumPlayer.setupAudioStreamNative()
*/
JNIEXPORT void JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_setupAudioStreamNative(
JNIEnv* env, jobject, jint sampleRate, jint numChannels) {
JNIEnv* env, jobject, jint numChannels) {
__android_log_print(ANDROID_LOG_INFO, TAG, "%s", "init()");

// we know in this case that the sample buffers are all 1-channel, 41K
sDTPlayer.setupAudioStream(sampleRate, numChannels);
sDTPlayer.setupAudioStream(numChannels);
}

/**
Expand All @@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_teardow
* Native (JNI) implementation of DrumPlayer.loadWavAssetNative()
*/
JNIEXPORT jboolean JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_loadWavAssetNative(
JNIEnv* env, jobject, jbyteArray bytearray, jint index, jfloat pan, jint rate, jint channels) {
JNIEnv* env, jobject, jbyteArray bytearray, jint index, jfloat pan, jint channels) {
int len = env->GetArrayLength (bytearray);

unsigned char* buf = new unsigned char[len];
Expand All @@ -80,12 +80,14 @@ JNIEXPORT jboolean JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_loa
WavStreamReader reader(&stream);
reader.parse();

jboolean isFormatValid =
(reader.getSampleRate() == rate) && (reader.getNumChannels() == channels);
jboolean isFormatValid = reader.getNumChannels() == channels;

SampleBuffer* sampleBuffer = new SampleBuffer();
sampleBuffer->loadSampleData(&reader);

int sampleRate = sDTPlayer.getDeviceSampleRate(2 /*channelCount*/);
sampleBuffer->resampleData(sampleRate);

OneShotSampleSource* source = new OneShotSampleSource(sampleBuffer, pan);
sDTPlayer.addSampleSource(source, sampleBuffer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class DrumPlayer {
// This IS NOT the channel format of the source samples
// (which must be mono).
val NUM_SAMPLE_CHANNELS: Int = 1; // All WAV resource must be mono
val SAMPLE_RATE: Int = 44100 // All the input samples are assumed to BE 44.1K
// All the input samples are assumed to be mono.
// val SAMPLE_RATE: Int = 44100 // All the input samples are assumed to BE 44.1K
// // All the input samples are assumed to be mono.

// Sample Buffer IDs
val BASSDRUM: Int = 0
Expand All @@ -55,7 +55,7 @@ class DrumPlayer {
}

fun setupAudioStream() {
setupAudioStreamNative(SAMPLE_RATE, NUM_PLAY_CHANNELS)
setupAudioStreamNative(NUM_PLAY_CHANNELS)
}

fun teardownAudioStream() {
Expand Down Expand Up @@ -89,7 +89,7 @@ class DrumPlayer {
var dataLen = assetFD.getLength().toInt()
var dataBytes: ByteArray = ByteArray(dataLen)
dataStream.read(dataBytes, 0, dataLen)
returnVal = loadWavAssetNative(dataBytes, index, pan, SAMPLE_RATE, NUM_SAMPLE_CHANNELS)
returnVal = loadWavAssetNative(dataBytes, index, pan, NUM_SAMPLE_CHANNELS)
assetFD.close()
} catch (ex: IOException) {
Log.i(TAG, "IOException" + ex)
Expand All @@ -98,11 +98,11 @@ class DrumPlayer {
return returnVal
}

external fun setupAudioStreamNative(sampleRate: Int, numChannels: Int)
external fun setupAudioStreamNative(numChannels: Int)
external fun teardownAudioStreamNative()

external fun loadWavAssetNative(
wavBytes: ByteArray, index: Int, pan: Float, rate: Int, channels: Int) : Boolean
wavBytes: ByteArray, index: Int, pan: Float, channels: Int) : Boolean
external fun unloadWavAssetsNative()

external fun trigger(drumIndex: Int)
Expand Down
1 change: 1 addition & 0 deletions samples/iolib/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set (PARSELIB_DIR ../../../../parselib)
include_directories(
${PARSELIB_DIR}/src/main/cpp
${OBOE_DIR}/include
${OBOE_DIR}/src/flowgraph
${CMAKE_CURRENT_LIST_DIR}
../../../../shared)

Expand Down
74 changes: 74 additions & 0 deletions samples/iolib/src/main/cpp/player/SampleBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@

#include "SampleBuffer.h"

// Resampler Includes
#include <resampler/MultiChannelResampler.h>

#include "wav/WavStreamReader.h"

using namespace resampler;

namespace iolib {

void SampleBuffer::loadSampleData(parselib::WavStreamReader* reader) {
Expand All @@ -41,4 +46,73 @@ void SampleBuffer::unloadSampleData() {
mNumSamples = 0;
}

class ResampleBlock {
public:
int32_t mSampleRate;
float* mBuffer;
int32_t mNumFrames;
};

void resampleData(const ResampleBlock& input, ResampleBlock* output) {
// Calculate output buffer size
double temp =
((double)input.mNumFrames * (double)output->mSampleRate) / (double)input.mSampleRate;

// round up
int32_t numOutFrames = (int32_t)(temp + 0.5);
output->mNumFrames = numOutFrames;

MultiChannelResampler *resampler = MultiChannelResampler::make(
1, // channel count
input.mSampleRate, // input sampleRate
output->mSampleRate, // output sampleRate
MultiChannelResampler::Quality::Medium); // conversion quality

float *inputBuffer = input.mBuffer;; // multi-channel buffer to be consumed
float *outputBuffer = new float[numOutFrames]; // multi-channel buffer to be filled
output->mBuffer = outputBuffer;
int numInputFrames = input.mNumFrames; // number of frames of input
int numOutputFrames = 0;
int channelCount = 1; // 1 for mono, 2 for stereo

int inputFramesLeft = numInputFrames;
while (inputFramesLeft > 0) {
if(resampler->isWriteNeeded()) {
resampler->writeNextFrame(inputBuffer);
inputBuffer += channelCount;
inputFramesLeft--;
} else {
resampler->readNextFrame(outputBuffer);
outputBuffer += channelCount;
numOutputFrames++;
}
}

delete resampler;
}

void SampleBuffer::resampleData(int sampleRate) {
if (mAudioProperties.sampleRate == sampleRate) {
// nothing to do
return;
}

ResampleBlock inputBlock;
inputBlock.mBuffer = mSampleData;
inputBlock.mNumFrames = mNumSamples;
inputBlock.mSampleRate = mAudioProperties.sampleRate;

ResampleBlock outputBlock;
outputBlock.mSampleRate = sampleRate;
iolib::resampleData(inputBlock, &outputBlock);

// delete previous samples
delete[] mSampleData;

// install the resampled data
mSampleData = outputBlock.mBuffer;
mNumSamples = outputBlock.mNumFrames;
mAudioProperties.sampleRate = outputBlock.mSampleRate;
}

} // namespace iolib
2 changes: 2 additions & 0 deletions samples/iolib/src/main/cpp/player/SampleBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class SampleBuffer {
void loadSampleData(parselib::WavStreamReader* reader);
void unloadSampleData();

void resampleData(int sampleRate);

virtual AudioProperties getProperties() const { return mAudioProperties; }

float* getSampleData() { return mSampleData; }
Expand Down
29 changes: 24 additions & 5 deletions samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace iolib {
constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)

SimpleMultiPlayer::SimpleMultiPlayer()
: mChannelCount(0), mSampleRate(0), mOutputReset(false)
: mChannelCount(0), mOutputReset(false)
{}

DataCallbackResult SimpleMultiPlayer::onAudioReady(AudioStream *oboeStream, void *audioData,
Expand Down Expand Up @@ -72,13 +72,34 @@ void SimpleMultiPlayer::onErrorBeforeClose(AudioStream *, Result error) {
__android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorBeforeClose() error:%d", error);
}

int SimpleMultiPlayer::getDeviceSampleRate(int32_t channelCount) {

// Create an audio stream
AudioStreamBuilder builder;
builder.setChannelCount(channelCount);
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setSharingMode(SharingMode::Exclusive);
builder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium);

oboe::ManagedStream audioStream;
Result result = builder.openManagedStream(audioStream);
if (result != Result::OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG,
"openStream failed. Error: %s", convertToText(result));
return 0;
} else {
int rate = audioStream->getSampleRate();
return rate;
}
}

bool SimpleMultiPlayer::openStream() {
__android_log_print(ANDROID_LOG_INFO, TAG, "openStream()");

// Create an audio stream
AudioStreamBuilder builder;
builder.setChannelCount(mChannelCount);
builder.setSampleRate(mSampleRate);
// builder.setSampleRate(mSampleRate); // we will resample to device rate
builder.setCallback(this);
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setSharingMode(SharingMode::Exclusive);
Expand Down Expand Up @@ -117,11 +138,9 @@ bool SimpleMultiPlayer::openStream() {
return true;
}

void SimpleMultiPlayer::setupAudioStream(int32_t sampleRate, int32_t channelCount) {
void SimpleMultiPlayer::setupAudioStream(int32_t channelCount) {
__android_log_print(ANDROID_LOG_INFO, TAG, "setupAudioStream()");
mChannelCount = channelCount;
mSampleRate = sampleRate;
mSampleRate = sampleRate;

openStream();
}
Expand Down
4 changes: 3 additions & 1 deletion samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ class SimpleMultiPlayer : public oboe::AudioStreamCallback {
virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override;

void setupAudioStream(int32_t sampleRate, int32_t channelCount);
void setupAudioStream(int32_t channelCount);
void teardownAudioStream();

static int getDeviceSampleRate(int32_t channelCount);

bool openStream();

// Wave Sample Loading...
Expand Down
2 changes: 1 addition & 1 deletion src/flowgraph/resampler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Only do this once, when you open your stream. Then use the sample resampler to p
2, // channel count
44100, // input sampleRate
48000, // output sampleRate
MultiChannelResampler::Medium); // conversion quality
MultiChannelResampler::Quality::Medium); // conversion quality

Possible values for quality include { Fastest, Low, Medium, High, Best }.
Higher quality levels will sound better but consume more CPU because they have more taps in the filter.
Expand Down

0 comments on commit 19143c3

Please sign in to comment.