Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2476,7 +2476,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FBLazyVector: c00c20551d40126351a6783c47ce75f5b374851b
hermes-engine: c399a2e224a0b13c589d76b4fc05e14bdd76fa88
hermes-engine: 91023181d4bc5948b457de5314623fbfe4f8604e
RCTDeprecation: 3bb167081b134461cfeb875ff7ae1945f8635257
RCTRequired: 74839f55d5058a133a0bc4569b0afec750957f64
RCTSwiftUI: 87a316382f3eab4dd13d2a0d0fd2adcce917361a
Expand All @@ -2485,7 +2485,7 @@ SPEC CHECKSUMS:
React: 1b1536b9099195944034e65b1830f463caaa8390
React-callinvoker: 6dff6d17d1d6cc8fdf85468a649bafed473c65f5
React-Core: 00faa4d038298089a1d5a5b21dde8660c4f0820d
React-Core-prebuilt: ab26be1216323aea7c76f96ca450bffa7bcd4a72
React-Core-prebuilt: a6d614de037caff7898424dfc22915ec792de921
React-CoreModules: a17807f849bfd86045b0b9a75ec8c19373b482f6
React-cxxreact: c7b53ace5827be54048288bce5c55f337c41e95f
React-debug: e1f00fcd2cef58a2897471a6d76a4ef5f5f90c74
Expand Down Expand Up @@ -2549,8 +2549,8 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 5787b37b8e2e51dfeab697ec031cc7c4080dcea2
ReactCodegen: d07ee3c8db75b43d1cbe479ae6affebf9925c733
ReactCommon: fe2a3af8975e63efa60f95fca8c34dc85deee360
ReactNativeDependencies: 212738cc51e6c4cc34ee487890497d6f41979ec0
RNAudioAPI: a36dcdaa5905d2b0e1f7170f7b5cb3cec944c029
ReactNativeDependencies: 4d5ce2683b6d74f7c686bf90a88c7d381295cf3c
RNAudioAPI: 0da654a83adfff638b0ccf05f7b07869a8e78cbe
RNGestureHandler: 187c5c7936abf427bc4d22d6c3b1ac80ad1f63c0
RNReanimated: 64f4b3b33b48b19e0ba76a352571b52b1e931981
RNScreens: 01b065ded2dfe7987bcce770ff3a196be417ff41
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithMemoryBlock) {
auto sampleRate = static_cast<float>(args[1].getNumber());

auto promise = promiseVendor_->createAsyncPromise([data, size, sampleRate]() -> PromiseResolver {
auto result = AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);
auto result = audiodecoder::decodeWithMemoryBlock(data, size, sampleRate);

if (result.is_err()) {
return [result = std::move(result)](
Expand All @@ -54,7 +54,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithFilePath) {
auto sampleRate = static_cast<float>(args[1].getNumber());

auto promise = promiseVendor_->createAsyncPromise([sourcePath, sampleRate]() -> PromiseResolver {
auto result = AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);
auto result = audiodecoder::decodeWithFilePath(sourcePath, sampleRate);

if (result.is_err()) {
return [result = std::move(result)](
Expand Down Expand Up @@ -84,7 +84,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {

auto promise = promiseVendor_->createAsyncPromise(
[b64, inputSampleRate, inputChannelCount, interleaved]() -> PromiseResolver {
auto result = AudioDecoder::decodeWithPCMInBase64(
auto result = audiodecoder::decodeWithPCMInBase64(
b64, inputSampleRate, inputChannelCount, interleaved);

if (result.is_err()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,14 @@ inline AudioFileSourceOptions parseAudioFileSourceOptions(
if (sourceValue.isString()) {
options.filePath = sourceValue.asString(runtime).utf8(runtime);
options.requiresFFmpeg =
AudioDecoder::pathHasExtension(options.filePath, {".mp4", ".m4a", ".aac"});
audiodecoder::pathHasExtension(options.filePath, {".mp4", ".m4a", ".aac"});
} else if (sourceValue.isObject()) {
auto sourceObj = sourceValue.asObject(runtime);
if (sourceObj.isArrayBuffer(runtime)) {
auto arrayBuffer = sourceObj.getArrayBuffer(runtime);
auto *data = arrayBuffer.data(runtime);
auto size = arrayBuffer.size(runtime);
auto format = AudioDecoder::detectAudioFormat(data, size);
auto format = audiodecoder::detectAudioFormat(data, size);
options.requiresFFmpeg =
format == AudioFormat::MP4 || format == AudioFormat::M4A || format == AudioFormat::AAC;
options.data = std::vector<uint8_t>(data, data + size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ void AudioFileSourceNode::initDecoders(
bool useFilePath,
const std::shared_ptr<BaseAudioContext> &context,
const std::shared_ptr<AudioFileDecoderState> &state) {
bool ok = false;
decoding::DecoderResult openResult = Ok(None);
if (requiresFFmpeg_) {
#if !RN_AUDIO_API_FFMPEG_DISABLED
decoder_ = std::make_unique<ffmpegdecoder::FFmpegDecoder>();
Expand All @@ -92,14 +92,14 @@ void AudioFileSourceNode::initDecoders(
decoder_ = std::make_unique<miniaudio_decoder::MiniAudioDecoder>();
}
if (useFilePath) {
ok = decoder_->openFile(static_cast<int>(context->getSampleRate()), state->filePath);
openResult = decoder_->openFile(static_cast<int>(context->getSampleRate()), state->filePath);
} else {
ok = decoder_->openMemory(
openResult = decoder_->openMemory(
static_cast<int>(context->getSampleRate()),
state->memoryData.data(),
state->memoryData.size());
}
if (ok) {
if (openResult.is_ok()) {
state->channels = decoder_->outputChannels();
state->sampleRate = static_cast<float>(decoder_->outputSampleRate());
duration_ = static_cast<double>(decoder_->getDurationInSeconds());
Expand Down Expand Up @@ -140,7 +140,7 @@ size_t AudioFileSourceNode::readFrames(float *buf, size_t frameCount) {
}

bool AudioFileSourceNode::seekDecoderToTime(double seconds) {
return decoder_->seekToTime(seconds);
return decoder_->seekToTime(seconds).is_ok();
}

void AudioFileSourceNode::applyPlaybackStateAfterSuccessfulSeek(double seconds) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class AudioFileSourceNode : public AudioScheduledSourceNode {
const std::shared_ptr<AudioFileDecoderState> &state);

std::shared_ptr<AudioFileDecoderState> decoderState_;
std::unique_ptr<decoding::IIncrementalAudioDecoder> decoder_;
std::unique_ptr<decoding::IncrementalAudioDecoder> decoder_;
float volume_;
bool requiresFFmpeg_;
bool filePaused_{false};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,142 +1,171 @@
#include <audioapi/core/utils/AudioDecoder.h>
#include <audioapi/dsp/VectorMath.h>
#include <audioapi/libs/base64/base64.h>
#include <audioapi/utils/AudioArray.hpp>

#include <audioapi/libs/miniaudio/decoders/libopus/miniaudio_libopus.h>
#include <audioapi/libs/miniaudio/decoders/libvorbis/miniaudio_libvorbis.h>
#include <audioapi/libs/miniaudio/miniaudio.h>
#include <audioapi/libs/decoding/IncrementalAudioDecoder.h>
#include <audioapi/libs/miniaudio/MiniAudioDecoding.h>

#if !RN_AUDIO_API_FFMPEG_DISABLED
#include <audioapi/libs/ffmpeg/FFmpegDecoding.h>
#endif // RN_AUDIO_API_FFMPEG_DISABLED

#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace audioapi {
namespace audioapi::audiodecoder {

// Drains an incremental decoder into an AudioBuffer. Total frame count is not
// known up front for some formats (e.g. Vorbis), so we read in fixed-size
// chunks and grow the interleaved accumulator until the decoder reports EOF.
AudioBufferResult decodeAll(decoding::IncrementalAudioDecoder &decoder) {
const int channels = std::max(1, decoder.outputChannels());
const auto outputSampleRate = static_cast<float>(decoder.outputSampleRate());

// Decoding audio in fixed-size chunks because total frame count can't be
// determined in advance. Note: ma_decoder_get_length_in_pcm_frames() always
// returns 0 for Vorbis decoders.
Result<std::vector<float>, std::string> AudioDecoder::readAllPcmFrames(
ma_decoder &decoder,
int outputChannels) {
std::vector<float> buffer;
std::vector<float> temp(CHUNK_SIZE * outputChannels);
ma_uint64 outFramesRead = 0;
std::vector<float> interleaved;
std::vector<float> chunk(
decoding::IncrementalAudioDecoder::CHUNK_SIZE * static_cast<size_t>(channels));

while (true) {
ma_uint64 tempFramesDecoded = 0;
ma_decoder_read_pcm_frames(&decoder, temp.data(), CHUNK_SIZE, &tempFramesDecoded);
if (tempFramesDecoded == 0) {
const size_t framesRead =
decoder.readPcmFrames(chunk.data(), decoding::IncrementalAudioDecoder::CHUNK_SIZE);
if (framesRead == 0) {
break;
}

buffer.insert(buffer.end(), temp.data(), temp.data() + tempFramesDecoded * outputChannels);
outFramesRead += tempFramesDecoded;
interleaved.insert(
interleaved.end(),
chunk.begin(),
chunk.begin() + static_cast<std::ptrdiff_t>(framesRead * static_cast<size_t>(channels)));
}

if (outFramesRead == 0) {
if (interleaved.empty()) {
return Err("Failed to decode any frames");
}

return Ok(std::move(buffer));
const size_t outputFrames = interleaved.size() / static_cast<size_t>(channels);
auto audioBuffer = std::make_shared<AudioBuffer>(outputFrames, channels, outputSampleRate);
audioBuffer->deinterleaveFrom(interleaved.data(), outputFrames);
return Ok(std::move(audioBuffer));
}

AudioBufferResult AudioDecoder::makeAudioBufferFromFloatBuffer(
const std::vector<float> &buffer,
float outputSampleRate,
int outputChannels) {
if (buffer.empty()) {
return Err("Buffer is empty");
// NOLINTBEGIN(readability-magic-numbers, cppcoreguidelines-avoid-magic-numbers)
AudioFormat detectAudioFormat(const void *data, size_t size) {
if (size < 12) {
return AudioFormat::UNKNOWN;
}
const auto *bytes = static_cast<const unsigned char *>(data);

auto outputFrames = buffer.size() / outputChannels;
auto audioBuffer = std::make_shared<AudioBuffer>(outputFrames, outputChannels, outputSampleRate);

audioBuffer->deinterleaveFrom(buffer.data(), outputFrames);

return Ok(std::move(audioBuffer));
}
// WAV/RIFF
if (std::memcmp(bytes, "RIFF", 4) == 0 && std::memcmp(bytes + 8, "WAVE", 4) == 0) {
return AudioFormat::WAV;
}

AudioBufferResult AudioDecoder::decodeWithMiniaudio(float sampleRate, DecoderSource source) {
ma_decoder decoder;
ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 0, static_cast<int>(sampleRate));
ma_decoding_backend_vtable *customBackends[] = {
ma_decoding_backend_libvorbis, ma_decoding_backend_libopus};

config.ppCustomBackendVTables = customBackends;
config.customBackendCount = sizeof(customBackends) / sizeof(customBackends[0]);

ma_result initResult = std::visit(
[&config, &decoder](auto &&arg) -> ma_result {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, MemorySource>) {
return ma_decoder_init_memory(arg.data, arg.size, &config, &decoder);
} else if constexpr (std::is_same_v<T, std::string>) {
return ma_decoder_init_file(arg.c_str(), &config, &decoder);
} else {
return MA_INVALID_ARGS;
}
},
source);

if (initResult != MA_SUCCESS) {
return Err(
"Failed to initialize miniaudio decoder: " +
std::string(ma_result_description(initResult)));
// OGG
if (std::memcmp(bytes, "OggS", 4) == 0) {
return AudioFormat::OGG;
}

auto outputSampleRate = static_cast<float>(decoder.outputSampleRate);
auto outputChannels = static_cast<int>(decoder.outputChannels);
// FLAC
if (std::memcmp(bytes, "fLaC", 4) == 0) {
return AudioFormat::FLAC;
}

auto result = readAllPcmFrames(decoder, outputChannels)
.and_then([outputSampleRate, outputChannels](std::vector<float> &&buffer) {
return makeAudioBufferFromFloatBuffer(
std::move(buffer), outputSampleRate, outputChannels);
});
// AAC starts with 0xFF 0xF1 or 0xFF 0xF9
if (bytes[0] == 0xFF && (bytes[1] & 0xF6) == 0xF0) {
return AudioFormat::AAC;
}

ma_decoder_uninit(&decoder);
// MP3: "ID3" or 11-bit frame sync (0xFF 0xE0)
if (std::memcmp(bytes, "ID3", 3) == 0) {
return AudioFormat::MP3;
}
if (bytes[0] == 0xFF && (bytes[1] & 0xE0) == 0xE0) {
return AudioFormat::MP3;
}

return result;
if (std::memcmp(bytes + 4, "ftyp", 4) == 0) {
if (std::memcmp(bytes + 8, "M4A ", 4) == 0) {
return AudioFormat::M4A;
}
if (std::memcmp(bytes + 8, "qt ", 4) == 0) {
return AudioFormat::MOV;
}
return AudioFormat::MP4;
}
return AudioFormat::UNKNOWN;
}
// NOLINTEND(readability-magic-numbers, cppcoreguidelines-avoid-magic-numbers)

bool pathHasExtension(const std::string &path, const std::vector<std::string> &extensions) {
std::string pathLower = path;
std::ranges::transform(pathLower, pathLower.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return std::ranges::any_of(
extensions, [&pathLower](const std::string &ext) { return pathLower.ends_with(ext); });
}

AudioBufferResult decodeWithFilePath(const std::string &path, float sampleRate) {
const int sr = static_cast<int>(sampleRate);

AudioBufferResult AudioDecoder::decodeWithFilePath(const std::string &path, float sampleRate) {
if (AudioDecoder::pathHasExtension(path, {".mp4", ".m4a", ".aac"})) {
if (needsFFmpegByPath(path)) {
#if !RN_AUDIO_API_FFMPEG_DISABLED
auto buffer = ffmpegdecoder::decodeWithFilePath(path, static_cast<int>(sampleRate));
if (buffer == nullptr) {
return Err("Failed to decode with file path using FFmpeg");
ffmpegdecoder::FFmpegDecoder decoder;
const auto openResult = decoder.openFile(sr, path);
if (openResult.is_err()) {
return Err("Failed to open file with FFmpeg decoder: " + openResult.unwrap_err());
}
return Ok(std::move(buffer));
auto result = decodeAll(decoder);
decoder.close();
return result;
#else
return Err("FFmpeg is disabled, cannot decode with file path");
#endif // RN_AUDIO_API_FFMPEG_DISABLED
}
return decodeWithMiniaudio(sampleRate, path);

miniaudio_decoder::MiniAudioDecoder decoder;
const auto openResult = decoder.openFile(sr, path);
if (openResult.is_err()) {
return Err("Failed to open file with miniaudio decoder: " + openResult.unwrap_err());
}
auto result = decodeAll(decoder);
decoder.close();
return result;
}

AudioBufferResult
AudioDecoder::decodeWithMemoryBlock(const void *data, size_t size, float sampleRate) {
const AudioFormat format = AudioDecoder::detectAudioFormat(data, size);
if (format == AudioFormat::MP4 || format == AudioFormat::M4A || format == AudioFormat::AAC) {
AudioBufferResult decodeWithMemoryBlock(const void *data, size_t size, float sampleRate) {
const int sr = static_cast<int>(sampleRate);
const AudioFormat format = detectAudioFormat(data, size);

if (needsFFmpeg(format)) {
#if !RN_AUDIO_API_FFMPEG_DISABLED
auto buffer = ffmpegdecoder::decodeWithMemoryBlock(data, size, static_cast<int>(sampleRate));
if (buffer == nullptr) {
return Err("Failed to decode with memory block using FFmpeg");
ffmpegdecoder::FFmpegDecoder decoder;
const auto openResult = decoder.openMemory(sr, data, size);
if (openResult.is_err()) {
return Err("Failed to open memory block with FFmpeg decoder: " + openResult.unwrap_err());
}
return Ok(std::move(buffer));
auto result = decodeAll(decoder);
decoder.close();
return result;
#else
return Err("FFmpeg is disabled, cannot decode memory block");
#endif // RN_AUDIO_API_FFMPEG_DISABLED
}
return decodeWithMiniaudio(sampleRate, MemorySource{.data = data, .size = size});

miniaudio_decoder::MiniAudioDecoder decoder;
const auto openResult = decoder.openMemory(sr, data, size);
if (openResult.is_err()) {
return Err("Failed to open memory block with miniaudio decoder: " + openResult.unwrap_err());
}
auto result = decodeAll(decoder);
decoder.close();
return result;
}

AudioBufferResult AudioDecoder::decodeWithPCMInBase64(
AudioBufferResult decodeWithPCMInBase64(
const std::string &data,
float inputSampleRate,
int inputChannelCount,
Expand All @@ -148,7 +177,7 @@ AudioBufferResult AudioDecoder::decodeWithPCMInBase64(
auto audioBuffer =
std::make_shared<AudioBuffer>(numFramesDecoded, inputChannelCount, inputSampleRate);

for (size_t ch = 0; ch < inputChannelCount; ++ch) {
for (int ch = 0; ch < inputChannelCount; ++ch) {
Comment thread
closetcaiman marked this conversation as resolved.
auto channelData = audioBuffer->getChannel(ch)->span();

for (size_t i = 0; i < numFramesDecoded; ++i) {
Expand All @@ -168,4 +197,4 @@ AudioBufferResult AudioDecoder::decodeWithPCMInBase64(
return Ok(std::move(audioBuffer));
}

} // namespace audioapi
} // namespace audioapi::audiodecoder
Loading
Loading