@@ -131,17 +131,13 @@ impl GraphDump {
131131 . count ( )
132132 }
133133
134- fn ports ( & self , node : u32 , direction : & str ) -> Vec < u32 > {
135- let mut ports: Vec < _ > = self
136- . objects ( "PipeWire:Interface:Port" )
137- . filter ( |object| {
138- json_u64 ( object, "/info/props/node.id" ) == Some ( u64:: from ( node) )
139- && json_str ( object, "/info/props/port.direction" ) == Some ( direction)
140- } )
141- . filter_map ( |object| json_u64 ( object, "/id" ) ?. try_into ( ) . ok ( ) )
142- . collect ( ) ;
143- ports. sort_unstable ( ) ;
144- ports
134+ fn port ( & self , node : u32 , direction : & str , channel : & str ) -> Option < u32 > {
135+ let port = self . objects ( "PipeWire:Interface:Port" ) . find ( |object| {
136+ json_u64 ( object, "/info/props/node.id" ) == Some ( u64:: from ( node) )
137+ && json_str ( object, "/info/props/port.direction" ) == Some ( direction)
138+ && json_str ( object, "/info/props/audio.channel" ) == Some ( channel)
139+ } ) ?;
140+ json_u64 ( port, "/id" ) ?. try_into ( ) . ok ( )
145141 }
146142
147143 fn inactive ( & self , name : & str ) -> bool {
@@ -318,13 +314,25 @@ impl IsolatedPipeWire {
318314 )
319315 }
320316
321- fn link_nodes ( & self , output_node : u32 , input_node : u32 , channels : usize , passive : bool ) {
322- let ( outputs, inputs) = self . wait_dump ( "test fixture ports" , |graph| {
323- let outputs = graph. ports ( output_node, "out" ) ;
324- let inputs = graph. ports ( input_node, "in" ) ;
325- ( outputs. len ( ) >= channels && inputs. len ( ) >= channels) . then_some ( ( outputs, inputs) )
317+ fn link_nodes (
318+ & self ,
319+ output_node : u32 ,
320+ input_node : u32 ,
321+ channels : & [ ( & str , & str ) ] ,
322+ passive : bool ,
323+ ) {
324+ let ports: Vec < _ > = self . wait_dump ( "test fixture ports" , |graph| {
325+ channels
326+ . iter ( )
327+ . map ( |( output, input) | {
328+ Some ( (
329+ graph. port ( output_node, "out" , output) ?,
330+ graph. port ( input_node, "in" , input) ?,
331+ ) )
332+ } )
333+ . collect :: < Option < Vec < _ > > > ( )
326334 } ) ;
327- for ( output, input) in outputs . into_iter ( ) . zip ( inputs ) . take ( channels ) {
335+ for ( output, input) in ports {
328336 let mut command = self . client_command ( "pw-link" ) ;
329337 command. arg ( "--wait" ) ;
330338 if passive {
@@ -352,14 +360,15 @@ struct ApplicationFixture {
352360
353361impl ApplicationFixture {
354362 fn active ( server : & IsolatedPipeWire , name : & str ) -> Self {
355- Self :: active_with_layout ( server, name, 2 , "[ FL, FR ]" )
363+ Self :: active_with_layout ( server, name, 2 , "[ FL, FR ]" , [ "FL" , "FR" ] )
356364 }
357365
358366 fn active_with_layout (
359367 server : & IsolatedPipeWire ,
360368 name : & str ,
361369 channels : usize ,
362370 channel_map : & str ,
371+ signal_targets : [ & str ; 2 ] ,
363372 ) -> Self {
364373 let target = format ! ( "{name}.sink" ) ;
365374 let capture_props = format ! (
@@ -381,19 +390,13 @@ impl ApplicationFixture {
381390 let capture = server. wait_dump ( "application capture node" , |graph| {
382391 graph. node_id ( & format ! ( "{name}.capture" ) )
383392 } ) ;
384- let signal_channels = channels. min ( 2 ) ;
385- let signal_map = if signal_channels == 1 {
386- "[ MONO ]"
387- } else {
388- "[ FL, FR ]"
389- } ;
390- let signal = server. test_source (
391- & format ! ( "{name}.signal" ) ,
392- "Audio/Source" ,
393- signal_channels,
394- signal_map,
393+ let signal = server. test_source ( & format ! ( "{name}.signal" ) , "Audio/Source" , 2 , "[ FL, FR ]" ) ;
394+ server. link_nodes (
395+ signal. id ,
396+ capture,
397+ & [ ( "FL" , signal_targets[ 0 ] ) , ( "FR" , signal_targets[ 1 ] ) ] ,
398+ false ,
395399 ) ;
396- server. link_nodes ( signal. id , capture, signal_channels, false ) ;
397400 Self {
398401 _source : source,
399402 _signal : signal,
@@ -468,7 +471,7 @@ fn wide_device(server: &IsolatedPipeWire, name: &str) -> (Process, Process, Ling
468471 graph. node_id ( & format ! ( "{source_name}.capture" ) )
469472 } ) ;
470473 let signal = server. test_source ( & format ! ( "{name}.tone" ) , "Audio/Source" , 2 , "[ FL, FR ]" ) ;
471- server. link_nodes ( signal. id , capture, 2 , false ) ;
474+ server. link_nodes ( signal. id , capture, & [ ( "FL" , "FL" ) , ( "FR" , "FR" ) ] , false ) ;
472475 ( loopback, source, signal)
473476}
474477
@@ -503,6 +506,7 @@ fn wait_for_mapped_signal(
503506 description : & str ,
504507 audio : & mut AudioReader ,
505508 positions : [ ChannelPosition ; MAX_CAPTURE_CHANNELS ] ,
509+ active : & [ usize ] ,
506510) {
507511 wait_for ( description, || {
508512 let mut captured = false ;
@@ -519,8 +523,13 @@ fn wait_for_mapped_signal(
519523 * peak = peak. max ( sample. abs ( ) ) ;
520524 }
521525 }
522- captured |= peaks[ ..2 ] . iter ( ) . all ( |peak| * peak > 0.01 )
523- && peaks[ 2 ..] . iter ( ) . all ( |peak| * peak < 0.001 ) ;
526+ captured |= peaks. iter ( ) . enumerate ( ) . all ( |( channel, & peak) | {
527+ if active. contains ( & channel) {
528+ peak > 0.01
529+ } else {
530+ peak < 0.001
531+ }
532+ } ) ;
524533 } ) ;
525534 captured. then_some ( ( ) )
526535 } ) ;
@@ -551,6 +560,7 @@ fn live_backend_recovers_after_server_restart() {
551560 "initial recovery PCM" ,
552561 & mut audio,
553562 ChannelPosition :: SURROUND ,
563+ & [ 0 , 1 ] ,
554564 ) ;
555565
556566 server. stop ( ) ;
@@ -574,6 +584,7 @@ fn live_backend_recovers_after_server_restart() {
574584 "recovered application PCM" ,
575585 & mut audio,
576586 ChannelPosition :: SURROUND ,
587+ & [ 0 , 1 ] ,
577588 ) ;
578589
579590 backend. shutdown ( ) ;
@@ -626,39 +637,49 @@ fn live_capture_preserves_graph_invariants() {
626637 "captured application PCM" ,
627638 & mut audio,
628639 ChannelPosition :: SURROUND ,
640+ & [ 0 , 1 ] ,
629641 ) ;
630642
631- let surround_name = format ! ( "openmeters-live-surround-{}" , std:: process:: id( ) ) ;
632- let surround_playback = format ! ( "{surround_name}.playback" ) ;
633- let surround = ApplicationFixture :: active_with_layout (
634- & server,
635- & surround_name,
636- 6 ,
637- "[ FL, FR, FC, LFE, RL, RR ]" ,
638- ) ;
639- server. wait_audio ( "surround application mix" , & mut audio, |graph| {
640- let tap = graph. node ( & tap_name) ?;
641- let source = graph. node_id ( & surround_playback) ?;
642- ( json_u64 ( tap, "/id" ) == Some ( u64:: from ( tap_id) )
643- && graph. link_count ( source, tap_id) == 6
644- && graph. link_count ( source_id, tap_id) == 2
645- && json_u64 ( tap, "/info/params/Format/0/channels" ) == Some ( MAX_CAPTURE_CHANNELS as u64 ) )
646- . then_some ( ( ) )
647- } ) ;
648- wait_for_mapped_signal (
649- "captured surround application mix" ,
650- & mut audio,
651- ChannelPosition :: SURROUND ,
652- ) ;
653- drop ( surround) ;
654- server. wait_audio ( "stable application mix" , & mut audio, |graph| {
655- let tap = graph. node ( & tap_name) ?;
656- ( json_u64 ( tap, "/id" ) == Some ( u64:: from ( tap_id) )
657- && graph. node ( & surround_playback) . is_none ( )
658- && graph. link_count ( source_id, tap_id) == 2
659- && json_u64 ( tap, "/info/params/Format/0/channels" ) == Some ( MAX_CAPTURE_CHANNELS as u64 ) )
660- . then_some ( ( ) )
661- } ) ;
643+ for ( channels, channel_map, active_channels) in [
644+ ( 6 , "[ RL, RR, FL, FR, FC, LFE ]" , [ 0 , 1 , 6 , 7 ] ) ,
645+ ( 8 , "[ RL, RR, FL, FR, FC, LFE, SL, SR ]" , [ 0 , 1 , 4 , 5 ] ) ,
646+ ] {
647+ let surround_name = format ! ( "openmeters-live-surround-{channels}-{}" , std:: process:: id( ) ) ;
648+ let surround_playback = format ! ( "{surround_name}.playback" ) ;
649+ let surround = ApplicationFixture :: active_with_layout (
650+ & server,
651+ & surround_name,
652+ channels,
653+ channel_map,
654+ [ "RL" , "RR" ] ,
655+ ) ;
656+ server. wait_audio ( "surround application mix" , & mut audio, |graph| {
657+ let tap = graph. node ( & tap_name) ?;
658+ let source = graph. node_id ( & surround_playback) ?;
659+ ( json_u64 ( tap, "/id" ) == Some ( u64:: from ( tap_id) )
660+ && graph. link_count ( source, tap_id) == channels
661+ && graph. link_count ( source_id, tap_id) == 2
662+ && json_u64 ( tap, "/info/params/Format/0/channels" )
663+ == Some ( MAX_CAPTURE_CHANNELS as u64 ) )
664+ . then_some ( ( ) )
665+ } ) ;
666+ wait_for_mapped_signal (
667+ "captured surround application mix" ,
668+ & mut audio,
669+ ChannelPosition :: SURROUND ,
670+ & active_channels,
671+ ) ;
672+ drop ( surround) ;
673+ server. wait_audio ( "stable application mix" , & mut audio, |graph| {
674+ let tap = graph. node ( & tap_name) ?;
675+ ( json_u64 ( tap, "/id" ) == Some ( u64:: from ( tap_id) )
676+ && graph. node ( & surround_playback) . is_none ( )
677+ && graph. link_count ( source_id, tap_id) == 2
678+ && json_u64 ( tap, "/info/params/Format/0/channels" )
679+ == Some ( MAX_CAPTURE_CHANNELS as u64 ) )
680+ . then_some ( ( ) )
681+ } ) ;
682+ }
662683
663684 let mut disabled = HashSet :: new ( ) ;
664685 disabled. insert ( identity) ;
@@ -702,7 +723,12 @@ fn live_capture_preserves_graph_invariants() {
702723 let paused_source_id = server. wait_dump ( "paused application" , |graph| {
703724 graph. node_id ( & paused_playback)
704725 } ) ;
705- server. link_nodes ( paused_source_id, target_id, 2 , true ) ;
726+ server. link_nodes (
727+ paused_source_id,
728+ target_id,
729+ & [ ( "FL" , "FL" ) , ( "FR" , "FR" ) ] ,
730+ true ,
731+ ) ;
706732 server. wait_audio ( "passive tap of paused route" , & mut audio, |graph| {
707733 ( graph. link_count ( paused_source_id, target_id) == 2
708734 && graph. link_count ( paused_source_id, tap_id) == 2
@@ -746,7 +772,7 @@ fn live_capture_preserves_graph_invariants() {
746772 && json_u64 ( tap, "/info/params/Format/0/channels" ) == Some ( MAX_CAPTURE_CHANNELS as u64 ) )
747773 . then_some ( ( ) )
748774 } ) ;
749- wait_for_mapped_signal ( "captured device PCM" , & mut audio, WIDE_POSITIONS ) ;
775+ wait_for_mapped_signal ( "captured device PCM" , & mut audio, WIDE_POSITIONS , & [ 0 , 1 ] ) ;
750776
751777 assert ! ( control. configure( CaptureConfig {
752778 mode: CaptureMode :: Device ,
0 commit comments