relationmeta: Add relationmeta2onvifmeta element

Add relationmeta2onvifmeta which convert relation metas
to ONVIF metas and add them to buffer.
Used relation metas are removed from buffer.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1464>
This commit is contained in:
Benjamin Gaignard 2024-10-22 16:53:35 +02:00 committed by GStreamer Marge Bot
parent de153222da
commit fc3cefc38c
12 changed files with 687 additions and 0 deletions

37
Cargo.lock generated
View file

@ -2756,6 +2756,21 @@ dependencies = [
"regex",
]
[[package]]
name = "gst-plugin-relationmeta"
version = "0.14.0-alpha.1"
dependencies = [
"chrono",
"glib",
"gst-plugin-version-helper",
"gstreamer",
"gstreamer-analytics",
"gstreamer-base",
"gstreamer-rtp",
"gstreamer-video",
"xmltree",
]
[[package]]
name = "gst-plugin-reqwest"
version = "0.14.0-alpha.1"
@ -3189,6 +3204,28 @@ dependencies = [
"system-deps 7.0.3",
]
[[package]]
name = "gstreamer-analytics"
version = "0.24.0"
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#d1ece4bb12bba4cbe9cc933206caaa2b79c7011d"
dependencies = [
"glib",
"gstreamer",
"gstreamer-analytics-sys",
"libc",
]
[[package]]
name = "gstreamer-analytics-sys"
version = "0.24.0"
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#d1ece4bb12bba4cbe9cc933206caaa2b79c7011d"
dependencies = [
"glib-sys",
"gstreamer-sys",
"libc",
"system-deps 7.0.3",
]
[[package]]
name = "gstreamer-app"
version = "0.24.0"

View file

@ -31,6 +31,7 @@ members = [
"net/onvif",
"net/raptorq",
"net/reqwest",
"net/relationmeta",
"net/rtp",
"net/rtsp",
"net/webrtchttp",
@ -86,6 +87,7 @@ default-members = [
"net/onvif",
"net/raptorq",
"net/reqwest",
"net/relationmeta",
"net/rtp",
"net/rtsp",
"net/webrtchttp",
@ -142,6 +144,7 @@ gdk-x11 = { package = "gdk4-x11", git = "https://github.com/gtk-rs/gtk4-rs", bra
gdk-win32 = { package = "gdk4-win32", git = "https://github.com/gtk-rs/gtk4-rs", branch = "main", features = ["v4_4"]}
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
gst-allocators = { package = "gstreamer-allocators", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
gst-analytics = { package = "gstreamer-analytics", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }

View file

@ -24,6 +24,7 @@ RS_PREFIXED = [
'rtp',
'rtsp',
'inter',
'relationmeta',
]
OVERRIDE = {

View file

@ -32,6 +32,7 @@ RENAMES = {
'rsrtsp': 'rtsp',
'rswebp': 'webp',
'rsonvif': 'onvif',
'rsrelationmeta': 'relationmeta',
'rstracers': 'tracers',
'rsclosedcaption': 'closedcaption',
'rswebrtc': 'webrtc',

View file

@ -7967,6 +7967,78 @@
"tracers": {},
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
},
"rsrelationmeta": {
"description": "GStreamer Rust Relation Meta Plugin",
"elements": {
"relationmeta2onvifmeta": {
"author": "Benjamin Gaignard <benjamin.gaignard@collabora.com>",
"description": "Convert relation metadata to ONVIF metadata",
"hierarchy": [
"GstRelationMeta2OnvifMeta",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"klass": "Metadata",
"pad-templates": {
"sink": {
"caps": "ANY",
"direction": "sink",
"presence": "always"
},
"src": {
"caps": "ANY",
"direction": "src",
"presence": "always"
}
},
"properties": {
"time-source": {
"blurb": "Time source for UTC timestamps",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "clock (0)",
"mutable": "ready",
"readable": true,
"type": "GstRsOnvifNtpTimeSource",
"writable": true
}
},
"rank": "none"
}
},
"filename": "gstrsrelationmeta",
"license": "MPL-2.0",
"other-types": {
"GstRsOnvifNtpTimeSource": {
"kind": "enum",
"values": [
{
"desc": "UNIX time based on realtime clock.",
"name": "clock",
"value": "0"
},
{
"desc": "Running time is in UTC",
"name": "running-time",
"value": "1"
},
{
"desc": "Pipeline clock is UTC",
"name": "clock-time",
"value": "2"
}
]
}
},
"package": "gst-plugin-relationmeta",
"source": "gst-plugin-relationmeta",
"tracers": {},
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
},
"rsrtp": {
"description": "GStreamer Rust RTP Plugin",
"elements": {

View file

@ -91,6 +91,9 @@ endif
if get_option('threadshare').allowed() or get_option('rtsp').allowed()
deps += [['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net']]
endif
if get_option('relationmeta').allowed()
deps += [['gstreamer-analytics-1.0', 'gst-plugins-bad', 'gstanalytics_dep', 'gstanalytics']]
endif
glib_dep = dependency('glib-2.0', version: glib_req)
deps_cache += {'glib-2.0': glib_dep}
@ -155,6 +158,7 @@ plugins = {
},
'raptorq': {'library': 'libgstraptorq'},
'reqwest': {'library': 'libgstreqwest'},
'relationmeta': {'library': 'libgstrsrelationmeta'},
'rtsp': {'library': 'libgstrsrtsp'},
'rtp': {'library': 'libgstrsrtp'},
'webrtchttp': {'library': 'libgstwebrtchttp'},

View file

@ -33,6 +33,7 @@ option('ndi', type: 'feature', value: 'auto', description: 'Build ndi plugin')
option('onvif', type: 'feature', value: 'auto', description: 'Build onvif plugin')
option('raptorq', type: 'feature', value: 'auto', description: 'Build raptorq plugin')
option('reqwest', type: 'feature', value: 'auto', description: 'Build reqwest plugin')
option('relationmeta', type: 'feature', value: 'auto', description: 'Build relationmeta plugin')
option('rtsp', type: 'feature', value: 'auto', description: 'Build rtsp plugin')
option('rtp', type: 'feature', value: 'auto', description: 'Build rtp plugin')
option('webrtc', type: 'feature', value: 'auto', yield: true, description: 'Build webrtc plugin')

View file

@ -0,0 +1,46 @@
[package]
name = "gst-plugin-relationmeta"
version.workspace = true
authors = ["Benjamin Gaignard <benjamin.gaignard@collabora.com>"]
repository.workspace = true
license = "MPL-2.0"
description = "GStreamer Rust Relation Meta Plugin"
edition.workspace = true
rust-version.workspace = true
[dependencies]
gst = { workspace = true, features = ["v1_24"] }
gst-rtp = { workspace = true, features = ["v1_24"] }
gst-base = { workspace = true, features = ["v1_24"] }
gst-video = { workspace = true, features = ["v1_24"] }
gst-analytics = { workspace = true }
chrono = { version = "0.4.31", default-features = false }
xmltree = "0.10"
glib = { workspace = true, features = ["v2_62"] }
[lib]
name = "gstrsrelationmeta"
crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper.workspace = true
[features]
static = []
capi = []
doc = ["gst/v1_18"]
[package.metadata.capi]
min_version = "0.9.21"
[package.metadata.capi.header]
enabled = false
[package.metadata.capi.library]
install_subdir = "gstreamer-1.0"
versioning = false
import_library = false
[package.metadata.capi.pkg_config]
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0, gstreamer-analytics-1.0"

View file

@ -0,0 +1,3 @@
fn main() {
gst_plugin_version_helper::info()
}

View file

@ -0,0 +1,42 @@
// Copyright (C) 2024 Benjamin Gaignard <benjamin.gaignard@collabora.com>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)]
/**
* plugin-rsrelationmeta:
*
* Since: plugins-rs-0.13.0
*/
use gst::glib;
pub(crate) const ONVIF_METADATA_SCHEMA: &str = "http://www.onvif.org/ver10/schema";
pub(crate) const ONVIF_METADATA_PREFIX: &str = "tt";
mod relationmeta2onvifmeta;
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
relationmeta2onvifmeta::register(plugin)?;
if !gst::meta::CustomMeta::is_registered("OnvifXMLFrameMeta") {
gst::meta::CustomMeta::register("OnvifXMLFrameMeta", &[]);
}
Ok(())
}
gst::plugin_define!(
rsrelationmeta,
env!("CARGO_PKG_DESCRIPTION"),
plugin_init,
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
"MPL-2.0",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_REPOSITORY"),
env!("BUILD_REL_DATE")
);

View file

@ -0,0 +1,449 @@
// Copyright (C) 2024 Benjamin Gaignard <benjamin.gaignard@collabora.com>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst_analytics::*;
use std::sync::LazyLock;
use std::sync::Mutex;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstRsOnvifNtpTimeSource")]
pub enum TimeSource {
#[enum_value(name = "UNIX time based on realtime clock.", nick = "clock")]
Clock,
#[enum_value(name = "Running time is in UTC", nick = "running-time")]
RunningTime,
#[enum_value(name = "Pipeline clock is UTC", nick = "clock-time")]
ClockTime,
}
const DEFAULT_TIME_SOURCE: TimeSource = TimeSource::Clock;
#[derive(Debug, Clone, Copy)]
struct Settings {
timesource: TimeSource,
}
impl Default for Settings {
fn default() -> Self {
Settings {
timesource: DEFAULT_TIME_SOURCE,
}
}
}
#[derive(Default)]
struct State {
video_info: Option<gst_video::VideoInfo>,
segment: gst::FormattedSegment<gst::ClockTime>,
}
pub struct RelationMeta2OnvifMeta {
// Input media stream with relation metadata
sinkpad: gst::Pad,
// Output media stream with ONVIF metadata
srcpad: gst::Pad,
state: Mutex<State>,
settings: Mutex<Settings>,
}
static CAT: LazyLock<gst::DebugCategory> = LazyLock::new(|| {
gst::DebugCategory::new(
"relationmeta2onvifmeta",
gst::DebugColorFlags::empty(),
Some("Relation metadata to ONVIF metadata"),
)
});
#[glib::object_subclass]
impl ObjectSubclass for RelationMeta2OnvifMeta {
const NAME: &'static str = "GstRelationMeta2OnvifMeta";
type Type = super::RelationMeta2OnvifMeta;
type ParentType = gst::Element;
fn with_class(klass: &Self::Class) -> Self {
let templ = klass.pad_template("sink").unwrap();
let sinkpad = gst::Pad::builder_from_template(&templ)
.chain_function(|pad, parent, buffer| {
RelationMeta2OnvifMeta::catch_panic_pad_function(
parent,
|| Err(gst::FlowError::Error),
|convert| convert.sink_chain(pad, buffer),
)
})
.event_function(|pad, parent, event| {
RelationMeta2OnvifMeta::catch_panic_pad_function(
parent,
|| false,
|convert| convert.sink_event(pad, event),
)
})
.flags(gst::PadFlags::PROXY_CAPS)
.flags(gst::PadFlags::PROXY_ALLOCATION)
.build();
let templ = klass.pad_template("src").unwrap();
let srcpad = gst::Pad::builder_from_template(&templ)
.flags(gst::PadFlags::PROXY_CAPS)
.flags(gst::PadFlags::PROXY_ALLOCATION)
.build();
Self {
srcpad,
sinkpad,
state: Mutex::new(State::default()),
settings: Mutex::new(Settings::default()),
}
}
}
impl ObjectImpl for RelationMeta2OnvifMeta {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: LazyLock<Vec<glib::ParamSpec>> = LazyLock::new(|| {
vec![
glib::ParamSpecEnum::builder_with_default("time-source", DEFAULT_TIME_SOURCE)
.nick("time source")
.blurb("Time source for UTC timestamps")
.mutable_ready()
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"time-source" => {
let mut settings = self.settings.lock().unwrap();
settings.timesource = value.get::<TimeSource>().expect("type checked upstream");
}
_ => unreachable!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"time-source" => {
let settings = self.settings.lock().unwrap();
settings.timesource.to_value()
}
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add_pad(&self.sinkpad).unwrap();
obj.add_pad(&self.srcpad).unwrap();
}
}
impl GstObjectImpl for RelationMeta2OnvifMeta {}
impl ElementImpl for RelationMeta2OnvifMeta {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: LazyLock<gst::subclass::ElementMetadata> = LazyLock::new(|| {
gst::subclass::ElementMetadata::new(
"Relation metadata to ONVIF metadata",
"Metadata",
"Convert relation metadata to ONVIF metadata",
"Benjamin Gaignard <benjamin.gaignard@collabora.com>",
)
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: LazyLock<Vec<gst::PadTemplate>> = 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_caps = gst::Caps::new_any();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&src_caps,
)
.unwrap();
vec![src_pad_template, sink_pad_template]
});
PAD_TEMPLATES.as_ref()
}
}
impl RelationMeta2OnvifMeta {
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
use gst::EventView;
match event.view() {
EventView::Caps(c) => {
let mut state = self.state.lock().unwrap();
let info = match gst_video::VideoInfo::from_caps(c.caps()) {
Ok(info) => info,
Err(_) => {
gst::error!(CAT, obj = pad, "Failed to parse caps {:?}", c.caps());
return false;
}
};
state.video_info = Some(info);
self.srcpad.push_event(event)
}
EventView::Segment(s) => {
let mut state = self.state.lock().unwrap();
let segment = s.segment().clone();
let segment = match segment.downcast::<gst::ClockTime>() {
Ok(segment) => segment,
Err(_) => {
gst::element_imp_error!(
self,
gst::CoreError::Event,
["Only time segments are supported"]
);
return false;
}
};
state.segment = segment.clone();
gst::Pad::event_default(pad, Some(&*self.obj()), event)
}
_ => gst::Pad::event_default(pad, Some(&*self.obj()), event),
}
}
fn get_utc_time(&self, running_time: gst::ClockTime) -> gst::ClockTime {
match self.settings.lock().unwrap().timesource {
TimeSource::Clock => {
if let (Some(base_time), Some(clock)) = (self.obj().base_time(), self.obj().clock())
{
let utc_now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_nanos()
.try_into()
.unwrap();
let utc_now = gst::ClockTime::from_nseconds(utc_now);
let running_time_now = clock.time().unwrap() - base_time;
let rt_diff = utc_now - running_time_now;
running_time_now + rt_diff
} else {
gst::error!(
CAT,
imp = self,
"Can only use the clock if the pipeline has a clock"
);
gst::ClockTime::ZERO
}
}
TimeSource::RunningTime => running_time,
TimeSource::ClockTime => {
if let Some(base_time) = self.obj().base_time() {
running_time + base_time
} else {
gst::warning!(
CAT,
imp = self,
"Clock time was selected but there is no base time"
);
gst::ClockTime::ZERO
}
}
}
}
fn sink_chain(
&self,
pad: &gst::Pad,
mut input_buffer: gst::Buffer,
) -> Result<gst::FlowSuccess, gst::FlowError> {
let metas = input_buffer.meta::<AnalyticsRelationMeta>();
let mut xml = xmltree::Element::new("MetadataStream");
xml.namespaces
.get_or_insert_with(|| xmltree::Namespace(Default::default()))
.put("tt", crate::ONVIF_METADATA_SCHEMA);
xml.namespace = Some(String::from(crate::ONVIF_METADATA_SCHEMA));
xml.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
let mut video_analytics = xmltree::Element::new("VideoAnalytics");
video_analytics.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
let state = self.state.lock().unwrap();
let current_time =
self.get_utc_time(state.segment.to_running_time(input_buffer.pts()).unwrap());
let dt =
gst::DateTime::from_unix_epoch_utc_usecs(current_time.useconds().try_into().unwrap())
.unwrap()
.to_g_date_time()
.unwrap()
.format_iso8601()
.unwrap();
let mut frame = xmltree::Element::new("Frame");
frame.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
frame
.attributes
.insert("UtcTime".to_string(), dt.to_string());
let mut transformation = xmltree::Element::new("Transformation");
transformation.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
let mut translate = xmltree::Element::new("Translate");
translate.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
translate
.attributes
.insert("x".to_string(), (-1.0).to_string());
translate
.attributes
.insert("y".to_string(), (-1.0).to_string());
let mut scale = xmltree::Element::new("Scale");
scale.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
let video_info = state.video_info.as_ref().unwrap();
let x = format!("{:.5}", 2_f64 / (video_info.width() as f64));
let y = format!("{:.5}", 2_f64 / (video_info.height() as f64));
scale.attributes.insert("x".to_string(), x);
scale.attributes.insert("y".to_string(), y);
transformation
.children
.push(xmltree::XMLNode::Element(translate));
transformation
.children
.push(xmltree::XMLNode::Element(scale));
frame
.children
.push(xmltree::XMLNode::Element(transformation));
if let Some(metas) = metas {
for meta in metas.iter::<AnalyticsODMtd>() {
let loc = meta.location().unwrap();
let mut object = xmltree::Element::new("Object");
object.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
object
.attributes
.insert("ObjectId".to_string(), meta.id().to_string());
let mut appearance = xmltree::Element::new("Appearance");
appearance.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
let mut shape = xmltree::Element::new("Shape");
shape.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
let mut bounding_box = xmltree::Element::new("BoundingBox");
bounding_box.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
bounding_box
.attributes
.insert("left".to_string(), loc.x.to_string());
bounding_box
.attributes
.insert("top".to_string(), loc.y.to_string());
bounding_box
.attributes
.insert("right".to_string(), (loc.x + loc.w).to_string());
bounding_box
.attributes
.insert("bottom".to_string(), (loc.y + loc.h).to_string());
shape.children.push(xmltree::XMLNode::Element(bounding_box));
let mut class = xmltree::Element::new("Class");
class.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
let mut t = xmltree::Element::new("Type");
t.prefix = Some(String::from(crate::ONVIF_METADATA_PREFIX));
t.attributes
.insert("Likelihood".to_string(), loc.loc_conf_lvl.to_string());
let obj_name = if let Some(obj_type) = meta.obj_type() {
obj_type.as_str()
} else {
"Unknown"
};
t.children
.push(xmltree::XMLNode::Text(obj_name.to_string()));
gst::trace!(
CAT,
imp = self,
"Transformed object: {}x{}@({}h{}) prob: {} name: {:?}",
loc.w,
loc.w,
loc.x,
loc.y,
loc.loc_conf_lvl,
meta.obj_type()
);
class.children.push(xmltree::XMLNode::Element(t));
appearance.children.push(xmltree::XMLNode::Element(shape));
appearance.children.push(xmltree::XMLNode::Element(class));
object.children.push(xmltree::XMLNode::Element(appearance));
frame.children.push(xmltree::XMLNode::Element(object));
}
} else {
gst::trace!(CAT, imp = self, "No meta, nothing to transform");
}
video_analytics
.children
.push(xmltree::XMLNode::Element(frame));
xml.children
.push(xmltree::XMLNode::Element(video_analytics));
let mut text = Vec::new();
if let Err(err) = xml.write_with_config(
&mut text,
xmltree::EmitterConfig {
perform_indent: true,
..xmltree::EmitterConfig::default()
},
) {
gst::error!(CAT, obj = pad, "Can't serialize XML element: {}", err);
}
gst::trace!(
CAT,
imp = self,
"Generated ONVIF xml: {}",
std::str::from_utf8(&text).unwrap()
);
let meta_buf = gst::Buffer::from_mut_slice(text);
let mut buflist = gst::BufferList::new();
buflist.get_mut().unwrap().add(meta_buf);
let buf = input_buffer.make_mut();
let mut onvif_meta = gst::meta::CustomMeta::add(buf, "OnvifXMLFrameMeta").unwrap();
let s = onvif_meta.mut_structure();
s.set("frames", buflist);
gst::trace!(CAT, obj = pad, "Send buffer {:?}", buf);
self.srcpad.push(input_buffer)
}
}

View file

@ -0,0 +1,28 @@
// Copyright (C) 2024 Benjamin Gaignard <benjamin.gaignard@collabora.com>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use gst::glib;
use gst::prelude::*;
mod imp;
glib::wrapper! {
pub struct RelationMeta2OnvifMeta(ObjectSubclass<imp::RelationMeta2OnvifMeta>) @extends gst::Element, gst::Object;
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
#[cfg(feature = "doc")]
imp::TimeSource::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
gst::Element::register(
Some(plugin),
"relationmeta2onvifmeta",
gst::Rank::NONE,
RelationMeta2OnvifMeta::static_type(),
)
}