【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文件中,添加

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+"

    //权限管理
    implementation 'com.yanzhenjie:permission:2.0.3'
}
(2)代码实现
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;
    }

    //监听
    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
/**
 * 初始化 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
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
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);
    }

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

(项目代码链接)[https://download.csdn.net/download/Android_points/11983375]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容