对于一个项目,我需要实现 VoIP 呼叫。在考虑了许多库之后,我以 liblinphone(linphone-sdk) 结束。
不幸的是,linphone 的文档不是很好,这就是我决定使用他们的教程文件制作一个快速测试应用程序的原因。我使用的实现和使用的文件添加在下面。
我真的希望有人能够回答这个问题,因为我在 stackoverflow 和 linphone 论坛上看到了很多未回答的类似问题。这确实有助于总体上改进 linphone-sdk 的文档。
设置:
- 带有 Asterisk 的 FreePBX VoIP 服务器
- 已配置扩展以尝试和测试呼叫
- 调用已测试。当我使用官方 linphone 应用程序从手机到计算机呼叫时,使用的 ip 的工作。
问题:
- 查看 /var/log/asterisk 的日志时,未注册向我的 VoIP 服务器注册 linphone-sdk。如果我注册一个应用程序,它会识别出它正在连接。
- 尽管提供的详细信息正确且已使用已发布的 VoIP 应用程序进行测试,但无法进行测试呼叫。(输入输出错误)
- 日志可以在下面找到
问题:
- 使用 linphone-sdk 注册和调用的正确方法是什么?
MainActivity.java(处理按钮,根据linphone-sdk提供的教程文件创建调用和注册线程)
package com.test.voip;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.linphone.core.LinphoneCoreException;
import org.linphone.core.tutorials.TutorialHelloWorld;
import org.linphone.mediastream.Log;
import org.w3c.dom.Text;
public class MainActivity extends AppCompatActivity{
private TutorialRegistration registrationTest;
private TutorialHelloWorld callingTest;
private TextView sipPassword;
private TextView sipAddress;
private TextView callAddress;
private Button buttonRegistration;
private Button buttonStopRegistration;
private Button buttonStopCall;
private Button buttonCall;
private Handler mHandler = new Handler() ;
@Override
protected void onCreate(Bundle savedInstanceState) {
//Run layout
super.onCreate(savedInstanceState);
setContentView(R.layout.register);
sipAddress = (TextView) findViewById(R.id.Address);
sipPassword = (TextView) findViewById(R.id.Password);
callAddress = (TextView) findViewById(R.id.CallAddress);
System.out.println("siplog: " + sipAddress.getText().toString() + sipPassword.getText().toString());
System.out.println("siplog: call adress= " + callAddress.getText().toString());
//Create registration & calling object
registrationTest = new TutorialRegistration();
callingTest = new TutorialHelloWorld();
// Get buttons from view
buttonRegistration = (Button) findViewById(R.id.RegisterButton);
buttonStopRegistration = (Button) findViewById(R.id.StopRegistrationButton);
buttonCall = (Button) findViewById(R.id.CallButton);
buttonStopCall = (Button) findViewById(R.id.StopCallButton);
buttonRegistration.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
RegistrationThread thread = new RegistrationThread();
thread.start();
}
});
buttonStopRegistration.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
registrationTest.stopMainLoop();
}
});
buttonCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CallingThread callingThread = new CallingThread();
callingThread.start();
}
});
buttonStopCall.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
callingTest.stopMainLoop();
}
});
}
private class RegistrationThread extends Thread {
@Override
public void run() {
super.run();
try {
// takes sip uri identity from the command line arguments
String userSipAddress = sipAddress.getText().toString();
// takes password from the command line arguments
String userSipPassword = sipPassword.getText().toString();
registrationTest.launchTutorial(userSipAddress, userSipPassword);
System.out.println("siplog: try launching tut registration");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private class CallingThread extends Thread {
@Override
public void run() {
super.run();
try {
callingTest.launchTutorial(callAddress.getText().toString());
} catch (LinphoneCoreException e) {
e.printStackTrace();
}
}
}
}
注册.java
/*
TutorialRegistration.java
Copyright (C) 2010 Belledonne Communications SARL
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.test.voip;
import java.nio.ByteBuffer;
import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCall.State;
import org.linphone.core.LinphoneCallStats;
import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneChatRoom;
import org.linphone.core.LinphoneContent;
import org.linphone.core.LinphoneCore;
import org.linphone.core.LinphoneCore.EcCalibratorStatus;
import org.linphone.core.LinphoneCore.GlobalState;
import org.linphone.core.LinphoneCore.LogCollectionUploadState;
import org.linphone.core.LinphoneCore.RegistrationState;
import org.linphone.core.LinphoneCore.RemoteProvisioningState;
import org.linphone.core.LinphoneAuthInfo;
import org.linphone.core.LinphoneCoreException;
import org.linphone.core.LinphoneCoreFactory;
import org.linphone.core.LinphoneCoreListener;
import org.linphone.core.LinphoneEvent;
import org.linphone.core.LinphoneFriend;
import org.linphone.core.LinphoneFriendList;
import org.linphone.core.LinphoneInfoMessage;
import org.linphone.core.LinphoneProxyConfig;
import org.linphone.core.PublishState;
import org.linphone.core.SubscriptionState;
/**
* This program is a _very_ simple usage example of liblinphone.
* Demonstrating how to initiate a SIP registration from a sip uri identity
* passed from the command line.
*
* First argument must be like sip:jehan@sip.linphone.org, second must be password.
* <br>
* ex registration sip:jehan@sip.linphone.org secret
*
* Ported from registration.c
*
* @author Guillaume Beraudo
*
*/
public class TutorialRegistration implements LinphoneCoreListener {
private boolean running;
private TutorialNotifier TutorialNotifier;
public TutorialRegistration(TutorialNotifier TutorialNotifier) {
this.TutorialNotifier = TutorialNotifier;
}
public TutorialRegistration() {
this.TutorialNotifier = new TutorialNotifier();
}
/*
* Registration state notification listener
*/
public void registrationState(LinphoneCore lc, LinphoneProxyConfig cfg,RegistrationState cstate, String smessage) {
write(cfg.getIdentity() + " : "+smessage);
}
public void show(LinphoneCore lc) {}
public void byeReceived(LinphoneCore lc, String from) {}
public void authInfoRequested(LinphoneCore lc, String realm, String username, String domain) {}
public void authenticationRequested(LinphoneCore lc, LinphoneAuthInfo authInfo, LinphoneCore.AuthMethod method) {}
public void displayStatus(LinphoneCore lc, String message) {}
public void displayMessage(LinphoneCore lc, String message) {}
public void displayWarning(LinphoneCore lc, String message) {}
public void globalState(LinphoneCore lc, GlobalState state, String message) {}
public void newSubscriptionRequest(LinphoneCore lc, LinphoneFriend lf,String url) {}
public void notifyPresenceReceived(LinphoneCore lc, LinphoneFriend lf) {}
public void callState(LinphoneCore lc, LinphoneCall call, State cstate, String msg) {}
public void callStatsUpdated(LinphoneCore lc, LinphoneCall call, LinphoneCallStats stats) {}
public void ecCalibrationStatus(LinphoneCore lc, EcCalibratorStatus status,int delay_ms, Object data) {}
public void callEncryptionChanged(LinphoneCore lc, LinphoneCall call,boolean encrypted, String authenticationToken) {}
public void notifyReceived(LinphoneCore lc, LinphoneCall call, LinphoneAddress from, byte[] event){}
public void dtmfReceived(LinphoneCore lc, LinphoneCall call, int dtmf) {}
// public static void main(String[] args) {
// // Check tutorial was called with the right number of arguments
// if (args.length != 2) {
// throw new IllegalArgumentException("Bad number of arguments");
// }
//
// // Create tutorial object
// TutorialRegistration tutorial = new TutorialRegistration();
// try {
// // takes sip uri identity from the command line arguments
// String userSipAddress = args[1];
// // takes password from the command line arguments
// String userSipPassword = args[2];
// tutorial.launchTutorial(userSipAddress, userSipPassword);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
public void launchTutorial(String sipAddress, String password) throws LinphoneCoreException {
final LinphoneCoreFactory lcFactory = LinphoneCoreFactory.instance();
// First instantiate the core Linphone object given only a listener.
// The listener will react to events in Linphone core.
LinphoneCore lc = lcFactory.createLinphoneCore(this, null);
System.out.println("siplog: created instance");
try {
System.out.println("siplog: try creating linphone adress");
// Parse identity
LinphoneAddress address = lcFactory.createLinphoneAddress(sipAddress);
System.out.println("siplog: linphone address created");
String username = address.getUserName();
String domain = address.getDomain();
System.out.println("siplog: username = " + address.getUserName());
System.out.println("siplog: domain = " + address.getDomain());
if (password != null) {
// create authentication structure from identity and add to linphone
lc.addAuthInfo(lcFactory.createAuthInfo(username, password, null, domain));
}
// create proxy config
LinphoneProxyConfig proxyCfg = lc.createProxyConfig(sipAddress, domain, null, true);
proxyCfg.setExpires(2000);
lc.addProxyConfig(proxyCfg); // add it to linphone
lc.setDefaultProxyConfig(proxyCfg);
// main loop for receiving notifications and doing background linphonecore work
running = true;
while (running) {
lc.iterate(); // first iterate initiates registration
sleep(50);
}
// Unregister
lc.getDefaultProxyConfig().edit();
lc.getDefaultProxyConfig().enableRegister(false);
lc.getDefaultProxyConfig().done();
while(lc.getDefaultProxyConfig().getState() != RegistrationState.RegistrationCleared) {
lc.iterate();
sleep(50);
}
// Then register again
lc.getDefaultProxyConfig().edit();
lc.getDefaultProxyConfig().enableRegister(true);
lc.getDefaultProxyConfig().done();
System.out.println("siplog: registered again");
while(lc.getDefaultProxyConfig().getState() != RegistrationState.RegistrationOk
&& lc.getDefaultProxyConfig().getState() != RegistrationState.RegistrationFailed) {
System.out.println("registration has failed");
lc.iterate();
sleep(50);
}
// Automatic unregistration on exit
} finally {
System.out.println("siplog: linphone exit");
write("Shutting down linphone...");
// You need to destroy the LinphoneCore object when no longer used
lc.destroy();
}
}
private void sleep(int ms) {
try {
Thread.sleep(ms);
} catch(InterruptedException ie) {
write("Interrupted!\nAborting");
return;
}
}
public void stopMainLoop() {
System.out.println("siplog: stopped mainloop for registration");
running=false;
}
private void write(String s) {
TutorialNotifier.notify(s);
}
@Override
public void messageReceived(LinphoneCore lc, LinphoneChatRoom cr,
LinphoneChatMessage message) {
// TODO Auto-generated method stub
}
@Override
public void messageReceivedUnableToDecrypted(LinphoneCore lc, LinphoneChatRoom cr, LinphoneChatMessage message) {
}
@Override
public void transferState(LinphoneCore lc, LinphoneCall call,
State new_call_state) {
// TODO Auto-generated method stub
}
@Override
public void infoReceived(LinphoneCore lc, LinphoneCall call, LinphoneInfoMessage info) {
// TODO Auto-generated method stub
}
@Override
public void subscriptionStateChanged(LinphoneCore lc, LinphoneEvent ev,
SubscriptionState state) {
// TODO Auto-generated method stub
}
@Override
public void notifyReceived(LinphoneCore lc, LinphoneEvent ev,
String eventName, LinphoneContent content) {
// TODO Auto-generated method stub
}
@Override
public void publishStateChanged(LinphoneCore lc, LinphoneEvent ev,
PublishState state) {
// TODO Auto-generated method stub
}
@Override
public void isComposingReceived(LinphoneCore lc, LinphoneChatRoom cr) {
// TODO Auto-generated method stub
}
@Override
public void configuringStatus(LinphoneCore lc,
RemoteProvisioningState state, String message) {
// TODO Auto-generated method stub
}
@Override
public void fileTransferProgressIndication(LinphoneCore lc,
LinphoneChatMessage message, LinphoneContent content, int progress) {
// TODO Auto-generated method stub
}
@Override
public void fileTransferRecv(LinphoneCore lc, LinphoneChatMessage message,
LinphoneContent content, byte[] buffer, int size) {
// TODO Auto-generated method stub
}
@Override
public int fileTransferSend(LinphoneCore lc, LinphoneChatMessage message,
LinphoneContent content, ByteBuffer buffer, int size) {
// TODO Auto-generated method stub
return 0;
}
@Override
public void uploadProgressIndication(LinphoneCore lc, int offset, int total) {
// TODO Auto-generated method stub
}
@Override
public void uploadStateChanged(LinphoneCore lc,
LogCollectionUploadState state, String info) {
// TODO Auto-generated method stub
}
@Override
public void friendListCreated(LinphoneCore lc, LinphoneFriendList list) {
// TODO Auto-generated method stub
}
@Override
public void friendListRemoved(LinphoneCore lc, LinphoneFriendList list) {
// TODO Auto-generated method stub
}
@Override
public void networkReachableChanged(LinphoneCore lc, boolean enable) {
}
}
调用.java
/*
TutorialHelloWorld.java
Copyright (C) 2010 Belledonne Communications SARL
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.test.voip;
import java.nio.ByteBuffer;
import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCall.State;
import org.linphone.core.LinphoneCallStats;
import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneChatRoom;
import org.linphone.core.LinphoneContent;
import org.linphone.core.LinphoneCore;
import org.linphone.core.LinphoneCore.EcCalibratorStatus;
import org.linphone.core.LinphoneCore.GlobalState;
import org.linphone.core.LinphoneCore.LogCollectionUploadState;
import org.linphone.core.LinphoneCore.RegistrationState;
import org.linphone.core.LinphoneCore.RemoteProvisioningState;
import org.linphone.core.LinphoneAuthInfo;
import org.linphone.core.LinphoneCoreException;
import org.linphone.core.LinphoneCoreFactory;
import org.linphone.core.LinphoneCoreListener;
import org.linphone.core.LinphoneEvent;
import org.linphone.core.LinphoneFriend;
import org.linphone.core.LinphoneFriendList;
import org.linphone.core.LinphoneInfoMessage;
import org.linphone.core.LinphoneProxyConfig;
import org.linphone.core.PublishState;
import org.linphone.core.SubscriptionState;
import org.linphone.core.tutorials.TutorialNotifier;
/**
* This program is a _very_ simple usage example of liblinphone.
* It just takes a sip-uri as first argument and attempts to call it.
*
* Ported from helloworld.c
*
* @author Guillaume Beraudo
*
*/
public class Calling implements LinphoneCoreListener {
private boolean running;
private org.linphone.core.tutorials.TutorialNotifier TutorialNotifier;
public Calling(TutorialNotifier TutorialNotifier) {
this.TutorialNotifier = TutorialNotifier;
}
public Calling() {
this.TutorialNotifier = new TutorialNotifier();
}
public void show(LinphoneCore lc) {}
public void byeReceived(LinphoneCore lc, String from) {}
public void authInfoRequested(LinphoneCore lc, String realm, String username, String domain) {}
public void authenticationRequested(LinphoneCore lc, LinphoneAuthInfo authInfo, LinphoneCore.AuthMethod method) {}
public void displayStatus(LinphoneCore lc, String message) {}
public void displayMessage(LinphoneCore lc, String message) {}
public void displayWarning(LinphoneCore lc, String message) {}
public void globalState(LinphoneCore lc, GlobalState state, String message) {}
public void registrationState(LinphoneCore lc, LinphoneProxyConfig cfg,RegistrationState cstate, String smessage) {}
public void newSubscriptionRequest(LinphoneCore lc, LinphoneFriend lf,String url) {}
public void notifyPresenceReceived(LinphoneCore lc, LinphoneFriend lf) {}
public void textReceived(LinphoneCore lc, LinphoneChatRoom cr,LinphoneAddress from, String message) {}
public void callStatsUpdated(LinphoneCore lc, LinphoneCall call, LinphoneCallStats stats) {}
public void ecCalibrationStatus(LinphoneCore lc, EcCalibratorStatus status,int delay_ms, Object data) {}
public void callEncryptionChanged(LinphoneCore lc, LinphoneCall call,boolean encrypted, String authenticationToken) {}
public void notifyReceived(LinphoneCore lc, LinphoneCall call, LinphoneAddress from, byte[] event){}
public void dtmfReceived(LinphoneCore lc, LinphoneCall call, int dtmf) {}
/*
* Call state notification listener
*/
public void callState(LinphoneCore lc, LinphoneCall call, State cstate, String msg){
write("State: " + msg);
if (State.CallEnd.equals(cstate))
running = false;
}
public void launchTutorial(String destinationSipAddress) throws LinphoneCoreException {
// First instantiate the core Linphone object given only a listener.
// The listener will react to events in Linphone core.
LinphoneCore lc = LinphoneCoreFactory.instance().createLinphoneCore(this, null);
try {
// Send the INVITE message to destination SIP address
LinphoneCall call = lc.invite(destinationSipAddress);
if (call == null) {
write("Could not place call to " + destinationSipAddress);
write("Aborting");
return;
}
write("Call to " + destinationSipAddress + " is in progress...");
// main loop for receiving notifications and doing background linphonecore work
running = true;
while (running) {
lc.iterate();
try{
Thread.sleep(50);
} catch(InterruptedException ie) {
write("Interrupted!\nAborting");
return;
}
}
if (!State.CallEnd.equals(call.getState())) {
write("Terminating the call");
lc.terminateCall(call);
}
} finally {
write("Shutting down...");
// You need to destroy the LinphoneCore object when no longer used
lc.destroy();
write("Exited");
}
}
public void stopMainLoop() {
running=false;
}
private void write(String s) {
TutorialNotifier.notify(s);
}
@Override
public void messageReceived(LinphoneCore lc, LinphoneChatRoom cr,
LinphoneChatMessage message) {
// TODO Auto-generated method stub
}
@Override
public void messageReceivedUnableToDecrypted(LinphoneCore lc, LinphoneChatRoom cr, LinphoneChatMessage message) {
}
@Override
public void transferState(LinphoneCore lc, LinphoneCall call,
State new_call_state) {
// TODO Auto-generated method stub
}
@Override
public void infoReceived(LinphoneCore lc, LinphoneCall call, LinphoneInfoMessage info) {
// TODO Auto-generated method stub
}
@Override
public void subscriptionStateChanged(LinphoneCore lc, LinphoneEvent ev,
SubscriptionState state) {
// TODO Auto-generated method stub
}
@Override
public void notifyReceived(LinphoneCore lc, LinphoneEvent ev,
String eventName, LinphoneContent content) {
// TODO Auto-generated method stub
}
@Override
public void publishStateChanged(LinphoneCore lc, LinphoneEvent ev,
PublishState state) {
// TODO Auto-generated method stub
}
@Override
public void isComposingReceived(LinphoneCore lc, LinphoneChatRoom cr) {
// TODO Auto-generated method stub
}
@Override
public void configuringStatus(LinphoneCore lc,
RemoteProvisioningState state, String message) {
// TODO Auto-generated method stub
}
@Override
public void fileTransferProgressIndication(LinphoneCore lc,
LinphoneChatMessage message, LinphoneContent content, int progress) {
// TODO Auto-generated method stub
}
@Override
public void fileTransferRecv(LinphoneCore lc, LinphoneChatMessage message,
LinphoneContent content, byte[] buffer, int size) {
// TODO Auto-generated method stub
}
@Override
public int fileTransferSend(LinphoneCore lc, LinphoneChatMessage message,
LinphoneContent content, ByteBuffer buffer, int size) {
// TODO Auto-generated method stub
return 0;
}
@Override
public void uploadProgressIndication(LinphoneCore lc, int offset, int total) {
// TODO Auto-generated method stub
}
@Override
public void uploadStateChanged(LinphoneCore lc,
LogCollectionUploadState state, String info) {
// TODO Auto-generated method stub
}
@Override
public void friendListCreated(LinphoneCore lc, LinphoneFriendList list) {
// TODO Auto-generated method stub
}
@Override
public void friendListRemoved(LinphoneCore lc, LinphoneFriendList list) {
// TODO Auto-generated method stub
}
@Override
public void networkReachableChanged(LinphoneCore lc, boolean enable) {
}
}
注册日志(点击“注册”按钮后)
I/System.out: siplog: created instance
I/System.out: siplog: try creating linphone adress
I/System.out: siplog: linphone address created
I/System.out: siplog: username = 1000
I/System.out: siplog: domain = 192.168.2.131
通话记录(点击“通话”按钮后)
I/System.out: State: Starting outgoing call
I/System.out: State: Outgoing call in progress
I/System.out: Call to sip:2000@192.168.2.131:5160 is in progress...
I/System.out: State: IO error
I/System.out: State: Call released