@@ -926,6 +926,106 @@ fn video_publish_consume() {
926926 assert_eq ! ( moq_origin_close( origin) , 0 ) ;
927927}
928928
929+ /// End-to-end native decode: publish real H.264 (encoded by moq-video) and
930+ /// consume it through `moq_consume_video_raw`, asserting decoded I420 frames.
931+ #[ test]
932+ fn video_raw_decode ( ) {
933+ // Encode a few gray frames to Annex-B (avc3, SPS/PPS inline on the keyframe).
934+ let mut config = moq_video:: encode:: Config :: new ( 320 , 240 , 30 ) ;
935+ config. kind = moq_video:: encode:: Kind :: Software ;
936+ let mut encoder = moq_video:: encode:: Encoder :: new ( & config) . expect ( "openh264 encoder" ) ;
937+ let gray = vec ! [ 0x80u8 ; 320 * 240 * 4 ] ;
938+ let mut frames: Vec < bytes:: Bytes > = Vec :: new ( ) ;
939+ for i in 0 ..5 {
940+ frames. extend ( encoder. encode_rgba ( & gray, 320 , 240 , i == 0 ) . unwrap ( ) ) ;
941+ }
942+ frames. extend ( encoder. finish ( ) . unwrap ( ) ) ;
943+ assert ! ( !frames. is_empty( ) , "encoder produced no frames" ) ;
944+
945+ let origin = id ( moq_origin_create ( ) ) ;
946+ let broadcast = id ( moq_publish_create ( ) ) ;
947+
948+ // The init's SPS/PPS only seed catalog metadata; avc3 frames carry their own
949+ // inline parameter sets, so the decoder reads the true 320x240 from the wire.
950+ let init = h264_init ( ) ;
951+ let format = b"avc3" ;
952+ let media = id ( unsafe {
953+ moq_publish_media_ordered (
954+ broadcast,
955+ format. as_ptr ( ) as * const c_char ,
956+ format. len ( ) ,
957+ init. as_ptr ( ) ,
958+ init. len ( ) ,
959+ )
960+ } ) ;
961+
962+ let path = b"video-raw-test" ;
963+ let _publish = id ( unsafe { moq_origin_publish ( origin, path. as_ptr ( ) as * const c_char , path. len ( ) , broadcast) } ) ;
964+
965+ let consume = request_broadcast ( origin, path) ;
966+ let catalog_cb = Callback :: new ( ) ;
967+ let catalog_task = id ( unsafe { moq_consume_catalog ( consume, Some ( channel_callback) , catalog_cb. ptr ) } ) ;
968+ let catalog_id = id ( catalog_cb. recv ( ) ) ;
969+
970+ // Subscribe + decode before publishing frames so the keyframe group is delivered.
971+ let output = moq_video_decoder_output { latency_max_ms : 10_000 } ;
972+ let frame_cb = Callback :: new ( ) ;
973+ let consumer = id ( unsafe { moq_consume_video_raw ( catalog_id, 0 , & output, Some ( channel_callback) , frame_cb. ptr ) } ) ;
974+
975+ for ( i, frame) in frames. iter ( ) . enumerate ( ) {
976+ assert_eq ! (
977+ unsafe { moq_publish_media_frame( media, frame. as_ptr( ) , frame. len( ) , ( i as u64 ) * 33_000 ) } ,
978+ 0
979+ ) ;
980+ }
981+
982+ // First decoded frame: packed I420 at the encoder resolution.
983+ let frame_id = id ( frame_cb. recv ( ) ) ;
984+ let mut frame = moq_video_frame {
985+ timestamp_us : 0 ,
986+ width : 0 ,
987+ height : 0 ,
988+ data : std:: ptr:: null ( ) ,
989+ data_size : 0 ,
990+ } ;
991+ assert_eq ! ( unsafe { moq_consume_video_raw_frame( frame_id, & mut frame) } , 0 ) ;
992+ assert_eq ! ( frame. width, 320 ) ;
993+ assert_eq ! ( frame. height, 240 ) ;
994+ assert_eq ! ( frame. data_size, 320 * 240 * 3 / 2 , "tightly-packed I420" ) ;
995+ assert ! ( !frame. data. is_null( ) ) ;
996+
997+ assert_eq ! ( moq_consume_video_raw_frame_free( frame_id) , 0 ) ;
998+ assert_eq ! ( moq_consume_video_raw_close( consumer) , 0 ) ;
999+
1000+ // Drain any other decoded frames already queued, then expect the terminal 0.
1001+ loop {
1002+ let code = frame_cb. recv ( ) ;
1003+ if code > 0 {
1004+ assert_eq ! ( moq_consume_video_raw_frame_free( id( code) ) , 0 ) ;
1005+ } else {
1006+ assert_eq ! ( code, 0 , "raw video close delivers terminal 0" ) ;
1007+ break ;
1008+ }
1009+ }
1010+ assert_eq ! ( moq_consume_catalog_free( catalog_id) , 0 ) ;
1011+ assert_eq ! ( moq_consume_catalog_close( catalog_task) , 0 ) ;
1012+ // The publisher may emit more than one catalog snapshot (e.g. as the track's
1013+ // stats settle), so drain any extra snapshots before the terminal.
1014+ loop {
1015+ let code = catalog_cb. recv ( ) ;
1016+ if code > 0 {
1017+ assert_eq ! ( moq_consume_catalog_free( id( code) ) , 0 ) ;
1018+ } else {
1019+ assert_eq ! ( code, 0 , "catalog close delivers terminal 0" ) ;
1020+ break ;
1021+ }
1022+ }
1023+ assert_eq ! ( moq_consume_close( consume) , 0 ) ;
1024+ assert_eq ! ( moq_publish_media_close( media) , 0 ) ;
1025+ assert_eq ! ( moq_publish_close( broadcast) , 0 ) ;
1026+ assert_eq ! ( moq_origin_close( origin) , 0 ) ;
1027+ }
1028+
9291029#[ test]
9301030fn multiple_frames_ordering ( ) {
9311031 let origin = id ( moq_origin_create ( ) ) ;
0 commit comments