CSipSimple 简单分析

简介

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:[email protected]**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx
To: <sip:[email protected]**.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:[email protected]**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx
To: <sip:[email protected]**.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:[email protected]**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx
To: <sip:[email protected]**.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:[email protected]**.net>;tag=M3IMkYd2B3u30Nto9ctSUMerD.7ya2kx
To: <sip:[email protected]**.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:[email protected]**.net SIP/2.0
Via: SIP/2.0/UDP 10.0.2.15:60591;rport;branch=z9hG4bKPjgXYRCe5ny..u9iYYumroyVEWYE8V0hYS
Max-Forwards: 70
From: <sip:[email protected]**.net>;tag=ciLmE1XieFD0ZkO0CblHPwCRMiQyL8Vb
To: <sip:[email protected]**.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:[email protected]**.net>;tag=ciLmE1XieFD0ZkO0CblHPwCRMiQyL8Vb
To: <sip:[email protected]**.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:[email protected]**.net>;tag=ciLmE1XieFD0ZkO0CblHPwCRMiQyL8Vb
To: <sip:[email protected]**.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:[email protected]**.net>;tag=afbf025b308b45bb32e1b93911cf7810-9f85
To: <sip:[email protected]**.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 原文链接

Tagged: ,

Comments are closed.