1use core::ptr::{addr_of, read_unaligned};
11
12use ffmpeg_next::ffi::{
13 AV_NOPTS_VALUE, AVChromaLocation, AVColorPrimaries, AVColorRange, AVColorSpace,
14 AVColorTransferCharacteristic, AVFrame, AVPictureType, AVSubtitleType, av_buffer_alloc,
15};
16use mediadecode::{
17 PixelFormat, Timebase, Timestamp,
18 channel::AudioChannelLayout,
19 color::{ChromaLocation, ColorInfo, ColorMatrix, ColorPrimaries, ColorRange, ColorTransfer},
20 frame::{AudioFrame, Dimensions, Plane, Rect, SubtitleFrame, VideoFrame},
21 subtitle::SubtitlePayload,
22};
23
24use crate::{
25 FfmpegBuffer, boundary,
26 extras::{AudioFrameExtra, PictureType, SideDataEntry, SubtitleFrameExtra, VideoFrameExtra},
27 pixdesc,
28 sample_format::SampleFormat,
29};
30
31#[derive(Debug, Clone)]
33#[non_exhaustive]
34pub enum ConvertError {
35 NullFrame,
37 UnsupportedPixelFormat(PixelFormat),
40 InvalidPlaneLayout {
42 plane: usize,
44 },
45 BufferAcquireFailed {
48 plane: usize,
50 },
51}
52
53impl core::fmt::Display for ConvertError {
54 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55 match self {
56 Self::NullFrame => write!(f, "convert: AVFrame pointer was null"),
57 Self::UnsupportedPixelFormat(pf) => {
58 write!(f, "convert: unsupported pixel format {pf:?}")
59 }
60 Self::InvalidPlaneLayout { plane } => {
61 write!(f, "convert: invalid layout on plane {plane}")
62 }
63 Self::BufferAcquireFailed { plane } => {
64 write!(f, "convert: could not acquire buffer ref for plane {plane}")
65 }
66 }
67 }
68}
69
70impl core::error::Error for ConvertError {}
71
72pub fn video_frame_from(
79 frame: &ffmpeg_next::Frame,
80 time_base: Timebase,
81) -> Result<VideoFrame<mediadecode::PixelFormat, VideoFrameExtra, FfmpegBuffer>, ConvertError> {
82 unsafe { av_frame_to_video_frame(frame.as_ptr(), time_base) }
85}
86
87pub fn audio_frame_from(
90 frame: &ffmpeg_next::frame::Audio,
91 time_base: Timebase,
92) -> Result<AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer>, ConvertError>
93{
94 unsafe { av_frame_to_audio_frame(frame.as_ptr(), time_base) }
97}
98
99pub fn subtitle_frame_from(
102 subtitle: &ffmpeg_next::Subtitle,
103 time_base: Timebase,
104) -> Result<SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer>, ConvertError> {
105 unsafe { av_subtitle_to_subtitle_frame(subtitle.as_ptr(), time_base) }
108}
109
110pub unsafe fn av_frame_to_video_frame(
123 av_frame: *const AVFrame,
124 time_base: Timebase,
125) -> Result<VideoFrame<mediadecode::PixelFormat, VideoFrameExtra, FfmpegBuffer>, ConvertError> {
126 if av_frame.is_null() {
127 return Err(ConvertError::NullFrame);
128 }
129 let format_raw = unsafe { (*av_frame).format };
144 let width_raw = unsafe { (*av_frame).width };
145 let height_raw = unsafe { (*av_frame).height };
146 let pts_raw = unsafe { (*av_frame).pts };
147 let duration_raw = unsafe { (*av_frame).duration };
148 let pix_fmt = boundary::from_av_pixel_format(format_raw);
149 let width = width_raw.max(0) as u32;
150 let height = height_raw.max(0) as u32;
151
152 if !pixdesc::is_deliverable(pix_fmt) {
158 return Err(ConvertError::UnsupportedPixelFormat(pix_fmt));
159 }
160 let geom = match pixdesc::plane_geometry(pix_fmt, width as usize, height as usize) {
168 Some(g) => g,
169 None => return Err(ConvertError::UnsupportedPixelFormat(pix_fmt)),
170 };
171
172 let mut planes_out: [Plane<FfmpegBuffer>; 4] = [
173 plane_placeholder()?,
174 plane_placeholder()?,
175 plane_placeholder()?,
176 plane_placeholder()?,
177 ];
178 let mut plane_count: u8 = 0;
179
180 #[allow(clippy::needless_range_loop)]
189 for plane_idx in 0..geom.count {
190 let linesize = unsafe { (*av_frame).linesize[plane_idx] };
193 if linesize <= 0 {
194 return Err(ConvertError::InvalidPlaneLayout { plane: plane_idx });
200 }
201 let data_ptr = unsafe { (*av_frame).data[plane_idx] };
202 if data_ptr.is_null() {
203 return Err(ConvertError::InvalidPlaneLayout { plane: plane_idx });
204 }
205 let plane_h = geom.height[plane_idx];
206 let row_bytes = geom.row_bytes[plane_idx];
207 if row_bytes > linesize as usize {
208 return Err(ConvertError::InvalidPlaneLayout { plane: plane_idx });
209 }
210 let (view, exported_stride) = if (linesize as usize) == row_bytes {
226 let plane_bytes = (plane_h)
227 .checked_mul(linesize as usize)
228 .ok_or(ConvertError::InvalidPlaneLayout { plane: plane_idx })?;
229 let buf = unsafe { find_backing_buffer(av_frame, data_ptr, plane_bytes) }
230 .ok_or(ConvertError::BufferAcquireFailed { plane: plane_idx })?;
231 let offset = unsafe { (data_ptr as usize).wrapping_sub((*buf).data as usize) };
235 let view = unsafe { FfmpegBuffer::from_ref_view(buf, offset, plane_bytes) }
238 .ok_or(ConvertError::BufferAcquireFailed { plane: plane_idx })?;
239 (view, linesize as u32)
240 } else {
241 let total_bytes = row_bytes
242 .checked_mul(plane_h)
243 .ok_or(ConvertError::InvalidPlaneLayout { plane: plane_idx })?;
244 let last_row_offset = (plane_h.saturating_sub(1))
254 .checked_mul(linesize as usize)
255 .ok_or(ConvertError::InvalidPlaneLayout { plane: plane_idx })?;
256 let readable_extent = last_row_offset
257 .checked_add(row_bytes)
258 .ok_or(ConvertError::InvalidPlaneLayout { plane: plane_idx })?;
259 unsafe { find_backing_buffer(av_frame, data_ptr, readable_extent) }
264 .ok_or(ConvertError::BufferAcquireFailed { plane: plane_idx })?;
265 let mut packed: std::vec::Vec<u8> = std::vec::Vec::new();
266 packed
267 .try_reserve_exact(total_bytes)
268 .map_err(|_| ConvertError::BufferAcquireFailed { plane: plane_idx })?;
269 for row_idx in 0..plane_h {
270 let row_offset = (row_idx)
271 .checked_mul(linesize as usize)
272 .ok_or(ConvertError::InvalidPlaneLayout { plane: plane_idx })?;
273 let row_slice =
278 unsafe { core::slice::from_raw_parts(data_ptr.add(row_offset) as *const u8, row_bytes) };
279 packed.extend_from_slice(row_slice);
280 }
281 let buf = FfmpegBuffer::copy_from_slice(&packed)
282 .ok_or(ConvertError::BufferAcquireFailed { plane: plane_idx })?;
283 (buf, row_bytes as u32)
284 };
285
286 planes_out[plane_idx] = Plane::new(view, exported_stride);
287 plane_count = (plane_idx + 1) as u8;
288 }
289
290 let pts = if pts_raw != AV_NOPTS_VALUE {
292 Some(Timestamp::new(pts_raw, time_base))
293 } else {
294 None
295 };
296 let duration = if duration_raw > 0 {
297 Some(Timestamp::new(duration_raw, time_base))
298 } else {
299 None
300 };
301
302 let visible_rect = unsafe { build_visible_rect(av_frame, width, height) };
304
305 let color_primaries_raw =
317 unsafe { read_unaligned(addr_of!((*av_frame).color_primaries) as *const i32) };
318 let color_trc_raw = unsafe { read_unaligned(addr_of!((*av_frame).color_trc) as *const i32) };
319 let colorspace_raw = unsafe { read_unaligned(addr_of!((*av_frame).colorspace) as *const i32) };
320 let color_range_raw = unsafe { read_unaligned(addr_of!((*av_frame).color_range) as *const i32) };
321 let chroma_location_raw =
322 unsafe { read_unaligned(addr_of!((*av_frame).chroma_location) as *const i32) };
323 let color = ColorInfo::UNSPECIFIED
324 .with_primaries(map_primaries(color_primaries_raw))
325 .with_transfer(map_transfer(color_trc_raw))
326 .with_matrix(map_matrix(colorspace_raw))
327 .with_range(map_range_for(pix_fmt, color_range_raw))
328 .with_chroma_location(map_chroma_loc(chroma_location_raw));
329
330 let extra = unsafe { build_video_frame_extra(av_frame) };
332
333 let mut out = VideoFrame::new(
336 Dimensions::new(width, height),
337 pix_fmt,
338 planes_out,
339 plane_count,
340 extra,
341 )
342 .with_pts(pts)
343 .with_duration(duration)
344 .with_color(color);
345 if let Some(r) = visible_rect {
346 out = out.with_visible_rect(Some(r));
347 }
348 Ok(out)
349}
350
351fn plane_placeholder() -> Result<Plane<FfmpegBuffer>, ConvertError> {
352 let raw = unsafe { av_buffer_alloc(0) };
356 let raw = if raw.is_null() {
359 unsafe { av_buffer_alloc(1) }
360 } else {
361 raw
362 };
363 if raw.is_null() {
364 return Err(ConvertError::BufferAcquireFailed { plane: 4 });
366 }
367 let buf =
368 unsafe { FfmpegBuffer::take(raw) }.ok_or(ConvertError::BufferAcquireFailed { plane: 4 })?;
369 Ok(Plane::new(buf, 0))
370}
371
372unsafe fn build_visible_rect(av_frame: *const AVFrame, width: u32, height: u32) -> Option<Rect> {
378 let crop_left = unsafe { (*av_frame).crop_left } as u32;
379 let crop_top = unsafe { (*av_frame).crop_top } as u32;
380 let crop_right = unsafe { (*av_frame).crop_right } as u32;
381 let crop_bottom = unsafe { (*av_frame).crop_bottom } as u32;
382 if crop_left == 0 && crop_top == 0 && crop_right == 0 && crop_bottom == 0 {
383 return None;
384 }
385 let x = crop_left;
386 let y = crop_top;
387 let w = width.saturating_sub(crop_left).saturating_sub(crop_right);
388 let h = height.saturating_sub(crop_top).saturating_sub(crop_bottom);
389 Some(Rect::new(x, y, w, h))
390}
391
392unsafe fn build_video_frame_extra(av_frame: *const AVFrame) -> VideoFrameExtra {
397 let mut out = VideoFrameExtra::default();
398 let sar_num = unsafe { (*av_frame).sample_aspect_ratio.num };
400 let sar_den = unsafe { (*av_frame).sample_aspect_ratio.den };
401 if sar_num > 0 && sar_den > 0 && (sar_num != 1 || sar_den != 1) {
402 out.set_sample_aspect_ratio(Some((sar_num as u32, sar_den as u32)));
403 }
404 let pict_type_raw = unsafe { read_unaligned(addr_of!((*av_frame).pict_type) as *const i32) };
410 out.set_picture_type(map_picture_type_raw(pict_type_raw));
411 let flags = unsafe { (*av_frame).flags };
415 out.set_key_frame(flags & ffmpeg_next::ffi::AV_FRAME_FLAG_KEY != 0);
416 out.set_interlaced(flags & ffmpeg_next::ffi::AV_FRAME_FLAG_INTERLACED != 0);
417 out.set_top_field_first(flags & ffmpeg_next::ffi::AV_FRAME_FLAG_TOP_FIELD_FIRST != 0);
418 let bet = unsafe { (*av_frame).best_effort_timestamp };
420 if bet != AV_NOPTS_VALUE {
421 out.set_best_effort_timestamp(Some(bet));
422 }
423 out.set_side_data(unsafe { collect_side_data(av_frame) });
425 out
426}
427
428const SIDE_DATA_MAX_ENTRIES: usize = 64;
435const SIDE_DATA_MAX_TOTAL_BYTES: usize = 256 * 1024;
442
443const SUBTITLE_MAX_RECTS: usize = 64;
447const SUBTITLE_MAX_TEXT_BYTES_PER_RECT: usize = 64 * 1024;
451const SUBTITLE_MAX_TEXT_TOTAL_BYTES: usize = 256 * 1024;
454const SUBTITLE_MAX_BITMAP_BYTES_PER_RECT: usize = 16 * 1024 * 1024;
458const SUBTITLE_MAX_BITMAP_TOTAL_BYTES: usize = 32 * 1024 * 1024;
460
461unsafe fn bounded_cstr_bytes<'a>(ptr: *const core::ffi::c_char, cap: usize) -> Option<&'a [u8]> {
479 let max = cap.saturating_add(1);
482 for i in 0..max {
483 let byte = unsafe { *(ptr.add(i) as *const u8) };
487 if byte == 0 {
488 return Some(unsafe { core::slice::from_raw_parts(ptr as *const u8, i) });
491 }
492 }
493 None
496}
497
498unsafe fn collect_side_data(av_frame: *const AVFrame) -> std::vec::Vec<SideDataEntry> {
512 let nb_side_data_raw = unsafe { (*av_frame).nb_side_data };
518 let side_data = unsafe { (*av_frame).side_data };
519 if nb_side_data_raw <= 0 || side_data.is_null() {
520 return Vec::new();
521 }
522 let count_raw = nb_side_data_raw as usize;
523 let count = count_raw.min(SIDE_DATA_MAX_ENTRIES);
524 if count_raw > SIDE_DATA_MAX_ENTRIES {
525 tracing::warn!(
526 cap = SIDE_DATA_MAX_ENTRIES,
527 requested = count_raw,
528 "mediadecode-ffmpeg: AVFrame.nb_side_data exceeds entry cap; truncating",
529 );
530 }
531 let mut out: Vec<SideDataEntry> = Vec::new();
532 if out.try_reserve_exact(count).is_err() {
533 return Vec::new();
534 }
535 let mut total_bytes: usize = 0;
536 for i in 0..count {
537 let sd = unsafe { *side_data.add(i) };
538 if sd.is_null() {
539 continue;
540 }
541 let kind = unsafe { read_unaligned(addr_of!((*sd).type_) as *const i32) };
545 let size = unsafe { (*sd).size };
546 let data_ptr = unsafe { (*sd).data };
547 let data_slice = if size == 0 || data_ptr.is_null() {
548 Vec::new()
549 } else {
550 let projected = total_bytes.saturating_add(size);
554 if projected > SIDE_DATA_MAX_TOTAL_BYTES {
555 tracing::warn!(
556 cap = SIDE_DATA_MAX_TOTAL_BYTES,
557 projected,
558 "mediadecode-ffmpeg: AVFrame side-data byte cap reached; dropping remaining entries",
559 );
560 break;
561 }
562 total_bytes = projected;
563 let mut buf: Vec<u8> = Vec::new();
566 if buf.try_reserve_exact(size).is_err() {
567 continue;
568 }
569 let src = unsafe { core::slice::from_raw_parts(data_ptr, size) };
572 buf.extend_from_slice(src);
573 buf
574 };
575 out.push(SideDataEntry::new(kind, data_slice));
576 }
577 out
578}
579
580unsafe fn find_backing_buffer(
589 av_frame: *const AVFrame,
590 data_ptr: *const u8,
591 bytes: usize,
592) -> Option<*mut ffmpeg_next::ffi::AVBufferRef> {
593 let buf_array_len = unsafe { (*av_frame).buf.len() };
594 for i in 0..buf_array_len {
595 let buf = unsafe { (*av_frame).buf[i] };
596 if buf.is_null() {
597 continue;
598 }
599 let buf_data = unsafe { (*buf).data as *const u8 };
600 let buf_size = unsafe { (*buf).size };
601 if buf_data.is_null() {
602 continue;
603 }
604 let start = buf_data as usize;
605 let Some(end) = start.checked_add(buf_size) else {
606 continue;
607 };
608 let dp = data_ptr as usize;
609 let Some(dp_end) = dp.checked_add(bytes) else {
610 continue;
611 };
612 if dp >= start && dp_end <= end {
613 return Some(buf);
614 }
615 }
616 None
617}
618
619fn map_primaries(raw: i32) -> ColorPrimaries {
620 match raw {
621 x if x == AVColorPrimaries::AVCOL_PRI_BT709 as i32 => ColorPrimaries::Bt709,
622 x if x == AVColorPrimaries::AVCOL_PRI_UNSPECIFIED as i32 => ColorPrimaries::Unspecified,
623 x if x == AVColorPrimaries::AVCOL_PRI_BT470M as i32 => ColorPrimaries::Bt470M,
624 x if x == AVColorPrimaries::AVCOL_PRI_BT470BG as i32 => ColorPrimaries::Bt470Bg,
625 x if x == AVColorPrimaries::AVCOL_PRI_SMPTE170M as i32 => ColorPrimaries::Smpte170M,
626 x if x == AVColorPrimaries::AVCOL_PRI_SMPTE240M as i32 => ColorPrimaries::Smpte240M,
627 x if x == AVColorPrimaries::AVCOL_PRI_FILM as i32 => ColorPrimaries::Film,
628 x if x == AVColorPrimaries::AVCOL_PRI_BT2020 as i32 => ColorPrimaries::Bt2020,
629 x if x == AVColorPrimaries::AVCOL_PRI_SMPTE428 as i32 => ColorPrimaries::SmpteSt428,
630 x if x == AVColorPrimaries::AVCOL_PRI_SMPTE431 as i32 => ColorPrimaries::SmpteRp431,
631 x if x == AVColorPrimaries::AVCOL_PRI_SMPTE432 as i32 => ColorPrimaries::SmpteEg432,
632 x if x == AVColorPrimaries::AVCOL_PRI_EBU3213 as i32 => ColorPrimaries::Ebu3213E,
633 _ => ColorPrimaries::Unspecified,
634 }
635}
636
637fn map_transfer(raw: i32) -> ColorTransfer {
638 match raw {
639 x if x == AVColorTransferCharacteristic::AVCOL_TRC_BT709 as i32 => ColorTransfer::Bt709,
640 x if x == AVColorTransferCharacteristic::AVCOL_TRC_UNSPECIFIED as i32 => {
641 ColorTransfer::Unspecified
642 }
643 x if x == AVColorTransferCharacteristic::AVCOL_TRC_GAMMA22 as i32 => ColorTransfer::Gamma22,
644 x if x == AVColorTransferCharacteristic::AVCOL_TRC_GAMMA28 as i32 => ColorTransfer::Gamma28,
645 x if x == AVColorTransferCharacteristic::AVCOL_TRC_SMPTE170M as i32 => ColorTransfer::Smpte170M,
646 x if x == AVColorTransferCharacteristic::AVCOL_TRC_SMPTE240M as i32 => ColorTransfer::Smpte240M,
647 x if x == AVColorTransferCharacteristic::AVCOL_TRC_LINEAR as i32 => ColorTransfer::Linear,
648 x if x == AVColorTransferCharacteristic::AVCOL_TRC_LOG as i32 => ColorTransfer::Log100,
649 x if x == AVColorTransferCharacteristic::AVCOL_TRC_LOG_SQRT as i32 => ColorTransfer::Log316,
650 x if x == AVColorTransferCharacteristic::AVCOL_TRC_IEC61966_2_4 as i32 => {
651 ColorTransfer::Iec6196624
652 }
653 x if x == AVColorTransferCharacteristic::AVCOL_TRC_BT1361_ECG as i32 => {
654 ColorTransfer::Bt1361Ecg
655 }
656 x if x == AVColorTransferCharacteristic::AVCOL_TRC_IEC61966_2_1 as i32 => {
657 ColorTransfer::Iec6196621
658 }
659 x if x == AVColorTransferCharacteristic::AVCOL_TRC_BT2020_10 as i32 => {
660 ColorTransfer::Bt2020_10Bit
661 }
662 x if x == AVColorTransferCharacteristic::AVCOL_TRC_BT2020_12 as i32 => {
663 ColorTransfer::Bt2020_12Bit
664 }
665 x if x == AVColorTransferCharacteristic::AVCOL_TRC_SMPTE2084 as i32 => {
666 ColorTransfer::SmpteSt2084Pq
667 }
668 x if x == AVColorTransferCharacteristic::AVCOL_TRC_SMPTE428 as i32 => ColorTransfer::SmpteSt428,
669 x if x == AVColorTransferCharacteristic::AVCOL_TRC_ARIB_STD_B67 as i32 => {
670 ColorTransfer::AribStdB67Hlg
671 }
672 _ => ColorTransfer::Unspecified,
673 }
674}
675
676fn map_matrix(raw: i32) -> ColorMatrix {
677 match raw {
678 x if x == AVColorSpace::AVCOL_SPC_BT709 as i32 => ColorMatrix::Bt709,
679 x if x == AVColorSpace::AVCOL_SPC_BT2020_NCL as i32 => ColorMatrix::Bt2020Ncl,
680 x if x == AVColorSpace::AVCOL_SPC_SMPTE170M as i32 => ColorMatrix::Bt601,
681 x if x == AVColorSpace::AVCOL_SPC_BT470BG as i32 => ColorMatrix::Bt601,
682 x if x == AVColorSpace::AVCOL_SPC_SMPTE240M as i32 => ColorMatrix::Smpte240m,
683 x if x == AVColorSpace::AVCOL_SPC_FCC as i32 => ColorMatrix::Fcc,
684 x if x == AVColorSpace::AVCOL_SPC_YCGCO as i32 => ColorMatrix::YCgCo,
685 _ => ColorMatrix::Bt709, }
687}
688
689fn map_range(raw: i32) -> ColorRange {
690 match raw {
691 x if x == AVColorRange::AVCOL_RANGE_JPEG as i32 => ColorRange::Full,
692 x if x == AVColorRange::AVCOL_RANGE_MPEG as i32 => ColorRange::Limited,
693 _ => ColorRange::Unspecified,
694 }
695}
696
697fn is_yuvj(pix_fmt: PixelFormat) -> bool {
703 matches!(
704 pix_fmt,
705 PixelFormat::Yuvj411p
706 | PixelFormat::Yuvj420p
707 | PixelFormat::Yuvj422p
708 | PixelFormat::Yuvj440p
709 | PixelFormat::Yuvj444p
710 )
711}
712
713fn map_range_for(pix_fmt: PixelFormat, color_range_raw: i32) -> ColorRange {
725 if is_yuvj(pix_fmt) {
726 return ColorRange::Full;
727 }
728 map_range(color_range_raw)
729}
730
731fn map_chroma_loc(raw: i32) -> ChromaLocation {
732 match raw {
733 x if x == AVChromaLocation::AVCHROMA_LOC_LEFT as i32 => ChromaLocation::Left,
734 x if x == AVChromaLocation::AVCHROMA_LOC_CENTER as i32 => ChromaLocation::Center,
735 x if x == AVChromaLocation::AVCHROMA_LOC_TOPLEFT as i32 => ChromaLocation::TopLeft,
736 x if x == AVChromaLocation::AVCHROMA_LOC_TOP as i32 => ChromaLocation::Top,
737 x if x == AVChromaLocation::AVCHROMA_LOC_BOTTOMLEFT as i32 => ChromaLocation::BottomLeft,
738 x if x == AVChromaLocation::AVCHROMA_LOC_BOTTOM as i32 => ChromaLocation::Bottom,
739 _ => ChromaLocation::Unspecified,
740 }
741}
742
743pub unsafe fn av_frame_to_audio_frame(
758 av_frame: *const AVFrame,
759 time_base: Timebase,
760) -> Result<AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer>, ConvertError>
761{
762 if av_frame.is_null() {
763 return Err(ConvertError::NullFrame);
764 }
765 let format_raw = unsafe { (*av_frame).format };
772 let sample_rate_raw = unsafe { (*av_frame).sample_rate };
773 let nb_samples_raw = unsafe { (*av_frame).nb_samples };
774 let pts_raw = unsafe { (*av_frame).pts };
775 let duration_raw = unsafe { (*av_frame).duration };
776 let bet_raw = unsafe { (*av_frame).best_effort_timestamp };
777
778 let sample_format = SampleFormat::from_raw(format_raw);
779 let sample_rate = sample_rate_raw.max(0) as u32;
780 let nb_samples = nb_samples_raw.max(0) as u32;
781
782 let ch_layout_ptr = unsafe { addr_of!((*av_frame).ch_layout) };
786 let channel_layout =
787 unsafe { crate::channel_layout::audio_channel_layout_from_raw_ptr(ch_layout_ptr) };
788 let channel_count_full = channel_layout.channels();
789 let channel_count = channel_count_full.min(255) as u8;
790
791 let is_planar = sample_format.is_planar();
793 let plane_count_full = if is_planar { channel_count as usize } else { 1 };
794 if plane_count_full > 8 {
802 return Err(ConvertError::InvalidPlaneLayout { plane: 8 });
803 }
804 let plane_count = plane_count_full as u8;
805
806 let linesize0 = unsafe { (*av_frame).linesize[0] };
813 if nb_samples > 0 && linesize0 <= 0 {
814 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
815 }
816 let plane_bytes = linesize0.max(0) as usize;
817 if nb_samples > 0 {
818 let bytes_per_sample = sample_format
819 .bytes_per_sample()
820 .ok_or(ConvertError::InvalidPlaneLayout { plane: 0 })? as usize;
821 let expected_per_plane = if is_planar {
822 (nb_samples as usize)
824 .checked_mul(bytes_per_sample)
825 .ok_or(ConvertError::InvalidPlaneLayout { plane: 0 })?
826 } else {
827 (nb_samples as usize)
829 .checked_mul(bytes_per_sample)
830 .and_then(|x| x.checked_mul(channel_count.max(1) as usize))
831 .ok_or(ConvertError::InvalidPlaneLayout { plane: 0 })?
832 };
833 if plane_bytes < expected_per_plane {
834 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
835 }
836 }
837
838 let mut planes_out: [Plane<FfmpegBuffer>; 8] = [
839 audio_plane_placeholder()?,
840 audio_plane_placeholder()?,
841 audio_plane_placeholder()?,
842 audio_plane_placeholder()?,
843 audio_plane_placeholder()?,
844 audio_plane_placeholder()?,
845 audio_plane_placeholder()?,
846 audio_plane_placeholder()?,
847 ];
848
849 #[allow(clippy::needless_range_loop)]
853 for plane_idx in 0..plane_count as usize {
854 let data_ptr = unsafe { (*av_frame).data[plane_idx] };
855 if data_ptr.is_null() {
856 return Err(ConvertError::InvalidPlaneLayout { plane: plane_idx });
862 }
863 let buf = unsafe { find_audio_backing_buffer(av_frame, data_ptr, plane_bytes) }
864 .ok_or(ConvertError::BufferAcquireFailed { plane: plane_idx })?;
865 let offset = unsafe { (data_ptr as usize).wrapping_sub((*buf).data as usize) };
868 let view = unsafe { FfmpegBuffer::from_ref_view(buf, offset, plane_bytes) }
871 .ok_or(ConvertError::BufferAcquireFailed { plane: plane_idx })?;
872 planes_out[plane_idx] = Plane::new(view, plane_bytes as u32);
873 }
874
875 let pts = if pts_raw != AV_NOPTS_VALUE {
876 Some(Timestamp::new(pts_raw, time_base))
877 } else {
878 None
879 };
880 let duration = if duration_raw > 0 {
881 Some(Timestamp::new(duration_raw, time_base))
882 } else {
883 None
884 };
885
886 let mut extra = AudioFrameExtra::default();
887 if bet_raw != AV_NOPTS_VALUE {
888 extra.set_best_effort_timestamp(Some(bet_raw));
889 }
890 extra.set_side_data(unsafe { collect_side_data(av_frame) });
894
895 Ok(
896 AudioFrame::new(
897 sample_rate,
898 nb_samples,
899 channel_count,
900 sample_format,
901 channel_layout,
902 planes_out,
903 plane_count,
904 extra,
905 )
906 .with_pts(pts)
907 .with_duration(duration),
908 )
909}
910
911fn audio_plane_placeholder() -> Result<Plane<FfmpegBuffer>, ConvertError> {
912 let raw = unsafe { av_buffer_alloc(1) };
913 if raw.is_null() {
914 return Err(ConvertError::BufferAcquireFailed { plane: 8 });
915 }
916 let buf =
917 unsafe { FfmpegBuffer::take(raw) }.ok_or(ConvertError::BufferAcquireFailed { plane: 8 })?;
918 Ok(Plane::new(buf, 0))
919}
920
921unsafe fn find_audio_backing_buffer(
924 av_frame: *const AVFrame,
925 data_ptr: *const u8,
926 bytes: usize,
927) -> Option<*mut ffmpeg_next::ffi::AVBufferRef> {
928 let buf_array_len = unsafe { (*av_frame).buf.len() };
933 for i in 0..buf_array_len {
934 let buf = unsafe { (*av_frame).buf[i] };
935 if buf.is_null() {
936 continue;
937 }
938 let buf_data = unsafe { (*buf).data as *const u8 };
939 let buf_size = unsafe { (*buf).size };
940 if buf_data.is_null() {
941 continue;
942 }
943 let start = buf_data as usize;
944 let Some(end) = start.checked_add(buf_size) else {
945 continue;
946 };
947 let dp = data_ptr as usize;
948 let Some(dp_end) = dp.checked_add(bytes) else {
949 continue;
950 };
951 if dp >= start && dp_end <= end {
952 return Some(buf);
953 }
954 }
955 None
956}
957
958pub unsafe fn av_subtitle_to_subtitle_frame(
982 av_subtitle: *const ffmpeg_next::ffi::AVSubtitle,
983 time_base: Timebase,
984) -> Result<SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer>, ConvertError> {
985 if av_subtitle.is_null() {
986 return Err(ConvertError::NullFrame);
987 }
988 let mut text_chunks: std::vec::Vec<u8> = std::vec::Vec::new();
993 let mut bitmap_regions: std::vec::Vec<mediadecode::subtitle::BitmapRegion<FfmpegBuffer>> =
994 std::vec::Vec::new();
995
996 let count_raw = unsafe { (*av_subtitle).num_rects } as usize;
997 let rects_ptr = unsafe { (*av_subtitle).rects };
998 if count_raw > 0 && rects_ptr.is_null() {
1002 return Err(ConvertError::NullFrame);
1003 }
1004 let count = count_raw.min(SUBTITLE_MAX_RECTS);
1013 if count_raw > SUBTITLE_MAX_RECTS {
1014 tracing::warn!(
1015 cap = SUBTITLE_MAX_RECTS,
1016 requested = count_raw,
1017 "mediadecode-ffmpeg: AVSubtitle.num_rects exceeds rect cap; truncating",
1018 );
1019 }
1020 let mut text_total_bytes: usize = 0;
1021 let mut bitmap_total_bytes: usize = 0;
1022
1023 let text_kind = AVSubtitleType::SUBTITLE_TEXT as i32;
1024 let ass_kind = AVSubtitleType::SUBTITLE_ASS as i32;
1025 let bitmap_kind = AVSubtitleType::SUBTITLE_BITMAP as i32;
1026 for i in 0..count {
1027 let rect_ptr = unsafe { *rects_ptr.add(i) };
1031 if rect_ptr.is_null() {
1032 continue;
1033 }
1034 let rect_type_raw = unsafe { read_unaligned(addr_of!((*rect_ptr).type_) as *const i32) };
1040 let rect_text_ptr = unsafe { (*rect_ptr).text };
1043 let rect_ass_ptr = unsafe { (*rect_ptr).ass };
1044 let rect_data0_ptr = unsafe { (*rect_ptr).data[0] };
1045 let rect_data1_ptr = unsafe { (*rect_ptr).data[1] };
1046 let rect_linesize0 = unsafe { (*rect_ptr).linesize[0] };
1047 let rect_w = unsafe { (*rect_ptr).w };
1048 let rect_h = unsafe { (*rect_ptr).h };
1049 let rect_x = unsafe { (*rect_ptr).x };
1050 let rect_y = unsafe { (*rect_ptr).y };
1051
1052 match rect_type_raw {
1053 x if x == text_kind && !rect_text_ptr.is_null() => {
1054 let bytes = unsafe { bounded_cstr_bytes(rect_text_ptr, SUBTITLE_MAX_TEXT_BYTES_PER_RECT) }
1064 .ok_or(ConvertError::InvalidPlaneLayout { plane: 0 })?;
1065 if bytes.len() > SUBTITLE_MAX_TEXT_BYTES_PER_RECT {
1069 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
1070 }
1071 let separator = if text_chunks.is_empty() { 0 } else { 1 };
1072 let projected = text_total_bytes
1073 .saturating_add(bytes.len())
1074 .saturating_add(separator);
1075 if projected > SUBTITLE_MAX_TEXT_TOTAL_BYTES {
1076 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
1077 }
1078 if separator == 1 {
1079 text_chunks.push(b'\n');
1080 }
1081 text_chunks.extend_from_slice(bytes);
1082 text_total_bytes = projected;
1083 }
1084 x if x == ass_kind && !rect_ass_ptr.is_null() => {
1085 let bytes = unsafe { bounded_cstr_bytes(rect_ass_ptr, SUBTITLE_MAX_TEXT_BYTES_PER_RECT) }
1088 .ok_or(ConvertError::InvalidPlaneLayout { plane: 0 })?;
1089 if bytes.len() > SUBTITLE_MAX_TEXT_BYTES_PER_RECT {
1090 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
1091 }
1092 let separator = if text_chunks.is_empty() { 0 } else { 1 };
1093 let projected = text_total_bytes
1094 .saturating_add(bytes.len())
1095 .saturating_add(separator);
1096 if projected > SUBTITLE_MAX_TEXT_TOTAL_BYTES {
1097 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
1098 }
1099 if separator == 1 {
1100 text_chunks.push(b'\n');
1101 }
1102 text_chunks.extend_from_slice(bytes);
1103 text_total_bytes = projected;
1104 }
1105 x if x == bitmap_kind => {
1106 let w = rect_w.max(0) as u32;
1110 let h = rect_h.max(0) as u32;
1111 let stride = rect_linesize0.max(0) as u32;
1112 if rect_data0_ptr.is_null() || stride == 0 || h == 0 {
1113 continue;
1114 }
1115 let data_len = (stride as usize)
1119 .checked_mul(h as usize)
1120 .ok_or(ConvertError::InvalidPlaneLayout { plane: 0 })?;
1121 if data_len > SUBTITLE_MAX_BITMAP_BYTES_PER_RECT {
1125 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
1126 }
1127 let projected_total = bitmap_total_bytes.saturating_add(data_len);
1128 if projected_total > SUBTITLE_MAX_BITMAP_TOTAL_BYTES {
1129 return Err(ConvertError::InvalidPlaneLayout { plane: 0 });
1130 }
1131 let data_slice = unsafe { core::slice::from_raw_parts(rect_data0_ptr, data_len) };
1134 let data_buf = FfmpegBuffer::copy_from_slice(data_slice)
1135 .ok_or(ConvertError::BufferAcquireFailed { plane: 0 })?;
1136 let palette_len = 256 * 4;
1137 let palette_buf = if rect_data1_ptr.is_null() {
1138 FfmpegBuffer::copy_from_slice(&[])
1139 .ok_or(ConvertError::BufferAcquireFailed { plane: 1 })?
1140 } else {
1141 let p = unsafe { core::slice::from_raw_parts(rect_data1_ptr, palette_len) };
1143 FfmpegBuffer::copy_from_slice(p).ok_or(ConvertError::BufferAcquireFailed { plane: 1 })?
1144 };
1145 bitmap_regions.push(mediadecode::subtitle::BitmapRegion::new(
1146 rect_x.max(0) as u32,
1147 rect_y.max(0) as u32,
1148 w,
1149 h,
1150 stride,
1151 data_buf,
1152 palette_buf,
1153 ));
1154 bitmap_total_bytes = projected_total;
1155 }
1156 _ => {}
1157 }
1158 }
1159
1160 let payload = if !text_chunks.is_empty() {
1161 let buf = FfmpegBuffer::copy_from_slice(&text_chunks)
1162 .ok_or(ConvertError::BufferAcquireFailed { plane: 0 })?;
1163 SubtitlePayload::Text {
1164 text: buf,
1165 language: None,
1166 }
1167 } else if !bitmap_regions.is_empty() {
1168 SubtitlePayload::Bitmap {
1169 regions: bitmap_regions,
1170 }
1171 } else {
1172 let buf =
1174 FfmpegBuffer::copy_from_slice(&[]).ok_or(ConvertError::BufferAcquireFailed { plane: 0 })?;
1175 SubtitlePayload::Text {
1176 text: buf,
1177 language: None,
1178 }
1179 };
1180
1181 let sub_pts = unsafe { (*av_subtitle).pts };
1182 let pts = if sub_pts != AV_NOPTS_VALUE {
1183 Some(Timestamp::new(sub_pts, time_base))
1184 } else {
1185 None
1186 };
1187
1188 let extra = SubtitleFrameExtra::new(unsafe { (*av_subtitle).start_display_time }, unsafe {
1189 (*av_subtitle).end_display_time
1190 });
1191
1192 Ok(SubtitleFrame::new(payload, extra).with_pts(pts))
1193}
1194
1195fn map_picture_type_raw(raw: i32) -> PictureType {
1196 match raw {
1197 x if x == AVPictureType::AV_PICTURE_TYPE_I as i32 => PictureType::I,
1198 x if x == AVPictureType::AV_PICTURE_TYPE_P as i32 => PictureType::P,
1199 x if x == AVPictureType::AV_PICTURE_TYPE_B as i32 => PictureType::B,
1200 x if x == AVPictureType::AV_PICTURE_TYPE_S as i32 => PictureType::S,
1201 x if x == AVPictureType::AV_PICTURE_TYPE_SI as i32 => PictureType::Si,
1202 x if x == AVPictureType::AV_PICTURE_TYPE_SP as i32 => PictureType::Sp,
1203 x if x == AVPictureType::AV_PICTURE_TYPE_BI as i32 => PictureType::Bi,
1204 _ => PictureType::Unspecified,
1205 }
1206}
1207
1208#[cfg(test)]
1209mod tests;