1#![forbid(unsafe_code)]
11
12#[cfg(feature = "demux")]
13use crate::Demux;
14#[cfg(feature = "mux")]
15use crate::{Mux, MuxOpen};
16#[cfg(feature = "demux")]
17use ebml_webm::Demuxer as CoreDemuxer;
18use ebml_webm::TrackInfo as CoreTrackInfo;
19pub use ebml_webm::{CuePoint, SeekEntry};
20#[cfg(feature = "mux")]
21use ebml_webm::{Muxer as CoreMuxer, mux::Live as CoreLive, mux::Open as CoreOpen};
22use mediaway_common::{
23 Bytes, CodecKind, Packet, Rational as MwRational, StreamInfo, VideoGeometry,
24};
25
26#[cfg(feature = "demux")]
28#[derive(Debug, Default)]
29pub struct Demuxer {
30 inner: CoreDemuxer,
31 streams: Vec<StreamInfo>,
32}
33
34#[cfg(feature = "demux")]
35impl Demuxer {
36 #[must_use]
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn push_bytes(&mut self, chunk: &[u8]) {
44 self.inner.push_bytes(chunk);
45 self.sync_streams();
46 }
47
48 #[must_use]
51 pub fn streams(&self) -> &[StreamInfo] {
52 &self.streams
53 }
54
55 #[must_use]
61 pub fn cues(&self) -> &[CuePoint] {
62 self.inner.cues()
63 }
64
65 #[must_use]
68 pub fn seek_head(&self) -> &[SeekEntry] {
69 self.inner.seek_head()
70 }
71
72 pub fn poll_packet(&mut self) -> Option<Packet> {
76 loop {
77 let frame = self.inner.poll_frame()?;
78 let id = track_id(frame.track_number);
79 if !self.streams.iter().any(|s| s.id() == id) {
80 continue;
81 }
82 return Some(Packet {
83 stream_id: id,
84 pts: frame.timecode,
85 dts: frame.timecode,
88 duration: frame.duration_ticks.unwrap_or(0),
92 is_keyframe: frame.is_keyframe,
93 is_discard: false,
94 payload: frame.payload,
95 });
96 }
97 }
98
99 fn sync_streams(&mut self) {
100 let tb = self.inner.time_base();
101 let time_base = MwRational::new(tb.num, tb.den);
102 self.streams = self
103 .inner
104 .streams()
105 .iter()
106 .filter_map(|t| to_stream_info(t, time_base))
107 .collect();
108 }
109}
110
111#[cfg(feature = "demux")]
112#[allow(clippy::use_self)]
113impl Demux for Demuxer {
114 fn push_bytes(&mut self, chunk: &[u8]) {
115 Demuxer::push_bytes(self, chunk);
116 }
117
118 fn streams(&self) -> &[StreamInfo] {
119 Demuxer::streams(self)
120 }
121
122 fn poll_packet(&mut self) -> Option<Packet> {
123 Demuxer::poll_packet(self)
124 }
125}
126
127fn track_id(track_number: u64) -> u32 {
133 u32::try_from(track_number).unwrap_or(u32::MAX)
134}
135
136#[cfg(feature = "demux")]
137fn to_stream_info(t: &CoreTrackInfo, time_base: MwRational) -> Option<StreamInfo> {
138 let codec = codec_kind(&t.codec_id)?;
139 let id = track_id(t.track_number);
140 if t.is_video() {
141 Some(StreamInfo::Video {
142 id,
143 codec,
144 time_base,
145 geometry: VideoGeometry {
146 width: t.width,
147 height: t.height,
148 },
149 extra_data: t.codec_private.clone().unwrap_or_default(),
150 })
151 } else {
152 Some(StreamInfo::Audio {
153 id,
154 codec,
155 time_base,
156 extra_data: Bytes::new(),
157 sample_rate: sample_rate_u32(t.sample_rate),
158 channels: channels_u16(t.channels),
159 })
160 }
161}
162
163#[cfg(feature = "demux")]
168#[allow(
169 clippy::cast_possible_truncation,
170 clippy::cast_sign_loss,
171 reason = "saturating conversion from EBML Float sample rate to Hz"
172)]
173fn sample_rate_u32(sample_rate: f64) -> u32 {
174 if sample_rate.is_finite() {
175 sample_rate.clamp(0.0, f64::from(u32::MAX)) as u32
176 } else {
177 0
178 }
179}
180
181#[cfg(feature = "demux")]
182#[allow(
183 clippy::cast_possible_truncation,
184 reason = "saturating conversion from EBML uint channel count to StreamInfo's u16"
185)]
186const fn channels_u16(channels: u32) -> u16 {
187 if channels > 0xFFFF {
188 u16::MAX
189 } else {
190 channels as u16
191 }
192}
193
194#[cfg(feature = "demux")]
197fn codec_kind(codec_id: &str) -> Option<CodecKind> {
198 match codec_id {
199 "V_VP8" => Some(CodecKind::Vp8),
200 "V_VP9" => Some(CodecKind::Vp9),
201 "V_AV1" => Some(CodecKind::Av1),
202 "A_OPUS" => Some(CodecKind::Opus),
203 "A_VORBIS" => Some(CodecKind::Vorbis),
204 id if id.starts_with("A_AAC") => Some(CodecKind::Aac),
205 _ => None,
206 }
207}
208
209#[cfg(feature = "mux")]
217const fn webm_codec_id(codec: CodecKind) -> Option<&'static str> {
218 match codec {
219 CodecKind::Vp8 => Some("V_VP8"),
220 CodecKind::Vp9 => Some("V_VP9"),
221 CodecKind::Av1 => Some("V_AV1"),
222 CodecKind::Opus => Some("A_OPUS"),
223 CodecKind::Vorbis => Some("A_VORBIS"),
224 CodecKind::Aac => Some("A_AAC"),
225 _ => None,
226 }
227}
228
229#[cfg(feature = "mux")]
231#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
232#[non_exhaustive]
233pub enum Error {
234 #[error(transparent)]
236 Mux(#[from] ebml_webm::MuxError),
237 #[error("WebM mux has no CodecID mapping for {0:?}")]
240 UnsupportedCodec(CodecKind),
241 #[error("push_packet references unregistered stream_id {0}")]
243 UnknownStream(u32),
244}
245
246#[cfg(feature = "mux")]
248#[derive(Debug, Clone, Copy, Default)]
249pub struct Open;
250
251#[cfg(feature = "mux")]
253#[derive(Debug, Clone, Copy, Default)]
254pub struct Live;
255
256#[cfg(feature = "mux")]
258#[derive(Debug)]
259pub struct Muxer<S = Open> {
260 open: Option<CoreMuxer<CoreOpen>>,
261 live: Option<CoreMuxer<CoreLive>>,
262 tracks_cache: Vec<StreamInfo>,
263 _state: core::marker::PhantomData<S>,
264}
265
266#[cfg(feature = "mux")]
267impl Muxer<Open> {
268 #[must_use]
270 pub fn new() -> Self {
271 Self {
272 open: Some(CoreMuxer::new()),
273 live: None,
274 tracks_cache: Vec::new(),
275 _state: core::marker::PhantomData,
276 }
277 }
278
279 #[must_use]
281 pub fn tracks(&self) -> &[StreamInfo] {
282 &self.tracks_cache
283 }
284
285 pub fn add_track(&mut self, track: StreamInfo) -> Result<u32, Error> {
298 fn track_extra(track: &StreamInfo) -> Option<Bytes> {
301 match track {
302 StreamInfo::Video { extra_data, .. } | StreamInfo::Audio { extra_data, .. }
303 if !extra_data.is_empty() =>
304 {
305 Some(extra_data.clone())
306 }
307 _ => None,
308 }
309 }
310 let codec_id =
311 webm_codec_id(track.codec()).ok_or_else(|| Error::UnsupportedCodec(track.codec()))?;
312 let id = track.id();
313 let core_track = match &track {
314 StreamInfo::Video {
315 geometry,
316 extra_data,
317 ..
318 } => CoreTrackInfo {
319 track_number: u64::from(id),
320 track_type: 1,
321 codec_id: codec_id.to_string(),
322 codec_private: (!extra_data.is_empty()).then(|| extra_data.clone()),
323 width: geometry.width,
324 height: geometry.height,
325 sample_rate: 8000.0,
326 channels: 1,
327 },
328 StreamInfo::Audio {
329 sample_rate,
330 channels,
331 ..
332 } => CoreTrackInfo {
333 track_number: u64::from(id),
334 track_type: 2,
335 codec_id: codec_id.to_string(),
336 codec_private: track_extra(&track),
337 width: 0,
338 height: 0,
339 sample_rate: f64::from(*sample_rate),
340 channels: u32::from(*channels),
341 },
342 _ => return Err(Error::UnsupportedCodec(track.codec())),
345 };
346 let open = self.open.as_mut().ok_or(Error::UnknownStream(id))?;
347 open.add_track(core_track)?;
348 self.tracks_cache.push(track);
349 Ok(id)
350 }
351
352 #[must_use]
354 pub fn begin(mut self) -> Muxer<Live> {
355 let open = self.open.take().unwrap_or_default();
356 Muxer {
357 open: None,
358 live: Some(open.begin()),
359 tracks_cache: self.tracks_cache,
360 _state: core::marker::PhantomData,
361 }
362 }
363}
364
365#[cfg(feature = "mux")]
366impl Default for Muxer<Open> {
367 fn default() -> Self {
368 Self::new()
369 }
370}
371
372#[cfg(feature = "mux")]
373impl Muxer<Live> {
374 #[must_use]
376 pub fn tracks(&self) -> &[StreamInfo] {
377 &self.tracks_cache
378 }
379
380 pub fn push_packet(&mut self, packet: &Packet) -> Result<(), Error> {
387 if !self.tracks_cache.iter().any(|t| t.id() == packet.stream_id) {
388 return Err(Error::UnknownStream(packet.stream_id));
389 }
390 let live = self
391 .live
392 .as_mut()
393 .ok_or(Error::UnknownStream(packet.stream_id))?;
394 live.push_frame(
395 u64::from(packet.stream_id),
396 packet.pts,
397 packet.is_keyframe,
398 &packet.payload,
399 )?;
400 Ok(())
401 }
402
403 pub fn flush(&mut self) {
405 if let Some(live) = self.live.as_mut() {
406 live.flush();
407 }
408 }
409
410 pub fn poll_bytes(&mut self, out: &mut Vec<u8>) -> usize {
412 self.live.as_mut().map_or(0, |m| m.poll_bytes(out))
413 }
414}
415
416#[cfg(feature = "mux")]
417#[allow(clippy::use_self)]
418impl Mux for Muxer<Live> {
419 type Error = Error;
420
421 fn push_packet(&mut self, packet: &Packet) -> Result<(), Self::Error> {
422 Muxer::<Live>::push_packet(self, packet)
423 }
424
425 fn flush(&mut self) {
426 Muxer::<Live>::flush(self);
427 }
428
429 fn poll_bytes(&mut self, out: &mut Vec<u8>) -> usize {
430 Muxer::<Live>::poll_bytes(self, out)
431 }
432
433 }
437
438#[cfg(feature = "mux")]
439#[allow(clippy::use_self)]
440impl MuxOpen for Muxer<Open> {
441 type Live = Muxer<Live>;
442 type Error = Error;
443
444 const FIRST_TRACK_ID: u32 = 1;
446
447 fn add_track(&mut self, track: StreamInfo) -> Result<u32, Self::Error> {
448 Muxer::<Open>::add_track(self, track)
449 }
450
451 fn begin(self) -> Self::Live {
452 Muxer::<Open>::begin(self)
453 }
454}
455
456#[cfg(test)]
457#[path = "webm_tests.rs"]
458mod tests;