Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl
2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
4081 changed files with 1185566 additions and 45 deletions
@@ -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);
}
@@ -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
@@ -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
@@ -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);
}
@@ -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
@@ -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;
}
@@ -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
@@ -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;
}
}
@@ -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