星号停止发送RTP数据

| 我正在使用JAIN SIP API的NIST实现在Java中开发SIP控制器。 我无法通过Asterisk进行从SIP控制器到软件电话的呼叫。 如果我直接使用其IP地址和端口号(而不是通过Asterisk)呼叫该软件电话,则一切正常。呼叫建立,软电话听到我发送的音频(RTP数据),并且我可以接收它发送给我的音频。 但是,当我通过Asterisk呼叫相同的软件电话时,呼叫建立了,并且我开始从软件电话(通过Asterisk)接收RTP数据。现在,我的发送流需要花费一些时间来设置,但是在配置它的同时,我从软件电话接收RTP数据。问题是,一旦我的发送流被初始化并开始传输RTP数据,我就停止从软件电话接收RTP数据!结果是在建立呼叫后,我最多听到软电话半秒钟或一秒钟,然后什么都听不到。在此阶段,网络电话可以听到我传出的RTP数据,但我听不到。 如果我不开始发送任何RTP数据,则继续从软件电话接收RTP数据。但是,一旦我开始传输,它就会停止! 在有帮助的情况下,这是建立呼叫的SIP对话的类型(>>表示呼出消息,而<<表示呼入消息):
>> INVITE sip:301@asterisk SIP/2.0  
Call-ID: 8b92ba1ca9c922bcd266dce086596ce4@10.0.85.3  
CSeq: 1 INVITE  
From: <sip:null>;tag=JqbJKA  
To: <sip:301@asterisk>  
Via: SIP/2.0/UDP 10.0.85.3:5060;branch=z9hG4bK34d24b3f748ac08a5ca46f500f110d38353436  
Max-Forwards: 70  
Contact: <sip:10.0.85.3:5060>  
Route: <sip:10.0.84.30;lr>  
Content-Type: application/sdp  
Content-Length: 106

v=0  
o=- 3515232260 3515232260 IN IP4 10.0.85.3  
s=-  
c=IN IP4 10.0.85.3  
t=0 0  
m=audio 42138 RTP/AVP 0  
a=rtpmap:0 PCMU/8000

<< SIP/2.0 407 Proxy Authentication Required  
Via: SIP/2.0/UDP 10.0.85.3:5060;branch=z9hG4bK34d24b3f748ac08a5ca46f500f110d38353436;received=10.0.85.3  
From: <sip:null>;tag=JqbJKA  
To: <sip:301@asterisk>;tag=as7077f414  
Call-ID: 8b92ba1ca9c922bcd266dce086596ce4@10.0.85.3  
CSeq: 1 INVITE  
User-Agent: Asterisk PBX (switchvox)  
Allow: INVITE,ACK,CANCEL,OPTIONS,BYE,REFER,SUBSCRIBE,NOTIFY  
Contact: <sip:301@10.0.84.30>  
Proxy-Authenticate: Digest realm=\"asterisk\",nonce=\"4a1cbda4\"  
Content-Length: 0


>> INVITE sip:301@asterisk SIP/2.0  
CSeq: 2 INVITE  
From: <sip:303@asterisk>;tag=JqbJKA  
To: <sip:301@asterisk>  
Via: SIP/2.0/UDP 10.0.85.3:5060;branch=z9hG4bKd1870f50e9fbf883b3e64fa3ef75dda9353436
Max-Forwards: 70  
Contact: <sip:10.0.85.3:5060>  
Route: <sip:10.0.84.30;lr>  
Proxy-Authorization: Digest username=\"303\",realm=\"asterisk\",nonce=\"4a1cbda4\",response=\"249b2b7d7c0e7b54499c632ba410365c\",algorithm=MD5,uri=\"sip:301@asterisk\",nc=00000001  
Call-ID: 8b92ba1ca9c922bcd266dce086596ce4@10.0.85.3  
Content-Type: application/sdp  
Content-Length: 106

v=0  
o=- 3515232260 3515232260 IN IP4 10.0.85.3  
s=-  
c=IN IP4 10.0.85.3  
t=0 0  
m=audio 42138 RTP/AVP 0  
a=rtpmap:0 PCMU/8000`

`<< SIP/2.0 100 Trying  
Via: SIP/2.0/UDP 10.0.85.3:5060;branch=z9hG4bKd1870f50e9fbf883b3e64fa3ef75dda9353436;received=10.0.85.3  
From: <sip:303@asterisk>;tag=JqbJKA  
To: <sip:301@asterisk>  
Call-ID: 8b92ba1ca9c922bcd266dce086596ce4@10.0.85.3  
CSeq: 2 INVITE  
User-Agent: Asterisk PBX (switchvox)  
Allow: INVITE,ACK,CANCEL,OPTIONS,BYE,R EFER,SUBSCRIBE,NOTIFY  
Contact: <sip:301@10.0.84.30>  
Content-Length: 0


`<< SIP/2.0 180 Ringing  
Via: SIP/2.0/UDP 10.0.85.3:5060;branch=z9hG4bKd1870f50e9fbf883b3e64fa3ef75dda9353436;received=10.0.85.3  
From: <sip:303@asterisk>;tag=JqbJKA  
To: <sip:301@asterisk>;tag=as00faa25e  
Call-ID: 8b92ba1ca9c922bcd266dce086596ce4@10.0.85.3  
CSeq: 2 INVITE  
User-Agent: Asterisk PBX (switchvox)  
Allow: INVITE,ACK,CANCEL,OPTIONS,BYE,REFER,SUBSCRIBE,NOTIFY  
Contact: <sip:301@10.0.84.30>  
Content-Length: 0`


<< SIP/2.0 200 OK  
Via: SIP/2.0/UDP 10.0.85.3:5060;branch=z9hG4bKd1870f50e9fbf883b3e64fa3ef75dda9353436;received=10.0.85.3  
From: <sip:303@asterisk>;tag=JqbJKA  
To: <sip:301@asterisk>;tag=as00faa25e  
Call-ID: 8b92ba1ca9c922bcd266dce086596ce4@10.0.85.3  
CSeq: 2 INVITE  
User-Agent: Asterisk PBX (switchvox)  
Allow: INVITE,ACK,CANCEL,OPTIONS,BYE,REFER,SUBSCRIBE,NOTIFY  
Contact: <sip:301@10.0.84.30>  
Content-Type: application/sdp  
Content-Length: 154

v=0  
o=root 2593 2593 IN IP4 10.0.84.30  
s=session  
c=IN IP4 10.0.84.30  
t=0 0  
m=audio 10294 RTP/AVP 0  
a=rtpmap:0 PCMU/8000  
a=silenceSupp:off - - - -

>> ACK sip:301@10.0.84.30 SIP/2.0  
Call-ID: 8b92ba1ca9c922bcd266dce086596ce4@10.0.85.3  
CSeq: 2 ACK  
Via: SIP/2.0/UDP 10.0.85.3:5060;branch=z9hG4bK7e16ebc0de9c6eaf901db0e2e58f495f353436  
From: <sip:303@asterisk>;tag=JqbJKA  
To: <sip:301@asterisk>;tag=as00faa25e  
Max-Forwards: 70  
Contact: <sip:10.0.85.3:5060>  
Content-Length: 0
这是设置RTP会话的代码。首先是一些声明:
private RTPManager sessionManager = null;  
private Processor processor = null;  
private SendStream sendStream;`
首先调用以下方法:
public void startMedia(String peerIp,int peerPort,int receivePort,String format) throws IOException,MediaException,InvalidSessionAddressException  
{  
    stopMedia();  
    this.format = format;  
    RTPSessionMgr rtpSessionMgr = new RTPSessionMgr();  
    rtpSessionMgr.initSession(new SessionAddress(),null,0.05,0.25);  
    InetAddress localhost = InetAddress.getLocalHost();  
    SessionAddress localAddr = new SessionAddress(localhost,receivePort,localhost,receivePort + 1);  
    InetAddress destAddr = InetAddress.getByName(peerIp);  
    rtpSessionMgr.startSession(localAddr,localAddr,new SessionAddress(destAddr,peerPort,destAddr,peerPort + 1),null);  
    sessionManager = rtpSessionMgr;  
    for (ReceiveStreamListener nextListener : receiveStreamListeners)  
        sessionManager.addReceiveStreamListener(nextListener);  
}
然后,要开始通过RTP播放声音,此方法称为:
public void transmitSound(DataSource ds) throws NoProcessorException,IOException,UnsupportedFormatException,NotRealizedError  
{  
    stopTransmittingSound();  
    processor = Manager.createProcessor(ds);  
    for (ControllerListener nextListener : controllerListeners)  
        processor.addControllerListener(nextListener);  
    processor.addControllerListener(myControllerListener);  
    processor.configure();  
}
这是控制器侦听器的controllerUpdate()方法:
public void controllerUpdate(ControllerEvent event)  
    {  
        if (processor.getState()==Processor.Configured)  
        {  
            processor.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW_RTP));  
            processor.getTrackControls()[0].setFormat(new AudioFormat(format,8000,8,1));  
            processor.realize();  
        }  
        else if (processor.getState()==Processor.Realized)  
        {  
            try  
            {  
                sendStream = sessionManager.createSendStream(processor.getDataOutput(),0);  
                sendStream.start();  
                processor.start();  
            }  
            catch (IOException e)  
            {  
                e.printStackTrace();  
            }  
            catch (UnsupportedFormatException e)  
            {  
                e.printStackTrace();  
            }  
            catch (NotRealizedError e)  
            {  
                e.printStackTrace();  
            }  
        }  
    }
发送ACK后基本上会发生以下情况: 我创建一个RTP会话进行传输和监听。 我开始初始化用于传输RTP的处理器。 同时,我收到了很多RTP数据。 处理器完成初始化,我开始发送RTP数据。 在此阶段,如果通过Asterisk,我将停止接收RTP数据。如果直接拨打网络电话,一切正常。 有任何想法吗?     
已邀请:
您确定对RTP发送部分的处理正确吗?根据我的理解,应该有一个用于发送和接收的套接字fd。您要为发送部件创建新的套接字fd并关闭recv fd吗?请检查并回复 您也可以有两个套接字fds,一个用于接收,另一个用于发送。 RTP RFC-3550没有提及任何实现。     
您应该尝试在邀请中添加媒体属性,如果您也使用ulaw,则可以添加:
a=rtpmap:0 PCMU/8000
还可以尝试进行更简单的测试,而不是拨打软件电话:
301,1,Answer
301,2,Echo
Echo将从您的客户端捕获rtp流并将其发送回给您。如果一切正常,那么您可以在2台正常工作的软电话之间拨打电话,并与您的客户比较跟踪。 另外,如果可能,请尝试发布您的拨号计划和两个用户的配置。 (技巧提示:如果为两个用户启用canreinvite = yes或directrtpsetup = yes,他们将能够直接在彼此之间交换rtp流,而不必使用星号作为桥接器)     
听起来像Asterisk可能正在尝试重新邀请您的呼叫,以使其直接在您的SIP应用程序和软件电话之间流动。 Asterisk这样做的优点是它使媒体路径更有效,Asterisk服务器将不再仅桥接信令的呼叫媒体。缺点是,如果涉及到NAT或SIP用户代理不支持re-INVITE,则可能会导致RTP无法通过,而您的情况就是如此。 如果是重新邀请问题,那么首先您应该能够使用SIP调试看到额外的邀请请求到达您的SIP应用程序或Asterisk控制台。其次,您可以通过在所使用的SIP帐户上设置canreinvite = no来停止Asterisk进行重新邀请。     
作为记录,我决定尝试使用其他PBX。我已经下载并安装了3CX电话系统,并且使用此PBX,一切正常! 现在,测试版的客户端在他的站点上使用Patton,所以我只希望这个问题特定于我们的Asterisk设置,并且不会在那里出现。     
我终于解决了这个问题!事实证明,问题不在于SIP消息,而在于建立RTP会话的代码。我仍然不太确定出了什么问题,但是似乎该代码仅在直接调用软件电话(即,不是通过PBX)或软件电话与PBX使用相同的IP地址时才有效。 这是错误的代码:
public void startMedia(String peerIp,int peerPort,int receivePort,String format) throws IOException,MediaException,InvalidSessionAddressException  
{  
    stopMedia();  
    this.format = format;  
    RTPSessionMgr rtpSessionMgr = new RTPSessionMgr();  
    rtpSessionMgr.initSession(new SessionAddress(),null,0.05,0.25);  
    InetAddress localhost = InetAddress.getLocalHost();  
    SessionAddress localAddr = new SessionAddress(localhost,receivePort,localhost,receivePort + 1);  
    InetAddress destAddr = InetAddress.getByName(peerIp);  
    rtpSessionMgr.startSession(localAddr,localAddr,new SessionAddress(destAddr,peerPort,destAddr,peerPort + 1),null);  
    sessionManager = rtpSessionMgr;  
    for (ReceiveStreamListener nextListener : receiveStreamListeners)  
        sessionManager.addReceiveStreamListener(nextListener);  
}
这段代码改编自一本关于Java SIP编程的书(我想为了维护作者的声誉,我不应该分享那本书)。 当我去看
RTPManager
类的javadoc时,我在文档中发现了一些示例代码,用于设置单播会话并使其适应我的情况。这是有效的更新的
startMedia()
方法:
public void startMedia(String peerIp,int peerPort,int receivePort,String format,int sampleRate,int sampleSizeInBits) throws IOException,MediaException,InvalidSessionAddressException
    {
        stopMedia();
        this.format = format;
        this.sampleRate = sampleRate;
        this.sampleSizeInBits = sampleSizeInBits;

sessionManager = RTPManager.newInstance();
        SessionAddress localAddress = new SessionAddress(InetAddress.getLocalHost(),receivePort);
        sessionManager.initialize(localAddress);
        for (ReceiveStreamListener nextListener : receiveStreamListeners)
            sessionManager.addReceiveStreamListener(nextListener);
        InetAddress ipAddress = InetAddress.getByName(peerIp);
        SessionAddress remoteAddress = new SessionAddress(ipAddress,peerPort);
        sessionManager.addTarget(remoteAddress);
    }
如您所见,尽管该代码使用相同的类,但它与我在本书中发现的代码有很大不同(这使得很难确定问题所在),但是它可以完美地工作!     

要回复问题请先登录注册