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, Speaker,
25 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>,
51 pub streams: Vec<StreamInfo>,
55 pub selected_video: Option<usize>,
59 pub selected_audio: Option<usize>,
63}
64
65impl ProbeResult {
66 #[must_use]
68 pub fn stream(&self, index: usize) -> Option<&StreamInfo> {
69 self.streams.iter().find(|stream| stream.index == index)
70 }
71
72 #[must_use]
74 pub fn selected_video_stream(&self) -> Option<&StreamInfo> {
75 self.selected_video.and_then(|index| self.stream(index))
76 }
77
78 #[must_use]
80 pub fn selected_audio_stream(&self) -> Option<&StreamInfo> {
81 self.selected_audio.and_then(|index| self.stream(index))
82 }
83}
84
85pub fn probe(path: impl AsRef<Path>) -> Result<ProbeResult, DecodeError> {
90 let input = open_input(path.as_ref())?;
91 probe_context(&input, path.as_ref())
92}
93
94pub fn visit_packet_times(
103 path: impl AsRef<Path>,
104 stream: usize,
105 mut visit: impl FnMut(f64) -> bool,
106) -> Result<(), DecodeError> {
107 let mut input = open_input(path.as_ref())?;
108 let time_base = input
109 .streams()
110 .get(stream)
111 .map(|found| found.time_base)
112 .ok_or_else(|| DecodeError::Unsupported(format!("the file has no stream {stream}")))?;
113 input.discard_all_but(stream);
114 while let Some(packet) = input.read_packet().map_err(adapter_error)? {
115 if usize::try_from(packet.stream_index).ok() != Some(stream) {
116 continue;
117 }
118 let time = [packet.pts, packet.dts]
119 .into_iter()
120 .find(|time| *time != ffi::AV_NOPTS_VALUE);
121 let Some(time) = time else { continue };
122 #[allow(clippy::cast_precision_loss)] let seconds = time as f64 * f64::from(time_base.num) / f64::from(time_base.den);
124 if !visit(seconds) {
125 break;
126 }
127 }
128 Ok(())
129}
130
131pub struct NativeDecoder {
133 pub(crate) input: DemuxInput,
134 pub(crate) streams: Vec<StreamInfo>,
135 pub(crate) container_duration: Option<MediaTime>,
136 pub(crate) video_index: usize,
137 pub(crate) time_base: AVRational,
138 pub(crate) decoder: AVCodecContext,
139 pub(crate) orientation: Orientation,
140 pub(crate) sample_aspect_ratio: SampleAspectRatio,
143 pub(crate) color: Option<ColorMetadata>,
145 pub(crate) declared_color: Option<ColorMetadata>,
148 pub(crate) video_scaler: crate::convert::FrameScaler,
149 pub(crate) pending: VecDeque<DecodedFrame>,
150 pub(crate) demux_eof: bool,
151 pub(crate) decoder_flushed: bool,
152 pub(crate) stages: DecodeStageTimings,
153 first_video_pts: Option<MediaTime>,
156 recovery: Recovery,
157 slack: MediaTime,
161 prefetched: Prefetched,
165}
166
167type Prefetched = VecDeque<Result<Option<AVPacket>, RsmpegError>>;
170
171fn read_first_packets(input: &mut DemuxInput, index: usize) -> (Prefetched, Vec<i64>) {
182 let mut read = VecDeque::new();
183 let mut timestamps = Vec::with_capacity(alpha2_time::ROUNDING_SCAN_FRAMES);
184 let (mut packets, mut bytes) = (0, 0_u64);
185 while packets < alpha2_time::ROUNDING_SCAN_FRAMES && bytes < alpha2_time::ROUNDING_SCAN_BYTES {
186 let packet = input.read_packet();
187 let more = match &packet {
188 Ok(Some(packet)) => {
189 if usize::try_from(packet.stream_index).ok() == Some(index) {
190 packets += 1;
191 bytes = bytes.saturating_add(u64::try_from(packet.size).unwrap_or(0));
192 if packet.pts != ffi::AV_NOPTS_VALUE {
193 timestamps.push(packet.pts);
194 }
195 }
196 true
197 }
198 Ok(None) | Err(_) => false,
199 };
200 read.push_back(packet);
201 if !more {
202 break;
203 }
204 }
205 (read, timestamps)
206}
207
208fn read_presentation_slack(
212 input: &mut DemuxInput,
213 video_index: usize,
214 time_base: AVRational,
215) -> (Prefetched, MediaTime) {
216 let (read, timestamps) = read_first_packets(input, video_index);
217 let slack = alpha2_time::presentation_slack(
218 i64::from(time_base.num),
219 i64::from(time_base.den),
220 alpha2_time::rounding_quantum(×tamps),
221 );
222 (read, slack)
223}
224
225#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
231pub struct DecodeStageTimings {
232 pub demux: Duration,
234 pub send_packet: Duration,
236 pub receive_frame: Duration,
240 pub convert: Duration,
243 pub seek: Duration,
245}
246
247impl DecodeStageTimings {
248 #[must_use]
250 pub fn merged(self, other: Self) -> Self {
251 Self {
252 demux: self.demux.saturating_add(other.demux),
253 send_packet: self.send_packet.saturating_add(other.send_packet),
254 receive_frame: self.receive_frame.saturating_add(other.receive_frame),
255 convert: self.convert.saturating_add(other.convert),
256 seek: self.seek.saturating_add(other.seek),
257 }
258 }
259}
260
261#[derive(Clone, Copy, PartialEq, Eq)]
263enum InputPosition {
264 Opened,
266 Moved,
268}
269
270#[derive(Clone, Copy, PartialEq, Eq)]
271enum AudioContinuity {
272 Continuous,
273 Damaged,
274}
275
276pub struct NativeAudioDecoder {
285 pub(crate) path: PathBuf,
286 pub(crate) input: DemuxInput,
287 input_position: InputPosition,
289 pub(crate) streams: Vec<StreamInfo>,
290 pub(crate) container_duration: Option<MediaTime>,
291 pub(crate) audio_index: usize,
292 pub(crate) time_base: AVRational,
293 pub(crate) decoder: AVCodecContext,
294 converter: Option<SwrContext>,
297 pub(crate) sample_rate: u32,
298 pub(crate) channel_count: usize,
299 speakers: SpeakerLayout,
301 speakers_assumed: bool,
303 output_layout: AVChannelLayout,
305 timeline: SourceTimeline,
307 pub(crate) pending: Vec<VecDeque<f32>>,
308 pub(crate) next_sample: Option<i64>,
311 pub(crate) block_frames: usize,
312 pub(crate) demux_eof: bool,
313 pub(crate) decoder_flushed: bool,
314 recovery: Recovery,
315 repair_audio: AudioContinuity,
316 made_up: Option<MadeUpTimestamps>,
319 grid: Option<(u32, alpha2_time::AudioFrameGrid)>,
323 prefetched: Prefetched,
327 frames_placed: u64,
330 seek_preroll: i64,
333}
334
335struct MadeUpTimestamps {
337 rate: u32,
339 drift: i64,
342 rate_changed: bool,
345}
346
347const AUDIO_SEEK_PREROLL_TICKS: i64 = TICKS_PER_SECOND / 10;
354
355const OPUS_SEEK_PREROLL_TICKS: i64 = TICKS_PER_SECOND * 2 / 5;
362
363impl NativeAudioDecoder {
364 pub fn set_decode_policy(&mut self, policy: DecodePolicy) {
366 self.recovery.policy = policy;
367 }
368
369 pub fn set_decode_reporter(
371 &mut self,
372 reporter: impl Fn(crate::DecodeDiagnostic) + Send + Sync + 'static,
373 ) {
374 self.recovery.reporter = Some(Box::new(reporter));
375 }
376
377 #[must_use]
379 pub fn decode_diagnostics(&self) -> &DecodeDiagnostics {
380 &self.recovery.diagnostics
381 }
382
383 pub fn open(path: impl AsRef<Path>, project_sample_rate: u32) -> Result<Self, DecodeError> {
393 if project_sample_rate == 0 {
394 return Err(DecodeError::OutOfRange);
395 }
396 let path = path.as_ref().to_owned();
397 let mut input = open_input(&path)?;
398 let probe = probe_context(&input, &path)?;
399 let selected = probe.selected_audio.ok_or_else(|| {
400 DecodeError::Unsupported("source has no audio stream this runtime decodes".into())
401 })?;
402 let stream = probe
403 .stream(selected)
404 .ok_or_else(|| DecodeError::Adapter("selected audio stream disappeared".into()))?;
405 check_source_sample_rate(i64::from(stream.sample_rate.unwrap_or(0)))?;
406 let source_channels =
407 usize::try_from(stream.channels.unwrap_or(0)).map_err(|_| DecodeError::OutOfRange)?;
408 if source_channels == 0 {
409 return Err(DecodeError::Unsupported(
410 "audio stream declares no channels".into(),
411 ));
412 }
413 check_audio_channels(source_channels)?;
414 let decoder = open_audio_decoder(&mut input, selected)?;
415 let (prefetched, timestamps) = read_first_packets(&mut input, selected);
416 let stream = input
417 .streams()
418 .get(selected)
419 .ok_or(DecodeError::OutOfRange)?;
420 let time_base = stream.time_base;
421 let seek_preroll = if stream.codecpar().codec_id == ffi::AV_CODEC_ID_OPUS {
422 OPUS_SEEK_PREROLL_TICKS
423 } else {
424 AUDIO_SEEK_PREROLL_TICKS
425 };
426 let made_up = (input.iformat().name() == c"aac")
429 .then(|| u32::try_from(stream.codecpar().sample_rate).ok())
430 .flatten()
431 .filter(|rate| *rate > 0)
432 .map(|rate| MadeUpTimestamps {
433 rate,
434 drift: 0,
435 rate_changed: false,
436 });
437 let grid = u32::try_from(stream.codecpar().sample_rate)
439 .ok()
440 .filter(|_| made_up.is_none())
441 .and_then(|rate| {
442 alpha2_time::audio_frame_grid(
443 ×tamps,
444 i64::from(time_base.num),
445 i64::from(time_base.den),
446 rate,
447 )
448 .map(|grid| (rate, grid))
449 });
450 let declared = [decoder.ch_layout(), stream.codecpar().ch_layout()]
453 .into_iter()
454 .find(|layout| {
455 layout.order != ffi::AV_CHANNEL_ORDER_UNSPEC
456 && usize::try_from(layout.nb_channels).ok() == Some(source_channels)
457 })
458 .map_or_else(
459 || vec![Speaker::Unnamed; source_channels],
460 |layout| layout_speakers(&layout),
461 );
462 let (speakers, speakers_assumed) = SpeakerLayout::declared_or_default(&declared)
463 .ok_or_else(|| unsupported_channels(source_channels))?;
464 let channel_count = speakers.channels();
465 let output_layout = ffmpeg_layout(&speakers)?;
466 Ok(Self {
467 path,
468 input,
469 input_position: InputPosition::Opened,
470 container_duration: probe.duration,
471 streams: probe.streams,
472 audio_index: selected,
473 time_base,
474 decoder,
475 converter: None,
476 sample_rate: project_sample_rate,
477 channel_count,
478 speakers,
479 speakers_assumed,
480 output_layout,
481 timeline: SourceTimeline::new(project_sample_rate, channel_count, None),
482 pending: (0..channel_count).map(|_| VecDeque::new()).collect(),
483 next_sample: None,
484 block_frames: 1_024,
485 demux_eof: false,
486 decoder_flushed: false,
487 recovery: Recovery::new(time_base),
488 repair_audio: AudioContinuity::Continuous,
489 made_up,
490 grid,
491 prefetched,
492 frames_placed: 0,
493 seek_preroll,
494 })
495 }
496
497 pub fn set_block_frames(&mut self, frames: usize) -> Result<(), DecodeError> {
502 if frames == 0 {
503 return Err(DecodeError::OutOfRange);
504 }
505 self.block_frames = frames;
506 Ok(())
507 }
508
509 #[must_use]
512 pub const fn channels(&self) -> usize {
513 self.channel_count
514 }
515
516 #[must_use]
519 pub const fn speakers(&self) -> SpeakerLayout {
520 self.speakers
521 }
522
523 #[must_use]
526 pub const fn speakers_assumed(&self) -> bool {
527 self.speakers_assumed
528 }
529
530 #[must_use]
538 pub fn audio_duration(&self) -> Option<MediaTime> {
539 effective_media_duration(
540 self.streams
541 .iter()
542 .find(|stream| stream.index == self.audio_index)
543 .and_then(|stream| stream.duration),
544 self.container_duration,
545 )
546 }
547
548 fn stream_start(&self) -> i64 {
551 self.input
552 .streams()
553 .get(self.audio_index)
554 .map(|stream| stream.start_time)
555 .filter(|start| *start != ffi::AV_NOPTS_VALUE)
556 .unwrap_or(0)
557 }
558
559 #[cfg(test)]
562 pub(crate) const fn peak_held_samples(&self) -> usize {
563 self.timeline.peak_held()
564 }
565
566 pub(crate) fn reset_to_start(&mut self) -> Result<(), DecodeError> {
572 if self.input_position == InputPosition::Opened {
573 self.decoder.flush_buffers();
574 } else {
575 let mut input = open_input(&self.path)?;
576 self.decoder = open_audio_decoder(&mut input, self.audio_index)?;
577 self.input = input;
578 self.input_position = InputPosition::Opened;
579 self.prefetched.clear();
580 }
581 self.restart(None);
582 Ok(())
583 }
584
585 fn restart(&mut self, first: Option<i64>) {
587 self.converter = None;
588 self.timeline = SourceTimeline::new(self.sample_rate, self.channel_count, first);
589 self.pending.iter_mut().for_each(VecDeque::clear);
590 self.next_sample = None;
591 self.demux_eof = false;
592 self.decoder_flushed = false;
593 self.repair_audio = AudioContinuity::Continuous;
594 self.recovery.reset_progress();
595 if let Some(made_up) = &mut self.made_up {
596 made_up.drift = 0;
597 }
598 }
599
600 fn made_up_rate_changed(&self) -> bool {
603 self.made_up
604 .as_ref()
605 .is_some_and(|made_up| made_up.rate_changed)
606 }
607
608 fn true_timestamp(&mut self, pts: Option<i64>, rate: u32, samples: i32) -> Option<i64> {
613 let time_base = self.time_base;
614 let Some(made_up) = &mut self.made_up else {
615 return pts;
616 };
617 let declared = made_up.rate;
618 made_up.rate_changed |= rate != declared;
619 let corrected = pts.map(|pts| pts.saturating_sub(made_up.drift));
620 let (num, den) = (
621 i128::from(time_base.num.max(1)),
622 i128::from(time_base.den.max(1)),
623 );
624 let samples = i128::from(samples.max(0));
625 let counted = samples * den / (i128::from(declared) * num);
628 let lasted = (2 * samples * den + i128::from(rate) * num) / (2 * i128::from(rate) * num);
629 made_up.drift = made_up
630 .drift
631 .saturating_add(i64::try_from(counted - lasted).unwrap_or(0));
632 corrected
633 }
634
635 pub(crate) fn receive_audio_frame(&mut self) -> Result<bool, DecodeError> {
636 let mut frame = match self.decoder.receive_frame() {
637 Ok(frame) => frame,
638 Err(RsmpegError::DecoderDrainError | RsmpegError::DecoderFlushedError) => {
639 return Ok(false);
640 }
641 Err(error) => {
642 self.recovery.error(error, "audio receive", None)?;
643 self.repair_audio = AudioContinuity::Damaged;
644 return Ok(true);
645 }
646 };
647 if frame.flags & i32::try_from(ffi::AV_FRAME_FLAG_CORRUPT).expect("flag fits i32") != 0
648 || frame.decode_error_flags != 0
649 {
650 self.recovery.corrupt("audio frame", Some(frame.pts))?;
651 self.repair_audio = AudioContinuity::Damaged;
652 return Ok(true);
653 }
654 let pts = Some(if frame.best_effort_timestamp == ffi::AV_NOPTS_VALUE {
655 frame.pts
656 } else {
657 frame.best_effort_timestamp
658 })
659 .filter(|pts| *pts != ffi::AV_NOPTS_VALUE);
660 if pts.is_some_and(|pts| !audio_timestamp_in_range(pts, self.time_base)) {
661 self.recovery.corrupt("audio timestamp", pts)?;
662 self.repair_audio = AudioContinuity::Damaged;
663 return Ok(true);
664 }
665 self.recovery.progress(pts.unwrap_or(ffi::AV_NOPTS_VALUE))?;
666 normalize_audio_frame_layout(&mut frame, &self.output_layout)?;
667 check_source_sample_rate(i64::from(frame.sample_rate))?;
670 let rate = u32::try_from(frame.sample_rate).map_err(|_| DecodeError::OutOfRange)?;
671 let pts = self.true_timestamp(pts, rate, frame.nb_samples);
672 let planes = self.convert_audio_frame(&frame)?;
673 self.place_audio(rate, pts, planes)?;
674 Ok(true)
675 }
676
677 fn convert_audio_frame(&mut self, frame: &AVFrame) -> Result<Vec<Vec<f32>>, DecodeError> {
679 let mut packed = audio_output_frame(frame.sample_rate, &self.output_layout);
680 let mut converter = match self.converter.take() {
681 Some(converter) => converter,
682 None => audio_converter(frame, &self.output_layout)?,
683 };
684 if let Err(error) = converter.convert_frame(Some(frame), &mut packed) {
685 if error.raw_error() != Some(ffi::AVERROR_INPUT_CHANGED) {
686 return Err(adapter_error(error));
687 }
688 converter = audio_converter(frame, &self.output_layout)?;
689 packed = audio_output_frame(frame.sample_rate, &self.output_layout);
690 converter
691 .convert_frame(Some(frame), &mut packed)
692 .map_err(adapter_error)?;
693 }
694 self.converter = Some(converter);
695 if packed.nb_samples != frame.nb_samples {
697 return Err(DecodeError::Adapter(format!(
698 "converting {} decoded audio samples gave {}",
699 frame.nb_samples, packed.nb_samples
700 )));
701 }
702 let mut planes = (0..self.channel_count)
703 .map(|_| VecDeque::new())
704 .collect::<Vec<_>>();
705 append_interleaved_audio(&packed, &mut planes)?;
706 Ok(planes.into_iter().map(Vec::from).collect())
707 }
708
709 fn place_audio(
718 &mut self,
719 rate: u32,
720 pts: Option<i64>,
721 mut planes: Vec<Vec<f32>>,
722 ) -> Result<(), DecodeError> {
723 let rate_i32 = i32::try_from(rate).map_err(|_| DecodeError::OutOfRange)?;
724 let at = pts.map(|pts| {
725 av_rescale_q_rnd(
726 pts,
727 self.time_base,
728 ra(1, rate_i32),
729 ffi::AV_ROUND_NEAR_INF | ffi::AV_ROUND_PASS_MINMAX,
730 )
731 });
732 let damaged = self.repair_audio == AudioContinuity::Damaged;
733 self.repair_audio = AudioContinuity::Continuous;
734 self.frames_placed = self.frames_placed.wrapping_add(1);
735 let frames = i64::try_from(planes.first().map_or(0, Vec::len)).unwrap_or(i64::MAX);
736 let Some(expected) = self.timeline.expected(rate) else {
737 let at = match (at, self.grid) {
740 (Some(at), Some((grid_rate, grid))) if !damaged && grid_rate == rate => {
741 grid.place(at, frames)
742 }
743 (at, _) => at.unwrap_or(0),
744 };
745 return self.timeline.place(rate, at, planes);
746 };
747 let Some(at) = at else {
748 if damaged {
749 return Err(DecodeError::Adapter(
750 "cannot locate damaged audio gap without PTS".into(),
751 ));
752 }
753 return self.timeline.place(rate, expected, planes);
754 };
755 let ahead = at
756 .checked_sub(expected)
757 .ok_or_else(|| DecodeError::Adapter("audio timestamps are out of range".into()))?;
758 if damaged {
759 if ahead < 0 || ahead > i64::from(rate) * 10 {
760 return Err(DecodeError::Adapter(
761 "damaged audio gap is ambiguous or exceeds 10 seconds".into(),
762 ));
763 }
764 if ahead > 0 {
765 self.recovery.audio_gap(
766 self.timeline.output_at(rate, expected),
767 self.timeline.output_at(rate, at),
768 self.sample_rate,
769 "corrupt input",
770 )?;
771 }
772 return self.timeline.place(rate, at, planes);
773 }
774 let den = i64::from(self.time_base.den.max(1));
776 let unit = (i64::from(rate) * i64::from(self.time_base.num) + den - 1) / den;
777 let tolerance = frames
778 .max((i64::from(rate) + 499) / 500)
779 .max(unit.saturating_mul(2));
780 if ahead.abs() < tolerance {
781 return self.timeline.place(rate, expected, planes);
782 }
783 if ahead > 0 {
784 self.recovery.timestamp_gap(
785 self.timeline.output_at(rate, expected),
786 self.timeline.output_at(rate, at),
787 self.sample_rate,
788 );
789 return self.timeline.place(rate, at, planes);
790 }
791 let repeated = usize::try_from(-ahead).unwrap_or(usize::MAX);
792 self.recovery.timestamp_overlap(
793 self.timeline.output_at(rate, at),
794 self.timeline
795 .output_at(rate, at.saturating_add(frames.min(-ahead))),
796 self.sample_rate,
797 );
798 for plane in &mut planes {
799 plane.drain(..repeated.min(plane.len()));
800 }
801 self.timeline.place(rate, expected, planes)
802 }
803
804 pub(crate) fn fill_pending(
805 &mut self,
806 frames: usize,
807 cancel: &CancelToken,
808 ) -> Result<(), DecodeError> {
809 interrupt_on_cancel(&self.input, cancel);
810 let mut budget = WorkBudget::new();
811 let mut foreign = false;
812 let mut placed = self.frames_placed;
813 loop {
814 let held = self.pending.first().map_or(0, VecDeque::len);
815 if held >= frames {
816 return Ok(());
817 }
818 if self.frames_placed != placed {
821 placed = self.frames_placed;
822 budget.progressed();
823 }
824 if std::mem::take(&mut foreign) {
827 if cancel.is_cancelled() {
828 return Err(DecodeError::Cancelled);
829 }
830 } else {
831 budget.step(cancel)?;
832 }
833 if let Some((start, _)) = self.timeline.produce(&mut self.pending, frames - held) {
834 if held == 0 {
835 self.next_sample = Some(start);
836 }
837 continue;
838 }
839 if self.receive_audio_frame()? {
840 continue;
841 }
842 if self.demux_eof {
843 if !self.decoder_flushed {
844 match self.decoder.send_packet(None) {
845 Ok(()) | Err(RsmpegError::DecoderFlushedError) => {}
846 Err(RsmpegError::DecoderFullError) => continue,
847 Err(error) => {
848 self.recovery.error(error, "flush", None)?;
849 continue;
850 }
851 }
852 self.decoder_flushed = true;
853 continue;
854 }
855 if self.repair_audio == AudioContinuity::Damaged {
856 self.recovery.damaged_tail("audio")?;
859 self.repair_audio = AudioContinuity::Continuous;
860 }
861 if !self.timeline.finished() {
862 self.timeline.finish();
863 continue;
864 }
865 return Ok(());
866 }
867 self.input_position = InputPosition::Moved;
868 let packet = match self
869 .prefetched
870 .pop_front()
871 .unwrap_or_else(|| self.input.read_packet())
872 {
873 Ok(Some(packet)) => packet,
874 Ok(None) => {
875 self.demux_eof = true;
876 continue;
877 }
878 Err(error) => {
879 let error = demux_failure(error, cancel)?;
880 self.recovery.error(error, "audio demux", None)?;
881 self.repair_audio = AudioContinuity::Damaged;
882 continue;
883 }
884 };
885 if usize::try_from(packet.stream_index).ok() != Some(self.audio_index) {
886 foreign = true;
887 continue;
888 }
889 if packet.flags & i32::try_from(ffi::AV_PKT_FLAG_CORRUPT).expect("flag fits i32") != 0 {
890 self.recovery.corrupt("audio packet", Some(packet.pts))?;
891 self.repair_audio = AudioContinuity::Damaged;
892 continue;
893 }
894 submit_packet(
895 |drain| {
896 if drain {
897 return self.receive_audio_frame();
898 }
899 match self.decoder.send_packet(Some(&packet)) {
900 Ok(()) => Ok(true),
901 Err(RsmpegError::DecoderFullError) => Ok(false),
902 Err(error) => {
903 self.recovery.error(error, "audio send", Some(packet.pts))?;
904 self.repair_audio = AudioContinuity::Damaged;
905 Ok(true)
906 }
907 }
908 },
909 &mut budget,
910 cancel,
911 )?;
912 }
913 }
914}
915
916impl Decoder for NativeAudioDecoder {
917 fn streams(&self) -> &[StreamInfo] {
918 &self.streams
919 }
920 fn flush(&mut self) -> Result<(), DecodeError> {
921 self.reset_to_start()
922 }
923 fn seek(
924 &mut self,
925 request: DecodeRequest,
926 cancel: &CancelToken,
927 ) -> Result<DecodeResult, DecodeError> {
928 if cancel.is_cancelled() {
929 return Err(DecodeError::Cancelled);
930 }
931 let target = request
932 .time
933 .ticks()
934 .saturating_mul(i64::from(self.sample_rate))
935 .div_euclid(TICKS_PER_SECOND);
936 let timestamp = av_rescale_q(
937 request.time.ticks().saturating_sub(self.seek_preroll),
938 tick_base(),
939 self.time_base,
940 );
941 let rate_changed = self.made_up_rate_changed();
945 let from_start = timestamp <= self.stream_start() || rate_changed;
946 if from_start {
947 self.reset_to_start()?;
948 } else {
949 interrupt_on_cancel(&self.input, cancel);
950 self.input_position = InputPosition::Moved;
951 self.prefetched.clear();
952 self.input
953 .seek(
954 i32::try_from(self.audio_index).map_err(|_| DecodeError::OutOfRange)?,
955 timestamp,
956 i32::try_from(ffi::AVSEEK_FLAG_BACKWARD).expect("seek flag fits i32"),
957 )
958 .map_err(|error| seek_failure(error, cancel))?;
959 }
960 self.decoder.flush_buffers();
961 self.restart(Some(target));
962 let mut event = self.decode_next(cancel)?;
963 if !from_start && !rate_changed && self.made_up_rate_changed() {
964 self.reset_to_start()?;
966 self.decoder.flush_buffers();
967 self.restart(Some(target));
968 event = self.decode_next(cancel)?;
969 }
970 if matches!(event, DecodeEvent::EndOfStream) {
971 return Err(DecodeError::OutOfRange);
972 }
973 Ok(DecodeResult { request, event })
974 }
975 fn decode_next(&mut self, cancel: &CancelToken) -> Result<DecodeEvent, DecodeError> {
976 if cancel.is_cancelled() {
977 return Err(DecodeError::Cancelled);
978 }
979 self.fill_pending(self.block_frames, cancel)?;
980 let frames = self
981 .pending
982 .first()
983 .map_or(0, VecDeque::len)
984 .min(self.block_frames);
985 if frames == 0 {
986 return Ok(DecodeEvent::EndOfStream);
987 }
988 let start_sample = self.next_sample.unwrap_or(0);
989 let block = AudioBlock {
990 start_sample,
991 sample_rate: self.sample_rate,
992 speakers: self.speakers,
993 channels: self
994 .pending
995 .iter_mut()
996 .map(|channel| channel.drain(..frames).collect())
997 .collect(),
998 };
999 self.next_sample =
1000 Some(start_sample.saturating_add(i64::try_from(frames).unwrap_or(i64::MAX)));
1001 Ok(DecodeEvent::Audio(block))
1002 }
1003}
1004
1005fn open_audio_decoder(
1008 input: &mut DemuxInput,
1009 stream_index: usize,
1010) -> Result<AVCodecContext, DecodeError> {
1011 input.discard_all_but(stream_index);
1012 let stream = input
1013 .streams()
1014 .get(stream_index)
1015 .ok_or(DecodeError::OutOfRange)?;
1016 let parameters = stream.codecpar();
1017 let codec = AVCodec::find_decoder(parameters.codec_id)
1018 .ok_or_else(|| DecodeError::Unsupported("audio decoder is unavailable".into()))?;
1019 let mut decoder = AVCodecContext::new(&codec);
1020 decoder.apply_codecpar(¶meters).map_err(adapter_error)?;
1021 decoder.set_pkt_timebase(stream.time_base);
1022 decoder.open(None).map_err(adapter_error)?;
1023 Ok(decoder)
1024}
1025
1026const AUDIO_TIMESTAMP_LIMIT_SECONDS: i128 = 1 << 31;
1031
1032fn audio_timestamp_in_range(pts: i64, time_base: AVRational) -> bool {
1035 let (num, den) = (i128::from(time_base.num), i128::from(time_base.den));
1036 num > 0 && den > 0 && (i128::from(pts) * num).abs() <= AUDIO_TIMESTAMP_LIMIT_SECONDS * den
1037}
1038
1039pub const SOURCE_SAMPLE_RATES: std::ops::RangeInclusive<u32> = 1_000..=768_000;
1044
1045fn check_source_sample_rate(rate: i64) -> Result<(), DecodeError> {
1046 if u32::try_from(rate).is_ok_and(|rate| SOURCE_SAMPLE_RATES.contains(&rate)) {
1047 return Ok(());
1048 }
1049 Err(DecodeError::Unsupported(format!(
1050 "the source's audio is {rate} Hz, and alpha2 reads audio from {} Hz to {} Hz",
1051 SOURCE_SAMPLE_RATES.start(),
1052 SOURCE_SAMPLE_RATES.end()
1053 )))
1054}
1055
1056pub(crate) fn audio_converter(
1061 frame: &AVFrame,
1062 output: &AVChannelLayout,
1063) -> Result<SwrContext, DecodeError> {
1064 if frame.sample_rate <= 0 || frame.format == ffi::AV_SAMPLE_FMT_NONE {
1065 return Err(DecodeError::Adapter(
1066 "decoded audio frame has an incomplete format".into(),
1067 ));
1068 }
1069 let input_layout = frame.ch_layout();
1072 let mut converter = SwrContext::new(
1073 output,
1074 ffi::AV_SAMPLE_FMT_FLT,
1075 frame.sample_rate,
1076 &input_layout,
1077 frame.format,
1078 frame.sample_rate,
1079 )
1080 .map_err(adapter_error)?;
1081 converter.init().map_err(adapter_error)?;
1082 Ok(converter)
1083}
1084
1085pub(crate) fn layout_speakers(layout: &AVChannelLayout) -> Vec<Speaker> {
1088 let channels = alpha2_ffmpeg_bridge::channel_layout_channels(layout);
1089 if channels.is_empty() {
1090 return vec![Speaker::Unnamed; usize::try_from(layout.nb_channels).unwrap_or(0)];
1091 }
1092 channels
1093 .into_iter()
1094 .map(|channel| Speaker::from_ffmpeg_channel(i64::from(channel)))
1095 .collect()
1096}
1097
1098pub(crate) fn ffmpeg_layout(speakers: &SpeakerLayout) -> Result<AVChannelLayout, DecodeError> {
1101 let channels = speakers
1102 .speakers()
1103 .iter()
1104 .map(|speaker| {
1105 ffi::AVChannel::try_from(speaker.ffmpeg_channel()).unwrap_or(ffi::AV_CHAN_UNKNOWN)
1106 })
1107 .collect::<Vec<_>>();
1108 alpha2_ffmpeg_bridge::channel_layout_of(&channels).map_err(|error| {
1109 DecodeError::Adapter(format!(
1110 "could not lay out audio channels {}: {error:?}",
1111 speakers.describe()
1112 ))
1113 })
1114}
1115
1116fn unsupported_channels(channels: usize) -> DecodeError {
1117 DecodeError::Unsupported(format!(
1118 "audio with {channels} channels is unsupported: the engine takes 1 to {MAX_AUDIO_CHANNELS}"
1119 ))
1120}
1121
1122fn check_audio_channels(channels: usize) -> Result<(), DecodeError> {
1123 if (1..=MAX_AUDIO_CHANNELS).contains(&channels) {
1124 Ok(())
1125 } else {
1126 Err(unsupported_channels(channels))
1127 }
1128}
1129
1130pub(crate) fn normalize_audio_frame_layout(
1148 frame: &mut AVFrame,
1149 stream: &AVChannelLayout,
1150) -> Result<(), DecodeError> {
1151 let (channels, named) = {
1152 let layout = frame.ch_layout();
1153 let named = matches!(
1154 layout.order,
1155 ffi::AV_CHANNEL_ORDER_NATIVE | ffi::AV_CHANNEL_ORDER_CUSTOM
1156 ) && !layout_speakers(&layout).contains(&Speaker::Unnamed);
1157 (layout.nb_channels, named)
1158 };
1159 if channels <= 0 {
1160 return Err(DecodeError::Adapter(
1161 "decoded audio frame has no channels".into(),
1162 ));
1163 }
1164 if named {
1165 return Ok(());
1166 }
1167 if channels == stream.nb_channels {
1168 let mut owned = AVChannelLayout::from_nb_channels(1);
1169 owned.copy(stream);
1170 frame.set_ch_layout(owned.into_inner());
1171 return Ok(());
1172 }
1173 let count = usize::try_from(channels).map_err(|_| DecodeError::OutOfRange)?;
1174 let speakers = SpeakerLayout::default_for(count).ok_or_else(|| unsupported_channels(count))?;
1175 frame.set_ch_layout(ffmpeg_layout(&speakers)?.into_inner());
1176 Ok(())
1177}
1178
1179pub(crate) fn audio_output_frame(sample_rate: i32, layout: &AVChannelLayout) -> AVFrame {
1180 let mut frame = AVFrame::new();
1181 frame.set_format(ffi::AV_SAMPLE_FMT_FLT);
1182 frame.set_sample_rate(sample_rate);
1183 let mut owned = AVChannelLayout::from_nb_channels(1);
1184 owned.copy(layout);
1185 frame.set_ch_layout(owned.into_inner());
1186 frame
1187}
1188
1189pub(crate) fn append_interleaved_audio(
1190 frame: &AVFrame,
1191 output: &mut [VecDeque<f32>],
1192) -> Result<(), DecodeError> {
1193 let packed = alpha2_ffmpeg_bridge::audio_f32_interleaved(frame).map_err(adapter_error)?;
1194 if output.is_empty() || packed.len() % output.len() != 0 {
1195 return Err(DecodeError::Adapter(
1196 "FFmpeg returned a partial audio frame".into(),
1197 ));
1198 }
1199 for frame in packed.chunks_exact(output.len()) {
1200 for (channel, sample) in output.iter_mut().zip(frame) {
1201 channel.push_back(*sample);
1202 }
1203 }
1204 Ok(())
1205}
1206
1207pub(crate) fn has_hw_config_method(methods: i32, method: impl Into<i64>) -> bool {
1213 i64::from(methods) & method.into() != 0
1214}
1215
1216const STREAM_START_SEEK: i64 = i64::MIN / 2;
1224
1225enum Landing {
1227 Presented(DecodedFrame),
1229 Late(DecodedFrame),
1231 Empty,
1233}
1234
1235impl NativeDecoder {
1236 pub fn set_decode_policy(&mut self, policy: DecodePolicy) {
1238 self.recovery.policy = policy;
1239 }
1240
1241 pub fn declare_color(&mut self, color: Option<ColorMetadata>) {
1246 self.declared_color = color;
1247 }
1248
1249 #[cfg(all(
1252 any(target_os = "linux", target_os = "macos", target_os = "windows"),
1253 feature = "hwdecode"
1254 ))]
1255 pub(crate) fn picture_chroma_location(&self, frame: &AVFrame) -> alpha2_media::ChromaLocation {
1256 crate::convert::decoded_chroma_location(self.decoder.codec_id, frame.chroma_location)
1257 }
1258
1259 pub(crate) fn picture_color(&self, frame: &AVFrame) -> Option<ColorMetadata> {
1262 self.declared_color
1263 .or_else(|| frame_color_metadata(frame))
1264 .or(self.color)
1265 }
1266
1267 pub fn set_decode_reporter(
1269 &mut self,
1270 reporter: impl Fn(crate::DecodeDiagnostic) + Send + Sync + 'static,
1271 ) {
1272 self.recovery.reporter = Some(Box::new(reporter));
1273 }
1274
1275 #[must_use]
1277 pub fn decode_diagnostics(&self) -> &DecodeDiagnostics {
1278 &self.recovery.diagnostics
1279 }
1280
1281 pub fn open(path: impl AsRef<Path>) -> Result<Self, DecodeError> {
1286 Self::open_with_threads(path, codec_thread_budget())
1287 }
1288
1289 pub fn open_with_threads(path: impl AsRef<Path>, threads: i64) -> Result<Self, DecodeError> {
1294 if !(1..=64).contains(&threads) {
1295 return Err(DecodeError::OutOfRange);
1296 }
1297 let mut input = open_input(path.as_ref())?;
1298 let probed = probe_context(&input, path.as_ref())?;
1299 let video_index = probed.selected_video.ok_or_else(|| {
1300 DecodeError::Unsupported("resource has no decodable video stream".into())
1301 })?;
1302 input.discard_all_but(video_index);
1303 let selected = probed
1304 .streams
1305 .iter()
1306 .find(|stream| stream.index == video_index)
1307 .ok_or(DecodeError::OutOfRange)?;
1308 let orientation = selected.orientation;
1309 let sample_aspect_ratio = selected.sample_aspect_ratio;
1310 let color = selected.color;
1312 let (time_base, decoder) = {
1313 let stream = &input.streams()[video_index];
1314 let parameters = stream.codecpar();
1315 let codec = software_video_decoder(parameters.codec_id)?;
1316 let mut decoder = AVCodecContext::new(&codec);
1317 decoder.apply_codecpar(¶meters).map_err(adapter_error)?;
1318 decoder.set_pkt_timebase(stream.time_base);
1319 let unused = alpha2_ffmpeg_bridge::open_codec_context(
1320 &mut decoder,
1321 video_decoder_options(threads),
1322 )
1323 .map_err(adapter_error)?;
1324 reject_unused_decode_options(unused)?;
1325 (stream.time_base, decoder)
1326 };
1327 let (prefetched, slack) = read_presentation_slack(&mut input, video_index, time_base);
1328 Ok(Self {
1329 time_base,
1330 input,
1331 container_duration: probed.duration,
1332 streams: probed.streams,
1333 video_index,
1334 decoder,
1335 orientation,
1336 sample_aspect_ratio,
1337 color,
1338 declared_color: None,
1339 video_scaler: crate::convert::FrameScaler::default(),
1340 pending: VecDeque::new(),
1341 demux_eof: false,
1342 decoder_flushed: false,
1343 stages: DecodeStageTimings::default(),
1344 first_video_pts: None,
1345 recovery: Recovery::new(time_base),
1346 slack,
1347 prefetched,
1348 })
1349 }
1350
1351 #[cfg(all(
1358 any(target_os = "linux", target_os = "macos", target_os = "windows"),
1359 feature = "hwdecode"
1360 ))]
1361 #[allow(clippy::too_many_lines)]
1362 pub(crate) fn open_hardware(
1363 path: impl AsRef<Path>,
1364 threads: i64,
1365 device: AVHWDeviceContext,
1366 spec: &HardwareDecoderSpec,
1367 ) -> Result<Self, HardwareOpenError> {
1368 use crate::video_frame::HardwareDecodeFallback::{
1369 DecoderOpenFailed, NoHardwareConfig, SourceUnavailable, UnsupportedColor,
1370 };
1371
1372 let unavailable = |error| HardwareOpenError::new(SourceUnavailable, error);
1373 let mut input = open_input(path.as_ref()).map_err(unavailable)?;
1374 let probed = probe_context(&input, path.as_ref()).map_err(unavailable)?;
1375 let video_index = probed.selected_video.ok_or_else(|| {
1376 HardwareOpenError::new(
1377 SourceUnavailable,
1378 DecodeError::Unsupported("resource has no decodable video stream".into()),
1379 )
1380 })?;
1381 input.discard_all_but(video_index);
1382 let selected = probed
1383 .streams
1384 .iter()
1385 .find(|stream| stream.index == video_index)
1386 .ok_or_else(|| HardwareOpenError::new(SourceUnavailable, DecodeError::OutOfRange))?;
1387 let orientation = selected.orientation;
1388 let sample_aspect_ratio = selected.sample_aspect_ratio;
1389 let color = selected.color;
1390 if selected.color.is_some_and(ColorMetadata::is_hdr) {
1391 return Err(HardwareOpenError::new(
1392 UnsupportedColor,
1393 DecodeError::Unsupported("hardware export currently supports SDR only".into()),
1394 ));
1395 }
1396 let (time_base, decoder) = {
1397 let stream = &input.streams()[video_index];
1398 let parameters = stream.codecpar();
1399 let preferred = AVCodec::find_decoder(parameters.codec_id).ok_or_else(|| {
1400 HardwareOpenError::new(
1401 NoHardwareConfig,
1402 DecodeError::Unsupported("selected video codec has no decoder".into()),
1403 )
1404 })?;
1405 let supported = |codec: &AVCodecRef<'_>| {
1406 (0..)
1407 .map_while(|index| codec.hw_config(index))
1408 .any(|config| {
1409 config.device_type == spec.device_type
1410 && config.pix_fmt == spec.pixel_format
1411 && (!spec.require_device_context
1412 || has_hw_config_method(
1413 config.methods,
1414 ffi::AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX,
1415 ))
1416 })
1417 };
1418 let Some(codec) =
1419 registered_video_decoders(parameters.codec_id).find(|codec| supported(codec))
1420 else {
1421 return Err(HardwareOpenError::new(
1422 NoHardwareConfig,
1423 DecodeError::Unsupported(format!(
1424 "decoder {} has no {} hardware configuration",
1425 preferred.name().to_string_lossy(),
1426 spec.backend
1427 )),
1428 ));
1429 };
1430 check_hardware_sample_profile(parameters.format, spec)?;
1431 check_hardware_crop(¶meters, spec)?;
1432 let open_failed = |error| HardwareOpenError::new(DecoderOpenFailed, error);
1433 let mut decoder = AVCodecContext::new(&codec);
1434 decoder
1435 .apply_codecpar(¶meters)
1436 .map_err(adapter_error)
1437 .map_err(open_failed)?;
1438 decoder.set_pkt_timebase(stream.time_base);
1439 decoder.set_get_format(Some(spec.get_format));
1440 decoder.set_hw_device_ctx(device);
1441 let unused = alpha2_ffmpeg_bridge::open_codec_context(
1444 &mut decoder,
1445 video_decoder_options(threads),
1446 )
1447 .map_err(adapter_error)
1448 .map_err(open_failed)?;
1449 reject_unused_decode_options(unused).map_err(open_failed)?;
1450 (stream.time_base, decoder)
1451 };
1452 let (prefetched, slack) = read_presentation_slack(&mut input, video_index, time_base);
1453 Ok(Self {
1454 time_base,
1455 input,
1456 container_duration: probed.duration,
1457 streams: probed.streams,
1458 video_index,
1459 decoder,
1460 orientation,
1461 sample_aspect_ratio,
1462 color,
1463 declared_color: None,
1464 video_scaler: crate::convert::FrameScaler::default(),
1465 pending: VecDeque::new(),
1466 demux_eof: false,
1467 decoder_flushed: false,
1468 stages: DecodeStageTimings::default(),
1469 first_video_pts: None,
1470 recovery: Recovery::hardware(time_base),
1471 slack,
1472 prefetched,
1473 })
1474 }
1475
1476 #[must_use]
1482 pub fn video_duration(&self) -> Option<MediaTime> {
1483 effective_media_duration(
1484 self.streams
1485 .iter()
1486 .find(|stream| stream.index == self.video_index)
1487 .and_then(|stream| stream.duration),
1488 self.container_duration,
1489 )
1490 }
1491
1492 pub(crate) fn take_stage_timings(&mut self) -> DecodeStageTimings {
1494 std::mem::take(&mut self.stages)
1495 }
1496
1497 pub(crate) fn receive_available_raw(
1498 &mut self,
1499 cancel: &CancelToken,
1500 ) -> Result<Option<DecodedFrame>, DecodeError> {
1501 let mut budget = WorkBudget::new();
1502 loop {
1503 budget.step(cancel)?;
1504 let started = Instant::now();
1505 let received = DecodedFrame::receive(&mut self.decoder);
1506 self.stages.receive_frame = self.stages.receive_frame.saturating_add(started.elapsed());
1507 match received {
1508 Ok(frame) => {
1509 if frame.flags
1510 & i32::try_from(ffi::AV_FRAME_FLAG_CORRUPT).expect("flag fits i32")
1511 != 0
1512 || frame.decode_error_flags != 0
1513 {
1514 self.recovery.corrupt("video frame", Some(frame.pts))?;
1515 continue;
1516 }
1517 #[cfg(test)]
1518 VIDEO_FRAMES_DECODED.with(|decoded| decoded.set(decoded.get() + 1));
1519 self.recovery.progress(
1520 if frame.best_effort_timestamp == ffi::AV_NOPTS_VALUE {
1521 frame.pts
1522 } else {
1523 frame.best_effort_timestamp
1524 },
1525 )?;
1526 return Ok(Some(frame));
1527 }
1528 Err(RsmpegError::DecoderDrainError | RsmpegError::DecoderFlushedError) => {
1529 return Ok(None);
1530 }
1531 Err(error) => {
1532 self.recovery.error(error, "video receive", None)?;
1533 }
1534 }
1535 }
1536 }
1537
1538 pub(crate) fn convert_received(
1539 &mut self,
1540 frame: &DecodedFrame,
1541 ) -> Result<DecodedVideoFrame, DecodeError> {
1542 let declared = self.picture_color(frame);
1545 let started = Instant::now();
1546 let converted = convert_video_frame(
1547 frame,
1548 self.decoder.codec_id,
1549 self.time_base,
1550 declared.unwrap_or(ColorMetadata::UNDECLARED),
1551 &mut self.video_scaler,
1552 );
1553 self.stages.convert = self.stages.convert.saturating_add(started.elapsed());
1554 let mut converted = converted?;
1555 if declared.is_none() && converted.color == Some(ColorMetadata::UNDECLARED) {
1558 converted.color = None;
1559 }
1560 converted.orientation = self.orientation;
1561 converted.sample_aspect_ratio = self.picture_sample_aspect_ratio(frame);
1562 Ok(converted)
1563 }
1564
1565 pub(crate) fn picture_sample_aspect_ratio(&self, frame: &AVFrame) -> SampleAspectRatio {
1569 if declared_sar(self.input.streams()[self.video_index].sample_aspect_ratio).is_some() {
1571 return self.sample_aspect_ratio;
1572 }
1573 declared_sar(frame.sample_aspect_ratio).unwrap_or(self.sample_aspect_ratio)
1574 }
1575
1576 pub(crate) fn raw_frame_pts(&self, frame: &AVFrame) -> Result<MediaTime, DecodeError> {
1577 let pts = if frame.best_effort_timestamp == ffi::AV_NOPTS_VALUE {
1578 frame.pts
1579 } else {
1580 frame.best_effort_timestamp
1581 };
1582 timestamp(pts, self.time_base).ok_or(DecodeError::OutOfRange)
1583 }
1584
1585 pub(crate) fn flush_video(&mut self) {
1587 let started = Instant::now();
1588 self.recovery.reset_progress();
1589 self.decoder.flush_buffers();
1590 self.stages.seek = self.stages.seek.saturating_add(started.elapsed());
1591 self.pending.clear();
1592 self.prefetched.clear();
1593 self.demux_eof = false;
1594 self.decoder_flushed = false;
1595 }
1596
1597 pub(crate) fn seek_raw(
1621 &mut self,
1622 target: MediaTime,
1623 cancel: &CancelToken,
1624 ) -> Result<DecodedFrame, DecodeError> {
1625 if cancel.is_cancelled() {
1626 return Err(DecodeError::Cancelled);
1627 }
1628 let start_time = self.input.streams()[self.video_index].start_time;
1629 let stream_start = timestamp(start_time, self.time_base).unwrap_or(MediaTime::ZERO);
1630 let deadline = self.presentation_deadline(target);
1631 let mut back_off = 0_i64;
1632 loop {
1633 let from = MediaTime::from_ticks(deadline.ticks().saturating_sub(back_off));
1636 let from_start =
1637 from <= stream_start || self.first_video_pts.is_some_and(|first| deadline < first);
1638 if !from_start {
1639 let position = av_rescale_q_rnd(
1641 from.ticks(),
1642 tick_base(),
1643 self.time_base,
1644 ffi::AV_ROUND_DOWN,
1645 );
1646 self.seek_video_to(position, cancel)?;
1647 } else if let Err(error) = self.seek_video_to(STREAM_START_SEEK, cancel) {
1648 if error == DecodeError::Cancelled {
1649 return Err(error);
1650 }
1651 let start = if start_time == ffi::AV_NOPTS_VALUE {
1652 0
1653 } else {
1654 start_time
1655 };
1656 self.seek_video_to(start, cancel)?;
1657 }
1658 let mut first = None;
1659 let landing = self.present_after_seek(target, &mut first, cancel)?;
1660 if from_start {
1661 self.first_video_pts = first.or(self.first_video_pts);
1663 }
1664 match landing {
1665 Landing::Presented(frame) => return Ok(frame),
1666 Landing::Late(frame) if from_start || first == self.first_video_pts => {
1667 return Ok(frame);
1668 }
1669 Landing::Empty if from_start => return Err(DecodeError::OutOfRange),
1670 Landing::Late(_) | Landing::Empty => {}
1671 }
1672 back_off = if back_off == 0 {
1673 TICKS_PER_SECOND
1674 } else {
1675 back_off.saturating_mul(2)
1676 };
1677 }
1678 }
1679
1680 fn seek_video_to(&mut self, position: i64, cancel: &CancelToken) -> Result<(), DecodeError> {
1682 interrupt_on_cancel(&self.input, cancel);
1683 let seek_started = Instant::now();
1684 let sought = self.input.seek(
1685 i32::try_from(self.video_index).map_err(|_| DecodeError::OutOfRange)?,
1686 position,
1687 i32::try_from(ffi::AVSEEK_FLAG_BACKWARD).expect("seek flag fits i32"),
1688 );
1689 self.stages.seek = self.stages.seek.saturating_add(seek_started.elapsed());
1690 sought.map_err(|error| seek_failure(error, cancel))?;
1691 self.flush_video();
1692 Ok(())
1693 }
1694
1695 fn present_after_seek(
1700 &mut self,
1701 target: MediaTime,
1702 first: &mut Option<MediaTime>,
1703 cancel: &CancelToken,
1704 ) -> Result<Landing, DecodeError> {
1705 let deadline = self.presentation_deadline(target);
1706 let mut presented: Option<DecodedFrame> = None;
1707 let mut budget = WorkBudget::new();
1708 loop {
1709 budget.step(cancel)?;
1710 let Some(raw) = self.decode_raw_next(cancel)? else {
1711 return Ok(presented.map_or(Landing::Empty, Landing::Presented));
1712 };
1713 budget.progressed();
1715 #[cfg(test)]
1716 crate::recovery::take_frame_delay();
1717 let pts = self.raw_frame_pts(&raw)?;
1718 first.get_or_insert(pts);
1719 if pts > deadline {
1720 return Ok(match presented {
1721 Some(frame) => {
1722 self.pending.push_front(raw);
1723 Landing::Presented(frame)
1724 }
1725 None => Landing::Late(raw),
1726 });
1727 }
1728 if pts >= target {
1731 return Ok(Landing::Presented(raw));
1732 }
1733 presented = Some(raw);
1734 }
1735 }
1736
1737 #[must_use]
1743 pub fn presentation_deadline(&self, time: MediaTime) -> MediaTime {
1744 time.presentation_deadline(self.slack)
1745 }
1746
1747 pub fn peek_video_pts(
1757 &mut self,
1758 cancel: &CancelToken,
1759 ) -> Result<Option<MediaTime>, DecodeError> {
1760 self.peek_raw_pts(cancel)
1761 }
1762
1763 pub(crate) fn peek_raw_pts(
1769 &mut self,
1770 cancel: &CancelToken,
1771 ) -> Result<Option<MediaTime>, DecodeError> {
1772 let Some(raw) = self.decode_raw_next(cancel)? else {
1773 return Ok(None);
1774 };
1775 let pts = self.raw_frame_pts(&raw);
1776 self.pending.push_front(raw);
1777 pts.map(Some)
1778 }
1779
1780 pub(crate) fn decode_raw_next(
1781 &mut self,
1782 cancel: &CancelToken,
1783 ) -> Result<Option<DecodedFrame>, DecodeError> {
1784 if cancel.is_cancelled() {
1785 return Err(DecodeError::Cancelled);
1786 }
1787 if let Some(frame) = self.pending.pop_front() {
1788 return Ok(Some(frame));
1789 }
1790 interrupt_on_cancel(&self.input, cancel);
1791 let mut budget = WorkBudget::new();
1792 let mut foreign = false;
1793 loop {
1794 if std::mem::take(&mut foreign) {
1796 if cancel.is_cancelled() {
1797 return Err(DecodeError::Cancelled);
1798 }
1799 } else {
1800 budget.step(cancel)?;
1801 }
1802 if let Some(frame) = self.pending.pop_front() {
1803 return Ok(Some(frame));
1804 }
1805 if let Some(frame) = self.receive_available_raw(cancel)? {
1806 return Ok(Some(frame));
1807 }
1808 if self.demux_eof {
1809 if !self.decoder_flushed {
1810 match self.decoder.send_packet(None) {
1811 Ok(()) | Err(RsmpegError::DecoderFlushedError) => {}
1812 Err(RsmpegError::DecoderFullError) => continue,
1813 Err(error) => {
1814 self.recovery.error(error, "flush", None)?;
1815 continue;
1816 }
1817 }
1818 self.decoder_flushed = true;
1819 continue;
1820 }
1821 self.recovery.video_eof()?;
1822 return Ok(None);
1823 }
1824 let demux_started = Instant::now();
1825 let packet = self
1826 .prefetched
1827 .pop_front()
1828 .unwrap_or_else(|| self.input.read_packet());
1829 self.stages.demux = self.stages.demux.saturating_add(demux_started.elapsed());
1830 let packet = match packet {
1831 Ok(Some(packet)) => packet,
1832 Ok(None) => {
1833 self.demux_eof = true;
1834 continue;
1835 }
1836 Err(error) => {
1837 let error = demux_failure(error, cancel)?;
1838 self.recovery.error(error, "video demux", None)?;
1839 continue;
1840 }
1841 };
1842 if usize::try_from(packet.stream_index).ok() != Some(self.video_index) {
1843 foreign = true;
1844 continue;
1845 }
1846 if packet.flags & i32::try_from(ffi::AV_PKT_FLAG_CORRUPT).expect("flag fits i32") != 0 {
1847 self.recovery.corrupt("video packet", Some(packet.pts))?;
1848 continue;
1849 }
1850 submit_packet(
1851 |drain| {
1852 if drain {
1853 let corrupt_before = self.recovery.diagnostics.corrupt_packets.len();
1854 if let Some(frame) = self.receive_available_raw(cancel)? {
1855 self.pending.push_back(frame);
1856 return Ok(true);
1857 }
1858 return Ok(self.recovery.diagnostics.corrupt_packets.len() > corrupt_before);
1859 }
1860 let started = Instant::now();
1861 let sent = self.decoder.send_packet(Some(&packet));
1862 self.stages.send_packet += started.elapsed();
1863 match sent {
1864 Ok(()) => Ok(true),
1865 Err(RsmpegError::DecoderFullError) => Ok(false),
1866 Err(error) => {
1867 self.recovery.error(error, "video send", Some(packet.pts))?;
1868 Ok(true)
1869 }
1870 }
1871 },
1872 &mut budget,
1873 cancel,
1874 )?;
1875 }
1876 }
1877}
1878
1879impl Decoder for NativeDecoder {
1880 fn streams(&self) -> &[StreamInfo] {
1881 &self.streams
1882 }
1883
1884 fn flush(&mut self) -> Result<(), DecodeError> {
1885 self.flush_video();
1886 Ok(())
1887 }
1888
1889 fn seek(
1890 &mut self,
1891 request: DecodeRequest,
1892 cancel: &CancelToken,
1893 ) -> Result<DecodeResult, DecodeError> {
1894 let raw = self.seek_raw(request.time, cancel)?;
1895 let frame = self.convert_received(&raw)?;
1896 Ok(DecodeResult {
1897 request,
1898 event: DecodeEvent::Video(frame),
1899 })
1900 }
1901
1902 fn decode_next(&mut self, cancel: &CancelToken) -> Result<DecodeEvent, DecodeError> {
1903 if cancel.is_cancelled() {
1904 return Err(DecodeError::Cancelled);
1905 }
1906 match self.decode_raw_next(cancel)? {
1907 Some(frame) => Ok(DecodeEvent::Video(self.convert_received(&frame)?)),
1908 None => Ok(DecodeEvent::EndOfStream),
1909 }
1910 }
1911}
1912
1913const HWACCEL_ONLY_DECODERS: &[&str] = &["av1"];
1919
1920pub(crate) fn registered_video_decoders(
1926 codec_id: ffi::AVCodecID,
1927) -> impl Iterator<Item = AVCodecRef<'static>> {
1928 AVCodec::iterate().filter(move |codec| {
1929 codec.id == codec_id
1930 && AVCodec::find_decoder_by_name(codec.name())
1931 .is_some_and(|found| found.as_ptr() == codec.as_ptr())
1932 })
1933}
1934
1935pub(crate) fn software_video_decoder(
1938 codec_id: ffi::AVCodecID,
1939) -> Result<AVCodecRef<'static>, DecodeError> {
1940 let Some(preferred) = AVCodec::find_decoder(codec_id) else {
1941 return Err(DecodeError::Unsupported(
1942 "selected video codec has no decoder".into(),
1943 ));
1944 };
1945 let hardware_only =
1946 |codec: &AVCodecRef<'_>| HWACCEL_ONLY_DECODERS.contains(&&*codec.name().to_string_lossy());
1947 if !hardware_only(&preferred) {
1948 return Ok(preferred);
1949 }
1950 let experimental = i32::try_from(ffi::AV_CODEC_CAP_EXPERIMENTAL).unwrap_or(0);
1951 AVCodec::iterate()
1952 .find(|codec| {
1953 codec.id == codec_id
1954 && AVCodec::find_decoder_by_name(codec.name())
1955 .is_some_and(|found| found.as_ptr() == codec.as_ptr())
1956 && codec.capabilities & experimental == 0
1957 && !hardware_only(codec)
1958 })
1959 .ok_or_else(|| DecodeError::Unsupported(hardware_only_reason(&preferred)))
1960}
1961
1962fn hardware_only_reason(decoder: &AVCodecRef<'_>) -> String {
1963 let name = decoder.name().to_string_lossy();
1964 format!(
1965 "{name} needs a hardware decoder; this runtime has no software {} decoder",
1966 name.to_uppercase()
1967 )
1968}
1969
1970#[must_use]
1976pub fn software_decode_unsupported(stream: &StreamInfo) -> Option<String> {
1977 if stream.kind != StreamKind::Video {
1978 return None;
1979 }
1980 let name = CString::new(stream.codec.as_str()).ok()?;
1981 let decoder = AVCodec::find_decoder_by_name(&name)?;
1982 software_video_decoder(decoder.id)
1983 .err()
1984 .map(|error| match error {
1985 DecodeError::Unsupported(reason) => reason,
1986 error => error.to_string(),
1987 })
1988}
1989
1990pub(crate) fn effective_media_duration(
1993 stream_duration: Option<MediaTime>,
1994 container_duration: Option<MediaTime>,
1995) -> Option<MediaTime> {
1996 stream_duration.into_iter().chain(container_duration).max()
1997}
1998
1999#[cfg(all(
2003 any(target_os = "linux", target_os = "macos", target_os = "windows"),
2004 feature = "hwdecode"
2005))]
2006fn check_hardware_crop(
2007 parameters: &rsmpeg::avcodec::AVCodecParameters,
2008 spec: &HardwareDecoderSpec,
2009) -> Result<(), HardwareOpenError> {
2010 if spec.keeps_leading_crop {
2011 return Ok(());
2012 }
2013 let extradata = alpha2_ffmpeg_bridge::codec_parameters_extradata(parameters);
2014 match crate::parameter_sets::leading_crop(parameters.codec_id, &extradata) {
2015 Some((left, top)) if left > 0 || top > 0 => Err(HardwareOpenError::new(
2016 crate::video_frame::HardwareDecodeFallback::UnsupportedCrop,
2017 DecodeError::Unsupported(format!(
2018 "{} decode does not show a picture cropped {left} columns from the left and {top} rows from the top",
2019 spec.backend
2020 )),
2021 )),
2022 _ => Ok(()),
2023 }
2024}
2025
2026#[cfg(all(
2034 any(target_os = "linux", target_os = "macos", target_os = "windows"),
2035 feature = "hwdecode"
2036))]
2037fn check_hardware_sample_profile(
2038 format: ffi::AVPixelFormat,
2039 spec: &HardwareDecoderSpec,
2040) -> Result<(), HardwareOpenError> {
2041 let Some(profile) = alpha2_ffmpeg_bridge::sample_profile(format) else {
2042 return Ok(());
2043 };
2044 let maximum_depth = if spec.ten_bit { 10 } else { 8 };
2045 if profile.is_yuv_420 && !profile.has_alpha && profile.bit_depth <= maximum_depth {
2046 return Ok(());
2047 }
2048 Err(HardwareOpenError::new(
2049 crate::video_frame::HardwareDecodeFallback::UnsupportedProfile,
2050 DecodeError::Unsupported(format!(
2051 "{} decode does not present {}-bit {} input",
2052 spec.backend,
2053 profile.bit_depth,
2054 if profile.is_yuv_420 {
2055 "4:2:0"
2056 } else {
2057 "non-4:2:0"
2058 }
2059 )),
2060 ))
2061}
2062
2063pub(crate) const SOURCE_CONTAINERS: &CStr =
2070 c"mov,matroska,mpegts,avi,mp3,aac,wav,flac,ogg,png_pipe,jpeg_pipe,webp_pipe,gif";
2071
2072pub(crate) fn open_input(path: &Path) -> Result<DemuxInput, DecodeError> {
2076 #[cfg(test)]
2077 INPUTS_OPENED.with(|opened| opened.set(opened.get() + 1));
2078 let text = path
2079 .to_str()
2080 .ok_or_else(|| DecodeError::Unsupported("media path is not UTF-8".into()))?;
2081 let path = CString::new(text)
2082 .map_err(|_| DecodeError::Unsupported("media path contains a NUL byte".into()))?;
2083 alpha2_ffmpeg_bridge::open_input(&path, SOURCE_CONTAINERS).map_err(|error| match error {
2084 OpenInputError::Refused(format) => DecodeError::Unsupported(format!(
2085 "the file is {format} input, which is not a container alpha2 reads"
2086 )),
2087 OpenInputError::Ffmpeg(error) => adapter_error(error),
2088 })
2089}
2090
2091#[cfg(test)]
2092thread_local! {
2093 pub(crate) static INPUTS_OPENED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2095 pub(crate) static VIDEO_FRAMES_DECODED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2097}
2098
2099pub(crate) fn interrupt_on_cancel(input: &DemuxInput, cancel: &CancelToken) {
2102 let cancel = cancel.clone();
2103 input.interrupt_when(move || cancel.is_cancelled());
2104}
2105
2106fn demux_failure(error: RsmpegError, cancel: &CancelToken) -> Result<RsmpegError, DecodeError> {
2108 if cancel.is_cancelled() {
2109 Err(DecodeError::Cancelled)
2110 } else {
2111 Ok(error)
2112 }
2113}
2114
2115fn seek_failure(error: RsmpegError, cancel: &CancelToken) -> DecodeError {
2117 demux_failure(error, cancel).map_or_else(|cancelled| cancelled, adapter_error)
2118}
2119
2120const STILL_IMAGE_FORMATS: [&CStr; 3] = [c"jpeg_pipe", c"png_pipe", c"webp_pipe"];
2124
2125fn is_still_image_format(input: &DemuxInput) -> bool {
2126 STILL_IMAGE_FORMATS.contains(&input.iformat().name())
2127}
2128
2129fn still_image_orientation(path: &Path, index: usize) -> Option<Orientation> {
2136 let mut input = open_input(path).ok()?;
2137 let (codec, mut decoder) = {
2138 let stream = input.streams().get(index)?;
2139 let parameters = stream.codecpar();
2140 let codec = software_video_decoder(parameters.codec_id).ok()?;
2141 let mut decoder = AVCodecContext::new(&codec);
2142 decoder.apply_codecpar(¶meters).ok()?;
2143 decoder.set_pkt_timebase(stream.time_base);
2144 (codec, decoder)
2145 };
2146 let mut options = video_decoder_options(1);
2147 if codec.max_lowres > 0 {
2148 options = options.set_int(c"lowres", i64::from(codec.max_lowres), 0);
2149 }
2150 alpha2_ffmpeg_bridge::open_codec_context(&mut decoder, options).ok()?;
2151 let mut sent = false;
2152 for _ in 0..16 {
2154 match decoder.receive_frame() {
2155 Ok(frame) => {
2156 return alpha2_ffmpeg_bridge::frame_display_rotation(&frame)
2157 .and_then(orientation_from_display);
2158 }
2159 Err(RsmpegError::DecoderDrainError) => {}
2160 Err(_) => return None,
2161 }
2162 match input.read_packet().ok()? {
2163 Some(packet) if usize::try_from(packet.stream_index).ok() == Some(index) => {
2164 decoder.send_packet(Some(&packet)).ok()?;
2165 sent = true;
2166 }
2167 Some(_) => {}
2168 None if sent => {
2169 decoder.send_packet(None).ok()?;
2170 sent = false;
2171 }
2172 None => return None,
2173 }
2174 }
2175 None
2176}
2177
2178#[allow(clippy::too_many_lines)]
2179const PARSED_END_READ: u64 = 250_000;
2183
2184fn parsed_end(input: &DemuxInput, path: &Path) -> Option<i64> {
2197 if input.duration_estimation_method != ffi::AVFMT_DURATION_FROM_PTS {
2198 return None;
2199 }
2200 let size = std::fs::metadata(path).ok()?.len();
2201 let mut tail = open_input(path).ok()?;
2202 let streams = tail
2203 .streams()
2204 .iter()
2205 .map(|stream| {
2206 let kind = stream.codecpar().codec_type();
2207 (kind.is_audio() || kind.is_video()).then_some(stream.time_base)
2208 })
2209 .collect::<Vec<_>>();
2210 let av_time_base = ra(1, i32::try_from(ffi::AV_TIME_BASE).ok()?);
2211 for doubling in 0..6 {
2212 let from = size.saturating_sub(PARSED_END_READ << doubling);
2213 tail.seek(
2214 -1,
2215 i64::try_from(from).ok()?,
2216 i32::try_from(ffi::AVSEEK_FLAG_BYTE).ok()?,
2217 )
2218 .ok()?;
2219 let mut end = None::<i64>;
2220 while let Ok(Some(packet)) = tail.read_packet() {
2221 let Some(Some(time_base)) = usize::try_from(packet.stream_index)
2222 .ok()
2223 .and_then(|index| streams.get(index))
2224 else {
2225 continue;
2226 };
2227 if packet.pts == ffi::AV_NOPTS_VALUE {
2228 continue;
2229 }
2230 let packet_end = av_rescale_q(
2231 packet.pts.saturating_add(packet.duration.max(0)),
2232 *time_base,
2233 av_time_base,
2234 );
2235 end = Some(end.map_or(packet_end, |end: i64| end.max(packet_end)));
2236 }
2237 if end.is_some() || from == 0 {
2238 return end;
2239 }
2240 }
2241 None
2242}
2243
2244pub(crate) fn probe_context(input: &DemuxInput, path: &Path) -> Result<ProbeResult, DecodeError> {
2246 let selected_video = input
2247 .best_stream(ffi::AVMEDIA_TYPE_VIDEO)
2248 .map_err(adapter_error)?;
2249 let selected_audio = input
2250 .best_stream(ffi::AVMEDIA_TYPE_AUDIO)
2251 .map_err(adapter_error)?;
2252 let streams = input
2253 .streams()
2254 .iter()
2255 .enumerate()
2256 .filter_map(|(index, stream)| {
2257 let parameters = stream.codecpar();
2258 let kind = if parameters.codec_type().is_video() {
2259 StreamKind::Video
2260 } else if parameters.codec_type().is_audio() {
2261 StreamKind::Audio
2262 } else {
2263 return None;
2264 };
2265 let codec = AVCodec::find_decoder(parameters.codec_id).map_or_else(
2266 || "unknown".to_owned(),
2267 |codec| codec.name().to_string_lossy().into_owned(),
2268 );
2269 let dimensions = (kind == StreamKind::Video).then(|| {
2270 (
2271 u32::try_from(parameters.width).unwrap_or(0),
2272 u32::try_from(parameters.height).unwrap_or(0),
2273 )
2274 });
2275 let frame_rate = stream
2276 .guess_framerate()
2277 .filter(|rate| rate.num > 0 && rate.den > 0)
2278 .and_then(|rate| {
2279 FrameRate::new(u32::try_from(rate.num).ok()?, u32::try_from(rate.den).ok()?)
2280 .ok()
2281 });
2282 Some(StreamInfo {
2283 index,
2284 kind,
2285 codec,
2286 duration: timestamp(stream.duration, stream.time_base),
2287 dimensions,
2288 frame_rate,
2289 sample_rate: (parameters.sample_rate > 0)
2290 .then(|| u32::try_from(parameters.sample_rate).unwrap_or(0)),
2291 channels: (kind == StreamKind::Audio)
2292 .then(|| u32::try_from(parameters.ch_layout.nb_channels).unwrap_or(0)),
2293 orientation: alpha2_ffmpeg_bridge::display_rotation(¶meters)
2294 .and_then(orientation_from_display)
2295 .or_else(|| {
2296 (kind == StreamKind::Video
2297 && Some(index) == selected_video
2298 && is_still_image_format(input))
2299 .then(|| still_image_orientation(path, index))
2300 .flatten()
2301 })
2302 .unwrap_or_else(|| Orientation::rotated(stream_rotation(stream.metadata()))),
2303 sample_aspect_ratio: guess_sar(
2304 stream.sample_aspect_ratio,
2305 parameters.sample_aspect_ratio,
2306 ),
2307 color: if kind == StreamKind::Video {
2308 color_metadata(¶meters)
2309 } else {
2310 None
2311 },
2312 })
2313 })
2314 .collect();
2315 let av_time_base = ra(
2316 1,
2317 i32::try_from(ffi::AV_TIME_BASE).expect("time base fits i32"),
2318 );
2319 let duration = parsed_end(input, path)
2320 .filter(|_| input.start_time != ffi::AV_NOPTS_VALUE)
2321 .map(|end| end.saturating_sub(input.start_time))
2322 .map_or(input.duration, |parsed| parsed.max(input.duration));
2323 Ok(ProbeResult {
2324 format: input.iformat().name().to_string_lossy().into_owned(),
2325 duration: (duration > 0)
2326 .then(|| MediaTime::from_ticks(av_rescale_q(duration, av_time_base, tick_base()))),
2327 start: (input.start_time != ffi::AV_NOPTS_VALUE).then(|| {
2328 MediaTime::from_ticks(av_rescale_q(
2329 input.start_time,
2330 ra(
2331 1,
2332 i32::try_from(ffi::AV_TIME_BASE).expect("time base fits i32"),
2333 ),
2334 tick_base(),
2335 ))
2336 }),
2337 streams,
2338 selected_video,
2339 selected_audio,
2340 })
2341}