diff options
| author | swurl <swurl@swurl.xyz> | 2025-05-31 02:33:02 -0400 |
|---|---|---|
| committer | swurl <swurl@swurl.xyz> | 2025-05-31 02:33:02 -0400 |
| commit | 6c655321e6b04b8648802e296bb5ca11d290ae68 (patch) | |
| tree | 5c906511e4a2910ce8b806bb9d763b524c28619e /externals/oboe/samples/RhythmGame/src/main/cpp/audio | |
| parent | c0cceff365f754f8859f4c9a0362ebdba7ea388b (diff) | |
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
Diffstat (limited to 'externals/oboe/samples/RhythmGame/src/main/cpp/audio')
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 |
