1#![allow(dead_code)]
20
21#[cfg(feature = "alloc")]
22extern crate alloc;
23#[cfg(feature = "alloc")]
24use alloc::{string::String, vec::Vec};
25use core::sync::atomic::{AtomicU64, Ordering};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub struct AviFlags(pub u32);
34
35impl AviFlags {
36 pub const AVIF_HASINDEX: u32 = 0x0000_0010;
38 pub const AVIF_ISINTERLEAVED: u32 = 0x0000_0100;
40 pub const AVIF_MUSTUSEINDEX: u32 = 0x0000_0020;
42 pub const AVIF_COPYRIGHTED: u32 = 0x0002_0000;
44
45 pub(crate) fn has_flag(&self, flag: u32) -> bool {
47 self.0 & flag != 0
48 }
49}
50
51#[derive(Clone, Copy, PartialEq, Eq)]
53pub struct FourCC(pub [u8; 4]);
54
55impl FourCC {
56 pub const RIFF: Self = Self(*b"RIFF");
57 pub const AVI: Self = Self(*b"AVI ");
58 pub const LIST: Self = Self(*b"LIST");
59 pub const AVIH: Self = Self(*b"avih");
60 pub const STRH: Self = Self(*b"strh");
61 pub const STRF: Self = Self(*b"strf");
62 pub const IDX1: Self = Self(*b"idx1");
63 pub const MOVI: Self = Self(*b"movi");
64 pub const HDRL: Self = Self(*b"hdrl");
65 pub const STRL: Self = Self(*b"strl");
66 pub const VIDS: Self = Self(*b"vids");
67 pub const AUDS: Self = Self(*b"auds");
68
69 pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
71 if data.len() < 4 {
72 return None;
73 }
74 Some(Self([data[0], data[1], data[2], data[3]]))
75 }
76}
77
78impl core::fmt::Debug for FourCC {
79 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80 let s: [u8; 4] = self.0;
81 write!(
82 f,
83 "FourCC('{}{}{}{}')",
84 s[0] as char, s[1] as char, s[2] as char, s[3] as char
85 )
86 }
87}
88
89#[derive(Debug, Clone, Copy, Default)]
91pub struct AviMainHeader {
92 pub microseconds_per_frame: u32,
94 pub max_bytes_per_sec: u32,
96 pub padding_granularity: u32,
98 pub flags: AviFlags,
100 pub total_frames: u32,
102 pub initial_frames: u32,
104 pub streams: u32,
106 pub suggested_buffer_size: u32,
108 pub width: u32,
110 pub height: u32,
112}
113
114impl AviMainHeader {
115 pub(crate) fn parse(data: &[u8]) -> Option<Self> {
117 if data.len() < 40 {
118 return None;
119 }
120 Some(Self {
121 microseconds_per_frame: read_u32_le(data, 0),
122 max_bytes_per_sec: read_u32_le(data, 4),
123 padding_granularity: read_u32_le(data, 8),
124 flags: AviFlags(read_u32_le(data, 12)),
125 total_frames: read_u32_le(data, 16),
126 initial_frames: read_u32_le(data, 20),
127 streams: read_u32_le(data, 24),
128 suggested_buffer_size: read_u32_le(data, 28),
129 width: read_u32_le(data, 32),
130 height: read_u32_le(data, 36),
131 })
132 }
133
134 pub(crate) fn frame_rate(&self) -> (u32, u32) {
137 if self.microseconds_per_frame == 0 {
138 return (0, 1);
139 }
140 (1_000_000, self.microseconds_per_frame)
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum StreamType {
148 Video,
150 Audio,
152 Unknown,
154}
155
156#[derive(Debug, Clone, Copy, Default)]
158pub struct AviStreamHeader {
159 pub stream_type: [u8; 4],
161 pub handler: [u8; 4],
163 pub flags: u32,
165 pub priority: u16,
167 pub language: u16,
169 pub initial_frames: u32,
171 pub scale: u32,
173 pub rate: u32,
175 pub start: u32,
177 pub length: u32,
179 pub suggested_buffer_size: u32,
181 pub quality: u32,
183 pub sample_size: u32,
185 pub frame_left: u16,
187 pub frame_top: u16,
189 pub frame_right: u16,
191 pub frame_bottom: u16,
193}
194
195impl AviStreamHeader {
196 pub(crate) fn parse(data: &[u8]) -> Option<Self> {
198 if data.len() < 56 {
199 return None;
200 }
201 let mut stream_type = [0u8; 4];
202 stream_type.copy_from_slice(&data[0..4]);
203 let mut handler = [0u8; 4];
204 handler.copy_from_slice(&data[4..8]);
205 Some(Self {
206 stream_type,
207 handler,
208 flags: read_u32_le(data, 8),
209 priority: read_u16_le(data, 12),
210 language: read_u16_le(data, 14),
211 initial_frames: read_u32_le(data, 16),
212 scale: read_u32_le(data, 20),
213 rate: read_u32_le(data, 24),
214 start: read_u32_le(data, 28),
215 length: read_u32_le(data, 32),
216 suggested_buffer_size: read_u32_le(data, 36),
217 quality: read_u32_le(data, 40),
218 sample_size: read_u32_le(data, 44),
219 frame_left: read_u16_le(data, 48),
220 frame_top: read_u16_le(data, 50),
221 frame_right: read_u16_le(data, 52),
222 frame_bottom: read_u16_le(data, 54),
223 })
224 }
225
226 pub(crate) fn get_stream_type(&self) -> StreamType {
228 if self.stream_type == *b"vids" {
229 StreamType::Video
230 } else if self.stream_type == *b"auds" {
231 StreamType::Audio
232 } else {
233 StreamType::Unknown
234 }
235 }
236
237 pub(crate) fn sample_rate(&self) -> (u32, u32) {
239 if self.scale == 0 {
240 return (0, 1);
241 }
242 (self.rate, self.scale)
243 }
244}
245
246#[derive(Debug, Clone, Copy, Default)]
249pub struct BitmapInfoHeader {
250 pub size: u32,
252 pub width: i32,
254 pub height: i32,
256 pub planes: u16,
258 pub bit_count: u16,
260 pub compression: u32,
262 pub image_size: u32,
264 pub x_pels_per_meter: i32,
266 pub y_pels_per_meter: i32,
268 pub colors_used: u32,
270 pub colors_important: u32,
272}
273
274impl BitmapInfoHeader {
275 pub(crate) fn parse(data: &[u8]) -> Option<Self> {
277 if data.len() < 40 {
278 return None;
279 }
280 Some(Self {
281 size: read_u32_le(data, 0),
282 width: read_i32_le(data, 4),
283 height: read_i32_le(data, 8),
284 planes: read_u16_le(data, 12),
285 bit_count: read_u16_le(data, 14),
286 compression: read_u32_le(data, 16),
287 image_size: read_u32_le(data, 20),
288 x_pels_per_meter: read_i32_le(data, 24),
289 y_pels_per_meter: read_i32_le(data, 28),
290 colors_used: read_u32_le(data, 32),
291 colors_important: read_u32_le(data, 36),
292 })
293 }
294
295 pub(crate) fn is_bottom_up(&self) -> bool {
297 self.height > 0
298 }
299
300 pub(crate) fn abs_height(&self) -> u32 {
302 if self.height < 0 {
303 (-(self.height as i64)) as u32
304 } else {
305 self.height as u32
306 }
307 }
308}
309
310#[derive(Debug, Clone, Copy, Default)]
313pub struct WaveFormatEx {
314 pub format_tag: u16,
316 pub channels: u16,
318 pub samples_per_sec: u32,
320 pub avg_bytes_per_sec: u32,
322 pub block_align: u16,
324 pub bits_per_sample: u16,
326 pub cb_size: u16,
328}
329
330impl WaveFormatEx {
331 pub const WAVE_FORMAT_PCM: u16 = 1;
333
334 pub(crate) fn parse(data: &[u8]) -> Option<Self> {
337 if data.len() < 16 {
338 return None;
339 }
340 let cb_size = if data.len() >= 18 {
341 read_u16_le(data, 16)
342 } else {
343 0
344 };
345 Some(Self {
346 format_tag: read_u16_le(data, 0),
347 channels: read_u16_le(data, 2),
348 samples_per_sec: read_u32_le(data, 4),
349 avg_bytes_per_sec: read_u32_le(data, 8),
350 block_align: read_u16_le(data, 12),
351 bits_per_sample: read_u16_le(data, 14),
352 cb_size,
353 })
354 }
355
356 pub(crate) fn is_pcm(&self) -> bool {
358 self.format_tag == Self::WAVE_FORMAT_PCM
359 }
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub struct AviIndexEntry {
365 pub chunk_id: [u8; 4],
367 pub flags: u32,
369 pub offset: u32,
371 pub size: u32,
373}
374
375impl AviIndexEntry {
376 pub const AVIIF_KEYFRAME: u32 = 0x0000_0010;
378
379 pub(crate) fn parse(data: &[u8]) -> Option<Self> {
381 if data.len() < 16 {
382 return None;
383 }
384 let mut chunk_id = [0u8; 4];
385 chunk_id.copy_from_slice(&data[0..4]);
386 Some(Self {
387 chunk_id,
388 flags: read_u32_le(data, 4),
389 offset: read_u32_le(data, 8),
390 size: read_u32_le(data, 12),
391 })
392 }
393
394 pub(crate) fn is_keyframe(&self) -> bool {
396 self.flags & Self::AVIIF_KEYFRAME != 0
397 }
398
399 pub(crate) fn stream_number(&self) -> u8 {
402 let d0 = self.chunk_id[0].wrapping_sub(b'0');
403 let d1 = self.chunk_id[1].wrapping_sub(b'0');
404 if d0 <= 9 && d1 <= 9 {
405 d0 * 10 + d1
406 } else {
407 0
408 }
409 }
410
411 pub(crate) fn is_video(&self) -> bool {
413 self.chunk_id[2] == b'd' && (self.chunk_id[3] == b'c' || self.chunk_id[3] == b'b')
414 }
415
416 pub(crate) fn is_audio(&self) -> bool {
418 self.chunk_id[2] == b'w' && self.chunk_id[3] == b'b'
419 }
420}
421
422#[cfg(feature = "alloc")]
424#[derive(Debug, Clone)]
425pub struct AviStreamInfo {
426 pub index: u32,
428 pub stream_type: StreamType,
430 pub header: AviStreamHeader,
432 pub video_format: Option<BitmapInfoHeader>,
434 pub audio_format: Option<WaveFormatEx>,
436}
437
438#[cfg(feature = "alloc")]
440#[derive(Debug, Clone)]
441pub struct AviContainer {
442 pub main_header: AviMainHeader,
444 pub streams: Vec<AviStreamInfo>,
446 pub index: Vec<AviIndexEntry>,
448 pub movi_offset: u32,
450 pub file_size: u32,
452}
453
454#[cfg(feature = "alloc")]
455impl AviContainer {
456 pub(crate) fn parse(data: &[u8]) -> Option<Self> {
461 if data.len() < 12 {
463 return None;
464 }
465 let riff = FourCC::from_bytes(data)?;
466 if riff != FourCC::RIFF {
467 return None;
468 }
469 let _file_size = read_u32_le(data, 4);
470 let form = FourCC::from_bytes(&data[8..])?;
471 if form != FourCC::AVI {
472 return None;
473 }
474
475 let mut main_header = AviMainHeader::default();
476 let mut streams = Vec::new();
477 let mut index = Vec::new();
478 let mut movi_offset: u32 = 0;
479 let mut pos: usize = 12;
480
481 while pos + 8 <= data.len() {
483 let chunk_id = FourCC::from_bytes(&data[pos..])?;
484 let chunk_size = read_u32_le(data, pos + 4) as usize;
485 let chunk_data_start = pos + 8;
486 let chunk_data_end = chunk_data_start.saturating_add(chunk_size).min(data.len());
487
488 if chunk_id == FourCC::LIST {
489 if chunk_data_end < chunk_data_start + 4 {
490 pos = aligned_next(chunk_data_end);
491 continue;
492 }
493 let list_type = FourCC::from_bytes(&data[chunk_data_start..])?;
494
495 if list_type == FourCC::HDRL {
496 Self::parse_hdrl(
498 &data[chunk_data_start + 4..chunk_data_end],
499 &mut main_header,
500 &mut streams,
501 );
502 } else if list_type == FourCC::MOVI {
503 movi_offset = (chunk_data_start + 4) as u32;
504 }
505 } else if chunk_id == FourCC::IDX1 {
506 Self::parse_idx1(&data[chunk_data_start..chunk_data_end], &mut index);
508 }
509
510 pos = aligned_next(chunk_data_end);
511 }
512
513 Some(Self {
514 main_header,
515 streams,
516 index,
517 movi_offset,
518 file_size: data.len() as u32,
519 })
520 }
521
522 fn parse_hdrl(data: &[u8], main_header: &mut AviMainHeader, streams: &mut Vec<AviStreamInfo>) {
524 let mut pos: usize = 0;
525 let mut stream_index: u32 = 0;
526
527 while pos + 8 <= data.len() {
528 let chunk_id_opt = FourCC::from_bytes(&data[pos..]);
529 let chunk_id = match chunk_id_opt {
530 Some(id) => id,
531 None => break,
532 };
533 let chunk_size = read_u32_le(data, pos + 4) as usize;
534 let chunk_data_start = pos + 8;
535 let chunk_data_end = chunk_data_start.saturating_add(chunk_size).min(data.len());
536
537 if chunk_id == FourCC::AVIH {
538 if let Some(hdr) = AviMainHeader::parse(&data[chunk_data_start..chunk_data_end]) {
539 *main_header = hdr;
540 }
541 } else if chunk_id == FourCC::LIST {
542 if chunk_data_end >= chunk_data_start + 4 {
544 let list_type = FourCC::from_bytes(&data[chunk_data_start..]);
545 if list_type == Some(FourCC::STRL) {
546 if let Some(info) = Self::parse_strl(
547 &data[chunk_data_start + 4..chunk_data_end],
548 stream_index,
549 ) {
550 streams.push(info);
551 stream_index += 1;
552 }
553 }
554 }
555 }
556
557 pos = aligned_next_rel(chunk_data_end);
559 }
560 }
561
562 fn parse_strl(data: &[u8], stream_index: u32) -> Option<AviStreamInfo> {
564 let mut header: Option<AviStreamHeader> = None;
565 let mut video_format: Option<BitmapInfoHeader> = None;
566 let mut audio_format: Option<WaveFormatEx> = None;
567 let mut pos: usize = 0;
568
569 while pos + 8 <= data.len() {
570 let chunk_id = FourCC::from_bytes(&data[pos..])?;
571 let chunk_size = read_u32_le(data, pos + 4) as usize;
572 let chunk_data_start = pos + 8;
573 let chunk_data_end = chunk_data_start.saturating_add(chunk_size).min(data.len());
574
575 if chunk_id == FourCC::STRH {
576 header = AviStreamHeader::parse(&data[chunk_data_start..chunk_data_end]);
577 } else if chunk_id == FourCC::STRF {
578 if let Some(ref hdr) = header {
579 match hdr.get_stream_type() {
580 StreamType::Video => {
581 video_format =
582 BitmapInfoHeader::parse(&data[chunk_data_start..chunk_data_end]);
583 }
584 StreamType::Audio => {
585 audio_format =
586 WaveFormatEx::parse(&data[chunk_data_start..chunk_data_end]);
587 }
588 StreamType::Unknown => {}
589 }
590 }
591 }
592
593 pos = aligned_next_rel(chunk_data_end);
594 }
595
596 let hdr = header?;
597 let stream_type = hdr.get_stream_type();
598 Some(AviStreamInfo {
599 index: stream_index,
600 stream_type,
601 header: hdr,
602 video_format,
603 audio_format,
604 })
605 }
606
607 fn parse_idx1(data: &[u8], index: &mut Vec<AviIndexEntry>) {
609 let mut pos: usize = 0;
610 while pos + 16 <= data.len() {
611 if let Some(entry) = AviIndexEntry::parse(&data[pos..]) {
612 index.push(entry);
613 }
614 pos += 16;
615 }
616 }
617
618 pub(crate) fn video_stream(&self) -> Option<&AviStreamInfo> {
620 self.streams
621 .iter()
622 .find(|s| s.stream_type == StreamType::Video)
623 }
624
625 pub(crate) fn audio_stream(&self) -> Option<&AviStreamInfo> {
627 self.streams
628 .iter()
629 .find(|s| s.stream_type == StreamType::Audio)
630 }
631
632 pub(crate) fn extract_frame<'a>(&self, data: &'a [u8], frame_index: usize) -> Option<&'a [u8]> {
637 let video_entries: Vec<&AviIndexEntry> =
638 self.index.iter().filter(|e| e.is_video()).collect();
639 let entry = video_entries.get(frame_index)?;
640
641 let abs_offset = (self.movi_offset as usize)
644 .checked_add(entry.offset as usize)?
645 .checked_add(8)?; let end = abs_offset.checked_add(entry.size as usize)?;
647
648 if end > data.len() {
649 return None;
650 }
651 Some(&data[abs_offset..end])
652 }
653
654 pub(crate) fn video_frame_count(&self) -> usize {
656 self.index.iter().filter(|e| e.is_video()).count()
657 }
658
659 pub(crate) fn audio_chunk_count(&self) -> usize {
661 self.index.iter().filter(|e| e.is_audio()).count()
662 }
663
664 pub(crate) fn video_index_entries(&self) -> Vec<&AviIndexEntry> {
666 self.index.iter().filter(|e| e.is_video()).collect()
667 }
668
669 pub(crate) fn audio_index_entries(&self) -> Vec<&AviIndexEntry> {
671 self.index.iter().filter(|e| e.is_audio()).collect()
672 }
673
674 pub(crate) fn demux_streams(&self) -> (Vec<AviIndexEntry>, Vec<AviIndexEntry>) {
677 let mut video = Vec::new();
678 let mut audio = Vec::new();
679 for entry in &self.index {
680 if entry.is_video() {
681 video.push(*entry);
682 } else if entry.is_audio() {
683 audio.push(*entry);
684 }
685 }
686 (video, audio)
687 }
688}
689
690#[derive(Debug, Clone, Copy, PartialEq, Eq)]
696pub enum FrameRateMode {
697 Duplicate,
699 Drop,
701 Pulldown32,
704 TimestampSelect,
706 LinearBlend,
708}
709
710#[derive(Debug, Clone)]
712pub struct FrameRateConverter {
713 pub src_fps_num: u32,
715 pub src_fps_den: u32,
717 pub dst_fps_num: u32,
719 pub dst_fps_den: u32,
721 pub mode: FrameRateMode,
723}
724
725impl FrameRateConverter {
726 pub fn new(
728 src_fps_num: u32,
729 src_fps_den: u32,
730 dst_fps_num: u32,
731 dst_fps_den: u32,
732 mode: FrameRateMode,
733 ) -> Self {
734 Self {
735 src_fps_num,
736 src_fps_den,
737 dst_fps_num,
738 dst_fps_den,
739 mode,
740 }
741 }
742
743 pub(crate) fn source_frame_for_output(&self, output_index: u32) -> u32 {
751 if self.src_fps_num == 0 || self.dst_fps_den == 0 {
752 return 0;
753 }
754 let numerator = (output_index as u64)
770 .checked_mul(self.src_fps_num as u64)
771 .and_then(|v| v.checked_mul(self.dst_fps_den as u64))
772 .unwrap_or(u64::MAX);
773 let denominator = (self.src_fps_den as u64)
774 .checked_mul(self.dst_fps_num as u64)
775 .max(Some(1))
776 .unwrap_or(1);
777 (numerator / denominator) as u32
778 }
779
780 pub(crate) fn pulldown_32_source(&self, output_index: u32) -> (u32, bool) {
792 let cycle = output_index / 5;
793 let phase = output_index % 5;
794 let base = cycle * 4;
795 match phase {
796 0 => (base, false),
797 1 => (base, true), 2 => (base + 1, false), 3 => (base + 2, false), 4 => (base + 2, true), _ => unreachable!(),
802 }
803 }
804
805 #[cfg(feature = "alloc")]
813 pub(crate) fn build_frame_map(
814 &self,
815 total_source_frames: u32,
816 total_output_frames: u32,
817 ) -> Vec<FrameMapEntry> {
818 let mut map = Vec::with_capacity(total_output_frames as usize);
819
820 for out_idx in 0..total_output_frames {
821 let entry = match self.mode {
822 FrameRateMode::Duplicate | FrameRateMode::Drop | FrameRateMode::TimestampSelect => {
823 let src = self
824 .source_frame_for_output(out_idx)
825 .min(total_source_frames.saturating_sub(1));
826 FrameMapEntry {
827 source_index: src,
828 blend_weight: 256,
829 }
830 }
831 FrameRateMode::Pulldown32 => {
832 let (src, _repeated) = self.pulldown_32_source(out_idx);
833 FrameMapEntry {
834 source_index: src.min(total_source_frames.saturating_sub(1)),
835 blend_weight: 256,
836 }
837 }
838 FrameRateMode::LinearBlend => {
839 self.compute_blend_entry(out_idx, total_source_frames)
840 }
841 };
842 map.push(entry);
843 }
844
845 map
846 }
847
848 fn compute_blend_entry(&self, output_index: u32, total_source_frames: u32) -> FrameMapEntry {
853 if self.dst_fps_num == 0 || self.src_fps_den == 0 || total_source_frames == 0 {
854 return FrameMapEntry {
855 source_index: 0,
856 blend_weight: 256,
857 };
858 }
859
860 let numerator = (output_index as u64)
862 .checked_mul(self.src_fps_num as u64)
863 .and_then(|v| v.checked_mul(self.dst_fps_den as u64))
864 .and_then(|v| v.checked_mul(256)) .unwrap_or(u64::MAX);
866 let denominator = (self.src_fps_den as u64)
867 .checked_mul(self.dst_fps_num as u64)
868 .max(Some(1))
869 .unwrap_or(1);
870 let src_pos_fp = (numerator / denominator) as u32;
871
872 let src_index = (src_pos_fp >> 8).min(total_source_frames.saturating_sub(1));
873 let frac = src_pos_fp & 0xFF; FrameMapEntry {
876 source_index: src_index,
877 blend_weight: frac as u16, }
879 }
880}
881
882#[derive(Debug, Clone, Copy, PartialEq, Eq)]
885pub struct FrameMapEntry {
886 pub source_index: u32,
888 pub blend_weight: u16,
892}
893
894#[cfg(feature = "alloc")]
899pub(crate) fn blend_frames(frame_a: &[u8], frame_b: &[u8], out: &mut [u8], weight: u16) {
900 let len = frame_a.len().min(frame_b.len()).min(out.len());
901 let w = weight as u32;
902 let inv_w = 256u32.saturating_sub(w);
903
904 for i in 0..len {
905 let a = frame_a[i] as u32;
906 let b = frame_b[i] as u32;
907 out[i] = ((a * inv_w + b * w) >> 8) as u8;
908 }
909}
910
911#[cfg(feature = "alloc")]
917#[derive(Debug, Clone, PartialEq, Eq)]
918pub struct SubtitleEntry {
919 pub sequence: u32,
921 pub start_ms: u64,
923 pub end_ms: u64,
925 pub text: String,
927}
928
929#[derive(Debug, Clone, Copy)]
931pub struct SubtitleConfig {
932 pub bottom_margin: u32,
934 pub horizontal_margin: u32,
936 pub font_width: u32,
938 pub font_height: u32,
940 pub bg_opacity: u8,
942 pub bg_color: (u8, u8, u8),
944 pub text_color: (u8, u8, u8),
946 pub padding: u32,
948}
949
950impl Default for SubtitleConfig {
951 fn default() -> Self {
952 Self {
953 bottom_margin: 40,
954 horizontal_margin: 20,
955 font_width: 8,
956 font_height: 16,
957 bg_opacity: 180,
958 bg_color: (0, 0, 0),
959 text_color: (255, 255, 255),
960 padding: 4,
961 }
962 }
963}
964
965#[cfg(feature = "alloc")]
967#[derive(Debug, Clone)]
968pub struct SubtitleTrack {
969 pub entries: Vec<SubtitleEntry>,
971}
972
973#[cfg(feature = "alloc")]
974impl SubtitleTrack {
975 pub(crate) fn parse_srt(input: &str) -> Self {
989 let mut entries = Vec::new();
990 let mut lines = input.lines().peekable();
991
992 while lines.peek().is_some() {
993 while let Some(&line) = lines.peek() {
995 if line.trim().is_empty() {
996 lines.next();
997 } else {
998 break;
999 }
1000 }
1001
1002 let seq_line = match lines.next() {
1004 Some(l) => l.trim(),
1005 None => break,
1006 };
1007 let sequence = match parse_u32_from_str(seq_line) {
1008 Some(n) => n,
1009 None => continue,
1010 };
1011
1012 let ts_line = match lines.next() {
1014 Some(l) => l.trim(),
1015 None => break,
1016 };
1017 let (start_ms, end_ms) = match parse_srt_timestamp_line(ts_line) {
1018 Some(t) => t,
1019 None => continue,
1020 };
1021
1022 let mut text = String::new();
1024 while let Some(&line) = lines.peek() {
1025 if line.trim().is_empty() {
1026 break;
1027 }
1028 if !text.is_empty() {
1029 text.push('\n');
1030 }
1031 text.push_str(lines.next().unwrap_or(""));
1032 }
1033
1034 entries.push(SubtitleEntry {
1035 sequence,
1036 start_ms,
1037 end_ms,
1038 text,
1039 });
1040 }
1041
1042 entries.sort_by_key(|e| e.start_ms);
1044
1045 Self { entries }
1046 }
1047
1048 pub(crate) fn active_at(&self, time_ms: u64) -> Option<&SubtitleEntry> {
1052 self.entries
1053 .iter()
1054 .find(|e| time_ms >= e.start_ms && time_ms < e.end_ms)
1055 }
1056
1057 pub(crate) fn all_active_at(&self, time_ms: u64) -> Vec<&SubtitleEntry> {
1059 self.entries
1060 .iter()
1061 .filter(|e| time_ms >= e.start_ms && time_ms < e.end_ms)
1062 .collect()
1063 }
1064
1065 pub(crate) fn len(&self) -> usize {
1067 self.entries.len()
1068 }
1069
1070 pub(crate) fn is_empty(&self) -> bool {
1072 self.entries.is_empty()
1073 }
1074}
1075
1076#[cfg(feature = "alloc")]
1088pub(crate) fn render_subtitle_overlay(
1089 buf: &mut [u8],
1090 stride: u32,
1091 width: u32,
1092 height: u32,
1093 text: &str,
1094 config: &SubtitleConfig,
1095) {
1096 if text.is_empty() || width == 0 || height == 0 {
1097 return;
1098 }
1099
1100 let fw = config.font_width;
1101 let fh = config.font_height;
1102 let pad = config.padding;
1103
1104 let text_area_width = width
1106 .saturating_sub(config.horizontal_margin * 2)
1107 .saturating_sub(pad * 2);
1108 if text_area_width < fw {
1109 return;
1110 }
1111 let max_chars_per_line = text_area_width / fw;
1112 if max_chars_per_line == 0 {
1113 return;
1114 }
1115
1116 let wrapped_lines = wrap_text(text, max_chars_per_line as usize);
1118 let num_lines = wrapped_lines.len() as u32;
1119 if num_lines == 0 {
1120 return;
1121 }
1122
1123 let box_text_height = num_lines * fh;
1125 let box_height = box_text_height + pad * 2;
1126 let max_line_len = wrapped_lines
1127 .iter()
1128 .map(|l| l.len() as u32)
1129 .max()
1130 .unwrap_or(0);
1131 let box_text_width = max_line_len * fw;
1132 let box_width = box_text_width + pad * 2;
1133
1134 let box_x = if width > box_width {
1136 (width - box_width) / 2
1137 } else {
1138 0
1139 };
1140 let box_y = if height > box_height + config.bottom_margin {
1141 height - box_height - config.bottom_margin
1142 } else {
1143 0
1144 };
1145
1146 draw_bg_box(
1148 buf, stride, width, height, box_x, box_y, box_width, box_height, config,
1149 );
1150
1151 let text_x = box_x + pad;
1153 let mut text_y = box_y + pad;
1154
1155 for line in &wrapped_lines {
1156 let line_width = line.len() as u32 * fw;
1158 let line_x = if box_text_width > line_width {
1159 text_x + (box_text_width - line_width) / 2
1160 } else {
1161 text_x
1162 };
1163
1164 draw_text_line(buf, stride, width, height, line_x, text_y, line, config);
1165 text_y += fh;
1166 }
1167}
1168
1169#[allow(clippy::too_many_arguments)]
1171fn draw_bg_box(
1172 buf: &mut [u8],
1173 stride: u32,
1174 _width: u32,
1175 height: u32,
1176 bx: u32,
1177 by: u32,
1178 bw: u32,
1179 bh: u32,
1180 config: &SubtitleConfig,
1181) {
1182 let alpha = config.bg_opacity as u32;
1183 let inv_alpha = 255u32.saturating_sub(alpha);
1184 let (br, bg, bb) = config.bg_color;
1185
1186 for dy in 0..bh {
1187 let py = by + dy;
1188 if py >= height {
1189 break;
1190 }
1191 let row_offset = (py * stride) as usize;
1192
1193 for dx in 0..bw {
1194 let px = bx + dx;
1195 let pixel_offset = row_offset + (px as usize) * 4;
1196 if pixel_offset + 3 >= buf.len() {
1197 continue;
1198 }
1199
1200 let existing_b = buf[pixel_offset] as u32;
1202 let existing_g = buf[pixel_offset + 1] as u32;
1203 let existing_r = buf[pixel_offset + 2] as u32;
1204
1205 buf[pixel_offset] = ((bb as u32 * alpha + existing_b * inv_alpha) / 255) as u8;
1206 buf[pixel_offset + 1] = ((bg as u32 * alpha + existing_g * inv_alpha) / 255) as u8;
1207 buf[pixel_offset + 2] = ((br as u32 * alpha + existing_r * inv_alpha) / 255) as u8;
1208 buf[pixel_offset + 3] = 0xFF;
1209 }
1210 }
1211}
1212
1213fn draw_text_line(
1215 buf: &mut [u8],
1216 stride: u32,
1217 _width: u32,
1218 height: u32,
1219 start_x: u32,
1220 start_y: u32,
1221 text: &str,
1222 config: &SubtitleConfig,
1223) {
1224 let (tr, tg, tb) = config.text_color;
1225
1226 for (i, ch) in text.chars().enumerate() {
1227 let gx = start_x + (i as u32) * config.font_width;
1228 let glyph = get_glyph(ch);
1229
1230 for row in 0..config.font_height.min(16) {
1231 let py = start_y + row;
1232 if py >= height {
1233 break;
1234 }
1235 let bits = glyph[row as usize];
1236
1237 for col in 0..config.font_width.min(8) {
1238 if bits & (0x80 >> col) != 0 {
1239 let px = gx + col;
1240 let pixel_offset = (py * stride) as usize + (px as usize) * 4;
1241 if pixel_offset + 3 < buf.len() {
1242 buf[pixel_offset] = tb;
1243 buf[pixel_offset + 1] = tg;
1244 buf[pixel_offset + 2] = tr;
1245 buf[pixel_offset + 3] = 0xFF;
1246 }
1247 }
1248 }
1249 }
1250 }
1251}
1252
1253fn get_glyph(ch: char) -> [u8; 16] {
1259 let code = ch as u32;
1260 if !(0x20..=0x7E).contains(&code) {
1261 return [0u8; 16];
1262 }
1263
1264 match ch {
1268 ' ' => [0; 16],
1269 'A' => [
1270 0x00, 0x00, 0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00,
1271 0x00, 0x00,
1272 ],
1273 'H' => [
1274 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00,
1275 0x00, 0x00,
1276 ],
1277 'e' => [
1278 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, 0x00, 0x00, 0x00,
1279 0x00, 0x00,
1280 ],
1281 'l' => [
1282 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00,
1283 0x00, 0x00,
1284 ],
1285 'o' => [
1286 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
1287 0x00, 0x00,
1288 ],
1289 '!' => [
1290 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
1291 0x00, 0x00,
1292 ],
1293 _ => {
1294 [
1296 0x00, 0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00, 0x00, 0x00, 0x00,
1297 0x00, 0x00,
1298 ]
1299 }
1300 }
1301}
1302
1303#[cfg(feature = "alloc")]
1306fn wrap_text(text: &str, max_chars: usize) -> Vec<String> {
1307 let mut result = Vec::new();
1308 if max_chars == 0 {
1309 return result;
1310 }
1311
1312 for raw_line in text.split('\n') {
1313 if raw_line.len() <= max_chars {
1314 result.push(String::from(raw_line));
1315 continue;
1316 }
1317
1318 let mut current_line = String::new();
1320 for word in raw_line.split(' ') {
1321 if current_line.is_empty() {
1322 if word.len() > max_chars {
1323 let mut start = 0;
1325 while start < word.len() {
1326 let end = (start + max_chars).min(word.len());
1327 result.push(String::from(&word[start..end]));
1328 start = end;
1329 }
1330 } else {
1331 current_line.push_str(word);
1332 }
1333 } else if current_line.len() + 1 + word.len() <= max_chars {
1334 current_line.push(' ');
1335 current_line.push_str(word);
1336 } else {
1337 result.push(current_line);
1338 current_line = String::from(word);
1339 }
1340 }
1341 if !current_line.is_empty() {
1342 result.push(current_line);
1343 }
1344 }
1345
1346 result
1347}
1348
1349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1355pub enum AudioPriorityClass {
1356 Critical,
1359 Normal,
1362 Background,
1365}
1366
1367impl AudioPriorityClass {
1368 pub(crate) fn period_ns(&self) -> u64 {
1370 match self {
1371 Self::Critical => 1_000_000, Self::Normal => 5_000_000, Self::Background => 20_000_000, }
1375 }
1376
1377 pub(crate) fn runtime_ns(&self) -> u64 {
1379 match self {
1380 Self::Critical => 500_000, Self::Normal => 2_000_000, Self::Background => 10_000_000, }
1384 }
1385
1386 pub(crate) fn deadline_ns(&self) -> u64 {
1388 self.period_ns()
1389 }
1390
1391 pub(crate) fn utilization_permille(&self) -> u64 {
1393 let period = self.period_ns();
1394 if period == 0 {
1395 return 1000;
1396 }
1397 self.runtime_ns()
1398 .checked_mul(1000)
1399 .map(|v| v / period)
1400 .unwrap_or(1000)
1401 }
1402}
1403
1404#[derive(Debug, Clone, Copy)]
1406pub struct AudioSchedParams {
1407 pub pid: u64,
1409 pub priority: AudioPriorityClass,
1411 pub period_ns: u64,
1413 pub runtime_ns: u64,
1415 pub cpu_reservation_permille: u32,
1417}
1418
1419impl AudioSchedParams {
1420 pub(crate) fn from_priority(pid: u64, priority: AudioPriorityClass) -> Self {
1422 Self {
1423 pid,
1424 priority,
1425 period_ns: priority.period_ns(),
1426 runtime_ns: priority.runtime_ns(),
1427 cpu_reservation_permille: priority.utilization_permille() as u32,
1428 }
1429 }
1430
1431 pub(crate) fn custom(pid: u64, period_ns: u64, runtime_ns: u64) -> Self {
1433 let cpu_reservation_permille = if period_ns > 0 {
1434 (runtime_ns.saturating_mul(1000) / period_ns) as u32
1435 } else {
1436 1000
1437 };
1438 Self {
1439 pid,
1440 priority: AudioPriorityClass::Normal,
1441 period_ns,
1442 runtime_ns,
1443 cpu_reservation_permille,
1444 }
1445 }
1446}
1447
1448#[derive(Debug, Clone, Copy, Default)]
1450pub struct AudioSchedStats {
1451 pub periods_completed: u64,
1453 pub on_time_wakes: u64,
1455 pub late_wakes: u64,
1457 pub underruns: u64,
1459 pub overruns: u64,
1461 pub max_jitter_ns: u64,
1463 pub min_jitter_ns: u64,
1465 pub total_jitter_ns: u64,
1467 pub last_wake_ns: u64,
1469 pub next_expected_wake_ns: u64,
1471}
1472
1473impl AudioSchedStats {
1474 pub(crate) fn avg_jitter_ns(&self) -> u64 {
1476 if self.periods_completed == 0 {
1477 return 0;
1478 }
1479 self.total_jitter_ns / self.periods_completed
1480 }
1481
1482 pub(crate) fn record_wake(&mut self, actual_wake_ns: u64, expected_wake_ns: u64) {
1487 self.periods_completed += 1;
1488
1489 let jitter = actual_wake_ns.abs_diff(expected_wake_ns);
1490
1491 self.total_jitter_ns = self.total_jitter_ns.saturating_add(jitter);
1492
1493 if jitter > self.max_jitter_ns {
1494 self.max_jitter_ns = jitter;
1495 }
1496 if self.min_jitter_ns == 0 || jitter < self.min_jitter_ns {
1497 self.min_jitter_ns = jitter;
1498 }
1499
1500 if actual_wake_ns <= expected_wake_ns {
1501 self.on_time_wakes += 1;
1502 } else {
1503 self.late_wakes += 1;
1504 }
1505
1506 self.last_wake_ns = actual_wake_ns;
1507 self.next_expected_wake_ns =
1508 actual_wake_ns.saturating_add(expected_wake_ns.saturating_sub(self.last_wake_ns));
1509 }
1510
1511 pub(crate) fn record_underrun(&mut self) {
1513 self.underruns += 1;
1514 }
1515
1516 pub(crate) fn record_overrun(&mut self) {
1518 self.overruns += 1;
1519 }
1520}
1521
1522#[cfg(feature = "alloc")]
1527#[derive(Debug)]
1528pub struct AudioScheduler {
1529 threads: Vec<AudioSchedParams>,
1531 stats: Vec<AudioSchedStats>,
1533 total_reservation_permille: u32,
1535 max_reservation_permille: u32,
1537}
1538
1539#[cfg(feature = "alloc")]
1540impl Default for AudioScheduler {
1541 fn default() -> Self {
1542 Self::new()
1543 }
1544}
1545
1546#[cfg(feature = "alloc")]
1547impl AudioScheduler {
1548 const MAX_AUDIO_THREADS: usize = 32;
1550
1551 pub fn new() -> Self {
1553 Self {
1554 threads: Vec::new(),
1555 stats: Vec::new(),
1556 total_reservation_permille: 0,
1557 max_reservation_permille: 800, }
1559 }
1560
1561 pub(crate) fn with_max_reservation(max_permille: u32) -> Self {
1563 Self {
1564 threads: Vec::new(),
1565 stats: Vec::new(),
1566 total_reservation_permille: 0,
1567 max_reservation_permille: max_permille.min(1000),
1568 }
1569 }
1570
1571 pub(crate) fn register_thread(
1578 &mut self,
1579 params: AudioSchedParams,
1580 ) -> Result<(), AudioSchedError> {
1581 if self.threads.iter().any(|t| t.pid == params.pid) {
1583 return Err(AudioSchedError::AlreadyRegistered);
1584 }
1585
1586 if self.threads.len() >= Self::MAX_AUDIO_THREADS {
1588 return Err(AudioSchedError::TooManyThreads);
1589 }
1590
1591 let new_total = self
1593 .total_reservation_permille
1594 .saturating_add(params.cpu_reservation_permille);
1595 if new_total > self.max_reservation_permille {
1596 return Err(AudioSchedError::InsufficientCpuBudget);
1597 }
1598
1599 self.total_reservation_permille = new_total;
1600 self.threads.push(params);
1601 self.stats.push(AudioSchedStats::default());
1602 Ok(())
1603 }
1604
1605 pub(crate) fn unregister_thread(&mut self, pid: u64) -> Result<(), AudioSchedError> {
1607 let idx = self
1608 .threads
1609 .iter()
1610 .position(|t| t.pid == pid)
1611 .ok_or(AudioSchedError::NotFound)?;
1612
1613 let params = self.threads.remove(idx);
1614 self.stats.remove(idx);
1615 self.total_reservation_permille = self
1616 .total_reservation_permille
1617 .saturating_sub(params.cpu_reservation_permille);
1618 Ok(())
1619 }
1620
1621 pub(crate) fn get_params(&self, pid: u64) -> Option<&AudioSchedParams> {
1623 self.threads.iter().find(|t| t.pid == pid)
1624 }
1625
1626 pub(crate) fn get_stats_mut(&mut self, pid: u64) -> Option<&mut AudioSchedStats> {
1628 let idx = self.threads.iter().position(|t| t.pid == pid)?;
1629 self.stats.get_mut(idx)
1630 }
1631
1632 pub(crate) fn get_stats(&self, pid: u64) -> Option<&AudioSchedStats> {
1634 let idx = self.threads.iter().position(|t| t.pid == pid)?;
1635 self.stats.get(idx)
1636 }
1637
1638 pub(crate) fn record_wake(
1640 &mut self,
1641 pid: u64,
1642 actual_ns: u64,
1643 expected_ns: u64,
1644 ) -> Result<(), AudioSchedError> {
1645 let stats = self.get_stats_mut(pid).ok_or(AudioSchedError::NotFound)?;
1646 stats.record_wake(actual_ns, expected_ns);
1647 Ok(())
1648 }
1649
1650 pub(crate) fn total_reservation(&self) -> u32 {
1652 self.total_reservation_permille
1653 }
1654
1655 pub(crate) fn thread_count(&self) -> usize {
1657 self.threads.len()
1658 }
1659
1660 pub(crate) fn available_budget(&self) -> u32 {
1662 self.max_reservation_permille
1663 .saturating_sub(self.total_reservation_permille)
1664 }
1665
1666 pub(crate) fn next_wake_time(&self, pid: u64, current_ns: u64) -> Option<u64> {
1668 let params = self.get_params(pid)?;
1669 Some(current_ns.saturating_add(params.period_ns))
1670 }
1671
1672 pub(crate) fn aggregate_stats(&self) -> AudioSchedStats {
1674 let mut agg = AudioSchedStats::default();
1675 for stats in &self.stats {
1676 agg.periods_completed = agg
1677 .periods_completed
1678 .saturating_add(stats.periods_completed);
1679 agg.on_time_wakes = agg.on_time_wakes.saturating_add(stats.on_time_wakes);
1680 agg.late_wakes = agg.late_wakes.saturating_add(stats.late_wakes);
1681 agg.underruns = agg.underruns.saturating_add(stats.underruns);
1682 agg.overruns = agg.overruns.saturating_add(stats.overruns);
1683 if stats.max_jitter_ns > agg.max_jitter_ns {
1684 agg.max_jitter_ns = stats.max_jitter_ns;
1685 }
1686 agg.total_jitter_ns = agg.total_jitter_ns.saturating_add(stats.total_jitter_ns);
1687 }
1688 agg
1689 }
1690}
1691
1692#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1694pub enum AudioSchedError {
1695 AlreadyRegistered,
1697 TooManyThreads,
1699 InsufficientCpuBudget,
1701 NotFound,
1703 InvalidParams,
1705}
1706
1707pub(crate) static AUDIO_TOTAL_UNDERRUNS: AtomicU64 = AtomicU64::new(0);
1709pub(crate) static AUDIO_TOTAL_OVERRUNS: AtomicU64 = AtomicU64::new(0);
1710pub(crate) static AUDIO_TOTAL_LATE_WAKES: AtomicU64 = AtomicU64::new(0);
1711
1712pub(crate) fn count_audio_underrun() {
1714 AUDIO_TOTAL_UNDERRUNS.fetch_add(1, Ordering::Relaxed);
1715}
1716
1717pub(crate) fn count_audio_overrun() {
1719 AUDIO_TOTAL_OVERRUNS.fetch_add(1, Ordering::Relaxed);
1720}
1721
1722pub(crate) fn count_audio_late_wake() {
1724 AUDIO_TOTAL_LATE_WAKES.fetch_add(1, Ordering::Relaxed);
1725}
1726
1727#[inline]
1733fn read_u32_le(data: &[u8], offset: usize) -> u32 {
1734 if offset + 4 > data.len() {
1735 return 0;
1736 }
1737 u32::from_le_bytes([
1738 data[offset],
1739 data[offset + 1],
1740 data[offset + 2],
1741 data[offset + 3],
1742 ])
1743}
1744
1745#[inline]
1747fn read_i32_le(data: &[u8], offset: usize) -> i32 {
1748 read_u32_le(data, offset) as i32
1749}
1750
1751#[inline]
1753fn read_u16_le(data: &[u8], offset: usize) -> u16 {
1754 if offset + 2 > data.len() {
1755 return 0;
1756 }
1757 u16::from_le_bytes([data[offset], data[offset + 1]])
1758}
1759
1760fn aligned_next(pos: usize) -> usize {
1762 (pos + 1) & !1
1763}
1764
1765fn aligned_next_rel(pos: usize) -> usize {
1767 (pos + 1) & !1
1768}
1769
1770fn parse_u32_from_str(s: &str) -> Option<u32> {
1772 let s = s.trim();
1773 if s.is_empty() {
1774 return None;
1775 }
1776 let mut result: u32 = 0;
1777 for &b in s.as_bytes() {
1778 if !b.is_ascii_digit() {
1779 return None;
1780 }
1781 result = result.checked_mul(10)?.checked_add((b - b'0') as u32)?;
1782 }
1783 Some(result)
1784}
1785
1786fn parse_srt_timestamp(s: &str) -> Option<u64> {
1788 let s = s.trim();
1790 if s.len() < 12 {
1791 return None;
1792 }
1793 let bytes = s.as_bytes();
1794
1795 let hours = parse_two_digits(bytes, 0)? as u64;
1796 if bytes[2] != b':' {
1798 return None;
1799 }
1800 let minutes = parse_two_digits(bytes, 3)? as u64;
1801 if bytes[5] != b':' {
1802 return None;
1803 }
1804 let seconds = parse_two_digits(bytes, 6)? as u64;
1805 if bytes[8] != b',' && bytes[8] != b'.' {
1807 return None;
1808 }
1809 let millis = parse_three_digits(bytes, 9)? as u64;
1810
1811 hours
1812 .checked_mul(3_600_000)?
1813 .checked_add(minutes.checked_mul(60_000)?)?
1814 .checked_add(seconds.checked_mul(1_000)?)?
1815 .checked_add(millis)
1816}
1817
1818fn parse_srt_timestamp_line(line: &str) -> Option<(u64, u64)> {
1820 let parts: Vec<&str> = line.split("-->").collect();
1821 if parts.len() != 2 {
1822 return None;
1823 }
1824 let start = parse_srt_timestamp(parts[0])?;
1825 let end = parse_srt_timestamp(parts[1])?;
1826 Some((start, end))
1827}
1828
1829fn parse_two_digits(bytes: &[u8], offset: usize) -> Option<u32> {
1831 if offset + 2 > bytes.len() {
1832 return None;
1833 }
1834 let d0 = bytes[offset].wrapping_sub(b'0');
1835 let d1 = bytes[offset + 1].wrapping_sub(b'0');
1836 if d0 > 9 || d1 > 9 {
1837 return None;
1838 }
1839 Some(d0 as u32 * 10 + d1 as u32)
1840}
1841
1842fn parse_three_digits(bytes: &[u8], offset: usize) -> Option<u32> {
1844 if offset + 3 > bytes.len() {
1845 return None;
1846 }
1847 let d0 = bytes[offset].wrapping_sub(b'0');
1848 let d1 = bytes[offset + 1].wrapping_sub(b'0');
1849 let d2 = bytes[offset + 2].wrapping_sub(b'0');
1850 if d0 > 9 || d1 > 9 || d2 > 9 {
1851 return None;
1852 }
1853 let hundreds = d0 as u32;
1854 let tens = d1 as u32;
1855 hundreds
1856 .checked_mul(100)?
1857 .checked_add(tens.checked_mul(10)?)?
1858 .checked_add(d2 as u32)
1859}
1860
1861#[cfg(test)]
1866mod tests {
1867 #[allow(unused_imports)]
1868 use alloc::vec;
1869
1870 use super::*;
1871
1872 #[test]
1875 fn test_fourcc_from_bytes() {
1876 let data = b"RIFF";
1877 let fcc = FourCC::from_bytes(data).unwrap();
1878 assert_eq!(fcc, FourCC::RIFF);
1879 }
1880
1881 #[test]
1882 fn test_fourcc_from_bytes_too_short() {
1883 let data = b"RI";
1884 assert!(FourCC::from_bytes(data).is_none());
1885 }
1886
1887 #[test]
1888 fn test_avi_flags() {
1889 let flags = AviFlags(AviFlags::AVIF_HASINDEX | AviFlags::AVIF_ISINTERLEAVED);
1890 assert!(flags.has_flag(AviFlags::AVIF_HASINDEX));
1891 assert!(flags.has_flag(AviFlags::AVIF_ISINTERLEAVED));
1892 assert!(!flags.has_flag(AviFlags::AVIF_COPYRIGHTED));
1893 }
1894
1895 #[test]
1896 fn test_avi_main_header_parse() {
1897 let mut data = [0u8; 40];
1898 data[0..4].copy_from_slice(&33333u32.to_le_bytes());
1900 data[16..20].copy_from_slice(&100u32.to_le_bytes());
1902 data[24..28].copy_from_slice(&2u32.to_le_bytes());
1904 data[32..36].copy_from_slice(&640u32.to_le_bytes());
1906 data[36..40].copy_from_slice(&480u32.to_le_bytes());
1908
1909 let hdr = AviMainHeader::parse(&data).unwrap();
1910 assert_eq!(hdr.microseconds_per_frame, 33333);
1911 assert_eq!(hdr.total_frames, 100);
1912 assert_eq!(hdr.streams, 2);
1913 assert_eq!(hdr.width, 640);
1914 assert_eq!(hdr.height, 480);
1915 }
1916
1917 #[test]
1918 fn test_avi_main_header_frame_rate() {
1919 let hdr = AviMainHeader {
1920 microseconds_per_frame: 33333,
1921 ..Default::default()
1922 };
1923 let (num, den) = hdr.frame_rate();
1924 assert_eq!(num, 1_000_000);
1926 assert_eq!(den, 33333);
1927 }
1928
1929 #[test]
1930 fn test_avi_main_header_parse_too_short() {
1931 let data = [0u8; 20];
1932 assert!(AviMainHeader::parse(&data).is_none());
1933 }
1934
1935 #[test]
1936 fn test_avi_stream_header_parse() {
1937 let mut data = [0u8; 56];
1938 data[0..4].copy_from_slice(b"vids");
1939 data[4..8].copy_from_slice(b"DIB ");
1940 data[20..24].copy_from_slice(&1u32.to_le_bytes());
1942 data[24..28].copy_from_slice(&30u32.to_le_bytes());
1944
1945 let hdr = AviStreamHeader::parse(&data).unwrap();
1946 assert_eq!(hdr.get_stream_type(), StreamType::Video);
1947 assert_eq!(hdr.sample_rate(), (30, 1));
1948 }
1949
1950 #[test]
1951 fn test_avi_stream_header_audio() {
1952 let mut data = [0u8; 56];
1953 data[0..4].copy_from_slice(b"auds");
1954
1955 let hdr = AviStreamHeader::parse(&data).unwrap();
1956 assert_eq!(hdr.get_stream_type(), StreamType::Audio);
1957 }
1958
1959 #[test]
1960 fn test_bitmap_info_header_parse() {
1961 let mut data = [0u8; 40];
1962 data[0..4].copy_from_slice(&40u32.to_le_bytes()); data[4..8].copy_from_slice(&320i32.to_le_bytes()); data[8..12].copy_from_slice(&240i32.to_le_bytes()); data[12..14].copy_from_slice(&1u16.to_le_bytes()); data[14..16].copy_from_slice(&24u16.to_le_bytes()); let bih = BitmapInfoHeader::parse(&data).unwrap();
1969 assert_eq!(bih.width, 320);
1970 assert_eq!(bih.height, 240);
1971 assert_eq!(bih.bit_count, 24);
1972 assert!(bih.is_bottom_up());
1973 assert_eq!(bih.abs_height(), 240);
1974 }
1975
1976 #[test]
1977 fn test_bitmap_info_header_top_down() {
1978 let mut data = [0u8; 40];
1979 data[0..4].copy_from_slice(&40u32.to_le_bytes());
1980 data[4..8].copy_from_slice(&320i32.to_le_bytes());
1981 data[8..12].copy_from_slice(&(-240i32).to_le_bytes()); let bih = BitmapInfoHeader::parse(&data).unwrap();
1984 assert!(!bih.is_bottom_up());
1985 assert_eq!(bih.abs_height(), 240);
1986 }
1987
1988 #[test]
1989 fn test_wave_format_ex_parse() {
1990 let mut data = [0u8; 18];
1991 data[0..2].copy_from_slice(&1u16.to_le_bytes()); data[2..4].copy_from_slice(&2u16.to_le_bytes()); data[4..8].copy_from_slice(&44100u32.to_le_bytes()); data[8..12].copy_from_slice(&176400u32.to_le_bytes()); data[12..14].copy_from_slice(&4u16.to_le_bytes()); data[14..16].copy_from_slice(&16u16.to_le_bytes()); let wfx = WaveFormatEx::parse(&data).unwrap();
1999 assert!(wfx.is_pcm());
2000 assert_eq!(wfx.channels, 2);
2001 assert_eq!(wfx.samples_per_sec, 44100);
2002 assert_eq!(wfx.bits_per_sample, 16);
2003 }
2004
2005 #[test]
2006 fn test_avi_index_entry() {
2007 let mut data = [0u8; 16];
2008 data[0..4].copy_from_slice(b"00dc");
2009 data[4..8].copy_from_slice(&0x10u32.to_le_bytes()); data[8..12].copy_from_slice(&1024u32.to_le_bytes()); data[12..16].copy_from_slice(&4096u32.to_le_bytes()); let entry = AviIndexEntry::parse(&data).unwrap();
2014 assert!(entry.is_keyframe());
2015 assert!(entry.is_video());
2016 assert!(!entry.is_audio());
2017 assert_eq!(entry.stream_number(), 0);
2018 assert_eq!(entry.offset, 1024);
2019 assert_eq!(entry.size, 4096);
2020 }
2021
2022 #[test]
2023 fn test_avi_index_entry_audio() {
2024 let mut data = [0u8; 16];
2025 data[0..4].copy_from_slice(b"01wb");
2026
2027 let entry = AviIndexEntry::parse(&data).unwrap();
2028 assert!(!entry.is_video());
2029 assert!(entry.is_audio());
2030 assert_eq!(entry.stream_number(), 1);
2031 }
2032
2033 #[test]
2036 fn test_source_frame_for_output_identity() {
2037 let conv = FrameRateConverter::new(30, 1, 30, 1, FrameRateMode::Duplicate);
2039 assert_eq!(conv.source_frame_for_output(0), 0);
2040 assert_eq!(conv.source_frame_for_output(1), 1);
2041 assert_eq!(conv.source_frame_for_output(10), 10);
2042 }
2043
2044 #[test]
2045 fn test_source_frame_for_output_downsample() {
2046 let conv = FrameRateConverter::new(60, 1, 30, 1, FrameRateMode::Drop);
2048 assert_eq!(conv.source_frame_for_output(0), 0);
2049 assert_eq!(conv.source_frame_for_output(1), 2);
2050 assert_eq!(conv.source_frame_for_output(2), 4);
2051 }
2052
2053 #[test]
2054 fn test_source_frame_for_output_upsample() {
2055 let conv = FrameRateConverter::new(24, 1, 48, 1, FrameRateMode::Duplicate);
2057 assert_eq!(conv.source_frame_for_output(0), 0);
2058 assert_eq!(conv.source_frame_for_output(1), 0);
2059 assert_eq!(conv.source_frame_for_output(2), 1);
2060 assert_eq!(conv.source_frame_for_output(3), 1);
2061 }
2062
2063 #[test]
2064 fn test_pulldown_32_pattern() {
2065 let conv = FrameRateConverter::new(24, 1, 30, 1, FrameRateMode::Pulldown32);
2066 assert_eq!(conv.pulldown_32_source(0), (0, false)); assert_eq!(conv.pulldown_32_source(1), (0, true)); assert_eq!(conv.pulldown_32_source(2), (1, false)); assert_eq!(conv.pulldown_32_source(3), (2, false)); assert_eq!(conv.pulldown_32_source(4), (2, true)); assert_eq!(conv.pulldown_32_source(5), (4, false)); assert_eq!(conv.pulldown_32_source(6), (4, true)); }
2077
2078 #[test]
2079 fn test_blend_frames_50_50() {
2080 let frame_a = vec![0u8, 100, 200, 50];
2081 let frame_b = vec![100u8, 200, 0, 150];
2082 let mut out = vec![0u8; 4];
2083
2084 blend_frames(&frame_a, &frame_b, &mut out, 128);
2085 assert_eq!(out[0], 50);
2087 assert_eq!(out[1], 150);
2089 assert_eq!(out[2], 100);
2091 assert_eq!(out[3], 100);
2093 }
2094
2095 #[test]
2096 fn test_blend_frames_all_a() {
2097 let frame_a = vec![100u8; 4];
2098 let frame_b = vec![200u8; 4];
2099 let mut out = vec![0u8; 4];
2100
2101 blend_frames(&frame_a, &frame_b, &mut out, 0);
2102 assert_eq!(out, vec![100u8; 4]);
2104 }
2105
2106 #[test]
2107 fn test_blend_frames_all_b() {
2108 let frame_a = vec![100u8; 4];
2109 let frame_b = vec![200u8; 4];
2110 let mut out = vec![0u8; 4];
2111
2112 blend_frames(&frame_a, &frame_b, &mut out, 256);
2113 assert_eq!(out, vec![200u8; 4]);
2115 }
2116
2117 #[test]
2118 fn test_frame_map_duplicate() {
2119 let conv = FrameRateConverter::new(24, 1, 48, 1, FrameRateMode::Duplicate);
2120 let map = conv.build_frame_map(4, 8);
2121 assert_eq!(map.len(), 8);
2122 assert_eq!(map[0].source_index, 0);
2124 assert_eq!(map[1].source_index, 0);
2125 assert_eq!(map[2].source_index, 1);
2126 assert_eq!(map[3].source_index, 1);
2127 }
2128
2129 #[test]
2132 fn test_parse_srt_timestamp() {
2133 let ts = parse_srt_timestamp("01:23:45,678").unwrap();
2134 assert_eq!(ts, 5_025_678);
2137 }
2138
2139 #[test]
2140 fn test_parse_srt_timestamp_zero() {
2141 let ts = parse_srt_timestamp("00:00:00,000").unwrap();
2142 assert_eq!(ts, 0);
2143 }
2144
2145 #[test]
2146 fn test_parse_srt_timestamp_dot_separator() {
2147 let ts = parse_srt_timestamp("00:00:01.500").unwrap();
2148 assert_eq!(ts, 1500);
2149 }
2150
2151 #[test]
2152 fn test_parse_srt_basic() {
2153 let srt_text = "1\n00:00:01,000 --> 00:00:04,000\nHello, world!\n\n2\n00:00:05,500 --> \
2154 00:00:08,000\nSecond subtitle\nwith two lines.\n";
2155 let track = SubtitleTrack::parse_srt(srt_text);
2156 assert_eq!(track.len(), 2);
2157
2158 assert_eq!(track.entries[0].sequence, 1);
2159 assert_eq!(track.entries[0].start_ms, 1000);
2160 assert_eq!(track.entries[0].end_ms, 4000);
2161 assert_eq!(track.entries[0].text, "Hello, world!");
2162
2163 assert_eq!(track.entries[1].sequence, 2);
2164 assert_eq!(track.entries[1].start_ms, 5500);
2165 assert_eq!(track.entries[1].end_ms, 8000);
2166 assert_eq!(track.entries[1].text, "Second subtitle\nwith two lines.");
2167 }
2168
2169 #[test]
2170 fn test_subtitle_active_at() {
2171 let srt_text =
2172 "1\n00:00:01,000 --> 00:00:04,000\nFirst\n\n2\n00:00:05,000 --> 00:00:08,000\nSecond\n";
2173 let track = SubtitleTrack::parse_srt(srt_text);
2174
2175 assert!(track.active_at(0).is_none());
2176 assert_eq!(track.active_at(1000).unwrap().text, "First");
2177 assert_eq!(track.active_at(3999).unwrap().text, "First");
2178 assert!(track.active_at(4000).is_none());
2179 assert_eq!(track.active_at(5000).unwrap().text, "Second");
2180 assert!(track.active_at(8000).is_none());
2181 }
2182
2183 #[test]
2184 fn test_wrap_text_short() {
2185 let lines = wrap_text("Hello world", 20);
2186 assert_eq!(lines.len(), 1);
2187 assert_eq!(lines[0], "Hello world");
2188 }
2189
2190 #[test]
2191 fn test_wrap_text_multiline() {
2192 let lines = wrap_text("This is a longer line of text", 15);
2193 assert!(lines.len() >= 2);
2194 for line in &lines {
2195 assert!(line.len() <= 15);
2196 }
2197 }
2198
2199 #[test]
2200 fn test_wrap_text_existing_newlines() {
2201 let lines = wrap_text("Line one\nLine two", 50);
2202 assert_eq!(lines.len(), 2);
2203 assert_eq!(lines[0], "Line one");
2204 assert_eq!(lines[1], "Line two");
2205 }
2206
2207 #[test]
2210 fn test_audio_priority_class_params() {
2211 assert_eq!(AudioPriorityClass::Critical.period_ns(), 1_000_000);
2212 assert_eq!(AudioPriorityClass::Critical.runtime_ns(), 500_000);
2213 assert_eq!(AudioPriorityClass::Critical.utilization_permille(), 500);
2214
2215 assert_eq!(AudioPriorityClass::Normal.period_ns(), 5_000_000);
2216 assert_eq!(AudioPriorityClass::Normal.runtime_ns(), 2_000_000);
2217 assert_eq!(AudioPriorityClass::Normal.utilization_permille(), 400);
2218
2219 assert_eq!(AudioPriorityClass::Background.period_ns(), 20_000_000);
2220 assert_eq!(AudioPriorityClass::Background.runtime_ns(), 10_000_000);
2221 assert_eq!(AudioPriorityClass::Background.utilization_permille(), 500);
2222 }
2223
2224 #[test]
2225 fn test_audio_scheduler_register() {
2226 let mut sched = AudioScheduler::new();
2227 let params = AudioSchedParams::from_priority(1, AudioPriorityClass::Normal);
2228 assert!(sched.register_thread(params).is_ok());
2229 assert_eq!(sched.thread_count(), 1);
2230 assert_eq!(sched.total_reservation(), 400);
2231 }
2232
2233 #[test]
2234 fn test_audio_scheduler_register_duplicate() {
2235 let mut sched = AudioScheduler::new();
2236 let params = AudioSchedParams::from_priority(1, AudioPriorityClass::Normal);
2237 assert!(sched.register_thread(params).is_ok());
2238 assert_eq!(
2239 sched.register_thread(params),
2240 Err(AudioSchedError::AlreadyRegistered)
2241 );
2242 }
2243
2244 #[test]
2245 fn test_audio_scheduler_cpu_budget() {
2246 let mut sched = AudioScheduler::with_max_reservation(500);
2247 let p1 = AudioSchedParams::from_priority(1, AudioPriorityClass::Normal);
2249 assert!(sched.register_thread(p1).is_ok());
2250 assert_eq!(sched.available_budget(), 100);
2251
2252 let p2 = AudioSchedParams::from_priority(2, AudioPriorityClass::Normal);
2254 assert_eq!(
2255 sched.register_thread(p2),
2256 Err(AudioSchedError::InsufficientCpuBudget)
2257 );
2258 }
2259
2260 #[test]
2261 fn test_audio_scheduler_unregister() {
2262 let mut sched = AudioScheduler::new();
2263 let params = AudioSchedParams::from_priority(1, AudioPriorityClass::Normal);
2264 assert!(sched.register_thread(params).is_ok());
2265 assert!(sched.unregister_thread(1).is_ok());
2266 assert_eq!(sched.thread_count(), 0);
2267 assert_eq!(sched.total_reservation(), 0);
2268 }
2269
2270 #[test]
2271 fn test_audio_scheduler_unregister_not_found() {
2272 let mut sched = AudioScheduler::new();
2273 assert_eq!(sched.unregister_thread(42), Err(AudioSchedError::NotFound));
2274 }
2275
2276 #[test]
2277 fn test_audio_sched_stats_record_wake() {
2278 let mut stats = AudioSchedStats::default();
2279
2280 stats.record_wake(1_000_000, 1_000_000);
2282 assert_eq!(stats.periods_completed, 1);
2283 assert_eq!(stats.on_time_wakes, 1);
2284 assert_eq!(stats.late_wakes, 0);
2285 assert_eq!(stats.max_jitter_ns, 0);
2286
2287 stats.record_wake(2_100_000, 2_000_000);
2289 assert_eq!(stats.periods_completed, 2);
2290 assert_eq!(stats.on_time_wakes, 1);
2291 assert_eq!(stats.late_wakes, 1);
2292 assert_eq!(stats.max_jitter_ns, 100_000);
2293 }
2294
2295 #[test]
2296 fn test_audio_sched_stats_underrun_overrun() {
2297 let mut stats = AudioSchedStats::default();
2298 stats.record_underrun();
2299 stats.record_underrun();
2300 stats.record_overrun();
2301 assert_eq!(stats.underruns, 2);
2302 assert_eq!(stats.overruns, 1);
2303 }
2304
2305 #[test]
2306 fn test_audio_sched_stats_avg_jitter() {
2307 let mut stats = AudioSchedStats::default();
2308 stats.record_wake(1_000_100, 1_000_000); stats.record_wake(2_000_200, 2_000_000); assert_eq!(stats.avg_jitter_ns(), 150); }
2312
2313 #[test]
2314 fn test_audio_scheduler_next_wake_time() {
2315 let mut sched = AudioScheduler::new();
2316 let params = AudioSchedParams::from_priority(1, AudioPriorityClass::Normal);
2317 assert!(sched.register_thread(params).is_ok());
2318
2319 let next = sched.next_wake_time(1, 10_000_000).unwrap();
2320 assert_eq!(next, 15_000_000); }
2322
2323 #[test]
2324 fn test_audio_scheduler_aggregate_stats() {
2325 let mut sched = AudioScheduler::with_max_reservation(1000);
2326 let p1 = AudioSchedParams::from_priority(1, AudioPriorityClass::Normal);
2327 let p2 = AudioSchedParams::from_priority(2, AudioPriorityClass::Background);
2328 assert!(sched.register_thread(p1).is_ok());
2329 assert!(sched.register_thread(p2).is_ok());
2330
2331 assert!(sched.record_wake(1, 1_000_000, 1_000_000).is_ok());
2333 assert!(sched.record_wake(2, 2_100_000, 2_000_000).is_ok());
2334 sched.get_stats_mut(1).unwrap().record_underrun();
2335
2336 let agg = sched.aggregate_stats();
2337 assert_eq!(agg.periods_completed, 2);
2338 assert_eq!(agg.on_time_wakes, 1);
2339 assert_eq!(agg.late_wakes, 1);
2340 assert_eq!(agg.underruns, 1);
2341 }
2342
2343 #[test]
2344 fn test_global_audio_counters() {
2345 let before = AUDIO_TOTAL_UNDERRUNS.load(Ordering::Relaxed);
2347 count_audio_underrun();
2348 let after = AUDIO_TOTAL_UNDERRUNS.load(Ordering::Relaxed);
2349 assert_eq!(after, before + 1);
2350 }
2351
2352 #[test]
2355 fn test_avi_container_parse_minimal() {
2356 let mut avi = Vec::new();
2358
2359 avi.extend_from_slice(b"RIFF");
2361 let size_pos = avi.len();
2362 avi.extend_from_slice(&0u32.to_le_bytes()); avi.extend_from_slice(b"AVI ");
2364
2365 avi.extend_from_slice(b"LIST");
2367 let hdrl_size_pos = avi.len();
2368 avi.extend_from_slice(&0u32.to_le_bytes()); avi.extend_from_slice(b"hdrl");
2370
2371 avi.extend_from_slice(b"avih");
2373 avi.extend_from_slice(&40u32.to_le_bytes()); let mut avih_data = [0u8; 40];
2375 avih_data[0..4].copy_from_slice(&33333u32.to_le_bytes()); avih_data[16..20].copy_from_slice(&1u32.to_le_bytes()); avih_data[24..28].copy_from_slice(&1u32.to_le_bytes()); avih_data[32..36].copy_from_slice(&320u32.to_le_bytes()); avih_data[36..40].copy_from_slice(&240u32.to_le_bytes()); avi.extend_from_slice(&avih_data);
2381
2382 let hdrl_size = (avi.len() - hdrl_size_pos - 4) as u32;
2384 avi[hdrl_size_pos..hdrl_size_pos + 4].copy_from_slice(&hdrl_size.to_le_bytes());
2385
2386 avi.extend_from_slice(b"LIST");
2388 avi.extend_from_slice(&4u32.to_le_bytes());
2389 avi.extend_from_slice(b"movi");
2390
2391 let riff_size = (avi.len() - 8) as u32;
2393 avi[size_pos..size_pos + 4].copy_from_slice(&riff_size.to_le_bytes());
2394
2395 let container = AviContainer::parse(&avi).unwrap();
2396 assert_eq!(container.main_header.width, 320);
2397 assert_eq!(container.main_header.height, 240);
2398 assert_eq!(container.main_header.microseconds_per_frame, 33333);
2399 assert_eq!(container.main_header.total_frames, 1);
2400 }
2401
2402 #[test]
2403 fn test_avi_container_parse_invalid() {
2404 let data = b"NOT_RIFF_DATA";
2406 assert!(AviContainer::parse(data).is_none());
2407 }
2408
2409 #[test]
2410 fn test_avi_demux_streams() {
2411 let mut container = AviContainer {
2412 main_header: AviMainHeader::default(),
2413 streams: Vec::new(),
2414 index: vec![
2415 AviIndexEntry {
2416 chunk_id: *b"00dc",
2417 flags: 0x10,
2418 offset: 0,
2419 size: 100,
2420 },
2421 AviIndexEntry {
2422 chunk_id: *b"01wb",
2423 flags: 0,
2424 offset: 108,
2425 size: 50,
2426 },
2427 AviIndexEntry {
2428 chunk_id: *b"00dc",
2429 flags: 0,
2430 offset: 166,
2431 size: 100,
2432 },
2433 ],
2434 movi_offset: 0,
2435 file_size: 0,
2436 };
2437
2438 let (video, audio) = container.demux_streams();
2439 assert_eq!(video.len(), 2);
2440 assert_eq!(audio.len(), 1);
2441 assert_eq!(container.video_frame_count(), 2);
2442 assert_eq!(container.audio_chunk_count(), 1);
2443 }
2444}