1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4use crate::{
5 NativeFrame, NativeFrameColorMetadata, NativeFrameHdrMetadata, NativeFrameLeaseToken,
6 NativeFrameMetadata, NativeFramePipelineProfile, NativeFrameReleaseTracking,
7 NativeFrameSyncInfo, NativeFrameTransform, NativeHandleKind, SourceNormalizerPacketMediaKind,
8 SourceNormalizerPacketTrackInfo, VisibleRect,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
13pub enum DecoderMediaKind {
14 #[default]
15 Video,
16 Audio,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub enum DecoderFrameFormat {
22 Rgba8888,
23 Bgra8888,
24 Yuv420p,
25 Nv12,
26 P010,
28 F32,
30 S16,
32 Unknown(String),
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
37pub enum DecoderPcmSampleLayout {
38 #[default]
39 Interleaved,
40 Planar,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct DecoderCodecCapability {
46 pub codec: String,
47 pub media_kind: DecoderMediaKind,
48 pub profiles: Vec<String>,
49 pub output_formats: Vec<DecoderFrameFormat>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
54pub struct DecoderCapabilities {
55 pub codecs: Vec<DecoderCodecCapability>,
56 pub supports_hardware_decode: bool,
57 pub supports_cpu_video_frames: bool,
58 pub supports_audio_frames: bool,
60 #[serde(default)]
62 pub supports_pcm_frames: bool,
63 pub supports_gpu_handles: bool,
64 #[serde(default)]
66 pub supports_presentation_release: bool,
67 pub supports_flush: bool,
68 pub supports_drain: bool,
69 pub max_sessions: Option<u32>,
70}
71
72impl DecoderCapabilities {
73 pub fn supports_codec(&self, codec: &str, media_kind: DecoderMediaKind) -> bool {
75 let codec = normalize_decoder_codec_identifier(codec);
76 self.codecs.iter().any(|capability| {
77 capability.media_kind == media_kind
78 && normalize_decoder_codec_identifier(&capability.codec) == codec
79 })
80 }
81}
82
83pub fn normalize_decoder_codec_identifier(codec: &str) -> String {
89 let normalized = codec.trim().to_ascii_lowercase();
90 let normalized = normalized
91 .strip_prefix("video/")
92 .or_else(|| normalized.strip_prefix("audio/"))
93 .unwrap_or(&normalized);
94 let Some((sample_entry, _profile)) = normalized.split_once('.') else {
95 return normalized.to_owned();
96 };
97 if matches!(
98 sample_entry,
99 "avc1" | "avc3" | "hvc1" | "hev1" | "dvh1" | "dvhe" | "vp09" | "av01" | "mp4a"
100 ) {
101 sample_entry.to_owned()
102 } else {
103 normalized.to_owned()
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
109pub struct DecoderSessionRequirements {
110 pub codec: String,
111 pub media_kind: DecoderMediaKind,
112 #[serde(default)]
113 pub native_handle_kind: Option<DecoderNativeHandleKind>,
114 #[serde(default)]
115 pub pipeline_profile: Option<NativeFramePipelineProfile>,
116 #[serde(default)]
117 pub native_device_context_kind: Option<DecoderNativeDeviceContextKind>,
118 #[serde(default)]
119 pub require_presentation_release: bool,
120 #[serde(default)]
121 pub require_pcm_output: bool,
122}
123
124impl DecoderSessionRequirements {
125 pub fn native_video(
127 codec: impl Into<String>,
128 native_handle_kind: DecoderNativeHandleKind,
129 pipeline_profile: NativeFramePipelineProfile,
130 ) -> Self {
131 Self {
132 codec: codec.into(),
133 media_kind: DecoderMediaKind::Video,
134 native_handle_kind: Some(native_handle_kind),
135 pipeline_profile: Some(pipeline_profile),
136 ..Self::default()
137 }
138 }
139
140 pub fn missing_capabilities(
142 &self,
143 capabilities: &DecoderCapabilities,
144 native_requirements: &DecoderNativeRequirements,
145 ) -> Vec<String> {
146 let mut missing = Vec::new();
147 if !capabilities.supports_codec(&self.codec, self.media_kind) {
148 missing.push(format!("{:?} codec {}", self.media_kind, self.codec));
149 }
150 if self.require_pcm_output && !capabilities.supports_pcm_frames {
151 missing.push("supportsPcmFrames".to_owned());
152 }
153 if self.require_presentation_release && !capabilities.supports_presentation_release {
154 missing.push("supportsPresentationRelease".to_owned());
155 }
156 if let Some(handle_kind) = &self.native_handle_kind
157 && !native_requirements
158 .output_handle_kinds
159 .contains(handle_kind)
160 {
161 missing.push(format!("outputHandleKind::{handle_kind:?}"));
162 }
163 if let Some(profile) = &self.pipeline_profile
164 && !native_requirements
165 .output_pipeline_profiles
166 .contains(profile)
167 {
168 missing.push(format!("pipelineProfile::{profile:?}"));
169 }
170 if let Some(context_kind) = &self.native_device_context_kind
171 && !native_requirements
172 .required_device_context_kinds
173 .contains(context_kind)
174 {
175 missing.push(format!("nativeDeviceContext::{context_kind:?}"));
176 }
177 missing
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
183pub struct DecoderSessionConfig {
184 pub codec: String,
185 pub media_kind: DecoderMediaKind,
186 pub extradata: Vec<u8>,
187 #[serde(default)]
188 pub bitstream_format: Option<DecoderBitstreamFormat>,
189 pub width: Option<u32>,
190 pub height: Option<u32>,
191 #[serde(default)]
192 pub coded_width: Option<u32>,
193 #[serde(default)]
194 pub coded_height: Option<u32>,
195 #[serde(default)]
196 pub reorder_depth: Option<u32>,
197 pub sample_rate: Option<u32>,
198 pub channels: Option<u16>,
199 #[serde(default)]
200 pub channel_layout: Option<String>,
201 #[serde(default)]
202 pub target_pcm_format: Option<DecoderFrameFormat>,
203 #[serde(default)]
204 pub target_pcm_sample_layout: Option<DecoderPcmSampleLayout>,
205 #[serde(default)]
206 pub codec_delay_samples: Option<u32>,
207 #[serde(default)]
208 pub priming_samples: Option<u32>,
209 #[serde(default)]
210 pub trailing_padding_samples: Option<u32>,
211 #[serde(default)]
212 pub seek_preroll_samples: Option<u32>,
213 pub prefer_hardware: bool,
214 #[serde(default)]
216 pub required_decoder_implementation_name: Option<String>,
217 pub require_cpu_output: bool,
218 #[serde(default)]
219 pub native_device_context: Option<DecoderNativeDeviceContext>,
220 #[serde(default)]
221 pub color: Option<NativeFrameColorMetadata>,
222 #[serde(default)]
223 pub hdr: Option<NativeFrameHdrMetadata>,
224}
225
226impl DecoderSessionConfig {
227 pub fn audio_from_source_normalizer_track(
229 track: &SourceNormalizerPacketTrackInfo,
230 target_pcm_format: DecoderFrameFormat,
231 target_pcm_sample_layout: DecoderPcmSampleLayout,
232 ) -> Result<Self, DecoderError> {
233 if track.media_kind != SourceNormalizerPacketMediaKind::Audio {
234 return Err(DecoderError::UnsupportedCapability {
235 capability: "source-normalizer-audio-track".to_owned(),
236 });
237 }
238 Ok(Self {
239 codec: track.codec.clone(),
240 media_kind: DecoderMediaKind::Audio,
241 extradata: track.extradata.clone(),
242 bitstream_format: track.bitstream_format.clone(),
243 sample_rate: track.sample_rate,
244 channels: track.channels,
245 channel_layout: track.channel_layout.clone(),
246 target_pcm_format: Some(target_pcm_format),
247 target_pcm_sample_layout: Some(target_pcm_sample_layout),
248 codec_delay_samples: track.codec_delay_samples,
249 priming_samples: track.priming_samples,
250 trailing_padding_samples: track.trailing_padding_samples,
251 seek_preroll_samples: track.seek_preroll_samples,
252 color: track.color.clone(),
253 hdr: track.hdr.clone(),
254 prefer_hardware: true,
255 require_cpu_output: true,
256 ..Self::default()
257 })
258 }
259
260 pub fn apple_native_audio_from_source_normalizer_track(
262 track: &SourceNormalizerPacketTrackInfo,
263 ) -> Result<Self, DecoderError> {
264 Self::audio_from_source_normalizer_track(
265 track,
266 DecoderFrameFormat::F32,
267 DecoderPcmSampleLayout::Interleaved,
268 )
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
274pub struct DecoderSessionInfo {
275 pub decoder_name: Option<String>,
276 pub selected_hardware_backend: Option<String>,
277 pub output_format: Option<DecoderFrameFormat>,
278}
279
280#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
282pub struct DecoderPacket {
283 pub pts_us: Option<i64>,
284 pub dts_us: Option<i64>,
285 pub duration_us: Option<i64>,
286 pub stream_index: u32,
287 #[serde(default)]
288 pub media_kind: DecoderMediaKind,
289 pub key_frame: bool,
290 pub discontinuity: bool,
291 #[serde(default)]
292 pub end_of_stream: bool,
293}
294
295impl TryFrom<crate::SourceNormalizerPacket> for DecoderPacket {
296 type Error = DecoderError;
297
298 fn try_from(packet: crate::SourceNormalizerPacket) -> Result<Self, Self::Error> {
299 let media_kind = match packet.media_kind {
300 SourceNormalizerPacketMediaKind::Audio => DecoderMediaKind::Audio,
301 SourceNormalizerPacketMediaKind::Video => DecoderMediaKind::Video,
302 SourceNormalizerPacketMediaKind::Subtitle => {
303 return Err(DecoderError::UnsupportedCapability {
304 capability: "source-normalizer-subtitle-packet".to_owned(),
305 });
306 }
307 };
308 Ok(Self {
309 pts_us: packet.pts_us,
310 dts_us: packet.dts_us,
311 duration_us: packet.duration_us,
312 stream_index: packet.stream_index,
313 media_kind,
314 key_frame: packet.key_frame,
315 discontinuity: packet.discontinuity,
316 end_of_stream: packet.end_of_stream,
317 })
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
323pub struct DecoderPacketResult {
324 pub accepted: bool,
325}
326
327impl Default for DecoderPacketResult {
328 fn default() -> Self {
329 Self { accepted: true }
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
335pub enum DecoderReceiveFrameStatus {
336 Frame,
337 NeedMoreInput,
338 Eof,
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
343pub enum DecoderNativeHandleKind {
344 CvPixelBuffer,
345 IoSurface,
346 MetalTexture,
347 DmaBuf,
348 VaapiSurface,
349 D3D11Texture2D,
350 DxgiSurface,
351 VulkanImage,
352 MediaCodecHardwareBuffer,
353 MediaCodecSurfaceTexture,
354 Unknown(String),
355}
356
357impl From<DecoderNativeHandleKind> for NativeHandleKind {
358 fn from(value: DecoderNativeHandleKind) -> Self {
359 match value {
360 DecoderNativeHandleKind::CvPixelBuffer => Self::CvPixelBuffer,
361 DecoderNativeHandleKind::IoSurface => Self::IoSurface,
362 DecoderNativeHandleKind::MetalTexture => Self::MetalTexture,
363 DecoderNativeHandleKind::DmaBuf => Self::DmaBuf,
364 DecoderNativeHandleKind::VaapiSurface => Self::VaapiSurface,
365 DecoderNativeHandleKind::D3D11Texture2D => Self::D3D11Texture2D,
366 DecoderNativeHandleKind::DxgiSurface => Self::DxgiSurface,
367 DecoderNativeHandleKind::VulkanImage => Self::VulkanImage,
368 DecoderNativeHandleKind::MediaCodecHardwareBuffer => Self::MediaCodecHardwareBuffer,
369 DecoderNativeHandleKind::MediaCodecSurfaceTexture => Self::MediaCodecSurfaceTexture,
370 DecoderNativeHandleKind::Unknown(name) => Self::Unknown(name),
371 }
372 }
373}
374
375impl From<NativeHandleKind> for DecoderNativeHandleKind {
376 fn from(value: NativeHandleKind) -> Self {
377 match value {
378 NativeHandleKind::CvPixelBuffer => Self::CvPixelBuffer,
379 NativeHandleKind::IoSurface => Self::IoSurface,
380 NativeHandleKind::MetalTexture => Self::MetalTexture,
381 NativeHandleKind::DmaBuf => Self::DmaBuf,
382 NativeHandleKind::VaapiSurface => Self::VaapiSurface,
383 NativeHandleKind::D3D11Texture2D => Self::D3D11Texture2D,
384 NativeHandleKind::DxgiSurface => Self::DxgiSurface,
385 NativeHandleKind::VulkanImage => Self::VulkanImage,
386 NativeHandleKind::MediaCodecHardwareBuffer => Self::MediaCodecHardwareBuffer,
387 NativeHandleKind::MediaCodecSurfaceTexture => Self::MediaCodecSurfaceTexture,
388 NativeHandleKind::Unknown(name) => Self::Unknown(name),
389 }
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
395pub enum DecoderNativeDeviceContextKind {
396 D3D11Device,
397 AndroidNativeWindow,
398 Unknown(String),
399}
400
401#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
403pub enum DecoderBitstreamFormat {
404 AnnexB,
405 Avcc,
406 Hvcc,
407 Unknown(String),
408}
409
410#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
412#[serde(tag = "kind", rename_all = "snake_case")]
413pub enum DecoderNativeDeviceContext {
414 #[serde(rename = "d3d11_device")]
415 D3D11Device {
416 device_ptr: usize,
417 },
418 #[serde(rename = "android_native_window")]
419 AndroidNativeWindow {
420 window_ptr: usize,
421 },
422 Unknown {
423 name: String,
424 },
425}
426
427impl DecoderNativeDeviceContext {
428 pub fn kind(&self) -> DecoderNativeDeviceContextKind {
429 match self {
430 Self::D3D11Device { .. } => DecoderNativeDeviceContextKind::D3D11Device,
431 Self::AndroidNativeWindow { .. } => DecoderNativeDeviceContextKind::AndroidNativeWindow,
432 Self::Unknown { name } => DecoderNativeDeviceContextKind::Unknown(name.clone()),
433 }
434 }
435
436 pub fn d3d11_device_ptr(&self) -> Option<usize> {
437 match self {
438 Self::D3D11Device { device_ptr } => Some(*device_ptr),
439 Self::AndroidNativeWindow { .. } | Self::Unknown { .. } => None,
440 }
441 }
442
443 pub fn android_native_window_ptr(&self) -> Option<usize> {
444 match self {
445 Self::AndroidNativeWindow { window_ptr } => Some(*window_ptr),
446 Self::D3D11Device { .. } | Self::Unknown { .. } => None,
447 }
448 }
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
453pub struct DecoderNativeRequirements {
454 pub required_device_context_kinds: Vec<DecoderNativeDeviceContextKind>,
455 pub output_handle_kinds: Vec<DecoderNativeHandleKind>,
456 #[serde(default)]
457 pub output_pipeline_profiles: Vec<NativeFramePipelineProfile>,
458 pub requires_native_device_context: bool,
459 pub accepted_bitstream_formats: Vec<DecoderBitstreamFormat>,
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
464pub struct DecoderVisibleRect {
465 pub x: u32,
466 pub y: u32,
467 pub width: u32,
468 pub height: u32,
469}
470
471impl From<DecoderVisibleRect> for VisibleRect {
472 fn from(value: DecoderVisibleRect) -> Self {
473 Self {
474 x: value.x,
475 y: value.y,
476 width: value.width,
477 height: value.height,
478 }
479 }
480}
481
482impl From<VisibleRect> for DecoderVisibleRect {
483 fn from(value: VisibleRect) -> Self {
484 Self {
485 x: value.x,
486 y: value.y,
487 width: value.width,
488 height: value.height,
489 }
490 }
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
495pub struct DecoderNativeFrameReleaseTracking {
496 pub frame_id: Option<u64>,
497 pub requires_release: bool,
498}
499
500impl From<DecoderNativeFrameReleaseTracking> for NativeFrameReleaseTracking {
501 fn from(value: DecoderNativeFrameReleaseTracking) -> Self {
502 Self {
503 frame_id: value.frame_id,
504 requires_release: value.requires_release,
505 }
506 }
507}
508
509impl From<NativeFrameReleaseTracking> for DecoderNativeFrameReleaseTracking {
510 fn from(value: NativeFrameReleaseTracking) -> Self {
511 Self {
512 frame_id: value.frame_id,
513 requires_release: value.requires_release,
514 }
515 }
516}
517
518#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
520pub struct DecoderNativeFrameMetadata {
521 pub media_kind: DecoderMediaKind,
522 pub format: DecoderFrameFormat,
523 pub codec: String,
524 pub pts_us: Option<i64>,
525 pub duration_us: Option<i64>,
526 pub width: u32,
527 pub height: u32,
528 #[serde(default)]
529 pub coded_width: Option<u32>,
530 #[serde(default)]
531 pub coded_height: Option<u32>,
532 #[serde(default)]
533 pub visible_rect: Option<DecoderVisibleRect>,
534 pub handle_kind: DecoderNativeHandleKind,
535 #[serde(default)]
536 pub pipeline_profile: Option<NativeFramePipelineProfile>,
537 #[serde(default)]
538 pub color_space: Option<String>,
539 #[serde(default)]
540 pub hdr_metadata: Option<String>,
541 #[serde(default)]
542 pub color: Option<NativeFrameColorMetadata>,
543 #[serde(default)]
544 pub hdr: Option<NativeFrameHdrMetadata>,
545 #[serde(default)]
546 pub sync_info: Option<NativeFrameSyncInfo>,
547 #[serde(default)]
548 pub transform: Option<NativeFrameTransform>,
549 #[serde(default)]
550 pub frame_id: Option<u64>,
551 #[serde(default)]
552 pub release_tracking: Option<DecoderNativeFrameReleaseTracking>,
553}
554
555impl From<DecoderNativeFrameMetadata> for NativeFrameMetadata {
556 fn from(value: DecoderNativeFrameMetadata) -> Self {
557 Self {
558 media_kind: value.media_kind,
559 format: value.format,
560 codec: value.codec,
561 pts_us: value.pts_us,
562 duration_us: value.duration_us,
563 width: value.width,
564 height: value.height,
565 coded_width: value.coded_width,
566 coded_height: value.coded_height,
567 visible_rect: value.visible_rect.map(Into::into),
568 handle_kind: value.handle_kind.into(),
569 pipeline_profile: value.pipeline_profile,
570 color_space: value.color_space,
571 hdr_metadata: value.hdr_metadata,
572 color: value.color,
573 hdr: value.hdr,
574 sync_info: value.sync_info,
575 transform: value.transform,
576 frame_id: value.frame_id,
577 release_tracking: value.release_tracking.map(Into::into),
578 }
579 }
580}
581
582impl From<NativeFrameMetadata> for DecoderNativeFrameMetadata {
583 fn from(value: NativeFrameMetadata) -> Self {
584 Self {
585 media_kind: value.media_kind,
586 format: value.format,
587 codec: value.codec,
588 pts_us: value.pts_us,
589 duration_us: value.duration_us,
590 width: value.width,
591 height: value.height,
592 coded_width: value.coded_width,
593 coded_height: value.coded_height,
594 visible_rect: value.visible_rect.map(Into::into),
595 handle_kind: value.handle_kind.into(),
596 pipeline_profile: value.pipeline_profile,
597 color_space: value.color_space,
598 hdr_metadata: value.hdr_metadata,
599 color: value.color,
600 hdr: value.hdr,
601 sync_info: value.sync_info,
602 transform: value.transform,
603 frame_id: value.frame_id,
604 release_tracking: value.release_tracking.map(Into::into),
605 }
606 }
607}
608
609#[derive(Debug, Clone, PartialEq, Eq)]
611pub struct DecoderNativeFrame {
612 pub metadata: DecoderNativeFrameMetadata,
613 pub handle: usize,
614 #[doc(hidden)]
615 pub lease_token: Option<NativeFrameLeaseToken>,
616}
617
618impl From<DecoderNativeFrame> for NativeFrame {
619 fn from(value: DecoderNativeFrame) -> Self {
620 Self {
621 metadata: value.metadata.into(),
622 handle: value.handle,
623 lease_token: value.lease_token,
624 }
625 }
626}
627
628impl From<NativeFrame> for DecoderNativeFrame {
629 fn from(value: NativeFrame) -> Self {
630 Self {
631 metadata: value.metadata.into(),
632 handle: value.handle,
633 lease_token: value.lease_token,
634 }
635 }
636}
637
638#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
640pub struct DecoderReceiveNativeFrameMetadata {
641 pub status: DecoderReceiveFrameStatus,
642 pub frame: Option<DecoderNativeFrameMetadata>,
643}
644
645impl DecoderReceiveNativeFrameMetadata {
646 pub fn frame(frame: DecoderNativeFrameMetadata) -> Self {
647 Self {
648 status: DecoderReceiveFrameStatus::Frame,
649 frame: Some(frame),
650 }
651 }
652
653 pub fn need_more_input() -> Self {
654 Self {
655 status: DecoderReceiveFrameStatus::NeedMoreInput,
656 frame: None,
657 }
658 }
659
660 pub fn eof() -> Self {
661 Self {
662 status: DecoderReceiveFrameStatus::Eof,
663 frame: None,
664 }
665 }
666}
667
668#[allow(
670 clippy::large_enum_variant,
671 reason = "boxing Frame would break the public decoder session API"
672)]
673#[derive(Debug, Clone, PartialEq, Eq)]
674pub enum DecoderReceiveNativeFrameOutput {
675 Frame(DecoderNativeFrame),
676 NeedMoreInput,
677 Eof,
678}
679
680#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
682pub struct DecoderPcmFrameMetadata {
683 pub media_kind: DecoderMediaKind,
684 pub format: DecoderFrameFormat,
685 pub codec: String,
686 pub pts_us: Option<i64>,
687 pub duration_us: Option<i64>,
688 pub sample_rate: u32,
689 pub channels: u16,
690 #[serde(default)]
691 pub channel_layout: Option<String>,
692 pub sample_layout: DecoderPcmSampleLayout,
693 pub frame_count: u32,
694 #[serde(default)]
695 pub discontinuity: bool,
696}
697
698impl DecoderPcmFrameMetadata {
699 pub fn audio(
701 codec: impl Into<String>,
702 format: DecoderFrameFormat,
703 sample_rate: u32,
704 channels: u16,
705 sample_layout: DecoderPcmSampleLayout,
706 frame_count: u32,
707 ) -> Self {
708 Self {
709 media_kind: DecoderMediaKind::Audio,
710 format,
711 codec: codec.into(),
712 pts_us: None,
713 duration_us: None,
714 sample_rate,
715 channels,
716 channel_layout: None,
717 sample_layout,
718 frame_count,
719 discontinuity: false,
720 }
721 }
722
723 pub fn validate(&self) -> Result<usize, DecoderError> {
725 if self.media_kind != DecoderMediaKind::Audio {
726 return Err(DecoderError::InvalidPacket {
727 message: "PCM frame media kind must be audio".to_owned(),
728 });
729 }
730 if self.codec.trim().is_empty() {
731 return Err(DecoderError::InvalidPacket {
732 message: "PCM frame codec must not be empty".to_owned(),
733 });
734 }
735 let bytes_per_sample = match self.format {
736 DecoderFrameFormat::F32 => 4,
737 DecoderFrameFormat::S16 => 2,
738 ref format => {
739 return Err(DecoderError::UnsupportedCapability {
740 capability: format!("pcm-format::{format:?}"),
741 });
742 }
743 };
744 if self.sample_rate == 0 || self.channels == 0 || self.frame_count == 0 {
745 return Err(DecoderError::InvalidPacket {
746 message: "PCM frame sample rate, channels, and frame count must be non-zero"
747 .to_owned(),
748 });
749 }
750 if self.duration_us.is_some_and(|duration| duration < 0) {
751 return Err(DecoderError::InvalidPacket {
752 message: "PCM frame duration must not be negative".to_owned(),
753 });
754 }
755 usize::try_from(self.frame_count)
756 .ok()
757 .and_then(|frames| frames.checked_mul(usize::from(self.channels)))
758 .and_then(|samples| samples.checked_mul(bytes_per_sample))
759 .ok_or_else(|| DecoderError::InvalidPacket {
760 message: "PCM frame payload length overflows host size".to_owned(),
761 })
762 }
763}
764
765#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
767pub struct DecoderPcmFrame {
768 pub metadata: DecoderPcmFrameMetadata,
769 pub data: Vec<u8>,
771}
772
773impl DecoderPcmFrame {
774 pub fn validate(&self) -> Result<(), DecoderError> {
776 let expected_len = self.metadata.validate()?;
777 if self.data.len() != expected_len {
778 return Err(DecoderError::InvalidPacket {
779 message: format!(
780 "PCM frame payload length {} does not match expected {}",
781 self.data.len(),
782 expected_len
783 ),
784 });
785 }
786 Ok(())
787 }
788}
789
790#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
792pub struct DecoderReceivePcmFrameMetadata {
793 pub status: DecoderReceiveFrameStatus,
794 pub frame: Option<DecoderPcmFrameMetadata>,
795}
796
797impl DecoderReceivePcmFrameMetadata {
798 pub fn frame(frame: DecoderPcmFrameMetadata) -> Self {
799 Self {
800 status: DecoderReceiveFrameStatus::Frame,
801 frame: Some(frame),
802 }
803 }
804
805 pub fn need_more_input() -> Self {
806 Self {
807 status: DecoderReceiveFrameStatus::NeedMoreInput,
808 frame: None,
809 }
810 }
811
812 pub fn eof() -> Self {
813 Self {
814 status: DecoderReceiveFrameStatus::Eof,
815 frame: None,
816 }
817 }
818}
819
820#[derive(Debug, Clone, PartialEq, Eq)]
822pub enum DecoderReceivePcmFrameOutput {
823 Frame(DecoderPcmFrame),
824 NeedMoreInput,
825 Eof,
826}
827
828#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
830pub struct DecoderOperationStatus {
831 pub completed: bool,
832}
833
834#[derive(Debug, Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
836pub enum DecoderError {
837 #[error("unsupported codec: {codec}")]
838 UnsupportedCodec { codec: String },
839 #[error("unsupported decoder capability: {capability}")]
840 UnsupportedCapability { capability: String },
841 #[error("decoder payload codec error: {message}")]
842 PayloadCodec { message: String },
843 #[error("decoder ABI violation: {message}")]
844 AbiViolation { message: String },
845 #[error("invalid packet: {message}")]
846 InvalidPacket { message: String },
847 #[error("decoder session is not configured")]
848 NotConfigured,
849 #[error("decoder needs more input")]
850 NeedMoreInput,
851 #[error("decoder reached end of stream")]
852 Eof,
853 #[error("decoder internal error: {message}")]
854 Internal { message: String },
855}
856
857impl DecoderError {
858 pub fn payload_codec(message: impl Into<String>) -> Self {
859 Self::PayloadCodec {
860 message: message.into(),
861 }
862 }
863
864 pub fn abi_violation(message: impl Into<String>) -> Self {
865 Self::AbiViolation {
866 message: message.into(),
867 }
868 }
869
870 pub fn internal(message: impl Into<String>) -> Self {
871 Self::Internal {
872 message: message.into(),
873 }
874 }
875}
876
877pub trait NativeDecoderPluginFactory: Send + Sync {
879 fn name(&self) -> &str;
880
881 fn capabilities(&self) -> DecoderCapabilities;
882
883 fn native_requirements(&self) -> DecoderNativeRequirements {
884 DecoderNativeRequirements::default()
885 }
886
887 fn supports_native_frame_presentation_release(&self) -> bool {
888 self.capabilities().supports_presentation_release
889 }
890
891 fn open_native_session(
892 &self,
893 config: &DecoderSessionConfig,
894 ) -> Result<Box<dyn NativeDecoderSession>, DecoderError>;
895}
896
897pub trait NativeDecoderSession: Send {
899 fn session_info(&self) -> DecoderSessionInfo;
900
901 fn send_packet(
902 &mut self,
903 packet: &DecoderPacket,
904 data: &[u8],
905 ) -> Result<DecoderPacketResult, DecoderError>;
906
907 fn receive_native_frame(&mut self) -> Result<DecoderReceiveNativeFrameOutput, DecoderError>;
908
909 fn receive_pcm_frame(&mut self) -> Result<DecoderReceivePcmFrameOutput, DecoderError> {
910 Err(DecoderError::UnsupportedCapability {
911 capability: "audio-pcm-output".to_owned(),
912 })
913 }
914
915 fn release_native_frame(&mut self, frame: DecoderNativeFrame) -> Result<(), DecoderError>;
916
917 fn release_native_frame_with_presentation(
918 &mut self,
919 _frame: DecoderNativeFrame,
920 _presented: bool,
921 ) -> Result<(), DecoderError> {
922 Err(DecoderError::UnsupportedCapability {
923 capability: "presentation-aware-native-frame-release".to_owned(),
924 })
925 }
926
927 fn flush(&mut self) -> Result<(), DecoderError>;
928
929 fn close(&mut self) -> Result<(), DecoderError>;
930}
931
932#[cfg(test)]
933mod tests {
934 use super::{
935 DecoderBitstreamFormat, DecoderCapabilities, DecoderCodecCapability, DecoderError,
936 DecoderFrameFormat, DecoderMediaKind, DecoderNativeDeviceContext,
937 DecoderNativeDeviceContextKind, DecoderNativeFrame, DecoderNativeFrameMetadata,
938 DecoderNativeFrameReleaseTracking, DecoderNativeHandleKind, DecoderPacket,
939 DecoderPacketResult, DecoderPcmFrame, DecoderPcmFrameMetadata, DecoderPcmSampleLayout,
940 DecoderReceiveFrameStatus, DecoderReceiveNativeFrameOutput, DecoderReceivePcmFrameMetadata,
941 DecoderSessionConfig, DecoderSessionInfo, DecoderVisibleRect, NativeDecoderSession,
942 normalize_decoder_codec_identifier,
943 };
944 use crate::{
945 NativeFrame, NativeFrameColorMetadata, NativeFrameHdrMetadata, NativeFrameMetadata,
946 NativeFramePipelineProfile, NativeFrameSyncInfo, NativeFrameTransform, NativeHandleKind,
947 };
948
949 fn decoder_native_frame() -> DecoderNativeFrame {
950 DecoderNativeFrame {
951 metadata: DecoderNativeFrameMetadata {
952 media_kind: DecoderMediaKind::Video,
953 format: DecoderFrameFormat::Nv12,
954 codec: "hevc".to_owned(),
955 pts_us: Some(125_000),
956 duration_us: Some(41_667),
957 width: 3_840,
958 height: 2_160,
959 coded_width: Some(3_840),
960 coded_height: Some(2_176),
961 visible_rect: Some(DecoderVisibleRect {
962 x: 0,
963 y: 0,
964 width: 3_840,
965 height: 2_160,
966 }),
967 handle_kind: DecoderNativeHandleKind::D3D11Texture2D,
968 pipeline_profile: Some(NativeFramePipelineProfile::D3D11Texture2D),
969 color_space: Some("bt709".to_owned()),
970 hdr_metadata: Some("hdr10".to_owned()),
971 color: Some(NativeFrameColorMetadata {
972 primaries: Some("bt2020".to_owned()),
973 transfer: Some("smpte2084".to_owned()),
974 matrix: Some("bt2020-ncl".to_owned()),
975 range: Some("limited".to_owned()),
976 bit_depth: Some(10),
977 }),
978 hdr: Some(NativeFrameHdrMetadata {
979 kind: "hdr10".to_owned(),
980 mastering_display: None,
981 content_light: None,
982 dolby_vision: None,
983 }),
984 sync_info: Some(NativeFrameSyncInfo {
985 kind: "d3d11_keyed_mutex".to_owned(),
986 handle: None,
987 value: Some(1),
988 }),
989 transform: Some(NativeFrameTransform {
990 rotation_degrees: 0,
991 mirrored_horizontal: false,
992 mirrored_vertical: false,
993 }),
994 frame_id: Some(99),
995 release_tracking: Some(DecoderNativeFrameReleaseTracking {
996 frame_id: Some(99),
997 requires_release: true,
998 }),
999 },
1000 handle: 0xfeed,
1001 lease_token: None,
1002 }
1003 }
1004
1005 #[test]
1006 fn decoder_native_frame_converts_to_shared_native_frame() {
1007 let decoder_frame = decoder_native_frame();
1008 let native_frame = NativeFrame::from(decoder_frame.clone());
1009
1010 assert_eq!(native_frame.handle, decoder_frame.handle);
1011 assert_eq!(
1012 native_frame.metadata.handle_kind,
1013 NativeHandleKind::D3D11Texture2D
1014 );
1015 assert_eq!(
1016 native_frame
1017 .metadata
1018 .visible_rect
1019 .as_ref()
1020 .map(|rect| rect.height),
1021 Some(2_160)
1022 );
1023 assert_eq!(
1024 native_frame
1025 .metadata
1026 .release_tracking
1027 .as_ref()
1028 .map(|tracking| tracking.requires_release),
1029 Some(true)
1030 );
1031 }
1032
1033 #[test]
1034 fn shared_native_frame_converts_back_to_decoder_native_frame() {
1035 let original = decoder_native_frame();
1036 let native_frame = NativeFrame::from(original.clone());
1037 let recovered = DecoderNativeFrame::from(native_frame);
1038
1039 assert_eq!(recovered, original);
1040 }
1041
1042 #[test]
1043 fn native_frame_metadata_converts_to_decoder_metadata() {
1044 let metadata = NativeFrameMetadata::from(decoder_native_frame().metadata);
1045 let decoder_metadata = DecoderNativeFrameMetadata::from(metadata);
1046
1047 assert_eq!(
1048 decoder_metadata.handle_kind,
1049 DecoderNativeHandleKind::D3D11Texture2D
1050 );
1051 assert_eq!(
1052 decoder_metadata.pipeline_profile,
1053 Some(NativeFramePipelineProfile::D3D11Texture2D)
1054 );
1055 assert_eq!(decoder_metadata.color_space.as_deref(), Some("bt709"));
1056 assert_eq!(decoder_metadata.frame_id, Some(99));
1057 assert_eq!(
1058 decoder_metadata
1059 .visible_rect
1060 .as_ref()
1061 .map(|rect| rect.width),
1062 Some(3_840)
1063 );
1064 }
1065
1066 #[test]
1067 fn android_native_handle_kinds_round_trip_between_decoder_and_shared_frames() {
1068 for handle_kind in [
1069 DecoderNativeHandleKind::MediaCodecHardwareBuffer,
1070 DecoderNativeHandleKind::MediaCodecSurfaceTexture,
1071 ] {
1072 let shared = NativeHandleKind::from(handle_kind.clone());
1073 let recovered = DecoderNativeHandleKind::from(shared);
1074
1075 assert_eq!(recovered, handle_kind);
1076 }
1077 }
1078
1079 #[test]
1080 fn android_native_window_device_context_round_trips_json_and_kind() {
1081 let context = DecoderNativeDeviceContext::AndroidNativeWindow { window_ptr: 0xabc };
1082
1083 let encoded = serde_json::to_string(&context).expect("serialize Android native context");
1084 let decoded: DecoderNativeDeviceContext =
1085 serde_json::from_str(&encoded).expect("deserialize Android native context");
1086
1087 assert_eq!(
1088 decoded.kind(),
1089 DecoderNativeDeviceContextKind::AndroidNativeWindow
1090 );
1091 assert_eq!(decoded.android_native_window_ptr(), Some(0xabc));
1092 assert_eq!(decoded.d3d11_device_ptr(), None);
1093 }
1094
1095 #[test]
1096 fn pcm_frame_metadata_pins_media_kind_to_audio_and_round_trips_json() {
1097 let mut metadata = DecoderPcmFrameMetadata::audio(
1098 "aac",
1099 DecoderFrameFormat::F32,
1100 48_000,
1101 2,
1102 DecoderPcmSampleLayout::Planar,
1103 1_024,
1104 );
1105 metadata.pts_us = Some(1_000_000);
1106 metadata.duration_us = Some(21_333);
1107 metadata.channel_layout = Some("stereo".to_owned());
1108 metadata.discontinuity = true;
1109 let frame = DecoderPcmFrame {
1110 metadata,
1111 data: vec![0, 1, 2, 3],
1112 };
1113
1114 let encoded = serde_json::to_vec(&frame).expect("pcm frame json encode");
1115 let decoded: DecoderPcmFrame =
1116 serde_json::from_slice(&encoded).expect("pcm frame json decode");
1117
1118 assert_eq!(decoded.metadata.media_kind, DecoderMediaKind::Audio);
1119 assert_eq!(decoded.metadata.codec, "aac");
1120 assert_eq!(decoded.metadata.format, DecoderFrameFormat::F32);
1121 assert_eq!(
1122 decoded.metadata.sample_layout,
1123 DecoderPcmSampleLayout::Planar
1124 );
1125 assert_eq!(decoded.metadata.frame_count, 1_024);
1126 assert_eq!(decoded.metadata.channel_layout.as_deref(), Some("stereo"));
1127 assert!(decoded.metadata.discontinuity);
1128 assert_eq!(decoded.data, vec![0, 1, 2, 3]);
1129 }
1130
1131 #[test]
1132 fn pcm_receive_metadata_uses_shared_receive_statuses() {
1133 let frame = DecoderPcmFrameMetadata::audio(
1134 "aac",
1135 DecoderFrameFormat::F32,
1136 48_000,
1137 2,
1138 DecoderPcmSampleLayout::Interleaved,
1139 512,
1140 );
1141
1142 assert_eq!(
1143 DecoderReceivePcmFrameMetadata::frame(frame.clone()).status,
1144 DecoderReceiveFrameStatus::Frame
1145 );
1146 assert_eq!(
1147 DecoderReceivePcmFrameMetadata::frame(frame)
1148 .frame
1149 .map(|metadata| metadata.media_kind),
1150 Some(DecoderMediaKind::Audio)
1151 );
1152 assert_eq!(
1153 DecoderReceivePcmFrameMetadata::need_more_input().status,
1154 DecoderReceiveFrameStatus::NeedMoreInput
1155 );
1156 assert_eq!(
1157 DecoderReceivePcmFrameMetadata::eof().status,
1158 DecoderReceiveFrameStatus::Eof
1159 );
1160 }
1161
1162 #[test]
1163 fn pcm_frame_validation_rejects_video_format_and_wrong_payload_length() {
1164 let frame = DecoderPcmFrame {
1165 metadata: DecoderPcmFrameMetadata::audio(
1166 "aac",
1167 DecoderFrameFormat::Nv12,
1168 48_000,
1169 2,
1170 DecoderPcmSampleLayout::Interleaved,
1171 256,
1172 ),
1173 data: vec![0; 4],
1174 };
1175
1176 let error = frame.validate().expect_err("invalid PCM must be rejected");
1177 assert!(matches!(error, DecoderError::UnsupportedCapability { .. }));
1178
1179 let frame = DecoderPcmFrame {
1180 metadata: DecoderPcmFrameMetadata::audio(
1181 "aac",
1182 DecoderFrameFormat::S16,
1183 48_000,
1184 2,
1185 DecoderPcmSampleLayout::Interleaved,
1186 256,
1187 ),
1188 data: vec![0; 4],
1189 };
1190 let error = frame
1191 .validate()
1192 .expect_err("wrong PCM payload must be rejected");
1193 assert!(matches!(error, DecoderError::InvalidPacket { .. }));
1194 }
1195
1196 #[test]
1197 fn decoder_packet_preserves_source_normalizer_media_kind() {
1198 let video = crate::SourceNormalizerPacket {
1199 pts_us: Some(1_000),
1200 dts_us: Some(900),
1201 duration_us: Some(33_333),
1202 stream_index: 0,
1203 media_kind: crate::SourceNormalizerPacketMediaKind::Video,
1204 key_frame: true,
1205 discontinuity: true,
1206 ..crate::SourceNormalizerPacket::default()
1207 };
1208 let video_packet = DecoderPacket::try_from(video).expect("video packet maps");
1209 assert_eq!(video_packet.media_kind, DecoderMediaKind::Video);
1210 assert_eq!(video_packet.stream_index, 0);
1211 assert!(video_packet.key_frame);
1212 assert!(video_packet.discontinuity);
1213
1214 let audio = crate::SourceNormalizerPacket {
1215 pts_us: Some(2_000),
1216 dts_us: Some(2_000),
1217 duration_us: Some(21_333),
1218 stream_index: 1,
1219 media_kind: crate::SourceNormalizerPacketMediaKind::Audio,
1220 sample_rate: Some(48_000),
1221 channels: Some(2),
1222 ..crate::SourceNormalizerPacket::default()
1223 };
1224 let audio_packet = DecoderPacket::try_from(audio).expect("audio packet maps");
1225 assert_eq!(audio_packet.media_kind, DecoderMediaKind::Audio);
1226 assert_eq!(audio_packet.stream_index, 1);
1227 assert_eq!(audio_packet.duration_us, Some(21_333));
1228 }
1229
1230 #[test]
1231 fn decoder_packet_rejects_source_normalizer_subtitle_packet() {
1232 let subtitle = crate::SourceNormalizerPacket {
1233 stream_index: 2,
1234 media_kind: crate::SourceNormalizerPacketMediaKind::Subtitle,
1235 ..crate::SourceNormalizerPacket::default()
1236 };
1237
1238 let error = DecoderPacket::try_from(subtitle)
1239 .expect_err("subtitle packets are not decoder packet input");
1240
1241 assert!(matches!(
1242 error,
1243 DecoderError::UnsupportedCapability { capability }
1244 if capability == "source-normalizer-subtitle-packet"
1245 ));
1246 }
1247
1248 #[test]
1249 fn audio_decoder_session_config_round_trips_pcm_output_preferences() {
1250 let config = DecoderSessionConfig {
1251 codec: "aac".to_owned(),
1252 media_kind: DecoderMediaKind::Audio,
1253 extradata: vec![0x12, 0x10],
1254 bitstream_format: Some(DecoderBitstreamFormat::Unknown("adts".to_owned())),
1255 sample_rate: Some(48_000),
1256 channels: Some(2),
1257 channel_layout: Some("stereo".to_owned()),
1258 target_pcm_format: Some(DecoderFrameFormat::F32),
1259 target_pcm_sample_layout: Some(DecoderPcmSampleLayout::Interleaved),
1260 codec_delay_samples: Some(0),
1261 priming_samples: Some(2_112),
1262 trailing_padding_samples: Some(512),
1263 seek_preroll_samples: Some(1_024),
1264 color: Some(NativeFrameColorMetadata {
1265 primaries: Some("bt709".to_owned()),
1266 transfer: Some("bt709".to_owned()),
1267 matrix: Some("bt709".to_owned()),
1268 range: Some("limited".to_owned()),
1269 bit_depth: Some(8),
1270 }),
1271 hdr: None,
1272 ..DecoderSessionConfig::default()
1273 };
1274
1275 let encoded = serde_json::to_vec(&config).expect("audio config json encode");
1276 let decoded: DecoderSessionConfig =
1277 serde_json::from_slice(&encoded).expect("audio config json decode");
1278
1279 assert_eq!(decoded.media_kind, DecoderMediaKind::Audio);
1280 assert_eq!(decoded.sample_rate, Some(48_000));
1281 assert_eq!(decoded.channels, Some(2));
1282 assert_eq!(decoded.channel_layout.as_deref(), Some("stereo"));
1283 assert_eq!(decoded.target_pcm_format, Some(DecoderFrameFormat::F32));
1284 assert_eq!(
1285 decoded.target_pcm_sample_layout,
1286 Some(DecoderPcmSampleLayout::Interleaved)
1287 );
1288 assert_eq!(decoded.codec_delay_samples, Some(0));
1289 assert_eq!(decoded.priming_samples, Some(2_112));
1290 assert_eq!(decoded.trailing_padding_samples, Some(512));
1291 assert_eq!(decoded.seek_preroll_samples, Some(1_024));
1292 assert_eq!(
1293 decoded.color.as_ref().and_then(|color| color.bit_depth),
1294 Some(8)
1295 );
1296 }
1297
1298 #[test]
1299 fn audio_decoder_session_config_maps_source_normalizer_audio_track() {
1300 let track = crate::SourceNormalizerPacketTrackInfo {
1301 stream_index: 1,
1302 media_kind: crate::SourceNormalizerPacketMediaKind::Audio,
1303 codec: "AAC".to_owned(),
1304 extradata: vec![0x12, 0x10],
1305 bitstream_format: Some(DecoderBitstreamFormat::Unknown("adts".to_owned())),
1306 width: None,
1307 height: None,
1308 coded_width: None,
1309 coded_height: None,
1310 reorder_depth: None,
1311 sample_rate: Some(48_000),
1312 channels: Some(2),
1313 channel_layout: Some("stereo".to_owned()),
1314 codec_delay_samples: Some(0),
1315 priming_samples: Some(2_112),
1316 trailing_padding_samples: Some(512),
1317 seek_preroll_samples: Some(1_024),
1318 color: Some(NativeFrameColorMetadata {
1319 primaries: Some("bt709".to_owned()),
1320 transfer: Some("bt709".to_owned()),
1321 matrix: Some("bt709".to_owned()),
1322 range: Some("limited".to_owned()),
1323 bit_depth: Some(8),
1324 }),
1325 hdr: None,
1326 frame_rate: None,
1327 time_base_num: Some(1),
1328 time_base_den: Some(48_000),
1329 };
1330
1331 let config = DecoderSessionConfig::apple_native_audio_from_source_normalizer_track(&track)
1332 .expect("audio track maps to decoder config");
1333
1334 assert_eq!(config.codec, "AAC");
1335 assert_eq!(config.media_kind, DecoderMediaKind::Audio);
1336 assert_eq!(config.extradata, vec![0x12, 0x10]);
1337 assert_eq!(
1338 config.bitstream_format,
1339 Some(DecoderBitstreamFormat::Unknown("adts".to_owned()))
1340 );
1341 assert_eq!(config.sample_rate, Some(48_000));
1342 assert_eq!(config.channels, Some(2));
1343 assert_eq!(config.channel_layout.as_deref(), Some("stereo"));
1344 assert_eq!(config.target_pcm_format, Some(DecoderFrameFormat::F32));
1345 assert_eq!(
1346 config.target_pcm_sample_layout,
1347 Some(DecoderPcmSampleLayout::Interleaved)
1348 );
1349 assert_eq!(config.codec_delay_samples, Some(0));
1350 assert_eq!(config.priming_samples, Some(2_112));
1351 assert_eq!(config.trailing_padding_samples, Some(512));
1352 assert_eq!(config.seek_preroll_samples, Some(1_024));
1353 assert_eq!(
1354 config.color.as_ref().and_then(|color| color.bit_depth),
1355 Some(8)
1356 );
1357 assert!(config.prefer_hardware);
1358 assert!(config.require_cpu_output);
1359 }
1360
1361 #[test]
1362 fn audio_decoder_session_config_rejects_source_normalizer_video_track() {
1363 let track = crate::SourceNormalizerPacketTrackInfo {
1364 stream_index: 0,
1365 media_kind: crate::SourceNormalizerPacketMediaKind::Video,
1366 codec: "H264".to_owned(),
1367 extradata: Vec::new(),
1368 bitstream_format: Some(DecoderBitstreamFormat::Avcc),
1369 width: Some(1_920),
1370 height: Some(1_080),
1371 coded_width: Some(1_920),
1372 coded_height: Some(1_080),
1373 reorder_depth: None,
1374 sample_rate: None,
1375 channels: None,
1376 channel_layout: None,
1377 codec_delay_samples: None,
1378 priming_samples: None,
1379 trailing_padding_samples: None,
1380 seek_preroll_samples: None,
1381 color: None,
1382 hdr: None,
1383 frame_rate: Some(30.0),
1384 time_base_num: Some(1),
1385 time_base_den: Some(90_000),
1386 };
1387
1388 let error = DecoderSessionConfig::apple_native_audio_from_source_normalizer_track(&track)
1389 .expect_err("video track is not an audio decoder input");
1390
1391 assert!(matches!(
1392 error,
1393 DecoderError::UnsupportedCapability { capability }
1394 if capability == "source-normalizer-audio-track"
1395 ));
1396 }
1397
1398 #[test]
1399 fn native_decoder_session_defaults_pcm_receive_to_capability_error() {
1400 let mut session = PcmUnsupportedDecoderSession;
1401 let error = session
1402 .receive_pcm_frame()
1403 .expect_err("default PCM receive should be unsupported");
1404
1405 assert!(matches!(
1406 error,
1407 DecoderError::UnsupportedCapability { capability }
1408 if capability == "audio-pcm-output"
1409 ));
1410 }
1411
1412 #[test]
1413 fn native_decoder_session_defaults_presentation_release_to_capability_error() {
1414 let mut session = PcmUnsupportedDecoderSession;
1415 let error = session
1416 .release_native_frame_with_presentation(decoder_native_frame(), true)
1417 .expect_err("default presentation release should be unsupported");
1418
1419 assert!(matches!(
1420 error,
1421 DecoderError::UnsupportedCapability { capability }
1422 if capability == "presentation-aware-native-frame-release"
1423 ));
1424 }
1425
1426 #[test]
1427 fn decoder_capabilities_match_mime_wrapped_profile_qualified_sample_entries() {
1428 let capabilities = DecoderCapabilities {
1429 codecs: vec![DecoderCodecCapability {
1430 codec: "AVC1".to_owned(),
1431 media_kind: DecoderMediaKind::Video,
1432 profiles: Vec::new(),
1433 output_formats: Vec::new(),
1434 }],
1435 ..DecoderCapabilities::default()
1436 };
1437
1438 assert!(capabilities.supports_codec("video/avc1.640028", DecoderMediaKind::Video));
1439 assert!(!capabilities.supports_codec("avc1garbage", DecoderMediaKind::Video));
1440 }
1441
1442 #[test]
1443 fn codec_normalization_does_not_truncate_custom_dotted_names() {
1444 assert_eq!(
1445 normalize_decoder_codec_identifier("Fixture.Video.V1"),
1446 "fixture.video.v1"
1447 );
1448 assert_eq!(normalize_decoder_codec_identifier("dvh1.05.06"), "dvh1");
1449 }
1450
1451 struct PcmUnsupportedDecoderSession;
1452
1453 impl NativeDecoderSession for PcmUnsupportedDecoderSession {
1454 fn session_info(&self) -> DecoderSessionInfo {
1455 DecoderSessionInfo::default()
1456 }
1457
1458 fn send_packet(
1459 &mut self,
1460 _packet: &DecoderPacket,
1461 _data: &[u8],
1462 ) -> Result<DecoderPacketResult, DecoderError> {
1463 Ok(DecoderPacketResult::default())
1464 }
1465
1466 fn receive_native_frame(
1467 &mut self,
1468 ) -> Result<DecoderReceiveNativeFrameOutput, DecoderError> {
1469 Ok(DecoderReceiveNativeFrameOutput::NeedMoreInput)
1470 }
1471
1472 fn release_native_frame(&mut self, _frame: DecoderNativeFrame) -> Result<(), DecoderError> {
1473 Ok(())
1474 }
1475
1476 fn flush(&mut self) -> Result<(), DecoderError> {
1477 Ok(())
1478 }
1479
1480 fn close(&mut self) -> Result<(), DecoderError> {
1481 Ok(())
1482 }
1483 }
1484}