Structured, Real-time SDP Manipulation in OpenSIPS 3.6

The ability to do fine but complex changes over the SDP at opensips.cfg level is as important as doing it for the SIP signaling. Such ability has been on our radar for quite some time now, as it was initially targeting the OpenSIPS 3.2 release.

But what are the problems, the reasons calling for this SDP related ability:

  • the need of pushing and merging changes over the SDP from different modules . The sipmsgops or rtpproxy modules are changing the SDP, the script also, but we had no (easy) way to make the changes visible between all the involved party (like if I change the codec order via sipmsgops, the rtpengine later will not see that)
  • no granular and structured access (read write) over the SDP. So far, from script level, you are able to line-oriented or regexp based changes over the SDP, without being able to follow its structure (like if an a line is part of the session, of a stream or of which stream)

Now, in OpenSIPS 3.6, we do solved these two problems – not only you do have fine-grained control over the SDP body, but also your SDP changes will become visible the moment you do them. By and large, there are two big improvements here:

Real-time Changes over the SDP Body

Starting with OpenSIPS 3.6, you can perform real-time changes over an arbitrary SDP (e.g. from current SIP message or an arbitrary holder), using the four new SDP manipulation variables: $sdp, $sdp.session, $sdp.stream and $sdp.line. When working with them, all changes are visible in real-time, as they enable Read/Write operations in any combination.

But how does this all play together with the multitude of SDP-mangling modules, such as rtpproxy, rtpengine, nathelper, sipmsgops, compression, etc.? Won’t there be many breakages?!

Without delving too deep here, we chose to deliver the Structured SDP feature in two steps. In this first step (3.6 release), we’ve kept the module code unchanged, in order to minimize the amount of breakages. Some modules are more complex than others, some perform a duplicated, custom SDP parsing logic (e.g. rtpproxy) and it’s difficult to fully rewrite them while still retaining stability, in a short window of time. Thus, for now they will continue to add body_lumps just as before, and the very first, subsequent “SDP Operation” using the new code will trigger a full SDP rebuild. And if you call codec_delete() afterwards (+ lumps) and then try to edit a random $sdp.line afterwards, then you will trigger another full SDP rebuild on first usage only. And so on.

Note: this first implementation makes the tacit assumption of a single SDP body in the current SIP message. If you are using OpenSIPS and running into scenarios with multi-part bodies with multiple SDP payloads that require editing… then please open up a feature request and we will fit it into the next release 😊

Fine-Grained Access to the SDP Body

For full details on the capabilities of the new variables, please refer to the opensips.org wiki. Here, I just want to pose a few questions, so you can get a good picture of what’s possible with OpenSIPS 3.6:

  • how do I change the bitrate of the Opus codec, but only in the 2nd audio stream?
  • how do I ensure a ptime:20 for audio streams only, without appending duplicate “a=ptime” lines?
  • how do I ensure a “a=nortpproxy:yes” line on each audio stream, without creating duplicates?

Below is a sample SDP snippet, showing a session with four streams going on: an audio, a video stream, then a slide-sharing video stream and, finally, a floor control stream to moderate the conference call.

With such SDP bodies, the SDP mangling support of the sipmsgops module begins to display its limitations, as its approach of regex-based control over such content becomes increasingly difficult and error-prone:

v=0
o=Magor 1429539042 1429539044 IN IP4 20.30.40.50
s=Magor
c=IN IP4 20.30.40.50
b=TIAS:2048000
b=AS:2048
b=CT:2048
t=0 0
m=audio 20052 RTP/AVP 115 9 120 6 70 0 8 99 97 112 101 # stream 0
a=rtpmap:115 G7221/32000
a=fmtp:115 bitrate=48000
a=rtpmap:9 G722/8000
a=rtpmap:120 SILK/24000
a=fmtp:120 useinbandfec=1; usedtx=0
a=rtpmap:6 DVI4/16000
...
a=rtpmap:97 iLBC/8000
a=fmtp:97 mode=30
a=rtpmap:112 opus/48000/2
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-16
a=silenceSupp:off - - - -
m=video 20056 RTP/AVP 96 97 # stream 1
b=TIAS:2048000
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=42e016; max-fs=8192; max-mbps=244800
a=rtpmap:97 H264/90000
a=fmtp:97 profile-level-id=64e016; max-fs=8192; max-mbps=244800
a=content:main
a=rtcp-fb:* ccm fir
m=video 20060 RTP/AVP 96 97 # stream 2
b=TIAS:2048000
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=42e016; max-fs=8192; max-mbps=244800
a=content:slides
a=rtcp-fb:* nack pli
m=application 20064 UDP/BFCP * # stream 3
a=floorctrl:c-only
a=setup:passive
a=connection:new
a=bfcpver:1

While this is barely a moderately-complex SDP sample, we can already see how using a regex-approach to editing it starts being problematic. You could edit the wrong attribute, you could insert duplicates or even cause malformed syntax. In contrast, the new $sdp variables are flexible, lightweight and the resulting opensips.cfg code is concise and readable.

For example, say we wanted to change the H264 bitrate to 60000, but only on payload-type 97 and on the “video” stream, not on the “slides” stream. Using the classic approach, the best attempt would be:

# bad: changes PT 96 instead of PT 97
replace_body("H264/90000", "H264/60000");

# bad: changes PT 96 + PT 97 + "slides" stream
replace_body_all("H264/90000", "H264/60000");

In short — there is not even a way to achieve this with the current support. With the new variables, this operation now becomes possible!

I will be going through all the $sdp pseudo-variable features part of the new SDP Manipulation support, in full details, on my Wednesday talk in the OpenSIPS Summit 2025, Amsterdam. Looking forward to seeing you there!

— Liviu

Leave a comment