Nvurisrcbin smart record It's happening for single source for multiple source in same pipeline first source output is coming

Please provide complete information as applicable to your setup.

**• Hardware Platform --------> GPU
**• DeepStream Version ---------> 7.0
• TensorRT Version -----------> 8.5
**• NVIDIA GPU Driver Version -----> 555

I am trying use nvurisrcbin for a feature in our code. I able to run for one camera, and saving the video,

  1. But for multiple stream :-
  • a. Lets say i have 2 source how do i specify which sourceId to record from.
  • b. How can we give record videos from multiple streams at same time with different duration and configuration
  1. And also I want to understand how I can use this in python for every stream (Smart Video Record — DeepStream documentation 6.4 documentation)
  • NvDsSRStatus
  • NvDsSRCreate
  • NvDsSRStart
  • NvDsSRStop
  • NvDsSRDestroy

my previous topic :- Nvurisrcbin with smart recording feature and how to use it?
Also went through this - Few questions regarding Smart Recording

code :-
smart_record.txt (17.6 KB)

@snehashish.debnath
@ junshengy

Similar questions have been answered

1.For Nvurisrcbin in Python,

ele.set_property("smart-record", 2) # enable smart record.
ele.set_property("smart-rec-dir-path", ".") # set record path.
.....

equivalent to native NvDsSRCreate.

ele.emit('start-sr', a, 2, 7, None)

equivalent to native NvDsSRStart,The following parameters correspond to native one by one

ele.emit('stop-sr', 0)

equivalent to native NvDsSRStop.

When nvurisrcbin exits, NvDsSRDestroy is automatically called.

Set parameters for each nvurisrcbin

ele.emit('stop-sr', 0)

for multiple stream will it by default always set to 0 , even for multiple streams

For multiple streams, you can create multiple nvurisrcbin instances. The sessionId can currently only be 0.

Hi,

I have a signal emission that looks like this:

ele.emit(‘start-sr’, a, 2, 7, None)

Where the start-sr signal has four parameters: gpointer sessionId, guint startTime, guint duration, and gpointer userData. In Python, I understand that a capsule object can be used to pass a gpointer. For example, in the code above, a is initialized as:

a = pyds.alloc_buffer1(4)

I have a few questions regarding this:

How exactly does the startTime parameter (which is set to 2 here) work? Can it accept negative values (e.g., -2) to represent going backwards in time?
Why is the userData parameter set to None? Is there a specific reason for not passing additional data here?

Any insights you could provide would be greatly appreciated!

Thank you in advance.

Here startTime specifies the seconds before the current time and duration specifies the seconds after the start of recording. 

If current time is t1, content from t1 - startTime to t1 + duration will be saved to file. 

Therefore a total of startTime + duration seconds of data will be recorded.

Negative values ​​are not accepted. More information refer to NVIDIA DeepStream SDK API Reference: Smart Record | NVIDIA Docs

These are some limitations of the python code. You can use the following code to get the signature of start-sr

signal_info = GObject.signal_query('start-sr', nvurisrcbin)
print(f"signal_info {signal_info.param_types}")
# the result is
# signal_info (<GType gpointer (68)>, <GType guint (28)>, <GType guint (28)>, <GType gpointer (68)>)

However, it is not possible to create an object of type <GType gpointer> directly from Python, and native code help is needed.

2 Likes

@junshengy Thank you

If I want to use “sr-done” as a signal How can I use it ?
Could you share some example of it ?

Apply this patch,then rebuild and reinstall the pyds*.whl.

diff --git a/bindings/src/bindfunctions.cpp b/bindings/src/bindfunctions.cpp
index eade8d5..7a4b459 100644
--- a/bindings/src/bindfunctions.cpp
+++ b/bindings/src/bindfunctions.cpp
@@ -586,7 +586,7 @@ namespace pydeepstream {
                  std::function<utils::RELEASEFUNC_SIG> const &func) {
                   utils::set_freefunc(meta, func);
               },
-             py::call_guard<py::gil_scoped_release>(),
+              py::call_guard<py::gil_scoped_release>(),
               "meta"_a,
               "func"_a,
               pydsdoc::methodsDoc::user_releasefunc);
@@ -601,10 +601,9 @@ namespace pydeepstream {
         // Required for backward compatibility
         m.def("unset_callback_funcs",
               []() {
-            utils::release_all_func();
-        },
-              py::call_guard<py::gil_scoped_release>()
-       );
+                  utils::release_all_func();
+              },
+              py::call_guard<py::gil_scoped_release>());
 
         m.def("alloc_char_buffer",
               [](size_t size) {
@@ -650,6 +649,14 @@ namespace pydeepstream {
               py::return_value_policy::reference,
               pydsdoc::methodsDoc::get_ptr);
 
+        m.def("get_native_ptr",
+              [](size_t ptr) {
+                  return (gpointer) ptr;
+              },
+              "ptr"_a,
+              py::return_value_policy::reference,
+              pydsdoc::methodsDoc::get_ptr);
+
         m.def("strdup",
               [](size_t ptr) -> size_t {
                   char *str = (char *) ptr;
diff --git a/bindings/src/utils.cpp b/bindings/src/utils.cpp
index 9f97794..009314a 100644
--- a/bindings/src/utils.cpp
+++ b/bindings/src/utils.cpp
@@ -28,6 +28,7 @@
 #include "nvdsmeta_schema.h"
 #include "utils.hpp"
 #include "nvds_obj_encode.h"
+#include "gst-nvdssr.h"
 #include "bind_string_property_definitions.h"
 #include "../../docstrings/utilsdoc.h"
 
@@ -103,6 +104,48 @@ namespace pydeepstream {
                      },
                      py::return_value_policy::reference,
                      pydsdoc::utilsdoc::NvDsObjEncUsrArgsDoc::cast);
+
+        py::enum_<NvDsSRContainerType>(m, "NvDsSRContainerType")
+                .value("NVDSSR_CONTAINER_MP4", NVDSSR_CONTAINER_MP4)
+                .value("NVDSSR_CONTAINER_MKV", NVDSSR_CONTAINER_MKV)
+                .export_values();
+
+        py::class_<NvDsSRRecordingInfo>(m, "NvDsSRRecordingInfo")
+                .def(py::init<>())
+                .def_property("filename", STRING_CHAR_ARRAY(NvDsSRRecordingInfo, filename))
+                .def_property("dirpath", STRING_CHAR_ARRAY(NvDsSRRecordingInfo, dirpath))
+                .def_readwrite("duration", &NvDsSRRecordingInfo::duration)
+                .def_readwrite("containerType", &NvDsSRRecordingInfo::containerType)
+                .def_readwrite("width", &NvDsSRRecordingInfo::width)
+                .def_readwrite("height", &NvDsSRRecordingInfo::height)
+                .def_readwrite("containsVideo", &NvDsSRRecordingInfo::containsVideo)
+                .def_readwrite("channels", &NvDsSRRecordingInfo::channels)
+                .def_readwrite("samplingRate", &NvDsSRRecordingInfo::samplingRate)
+                .def_readwrite("containsAudio", &NvDsSRRecordingInfo::containsAudio)
+                .def("cast", [](size_t data) {
+                        return (NvDsSRRecordingInfo *) data;
+                     },
+                     py::return_value_policy::reference)
+                .def("cast", [](void* data) {
+                        return (NvDsSRRecordingInfo *) data;
+                     },
+                     py::return_value_policy::reference);
+        struct SRUserContext {
+            int sessionid;
+            char name[32];
+        };
+        py::class_<SRUserContext>(m, "SRUserContext")
+                .def(py::init<>())
+                .def_readwrite("sessionid", &SRUserContext::sessionid)
+                .def_property("name", STRING_CHAR_ARRAY(SRUserContext, name))
+                .def("cast", [](size_t data) {
+                        return (SRUserContext *) data;
+                     },
+                     py::return_value_policy::reference)
+                .def("cast", [](void* data) {
+                        return (SRUserContext *) data;
+                     },
+                     py::return_value_policy::reference);
     }
 }

Sample code modify from deepstream_test_1.py

diff --git a/apps/deepstream-test1/deepstream_test_1.py b/apps/deepstream-test1/deepstream_test_1.py
index 1367fb4..6070649 100755
--- a/apps/deepstream-test1/deepstream_test_1.py
+++ b/apps/deepstream-test1/deepstream_test_1.py
@@ -23,8 +23,10 @@ import os
 import gi
 gi.require_version('Gst', '1.0')
 from gi.repository import GLib, Gst
+from gi.repository import GObject
 from common.platform_info import PlatformInfo
 from common.bus_call import bus_call
+import ctypes
 
 import pyds
 
@@ -116,9 +118,56 @@ def osd_sink_pad_buffer_probe(pad,info,u_data):
             l_frame=l_frame.next
         except StopIteration:
             break
-                       
+
     return Gst.PadProbeReturn.OK       
 
+nvurisrcbin = None
+
+def stop_record_callback():
+    global nvurisrcbin
+    nvurisrcbin.emit('stop-sr', 0)
+
+# keep same as native for get size of native struct
+class SRUserContext(ctypes.Structure):
+    _fields_ = [
+        ("sessionid", ctypes.c_int),
+        ("name", ctypes.c_char * 32)
+    ]
+
+# GstElement * src, NvDsSRRecordingInfo* recordingInfo, void* data, void* user_data
+def record_done(nvurisrcbin, recordingInfo, user_ctx, user_data):
+    print(f"record_done nvurisrcbin {nvurisrcbin} recordingInfo {recordingInfo} \
+            user_ctx {user_ctx} user_data {user_data}")
+    info = pyds.NvDsSRRecordingInfo.cast(hash(recordingInfo))
+    sr = pyds.SRUserContext.cast(hash(user_ctx))
+    print(f"filename {info.filename} -- dir {info.dirpath} {info.width} x {info.height}")
+    print(f"session id {sr.sessionid} -- name {sr.name}")
+
+
+def cb_newpad(decodebin, decoder_src_pad, data):
+    print("In cb_newpad\n")
+    caps=decoder_src_pad.get_current_caps()
+    if not caps:
+        caps = decoder_src_pad.query_caps()
+    gststruct=caps.get_structure(0)
+    gstname=gststruct.get_name()
+    source_bin=data
+    features=caps.get_features(0)
+
+    # Need to check if the pad created by the decodebin is for video and not
+    # audio.
+    print("gstname=",gstname)
+    if(gstname.find("video")!=-1):
+        # Link the decodebin pad only if decodebin has picked nvidia
+        # decoder plugin nvdec_*. We do this by checking if the pad caps contain
+        # NVMM memory features.
+        print("features=",features)
+        if features.contains("memory:NVMM"):
+            # Get the source bin ghost pad
+            sink_pad=source_bin.request_pad_simple("sink_0")
+            ret = decoder_src_pad.link(sink_pad)
+        else:
+            sys.stderr.write(" Error: Decodebin did not pick nvidia decoder plugin.\n")
 
 def main(args):
     # Check input arguments
@@ -130,6 +179,7 @@ def main(args):
     # Standard GStreamer initialization
     Gst.init(None)
 
+    uri = args[1]
     # Create gstreamer elements
     # Create Pipeline element that will form a connection of other elements
     print("Creating Pipeline \n ")
@@ -138,30 +188,25 @@ def main(args):
     if not pipeline:
         sys.stderr.write(" Unable to create Pipeline \n")
 
-    # Source element for reading from the file
-    print("Creating Source \n ")
-    source = Gst.ElementFactory.make("filesrc", "file-source")
-    if not source:
-        sys.stderr.write(" Unable to create Source \n")
-
-    # Since the data format in the input file is elementary h264 stream,
-    # we need a h264parser
-    print("Creating H264Parser \n")
-    h264parser = Gst.ElementFactory.make("h264parse", "h264-parser")
-    if not h264parser:
-        sys.stderr.write(" Unable to create h264 parser \n")
-
-    # Use nvdec_h264 for hardware accelerated decode on GPU
-    print("Creating Decoder \n")
-    decoder = Gst.ElementFactory.make("nvv4l2decoder", "nvv4l2-decoder")
-    if not decoder:
-        sys.stderr.write(" Unable to create Nvv4l2 Decoder \n")
+    global nvurisrcbin
+    nvurisrcbin = Gst.ElementFactory.make("nvurisrcbin", "uri-decode-bin")
+    if not nvurisrcbin:
+        sys.stderr.write(" Unable to create nvurisrcbin \n")
+
+    nvurisrcbin.set_property("smart-record", 2) # enable smart record.
+    nvurisrcbin.set_property("smart-rec-dir-path", ".") # set record path.
 
     # Create nvstreammux instance to form batches from one or more sources.
     streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer")
     if not streammux:
         sys.stderr.write(" Unable to create NvStreamMux \n")
 
+    nvurisrcbin.set_property("uri", uri)
+    # Connect to the "pad-added" signal of the decodebin which generates a
+    # callback once a new pad for raw data has beed created by the decodebin
+    nvurisrcbin.connect("pad-added", cb_newpad, streammux)
+    nvurisrcbin.connect("sr-done", record_done, pipeline)
+
     # Use nvinfer to run inferencing on decoder's output,
     # behaviour of inferencing is set through config file
     pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
@@ -196,7 +241,6 @@ def main(args):
             sys.stderr.write(" Unable to create egl sink \n")
 
     print("Playing file %s " %args[1])
-    source.set_property('location', args[1])
     if os.environ.get('USE_NEW_NVSTREAMMUX') != 'yes': # Only set these properties if not using new gst-nvstreammux
         streammux.set_property('width', 1920)
         streammux.set_property('height', 1080)
@@ -206,9 +250,7 @@ def main(args):
     pgie.set_property('config-file-path', "dstest1_pgie_config.txt")
 
     print("Adding elements to Pipeline \n")
-    pipeline.add(source)
-    pipeline.add(h264parser)
-    pipeline.add(decoder)
+    pipeline.add(nvurisrcbin)
     pipeline.add(streammux)
     pipeline.add(pgie)
     pipeline.add(nvvidconv)
@@ -219,16 +261,6 @@ def main(args):
     # file-source -> h264-parser -> nvh264-decoder ->
     # nvinfer -> nvvidconv -> nvosd -> video-renderer
     print("Linking elements in the Pipeline \n")
-    source.link(h264parser)
-    h264parser.link(decoder)
-
-    sinkpad = streammux.request_pad_simple("sink_0")
-    if not sinkpad:
-        sys.stderr.write(" Unable to get the sink pad of streammux \n")
-    srcpad = decoder.get_static_pad("src")
-    if not srcpad:
-        sys.stderr.write(" Unable to get source pad of decoder \n")
-    srcpad.link(sinkpad)
     streammux.link(pgie)
     pgie.link(nvvidconv)
     nvvidconv.link(nvosd)
@@ -252,12 +284,30 @@ def main(args):
     # start play back and listen to events
     print("Starting pipeline \n")
     pipeline.set_state(Gst.State.PLAYING)
+
+    sessionid = pyds.get_native_ptr(pyds.alloc_buffer(4))
+    print(f"sessionid {sessionid}")
+
+    sr_user_context_size = ctypes.sizeof(SRUserContext)
+    sr_user_context_buf = pyds.get_native_ptr(pyds.alloc_buffer(sr_user_context_size))
+    sr_user_context = pyds.SRUserContext.cast(sr_user_context_buf)
+    sr_user_context.sessionid = 42
+    sr_user_context.name = "sr-demo"
+    print(f"sr_user_context_buf {sr_user_context_buf}")
+
+    # signal_info = GObject.signal_query('start-sr', nvurisrcbin)
+    # print(f"signal_info {signal_info.param_types}")
+    nvurisrcbin.emit('start-sr', sessionid, 2, 7, sr_user_context_buf)
+    GLib.timeout_add(5000, stop_record_callback)
+
     try:
         loop.run()
     except:
         pass
     # cleanup
     pipeline.set_state(Gst.State.NULL)
+    pyds.free_gbuffer(sessionid)
+    pyds.free_gbuffer(sr_user_context_buf)
 
 if __name__ == '__main__':
     sys.exit(main(sys.argv))

Thanks @junshengy

I have some more query !

  1. How I can understand my recording got stopped and saved .So, I can request another recording !!
  2. Through “smart-rec-cache” this property we are setting to 30sec. Past 30sec we can create video out of it, If I want past 20 to 30sec video can I mention that ?
    ele.emit(‘start-sr’, a, 5, 5, None)
    Here I will receive 10 sec of video (5sec backwards and 5sec forwards )

If I want to get past 20 to 30 (10sec) video How I can give it here ?
ele.emit(‘start-sr’, a, 30, -20, None)
“-” sign can work ? or Can I achieve it ?

Also one more question is If I gave “smart-rec-cache” for every camera 0 vs 30 vs 50 vs 100
I am getting any changes in GPU memory ----> does it last that much sec frame in a queue(internally ) ?
Or How much sec backward I can go ?

For additional recording emit additional start-sr/stop-sr signals to nvurisrcbin

Negative values ​​are not accepted

The backward time is usually the value of Smart-rec-cache, but this value is also related to the interval between I-frames.

diff --git a/bindings/src/bindfunctions.cpp b/bindings/src/bindfunctions.cpp
index eade8d5..7a4b459 100644
--- a/bindings/src/bindfunctions.cpp
+++ b/bindings/src/bindfunctions.cpp
@@ -586,7 +586,7 @@ namespace pydeepstream {
                  std::function<utils::RELEASEFUNC_SIG> const &func) {
                   utils::set_freefunc(meta, func);
               },
-             py::call_guard<py::gil_scoped_release>(),
+              py::call_guard<py::gil_scoped_release>(),
               "meta"_a,
               "func"_a,
               pydsdoc::methodsDoc::user_releasefunc);
@@ -601,10 +601,9 @@ namespace pydeepstream {
         // Required for backward compatibility
         m.def("unset_callback_funcs",
               []() {
-            utils::release_all_func();
-        },
-              py::call_guard<py::gil_scoped_release>()
-       );
+                  utils::release_all_func();
+              },
+              py::call_guard<py::gil_scoped_release>());
 
         m.def("alloc_char_buffer",
               [](size_t size) {
@@ -650,6 +649,14 @@ namespace pydeepstream {
               py::return_value_policy::reference,
               pydsdoc::methodsDoc::get_ptr);
 
+        m.def("get_native_ptr",
+              [](size_t ptr) {
+                  return (gpointer) ptr;
+              },
+              "ptr"_a,
+              py::return_value_policy::reference,
+              pydsdoc::methodsDoc::get_ptr);
+
         m.def("strdup",
               [](size_t ptr) -> size_t {
                   char *str = (char *) ptr;
diff --git a/bindings/src/utils.cpp b/bindings/src/utils.cpp
index 9f97794..009314a 100644
--- a/bindings/src/utils.cpp
+++ b/bindings/src/utils.cpp
@@ -28,6 +28,7 @@
 #include "nvdsmeta_schema.h"
 #include "utils.hpp"
 #include "nvds_obj_encode.h"
+#include "gst-nvdssr.h"
 #include "bind_string_property_definitions.h"
 #include "../../docstrings/utilsdoc.h"
 
@@ -103,6 +104,48 @@ namespace pydeepstream {
                      },
                      py::return_value_policy::reference,
                      pydsdoc::utilsdoc::NvDsObjEncUsrArgsDoc::cast);
+
+        py::enum_<NvDsSRContainerType>(m, "NvDsSRContainerType")
+                .value("NVDSSR_CONTAINER_MP4", NVDSSR_CONTAINER_MP4)
+                .value("NVDSSR_CONTAINER_MKV", NVDSSR_CONTAINER_MKV)
+                .export_values();
+
+        py::class_<NvDsSRRecordingInfo>(m, "NvDsSRRecordingInfo")
+                .def(py::init<>())
+                .def_property("filename", STRING_CHAR_ARRAY(NvDsSRRecordingInfo, filename))
+                .def_property("dirpath", STRING_CHAR_ARRAY(NvDsSRRecordingInfo, dirpath))
+                .def_readwrite("duration", &NvDsSRRecordingInfo::duration)
+                .def_readwrite("containerType", &NvDsSRRecordingInfo::containerType)
+                .def_readwrite("width", &NvDsSRRecordingInfo::width)
+                .def_readwrite("height", &NvDsSRRecordingInfo::height)
+                .def_readwrite("containsVideo", &NvDsSRRecordingInfo::containsVideo)
+                .def_readwrite("channels", &NvDsSRRecordingInfo::channels)
+                .def_readwrite("samplingRate", &NvDsSRRecordingInfo::samplingRate)
+                .def_readwrite("containsAudio", &NvDsSRRecordingInfo::containsAudio)
+                .def("cast", [](size_t data) {
+                        return (NvDsSRRecordingInfo *) data;
+                     },
+                     py::return_value_policy::reference)
+                .def("cast", [](void* data) {
+                        return (NvDsSRRecordingInfo *) data;
+                     },
+                     py::return_value_policy::reference);
+        struct SRUserContext {
+            int sessionid;
+            char name[32];
+        };
+        py::class_<SRUserContext>(m, "SRUserContext")
+                .def(py::init<>())
+                .def_readwrite("sessionid", &SRUserContext::sessionid)
+                .def_property("name", STRING_CHAR_ARRAY(SRUserContext, name))
+                .def("cast", [](size_t data) {
+                        return (SRUserContext *) data;
+                     },
+                     py::return_value_policy::reference)
+                .def("cast", [](void* data) {
+                        return (SRUserContext *) data;
+                     },
+                     py::return_value_policy::reference);
     }
 }

we are not able to locate this can you please cheak if it’s the same file

@junshengy Thank you so much for this part of the code

sr_user_context_size = ctypes.sizeof(SRUserContext)
+    sr_user_context_buf = pyds.get_native_ptr(pyds.alloc_buffer(sr_user_context_size))
+    sr_user_context = pyds.SRUserContext.cast(sr_user_context_buf)
+    sr_user_context.sessionid = 42
+    sr_user_context.name = "sr-demo"
+    print(f"sr_user_context_buf {sr_user_context_buf}")
+
+    # signal_info = GObject.signal_query('start-sr', nvurisrcbin)
+    # print(f"signal_info {signal_info.param_types}")
+    nvurisrcbin.emit('start-sr', sessionid, 2, 7, sr_user_context_buf)

but what I want is nvurisrcbin should emit

"sr-done" :  void user_function (GstElement* object,
                                   gpointer arg0,
                                   gpointer arg1,
                                   gpointer user_data);

Once the recording is done we don’t want to wait for 5 seconds then call the stop_record_callback as guint duration is a variable length, like we have multiple cameras and variable length video file we want

GLib.timeout_add(5000, stop_record_callback)

This is just to show the usage of the API.

In fact, if you do not call stop_record_callback,

the recording will end when the duration is reached, and then the record_done callback function will be triggered.

Sorry, I provided this patch based on a development branch that is not yet public, so there are some differences. You need to port this patch based on the DS-7.0 branch.