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 #[cfg(feature = "audio")]
57 AAC = codec_id!(Audio, AudioCodecID, AAC),
58 #[cfg(feature = "audio")]
59 Opus = codec_id!(Audio, AudioCodecID, Opus),
60 #[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}