mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-21 19:11:02 +00:00
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:
parent
de153222da
commit
fc3cefc38c
12 changed files with 687 additions and 0 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -2756,6 +2756,21 @@ dependencies = [
|
||||||
"regex",
|
"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]]
|
[[package]]
|
||||||
name = "gst-plugin-reqwest"
|
name = "gst-plugin-reqwest"
|
||||||
version = "0.14.0-alpha.1"
|
version = "0.14.0-alpha.1"
|
||||||
|
@ -3189,6 +3204,28 @@ dependencies = [
|
||||||
"system-deps 7.0.3",
|
"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]]
|
[[package]]
|
||||||
name = "gstreamer-app"
|
name = "gstreamer-app"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
|
|
|
@ -31,6 +31,7 @@ members = [
|
||||||
"net/onvif",
|
"net/onvif",
|
||||||
"net/raptorq",
|
"net/raptorq",
|
||||||
"net/reqwest",
|
"net/reqwest",
|
||||||
|
"net/relationmeta",
|
||||||
"net/rtp",
|
"net/rtp",
|
||||||
"net/rtsp",
|
"net/rtsp",
|
||||||
"net/webrtchttp",
|
"net/webrtchttp",
|
||||||
|
@ -86,6 +87,7 @@ default-members = [
|
||||||
"net/onvif",
|
"net/onvif",
|
||||||
"net/raptorq",
|
"net/raptorq",
|
||||||
"net/reqwest",
|
"net/reqwest",
|
||||||
|
"net/relationmeta",
|
||||||
"net/rtp",
|
"net/rtp",
|
||||||
"net/rtsp",
|
"net/rtsp",
|
||||||
"net/webrtchttp",
|
"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"]}
|
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 = { 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-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-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-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" }
|
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
|
|
@ -24,6 +24,7 @@ RS_PREFIXED = [
|
||||||
'rtp',
|
'rtp',
|
||||||
'rtsp',
|
'rtsp',
|
||||||
'inter',
|
'inter',
|
||||||
|
'relationmeta',
|
||||||
]
|
]
|
||||||
|
|
||||||
OVERRIDE = {
|
OVERRIDE = {
|
||||||
|
|
|
@ -32,6 +32,7 @@ RENAMES = {
|
||||||
'rsrtsp': 'rtsp',
|
'rsrtsp': 'rtsp',
|
||||||
'rswebp': 'webp',
|
'rswebp': 'webp',
|
||||||
'rsonvif': 'onvif',
|
'rsonvif': 'onvif',
|
||||||
|
'rsrelationmeta': 'relationmeta',
|
||||||
'rstracers': 'tracers',
|
'rstracers': 'tracers',
|
||||||
'rsclosedcaption': 'closedcaption',
|
'rsclosedcaption': 'closedcaption',
|
||||||
'rswebrtc': 'webrtc',
|
'rswebrtc': 'webrtc',
|
||||||
|
|
|
@ -7967,6 +7967,78 @@
|
||||||
"tracers": {},
|
"tracers": {},
|
||||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
"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": {
|
"rsrtp": {
|
||||||
"description": "GStreamer Rust RTP Plugin",
|
"description": "GStreamer Rust RTP Plugin",
|
||||||
"elements": {
|
"elements": {
|
||||||
|
|
|
@ -91,6 +91,9 @@ endif
|
||||||
if get_option('threadshare').allowed() or get_option('rtsp').allowed()
|
if get_option('threadshare').allowed() or get_option('rtsp').allowed()
|
||||||
deps += [['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net']]
|
deps += [['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net']]
|
||||||
endif
|
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)
|
glib_dep = dependency('glib-2.0', version: glib_req)
|
||||||
deps_cache += {'glib-2.0': glib_dep}
|
deps_cache += {'glib-2.0': glib_dep}
|
||||||
|
@ -155,6 +158,7 @@ plugins = {
|
||||||
},
|
},
|
||||||
'raptorq': {'library': 'libgstraptorq'},
|
'raptorq': {'library': 'libgstraptorq'},
|
||||||
'reqwest': {'library': 'libgstreqwest'},
|
'reqwest': {'library': 'libgstreqwest'},
|
||||||
|
'relationmeta': {'library': 'libgstrsrelationmeta'},
|
||||||
'rtsp': {'library': 'libgstrsrtsp'},
|
'rtsp': {'library': 'libgstrsrtsp'},
|
||||||
'rtp': {'library': 'libgstrsrtp'},
|
'rtp': {'library': 'libgstrsrtp'},
|
||||||
'webrtchttp': {'library': 'libgstwebrtchttp'},
|
'webrtchttp': {'library': 'libgstwebrtchttp'},
|
||||||
|
|
|
@ -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('onvif', type: 'feature', value: 'auto', description: 'Build onvif plugin')
|
||||||
option('raptorq', type: 'feature', value: 'auto', description: 'Build raptorq plugin')
|
option('raptorq', type: 'feature', value: 'auto', description: 'Build raptorq plugin')
|
||||||
option('reqwest', type: 'feature', value: 'auto', description: 'Build reqwest 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('rtsp', type: 'feature', value: 'auto', description: 'Build rtsp plugin')
|
||||||
option('rtp', type: 'feature', value: 'auto', description: 'Build rtp plugin')
|
option('rtp', type: 'feature', value: 'auto', description: 'Build rtp plugin')
|
||||||
option('webrtc', type: 'feature', value: 'auto', yield: true, description: 'Build webrtc plugin')
|
option('webrtc', type: 'feature', value: 'auto', yield: true, description: 'Build webrtc plugin')
|
||||||
|
|
46
net/relationmeta/Cargo.toml
Normal file
46
net/relationmeta/Cargo.toml
Normal 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"
|
3
net/relationmeta/build.rs
Normal file
3
net/relationmeta/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
gst_plugin_version_helper::info()
|
||||||
|
}
|
42
net/relationmeta/src/lib.rs
Normal file
42
net/relationmeta/src/lib.rs
Normal 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")
|
||||||
|
);
|
449
net/relationmeta/src/relationmeta2onvifmeta/imp.rs
Normal file
449
net/relationmeta/src/relationmeta2onvifmeta/imp.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
28
net/relationmeta/src/relationmeta2onvifmeta/mod.rs
Normal file
28
net/relationmeta/src/relationmeta2onvifmeta/mod.rs
Normal 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(),
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue