Skip to content

Commit

Permalink
fix: use vector for decoded data (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
itsramiel authored Mar 1, 2025
1 parent 44e2742 commit fa895a4
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 111 deletions.
19 changes: 4 additions & 15 deletions android/src/main/cpp/audio/AAssetDataSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,25 @@

#include "NDKExtractor.h"

constexpr int kMaxCompressionRatio { 12 };

NewFromCompressedAssetResult
AAssetDataSource::newFromCompressedAsset(int fd, int offset,
int length, AudioProperties targetProperties) {


// 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).
const long maximumDataSizeInBytes = kMaxCompressionRatio * length * sizeof(int16_t);
auto decodedData = new uint8_t[maximumDataSizeInBytes];

auto decodeResult = NDKExtractor::decodeFileDescriptor(fd, offset, length, decodedData, targetProperties);
auto decodeResult = NDKExtractor::decodeFileDescriptor(fd, offset, length, targetProperties);
if(decodeResult.error) {
return {.dataSource = nullptr, .error = decodeResult.error };
}

auto numSamples = decodeResult.bytesRead / sizeof(int16_t);
auto numSamples = decodeResult.data->size() / sizeof(int16_t);

// 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);

// The NDK decoder can only decode to int16, we need to convert to floats
oboe::convertPcm16ToFloat(
reinterpret_cast<int16_t*>(decodedData),
reinterpret_cast<int16_t*>(decodeResult.data->data()),
outputBuffer.get(),
decodeResult.bytesRead / sizeof(int16_t));

delete[] decodedData;
static_cast<int32_t>(numSamples));

return {
.dataSource = new AAssetDataSource(std::move(outputBuffer),
Expand Down
142 changes: 48 additions & 94 deletions android/src/main/cpp/audio/NDKExtractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,23 @@

#include "NDKExtractor.h"

DecodeFileDescriptorResult NDKExtractor::decodeFileDescriptor(int fd, int offset, int length, uint8_t* targetData, AudioProperties targetProperties) {
LOGD("Using NDK decoder");

// Extract the audio frames
AMediaExtractor *extractor = AMediaExtractor_new();
media_status_t amresult = AMediaExtractor_setDataSourceFd(extractor, fd,
static_cast<off64_t>(offset),
static_cast<off64_t>(length));
if (amresult != AMEDIA_OK){
LOGE("Error setting extractor data source, err %d", amresult);
return {.bytesRead = 0, .error = "Decoding sound file failed"};
DecodeFileDescriptorResult NDKExtractor::decodeFileDescriptor(int fd, int offset, int length, AudioProperties targetProperties) {
auto extractor = AMediaExtractor_new();
auto amResult = AMediaExtractor_setDataSourceFd(
extractor,
fd,
static_cast<off64_t>(offset),
static_cast<off64_t>(length));

if (amResult != AMEDIA_OK){
return {.error = "Decoding sound file failed"};
}

// Specify our desired output format by creating it from our source
AMediaFormat *format = AMediaExtractor_getTrackFormat(extractor, 0);
auto format = AMediaExtractor_getTrackFormat(extractor, 0);

int32_t sampleRate;
if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate)){
LOGD("File sample rate: %d", sampleRate);
if (sampleRate != targetProperties.sampleRate){
std::stringstream error;
error
Expand All @@ -53,15 +51,14 @@ DecodeFileDescriptorResult NDKExtractor::decodeFileDescriptor(int fd, int offset
<< ", doesn't match the sample rate of the stream, "
<< targetProperties.sampleRate << ".";

return {.bytesRead = 0, .error = error.str()};
return {.error = error.str()};
}
} else {
return {.bytesRead = 0, .error = "Failed to load sound file: could not determine sample rate"};
return {.error = "Failed to load sound file: could not determine sample rate"};
};

int32_t channelCount;
if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &channelCount)){
LOGD("File channel count: %d", channelCount);
if (channelCount != targetProperties.channelCount){
std::stringstream error;
error
Expand All @@ -70,113 +67,70 @@ DecodeFileDescriptorResult NDKExtractor::decodeFileDescriptor(int fd, int offset
<< ", doesn't match the channel count of the stream, "
<< targetProperties.channelCount << ".";

return {.bytesRead = 0, .error = error.str()};
return {.error = error.str()};
}
} else {
return {.bytesRead = 0, .error = "Failed to load sound file: could not determine channel count"};
return {.error = "Failed to load sound file: could not determine channel count"};
}

const char *mimeType;
if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mimeType)) {
LOGD("File mime type: %s", mimeType);
} else {
return {.bytesRead = 0, .error = "Failed to load sound file: could not determine mimeType"};
if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mimeType)) {
return {.error = "Failed to load sound file: could not determine mimeType"};
}

// Obtain the correct decoder
AMediaCodec *codec = nullptr;
AMediaExtractor_selectTrack(extractor, 0);
codec = AMediaCodec_createDecoderByType(mimeType);
auto codec = AMediaCodec_createDecoderByType(mimeType);
AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
AMediaCodec_start(codec);

std::vector<uint8_t> data{};
// 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);
auto inputIndex = AMediaCodec_dequeueInputBuffer(codec, 2000);

// 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 {
if(inputIndex >= 0) {

// Obtain the actual buffer and read the encoded data into it
size_t inputSize;
uint8_t *inputBuffer = AMediaCodec_getInputBuffer(codec, inputIndex, &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);
size_t inputSize {};
auto inputBuffer = AMediaCodec_getInputBuffer(codec, inputIndex, &inputSize);

if(inputBuffer) {
auto sampleSize = AMediaExtractor_readSampleData(extractor, inputBuffer, inputSize);
auto presentationTimeUs = AMediaExtractor_getSampleTime(extractor);

if (sampleSize > 0){
AMediaCodec_queueInputBuffer(codec, inputIndex, 0, sampleSize, presentationTimeUs, 0);
AMediaExtractor_advance(extractor);
} else {
isExtracting = false;
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;
if(isDecoding) {
AMediaCodecBufferInfo bufferInfo{};
auto outputIndex = AMediaCodec_dequeueOutputBuffer(codec, &bufferInfo, 2000);
while(outputIndex >= 0) {
auto outputBuffer = AMediaCodec_getOutputBuffer(codec, outputIndex, nullptr);
if(outputBuffer) {
data.insert(data.end(), outputBuffer, outputBuffer + bufferInfo.size);
}

// Valid index, acquire buffer
size_t outputSize;
uint8_t *outputBuffer = AMediaCodec_getOutputBuffer(codec, outputIndex, &outputSize);

// 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;
}
outputIndex = AMediaCodec_dequeueOutputBuffer(codec, &bufferInfo, 0);
}

if(bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
isDecoding = false;
}
}
}
Expand All @@ -186,6 +140,6 @@ DecodeFileDescriptorResult NDKExtractor::decodeFileDescriptor(int fd, int offset
AMediaCodec_delete(codec);
AMediaExtractor_delete(extractor);

return {.bytesRead = bytesWritten, .error = std::nullopt};
return {.data = data};
}

4 changes: 2 additions & 2 deletions android/src/main/cpp/audio/NDKExtractor.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
#include "utils/logging.h"

struct DecodeFileDescriptorResult {
int32_t bytesRead;
std::optional<std::vector<uint8_t>> data;
std::optional<std::string> error;
};


class NDKExtractor {
public:
static DecodeFileDescriptorResult decodeFileDescriptor(int fd, int offset, int length, uint8_t *targetData, AudioProperties targetProperties);
static DecodeFileDescriptorResult decodeFileDescriptor(int fd, int offset, int length, AudioProperties targetProperties);
};

#endif //AUDIOPLAYBACK_NDKMEDIAEXTRACTOR_H

0 comments on commit fa895a4

Please sign in to comment.