11use std:: fmt;
22
33use anyhow:: Context ;
4+ use cadeau:: xmf:: vpx:: VpxCodec ;
45use webm_iterable:: matroska_spec:: { Block , Master , MatroskaSpec , SimpleBlock } ;
56
67#[ derive( Clone ) ]
@@ -35,7 +36,7 @@ impl fmt::Debug for VideoBlock {
3536}
3637
3738impl VideoBlock {
38- pub ( crate ) fn new ( tag : MatroskaSpec , cluster_timestamp : Option < u64 > ) -> anyhow:: Result < Self > {
39+ pub ( crate ) fn new ( tag : MatroskaSpec , cluster_timestamp : Option < u64 > , codec : VpxCodec ) -> anyhow:: Result < Self > {
3940 let result = match tag {
4041 MatroskaSpec :: BlockGroup ( Master :: Full ( children) ) => {
4142 let block = children
@@ -51,7 +52,10 @@ impl VideoBlock {
5152
5253 let block = Block :: try_from ( block) ?;
5354 let timestamp = block. timestamp ;
54- let is_key_frame = block. read_frame_data ( ) ?. iter ( ) . any ( |frame| is_key_frame ( frame. data ) ) ;
55+ let is_key_frame = block
56+ . read_frame_data ( ) ?
57+ . iter ( )
58+ . any ( |frame| is_vpx_key_frame ( frame. data , codec) ) ;
5559
5660 Self {
5761 cluster_timestamp,
@@ -121,9 +125,144 @@ impl VideoBlock {
121125 }
122126}
123127
124- fn is_key_frame ( buffer : & [ u8 ] ) -> bool {
128+ pub ( crate ) fn is_vpx_key_frame ( buffer : & [ u8 ] , codec : VpxCodec ) -> bool {
129+ match codec {
130+ VpxCodec :: VP8 => is_vp8_key_frame ( buffer) ,
131+ VpxCodec :: VP9 => is_vp9_key_frame ( buffer) ,
132+ }
133+ }
134+
135+ /// VP8 keyframe detection.
136+ ///
137+ /// RFC 6386 Section 9.1 "Uncompressed Data Chunk":
138+ /// https://datatracker.ietf.org/doc/html/rfc6386#section-9.1
139+ ///
140+ /// First byte layout (LSB-first bitstream):
141+ /// bit 0: frame_type (0 = key frame, 1 = inter frame)
142+ /// bits 1-2: version
143+ /// bit 3: show_frame
144+ /// bits 4-7: first_part_size (bits 0-3)
145+ ///
146+ /// We only need bit 0: `buffer[0] & 0x1 == 0` means keyframe.
147+ fn is_vp8_key_frame ( buffer : & [ u8 ] ) -> bool {
125148 if buffer. is_empty ( ) {
126149 return false ;
127150 }
128151 buffer[ 0 ] & 0x1 == 0
129152}
153+
154+ /// VP9 keyframe detection.
155+ ///
156+ /// VP9 Bitstream & Decoding Process Specification v0.6, Section 6.2 "Uncompressed header syntax":
157+ /// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf
158+ ///
159+ /// Unlike VP8 which uses a LSB-first bitstream, VP9 uses a MSB-first bitstream.
160+ /// The first byte layout depends on the profile:
161+ ///
162+ /// For profiles 0-2 (first byte, MSB to LSB):
163+ /// bits 7-6: frame_marker (must be 0b10 to identify VP9)
164+ /// bit 5: profile_low_bit
165+ /// bit 4: profile_high_bit
166+ /// bit 3: show_existing_frame (if 1, frame is a reference to an already-decoded frame, not a keyframe)
167+ /// bit 2: frame_type (0 = KEY_FRAME, 1 = NON_KEY_FRAME)
168+ /// bits 1-0: (remaining header fields)
169+ ///
170+ /// For profile 3 (first byte, MSB to LSB):
171+ /// bits 7-6: frame_marker (must be 0b10)
172+ /// bit 5: profile_low_bit (1)
173+ /// bit 4: profile_high_bit (1)
174+ /// bit 3: reserved_zero
175+ /// bit 2: show_existing_frame
176+ /// bit 1: frame_type (0 = KEY_FRAME, 1 = NON_KEY_FRAME)
177+ /// bit 0: (remaining header fields)
178+ ///
179+ /// Profile 3 has an extra reserved_zero bit after the profile bits, which shifts
180+ /// show_existing_frame and frame_type one position to the right.
181+ ///
182+ /// Note: the profile is encoded with swapped bit order: `profile = (high_bit << 1) | low_bit`,
183+ /// i.e. `profile = (bit4 << 1) | bit5`.
184+ ///
185+ /// A frame is a keyframe when: show_existing_frame == 0 AND frame_type == 0.
186+ pub ( crate ) fn is_vp9_key_frame ( buffer : & [ u8 ] ) -> bool {
187+ if buffer. is_empty ( ) {
188+ return false ;
189+ }
190+ let b0 = buffer[ 0 ] ;
191+
192+ // Validate frame_marker (bits 7-6) is 0b10
193+ if ( b0 >> 6 ) != 0b10 {
194+ return false ;
195+ }
196+
197+ // profile = (high_bit << 1) | low_bit = (bit4 << 1) | bit5
198+ let profile = ( ( ( b0 >> 4 ) & 1 ) << 1 ) | ( ( b0 >> 5 ) & 1 ) ;
199+
200+ if profile == 3 {
201+ // Profile 3: show_existing_frame is bit 2, frame_type is bit 1
202+ ( b0 & 0x04 ) == 0 && ( b0 & 0x02 ) == 0
203+ } else {
204+ // Profiles 0-2: show_existing_frame is bit 3, frame_type is bit 2
205+ ( b0 & 0x08 ) == 0 && ( b0 & 0x04 ) == 0
206+ }
207+ }
208+
209+ #[ cfg( test) ]
210+ mod tests {
211+ use super :: is_vp9_key_frame;
212+
213+ #[ test]
214+ fn vp9_empty_buffer_is_not_keyframe ( ) {
215+ assert ! ( !is_vp9_key_frame( & [ ] ) ) ;
216+ }
217+
218+ #[ test]
219+ fn vp9_marker_mismatch_is_not_keyframe ( ) {
220+ // frame_marker (bits 7-6) != 0b10 → rejected even if other bits look like keyframe.
221+ // 0x00 → frame_marker = 0b00
222+ assert ! ( !is_vp9_key_frame( & [ 0x00 ] ) ) ;
223+ }
224+
225+ #[ test]
226+ fn vp9_profiles_0_to_2_keyframe_detected ( ) {
227+ // Profile 0: frame_marker=0b10, profile_low=0, profile_high=0,
228+ // show_existing_frame(bit3)=0, frame_type(bit2)=0.
229+ // 0b1000_0000 = 0x80
230+ assert ! ( is_vp9_key_frame( & [ 0x80 ] ) ) ;
231+ }
232+
233+ #[ test]
234+ fn vp9_profiles_0_to_2_show_existing_frame_is_not_keyframe ( ) {
235+ // Profile 0: show_existing_frame(bit3)=1, frame_type(bit2)=0.
236+ // 0b1000_1000 = 0x88
237+ assert ! ( !is_vp9_key_frame( & [ 0x88 ] ) ) ;
238+ }
239+
240+ #[ test]
241+ fn vp9_profiles_0_to_2_inter_frame_is_not_keyframe ( ) {
242+ // Profile 0: show_existing_frame(bit3)=0, frame_type(bit2)=1.
243+ // 0b1000_0100 = 0x84
244+ assert ! ( !is_vp9_key_frame( & [ 0x84 ] ) ) ;
245+ }
246+
247+ #[ test]
248+ fn vp9_profile_3_keyframe_detected ( ) {
249+ // Profile 3: frame_marker=0b10, profile_low(bit5)=1, profile_high(bit4)=1,
250+ // reserved_zero(bit3)=0, show_existing_frame(bit2)=0, frame_type(bit1)=0.
251+ // 0b1011_0000 = 0xB0
252+ assert ! ( is_vp9_key_frame( & [ 0xB0 ] ) ) ;
253+ }
254+
255+ #[ test]
256+ fn vp9_profile_3_show_existing_frame_is_not_keyframe ( ) {
257+ // Profile 3: show_existing_frame(bit2)=1, frame_type(bit1)=0.
258+ // 0b1011_0100 = 0xB4
259+ assert ! ( !is_vp9_key_frame( & [ 0xB4 ] ) ) ;
260+ }
261+
262+ #[ test]
263+ fn vp9_profile_3_inter_frame_is_not_keyframe ( ) {
264+ // Profile 3: show_existing_frame(bit2)=0, frame_type(bit1)=1.
265+ // 0b1011_0010 = 0xB2
266+ assert ! ( !is_vp9_key_frame( & [ 0xB2 ] ) ) ;
267+ }
268+ }
0 commit comments