1use std::io::Read;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use crate::gpu::{GpuFrame, GpuScaler};
6use crate::mp4::Reader;
7
8use super::demux::Demux;
9use super::ffmpeg::{Child, Ffmpeg, pix_fmt};
10use super::native;
11use super::picture::{Color, even, fit_width, resize};
12use super::probe::{Probe, probe};
13use super::route::{Caps, Choice, Prefs, pick_decode};
14use crate::media::{Error, Frame, PixelFormat, Result, Route, TimeBase};
15
16#[derive(Clone)]
17pub struct Source {
18 pub path: PathBuf,
19 pub probe: Probe,
20 pub route: Route,
21 pub reasons: Vec<String>,
22 pub ffmpeg: Option<Ffmpeg>,
23 pub ffmpeg_codec: Option<String>,
24 pub demux: Option<Demux>,
25}
26
27impl std::fmt::Debug for Video {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 write!(f, "Video({} {}x{})", self.route, self.width, self.height)
30 }
31}
32
33pub fn source(path: &Path, prefs: &Prefs) -> Result<Source> {
34 let probe = probe(path, prefs)?;
35 let caps = Caps::detect(prefs);
36 let demux = Demux::open(path).ok();
37 let (mut route, mut reasons, mut ffmpeg_codec) = match probe.video_codec {
38 Some(codec) => {
39 let p = pick_decode(codec, prefs, &caps)?;
40 (p.route, p.reasons, p.ffmpeg_codec)
41 }
42 None => match (&prefs.decode, &caps.ffmpeg) {
43 (Choice::Ffmpeg(u), Some(_)) => (Route::Ffmpeg, Vec::new(), u.codec.clone()),
44 _ => {
45 return Err(Error::Unsupported(format!(
46 "{} video decodes only through ffmpeg picked in the settings",
47 probe.video_codec_name
48 )));
49 }
50 },
51 };
52 if route != Route::Ffmpeg && demux.is_none() {
53 match (&caps.ffmpeg, &prefs.decode) {
54 (Some(_), Choice::Ffmpeg(u)) => {
55 reasons.push("our MP4 and MKV readers can't open this file".to_string());
56 route = Route::Ffmpeg;
57 ffmpeg_codec = u.codec.clone();
58 }
59 _ => {
60 return Err(Error::Unsupported(
61 "our MP4 and MKV readers can't open this file; pick ffmpeg in the settings to read it"
62 .to_string(),
63 ));
64 }
65 }
66 }
67 Ok(Source {
68 path: path.to_path_buf(),
69 probe,
70 route,
71 reasons,
72 ffmpeg: caps.ffmpeg,
73 ffmpeg_codec,
74 demux,
75 })
76}
77
78impl Source {
79 pub fn ffmpeg(&self) -> Result<&Ffmpeg> {
80 self.ffmpeg
81 .as_ref()
82 .ok_or_else(|| Error::Unsupported("ffmpeg is not picked in the settings".to_string()))
83 }
84
85 pub fn native(&self) -> Error {
86 Error::Unsupported(format!(
87 "the {} route for {} is not in this build",
88 self.route, self.probe.video_codec_name
89 ))
90 }
91
92 pub fn demux(&self) -> Result<&Demux> {
93 self.demux.as_ref().ok_or_else(|| {
94 Error::Unsupported("our MP4 and MKV readers can't open this file".to_string())
95 })
96 }
97
98 pub fn mp4(&self) -> Option<&Arc<Reader>> {
99 self.demux.as_ref().and_then(Demux::mp4)
100 }
101}
102
103enum Inner {
104 Ffmpeg { child: Child, next: i64, got: u64 },
105 Native(Box<native::Frames>, (u32, u32)),
106}
107
108pub struct Video {
109 inner: Inner,
110 pub route: Route,
111 pub reasons: Vec<String>,
112 pub width: u32,
113 pub height: u32,
114 pub format: PixelFormat,
115 pub time_base: TimeBase,
116 left: Option<u64>,
117}
118
119pub fn decode_video(path: &Path, from: f64, height: Option<u32>, prefs: &Prefs) -> Result<Video> {
120 let src = source(path, prefs)?;
121 let p = &src.probe;
122 let size = height
123 .filter(|h| *h > 0 && p.height > 0 && *h != p.height)
124 .map(|h| (fit_width(p.width, p.height, h), h));
125 open(&src, from, None, size, None)
126}
127
128pub fn open(
129 src: &Source,
130 from: f64,
131 frames: Option<u64>,
132 size: Option<(u32, u32)>,
133 fps: Option<(u32, u32)>,
134) -> Result<Video> {
135 let p = &src.probe;
136 match src.route {
137 Route::Cpu | Route::Gpu => {
138 let decoded = native::Frames::open(src.demux()?.clone(), from, src.route)?;
139 let mut reasons = src.reasons.clone();
140 reasons.extend(decoded.reason.clone());
141 return Ok(native_video(
142 decoded,
143 reasons,
144 size.unwrap_or((p.width, p.height)),
145 fps.unwrap_or(p.fps),
146 format_of(p),
147 frames,
148 ));
149 }
150 Route::Ffmpeg => {}
151 }
152 let f = src.ffmpeg()?;
153 let format = format_of(p);
154 let (width, out_height) = size.unwrap_or((p.width, p.height));
155 if width == 0 || out_height == 0 {
156 return Err(Error::Invalid(format!("a picture of {width}x{out_height}")));
157 }
158 let fps = fps.unwrap_or(p.fps);
159 let mut args: Vec<String> = f.decoder_args(src.ffmpeg_codec.as_deref());
160 args.extend([
161 "-noautorotate".into(),
162 "-ss".into(),
163 format!("{:.6}", from.max(0.0)),
164 "-i".into(),
165 src.path.display().to_string(),
166 "-map".into(),
167 "0:v:0".into(),
168 "-fps_mode".into(),
169 "cfr".into(),
170 "-r".into(),
171 format!("{}/{}", fps.0, fps.1),
172 ]);
173 if let Some(n) = frames {
174 args.extend(["-frames:v".into(), n.to_string()]);
175 }
176 if (width, out_height) != (p.width, p.height) {
177 args.extend([
178 "-vf".into(),
179 format!("scale={width}:{out_height}:flags=area"),
180 ]);
181 }
182 args.extend([
183 "-f".into(),
184 "rawvideo".into(),
185 "-pix_fmt".into(),
186 pix_fmt(format).into(),
187 "-".into(),
188 ]);
189 let child = f.spawn(&args, false, true)?;
190 Ok(Video {
191 inner: Inner::Ffmpeg {
192 child,
193 next: frame_at(from.max(0.0), fps),
194 got: 0,
195 },
196 route: Route::Ffmpeg,
197 reasons: src.reasons.clone(),
198 width,
199 height: out_height,
200 format,
201 time_base: TimeBase::new(fps.1, fps.0),
202 left: frames,
203 })
204}
205
206impl Video {
207 pub fn recycle(&mut self, frame: Frame) {
208 if let Inner::Native(frames, _) = &mut self.inner {
209 frames.recycle(frame);
210 }
211 }
212
213 fn read(&mut self) -> Result<Option<Frame>> {
214 if self.left == Some(0) {
215 return Ok(None);
216 }
217 let (child, next, got) = match &mut self.inner {
218 Inner::Ffmpeg { child, next, got } => (child, next, got),
219 Inner::Native(frames, fps) => {
220 let Some(mut frame) = frames.next_frame()? else {
221 return Ok(None);
222 };
223 frame.pts = frame_at(frames.seconds(frame.pts), *fps);
224 frame.time_base = self.time_base;
225 if (frame.width, frame.height) != (self.width, self.height) {
226 frame = resize(&frame, self.width, self.height);
227 }
228 if let Some(n) = self.left.as_mut() {
229 *n -= 1;
230 }
231 return Ok(Some(frame));
232 }
233 };
234 let mut frame = Frame::new(self.format, self.width, self.height, *next, self.time_base);
235 let Some(out) = child.stdout.as_mut() else {
236 return Ok(None);
237 };
238 for (i, plane) in frame.planes.iter_mut().enumerate() {
239 match out.read_exact(&mut plane.data) {
240 Ok(()) => {}
241 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof && i == 0 => {
242 self.left = Some(0);
243 let err = child.stderr_now();
244 if !err.is_empty() && *got == 0 {
245 return Err(Error::Backend(format!("ffmpeg: {err}")));
246 }
247 return Ok(None);
248 }
249 Err(e) => return Err(Error::Io(e)),
250 }
251 }
252 *next += 1;
253 *got += 1;
254 if let Some(n) = self.left.as_mut() {
255 *n -= 1;
256 }
257 Ok(Some(frame))
258 }
259}
260
261impl Iterator for Video {
262 type Item = Result<Frame>;
263
264 fn next(&mut self) -> Option<Result<Frame>> {
265 self.read().transpose()
266 }
267}
268
269pub const IN_FLIGHT: usize = 8;
270
271pub enum VideoFrame {
272 Gpu(GpuFrame),
273 Cpu(Frame),
274}
275
276impl VideoFrame {
277 pub fn pts(&self) -> i64 {
278 match self {
279 VideoFrame::Gpu(f) => f.pts(),
280 VideoFrame::Cpu(f) => f.pts,
281 }
282 }
283
284 pub fn size(&self) -> (u32, u32) {
285 match self {
286 VideoFrame::Gpu(f) => f.size(),
287 VideoFrame::Cpu(f) => (f.width, f.height),
288 }
289 }
290
291 pub fn on_gpu(&self) -> bool {
292 matches!(self, VideoFrame::Gpu(_))
293 }
294}
295
296enum GpuRoute {
297 Gpu {
298 scaler: Option<GpuScaler>,
299 frames: Box<native::Frames>,
300 fps: (u32, u32),
301 },
302 Cpu(Video),
303}
304
305pub struct GpuVideo {
306 inner: GpuRoute,
307 pub route: Route,
308 pub reasons: Vec<String>,
309 pub width: u32,
310 pub height: u32,
311 pub format: PixelFormat,
312 pub time_base: TimeBase,
313 pub color: Color,
314}
315
316impl std::fmt::Debug for GpuVideo {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 write!(
319 f,
320 "GpuVideo({} {}x{}, {} on the GPU)",
321 self.route,
322 self.width,
323 self.height,
324 if self.on_gpu() { "frames" } else { "no frames" }
325 )
326 }
327}
328
329fn format_of(p: &Probe) -> PixelFormat {
330 if p.bit_depth > 8 {
331 PixelFormat::Yuv420p10
332 } else {
333 PixelFormat::Yuv420p8
334 }
335}
336
337pub(super) fn frame_at(seconds: f64, fps: (u32, u32)) -> i64 {
338 (seconds * fps.0 as f64 / fps.1.max(1) as f64).round() as i64
339}
340
341fn stream_color(src: &Source) -> Color {
342 let found = src
343 .demux
344 .as_ref()
345 .and_then(|d| d.stream(d.video().ok()?).ok()?.color);
346 let (matrix, full_range) = found.unwrap_or((2, false));
347 Color { matrix, full_range }
348}
349
350pub fn decode_video_gpu(
351 path: &Path,
352 from: f64,
353 size: Option<(u32, u32)>,
354 prefs: &Prefs,
355) -> Result<GpuVideo> {
356 let src = source(path, prefs)?;
357 let p = &src.probe;
358 let size = size
359 .filter(|&(w, h)| w > 0 && h > 0)
360 .map(|(w, h)| (even(w), even(h)))
361 .unwrap_or((p.width, p.height));
362 let fps = p.fps;
363 let color = stream_color(&src);
364 let format = format_of(p);
365 if src.route == Route::Gpu {
366 let frames = native::Frames::open(src.demux()?.clone(), from, Route::Gpu)?;
367 let mut reasons = src.reasons.clone();
368 reasons.extend(frames.reason.clone());
369 if frames.route() == Route::Gpu {
370 let ten_bit = format == PixelFormat::Yuv420p10;
371 let scaler = match GpuScaler::with_frames(size.0, size.1, ten_bit, IN_FLIGHT) {
372 Ok(s) => Some(s),
373 Err(e) if size == (p.width, p.height) => {
374 reasons.push(format!(
375 "no GPU scaler ({e}); frames are the decoder's own surfaces"
376 ));
377 None
378 }
379 Err(e) => {
380 reasons.push(format!("no GPU scaler ({e}); frames are scaled on the CPU"));
381 None
382 }
383 };
384 return Ok(GpuVideo {
385 inner: GpuRoute::Gpu {
386 scaler,
387 frames: Box::new(frames),
388 fps,
389 },
390 route: Route::Gpu,
391 reasons,
392 width: size.0,
393 height: size.1,
394 format,
395 time_base: TimeBase::new(fps.1, fps.0),
396 color,
397 });
398 }
399 let video = native_video(frames, reasons, size, fps, format, None);
400 return Ok(GpuVideo::cpu(video, color));
401 }
402 let video = open(&src, from, None, Some(size), Some(fps))?;
403 Ok(GpuVideo::cpu(video, color))
404}
405
406fn native_video(
407 frames: native::Frames,
408 reasons: Vec<String>,
409 size: (u32, u32),
410 fps: (u32, u32),
411 format: PixelFormat,
412 left: Option<u64>,
413) -> Video {
414 Video {
415 route: frames.route(),
416 inner: Inner::Native(Box::new(frames), fps),
417 reasons,
418 width: size.0,
419 height: size.1,
420 format,
421 time_base: TimeBase::new(fps.1, fps.0),
422 left,
423 }
424}
425
426impl GpuVideo {
427 fn cpu(video: Video, color: Color) -> Self {
428 Self {
429 route: video.route,
430 reasons: video.reasons.clone(),
431 width: video.width,
432 height: video.height,
433 format: video.format,
434 time_base: video.time_base,
435 color,
436 inner: GpuRoute::Cpu(video),
437 }
438 }
439
440 pub fn on_gpu(&self) -> bool {
441 matches!(self.inner, GpuRoute::Gpu { .. })
442 }
443
444 pub fn recycle(&mut self, frame: VideoFrame) {
445 let VideoFrame::Cpu(frame) = frame else {
446 return;
447 };
448 match &mut self.inner {
449 GpuRoute::Gpu { frames, .. } => frames.recycle(frame),
450 GpuRoute::Cpu(video) => video.recycle(frame),
451 }
452 }
453
454 fn read(&mut self) -> Result<Option<VideoFrame>> {
455 let size = (self.width, self.height);
456 let time_base = self.time_base;
457 let (frames, scaler, fps) = match &mut self.inner {
458 GpuRoute::Cpu(video) => return Ok(video.read()?.map(VideoFrame::Cpu)),
459 GpuRoute::Gpu {
460 frames,
461 scaler,
462 fps,
463 } => (frames, scaler, *fps),
464 };
465 let Some(mut frame) = frames.next_gpu()? else {
466 return Ok(None);
467 };
468 let pts = frame_at(frames.seconds(frame.pts()), fps);
469 if !frame.on_gpu() {
470 let Some(mut cpu) = frame.into_cpu() else {
471 return Ok(None);
472 };
473 cpu.pts = pts;
474 cpu.time_base = time_base;
475 if (cpu.width, cpu.height) != size {
476 cpu = resize(&cpu, size.0, size.1);
477 }
478 return Ok(Some(VideoFrame::Cpu(cpu)));
479 }
480 frame.retime(pts, time_base);
481 match scaler {
482 Some(s) => Ok(Some(VideoFrame::Gpu(s.scale(&frame)?))),
483 None if frame.size() == size => Ok(Some(VideoFrame::Gpu(frame))),
484 None => {
485 let cpu = frame.download()?;
486 Ok(Some(VideoFrame::Cpu(resize(&cpu, size.0, size.1))))
487 }
488 }
489 }
490}
491
492impl Iterator for GpuVideo {
493 type Item = Result<VideoFrame>;
494
495 fn next(&mut self) -> Option<Result<VideoFrame>> {
496 self.read().transpose()
497 }
498}