Repository navigation
perf(export): encode MP4 export audio with AudioToolbox on macOS - #2623
Merged
richiemcilroy merged 4 commits intoOct 20, 2026
Merged
Conversation
FFmpeg's built-in AAC encoder is the busiest CPU thread in an MP4 export. On macOS, exports now encode audio with AudioToolbox (`aac_at`, already in the bundled FFmpeg) in average-bitrate mode and fall back to FFmpeg's encoder if it fails to open. Recording paths keep FFmpeg's encoder: their fragmented MP4s keep encoder priming in the timeline, and AudioToolbox primes 2112 samples instead of 1024. libavcodec pads AudioToolbox's final frame to a full frame and its packet durations count the padding, so the audio track is trimmed back to the end of the input. Encoders that accept a short final frame (FFmpeg `aac`, `libopus`) are not affected. Adds an export benchmark example to the GPUI workspace that exports the way the export page does.
Contributor
|
@vagxrth is attempting to deploy a commit to the Cap Software Inc Team on Vercel. A member of the Team first needs to authorize it. |
Member
|
Thanks @vagxrth! Greptile is at 4/5 here. We only merge once Greptile reaches 5/5, so please address its findings in the summary above (or reply to them if you think one is wrong), then comment |
Contributor
Author
|
@greptileai review |
1 similar comment
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@richiemcilroy Greptile is at 5/5 on the latest commit now.
Ready for the verification suite whenever you are. Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MP4 exports encode their audio track with FFmpeg's built-in AAC encoder (
aac, two-loop search). On macOS that encoder is most of the CPU work an export does: in a 60 s export it retires ~56G of the export process's ~76G instructions, on a thread (export-audio) that sampling shows insideavcodec_send_frame~99% of the time.This PR encodes export audio with AudioToolbox's AAC encoder (
aac_at, FFmpeg's wrapper, already in the bundled native-deps) in average-bitrate mode. If AudioToolbox fails to open, the export falls back to FFmpeg's encoder exactly as before. Other platforms are unchanged. Studio recordings moved to AudioToolbox separately in #2626; this PR reuses that encoder setup for exports.Results
Release build of the GPUI workspace (
codegen-units = 1, thin LTO), Apple M2 (8 cores, 8 GB), macOS 27.0. "Before" ismainas of Oct 15, "after" is this branch. Theexport-benchmarkexample added here callsExporterBase::build_for_mp4+Mp4ExportSettings::export_preparedthe way the GPUI export page does. Before and after binaries ran alternately; instructions and cycles come from/usr/bin/time -l. Medians of 7.Cap's reference recording (
reference-recording.cap, 30.8 s, two segments, mic + system audio):60 s Studio recording (3024×1964 @ 60 fps display from VideoToolbox at Cap's studio bitrate, 720p camera, 48 kHz mono mic with a minute of speech, 60 Hz cursor, default editor config):
The same project exported without a mic track retires 19.3G instructions at 720p30, so the audio path itself goes from ~56G to ~3.3G instructions (~17× less).
On an idle machine wall time doesn't move: MP4 export on an M2 is bound by the hardware H.264 encoder (VideoToolbox runs at ~190 fps at 1080p, two or three sessions in parallel share that rate, and exporting the project without any audio is no faster). The gain shows up when the Mac is doing anything else, because the export no longer needs a core to itself for its audio. The "busy" columns run four
yesprocesses for the whole benchmark.Encoder alone (FFmpeg CLI, 60 s of 48 kHz stereo at 320 kbps): FFmpeg's
aactakes 3.35 s on speech, 3.00 s on music-like content; AudioToolbox ABR takes 0.43 s and 0.42 s (7–8×).Re-checked after merging current
main(mergebca5f2a, reference recording, medians of 7, alternating). "Before" is this branch with the exporter callingAACEncoder::initagain, which ismain's export path:Other desktop apps were using about 1.5 cores when this run started, so its first wall column is a light-load number, not an idle one (the clean idle numbers are in the first table, where wall time didn't move).
Output
main: the sync project still exports with a 0.00 ms offset at 60 and 30 fps, before and after, with both streams exactly 5.000 s long. The export's first audio packet is at −2112 (AudioToolbox priming, hidden by the edit list, no leading trim) and the track ends on sample 240,000 in both builds.1478656 + 624vs1478592 + 688= 1479280 on the reference recording; exactly 5.000 s on the sync project). The audio edit list's millisecond field can differ by 1 ms (30.818 vs 30.819 s) because the two encoders' priming lengths round differently.maineither; the audio track is deterministic in both builds.Priming: why exports don't trim like recordings
AudioToolbox primes 2112 samples, FFmpeg's encoder 1024. A plain MP4 hides priming with its edit list; a fragmented MP4 keeps it in the timeline. Decoding a click encoded at exactly 1.000 s:
aacaac_at#2626 handles the fragmented row by dropping the first 1088 input samples of each recording track, so content lands where FFmpeg's encoder put it. An export is a plain MP4: its edit list already skips all 2112 priming samples, so it needs no trim and keeps every input sample.
init_with_codectherefore takes aPrimingargument: recordings passTrimToFfmpegAac(their behaviour is unchanged), exports andAACEncoder::initpassKeep. With the recording trim an export would lose its first 22.7 ms of audio; the export test has a click at sample 240 that fails in that case.Changes
AACEncoder::init_for_export: on macOS opensaac_atwithaac_at_mode=abrat the exporter's existing 320 kbps throughinit_with_codec, and falls back toaac; elsewhere it isinit.init_with_codectakes aPrimingargument (above).crates/export/src/mp4.rs: the MP4 exporter callsinit_for_export.apps/desktop-gpui/examples/export-benchmark.rs: exports a project like the GPUI export page and prints wall time, frames per second and process CPU time.EndTrimmedSink: AudioToolbox only takes full frames, and libavcodec's padding of the last one made the track up to a frame longer than its input) and the codec-driven sample format reachedmainthrough perf(recording): encode macOS studio audio with AudioToolbox AAC #2626 under the same names, so they are no longer part of this diff.Checks
aac_at;benchmark_export_aac_encoder(ignored) compares the two encoders.cargo test -p cap-enc-ffmpeg: 185 passed (macOS), including perf(recording): encode macOS studio audio with AudioToolbox AAC #2626's recording tests.cargo clippy -p cap-enc-ffmpeg -p cap-export --all-targets -- -D warningsandcargo clippy --example export-benchmark -- -D warnings(GPUI workspace) are clean;cargo fmt --allis clean.Reproduce


Confidence Score: 5/5
The PR appears safe to merge; no new actionable issues were found.Summary
MP4 exports now try AudioToolbox AAC on macOS with average bitrate, then fall back to FFmpeg AAC if opening fails.
The full PR diff was reviewed because the previous and current commits have different merge bases with the target commit. Tests were not run during this review.
Reviews (4) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile