Dynamically removing webrtcsrc

Hi everyone,
I have a question about properly removing elements from a GStreamer pipeline, particularly when using webrtcsrc. I know this topic has been discussed quite often, such as here, but I’m not entirely sure I understand it fully.
For example, in the follwoing script, I instantiate webrtcsrc elements based on the presence of a specific client. It seems I’m not correctly destroying the webrtcsrc (sub)elements.

def _removing_webrtcsrc(self) -> None:
    self._peer_audio_id = ""
    if self._webrtcsrc is not None:
        self._webrtcsrc.set_state(Gst.State.NULL)
        self.pipeline.remove(self._webrtcsrc)
        self._webrtcsrc = None
        for elt in self._receiver_elements:
            self.pipeline.remove(elt)
            elt.set_state(Gst.State.NULL)
        self._receiver_elements.clear()
    self._logger.debug("webrtcsrc removed")

def _webrtcsrc_pad_added_cb(self, webrtcsrc: Gst.Element, pad: Gst.Pad) -> None:
        if pad is not None and pad.get_name().startswith("audio"):  # type: ignore[union-attr]
            self._logger.info("Connecting audio client")

            volume = Gst.ElementFactory.make("volume")
            assert volume is not None
            volume.set_property("volume", 0.2)
            sink = Gst.ElementFactory.make("alsasink")
            sink.set_property("device", "hw:4,0")
            assert sink is not None

            self.pipeline.add(volume)
            self.pipeline.add(sink)
            self._receiver_elements.append(volume)
            self._receiver_elements.append(sink)

            volume.link(sink)
            pad.link(volume.get_static_pad("sink"))  # type: ignore[arg-type]

            volume.sync_state_with_parent()
            sink.sync_state_with_parent()


 def _add_webrtcsrc(self, peer_audio_id: str) -> Gst.Element:
        webrtcsrc = Gst.ElementFactory.make("webrtcsrc")
        assert webrtcsrc is not None
        self._webrtcsrc_count += 1

        signaller = webrtcsrc.get_property("signaller")
        signaller.set_property("producer-peer-id", peer_audio_id)
        signaller.set_property("uri", "ws://127.0.0.1:8443")

        webrtcsrc.connect("pad-added", self._webrtcsrc_pad_added_cb)

        self.pipeline.add(webrtcsrc)
        webrtcsrc.sync_state_with_parent()
        return webrtcsrc

When I close the program, I encounter the following critical errors:

(python:5872): GStreamer-CRITICAL **: 15:09:59.621:
Trying to dispose element typefind, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

(python:5872): GStreamer-CRITICAL **: 15:09:59.621:
Trying to dispose element parsebin0, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

(python:5872): GStreamer-CRITICAL **: 15:09:59.621:
Trying to dispose element multiqueue0, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

(python:5872): GStreamer-CRITICAL **: 15:09:59.621:
Trying to dispose element decodebin3-0, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

(python:5872): GStreamer-CRITICAL **: 15:09:59.621:
Trying to dispose element bin0, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

(python:5872): GStreamer-CRITICAL **: 15:09:59.622:
Trying to dispose element rtpfunnel0, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

(python:5872): GStreamer-CRITICAL **: 15:09:59.622:
Trying to dispose element webrtcbin0, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

How would you do that properly?

Thanks

PS: using gst-plugins-rs 0.14.1-126f022f

Could you please test with the changes in this MR? It should resolve the issue you are observing.

Hi,

Thanks for looking into this!
But sorry the problem is still there. It seems that the patched version of webrtcsrc is correctly installed : 0.15.0-alpha.1-0c5429e8

Can you help with a minimal reproducer example? When exactly is _removing_webrtcsrc called? pad-removed does not seem used, only pad-added. A more complete picture would help.

webrtcsrc itself destroys it child elements in either of session-ended from signaller or PausedToReady state change.

Sorry for the delay.

Here’s an example: minimal.py · GitHub

The gst-signalling dependency is a home made code to communicate with the signalling server in python gst-signalling-py/src/gst_signalling/gst_listener.py at develop · pollen-robotics/gst-signalling-py · GitHub

I could not get the minimal Python example to run. The webrtcsrc element does not get added. Had to change the roles check to listener instead of producer for it to actually add the webrtcsrc element, there still seem to be other things not working as intended.

A simpler gst-launch pipeline test I am using here,

WEBRTCSINK_SIGNALLING_SERVER_LOG=debug cargo run --bin gst-webrtc-signalling-server
GST_PLUGIN_PATH=target/debug GST_DEBUG=3 gst-launch-1.0 -e audiotestsrc wave=ticks ! audioconvert ! webrtcsink
GST_DEBUG="3,webrtcsrc:6,webrtc-signaller:6,GST_TRACER:7" GST_TRACERS="leaks" GST_PLUGIN_PATH=target/debug gst-launch-1.0 webrtcsrc connect-to-first-producer=true ! audioconvert ! autoaudiosink

Note also the leak tracer in the webrtcsrc pipeline.

Closing the sink side pipeline and then the signaller, does not show problems. I would expect the elements not disposed error to show up here. Some leaks are reported but those are the static caps declared in net/webrtc/src/utils.rs and used by webrtcsrc and one with DTLS agent which should be OK.

My listener is looking for a specific producer named client
hence gst-launch-1.0 -e audiotestsrc ! webrtcsink meta="meta,name=client
Then webrtcsrc is created and added to the pipeline.

If the listener detects that the client is not here anymore, the function
_removing_webrtcsrc is called.

The webrtcsink side pipeline was the same for the Python test.

GST_PLUGIN_PATH=target/debug GST_DEBUG=3 gst-launch-1.0 -e audiotestsrc wave=ticks ! audioconvert ! webrtcsink meta="meta,name=client"

Here are the logs below. Client is connected with id is never seen.

2025-09-05 13:07:26,468 - websockets.client - DEBUG - = connection is OPEN
2025-09-05 13:07:26,468 - gst_signalling.gst_signalling - INFO - Connected.
2025-09-05 13:07:26,468 - gst_signalling.gst_signalling - INFO - Starting input message handler.
2025-09-05 13:07:26,469 - websockets.client - DEBUG - < TEXT '{"type":"welcome","peerId":"fb51fb5f-f474-411d-815a-0791d3616562"}' [66 bytes]
2025-09-05 13:07:26,469 - gst_signalling.gst_signalling - DEBUG - Received message: {"type":"welcome","peerId":"fb51fb5f-f474-411d-815a-0791d3616562"}
2025-09-05 13:07:26,469 - gst_signalling.gst_signalling - DEBUG - Sending message: {'type': 'setPeerStatus', 'roles': ['listener'], 'meta': {'name': 'client'}, 'peerId': 'fb51fb5f-f474-411d-815a-0791d3616562'}
2025-09-05 13:07:26,469 - websockets.client - DEBUG - > TEXT '{"type": "setPeerStatus", "roles": ["listener"]...11d-815a-0791d3616562"}' [126 bytes]
2025-09-05 13:07:26,469 - websockets.client - DEBUG - < TEXT '{"type":"peerStatusChanged","roles":["listener"...11d-815a-0791d3616562"}' [122 bytes]
2025-09-05 13:07:26,469 - gst_signalling.gst_signalling - DEBUG - Received message: {"type":"peerStatusChanged","roles":["listener"],"meta":{"name":"client"},"peerId":"fb51fb5f-f474-411d-815a-0791d3616562"}
2025-09-05 13:07:26,469 - __main__ - DEBUG - Peer "fb51fb5f-f474-411d-815a-0791d3616562" changed roles to ['listener'] with meta {'name': 'client'}
2025-09-05 13:07:56,470 - websockets.client - DEBUG - < PING '' [0 bytes]
2025-09-05 13:07:56,471 - websockets.client - DEBUG - > PONG '' [0 bytes]

If I do change the "producer" in roles to "listener" in roles, then

2025-09-05 13:10:15,557 - __main__ - DEBUG - Peer "e960aa00-245b-4a6f-be4d-5b48e706a274" changed roles to ['listener'] with meta {'name': 'client'}
2025-09-05 13:10:15,557 - __main__ - DEBUG - Pipeline is ready and playing
2025-09-05 13:10:15,564 - __main__ - INFO - Added webrtcsrc
2025-09-05 13:10:15,564 - __main__ - INFO - Client is connected with id : e960aa00-245b-4a6f-be4d-5b48e706a274

The producer which the signalling server reports is never detected by the minimal example.

Ok strange, I still need to have it as producer. But the minimal.py should be started first. I mean it’s going to catch client producers after it’s started.

Anyway if it works as a listener for you that’s ok. The point is when I start and stop this
GST_PLUGIN_PATH=target/debug GST_DEBUG=3 gst-launch-1.0 -e audiotestsrc wave=ticks ! audioconvert ! webrtcsink meta="meta,name=client"
multiple times, I’ll have the issue. webrtcsrc is added and removed dynamically from the pipe but not properly according to the error message displayed when I exit minimal.py.

The basic command line test works without any issue. It’s not exactly the same situation

Thanks for confirming. I will look at reproducing the same.

I can reproduce the problem on main but not with !2491. Have kept the minimal.py example as is, except for commenting out the device property setting on alsasink.

Here is an excerpt of the log.

2025-09-05 14:21:35,069 - gst_signalling.gst_signalling - DEBUG - Received message: {"type":"peerStatusChanged","roles":[],"meta":{"name":"client"},"peerId":"0efe6ba3-0131-4c61-b0d6-d7ec776e00ca"}
2025-09-05 14:21:35,069 - __main__ - DEBUG - Peer "0efe6ba3-0131-4c61-b0d6-d7ec776e00ca" changed roles to [] with meta {'name': 'client'}
2025-09-05 14:21:35,078 - __main__ - DEBUG - webrtcsrc removed
2025-09-05 14:21:35,079 - __main__ - INFO - Client  is disconnected
2025-09-05 14:21:35,079 - websockets.client - DEBUG - < TEXT '{"type":"peerStatusChanged","roles":[],"meta":n...455-a269-7f03f95247ac"}' [99 bytes]
2025-09-05 14:21:35,079 - gst_signalling.gst_signalling - DEBUG - Received message: {"type":"peerStatusChanged","roles":[],"meta":null,"peerId":"68a174bc-bb8a-4455-a269-7f03f95247ac"}
2025-09-05 14:21:35,079 - __main__ - DEBUG - Peer "68a174bc-bb8a-4455-a269-7f03f95247ac" changed roles to [] with meta None
^C2025-09-05 14:21:37,322 - root - INFO - Main task was cancelled
2025-09-05 14:21:37,322 - __main__ - DEBUG - Stopping WebRTC
# GitSources → gst-plugins-rs ⦿ [webrtcsrc-session-eos] λ:

The same on main gives

2025-09-05 14:24:23,360 - __main__ - DEBUG - webrtcsrc removed
2025-09-05 14:24:23,360 - __main__ - INFO - Client  is disconnected
2025-09-05 14:24:23,360 - websockets.client - DEBUG - < TEXT '{"type":"peerStatusChanged","roles":[],"meta":n...921-b6c4-b22f61144be7"}' [99 bytes]
2025-09-05 14:24:23,360 - gst_signalling.gst_signalling - DEBUG - Received message: {"type":"peerStatusChanged","roles":[],"meta":null,"peerId":"4e14857d-f7a8-4921-b6c4-b22f61144be7"}
2025-09-05 14:24:23,360 - __main__ - DEBUG - Peer "4e14857d-f7a8-4921-b6c4-b22f61144be7" changed roles to [] with meta None
^C2025-09-05 14:24:25,859 - root - INFO - Main task was cancelled
2025-09-05 14:24:25,859 - __main__ - DEBUG - Stopping WebRTC

(python3:209080): GStreamer-CRITICAL **: 14:24:25.860:
Trying to dispose element multiqueue0, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

....

(python3:209080): GStreamer-CRITICAL **: 14:24:25.860:
Trying to dispose element rtpbin, but it is in PAUSED instead of the NULL state.
You need to explicitly set elements to the NULL state before
dropping the final reference, to allow them to clean up.
This problem may also be caused by a refcounting bug in the
application or some element.

# GitSources → gst-plugins-rs ⦿ [main] λ:

My steps to reproduce were,

  • Start signalling server
  • Start minimal Python application
  • Start webrtcsink pipeline
  • Ctrl-c to stop webrtcsink pipeline
  • Ctrl-c the application only after it shows Client is disconnected/webrtc removed log.

Sorry I still have the problem with your branch. I’ve recloned and compiled the plugin.
I’m using GStreamer 1.22.0 from the OS RPI Lite version.

Note that sometimes it goes well, but most of the time I’ve got the error. Especially if i start and then ctrl+c a webrtcsink pipeline