sipwise / rtpengine

The Sipwise media proxy for Kamailio
GNU General Public License v3.0
777 stars 368 forks source link

How to specify bit rate when transcoding to AMR? #681

Closed whyteks closed 5 years ago

whyteks commented 5 years ago

Hi!

context: Recently I had to use AMR at 12k2 for 3G voice and ended up having to change the default in codeclib.c in order to get this to work, (the default is wrong by the way, 6600 is not a valid AMR bitrate)

I have been trying to figure out how to specify the the desired bitrate.

In a test setup, in kamailio's rtpengine_manage() I currently have this flag: codec-transcode-AMR/8000/1/7400

If I add some extra logging in __ssrc_handler_transcode_new()

    ilog(LOG_INFO, "Creating SSRC transcoder from %s/%u/%i/%i to "
            "%s/%u/%i/%i",
            h->source_pt.codec_def->rtpname, h->source_pt.clock_rate,
            h->source_pt.channels, h->source_pt.bitrate,
            h->dest_pt.codec_def->rtpname, h->dest_pt.clock_rate,
            h->dest_pt.channels, h->dest_pt.bitrate);

I get

[1546468599.779062] INFO: [483a889d-c19f4587@192.168.11.121]: Creating SSRC transcoder from AMR/8000/1/7400 to PCMA/8000/1/0 We then call encoder_config_fmtp() with the dest_pt and decoder_new_fmtp() with the source_pt and this is where I am a bit lost.

Initially I thought that something was maybe getting swapped someplace, I got as far as here, and it all looks ok:

` 0x00005585bb5bfe8d in __make_transcoder (dest=0x7fd2f400b000, source=0x7fd2f400b4f0, handler=0x7fd2f4011000) at codec.c:172 172 in codec.c

(gdb) print *dest $43 = {payload_type = 8, encoding_with_params = {s = 0x7fd2f4003c68 "PCMA/8000", len = 9}, encoding = {s = 0x7fd2f4003c72 "PCMA", len = 4}, clock_rate = 8000, encoding_parameters = {s = 0x0, len = 0}, channels = 1, format_parameters = {s = 0x0, len = 0}, ptime = 20, bitrate = 0, codec_def = 0x5585bb7e4800 <__codec_defs>}

(gdb) print *source $44 = {payload_type = 98, encoding_with_params = {s = 0x7fd2f4003d37 "AMR/8000", len = 8}, encoding = {s = 0x7fd2f4003d40 "AMR", len = 3}, clock_rate = 8000, encoding_parameters = {s = 0x7fd2f4003d44 "", len = 0}, channels = 1, format_parameters = {s = 0x7fd2f4003d45 "octet-align=1", len = 13}, ptime = 20, bitrate = 7400, codec_def = 0x5585bb7e5190 <__codec_defs+2448>} `

From my point of view in kamailio, I am saying, I'm sending an INVITE and I expect AMR so I should spec the bitrate here (and I should include mode= in the SDP)

Then we should intialise the transcoder with the correct bitrate, no? So when the 200 comes back, we init the other side of the codec. I have this in kamailio: codec-transcode-PCMA/8000/1

Then I see [1546472176.856994] INFO: [569be4c3-d4cb9b4b@192.168.11.121]: Creating SSRC transcoder from PCMA/8000/1/0 to AMR/8000/1/0 [1546472176.886303] WARNING: [569be4c3-d4cb9b4b@192.168.11.121]: av_log: bitrate not supported: use one of 4.75k, 5.15k, 5.90k, 6.70k, 7.40k, 7.95k, 10.20k, 12.20k, using 6.70k

The warning is due to the 6600 in the default (maybe you left it that way to see this??) So now we're sending AMR to the endpoint at 6k7 :(

I'm probably missing something, I spent a long time tracing through the code today and checking values in gdb so I'm probably too tired to be even writing this ticket.... but i thought maybe best to open the conversation.

to sum up, I can't find anyway to change the AMR bitrate other than patching codeclib.c

Thanks!

whyteks commented 5 years ago

What I wrote above is looking kind of cryptic, now with a slight fresh look.

I'm starting to think this might be a hard problem, or at least not trival. It may involve the need to parse mode-* params as described in RFC4867 Section 8.3, and indeed add mode-set to the fmtp in the case that we are initiating the AMR. I'm not sure however, how the AMR library has to be initialised in order to support later mode changing in RTP .

For the moment, I'd like to try to clarify this:

Initiating a call from PCMA only SIP UA, destined for the GSM side, we know we have to transcode to/from AMR, so we have codec-mask-all AND codec-transcode-AMR/8000/1/7400 in kamailio.

So we are now going to send the INVITE to the GSM side, and ask it to send AMR, (we should expect ringback early media) As far as I can make out, we first initialise an encoder prepared to accept this AMR and tx to PCMA:

 Creating SSRC transcoder from AMR/8000/1/7400 to PCMA/8000/1/0 

So we are calling encoder_config_fmtp() the key param here being h->dest_pt.codec_def which is PCMA codec definition.

Then we call decoder_new_fmtp() with h->source_pt.codec_def but we do not pass the bitrate param from the kamailio ng comand anywhere. Ah.. but we pass the fmtp. does the lib parse mode-set? OK.. I'll be back.

whyteks commented 5 years ago

So.. from decoder_new_fmtp() we are calling amr_set_encdec_options() with the fmtp string, but here we are not parsing for mode-set. In fact we maybe cannot set the bitrate anywhere for the decoder, because decoder_s has no element bitrate. but shouldn't we be telling the AMR decoder what bitrate to expect?

rfuchs commented 5 years ago

The problem is that the association between the AMR decoder (which pointlessly knows the bitrate we want to use) and the AMR encoder (which is created during the answer phase and so doesn't know the bitrate) is missing. Working on this right now.

rfuchs commented 5 years ago

As a side note, you shouldn't specify any codec/transcode flags in the answer, as the codec matchup is done automatically using the flags from the offer. (I will probably disallow/ignore those flags in an answer in the near future to prevent this common mistake.) But this also means that it's not possible to specify a custom bitrate for codecs that are implicitly accepted for transcoding. Fix coming for this also.

whyteks commented 5 years ago

Looking at the reverse situation where the call is originated from the GSM side:

If the GSM network is configured to use AMR mode 4 (7k4) then we setup a transcoder:

Creating SSRC transcoder from PCMA/8000/1/0 to AMR/8000/1/0

to decode early media from the PCMA leg, but we end up sending 6k7:

av_log: bitrate not supported: use one of 4.75k, 5.15k, 5.90k, 6.70k, 7.40k, 7.95k, 10.20k, 12.20k, using 6.70k

because there is in fact, no way to specify the bitrate parameter in the NG control, that is, the param that ends up reaching __ssrc_handler_transcode_new() as h->dest_pt.bitrate. This is ALWAYS going to be zero, hence here:

    if (encoder_config_fmtp(ch->encoder, h->dest_pt.codec_def,
            h->dest_pt.bitrate ? : h->dest_pt.codec_def->default_bitrate,
            ch->ptime,
            &enc_format, &ch->encoder_format, &h->dest_pt.format_parameters))

h->dest_pt.codec_def->default_bitrate is ALWAYS going to be used. In this test case then, the result is that AMR 6k7 reaches GSM side and is not decoded as it is not part of the allowed AMR multirate set.

so what are the options? Allow this to be configured in NG? or parse the fmtp from the SIP UA for mode-set?

whyteks commented 5 years ago

oh hi Richard, you posted while I was composing that last one...

so 1) Yes, I confirmed for myself that informing the decoder of the bitrate is pointless, it just decodes what it gets. 2) On specifying codec/transcode flags in the answer(), I am using rtpengine_manage() in kamailio, one distinct line for each direction. I guess I need then to check if it is the original INVITE or a 183/200 and leave out the codec/transcode on the latter.

current config block (wrong?): 172.16.0.1 is the SIP gateway on the GSM side.

    if (src_ip=~"172\.16\.0\.1") {
            if (is_method("INVITE") && has_body("application/sdp")) {
                    rtpengine_manage("replace-origin replace-session-connection codec-mask-all codec-transcode-PCMA/8000/1 ICE=remove");
            }
    } else {
            if (is_method("INVITE") && has_body("application/sdp")) {
                    rtpengine_manage("replace-origin replace-session-connection codec-mask-all codec-transcode-AMR/8000/1/7400 ICE=remove");
            }
    }
rfuchs commented 5 years ago

Should be good, except that as mentioned, the answer should not contain codec/transcoding options. I'll add a commit to fix this as well.

For setting the bitrate for accepted codecs, there will be a codec-set option, so you would have codec-transcode-PCMA/8000/1 codec-set-AMR/8000/1/7400

whyteks commented 5 years ago

For reference, I did this to be compliant. (maybe there's better ways, I'm no kamailio config expert):

    if (src_ip=~"172\.16\.0\.1") {

            if (is_method("INVITE") && has_body("application/sdp")) {
                    if ($mt == 1) {
                            rtpengine_manage("replace-origin replace-session-connection codec-mask-all codec-transcode-PCMA/8000/1 ICE=remove");
                    } else {
                            rtpengine_manage("replace-origin replace-session-connection");
                    }
            }

    } else {

            if (is_method("INVITE") && has_body("application/sdp")) {
                    if ($mt == 1) {
                            rtpengine_manage("replace-origin replace-session-connection codec-mask-all codec-transcode-AMR/8000/1/7400 ICE=remove");
                    } else {
                            rtpengine_manage("replace-origin replace-session-connection");
                    }

            }
    }
whyteks commented 5 years ago

BTW, what do you think about parsing mode-set from fmtp as well? the RFC says we should IIUC.

rfuchs commented 5 years ago

The above should work as long as the offer/answer always occurs in the INVITE/200. SIP also allows for offer/answer in 200/ACK, with the original INVITE carrying no SDP.

Re mode-set, I don't believe ffmpeg supports this, so we're out of luck here.

rfuchs commented 5 years ago

Topmost commit for these fixes is e1d6c83 or branch rfuchs/681

whyteks commented 5 years ago

Nice! This works for me now in both directions with AMR mode 4 active on the GSM side and this kamailio snippet:

    if (src_ip=~"172\.16\.0\.1") {

            if (is_method("INVITE") && has_body("application/sdp")) {
                    if ($mt == 1) {
                            rtpengine_manage("replace-origin replace-session-connection codec-mask-all codec-set-AMR/8000/1/7400 codec-transcode-PCMA/8000/1 ICE=remove");
                    } else {
                            rtpengine_manage("replace-origin replace-session-connection");
                    }
            }

    } else {

            if (is_method("INVITE") && has_body("application/sdp")) {
                    if ($mt == 1) {
                            rtpengine_manage("replace-origin replace-session-connection codec-mask-all codec-transcode-AMR/8000/1/7400 ICE=remove");
                    } else {
                            rtpengine_manage("replace-origin replace-session-connection");
                    }

            }
    }
rfuchs commented 5 years ago

With all of the commits applied, you should be able to remove the case distinction between offer and answer.