At Sipfront, we use sipp to drive the SIP signaling part of our load tests, because sipp gives us the flexibility to craft our desired SIP messages, and it’s unbeaten in signaling performance. We do not utilize it at all to drive the RTP generation (that’s handed off to kamailio and rtpengine), but we do use it to negotiate the codecs to be used in the RTP stream.

In this blog post, we’ll look into how to negotiate the OPUS codec using sipp in an SDP offer/answer.

Negotiating codecs with SDP

One crucial part of the SIP signaling is the Session Description Protocol (SDP), which describes multimedia sessions in a format understood by both the caller and the callee. When setting up a SIP call, the codecs that will be used for the call are negotiated using SDP. Traditional codecs have a fixed map id assigned to it, making it easy to refer to them (e.g. PCMU being 0, and PCMA being 8).

An example SDP offer for a call proposing PCMU and PCMA could look like this:

v=0
o=- 987654321 987654321 IN IP4 192.168.1.101
c=IN IP4 192.168.1.101
t=0 0
m=audio 10000 RTP/AVP 0 8
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000

Using sipp, you can hard-code your answer in your UAS scenario file like that to negotiate PCMA:

      <send>
        <![CDATA[  
            SIP/2.0 200 OK
            [last_Via:]
            [last_From:]
            [last_To:];tag=[call_number]
            [last_Call-ID:]
            [last_CSeq:]
            [last_Record-Route:]
            Contact: <sip:[local_ip]:[local_port];transport=[transport]>
            Content-Type: application/sdp
            Content-Length: [len]

            v=0
            s=-
            c=IN IP[media_ip_type] [media_ip]
            t=0 0
            m=audio [media_port] RTP/AVP 8
            a=rtpmap:8 PCMA/8000
        ]]>
      </send>

Understanding RTP map IDs

RTP map IDs as seen above serve as identifiers for the encoding methods or codecs used in the RTP payload. These IDs enable the receiver to identify and properly decode the incoming RTP packets.

Static RTP payload types are predefined and have values between 0 to 95. Examples include:

  • PCMU (G.711 ulaw): RTP map ID = 0
  • PCMA (G.711 alaw): RTP map ID = 8

The full list of static map ids is defined in RFC3551.

The advantage of static payload types is that both ends of a communication link immediately know which codec corresponds to a given number.

Dynamic RTP payload types are dynamically negotiated and have values between 96 to 127. The OPUS codec falls into this category. In this case, the involved parties have to store the mapping between the dynamic RTP payload type and the codec used, as well as additional information for the codec being negotiated, such as forward error correction etc.

Using sipp to negotiate OPUS

A typical SDP offer containing OPUS and PCMA will look like this:

v=0
o=- 123456789 123456789 IN IP4 192.168.1.100
c=IN IP4 192.168.1.100
t=0 0
m=audio 1000 RTP/AVP 96 8
a=rtpmap:96 opus/48000/2
a=fmtp:96 useinbandfec=1; minptime=10; maxplaybackrate=48000
a=rtpmap:8 PCMA/8000

Since OPUS can have a dynamic RTP map id anywhere between 96 and 127, we cannot just hardcode 96 in our response, rather than having to match the inbound map id for OPUS.

We can do this by utilizing the ereg action of sipp for the incoming SIP INVITE:

    <recv request="INVITE">
        <action>
            <assign assign_to="opus_id" value="1000" />
            <assignstr assign_to="rtp_maps" value="8" />
            <assignstr assign_to="opus_sdp" value="" />
            <ereg regexp="a=rtpmap:([[:digit:]]+) opus"
                  search_in="body"
                  assign_to="1,opus_id"/>
            <log message="Inbound invite has opus rtpmap '[$opus_id]', full map is '[$1]'"/>
            <!-- less_than seems to be the only op that works in sipp? -->
            <test assign_to="add_opus" variable="opus_id" compare="less_than" value="1000" />
            <log message="Var add_opus is '[$add_opus]'"/>
        </action>
    </recv>

This will parse the inbound SDP for the opus rtpmap and assign it to the variable opus_id. It will also set a flag add_opus indicating whether OPUS has been offered at all.

What’s interesting here is that when playing with various test operators, only less_than seemed to work for us, which is why we have to set a threshold of 1000 to determine whether OPUS has been offered at all. Your milage might vary here.

Using these two flags, we can start preparing the content for our SDP answer:

    <nop condexec="add_opus">
        <action>
            <log message="Adding opus to rtpmap for reply"/>
            <assignstr assign_to="rtp_maps" value="8 [$opus_id]" />
            <assignstr assign_to="opus_sdp" value="\na=rtpmap:[$opus_id] opus/48000/2\na=fmtp:[$opus_id] stereo=1;sprop-stereo=1;maxaveragebitrate=40000;cbr=0;useinbandfec=1;usedtx=0" />
            <log message="Var opus_sdp is '[$opus_sdp]'"/>
        </action>
    </nop>

So if the add_opus flag is set, we add the OPUS rtpmap id to our list of accepted codecs. Note that in this case, we add it after PCMA. That might not be exactly what you want, so you need to tweak here to put OPUS first if you need to, or remove the PCMA id (8), depending on your use case. Based on the OPUS map ID we extracted, we also add an fmtp attribute defining the details of our OPUS answer. Depending on how you generate the OPUS stream, that might be different for you too. Using rtpengine, it will honor those flags and generate OPUS accordingly in our Sipfront setup.

What’s left is sending the answer back using a SIP 200 OK response.

    <send>
        <![CDATA[  
            SIP/2.0 200 OK
            [last_Via:]
            [last_From:]
            [last_To:];tag=[call_number]
            [last_Call-ID:]
            [last_CSeq:]
            [last_Record-Route:]
            Contact: <sip:[local_ip]:[local_port];transport=[transport]>
            Content-Type: application/sdp
            Content-Length: [len]

            v=0
            o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
            s=-
            c=IN IP[media_ip_type] [media_ip]
            t=0 0
            m=audio [media_port] RTP/AVP [$rtp_maps]
            a=rtpmap:8 PCMA/8000[$opus_sdp]
            a=sendrecv
        ]]>
    </send>

Conclusion

Using sipp, you can easily negotiate dynamic RTP map IDs for OPUS. The same approach can be used to negotiate other dynamic codecs, such as AMR and others. Make sure to adapt the messages above to strip unnecessary parts (such as always answering with PCMA in addition to an optional OPUS offer).

comments powered by Disqus