[原]CSipSimple 简单分析
- - banketreeCSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话. 连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果才好. 用户首先选择使用哪国哪个类型,这是由com.csipsimple.wizards.impl包下完成的.
CSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话。连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果才好。下面简单分析一下其。
1、注册流程
用户首先选择使用哪国哪个类型,这是由com.csipsimple.wizards.impl包下完成的。该包下实现接口WizardIface,接口方法中有
SipProfile buildAccount(SipProfile account);产生一个帐号文件。
然后在BasePrefsWizard类下保存帐号,代码如下:
/** * Save the account with given wizard id * @param wizardId the wizard to use for account entry */ private void saveAccount(String wizardId) { //保存帐号(帐号保存到共享数据库中) boolean needRestart = false; PreferencesWrapper prefs = new PreferencesWrapper(getApplicationContext()); account = wizard.buildAccount(account); account.wizard = wizardId; if (account.id == SipProfile.INVALID_ID) { // This account does not exists yet prefs.startEditing(); wizard.setDefaultParams(prefs); prefs.endEditing(); applyNewAccountDefault(account); Uri uri = getContentResolver().insert(SipProfile.ACCOUNT_URI, account.getDbContentValues()); // After insert, add filters for this wizard account.id = ContentUris.parseId(uri); List<Filter> filters = wizard.getDefaultFilters(account); if (filters != null) { for (Filter filter : filters) { // Ensure the correct id if not done by the wizard filter.account = (int) account.id; getContentResolver().insert(SipManager.FILTER_URI, filter.getDbContentValues()); } } // Check if we have to restart needRestart = wizard.needRestart(); } else { // TODO : should not be done there but if not we should add an // option to re-apply default params prefs.startEditing(); wizard.setDefaultParams(prefs); prefs.endEditing(); getContentResolver().update(ContentUris.withAppendedId(SipProfile.ACCOUNT_ID_URI_BASE, account.id), account.getDbContentValues(), null, null); } // Mainly if global preferences were changed, we have to restart sip stack if (needRestart) { //保存完毕后发送重新加载sip Intent intent = new Intent(SipManager.ACTION_SIP_REQUEST_RESTART); sendBroadcast(intent); } }
public void restartSipStack() throws SameThreadException { if(stopSipStack()) { startSipStack(); }else { Log.e(THIS_FILE, "Can't stop ... so do not restart ! "); } } //private KeepAliveTimer kaAlarm; // This is always done in SipExecutor thread private void startSipStack() throws SameThreadException { //Cache some prefs supportMultipleCalls = prefsWrapper.getPreferenceBooleanValue(SipConfigManager.SUPPORT_MULTIPLE_CALLS); if(!isConnectivityValid()) { notifyUserOfMessage(R.string.connection_not_valid); Log.e(THIS_FILE, "No need to start sip"); return; } Log.d(THIS_FILE, "Start was asked and we should actually start now"); if(pjService == null) { Log.d(THIS_FILE, "Start was asked and pjService in not there"); if(!loadStack()) { Log.e(THIS_FILE, "Unable to load SIP stack !! "); return; } } Log.d(THIS_FILE, "Ask pjservice to start itself"); //presenceMgr.startMonitoring(this); if(pjService.sipStart()) { // This should be done after in acquire resource // But due to http://code.google.com/p/android/issues/detail?id=21635 // not a good idea applyComponentEnablingState(true); registerBroadcasts(); Log.d(THIS_FILE, "Add all accounts"); addAllAccounts(); //关键添加帐户 } } /** * Add accounts from database */ private void addAllAccounts() throws SameThreadException {//从数据库中读取所有的帐户信息 Log.d(THIS_FILE, "We are adding all accounts right now...."); boolean hasSomeSuccess = false; Cursor c = getContentResolver().query(SipProfile.ACCOUNT_URI, DBProvider.ACCOUNT_FULL_PROJECTION, SipProfile.FIELD_ACTIVE + "=?", new String[] {"1"}, null); if (c != null) { try { int index = 0; if(c.getCount() > 0) { c.moveToFirst(); do { SipProfile account = new SipProfile(c); if (pjService != null && pjService.addAccount(account) ) {//加入到pjsip hasSomeSuccess = true; } index ++; } while (c.moveToNext() && index < 10); } } catch (Exception e) { Log.e(THIS_FILE, "Error on looping over sip profiles", e); } finally { c.close(); } } hasSomeActiveAccount = hasSomeSuccess; if (hasSomeSuccess) { acquireResources(); } else { releaseResources(); if (notificationManager != null) { notificationManager.cancelRegisters(); } } } //设置帐户注册状态信息 public boolean setAccountRegistration(SipProfile account, int renew, boolean forceReAdd) throws SameThreadException { boolean status = false; if(pjService != null) { status = pjService.setAccountRegistration(account, renew, forceReAdd); } return status; } /** * Remove accounts from database 从数据库中移除帐号信息 */ private void unregisterAllAccounts(boolean cancelNotification) throws SameThreadException { releaseResources(); Log.d(THIS_FILE, "Remove all accounts"); Cursor c = getContentResolver().query(SipProfile.ACCOUNT_URI, DBProvider.ACCOUNT_FULL_PROJECTION, null, null, null); if (c != null) { try { c.moveToFirst(); do { SipProfile account = new SipProfile(c); setAccountRegistration(account, 0, false); } while (c.moveToNext() ); } catch (Exception e) { Log.e(THIS_FILE, "Error on looping over sip profiles", e); } finally { c.close(); } } if (notificationManager != null && cancelNotification) { notificationManager.cancelRegisters(); } } //重新加载帐户数据库 private void reAddAllAccounts() throws SameThreadException { Log.d(THIS_FILE, "RE REGISTER ALL ACCOUNTS"); unregisterAllAccounts(false); addAllAccounts(); }
public boolean addAccount(SipProfile profile) throws SameThreadException {//底层注册 int status = pjsuaConstants.PJ_FALSE; if (!created) { //是否已创建 Log.e(THIS_FILE, "PJSIP is not started here, nothing can be done"); return status == pjsuaConstants.PJ_SUCCESS; } PjSipAccount account = new PjSipAccount(profile); //帐户信息 account.applyExtraParams(service); // Force the use of a transport /* * switch (account.transport) { case SipProfile.TRANSPORT_UDP: if * (udpTranportId != null) { * //account.cfg.setTransport_id(udpTranportId); } break; case * SipProfile.TRANSPORT_TCP: if (tcpTranportId != null) { // * account.cfg.setTransport_id(tcpTranportId); } break; case * SipProfile.TRANSPORT_TLS: if (tlsTransportId != null) { // * account.cfg.setTransport_id(tlsTransportId); } break; default: break; * } */ SipProfileState currentAccountStatus = getProfileState(profile); account.cfg.setRegister_on_acc_add(pjsuaConstants.PJ_FALSE);//注册 if (currentAccountStatus.isAddedToStack()) {//是否加入到堆栈 pjsua.csipsimple_set_acc_user_data(currentAccountStatus.getPjsuaId(), account.css_cfg);//设置帐户信息 status = pjsua.acc_modify(currentAccountStatus.getPjsuaId(), account.cfg);//修改配置信息 beforeAccountRegistration(currentAccountStatus.getPjsuaId(), profile);//调用注册前函数 ContentValues cv = new ContentValues(); cv.put(SipProfileState.ADDED_STATUS, status); service.getContentResolver().update( ContentUris.withAppendedId(SipProfile.ACCOUNT_STATUS_ID_URI_BASE, profile.id), cv, null, null); //更新帐户信息 if (!account.wizard.equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) { // Re register if (status == pjsuaConstants.PJ_SUCCESS) { status = pjsua.acc_set_registration(currentAccountStatus.getPjsuaId(), 1); if (status == pjsuaConstants.PJ_SUCCESS) { pjsua.acc_set_online_status(currentAccountStatus.getPjsuaId(), 1);//更新帐户状态 } } } } else { int[] accId = new int[1]; if (account.wizard.equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) { // We already have local account by default // For now consider we are talking about UDP one // In the future local account should be set per transport switch (account.transport) { //选择穿透方式 case SipProfile.TRANSPORT_UDP: accId[0] = prefsWrapper.useIPv6() ? localUdp6AccPjId : localUdpAccPjId; break; case SipProfile.TRANSPORT_TCP: accId[0] = prefsWrapper.useIPv6() ? localTcp6AccPjId : localTcpAccPjId; break; case SipProfile.TRANSPORT_TLS: accId[0] = prefsWrapper.useIPv6() ? localTls6AccPjId : localTlsAccPjId; break; default: // By default use UDP accId[0] = localUdpAccPjId; break; } pjsua.csipsimple_set_acc_user_data(accId[0], account.css_cfg);//设置用户配置信息 // TODO : use video cfg here // nCfg.setVid_in_auto_show(pjsuaConstants.PJ_TRUE); // nCfg.setVid_out_auto_transmit(pjsuaConstants.PJ_TRUE); // status = pjsua.acc_modify(accId[0], nCfg); } else { // Cause of standard account different from local account :) status = pjsua.acc_add(account.cfg, pjsuaConstants.PJ_FALSE, accId); pjsua.csipsimple_set_acc_user_data(accId[0], account.css_cfg); beforeAccountRegistration(accId[0], profile); pjsua.acc_set_registration(accId[0], 1); } if (status == pjsuaConstants.PJ_SUCCESS) {//成功设置状态信息 SipProfileState ps = new SipProfileState(profile); ps.setAddedStatus(status); ps.setPjsuaId(accId[0]); service.getContentResolver().insert( ContentUris.withAppendedId(SipProfile.ACCOUNT_STATUS_ID_URI_BASE, account.id), ps.getAsContentValue()); pjsua.acc_set_online_status(accId[0], 1); } } return status == pjsuaConstants.PJ_SUCCESS; } void beforeAccountRegistration(int pjId, SipProfile profile) { //注册前触发 for (PjsipModule mod : pjsipModules.values()) { mod.onBeforeAccountStartRegistration(pjId, profile); } }
REGISTER sip:www.**.net:5060 SIP/2.0 Via: SIP/2.0/UDP 10.0.2.15:60591;rport;branch=z9hG4bKPjfvTjKWT5urgwc2nwez3BgasaQYYDLpTj Route: <sip:www.**.net:5060;transport=udp;lr> Max-Forwards: 70 From: <sip:1001@www.**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx To: <sip:1001@www.**.net> Call-ID: srV7dgEt90L2cGD7t--2OVnQGFhPXcbB CSeq: 63463 REGISTER User-Agent: CSipSimple_generic-8/r2353 Contact: <sip:[email protected]:60591;ob> Expires: 900 Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS Content-Length: 0 SIP/2.0 401 Unauthorized Via: SIP/2.0/UDP 10.0.2.15:60591;rport=52571;branch=z9hG4bKPjfvTjKWT5urgwc2nwez3BgasaQYYDLpTj;received=192.168.1.154 From: <sip:1001@www.**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx To: <sip:1001@www.**.net>;tag=4f5e0299ccbb80ebb6598255e669265c.a5b5 Call-ID: srV7dgEt90L2cGD7t--2OVnQGFhPXcbB CSeq: 63463 REGISTER WWW-Authenticate: Digest realm="www.**.net", nonce="Ux5l0lMeZKaFznRge1gZtxoYW//UWjA2" Server: kamailio (4.0.3 (x86_64/linux)) Content-Length: 0 REGISTER sip:www.**.net:5060 SIP/2.0 Via: SIP/2.0/UDP 10.0.2.15:60591;rport;branch=z9hG4bKPjrw3ueP2qlwf7pE6T2eM.b..-AeoqKmdc Route: <sip:www.**.net:5060;transport=udp;lr> Max-Forwards: 70 From: <sip:1001@www.**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx To: <sip:1001@www.**.net> Call-ID: srV7dgEt90L2cGD7t--2OVnQGFhPXcbB CSeq: 63464 REGISTER User-Agent: CSipSimple_generic-8/r2353 Contact: <sip:[email protected]:60591;ob> Expires: 900 Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS Authorization: Digest username="1001", realm="www.**.net", nonce="Ux5l0lMeZKaFznRge1gZtxoYW//UWjA2", uri="sip:www.**.net:5060", response="8a006ec04c954b1533a5a895d77929c5" Content-Length: 0 SIP/2.0 200 OK Via: SIP/2.0/UDP 10.0.2.15:60591;rport=52571;branch=z9hG4bKPjrw3ueP2qlwf7pE6T2eM.b..-AeoqKmdc;received=192.168.1.154 From: <sip:1001@www.**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx To: <sip:1001@www.**.net>;tag=4f5e0299ccbb80ebb6598255e669265c.eb21 Call-ID: srV7dgEt90L2cGD7t--2OVnQGFhPXcbB CSeq: 63464 REGISTER Contact: <sip:[email protected]:60591;ob>;expires=600;received="sip:192.168.1.154:52571" Server: kamailio (4.0.3 (x86_64/linux)) Content-Length: 0 SUBSCRIBE sip:1001@www.**.net SIP/2.0 Via: SIP/2.0/UDP 10.0.2.15:60591;rport;branch=z9hG4bKPjgXYRCe5ny..u9iYYumroyVEWYE8V0hYS Max-Forwards: 70 From: <sip:1001@www.**.net>;tag=ciLmE1XieFD0ZkO0CblHPwCRMiQyL8Vb To: <sip:1001@www.**.net> Contact: <sip:[email protected]:52571;ob> Call-ID: AEzcRXNFhpLpFEJDhAJ-qtZxbZASJmfp CSeq: 24382 SUBSCRIBE Route: <sip:www.**.net:5060;transport=udp;lr> Event: message-summary Expires: 3600 Supported: replaces, 100rel, timer, norefersub Accept: application/simple-message-summary Allow-Events: presence, message-summary, refer User-Agent: CSipSimple_generic-8/r2353 Content-Length: 0 SIP/2.0 407 Proxy Authentication Required Via: SIP/2.0/UDP 10.0.2.15:60591;rport=52571;branch=z9hG4bKPjgXYRCe5ny..u9iYYumroyVEWYE8V0hYS;received=192.168.1.154 From: <sip:1001@www.**.net>;tag=ciLmE1XieFD0ZkO0CblHPwCRMiQyL8Vb To: <sip:1001@www.**.net>;tag=4f5e0299ccbb80ebb6598255e669265c.e4b6 Call-ID: AEzcRXNFhpLpFEJDhAJ-qtZxbZASJmfp CSeq: 24382 SUBSCRIBE Proxy-Authenticate: Digest realm="www.**.net", nonce="Ux5l0lMeZKaFznRge1gZtxoYW//UWjA2" Server: kamailio (4.0.3 (x86_64/linux)) Content-Length: 0 SIP/2.0 202 OK Via: SIP/2.0/UDP 10.0.2.15:60591;rport=52571;branch=z9hG4bKPjRhk4xHkOkvy82g1N2n3I-d0m2CHWwlJT;received=192.168.1.154 From: <sip:1001@www.**.net>;tag=ciLmE1XieFD0ZkO0CblHPwCRMiQyL8Vb To: <sip:1001@www.**.net>;tag=afbf025b308b45bb32e1b93911cf7810-9f85 Call-ID: AEzcRXNFhpLpFEJDhAJ-qtZxbZASJmfp CSeq: 24383 SUBSCRIBE Expires: 3600 Contact: <sip:113.195.206.200:5060;transport=udp> Server: kamailio (4.0.3 (x86_64/linux)) Content-Length: 0 SIP/2.0 200 OK Via: SIP/2.0/UDP 113.195.206.200;received=113.195.206.200;branch=z9hG4bKa281.981eed9.0 Call-ID: AEzcRXNFhpLpFEJDhAJ-qtZxbZASJmfp From: <sip:1001@www.**.net>;tag=afbf025b308b45bb32e1b93911cf7810-9f85 To: <sip:1001@www.**.net>;tag=ciLmE1XieFD0ZkO0CblHPwCRMiQyL8Vb CSeq: 2 NOTIFY Contact: <sip:[email protected]:52571;ob> Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS Supported: replaces, 100rel, timer, norefersub Content-Length: 0
电话的拨打在SipService代码中,代码如下:
/** * {@inheritDoc} */ @Override public void makeCall(final String callee, final int accountId) throws RemoteException { makeCallWithOptions(callee, accountId, null); } @Override public void makeCallWithOptions(final String callee, final int accountId, final Bundle options) throws RemoteException { SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null); //We have to ensure service is properly started and not just binded SipService.this.startService(new Intent(SipService.this, SipService.class));//开启服务 if(pjService == null) { Log.e(THIS_FILE, "Can't place call if service not started"); // TODO - we should return a failing status here return; } if(!supportMultipleCalls) { // Check if there is no ongoing calls if so drop this request by alerting user SipCallSession activeCall = pjService.getActiveCallInProgress();//已有电话 if(activeCall != null) { if(!CustomDistribution.forceNoMultipleCalls()) { notifyUserOfMessage(R.string.not_configured_multiple_calls); } return; } } getExecutor().execute(new SipRunnable() { @Override protected void doRun() throws SameThreadException { pjService.makeCall(callee, accountId, options);//底层拨打 } }); }
/** * Make a call * * @param callee * remote contact ot call If not well formated we try to add * domain name of the default account */ public int makeCall(String callee, int accountId, Bundle b) throws SameThreadException { if (!created) { // 未创建 return -1; } final ToCall toCall = sanitizeSipUri(callee, accountId);// 构造对应sip地址 if (toCall != null) { pj_str_t uri = pjsua.pj_str_copy(toCall.getCallee()); // Nothing to do with this values byte[] userData = new byte[1]; int[] callId = new int[1]; pjsua_call_setting cs = new pjsua_call_setting(); pjsua_msg_data msgData = new pjsua_msg_data(); int pjsuaAccId = toCall.getPjsipAccountId(); // Call settings to add video pjsua.call_setting_default(cs);// 添加电话配置信息 cs.setAud_cnt(1); cs.setVid_cnt(0); if (b != null && b.getBoolean(SipCallSession.OPT_CALL_VIDEO, false)) { cs.setVid_cnt(1); } cs.setFlag(0); pj_pool_t pool = pjsua.pool_create("call_tmp", 512, 512);// 池 // Msg data to add headers pjsua.msg_data_init(msgData); // 构造消息信息 pjsua.csipsimple_init_acc_msg_data(pool, pjsuaAccId, msgData); if (b != null) { Bundle extraHeaders = b .getBundle(SipCallSession.OPT_CALL_EXTRA_HEADERS); if (extraHeaders != null) { for (String key : extraHeaders.keySet()) { try { String value = extraHeaders.getString(key); if (!TextUtils.isEmpty(value)) { int res = pjsua .csipsimple_msg_data_add_string_hdr( pool, msgData, pjsua.pj_str_copy(key), pjsua.pj_str_copy(value)); if (res == pjsuaConstants.PJ_SUCCESS) { Log.e(THIS_FILE, "Failed to add Xtra hdr (" + key + " : " + value + ") probably not X- header"); } } } catch (Exception e) { Log.e(THIS_FILE, "Invalid header value for key : " + key); } } } } // 拨打电话 int status = pjsua.call_make_call(pjsuaAccId, uri, cs, userData, msgData, callId); if (status == pjsuaConstants.PJ_SUCCESS) { dtmfToAutoSend.put(callId[0], toCall.getDtmf()); Log.d(THIS_FILE, "DTMF - Store for " + callId[0] + " - " + toCall.getDtmf()); } pjsua.pj_pool_release(pool); // 释放 return status; } else { service.notifyUserOfMessage(service .getString(R.string.invalid_sip_uri) + " : " + callee); } return -1; }
电话监听在UAStateReceiver中,该类是继承Callback的,Callback是调用jni的类,关键代码如下:
/* * private class IncomingCallInfos { public SipCallSession callInfo; public * Integer accId; } */ @Override public void on_incoming_call(final int accId, final int callId, SWIGTYPE_p_pjsip_rx_data rdata) { lockCpu(); // Check if we have not already an ongoing call boolean hasOngoingSipCall = false; if (pjService != null && pjService.service != null) { SipCallSessionImpl[] calls = getCalls(); if (calls != null) { for (SipCallSessionImpl existingCall : calls) { if (!existingCall.isAfterEnded() && existingCall.getCallId() != callId) { if (!pjService.service.supportMultipleCalls) { Log.e(THIS_FILE, "Settings to not support two call at the same time !!!"); // If there is an ongoing call and we do not support // multiple calls // Send busy here pjsua.call_hangup(callId, StatusCode.BUSY_HERE, null, null); unlockCpu(); return; } else { hasOngoingSipCall = true; } } } } } try { SipCallSessionImpl callInfo = updateCallInfoFromStack(callId, null); Log.d(THIS_FILE, "Incoming call << for account " + accId); // Extra check if set reference counted is false ??? if (!ongoingCallLock.isHeld()) { ongoingCallLock.acquire(); } final String remContact = callInfo.getRemoteContact(); callInfo.setIncoming(true); notificationManager.showNotificationForCall(callInfo); // Auto answer feature SipProfile acc = pjService.getAccountForPjsipId(accId); Bundle extraHdr = new Bundle(); fillRDataHeader("Call-Info", rdata, extraHdr); final int shouldAutoAnswer = pjService.service.shouldAutoAnswer(remContact, acc, extraHdr); Log.d(THIS_FILE, "Should I anto answer ? " + shouldAutoAnswer); if (shouldAutoAnswer >= 200) { // Automatically answer incoming calls with 200 or higher final // code pjService.callAnswer(callId, shouldAutoAnswer); } else { // Ring and inform remote about ringing with 180/RINGING pjService.callAnswer(callId, 180); if (pjService.mediaManager != null) { if (pjService.service.getGSMCallState() == TelephonyManager.CALL_STATE_IDLE && !hasOngoingSipCall) { pjService.mediaManager.startRing(remContact); } else { pjService.mediaManager.playInCallTone(MediaManager.TONE_CALL_WAITING); } } broadCastAndroidCallState("RINGING", remContact); } if (shouldAutoAnswer < 300) { // Or by api launchCallHandler(callInfo); Log.d(THIS_FILE, "Incoming call >>"); } } catch (SameThreadException e) { // That's fine we are in a pjsip thread } finally { unlockCpu(); } }
我们知道CSipsimple中的音频编解码、视频编解码是以插件的形式加入的。我们先看下它是如何加入的。
在PjSipService中sipStart函数中有如下代码:
// Audio implementation 加入音频插件 int implementation = prefsWrapper .getPreferenceIntegerValue(SipConfigManager.AUDIO_IMPLEMENTATION); if (implementation == SipConfigManager.AUDIO_IMPLEMENTATION_OPENSLES) { dynamic_factory audImp = cssCfg.getAudio_implementation(); audImp.setInit_factory_name(pjsua .pj_str_copy("pjmedia_opensl_factory")); File openslLib = NativeLibManager.getBundledStackLibFile( service, "libpj_opensl_dev.so"); audImp.setShared_lib_path(pjsua.pj_str_copy(openslLib .getAbsolutePath())); cssCfg.setAudio_implementation(audImp); Log.d(THIS_FILE, "Use OpenSL-ES implementation"); } // Video implementation 加入视频插件 if (prefsWrapper .getPreferenceBooleanValue(SipConfigManager.USE_VIDEO)) { // TODO :: Have plugins per capture / render / video codec / // converter Map<String, DynCodecInfos> videoPlugins = ExtraPlugins .getDynCodecPlugins(service, SipManager.ACTION_GET_VIDEO_PLUGIN); if (videoPlugins.size() > 0) { DynCodecInfos videoPlugin = videoPlugins.values() .iterator().next(); pj_str_t pjVideoFile = pjsua .pj_str_copy(videoPlugin.libraryPath); Log.d(THIS_FILE, "Load video plugin at " + videoPlugin.libraryPath); // Render { dynamic_factory vidImpl = cssCfg .getVideo_render_implementation(); vidImpl.setInit_factory_name(pjsua .pj_str_copy("pjmedia_webrtc_vid_render_factory")); vidImpl.setShared_lib_path(pjVideoFile); } // Capture { dynamic_factory vidImpl = cssCfg .getVideo_capture_implementation(); vidImpl.setInit_factory_name(pjsua .pj_str_copy("pjmedia_webrtc_vid_capture_factory")); vidImpl.setShared_lib_path(pjVideoFile); /* * -- For testing video screen -- Not yet released * try { ComponentName cmp = new * ComponentName("com.csipsimple.plugins.video", * "com.csipsimple.plugins.video.CaptureReceiver"); * DynCodecInfos screenCapt = new * ExtraPlugins.DynCodecInfos(service, cmp); * vidImpl.setInit_factory_name(pjsua * .pj_str_copy(screenCapt.factoryInitFunction)); * vidImpl.setShared_lib_path(pjsua * .pj_str_copy(screenCapt.libraryPath)); } catch * (NameNotFoundException e) { Log.e(THIS_FILE, * "Not found capture plugin"); } */ } // Video codecs 加入视频解码 availableCodecs = ExtraPlugins.getDynCodecPlugins( service, SipManager.ACTION_GET_EXTRA_VIDEO_CODECS); cssCodecs = cssCfg.getExtra_vid_codecs(); dynamic_factory[] cssCodecsDestroy = cssCfg .getExtra_vid_codecs_destroy(); i = 0; for (Entry<String, DynCodecInfos> availableCodec : availableCodecs .entrySet()) { DynCodecInfos dyn = availableCodec.getValue(); if (!TextUtils.isEmpty(dyn.libraryPath)) { // Create cssCodecs[i].setShared_lib_path(pjsua .pj_str_copy(dyn.libraryPath)); cssCodecs[i].setInit_factory_name(pjsua .pj_str_copy(dyn.factoryInitFunction)); // Destroy cssCodecsDestroy[i].setShared_lib_path(pjsua .pj_str_copy(dyn.libraryPath)); cssCodecsDestroy[i] .setInit_factory_name(pjsua .pj_str_copy(dyn.factoryDeinitFunction)); } i++; } cssCfg.setExtra_vid_codecs_cnt(i); // Converter dynamic_factory convertImpl = cssCfg.getVid_converter(); convertImpl.setShared_lib_path(pjVideoFile); convertImpl .setInit_factory_name(pjsua .pj_str_copy("pjmedia_libswscale_converter_init")); } }
<!-- Extra codecs 音频插件--> <receiver android:name="com.csipsimple.plugins.codecs.ReceiverSILK" android:exported="false" > <meta-data android:name="lib_name" android:value="libpj_silk_codec.so" /> <meta-data android:name="init_factory" android:value="pjmedia_codec_silk_init" /> <intent-filter> <action android:name="com.csipsimple.codecs.action.REGISTER_CODEC" /> </intent-filter> </receiver>
<!-- Receiver for standard video 视频插件 --> <receiver android:name=".PluginReceiver" > <intent-filter> <action android:name="com.csipsimple.plugins.action.REGISTER_VIDEO" /> </intent-filter> <meta-data android:name="lib_name" android:value="libpj_video_android.so" /> <!-- For now it does not matter in the future we should have one per device, codec, and converter (if needed) --> <meta-data android:name="init_factory" android:value="pjmedia_webrtc_vid_render_factory" /> </receiver> <!-- Receiver for video capture <receiver android:name=".CaptureReceiver" > <intent-filter> <action android:name="com.csipsimple.plugins.action.REGISTER_CAPTURE_VIDEO" /> </intent-filter> <meta-data android:name="lib_name" android:value="libpj_screen_capture_android.so" /> <meta-data android:name="init_factory" android:value="pjmedia_webrtc_vid_capture_factory" /> </receiver> --> <receiver android:name=".PluginReceiverFfmpeg" > <intent-filter> <action android:name="com.csipsimple.codecs.action.REGISTER_VIDEO_CODEC" /> </intent-filter> <meta-data android:name="lib_name" android:value="libpj_video_android.so" /> <meta-data android:name="init_factory" android:value="pjmedia_codec_ffmpeg_vid_init" /> <meta-data android:name="deinit_factory" android:value="pjmedia_codec_ffmpeg_vid_deinit" /> </receiver> <receiver android:name=".PluginReceiverVpx" > <intent-filter> <action android:name="com.csipsimple.codecs.action.REGISTER_VIDEO_CODEC" /> </intent-filter> <meta-data android:name="lib_name" android:value="libpj_vpx.so" /> <meta-data android:name="init_factory" android:value="pjmedia_codec_vpx_init" /> <meta-data android:name="deinit_factory" android:value="pjmedia_codec_vpx_deinit" /> </receiver>
它是如何消除回音的?
在PjSipService文件中,有如下函数:
//消除回音 public void setEchoCancellation(boolean on) throws SameThreadException { if (created && userAgentReceiver != null) { Log.d(THIS_FILE, "set echo cancelation " + on); pjsua.set_ec( on ? prefsWrapper.getEchoCancellationTail() : 0, prefsWrapper .getPreferenceIntegerValue(SipConfigManager.ECHO_MODE)); } }