From de153222dae803db0accdfeff824b13cab9e2b64 Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Wed, 7 Feb 2024 16:39:04 +0100 Subject: [PATCH] onvif: Add onvifmetadataextractor element onvifmetadataextractor does the opposite operation than onvifmetadatacombiner, it extracts ONVIF metadatas from the stream buffer and export them as buffers which could be used by rtponvifpay element. Part-of: --- docs/plugins/gst_plugins_cache.json | 44 ++++ net/onvif/src/lib.rs | 2 + net/onvif/src/onvifmetadataextractor/imp.rs | 241 ++++++++++++++++++++ net/onvif/src/onvifmetadataextractor/mod.rs | 23 ++ 4 files changed, 310 insertions(+) create mode 100644 net/onvif/src/onvifmetadataextractor/imp.rs create mode 100644 net/onvif/src/onvifmetadataextractor/mod.rs 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(), + ) +}