1use std::sync::Arc;
9use std::time::Duration;
10
11use crate::color::ColorSpace;
12use crate::error::{Error, Result};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16#[non_exhaustive]
17pub enum PixelFormat {
18 I420,
21 I422,
23 I444,
25 Nv12,
28 P010,
30 Rgba8,
32 Bgra8,
34}
35
36impl PixelFormat {
37 pub fn plane_count(self) -> usize {
39 match self {
40 PixelFormat::I420 | PixelFormat::I422 | PixelFormat::I444 => 3,
41 PixelFormat::Nv12 | PixelFormat::P010 => 2,
42 PixelFormat::Rgba8 | PixelFormat::Bgra8 => 1,
43 }
44 }
45
46 pub fn bit_depth(self) -> u32 {
48 match self {
49 PixelFormat::P010 => 10,
50 _ => 8,
51 }
52 }
53
54 pub fn bytes_per_sample(self) -> usize {
57 match self {
58 PixelFormat::P010 => 2,
59 _ => 1,
60 }
61 }
62
63 pub fn is_yuv(self) -> bool {
65 !matches!(self, PixelFormat::Rgba8 | PixelFormat::Bgra8)
66 }
67
68 pub fn chroma_shift(self) -> (u32, u32) {
73 match self {
74 PixelFormat::I420 | PixelFormat::Nv12 | PixelFormat::P010 => (1, 1),
75 PixelFormat::I422 => (1, 0),
76 _ => (0, 0),
77 }
78 }
79
80 pub fn plane_dimensions(self, plane: usize, width: u32, height: u32) -> Option<(u32, u32)> {
85 if plane >= self.plane_count() {
86 return None;
87 }
88
89 if plane == 0 {
92 return Some((width, height));
93 }
94
95 let (h_shift, v_shift) = self.chroma_shift();
96 let chroma_width = width.div_ceil(1 << h_shift);
97 let chroma_height = height.div_ceil(1 << v_shift);
98
99 Some((chroma_width, chroma_height))
100 }
101
102 pub fn samples_per_pixel(self, plane: usize) -> usize {
105 match (self, plane) {
106 (PixelFormat::Rgba8 | PixelFormat::Bgra8, _) => 4,
107 (PixelFormat::Nv12 | PixelFormat::P010, 1) => 2,
108 _ => 1,
109 }
110 }
111
112 pub fn tight_stride(self, plane: usize, width: u32) -> usize {
114 let (plane_width, _) = self.plane_dimensions(plane, width, 1).unwrap_or((width, 1));
115
116 plane_width as usize * self.samples_per_pixel(plane) * self.bytes_per_sample()
117 }
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122pub struct Plane {
123 pub offset: usize,
125 pub stride: usize,
131}
132
133#[derive(Clone, Debug)]
138pub struct Frame {
139 width: u32,
140 height: u32,
141 format: PixelFormat,
142 color: ColorSpace,
143 pts: Duration,
144 planes: Vec<Plane>,
145 data: Arc<Vec<u8>>,
146}
147
148impl Frame {
149 pub fn packed(
156 width: u32,
157 height: u32,
158 format: PixelFormat,
159 color: ColorSpace,
160 pts: Duration,
161 data: Vec<u8>,
162 ) -> Result<Self> {
163 let (planes, needed) = packed_layout(format, width, height)?;
164
165 if data.len() < needed {
166 return Err(Error::BadFrame {
167 reason: format!(
168 "{width}×{height} {format:?} needs {needed} bytes, got {}",
169 data.len()
170 ),
171 });
172 }
173
174 Ok(Frame {
175 width,
176 height,
177 format,
178 color,
179 pts,
180 planes,
181 data: Arc::new(data),
182 })
183 }
184
185 pub fn with_planes(
192 width: u32,
193 height: u32,
194 format: PixelFormat,
195 color: ColorSpace,
196 pts: Duration,
197 planes: Vec<Plane>,
198 data: Vec<u8>,
199 ) -> Result<Self> {
200 if width == 0 || height == 0 {
201 return Err(Error::BadFrame {
202 reason: format!("a {width}×{height} picture has no pixels"),
203 });
204 }
205
206 if planes.len() != format.plane_count() {
207 return Err(Error::BadFrame {
208 reason: format!(
209 "{format:?} has {} planes, {} given",
210 format.plane_count(),
211 planes.len()
212 ),
213 });
214 }
215
216 for (index, plane) in planes.iter().enumerate() {
217 let (plane_width, plane_height) = format
218 .plane_dimensions(index, width, height)
219 .expect("plane count was checked");
220
221 let row_bytes =
222 plane_width as usize * format.samples_per_pixel(index) * format.bytes_per_sample();
223
224 if plane.stride < row_bytes {
225 return Err(Error::BadFrame {
226 reason: format!(
227 "plane {index}: stride {} is shorter than a {row_bytes}-byte row",
228 plane.stride
229 ),
230 });
231 }
232
233 let span = plane
236 .stride
237 .checked_mul(plane_height.saturating_sub(1) as usize)
238 .and_then(|full| full.checked_add(row_bytes))
239 .and_then(|span| span.checked_add(plane.offset))
240 .ok_or_else(|| Error::BadFrame {
241 reason: format!("plane {index}: the layout overflows a usize"),
242 })?;
243
244 if span > data.len() {
245 return Err(Error::BadFrame {
246 reason: format!(
247 "plane {index} runs to byte {span} of a {}-byte buffer",
248 data.len()
249 ),
250 });
251 }
252 }
253
254 Ok(Frame {
255 width,
256 height,
257 format,
258 color,
259 pts,
260 planes,
261 data: Arc::new(data),
262 })
263 }
264
265 pub fn width(&self) -> u32 {
267 self.width
268 }
269
270 pub fn height(&self) -> u32 {
272 self.height
273 }
274
275 pub fn format(&self) -> PixelFormat {
277 self.format
278 }
279
280 pub fn color(&self) -> ColorSpace {
282 self.color
283 }
284
285 pub fn pts(&self) -> Duration {
287 self.pts
288 }
289
290 pub fn with_pts(&self, pts: Duration) -> Self {
293 Frame {
294 pts,
295 data: Arc::clone(&self.data),
296 planes: self.planes.clone(),
297 ..*self
298 }
299 }
300
301 pub fn planes(&self) -> &[Plane] {
303 &self.planes
304 }
305
306 pub fn plane_data(&self, plane: usize) -> Option<&[u8]> {
308 let descriptor = self.planes.get(plane)?;
309 let (plane_width, plane_height) =
310 self.format
311 .plane_dimensions(plane, self.width, self.height)?;
312
313 let row_bytes = plane_width as usize
314 * self.format.samples_per_pixel(plane)
315 * self.format.bytes_per_sample();
316 let span = descriptor.stride * (plane_height as usize - 1) + row_bytes;
317
318 self.data.get(descriptor.offset..descriptor.offset + span)
319 }
320
321 pub fn row(&self, plane: usize, row: u32) -> Option<&[u8]> {
323 let descriptor = self.planes.get(plane)?;
324 let (plane_width, plane_height) =
325 self.format
326 .plane_dimensions(plane, self.width, self.height)?;
327
328 if row >= plane_height {
329 return None;
330 }
331
332 let row_bytes = plane_width as usize
333 * self.format.samples_per_pixel(plane)
334 * self.format.bytes_per_sample();
335 let start = descriptor.offset + descriptor.stride * row as usize;
336
337 self.data.get(start..start + row_bytes)
338 }
339
340 pub fn data(&self) -> &[u8] {
342 &self.data
343 }
344
345 pub fn byte_len(&self) -> usize {
348 self.data.len()
349 }
350}
351
352fn packed_layout(format: PixelFormat, width: u32, height: u32) -> Result<(Vec<Plane>, usize)> {
354 if width == 0 || height == 0 {
355 return Err(Error::BadFrame {
356 reason: format!("a {width}×{height} picture has no pixels"),
357 });
358 }
359
360 let mut planes = Vec::with_capacity(format.plane_count());
361 let mut offset = 0_usize;
362
363 for index in 0..format.plane_count() {
364 let (plane_width, plane_height) = format
365 .plane_dimensions(index, width, height)
366 .expect("index is below the plane count");
367
368 let stride =
369 plane_width as usize * format.samples_per_pixel(index) * format.bytes_per_sample();
370
371 planes.push(Plane { offset, stride });
372
373 offset = stride
374 .checked_mul(plane_height as usize)
375 .and_then(|size| size.checked_add(offset))
376 .ok_or_else(|| Error::BadFrame {
377 reason: format!("{width}×{height} {format:?} does not fit in memory"),
378 })?;
379 }
380
381 Ok((planes, offset))
382}
383
384pub fn packed_size(format: PixelFormat, width: u32, height: u32) -> Result<usize> {
388 packed_layout(format, width, height).map(|(_, size)| size)
389}
390
391#[derive(Debug, Default)]
397pub struct FramePool {
398 buffers: Vec<Vec<u8>>,
399 capacity: usize,
400 reused: u64,
401 allocated: u64,
402}
403
404impl FramePool {
405 pub fn new(capacity: usize) -> Self {
410 FramePool {
411 buffers: Vec::with_capacity(capacity),
412 capacity,
413 reused: 0,
414 allocated: 0,
415 }
416 }
417
418 pub fn take(&mut self, size: usize) -> Vec<u8> {
420 if let Some(index) = self
423 .buffers
424 .iter()
425 .position(|buffer| buffer.capacity() >= size)
426 {
427 let mut buffer = self.buffers.swap_remove(index);
428 buffer.clear();
429 buffer.resize(size, 0);
430 self.reused += 1;
431
432 return buffer;
433 }
434
435 self.allocated += 1;
436
437 vec![0; size]
438 }
439
440 pub fn give(&mut self, buffer: Vec<u8>) {
442 if self.buffers.len() < self.capacity {
443 self.buffers.push(buffer);
444 }
445 }
446
447 pub fn reclaim(&mut self, frame: Frame) {
452 if let Ok(buffer) = Arc::try_unwrap(frame.data) {
453 self.give(buffer);
454 }
455 }
456
457 pub fn statistics(&self) -> (u64, u64) {
460 (self.reused, self.allocated)
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 fn frame_of(format: PixelFormat, width: u32, height: u32) -> Frame {
469 let size = packed_size(format, width, height).unwrap();
470
471 Frame::packed(
472 width,
473 height,
474 format,
475 ColorSpace::default(),
476 Duration::ZERO,
477 vec![0; size],
478 )
479 .unwrap()
480 }
481
482 #[test]
483 fn i420_is_one_and_a_half_bytes_a_pixel() {
484 let size = packed_size(PixelFormat::I420, 1920, 1080).unwrap();
485
486 assert_eq!(size, 1920 * 1080 * 3 / 2);
487 }
488
489 #[test]
492 fn odd_dimensions_round_chroma_up() {
493 let format = PixelFormat::I420;
494
495 assert_eq!(format.plane_dimensions(1, 1921, 1081), Some((961, 541)));
496
497 let size = packed_size(format, 1921, 1081).unwrap();
498 assert_eq!(size, 1921 * 1081 + 2 * (961 * 541));
499 }
500
501 #[test]
502 fn nv12_has_two_planes_and_interleaved_chroma() {
503 assert_eq!(PixelFormat::Nv12.plane_count(), 2);
504 assert_eq!(PixelFormat::Nv12.samples_per_pixel(1), 2);
505
506 let size = packed_size(PixelFormat::Nv12, 640, 480).unwrap();
507 assert_eq!(size, 640 * 480 * 3 / 2);
508 }
509
510 #[test]
511 fn p010_takes_two_bytes_a_sample() {
512 assert_eq!(PixelFormat::P010.bit_depth(), 10);
513 assert_eq!(
514 packed_size(PixelFormat::P010, 640, 480).unwrap(),
515 640 * 480 * 3
516 );
517 }
518
519 #[test]
520 fn a_frame_too_small_for_its_dimensions_is_refused() {
521 let error = Frame::packed(
522 1920,
523 1080,
524 PixelFormat::I420,
525 ColorSpace::default(),
526 Duration::ZERO,
527 vec![0; 100],
528 );
529
530 assert!(matches!(error, Err(Error::BadFrame { .. })));
531 }
532
533 #[test]
534 fn a_zero_sized_picture_is_refused() {
535 assert!(matches!(
536 packed_size(PixelFormat::I420, 0, 1080),
537 Err(Error::BadFrame { .. })
538 ));
539 }
540
541 #[test]
545 fn padded_strides_are_accepted_and_the_last_row_need_not_be_padded() {
546 let width = 100_u32;
547 let height = 10_u32;
548 let stride = 128_usize;
549
550 let luma = stride * (height as usize - 1) + width as usize;
551 let chroma_rows = height.div_ceil(2) as usize;
552 let chroma = stride * (chroma_rows - 1) + width.div_ceil(2) as usize;
553
554 let planes = vec![
555 Plane { offset: 0, stride },
556 Plane {
557 offset: luma,
558 stride,
559 },
560 Plane {
561 offset: luma + chroma,
562 stride,
563 },
564 ];
565
566 let total = luma + 2 * chroma;
567
568 let frame = Frame::with_planes(
569 width,
570 height,
571 PixelFormat::I420,
572 ColorSpace::default(),
573 Duration::ZERO,
574 planes,
575 vec![0; total],
576 )
577 .unwrap();
578
579 assert_eq!(frame.row(0, 9).unwrap().len(), width as usize);
580 assert!(frame.row(0, 10).is_none(), "there is no eleventh row");
581 }
582
583 #[test]
584 fn a_stride_shorter_than_a_row_is_refused() {
585 let planes = vec![
586 Plane {
587 offset: 0,
588 stride: 10,
589 },
590 Plane {
591 offset: 0,
592 stride: 10,
593 },
594 Plane {
595 offset: 0,
596 stride: 10,
597 },
598 ];
599
600 let error = Frame::with_planes(
601 100,
602 10,
603 PixelFormat::I420,
604 ColorSpace::default(),
605 Duration::ZERO,
606 planes,
607 vec![0; 10_000],
608 );
609
610 let Err(Error::BadFrame { reason }) = error else {
611 panic!("a 10-byte stride cannot hold a 100-byte row");
612 };
613
614 assert!(reason.contains("stride"), "{reason}");
615 }
616
617 #[test]
620 fn a_plane_that_runs_past_the_buffer_is_refused() {
621 let planes = vec![
622 Plane {
623 offset: 0,
624 stride: 1024,
625 },
626 Plane {
627 offset: 0,
628 stride: 1024,
629 },
630 Plane {
631 offset: 0,
632 stride: 1024,
633 },
634 ];
635
636 let error = Frame::with_planes(
637 100,
638 100,
639 PixelFormat::I420,
640 ColorSpace::default(),
641 Duration::ZERO,
642 planes,
643 vec![0; 1000],
644 );
645
646 assert!(matches!(error, Err(Error::BadFrame { .. })));
647 }
648
649 #[test]
650 fn retiming_shares_the_pixels() {
651 let frame = frame_of(PixelFormat::I420, 64, 64);
652 let moved = frame.with_pts(Duration::from_millis(500));
653
654 assert_eq!(moved.pts(), Duration::from_millis(500));
655 assert_eq!(frame.pts(), Duration::ZERO);
656 assert!(
657 Arc::ptr_eq(&frame.data, &moved.data),
658 "retiming copied the picture"
659 );
660 }
661
662 #[test]
663 fn the_pool_stops_allocating_once_it_is_warm() {
664 let mut pool = FramePool::new(4);
665 let size = packed_size(PixelFormat::I420, 320, 240).unwrap();
666
667 for _ in 0..10 {
668 let buffer = pool.take(size);
669 pool.give(buffer);
670 }
671
672 let (reused, allocated) = pool.statistics();
673 assert_eq!(allocated, 1, "one allocation should have served all ten");
674 assert_eq!(reused, 9);
675 }
676
677 #[test]
678 fn the_pool_will_not_take_a_buffer_still_being_read() {
679 let mut pool = FramePool::new(4);
680 let frame = frame_of(PixelFormat::I420, 64, 64);
681 let still_held = frame.clone();
682
683 pool.reclaim(frame);
684 assert_eq!(pool.buffers.len(), 0, "that buffer is still in use");
685
686 drop(still_held);
687 }
688}