mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-25 08:49:09 +00:00
@@ -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
|
||||
Reference in New Issue
Block a user