Codec Negotiation prefers transcoding over re-negotiation

Hi,

I’m seeing a weird behaviour on my Asterisk 15.3.0 with codec negotiation. Asterisk seems to prefer transcoding over selecting another codec.

My provider doesn’t allow transcoding ever. So either I use a codec they support or my call will be rejected. Depending on the destination they allow G722 for HD Calling or they don’t (depending on what the destination supports), I will only know that after the first INVITE.

In my pjsip config I have set for my phones and my providers endpoint:

disallow = all
allow = g722,alaw,ulaw

My phones all support g722, so internally I am always using g722. When calling an external number the phone still always uses G722, even if the other end just supports ulaw. Asterisk does the transcoding in this case (which has a very negative effect on the performance), I would expect it to tell the phone to switch to ulaw (just like my provider seems to do it) if the other end doesn’t support G722 and only do transcoding if there is no common codec between the 2 parties.

Is this a configuration issue or is this maybe even the intended behaviour?

Both chan_sip and chan_pjsip[1] provide mechanisms to allow control over codecs in the dialplan. The chan_pjsip module also provides dialplan control for renegotiation[2]. Things don’t really automatically renegotiate right now, though. Fully flushing out such things is not something anyone has done.

[1] https://wiki.asterisk.org/wiki/display/AST/Asterisk+16+Function_PJSIP_MEDIA_OFFER
[2] https://wiki.asterisk.org/wiki/display/AST/Asterisk+16+Function_PJSIP_SEND_SESSION_REFRESH

So what I would have to do there is only allow alaw/ulaw at first, initiate the call, check if the Provider end supports g722 (how would I do that?) and if it does add it to the allowed codecs and do a renegotiation? Or is it sufficient to do trigger a renegotiation once it is established?

Is automated codec renegotiation planned (or wanted)? Should I open a Bug Report/Feature request for this?

I haven’t mucked with trying to implement such a thing, so I only know of the dialplan functionality available.

As for filing an issue I know of noone working on such a thing as people are generally content with transcoding occurring. We also don’t accept feature requests on the issue tracker currently.

Would this feature-request even go against asterisk or is that something that has to go towards the pjsip project?

Are you sure that PJSIP_MEDIA_OFFER works on both sides? I have written a macro and called it using G(coderenegotiation), disabled g722 by calling Set(PJSIP_MEDIA_OFFER(audio)=!all,alaw,ulaw) and done a Set(PJSIP_SEND_SESSION_REFRESH()=invite) afterwards, checking pjsip history only shows the renegotiation on the provider side, the calling party never received another INVITE after the 183 Session Progress.

You have to see it this way: Transcoding in my case uses 5 times the CPU power of normal bridging, so I can have 5 times less calls than without transcoding. That’s a huge performance impact with absolutely 0 advantage in this case. Also the bandwith is higher than it has to be.

This functionality would be within Asterisk. I haven’t experimented with the dialplan functions within the specific scenario you are doing, so I don’t know the specific interactions.

What platform or device are you using this on?

I’m running this on a router with OpenWRT (so the performance gain for me would be huge in this case, from 2 simulteanous calls to 10 which would be more than enough).

I just found this thread so the demand seems to be there (or was there 10 years ago). I also found this one and when searching for “asterisk unnecessary transcoding” there are many more.

I’d also not consider this a Feature Request but a Bug instead as transcoding should only happen when there is absolutely no other option (and in this case there is another option). So either fixing this or implementing an option per endpoint like “avoid_transcoding” is really necessary I think. Having that option would be the more user-friendly approach as it can be set to true if someone doesn’t want transcoding to happen unless its absolutely necessary (which I think 90% of the users would want, nobody want’s to put load on their servers while getting absolutely no benefit from it) and if someone does indeed want that they can turn it off…

It’d be a feature request to have tighter control over codec negotiation such as to prevent transcoding and renegotiate. The reason that noone has likely proposed and contributed such changes is that your situation is not what most people do. Most individuals do not run Asterisk on such a device as one running OpenWRT and transcoding the codecs which are commonly used are not a burden for them. For example we run hosted Switchvox as well as PBXact and in the grand scheme of things the transcoding is a drop in the bucket.

We review any and all changes that go up for inclusion though so if someone did work on this we’d certainly look it over. I’d urge them though to propose a design and test cases before actually implementing it though. We have to ensure that such changes would not impact users unless they used them.

I can’t agree completely with that: Transcoding just because “we can” is not good. When you run a huge PBX with hundreds of calls at the same time it would probably be an issue pretty soon if you start transcoding most of the calls just because “you can”. Also you could use much smaller, cheaper and energy efficient hardware if you don’t do useless (that’s what it is, we encode in one format knowing that we have to transcode to a different format later on while we would be able to encode directly to the different format) transcoding.

Interestingly 2008 it was already aknowledged as an issue and that there is “a major lack in the entire area of codec negotiation - both pre-call, as well as changing codecs mid-call”, unfortunately things don’t seem to have changed since then, it’s still the same just that nobody calls it a “major lack” anymore, instead over the last 10 years it has become normal. It seems like there was a proposal for something similar and even a patch, but it was rejected because “While this issue has provided a nice patch that has been useful to people, I am going to close it out as we will be proceeding with a different approach”. So is the different approach to ignore the issue and just throw more hardware at it to make it run smooth? That’s how it seems like, I might have missed something though.

If you would be unnecessarily transcoding 80% of your calls I assume pretty soon you would start working on that bug/feature request :wink: If you dare, give it a try and force one of your PBXs to do transcoding on all calls (from G722 to alaw/ulaw) and I think you will run into issues pretty soon.

Now talking about the solution: Asterisk is a huge codebase, first of all the question would be: What branch should it be implemented against? Master or 15 (as I am running 15 and would love to benefit from it). Assuming it’s master, would 15 benefit from it aswell? Same question if it’s 15 for master. After figuring out what branch is the right one, it’s even difficult to figure out where the actual codec negotiation occurs. I would assume that the solution is something as simple as checking if the currently selected codec for the destination is supported by the source and then sending a re-INVITE to the source. Add an additional per-endpoint option to allow/disallow it and it’s done. While this is a very brief description, can anybody think of any issues this might cause?

Do you need the audio to pass through asterisk at all?

If you are not using DTMF based features or do not require call recording why not allow direct RTP between your provider and your endpoint devices?

Many many many situations cause cause transcoding to occur and it’s honestly fine on modern CPUs. The translation path for g722 and ulaw/alaw is actually optimized (it has a direct path) and not particularly costly. It doesn’t even go through the resampler to convert between 8kHz/16kHz. People also force transcoding already through different use cases. I agree it would be nice if things were better in that regard.

Asterisk 15 is no longer supported and does not receive bug fixes or new features[1]. New features can always go into master. New features are eligible for release branches (13 and 16 currently) if they are backwards compatible and also include test coverage. The 16 branch is further along and has stream support and more insight into what was actually negotiated.

As for actually implementing it - it’s not as easy as one would expect. Each side is negotiated independently and except for when the initial request occurs, doesn’t have any contact with the remote side. The new stream frames in Asterisk 16 MAY provide the information but they were done for video support and their audio support isn’t as complete. For example you can’t just reach across and see what the destination negotiated from the source side. It’s not something I’ve given that much thought to ultimately and there’s a lot of legacy code/behavior. I wrote the stream support and even I had trouble understanding how the audio part goes and still don’t fully get it. I do think the new stream support is probably the best bet for having it be possible.

[1] https://wiki.asterisk.org/wiki/display/AST/Asterisk+Versions

Unfortunately I do need it to pass through asterisk. I wish I could get around that but unfortunately I can’t.

I figured out that FreeSWITCH does actually support what I need, and they have it beautifully explained here. So what I am looking for is called Late Negotiation, and seems like there is not even the need for a re-INVITE, it’s simply passing through what A offers to B and letting B select what they would like and send that choice back to A. As you wrote that they don’t have contact “except for when the initial request occurs” we could probably use that contact to pass the list down to the destination, all that’s left is getting the response back to A then once B has made it’s choice.

I think late negotiation is becoming more and more popular and important, I have just done a brief search and seems like asterisk is indeed the only “big” PBX-Project that doesn’t support it (yet).

And thanks for pointing out that I’m running an EOL version, I thought I still have some time left before I have to switch but apparently I ran out of time. Switching to 16 ASAP (while I am cosidering FreeSWITCH at the moment aswell).

Please provide details of the part of RFC 3261 that even imposes a SHOULD requirement for this.

It is a feature request.

(There isn’t even a requirement that the two sides offer codecs with a common sub-set.)

Common sense already says that you should not waste resources for no reason. If I continue your thoughts I’m very soon at this point: Why even care about efficiency at all, there’s no SIP-related RFC that says that a PBX SHOULD/MUST be efficient. Also there’s no RFC prohibiting crashes of the PBX…

Be sure you answer the call before doing codec renegotiation.

-- Executing [xxxxxxx@outgoing:2] Dial("PJSIP/Phone-1-00000076", "PJSIP/xxxxxxxx@Out-Default,,TKM(codecrenegotiation)") in new stack
-- Called PJSIP/xxxxxx@Out-Default
   > 0x119a110 -- Strict RTP learning after remote address set to: xx.xxx.xxx.xxx:25148
   > 0x119a110 -- Strict RTP switching to RTP target address xx.xxx.xxx.xxx:25148 as source
-- PJSIP/Out-Default-00000077 is making progress passing it to PJSIP/Phone-1-00000076
   > 0x1197690 -- Strict RTP learning after remote address set to: 192.168.4.22:50086
   > 0x1197690 -- Strict RTP switching to RTP target address 192.168.4.22:50086 as source
-- PJSIP/Out-Default-00000077 is making progress passing it to PJSIP/Phone-1-00000076
-- PJSIP/Out-Default-00000077 is making progress passing it to PJSIP/Phone-1-00000076
-- PJSIP/Out-Default-00000077 answered PJSIP/Phone-1-00000076
-- Executing [s@macro-codecrenegotiation:1] Set("PJSIP/Out-Default-00000077", "PJSIP_MEDIA_OFFER(audio)=!all,alaw,ulaw") in new stack
-- Executing [s@macro-codecrenegotiation:2] Set("PJSIP/Out-Default-00000077", "PJSIP_SEND_SESSION_REFRESH()=invite") in new stack
-- Channel PJSIP/Out-Default-00000077 joined 'simple_bridge' basic-bridge <9b0bab78-47c5-40b0-a154-d6e0d4e32fa9>
-- Channel PJSIP/Phone-1-00000076 joined 'simple_bridge' basic-bridge <9b0bab78-47c5-40b0-a154-d6e0d4e32fa9>

Was that done too early? Does the bridge need to be intact before I execute that macro? If yes, how can I achieve that?

This is how I do it:

Main Outbound context:
exten => _1NXXNXXXXXX,n,Gosub(Outgoing-Call-Update-Codecs,s,1({AOR},{SIPACCOUNT}))
exten => _1NXXNXXXXXX,n,Dial(PJSIP/{PREFIX}{EXTEN}@{SIPACCOUNT},60,b(Outgoing-Leg-Call-Stats-Handler,s,1())F(Email-Recording,s,1)gKL({TIMELIMIT})RTX)

Subroutine called by above:
[Outgoing-Call-Update-Codecs]
exten => s,1,Answer(5)
exten => s,n,Set(_UPDATEDCODECS={ODBC_ENDPOINTS({ARG2},allow)})
exten => s,n,Set(PJSIP_MEDIA_OFFER(audio)={UPDATEDCODECS}) exten => s,n,Set(PJSIP_MEDIA_OFFER(video)=!all) exten => s,n,GotoIf([ “{ARG1}" = "" ]?NoAor) exten => s,n,Set(REFRESHTYPE={ODBC_ENDPOINTS({ARG1},connected_line_method)}) exten => s,n(NoAor),ExecIf([”{REFRESHTYPE}" = ""]?Set(REFRESHTYPE=invite)) exten => s,n,Set(PJSIP_SEND_SESSION_REFRESH()={REFRESHTYPE})
exten => s,n,Return()

This uses a realtime database to pull the variables from but should give you an idea about what I meant.
I use this to be able to call outbound through the providers using ulaw and still be able to use g722 internaly. I don’t know if it would be able to connect to the provider using g722 as well though as I don’t have any providers that offer it.

Easiest solution seems to be to just disallow G.722 towards the carrier. I don’t think anyone is going to be disappointed when a call that they had no expectation of being G.722 (due to not all calls being delivered with G.722 by the carrier) ends up being G.711. You’ll have HD voice within the company and good old G.711 for external calls, which is still perfectly acceptable.

1 Like