1use std::collections::VecDeque;
4use std::ffi::{CStr, CString};
5use std::path::{Path, PathBuf};
6use std::time::{Duration, Instant};
7
8use crate::audio_timeline::SourceTimeline;
9use crate::recovery::{DecodeDiagnostics, DecodePolicy, Recovery, WorkBudget, submit_packet};
10
11use crate::convert::{
12 adapter_error, codec_thread_budget, color_metadata, convert_video_frame, declared_sar,
13 frame_color_metadata, guess_sar, orientation_from_display, reject_unused_decode_options,
14 stream_rotation, tick_base, timestamp, video_decoder_options,
15};
16#[cfg(all(
17 any(target_os = "linux", target_os = "macos", target_os = "windows"),
18 feature = "hwdecode"
19))]
20use crate::video_frame::{HardwareDecoderSpec, HardwareOpenError};
21use alpha2_ffmpeg_bridge::{DecodedFrame, DemuxInput, OpenInputError};
22use alpha2_media::{
23 AudioBlock, CancelToken, ColorMetadata, DecodeError, DecodeEvent, DecodeRequest, DecodeResult,
24 DecodedVideoFrame, Decoder, MAX_AUDIO_CHANNELS, Orientation, SampleAspectRatio, SourceAlpha,
25 Speaker, SpeakerLayout, StreamInfo, StreamKind,
26};
27use alpha2_time::{FrameRate, MediaTime, TICKS_PER_SECOND};
28use rsmpeg::avcodec::{AVCodec, AVCodecContext, AVCodecRef, AVPacket};
29#[cfg(all(
30 any(target_os = "linux", target_os = "macos", target_os = "windows"),
31 feature = "hwdecode"
32))]
33use rsmpeg::avutil::AVHWDeviceContext;
34use rsmpeg::avutil::{AVChannelLayout, AVFrame, AVRational, av_rescale_q, av_rescale_q_rnd, ra};
35use rsmpeg::error::RsmpegError;
36use rsmpeg::ffi;
37use rsmpeg::swresample::SwrContext;
38
39#[derive(Clone, Debug, PartialEq)]
41pub struct ProbeResult {
42 pub format: String,
44 pub duration: Option<MediaTime>,
47 pub start: Option<MediaTime>,
50 pub streams: Vec<StreamInfo>,
53 pub selected_video: Option<usize>,
56 pub selected_audio: Option<usize>,
58}
59
60impl ProbeResult {
61 #[must_use]
63 pub fn stream(&self, index: usize) -> Option<&StreamInfo> {
64 self.streams.iter().find(|stream| stream.index == index)
65 }
66
67 #[must_use]
69 pub fn selected_video_stream(&self) -> Option<&StreamInfo> {
70 self.selected_video.and_then(|index| self.stream(index))
71 }
72
73 #[must_use]
75 pub fn selected_audio_stream(&self) -> Option<&StreamInfo> {
76 self.selected_audio.and_then(|index| self.stream(index))
77 }
78
79 pub fn audio_streams(&self) -> impl Iterator<Item = &StreamInfo> {
81 self.streams
82 .iter()
83 .filter(|stream| stream.kind == StreamKind::Audio)
84 }
85
86 #[must_use]
88 pub fn audio_stream(&self, ordinal: u32) -> Option<&StreamInfo> {
89 self.audio_streams().nth(usize::try_from(ordinal).ok()?)
90 }
91
92 #[must_use]
94 pub fn selected_audio_ordinal(&self) -> Option<u32> {
95 let selected = self.selected_audio?;
96 let position = self
97 .audio_streams()
98 .position(|stream| stream.index == selected)?;
99 u32::try_from(position).ok()
100 }
101}
102
103pub fn probe(path: impl AsRef<Path>) -> Result<ProbeResult, DecodeError> {
108 let input = open_input(path.as_ref())?;
109 probe_context(&input, path.as_ref())
110}
111
112pub fn visit_packet_times(
118 path: impl AsRef<Path>,
119 stream: usize,
120 mut visit: impl FnMut(f64) -> bool,
121) -> Result<(), DecodeError> {
122 let mut input = open_input(path.as_ref())?;
123 let time_base = input
124 .streams()
125 .get(stream)
126 .map(|found| found.time_base)
127 .ok_or_else(|| DecodeError::Unsupported(format!("the file has no stream {stream}")))?;
128 input.discard_all_but(stream);
129 while let Some(packet) = input.read_packet().map_err(adapter_error)? {
130 if usize::try_from(packet.stream_index).ok() != Some(stream) {
131 continue;
132 }
133 let time = [packet.pts, packet.dts]
134 .into_iter()
135 .find(|time| *time != ffi::AV_NOPTS_VALUE);
136 let Some(time) = time else { continue };
137 #[allow(clippy::cast_precision_loss)] let seconds = time as f64 * f64::from(time_base.num) / f64::from(time_base.den);
139 if !visit(seconds) {
140 break;
141 }
142 }
143 Ok(())
144}
145
146pub struct NativeDecoder {
148 pub(crate) input: DemuxInput,
149 pub(crate) streams: Vec<StreamInfo>,
150 pub(crate) container_duration: Option<MediaTime>,
151 pub(crate) video_index: usize,
152 pub(crate) time_base: AVRational,
153 pub(crate) decoder: AVCodecContext,
154 pub(crate) orientation: Orientation,
155 pub(crate) sample_aspect_ratio: SampleAspectRatio,
157 pub(crate) color: Option<ColorMetadata>,
159 pub(crate) declared_color: Option<ColorMetadata>,
161 pub(crate) video_scaler: crate::convert::FrameScaler,
162 pub(crate) pending: VecDeque<DecodedFrame>,
163 pub(crate) demux_eof: bool,
164 pub(crate) decoder_flushed: bool,
165 pub(crate) stages: DecodeStageTimings,
166 first_video_pts: Option<MediaTime>,
168 recovery: Recovery,
169 slack: MediaTime,
172 prefetched: Prefetched,
174 dropped_layer: Option<DroppedLayer>,
176}
177
178#[derive(Clone, Debug, Eq, PartialEq)]
180pub struct DroppedLayer {
181 pub code: &'static str,
183 pub reason: String,
185}
186
187#[cfg(test)]
188thread_local! {
189 pub(crate) static WITHOUT_LIBVPX_VP9: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
191}
192
193type Prefetched = VecDeque<Result<Option<AVPacket>, RsmpegError>>;
195
196fn read_first_packets(input: &mut DemuxInput, index: usize) -> (Prefetched, Vec<i64>) {
202 let mut read = VecDeque::new();
203 let mut timestamps = Vec::with_capacity(alpha2_time::ROUNDING_SCAN_FRAMES);
204 let (mut packets, mut bytes) = (0, 0_u64);
205 while packets < alpha2_time::ROUNDING_SCAN_FRAMES && bytes < alpha2_time::ROUNDING_SCAN_BYTES {
206 let packet = input.read_packet();
207 let more = match &packet {
208 Ok(Some(packet)) => {
209 if usize::try_from(packet.stream_index).ok() == Some(index) {
210 packets += 1;
211 bytes = bytes.saturating_add(u64::try_from(packet.size).unwrap_or(0));
212 if packet.pts != ffi::AV_NOPTS_VALUE {
213 timestamps.push(packet.pts);
214 }
215 }
216 true
217 }
218 Ok(None) | Err(_) => false,
219 };
220 read.push_back(packet);
221 if !more {
222 break;
223 }
224 }
225 (read, timestamps)
226}
227
228fn read_presentation_slack(
230 input: &mut DemuxInput,
231 video_index: usize,
232 time_base: AVRational,
233) -> (Prefetched, MediaTime) {
234 let (read, timestamps) = read_first_packets(input, video_index);
235 let slack = alpha2_time::presentation_slack(
236 i64::from(time_base.num),
237 i64::from(time_base.den),
238 alpha2_time::rounding_quantum(×tamps),
239 );
240 (read, slack)
241}
242
243#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
246pub struct DecodeStageTimings {
247 pub demux: Duration,
249 pub send_packet: Duration,
251 pub receive_frame: Duration,
254 pub convert: Duration,
257 pub seek: Duration,
259}
260
261impl DecodeStageTimings {
262 #[must_use]
264 pub fn merged(self, other: Self) -> Self {
265 Self {
266 demux: self.demux.saturating_add(other.demux),
267 send_packet: self.send_packet.saturating_add(other.send_packet),
268 receive_frame: self.receive_frame.saturating_add(other.receive_frame),
269 convert: self.convert.saturating_add(other.convert),
270 seek: self.seek.saturating_add(other.seek),
271 }
272 }
273}
274
275#[derive(Clone, Copy, PartialEq, Eq)]
276enum InputPosition {
277 Opened,
279 Moved,
280}
281
282#[derive(Clone, Copy, PartialEq, Eq)]
283enum AudioContinuity {
284 Continuous,
285 Damaged,
286}
287
288pub struct NativeAudioDecoder {
295 pub(crate) path: PathBuf,
296 pub(crate) input: DemuxInput,
297 input_position: InputPosition,
298 pub(crate) streams: Vec<StreamInfo>,
299 pub(crate) container_duration: Option<MediaTime>,
300 pub(crate) audio_index: usize,
301 pub(crate) time_base: AVRational,
302 pub(crate) decoder: AVCodecContext,
303 converter: Option<SwrContext>,
305 pub(crate) sample_rate: u32,
306 pub(crate) channel_count: usize,
307 speakers: SpeakerLayout,
309 speakers_assumed: bool,
311 output_layout: AVChannelLayout,
313 timeline: SourceTimeline,
315 pub(crate) pending: Vec<VecDeque<f32>>,
316 pub(crate) next_sample: Option<i64>,
319 pub(crate) block_frames: usize,
320 pub(crate) demux_eof: bool,
321 pub(crate) decoder_flushed: bool,
322 recovery: Recovery,
323 repair_audio: AudioContinuity,
324 made_up: Option<MadeUpTimestamps>,
327 grid: Option<(u32, alpha2_time::AudioFrameGrid)>,
330 prefetched: Prefetched,
332 frames_placed: u64,
334 seek_preroll: i64,
336}
337
338fn audio_stream_index(probe: &ProbeResult, stream: Option<u32>) -> Result<usize, DecodeError> {
341 match stream {
342 None => probe.selected_audio.ok_or_else(|| {
343 DecodeError::Unsupported("source has no audio stream this runtime decodes".into())
344 }),
345 Some(ordinal) => probe
346 .audio_stream(ordinal)
347 .map(|info| info.index)
348 .ok_or_else(|| {
349 DecodeError::Unsupported(format!(
350 "source has no audio stream {ordinal}; it has {}",
351 probe.audio_streams().count()
352 ))
353 }),
354 }
355}
356
357struct MadeUpTimestamps {
359 rate: u32,
361 drift: i64,
364 rate_changed: bool,
366}
367
368const AUDIO_SEEK_PREROLL_TICKS: i64 = TICKS_PER_SECOND / 10;
373
374const OPUS_SEEK_PREROLL_TICKS: i64 = TICKS_PER_SECOND * 2 / 5;
379
380impl NativeAudioDecoder {
381 pub fn set_decode_policy(&mut self, policy: DecodePolicy) {
383 self.recovery.policy = policy;
384 }
385
386 pub fn set_decode_reporter(
388 &mut self,
389 reporter: impl Fn(crate::DecodeDiagnostic) + Send + Sync + 'static,
390 ) {
391 self.recovery.reporter = Some(Box::new(reporter));
392 }
393
394 #[must_use]
396 pub fn decode_diagnostics(&self) -> &DecodeDiagnostics {
397 &self.recovery.diagnostics
398 }
399
400 pub fn open(path: impl AsRef<Path>, project_sample_rate: u32) -> Result<Self, DecodeError> {
406 Self::open_stream(path, project_sample_rate, None)
407 }
408
409 pub fn open_stream(
415 path: impl AsRef<Path>,
416 project_sample_rate: u32,
417 stream: Option<u32>,
418 ) -> Result<Self, DecodeError> {
419 if project_sample_rate == 0 {
420 return Err(DecodeError::OutOfRange);
421 }
422 let path = path.as_ref().to_owned();
423 let mut input = open_input(&path)?;
424 let probe = probe_context(&input, &path)?;
425 let selected = audio_stream_index(&probe, stream)?;
426 let stream = probe
427 .stream(selected)
428 .ok_or_else(|| DecodeError::Adapter("selected audio stream disappeared".into()))?;
429 check_source_sample_rate(i64::from(stream.sample_rate.unwrap_or(0)))?;
430 let source_channels =
431 usize::try_from(stream.channels.unwrap_or(0)).map_err(|_| DecodeError::OutOfRange)?;
432 if source_channels == 0 {
433 return Err(DecodeError::Unsupported(
434 "audio stream declares no channels".into(),
435 ));
436 }
437 check_audio_channels(source_channels)?;
438 let decoder = open_audio_decoder(&mut input, selected)?;
439 let (prefetched, timestamps) = read_first_packets(&mut input, selected);
440 let stream = input
441 .streams()
442 .get(selected)
443 .ok_or(DecodeError::OutOfRange)?;
444 let time_base = stream.time_base;
445 let seek_preroll = if stream.codecpar().codec_id == ffi::AV_CODEC_ID_OPUS {
446 OPUS_SEEK_PREROLL_TICKS
447 } else {
448 AUDIO_SEEK_PREROLL_TICKS
449 };
450 let made_up = (input.iformat().name() == c"aac")
453 .then(|| u32::try_from(stream.codecpar().sample_rate).ok())
454 .flatten()
455 .filter(|rate| *rate > 0)
456 .map(|rate| MadeUpTimestamps {
457 rate,
458 drift: 0,
459 rate_changed: false,
460 });
461 let grid = u32::try_from(stream.codecpar().sample_rate)
463 .ok()
464 .filter(|_| made_up.is_none())
465 .and_then(|rate| {
466 alpha2_time::audio_frame_grid(
467 ×tamps,
468 i64::from(time_base.num),
469 i64::from(time_base.den),
470 rate,
471 )
472 .map(|grid| (rate, grid))
473 });
474 let declared = [decoder.ch_layout(), stream.codecpar().ch_layout()]
477 .into_iter()
478 .find(|layout| {
479 layout.order != ffi::AV_CHANNEL_ORDER_UNSPEC
480 && usize::try_from(layout.nb_channels).ok() == Some(source_channels)
481 })
482 .map_or_else(
483 || vec![Speaker::Unnamed; source_channels],
484 |layout| layout_speakers(&layout),
485 );
486 let (speakers, speakers_assumed) = SpeakerLayout::declared_or_default(&declared)
487 .ok_or_else(|| unsupported_channels(source_channels))?;
488 let channel_count = speakers.channels();
489 let output_layout = ffmpeg_layout(&speakers)?;
490 Ok(Self {
491 path,
492 input,
493 input_position: InputPosition::Opened,
494 container_duration: probe.duration,
495 streams: probe.streams,
496 audio_index: selected,
497 time_base,
498 decoder,
499 converter: None,
500 sample_rate: project_sample_rate,
501 channel_count,
502 speakers,
503 speakers_assumed,
504 output_layout,
505 timeline: SourceTimeline::new(project_sample_rate, channel_count, None),
506 pending: (0..channel_count).map(|_| VecDeque::new()).collect(),
507 next_sample: None,
508 block_frames: 1_024,
509 demux_eof: false,
510 decoder_flushed: false,
511 recovery: Recovery::new(time_base),
512 repair_audio: AudioContinuity::Continuous,
513 made_up,
514 grid,
515 prefetched,
516 frames_placed: 0,
517 seek_preroll,
518 })
519 }
520
521 pub fn set_block_frames(&mut self, frames: usize) -> Result<(), DecodeError> {
526 if frames == 0 {
527 return Err(DecodeError::OutOfRange);
528 }
529 self.block_frames = frames;
530 Ok(())
531 }
532
533 #[must_use]
535 pub const fn channels(&self) -> usize {
536 self.channel_count
537 }
538
539 #[must_use]
541 pub const fn speakers(&self) -> SpeakerLayout {
542 self.speakers
543 }
544
545 #[must_use]
547 pub const fn speakers_assumed(&self) -> bool {
548 self.speakers_assumed
549 }
550
551 #[must_use]
557 pub fn audio_duration(&self) -> Option<MediaTime> {
558 effective_media_duration(
559 self.streams
560 .iter()
561 .find(|stream| stream.index == self.audio_index)
562 .and_then(|stream| stream.duration),
563 self.container_duration,
564 )
565 }
566
567 fn stream_start(&self) -> i64 {
569 self.input
570 .streams()
571 .get(self.audio_index)
572 .map(|stream| stream.start_time)
573 .filter(|start| *start != ffi::AV_NOPTS_VALUE)
574 .unwrap_or(0)
575 }
576
577 #[cfg(test)]
579 pub(crate) const fn peak_held_samples(&self) -> usize {
580 self.timeline.peak_held()
581 }
582
583 pub(crate) fn reset_to_start(&mut self) -> Result<(), DecodeError> {
586 if self.input_position == InputPosition::Opened {
587 self.decoder.flush_buffers();
588 } else {
589 let mut input = open_input(&self.path)?;
590 self.decoder = open_audio_decoder(&mut input, self.audio_index)?;
591 self.input = input;
592 self.input_position = InputPosition::Opened;
593 self.prefetched.clear();
594 }
595 self.restart(None);
596 Ok(())
597 }
598
599 fn restart(&mut self, first: Option<i64>) {
601 self.converter = None;
602 self.timeline = SourceTimeline::new(self.sample_rate, self.channel_count, first);
603 self.pending.iter_mut().for_each(VecDeque::clear);
604 self.next_sample = None;
605 self.demux_eof = false;
606 self.decoder_flushed = false;
607 self.repair_audio = AudioContinuity::Continuous;
608 self.recovery.reset_progress();
609 if let Some(made_up) = &mut self.made_up {
610 made_up.drift = 0;
611 }
612 }
613
614 fn made_up_rate_changed(&self) -> bool {
615 self.made_up
616 .as_ref()
617 .is_some_and(|made_up| made_up.rate_changed)
618 }
619
620 fn true_timestamp(&mut self, pts: Option<i64>, rate: u32, samples: i32) -> Option<i64> {
623 let time_base = self.time_base;
624 let Some(made_up) = &mut self.made_up else {
625 return pts;
626 };
627 let declared = made_up.rate;
628 made_up.rate_changed |= rate != declared;
629 let corrected = pts.map(|pts| pts.saturating_sub(made_up.drift));
630 let (num, den) = (
631 i128::from(time_base.num.max(1)),
632 i128::from(time_base.den.max(1)),
633 );
634 let samples = i128::from(samples.max(0));
635 let counted = samples * den / (i128::from(declared) * num);
638 let lasted = (2 * samples * den + i128::from(rate) * num) / (2 * i128::from(rate) * num);
639 made_up.drift = made_up
640 .drift
641 .saturating_add(i64::try_from(counted - lasted).unwrap_or(0));
642 corrected
643 }
644
645 pub(crate) fn receive_audio_frame(&mut self) -> Result<bool, DecodeError> {
646 let mut frame = match self.decoder.receive_frame() {
647 Ok(frame) => frame,
648 Err(RsmpegError::DecoderDrainError | RsmpegError::DecoderFlushedError) => {
649 return Ok(false);
650 }
651 Err(error) => {
652 self.recovery.error(error, "audio receive", None)?;
653 self.repair_audio = AudioContinuity::Damaged;
654 return Ok(true);
655 }
656 };
657 if frame.flags & i32::try_from(ffi::AV_FRAME_FLAG_CORRUPT).expect("flag fits i32") != 0
658 || frame.decode_error_flags != 0
659 {
660 self.recovery.corrupt("audio frame", Some(frame.pts))?;
661 self.repair_audio = AudioContinuity::Damaged;
662 return Ok(true);
663 }
664 let pts = Some(if frame.best_effort_timestamp == ffi::AV_NOPTS_VALUE {
665 frame.pts
666 } else {
667 frame.best_effort_timestamp
668 })
669 .filter(|pts| *pts != ffi::AV_NOPTS_VALUE);
670 if pts.is_some_and(|pts| !audio_timestamp_in_range(pts, self.time_base)) {
671 self.recovery.corrupt("audio timestamp", pts)?;
672 self.repair_audio = AudioContinuity::Damaged;
673 return Ok(true);
674 }
675 self.recovery.progress(pts.unwrap_or(ffi::AV_NOPTS_VALUE))?;
676 normalize_audio_frame_layout(&mut frame, &self.output_layout)?;
677 check_source_sample_rate(i64::from(frame.sample_rate))?;
679 let rate = u32::try_from(frame.sample_rate).map_err(|_| DecodeError::OutOfRange)?;
680 let pts = self.true_timestamp(pts, rate, frame.nb_samples);
681 let planes = self.convert_audio_frame(&frame)?;
682 self.place_audio(rate, pts, planes)?;
683 Ok(true)
684 }
685
686 fn convert_audio_frame(&mut self, frame: &AVFrame) -> Result<Vec<Vec<f32>>, DecodeError> {
688 let mut packed = audio_output_frame(frame.sample_rate, &self.output_layout);
689 let mut converter = match self.converter.take() {
690 Some(converter) => converter,
691 None => audio_converter(frame, &self.output_layout)?,
692 };
693 if let Err(error) = converter.convert_frame(Some(frame), &mut packed) {
694 if error.raw_error() != Some(ffi::AVERROR_INPUT_CHANGED) {
695 return Err(adapter_error(error));
696 }
697 converter = audio_converter(frame, &self.output_layout)?;
698 packed = audio_output_frame(frame.sample_rate, &self.output_layout);
699 converter
700 .convert_frame(Some(frame), &mut packed)
701 .map_err(adapter_error)?;
702 }
703 self.converter = Some(converter);
704 if packed.nb_samples != frame.nb_samples {
706 return Err(DecodeError::Adapter(format!(
707 "converting {} decoded audio samples gave {}",
708 frame.nb_samples, packed.nb_samples
709 )));
710 }
711 let mut planes = (0..self.channel_count)
712 .map(|_| VecDeque::new())
713 .collect::<Vec<_>>();
714 append_interleaved_audio(&packed, &mut planes)?;
715 Ok(planes.into_iter().map(Vec::from).collect())
716 }
717
718 fn place_audio(
725 &mut self,
726 rate: u32,
727 pts: Option<i64>,
728 mut planes: Vec<Vec<f32>>,
729 ) -> Result<(), DecodeError> {
730 let rate_i32 = i32::try_from(rate).map_err(|_| DecodeError::OutOfRange)?;
731 let at = pts.map(|pts| {
732 av_rescale_q_rnd(
733 pts,
734 self.time_base,
735 ra(1, rate_i32),
736 ffi::AV_ROUND_NEAR_INF | ffi::AV_ROUND_PASS_MINMAX,
737 )
738 });
739 let damaged = self.repair_audio == AudioContinuity::Damaged;
740 self.repair_audio = AudioContinuity::Continuous;
741 self.frames_placed = self.frames_placed.wrapping_add(1);
742 let frames = i64::try_from(planes.first().map_or(0, Vec::len)).unwrap_or(i64::MAX);
743 let Some(expected) = self.timeline.expected(rate) else {
744 let at = match (at, self.grid) {
747 (Some(at), Some((grid_rate, grid))) if !damaged && grid_rate == rate => {
748 grid.place(at, frames)
749 }
750 (at, _) => at.unwrap_or(0),
751 };
752 return self.timeline.place(rate, at, planes);
753 };
754 let Some(at) = at else {
755 if damaged {
756 return Err(DecodeError::Adapter(
757 "cannot locate damaged audio gap without PTS".into(),
758 ));
759 }
760 return self.timeline.place(rate, expected, planes);
761 };
762 let ahead = at
763 .checked_sub(expected)
764 .ok_or_else(|| DecodeError::Adapter("audio timestamps are out of range".into()))?;
765 if damaged {
766 if ahead < 0 || ahead > i64::from(rate) * 10 {
767 return Err(DecodeError::Adapter(
768 "damaged audio gap is ambiguous or exceeds 10 seconds".into(),
769 ));
770 }
771 if ahead > 0 {
772 self.recovery.audio_gap(
773 self.timeline.output_at(rate, expected),
774 self.timeline.output_at(rate, at),
775 self.sample_rate,
776 "corrupt input",
777 )?;
778 }
779 return self.timeline.place(rate, at, planes);
780 }
781 let den = i64::from(self.time_base.den.max(1));
783 let unit = (i64::from(rate) * i64::from(self.time_base.num) + den - 1) / den;
784 let tolerance = frames
785 .max((i64::from(rate) + 499) / 500)
786 .max(unit.saturating_mul(2));
787 if ahead.abs() < tolerance {
788 return self.timeline.place(rate, expected, planes);
789 }
790 if ahead > 0 {
791 self.recovery.timestamp_gap(
792 self.timeline.output_at(rate, expected),
793 self.timeline.output_at(rate, at),
794 self.sample_rate,
795 );
796 return self.timeline.place(rate, at, planes);
797 }
798 let repeated = usize::try_from(-ahead).unwrap_or(usize::MAX);
799 self.recovery.timestamp_overlap(
800 self.timeline.output_at(rate, at),
801 self.timeline
802 .output_at(rate, at.saturating_add(frames.min(-ahead))),
803 self.sample_rate,
804 );
805 for plane in &mut planes {
806 plane.drain(..repeated.min(plane.len()));
807 }
808 self.timeline.place(rate, expected, planes)
809 }
810
811 pub(crate) fn fill_pending(
812 &mut self,
813 frames: usize,
814 cancel: &CancelToken,
815 ) -> Result<(), DecodeError> {
816 interrupt_on_cancel(&self.input, cancel);
817 let mut budget = WorkBudget::new();
818 let mut foreign = false;
819 let mut placed = self.frames_placed;
820 loop {
821 let held = self.pending.first().map_or(0, VecDeque::len);
822 if held >= frames {
823 return Ok(());
824 }
825 if self.frames_placed != placed {
828 placed = self.frames_placed;
829 budget.progressed();
830 }
831 if std::mem::take(&mut foreign) {
834 if cancel.is_cancelled() {
835 return Err(DecodeError::Cancelled);
836 }
837 } else {
838 budget.step(cancel)?;
839 }
840 if let Some((start, _)) = self.timeline.produce(&mut self.pending, frames - held) {
841 if held == 0 {
842 self.next_sample = Some(start);
843 }
844 continue;
845 }
846 if self.receive_audio_frame()? {
847 continue;
848 }
849 if self.demux_eof {
850 if !self.decoder_flushed {
851 match self.decoder.send_packet(None) {
852 Ok(()) | Err(RsmpegError::DecoderFlushedError) => {}
853 Err(RsmpegError::DecoderFullError) => continue,
854 Err(error) => {
855 self.recovery.error(error, "flush", None)?;
856 continue;
857 }
858 }
859 self.decoder_flushed = true;
860 continue;
861 }
862 if self.repair_audio == AudioContinuity::Damaged {
863 self.recovery.damaged_tail("audio")?;
866 self.repair_audio = AudioContinuity::Continuous;
867 }
868 if !self.timeline.finished() {
869 self.timeline.finish();
870 continue;
871 }
872 return Ok(());
873 }
874 self.input_position = InputPosition::Moved;
875 let packet = match self
876 .prefetched
877 .pop_front()
878 .unwrap_or_else(|| self.input.read_packet())
879 {
880 Ok(Some(packet)) => packet,
881 Ok(None) => {
882 self.demux_eof = true;
883 continue;
884 }
885 Err(error) => {
886 let error = demux_failure(error, cancel)?;
887 self.recovery.error(error, "audio demux", None)?;
888 self.repair_audio = AudioContinuity::Damaged;
889 continue;
890 }
891 };
892 if usize::try_from(packet.stream_index).ok() != Some(self.audio_index) {
893 foreign = true;
894 continue;
895 }
896 if packet.flags & i32::try_from(ffi::AV_PKT_FLAG_CORRUPT).expect("flag fits i32") != 0 {
897 self.recovery.corrupt("audio packet", Some(packet.pts))?;
898 self.repair_audio = AudioContinuity::Damaged;
899 continue;
900 }
901 submit_packet(
902 |drain| {
903 if drain {
904 return self.receive_audio_frame();
905 }
906 match self.decoder.send_packet(Some(&packet)) {
907 Ok(()) => Ok(true),
908 Err(RsmpegError::DecoderFullError) => Ok(false),
909 Err(error) => {
910 self.recovery.error(error, "audio send", Some(packet.pts))?;
911 self.repair_audio = AudioContinuity::Damaged;
912 Ok(true)
913 }
914 }
915 },
916 &mut budget,
917 cancel,
918 )?;
919 }
920 }
921}
922
923impl Decoder for NativeAudioDecoder {
924 fn streams(&self) -> &[StreamInfo] {
925 &self.streams
926 }
927 fn flush(&mut self) -> Result<(), DecodeError> {
928 self.reset_to_start()
929 }
930 fn seek(
931 &mut self,
932 request: DecodeRequest,
933 cancel: &CancelToken,
934 ) -> Result<DecodeResult, DecodeError> {
935 if cancel.is_cancelled() {
936 return Err(DecodeError::Cancelled);
937 }
938 let target = request
939 .time
940 .ticks()
941 .saturating_mul(i64::from(self.sample_rate))
942 .div_euclid(TICKS_PER_SECOND);
943 let timestamp = av_rescale_q(
944 request.time.ticks().saturating_sub(self.seek_preroll),
945 tick_base(),
946 self.time_base,
947 );
948 let rate_changed = self.made_up_rate_changed();
952 let from_start = timestamp <= self.stream_start() || rate_changed;
953 if from_start {
954 self.reset_to_start()?;
955 } else {
956 interrupt_on_cancel(&self.input, cancel);
957 self.input_position = InputPosition::Moved;
958 self.prefetched.clear();
959 self.input
960 .seek(
961 i32::try_from(self.audio_index).map_err(|_| DecodeError::OutOfRange)?,
962 timestamp,
963 i32::try_from(ffi::AVSEEK_FLAG_BACKWARD).expect("seek flag fits i32"),
964 )
965 .map_err(|error| seek_failure(error, cancel))?;
966 }
967 self.decoder.flush_buffers();
968 self.restart(Some(target));
969 let mut event = self.decode_next(cancel)?;
970 if !from_start && !rate_changed && self.made_up_rate_changed() {
971 self.reset_to_start()?;
973 self.decoder.flush_buffers();
974 self.restart(Some(target));
975 event = self.decode_next(cancel)?;
976 }
977 if matches!(event, DecodeEvent::EndOfStream) {
978 return Err(DecodeError::OutOfRange);
979 }
980 Ok(DecodeResult { request, event })
981 }
982 fn decode_next(&mut self, cancel: &CancelToken) -> Result<DecodeEvent, DecodeError> {
983 if cancel.is_cancelled() {
984 return Err(DecodeError::Cancelled);
985 }
986 self.fill_pending(self.block_frames, cancel)?;
987 let frames = self
988 .pending
989 .first()
990 .map_or(0, VecDeque::len)
991 .min(self.block_frames);
992 if frames == 0 {
993 return Ok(DecodeEvent::EndOfStream);
994 }
995 let start_sample = self.next_sample.unwrap_or(0);
996 let block = AudioBlock {
997 start_sample,
998 sample_rate: self.sample_rate,
999 speakers: self.speakers,
1000 channels: self
1001 .pending
1002 .iter_mut()
1003 .map(|channel| channel.drain(..frames).collect())
1004 .collect(),
1005 };
1006 self.next_sample =
1007 Some(start_sample.saturating_add(i64::try_from(frames).unwrap_or(i64::MAX)));
1008 Ok(DecodeEvent::Audio(block))
1009 }
1010}
1011
1012fn open_audio_decoder(
1014 input: &mut DemuxInput,
1015 stream_index: usize,
1016) -> Result<AVCodecContext, DecodeError> {
1017 input.discard_all_but(stream_index);
1018 let stream = input
1019 .streams()
1020 .get(stream_index)
1021 .ok_or(DecodeError::OutOfRange)?;
1022 let parameters = stream.codecpar();
1023 let codec = AVCodec::find_decoder(parameters.codec_id)
1024 .ok_or_else(|| DecodeError::Unsupported("audio decoder is unavailable".into()))?;
1025 let mut decoder = AVCodecContext::new(&codec);
1026 decoder.apply_codecpar(¶meters).map_err(adapter_error)?;
1027 decoder.set_pkt_timebase(stream.time_base);
1028 decoder.open(None).map_err(adapter_error)?;
1029 Ok(decoder)
1030}
1031
1032const AUDIO_TIMESTAMP_LIMIT_SECONDS: i128 = 1 << 31;
1035
1036fn audio_timestamp_in_range(pts: i64, time_base: AVRational) -> bool {
1037 let (num, den) = (i128::from(time_base.num), i128::from(time_base.den));
1038 num > 0 && den > 0 && (i128::from(pts) * num).abs() <= AUDIO_TIMESTAMP_LIMIT_SECONDS * den
1039}
1040
1041pub const SOURCE_SAMPLE_RATES: std::ops::RangeInclusive<u32> = 1_000..=768_000;
1043
1044fn check_source_sample_rate(rate: i64) -> Result<(), DecodeError> {
1045 if u32::try_from(rate).is_ok_and(|rate| SOURCE_SAMPLE_RATES.contains(&rate)) {
1046 return Ok(());
1047 }
1048 Err(DecodeError::Unsupported(format!(
1049 "the source's audio is {rate} Hz, and alpha2 reads audio from {} Hz to {} Hz",
1050 SOURCE_SAMPLE_RATES.start(),
1051 SOURCE_SAMPLE_RATES.end()
1052 )))
1053}
1054
1055pub(crate) fn audio_converter(
1058 frame: &AVFrame,
1059 output: &AVChannelLayout,
1060) -> Result<SwrContext, DecodeError> {
1061 if frame.sample_rate <= 0 || frame.format == ffi::AV_SAMPLE_FMT_NONE {
1062 return Err(DecodeError::Adapter(
1063 "decoded audio frame has an incomplete format".into(),
1064 ));
1065 }
1066 let input_layout = frame.ch_layout();
1069 let mut converter = SwrContext::new(
1070 output,
1071 ffi::AV_SAMPLE_FMT_FLT,
1072 frame.sample_rate,
1073 &input_layout,
1074 frame.format,
1075 frame.sample_rate,
1076 )
1077 .map_err(adapter_error)?;
1078 converter.init().map_err(adapter_error)?;
1079 Ok(converter)
1080}
1081
1082pub(crate) fn layout_speakers(layout: &AVChannelLayout) -> Vec<Speaker> {
1084 let channels = alpha2_ffmpeg_bridge::channel_layout_channels(layout);
1085 if channels.is_empty() {
1086 return vec![Speaker::Unnamed; usize::try_from(layout.nb_channels).unwrap_or(0)];
1087 }
1088 channels
1089 .into_iter()
1090 .map(|channel| Speaker::from_ffmpeg_channel(i64::from(channel)))
1091 .collect()
1092}
1093
1094pub(crate) fn ffmpeg_layout(speakers: &SpeakerLayout) -> Result<AVChannelLayout, DecodeError> {
1096 let channels = speakers
1097 .speakers()
1098 .iter()
1099 .map(|speaker| {
1100 ffi::AVChannel::try_from(speaker.ffmpeg_channel()).unwrap_or(ffi::AV_CHAN_UNKNOWN)
1101 })
1102 .collect::<Vec<_>>();
1103 alpha2_ffmpeg_bridge::channel_layout_of(&channels).map_err(|error| {
1104 DecodeError::Adapter(format!(
1105 "could not lay out audio channels {}: {error:?}",
1106 speakers.describe()
1107 ))
1108 })
1109}
1110
1111fn unsupported_channels(channels: usize) -> DecodeError {
1112 DecodeError::Unsupported(format!(
1113 "audio with {channels} channels is unsupported: the engine takes 1 to {MAX_AUDIO_CHANNELS}"
1114 ))
1115}
1116
1117fn check_audio_channels(channels: usize) -> Result<(), DecodeError> {
1118 if (1..=MAX_AUDIO_CHANNELS).contains(&channels) {
1119 Ok(())
1120 } else {
1121 Err(unsupported_channels(channels))
1122 }
1123}
1124
1125pub(crate) fn normalize_audio_frame_layout(
1132 frame: &mut AVFrame,
1133 stream: &AVChannelLayout,
1134) -> Result<(), DecodeError> {
1135 let (channels, named) = {
1136 let layout = frame.ch_layout();
1137 let named = matches!(
1138 layout.order,
1139 ffi::AV_CHANNEL_ORDER_NATIVE | ffi::AV_CHANNEL_ORDER_CUSTOM
1140 ) && !layout_speakers(&layout).contains(&Speaker::Unnamed);
1141 (layout.nb_channels, named)
1142 };
1143 if channels <= 0 {
1144 return Err(DecodeError::Adapter(
1145 "decoded audio frame has no channels".into(),
1146 ));
1147 }
1148 if named {
1149 return Ok(());
1150 }
1151 if channels == stream.nb_channels {
1152 let mut owned = AVChannelLayout::from_nb_channels(1);
1153 owned.copy(stream);
1154 frame.set_ch_layout(owned.into_inner());
1155 return Ok(());
1156 }
1157 let count = usize::try_from(channels).map_err(|_| DecodeError::OutOfRange)?;
1158 let speakers = SpeakerLayout::default_for(count).ok_or_else(|| unsupported_channels(count))?;
1159 frame.set_ch_layout(ffmpeg_layout(&speakers)?.into_inner());
1160 Ok(())
1161}
1162
1163pub(crate) fn audio_output_frame(sample_rate: i32, layout: &AVChannelLayout) -> AVFrame {
1164 let mut frame = AVFrame::new();
1165 frame.set_format(ffi::AV_SAMPLE_FMT_FLT);
1166 frame.set_sample_rate(sample_rate);
1167 let mut owned = AVChannelLayout::from_nb_channels(1);
1168 owned.copy(layout);
1169 frame.set_ch_layout(owned.into_inner());
1170 frame
1171}
1172
1173pub(crate) fn append_interleaved_audio(
1174 frame: &AVFrame,
1175 output: &mut [VecDeque<f32>],
1176) -> Result<(), DecodeError> {
1177 let packed = alpha2_ffmpeg_bridge::audio_f32_interleaved(frame).map_err(adapter_error)?;
1178 if output.is_empty() || packed.len() % output.len() != 0 {
1179 return Err(DecodeError::Adapter(
1180 "FFmpeg returned a partial audio frame".into(),
1181 ));
1182 }
1183 for frame in packed.chunks_exact(output.len()) {
1184 for (channel, sample) in output.iter_mut().zip(frame) {
1185 channel.push_back(*sample);
1186 }
1187 }
1188 Ok(())
1189}
1190
1191pub(crate) fn has_hw_config_method(methods: i32, method: impl Into<i64>) -> bool {
1196 i64::from(methods) & method.into() != 0
1197}
1198
1199const STREAM_START_SEEK: i64 = i64::MIN / 2;
1204
1205enum Landing {
1206 Presented(DecodedFrame),
1208 Late(DecodedFrame),
1210 Empty,
1212}
1213
1214impl NativeDecoder {
1215 pub fn set_decode_policy(&mut self, policy: DecodePolicy) {
1217 self.recovery.policy = policy;
1218 }
1219
1220 pub fn declare_color(&mut self, color: Option<ColorMetadata>) {
1223 self.declared_color = color;
1224 }
1225
1226 #[cfg(all(
1228 any(target_os = "linux", target_os = "macos", target_os = "windows"),
1229 feature = "hwdecode"
1230 ))]
1231 pub(crate) fn picture_chroma_location(&self, frame: &AVFrame) -> alpha2_media::ChromaLocation {
1232 crate::convert::decoded_chroma_location(self.decoder.codec_id, frame.chroma_location)
1233 }
1234
1235 pub(crate) fn picture_color(&self, frame: &AVFrame) -> Option<ColorMetadata> {
1237 self.declared_color
1238 .or_else(|| frame_color_metadata(frame))
1239 .or(self.color)
1240 }
1241
1242 pub fn set_decode_reporter(
1244 &mut self,
1245 reporter: impl Fn(crate::DecodeDiagnostic) + Send + Sync + 'static,
1246 ) {
1247 self.recovery.reporter = Some(Box::new(reporter));
1248 }
1249
1250 #[must_use]
1252 pub fn decode_diagnostics(&self) -> &DecodeDiagnostics {
1253 &self.recovery.diagnostics
1254 }
1255
1256 pub fn open(path: impl AsRef<Path>) -> Result<Self, DecodeError> {
1261 Self::open_with_threads(path, codec_thread_budget())
1262 }
1263
1264 pub fn open_with_threads(path: impl AsRef<Path>, threads: i64) -> Result<Self, DecodeError> {
1269 if !(1..=64).contains(&threads) {
1270 return Err(DecodeError::OutOfRange);
1271 }
1272 let mut input = open_input(path.as_ref())?;
1273 let probed = probe_context(&input, path.as_ref())?;
1274 let video_index = probed.selected_video.ok_or_else(|| {
1275 DecodeError::Unsupported("resource has no decodable video stream".into())
1276 })?;
1277 input.discard_all_but(video_index);
1278 let selected = probed
1279 .streams
1280 .iter()
1281 .find(|stream| stream.index == video_index)
1282 .ok_or(DecodeError::OutOfRange)?;
1283 let orientation = selected.orientation;
1284 let sample_aspect_ratio = selected.sample_aspect_ratio;
1285 let color = selected.color;
1287 let dropped_layer;
1288 let (time_base, decoder) = {
1289 let stream = &input.streams()[video_index];
1290 let parameters = stream.codecpar();
1291 let (codec, dropped) = software_decoder_for(¶meters, selected.alpha)?;
1292 dropped_layer = dropped;
1293 let mut decoder = AVCodecContext::new(&codec);
1294 decoder.apply_codecpar(¶meters).map_err(adapter_error)?;
1295 decoder.set_pkt_timebase(stream.time_base);
1296 let unused = alpha2_ffmpeg_bridge::open_codec_context(
1297 &mut decoder,
1298 video_decoder_options(threads),
1299 )
1300 .map_err(adapter_error)?;
1301 reject_unused_decode_options(unused)?;
1302 (stream.time_base, decoder)
1303 };
1304 let (prefetched, slack) = read_presentation_slack(&mut input, video_index, time_base);
1305 Ok(Self {
1306 time_base,
1307 input,
1308 container_duration: probed.duration,
1309 streams: probed.streams,
1310 video_index,
1311 decoder,
1312 orientation,
1313 sample_aspect_ratio,
1314 color,
1315 declared_color: None,
1316 video_scaler: crate::convert::FrameScaler::default(),
1317 pending: VecDeque::new(),
1318 demux_eof: false,
1319 decoder_flushed: false,
1320 stages: DecodeStageTimings::default(),
1321 first_video_pts: None,
1322 recovery: Recovery::new(time_base),
1323 slack,
1324 prefetched,
1325 dropped_layer,
1326 })
1327 }
1328
1329 #[cfg(all(
1333 any(target_os = "linux", target_os = "macos", target_os = "windows"),
1334 feature = "hwdecode"
1335 ))]
1336 #[allow(clippy::too_many_lines)]
1337 pub(crate) fn open_hardware(
1338 path: impl AsRef<Path>,
1339 threads: i64,
1340 device: AVHWDeviceContext,
1341 spec: &HardwareDecoderSpec<'_>,
1342 ) -> Result<Self, HardwareOpenError> {
1343 use crate::video_frame::HardwareDecodeFallback::{
1344 DecoderOpenFailed, NoHardwareConfig, SourceUnavailable, UnsupportedColor,
1345 };
1346
1347 let unavailable = |error| HardwareOpenError::new(SourceUnavailable, error);
1348 let mut input = open_input(path.as_ref()).map_err(unavailable)?;
1349 let probed = probe_context(&input, path.as_ref()).map_err(unavailable)?;
1350 let video_index = probed.selected_video.ok_or_else(|| {
1351 HardwareOpenError::new(
1352 SourceUnavailable,
1353 DecodeError::Unsupported("resource has no decodable video stream".into()),
1354 )
1355 })?;
1356 input.discard_all_but(video_index);
1357 let selected = probed
1358 .streams
1359 .iter()
1360 .find(|stream| stream.index == video_index)
1361 .ok_or_else(|| HardwareOpenError::new(SourceUnavailable, DecodeError::OutOfRange))?;
1362 let orientation = selected.orientation;
1363 let sample_aspect_ratio = selected.sample_aspect_ratio;
1364 let color = selected.color;
1365 if selected.color.is_some_and(ColorMetadata::is_hdr) {
1366 return Err(HardwareOpenError::new(
1367 UnsupportedColor,
1368 DecodeError::Unsupported("hardware export currently supports SDR only".into()),
1369 ));
1370 }
1371 if let Some(alpha) = selected.alpha {
1374 return Err(HardwareOpenError::new(
1375 crate::video_frame::HardwareDecodeFallback::AlphaChannel,
1376 DecodeError::Unsupported(format!(
1377 "the stream carries alpha ({}), which {} decode drops",
1378 alpha.as_str(),
1379 spec.backend
1380 )),
1381 ));
1382 }
1383 let (time_base, decoder) = {
1384 let stream = &input.streams()[video_index];
1385 let parameters = stream.codecpar();
1386 let preferred = AVCodec::find_decoder(parameters.codec_id).ok_or_else(|| {
1387 HardwareOpenError::new(
1388 NoHardwareConfig,
1389 DecodeError::Unsupported("selected video codec has no decoder".into()),
1390 )
1391 })?;
1392 if let Some(codecs) = spec.hardware_codecs {
1393 let listed = hardware_codec_name(parameters.codec_id);
1394 if !listed.is_some_and(|name| codecs.contains(&name)) {
1395 let name = listed.map_or_else(
1396 || preferred.name().to_string_lossy().into_owned(),
1397 str::to_owned,
1398 );
1399 return Err(HardwareOpenError::new(
1400 NoHardwareConfig,
1401 DecodeError::Unsupported(format!(
1402 "{} has no hardware decoder for {name} here",
1403 spec.backend
1404 )),
1405 ));
1406 }
1407 }
1408 let supported = |codec: &AVCodecRef<'_>| {
1409 (0..)
1410 .map_while(|index| codec.hw_config(index))
1411 .any(|config| {
1412 config.device_type == spec.device_type
1413 && config.pix_fmt == spec.pixel_format
1414 && (!spec.require_device_context
1415 || has_hw_config_method(
1416 config.methods,
1417 ffi::AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX,
1418 ))
1419 })
1420 };
1421 let Some(codec) =
1422 registered_video_decoders(parameters.codec_id).find(|codec| supported(codec))
1423 else {
1424 return Err(HardwareOpenError::new(
1425 NoHardwareConfig,
1426 DecodeError::Unsupported(format!(
1427 "decoder {} has no {} hardware configuration",
1428 preferred.name().to_string_lossy(),
1429 spec.backend
1430 )),
1431 ));
1432 };
1433 check_hardware_sample_profile(parameters.format, spec)?;
1434 check_hardware_crop(¶meters, spec)?;
1435 let open_failed = |error| HardwareOpenError::new(DecoderOpenFailed, error);
1436 let mut decoder = AVCodecContext::new(&codec);
1437 decoder
1438 .apply_codecpar(¶meters)
1439 .map_err(adapter_error)
1440 .map_err(open_failed)?;
1441 decoder.set_pkt_timebase(stream.time_base);
1442 decoder.set_get_format(Some(spec.get_format));
1443 decoder.set_hw_device_ctx(device);
1444 let unused = alpha2_ffmpeg_bridge::open_codec_context(
1446 &mut decoder,
1447 video_decoder_options(threads),
1448 )
1449 .map_err(adapter_error)
1450 .map_err(open_failed)?;
1451 reject_unused_decode_options(unused).map_err(open_failed)?;
1452 (stream.time_base, decoder)
1453 };
1454 let (prefetched, slack) = read_presentation_slack(&mut input, video_index, time_base);
1455 Ok(Self {
1456 time_base,
1457 input,
1458 container_duration: probed.duration,
1459 streams: probed.streams,
1460 video_index,
1461 decoder,
1462 orientation,
1463 sample_aspect_ratio,
1464 color,
1465 declared_color: None,
1466 video_scaler: crate::convert::FrameScaler::default(),
1467 pending: VecDeque::new(),
1468 demux_eof: false,
1469 decoder_flushed: false,
1470 stages: DecodeStageTimings::default(),
1471 first_video_pts: None,
1472 recovery: Recovery::hardware(time_base),
1473 slack,
1474 prefetched,
1475 dropped_layer: None,
1476 })
1477 }
1478
1479 #[must_use]
1481 pub const fn dropped_layer(&self) -> Option<&DroppedLayer> {
1482 self.dropped_layer.as_ref()
1483 }
1484
1485 #[must_use]
1490 pub fn video_duration(&self) -> Option<MediaTime> {
1491 effective_media_duration(
1492 self.streams
1493 .iter()
1494 .find(|stream| stream.index == self.video_index)
1495 .and_then(|stream| stream.duration),
1496 self.container_duration,
1497 )
1498 }
1499
1500 pub(crate) fn take_stage_timings(&mut self) -> DecodeStageTimings {
1502 std::mem::take(&mut self.stages)
1503 }
1504
1505 pub(crate) fn receive_available_raw(
1506 &mut self,
1507 cancel: &CancelToken,
1508 ) -> Result<Option<DecodedFrame>, DecodeError> {
1509 let mut budget = WorkBudget::new();
1510 loop {
1511 budget.step(cancel)?;
1512 let started = Instant::now();
1513 let received = DecodedFrame::receive(&mut self.decoder);
1514 self.stages.receive_frame = self.stages.receive_frame.saturating_add(started.elapsed());
1515 match received {
1516 Ok(frame) => {
1517 if frame.flags
1518 & i32::try_from(ffi::AV_FRAME_FLAG_CORRUPT).expect("flag fits i32")
1519 != 0
1520 || frame.decode_error_flags != 0
1521 {
1522 self.recovery.corrupt("video frame", Some(frame.pts))?;
1523 continue;
1524 }
1525 #[cfg(test)]
1526 VIDEO_FRAMES_DECODED.with(|decoded| decoded.set(decoded.get() + 1));
1527 self.recovery.progress(
1528 if frame.best_effort_timestamp == ffi::AV_NOPTS_VALUE {
1529 frame.pts
1530 } else {
1531 frame.best_effort_timestamp
1532 },
1533 )?;
1534 return Ok(Some(frame));
1535 }
1536 Err(RsmpegError::DecoderDrainError | RsmpegError::DecoderFlushedError) => {
1537 return Ok(None);
1538 }
1539 Err(error) => {
1540 self.recovery.error(error, "video receive", None)?;
1541 }
1542 }
1543 }
1544 }
1545
1546 pub(crate) fn convert_received(
1547 &mut self,
1548 frame: &DecodedFrame,
1549 ) -> Result<DecodedVideoFrame, DecodeError> {
1550 let declared = self.picture_color(frame);
1552 let started = Instant::now();
1553 let converted = convert_video_frame(
1554 frame,
1555 self.decoder.codec_id,
1556 self.time_base,
1557 declared.unwrap_or(ColorMetadata::UNDECLARED),
1558 &mut self.video_scaler,
1559 );
1560 self.stages.convert = self.stages.convert.saturating_add(started.elapsed());
1561 let mut converted = converted?;
1562 if declared.is_none() && converted.color == Some(ColorMetadata::UNDECLARED) {
1565 converted.color = None;
1566 }
1567 converted.orientation = self.orientation;
1568 converted.sample_aspect_ratio = self.picture_sample_aspect_ratio(frame);
1569 Ok(converted)
1570 }
1571
1572 pub(crate) fn picture_sample_aspect_ratio(&self, frame: &AVFrame) -> SampleAspectRatio {
1575 if declared_sar(self.input.streams()[self.video_index].sample_aspect_ratio).is_some() {
1577 return self.sample_aspect_ratio;
1578 }
1579 declared_sar(frame.sample_aspect_ratio).unwrap_or(self.sample_aspect_ratio)
1580 }
1581
1582 pub(crate) fn raw_frame_pts(&self, frame: &AVFrame) -> Result<MediaTime, DecodeError> {
1583 let pts = if frame.best_effort_timestamp == ffi::AV_NOPTS_VALUE {
1584 frame.pts
1585 } else {
1586 frame.best_effort_timestamp
1587 };
1588 timestamp(pts, self.time_base).ok_or(DecodeError::OutOfRange)
1589 }
1590
1591 pub(crate) fn flush_video(&mut self) {
1593 let started = Instant::now();
1594 self.recovery.reset_progress();
1595 self.decoder.flush_buffers();
1596 self.stages.seek = self.stages.seek.saturating_add(started.elapsed());
1597 self.pending.clear();
1598 self.prefetched.clear();
1599 self.demux_eof = false;
1600 self.decoder_flushed = false;
1601 }
1602
1603 pub(crate) fn seek_raw(
1617 &mut self,
1618 target: MediaTime,
1619 cancel: &CancelToken,
1620 ) -> Result<DecodedFrame, DecodeError> {
1621 if cancel.is_cancelled() {
1622 return Err(DecodeError::Cancelled);
1623 }
1624 let start_time = self.input.streams()[self.video_index].start_time;
1625 let stream_start = timestamp(start_time, self.time_base).unwrap_or(MediaTime::ZERO);
1626 let deadline = self.presentation_deadline(target);
1627 let mut back_off = 0_i64;
1628 loop {
1629 let from = MediaTime::from_ticks(deadline.ticks().saturating_sub(back_off));
1632 let from_start =
1633 from <= stream_start || self.first_video_pts.is_some_and(|first| deadline < first);
1634 if !from_start {
1635 let position = av_rescale_q_rnd(
1637 from.ticks(),
1638 tick_base(),
1639 self.time_base,
1640 ffi::AV_ROUND_DOWN,
1641 );
1642 self.seek_video_to(position, cancel)?;
1643 } else if let Err(error) = self.seek_video_to(STREAM_START_SEEK, cancel) {
1644 if error == DecodeError::Cancelled {
1645 return Err(error);
1646 }
1647 let start = if start_time == ffi::AV_NOPTS_VALUE {
1648 0
1649 } else {
1650 start_time
1651 };
1652 self.seek_video_to(start, cancel)?;
1653 }
1654 let mut first = None;
1655 let landing = self.present_after_seek(target, &mut first, cancel)?;
1656 if from_start {
1657 self.first_video_pts = first.or(self.first_video_pts);
1659 }
1660 match landing {
1661 Landing::Presented(frame) => return Ok(frame),
1662 Landing::Late(frame) if from_start || first == self.first_video_pts => {
1663 return Ok(frame);
1664 }
1665 Landing::Empty if from_start => return Err(DecodeError::OutOfRange),
1666 Landing::Late(_) | Landing::Empty => {}
1667 }
1668 back_off = if back_off == 0 {
1669 TICKS_PER_SECOND
1670 } else {
1671 back_off.saturating_mul(2)
1672 };
1673 }
1674 }
1675
1676 fn seek_video_to(&mut self, position: i64, cancel: &CancelToken) -> Result<(), DecodeError> {
1677 interrupt_on_cancel(&self.input, cancel);
1678 let seek_started = Instant::now();
1679 let sought = self.input.seek(
1680 i32::try_from(self.video_index).map_err(|_| DecodeError::OutOfRange)?,
1681 position,
1682 i32::try_from(ffi::AVSEEK_FLAG_BACKWARD).expect("seek flag fits i32"),
1683 );
1684 self.stages.seek = self.stages.seek.saturating_add(seek_started.elapsed());
1685 sought.map_err(|error| seek_failure(error, cancel))?;
1686 self.flush_video();
1687 Ok(())
1688 }
1689
1690 fn present_after_seek(
1693 &mut self,
1694 target: MediaTime,
1695 first: &mut Option<MediaTime>,
1696 cancel: &CancelToken,
1697 ) -> Result<Landing, DecodeError> {
1698 let deadline = self.presentation_deadline(target);
1699 let mut presented: Option<DecodedFrame> = None;
1700 let mut budget = WorkBudget::new();
1701 loop {
1702 budget.step(cancel)?;
1703 let Some(raw) = self.decode_raw_next(cancel)? else {
1704 return Ok(presented.map_or(Landing::Empty, Landing::Presented));
1705 };
1706 budget.progressed();
1708 #[cfg(test)]
1709 crate::recovery::take_frame_delay();
1710 let pts = self.raw_frame_pts(&raw)?;
1711 first.get_or_insert(pts);
1712 if pts > deadline {
1713 return Ok(match presented {
1714 Some(frame) => {
1715 self.pending.push_front(raw);
1716 Landing::Presented(frame)
1717 }
1718 None => Landing::Late(raw),
1719 });
1720 }
1721 if pts >= target {
1723 return Ok(Landing::Presented(raw));
1724 }
1725 presented = Some(raw);
1726 }
1727 }
1728
1729 #[must_use]
1732 pub fn presentation_deadline(&self, time: MediaTime) -> MediaTime {
1733 time.presentation_deadline(self.slack)
1734 }
1735
1736 pub fn peek_video_pts(
1744 &mut self,
1745 cancel: &CancelToken,
1746 ) -> Result<Option<MediaTime>, DecodeError> {
1747 self.peek_raw_pts(cancel)
1748 }
1749
1750 pub(crate) fn peek_raw_pts(
1756 &mut self,
1757 cancel: &CancelToken,
1758 ) -> Result<Option<MediaTime>, DecodeError> {
1759 let Some(raw) = self.decode_raw_next(cancel)? else {
1760 return Ok(None);
1761 };
1762 let pts = self.raw_frame_pts(&raw);
1763 self.pending.push_front(raw);
1764 pts.map(Some)
1765 }
1766
1767 pub(crate) fn decode_raw_next(
1768 &mut self,
1769 cancel: &CancelToken,
1770 ) -> Result<Option<DecodedFrame>, DecodeError> {
1771 if cancel.is_cancelled() {
1772 return Err(DecodeError::Cancelled);
1773 }
1774 if let Some(frame) = self.pending.pop_front() {
1775 return Ok(Some(frame));
1776 }
1777 interrupt_on_cancel(&self.input, cancel);
1778 let mut budget = WorkBudget::new();
1779 let mut foreign = false;
1780 loop {
1781 if std::mem::take(&mut foreign) {
1783 if cancel.is_cancelled() {
1784 return Err(DecodeError::Cancelled);
1785 }
1786 } else {
1787 budget.step(cancel)?;
1788 }
1789 if let Some(frame) = self.pending.pop_front() {
1790 return Ok(Some(frame));
1791 }
1792 if let Some(frame) = self.receive_available_raw(cancel)? {
1793 return Ok(Some(frame));
1794 }
1795 if self.demux_eof {
1796 if !self.decoder_flushed {
1797 match self.decoder.send_packet(None) {
1798 Ok(()) | Err(RsmpegError::DecoderFlushedError) => {}
1799 Err(RsmpegError::DecoderFullError) => continue,
1800 Err(error) => {
1801 self.recovery.error(error, "flush", None)?;
1802 continue;
1803 }
1804 }
1805 self.decoder_flushed = true;
1806 continue;
1807 }
1808 self.recovery.video_eof()?;
1809 return Ok(None);
1810 }
1811 let demux_started = Instant::now();
1812 let packet = self
1813 .prefetched
1814 .pop_front()
1815 .unwrap_or_else(|| self.input.read_packet());
1816 self.stages.demux = self.stages.demux.saturating_add(demux_started.elapsed());
1817 let packet = match packet {
1818 Ok(Some(packet)) => packet,
1819 Ok(None) => {
1820 self.demux_eof = true;
1821 continue;
1822 }
1823 Err(error) => {
1824 let error = demux_failure(error, cancel)?;
1825 self.recovery.error(error, "video demux", None)?;
1826 continue;
1827 }
1828 };
1829 if usize::try_from(packet.stream_index).ok() != Some(self.video_index) {
1830 foreign = true;
1831 continue;
1832 }
1833 if packet.flags & i32::try_from(ffi::AV_PKT_FLAG_CORRUPT).expect("flag fits i32") != 0 {
1834 self.recovery.corrupt("video packet", Some(packet.pts))?;
1835 continue;
1836 }
1837 submit_packet(
1838 |drain| {
1839 if drain {
1840 let corrupt_before = self.recovery.diagnostics.corrupt_packets.len();
1841 if let Some(frame) = self.receive_available_raw(cancel)? {
1842 self.pending.push_back(frame);
1843 return Ok(true);
1844 }
1845 return Ok(self.recovery.diagnostics.corrupt_packets.len() > corrupt_before);
1846 }
1847 let started = Instant::now();
1848 let sent = self.decoder.send_packet(Some(&packet));
1849 self.stages.send_packet += started.elapsed();
1850 match sent {
1851 Ok(()) => Ok(true),
1852 Err(RsmpegError::DecoderFullError) => Ok(false),
1853 Err(error) => {
1854 self.recovery.error(error, "video send", Some(packet.pts))?;
1855 Ok(true)
1856 }
1857 }
1858 },
1859 &mut budget,
1860 cancel,
1861 )?;
1862 }
1863 }
1864}
1865
1866impl Decoder for NativeDecoder {
1867 fn streams(&self) -> &[StreamInfo] {
1868 &self.streams
1869 }
1870
1871 fn flush(&mut self) -> Result<(), DecodeError> {
1872 self.flush_video();
1873 Ok(())
1874 }
1875
1876 fn seek(
1877 &mut self,
1878 request: DecodeRequest,
1879 cancel: &CancelToken,
1880 ) -> Result<DecodeResult, DecodeError> {
1881 let raw = self.seek_raw(request.time, cancel)?;
1882 let frame = self.convert_received(&raw)?;
1883 Ok(DecodeResult {
1884 request,
1885 event: DecodeEvent::Video(frame),
1886 })
1887 }
1888
1889 fn decode_next(&mut self, cancel: &CancelToken) -> Result<DecodeEvent, DecodeError> {
1890 if cancel.is_cancelled() {
1891 return Err(DecodeError::Cancelled);
1892 }
1893 match self.decode_raw_next(cancel)? {
1894 Some(frame) => Ok(DecodeEvent::Video(self.convert_received(&frame)?)),
1895 None => Ok(DecodeEvent::EndOfStream),
1896 }
1897 }
1898}
1899
1900const HWACCEL_ONLY_DECODERS: &[&str] = &["av1"];
1903
1904pub(crate) fn registered_video_decoders(
1908 codec_id: ffi::AVCodecID,
1909) -> impl Iterator<Item = AVCodecRef<'static>> {
1910 AVCodec::iterate().filter(move |codec| {
1911 codec.id == codec_id
1912 && AVCodec::find_decoder_by_name(codec.name())
1913 .is_some_and(|found| found.as_ptr() == codec.as_ptr())
1914 })
1915}
1916
1917pub(crate) fn software_video_decoder(
1919 codec_id: ffi::AVCodecID,
1920) -> Result<AVCodecRef<'static>, DecodeError> {
1921 let Some(preferred) = AVCodec::find_decoder(codec_id) else {
1922 return Err(DecodeError::Unsupported(
1923 "selected video codec has no decoder".into(),
1924 ));
1925 };
1926 let hardware_only =
1927 |codec: &AVCodecRef<'_>| HWACCEL_ONLY_DECODERS.contains(&&*codec.name().to_string_lossy());
1928 if !hardware_only(&preferred) {
1929 return Ok(preferred);
1930 }
1931 let experimental = i32::try_from(ffi::AV_CODEC_CAP_EXPERIMENTAL).unwrap_or(0);
1932 AVCodec::iterate()
1933 .find(|codec| {
1934 codec.id == codec_id
1935 && AVCodec::find_decoder_by_name(codec.name())
1936 .is_some_and(|found| found.as_ptr() == codec.as_ptr())
1937 && codec.capabilities & experimental == 0
1938 && !hardware_only(codec)
1939 })
1940 .ok_or_else(|| DecodeError::Unsupported(hardware_only_reason(&preferred)))
1941}
1942
1943const NO_LIBVPX_VP9: &str = "this FFmpeg runtime has no libvpx-vp9 decoder";
1944const HEVC_ALPHA_NOT_DECODED: &str = "alpha in an HEVC auxiliary layer is not decoded";
1945
1946fn libvpx_vp9() -> Option<AVCodecRef<'static>> {
1948 #[cfg(test)]
1949 if WITHOUT_LIBVPX_VP9.with(std::cell::Cell::get) {
1950 return None;
1951 }
1952 AVCodec::find_decoder_by_name(c"libvpx-vp9")
1953}
1954
1955#[must_use]
1957pub fn decodes_vp9_alpha() -> bool {
1958 libvpx_vp9().is_some()
1959}
1960
1961#[must_use]
1963pub fn alpha_dropped_reason(stream: &StreamInfo) -> Option<&'static str> {
1964 match stream.alpha? {
1965 SourceAlpha::Vp9SideData => libvpx_vp9().is_none().then_some(NO_LIBVPX_VP9),
1966 SourceAlpha::HevcAuxiliaryLayer => Some(HEVC_ALPHA_NOT_DECODED),
1967 SourceAlpha::Planar => None,
1968 }
1969}
1970
1971fn software_decoder_for(
1976 parameters: &rsmpeg::avcodec::AVCodecParameters,
1977 alpha: Option<SourceAlpha>,
1978) -> Result<(AVCodecRef<'static>, Option<DroppedLayer>), DecodeError> {
1979 let codec = software_video_decoder(parameters.codec_id)?;
1980 let dropped = |code, reason: &str| {
1981 Some(DroppedLayer {
1982 code,
1983 reason: reason.to_owned(),
1984 })
1985 };
1986 match alpha {
1987 Some(SourceAlpha::Vp9SideData) => Ok(match libvpx_vp9() {
1988 Some(libvpx) => (libvpx, None),
1989 None => (codec, dropped("alpha-dropped", NO_LIBVPX_VP9)),
1990 }),
1991 Some(SourceAlpha::HevcAuxiliaryLayer) => {
1992 Ok((codec, dropped("alpha-dropped", HEVC_ALPHA_NOT_DECODED)))
1993 }
1994 _ if parameters.codec_id == ffi::AV_CODEC_ID_HEVC
1995 && crate::parameter_sets::hevc_layers(
1996 &alpha2_ffmpeg_bridge::codec_parameters_extradata(parameters),
1997 ) == Some(crate::parameter_sets::HevcLayers::Extra) =>
1998 {
1999 Ok((
2000 codec,
2001 dropped(
2002 "extra-layers-dropped",
2003 "the HEVC layers besides the base layer are not decoded",
2004 ),
2005 ))
2006 }
2007 _ => Ok((codec, None)),
2008 }
2009}
2010
2011fn source_alpha(
2014 metadata: Option<rsmpeg::avutil::AVDictionaryRef<'_>>,
2015 parameters: &rsmpeg::avcodec::AVCodecParameters,
2016) -> Option<SourceAlpha> {
2017 let tagged = || {
2018 metadata
2019 .and_then(|metadata| {
2020 metadata
2021 .get(c"alpha_mode", None, 0)
2022 .map(|entry| entry.value().to_bytes() == b"1")
2023 })
2024 .unwrap_or(false)
2025 };
2026 match parameters.codec_id {
2027 ffi::AV_CODEC_ID_VP9 if tagged() => return Some(SourceAlpha::Vp9SideData),
2028 ffi::AV_CODEC_ID_HEVC
2029 if crate::parameter_sets::hevc_layers(
2030 &alpha2_ffmpeg_bridge::codec_parameters_extradata(parameters),
2031 ) == Some(crate::parameter_sets::HevcLayers::AuxiliaryAlpha) =>
2032 {
2033 return Some(SourceAlpha::HevcAuxiliaryLayer);
2034 }
2035 _ => {}
2036 }
2037 alpha2_ffmpeg_bridge::sample_profile(parameters.format)
2038 .filter(|profile| profile.has_alpha)
2039 .map(|_| SourceAlpha::Planar)
2040}
2041
2042fn hardware_only_reason(decoder: &AVCodecRef<'_>) -> String {
2043 let name = decoder.name().to_string_lossy();
2044 format!(
2045 "{name} needs a hardware decoder; this runtime has no software {} decoder",
2046 name.to_uppercase()
2047 )
2048}
2049
2050#[must_use]
2053pub fn software_decode_unsupported(stream: &StreamInfo) -> Option<String> {
2054 if stream.kind != StreamKind::Video {
2055 return None;
2056 }
2057 let name = CString::new(stream.codec.as_str()).ok()?;
2058 let decoder = AVCodec::find_decoder_by_name(&name)?;
2059 software_video_decoder(decoder.id)
2060 .err()
2061 .map(|error| match error {
2062 DecodeError::Unsupported(reason) => reason,
2063 error => error.to_string(),
2064 })
2065}
2066
2067pub(crate) fn effective_media_duration(
2069 stream_duration: Option<MediaTime>,
2070 container_duration: Option<MediaTime>,
2071) -> Option<MediaTime> {
2072 stream_duration.into_iter().chain(container_duration).max()
2073}
2074
2075fn decoded_frame_bytes(format: i32, (width, height): (u32, u32)) -> Option<u64> {
2077 let size = rsmpeg::avutil::AVImage::get_buffer_size(
2078 format,
2079 i32::try_from(width).ok()?,
2080 i32::try_from(height).ok()?,
2081 1,
2082 )?;
2083 u64::try_from(size).ok().filter(|bytes| *bytes > 0)
2084}
2085
2086#[cfg(all(
2088 any(target_os = "linux", target_os = "macos", target_os = "windows"),
2089 feature = "hwdecode"
2090))]
2091const fn hardware_codec_name(codec_id: ffi::AVCodecID) -> Option<&'static str> {
2092 match codec_id {
2093 ffi::AV_CODEC_ID_H264 => Some("h264"),
2094 ffi::AV_CODEC_ID_HEVC => Some("hevc"),
2095 ffi::AV_CODEC_ID_VP9 => Some("vp9"),
2096 ffi::AV_CODEC_ID_AV1 => Some("av1"),
2097 _ => None,
2098 }
2099}
2100
2101#[cfg(all(
2104 any(target_os = "linux", target_os = "macos", target_os = "windows"),
2105 feature = "hwdecode"
2106))]
2107fn check_hardware_crop(
2108 parameters: &rsmpeg::avcodec::AVCodecParameters,
2109 spec: &HardwareDecoderSpec<'_>,
2110) -> Result<(), HardwareOpenError> {
2111 if spec.keeps_leading_crop {
2112 return Ok(());
2113 }
2114 let extradata = alpha2_ffmpeg_bridge::codec_parameters_extradata(parameters);
2115 match crate::parameter_sets::leading_crop(parameters.codec_id, &extradata) {
2116 Some((left, top)) if left > 0 || top > 0 => Err(HardwareOpenError::new(
2117 crate::video_frame::HardwareDecodeFallback::UnsupportedCrop,
2118 DecodeError::Unsupported(format!(
2119 "{} decode does not show a picture cropped {left} columns from the left and {top} rows from the top",
2120 spec.backend
2121 )),
2122 )),
2123 _ => Ok(()),
2124 }
2125}
2126
2127#[cfg(all(
2132 any(target_os = "linux", target_os = "macos", target_os = "windows"),
2133 feature = "hwdecode"
2134))]
2135fn check_hardware_sample_profile(
2136 format: ffi::AVPixelFormat,
2137 spec: &HardwareDecoderSpec<'_>,
2138) -> Result<(), HardwareOpenError> {
2139 let Some(profile) = alpha2_ffmpeg_bridge::sample_profile(format) else {
2140 return Ok(());
2141 };
2142 let maximum_depth = if spec.ten_bit { 10 } else { 8 };
2143 if profile.is_yuv_420 && !profile.has_alpha && profile.bit_depth <= maximum_depth {
2144 return Ok(());
2145 }
2146 Err(HardwareOpenError::new(
2147 crate::video_frame::HardwareDecodeFallback::UnsupportedProfile,
2148 DecodeError::Unsupported(format!(
2149 "{} decode does not present {}-bit {} input",
2150 spec.backend,
2151 profile.bit_depth,
2152 if profile.is_yuv_420 {
2153 "4:2:0"
2154 } else {
2155 "non-4:2:0"
2156 }
2157 )),
2158 ))
2159}
2160
2161pub(crate) const SOURCE_CONTAINERS: &CStr =
2165 c"mov,matroska,mpegts,avi,mp3,aac,wav,flac,ogg,png_pipe,jpeg_pipe,webp_pipe,gif";
2166
2167pub(crate) fn open_input(path: &Path) -> Result<DemuxInput, DecodeError> {
2170 #[cfg(test)]
2171 INPUTS_OPENED.with(|opened| opened.set(opened.get() + 1));
2172 alpha2_ffmpeg_bridge::open_input(path, SOURCE_CONTAINERS).map_err(|error| match error {
2173 OpenInputError::Path(error) => DecodeError::Unsupported(format!("media path {error}")),
2174 OpenInputError::Refused(format) => DecodeError::Unsupported(format!(
2175 "the file is {format} input, which is not a container alpha2 reads"
2176 )),
2177 OpenInputError::Ffmpeg(error) => adapter_error(error),
2178 })
2179}
2180
2181#[cfg(test)]
2182thread_local! {
2183 pub(crate) static INPUTS_OPENED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2185 pub(crate) static VIDEO_FRAMES_DECODED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2187}
2188
2189pub(crate) fn interrupt_on_cancel(input: &DemuxInput, cancel: &CancelToken) {
2191 let cancel = cancel.clone();
2192 input.interrupt_when(move || cancel.is_cancelled());
2193}
2194
2195fn demux_failure(error: RsmpegError, cancel: &CancelToken) -> Result<RsmpegError, DecodeError> {
2197 if cancel.is_cancelled() {
2198 Err(DecodeError::Cancelled)
2199 } else {
2200 Ok(error)
2201 }
2202}
2203
2204fn seek_failure(error: RsmpegError, cancel: &CancelToken) -> DecodeError {
2206 demux_failure(error, cancel).map_or_else(|cancelled| cancelled, adapter_error)
2207}
2208
2209const STILL_IMAGE_FORMATS: [&CStr; 3] = [c"jpeg_pipe", c"png_pipe", c"webp_pipe"];
2212
2213fn is_still_image_format(input: &DemuxInput) -> bool {
2214 STILL_IMAGE_FORMATS.contains(&input.iformat().name())
2215}
2216
2217fn still_image_orientation(path: &Path, index: usize) -> Option<Orientation> {
2220 let mut input = open_input(path).ok()?;
2221 let (codec, mut decoder) = {
2222 let stream = input.streams().get(index)?;
2223 let parameters = stream.codecpar();
2224 let codec = software_video_decoder(parameters.codec_id).ok()?;
2225 let mut decoder = AVCodecContext::new(&codec);
2226 decoder.apply_codecpar(¶meters).ok()?;
2227 decoder.set_pkt_timebase(stream.time_base);
2228 (codec, decoder)
2229 };
2230 let mut options = video_decoder_options(1);
2231 if codec.max_lowres > 0 {
2232 options = options.set_int(c"lowres", i64::from(codec.max_lowres), 0);
2233 }
2234 alpha2_ffmpeg_bridge::open_codec_context(&mut decoder, options).ok()?;
2235 let mut sent = false;
2236 for _ in 0..16 {
2238 match decoder.receive_frame() {
2239 Ok(frame) => {
2240 return alpha2_ffmpeg_bridge::frame_display_rotation(&frame)
2241 .and_then(orientation_from_display);
2242 }
2243 Err(RsmpegError::DecoderDrainError) => {}
2244 Err(_) => return None,
2245 }
2246 match input.read_packet().ok()? {
2247 Some(packet) if usize::try_from(packet.stream_index).ok() == Some(index) => {
2248 decoder.send_packet(Some(&packet)).ok()?;
2249 sent = true;
2250 }
2251 Some(_) => {}
2252 None if sent => {
2253 decoder.send_packet(None).ok()?;
2254 sent = false;
2255 }
2256 None => return None,
2257 }
2258 }
2259 None
2260}
2261
2262#[allow(clippy::too_many_lines)]
2263const PARSED_END_READ: u64 = 250_000;
2266
2267fn parsed_end(input: &DemuxInput, path: &Path) -> Option<i64> {
2274 if input.duration_estimation_method != ffi::AVFMT_DURATION_FROM_PTS {
2275 return None;
2276 }
2277 let size = std::fs::metadata(path).ok()?.len();
2278 let mut tail = open_input(path).ok()?;
2279 let streams = tail
2280 .streams()
2281 .iter()
2282 .map(|stream| {
2283 let kind = stream.codecpar().codec_type();
2284 (kind.is_audio() || kind.is_video()).then_some(stream.time_base)
2285 })
2286 .collect::<Vec<_>>();
2287 let av_time_base = ra(1, i32::try_from(ffi::AV_TIME_BASE).ok()?);
2288 for doubling in 0..6 {
2289 let from = size.saturating_sub(PARSED_END_READ << doubling);
2290 tail.seek(
2291 -1,
2292 i64::try_from(from).ok()?,
2293 i32::try_from(ffi::AVSEEK_FLAG_BYTE).ok()?,
2294 )
2295 .ok()?;
2296 let mut end = None::<i64>;
2297 while let Ok(Some(packet)) = tail.read_packet() {
2298 let Some(Some(time_base)) = usize::try_from(packet.stream_index)
2299 .ok()
2300 .and_then(|index| streams.get(index))
2301 else {
2302 continue;
2303 };
2304 if packet.pts == ffi::AV_NOPTS_VALUE {
2305 continue;
2306 }
2307 let packet_end = av_rescale_q(
2308 packet.pts.saturating_add(packet.duration.max(0)),
2309 *time_base,
2310 av_time_base,
2311 );
2312 end = Some(end.map_or(packet_end, |end: i64| end.max(packet_end)));
2313 }
2314 if end.is_some() || from == 0 {
2315 return end;
2316 }
2317 }
2318 None
2319}
2320
2321fn guessed_frame_rate(stream: &rsmpeg::avformat::AVStreamRef<'_>) -> Option<FrameRate> {
2322 stream
2323 .guess_framerate()
2324 .filter(|rate| rate.num > 0 && rate.den > 0)
2325 .and_then(|rate| {
2326 FrameRate::new(u32::try_from(rate.num).ok()?, u32::try_from(rate.den).ok()?).ok()
2327 })
2328}
2329
2330fn metadata_text(
2332 metadata: Option<rsmpeg::avutil::AVDictionaryRef<'_>>,
2333 key: &std::ffi::CStr,
2334) -> Option<String> {
2335 let metadata = metadata?;
2336 let entry = metadata.get(key, None, 0)?;
2337 let value = entry.value().to_string_lossy().trim().to_owned();
2338 (!value.is_empty()).then_some(value)
2339}
2340
2341pub(crate) fn probe_context(input: &DemuxInput, path: &Path) -> Result<ProbeResult, DecodeError> {
2343 let selected_video = input
2344 .best_stream(ffi::AVMEDIA_TYPE_VIDEO)
2345 .map_err(adapter_error)?;
2346 let selected_audio = input
2347 .best_stream(ffi::AVMEDIA_TYPE_AUDIO)
2348 .map_err(adapter_error)?;
2349 let streams = input
2350 .streams()
2351 .iter()
2352 .enumerate()
2353 .filter_map(|(index, stream)| {
2354 let parameters = stream.codecpar();
2355 let kind = if parameters.codec_type().is_video() {
2356 StreamKind::Video
2357 } else if parameters.codec_type().is_audio() {
2358 StreamKind::Audio
2359 } else {
2360 return None;
2361 };
2362 let codec = AVCodec::find_decoder(parameters.codec_id).map_or_else(
2363 || "unknown".to_owned(),
2364 |codec| codec.name().to_string_lossy().into_owned(),
2365 );
2366 let dimensions = (kind == StreamKind::Video).then(|| {
2367 (
2368 u32::try_from(parameters.width).unwrap_or(0),
2369 u32::try_from(parameters.height).unwrap_or(0),
2370 )
2371 });
2372 let frame_rate = guessed_frame_rate(stream);
2373 Some(StreamInfo {
2374 index,
2375 kind,
2376 codec,
2377 duration: timestamp(stream.duration, stream.time_base),
2378 dimensions,
2379 frame_rate,
2380 sample_rate: (parameters.sample_rate > 0)
2381 .then(|| u32::try_from(parameters.sample_rate).unwrap_or(0)),
2382 channels: (kind == StreamKind::Audio)
2383 .then(|| u32::try_from(parameters.ch_layout.nb_channels).unwrap_or(0)),
2384 orientation: alpha2_ffmpeg_bridge::display_rotation(¶meters)
2385 .and_then(orientation_from_display)
2386 .or_else(|| {
2387 (kind == StreamKind::Video
2388 && Some(index) == selected_video
2389 && is_still_image_format(input))
2390 .then(|| still_image_orientation(path, index))
2391 .flatten()
2392 })
2393 .unwrap_or_else(|| Orientation::rotated(stream_rotation(stream.metadata()))),
2394 sample_aspect_ratio: guess_sar(
2395 stream.sample_aspect_ratio,
2396 parameters.sample_aspect_ratio,
2397 ),
2398 color: if kind == StreamKind::Video {
2399 color_metadata(¶meters)
2400 } else {
2401 None
2402 },
2403 alpha: (kind == StreamKind::Video)
2404 .then(|| source_alpha(stream.metadata(), ¶meters))
2405 .flatten(),
2406 frame_bytes: dimensions
2407 .and_then(|size| decoded_frame_bytes(parameters.format, size)),
2408 language: metadata_text(stream.metadata(), c"language"),
2409 title: metadata_text(stream.metadata(), c"title"),
2410 })
2411 })
2412 .collect();
2413 let av_time_base = ra(
2414 1,
2415 i32::try_from(ffi::AV_TIME_BASE).expect("time base fits i32"),
2416 );
2417 let duration = parsed_end(input, path)
2418 .filter(|_| input.start_time != ffi::AV_NOPTS_VALUE)
2419 .map(|end| end.saturating_sub(input.start_time))
2420 .map_or(input.duration, |parsed| parsed.max(input.duration));
2421 Ok(ProbeResult {
2422 format: input.iformat().name().to_string_lossy().into_owned(),
2423 duration: (duration > 0)
2424 .then(|| MediaTime::from_ticks(av_rescale_q(duration, av_time_base, tick_base()))),
2425 start: (input.start_time != ffi::AV_NOPTS_VALUE).then(|| {
2426 MediaTime::from_ticks(av_rescale_q(
2427 input.start_time,
2428 ra(
2429 1,
2430 i32::try_from(ffi::AV_TIME_BASE).expect("time base fits i32"),
2431 ),
2432 tick_base(),
2433 ))
2434 }),
2435 streams,
2436 selected_video,
2437 selected_audio,
2438 })
2439}