Skip to content
Merged
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
10 changes: 8 additions & 2 deletions packages/audiodocs/docs/utils/file-concatenation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

<details>
<summary>Example usage with rotated recordings</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ class AdtsBackend : public MediaCodecBackend {
}
samplingFrequencyIndex_ = *adts::samplingFrequencyIndex(static_cast<int>(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";
}
Expand All @@ -698,8 +698,7 @@ class AdtsBackend : public MediaCodecBackend {
static_cast<int>(encoding.bitRate),
true);
if (!err.empty()) {
std::fclose(file_);
file_ = nullptr;
file_.reset();
return err;
}
return "";
Expand Down Expand Up @@ -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<std::FILE, decltype(&std::fclose)> file_{nullptr, &std::fclose};
uint8_t samplingFrequencyIndex_{0};
size_t bytesWritten_{0};
};
Expand Down
Loading