Android网络通话关于语音通话LinPhone笔记_android(一)

Linphone是一款开源基于SIP协议的语音视频电话软件,可移植到移动端Android、IOS、WindowsPhone8,桌面系统包括GNU/Linux、Windows、Mac,以及Web浏览器。

文档直连

1、linehone官网 : http://www.linphone.org/technical-corner/liblinphone
2、官网-android文档: https://wiki.linphone.org/xwiki/wiki/public/view/Lib/Getting%20started/Android/
3、官网-android案例: https://github.com/BelledonneCommunications/linphone-android
4、可以直接从maven库下载android的aar文件
https://linphone.org/releases/maven_repository/org/linphone/linphone-sdk-android/
5、Android基于网络的VoIP电话的实现linphone https://blog.csdn.net/weixin_39947864/article/details/81910485
6、Linphone探索:1 . Linphone官方源码探究 https://blog.csdn.net/u012812482/article/details/51491226

以下内容分为两个部分,第一部分运行官方demo,简单了解linphone的语音通话功能;第二部分就是在自己的项目中集成linphone库,并自定义linphone的工具类库便于直接使用。【注意点:Android6.0之后的项目要动态授权--语音权限】

第一部分:运行官方简单的demo

首先下载liphone-android(下载)编译项目,simple文件就是linphone语音通话最简单的演示,界面如下:

image.png

image.png

官方的项目中还包含详细的工具库,可参考。

Linphone官方源码探究

以下内容来源:Linphone探索 https://blog.csdn.net/u012812482/article/details/51491226

BluetoothManager:蓝牙管理器。
BootReceiver:继承自BroadcastReceiver的类,用于在设备启动时自动启动LinphoneService。
CallActivity:通话界面。
CallAudioFragment:通话音频界面。
CallIncomingActivity:来电界面。电话的接听,挂断。 当前没有活动电话的情况下:可以通过按键挂断和接听来电。
通过LinphoneCoreListenerBase类,复写callState(电话方法)监听liphone内核电话状态,如果电话已经被挂断(分两种情况,对方挂断,本方挂断)则挂断电话。如果linphone内核已经检测到音频流(这里是铃声的流)则使linphone内核打开手机的喇叭,此时来电铃声就会播放。
CallManager:通话管理。
inviteAddress:向某个地址发起invite 请求
reinviteWithVideo:向当前的音频通路发起视频的invite请求,若当前带宽太窄或当前通路已经有视频流,这不发起invite请求。
reinvite:根据当前的profile向当前通路发起invite请求。
updateCall:改变当前视频通话的视频尺寸,调用该方法将会在重新建立流媒体通道和重新设置电话参数时重新打开摄像头。
CallOutgoingActivity:拨出电话界面。
CallVideoFragment:视频通话界面。 在当前的Activity中放置了一个继承自viewsurface的控件,用来绘制视频界面。
LinphoneManager:Linphone管理器。
启动管理:linphone内核(core),
文件管理:来电铃声、信息铃声、暂停铃声、配置文件
在线状态:在线,离线。
网络状态:网络状态发生变化。
通话管理:拨出,挂断,DTMF,接听。
短消息:状态
设备管理:摄像头,
编码器管理:音、视频编码器的检测
通道管理(Tunnel):
音量管理
音频通道:喇叭或者蓝牙
铃声管理:启动或者停止

第二部分,在自己项目中集成linphone库,语音通话界面如下:

image.png

image.png

自己项目具体实现

(1)添加Linphone的依赖库

如何在自己的项目中,引入linphone的库,具体步骤如下:
1、在项目的build.gradle文件中,添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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文件,添加:

1
2
3
4
5
6
7
8
dependencies {
    //linphone
    debugImplementation "org.linphone:linphone-sdk-android-debug:4.2+"
    releaseImplementation "org.linphone:linphone-sdk-android:4.2+"

    //权限管理
    implementation 'com.yanzhenjie:permission:2.0.3'
}
(2)代码实现
LinphoneService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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;
    }

    //监听
    private CoreListenerStub mCoreListnerStub = new CoreListenerStub() {
        /**
         *  通话状态
         * @param lc
         * @param call
         * @param cstate
         * @param message
         */
        @Override
        public void onCallStateChanged(Core lc, Call call, Call.State cstate, String message) {
            Log.i("zss", "---- 通话状态  [ 状态:" + 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();
                }
            }
        }

        /**
         * 注册状态
         * @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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
 * 初始化 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); //日志开关
        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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class PhoneServiceCallback {
    /**
     * 注册状态
     */
    public void onRegistrationStateChanged(Core lc, ProxyConfig cfg, RegistrationState cstate, String message) {
    }


    /**
     * 来电状态
     *
     * @param linphoneCall
     */
    public void incomingCall(Call linphoneCall) {
    }

    /**
     * 电话接通
     */
    public void callConnected() {
    }

    /**
     * 电话被挂断
     */
    public void callReleased() {
    }

    /**
     * 正在呼叫
     */
    public void OutgoingProgress(){
    }

}
LinphoneUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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);
    }

    /**
     * 注册到服务器
     *
     * @param name     账号名
     * @param password 密码
     * @param host     IP地址:端口号
     */
    public void registerUserAuth(String name, String password, String host) {
        registerUserAuth(name, password, host, TransportType.Udp);
    }

    /**
     * 注册到服务器
     *
     * @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();

    }

    /**
     * 是否已经注册了
     *
     * @return
     */
    public boolean isRegistered() {
        AuthInfo[] authInfos = mLinphoneCore.getAuthInfoList();
        if (authInfos.length > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 拨打电话
     *
     * @param phone 手机号
     * @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);
        }
    }
   
}

项目代码链接