diff --git a/packages/audiodocs/docs/utils/file-concatenation.mdx b/packages/audiodocs/docs/utils/file-concatenation.mdx index 36c03a7bf..41b93f802 100644 --- a/packages/audiodocs/docs/utils/file-concatenation.mdx +++ b/packages/audiodocs/docs/utils/file-concatenation.mdx @@ -21,12 +21,17 @@ recording is finished. ::: :::caution -**Only WAV, M4A, and FLAC concatenation is supported** — the recording formats +**Only WAV, M4A, FLAC, and ADTS concatenation is supported** — the recording formats available on both iOS and Android. WAV and FLAC decode the segments and write them out again through the system encoder — WAV as 32-bit float PCM, FLAC losslessly. M4A remuxes AAC packets with system APIs — iOS AVFoundation and Android MediaExtractor/MediaMuxer. + +ADTS (`.aac`) appends complete encoded frames without decoding or re-encoding. +Inputs must use the same sample rate and channel configuration. An incomplete +trailing frame, such as one left by an app crash, is discarded before the next +file's frames are appended. ::: :::tip Segment size and quality @@ -68,7 +73,8 @@ come from the same recorder configuration: Use an output path with an extension that matches the input files, for example joining `.m4a` segments into an `.m4a` output file, `.wav` segments into a -`.wav` output file and so on. +`.wav` output file, `.flac` segments into a `.flac` output file, or ADTS `.aac` +segments into an `.aac` output file.
Example usage with rotated recordings diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidEncoder.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidEncoder.cpp index 1f7abe592..9b9176ffc 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidEncoder.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AndroidEncoder.cpp @@ -686,7 +686,7 @@ class AdtsBackend : public MediaCodecBackend { } samplingFrequencyIndex_ = *adts::samplingFrequencyIndex(static_cast(desired.sampleRate)); - file_ = std::fopen(filePath.c_str(), "wb"); + file_.reset(std::fopen(filePath.c_str(), "wb")); if (file_ == nullptr) { return "AdtsBackend: failed to open file for writing"; } @@ -698,8 +698,7 @@ class AdtsBackend : public MediaCodecBackend { static_cast(encoding.bitRate), true); if (!err.empty()) { - std::fclose(file_); - file_ = nullptr; + file_.reset(); return err; } return ""; @@ -731,28 +730,30 @@ class AdtsBackend : public MediaCodecBackend { return "AdtsBackend: encoded frame too long for an ADTS header"; } - if (std::fwrite(header->data(), 1, header->size(), file_) != header->size() || - std::fwrite(data + info.offset, 1, payloadSize, file_) != payloadSize) { + if (std::fwrite(header->data(), 1, header->size(), file_.get()) != header->size() || + std::fwrite(data + info.offset, 1, payloadSize, file_.get()) != payloadSize) { return "AdtsBackend: short write"; } // Flushed per frame, so the crash-recoverability the format exists for holds at frame // granularity rather than at stdio's buffer size. - std::fflush(file_); + if (std::fflush(file_.get()) != 0) { + return "AdtsBackend: failed to flush file"; + } bytesWritten_ += header->size() + payloadSize; return ""; } std::string finalize() override { codec_.reset(); - if (file_ != nullptr) { - std::fclose(file_); - file_ = nullptr; + if (file_ != nullptr && std::fclose(file_.release()) != 0) { + return "AdtsBackend: failed to close file"; } return ""; } private: - std::FILE *file_{nullptr}; + // Also closes the file when codec draining fails before finalize() is reached. + std::unique_ptr file_{nullptr, &std::fclose}; uint8_t samplingFrequencyIndex_{0}; size_t bytesWritten_{0}; };