1use std::sync::Arc;
7
8use oximedia_audio::{AudioFrame, ChannelLayout};
9use oximedia_codec::VideoFrame;
10use oximedia_core::{PixelFormat, SampleFormat, Timestamp};
11
12#[derive(Clone, Debug)]
14pub enum FilterFrame {
15 Video(VideoFrame),
17 Audio(AudioFrame),
19}
20
21impl FilterFrame {
22 #[must_use]
24 pub fn timestamp(&self) -> &Timestamp {
25 match self {
26 Self::Video(f) => &f.timestamp,
27 Self::Audio(f) => &f.timestamp,
28 }
29 }
30
31 #[must_use]
33 pub fn is_video(&self) -> bool {
34 matches!(self, Self::Video(_))
35 }
36
37 #[must_use]
39 pub fn is_audio(&self) -> bool {
40 matches!(self, Self::Audio(_))
41 }
42
43 #[must_use]
45 pub fn as_video(&self) -> Option<&VideoFrame> {
46 match self {
47 Self::Video(f) => Some(f),
48 Self::Audio(_) => None,
49 }
50 }
51
52 #[must_use]
54 pub fn as_audio(&self) -> Option<&AudioFrame> {
55 match self {
56 Self::Video(_) => None,
57 Self::Audio(f) => Some(f),
58 }
59 }
60
61 pub fn as_video_mut(&mut self) -> Option<&mut VideoFrame> {
63 match self {
64 Self::Video(f) => Some(f),
65 Self::Audio(_) => None,
66 }
67 }
68
69 pub fn as_audio_mut(&mut self) -> Option<&mut AudioFrame> {
71 match self {
72 Self::Video(_) => None,
73 Self::Audio(f) => Some(f),
74 }
75 }
76}
77
78impl From<VideoFrame> for FilterFrame {
79 fn from(frame: VideoFrame) -> Self {
80 Self::Video(frame)
81 }
82}
83
84impl From<AudioFrame> for FilterFrame {
85 fn from(frame: AudioFrame) -> Self {
86 Self::Audio(frame)
87 }
88}
89
90#[derive(Clone, Debug)]
95pub struct FrameRef {
96 inner: Arc<FilterFrame>,
97}
98
99impl FrameRef {
100 pub fn new(frame: FilterFrame) -> Self {
102 Self {
103 inner: Arc::new(frame),
104 }
105 }
106
107 #[must_use]
109 pub fn frame(&self) -> &FilterFrame {
110 &self.inner
111 }
112
113 pub fn try_unwrap(self) -> Option<FilterFrame> {
117 Arc::try_unwrap(self.inner).ok()
118 }
119
120 #[must_use]
122 pub fn ref_count(&self) -> usize {
123 Arc::strong_count(&self.inner)
124 }
125
126 #[must_use]
131 pub fn make_mut(self) -> FilterFrame {
132 match Arc::try_unwrap(self.inner) {
133 Ok(frame) => frame,
134 Err(arc) => (*arc).clone(),
135 }
136 }
137}
138
139impl From<FilterFrame> for FrameRef {
140 fn from(frame: FilterFrame) -> Self {
141 Self::new(frame)
142 }
143}
144
145impl From<VideoFrame> for FrameRef {
146 fn from(frame: VideoFrame) -> Self {
147 Self::new(FilterFrame::Video(frame))
148 }
149}
150
151impl From<AudioFrame> for FrameRef {
152 fn from(frame: AudioFrame) -> Self {
153 Self::new(FilterFrame::Audio(frame))
154 }
155}
156
157pub fn simd_copy_frame(src: &[u8], dst: &mut [u8]) {
172 assert_eq!(src.len(), dst.len(), "simd_copy_frame: length mismatch");
173
174 #[cfg(target_arch = "x86_64")]
175 {
176 if is_x86_feature_detected!("avx2") {
177 avx2_copy_safe(src, dst);
178 return;
179 }
180 }
181
182 dst.copy_from_slice(src);
183}
184
185#[cfg(target_arch = "x86_64")]
192#[inline(never)]
193fn avx2_copy_safe(src: &[u8], dst: &mut [u8]) {
194 const CHUNK: usize = 32;
195 let chunks = src.len() / CHUNK;
196 for i in 0..chunks {
198 let offset = i * CHUNK;
199 dst[offset..offset + CHUNK].copy_from_slice(&src[offset..offset + CHUNK]);
200 }
201 let done = chunks * CHUNK;
203 if done < src.len() {
204 dst[done..].copy_from_slice(&src[done..]);
205 }
206}
207
208#[derive(Debug, Clone)]
216pub struct FramePoolConfig {
217 pub pre_allocate: usize,
219 pub max_size: usize,
221 pub frame_bytes: usize,
223}
224
225impl Default for FramePoolConfig {
226 fn default() -> Self {
227 Self {
228 pre_allocate: 0,
229 max_size: 32,
230 frame_bytes: 0,
231 }
232 }
233}
234
235pub struct FramePool {
240 capacity: usize,
242 video_frames: Vec<VideoFrame>,
244 audio_frames: Vec<AudioFrame>,
246 free_list: Vec<Vec<u8>>,
248 raw_max: usize,
250 raw_frame_bytes: usize,
252}
253
254impl FramePool {
255 #[must_use]
257 pub fn new(capacity: usize) -> Self {
258 Self {
259 capacity,
260 video_frames: Vec::with_capacity(capacity),
261 audio_frames: Vec::with_capacity(capacity),
262 free_list: Vec::new(),
263 raw_max: capacity,
264 raw_frame_bytes: 0,
265 }
266 }
267
268 #[must_use]
275 pub fn with_config(config: FramePoolConfig) -> Self {
276 let pre = config.pre_allocate.min(config.max_size);
277 let mut free_list = Vec::with_capacity(pre.max(config.max_size));
278 for _ in 0..pre {
279 free_list.push(vec![0u8; config.frame_bytes]);
280 }
281 Self {
282 capacity: config.max_size,
283 video_frames: Vec::new(),
284 audio_frames: Vec::new(),
285 free_list,
286 raw_max: config.max_size,
287 raw_frame_bytes: config.frame_bytes,
288 }
289 }
290
291 pub fn acquire_raw(&mut self) -> Vec<u8> {
297 self.free_list.pop().unwrap_or_else(|| {
298 if self.raw_frame_bytes > 0 {
299 vec![0u8; self.raw_frame_bytes]
300 } else {
301 Vec::new()
302 }
303 })
304 }
305
306 pub fn release_raw(&mut self, buf: Vec<u8>) {
310 if self.free_list.len() < self.raw_max {
311 self.free_list.push(buf);
312 }
313 }
314
315 #[must_use]
317 pub fn pre_allocated_count(&self) -> usize {
318 self.free_list.len()
319 }
320
321 #[must_use]
323 pub fn get_video_frame(&mut self, format: PixelFormat, width: u32, height: u32) -> VideoFrame {
324 if let Some(pos) = self
326 .video_frames
327 .iter()
328 .position(|f| f.format == format && f.width == width && f.height == height)
329 {
330 return self.video_frames.swap_remove(pos);
331 }
332
333 let mut frame = VideoFrame::new(format, width, height);
335 frame.allocate();
336 frame
337 }
338
339 pub fn return_video_frame(&mut self, frame: VideoFrame) {
341 if self.video_frames.len() < self.capacity {
342 self.video_frames.push(frame);
343 }
344 }
345
346 #[must_use]
348 pub fn get_audio_frame(
349 &mut self,
350 format: SampleFormat,
351 sample_rate: u32,
352 channels: ChannelLayout,
353 ) -> AudioFrame {
354 if let Some(pos) = self.audio_frames.iter().position(|f| {
356 f.format == format && f.sample_rate == sample_rate && f.channels == channels
357 }) {
358 return self.audio_frames.swap_remove(pos);
359 }
360
361 AudioFrame::new(format, sample_rate, channels)
363 }
364
365 pub fn return_audio_frame(&mut self, frame: AudioFrame) {
367 if self.audio_frames.len() < self.capacity {
368 self.audio_frames.push(frame);
369 }
370 }
371
372 pub fn clear(&mut self) {
374 self.video_frames.clear();
375 self.audio_frames.clear();
376 }
377
378 #[must_use]
380 pub fn video_frame_count(&self) -> usize {
381 self.video_frames.len()
382 }
383
384 #[must_use]
386 pub fn audio_frame_count(&self) -> usize {
387 self.audio_frames.len()
388 }
389}
390
391impl Default for FramePool {
392 fn default() -> Self {
393 Self::new(16)
394 }
395}
396
397#[derive(Clone, Debug)]
409pub enum SharedFrame {
410 Owned(Vec<u8>),
412 Shared(Arc<Vec<u8>>),
414}
415
416impl SharedFrame {
417 #[must_use]
419 pub fn as_bytes(&self) -> &[u8] {
420 match self {
421 Self::Owned(v) => v.as_slice(),
422 Self::Shared(arc) => arc.as_slice(),
423 }
424 }
425
426 #[must_use]
433 pub fn into_owned(self) -> Vec<u8> {
434 match self {
435 Self::Owned(v) => v,
436 Self::Shared(arc) => match Arc::try_unwrap(arc) {
437 Ok(v) => v,
438 Err(arc) => (*arc).clone(),
439 },
440 }
441 }
442
443 #[must_use]
448 pub fn try_share(&self) -> Arc<Vec<u8>> {
449 match self {
450 Self::Owned(v) => Arc::new(v.clone()),
451 Self::Shared(arc) => Arc::clone(arc),
452 }
453 }
454
455 #[must_use]
459 pub fn promote(self) -> Self {
460 match self {
461 Self::Owned(v) => Self::Shared(Arc::new(v)),
462 already_shared => already_shared,
463 }
464 }
465
466 #[must_use]
468 pub fn is_shared(&self) -> bool {
469 matches!(self, Self::Shared(_))
470 }
471
472 #[must_use]
474 pub fn is_owned(&self) -> bool {
475 matches!(self, Self::Owned(_))
476 }
477}
478
479impl From<Vec<u8>> for SharedFrame {
480 fn from(v: Vec<u8>) -> Self {
481 Self::Owned(v)
482 }
483}
484
485impl From<Arc<Vec<u8>>> for SharedFrame {
486 fn from(arc: Arc<Vec<u8>>) -> Self {
487 Self::Shared(arc)
488 }
489}
490
491pub trait ZeroCopyPort {
497 fn accepts_zero_copy(&self) -> bool;
500
501 fn pass_frame(&self, frame: SharedFrame) -> SharedFrame;
507}
508
509#[cfg(test)]
512mod tests {
513 use super::*;
514
515 #[test]
516 fn test_filter_frame_video() {
517 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
518 let frame = FilterFrame::Video(video);
519
520 assert!(frame.is_video());
521 assert!(!frame.is_audio());
522 assert!(frame.as_video().is_some());
523 assert!(frame.as_audio().is_none());
524 }
525
526 #[test]
527 fn test_filter_frame_audio() {
528 let audio = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Stereo);
529 let frame = FilterFrame::Audio(audio);
530
531 assert!(!frame.is_video());
532 assert!(frame.is_audio());
533 assert!(frame.as_video().is_none());
534 assert!(frame.as_audio().is_some());
535 }
536
537 #[test]
538 fn test_frame_ref() {
539 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
540 let frame = FilterFrame::Video(video);
541 let frame_ref = FrameRef::new(frame);
542
543 assert_eq!(frame_ref.ref_count(), 1);
544
545 let frame_ref2 = frame_ref.clone();
546 assert_eq!(frame_ref.ref_count(), 2);
547 assert_eq!(frame_ref2.ref_count(), 2);
548 }
549
550 #[test]
551 fn test_frame_ref_try_unwrap() {
552 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
553 let frame = FilterFrame::Video(video);
554 let frame_ref = FrameRef::new(frame);
555
556 let unwrapped = frame_ref.try_unwrap();
558 assert!(unwrapped.is_some());
559 }
560
561 #[test]
562 fn test_frame_ref_make_mut() {
563 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
564 let frame = FilterFrame::Video(video);
565 let frame_ref = FrameRef::new(frame);
566 let frame_ref2 = frame_ref.clone();
567
568 let owned = frame_ref.make_mut();
570 assert!(owned.is_video());
571
572 assert!(frame_ref2.frame().is_video());
574 }
575
576 #[test]
577 fn test_frame_pool() {
578 let mut pool = FramePool::new(4);
579
580 let frame = pool.get_video_frame(PixelFormat::Yuv420p, 1920, 1080);
582 assert_eq!(frame.width, 1920);
583 assert_eq!(frame.height, 1080);
584
585 pool.return_video_frame(frame);
587 assert_eq!(pool.video_frame_count(), 1);
588
589 let frame2 = pool.get_video_frame(PixelFormat::Yuv420p, 1920, 1080);
591 assert_eq!(frame2.width, 1920);
592 assert_eq!(pool.video_frame_count(), 0);
593 }
594
595 #[test]
596 fn test_filter_frame_from() {
597 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
598 let frame: FilterFrame = video.into();
599 assert!(frame.is_video());
600
601 let audio = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Stereo);
602 let frame: FilterFrame = audio.into();
603 assert!(frame.is_audio());
604 }
605
606 #[test]
607 fn test_frame_timestamp() {
608 use oximedia_core::Rational;
609
610 let mut video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
611 video.timestamp = Timestamp::new(1000, Rational::new(1, 1000));
612 let frame = FilterFrame::Video(video);
613
614 assert_eq!(frame.timestamp().pts, 1000);
615 }
616
617 #[test]
620 fn test_simd_copy_correctness() {
621 let src: Vec<u8> = (0u32..4096).map(|i| (i % 251) as u8).collect();
623 let mut dst = vec![0u8; 4096];
624 super::simd_copy_frame(&src, &mut dst);
625 assert_eq!(src, dst, "4096-byte copy must be byte-perfect");
626 }
627
628 #[test]
629 fn test_simd_copy_non_aligned() {
630 let src: Vec<u8> = (0u32..4097).map(|i| (i % 197) as u8).collect();
632 let mut dst = vec![0u8; 4097];
633 super::simd_copy_frame(&src, &mut dst);
634 assert_eq!(src, dst, "4097-byte copy (tail) must be byte-perfect");
635 }
636
637 #[test]
640 fn test_pool_pre_allocation_count() {
641 let config = FramePoolConfig {
642 pre_allocate: 5,
643 max_size: 10,
644 frame_bytes: 64,
645 };
646 let pool = FramePool::with_config(config);
647 assert_eq!(
648 pool.pre_allocated_count(),
649 5,
650 "pool must expose 5 pre-allocated buffers before any acquire"
651 );
652 }
653
654 #[test]
655 fn test_pool_pre_allocation_acquire() {
656 let config = FramePoolConfig {
657 pre_allocate: 5,
658 max_size: 10,
659 frame_bytes: 64,
660 };
661 let mut pool = FramePool::with_config(config);
662
663 for i in 0..5 {
665 let buf = pool.acquire_raw();
666 assert_eq!(buf.len(), 64, "pre-allocated buffer {i} must be 64 bytes");
667 }
668 assert_eq!(
669 pool.pre_allocated_count(),
670 0,
671 "free-list should be empty now"
672 );
673
674 let buf6 = pool.acquire_raw();
676 assert_eq!(buf6.len(), 64, "6th (dynamic) buffer must also be 64 bytes");
677 }
678
679 #[test]
682 fn test_shared_frame_zero_copy_count() {
683 let data: Vec<u8> = vec![1, 2, 3, 4];
684 let arc = Arc::new(data);
685 let frame = SharedFrame::Shared(Arc::clone(&arc));
686
687 let shared = frame.try_share();
690 assert_eq!(
692 Arc::strong_count(&shared),
693 3,
694 "strong_count must be 3 after arc + frame + try_share clone"
695 );
696 }
697
698 #[test]
699 fn test_shared_frame_into_owned_clone() {
700 let data: Vec<u8> = vec![10, 20, 30];
701 let arc = Arc::new(data.clone());
702 let _second_ref = Arc::clone(&arc);
704 let frame = SharedFrame::Shared(arc);
705
706 let owned = frame.into_owned();
707 assert_eq!(owned, data, "cloned bytes must match the original");
708 }
709
710 #[test]
711 fn test_owned_frame_to_shared() {
712 let data: Vec<u8> = vec![7, 8, 9];
713 let frame = SharedFrame::Owned(data.clone());
714
715 assert!(frame.is_owned());
716
717 let promoted = frame.promote();
718 assert!(
719 promoted.is_shared(),
720 "Owned frame must become Shared after promote()"
721 );
722
723 assert_eq!(promoted.as_bytes(), data.as_slice());
725 }
726}