Add 'mix' flag to subscribe request for mixed audio output#2088
Draft
fergusean wants to merge 1 commit intosipwise:masterfrom
Draft
Add 'mix' flag to subscribe request for mixed audio output#2088fergusean wants to merge 1 commit intosipwise:masterfrom
fergusean wants to merge 1 commit intosipwise:masterfrom
Conversation
e255f32 to
9faa00b
Compare
…ream When subscribing to a call with multiple sources (from-tags or all), the new 'mix' flag causes rtpengine to create a single destination media that mixes all audio sources via audio_player + mix_buffer, instead of creating separate media streams per source. Additional features and fixes included: - Fix G722 audio in mix mode: decoder output format now applies default_clockrate_fact (G722 RTP clock 8000 → actual 16000 Hz) - Fix mix tap surviving leg detach: monologue_stop now uses targeted codec_handlers_stop per source media instead of tearing down all handlers on subscriber medias - Add 'auto-all' flag for dynamic subscription: new legs joining via connect are automatically added to mix subscribers - Add tests for mix+G722, mix+detach, and auto-all scenarios Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9faa00b to
7d27cc3
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.
Summary
Adds a
mixflag tosubscribe request. When set, instead of creating one destination media per source (the default for multi-source subscriptions), all audio sources are routed to a single destination media with theaudio_player+mix_bufferpipeline activated. The subscriber receives one mixed audio stream combining both sides of the call.This enables call monitoring / listen-in scenarios where a single mixed output is needed rather than separate per-party streams.
Usage
{ "command": "subscribe request", "call-id": "...", "from-tags": ["tag-A", "tag-B"], "flags": ["mix"] }Or equivalently with the
allshorthand:{ "flags": ["all", "mix"] }The response SDP contains a single
m=audioline (vs two withoutmix). Non-audio media types (video, etc.) still get separate per-source destinations.Design
Reuses the existing
injectinfrastructure rather than introducing new mixing logic:monologue_subscribe_request_mix()creates one shared audio destination media, subscribes all audio sources to it, then callsinject_reconfigure_destination_media()withforce_audio_player=trueaudio_player→mix_bufferpipeline: each source's decoded PCM frames are written to the mix buffer (keyed by SSRC), mixed, and re-encoded to the negotiated output codecMEDIA_FLAG_MIX(bit 41) on the destination media lets the answer path (monologue_subscribe_answer) correctly handle codec setup for all subscriptions viainject_reconfigure_destination_media()rather than the normal single-sourcecodec_handlers_update()paircall_subscribe_request_ng()now iterates all subscriptions per destination media (not justhead), sofrom-tagsis correctly populated in mix modeRe-subscribe and mode transitions
Re-subscribing with the same
to-tagis fully supported in both directions:subscription_store_ht) rather than leaking a new oneaudio_playerand clearsMEDIA_FLAG_AUDIO_PLAYER+MEDIA_FLAG_MIXon previously mixed medias before reuse, so the reused media routes packets through normal forwarding instead of the mix buffer. Usesmedia_subscriptions.head(not.length) as the reuse guard sincei_queue_deletedoesn't update the length field.media->index— so thatmonologue_subscribe_answerresolves the answer SDP's first m=audio line to the active mixed destination regardless of which old media index was reuseddst_ml->mediasacross mix/non-mix transitions, sincecall_get_mediaallocates atmedias->len + 1monologue_unsubscribe()already removes all subscriptions andaudio_player_freeis called viacall_media_freeBug fixes
__ssrc_handler_decode_new()now appliesdefault_clockrate_factto compute the actual sample rate (G722: RTP clock 8000 → actual 16000 Hz). Without this, the mix buffer was initialized at the correct sample rate but received decoded audio at half rate, producing scrambled output.monologue_stop()now uses targetedcodec_handlers_stop(sink=media)per subscriber media instead of callingmedia_stop()on subscriber medias. The old approach tore down ALL codec handlers on the subscriber (including unrelated audio_player handlers for mix subscriptions), causing the tap to go silent when a connected leg was deleted.Auto-all dynamic subscriptions
A new
auto-allflag enables dynamic subscription updates. When set alongsideall+mix, the subscriber monologue is marked withML_FLAG_AUTO_ALL. Whendialogue_connect()is called (adding new legs to the call),__update_auto_all_subscribers()scans for auto-all monologues and incrementally adds any new audio sources to their mix subscriptions, preserving the original egress mode and RTCP mirror configuration.MEDIA_FLAG_MIX bit allocation
MEDIA_FLAG_MIXuses bit 41, not bit 39, to avoid collision withSHARED_FLAG_EXTMAP_SHORT(also bit 39).media_update_flags()callsbf_copy_same()withSHARED_FLAG_EXTMAP_SHORT, which would silently clear a MIX flag at the same bit position during subscribe answer processing.Changed files
include/call_interfaces.hmix:1andauto_all:1bitfields tosdp_ng_flagsinclude/call.hMEDIA_FLAG_MIX(bit 41) andML_FLAG_AUTO_ALL(bit 29)daemon/call_interfaces.cmixandauto-allflags; set/clearML_FLAG_AUTO_ALL; iterate all subscriptions in response builderdaemon/call.cmonologue_subscribe_request_mix(); fixmonologue_subscribe_answer()for multi-sub; audio_player deactivation on mix→non-mix; stale media retirement + array compaction on non-mix→mix;headguard inmonologue_subscribe_request1(); targetedcodec_handlers_stopinmonologue_stop();__update_auto_all_subscribers()called fromdialogue_connect()daemon/codec.cdefault_clockrate_factto decoder output formatdocs/ng_control_protocol.mdmixandauto-allflagst/auto-daemon-tests-pubsub.plTests
Twelve new test cases in
auto-daemon-tests-pubsub.pl:from-tags: [A, B], verifies singlem=audiooutput, bidirectional traffic, subscriber can't send backflags: [all, mix]to-tag, verifies singlem=audiois preserved (no media leak)to-tag, verifies transition to two separatem=audiolines with correct independent RTP routingto-tag, verifies collapse to singlem=audioand correct subscribe answerfrom-tagsorder, forcing the mix path to pick the higher-indexed destination media and creating a leading NULL hole in the array; verifies compaction makes subscribe answer succeedclockrate_fact(8000→16000) is handled correctlyall+auto-all, then connect a new dialogue; verifies new legs are dynamically added to subscriber subscriptions (confirmed viaquerybefore re-subscribe to prove incremental update, not rebuild)🤖 Generated with Claude Code