producer simulcast ssrc mixed up / race condition ?

Hi,

On chrome@142 with mediasoup@3.19.11 & mediasoup-client@3.18.0, when we do in a row :
publish track 1 → publish track 2 → unpublish track 1, sometimes the track 2 is not received.
I can’t find a root cause in debug logs, but it seems that the producer of track2 has the ssrc (in score event) of the track 1.
Is it possible that a race condition produce this issue ?

Thank you

I don’t really know what “publish/unpublish a track” is. We don’t use that nomenclature in mediasoup ecosystem at all.

I’d be surprise if such a bug existed because we secure all operations by using a queue so it’s definitely unlikely to happen. You say “sometimes the track 2 is not received” meaning that your scenario involving signaling messages to your server and remote clients that consume these tracks. The problem could be there.

Hi,

Yes, publish/unpublish in my nomenclature means creating and closing of producer on the client side plus the signaling to the server (and remotes).

I can’t find any issue in my signaling, I mean messages between client and server are correct (producer ids match). I just see one possible race condition with the consumer : the remote could consume “between” the producer creation and its closure, but this case is handled in my code (the consumer is directly closed instead of resumed).

The strange part is not really on consumer but directly on the producer (and browser), as said before the producer of track 2 triggers score events which contains ssrcs of the producer 1. Also in webrtc-internals, when the problem occur, I dont see the remote-inbound-rtp (outbound-rtp exists) of the producer 2. So it seems the server does not “receive” the tracks. (the local and remote offer seem correct : mid match and a:simulcast is correct, no ssrc in the sdp but I think it’s normal)

See the logs of producers scores (producer 1 (closed): 0bf2677b-215d-4c4a-ad76-f91008558c5e, producer 2: 921ce2f7-ca00-47b2-a175-b199b39cd9b9) :

"producerId": "0bf2677b-215d-4c4a-ad76-f91008558c5e",

    "score": [

        {

            "encodingIdx": 0,

            "ssrc": 953422320,

            "rid": "r0",

            "score": 10

        },

        {

            "encodingIdx": 1,

            "ssrc": 843441212,

            "rid": "r1",

            "score": 10

        },

        {

            "encodingIdx": 2,

            "ssrc": 1418984211,

            "rid": "r2",

            "score": 10

        }

    ],

    "producerMid": "4"



"producerId": "921ce2f7-ca00-47b2-a175-b199b39cd9b9",

    "score": [

        {

            "encodingIdx": 0,

            "ssrc": 953422320,

            "rid": "r0",

            "score": 10

        },

        {

            "encodingIdx": 1,

            "ssrc": 843441212,

            "rid": "r1",

            "score": 10

        },

        {

            "encodingIdx": 2,

            "ssrc": 1418984211,

            "rid": "r2",

            "score": 10

        }

    ],

    "producerMid": "5"

In my tests, I’ve tried to delay the consumption of the remote, it doesn’t fix the issue. But if I delay the close of the producer, then the problem disappears.
Btw, I’ve seen that creating and closing of producers is sequential using the awaitqueue, but the close message to server does not depend on awaitqueue, so in general the producer is closed before on the server and after on client (because the client waits the end of producer 2 creation).

Enabling debug logs in your node app should already help you thoubleshot it. You’ll see logs when every Producer/Consumer is created/closed, etc.

`DEBUG=“mediasoup* *ERROR*”`

The producer creation and closure actions in mediasoup-client are executed sequentially, so there is no reason for bad things to happen no matter you create and close them in a row. Let’s do this please:

Try to replicate the issue in the mediasoup-demo app. Run in locally and make the needed change sin app/src/RoomClient.js to reproduce the problem. Try to reproduce it without involving consumers (if possible). Create a branch/PR in mediasoup-demo with the modified code and expose the issue in GH. Could you do that?

Looking at logs, there is also something strange.

It logs this :
2025-11-19T10:18:55.428Z mediasoup:Channel [pid:136] RTC::Producer::ReceiveRtpPacket() | key frame received [ssrc:2193592958, seq:4921]

2025-11-19T10:18:55.473Z mediasoup:WARN:Channel [pid:136] RTC::Producer::GetRtpStream() | ignoring packet with unknown ssrc but already handled RID

2025-11-19T10:18:55.473Z mediasoup:WARN:Channel [pid:136] RTC::Producer::ReceiveRtpPacket() | no stream found for received packet [ssrc:3012504380]

where ssrc:2193592958 is the 1st producer and ssrc:3012504380 is the 2nd.

But the log of ssrc:3012504380 happens before the ws message to create the 2nd producer (that should matches this ssrc) is received by the server.
So it seems the producer on client side starts sending rtp packets before the callback of sendtransport.on(‘produce’) is called.
Is it normal behavior ?

I tried but couldn’t reproduce yet, but I’m not sure if it’s caused by network latency or not. I will try again

Yes. Obviously the browser doesn’t wait for custom application things to happen in a server.

Please, do not focus on those warning logs. If this is the first time you are looking at them then you will identify all them as the culprit of your issue. Try to reproduce the issue (hopefully in the mediasoup-demo) and write to the console the values in the “produce” event in client side and so on until the potential issue can be shown.

I’ve tried to reproduce the issue and found a bug in the mediasoup-demo application in the app client side (the browser application). But honestly such an app is not a production ready app.

The problem is the following:

  1. Bob is in the room.
  2. Alice joins and its RoomClient invokes await this.enableWebcam(); await this.disableWebcam(); await this.enableWebcam(); in a row.
  3. Bob receives newConsumer, then consumerClosed and then another newConsumer. However, we are not queuing the processing of those received messages so at the time consumerClosed arrives, the Consumer it refers to was not yet created (because it requires an async call to recvTransport.consume() that takes some time).
  4. Result is that, from Bob point of view, there are 2 active video consumers from Alice and, due to how the code is written, probably the first one is rendered, resulting in black video.

Solution in this commit: app: use AwaitQueue to queue server side consumers related requests/n… · versatica/mediasoup-demo@e4ab32a · GitHub

Other than this, I’ve NOT seen any potential issue in mediasoup or mediasoup-client. There are tons of tests and the related code has been tested in production grade applications for long years.

Hi,

I think I managed to reproduce the error based on demo.
Here is the code to reproduce : Comparing versatica:v3...hugomallet:issue/ssrcMixedUp · versatica/mediasoup-demo · GitHub

Use this URL for producer : http://localhost:5555/?roomId=dev&_throttleSecret=foo&info=true&stats=false&consume=false&forceVP8=true&enableWebcamLayers=true&numSimulcastStreams=3&mic=false

Then enable the webcam. It might not reproduce the issue every time, so wait 10s (delays) and disable/enable the webcam again until reproduced.

When the problem appears, you’ll see many “no suitable Producer for received RTP packet” logs.

I guess there is an override of ssrc because of same rid when the 2nd producer is created on the server after the 1st is closed on the server (but still running on client-side).
My guess is the issue is coming from here (I havent tested): mediasoup/worker/src/RTC/RtpListener.cpp at v3 · versatica/mediasoup · GitHub

Could you please just make minimal required changes in the demo app to reproduce the issue? In that branch you are introducing canvas stuff (couldn’t it just be an additional call to getUserMedia() somewhere?). You are also changing wss to ws (no idea why because the demo works locally as it is). And in server side you are making changes that affect what other peers consume, but AFAIU this issue is not about consuming but about producing.

If you create 2 video producers in the same transport, both with simulcast (mid and rid entries in their rtpParameters) then, if both producers have the same rid values… mmm, unclear what would happen… Indeed the ridTable of RtpListener.cpp can only contain RID keys for one of them (the first one). I’m checking it but I’d appreciate if you could describe the issue now that we are getting closer. I don’t really understand what the “effective” issue is. You said that you create a producer (I assume with simulcast and rid in its N encodings), then you create another one, but maybe you close the first one first… and the problem is that the packets sent by the latest producer are not matched by mediasoup so they are discarded? Please detail that flow in detail.

Actually I cannot see what could be wrong. If I create 2 video producers with MID 0 and MID 1, both with simulcast with same RIDs “r0”, “r1” and “r2”, I call transport.dump() in server and check its rtpListener object, the result is the expected one:

transport.rtpListener: {
  ssrcTable: [
    {
      key: 1513882199,
      value: '4f812812-a347-4de0-a3f3-0dec350f04c9'
    },
    {
      key: 2163914333,
      value: '3bfc5df0-9781-41f3-b773-06f74f1c563f'
    },
    {
      key: 3005521710,
      value: '3bfc5df0-9781-41f3-b773-06f74f1c563f'
    },
    {
      key: 1352092390,
      value: '3bfc5df0-9781-41f3-b773-06f74f1c563f'
    },
    {
      key: 1280107522,
      value: '4f812812-a347-4de0-a3f3-0dec350f04c9'
    },
    {
      key: 368153787,
      value: '4f812812-a347-4de0-a3f3-0dec350f04c9'
    }
  ],
  midTable: [
    {
      key: '2',
      value: '3bfc5df0-9781-41f3-b773-06f74f1c563f'
    },
    {
      key: '1',
      value: '4f812812-a347-4de0-a3f3-0dec350f04c9'
    }
  ],
  ridTable: [
    {
      key: 'r2',
      value: '4f812812-a347-4de0-a3f3-0dec350f04c9'
    },
    {
      key: 'r1',
      value: '4f812812-a347-4de0-a3f3-0dec350f04c9'
    },
    {
      key: 'r0',
      value: '4f812812-a347-4de0-a3f3-0dec350f04c9'
    }
  ]
} 

The midTable has 2 entries, one for the first Producer with MID “0” and another one for the second Producer with MID “1”.

The ridTable only has RID entries for the first Producer due to the logic in RtpListener::AddProducer() in RtpListener.cpp.

Ok, so if eventually a packet of Producer 2 arrives, and such a RTP packet doesn’t contain the MID header extension but it contains RID “r0”, then RtpListener.getProducer() would set ssrcTable with the SSRC of Producer 2 but its value would point to Producer 1… is that?

The flow (with my assumption on ssrc table override) :

client pushes “create producer #1” to awaitqueue (awaitqueue:+producer#1)
client pushes "create producer #2 to awaitqueue (awaitqueue:+producer#1+producer#2)
client executes awaitqueue +producer#1
client creates producer #1 (awaitqueue:+producer#2)
client send ws message to server “create server producer #1
rtp packets #1 are sent from client to server, server IGNORE them
[…]
server receive ws message “create server producer #1
server creates “server producer #1
rtp packets #1 are sent from client to server, server matches mid of packet and producer #1, and HANDLE them
[…]
client callback for producer #1 is called (producer #1 promise is fulfilled on client) (awaitqueue:+producer#2)
client pushes “close producer #1” to awaitqueue (awaitqueue:+producer#2-producer#1)
client executes awaitqueue +producer#2
client creates producer #2 (awaitqueue:-producer#1)
client send ws message to server “create server producer for #2
rtp packets #1 are sent from client to server, server HANDLE them
rtp packets #2 are sent from client to server, server IGNORE them
[…]
client send ws message “close server producer for #1” (awaitqueue not involved here)
server receive ws message “close server producer for #1
server closes producer #1
rtp packets #1 are sent from client to server, server IGNORE them
rtp packets #2 are sent from client to server, server IGNORE them
[…]
server receive ws message “create server producer #2
server creates “server producer #2
rtp packets #1 are sent from client to server, server SHOULD IGNORE them
rtp packets #2 are sent from client to server, server SHOULD HANDLE them
[…]

==> At this point producer #1 is closed on server and producer #2 exists on server, but the producer #1 still exists on client.
but my assumption is that if the server receive rtp packets of closed #1, then it overrides
the ssrc table of server producer #2 (mid of packets #1 matches nothing but rid of #1 matches #2)

then,
client callback for producer #2 is called (producer #2 promise is fulfilled on client) (awaitqueue:-producer#1)
client executes awaitqueue -producer#1
client closes producer #1 (awaitqueue:empty)
rtp packets #2 are sent from client to server, server IGNORE them
[…]

I logged the transport.dump(), mid and rid are up to date with last producer as expected but ssrc table contains in general only two entries (instead of 3) and its keys don’t match ssrc on client side.
Can you reproduce using my example ?

So the problem is that, while Producer 1 is closed on the server but alive in client, RTP packets of Producer 1 are identified as if they belong to Producer 2 and hence a new RTP stream with the ssrc of the packet is created in the server side Producer 2 and from now on all RTP packets of Profucers 1 and 2 in client are consumed by Producer 2 in the server and forwarded to Consumers and hence problems.

I don’t really need to reproduce the problem using your code, the problem is clear, thanks.

I will ask you to please open an issue in GitHub of mediasoup with the summary of this last comment I’m replying to. Reason is that I don’t know if I will be able to work on this immediately and I don’t want to forget about it. Please also reference this Forum topic in the GH issue. Thanks.

BTW the solution is to ONLY lookup in the ridTable if the packet doesn’t have MID extension. That would not fix all the possible exotic cases (such as a client sending packets with RID but without MID) but would solve the problem in real scenarios because all WebRTC clients send MID extension if they also send RID.

Ok, I got some time to write a PR fixing it: Only look up the RTP packet’s RID extension if the packet doesn’t have MID extension by ibc · Pull Request #1666 · versatica/mediasoup · GitHub

@Hugo can you try your scenario with this branch?

Hi, thank you. Using your branch I can’t reproduce on demo but still on my app (with incorrect ssrc still in producer score). I try more things and keep you posted

What is your app? Is it a mediasoup-client based browser app or what exactly?