diff --git a/samples/drumthumper/build.gradle b/samples/drumthumper/build.gradle index 6778cbb6b..e79f2b0bb 100644 --- a/samples/drumthumper/build.gradle +++ b/samples/drumthumper/build.gradle @@ -12,7 +12,7 @@ android { // products. The current owner of Oboe sample apps on Google Play is Phil Burk, who // publishes using the application Id prefix of "com.plausiblesoftware". applicationId "com.plausiblesoftware.drumthumper" - minSdkVersion 26 + minSdkVersion 16 targetSdkVersion 29 versionCode 1 versionName "1.0" diff --git a/samples/iolib/build.gradle b/samples/iolib/build.gradle index b7b91597d..fe651f3ca 100644 --- a/samples/iolib/build.gradle +++ b/samples/iolib/build.gradle @@ -4,9 +4,8 @@ android { compileSdkVersion 29 buildToolsVersion "29.0.1" - defaultConfig { - minSdkVersion 26 + minSdkVersion 16 targetSdkVersion 29 versionCode 1 versionName "1.0" diff --git a/samples/parselib/build.gradle b/samples/parselib/build.gradle index beaf7928a..60192977d 100644 --- a/samples/parselib/build.gradle +++ b/samples/parselib/build.gradle @@ -4,7 +4,6 @@ android { compileSdkVersion 29 buildToolsVersion "29.0.1" - defaultConfig { minSdkVersion 16 targetSdkVersion 29 diff --git a/samples/parselib/src/main/cpp/wav/WavStreamReader.cpp b/samples/parselib/src/main/cpp/wav/WavStreamReader.cpp index 6d3dbcd39..968c8a987 100644 --- a/samples/parselib/src/main/cpp/wav/WavStreamReader.cpp +++ b/samples/parselib/src/main/cpp/wav/WavStreamReader.cpp @@ -28,14 +28,16 @@ static const char *TAG = "WavStreamReader"; +static constexpr int kConversionBufferFrames = 16; + namespace parselib { WavStreamReader::WavStreamReader(InputStream *stream) { mStream = stream; - mWavChunk = 0; - mFmtChunk = 0; - mDataChunk = 0; + mWavChunk = nullptr; + mFmtChunk = nullptr; + mDataChunk = nullptr; mAudioDataStartPos = -1; } @@ -76,21 +78,21 @@ void WavStreamReader::parse() { // __android_log_print(ANDROID_LOG_INFO, TAG, "[%c%c%c%c]", // tagStr[0], tagStr[1], tagStr[2], tagStr[3]); - std::shared_ptr chunk = 0; + std::shared_ptr chunk = nullptr; if (tag == WavRIFFChunkHeader::RIFFID_RIFF) { - chunk = mWavChunk = std::shared_ptr(new WavRIFFChunkHeader(tag)); + chunk = mWavChunk = std::make_shared(WavRIFFChunkHeader(tag)); mWavChunk->read(mStream); } else if (tag == WavFmtChunkHeader::RIFFID_FMT) { - chunk = mFmtChunk = std::shared_ptr(new WavFmtChunkHeader(tag)); + chunk = mFmtChunk = std::make_shared(WavFmtChunkHeader(tag)); mFmtChunk->read(mStream); } else if (tag == WavChunkHeader::RIFFID_DATA) { - chunk = mDataChunk = std::shared_ptr(new WavChunkHeader(tag)); + chunk = mDataChunk = std::make_shared(WavChunkHeader(tag)); mDataChunk->read(mStream); // We are now positioned at the start of the audio data. mAudioDataStartPos = mStream->getPos(); mStream->advance(mDataChunk->mChunkSize); } else { - chunk = std::shared_ptr(new WavChunkHeader(tag)); + chunk = std::make_shared(WavChunkHeader(tag)); chunk->read(mStream); mStream->advance(chunk->mChunkSize); // skip the body } @@ -110,48 +112,206 @@ void WavStreamReader::positionToAudio() { } } -int WavStreamReader::getDataFloat(float *buff, int numFrames) { - // __android_log_print(ANDROID_LOG_INFO, TAG, "getData(%d)", numFrames); +/** + * Read and convert samples in PCM8 format to float + */ +int WavStreamReader::getDataFloat_PCM8(float *buff, int numFrames) { + int numChannels = mFmtChunk->mNumChannels; + + int buffOffset = 0; + int totalFramesRead = 0; + + static constexpr int kSampleSize = sizeof(u_int8_t); + static constexpr float kSampleFullScale = (float)0x80; + static constexpr float kInverseScale = 1.0f / kSampleFullScale; - if (mDataChunk == 0 || mFmtChunk == 0) { - return 0; + u_int8_t readBuff[kConversionBufferFrames * numChannels]; + int framesLeft = numFrames; + while (framesLeft > 0) { + int framesThisRead = std::min(framesLeft, kConversionBufferFrames); + //__android_log_print(ANDROID_LOG_INFO, TAG, "read(%d)", framesThisRead); + int numFramesRead = + mStream->read(readBuff, framesThisRead * kSampleSize * numChannels) / + (kSampleSize * numChannels); + totalFramesRead += numFramesRead; + + // Convert & Scale + for (int offset = 0; offset < numFramesRead * numChannels; offset++) { + // PCM8 is unsigned, so we need to make it signed before scaling/converting + buff[buffOffset++] = ((float) readBuff[offset] - kSampleFullScale) + * kInverseScale; + } + + if (numFramesRead < framesThisRead) { + break; // none left + } + + framesLeft -= framesThisRead; } + return totalFramesRead; +} + +/** + * Read and convert samples in PCM16 format to float + */ +int WavStreamReader::getDataFloat_PCM16(float *buff, int numFrames) { + int numChannels = mFmtChunk->mNumChannels; + + int buffOffset = 0; int totalFramesRead = 0; - int numChans = mFmtChunk->mNumChannels; + static constexpr int kSampleSize = sizeof(int16_t); + static constexpr float kSampleFullScale = (float) 0x8000; + static constexpr float kInverseScale = 1.0f / kSampleFullScale; + + int16_t readBuff[kConversionBufferFrames * numChannels]; + int framesLeft = numFrames; + while (framesLeft > 0) { + int framesThisRead = std::min(framesLeft, kConversionBufferFrames); + //__android_log_print(ANDROID_LOG_INFO, TAG, "read(%d)", framesThisRead); + int numFramesRead = + mStream->read(readBuff, framesThisRead * kSampleSize * numChannels) / + (kSampleSize * numChannels); + totalFramesRead += numFramesRead; + + // Convert & Scale + for (int offset = 0; offset < numFramesRead * numChannels; offset++) { + buff[buffOffset++] = (float) readBuff[offset] * kInverseScale; + } + + if (numFramesRead < framesThisRead) { + break; // none left + } + + framesLeft -= framesThisRead; + } + + return totalFramesRead; +} + +/** + * Read and convert samples in PCM24 format to float + */ +int WavStreamReader::getDataFloat_PCM24(float *buff, int numFrames) { + int numChannels = mFmtChunk->mNumChannels; + int numSamples = numFrames * numChannels; + + static constexpr float kSampleFullScale = (float) 0x80000000; + static constexpr float kInverseScale = 1.0f / kSampleFullScale; + + uint8_t buffer[3]; + for(int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) { + if (mStream->read(buffer, 3) < 3) { + break; // no more data + } + int32_t sample = (buffer[0] << 8) | (buffer[1] << 16) | (buffer[2] << 24); + buff[sampleIndex] = (float)sample * kInverseScale; + } + + return numFrames; +} + +/** + * Read and convert samples in Float32 format to float + */ +int WavStreamReader::getDataFloat_Float32(float *buff, int numFrames) { + // Turns out that WAV Float32 is just Android floats + int numChannels = mFmtChunk->mNumChannels; + + return mStream->read(buff, numFrames * sizeof(float) * numChannels) / + (sizeof(float) * numChannels); +} + +/** + * Read and convert samples in PCM32 format to float + */ +int WavStreamReader::getDataFloat_PCM32(float *buff, int numFrames) { + int numChannels = mFmtChunk->mNumChannels; + int buffOffset = 0; + int totalFramesRead = 0; + + static constexpr int kSampleSize = sizeof(int32_t); + static constexpr float kSampleFullScale = (float) 0x80000000; + static constexpr float kInverseScale = 1.0f / kSampleFullScale; + + int32_t readBuff[kConversionBufferFrames * numChannels]; + int framesLeft = numFrames; + while (framesLeft > 0) { + int framesThisRead = std::min(framesLeft, kConversionBufferFrames); + //__android_log_print(ANDROID_LOG_INFO, TAG, "read(%d)", framesThisRead); + int numFramesRead = + mStream->read(readBuff, framesThisRead * kSampleSize* numChannels) / + (kSampleSize * numChannels); + totalFramesRead += numFramesRead; + + // convert & Scale + for (int offset = 0; offset < numFramesRead * numChannels; offset++) { + buff[buffOffset++] = (float) readBuff[offset] * kInverseScale; + } - // TODO - Manage other input formats - if (mFmtChunk->mSampleSize == 16) { - short *readBuff = new short[128 * numChans]; - int framesLeft = numFrames; - while (framesLeft > 0) { - int framesThisRead = std::min(framesLeft, 128); - //__android_log_print(ANDROID_LOG_INFO, TAG, "read(%d)", framesThisRead); - int numFramesRead = - mStream->read(readBuff, framesThisRead * sizeof(short) * numChans) / - (sizeof(short) * numChans); - totalFramesRead += numFramesRead; - - // convert - for (int offset = 0; offset < numFramesRead * numChans; offset++) { - buff[buffOffset++] = (float) readBuff[offset] / (float) 0x7FFF; + if (numFramesRead < framesThisRead) { + break; // none left + } + + framesLeft -= framesThisRead; + } + + return totalFramesRead; +} + +int WavStreamReader::getDataFloat(float *buff, int numFrames) { + // __android_log_print(ANDROID_LOG_INFO, TAG, "getData(%d)", numFrames); + + if (mDataChunk == nullptr || mFmtChunk == nullptr) { + return ERR_INVALID_STATE; + } + + int numFramesRead = 0; + switch (mFmtChunk->mSampleSize) { + case 8: + numFramesRead = getDataFloat_PCM8(buff, numFrames); + break; + + case 16: + numFramesRead = getDataFloat_PCM16(buff, numFrames); + break; + + case 24: + if (mFmtChunk->mEncodingId == WavFmtChunkHeader::ENCODING_PCM) { + numFramesRead = getDataFloat_PCM24(buff, numFrames); + } else { + __android_log_print(ANDROID_LOG_INFO, TAG, "invalid encoding:%d mSampleSize:%d", + mFmtChunk->mEncodingId, mFmtChunk->mSampleSize); } + break; - if (numFramesRead < framesThisRead) { - break; // none left + case 32: + if (mFmtChunk->mEncodingId == WavFmtChunkHeader::ENCODING_PCM) { + numFramesRead = getDataFloat_PCM32(buff, numFrames); + } else if (mFmtChunk->mEncodingId == WavFmtChunkHeader::ENCODING_IEEE_FLOAT) { + numFramesRead = getDataFloat_Float32(buff, numFrames); + } else { + __android_log_print(ANDROID_LOG_INFO, TAG, "invalid encoding:%d mSampleSize:%d", + mFmtChunk->mEncodingId, mFmtChunk->mSampleSize); } + break; - framesLeft -= framesThisRead; - } - delete[] readBuff; + default: + __android_log_print(ANDROID_LOG_INFO, TAG, "invalid encoding:%d mSampleSize:%d", + mFmtChunk->mEncodingId, mFmtChunk->mSampleSize); + return ERR_INVALID_FORMAT; + } - // __android_log_print(ANDROID_LOG_INFO, TAG, " returns:%d", totalFramesRead); - return totalFramesRead; + // Zero out any unread frames + if (numFramesRead < numFrames) { + int numChannels = getNumChannels(); + memset(buff + (numFramesRead * numChannels), 0, + (numFrames - numFramesRead) * sizeof(buff[0]) * numChannels); } - return 0; + return numFramesRead; } } // namespace parselib diff --git a/samples/parselib/src/main/cpp/wav/WavStreamReader.h b/samples/parselib/src/main/cpp/wav/WavStreamReader.h index ff6206351..1f7ee12f9 100644 --- a/samples/parselib/src/main/cpp/wav/WavStreamReader.h +++ b/samples/parselib/src/main/cpp/wav/WavStreamReader.h @@ -52,6 +52,9 @@ class WavStreamReader { // Data access void positionToAudio(); + static constexpr int ERR_INVALID_FORMAT = -1; + static constexpr int ERR_INVALID_STATE = -2; + int getDataFloat(float *buff, int numFrames); // int getData16(short *buff, int numFramees); @@ -66,6 +69,19 @@ class WavStreamReader { long mAudioDataStartPos; std::map> mChunkMap; + +private: + /* + * Individual Format Readers/Converters + */ + int getDataFloat_PCM8(float *buff, int numFrames); + + int getDataFloat_PCM16(float *buff, int numFrames); + + int getDataFloat_PCM24(float *buff, int numFrames); + + int getDataFloat_Float32(float *buff, int numFrames); + int getDataFloat_PCM32(float *buff, int numFrames); }; } // namespace parselib