Skip to main content

player_plugin/
decoder.rs

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/// Media kind handled by a decoder plugin.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
13pub enum DecoderMediaKind {
14    #[default]
15    Video,
16    Audio,
17}
18
19/// Decoded frame formats advertised by decoder plugins.
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub enum DecoderFrameFormat {
22    Rgba8888,
23    Bgra8888,
24    Yuv420p,
25    Nv12,
26    /// 10-bit 4:2:0 bi-planar YUV, commonly exposed as P010.
27    P010,
28    /// IEEE 754 binary32 PCM samples, encoded little-endian in `DecoderPcmFrame::data`.
29    F32,
30    /// Signed 16-bit PCM samples, encoded little-endian in `DecoderPcmFrame::data`.
31    S16,
32    Unknown(String),
33}
34
35/// PCM sample layout returned by audio decoder plugins.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
37pub enum DecoderPcmSampleLayout {
38    #[default]
39    Interleaved,
40    Planar,
41}
42
43/// Describes one codec a decoder plugin can open.
44#[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/// Decoder plugin capability payload returned through the dynamic ABI.
53#[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    /// Supports decoded audio frames in plugin-managed audio sessions.
59    pub supports_audio_frames: bool,
60    /// Supports decoded PCM frame output through `receive_pcm_frame`.
61    #[serde(default)]
62    pub supports_pcm_frames: bool,
63    pub supports_gpu_handles: bool,
64    /// Supports release calls that distinguish presented frames from discarded frames.
65    #[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    /// Returns whether this plugin advertises support for a codec/media pair.
74    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
83/// Normalizes MIME-wrapped and profile-qualified decoder codec identifiers.
84///
85/// Profile suffix removal is intentionally limited to standardized sample
86/// entry identifiers. Custom codec names retain dots so unrelated identities
87/// cannot collapse onto the same capability.
88pub 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/// Requirements a host session needs from a decoder plugin.
108#[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    /// Builds video native-frame requirements for an output handle/profile pair.
126    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    /// Returns missing capability names for this requirement.
141    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/// Configuration used to open a decoder session.
182#[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    /// Platform decoder implementation selected by a trusted host capability probe.
215    #[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    /// Builds an audio decoder session config from a SourceNormalizer audio track.
228    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    /// Builds the default Apple PCM output preference for native audio.
261    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/// Optional session metadata returned by a plugin after opening a decoder.
273#[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/// Compressed packet metadata passed to `NativeDecoderSession::send_packet`.
281#[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/// Result returned after sending one compressed packet.
322#[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/// Receive state encoded in frame metadata over the C ABI.
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
335pub enum DecoderReceiveFrameStatus {
336    Frame,
337    NeedMoreInput,
338    Eof,
339}
340
341/// Native frame handle kinds returned by the decoder plugin ABI.
342#[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/// Native graphics device/context kinds that a host may share with a decoder plugin.
394#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
395pub enum DecoderNativeDeviceContextKind {
396    D3D11Device,
397    AndroidNativeWindow,
398    Unknown(String),
399}
400
401/// Compressed video bitstream representation expected by a native decoder.
402#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
403pub enum DecoderBitstreamFormat {
404    AnnexB,
405    Avcc,
406    Hvcc,
407    Unknown(String),
408}
409
410/// Borrowed native device/context pointer passed from host to decoder plugin.
411#[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/// Native-frame decoder requirements advertised through the plugin ABI.
452#[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/// Visible content rectangle within a coded native frame.
463#[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/// Release tracking diagnostics attached to a native frame.
494#[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/// Metadata for a decoded native frame. The native handle is transferred separately.
519#[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/// A decoded native frame returned by the Rust-side decoder session trait.
610#[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/// Metadata returned by the dynamic native-frame receive call.
639#[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/// Rust-side receive result returned by native decoder sessions.
669#[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/// Metadata for a decoded PCM audio frame.
681#[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    /// Creates PCM metadata and pins `media_kind` to audio.
700    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    /// Validates the metadata before it crosses the decoder/session boundary.
724    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/// A decoded PCM audio frame returned by an audio decoder session.
766#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
767pub struct DecoderPcmFrame {
768    pub metadata: DecoderPcmFrameMetadata,
769    /// PCM sample bytes in the declared layout. F32 and S16 samples are always little-endian.
770    pub data: Vec<u8>,
771}
772
773impl DecoderPcmFrame {
774    /// Validates metadata and the owned PCM payload length.
775    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/// Receive state encoded in PCM frame metadata over the future audio decoder ABI.
791#[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/// Rust-side receive result returned by audio decoder sessions.
821#[derive(Debug, Clone, PartialEq, Eq)]
822pub enum DecoderReceivePcmFrameOutput {
823    Frame(DecoderPcmFrame),
824    NeedMoreInput,
825    Eof,
826}
827
828/// Empty success payload used by flush/close operations.
829#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
830pub struct DecoderOperationStatus {
831    pub completed: bool,
832}
833
834/// Error payload shared by decoder plugins and host-side adapters.
835#[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
877/// Creates native-frame decoder sessions for one plugin.
878pub 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
897/// Stateful native-frame decoder session created by a decoder plugin factory.
898pub 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}