1#[cfg(any(target_os = "linux", target_os = "windows"))]
2#[cfg_attr(target_os = "windows", allow(dead_code))]
3mod biplanar;
4mod nal;
5
6#[cfg(test)]
7mod tests;
8
9#[cfg(target_os = "linux")]
10mod vaapi;
11
12#[cfg(any(
13 target_os = "windows",
14 all(target_os = "linux", target_pointer_width = "64")
15))]
16mod nvenc;
17
18#[cfg(target_os = "macos")]
19mod videotoolbox;
20
21#[cfg(target_os = "windows")]
22mod media_foundation;
23
24use crate::media::{self, Codec, Error, Frame, Packet, Result, Rgba, TimeBase};
25use std::sync::Arc;
26
27#[cfg(target_os = "linux")]
28pub use vaapi::{DmaBuf, DmaLayer, DmaObject, DmaPlane};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum Direction {
32 Decode,
33 Encode,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum Backend {
38 VaApi,
39 Nvenc,
40 VideoToolbox,
41 MediaFoundation,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum Profile {
46 Baseline,
47 Main,
48 High,
49 Main10,
50}
51
52#[deprecated(note = "use pito_codecs::media::Rate")]
53pub type Rate = crate::media::Rate;
54
55#[deprecated(note = "use pito_codecs::media::Bitstream")]
56pub type Bitstream = crate::media::Bitstream;
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct EncoderConfig {
60 pub width: u32,
61 pub height: u32,
62 pub fps: (u32, u32),
63 pub profile: Profile,
64 pub rate: media::Rate,
65 pub gop: u32,
66 pub b_frames: u8,
67 pub bitstream: media::Bitstream,
68}
69
70impl Default for EncoderConfig {
71 fn default() -> Self {
72 Self {
73 width: 1920,
74 height: 1080,
75 fps: (30, 1),
76 profile: Profile::High,
77 rate: media::Rate::Crf(20),
78 gop: 0,
79 b_frames: 0,
80 bitstream: media::Bitstream::LengthPrefixed,
81 }
82 }
83}
84
85pub fn available() -> Vec<(Codec, Direction, Backend)> {
86 let mut out = Vec::new();
87 #[cfg(target_os = "linux")]
88 out.extend(
89 vaapi::available()
90 .into_iter()
91 .map(|(c, d)| (c, d, Backend::VaApi)),
92 );
93 #[cfg(any(
94 target_os = "windows",
95 all(target_os = "linux", target_pointer_width = "64")
96 ))]
97 out.extend(
98 nvenc::available()
99 .into_iter()
100 .map(|(c, d)| (c, d, Backend::Nvenc)),
101 );
102 #[cfg(target_os = "macos")]
103 out.extend(
104 videotoolbox::available()
105 .into_iter()
106 .map(|(c, d)| (c, d, Backend::VideoToolbox)),
107 );
108 #[cfg(target_os = "windows")]
109 out.extend(
110 media_foundation::available()
111 .into_iter()
112 .map(|(c, d)| (c, d, Backend::MediaFoundation)),
113 );
114 out
115}
116
117pub fn driver(backend: Backend) -> Result<String> {
118 match backend {
119 #[cfg(target_os = "linux")]
120 Backend::VaApi => vaapi::driver(),
121 b => Err(Error::Unsupported(format!("{b:?} on this system"))),
122 }
123}
124
125#[cfg_attr(
126 not(all(
127 feature = "h264",
128 feature = "hevc",
129 feature = "aac",
130 feature = "mp4",
131 feature = "filter",
132 feature = "mkv",
133 feature = "vp9"
134 )),
135 allow(dead_code)
136)]
137pub(crate) fn color(codec: Codec, extradata: &[u8]) -> Option<(u8, bool)> {
138 let sets = nal::parse_extradata(codec, extradata).ok()?.parameter_sets;
139 match codec {
140 Codec::H264 => {
141 let nal = sets
142 .iter()
143 .find(|n| n.first().is_some_and(|h| h & 0x1f == 7))?;
144 let rbsp = crate::syntax::bits::unescape_rbsp(nal.get(1..)?);
145 let vui = crate::syntax::h264::params::Sps::parse(&rbsp).ok()?.vui?;
146 Some((vui.matrix_coefficients, vui.full_range))
147 }
148 Codec::Hevc => {
149 let nal = sets
150 .iter()
151 .find(|n| n.first().is_some_and(|h| (h >> 1) & 0x3f == 33))?;
152 let rbsp = crate::syntax::bits::unescape_rbsp(nal.get(2..)?);
153 let vui = crate::syntax::hevc::params::Sps::parse(&rbsp).ok()?.vui?;
154 Some((vui.matrix_coefficients, vui.full_range))
155 }
156 _ => None,
157 }
158}
159
160pub(crate) trait DecoderBackend: Send {
161 fn backend(&self) -> Backend;
162 fn name(&self) -> &str;
163 fn decode(&mut self, packet: &Packet) -> Result<Vec<Frame>>;
164 fn flush(&mut self) -> Result<Vec<Frame>>;
165 fn reset(&mut self);
166 fn stills(&mut self) {}
167 fn decode_gpu(&mut self, packet: &Packet) -> Result<Vec<GpuFrame>> {
168 Ok(self
169 .decode(packet)?
170 .into_iter()
171 .map(GpuFrame::cpu)
172 .collect())
173 }
174 fn flush_gpu(&mut self) -> Result<Vec<GpuFrame>> {
175 Ok(self.flush()?.into_iter().map(GpuFrame::cpu).collect())
176 }
177}
178
179pub(crate) trait EncoderBackend: Send {
180 fn backend(&self) -> Backend;
181 fn name(&self) -> &str;
182 fn b_frames(&self) -> u8;
183 fn encode(&mut self, frame: &Frame) -> Result<Vec<Packet>>;
184 fn flush(&mut self) -> Result<Vec<Packet>>;
185 fn extradata(&self) -> Option<&[u8]>;
186 fn encode_gpu(&mut self, frame: &GpuFrame) -> Result<Vec<Packet>> {
187 self.encode(&frame.download()?)
188 }
189 fn composes(&self) -> bool {
190 false
191 }
192 fn encode_gpu_over(&mut self, _frame: &GpuFrame, _layer: &Arc<Rgba>) -> Result<Vec<Packet>> {
193 Err(Error::Unsupported(
194 "this GPU encoder can't draw an overlay on the GPU".into(),
195 ))
196 }
197}
198
199type DecoderMaker = fn(Codec, &[u8]) -> Result<Box<dyn DecoderBackend>>;
200type EncoderMaker = fn(Codec, &EncoderConfig) -> Result<Box<dyn EncoderBackend>>;
201
202#[allow(clippy::vec_init_then_push)]
203fn decoders() -> Vec<(&'static str, DecoderMaker)> {
204 let mut out: Vec<(&'static str, DecoderMaker)> = Vec::new();
205 #[cfg(target_os = "linux")]
206 out.push(("VA-API", vaapi::decoder));
207 #[cfg(target_os = "macos")]
208 out.push(("VideoToolbox", videotoolbox::decoder));
209 #[cfg(target_os = "windows")]
210 out.push(("Media Foundation", media_foundation::decoder));
211 out
212}
213
214#[allow(clippy::vec_init_then_push)]
215fn encoders() -> Vec<(&'static str, EncoderMaker)> {
216 let mut out: Vec<(&'static str, EncoderMaker)> = Vec::new();
217 #[cfg(target_os = "linux")]
218 out.push(("VA-API", vaapi::encoder));
219 #[cfg(any(
220 target_os = "windows",
221 all(target_os = "linux", target_pointer_width = "64")
222 ))]
223 out.push(("NVENC", nvenc::encoder));
224 #[cfg(target_os = "macos")]
225 out.push(("VideoToolbox", videotoolbox::encoder));
226 out
227}
228
229fn first<T>(makers: Vec<(&'static str, impl Fn() -> Result<T>)>, none: &str) -> Result<T> {
230 let mut why = Vec::new();
231 for (name, make) in makers {
232 match make() {
233 Ok(v) => return Ok(v),
234 Err(e) => why.push(format!("{name}: {e}")),
235 }
236 }
237 if why.is_empty() {
238 why.push(none.to_string());
239 }
240 Err(Error::Unsupported(why.join("; ")))
241}
242
243pub struct HwDecoder {
244 inner: Box<dyn DecoderBackend>,
245 time_base: TimeBase,
246}
247
248impl HwDecoder {
249 pub fn new(codec: Codec, extradata: &[u8]) -> Result<Self> {
250 let makers = decoders()
251 .into_iter()
252 .map(|(name, make)| (name, move || make(codec, extradata)))
253 .collect();
254 let inner = first(makers, "no GPU decoder on this system")?;
255 Ok(Self {
256 inner,
257 time_base: TimeBase::new(1, 90000),
258 })
259 }
260
261 pub fn with_time_base(mut self, time_base: TimeBase) -> Self {
262 self.time_base = time_base;
263 self
264 }
265
266 pub fn backend(&self) -> Backend {
267 self.inner.backend()
268 }
269
270 pub fn name(&self) -> &str {
271 self.inner.name()
272 }
273
274 pub fn decode(&mut self, packet: &Packet) -> Result<Vec<Frame>> {
275 let frames = self.inner.decode(packet)?;
276 Ok(self.stamp(frames))
277 }
278
279 pub fn flush(&mut self) -> Result<Vec<Frame>> {
280 let frames = self.inner.flush()?;
281 Ok(self.stamp(frames))
282 }
283
284 pub fn decode_gpu(&mut self, packet: &Packet) -> Result<Vec<GpuFrame>> {
285 let frames = self.inner.decode_gpu(packet)?;
286 Ok(self.stamp_gpu(frames))
287 }
288
289 pub fn flush_gpu(&mut self) -> Result<Vec<GpuFrame>> {
290 let frames = self.inner.flush_gpu()?;
291 Ok(self.stamp_gpu(frames))
292 }
293
294 pub fn reset(&mut self) {
295 self.inner.reset();
296 }
297
298 pub(crate) fn stills(&mut self) {
299 self.inner.stills();
300 }
301
302 fn stamp(&self, mut frames: Vec<Frame>) -> Vec<Frame> {
303 for f in &mut frames {
304 f.time_base = self.time_base;
305 }
306 frames
307 }
308
309 fn stamp_gpu(&self, mut frames: Vec<GpuFrame>) -> Vec<GpuFrame> {
310 for f in &mut frames {
311 f.time_base = self.time_base;
312 }
313 frames
314 }
315}
316
317pub(crate) enum GpuInner {
318 Cpu(Frame),
319 #[cfg(target_os = "linux")]
320 VaApi(vaapi::GpuSurface),
321}
322
323pub struct GpuFrame {
324 pub(crate) inner: GpuInner,
325 time_base: TimeBase,
326}
327
328impl GpuFrame {
329 pub(crate) fn cpu(frame: Frame) -> Self {
330 let time_base = frame.time_base;
331 Self {
332 inner: GpuInner::Cpu(frame),
333 time_base,
334 }
335 }
336
337 #[cfg(target_os = "linux")]
338 pub(crate) fn vaapi(surface: vaapi::GpuSurface) -> Self {
339 Self {
340 inner: GpuInner::VaApi(surface),
341 time_base: TimeBase::new(1, 90000),
342 }
343 }
344
345 pub fn pts(&self) -> i64 {
346 match &self.inner {
347 GpuInner::Cpu(f) => f.pts,
348 #[cfg(target_os = "linux")]
349 GpuInner::VaApi(g) => g.pts,
350 }
351 }
352
353 pub fn time_base(&self) -> TimeBase {
354 self.time_base
355 }
356
357 pub fn size(&self) -> (u32, u32) {
358 match &self.inner {
359 GpuInner::Cpu(f) => (f.width, f.height),
360 #[cfg(target_os = "linux")]
361 GpuInner::VaApi(g) => (g.width(), g.height()),
362 }
363 }
364
365 pub fn on_gpu(&self) -> bool {
366 !matches!(self.inner, GpuInner::Cpu(_))
367 }
368
369 pub fn surface(&self) -> Option<(u64, u32)> {
370 match &self.inner {
371 GpuInner::Cpu(_) => None,
372 #[cfg(target_os = "linux")]
373 GpuInner::VaApi(g) => Some(g.key()),
374 }
375 }
376
377 pub fn sync(&self) -> Result<()> {
378 match &self.inner {
379 GpuInner::Cpu(_) => Ok(()),
380 #[cfg(target_os = "linux")]
381 GpuInner::VaApi(g) => g.sync().map_err(Error::Backend),
382 }
383 }
384
385 #[cfg(target_os = "linux")]
386 pub fn dmabuf(&self, separate_layers: bool) -> Result<DmaBuf> {
387 match &self.inner {
388 GpuInner::VaApi(g) => g.dmabuf(separate_layers).map_err(Error::Backend),
389 GpuInner::Cpu(_) => Err(Error::Unsupported(
390 "this frame is in system memory, not on the GPU".into(),
391 )),
392 }
393 }
394
395 pub fn download(&self) -> Result<Frame> {
396 let frame = match &self.inner {
397 GpuInner::Cpu(f) => f.clone(),
398 #[cfg(target_os = "linux")]
399 GpuInner::VaApi(g) => g.download().map_err(Error::Backend)?,
400 };
401 Ok(Frame {
402 time_base: self.time_base,
403 ..frame
404 })
405 }
406}
407
408#[cfg_attr(
409 not(all(
410 feature = "h264",
411 feature = "hevc",
412 feature = "aac",
413 feature = "mp4",
414 feature = "filter",
415 feature = "mkv",
416 feature = "vp9"
417 )),
418 allow(dead_code)
419)]
420impl GpuFrame {
421 pub(crate) fn retime(&mut self, pts: i64, time_base: TimeBase) {
422 self.time_base = time_base;
423 match &mut self.inner {
424 GpuInner::Cpu(f) => {
425 f.pts = pts;
426 f.time_base = time_base;
427 }
428 #[cfg(target_os = "linux")]
429 GpuInner::VaApi(g) => g.pts = pts,
430 }
431 }
432
433 pub(crate) fn into_cpu(self) -> Option<Frame> {
434 match self.inner {
435 GpuInner::Cpu(f) => Some(f),
436 #[cfg(target_os = "linux")]
437 GpuInner::VaApi(_) => None,
438 }
439 }
440}
441
442pub struct GpuScaler {
443 #[cfg(target_os = "linux")]
444 inner: vaapi::Scaler,
445 size: (u32, u32),
446}
447
448impl GpuScaler {
449 pub fn new(width: u32, height: u32, ten_bit: bool) -> Result<Self> {
450 Self::with_frames(width, height, ten_bit, 8)
451 }
452
453 pub fn with_frames(width: u32, height: u32, ten_bit: bool, frames: usize) -> Result<Self> {
454 if width == 0 || height == 0 {
455 return Err(Error::Invalid("scaler size must be set".into()));
456 }
457 #[cfg(target_os = "linux")]
458 {
459 let bits = if ten_bit { 10 } else { 8 };
460 let inner =
461 vaapi::Scaler::new((width, height), bits, frames).map_err(Error::Backend)?;
462 Ok(Self {
463 inner,
464 size: (width, height),
465 })
466 }
467 #[cfg(not(target_os = "linux"))]
468 {
469 let _ = (ten_bit, frames);
470 Err(Error::Unsupported("no GPU scaler on this system".into()))
471 }
472 }
473
474 pub fn size(&self) -> (u32, u32) {
475 self.size
476 }
477
478 pub fn scale(&self, frame: &GpuFrame) -> Result<GpuFrame> {
479 match &frame.inner {
480 #[cfg(target_os = "linux")]
481 GpuInner::VaApi(g) => {
482 let out = self.inner.scale(g).map_err(Error::Backend)?;
483 Ok(GpuFrame {
484 inner: GpuInner::VaApi(out),
485 time_base: frame.time_base,
486 })
487 }
488 GpuInner::Cpu(_) => Err(Error::Unsupported(
489 "the GPU scaler takes frames on the GPU".into(),
490 )),
491 }
492 }
493}
494
495pub struct HwEncoder {
496 inner: Box<dyn EncoderBackend>,
497}
498
499impl HwEncoder {
500 pub fn new(codec: Codec, config: &EncoderConfig) -> Result<Self> {
501 let makers = encoders()
502 .into_iter()
503 .map(|(name, make)| (name, move || make(codec, config)))
504 .collect();
505 let inner = first(makers, "no GPU encoder on this system")?;
506 Ok(Self { inner })
507 }
508
509 pub fn backend(&self) -> Backend {
510 self.inner.backend()
511 }
512
513 pub fn name(&self) -> &str {
514 self.inner.name()
515 }
516
517 pub fn b_frames(&self) -> u8 {
518 self.inner.b_frames()
519 }
520
521 pub fn encode(&mut self, frame: &Frame) -> Result<Vec<Packet>> {
522 self.inner.encode(frame)
523 }
524
525 pub fn encode_gpu(&mut self, frame: &GpuFrame) -> Result<Vec<Packet>> {
526 self.inner.encode_gpu(frame)
527 }
528
529 pub fn composes(&self) -> bool {
530 self.inner.composes()
531 }
532
533 pub fn encode_gpu_over(&mut self, frame: &GpuFrame, layer: &Arc<Rgba>) -> Result<Vec<Packet>> {
534 self.inner.encode_gpu_over(frame, layer)
535 }
536
537 pub fn flush(&mut self) -> Result<Vec<Packet>> {
538 self.inner.flush()
539 }
540
541 pub fn extradata(&self) -> Option<&[u8]> {
542 self.inner.extradata()
543 }
544}