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        if other.format.is_some() {
112            self.format = other.format;
113        }
114        if other.samples.is_some() {
115            self.samples = other.samples;
116        }
117        if other.sample_rate.is_some() {
118            self.sample_rate = other.sample_rate;
119        }
120        if other.channel_layout.is_some() {
121            self.channel_layout = other.channel_layout.clone();
122        }
123    }
124
125    pub(crate) fn update_with_option(&mut self, key: &str, value: &Variant) {
126        match key {
127            "sample_format" => self.format = value.get_uint32().and_then(|fmt| SampleFormat::try_from(fmt as usize).ok()),
128            "samples" => self.samples = value.get_uint32().and_then(NonZeroU32::new),
129            "sample_rate" => self.sample_rate = value.get_uint32().and_then(NonZeroU32::new),
130            "channels" => self.channel_layout = value.get_uint8().and_then(|c| ChannelLayout::default_from_channels(c).ok()),
131            _ => {}
132        }
133    }
134}
135
136#[cfg(feature = "video")]
137#[derive(Clone, Debug, Default)]
138pub struct VideoParameters {
139    pub format: Option<PixelFormat>,
140    pub width: Option<NonZeroU32>,
141    pub height: Option<NonZeroU32>,
142    pub color_range: Option<ColorRange>,
143    pub color_matrix: Option<ColorMatrix>,
144    pub color_primaries: Option<ColorPrimaries>,
145    pub color_transfer_characteristics: Option<ColorTransferCharacteristics>,
146    pub chroma_location: Option<ChromaLocation>,
147    pub frame_rate: Option<Rational64>,
148}
149
150#[cfg(feature = "video")]
151impl VideoParameters {
152    pub(crate) fn update(&mut self, other: &VideoParameters) {
153        if other.format.is_some() {
154            self.format = other.format;
155        }
156        if other.width.is_some() {
157            self.width = other.width;
158        }
159        if other.height.is_some() {
160            self.height = other.height;
161        }
162        if other.color_range.is_some() {
163            self.color_range = other.color_range;
164        }
165        if other.color_matrix.is_some() {
166            self.color_matrix = other.color_matrix;
167        }
168        if other.color_primaries.is_some() {
169            self.color_primaries = other.color_primaries;
170        }
171        if other.color_transfer_characteristics.is_some() {
172            self.color_transfer_characteristics = other.color_transfer_characteristics;
173        }
174        if other.chroma_location.is_some() {
175            self.chroma_location = other.chroma_location;
176        }
177        if other.frame_rate.is_some() {
178            self.frame_rate = other.frame_rate;
179        }
180    }
181
182    pub(crate) fn update_with_option(&mut self, key: &str, value: &Variant) {
183        match key {
184            "pixel_format" => self.format = value.get_uint32().and_then(|f| PixelFormat::try_from(f as usize).ok()),
185            "width" => self.width = value.get_uint32().and_then(NonZeroU32::new),
186            "height" => self.height = value.get_uint32().and_then(NonZeroU32::new),
187            "color_range" => self.color_range = value.get_uint32().map(|v| ColorRange::from(v as usize)),
188            "color_matrix" => self.color_matrix = value.get_uint32().and_then(|v| ColorMatrix::try_from(v as usize).ok()),
189            "color_primaries" => self.color_primaries = value.get_uint32().and_then(|v| ColorPrimaries::try_from(v as usize).ok()),
190            "color_transfer_characteristics" => {
191                self.color_transfer_characteristics = value.get_uint32().and_then(|v| ColorTransferCharacteristics::try_from(v as usize).ok())
192            }
193            "chroma_location" => self.chroma_location = value.get_uint32().map(|v| ChromaLocation::from(v as usize)),
194            _ => {}
195        }
196    }
197}
198
199pub trait CodecInfomation {
200    fn id(&self) -> CodecID;
201    fn name(&self) -> &'static str;
202}
203
204pub trait Codec<T: CodecConfiguration>: CodecInfomation {
205    fn configure(&mut self, parameters: Option<&T::Parameters>, options: Option<&Variant>) -> Result<()>;
206    fn set_option(&mut self, key: &str, value: &Variant) -> Result<()>;
207}
208
209pub trait CodecBuilder<T: CodecConfiguration>: Any + Send + Sync {
210    fn id(&self) -> CodecID;
211    fn name(&self) -> &'static str;
212}
213
214pub(crate) struct CodecList<T: CodecConfiguration> {
215    pub(crate) codecs: HashMap<CodecID, Vec<Arc<dyn CodecBuilder<T>>>>,
216}
217
218pub(crate) type LazyCodecList<T> = LazyLock<RwLock<CodecList<T>>>;
219
220pub(crate) fn register_codec<T>(codec_list: &LazyCodecList<T>, builder: Arc<dyn CodecBuilder<T>>, default: bool) -> Result<()>
221where
222    T: CodecConfiguration,
223{
224    let mut codec_list = codec_list.write().map_err(|err| Error::Invalid(err.to_string()))?;
225    let entry = codec_list.codecs.entry(builder.id()).or_default();
226
227    if default {
228        entry.insert(0, builder);
229    } else {
230        entry.push(builder);
231    }
232
233    Ok(())
234}
235
236pub(crate) fn find_codec<T>(codec_list: &LazyCodecList<T>, id: CodecID) -> Result<Arc<dyn CodecBuilder<T>>>
237where
238    T: CodecConfiguration,
239{
240    let codec_list = codec_list.read().map_err(|err| Error::Invalid(err.to_string()))?;
241
242    if let Some(builders) = codec_list.codecs.get(&id) {
243        if let Some(builder) = builders.first() {
244            return Ok(builder.clone());
245        }
246    }
247
248    Err(Error::NotFound(format!("codec: {:?}", id)))
249}
250
251pub(crate) fn find_codec_by_name<T>(codec_list: &LazyCodecList<T>, name: &str) -> Result<Arc<dyn CodecBuilder<T>>>
252where
253    T: CodecConfiguration,
254{
255    let codec_list = codec_list.read().map_err(|err| Error::Invalid(err.to_string()))?;
256
257    for builders in codec_list.codecs.values() {
258        for builder in builders {
259            if builder.name() == name {
260                return Ok(builder.clone());
261            }
262        }
263    }
264
265    Err(Error::NotFound(format!("codec: {}", name)))
266}