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 packages/react-native-audio-api/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
AudioAPI_kotlinVersion=1.7.0
AudioAPI_minSdkVersion=21
AudioAPI_targetSdkVersion=31
AudioAPI_compileSdkVersion=31
AudioAPI_ndkversion=21.4.7075529
AudioAPI_minSdkVersion=24
AudioAPI_targetSdkVersion=36
AudioAPI_compileSdkVersion=36
AudioAPI_ndkVersion=27.1.12297006
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ if(CMAKE_C_COMPILER_ID MATCHES "Clang")
)
endif()

# AMediaDataSource (API 28) is weak-linked so the library still loads on older
# devices; calls are guarded with __builtin_available and the guard is enforced
# at compile time. Requires NDK r26+.
set_source_files_properties(
"${ANDROID_CPP_DIR}/audioapi/android/AndroidDecodingDataSource.cpp"
PROPERTIES
COMPILE_FLAGS "-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability"
)

set(INCLUDE_DIR ${COMMON_CPP_DIR}/audioapi/external/include)
set(FFMPEG_INCLUDE_DIR ${COMMON_CPP_DIR}/audioapi/external/include_ffmpeg)
set(EXTERNAL_DIR ${COMMON_CPP_DIR}/audioapi/external/android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ constexpr int64_t kCodecTimeoutUs = 5000;
// input without ever flagging EOS output (defensive against a stuck codec).
constexpr int kMaxTryAgainAfterEos = 200;

struct MediaExtractorDeleter {
void operator()(AMediaExtractor *extractor) const {
if (extractor != nullptr) {
AMediaExtractor_delete(extractor);
template <auto DeleteFn>
struct FnDeleter {
void operator()(auto *ptr) const {
if (ptr != nullptr) {
DeleteFn(ptr);
}
}
};
Expand All @@ -51,17 +52,9 @@ struct MediaCodecDeleter {
}
};

struct FileDeleter {
void operator()(FILE *file) const {
if (file != nullptr) {
fclose(file);
}
}
};

using MediaExtractorPtr = std::unique_ptr<AMediaExtractor, MediaExtractorDeleter>;
using MediaExtractorPtr = std::unique_ptr<AMediaExtractor, FnDeleter<AMediaExtractor_delete>>;
using MediaCodecPtr = std::unique_ptr<AMediaCodec, MediaCodecDeleter>;
using FilePtr = std::unique_ptr<FILE, FileDeleter>;
using FilePtr = std::unique_ptr<FILE, FnDeleter<fclose>>;

decoding::DecoderResult attachMemoryExtractorViaTempFile(
AMediaExtractor *extractor,
Expand Down Expand Up @@ -444,21 +437,20 @@ decoding::DecoderResult settleCodecOutputFormat(AndroidDecoderState &state) {

decoding::DecoderResult attachMemoryExtractor(
AndroidDecoderState &state,
const std::vector<uint8_t> &data) {
std::vector<uint8_t> data) {
state.extractor.reset(AMediaExtractor_new());
if (state.extractor == nullptr) {
return Err("AndroidDecoder::open: AMediaExtractor_new failed");
}

AMediaExtractor *extractor = state.extractor.get();
if (android_get_device_api_level() >= kMediaDataSourceMinApiLevel) {
std::vector<uint8_t> owned = data;
return attachMemoryExtractorViaDataSource(
extractor,
state.dataSource,
state.encodedMemory,
state.memorySourceContext,
std::move(owned));
std::move(data));
}

return attachMemoryExtractorViaTempFile(extractor, state.tempFile, data);
Expand Down Expand Up @@ -488,22 +480,7 @@ decoding::DecoderResult AndroidDecoder::open(const decoding::LocalFileSource &so
return Err("AndroidDecoder::open setDataSource failed");
}

if (auto configured = configureExtractorMetadata(*state, source.sampleRate);
configured.is_err()) {
return configured;
}

if (auto settled = settleCodecOutputFormat(*state); settled.is_err()) {
return settled;
}

outputChannels_ = state->channels;
outputSampleRate_ = state->outputRate;
framePosition_ = 0;
setTotalPcmFramesFromDuration(state->durationSeconds);
impl_ = std::move(state);
open_ = true;
return Ok(None);
return finishOpen(std::move(state), source.sampleRate);
}

decoding::DecoderResult AndroidDecoder::open(const decoding::EncodedMemorySource &source) {
Expand All @@ -518,8 +495,13 @@ decoding::DecoderResult AndroidDecoder::open(const decoding::EncodedMemorySource
return attached;
}

if (auto configured = configureExtractorMetadata(*state, source.sampleRate);
configured.is_err()) {
return finishOpen(std::move(state), source.sampleRate);
}

decoding::DecoderResult AndroidDecoder::finishOpen(
std::unique_ptr<AndroidDecoderState> state,
int sampleRate) {
if (auto configured = configureExtractorMetadata(*state, sampleRate); configured.is_err()) {
return configured;
}

Expand Down Expand Up @@ -560,19 +542,19 @@ size_t AndroidDecoder::readPcmFrames(float *outInterleaved, size_t frameCount) {
}
outputSampleRate_ = state.outputRate;

const int ch = callerChannels;
const bool resample = state.resampler != nullptr;
std::vector<float> &served = resample ? state.outLeftover : state.nativeLeftover;
size_t &cursor = resample ? state.outCursor : state.nativeCursor;

const size_t avail = availableFrames(served, cursor, ch);
const size_t avail = availableFrames(served, cursor, callerChannels);
if (avail > 0) {
const size_t take = std::min(avail, frameCount - filled);
const size_t samples = take * static_cast<size_t>(callerChannels);
std::memcpy(
outInterleaved + filled * static_cast<size_t>(ch),
outInterleaved + filled * static_cast<size_t>(callerChannels),
served.data() + cursor,
take * static_cast<size_t>(ch) * sizeof(float));
cursor += take * static_cast<size_t>(ch);
samples * sizeof(float));
cursor += samples;
filled += take;
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class AndroidDecoder : public decoding::OsDecoderBase {
[[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override;

private:
[[nodiscard]] decoding::DecoderResult finishOpen(
std::unique_ptr<AndroidDecoderState> state,
int sampleRate);

void releaseImpl() override;

// Opaque NDK state (defined in AndroidDecoding.cpp).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,91 +1,32 @@
#include <audioapi/android/AndroidDecodingDataSource.h>

#include <media/NdkMediaDataSource.h>
#include <media/NdkMediaError.h>
#include <media/NdkMediaExtractor.h>

#include <dlfcn.h>
#include <algorithm>
#include <cstring>
#include <utility>
#include <vector>

// AMediaDataSource / setDataSourceCustom require API 28. To support lower API
// levels at build/link time while using these on newer devices, we load them
// dynamically from libmediandk.so.
#if __has_include(<media/NdkMediaDataSource.h>)
#include <media/NdkMediaDataSource.h>
#else
// Fallback definitions if header is missing (unlikely given it compiled).
struct AMediaDataSource;
typedef ssize_t (
*AMediaDataSourceReadAt)(void *userdata, off64_t offset, void *buffer, size_t size);
typedef ssize_t (*AMediaDataSourceGetSize)(void *userdata);
typedef void (*AMediaDataSourceClose)(void *userdata);
#endif
// AMediaDataSource / setDataSourceCustom require API 28 while minSdk is lower.
// This translation unit is compiled with __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
// (see CMakeLists.txt), so these symbols become weak imports that resolve to
// nullptr on older devices instead of failing library load; every use is guarded
// with __builtin_available. Requires NDK r26+.

namespace audioapi::android_decoder {

namespace {

// Function pointer types for dynamic loading.
typedef AMediaDataSource *(*pfn_AMediaDataSource_new)();
typedef void (*pfn_AMediaDataSource_delete)(AMediaDataSource *);
typedef void (*pfn_AMediaDataSource_setUserdata)(AMediaDataSource *, void *);
typedef void (*pfn_AMediaDataSource_setReadAt)(AMediaDataSource *, AMediaDataSourceReadAt);
typedef void (*pfn_AMediaDataSource_setGetSize)(AMediaDataSource *, AMediaDataSourceGetSize);
typedef void (*pfn_AMediaDataSource_setClose)(AMediaDataSource *, AMediaDataSourceClose);
typedef media_status_t (
*pfn_AMediaExtractor_setDataSourceCustom)(AMediaExtractor *, AMediaDataSource *);

struct DynamicNDK {
pfn_AMediaDataSource_new AMediaDataSource_new = nullptr;
pfn_AMediaDataSource_delete AMediaDataSource_delete = nullptr;
pfn_AMediaDataSource_setUserdata AMediaDataSource_setUserdata = nullptr;
pfn_AMediaDataSource_setReadAt AMediaDataSource_setReadAt = nullptr;
pfn_AMediaDataSource_setGetSize AMediaDataSource_setGetSize = nullptr;
pfn_AMediaDataSource_setClose AMediaDataSource_setClose = nullptr;
pfn_AMediaExtractor_setDataSourceCustom AMediaExtractor_setDataSourceCustom = nullptr;

bool initialized = false;

static DynamicNDK &get() {
static DynamicNDK instance;
if (!instance.initialized) {
void *lib = dlopen("libmediandk.so", RTLD_NOW);
if (lib) {
instance.AMediaDataSource_new =
(pfn_AMediaDataSource_new)dlsym(lib, "AMediaDataSource_new");
instance.AMediaDataSource_delete =
(pfn_AMediaDataSource_delete)dlsym(lib, "AMediaDataSource_delete");
instance.AMediaDataSource_setUserdata =
(pfn_AMediaDataSource_setUserdata)dlsym(lib, "AMediaDataSource_setUserdata");
instance.AMediaDataSource_setReadAt =
(pfn_AMediaDataSource_setReadAt)dlsym(lib, "AMediaDataSource_setReadAt");
instance.AMediaDataSource_setGetSize =
(pfn_AMediaDataSource_setGetSize)dlsym(lib, "AMediaDataSource_setGetSize");
instance.AMediaDataSource_setClose =
(pfn_AMediaDataSource_setClose)dlsym(lib, "AMediaDataSource_setClose");
instance.AMediaExtractor_setDataSourceCustom =
(pfn_AMediaExtractor_setDataSourceCustom)dlsym(
lib, "AMediaExtractor_setDataSourceCustom");
}
instance.initialized = true;
}
return instance;
}

bool isAvailable() const {
return AMediaDataSource_new != nullptr && AMediaDataSource_delete != nullptr &&
AMediaExtractor_setDataSourceCustom != nullptr;
}
};

ssize_t memoryDataSourceReadAt(void *userdata, off64_t offset, void *buffer, size_t size) {
auto *context = static_cast<MemoryDataSourceContext *>(userdata);
if (context == nullptr || context->data == nullptr || offset < 0 ||
static_cast<size_t>(offset) >= context->size) {
if (context == nullptr || context->data == nullptr || offset < 0) {
return -1;
}
if (static_cast<size_t>(offset) >= context->size) {
return 0;
}
const size_t available = context->size - static_cast<size_t>(offset);
const size_t toCopy = std::min(size, available);
if (toCopy > 0) {
Expand Down Expand Up @@ -126,13 +67,13 @@ AndroidMemoryDataSource &AndroidMemoryDataSource::operator=(
}

void AndroidMemoryDataSource::reset() {
if (handle_ != nullptr) {
auto &ndk = DynamicNDK::get();
if (ndk.AMediaDataSource_delete) {
ndk.AMediaDataSource_delete(static_cast<AMediaDataSource *>(handle_));
}
handle_ = nullptr;
if (handle_ == nullptr) {
return;
}
if (__builtin_available(android 28, *)) {
AMediaDataSource_delete(static_cast<AMediaDataSource *>(handle_));
}
handle_ = nullptr;
}

decoding::DecoderResult attachMemoryExtractorViaDataSource(
Expand All @@ -141,33 +82,30 @@ decoding::DecoderResult attachMemoryExtractorViaDataSource(
std::vector<uint8_t> &encodedMemory,
MemoryDataSourceContext &memorySourceContext,
std::vector<uint8_t> data) {
if (__builtin_available(android 28, *)) {
encodedMemory = std::move(data);
memorySourceContext = {encodedMemory.data(), encodedMemory.size()};

auto &ndk = DynamicNDK::get();
if (!ndk.isAvailable()) {
return Err(
"AndroidDecoder: AMediaDataSource is not supported on this device (requires API 28)");
}

encodedMemory = std::move(data);
memorySourceContext = {encodedMemory.data(), encodedMemory.size()};
AMediaDataSource *rawDataSource = AMediaDataSource_new();
if (rawDataSource == nullptr) {
return Err("AndroidDecoder::open: AMediaDataSource_new failed");
}
AMediaDataSource_setUserdata(rawDataSource, &memorySourceContext);
AMediaDataSource_setReadAt(rawDataSource, memoryDataSourceReadAt);
AMediaDataSource_setGetSize(rawDataSource, memoryDataSourceGetSize);
AMediaDataSource_setClose(rawDataSource, memoryDataSourceClose);

if (AMediaExtractor_setDataSourceCustom(extractor, rawDataSource) != AMEDIA_OK) {
AMediaDataSource_delete(rawDataSource);
return Err("AndroidDecoder::open setDataSourceCustom failed");
}

AMediaDataSource *rawDataSource = ndk.AMediaDataSource_new();
if (rawDataSource == nullptr) {
return Err("AndroidDecoder::open: AMediaDataSource_new failed");
}
ndk.AMediaDataSource_setUserdata(rawDataSource, &memorySourceContext);
ndk.AMediaDataSource_setReadAt(rawDataSource, memoryDataSourceReadAt);
ndk.AMediaDataSource_setGetSize(rawDataSource, memoryDataSourceGetSize);
ndk.AMediaDataSource_setClose(rawDataSource, memoryDataSourceClose);

if (ndk.AMediaExtractor_setDataSourceCustom(extractor, rawDataSource) != AMEDIA_OK) {
ndk.AMediaDataSource_delete(rawDataSource);
return Err("AndroidDecoder::open setDataSourceCustom failed");
dataSource.reset();
dataSource.handle_ = rawDataSource;
return Ok(None);
}

dataSource.reset();
dataSource.handle_ = rawDataSource;
return Ok(None);
return Err("AndroidDecoder: AMediaDataSource is not supported on this device (requires API 28)");
}

} // namespace audioapi::android_decoder
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ struct AMediaExtractor;
namespace audioapi::android_decoder {

// AMediaDataSource / setDataSourceCustom require API 28. minSdk may be lower, so
// implementations live in AndroidDecodingDataSource.cpp (compiled at API 28).
// AndroidDecodingDataSource.cpp weak-links these symbols and guards every call;
// below this level callers must use the temp-file fallback instead.
constexpr int kMediaDataSourceMinApiLevel = 28;

struct MemoryDataSourceContext {
const uint8_t *data = nullptr;
size_t size = 0;
};

/// Opaque owning handle for AMediaDataSource (defined in the API 28 translation unit).
/// Opaque owning handle for AMediaDataSource (kept as void* so NDK media types
/// stay out of cross-platform includes).
class AndroidMemoryDataSource {
public:
AndroidMemoryDataSource() = default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class IOSDecoder : public decoding::OsDecoderBase {
[[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override;

private:
[[nodiscard]] decoding::DecoderResult finishOpen(
std::unique_ptr<IosDecoderState> state,
int requestedSampleRate);

void releaseImpl() override;

// Opaque Core Audio state (defined in IOSDecoding.mm).
Expand Down
Loading
Loading