aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/samples/RhythmGame/src/main/cpp/audio
diff options
context:
space:
mode:
authorswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
committerswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
commit6c655321e6b04b8648802e296bb5ca11d290ae68 (patch)
tree5c906511e4a2910ce8b806bb9d763b524c28619e /externals/oboe/samples/RhythmGame/src/main/cpp/audio
parentc0cceff365f754f8859f4c9a0362ebdba7ea388b (diff)
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
Diffstat (limited to 'externals/oboe/samples/RhythmGame/src/main/cpp/audio')
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp86
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h50
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/DataSource.h32
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.cpp318
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.h55
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.cpp199
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.h33
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.cpp59
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.h60
9 files changed, 892 insertions, 0 deletions
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp
new file mode 100644
index 0000000000..1c697878c2
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 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 <utils/logging.h>
+#include <oboe/Oboe.h>
+
+#include "AAssetDataSource.h"
+
+#if !defined(USE_FFMPEG)
+#error USE_FFMPEG should be defined in app.gradle
+#endif
+
+#if USE_FFMPEG==1
+#include "FFMpegExtractor.h"
+#else
+#include "NDKExtractor.h"
+#endif
+
+
+constexpr int kMaxCompressionRatio { 12 };
+
+AAssetDataSource* AAssetDataSource::newFromCompressedAsset(
+ AAssetManager &assetManager,
+ const char *filename,
+ const AudioProperties targetProperties) {
+
+ AAsset *asset = AAssetManager_open(&assetManager, filename, AASSET_MODE_UNKNOWN);
+ if (!asset) {
+ LOGE("Failed to open asset %s", filename);
+ return nullptr;
+ }
+
+ off_t assetSize = AAsset_getLength(asset);
+ LOGD("Opened %s, size %ld", filename, assetSize);
+
+ // Allocate memory to store the decompressed audio. We don't know the exact
+ // size of the decoded data until after decoding so we make an assumption about the
+ // maximum compression ratio and the decoded sample format (float for FFmpeg, int16 for NDK).
+#if USE_FFMPEG==true
+ const long maximumDataSizeInBytes = kMaxCompressionRatio * assetSize * sizeof(float);
+ auto decodedData = new uint8_t[maximumDataSizeInBytes];
+
+ int64_t bytesDecoded = FFMpegExtractor::decode(asset, decodedData, targetProperties);
+ auto numSamples = bytesDecoded / sizeof(float);
+#else
+ const long maximumDataSizeInBytes = kMaxCompressionRatio * assetSize * sizeof(int16_t);
+ auto decodedData = new uint8_t[maximumDataSizeInBytes];
+
+ int64_t bytesDecoded = NDKExtractor::decode(asset, decodedData, targetProperties);
+ auto numSamples = bytesDecoded / sizeof(int16_t);
+#endif
+
+ // Now we know the exact number of samples we can create a float array to hold the audio data
+ auto outputBuffer = std::make_unique<float[]>(numSamples);
+
+#if USE_FFMPEG==1
+ memcpy(outputBuffer.get(), decodedData, (size_t)bytesDecoded);
+#else
+ // The NDK decoder can only decode to int16, we need to convert to floats
+ oboe::convertPcm16ToFloat(
+ reinterpret_cast<int16_t*>(decodedData),
+ outputBuffer.get(),
+ bytesDecoded / sizeof(int16_t));
+#endif
+
+ delete[] decodedData;
+ AAsset_close(asset);
+
+ return new AAssetDataSource(std::move(outputBuffer),
+ numSamples,
+ targetProperties);
+}
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h
new file mode 100644
index 0000000000..cbcb433a34
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 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 RHYTHMGAME_AASSETDATASOURCE_H
+#define RHYTHMGAME_AASSETDATASOURCE_H
+
+#include <android/asset_manager.h>
+#include <GameConstants.h>
+#include "DataSource.h"
+
+class AAssetDataSource : public DataSource {
+
+public:
+ int64_t getSize() const override { return mBufferSize; }
+ AudioProperties getProperties() const override { return mProperties; }
+ const float* getData() const override { return mBuffer.get(); }
+
+ static AAssetDataSource* newFromCompressedAsset(
+ AAssetManager &assetManager,
+ const char *filename,
+ AudioProperties targetProperties);
+
+private:
+
+ AAssetDataSource(std::unique_ptr<float[]> data, size_t size,
+ const AudioProperties properties)
+ : mBuffer(std::move(data))
+ , mBufferSize(size)
+ , mProperties(properties) {
+ }
+
+ const std::unique_ptr<float[]> mBuffer;
+ const int64_t mBufferSize;
+ const AudioProperties mProperties;
+
+};
+#endif //RHYTHMGAME_AASSETDATASOURCE_H
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/DataSource.h b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/DataSource.h
new file mode 100644
index 0000000000..90284536bb
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/DataSource.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 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 RHYTHMGAME_AUDIOSOURCE_H
+#define RHYTHMGAME_AUDIOSOURCE_H
+
+#include <cstdint>
+#include <GameConstants.h>
+
+class DataSource {
+public:
+ virtual ~DataSource(){};
+ virtual int64_t getSize() const = 0;
+ virtual AudioProperties getProperties() const = 0;
+ virtual const float* getData() const = 0;
+};
+
+
+#endif //RHYTHMGAME_AUDIOSOURCE_H
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.cpp b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.cpp
new file mode 100644
index 0000000000..97d212f369
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.cpp
@@ -0,0 +1,318 @@
+/*
+ * 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 <oboe/Definitions.h>
+#include "FFMpegExtractor.h"
+#include "utils/logging.h"
+
+constexpr int kInternalBufferSize = 1152; // Use MP3 block size. https://wiki.hydrogenaud.io/index.php?title=MP3
+
+int read(void *opaque, uint8_t *buf, int buf_size) {
+
+ auto asset = (AAsset *) opaque;
+ int bytesRead = AAsset_read(asset, buf, (size_t)buf_size);
+ return bytesRead;
+}
+
+int64_t seek(void *opaque, int64_t offset, int whence){
+
+ auto asset = (AAsset*)opaque;
+
+ // See https://www.ffmpeg.org/doxygen/3.0/avio_8h.html#a427ff2a881637b47ee7d7f9e368be63f
+ if (whence == AVSEEK_SIZE) return AAsset_getLength(asset);
+ if (AAsset_seek(asset, offset, whence) == -1){
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+bool FFMpegExtractor::createAVIOContext(AAsset *asset, uint8_t *buffer, uint32_t bufferSize,
+ AVIOContext **avioContext) {
+
+ constexpr int isBufferWriteable = 0;
+
+ *avioContext = avio_alloc_context(
+ buffer, // internal buffer for FFmpeg to use
+ bufferSize, // For optimal decoding speed this should be the protocol block size
+ isBufferWriteable,
+ asset, // Will be passed to our callback functions as a (void *)
+ read, // Read callback function
+ nullptr, // Write callback function (not used)
+ seek); // Seek callback function
+
+ if (*avioContext == nullptr){
+ LOGE("Failed to create AVIO context");
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool
+FFMpegExtractor::createAVFormatContext(AVIOContext *avioContext, AVFormatContext **avFormatContext) {
+
+ *avFormatContext = avformat_alloc_context();
+ (*avFormatContext)->pb = avioContext;
+
+ if (*avFormatContext == nullptr){
+ LOGE("Failed to create AVFormatContext");
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool FFMpegExtractor::openAVFormatContext(AVFormatContext *avFormatContext) {
+
+ int result = avformat_open_input(&avFormatContext,
+ "", /* URL is left empty because we're providing our own I/O */
+ nullptr /* AVInputFormat *fmt */,
+ nullptr /* AVDictionary **options */
+ );
+
+ if (result == 0) {
+ return true;
+ } else {
+ LOGE("Failed to open file. Error code %s", av_err2str(result));
+ return false;
+ }
+}
+
+bool FFMpegExtractor::getStreamInfo(AVFormatContext *avFormatContext) {
+
+ int result = avformat_find_stream_info(avFormatContext, nullptr);
+ if (result == 0 ){
+ return true;
+ } else {
+ LOGE("Failed to find stream info. Error code %s", av_err2str(result));
+ return false;
+ }
+}
+
+AVStream *FFMpegExtractor::getBestAudioStream(AVFormatContext *avFormatContext) {
+
+ int streamIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
+
+ if (streamIndex < 0){
+ LOGE("Could not find stream");
+ return nullptr;
+ } else {
+ return avFormatContext->streams[streamIndex];
+ }
+}
+
+int64_t FFMpegExtractor::decode(
+ AAsset *asset,
+ uint8_t *targetData,
+ AudioProperties targetProperties) {
+
+ LOGI("Decoder: FFMpeg");
+
+ int returnValue = -1; // -1 indicates error
+
+ // Create a buffer for FFmpeg to use for decoding (freed in the custom deleter below)
+ auto buffer = reinterpret_cast<uint8_t*>(av_malloc(kInternalBufferSize));
+
+ // Create an AVIOContext with a custom deleter
+ std::unique_ptr<AVIOContext, void(*)(AVIOContext *)> ioContext {
+ nullptr,
+ [](AVIOContext *c) {
+ av_free(c->buffer);
+ avio_context_free(&c);
+ }
+ };
+ {
+ AVIOContext *tmp = nullptr;
+ if (!createAVIOContext(asset, buffer, kInternalBufferSize, &tmp)){
+ LOGE("Could not create an AVIOContext");
+ return returnValue;
+ }
+ ioContext.reset(tmp);
+ }
+
+ // Create an AVFormatContext using the avformat_free_context as the deleter function
+ std::unique_ptr<AVFormatContext, decltype(&avformat_free_context)> formatContext {
+ nullptr,
+ &avformat_free_context
+ };
+ {
+ AVFormatContext *tmp;
+ if (!createAVFormatContext(ioContext.get(), &tmp)) return returnValue;
+ formatContext.reset(tmp);
+ }
+
+ if (!openAVFormatContext(formatContext.get())) return returnValue;
+
+ if (!getStreamInfo(formatContext.get())) return returnValue;
+
+ // Obtain the best audio stream to decode
+ AVStream *stream = getBestAudioStream(formatContext.get());
+ if (stream == nullptr || stream->codecpar == nullptr){
+ LOGE("Could not find a suitable audio stream to decode");
+ return returnValue;
+ }
+
+ printCodecParameters(stream->codecpar);
+
+ // Find the codec to decode this stream
+ AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
+ if (!codec){
+ LOGE("Could not find codec with ID: %d", stream->codecpar->codec_id);
+ return returnValue;
+ }
+
+ // Create the codec context, specifying the deleter function
+ std::unique_ptr<AVCodecContext, void(*)(AVCodecContext *)> codecContext {
+ nullptr,
+ [](AVCodecContext *c) { avcodec_free_context(&c); }
+ };
+ {
+ AVCodecContext *tmp = avcodec_alloc_context3(codec);
+ if (!tmp){
+ LOGE("Failed to allocate codec context");
+ return returnValue;
+ }
+ codecContext.reset(tmp);
+ }
+
+ // Copy the codec parameters into the context
+ if (avcodec_parameters_to_context(codecContext.get(), stream->codecpar) < 0){
+ LOGE("Failed to copy codec parameters to codec context");
+ return returnValue;
+ }
+
+ // Open the codec
+ if (avcodec_open2(codecContext.get(), codec, nullptr) < 0){
+ LOGE("Could not open codec");
+ return returnValue;
+ }
+
+ // prepare resampler
+ int32_t outChannelLayout = (1 << targetProperties.channelCount) - 1;
+ LOGD("Channel layout %d", outChannelLayout);
+
+ SwrContext *swr = swr_alloc();
+ av_opt_set_int(swr, "in_channel_count", stream->codecpar->channels, 0);
+ av_opt_set_int(swr, "out_channel_count", targetProperties.channelCount, 0);
+ av_opt_set_int(swr, "in_channel_layout", stream->codecpar->channel_layout, 0);
+ av_opt_set_int(swr, "out_channel_layout", outChannelLayout, 0);
+ av_opt_set_int(swr, "in_sample_rate", stream->codecpar->sample_rate, 0);
+ av_opt_set_int(swr, "out_sample_rate", targetProperties.sampleRate, 0);
+ av_opt_set_int(swr, "in_sample_fmt", stream->codecpar->format, 0);
+ av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
+ av_opt_set_int(swr, "force_resampling", 1, 0);
+
+ // Check that resampler has been inited
+ int result = swr_init(swr);
+ if (result != 0){
+ LOGE("swr_init failed. Error: %s", av_err2str(result));
+ return returnValue;
+ };
+ if (!swr_is_initialized(swr)) {
+ LOGE("swr_is_initialized is false\n");
+ return returnValue;
+ }
+
+ // Prepare to read data
+ int bytesWritten = 0;
+ AVPacket avPacket; // Stores compressed audio data
+ av_init_packet(&avPacket);
+ AVFrame *decodedFrame = av_frame_alloc(); // Stores raw audio data
+ int bytesPerSample = av_get_bytes_per_sample((AVSampleFormat)stream->codecpar->format);
+
+ LOGD("Bytes per sample %d", bytesPerSample);
+
+ LOGD("DECODE START");
+
+ // While there is more data to read, read it into the avPacket
+ while (av_read_frame(formatContext.get(), &avPacket) == 0){
+
+ if (avPacket.stream_index == stream->index && avPacket.size > 0) {
+
+ // Pass our compressed data into the codec
+ result = avcodec_send_packet(codecContext.get(), &avPacket);
+ if (result != 0) {
+ LOGE("avcodec_send_packet error: %s", av_err2str(result));
+ goto cleanup;
+ }
+
+ // Retrieve our raw data from the codec
+ result = avcodec_receive_frame(codecContext.get(), decodedFrame);
+ if (result == AVERROR(EAGAIN)) {
+ // The codec needs more data before it can decode
+ LOGI("avcodec_receive_frame returned EAGAIN");
+ av_packet_unref(&avPacket);
+ continue;
+ } else if (result != 0) {
+ LOGE("avcodec_receive_frame error: %s", av_err2str(result));
+ goto cleanup;
+ }
+
+ // DO RESAMPLING
+ auto dst_nb_samples = (int32_t) av_rescale_rnd(
+ swr_get_delay(swr, decodedFrame->sample_rate) + decodedFrame->nb_samples,
+ targetProperties.sampleRate,
+ decodedFrame->sample_rate,
+ AV_ROUND_UP);
+
+ short *buffer1;
+ av_samples_alloc(
+ (uint8_t **) &buffer1,
+ nullptr,
+ targetProperties.channelCount,
+ dst_nb_samples,
+ AV_SAMPLE_FMT_FLT,
+ 0);
+ int frame_count = swr_convert(
+ swr,
+ (uint8_t **) &buffer1,
+ dst_nb_samples,
+ (const uint8_t **) decodedFrame->data,
+ decodedFrame->nb_samples);
+
+ int64_t bytesToWrite = frame_count * sizeof(float) * targetProperties.channelCount;
+ memcpy(targetData + bytesWritten, buffer1, (size_t)bytesToWrite);
+ bytesWritten += bytesToWrite;
+ av_freep(&buffer1);
+
+ av_packet_unref(&avPacket);
+ }
+ }
+
+ av_frame_free(&decodedFrame);
+ LOGD("DECODE END");
+
+ returnValue = bytesWritten;
+
+ cleanup:
+ return returnValue;
+}
+
+void FFMpegExtractor::printCodecParameters(AVCodecParameters *params) {
+
+ LOGD("Stream properties");
+ LOGD("Channels: %d", params->channels);
+ LOGD("Channel layout: %" PRId64, params->channel_layout);
+ LOGD("Sample rate: %d", params->sample_rate);
+ LOGD("Format: %s", av_get_sample_fmt_name((AVSampleFormat)params->format));
+ LOGD("Frame size: %d", params->frame_size);
+}
+
+
+
+
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.h b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.h
new file mode 100644
index 0000000000..a439b37c0a
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.h
@@ -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.
+ */
+
+#ifndef FFMPEG_FFMPEGEXTRACTOR_H
+#define FFMPEG_FFMPEGEXTRACTOR_H
+
+
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libswresample/swresample.h>
+#include <libavutil/opt.h>
+}
+
+#include <cstdint>
+#include <android/asset_manager.h>
+#include <GameConstants.h>
+
+class FFMpegExtractor {
+public:
+ static int64_t decode(AAsset *asset, uint8_t *targetData, AudioProperties targetProperties);
+
+private:
+ static bool createAVIOContext(AAsset *asset, uint8_t *buffer, uint32_t bufferSize,
+ AVIOContext **avioContext);
+
+ static bool createAVFormatContext(AVIOContext *avioContext, AVFormatContext **avFormatContext);
+
+ static bool openAVFormatContext(AVFormatContext *avFormatContext);
+
+ static int32_t cleanup(AVIOContext *avioContext, AVFormatContext *avFormatContext);
+
+ static bool getStreamInfo(AVFormatContext *avFormatContext);
+
+ static AVStream *getBestAudioStream(AVFormatContext *avFormatContext);
+
+ static AVCodec *findCodec(AVCodecID id);
+
+ static void printCodecParameters(AVCodecParameters *params);
+};
+
+
+#endif //FFMPEG_FFMPEGEXTRACTOR_H
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.cpp b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.cpp
new file mode 100644
index 0000000000..ac50940d91
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.cpp
@@ -0,0 +1,199 @@
+/*
+ * 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 <sys/types.h>
+
+#include <cstring>
+
+#include <media/NdkMediaExtractor.h>
+#include <utils/logging.h>
+#include <cinttypes>
+
+#include "NDKExtractor.h"
+
+int32_t NDKExtractor::decode(AAsset *asset, uint8_t *targetData, AudioProperties targetProperties) {
+
+ LOGD("Using NDK decoder");
+
+ // open asset as file descriptor
+ off_t start, length;
+ int fd = AAsset_openFileDescriptor(asset, &start, &length);
+
+ // Extract the audio frames
+ AMediaExtractor *extractor = AMediaExtractor_new();
+ media_status_t amresult = AMediaExtractor_setDataSourceFd(extractor, fd,
+ static_cast<off64_t>(start),
+ static_cast<off64_t>(length));
+ if (amresult != AMEDIA_OK){
+ LOGE("Error setting extractor data source, err %d", amresult);
+ return 0;
+ }
+
+ // Specify our desired output format by creating it from our source
+ AMediaFormat *format = AMediaExtractor_getTrackFormat(extractor, 0);
+
+ int32_t sampleRate;
+ if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate)){
+ LOGD("Source sample rate %d", sampleRate);
+ if (sampleRate != targetProperties.sampleRate){
+ LOGE("Input (%d) and output (%d) sample rates do not match. "
+ "NDK decoder does not support resampling.",
+ sampleRate,
+ targetProperties.sampleRate);
+ return 0;
+ }
+ } else {
+ LOGE("Failed to get sample rate");
+ return 0;
+ };
+
+ int32_t channelCount;
+ if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &channelCount)){
+ LOGD("Got channel count %d", channelCount);
+ if (channelCount != targetProperties.channelCount){
+ LOGE("NDK decoder does not support different "
+ "input (%d) and output (%d) channel counts",
+ channelCount,
+ targetProperties.channelCount);
+ }
+ } else {
+ LOGE("Failed to get channel count");
+ return 0;
+ }
+
+ const char *formatStr = AMediaFormat_toString(format);
+ LOGD("Output format %s", formatStr);
+
+ const char *mimeType;
+ if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mimeType)) {
+ LOGD("Got mime type %s", mimeType);
+ } else {
+ LOGE("Failed to get mime type");
+ return 0;
+ }
+
+ // Obtain the correct decoder
+ AMediaCodec *codec = nullptr;
+ AMediaExtractor_selectTrack(extractor, 0);
+ codec = AMediaCodec_createDecoderByType(mimeType);
+ AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
+ AMediaCodec_start(codec);
+
+ // DECODE
+
+ bool isExtracting = true;
+ bool isDecoding = true;
+ int32_t bytesWritten = 0;
+
+ while(isExtracting || isDecoding){
+
+ if (isExtracting){
+
+ // Obtain the index of the next available input buffer
+ ssize_t inputIndex = AMediaCodec_dequeueInputBuffer(codec, 2000);
+ //LOGV("Got input buffer %d", inputIndex);
+
+ // The input index acts as a status if its negative
+ if (inputIndex < 0){
+ if (inputIndex == AMEDIACODEC_INFO_TRY_AGAIN_LATER){
+ // LOGV("Codec.dequeueInputBuffer try again later");
+ } else {
+ LOGE("Codec.dequeueInputBuffer unknown error status");
+ }
+ } else {
+
+ // Obtain the actual buffer and read the encoded data into it
+ size_t inputSize;
+ uint8_t *inputBuffer = AMediaCodec_getInputBuffer(codec, inputIndex, &inputSize);
+ //LOGV("Sample size is: %d", inputSize);
+
+ ssize_t sampleSize = AMediaExtractor_readSampleData(extractor, inputBuffer, inputSize);
+ auto presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
+
+ if (sampleSize > 0){
+
+ // Enqueue the encoded data
+ AMediaCodec_queueInputBuffer(codec, inputIndex, 0, sampleSize,
+ presentationTimeUs,
+ 0);
+ AMediaExtractor_advance(extractor);
+
+ } else {
+ LOGD("End of extractor data stream");
+ isExtracting = false;
+
+ // We need to tell the codec that we've reached the end of the stream
+ AMediaCodec_queueInputBuffer(codec, inputIndex, 0, 0,
+ presentationTimeUs,
+ AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
+ }
+ }
+ }
+
+ if (isDecoding){
+ // Dequeue the decoded data
+ AMediaCodecBufferInfo info;
+ ssize_t outputIndex = AMediaCodec_dequeueOutputBuffer(codec, &info, 0);
+
+ if (outputIndex >= 0){
+
+ // Check whether this is set earlier
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM){
+ LOGD("Reached end of decoding stream");
+ isDecoding = false;
+ }
+
+ // Valid index, acquire buffer
+ size_t outputSize;
+ uint8_t *outputBuffer = AMediaCodec_getOutputBuffer(codec, outputIndex, &outputSize);
+
+ /*LOGV("Got output buffer index %d, buffer size: %d, info size: %d writing to pcm index %d",
+ outputIndex,
+ outputSize,
+ info.size,
+ m_writeIndex);*/
+
+ // copy the data out of the buffer
+ memcpy(targetData + bytesWritten, outputBuffer, info.size);
+ bytesWritten+=info.size;
+ AMediaCodec_releaseOutputBuffer(codec, outputIndex, false);
+ } else {
+
+ // The outputIndex doubles as a status return if its value is < 0
+ switch(outputIndex){
+ case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
+ LOGD("dequeueOutputBuffer: try again later");
+ break;
+ case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
+ LOGD("dequeueOutputBuffer: output buffers changed");
+ break;
+ case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED:
+ LOGD("dequeueOutputBuffer: output outputFormat changed");
+ format = AMediaCodec_getOutputFormat(codec);
+ LOGD("outputFormat changed to: %s", AMediaFormat_toString(format));
+ break;
+ }
+ }
+ }
+ }
+
+ // Clean up
+ AMediaFormat_delete(format);
+ AMediaCodec_delete(codec);
+ AMediaExtractor_delete(extractor);
+
+ return bytesWritten;
+}
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.h b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.h
new file mode 100644
index 0000000000..0073f7262d
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.h
@@ -0,0 +1,33 @@
+/*
+ * 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 FFMPEG_NDKMEDIAEXTRACTOR_H
+#define FFMPEG_NDKMEDIAEXTRACTOR_H
+
+
+#include <cstdint>
+#include <android/asset_manager.h>
+#include <GameConstants.h>
+
+
+class NDKExtractor {
+
+public:
+ static int32_t decode(AAsset *asset, uint8_t *targetData, AudioProperties targetProperties);
+};
+
+
+#endif //FFMPEG_NDKMEDIAEXTRACTOR_H
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.cpp b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.cpp
new file mode 100644
index 0000000000..e73a9c351a
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "Player.h"
+#include "utils/logging.h"
+
+void Player::renderAudio(float *targetData, int32_t numFrames){
+
+ const AudioProperties properties = mSource->getProperties();
+
+ if (mIsPlaying){
+
+ int64_t framesToRenderFromData = numFrames;
+ int64_t totalSourceFrames = mSource->getSize() / properties.channelCount;
+ const float *data = mSource->getData();
+
+ // Check whether we're about to reach the end of the recording
+ if (!mIsLooping && mReadFrameIndex + numFrames >= totalSourceFrames){
+ framesToRenderFromData = totalSourceFrames - mReadFrameIndex;
+ mIsPlaying = false;
+ }
+
+ for (int i = 0; i < framesToRenderFromData; ++i) {
+ for (int j = 0; j < properties.channelCount; ++j) {
+ targetData[(i*properties.channelCount)+j] = data[(mReadFrameIndex*properties.channelCount)+j];
+ }
+
+ // Increment and handle wraparound
+ if (++mReadFrameIndex >= totalSourceFrames) mReadFrameIndex = 0;
+ }
+
+ if (framesToRenderFromData < numFrames){
+ // fill the rest of the buffer with silence
+ renderSilence(&targetData[framesToRenderFromData], numFrames * properties.channelCount);
+ }
+
+ } else {
+ renderSilence(targetData, numFrames * properties.channelCount);
+ }
+}
+
+void Player::renderSilence(float *start, int32_t numSamples){
+ for (int i = 0; i < numSamples; ++i) {
+ start[i] = 0;
+ }
+}
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.h b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.h
new file mode 100644
index 0000000000..9c7eebace2
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.h
@@ -0,0 +1,60 @@
+/*
+ * 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 RHYTHMGAME_SOUNDRECORDING_H
+#define RHYTHMGAME_SOUNDRECORDING_H
+
+#include <cstdint>
+#include <array>
+
+#include <chrono>
+#include <memory>
+#include <atomic>
+
+#include <android/asset_manager.h>
+
+#include "shared/IRenderableAudio.h"
+#include "DataSource.h"
+
+class Player : public IRenderableAudio{
+
+public:
+ /**
+ * Construct a new Player from the given DataSource. Players can share the same data source.
+ * For example, you could play two identical sounds concurrently by creating 2 Players with the
+ * same data source.
+ *
+ * @param source
+ */
+ Player(std::shared_ptr<DataSource> source)
+ : mSource(source)
+ {};
+
+ void renderAudio(float *targetData, int32_t numFrames);
+ void resetPlayHead() { mReadFrameIndex = 0; };
+ void setPlaying(bool isPlaying) { mIsPlaying = isPlaying; resetPlayHead(); };
+ void setLooping(bool isLooping) { mIsLooping = isLooping; };
+
+private:
+ int32_t mReadFrameIndex = 0;
+ std::atomic<bool> mIsPlaying { false };
+ std::atomic<bool> mIsLooping { false };
+ std::shared_ptr<DataSource> mSource;
+
+ void renderSilence(float*, int32_t);
+};
+
+#endif //RHYTHMGAME_SOUNDRECORDING_H