Initial commit

This commit is contained in:
2024-06-24 11:55:56 +03:00
commit 9c622e0ff4
8 changed files with 535 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
builddir
build
.vscode

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "gst_root"]
path = gst_root
url = https://gitlab.freedesktop.org/gstreamer/gstreamer.git

81
README.md Normal file
View File

@@ -0,0 +1,81 @@
# GStreamer Microsecond Timestamp Overlay
This is an adaptation of the `clockoverlay` and `timeoverlay` plugins from gstreamer-base. Stripped bare and just overlaying the Linux epoch time, with a microsecond resolution.
## Use-case Examples
A single stream solution can be tested like this:
```bash
gst-launch-1.0 \
videotestsrc is-live=true do-timestamp=true \
! msoverlay halignment=left valignment=top \
! video/x-raw,format=RGB,framerate=30/1,width=640,height=480 \
! videoconvert \
! video/x-raw,format=NV12 \
! nvautogpuh264enc tune=3 \
! rtph264pay config-interval=0 pt=96 \
! rtph264depay \
! h264parse \
! nvh264dec \
! msoverlay halignment=left valignment=bottom \
! autovideosink
```
Take a screenshot of the output above and subtract the top-left number from the bottom-left to see the total pipeline latency.
For a more comprehensive solution we can also make the stream traverse the network stack:
Server:
```bash
gst-launch-1.0 videotestsrc is-live=true do-timestamp=true ! msoverlay halignment=left valignment=top ! video/x-raw,format=RGB,framerate=60/1,width=1920,height=1080 ! videoconvert ! video/x-raw,format=NV12 ! nvautogpuh264enc tune=3 ! rtph264pay config-interval=1 pt=96 ! rtpsink uri=rtp://127.0.0.1:5000
```
Live client:
```bash
gst-launch-1.0 rtpsrc uri=rtp://127.0.0.1:5000?encoding-name=H264 ! rtph264depay ! h264parse ! nvh264dec ! msoverlay halignment=left valignment=bottom ! autovideosink
```
PNG sampling client outputting 5 frames, one per second:
```bash
gst-launch-1.0 rtpsrc uri=rtp://127.0.0.1:5000?encoding-name=H264 ! rtph264depay ! h264parse ! nvh264dec ! msoverlay halignment=left valignment=bottom ! videorate drop-only=true ! video/x-raw,framerate=1/1 ! pngenc ! multifilesink num-buffers=10 location="latency_%02d.png"
```
It is essential both the client and server run on the same machine, unless you _really_ trust your system clock accuracy.
## Dependencies
TODO. Basically the same dependencies as for building the gstreamer-plugins-base packages.
## Building
```bash
# Assuming you're in the project root
# Option A is to use the submodule to fetch the GStreamer sources:
git submodule update --init --recursive
# Older versions require deleting the builddir and omitting the '--reconfigure' option
meson setup --reconfigure builddir
# Option B is to use an existing GStreamer source by specifying the 'gst_root' (the GStreamer mono-repo root)
# meson setup --reconfigure builddir -Dgst_root=${WHERE_I_CLONED_GSTREAMER}/gstreamer
meson compile -C builddir
# This will install the built plugin file
meson install -C builddir
# Confirm installation
gst-inspect-1.0 | grep msoverlay
```
## Verification
This should show you a fairly extensive list of options and details:
```bash
gst-inspect-1.0 msoverlay
```

1
gst_root Submodule

Submodule gst_root added at 9e1abc0797

88
include/gstmsoverlay.h Normal file
View File

@@ -0,0 +1,88 @@
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2005> Tim-Philipp Müller <tim@centricular.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __GST_MS_OVERLAY_H__
#define __GST_MS_OVERLAY_H__
#include "gstbasetextoverlay.h"
G_BEGIN_DECLS
GST_ELEMENT_REGISTER_DECLARE (msoverlay);
#define GST_TYPE_MS_OVERLAY \
(gst_ms_overlay_get_type())
#define GST_MS_OVERLAY(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MS_OVERLAY,GstMsOverlay))
#define GST_MS_OVERLAY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MS_OVERLAY,GstMsOverlayClass))
#define GST_IS_MS_OVERLAY(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MS_OVERLAY))
#define GST_IS_MS_OVERLAY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MS_OVERLAY))
typedef struct _GstMsOverlay GstMsOverlay;
typedef struct _GstMsOverlayClass GstMsOverlayClass;
/**
* GstMsOverlay:
*
* Opaque msoverlay data structure.
*/
struct _GstMsOverlay {
GstBaseTextOverlay textoverlay;
gchar *format; /* as in strftime () */
/* for wcsftime */
gunichar2 *wformat;
gchar *text;
};
struct _GstMsOverlayClass {
GstBaseTextOverlayClass parent_class;
};
GType gst_ms_overlay_get_type (void);
// /* This is a hack hat allows us to use nonliterals for strftime without
// * triggering a warning from -Wformat-nonliteral. We need to allow this
// * because we export the format string as a property of the element.
// * For the inspiration of this and a discussion of why this is necessary,
// * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39438
// */
// #ifdef __GNUC__
// #pragma GCC system_header
// static size_t my_strftime(char *s, size_t max, const char *format,
// const struct tm *tm)
// {
// return strftime (s, max, format, tm);
// }
// #define strftime my_strftime
// #endif
G_END_DECLS
#endif /* __GST_MS_OVERLAY_H__ */

70
meson.build Normal file
View File

@@ -0,0 +1,70 @@
project('msoverlay', 'c',
version : '1.24.3',
license : 'LGPL',
default_options: [
'prefix=/usr',
'warning_level=2',
'buildtype=debugoptimized'
])
gst_dep = dependency('gstreamer-1.0')
gstbase_dep = dependency('gstreamer-base-1.0')
gstvideo_dep = dependency('gstreamer-video-1.0')
pango_dep = dependency('pangocairo', version : '>=1.22.0', required : true,
fallback: ['pango', 'libpangocairo_dep'],
default_options: ['cairo=enabled'])
cc = meson.get_compiler('c')
m_dep = cc.find_library('m')
gst_version = gst_dep.version()
version_arr = gst_version.split('.')
gst_version_major = version_arr[0].to_int()
gst_version_minor = version_arr[1].to_int()
gst_version_micro = version_arr[2].to_int()
api_version = '1.0'
soversion = 0
libversion = '@0@.@1@.0'.format(soversion, gst_version_minor * 100 + gst_version_micro)
plugins_install_dir = join_paths(get_option('libdir'), 'gstreamer-1.0')
plugin_c_args = ['-DHAVE_CONFIG_H']
cdata = configuration_data()
cdata.set_quoted('VERSION', gst_version)
cdata.set_quoted('PACKAGE', 'msoverlay')
cdata.set_quoted('PACKAGE_VERSION', gst_version)
cdata.set_quoted('PACKAGE_NAME', 'GStreamer ms overlay')
cdata.set_quoted('GST_PACKAGE_NAME', 'GStreamer ms overlay')
cdata.set_quoted('GST_PACKAGE_ORIGIN', 'https://gstreamer.freedesktop.org')
cdata.set_quoted('GST_API_VERSION', api_version)
cdata.set_quoted('GST_LICENSE', 'LGPL')
cdata.set_quoted('LIBDIR', join_paths(get_option('prefix'), get_option('libdir')))
gst_root = get_option('gst_root')
plugin_sources = files(
'src/gstmsoverlay.c',
'include/gstmsoverlay.h',
join_paths(gst_root, 'subprojects/gst-plugins-base/ext/pango/gstbasetextoverlay.c'),
join_paths(gst_root, 'subprojects/gst-plugins-base/ext/pango/gstpangoelement.c')
)
install_dir = join_paths(get_option('libdir'), 'gstreamer-1.0')
include_dirs = include_directories(join_paths(gst_root, 'subprojects/gst-plugins-base/ext/pango/'), 'include')
msoverlay = library(
'msoverlay',
plugin_sources,
c_args: plugin_c_args,
include_directories: include_dirs,
dependencies: [gst_dep, gstbase_dep, gstvideo_dep, pango_dep, m_dep],
install: true,
install_dir: plugins_install_dir
)
configure_file(output : 'config.h', configuration : cdata)

13
meson_options.txt Normal file
View File

@@ -0,0 +1,13 @@
# Common feature options
option('gobject-cast-checks', type : 'feature', value : 'auto', yield : true,
description: 'Enable run-time GObject cast checks (auto = enabled for development, disabled for stable releases)')
option('glib-asserts', type : 'feature', value : 'enabled', yield : true,
description: 'Enable GLib assertion (auto = enabled for development, disabled for stable releases)')
option('package-name', type : 'string', yield : true,
description : 'package name to use in plugins')
option('package-origin', type : 'string', value : 'Unknown package origin', yield : true,
description : 'package origin URL to use in plugins')
option('gst_root', type: 'string', value: './gst_root', description: 'Root path for GStreamer build')

276
src/gstmsoverlay.c Normal file
View File

@@ -0,0 +1,276 @@
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2005> Tim-Philipp Müller <tim@centricular.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-msoverlay
* @title: msoverlay
* @see_also: #GstBaseTextOverlay, #GstTimeOverlay
*
* This element overlays the Linux epoch time on top of a video
* stream. You can position the text and configure the font details
* using its properties.
*
* By default, the time is displayed in the top left corner of the picture, with some
* padding to the left and to the top.
*
* ## Example launch lines
* |[
* gst-launch-1.0 -v videotestsrc ! msoverlay ! autovideosink
* ]|
* Display the current wall clock time in the top left corner of the video picture
* |[
* gst-launch-1.0 -v videotestsrc ! msoverlay halignment=right valignment=bottom font-desc="Sans, 36" ! videoconvert ! autovideosink
* ]|
* Another pipeline that displays the epoch time with some leading
* text in the bottom right corner of the video picture, with the background
* of the text being shaded in order to make it more legible on top of a
* bright video background.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/video/video.h>
// #include <time.h>
#include <sys/time.h>
#include "gstmsoverlay.h"
#include "gstpangoelements.h"
#define DEFAULT_PROP_TIMEFORMAT "%S"
enum
{
PROP_0,
// PROP_TIMEFORMAT,
PROP_LAST
};
#define gst_ms_overlay_parent_class parent_class
G_DEFINE_TYPE (GstMsOverlay, gst_ms_overlay, GST_TYPE_BASE_TEXT_OVERLAY);
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (msoverlay, "msoverlay",
GST_RANK_NONE, GST_TYPE_MS_OVERLAY, pango_element_init (plugin));
static void gst_ms_overlay_finalize (GObject * object);
static void gst_ms_overlay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_ms_overlay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gchar *
gst_ms_overlay_render_time (GstMsOverlay * overlay)
{
struct timeval tv;
if (gettimeofday(&tv, NULL) != 0) {
return g_strdup("XXX.XXXXXX");
}
gchar buf[256];
int written = g_snprintf(buf, sizeof(buf), "%ld.%06ld", tv.tv_sec, tv.tv_usec);
if (written <= 0 || written >= sizeof(buf)) {
return g_strdup("YYY.YYYYYY");
}
return g_strdup (buf);
}
/* Called with lock held */
static gchar *
gst_ms_overlay_get_text (GstBaseTextOverlay * overlay,
GstBuffer * video_frame)
{
gchar *time_str, *txt, *ret;
GstMsOverlay *ms_overlay = GST_MS_OVERLAY (overlay);
txt = g_strdup (overlay->default_text);
GST_OBJECT_LOCK (overlay);
time_str = gst_ms_overlay_render_time (ms_overlay);
GST_OBJECT_UNLOCK (overlay);
if (txt != NULL && *txt != '\0') {
ret = g_strdup_printf ("%s %s", txt, time_str);
} else {
ret = time_str;
time_str = NULL;
}
if (g_strcmp0 (ret, ms_overlay->text)) {
overlay->need_render = TRUE;
g_free (ms_overlay->text);
ms_overlay->text = g_strdup (ret);
}
g_free (txt);
g_free (time_str);
return ret;
}
static void
gst_ms_overlay_class_init (GstMsOverlayClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBaseTextOverlayClass *gsttextoverlay_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gsttextoverlay_class = (GstBaseTextOverlayClass *) klass;
gobject_class->finalize = gst_ms_overlay_finalize;
gobject_class->set_property = gst_ms_overlay_set_property;
gobject_class->get_property = gst_ms_overlay_get_property;
gst_element_class_set_static_metadata (gstelement_class, "Millisecond overlay",
"Filter/Editor/Video",
"Overlays the current timestamp in ms on a video stream",
"Stefan Hamminga <stefan@rbts.co>");
gsttextoverlay_class->get_text = gst_ms_overlay_get_text;
// g_object_class_install_property (gobject_class, PROP_TIMEFORMAT,
// g_param_spec_string ("time-format", "Date/Time Format",
// "Format to use for time and date value, as in strftime.",
// DEFAULT_PROP_TIMEFORMAT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_ms_overlay_finalize (GObject * object)
{
GstMsOverlay *overlay = GST_MS_OVERLAY (object);
g_free (overlay->format);
g_free (overlay->text);
overlay->format = NULL;
g_free (overlay->wformat);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_ms_overlay_init (GstMsOverlay * overlay)
{
GstBaseTextOverlay *textoverlay;
PangoContext *context;
PangoFontDescription *font_description;
textoverlay = GST_BASE_TEXT_OVERLAY (overlay);
textoverlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
textoverlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT;
overlay->format = g_strdup (DEFAULT_PROP_TIMEFORMAT);
#ifdef G_OS_WIN32
overlay->wformat =
g_utf8_to_utf16 (DEFAULT_PROP_TIMEFORMAT, -1, NULL, NULL, NULL);
#endif
context = textoverlay->pango_context;
pango_context_set_language (context, pango_language_from_string ("en_US"));
pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
font_description = pango_font_description_new ();
pango_font_description_set_family_static (font_description, "Monospace");
pango_font_description_set_style (font_description, PANGO_STYLE_NORMAL);
pango_font_description_set_variant (font_description, PANGO_VARIANT_NORMAL);
pango_font_description_set_weight (font_description, PANGO_WEIGHT_NORMAL);
pango_font_description_set_stretch (font_description, PANGO_STRETCH_NORMAL);
pango_font_description_set_size (font_description, 18 * PANGO_SCALE);
pango_context_set_font_description (context, font_description);
pango_font_description_free (font_description);
}
static void
gst_ms_overlay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMsOverlay *overlay = GST_MS_OVERLAY (object);
GST_OBJECT_LOCK (overlay);
switch (prop_id) {
// case PROP_TIMEFORMAT:
// g_free (overlay->format);
// overlay->format = g_value_dup_string (value);
// if (!overlay->format)
// overlay->format = g_strdup (DEFAULT_PROP_TIMEFORMAT);
// #ifdef G_OS_WIN32
// g_free (overlay->wformat);
// overlay->wformat =
// g_utf8_to_utf16 (overlay->format, -1, NULL, NULL, NULL);
// #endif
// break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_OBJECT_UNLOCK (overlay);
}
static void
gst_ms_overlay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMsOverlay *overlay = GST_MS_OVERLAY (object);
GST_OBJECT_LOCK (overlay);
switch (prop_id) {
// case PROP_TIMEFORMAT:
// g_value_set_string (value, overlay->format);
// break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_OBJECT_UNLOCK (overlay);
}
static gboolean
plugin_init (GstPlugin * plugin)
{
return GST_ELEMENT_REGISTER (msoverlay, plugin);
}
GST_PLUGIN_DEFINE (
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
msoverlay,
PACKAGE_NAME,
plugin_init,
VERSION,
GST_LICENSE,
PACKAGE,
GST_PACKAGE_ORIGIN
)