我正在使用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后基本上发生的事情:
有什么想法吗?
答案 0 :(得分:1)
您确定您对RTP发送部分的处理是否正确?根据我的理解,应该有一个插槽fd用于发送和接收。您是否为发送部件创建新的套接字fd并关闭recv fd?请检查并回复
您还可以有两个插槽fds用于接收,另一个用于发送。 RTP RFC-3550对实现没有任何说明。
答案 1 :(得分:0)
你应该尝试在邀请中添加媒体属性,如果你也在使用ulaw,那么你可以添加:
a=rtpmap:0 PCMU/8000
还可以尝试更简单的测试,而不是拨打软电话:
301,1,Answer
301,2,Echo
Echo将从您的客户端捕获rtp流并将其发送给您。如果一切正常,那么您可以在2个工作的软电话之间拨打电话,并与您的客户进行比较。 如果可能的话,尝试发布您的拨号方案和两个用户配置。 (小提示:如果你为两个用户启用canreinvite = yes或directrtpsetup = yes,他们将能够直接在彼此之间交换rtp流,而不是使用星号作为桥接)
答案 2 :(得分:0)
听起来像Asterisk可能会尝试重新邀请您的呼叫,以便它直接在您的SIP应用和软电话之间流动。 Asterisk这样做的好处是它使媒体路径更有效率,Asterisk服务器将不再仅仅通过信令桥接呼叫媒体。缺点是,如果涉及到NAT,或者如果SIP用户代理不支持re-INVITE,则可能导致RTP通过问题,这可能是您的情况。
如果是重新邀请问题,那么首先您应该能够看到额外的INVITE请求使用SIP调试到达您的SIP应用程序或Asterisk控制台。其次,你可以通过在你正在使用的SIP帐户上设置canreinvite = no来阻止Asterisk进行re-INVITE。
答案 3 :(得分:0)
为了记录,我决定尝试不同的PBX。我已经下载并安装了3CX电话系统,而且这款PBX一切都运行良好!
现在,测试版的客户端在他的网站上使用了Patton,所以我只希望这个问题特定于我们的Asterisk设置,并且它不会在那里显示。
答案 4 :(得分:0)
我终于解决了这个问题!事实证明问题不在于SIP消息,而在于设置RTP会话的代码。我仍然不太确定出了什么问题,但似乎此代码仅在直接调用软电话时(即不通过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编程的书籍(我想,为了保留作者的声誉,我不应该分享那本书)。
当我去查看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);
}
正如你可以看到这段代码 - 虽然它使用了相同的类 - 与我在书中发现的很不一样(这使得很难确定问题是什么),但它完美无缺!