[原]CSipSimple 简单分析

标签: | 发表时间:2014-03-11 18:30 | 作者:banketree
出处:http://blog.csdn.net/banketree
简介

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);
		}
	}

然后执行SipService中的
  
  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();
	}

真正实现注册的是在PjSipService中,关键代码如下:

 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

2、电话拨打、电话监听

电话的拨打在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();
        }

    }

3、音频视频编解码

我们知道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"));
					}
				}

这只是对音频、视频编解码信息的读取,那具体是如何加入的呢?看一下AndroidManifest.xml文件,原来是通过广播添加的,代码如下:

      <!-- 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));
		}
	}

原来是通过底层进行回音消除的。

结束
简单的分析一下CSipSimple,对sip的认识又进了一步,近期将对它进行再封装。


作者:banketree 发表于2014-3-11 10:30:30 原文链接
阅读:140 评论:0 查看评论

相关 [csipsimple 分析] 推荐:

[原]CSipSimple 简单分析

- - banketree
CSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话. 连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果才好. 用户首先选择使用哪国哪个类型,这是由com.csipsimple.wizards.impl包下完成的.

竞品分析

- 章明 - 互联网的那点事
关于竞品分析,之前天行(@天行Aeros)有篇文章《设计公式:简单有效的竞品分析》已经进行了介绍,本文在该文章的基础之上再进行一些分享,希望对大家有用. 竞品分析(Competitive Analysis)一词最早源于经济学领域. 市场营销和战略管理方面的竞品分析是指对现有的或潜在的竞争产品的优势和劣势进行评价.

Excel-对比图分析(差异分析)

- - CSDN博客数据库推荐文章
本文摘自作者《网站数据分析:数据驱动的网站管理、优化和运营 》: http://item.jd.com/11295690.html. 对比分析就是将两个或两个以上的数据进行比较,分析它们之间的差异,从而揭示这些数据所代表事物的发展变化情况和规律. 通过对比,可以很直观地看出事物在某方面的差距,并且可以准确、量化地表示出差距的多少.

Netty代码分析

- LightingMan - 淘宝JAVA中间件团队博客
Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序[官方定义],整体来看其包含了以下内容:1.提供了丰富的协议编解码支持,2.实现自有的buffer系统,减少复制所带来的消耗,3.整套channel的实现,4.基于事件的过程流转以及完整的网络事件响应与扩展,5.丰富的example.

分析帝转世

- JoyLee - Lzhi&#39;s Views
纪晓岚与和珅的经典对白(和绅说得是实话啊). 本文网址:http://www.lzhi.org/views/652030. 欢迎加入500人超级QQ群:108869281,交流最新好文章.

fqueue初步分析

- tangfl - 淘宝JAVA中间件团队博客
    fqueue是国产的一个类似memcacheq,kestrel这样的支持memcached协议的轻量级开源MQ. http://code.google.com/p/fqueue/downloads/list,介绍和特点都可以看主页,我就不废话了.     今天老大提到, co了源码看了下,写个初步分析报告.

"偷情的分析

- 阳阳 - Cao Liu
一、弦断无人听��风情万种的才情女子,下嫁给木头人或者肌肉男. 他永远不可能对落叶秋风或者飞雪骄阳产生莫名感触,并视此种举动为无知可笑. 你梦想中一直有一个优雅书生,你可以和他笑傲江湖,共赏风花雪月. 有一天,这个人悄然降临,似乎是一种无可逃脱的宿命.   二、你是疯儿我是傻��挑不出他品德上的毛病.