I ran another test to verify whether nvmultiurisrcbin
works properly. First, I created a simple pipeline using the following gst-launch-1.0
command that I had executed and tested before:
gst-launch-1.0 nvmultiurisrcbin port=9456 ip-address=localhost batched-push-timeout=33333 max-batch-size=10 drop-pipeline-eos=1 rtsp-reconnect-interval=1 live-source=1 width=1920 height=1080 ! nvmultistreamtiler ! fakesink async=false
Then, I reproduced the same logic in C++:
#include <gst/gst.h>
#include <iostream>
#include <string>
class PipelineManager {
private:
GstElement *pipeline;
GstElement *nvmultiurisrcbin;
GstElement *nvmultistreamtiler;
GstElement *fakesink;
GMainLoop *main_loop;
public:
PipelineManager()
: pipeline(nullptr),
nvmultiurisrcbin(nullptr),
nvmultistreamtiler(nullptr),
fakesink(nullptr),
main_loop(nullptr) {}
~PipelineManager() { cleanup(); }
bool create_pipeline() {
// Initialize GStreamer
gst_init(nullptr, nullptr);
// Create elements
pipeline = gst_pipeline_new("multi-uri-pipeline");
if (!pipeline) {
std::cerr << "Failed to create pipeline" << std::endl;
return false;
}
nvmultiurisrcbin =
gst_element_factory_make("nvmultiurisrcbin", "nvmultiurisrcbin");
if (!nvmultiurisrcbin) {
std::cerr << "Failed to create nvmultiurisrcbin" << std::endl;
return false;
}
nvmultistreamtiler = gst_element_factory_make("nvmultistreamtiler",
"nvmultistreamtiler");
if (!nvmultistreamtiler) {
std::cerr << "Failed to create nvmultistreamtiler" << std::endl;
return false;
}
fakesink = gst_element_factory_make("fakesink", "fakesink");
if (!fakesink) {
std::cerr << "Failed to create fakesink" << std::endl;
return false;
}
// Add elements to pipeline
gst_bin_add_many(GST_BIN(pipeline), nvmultiurisrcbin,
nvmultistreamtiler, fakesink, nullptr);
// Set properties for nvmultiurisrcbin
g_object_set(G_OBJECT(nvmultiurisrcbin), "port", "9456", nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "ip-address", "localhost",
nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "batched-push-timeout", 33333,
nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "max-batch-size", 10, nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "drop-pipeline-eos", 1,
nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "rtsp-reconnect-interval", 1,
nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "live-source", 1, nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "width", 1920, nullptr);
g_object_set(G_OBJECT(nvmultiurisrcbin), "height", 1080, nullptr);
// Try setting a config file that enables REST API
// g_object_set(G_OBJECT(nvmultiurisrcbin), "config-file-path",
// "/etc/deepstream/rest_api.conf", NULL);
g_object_set(G_OBJECT(nvmultiurisrcbin), "uri-list",
"file:///root/Put.mp4", NULL);
// g_object_set(G_OBJECT(nvmultiurisrcbin), "rtsp-reconnect-attempts",
// 10, NULL);
g_object_set(G_OBJECT(nvmultiurisrcbin), "drop-frame-interval", 5,
NULL); // Skip frames if decoding lags behind.
g_object_set(G_OBJECT(nvmultiurisrcbin), "file-loop", FALSE, NULL);
g_object_set(G_OBJECT(nvmultiurisrcbin), "gpu-id", 0, NULL);
g_object_set(
G_OBJECT(nvmultiurisrcbin), "cudadec-memtype", 0,
NULL); // Memory type for CUDA decoding (0=default,
// 1=NVBUF_MEM_CUDA_PINNED, 2=NVBUF_MEM_CUDA_DEVICE,
// 3=NVBUF_MEM_CUDA_UNIFIED).
g_object_set(G_OBJECT(nvmultiurisrcbin), "latency", 200,
NULL); // Network jitter buffer latency (milliseconds).
// Used for RTSP.
g_object_set(G_OBJECT(nvmultiurisrcbin), "sensor-id-list",
"UniqueSensorId1", NULL);
g_object_set(G_OBJECT(nvmultiurisrcbin), "sensor-name-list",
"UniqueSensorName1", NULL);
g_object_set(G_OBJECT(nvmultiurisrcbin), "buffer-pool-size", 16, NULL);
g_object_set(G_OBJECT(nvmultiurisrcbin), "disable-audio", TRUE, NULL);
// Set properties for fakesink
g_object_set(G_OBJECT(fakesink), "async", FALSE, nullptr);
// Link elements
if (!gst_element_link_many(nvmultiurisrcbin, nvmultistreamtiler,
fakesink, nullptr)) {
std::cerr << "Failed to link elements" << std::endl;
return false;
}
return true;
}
bool start_pipeline() {
// Set pipeline to PLAYING state
GstStateChangeReturn ret =
gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
std::cerr << "Failed to start pipeline" << std::endl;
return false;
}
// Create and run main loop
main_loop = g_main_loop_new(nullptr, FALSE);
// Add bus watch for messages
GstBus *bus = gst_element_get_bus(pipeline);
gst_bus_add_watch(bus, bus_callback, this);
gst_object_unref(bus);
std::cout << "Pipeline started successfully" << std::endl;
std::cout << "REST server available at: http://localhost:9456"
<< std::endl;
g_main_loop_run(main_loop);
return true;
}
void stop_pipeline() {
if (pipeline) {
gst_element_set_state(pipeline, GST_STATE_NULL);
}
if (main_loop) {
g_main_loop_quit(main_loop);
}
}
void cleanup() {
if (pipeline) {
gst_object_unref(pipeline);
pipeline = nullptr;
}
if (main_loop) {
g_main_loop_unref(main_loop);
main_loop = nullptr;
}
}
private:
static gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data) {
(void)bus;
PipelineManager *self = static_cast<PipelineManager *>(data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR: {
gchar *debug;
GError *error;
gst_message_parse_error(msg, &error, &debug);
std::cerr << "Error: " << error->message << std::endl;
if (debug) {
std::cerr << "Debug info: " << debug << std::endl;
}
g_error_free(error);
g_free(debug);
self->stop_pipeline();
break;
}
case GST_MESSAGE_EOS:
std::cout << "End of stream" << std::endl;
self->stop_pipeline();
break;
case GST_MESSAGE_STATE_CHANGED: {
if (GST_MESSAGE_SRC(msg) == GST_OBJECT(self->pipeline)) {
GstState old_state, new_state, pending;
gst_message_parse_state_changed(msg, &old_state, &new_state,
&pending);
std::cout << "Pipeline state changed from "
<< gst_element_state_get_name(old_state) << " to "
<< gst_element_state_get_name(new_state)
<< std::endl;
}
break;
}
default:
break;
}
return TRUE;
}
};
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
PipelineManager pipeline_manager;
// Create pipeline
if (!pipeline_manager.create_pipeline()) {
std::cerr << "Failed to create pipeline" << std::endl;
return -1;
}
// Start pipeline
if (!pipeline_manager.start_pipeline()) {
std::cerr << "Failed to start pipeline" << std::endl;
return -1;
}
// Cleanup
pipeline_manager.cleanup();
return 0;
}
I tested adding another stream via curl:
curl -v -XPOST 'http://localhost:9456/api/v1/stream/add' -d '{
"key": "sensor",
"value": {
"camera_id": "uniqueSensorID2",
"camera_name": "front_door_1",
"camera_url": "file:///root/P.mp4",
"change": "camera_add",
"metadata": {
"resolution": "1920 x1080",
"codec": "h264",
"framerate": 30
}
},
"headers": {
"source": "vst",
"created_at": "2021-06-01T14:34:13.417Z"
}
}'
Program output:
Civetweb version: v1.16
Server running at port: 9456
Pipeline started successfully
REST server available at: http://localhost:9456
Pipeline state changed from NULL to READY
Pipeline state changed from READY to PAUSED
Failed to query video capabilities: Invalid argument
Pipeline state changed from PAUSED to PLAYING
uri:/api/v1/stream/add
method:POST
Failed to query video capabilities: Invalid argument
nvstreammux: Successfully handled EOS for source_id=1
nvstreammux: Successfully handled EOS for source_id=0
Curl response:
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:9456...
* Connected to localhost (127.0.0.1) port 9456 (#0)
> POST /api/v1/stream/add HTTP/1.1
> Host: localhost:9456
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Length: 427
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< Content-Type: text/plain
< Content-Length: 67
< Connection: close
<
{
"reason" : "STREAM_ADD_SUCCESS",
"status" : "HTTP/1.1 200 OK"
* Closing connection 0
This confirms that I was able to successfully add another stream.
As an additional note: in my main project I also use Prometheus for performance monitoring. Could that have any side effects on the behavior of nvmultiurisrcbin
or REST server?
Finally, here’s the relevant part of my main pipeline structure for context:
if (sink_manager->display_output < 3) {
gst_bin_add_many(GST_BIN(pipeline),
nv_infer_server_manager->primary_detector,
nv_tracker_manager->tracker,
face_nv_infer_server_manager->face_detector,
// gstds_example_manager->custom_plugin,
tiler_manager->tiler, queue_array[2].queue,
nv_video_convert_manager->nvvidconv, nv_osd_manager->nvosd,
sink_manager->sink, NULL);
/* we link the elements together
* nvstreammux -> nvinfer -> nvtiler -> nvvidconv -> nvosd ->
* video-renderer */
if (!gst_element_link_many( // streammux_manager->streammux,
SourceBin::nvmultiurisrcbin, nv_video_convert_manager->nvvidconv,
nv_infer_server_manager->primary_detector,
nv_tracker_manager->tracker,
face_nv_infer_server_manager->face_detector,
// gstds_example_manager->custom_plugin,
tiler_manager->tiler, nv_osd_manager->nvosd, sink_manager->sink,
NULL)) {
g_printerr("Elements could not be linked.\n");
return false;
}
} else {
gst_bin_add_many(
GST_BIN(pipeline), nv_infer_server_manager->primary_detector,
nv_tracker_manager->tracker,
face_nv_infer_server_manager->face_detector,
// gstds_example_manager->custom_plugin,
tiler_manager->tiler, queue_array[2].queue,
nv_video_convert_manager->nvvidconv, nv_osd_manager->nvosd,
sink_manager->nvvidconv_postosd, sink_manager->caps,
sink_manager->encoder, sink_manager->rtppay, sink_manager->sink, NULL);
// Link the elements together:
// file-source -> h264-parser -> nvh264-decoder ->
// nvinfer -> nvvidconv -> nvosd -> nvvidconv_postosd ->
// caps -> encoder -> rtppay -> udpsink
if (!gst_element_link_many( // streammux_manager->streammux,
SourceBin::nvmultiurisrcbin, nv_video_convert_manager->nvvidconv,
nv_infer_server_manager->primary_detector,
nv_tracker_manager->tracker,
face_nv_infer_server_manager->face_detector,
// gstds_example_manager->custom_plugin,
tiler_manager->tiler, nv_osd_manager->nvosd,
sink_manager->nvvidconv_postosd, sink_manager->caps,
sink_manager->encoder, sink_manager->rtppay, sink_manager->sink,
NULL)) {
g_printerr("Elements could not be linked.\n");
return false;
}
}