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:1001@10.0.2.15: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:1001@10.0.2.15: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:1001@10.0.2.15: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:1001@192.168.1.154: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:1001@192.168.1.154: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));
}
}