Gst_element_set_state() is hanging while setting the element to NULL

Hi, I am new to gstreamer, and in parallel to implementing an RTSP server (as explained in my previous post), I am also implementing an RTP streamer that I would like to dynamically shut down and restart. Unlike with RTSP server however, I am encountering a serious issue with the RTP streamer that freezes the entire application:

During the shut down routine of the RTP streamer, the RTP pipeline has to be set to NULL via gst_element_set_state(rtpPipeline, GST_STATE_NULL) before the pipeline can be unreferencend (based on what I gathered from here). Yet this call can occasionally hang indefinitely (for what reason I have no idea). This bug that has already been discussed several times online:

(please excuse the formatting, as a new user I can only post two links)

https://forums.developer.nvidia.com/t/gst-element-set-state-is-hanging-while-setting-the-element-to-null/221460
https://forums.developer.nvidia.com/t/gst-element-set-state-is-hanging-while-setting-the-element-to-null/262517/11
https://forums.developer.nvidia.com/t/pipeline-set-state-meet-segmentation-fault-core-dumped-and-app-crashed/186665
https://gstreamer-devel.narkive.com/fMlsKCKO/gstreamer-hangs-on-gst-element-set-state-when-trying-to-stop-a-transcode-stream
https://discourse.gnome.org/t/can-gst-element-set-state-block/5895
https://bugzilla.gnome.org/show_bug.cgi?id=692145
https://community.nxp.com/t5/i-MX-Processors/Gstreamer-0-10-function-gst-element-set-state-hang/m-p/287650?profile.language=en
https://e2e.ti.com/support/processors-group/processors/f/processors-forum/560240/on-state-change-from-playing-to-null-hangs

Is there any way to stop this from occurring? As with the RTSP server issue, I have created an MWE to experiment with, however in this case, having extracted all the RTP functionality from my original application, I am unable to force the MWE to crash. Here it is anyways:

#include <StreamerTest.h>

/**
 \brief			Run the RTP main loop, called by an external thread
 \return        void
*/
void StreamerTest::RunRtpLoop() {
	g_main_loop_run(rtpLoop);
    std::cout << "Running RTP main loop." << std::endl;
}

static void on_rtp_pipeline_finalized(gpointer data, GObject* where_the_object_was) {
    std::cout << "RTP pipeline finalized. Pointer was: " << where_the_object_was << std::endl;
}

/**
 \brief			Initialize the RTP server using a GStreamer pipeline
 \param[in]		launchString		the string that contains the streaming parameters like format, encoding, etc.
 \return		true: success,    false: error
*/
bool StreamerTest::InitializeRtpServer(std::string launchString) {

	{
		std::lock_guard<std::mutex> lock(rtpMutex);

		rtpLoop = g_main_loop_new(nullptr, false);
        std::cout << "RTP main loop created." << std::endl;

		GError* error = nullptr;
		rtpPipeline = gst_parse_launch(launchString.c_str(), &error);
        g_object_weak_ref(G_OBJECT(rtpPipeline), on_rtp_pipeline_finalized, nullptr);
        std::cout << "RTP pipeline created with the following launch string:\n\n" << launchString << "\n" << std::endl;

		gst_element_set_state(rtpPipeline, GST_STATE_PLAYING);
        std::cout << "RTP pipeline state set to PLAYING." << std::endl;
	}
    
	rtpThread = std::thread(&StreamerTest::RunRtpLoop, this);
    std::cout << "RTP streamer started." << std::endl;

	return true;
}

/**
 \brief		Stop the RTP server
 \return	true: success,    false: error
*/
bool StreamerTest::StopRtpServer() {
    bool success = true;
    
	// std::atomic<bool> shutdownComplete{false};
    
	// auto shutdownTask = std::async(std::launch::async, [&](){

	{		
		std::lock_guard<std::mutex> lock(rtpMutex);

		if (rtpLoop && g_main_loop_is_running(rtpLoop)) {
			g_main_loop_quit(rtpLoop);
			std::cout << "RTP main loop stopped."<< std::endl;
		} else {
			std::cout << "Failed to stop RTP main loop."<< std::endl;
			success = false;
		}
	}

	if (rtpThread.joinable()) {
		rtpThread.join();
		std::cout << "RTP thread joined."<< std::endl;
	} else {
		std::cout << "Failed to join RTP thread."<< std::endl;
		success = false;
	}

	if (rtpLoop) {
		g_main_loop_unref(rtpLoop);
		rtpLoop = nullptr;
		std::cout << "RTP main loop unreferenced."<< std::endl;
	} else {
		std::cout << "Failed to unreference RTP main loop."<< std::endl;
		success = false;
	}

	if (rtpPipeline) {
		// // 1. Send EOS
		// gst_element_send_event(rtpPipeline, gst_event_new_eos());

		// // 2. Wait for EOS to propagate
		// std::this_thread::sleep_for(std::chrono::milliseconds(200));

		// // 3. Transition to PAUSED
		// gst_element_set_state(rtpPipeline, GST_STATE_PAUSED);
		// gst_element_get_state(rtpPipeline, nullptr, nullptr, GST_SECOND);

		// 4. Transition to NULL with timeout
		gst_element_set_state(rtpPipeline, GST_STATE_NULL);
        std::cout << "RTP pipeline state set to NULL." << std::endl;
		// gst_element_get_state(rtpPipeline, nullptr, nullptr, GST_SECOND * 2);
        
		g_clear_object(&rtpPipeline);
		// rtpPipeline = nullptr;
		std::cout << "RTP pipeline unreferenced." << std::endl;
	} else {
		std::cout << "Failed to stop and unreference RTP pipeline." << std::endl;
		success = false;
	}

        // shutdownComplete = true;
	// });

    // // Watchdog timeout
    // if (shutdownTask.wait_for(std::chrono::seconds(5)) != std::future_status::ready) {
    //     // First try graceful exit
    //     LOG_ERROR1(VDUHardware::logger, "Encoder %u: RTP streamer shutdown timed out. Attempting graceful exit.", enc_idx);
    //     std::exit(EXIT_FAILURE);

    //     // If that fails (e.g., stuck destructors), force kill
    //     LOG_ERROR1(VDUHardware::logger, "Encoder %u: Graceful exit failed. Forcing process termination.", enc_idx);
    //     std::raise(SIGKILL);

    // }

	// gst_deinit();

    return success;
}

bool StreamerTest::ToggleRtpServer() {
    if (!rtpServerRunning) {
        std::string launchString = "videotestsrc ! x264enc tune=zerolatency ! rtph264pay config-interval=1 pt=96 ! udpsink host=127.0.0.1 port=5000";
		std::cout << "Trying to start RTP server........." << std::endl;
        if (InitializeRtpServer(launchString)) {
            rtpServerRunning = true;
            return true;
        }
    } else {
		std::cout << "Trying to stop RTP server........." << std::endl;
        StopRtpServer();
        rtpServerRunning = false;
        return true;
		std::cout << "RTP server stopped........." << std::endl;
    }

    std::cout << "ToggleRtpServer returning........." << std::endl;
    return false;
}

And there the corresponding StreamerTest.h:

#ifndef STREAMERTEST

#include <stdlib.h>
#include <string>
#include <thread>
#include <mutex>
#include <atomic>
#include <iostream>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

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

#include <gst/gstinfo.h>

#pragma GCC diagnostic pop


class StreamerTest {

public:

    StreamerTest();
    ~StreamerTest();
    bool ToggleRtpServer();

private:

	std::string usedRtpLaunchString;
	bool rtpServerRunning = false;
	GstElement* rtpPipeline;
	std::thread rtpThread;
	std::mutex rtpMutex;
    GMainLoop* rtpLoop;
	void RunRtpLoop();
	bool InitializeRtpServer(std::string launchString);
	bool PauseRtpServer();

};

#endif

And the main.cpp:


#include "StreamerTest.h"

int main() {
    StreamerTest streamer;
    
    int toggleCount = 0;
    // while (streamer.ToggleRTSPServer()) {
    while (streamer.ToggleRtpServer()) {
        ++toggleCount;
        std::cout << "\n--------------------- Successful Toggle Count: " << toggleCount << " ---------------------\n\n" << std::endl;
		std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }

    return 0;
}

As you can see, I have tried many different things to stop gst_element_set_state() from hanging, but to no avail.

Again, I am cross-compiling with gstreamer version 1.16.3 and glib version 2.64.5.

GStreamer 1.16 was released 6.5 years ago, I would strongly encourage you to upgrade your GStreamer/GLib versions.

(People routinely build and deploy newer GStreamer on Jetson, it shouldn’t be a problem.)

Hi, thank you! That’s a very fair point. Unfortunately, I am working with a legacy code base, so I’m not sure how easy it will be to use newer versions. The system currently runs on PetaLinux version 2021.2.

Have you looked into getting the more up-to-date recipes into the petalinux build’s layer configuration? It has been a few years since I did this, but petalinux is a distro based on Yocto/OpenEmbedded’s own layers.

For example, if you were to copy the [recipes-multimedia/gstreamer](openembedded-core/meta/recipes-multimedia/gstreamer/gstreamer1.0_1.26.5.bb at master · openembedded/openembedded-core) into your own layer, you may find that the next time you build petalinux, it picks up the newer version of GStreamer and its dependencies.

This won’t be for the faint of heart though, as there may be other things needed from that version of the layer. I see the recipe I linked to above inherits upstream-version-is-uneven and gobject-introspection which are over in the classes-recipe. They may already exist in your layers, so you’ll have to compare, maybe update them as well, etc. There are a lot of ways this can eat time if you’re not comfortable working with a Yocto build.

Cheers,

Thomas

Hi Thomas,

thanks a lot! I have no experience with building Yocto layers, but I knew that updating the libraries would likely not be easy and come with a rats tail, so this is very much appreciated. I am certainly going to look into it and see if I can manage.

In the meantime, I did manage to produce some gstreamer logs of when of the application started to hang. I’m not really sure what to make of it though:

[GStreamer][GST_STATES] current READY pending VOID_PENDING, desired next NULL
[GStreamer][bin] no message found matching types 00100000
[GStreamer][bin]   async-start
[GStreamer][bin] setting element omxh264enc-omxh264enc7 to NULL, base_time 0:01:32.680171619
[GStreamer][GST_STATES] set_state to NULL
[GStreamer][GST_STATES] setting target state to NULL
[GStreamer][GST_STATES] current READY, old_pending VOID_PENDING, next VOID_PENDING, old return SUCCESS
[GStreamer][GST_STATES] final: setting state from READY to NULL
[GStreamer][GST_ELEMENT_PADS] deactivate pads
[GStreamer][GST_PADS] pad was inactive
[GStreamer][GST_PADS] pad was inactive
[GStreamer][GST_ELEMENT_PADS] pad deactivation successful
[GStreamer][omxvideoenc] Closing encoder
[GStreamer][omxvideoenc] Shutting down encoder
[GStreamer][omx] Getting state of encoder
[GStreamer][omx] encoder timeout while waiting for state change
[GStreamer][omx] encoder returning state Invalid
[GStreamer][omx] Setting encoder state from Executing to Loaded
[GStreamer][OMX_API_TRACE] SendCommand, command=(string)SetState, state=(string)Loaded;
[GStreamer][omx] Deallocating buffers of encoder port 0
[GStreamer][omx] Trying to free used buffer 0xffffe802cde0 of encoder port 0
[GStreamer][omx] encoder: deallocating buffer 0xffffe802cde0 (0x28)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] encoder got error: Port unpopulated (0x8000101c)
[GStreamer][omx] encoder: deallocating buffer 0xaaaaaad94140 (0x36)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] encoder got error: Port unpopulated (0x8000101c)
[GStreamer][omx] encoder: deallocating buffer 0xaaaaaad954c0 (0x2b)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] encoder got error: Port unpopulated (0x8000101c)
[GStreamer][omx] encoder: deallocating buffer 0xffffe802d460 (0x37)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] encoder got error: Port unpopulated (0x8000101c)
[GStreamer][omx] encoder: deallocating buffer 0xaaaaaad94b00 (0x2c)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] encoder got error: Port unpopulated (0x8000101c)
[GStreamer][omx] Getting encoder parameter at index 0x02000001
[GStreamer][omx] Got encoder parameter at index 0x02000001: None (0x00000000)
[GStreamer][omx] Updated encoder port 0 definition: None (0x00000000)
[GStreamer][omx] Deallocated buffers of encoder port 0: None (0x00000000)
[GStreamer][omx] Deallocating buffers of encoder port 1
[GStreamer][omx] encoder: deallocating buffer 0xffffe802caa0 (0xffffec44f000)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] encoder got error: Port unpopulated (0x8000101c)
[GStreamer][omx] encoder: deallocating buffer 0xaaaaaad95800 (0xffffec266000)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] encoder got error: Port unpopulated (0x8000101c)
[GStreamer][omx] encoder: deallocating buffer 0xffffe802d120 (0xffffec07d000)
[GStreamer][OMX_API_TRACE] EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
[GStreamer][omx] 0:01:39.600353049 e[336m  695e[00m 0xaaaaaab52c00 e[31;01mERROR  e[00m e[00m                 omx gstomx.c:3088:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m Trying to free used buffer 0xffffe802d7a0 of encoder port 1
0:01:39.600387899 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:3101:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m encoder: deallocating buffer 0xffffe802d7a0 (0xffffdb616000)
0:01:39.600445850 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m       OMX_API_TRACE gstomx.c:780:log_omx_api_trace_event:<omxh264enc-omxh264enc7>e[00m EventError, error=(string)"Port\ unpopulated", extra-info=(string)None;
0:01:39.600481570 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:903:EventHandler:<omxh264enc-omxh264enc7>e[00m encoder got error: Port unpopulated (0x8000101c)
0:01:39.601018746 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2109:gst_omx_component_get_parameter:<omxh264enc-omxh264enc7>e[00m Getting encoder parameter at index 0x02000001
0:01:39.601102736 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2113:gst_omx_component_get_parameter:<omxh264enc-omxh264enc7>e[00m Got encoder parameter at index 0x02000001: None (0x00000000)
0:01:39.601139797 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2314:gst_omx_port_update_port_definition:<omxh264enc-omxh264enc7>e[00m Updated encoder port 1 definition: None (0x00000000)
0:01:39.601173297 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:3125:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m Deallocated buffers of encoder port 1: None (0x00000000)
0:01:39.601204007 e[336m  695e[00m 0xaaaaaab52c00 e[36mINFO   e[00m e[00m                 omx gstomx.c:1227:gst_omx_component_free:<omxh264enc-omxh264enc7>e[00m Unloading component 0xffffe8012a10 encoder
0:01:39.601232168 e[336m  695e[00m 0xaaaaaab52c00 e[36mINFO   e[00m e[00m                 omx gstomx.c:3061:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m Deallocating buffers of encoder port 0
0:01:39.601259538 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:3067:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m No buffers allocated for encoder port 0
0:01:39.601287738 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2109:gst_omx_component_get_parameter:<omxh264enc-omxh264enc7>e[00m Getting encoder parameter at index 0x02000001
0:01:39.601357669 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2113:gst_omx_component_get_parameter:<omxh264enc-omxh264enc7>e[00m Got encoder parameter at index 0x02000001: None (0x00000000)
0:01:39.601392489 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2314:gst_omx_port_update_port_definition:<omxh264enc-omxh264enc7>e[00m Updated encoder port 0 definition: None (0x00000000)
0:01:39.601432940 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:3125:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m Deallocated buffers of encoder port 0: None (0x00000000)
0:01:39.601462330 e[336m  695e[00m 0xaaaaaab52c00 e[36mINFO   e[00m e[00m                 omx gstomx.c:3061:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m Deallocating buffers of encoder port 1
0:01:39.601490160 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:3067:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m No buffers allocated for encoder port 1
0:01:39.601518171 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2109:gst_omx_component_get_parameter:<omxh264enc-omxh264enc7>e[00m Getting encoder parameter at index 0x02000001
0:01:39.601588281 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2113:gst_omx_component_get_parameter:<omxh264enc-omxh264enc7>e[00m Got encoder parameter at index 0x02000001: None (0x00000000)
0:01:39.601622622 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:2314:gst_omx_port_update_port_definition:<omxh264enc-omxh264enc7>e[00m Updated encoder port 1 definition: None (0x00000000)
0:01:39.601655602 e[336m  695e[00m 0xaaaaaab52c00 e[37mDEBUG  e[00m e[00m                 omx gstomx.c:3125:gst_omx_port_deallocate_buffers_unlocked:<omxh264enc-omxh264enc7>e[00m Deallocated buffers of encoder port 1: None (0x00000000)

You’re welcome. I used to do yocto/petalinux builds on the regular, but definitely pushed FPGA devs towards not using petalinux because of all the wrapper scripting they do that made setting up some really basic things super frustrating (like tftp boot; you could configure it in one of their menu config things but trying to specify the kernel boot args was impossible because the setting would get wiped each time you exited some other menu config tool of theirs). In other situations, their tooling would regenerate other files from templates that you couldn’t edit in any useful way, precluding the use of other options. Eventually I would get their BSP working in an extractable way (kernel patches, etc.) and then port forward to a more recent version of Poky (the default reference distro of Yocto) so I could have access to newer libraries. Dealing with Xilinx’s reliance on black-box FSBL and so forth though always made that a challenge. Perhaps it has become better in recent years; I haven’t touched it in about 4-5.

Searching for how to add layers to Petalinux around your year/distro, this is what I found:

Steps for Application Auto Run at Startup • PetaLinux Tools Documentation Reference Guide (UG1144) • Reader • AMD Technical Information Portal

By the read, you’ll find wherever the user’s application layer is supposed to be and start adding recipes, classes, etc. Just make sure that the directory structure you add in there is exactly the same as the recipes that should get overlaid. So if something is in recipes-multimedia/gstreamer, you need to do that as well or else you may see strangeness in how bitbake identifies which version of a package is available to you.

Later when you define your image, you can specify the preferred version of a package to enforce that your deployed target will in-fact only get 1.26.5 (per the previous message) vs. some other version. IIRC it’s like PREFERRED_VERSION_gstreamer = “1.26.5” in your <whatever image>.bb file.