Skip to content

Commit 141c54e

Browse files
authored
[PowerPlay] Add Local Playback and support (#2363)
* Add playback seeking and progress tracking. * Add support for loading and playing local WAV files * Update audio engine to probe and return WAV metadata during asset loading * Code nits * Cleanup: Remove unnecessary comments and metadata * Remove redundant comments and documentation blocks across `MainActivity.kt` and `PowerPlayMultiPlayer.cpp`. * Delete an unused Kotlin compiler session file. * Set audio usage and content type for PowerPlayMultiPlayer * refactor: remove redundant whitespace and empty lines in MainActivity.kt * parselib: Support WAVEFORMATEXTENSIBLE and improve WAV parsing robustness
1 parent 7556fc2 commit 141c54e

15 files changed

Lines changed: 548 additions & 92 deletions

File tree

samples/parselib/src/main/cpp/wav/WavFmtChunkHeader.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,36 @@ void WavFmtChunkHeader::read(InputStream *stream) {
6464
stream->read(&mBlockAlign, sizeof(mBlockAlign));
6565
stream->read(&mSampleSize, sizeof(mSampleSize));
6666

67-
if (mEncodingId != ENCODING_PCM && mEncodingId != ENCODING_IEEE_FLOAT) {
68-
// only read this if NOT PCM
67+
if (mEncodingId == ENCODING_EXTENSIBLE) {
6968
stream->read(&mExtraBytes, sizeof(mExtraBytes));
69+
70+
RiffInt16 validBitsPerSample;
71+
stream->read(&validBitsPerSample, sizeof(validBitsPerSample));
72+
73+
RiffInt32 channelMask;
74+
stream->read(&channelMask, sizeof(channelMask));
75+
76+
RiffInt16 subFormatEncoding;
77+
stream->read(&subFormatEncoding, sizeof(subFormatEncoding));
78+
mEncodingId = subFormatEncoding;
79+
80+
stream->advance(14);
81+
} else if (mEncodingId != ENCODING_PCM &&
82+
mEncodingId != ENCODING_IEEE_FLOAT) {
83+
84+
// other non-PCM formats (e.g., ADPCM)
85+
stream->read(&mExtraBytes, sizeof(mExtraBytes));
86+
87+
// Skip any remaining extension data beyond the 18 bytes already read
88+
int bytesRemaining = mChunkSize - 18;
89+
if (bytesRemaining > 0) {
90+
stream->advance(bytesRemaining);
91+
}
7092
} else {
7193
mExtraBytes = (short) (mChunkSize - 16);
94+
if (mExtraBytes > 0) {
95+
stream->advance(mExtraBytes);
96+
}
7297
}
7398
}
7499

samples/parselib/src/main/cpp/wav/WavFmtChunkHeader.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class WavFmtChunkHeader : public WavChunkHeader {
3333
static const short ENCODING_PCM = 1;
3434
static const short ENCODING_ADPCM = 2; // Microsoft ADPCM Format
3535
static const short ENCODING_IEEE_FLOAT = 3; // samples from -1.0 -> 1.0
36+
static const short ENCODING_EXTENSIBLE = (short)0xFFFE; // WAVEFORMATEXTENSIBLE
3637

3738
RiffInt16 mEncodingId; /** Microsoft WAV encoding ID (see above) */
3839
RiffInt16 mNumChannels;

samples/parselib/src/main/cpp/wav/WavStreamReader.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,16 +202,18 @@ int WavStreamReader::getDataFloat_PCM24(float *buff, int numFrames) {
202202
static constexpr float kSampleFullScale = (float) 0x80000000;
203203
static constexpr float kInverseScale = 1.0f / kSampleFullScale;
204204

205+
int samplesRead = 0;
205206
uint8_t buffer[3];
206207
for(int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) {
207208
if (mStream->read(buffer, 3) < 3) {
208209
break; // no more data
209210
}
210211
int32_t sample = (buffer[0] << 8) | (buffer[1] << 16) | (buffer[2] << 24);
211212
buff[sampleIndex] = (float)sample * kInverseScale;
213+
samplesRead++;
212214
}
213215

214-
return numFrames;
216+
return samplesRead / numChannels;
215217
}
216218

217219
/**

samples/parselib/src/main/cpp/wav/WavStreamReader.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,23 @@ class WavStreamReader {
3535
public:
3636
WavStreamReader(InputStream *stream);
3737

38-
int getSampleRate() { return mFmtChunk->mSampleRate; }
38+
bool isValid() { return mFmtChunk != nullptr && mDataChunk != nullptr; }
39+
40+
int getSampleRate() { return mFmtChunk != nullptr ? mFmtChunk->mSampleRate : 0; }
3941

4042
int getNumSampleFrames() {
43+
if (mDataChunk == nullptr || mFmtChunk == nullptr ||
44+
mFmtChunk->mSampleSize == 0 || mFmtChunk->mNumChannels == 0) {
45+
return 0;
46+
}
4147
return mDataChunk->mChunkSize / (mFmtChunk->mSampleSize / 8) / mFmtChunk->mNumChannels;
4248
}
4349

44-
int getNumChannels() { return mFmtChunk != 0 ? mFmtChunk->mNumChannels : 0; }
50+
int getNumChannels() { return mFmtChunk != nullptr ? mFmtChunk->mNumChannels : 0; }
4551

4652
int getSampleEncoding();
4753

48-
int getBitsPerSample() { return mFmtChunk->mSampleSize; }
54+
int getBitsPerSample() { return mFmtChunk != nullptr ? mFmtChunk->mSampleSize : 0; }
4955

5056
void parse();
5157

-3.8 MB
Binary file not shown.
-13.7 MB
Binary file not shown.

samples/powerplay/src/main/cpp/PowerPlayJNI.cpp

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,67 @@ Java_com_google_oboe_samples_powerplay_engine_PowerPlayAudioPlayer_getDurationMi
359359
return (jlong) player.getDurationMillis(index);
360360
}
361361

362+
/**
363+
* Native (JNI) implementation of PowerPlayAudioPlayer.getWavFileInfoNative()
364+
* Parses WAV header and returns audio properties without loading sample data.
365+
* Returns an intArray: [sampleRate, numChannels, bitsPerSample, encoding, numFramesHigh, numFramesLow]
366+
*/
367+
JNIEXPORT jintArray JNICALL
368+
Java_com_google_oboe_samples_powerplay_engine_PowerPlayAudioPlayer_getWavFileInfoNative(
369+
JNIEnv *env,
370+
jobject,
371+
jbyteArray bytearray) {
372+
const int32_t len = env->GetArrayLength(bytearray);
373+
auto *buf = new unsigned char[len];
374+
env->GetByteArrayRegion(bytearray, 0, len, reinterpret_cast<jbyte *>(buf));
375+
376+
MemInputStream stream(buf, len);
377+
WavStreamReader reader(&stream);
378+
reader.parse();
379+
380+
if (!reader.isValid()) {
381+
__android_log_print(ANDROID_LOG_ERROR, TAG,
382+
"getWavFileInfoNative: WAV parse failed - missing fmt or data chunk");
383+
jintArray result = env->NewIntArray(6);
384+
jint values[6] = {0, 0, 0, 0, 0, 0};
385+
env->SetIntArrayRegion(result, 0, 6, values);
386+
delete[] buf;
387+
return result;
388+
}
389+
390+
int32_t sampleRate = reader.getSampleRate();
391+
int32_t numChannels = reader.getNumChannels();
392+
int32_t bitsPerSample = reader.getBitsPerSample();
393+
int32_t encoding = reader.getSampleEncoding();
394+
int64_t numFrames = static_cast<int64_t>(reader.getNumSampleFrames());
395+
396+
// Pack into int array: [sampleRate, numChannels, bitsPerSample, encoding, numFramesHigh, numFramesLow]
397+
jintArray result = env->NewIntArray(6);
398+
jint values[6] = {
399+
sampleRate,
400+
numChannels,
401+
bitsPerSample,
402+
encoding,
403+
static_cast<jint>((numFrames >> 32) & 0xFFFFFFFF),
404+
static_cast<jint>(numFrames & 0xFFFFFFFF)
405+
};
406+
env->SetIntArrayRegion(result, 0, 6, values);
407+
408+
delete[] buf;
409+
return result;
410+
}
411+
412+
/**
413+
* Native (JNI) implementation of PowerPlayAudioPlayer.removeSampleSourceNative()
414+
*/
415+
JNIEXPORT jboolean JNICALL
416+
Java_com_google_oboe_samples_powerplay_engine_PowerPlayAudioPlayer_removeSampleSourceNative(
417+
JNIEnv *env,
418+
jobject,
419+
jint index) {
420+
return player.removeSampleSource(index);
421+
}
422+
362423
#ifdef __cplusplus
363424
}
364425
#endif
365-

samples/powerplay/src/main/cpp/PowerPlayMultiPlayer.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ bool PowerPlayMultiPlayer::openStream(oboe::PerformanceMode performanceMode) {
5555
->setFormat(AudioFormat::Float)
5656
->setSampleRate(48000)
5757
->setPerformanceMode(performanceMode)
58+
->setUsage(Usage::Media)
59+
->setContentType(ContentType::Music)
5860
->setFramesPerDataCallback(128)
5961
->setSharingMode(SharingMode::Exclusive);
6062

@@ -320,3 +322,31 @@ int64_t PowerPlayMultiPlayer::getDurationMillis(int32_t index) {
320322

321323
return (static_cast<int64_t>(sampleBuffer->getNumSamples() / channelCount) * 1000) / sampleRate;
322324
}
325+
326+
bool PowerPlayMultiPlayer::removeSampleSource(int32_t index) {
327+
if (index < 0 || index >= mSampleSources.size()) {
328+
__android_log_print(ANDROID_LOG_ERROR, TAG,
329+
"removeSampleSource: Invalid index %d (size=%zu)",
330+
index, mSampleSources.size());
331+
return false;
332+
}
333+
334+
if (mSampleSources[index]->isPlaying()) {
335+
mSampleSources[index]->setStopMode(false);
336+
}
337+
338+
delete mSampleSources[index];
339+
#pragma GCC diagnostic push
340+
#pragma GCC diagnostic ignored "-Wdelete-non-abstract-non-virtual-dtor"
341+
delete mSampleBuffers[index];
342+
#pragma GCC diagnostic pop
343+
344+
mSampleSources.erase(mSampleSources.begin() + index);
345+
mSampleBuffers.erase(mSampleBuffers.begin() + index);
346+
mNumSampleBuffers--;
347+
348+
__android_log_print(ANDROID_LOG_INFO, TAG,
349+
"removeSampleSource: Removed index %d, %d sources remaining",
350+
index, mNumSampleBuffers);
351+
return true;
352+
}

samples/powerplay/src/main/cpp/PowerPlayMultiPlayer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class PowerPlayMultiPlayer : public iolib::SimpleMultiPlayer {
5858

5959
int64_t getDurationMillis(int32_t index);
6060

61+
bool removeSampleSource(int32_t index);
62+
6163
private:
6264
class MyPresentationCallback : public oboe::AudioStreamPresentationCallback {
6365
public:

0 commit comments

Comments
 (0)