media_codec/
codec.rs

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