api: allow pipeline draining during flush on short streams - #180
Merged
Conversation
Contributor
Author
|
This fixes the LC side but there is still an open HE lag that I'm chasing. It is safe to merge as it is technically correct. |
nschimme
force-pushed
the
fix-flush-drain
branch
from
August 27, 2026 18:49
5f36787 to
d89997c
Compare
Contributor
Author
|
Fixed the HE side, this is good to go now |
faacEncEncode()'s API contract instructs callers to pass samplesInput == 0 to initiate a flush, and to keep calling until 0 bytes are returned. However, the encoder previously gated output on a look-ahead depth check (frameNum <= LOOKAHEAD_DEPTH), returning 0 bytes while filling the pipeline. For audio streams shorter than LOOKAHEAD_DEPTH, the very first flush call hit this look-ahead guard and returned 0. Callers adhering to the API contract interpreted this 0 as "fully drained" and stopped polling, causing the encoder to discard all buffered audio. Fix this by bypassing the look-ahead check when flushing is active. This allows the pipeline to continue emitting encoded frames frame-by-frame until the core look-ahead delay is completely drained. Additionally, treat a NULL inputBuffer as an implicit flush trigger to safely handle samplesInput/inputBuffer parameter mismatches. HE-AAC needs one more flush tick than LC: its SBR coded-payload ring (frameFIFO, sized SBR_FRAME_FIFO) trails the core FIFO by one extra tick, so the shared LOOKAHEAD_DEPTH + 1 budget still dropped real trailing audio near a frame boundary. Widen the budget to SBR_FRAME_FIFO for HE_V1 so the ring drains fully before flushFrame's 0-return.
nschimme
force-pushed
the
fix-flush-drain
branch
from
August 27, 2026 18:57
d89997c to
4a42f15
Compare
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.
This is a correctness fix with a minor performance hit.
Refactors
faacEncEncode()during end-of-stream flushing to absorb internal look-ahead priming ticks inside the encoder and extends the flush budget for HE-AAC, preventing premature 0-byte returns on short audio streams and truncated SBR tails.The Problem
faacEncEncode()requires callers to passsamplesInput == 0to flush and poll until0bytes are returned. However, the encoder previously applied the look-ahead fill check (frameNum <= LOOKAHEAD_DEPTH) unconditionally. For input streams shorter than the look-ahead depth, initial flush calls hit this gate and returned0bytes while still priming. Callers adhering to the API contract interpreted this initial0as "drain complete" and stopped calling, discarding all buffered audio tail.HE_V1) utilizes an SBR coded-payload ring (frameFIFO, sizedSBR_FRAME_FIFO) that trails the core FIFO by one extra tick. The standard LC flush budget (LOOKAHEAD_DEPTH + 1) returned0before trailing SBR envelope payloads were fully emitted, dropping real trailing audio near frame boundaries.Architectural Fix
Instead of forcing callers to disambiguate "still warming up look-ahead" from "genuinely drained,"
faacEncEncode()now handles flush ticks via an internaldo { ... } while (flushing)loop:flushing == 1): Loops internally to absorb no-output look-ahead priming ticks until a real frame is produced OR the pipeline is fully drained. This guarantees that a0-byte return strictly signifies a fully drained pipeline.LOOKAHEAD_DEPTH + 1toSBR_FRAME_FIFOwhenHE_V1is active, allowing the SBR payload ring to drain completely before emitting the final 0-byte return.flushing == 0): Retains single-pass execution—returns0when accumulating, or proceeds downstream once a full frame is ready.inputBuffer == NULLas an implicit flush trigger to guard against caller parameter mismatches.Testing
Benchmark: https://github.com/nschimme/faac/actions/runs/33096630745