pion / webrtc

Pure Go implementation of the WebRTC API
https://pion.ly
MIT License
13.9k stars 1.66k forks source link

Problem/solution: stream disconnects after 30 seconds when remote doesn't support rtcp-rsize #2767

Open adriancable opened 6 months ago

adriancable commented 6 months ago

Background: I have been having issues with Pion streaming from specific WebRTC cameras, where everything would connect and I would get RTP data for exactly 30 seconds, at which point the remote would send a DTLS CloseNotify alert and the connection would drop. Everything worked fine when using Chrome instead of Pion. I did a lot of digging looking at tcpdumps to try and find out what was different between Chrome vs Pion, and I finally got to the bottom of it. This is possibly the same issue as #2607.

The problem stems from the fact that Pion only supports rtcp-rsize. If the remote does not, it means there is no way for Pion to send RTCP packets (like REMB, PLI) except RR, since these packets would need to be included as part of an RTCP RR compound packet, and Pion's RR generator interceptor just sends 'bare' RRs, not compound packets with other needed stuff. Remotes that do not support rtcp-rsize will generally ignore non-compound RTCP packets so there is no way to send any RTCP except RRs/SRs to the remote in this case.

The exact issue in my case is that the camera requires PLIs to be sent at a regular interval as a kind of 'keepalive'. Because the camera doesn't support rtcp-rsize, the PLIs need to be included as part of an RTCP RR compound packet, but this is not possible with Pion as it stands. So there is no way to stop the camera timing out the connection.

Modern libwebrtc/Chrome of course supports rtcp-rsize, but old versions do not and since a lot of devices use libraries which may seldom if ever get updated, this is a 'real' issue and may be responsible for the disconnects after a successful stream start reported by various people.

I 'solved' this by making a new RR generator interceptor (compound_receiver_interceptor.go) that follows the current behaviour but also allows arbitrary RTCP packets to be submitted to the interceptor at any time, which then get added onto the next RTCP RR report which then gets sent to the remote as a compound packet at the report interval. Adding PLIs to these RTCP RR reports then solves the issue. But this feels hacky and I'm not sure is the right way to support remotes that do not support reduced size RTCP. In fact I don't really have a good feeling for the 'right' way to handle this, so am open to thoughts.

Sean-Der commented 6 months ago

Damn, nice sleuthing @adriancable. I don't have the full answer yet, but this seems solvable!

At Bind time each Interceptor should get notified if rtcp-rsize is supported or not. I can go and fix it so they actually take it into account.

Example here

Sean-Der commented 6 months ago

It sounds like we also need another Inteceptor at the end that detects 'Compound Required'. It would then hold on to packets until a RTCP RR was found

adriancable commented 6 months ago

@Sean-Der - the approach I took is to add a GeneratorOption to receiver_generator.go where the client can supply a function which gets called when the interceptor generates the RR. The function returns nil, or an RTCP packet, and in the latter case the interceptor sends a compound packet containing the RR and the packet returned by the function.

This is a slightly different approach to your idea of being able to supply the packets at any time and the interceptor holds onto them until an RTCP RR is produced and then sends out a compound packet with the RTCP RR + those queued packets. I probably prefer your approach to mine if it can be implemented cleanly. I don't have a good mental image of what a nice API to do this would be, but that's mostly because I haven't spent too much time thinking about it.

We do also need to respect a:rtcp-rsize so that for example if for example the remote offers an SDP without it, we also answer without it. (right now Pion's answer always includes it, which is wrong)

I've attached my implementation so you can take a look as a starting point. compound_receiver_option.go.txt compound_receiver_interceptor.go.txt