Skip to content

Feat/opus decoder #580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from 19 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
2 changes: 1 addition & 1 deletion apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2955,7 +2955,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 3267432b637c9b38e86961b287f784ee1b08dde0
ReactCodegen: d82f538f70f00484d418803f74b5a0ea09cc8689
ReactCommon: b028d09a66e60ebd83ca59d8cc9a1216360db147
RNAudioAPI: 5d67008b0e2d36e255efb1b157c801e6f2dfa938
RNAudioAPI: 7ba48681da626677a851e7b91d7910b776410976
RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0
RNReanimated: 402e6a3b84071df4da6264630a1b99962a113d2d
RNScreens: ee2abe7e0c548eed14e92742e81ed991165c56aa
Expand Down
4 changes: 3 additions & 1 deletion packages/audiodocs/docs/core/base-audio-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ The above method lets you create `StereoPannerNode`.

#### Returns [`StereoPannerNode`](/docs/effects/stereo-panner-node).

### `createBiquadFilter
### `createBiquadFilter`

The above method lets you create `BiquadFilterNode`.

Expand All @@ -147,6 +147,8 @@ Supported file formats:
- mp3
- wav
- flac
- opus
- ogg
:::

### `decodeAudioData`
Expand Down
38 changes: 31 additions & 7 deletions packages/react-native-audio-api/RNAudioAPI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,39 @@ Pod::Spec.new do |s|

s.compiler_flags = "#{folly_flags}"

s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) HAVE_ACCELERATE=1',
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\" \"$(PODS_TARGET_SRCROOT)/ios\"",
'OTHER_CFLAGS' => "$(inherited) #{folly_flags} #{fabric_flags} #{version_flag}"
}
external_dir = "$(PODS_TARGET_SRCROOT)/common/cpp/audioapi/external"
# lib_dir = "$(PODS_TARGET_SRCROOT)/common/cpp/audioapi/external/$(PLATFORM_NAME)"
lib_dir = "$(SRCROOT)/../../../packages/react-native-audio-api/common/cpp/audioapi/external/$(PLATFORM_NAME)"


s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) HAVE_ACCELERATE=1',
"HEADER_SEARCH_PATHS" => %W[
$(PODS_TARGET_SRCROOT)/common/cpp
$(PODS_TARGET_SRCROOT)/ios
#{external_dir}/include
#{external_dir}/include/opus
#{external_dir}/include/vorbis
].join(" "),
'OTHER_CFLAGS' => "$(inherited) #{folly_flags} #{fabric_flags} #{version_flag}"
}

s.user_target_xcconfig = {
'OTHER_LDFLAGS' => %W[
$(inherited)
-force_load #{lib_dir}/libopusfile.a
-force_load #{lib_dir}/libopus.a
-force_load #{lib_dir}/libogg.a
-force_load #{lib_dir}/libvorbis.a
-force_load #{lib_dir}/libvorbisenc.a
-force_load #{lib_dir}/libvorbisfile.a
].join(" ")
}
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
install_modules_dependencies(s)
end


Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ cmake_minimum_required(VERSION 3.12.0)
file(GLOB_RECURSE ANDROID_CPP_SOURCES CONFIGURE_DEPENDS "${ANDROID_CPP_DIR}/audioapi/*.cpp")
file(GLOB_RECURSE COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/audioapi/*.cpp" "${COMMON_CPP_DIR}/audioapi/*.c")

set(EXTERNAL_DIR ${COMMON_CPP_DIR}/audioapi/external)
set(ARCH_LIB_DIR ${EXTERNAL_DIR}/${ANDROID_ABI})
set(INCLUDE_DIR ${EXTERNAL_DIR}/include)

add_library(react-native-audio-api SHARED ${ANDROID_CPP_SOURCES} ${COMMON_CPP_SOURCES})

foreach(lib IN ITEMS opus opusfile ogg vorbis vorbisenc vorbisfile)
add_library(${lib} STATIC IMPORTED)
set_target_properties(${lib} PROPERTIES IMPORTED_LOCATION ${ARCH_LIB_DIR}/lib${lib}.a)
endforeach()

find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG)
find_package(oboe REQUIRED CONFIG)
Expand All @@ -14,6 +23,9 @@ target_include_directories(
PRIVATE
"${COMMON_CPP_DIR}"
"${ANDROID_CPP_DIR}"
"${INCLUDE_DIR}"
"${INCLUDE_DIR}/opus"
"${INCLUDE_DIR}/vorbis"
"${REACT_NATIVE_DIR}/ReactCommon"
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/react/turbomodule"
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
Expand All @@ -40,4 +52,13 @@ else()
)
endif()

target_link_libraries(react-native-audio-api ${LINK_LIBRARIES} ${RN_VERSION_LINK_LIBRARIES})
target_link_libraries(react-native-audio-api
${LINK_LIBRARIES}
${RN_VERSION_LINK_LIBRARIES}
opusfile
opus
ogg
vorbis
vorbisenc
vorbisfile
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,110 +7,123 @@
#define MINIAUDIO_IMPLEMENTATION
#include <audioapi/libs/miniaudio/miniaudio.h>

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

// #include <android/log.h>

namespace audioapi {

// 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.
std::vector<int16_t> AudioDecoder::readAllPcmFrames(
ma_decoder &decoder,
int numChannels,
ma_uint64 &outFramesRead) const {
std::vector<int16_t> buffer;
int16_t temp[CHUNK_SIZE * numChannels];
outFramesRead = 0;

while (true) {
ma_uint64 tempFramesDecoded = 0;
ma_decoder_read_pcm_frames(&decoder, temp, CHUNK_SIZE, &tempFramesDecoded);
if (tempFramesDecoded == 0) {
break;
}

buffer.insert(buffer.end(), temp, temp + tempFramesDecoded * numChannels);
outFramesRead += tempFramesDecoded;
}

return buffer;
}

std::shared_ptr<AudioBus> AudioDecoder::makeAudioBusFromInt16Buffer(
const std::vector<int16_t> &buffer,
int numChannels,
float sampleRate) const {
auto outputFrames = buffer.size() / numChannels;
auto audioBus =
std::make_shared<AudioBus>(outputFrames, numChannels, sampleRate);

for (int ch = 0; ch < numChannels; ++ch) {
auto channelData = audioBus->getChannel(ch)->getData();
for (int i = 0; i < outputFrames; ++i) {
channelData[i] = int16ToFloat(buffer[i * numChannels + ch]);
}
}
return audioBus;
}

std::shared_ptr<AudioBus> AudioDecoder::decodeWithFilePath(
const std::string &path) const {
ma_decoding_backend_vtable *customBackends[] = {
ma_decoding_backend_libvorbis, ma_decoding_backend_libopus};

ma_decoder decoder;
ma_decoder_config config = ma_decoder_config_init(
ma_format_s16, numChannels_, static_cast<int>(sampleRate_));
ma_result result = ma_decoder_init_file(path.c_str(), &config, &decoder);
if (result != MA_SUCCESS) {
config.ppCustomBackendVTables = customBackends;
config.customBackendCount =
sizeof(customBackends) / sizeof(customBackends[0]);

if (ma_decoder_init_file(path.c_str(), &config, &decoder) != MA_SUCCESS) {
// __android_log_print(
// ANDROID_LOG_ERROR,
// "AudioDecoder",
// "Failed to initialize decoder for file: %s",
// path.c_str());
// ANDROID_LOG_ERROR,
// "AudioDecoder",
// "Failed to initialize decoder for file: %s",
// path.c_str());
ma_decoder_uninit(&decoder);

return nullptr;
}

ma_uint64 totalFrameCount;
ma_decoder_get_length_in_pcm_frames(&decoder, &totalFrameCount);

std::vector<int16_t> buffer(totalFrameCount * numChannels_);

ma_uint64 framesDecoded;
ma_decoder_read_pcm_frames(
&decoder, buffer.data(), totalFrameCount, &framesDecoded);

if (framesDecoded == 0) {
ma_uint64 framesRead = 0;
auto buffer = readAllPcmFrames(decoder, numChannels_, framesRead);
if (framesRead == 0) {
// __android_log_print(ANDROID_LOG_ERROR, "AudioDecoder", "Failed to
// decode");

ma_decoder_uninit(&decoder);
return nullptr;
}

auto outputFrames = buffer.size() / numChannels_;
auto audioBus =
std::make_shared<AudioBus>(outputFrames, numChannels_, sampleRate_);

for (int i = 0; i < numChannels_; ++i) {
auto channelData = audioBus->getChannel(i)->getData();

for (ma_uint64 j = 0; j < outputFrames; ++j) {
channelData[j] = int16ToFloat(buffer[j * numChannels_ + i]);
}
}

ma_decoder_uninit(&decoder);

return audioBus;
return makeAudioBusFromInt16Buffer(buffer, numChannels_, sampleRate_);
}

std::shared_ptr<AudioBus> AudioDecoder::decodeWithMemoryBlock(
const void *data,
size_t size) const {
ma_decoding_backend_vtable *customBackends[] = {
ma_decoding_backend_libvorbis, ma_decoding_backend_libopus};

ma_decoder decoder;
ma_decoder_config config = ma_decoder_config_init(
ma_format_s16, numChannels_, static_cast<int>(sampleRate_));
ma_result result = ma_decoder_init_memory(data, size, &config, &decoder);
if (result != MA_SUCCESS) {
config.ppCustomBackendVTables = customBackends;
config.customBackendCount =
sizeof(customBackends) / sizeof(customBackends[0]);

if (ma_decoder_init_memory(data, size, &config, &decoder) != MA_SUCCESS) {
// __android_log_print(
// ANDROID_LOG_ERROR,
// "AudioDecoder",
// "Failed to initialize decoder for memory block");
// ANDROID_LOG_ERROR,
// "AudioDecoder",
// "Failed to initialize decoder for memory block");
ma_decoder_uninit(&decoder);

return nullptr;
}

ma_uint64 totalFrameCount;
ma_decoder_get_length_in_pcm_frames(&decoder, &totalFrameCount);

std::vector<int16_t> buffer(totalFrameCount * numChannels_);

ma_uint64 framesDecoded;
ma_decoder_read_pcm_frames(
&decoder, buffer.data(), totalFrameCount, &framesDecoded);

if (framesDecoded == 0) {
ma_uint64 framesRead = 0;
auto buffer = readAllPcmFrames(decoder, numChannels_, framesRead);
if (framesRead == 0) {
// __android_log_print(ANDROID_LOG_ERROR, "AudioDecoder", "Failed to
// decode");

ma_decoder_uninit(&decoder);
return nullptr;
}

auto outputFrames = buffer.size() / numChannels_;
auto audioBus =
std::make_shared<AudioBus>(outputFrames, numChannels_, sampleRate_);

for (int i = 0; i < numChannels_; ++i) {
auto channelData = audioBus->getChannel(i)->getData();

for (ma_uint64 j = 0; j < outputFrames; ++j) {
channelData[j] = int16ToFloat(buffer[j * numChannels_ + i]);
}
}

ma_decoder_uninit(&decoder);

return audioBus;
return makeAudioBusFromInt16Buffer(buffer, numChannels_, sampleRate_);
}

std::shared_ptr<AudioBus> AudioDecoder::decodeWithPCMInBase64(
Expand Down
Loading
Loading