DeepStream SDK FAQ

31.[DSx_All_App] Use nvurisrcbin plugin to do smart record in Python

31.1 Apply the following patch and rebuild the pyds

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_FREE_EXISTING(NvDsSRRecordingInfo, filename))
+                .def_property("dirpath", STRING_FREE_EXISTING(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);
     }
 }

31.2 Build and reinstall the pyds-xxxx-py3-none-linux_xxxx.whl

# Please make sure you have compiled and installed pyds correctly. 
# If the directory does not exist, please refer to sources/deepstream_python_apps/apps/README
cd /opt/nvidia/deepstream/deepstream/sources/deepstream_python_apps/bindings/build

# rebuild the pyds whl
make

# uninstall old version
pip uninstall pyds

# Reinstall the modified version
pip install ./pyds-xxxx-py3-none-linux_xxxx.whl

31.3 Sample code based on deeptream-test1.py
The nvurisrcbin plugin wraps the smart recording feature.

diff --git a/apps/deepstream-test1/deepstream_test_1.py b/apps/deepstream-test1/deepstream_test_1.py
index 1367fb4..ff913fe 100755
--- a/apps/deepstream-test1/deepstream_test_1.py
+++ b/apps/deepstream-test1/deepstream_test_1.py
@@ -25,6 +25,7 @@ gi.require_version('Gst', '1.0')
 from gi.repository import GLib, Gst
 from common.platform_info import PlatformInfo
 from common.bus_call import bus_call
+import ctypes
 
 import pyds
 
@@ -110,15 +111,62 @@ def osd_sink_pad_buffer_probe(pad,info,u_data):
         # set(red, green, blue, alpha); set to Black
         py_nvosd_text_params.text_bg_clr.set(0.0, 0.0, 0.0, 1.0)
         # Using pyds.get_string() to get display_text as string
-        print(pyds.get_string(py_nvosd_text_params.display_text))
+        # print(pyds.get_string(py_nvosd_text_params.display_text))
         pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta)
         try:
             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('******sr done*****')
+    print(f"record_done nvurisrcbin {nvurisrcbin} recordingInfo {recordingInfo}"
+          f" 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
@@ -126,6 +174,7 @@ def main(args):
         sys.stderr.write("usage: %s <media file or uri>\n" % args[0])
         sys.exit(1)
 
+    uri = args[1]
     platform_info = PlatformInfo()
     # Standard GStreamer initialization
     Gst.init(None)
@@ -138,30 +187,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 +240,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 +249,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 +260,7 @@ 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,37 @@ 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}")
+
+    # stop smart record manually, optional
+    # GLib.timeout_add(5000, stop_record_callback)
+    def start_sr_function():
+        nvurisrcbin.emit('start-sr', sessionid, 2, 5, sr_user_context_buf)
+        print('******start sr*****')
+        return True
+    timer_id = GLib.timeout_add_seconds(10, start_sr_function)
+
     try:
         loop.run()
     except:
         pass
     # cleanup
     pipeline.set_state(Gst.State.NULL)
+    # release native memory
+    pyds.free_gbuffer(sessionid)
+    pyds.free_gbuffer(sr_user_context_buf)
 
 if __name__ == '__main__':
     sys.exit(main(sys.argv))

31.4 Below is an explanation of the behavior of the sample code

  1. Set parameters for smart record
    After creating the nvurisrcbin element, you need to set smart record parameters before starting pipeline. The
    explanation of the parameters can be found in nvurisrcbin.html.

    # For more parameters, please run gst-inspect-1.0 nvurisrcbin to check.
    nvurisrcbin.set_property("smart-record", 2) # enable smart record.
    nvurisrcbin.set_property("smart-rec-dir-path", ".") # set record path.
    
  2. Send a start record signal

    # The sessionid must be of gpointer type, so it needs to be created using native code(pyds). 
    # This value is currently a reserved value and the default is 0.
    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"
    
    # The user context parameter is optional. 
    # If not needed, set it to None. Similarly, the type of this parameter is gpointer type and needs to be created 
    # using pyds native code. Please refer to the patch above.
    nvurisrcbin.emit('start-sr', sessionid, 2, 7, sr_user_context_buf)
    
  3. Send a stop record signal(Optional)

    # Currently session id can only be set to 0
    nvurisrcbin.emit('stop-sr', 0)
    
  4. Wait the sr-done action (Optional)
    Here is the documentation of NvDsSRRecordingInfo

    nvurisrcbin.connect("sr-done", record_done, pipeline)
    

related topic:
[Smart recording on Azure IOTEDGE]
[Smart record in Python]
[Smart Record in python]
[Using smart-record in python]