Skip to content

feat: add ADTS AAC file format for crash-recoverable compressed recording - #1319

Open
wildseansy wants to merge 1 commit into
software-mansion:mainfrom
wildseansy:feat/adts-recording-format
Open

wildseansy wants to merge 1 commit into
software-mansion:mainfrom
wildseansy:feat/adts-recording-format

Conversation

@wildseansy

Copy link
Copy Markdown
Contributor

Closes # — no existing issue tracks this. The format has been in production use through a fork of this library (the Tonic music-lesson recorder); design notes are below, and I am happy to move them into a discussion or feature request first if you prefer that flow.

⚠️ Breaking changes ⚠️

  • None. FileFormat.Adts = 4 is appended; existing values, defaults and behaviour are unchanged.

Introduced changes

  • New FileFormat.Adts recorder output: raw AAC-LC frames in ADTS framing, .aac extension. Every frame carries its own sync word and length header, so a recording cut off by a crash or force-kill stays decodable up to the last complete frame — the recoverability Flac already offers, at lossy-AAC file sizes (~10× smaller) and without a post-processing transcode. The existing AAC bitrate/quality properties apply exactly as for M4A.
  • iOS: FileOptions.mm maps ADTS to kAudioFormatMPEG4AAC with the aac extension. AVFoundation selects the ADTS file type from the extension; the existing AAC settings path is unchanged.
  • Android: FFmpegAudioFileWriter writes ADTS without a muxer. The prebuilt FFmpeg from software-mansion-labs/rn-audio-libs is configured with --enable-muxer=wav,mp4,flac,caf (no adts), so avformat_alloc_output_context2("adts") would fail at record start. ADTS does not need a muxer: for every encoded packet the writer emits a hand-built 7-byte header (syncword, MPEG-4, AAC-LC profile, sample-rate index, channel configuration, 13-bit frame length, VBR buffer fullness) followed by the raw packet, through an AVIOContext opened with avio_open. Encoder, resampler and FIFO paths are unchanged; the periodic avio_flush runs on the ADTS context so crash durability matches the container path; no trailer is written (an ADTS stream is complete after its last frame); rollbackFailedOpen() closes the ADTS context; finalizeOutput() derives the duration from nextPts_ as before.
  • openAdtsIO() rejects sample rates with no ADTS frequency index and channel counts outside 1–6 up front, so an unsupported configuration fails at start instead of producing an undecodable file.
  • openFile() now routes every failure in the init chain through a single or_else → rollbackFailedOpen(), so a late failure cannot leak an open AVIOContext or a partial output file (previously only the pool-allocation failure rolled back).
  • Docs: FileFormat enum and the Android/FFmpeg caution in inputs/audio-recorder.mdx; FFmpeg usage table in other/runtime-flags.mdx.

Design notes

  • Why not FFmpeg's adts muxer? It is not compiled into the prebuilt libraries, and enabling it means a rebuild and re-release of rn-audio-libs. The ADTS header is 7 bytes of fixed layout, so writing it by hand is a smaller change than the dependency bump and works on today's binaries.
  • Why a new FileFormat rather than an option on M4A? The output has a different (absent) container and a different extension, and consumers need to know to expect a raw stream. A distinct enum value keeps that explicit and mirrors how Flac and Caf are exposed.
  • Rotation / concatenation: ADTS streams concatenate by byte append, so rotated segments can be joined without re-muxing. This PR does not add an ADTS branch to concatAudioFiles; that can follow separately if wanted.

Checklist

  • Linked relevant issue — none exists; rationale above
  • Updated relevant documentation
  • Added/Conducted relevant tests — manual, see below; the FFmpeg writer backend has no unit harness in the repo today
  • Performed self-review of the code
  • Updated Web Audio API coverage — N/A, recorder file output is outside the Web Audio API
  • Added support for web — N/A, recorder file output has no web implementation
  • Updated old arch android spec file — N/A, no native module method or signature changes; the format travels through the existing recorder options

Verification

  • Android (Pixel 3 XL, Android 12): 64 s recording → 2,758 ADTS frames, 0 bad sync words, AAC-LC 44.1 kHz mono; size and duration returned by stop() match the file. A snapshot of the file taken mid-recording is a frame-aligned byte prefix of the final file, confirming the periodic flush keeps a killed recording decodable.
  • iOS (iPhone 17 Pro simulator, iOS 26.5): in-progress .aac files pulled mid-recording parse with every ADTS header valid.
  • clang-format 23, cpplint 2.0.2, prettier 3.3.3 and commitlint (config-conventional) clean on the changed files.

🤖 Generated with Claude Code

…ding

Adds FileFormat.Adts as a recorder output option. ADTS frames are
self-delimiting (each carries a sync word and a frame-length header), so
a recording interrupted by a crash or force-kill stays decodable up to
the last complete frame - the same recoverability FLAC offers today, at
lossy-AAC file sizes (~10x smaller) and with no post-processing
transcode.

iOS: maps ADTS to kAudioFormatMPEG4AAC with an .aac extension; the
existing AAC bitrate settings path applies unchanged.

Android: the FFmpeg writer produces ADTS without a muxer. The prebuilt
FFmpeg from software-mansion-labs/rn-audio-libs is configured with
--enable-muxer=wav,mp4,flac,caf (no adts), so
avformat_alloc_output_context2("adts") would fail at record start.
ADTS does not need one: for each encoded AAC packet the writer builds
the 7-byte ADTS header by hand (syncword 0xFFF, MPEG-4, AAC-LC profile,
sample-rate index from the encoder rate, channel configuration, 13-bit
frame length = 7 + packet size, buffer fullness 0x7FF) and writes header
plus raw packet straight to an AVIOContext opened with avio_open. The
encoder, resampler and FIFO paths are unchanged; the periodic avio_flush
is retained on the ADTS AVIOContext so crash durability is the same as
the container path. No trailer is written - an ADTS stream is complete
after its last frame. rollbackFailedOpen() closes the ADTS AVIOContext
and clears the flag, and finalizeOutput() derives the ADTS duration from
nextPts_ / encoder sample rate like the container path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mdydek

mdydek commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

currently in #1183 we are deleting ffmpeg support when it comes to encoding in favor of OS apis, but it looks like ADTS AAC format will be easy to onboard there. Leaving for now to not forget about it, but we will not proceed with this implementation at this time

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants