diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index f71e645ab..edd54cb52 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -7635,6 +7635,50 @@ }, "rank": "primary" }, + "onvifmetadataextractor": { + "author": "Benjamin Gaignard ", + "description": "Extract the ONVIF GstMeta into a separate stream", + "hierarchy": [ + "GstOnvifMetadataExtractor", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Video/Metadata", + "pad-templates": { + "meta_src": { + "caps": "application/x-onvif-metadata:\n", + "direction": "src", + "presence": "always" + }, + "sink": { + "caps": "ANY", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "ANY", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "remove-onvif-metadata": { + "blurb": "Remove ONVIF metadata from output stream", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + } + }, + "rank": "none" + }, "onvifmetadataoverlay": { "author": "Mathieu Duponchelle ", "description": "Renders ONVIF analytics meta over raw video frames", diff --git a/net/onvif/src/lib.rs b/net/onvif/src/lib.rs index 4abf5012c..14bc1d1d9 100644 --- a/net/onvif/src/lib.rs +++ b/net/onvif/src/lib.rs @@ -17,6 +17,7 @@ use std::sync::LazyLock; mod onvifmetadatacombiner; mod onvifmetadatadepay; +mod onvifmetadataextractor; mod onvifmetadataoverlay; mod onvifmetadataparse; mod onvifmetadatapay; @@ -119,6 +120,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { onvifmetadatacombiner::register(plugin)?; onvifmetadataoverlay::register(plugin)?; onvifmetadataparse::register(plugin)?; + onvifmetadataextractor::register(plugin)?; if !gst::meta::CustomMeta::is_registered("OnvifXMLFrameMeta") { gst::meta::CustomMeta::register("OnvifXMLFrameMeta", &[]); diff --git a/net/onvif/src/onvifmetadataextractor/imp.rs b/net/onvif/src/onvifmetadataextractor/imp.rs new file mode 100644 index 000000000..afb55aaea --- /dev/null +++ b/net/onvif/src/onvifmetadataextractor/imp.rs @@ -0,0 +1,241 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use gst::glib; +use gst::prelude::*; +use gst::subclass::prelude::*; +use std::ops::ControlFlow; +use std::sync::LazyLock; +use std::sync::Mutex; + +static CAT: LazyLock = LazyLock::new(|| { + gst::DebugCategory::new( + "onvifmetadataextractor", + gst::DebugColorFlags::empty(), + Some("ONVIF Metadata Extractor Element"), + ) +}); + +#[derive(Default)] +struct Settings { + remove_metadata: bool, +} + +pub struct OnvifMetadataExtractor { + // Input media stream + sink_pad: gst::Pad, + // Output media stream + src_pad: gst::Pad, + // Output metadata stream, complete VideoAnalytics XML documents + meta_src_pad: gst::Pad, + settings: Mutex, + flow_combiner: Mutex, +} + +#[glib::object_subclass] +impl ObjectSubclass for OnvifMetadataExtractor { + const NAME: &'static str = "GstOnvifMetadataExtractor"; + type Type = super::OnvifMetadataExtractor; + type ParentType = gst::Element; + + fn with_class(klass: &Self::Class) -> Self { + let templ = klass.pad_template("sink").unwrap(); + let sink_pad = gst::Pad::builder_from_template(&templ) + .chain_function(|pad, parent, buffer| { + OnvifMetadataExtractor::catch_panic_pad_function( + parent, + || Err(gst::FlowError::Error), + |extractor| extractor.sink_chain(pad, buffer), + ) + }) + .event_function(|pad, parent, event| { + OnvifMetadataExtractor::catch_panic_pad_function( + parent, + || false, + |extractor| extractor.sink_event(pad, event), + ) + }) + .query_function(|pad, parent, query| { + Self::catch_panic_pad_function( + parent, + || false, + |extractor| extractor.sink_query(pad, query), + ) + }) + .build(); + let templ = klass.pad_template("src").unwrap(); + let src_pad = gst::Pad::builder_from_template(&templ).build(); + let templ = klass.pad_template("meta_src").unwrap(); + let meta_src_pad = gst::Pad::builder_from_template(&templ).build(); + + Self { + sink_pad, + src_pad, + meta_src_pad, + settings: Mutex::new(Settings::default()), + flow_combiner: Mutex::new(gst_base::UniqueFlowCombiner::new()), + } + } +} + +impl ObjectImpl for OnvifMetadataExtractor { + fn constructed(&self) { + self.parent_constructed(); + + let obj = self.obj(); + obj.add_pad(&self.sink_pad).unwrap(); + obj.add_pad(&self.src_pad).unwrap(); + obj.add_pad(&self.meta_src_pad).unwrap(); + self.flow_combiner.lock().unwrap().add_pad(&self.src_pad); + self.flow_combiner + .lock() + .unwrap() + .add_pad(&self.meta_src_pad); + } + + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: LazyLock> = LazyLock::new(|| { + vec![glib::ParamSpecBoolean::builder("remove-onvif-metadata") + .nick("Remove ONVIF metadata") + .blurb("Remove ONVIF metadata from output stream") + .default_value(false) + .build()] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "remove-onvif-metadata" => { + self.settings.lock().unwrap().remove_metadata = value.get().unwrap(); + } + _ => unimplemented!(), + }; + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "remove-onvif-metadata" => self.settings.lock().unwrap().remove_metadata.to_value(), + _ => unimplemented!(), + } + } +} + +impl GstObjectImpl for OnvifMetadataExtractor {} + +impl ElementImpl for OnvifMetadataExtractor { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: LazyLock = LazyLock::new(|| { + gst::subclass::ElementMetadata::new( + "ONVIF metadata extractor", + "Video/Metadata", + "Extract the ONVIF GstMeta into a separate stream", + "Benjamin Gaignard ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: LazyLock> = LazyLock::new(|| { + let sink_caps = gst::Caps::new_any(); + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &sink_caps, + ) + .unwrap(); + + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &sink_caps, + ) + .unwrap(); + + let meta_caps = gst::Caps::builder("application/x-onvif-metadata").build(); + let meta_src_pad_template = gst::PadTemplate::new( + "meta_src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &meta_caps, + ) + .unwrap(); + + vec![sink_pad_template, src_pad_template, meta_src_pad_template] + }); + + PAD_TEMPLATES.as_ref() + } +} + +impl OnvifMetadataExtractor { + fn sink_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool { + gst::trace!(CAT, obj = pad, "Received query: {query:?}"); + gst::Pad::query_default(&self.sink_pad, Some(&*self.obj()), query) + } + + fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool { + use gst::EventView; + + gst::log!(CAT, obj = pad, "Handling event {:?}", event); + + match event.view() { + EventView::Caps(..) => { + self.src_pad.push_event(event); + let caps = &self.meta_src_pad.pad_template_caps(); + self.meta_src_pad.push_event(gst::event::Caps::new(caps)) + } + _ => gst::Pad::event_default(pad, Some(&*self.obj()), event), + } + } + + fn sink_chain( + &self, + pad: &gst::Pad, + mut buffer: gst::Buffer, + ) -> Result { + gst::log!(CAT, obj = pad, "Handling buffer {:?}", buffer); + + let remove = self.settings.lock().unwrap().remove_metadata; + + if let Ok(metas) = + gst::meta::CustomMeta::from_mut_buffer(buffer.make_mut(), "OnvifXMLFrameMeta") + { + let s = metas.structure(); + + if let Ok(frames) = s.get::("frames") { + frames.foreach(|meta_buf, _idx| { + let res = self.meta_src_pad.push(meta_buf.clone()); + let _ = self + .flow_combiner + .lock() + .unwrap() + .update_pad_flow(&self.meta_src_pad, res); + if res.is_ok() { + ControlFlow::Continue(()) + } else { + ControlFlow::Break(()) + } + }); + } + + if remove && metas.remove().is_err() { + return Err(gst::FlowError::Error); + } + } + + let res = self.src_pad.push(buffer); + self.flow_combiner + .lock() + .unwrap() + .update_pad_flow(&self.src_pad, res) + } +} diff --git a/net/onvif/src/onvifmetadataextractor/mod.rs b/net/onvif/src/onvifmetadataextractor/mod.rs new file mode 100644 index 000000000..154d74ee9 --- /dev/null +++ b/net/onvif/src/onvifmetadataextractor/mod.rs @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use gst::glib; +use gst::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct OnvifMetadataExtractor(ObjectSubclass) @extends gst::Element, gst::Object; +} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "onvifmetadataextractor", + gst::Rank::NONE, + OnvifMetadataExtractor::static_type(), + ) +}