diff --git a/mux/fmp4/examples/hls_vod.rs b/mux/fmp4/examples/hls_vod.rs index c84102f48..55b064977 100644 --- a/mux/fmp4/examples/hls_vod.rs +++ b/mux/fmp4/examples/hls_vod.rs @@ -360,6 +360,10 @@ impl AudioStream { .property("samplesperbuffer", 4410) .property_from_str("wave", &self.wave) .build()?; + let taginject = gst::ElementFactory::make("taginject") + .property_from_str("tags", &format!("language-code={}", self.lang)) + .property_from_str("scope", "stream") + .build()?; let raw_capsfilter = gst::ElementFactory::make("capsfilter") .property( "caps", @@ -374,9 +378,23 @@ impl AudioStream { .build()?; let appsink = gst_app::AppSink::builder().buffer_list(true).build(); - pipeline.add_many([&src, &raw_capsfilter, &enc, &mux, appsink.upcast_ref()])?; + pipeline.add_many([ + &src, + &taginject, + &raw_capsfilter, + &enc, + &mux, + appsink.upcast_ref(), + ])?; - gst::Element::link_many([&src, &raw_capsfilter, &enc, &mux, appsink.upcast_ref()])?; + gst::Element::link_many([ + &src, + &taginject, + &raw_capsfilter, + &enc, + &mux, + appsink.upcast_ref(), + ])?; probe_encoder(state, enc); @@ -416,7 +434,7 @@ fn main() -> Result<(), Error> { }, AudioStream { name: "audio_1".to_string(), - lang: "fre".to_string(), + lang: "fra".to_string(), default: false, wave: "white-noise".to_string(), }, diff --git a/mux/fmp4/src/fmp4mux/boxes.rs b/mux/fmp4/src/fmp4mux/boxes.rs index e2fdeae74..cf0de8147 100644 --- a/mux/fmp4/src/fmp4mux/boxes.rs +++ b/mux/fmp4/src/fmp4mux/boxes.rs @@ -700,7 +700,6 @@ fn write_tref( fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 { let lang = lang.borrow(); - // TODO: Need to relax this once we get the language code from tags assert!(lang.iter().all(u8::is_ascii_lowercase)); (((lang[0] as u16 - 0x60) & 0x1F) << 10) @@ -710,7 +709,7 @@ fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 { fn write_mdhd( v: &mut Vec, - _cfg: &super::HeaderConfiguration, + cfg: &super::HeaderConfiguration, stream: &super::HeaderStream, creation_time: u64, ) -> Result<(), Error> { @@ -724,8 +723,11 @@ fn write_mdhd( v.extend(0u64.to_be_bytes()); // Language as ISO-639-2/T - // TODO: get actual language from the tags - v.extend(language_code(b"und").to_be_bytes()); + if let Some(lang) = cfg.language_code { + v.extend(language_code(lang).to_be_bytes()); + } else { + v.extend(language_code(b"und").to_be_bytes()); + } // Pre-defined v.extend([0u8; 2]); diff --git a/mux/fmp4/src/fmp4mux/imp.rs b/mux/fmp4/src/fmp4mux/imp.rs index 501a482ed..73d3cc460 100644 --- a/mux/fmp4/src/fmp4mux/imp.rs +++ b/mux/fmp4/src/fmp4mux/imp.rs @@ -250,6 +250,8 @@ struct State { end_pts: Option, /// Start DTS of the whole stream start_dts: Option, + /// Language code from tags + language_code: Option<[u8; 3]>, /// Start PTS of the current fragment fragment_start_pts: Option, @@ -2707,6 +2709,7 @@ impl FMP4Mux { streams, write_mehd: settings.write_mehd, duration: if at_eos { duration } else { None }, + language_code: state.language_code, start_utc_time: if variant == super::Variant::ONVIF { state .earliest_pts @@ -3140,8 +3143,22 @@ impl AggregatorImpl for FMP4Mux { self.parent_sink_event(aggregator_pad, event) } - EventView::Tag(_ev) => { - // TODO: Maybe store for putting into the headers of the next fragment? + EventView::Tag(ev) => { + if let Some(tag_value) = ev.tag().get::() { + let lang = tag_value.get(); + gst::trace!(CAT, imp: self, "Received language code from tags: {:?}", lang); + + // Language as ISO-639-2/T + if lang.len() == 3 && lang.chars().all(|c| c.is_ascii_lowercase()) { + let mut state = self.state.lock().unwrap(); + + let mut language_code: [u8; 3] = [0; 3]; + for (out, c) in Iterator::zip(language_code.iter_mut(), lang.chars()) { + *out = c as u8; + } + state.language_code = Some(language_code); + } + } self.parent_sink_event(aggregator_pad, event) } diff --git a/mux/fmp4/src/fmp4mux/mod.rs b/mux/fmp4/src/fmp4mux/mod.rs index bb50c2897..f6049b2d8 100644 --- a/mux/fmp4/src/fmp4mux/mod.rs +++ b/mux/fmp4/src/fmp4mux/mod.rs @@ -85,6 +85,7 @@ pub(crate) struct HeaderConfiguration { write_mehd: bool, duration: Option, + language_code: Option<[u8; 3]>, /// Start UTC time in ONVIF mode. /// Since Jan 1 1601 in 100ns units. diff --git a/mux/mp4/src/mp4mux/boxes.rs b/mux/mp4/src/mp4mux/boxes.rs index 170fbc9cc..cedee908a 100644 --- a/mux/mp4/src/mp4mux/boxes.rs +++ b/mux/mp4/src/mp4mux/boxes.rs @@ -459,7 +459,6 @@ fn write_mdia( fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 { let lang = lang.borrow(); - // TODO: Need to relax this once we get the language code from tags assert!(lang.iter().all(u8::is_ascii_lowercase)); (((lang[0] as u16 - 0x60) & 0x1F) << 10) @@ -469,7 +468,7 @@ fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 { fn write_mdhd( v: &mut Vec, - _header: &super::Header, + header: &super::Header, stream: &super::Stream, creation_time: u64, ) -> Result<(), Error> { @@ -492,8 +491,11 @@ fn write_mdhd( v.extend(duration.to_be_bytes()); // Language as ISO-639-2/T - // TODO: get actual language from the tags - v.extend(language_code(b"und").to_be_bytes()); + if let Some(lang) = header.language_code { + v.extend(language_code(lang).to_be_bytes()); + } else { + v.extend(language_code(b"und").to_be_bytes()); + } // Pre-defined v.extend([0u8; 2]); diff --git a/mux/mp4/src/mp4mux/imp.rs b/mux/mp4/src/mp4mux/imp.rs index bb9a791d5..93154c769 100644 --- a/mux/mp4/src/mp4mux/imp.rs +++ b/mux/mp4/src/mp4mux/imp.rs @@ -153,6 +153,9 @@ struct State { /// Size of the `mdat` as written so far. mdat_size: u64, + + /// Language code from tags + language_code: Option<[u8; 3]>, } #[derive(Default)] @@ -1166,6 +1169,25 @@ impl AggregatorImpl for MP4Mux { } self.parent_sink_event_pre_queue(aggregator_pad, event) } + EventView::Tag(ev) => { + if let Some(tag_value) = ev.tag().get::() { + let lang = tag_value.get(); + gst::trace!(CAT, imp: self, "Received language code from tags: {:?}", lang); + + // Language as ISO-639-2/T + if lang.len() == 3 && lang.chars().all(|c| c.is_ascii_lowercase()) { + let mut state = self.state.lock().unwrap(); + + let mut language_code: [u8; 3] = [0; 3]; + for (out, c) in Iterator::zip(language_code.iter_mut(), lang.chars()) { + *out = c as u8; + } + state.language_code = Some(language_code); + } + } + + self.parent_sink_event_pre_queue(aggregator_pad, event) + } _ => self.parent_sink_event_pre_queue(aggregator_pad, event), } } @@ -1365,6 +1387,7 @@ impl AggregatorImpl for MP4Mux { variant: self.obj().class().as_ref().variant, movie_timescale: settings.movie_timescale, streams, + language_code: state.language_code, }) .map_err(|err| { gst::error!(CAT, imp: self, "Failed to create moov box: {err}"); diff --git a/mux/mp4/src/mp4mux/mod.rs b/mux/mp4/src/mp4mux/mod.rs index 5dd012bb3..7ab685ac5 100644 --- a/mux/mp4/src/mp4mux/mod.rs +++ b/mux/mp4/src/mp4mux/mod.rs @@ -135,6 +135,7 @@ pub(crate) struct Header { /// Pre-defined movie timescale if not 0. movie_timescale: u32, streams: Vec, + language_code: Option<[u8; 3]>, } #[allow(clippy::upper_case_acronyms)]