fix(transfer): flush partial bridge PCM frame on clean WebSocket close - #275
Merged
Merged
Conversation
- pad and flush the buffered PCM tail as a final frame when the bridge WebSocket closes cleanly, for both PCM and track forward sinks - extract the bridge forward loop into bridge_forward_loop so the close-to-flush path is exercised at its real boundary by tests - add unit test asserting the zero-padded tail frame is flushed once
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.
Problem
In
SipSession::connect_bridge, the bridge forward loop reads PCM16 audiofrom the bridge WebSocket and repackages it into fixed 20 ms frames before
forwarding to its sink:
Pcmsink: samples pushed to a PCM channelTracksink: samples (re)sampler → codec encoder → RTC track as RTP framesThe loop only emitted a frame when the buffer held at least
samples_per_framesamples. When the remote side closed the WebSocketcleanly (
Message::Closeor stream EOF), any buffered samples shorter thanone frame — up to 20 ms of audio — were silently discarded, because no code
path ever drained a partial buffer.
So the tail of bridge audio was truncated at the end of every cleanly closed
bridge stream. For example, an audio service that streams a prompt over the
bridge and then closes the socket loses the final few milliseconds of the
prompt on the SIP leg.
Fix
bridge_forward_loop<S: Stream<Item = Result<Message, Error>>>. This is abehavior-preserving move: it makes the close→flush wiring testable at its
real boundary with an in-memory stream instead of a live WebSocket.
take_bridge_pcm_frame(buffered, samples_per_frame, flush_tail)as thesingle frame-extraction point, called before sink dispatch. When the stream
ends cleanly and a non-empty partial frame remains, it zero-pads the tail
to exactly one full frame and emits it once. Since frame extraction is now
shared, both sinks benefit: the
Pcmsink receives the padded tail frame,and the
Trackpath encodes it into a final RTP frame.Message::Close/ EOF) is the only signal that a shortchunk is final. Deliberate boundaries, unchanged from before:
since the buffered data may be incomplete/corrupt;
forward_cancelstill discards the buffer.Tests
test_bridge_forward_flushes_pcm_tail_on_remote_close: feeds 40 samples(5 ms at 8 kHz — short of one 20 ms frame) followed by
Message::Closeinto
bridge_forward_loopwith aPcmsink. Asserts the loop terminateson remote close and flushes exactly one 160-sample frame containing the
original samples followed by zero padding.
test_voip_bridge_echo_integration→test_voip_bridge_flushes_partial_pcm_tail: sends 25 ms (200 samples)through a real echo WebSocket server so that one full frame plus a 5 ms
tail remains at clean close, then drives the flushed frames through the
real
ChannelAudioSourceused by the media bridge egress path and assertsthe full frame, the padded tail, and subsequent EOF.