Linphone是一款開源基于SIP協(xié)議的語音視頻電話軟件,可移植到移動端Android、IOS、WindowsPhone8,桌面系統(tǒng)包括GNU/Linux、Windows、Mac,以及Web瀏覽器。
文檔直連
1、linehone官網(wǎng) : http://www.linphone.org/technical-corner/liblinphone
2、官網(wǎng)-android文檔: https://wiki.linphone.org/xwiki/wiki/public/view/Lib/Getting%20started/Android/
3、官網(wǎng)-android案例: https://github.com/BelledonneCommunications/linphone-android
4、可以直接從maven庫下載android的aar文件
https://linphone.org/releases/maven_repository/org/linphone/linphone-sdk-android/
5、Android基于網(wǎng)絡(luò)的VoIP電話的實現(xiàn)linphone https://blog.csdn.net/weixin_39947864/article/details/81910485
6、Linphone探索:1 . Linphone官方源碼探究 https://blog.csdn.net/u012812482/article/details/51491226
以下內(nèi)容分為兩個部分,第一部分運(yùn)行官方demo,簡單了解linphone的語音通話功能;第二部分就是在自己的項目中集成linphone庫,并自定義linphone的工具類庫便于直接使用。【注意點(diǎn):Android6.0之后的項目要動態(tài)授權(quán)--語音權(quán)限】
第一部分:運(yùn)行官方簡單的demo
首先下載liphone-android(下載)編譯項目,simple文件就是linphone語音通話最簡單的演示,界面如下:
官方的項目中還包含詳細(xì)的工具庫,可參考。
Linphone官方源碼探究
以下內(nèi)容來源:Linphone探索 https://blog.csdn.net/u012812482/article/details/51491226
BluetoothManager:藍(lán)牙管理器。
BootReceiver:繼承自BroadcastReceiver的類,用于在設(shè)備啟動時自動啟動LinphoneService。
CallActivity:通話界面。
CallAudioFragment:通話音頻界面。
CallIncomingActivity:來電界面。電話的接聽,掛斷。 當(dāng)前沒有活動電話的情況下:可以通過按鍵掛斷和接聽來電。
通過LinphoneCoreListenerBase類,復(fù)寫callState(電話方法)監(jiān)聽liphone內(nèi)核電話狀態(tài),如果電話已經(jīng)被掛斷(分兩種情況,對方掛斷,本方掛斷)則掛斷電話。如果linphone內(nèi)核已經(jīng)檢測到音頻流(這里是鈴聲的流)則使linphone內(nèi)核打開手機(jī)的喇叭,此時來電鈴聲就會播放。
CallManager:通話管理。
inviteAddress:向某個地址發(fā)起invite 請求
reinviteWithVideo:向當(dāng)前的音頻通路發(fā)起視頻的invite請求,若當(dāng)前帶寬太窄或當(dāng)前通路已經(jīng)有視頻流,這不發(fā)起invite請求。
reinvite:根據(jù)當(dāng)前的profile向當(dāng)前通路發(fā)起invite請求。
updateCall:改變當(dāng)前視頻通話的視頻尺寸,調(diào)用該方法將會在重新建立流媒體通道和重新設(shè)置電話參數(shù)時重新打開攝像頭。
CallOutgoingActivity:撥出電話界面。
CallVideoFragment:視頻通話界面。 在當(dāng)前的Activity中放置了一個繼承自viewsurface的控件,用來繪制視頻界面。
LinphoneManager:Linphone管理器。
啟動管理:linphone內(nèi)核(core),
文件管理:來電鈴聲、信息鈴聲、暫停鈴聲、配置文件
在線狀態(tài):在線,離線。
網(wǎng)絡(luò)狀態(tài):網(wǎng)絡(luò)狀態(tài)發(fā)生變化。
通話管理:撥出,掛斷,DTMF,接聽。
短消息:狀態(tài)
設(shè)備管理:攝像頭,
編碼器管理:音、視頻編碼器的檢測
通道管理(Tunnel):
音量管理
音頻通道:喇叭或者藍(lán)牙
鈴聲管理:啟動或者停止
第二部分,在自己項目中集成linphone庫,語音通話界面如下:
自己項目具體實現(xiàn)
(1)添加Linphone的依賴庫
如何在自己的項目中,引入linphone的庫,具體步驟如下:
1、在項目的build.gradle文件中,添加
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
allprojects {
repositories {
jcenter()
google()
//linphone
maven {
url "https://linphone.org/releases/maven_repository/"
}
}
}
2、在model項目中的build.gradle文件,添加:
dependencies {
//linphone
debugImplementation "org.linphone:linphone-sdk-android-debug:4.2+"
releaseImplementation "org.linphone:linphone-sdk-android:4.2+"
//權(quán)限管理
implementation 'com.yanzhenjie:permission:2.0.3'
}
(2)代碼實現(xiàn)
LinphoneService
public class LinphoneService extends Service {
private static LinphoneService sInstance;
private static PhoneServiceCallback sPhoneServiceCallback;
private Core mCore;
public static void addCallback(PhoneServiceCallback phoneServiceCallback) {
sPhoneServiceCallback = phoneServiceCallback;
}
public static boolean isReady() {
return sInstance != null;
}
public static Core getCore() {
return sInstance.mCore;
}
//監(jiān)聽
private CoreListenerStub mCoreListnerStub = new CoreListenerStub() {
/**
* 通話狀態(tài)
* @param lc
* @param call
* @param cstate
* @param message
*/
@Override
public void onCallStateChanged(Core lc, Call call, Call.State cstate, String message) {
Log.i("zss", "---- 通話狀態(tài) [ 狀態(tài):" + cstate + " ;消息: " + message + " ]");
if (cstate == Call.State.IncomingReceived) { //來電
// Log.i("zss","----- getRemoteAddress().getUsername: " + call.getRemoteAddress().getUsername() + " getRemoteAddress().getDomain: " + call.getRemoteAddress().getDomain() + " getRemoteAddress().getDisplayName:" + call.getRemoteAddress().getDisplayName() + " getRemoteAddress().getPort:" + call.getRemoteAddress().getPort() + " getUsername: " + call.getRemoteAddress().getPassword() );
// Log.i("zss", "----getTlsCert: " + authInfo[i].getTlsCert() + " getTlsCertPath:" + authInfo[i].getTlsCertPath());
Intent intent = new Intent(LinphoneService.this, CustomReceiveActivity.class);
CustomReceiveActivity.getReceivedCallFromService(call);
ReceiveDataModel receiveDataModel = new ReceiveDataModel();
receiveDataModel.setActiveCall(false);
receiveDataModel.setNum(call.getRemoteAddress().getUsername());
intent.putExtra("ReceiveDataModel", receiveDataModel);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
// if (null != sPhoneServiceCallback) {
// Log.i("zss", "---- sPhoneServiceCallback ");
// sPhoneServiceCallback.incomingCall(call);
// }
} else if (cstate == Call.State.OutgoingProgress) { //正在呼叫
} else if (cstate == Call.State.Connected) { //接通或者拒絕
if (null != sPhoneServiceCallback) {
sPhoneServiceCallback.callConnected();
}
} else if (cstate == Call.State.End || (cstate == Call.State.Released)) { //掛斷,未接
if (null != sPhoneServiceCallback) {
sPhoneServiceCallback.callReleased();
}
}
}
/**
* 注冊狀態(tài)
* @param lc
* @param cfg
* @param cstate
* @param message
*/
@Override
public void onRegistrationStateChanged(Core lc, ProxyConfig cfg, RegistrationState cstate, String message) {
if (null != sPhoneServiceCallback) {
sPhoneServiceCallback.onRegistrationStateChanged(lc, cfg, cstate, message);
}
}
};
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.i("zss", "---- Service_onCreate ");
LinphoneManager.createAndStart(LinphoneService.this, mCoreListnerStub);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Log.i("zss", "---- Service_onStartCommand ");
// If our Service is already running, no need to continue
if (sInstance != null) {
return START_STICKY;
}
sInstance = this;
return START_STICKY;
}
@Override
public void onDestroy() {
Log.i("zss", "---- Service_onDestroy ");
sInstance = null;
LinphoneManager.destroy();
super.onDestroy();
}
@Override
public void onTaskRemoved(Intent rootIntent) {
Log.i("zss", "---- Service_onTaskRemoved ");
sInstance = null;
LinphoneManager.destroy();
// For this sample we will kill the Service at the same time we kill the app
stopSelf();
super.onTaskRemoved(rootIntent);
}
}
LinphoneManager
/**
* 初始化 linphone
*/
public class LinphoneManager {
private Context mServiceContext;
private static LinphoneManager instance;
private static boolean sExited;
private String mLinphoneFactoryConfigFile = null;
public String mLinphoneConfigFile = null;
private String mLPConfigXsd = null;
private String mLinphoneRootCaFile = null;
private String mRingSoundFile = null;
private String mRingBackSoundFile = null;
private String mPauseSoundFile = null;
private String mChatDatabaseFile = null;
private String mUserCerts = null;
private Resources mResources;
private Core mCore;
private CoreListener mCoreListener;
private Timer mTimer;
private Handler mHandler;
public LinphoneManager(Context serviceContext) {
mServiceContext = serviceContext;
sExited = false;
String basePath = mServiceContext.getFilesDir().getAbsolutePath();
mLPConfigXsd = basePath + "/lpconfig.xsd";
mLinphoneFactoryConfigFile = basePath + "/linphonerc";
mLinphoneConfigFile = basePath + "/.linphonerc";
mLinphoneRootCaFile = basePath + "/rootca.pem";
mRingSoundFile = basePath + "/dont_wait_too_long.mkv"; //dont_wait_too_long.mkv oldphone_mono.wav
mRingBackSoundFile = basePath + "/ringback.wav";
mPauseSoundFile = basePath + "/toy_mono.wav";
mChatDatabaseFile = basePath + "/linphone-history.db";
mUserCerts = basePath + "/user-certs";
// mErrorToneFile = basePath + "/error.wav";
mResources = serviceContext.getResources();
Factory.instance().setLogCollectionPath(basePath);
Factory.instance().enableLogCollection(LogCollectionState.Enabled); //日志開關(guān)
Factory.instance().setDebugMode(true, "Linphone");
mHandler = new Handler();
}
public synchronized static final LinphoneManager createAndStart(Context context, CoreListener coreListener) {
if (instance != null) {
throw new RuntimeException("Linphone Manager is already initialized");
}
instance = new LinphoneManager(context);
instance.startLibLinphone(context, coreListener);
return instance;
}
private synchronized void startLibLinphone(Context context, CoreListener coreListener) {
try {
mCoreListener = coreListener;
copyAssetsFromPackage();
// Create the Core and add our listener
mCore = Factory.instance().createCore(mLinphoneConfigFile, mLinphoneFactoryConfigFile, context);
mCore.addListener(coreListener);
initLibLinphone();
// Core must be started after being created and configured
mCore.start();
// We also MUST call the iterate() method of the Core on a regular basis
TimerTask lTask = new TimerTask() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mCore != null) {
mCore.iterate();
}
}
});
}
};
mTimer = new Timer("Linphone scheduler");
mTimer.schedule(lTask, 0, 20);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void copyAssetsFromPackage() throws IOException {
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.dont_wait_too_long, mRingSoundFile);
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.ringback, mRingBackSoundFile);
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.toy_mono, mPauseSoundFile);
LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_default, mLinphoneConfigFile);
LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_factory, new File(mLinphoneFactoryConfigFile).getName());
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.lpconfig, mLPConfigXsd);
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.rootca, mLinphoneRootCaFile);
}
private void initLibLinphone() {
File f = new File(mUserCerts);
if (!f.exists()) {
if (!f.mkdir()) {
Log.e(mUserCerts + " can't be created.");
}
}
mCore.setUserCertificatesPath(mUserCerts);
}
public static synchronized Core getCoreIfManagerNotDestroyOrNull() {
if (sExited || instance == null) {
Log.e("Trying to get linphone core while LinphoneManager already destroyed or not created");
return null;
}
return getCore();
}
public static synchronized final Core getCore() {
return getInstance().mCore;
}
public static final boolean isInstanceiated() {
return instance != null;
}
public static synchronized final LinphoneManager getInstance() {
if (instance != null) {
return instance;
}
if (sExited) {
throw new RuntimeException("Linphone Manager was already destroyed. " + "Better use getLcIfManagerNotDestroyed and check returned value");
}
throw new RuntimeException("Linphone Manager should be created before accessed");
}
public static synchronized void destroy() {
if (instance == null) {
return;
}
sExited = true;
instance.doDestroy();
}
private void doDestroy() {
try {
mCore.removeListener(mCoreListener);
mTimer.cancel();
mCore.stop();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
mCore = null;
instance = null;
}
}
}
PhoneServiceCallback
public abstract class PhoneServiceCallback {
/**
* 注冊狀態(tài)
*/
public void onRegistrationStateChanged(Core lc, ProxyConfig cfg, RegistrationState cstate, String message) {
}
/**
* 來電狀態(tài)
*
* @param linphoneCall
*/
public void incomingCall(Call linphoneCall) {
}
/**
* 電話接通
*/
public void callConnected() {
}
/**
* 電話被掛斷
*/
public void callReleased() {
}
/**
* 正在呼叫
*/
public void OutgoingProgress(){
}
}
LinphoneUtils
public class LinphoneUtils {
public static void copyIfNotExist(Context context, int ressourceId, String target) throws IOException {
File lFileToCopy = new File(target);
if (!lFileToCopy.exists()) {
copyFromPackage(context, ressourceId, lFileToCopy.getName());
}
}
public static void copyFromPackage(Context context, int ressourceId, String target) throws IOException {
FileOutputStream lOutputStream = context.openFileOutput(target, 0);
InputStream lInputStream = context.getResources().openRawResource(ressourceId);
int readByte;
byte[] buff = new byte[8048];
while ((readByte = lInputStream.read(buff)) != -1) {
lOutputStream.write(buff, 0, readByte);
}
lOutputStream.flush();
lOutputStream.close();
lInputStream.close();
}
}
PhoneVoiceUtils
public class PhoneVoiceUtils {
private static volatile PhoneVoiceUtils sPhoneVoiceUtils;
private Core mLinphoneCore = null;
public static PhoneVoiceUtils getInstance() {
if (sPhoneVoiceUtils == null) {
synchronized (PhoneVoiceUtils.class) {
if (sPhoneVoiceUtils == null) {
sPhoneVoiceUtils = new PhoneVoiceUtils();
}
}
}
return sPhoneVoiceUtils;
}
private PhoneVoiceUtils() {
mLinphoneCore = LinphoneManager.getCore();
// mLinphoneCore.enableEchoCancellation(true);
// mLinphoneCore.enableEchoLimiter(true);
}
/**
* 注冊到服務(wù)器
*
* @param name 賬號名
* @param password 密碼
* @param host IP地址:端口號
*/
public void registerUserAuth(String name, String password, String host) {
registerUserAuth(name, password, host, TransportType.Udp);
}
/**
* 注冊到服務(wù)器
*
* @param name 賬號名
* @param password 密碼
* @param host IP地址:端口號
* @param type TransportType.Udp TransportType.Tcp TransportType.Tls
*/
public void registerUserAuth(String name, String password, String host, TransportType type) {
// String identify = "sip:" + name + "@" + host;
AccountCreator mAccountCreator = mLinphoneCore.createAccountCreator(null);
mAccountCreator.setUsername(name);
mAccountCreator.setDomain(host);
mAccountCreator.setPassword(password);
mAccountCreator.setTransport(type);
ProxyConfig cfg = mAccountCreator.createProxyConfig();
// Make sure the newly created one is the default
mLinphoneCore.setDefaultProxyConfig(cfg);
}
//取消注冊
public void unRegisterUserAuth() {
mLinphoneCore.clearAllAuthInfo();
}
/**
* 是否已經(jīng)注冊了
*
* @return
*/
public boolean isRegistered() {
AuthInfo[] authInfos = mLinphoneCore.getAuthInfoList();
if (authInfos.length > 0) {
return true;
} else {
return false;
}
}
/**
* 撥打電話
*
* @param phone 手機(jī)號
* @return
*/
public Call startSingleCallingTo(String phone) {
Call call = null;
try {
Address addressToCall = mLinphoneCore.interpretUrl(phone);
CallParams params = mLinphoneCore.createCallParams(null);
params.enableVideo(false); //不可視頻
if (addressToCall != null) {
call = mLinphoneCore.inviteAddressWithParams(addressToCall, params);
}
} catch (Exception e) {
e.printStackTrace();
}
return call;
}
/**
* 掛斷電話
*/
public void hangUp() {
if (mLinphoneCore == null) {
mLinphoneCore = LinphoneManager.getCore();
}
Call currentCall = mLinphoneCore.getCurrentCall();
if (currentCall != null) {
mLinphoneCore.terminateCall(currentCall);
} else if (mLinphoneCore.isInConference()) {
mLinphoneCore.terminateConference();
} else {
mLinphoneCore.terminateAllCalls();
}
}
/**
* 是否靜音
*
* @param isMicMuted
*/
public void toggleMicro(boolean isMicMuted) {
if (mLinphoneCore == null) {
mLinphoneCore = LinphoneManager.getCore();
}
mLinphoneCore.enableMic(isMicMuted);
}
/**
* 接聽來電
*
* @param
*/
public void receiveCall(Call call) {
if (mLinphoneCore == null) {
mLinphoneCore = LinphoneManager.getCore();
}
CallParams params = mLinphoneCore.createCallParams(call);
params.enableVideo(false);
if (null != call) {
call.acceptWithParams(params);
}
}
}
(項目代碼鏈接)[https://download.csdn.net/download/Android_points/11983375]