Skip to content

Commit

Permalink
feat: add error handling on Android (#8)
Browse files Browse the repository at this point in the history
* feat: add error handling on Android

* feat: add error handling on iOS
  • Loading branch information
itsramiel authored Nov 27, 2024
1 parent e684f16 commit 29f6548
Show file tree
Hide file tree
Showing 19 changed files with 586 additions and 518 deletions.
22 changes: 22 additions & 0 deletions android/src/main/cpp/AudioConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,33 @@
#define AUDIOPLAYBACK_AUDIOCONSTANTS_H

#include <cstdint>
#include <optional>
#include <string>

struct AudioProperties {
int32_t channelCount;
int32_t sampleRate;
};

struct SetupAudioStreamResult {
std::optional<std::string> error;
};

struct OpenAudioStreamResult {
std::optional<std::string> error;
};

struct CloseAudioStreamResult {
std::optional<std::string> error;
};

struct LoadSoundResult {
std::optional<std::string> id;
std::optional<std::string> error;
};

struct UnloadSoundResult {
std::optional<std::string> error;
};

#endif //AUDIOPLAYBACK_AUDIOCONSTANTS_H
64 changes: 31 additions & 33 deletions android/src/main/cpp/AudioEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@

#include "audio/AAssetDataSource.h"

void AudioEngine::setupAudioStream(double sampleRate, double channelCount) {
SetupAudioStreamResult AudioEngine::setupAudioStream(double sampleRate, double channelCount) {
if(mAudioStream) {
LOGD("Setting up an audio stream while one is already available");
return;
return { .error = "Setting up an audio stream while one is already available"};
}

mDesiredSampleRate = static_cast<int32_t>(sampleRate);
Expand All @@ -32,49 +31,50 @@ void AudioEngine::setupAudioStream(double sampleRate, double channelCount) {
oboe::Result result = builder.openStream(mAudioStream);

if( result != oboe::Result::OK) {
LOGE("Failed to open stream. Error: %s", convertToText(result));
auto error = "Failed to open stream:" + std::string (convertToText(result));
return { .error = error};
} else {
LOGE("Opened stream successfully");
return {.error = std::nullopt};
}

}


void AudioEngine::openAudioStream() {
OpenAudioStreamResult AudioEngine::openAudioStream() {
if(!mAudioStream) {
LOGD("There is no audio stream to start");
return;
return {.error = "There is no audio stream to start" };
}
auto streamState = mAudioStream->getState();

if(streamState == oboe::StreamState::Starting || streamState == oboe::StreamState::Started) {
LOGD("Audio stream was requested to start but it is already started");
return;
return {.error = "Audio stream was requested to start but it is already started"};
}

oboe::Result result = mAudioStream->requestStart();
if(result != oboe::Result::OK) {
LOGE("Failed to start stream, Error: %s", oboe::convertToText(result));
auto error = "Failed to start stream, Error: %s" + std::string(oboe::convertToText(result));
return {.error = error};
} else {
return {.error = std::nullopt};
}
}

void AudioEngine::closeAudioStream() {
CloseAudioStreamResult AudioEngine::closeAudioStream() {
if(!mAudioStream) {
LOGD("There is no audio stream to close");
return;
return { .error = "There is no audio stream to close" };
}
auto streamState = mAudioStream->getState();

if(streamState == oboe::StreamState::Closing || streamState == oboe::StreamState::Closed) {
LOGD("Audio stream was requested to close but it is already closed");
return;
return { .error = "Audio stream was requested to close but it is already closed" };
}

oboe::Result result = mAudioStream->close();
if(result != oboe::Result::OK) {
LOGE("Failed to start stream, Error: %s", oboe::convertToText(result));
auto error ="Failed to close stream: %s" + std::string(oboe::convertToText(result));
return {.error = error};
}
mAudioStream = nullptr;
return {.error = std::nullopt};
}

oboe::DataCallbackResult
Expand Down Expand Up @@ -115,7 +115,7 @@ void AudioEngine::seekSoundsTo(const std::vector<std::pair<std::string, double>>
}
}

std::optional<std::string> AudioEngine::loadSound(int fd, int offset, int length) {
LoadSoundResult AudioEngine::loadSound(int fd, int offset, int length) {
auto playersSize = mPlayers.size();

LOGD("Loading audio with already %d sounds loaded", playersSize);
Expand All @@ -125,30 +125,28 @@ std::optional<std::string> AudioEngine::loadSound(int fd, int offset, int length
.sampleRate = mDesiredSampleRate
};

std::shared_ptr<AAssetDataSource> mClapSource {
AAssetDataSource::newFromCompressedAsset(fd, offset, length, targetProperties)
};

if(mClapSource == nullptr) {
LOGE("Could not load source data for clap sound");
return std::nullopt;
auto compressedAssetResult = AAssetDataSource::newFromCompressedAsset(fd, offset, length, targetProperties);

if(compressedAssetResult.error) {
return {.id = std::nullopt, .error = compressedAssetResult.error};
} else if(compressedAssetResult.dataSource == nullptr) {
return {.id = std::nullopt, .error = "An unknown error occurred while loading the audio file. Please create an issue with a reproducible"};
}

std::string id = uuid::generate_uuid_v4();
mPlayers[id] = std::make_unique<Player>(std::move(mClapSource));
return id;
mPlayers[id] = std::make_unique<Player>(compressedAssetResult.dataSource);
return {.id = id, .error = std::nullopt};
}

void AudioEngine::unloadSound(const std::string &playerId) {
auto playersSize = mPlayers.size();

LOGD("Unloading audio with already %d sounds loaded", playersSize);

UnloadSoundResult AudioEngine::unloadSound(const std::string &playerId) {
auto it = mPlayers.find(playerId);
if(it != mPlayers.end()) {
mPlayers.erase(it);
} else {
LOGE("Player with identifier: %s not found", playerId.c_str());
return {.error = "Audio file could not be unloaded because it is not found"};
}

return {.error = std::nullopt};
}

11 changes: 6 additions & 5 deletions android/src/main/cpp/AudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@

#include <oboe/Oboe.h>
#include "audio/Player.h"
#include "AudioConstants.h"
#include <android/asset_manager.h>

class AudioEngine : public oboe::AudioStreamDataCallback{
public:
void setupAudioStream(double sampleRate, double channelCount);
void openAudioStream();
void closeAudioStream();
std::optional<std::string> loadSound(int fd, int offset, int length);
void unloadSound(const std::string &playerId);
SetupAudioStreamResult setupAudioStream(double sampleRate, double channelCount);
OpenAudioStreamResult openAudioStream();
CloseAudioStreamResult closeAudioStream();
void playSounds(const std::vector<std::pair<std::string, bool>>&);
void loopSounds(const std::vector<std::pair<std::string, bool>>&);
void seekSoundsTo(const std::vector<std::pair<std::string, double>>&);
LoadSoundResult loadSound(int fd, int offset, int length);
UnloadSoundResult unloadSound(const std::string &playerId);

oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override;

Expand Down
24 changes: 14 additions & 10 deletions android/src/main/cpp/audio/AAssetDataSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

constexpr int kMaxCompressionRatio { 12 };

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

Expand All @@ -35,9 +35,12 @@ AAssetDataSource::newFromCompressedAsset(int fd, int offset,
const long maximumDataSizeInBytes = kMaxCompressionRatio * length * sizeof(int16_t);
auto decodedData = new uint8_t[maximumDataSizeInBytes];

int64_t bytesDecoded = NDKExtractor::decodeFileDescriptor(fd, offset, length, decodedData, targetProperties);
auto numSamples = bytesDecoded / sizeof(int16_t);
LOGE("Number of samples: %lld", numSamples);
auto decodeResult = NDKExtractor::decodeFileDescriptor(fd, offset, length, decodedData, targetProperties);
if(decodeResult.error) {
return {.dataSource = nullptr, .error = decodeResult.error };
}

auto numSamples = decodeResult.bytesRead / 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);
Expand All @@ -46,13 +49,14 @@ AAssetDataSource::newFromCompressedAsset(int fd, int offset,
oboe::convertPcm16ToFloat(
reinterpret_cast<int16_t*>(decodedData),
outputBuffer.get(),
bytesDecoded / sizeof(int16_t));
decodeResult.bytesRead / sizeof(int16_t));

delete[] decodedData;
// TODO: handle closing asset
// AAsset_close(asset);

return new AAssetDataSource(std::move(outputBuffer),
numSamples,
targetProperties);
return {
.dataSource = new AAssetDataSource(std::move(outputBuffer),
numSamples,
targetProperties),
.error = std::nullopt
};
}
16 changes: 12 additions & 4 deletions android/src/main/cpp/audio/AAssetDataSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,26 @@
#ifndef AUDIOPLAYBACK_AASSETDATASOURCE_H
#define AUDIOPLAYBACK_AASSETDATASOURCE_H

#include <optional>
#include <android/asset_manager.h>
#include <AudioConstants.h>
#include "DataSource.h"

class AAssetDataSource;

struct NewFromCompressedAssetResult {
AAssetDataSource *dataSource;
std::optional<std::string> error;
};

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(); }
[[nodiscard]] int64_t getSize() const override { return mBufferSize; }
[[nodiscard]] AudioProperties getProperties() const override { return mProperties; }
[[nodiscard]] const float* getData() const override { return mBuffer.get(); }

static AAssetDataSource* newFromCompressedAsset(
static NewFromCompressedAssetResult newFromCompressedAsset(
int fd, int offset, int length,
AudioProperties targetProperties);

Expand Down
Loading

0 comments on commit 29f6548

Please sign in to comment.