media_codec/
codec.rs

1use std::{
2    any::Any,
3    collections::HashMap,
4    num::NonZeroU32,
5    sync::{Arc, LazyLock, RwLock},
6};
7
8#[cfg(feature = "audio")]
9use media_core::audio::{ChannelLayout, SampleFormat};
10#[cfg(feature = "video")]
11use media_core::video::{ChromaLocation, ColorMatrix, ColorPrimaries, ColorRange, ColorTransferCharacteristics, PixelFormat};
12use media_core::{error::Error, variant::Variant, MediaType, Result};
13#[cfg(feature = "video")]
14use num_rational::Rational64;
15
16#[cfg(feature = "audio")]
17#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
18#[repr(u16)]
19enum AudioCodecID {
20    AAC = 1,
21    Opus,
22}
23
24#[cfg(feature = "video")]
25#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
26#[repr(u16)]
27enum VideoCodecID {
28    H264 = 1,
29    HEVC,
30    VP8,
31    VP9,
32    AV1,
33}
34
35macro_rules! codec_id {
36    ($media_type:ident, $id_enum:ident, $id:ident) => {
37        ((MediaType::$media_type as u32) << 16) | ($id_enum::$id as u32)
38    };
39}
40
41#[repr(u32)]
42#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
43pub enum CodecID {
44    // Audio codecs
45    #[cfg(feature = "audio")]
46    AAC  = codec_id!(Audio, AudioCodecID, AAC),
47    #[cfg(feature = "audio")]
48    Opus = codec_id!(Audio, AudioCodecID, Opus),
49    // Video codecs
50    #[cfg(feature = "video")]
51    H264 = codec_id!(Video, VideoCodecID, H264),
52    #[cfg(feature = "video")]
53    HEVC = codec_id!(Video, VideoCodecID, HEVC),
54    #[cfg(feature = "video")]
55    VP8  = codec_id!(Video, VideoCodecID, VP8),
56    #[cfg(feature = "video")]
57    VP9  = codec_id!(Video, VideoCodecID, VP9),
58    #[cfg(feature = "video")]
59    AV1  = codec_id!(Video, VideoCodecID, AV1),
60}
61
62impl CodecID {
63    pub fn media_type(&self) -> MediaType {
64        match ((*self as u32) >> 16) as u16 {
65            #[cfg(feature = "audio")]
66            x if x == MediaType::Audio as u16 => MediaType::Audio,
67            #[cfg(feature = "video")]
68            x if x == MediaType::Video as u16 => MediaType::Video,
69            _ => unreachable!(),
70        }
71    }
72}
73
74#[derive(Clone, Copy, Debug, Eq, PartialEq)]
75pub enum CodecType {
76    Decoder,
77    Encoder,
78}
79
80pub trait CodecParameters: Clone + Send + Sync + 'static {
81    fn media_type() -> MediaType;
82    fn codec_type() -> CodecType;
83}
84
85pub trait CodecConfiguration: Clone + Send + Sync + 'static {
86    type Parameters: CodecParameters;
87
88    fn media_type() -> MediaType {
89        Self::Parameters::media_type()
90    }
91    fn codec_type() -> CodecType {
92        Self::Parameters::codec_type()
93    }
94    fn from_parameters(parameters: &Self::Parameters) -> Result<Self>;
95    fn configure(&mut self, parameters: &Self::Parameters) -> Result<()>;
96    fn configure_with_option(&mut self, key: &str, value: &Variant) -> Result<()>;
97}
98
99#[cfg(feature = "audio")]
100#[derive(Clone, Debug, Default)]
101pub struct AudioParameters {
102    pub format: Option<SampleFormat>,
103    pub samples: Option<NonZeroU32>,
104    pub sample_rate: Option<NonZeroU32>,
105    pub channel_layout: Option<ChannelLayout>,
106}
107
108#[cfg(feature = "audio")]
109impl AudioParameters {
110    pub(crate) fn update(&mut self, other: &AudioParameters) {
111        self.format = other.format.or(self.format);
112        self.samples = other.samples.or(self.samples);
113        self.sample_rate = other.sample_rate.or(self.sample_rate);
114        if other.channel_layout.is_some() {
115            self.channel_layout = other.channel_layout.clone();
116        }
117    }
118
119    pub(crate) fn update_with_option(&mut self, key: &str, value: &Variant) {
120        match key {
121            "sample_format" => self.format = value.get_uint32().and_then(|fmt| SampleFormat::try_from(fmt as usize).ok()),
122            "samples" => self.samples = value.get_uint32().and_then(NonZeroU32::new),
123            "sample_rate" => self.sample_rate = value.get_uint32().and_then(NonZeroU32::new),
124            "channels" => self.channel_layout = value.get_uint8().and_then(|c| ChannelLayout::default_from_channels(c).ok()),
125            _ => {}
126        }
127    }
128}
129
130#[cfg(feature = "video")]
131#[derive(Clone, Debug, Default)]
132pub struct VideoParameters {
133    pub format: Option<PixelFormat>,
134    pub width: Option<NonZeroU32>,
135    pub height: Option<NonZeroU32>,
136    pub color_range: Option<ColorRange>,
137    pub color_matrix: Option<ColorMatrix>,
138    pub color_primaries: Option<ColorPrimaries>,
139    pub color_transfer_characteristics: Option<ColorTransferCharacteristics>,
140    pub chroma_location: Option<ChromaLocation>,
141    pub frame_rate: Option<Rational64>,
142}
143
144#[cfg(feature = "video")]
145impl VideoParameters {
146    pub(crate) fn update(&mut self, other: &VideoParameters) {
147        self.format = other.format.or(self.format);
148        self.width = other.width.or(self.width);
149        self.height = other.height.or(self.height);
150        self.color_range = other.color_range.or(self.color_range);
151        self.color_matrix = other.color_matrix.or(self.color_matrix);
152        self.color_primaries = other.color_primaries.or(self.color_primaries);
153        self.color_transfer_characteristics = other.color_transfer_characteristics.or(self.color_transfer_characteristics);
154        self.chroma_location = other.chroma_location.or(self.chroma_location);
155        self.frame_rate = other.frame_rate.or(self.frame_rate);
156    }
157
158    pub(crate) fn update_with_option(&mut self, key: &str, value: &Variant) {
159        match key {
160            "pixel_format" => self.format = value.get_uint32().and_then(|f| PixelFormat::try_from(f as usize).ok()),
161            "width" => self.width = value.get_uint32().and_then(NonZeroU32::new),
162            "height" => self.height = value.get_uint32().and_then(NonZeroU32::new),
163            "color_range" => self.color_range = value.get_uint32().map(|v| ColorRange::from(v as usize)),
164            "color_matrix" => self.color_matrix = value.get_uint32().and_then(|v| ColorMatrix::try_from(v as usize).ok()),
165            "color_primaries" => self.color_primaries = value.get_uint32().and_then(|v| ColorPrimaries::try_from(v as usize).ok()),
166            "color_transfer_characteristics" => {
167                self.color_transfer_characteristics = value.get_uint32().and_then(|v| ColorTransferCharacteristics::try_from(v as usize).ok())
168            }
169            "chroma_location" => self.chroma_location = value.get_uint32().map(|v| ChromaLocation::from(v as usize)),
170            _ => {}
171        }
172    }
173}
174
175pub trait CodecInfomation {
176    fn id(&self) -> CodecID;
177    fn name(&self) -> &'static str;
178}
179
180pub trait Codec<T: CodecConfiguration>: CodecInfomation {
181    fn configure(&mut self, parameters: Option<&T::Parameters>, options: Option<&Variant>) -> Result<()>;
182    fn set_option(&mut self, key: &str, value: &Variant) -> Result<()>;
183}
184
185pub trait CodecBuilder<T: CodecConfiguration>: Any + Send + Sync {
186    fn id(&self) -> CodecID;
187    fn name(&self) -> &'static str;
188}
189
190pub(crate) struct CodecList<T: CodecConfiguration> {
191    pub(crate) codecs: HashMap<CodecID, Vec<Arc<dyn CodecBuilder<T>>>>,
192}
193
194pub(crate) type LazyCodecList<T> = LazyLock<RwLock<CodecList<T>>>;
195
196pub(crate) fn register_codec<T>(codec_list: &LazyCodecList<T>, builder: Arc<dyn CodecBuilder<T>>, default: bool) -> Result<()>
197where
198    T: CodecConfiguration,
199{
200    let mut codec_list = codec_list.write().map_err(|err| Error::Invalid(err.to_string()))?;
201    let entry = codec_list.codecs.entry(builder.id()).or_default();
202
203    if default {
204        entry.insert(0, builder);
205    } else {
206        entry.push(builder);
207    }
208
209    Ok(())
210}
211
212pub(crate) fn find_codec<T>(codec_list: &LazyCodecList<T>, id: CodecID) -> Result<Arc<dyn CodecBuilder<T>>>
213where
214    T: CodecConfiguration,
215{
216    let codec_list = codec_list.read().map_err(|err| Error::Invalid(err.to_string()))?;
217
218    if let Some(builders) = codec_list.codecs.get(&id) {
219        if let Some(builder) = builders.first() {
220            return Ok(builder.clone());
221        }
222    }
223
224    Err(Error::NotFound(format!("codec: {:?}", id)))
225}
226
227pub(crate) fn find_codec_by_name<T>(codec_list: &LazyCodecList<T>, name: &str) -> Result<Arc<dyn CodecBuilder<T>>>
228where
229    T: CodecConfiguration,
230{
231    let codec_list = codec_list.read().map_err(|err| Error::Invalid(err.to_string()))?;
232
233    for builders in codec_list.codecs.values() {
234        for builder in builders {
235            if builder.name() == name {
236                return Ok(builder.clone());
237            }
238        }
239    }
240
241    Err(Error::NotFound(format!("codec: {}", name)))
242}