RTSP Server cleanup from thread does not stop client app's video

Upon stopping our rtsp server, the client’s video keeps playing.

Our GstRtspServer runs within a thread – we don’t want to start and stop processes. We exit the loop by calling g_main_loop_quit. Within that thread we also try to cleanup:

Hi,
For more information, is your server on Xavier and client on x86 PC? Or other cases?

Yes. Xavier and client is x86 gst-launch app.

gst-launch-1.0 rtspsrc location=rtsp://imaging:5100/camera latency=30 ! decodebin ! autovideosink

Hi,
Please launch the pipeline through test-launch and try again. A reference link:
https://devtalk.nvidia.com/default/topic/1018689/jetson-tx2/vlc-playing-gstreamer-flow/post/5187270/#5187270

We have verified this usecase on Xavier and it runs fine.

The test-lauch app is not quite the same scenario. I can modify it to catch a sigint and exit the loop then cleanup and then pause for user input – but not exit the application. I’ll do that and see if the client side video shuts down.

BTW – I also tried setting gst_rtsp_media_factory_set_eos_shutdown() to true – but that did not work either.

I created a test app based on test-launch.c which shows my problem:

#include
#include
#include
#include

#include <unistd.h>
#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>

/*
*The Player:
*

  • gst-launch-1.0 -v rtspsrc location=rtsp://imaging:5454/test ! decodebin ! autovideosink
    */

const char* PIPELINE_DESCRIPTION =
"( "
"videotestsrc is-live=1 ! "
"video/x-raw, width=1920, height=1080, framerate=(fraction)30/1 ! "
"omxh264enc insert-sps-pps=1 ! "
“rtph264pay name=pay0 timestamp-offset=0 pt=96 config-interval=1 "
" )”;

#define DEFAULT_RTSP_PORT “5454”

static char* port = static_cast<char*>(DEFAULT_RTSP_PORT);

static GOptionEntry entries = {
{
“port”, ‘p’, 0, G_OPTION_ARG_STRING, &port,
"Port to listen on (default: " DEFAULT_RTSP_PORT “)”, “PORT”
},
{NULL}
};

GMainLoop* loop = nullptr;

void mainSignalHandler(int32_t s)
{
if (nullptr == loop)
return;

if (g_main_loop_is_running(loop))
    g_main_loop_quit(loop);

}

int
main(int argc, char* argv)
{
GstRTSPServer* server;
GstRTSPMountPoints* mounts;
GstRTSPMediaFactory* factory;
GOptionContext* optctx;
GError* error = NULL;

std::signal(SIGINT, mainSignalHandler);

gst_init(&argc, &argv);

loop = g_main_loop_new(NULL, FALSE);

/* create a server instance */
server = gst_rtsp_server_new();
g_object_set(server, "service", port, NULL);

/* get the mount points for this server, every server has a default object
 * that be used to map uri mount points to media factories */
mounts = gst_rtsp_server_get_mount_points(server);

/* make a media factory for a test stream. The default media factory can use
 * gst-launch syntax to create pipelines.
 * any launch line works as long as it contains elements named pay%d. Each
 * element with pay%d names will be a stream */
factory = gst_rtsp_media_factory_new();
gst_rtsp_media_factory_set_launch(factory, PIPELINE_DESCRIPTION);
gst_rtsp_media_factory_set_shared(factory, TRUE);

/* attach the test factory to the /test url */
gst_rtsp_mount_points_add_factory(mounts, "/test", factory);

/* don't need the ref to the mapper anymore */
g_object_unref(mounts);

/* attach the server to the default maincontext */
const auto sourceID = gst_rtsp_server_attach(server, NULL);

/* start serving */
g_print("stream ready at rtsp://127.0.0.1:%s/test\n", port);
g_main_loop_run(loop);

// cleanup

g_source_remove(sourceID);
g_main_loop_unref(loop);
auto smounts = gst_rtsp_server_get_mount_points(server);
if (nullptr != smounts)
    gst_rtsp_mount_points_remove_factory(smounts, "/test");
g_object_unref(smounts);

while (0 < GST_OBJECT_REFCOUNT_VALUE(server))
{
    g_print("Ref Count: %u\n", GST_OBJECT_REFCOUNT_VALUE(server));
    g_object_unref(server);
}

g_print("Exited loop. Waiting for user input to terminate\n");

std::string input;
std::getline(std::cin, input);

return 0;

}

What’s not happening: The client player video is not stopping when the loop is exited and the rtsp stream is cleaned up. Steps to reproduce:

  1. Run test-launch
  2. Run gst-launch player
  3. View the video
  4. Ctrl-c the test-launch app
  5. Wait for the printed message that says the loop has exited and things have been cleaned up
  6. Notice that the video player is still working just fine
  7. If you do exit test-launch, then the video player does stop

I did forget to put in the media factory call to ask for an EOS on cleanup; however, I’ve tried that and it makes no difference.

Hi,
Could you try x264enc to check if the issue is specific to omxh264enc? It depends on many native gstreamer plugins to construct rtsp streaming. In the pipeline, hardware encoder omxh264enc is leveraged to get better performance. We would like to ensure it is something wrong in our plugin, making the trouble.

Sure. BTW - during cleanup the loop that unrefs the context shows that there is one outstanding reference. I tried to account for everything but there is an extra ref on it.

Using x264enc, the test app behaves exactly the same: video keeps playing on the client side even though we have quit the loop and cleanup on the thread.

Posting complete example:

/// @file test-launch.cpp
/// @brief modified from gstreamer example to show potential bug
/// where client video never gets an EOS after rtsp stream cleanup on thread

#include
#include
#include
#include <unistd.h>
#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>

/*
*The Player used to playback video from this rtsp server:
*

  • gst-launch-1.0 -v rtspsrc location=rtsp://imaging:5454/test ! decodebin ! autovideosink sync=false

*/

/* omx h264 encoder pipeline */

const char* PIPELINE_DESCRIPTION_OMX =
"( "
"videotestsrc is-live=1 ! "
"video/x-raw, width=1920, height=1080, framerate=(fraction)30/1 ! "
"omxh264enc insert-sps-pps=1 ! "
“rtph264pay name=pay0 timestamp-offset=0 pt=96 config-interval=1 "
" )”;

/* x264 encoder pipeline */

const char* PIPELINE_DESCRIPTION =
"( "
"videotestsrc is-live=1 ! "
"x264enc ! "
“rtph264pay name=pay0 pt=96 "
" )”;

/* defaut port */

#define DEFAULT_RTSP_PORT “5454”

/* global instance of loop so we can stop streaming and shutdown thread */

GMainLoop* loop = nullptr;

/* sigint handler to stop streaming and shutdown thread */

void mainSignalHandler(int32_t s)
{
if (nullptr == loop)
return;

if (g_main_loop_is_running(loop))
    g_main_loop_quit(loop);

}

/* Thread function which runs rtsp video server */

void streamVideo()
{
g_print(“Video stream running, press ctrl-c to quit main loop and stop streaming…\n”);

/* create main loop */

loop = g_main_loop_new(nullptr, FALSE);

/* create a server instance */

GstRTSPServer* server = gst_rtsp_server_new();
g_object_set(server, "service", DEFAULT_RTSP_PORT, nullptr);

/* get the mount points for this server, every server has a default object
 * that be used to map uri mount points to media factories */

GstRTSPMountPoints* mounts = gst_rtsp_server_get_mount_points(server);

/* make a media factory for a test stream. The default media factory can use
 * gst-launch syntax to create pipelines.
 * any launch line works as long as it contains elements named pay%d. Each
 * element with pay%d names will be a stream */

GstRTSPMediaFactory* factory = gst_rtsp_media_factory_new();
gst_rtsp_media_factory_set_launch(factory, PIPELINE_DESCRIPTION);
gst_rtsp_media_factory_set_shared(factory, TRUE);

/* attach the test factory to the /test url */

gst_rtsp_mount_points_add_factory(mounts, "/test", factory);

/* don't need the ref to the mapper anymore */

g_object_unref(mounts);

/* attach the server to the default maincontext */

const auto sourceID = gst_rtsp_server_attach(server, NULL);

/* start serving */

g_print("stream ready at rtsp://127.0.0.1:%s/test\n", DEFAULT_RTSP_PORT);
g_main_loop_run(loop);

// cleanup

g_source_remove(sourceID);
g_main_loop_unref(loop);

loop = nullptr;

auto smounts = gst_rtsp_server_get_mount_points(server);

if (nullptr != smounts)
    gst_rtsp_mount_points_remove_factory(smounts, "/test");

g_object_unref(smounts);

while (0 < GST_OBJECT_REFCOUNT_VALUE(server))
{
    g_print("Ref Count: %u\n", GST_OBJECT_REFCOUNT_VALUE(server));
    g_object_unref(server);
}

/* setup sigint handler to quit main loop */

std::signal(SIGINT, nullptr);

// TODO: find out why there is an extra ref on server object

g_print("Exiting thread...\n");

}

/// main application entry point
/// - initialize gstreamer, then run rtsp streams from thread
/// - shutdown rtsp stream when user presses ctrl-c (via sigint handler)

int main(int argc, char* argv)
{
/* initialize gstreamer */

gst_init(&argc, &argv);

/* run rtsp video server in a thread, detect ctrl-c, terminate thread, restart thread */

while (true)
{
    /* setup sigint handler to quit main loop */

    std::signal(SIGINT, mainSignalHandler);

    // start thread
    {
        std::thread t(streamVideo);

        t.join();
    }

    /* setup sigint handler to allow user to exit */

    std::signal(SIGINT, nullptr);

    g_print("Exited thread. Press enter to spawn another or ctrl-c to exit.\n");

    std::string input;

    std::getline(std::cin, input);
}

return 0;

}

Hi,
Would like to suggest you go to
http://gstreamer-devel.966125.n4.nabble.com/

It is more like a behavior in native gstreamer plugins. Developers on gstreamer forum can be more experienced to share guidance.

ok thanks.

Here is a sample app that runs the rtsp server in a thread, allows the rtsp server to be stopped and/or restarted. This is based upon test-launch.c and test-video-disconnect.c.

/// @file threaded-rtsp-server.cpp
/// @brief Example that shows one way to run an rtsp server from a thread
/// You can stop and restart the rtsp server

#include
#include
#include
#include
#include
#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>
#include

// To view the rtsp stream run this: gst-launch-1.0 -v rtspsrc location=rtsp://imaging:5454/test ! decodebin ! autovideosink sync=false

// define port and pipeline

const char* port = “5454”;

const char* pipeline =
"( "
"videotestsrc is-live=1 ! "
"x264enc ! "
“rtph264pay name=pay0 pt=96 "
" )”;

// globals

GstRTSPServer* g_server = nullptr;;
GMainLoop* g_loop = nullptr;
std::atomic<uint32_t> g_clientCount(0);

/// @brief Removes clients from the server, called by rtsp server

GstRTSPFilterResult clientFilter(GstRTSPServer* server, GstRTSPClient* client, gpointer user)
{
return GST_RTSP_FILTER_REMOVE;
}

/// @brief Closes clients, called by the app

void closeClients(GstRTSPServer* server)
{
g_print(“closing clients - count: %u…\n”, g_clientCount.load());

if (0 < g_clientCount.load())
	gst_rtsp_server_client_filter(server, clientFilter, nullptr);

}

/// @brief sigint handler that allows user to stop rtsp server by pressing ctrl-c

void signalHandler(int signal)
{
g_print(“SIGINT caught\n”);

if (nullptr != g_loop)
{
	g_print("quitting main loop\n");

	g_main_loop_quit(g_loop);
}

}

/// @brief tracks closed clients, called by an rtsp client when it closes

void clientClosed(GstRTSPClient* client, gpointer user)
{
g_print(“client closed - count: %u\n”, --g_clientCount);
}

/// @brief tracks connected clients, called by rtsp server

void clientConnected(GstRTSPServer* server, GstRTSPClient* client, gpointer user)
{
// hook the client close callback

g_signal_connect(client, "closed", reinterpret_cast<GCallback>(clientClosed), nullptr);

g_print("client-connected -- count: %u\n", ++g_clientCount);

}

/// @brief main entry point

int main(int argc, char* argv)
{
try
{
// setup sigint handler that allow user to close rtsp server app

	signal(SIGINT, signalHandler);

	// initialize gstreamer

	gst_init(&argc, &argv);

	// setup rtsp server

	g_loop = g_main_loop_new(nullptr, false);

	const auto factory = gst_rtsp_media_factory_new();

	gst_rtsp_media_factory_set_eos_shutdown(factory, true);
	gst_rtsp_media_factory_set_launch(factory, pipeline);
	gst_rtsp_media_factory_set_shared(factory, true);

	g_server = gst_rtsp_server_new();

	g_object_set(g_server, "service", port, nullptr);

	const auto mounts = gst_rtsp_server_get_mount_points(g_server);

	gst_rtsp_mount_points_add_factory(mounts, "/test", factory);

	g_object_unref(mounts);

	const auto sourceID = gst_rtsp_server_attach(g_server, nullptr);

	g_signal_connect(g_server, "client-connected", reinterpret_cast<GCallback>(clientConnected), nullptr);

	// loop until user wants to close app

	while (true)
	{
		// run the loop in a thread
		{
			std::thread t([]
			{
				g_print("stream ready at rtsp://127.0.0.1:%s/test\n", port);

				g_main_loop_run(g_loop);

				g_print("exiting thread...\n");
			});

			t.join();

			g_print("thread exited\n");
		}

		// close clients

		if (0 < g_clientCount.load())
		{
			g_print("closing clients\n");

			closeClients(g_server);
		}

		// get user input

		g_print("press enter to continue or 'q' to quit\n");

		std::string input;

		std::getline(std::cin, input);

		if (input.find('q') != std::string::npos)
			break;

		// reset sigint handler
		
		signal(SIGINT, signalHandler);
	}

	// user decided to exit, so cleanup

	g_print("cleanup...\n");

	g_print("remove source\n");
	g_source_remove(sourceID);

	if (G_IS_OBJECT(g_server))
	{
		g_print("Ref Count: %u\n", GST_OBJECT_REFCOUNT_VALUE(g_server));

		g_print("unref server\n");
		g_object_unref(g_server);

		g_server = nullptr;
	}

	if (G_IS_OBJECT(g_loop))
	{
		g_print("unref loop\n");
		g_object_unref(g_loop);

		g_loop = nullptr;
	}
}
catch (std::exception& e)
{
	g_print("exception: %s\n", e.what());
}

// not necessary to call, but if it does exit cleanly (e.g. deinit does not hang), then we have done a good job cleaning up

g_print("deinit...\n");

gst_deinit();

return 0;

}

Nice job! Thanks for sharing with other developers.