use oxideav_rtmp::flv::{
build_audio, build_video, parse_audio, parse_video, AudioTag, Multitrack, MultitrackTrack,
VideoTag, AUDIO_PACKET_TYPE_CODED_FRAMES, AUDIO_PACKET_TYPE_SEQUENCE_START,
AV_MULTITRACK_TYPE_MANY_TRACKS, AV_MULTITRACK_TYPE_MANY_TRACKS_MANY_CODECS,
AV_MULTITRACK_TYPE_ONE_TRACK, EX_PACKET_TYPE_CODED_FRAMES, EX_PACKET_TYPE_SEQUENCE_START,
FOURCC_AAC, FOURCC_AV1, FOURCC_AVC, FOURCC_FLAC, FOURCC_HEVC, FOURCC_OPUS, FOURCC_VVC,
VIDEO_FRAME_INTER, VIDEO_FRAME_KEYFRAME,
};
#[test]
fn video_one_track_codedframes_avc_matches_spec_wire_layout() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_ONE_TRACK,
tracks: vec![MultitrackTrack {
fourcc: None,
track_id: 0,
body: b"\x00\x00\x00\x04NALU".to_vec(),
}],
};
let tag = VideoTag::multitrack_tag(
VIDEO_FRAME_KEYFRAME,
EX_PACKET_TYPE_CODED_FRAMES,
Some(FOURCC_AVC),
mt,
);
let wire = build_video(&tag);
assert_eq!(
wire,
vec![
0x96, 0x01, b'a', b'v', b'c', b'1', 0x00, 0x00, 0x00, 0x00, 0x04, b'N', b'A', b'L',
b'U',
]
);
let back = parse_video(&wire).expect("parse");
assert!(back.is_multitrack());
let mt_back = back.multitrack.as_ref().unwrap();
assert_eq!(mt_back.multitrack_type, AV_MULTITRACK_TYPE_ONE_TRACK);
assert_eq!(mt_back.tracks.len(), 1);
assert_eq!(mt_back.tracks[0].track_id, 0);
assert_eq!(mt_back.tracks[0].fourcc, None);
assert_eq!(mt_back.tracks[0].body, b"\x00\x00\x00\x04NALU".to_vec());
assert_eq!(back.fourcc, Some(FOURCC_AVC));
assert_eq!(back.ex_packet_type, Some(EX_PACKET_TYPE_CODED_FRAMES));
}
#[test]
fn video_many_tracks_hevc_two_tracks_round_trip() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS,
tracks: vec![
MultitrackTrack {
fourcc: None,
track_id: 0,
body: vec![0x10; 32],
},
MultitrackTrack {
fourcc: None,
track_id: 1,
body: vec![0x20; 64],
},
],
};
let tag = VideoTag::multitrack_tag(
VIDEO_FRAME_INTER,
EX_PACKET_TYPE_CODED_FRAMES,
Some(FOURCC_HEVC),
mt,
);
let wire = build_video(&tag);
let back = parse_video(&wire).expect("parse");
let mt_back = back.multitrack.as_ref().unwrap();
assert_eq!(mt_back.tracks.len(), 2);
assert_eq!(mt_back.tracks[0].body.len(), 32);
assert_eq!(mt_back.tracks[1].body.len(), 64);
assert_eq!(back, tag);
}
#[test]
fn video_many_tracks_many_codecs_round_trip_with_hevc_and_av1() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS_MANY_CODECS,
tracks: vec![
MultitrackTrack {
fourcc: Some(FOURCC_HEVC),
track_id: 0,
body: b"hevc-coded-bytes".to_vec(),
},
MultitrackTrack {
fourcc: Some(FOURCC_AV1),
track_id: 1,
body: b"av1-obu-bytes".to_vec(),
},
],
};
let tag = VideoTag::multitrack_tag(
VIDEO_FRAME_KEYFRAME,
EX_PACKET_TYPE_CODED_FRAMES,
None,
mt.clone(),
);
assert_eq!(tag.fourcc, None);
let wire = build_video(&tag);
assert_eq!(wire[1], 0x21);
let back = parse_video(&wire).expect("parse");
assert_eq!(back.fourcc, None);
let mt_back = back.multitrack.as_ref().unwrap();
assert_eq!(mt_back, &mt);
}
#[test]
fn video_sequence_start_many_tracks_vvc_round_trip() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS,
tracks: vec![
MultitrackTrack {
fourcc: None,
track_id: 0,
body: b"vvcc-default".to_vec(),
},
MultitrackTrack {
fourcc: None,
track_id: 1,
body: b"vvcc-alt".to_vec(),
},
],
};
let tag = VideoTag::multitrack_tag(
VIDEO_FRAME_KEYFRAME,
EX_PACKET_TYPE_SEQUENCE_START,
Some(FOURCC_VVC),
mt,
);
let wire = build_video(&tag);
assert_eq!(wire[1], 0x10);
let back = parse_video(&wire).expect("parse");
assert_eq!(back, tag);
assert_eq!(back.ex_packet_type, Some(EX_PACKET_TYPE_SEQUENCE_START));
}
#[test]
fn video_parse_rejects_inner_packet_type_multitrack() {
let wire = [0x96u8, 0x06, b'h', b'v', b'c', b'1', 0x00];
let err = parse_video(&wire).unwrap_err();
assert!(format!("{err:?}").contains("MUST NOT"));
}
#[test]
fn audio_one_track_opus_codedframes_round_trip() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_ONE_TRACK,
tracks: vec![MultitrackTrack {
fourcc: None,
track_id: 0,
body: b"opus-self-delimited-frames".to_vec(),
}],
};
let tag = AudioTag::multitrack_tag(
AUDIO_PACKET_TYPE_CODED_FRAMES,
Some(FOURCC_OPUS),
mt.clone(),
);
let wire = build_audio(&tag);
let back = parse_audio(&wire).expect("parse");
assert!(back.is_multitrack());
assert_eq!(back.audio_fourcc, Some(FOURCC_OPUS));
assert_eq!(back.ex_packet_type, Some(AUDIO_PACKET_TYPE_CODED_FRAMES));
assert_eq!(back.multitrack.as_ref().unwrap(), &mt);
}
#[test]
fn audio_many_tracks_aac_two_tracks_round_trip() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS,
tracks: vec![
MultitrackTrack {
fourcc: None,
track_id: 0,
body: vec![0xAA; 96],
},
MultitrackTrack {
fourcc: None,
track_id: 1,
body: vec![0xBB; 128],
},
],
};
let tag =
AudioTag::multitrack_tag(AUDIO_PACKET_TYPE_CODED_FRAMES, Some(FOURCC_AAC), mt.clone());
let wire = build_audio(&tag);
assert_eq!(wire[1], 0x11);
let back = parse_audio(&wire).expect("parse");
assert_eq!(back.multitrack.as_ref().unwrap(), &mt);
}
#[test]
fn audio_many_tracks_many_codecs_opus_flac_round_trip() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS_MANY_CODECS,
tracks: vec![
MultitrackTrack {
fourcc: Some(FOURCC_OPUS),
track_id: 0,
body: b"primary-opus".to_vec(),
},
MultitrackTrack {
fourcc: Some(FOURCC_FLAC),
track_id: 1,
body: b"localization-flac".to_vec(),
},
],
};
let tag = AudioTag::multitrack_tag(AUDIO_PACKET_TYPE_CODED_FRAMES, None, mt.clone());
assert_eq!(tag.audio_fourcc, None);
let wire = build_audio(&tag);
assert_eq!(wire[1], 0x21);
let back = parse_audio(&wire).expect("parse");
assert_eq!(back.audio_fourcc, None);
assert_eq!(back.multitrack.as_ref().unwrap(), &mt);
}
#[test]
fn audio_sequence_start_many_tracks_aac_round_trip() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS,
tracks: vec![
MultitrackTrack {
fourcc: None,
track_id: 0,
body: vec![0x12, 0x10], },
MultitrackTrack {
fourcc: None,
track_id: 1,
body: vec![0x12, 0x08], },
],
};
let tag = AudioTag::multitrack_tag(
AUDIO_PACKET_TYPE_SEQUENCE_START,
Some(FOURCC_AAC),
mt.clone(),
);
let wire = build_audio(&tag);
assert_eq!(wire[0], 0x95);
assert_eq!(wire[1], 0x10);
let back = parse_audio(&wire).expect("parse");
assert_eq!(back.multitrack.as_ref().unwrap(), &mt);
assert_eq!(back.ex_packet_type, Some(AUDIO_PACKET_TYPE_SEQUENCE_START));
}
#[test]
fn audio_parse_rejects_inner_packet_type_multitrack() {
let wire = [0x95u8, 0x05, b'O', b'p', b'u', b's', 0x00];
let err = parse_audio(&wire).unwrap_err();
assert!(format!("{err:?}").contains("MUST NOT"));
}
#[test]
fn track_ordering_preserved_across_round_trip() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS,
tracks: vec![
MultitrackTrack {
fourcc: None,
track_id: 7,
body: b"first".to_vec(),
},
MultitrackTrack {
fourcc: None,
track_id: 0,
body: b"second".to_vec(),
},
MultitrackTrack {
fourcc: None,
track_id: 3,
body: b"third".to_vec(),
},
],
};
let tag = VideoTag::multitrack_tag(
VIDEO_FRAME_KEYFRAME,
EX_PACKET_TYPE_CODED_FRAMES,
Some(FOURCC_HEVC),
mt.clone(),
);
let wire = build_video(&tag);
let back = parse_video(&wire).expect("parse");
let tracks = &back.multitrack.unwrap().tracks;
assert_eq!(tracks.len(), 3);
assert_eq!(tracks[0].track_id, 7);
assert_eq!(tracks[1].track_id, 0);
assert_eq!(tracks[2].track_id, 3);
assert_eq!(tracks[0].body, b"first");
assert_eq!(tracks[1].body, b"second");
assert_eq!(tracks[2].body, b"third");
}
#[test]
fn empty_track_body_round_trips() {
let mt = Multitrack {
multitrack_type: AV_MULTITRACK_TYPE_MANY_TRACKS,
tracks: vec![
MultitrackTrack {
fourcc: None,
track_id: 0,
body: Vec::new(),
},
MultitrackTrack {
fourcc: None,
track_id: 1,
body: b"payload".to_vec(),
},
],
};
let tag = VideoTag::multitrack_tag(
VIDEO_FRAME_INTER,
EX_PACKET_TYPE_CODED_FRAMES,
Some(FOURCC_HEVC),
mt.clone(),
);
let wire = build_video(&tag);
let back = parse_video(&wire).expect("parse");
assert_eq!(back.multitrack.as_ref().unwrap(), &mt);
}