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