diff --git a/packages/react-native-audio-api/android/gradle.properties b/packages/react-native-audio-api/android/gradle.properties index 39c6a86f5..4aec2ffe3 100644 --- a/packages/react-native-audio-api/android/gradle.properties +++ b/packages/react-native-audio-api/android/gradle.properties @@ -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 diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/CMakeLists.txt b/packages/react-native-audio-api/android/src/main/cpp/audioapi/CMakeLists.txt index 2c6b3b920..f9d8231d3 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/CMakeLists.txt +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/CMakeLists.txt @@ -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) diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.cpp index 3c10b6f63..475023836 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.cpp @@ -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 +struct FnDeleter { + void operator()(auto *ptr) const { + if (ptr != nullptr) { + DeleteFn(ptr); } } }; @@ -51,17 +52,9 @@ struct MediaCodecDeleter { } }; -struct FileDeleter { - void operator()(FILE *file) const { - if (file != nullptr) { - fclose(file); - } - } -}; - -using MediaExtractorPtr = std::unique_ptr; +using MediaExtractorPtr = std::unique_ptr>; using MediaCodecPtr = std::unique_ptr; -using FilePtr = std::unique_ptr; +using FilePtr = std::unique_ptr>; decoding::DecoderResult attachMemoryExtractorViaTempFile( AMediaExtractor *extractor, @@ -444,7 +437,7 @@ decoding::DecoderResult settleCodecOutputFormat(AndroidDecoderState &state) { decoding::DecoderResult attachMemoryExtractor( AndroidDecoderState &state, - const std::vector &data) { + std::vector data) { state.extractor.reset(AMediaExtractor_new()); if (state.extractor == nullptr) { return Err("AndroidDecoder::open: AMediaExtractor_new failed"); @@ -452,13 +445,12 @@ decoding::DecoderResult attachMemoryExtractor( AMediaExtractor *extractor = state.extractor.get(); if (android_get_device_api_level() >= kMediaDataSourceMinApiLevel) { - std::vector owned = data; return attachMemoryExtractorViaDataSource( extractor, state.dataSource, state.encodedMemory, state.memorySourceContext, - std::move(owned)); + std::move(data)); } return attachMemoryExtractorViaTempFile(extractor, state.tempFile, data); @@ -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) { @@ -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 state, + int sampleRate) { + if (auto configured = configureExtractorMetadata(*state, sampleRate); configured.is_err()) { return configured; } @@ -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 &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(callerChannels); std::memcpy( - outInterleaved + filled * static_cast(ch), + outInterleaved + filled * static_cast(callerChannels), served.data() + cursor, - take * static_cast(ch) * sizeof(float)); - cursor += take * static_cast(ch); + samples * sizeof(float)); + cursor += samples; filled += take; continue; } diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.h index 840c851c4..ee48c8536 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecoding.h @@ -32,6 +32,10 @@ class AndroidDecoder : public decoding::OsDecoderBase { [[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override; private: + [[nodiscard]] decoding::DecoderResult finishOpen( + std::unique_ptr state, + int sampleRate); + void releaseImpl() override; // Opaque NDK state (defined in AndroidDecoding.cpp). diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.cpp index b88f416de..0ffecd589 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.cpp @@ -1,91 +1,32 @@ #include +#include #include #include -#include #include #include #include #include -// 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() -#include -#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(userdata); - if (context == nullptr || context->data == nullptr || offset < 0 || - static_cast(offset) >= context->size) { + if (context == nullptr || context->data == nullptr || offset < 0) { return -1; } + if (static_cast(offset) >= context->size) { + return 0; + } const size_t available = context->size - static_cast(offset); const size_t toCopy = std::min(size, available); if (toCopy > 0) { @@ -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(handle_)); - } - handle_ = nullptr; + if (handle_ == nullptr) { + return; + } + if (__builtin_available(android 28, *)) { + AMediaDataSource_delete(static_cast(handle_)); } + handle_ = nullptr; } decoding::DecoderResult attachMemoryExtractorViaDataSource( @@ -141,33 +82,30 @@ decoding::DecoderResult attachMemoryExtractorViaDataSource( std::vector &encodedMemory, MemoryDataSourceContext &memorySourceContext, std::vector 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 diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.h index 8d2aacefe..d8f17b3a2 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidDecodingDataSource.h @@ -11,7 +11,8 @@ 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 { @@ -19,7 +20,8 @@ struct MemoryDataSourceContext { 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; diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.h b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.h index 6caa20f78..622872eb3 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.h +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.h @@ -31,6 +31,10 @@ class IOSDecoder : public decoding::OsDecoderBase { [[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override; private: + [[nodiscard]] decoding::DecoderResult finishOpen( + std::unique_ptr state, + int requestedSampleRate); + void releaseImpl() override; // Opaque Core Audio state (defined in IOSDecoding.mm). diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.mm b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.mm index bad0d93e4..adedfdda3 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSDecoding.mm @@ -18,27 +18,19 @@ namespace audioapi::ios_decoder { namespace { -struct ExtAudioFileDeleter { - void operator()(std::remove_pointer_t *file) const +template +struct FnDeleter { + void operator()(auto *file) const { if (file != nullptr) { - ExtAudioFileDispose(file); - } - } -}; - -struct AudioFileDeleter { - void operator()(std::remove_pointer_t *file) const - { - if (file != nullptr) { - AudioFileClose(file); + DisposeFn(file); } } }; using ExtAudioFilePtr = - std::unique_ptr, ExtAudioFileDeleter>; -using AudioFilePtr = std::unique_ptr, AudioFileDeleter>; + std::unique_ptr, FnDeleter>; +using AudioFilePtr = std::unique_ptr, FnDeleter>; } // namespace @@ -90,37 +82,37 @@ SInt64 memoryGetSizeProc(void *inClientData) } // Sniffs a container hint from the leading magic bytes so AudioFile can pick the -// right parser for in-memory data. +// right parser for in-memory data. Returns 0 when the container is unrecognized. // NOLINTBEGIN(readability-magic-numbers, cppcoreguidelines-avoid-magic-numbers) -const char *guessFileTypeExtension(const void *data, size_t size) +AudioFileTypeID guessFileTypeHint(const void *data, size_t size) { if (data == nullptr || size < 12) { - return "bin"; + return 0; } const auto *bytes = static_cast(data); if (std::memcmp(bytes, "RIFF", 4) == 0 && std::memcmp(bytes + 8, "WAVE", 4) == 0) { - return "wav"; + return kAudioFileWAVEType; } if (std::memcmp(bytes, "fLaC", 4) == 0) { - return "flac"; + return kAudioFileFLACType; } if (bytes[0] == 0xFF && (bytes[1] & 0xF6) == 0xF0) { - return "aac"; + return kAudioFileAAC_ADTSType; } if (std::memcmp(bytes, "ID3", 3) == 0 || (bytes[0] == 0xFF && (bytes[1] & 0xE0) == 0xE0)) { - return "mp3"; + return kAudioFileMP3Type; } if (std::memcmp(bytes + 4, "ftyp", 4) == 0) { - return "m4a"; + return kAudioFileM4AType; } if (std::memcmp(bytes, "caff", 4) == 0) { - return "caf"; + return kAudioFileCAFType; } if (std::memcmp(bytes, "FORM", 4) == 0 && (std::memcmp(bytes + 8, "AIFF", 4) == 0 || std::memcmp(bytes + 8, "AIFC", 4) == 0)) { - return "aiff"; + return kAudioFileAIFFType; } - return "bin"; + return 0; } // NOLINTEND(readability-magic-numbers, cppcoreguidelines-avoid-magic-numbers) @@ -133,15 +125,12 @@ SInt64 memoryGetSizeProc(void *inClientData) close(); } -// Sets the interleaved-float client format and caches channel/rate/duration. -static decoding::DecoderResult configureExtAudioFile( - ExtAudioFileRef extFile, - int requestedSampleRate, - int &outChannels, - int &outSampleRate, - double &outDurationSeconds, - double &outFileSampleRate) +decoding::DecoderResult IOSDecoder::finishOpen( + std::unique_ptr state, + int requestedSampleRate) { + ExtAudioFileRef extFile = state->extFile.get(); + AudioStreamBasicDescription fileFormat{}; UInt32 propSize = sizeof(fileFormat); OSStatus status = ExtAudioFileGetProperty( @@ -185,12 +174,17 @@ SInt64 memoryGetSizeProc(void *inClientData) fileLengthFrames = 0; // Unknown (e.g. raw ADTS AAC) — report duration 0. } - outChannels = channels; - outSampleRate = outRate; - outFileSampleRate = fileFormat.mSampleRate; - outDurationSeconds = fileFormat.mSampleRate > 0 + const double durationSeconds = fileFormat.mSampleRate > 0 ? static_cast(fileLengthFrames) / static_cast(fileFormat.mSampleRate) : 0.0; + + state->fileSampleRate = fileFormat.mSampleRate; + outputChannels_ = channels; + outputSampleRate_ = outRate; + framePosition_ = 0; + setTotalPcmFramesFromDuration(durationSeconds); + impl_ = std::move(state); + open_ = true; return Ok(None); } @@ -216,23 +210,7 @@ SInt64 memoryGetSizeProc(void *inClientData) auto state = std::make_unique(); state->extFile.reset(extFile); - double durationSeconds = 0.0; - auto configured = configureExtAudioFile( - state->extFile.get(), - source.sampleRate, - outputChannels_, - outputSampleRate_, - durationSeconds, - state->fileSampleRate); - if (configured.is_err()) { - return configured; - } - - impl_ = std::move(state); - framePosition_ = 0; - setTotalPcmFramesFromDuration(durationSeconds); - open_ = true; - return Ok(None); + return finishOpen(std::move(state), source.sampleRate); } } @@ -249,23 +227,7 @@ SInt64 memoryGetSizeProc(void *inClientData) // Wrap the owned bytes with AudioFile callbacks, then wrap that with // ExtAudioFile so we get the same read/seek/convert path as open without // touching the filesystem. - AudioFileTypeID hint = 0; - const char *ext = guessFileTypeExtension(source.data.data(), source.data.size()); - if (std::strcmp(ext, "wav") == 0) { - hint = kAudioFileWAVEType; - } else if (std::strcmp(ext, "mp3") == 0) { - hint = kAudioFileMP3Type; - } else if (std::strcmp(ext, "aac") == 0) { - hint = kAudioFileAAC_ADTSType; - } else if (std::strcmp(ext, "m4a") == 0) { - hint = kAudioFileM4AType; - } else if (std::strcmp(ext, "caf") == 0) { - hint = kAudioFileCAFType; - } else if (std::strcmp(ext, "aiff") == 0) { - hint = kAudioFileAIFFType; - } else if (std::strcmp(ext, "flac") == 0) { - hint = kAudioFileFLACType; - } + const AudioFileTypeID hint = guessFileTypeHint(source.data.data(), source.data.size()); AudioFileID audioFile = nullptr; OSStatus status = AudioFileOpenWithCallbacks( @@ -283,23 +245,7 @@ SInt64 memoryGetSizeProc(void *inClientData) } state->extFile.reset(extFile); - double durationSeconds = 0.0; - auto configured = configureExtAudioFile( - state->extFile.get(), - source.sampleRate, - outputChannels_, - outputSampleRate_, - durationSeconds, - state->fileSampleRate); - if (configured.is_err()) { - return configured; - } - - impl_ = std::move(state); - framePosition_ = 0; - setTotalPcmFramesFromDuration(durationSeconds); - open_ = true; - return Ok(None); + return finishOpen(std::move(state), source.sampleRate); } size_t IOSDecoder::readPcmFrames(float *outInterleaved, size_t frameCount)