研究了SipDroid2.7,自己对它的理解也渐渐的清晰了。
那它是怎样实现电话拨打以及电话监听的?它的音频接收以及发送是怎么实现的?它的视频又是怎么一回事?它在模拟器上的端口为什么总是变化的?它又是如何处理登陆超时以及通话出错的?
带着这些疑问进入它的代码思想境界!
使用yate搭配服务器,然后使用了一个yate与SipDroid客户端进行通话!~至于怎么搭配服务器以及SipDroid的配置设置,此处就不讨论了!~
登陆后的标识效果如图:
然后我使用了yate客户端程序拨打了SipDroid程序段上的帐号(banketree),如图:
接通后的效果图像如图:
好了,进入我们的主题了!~
它是怎样实现电话拨打以及电话监听的?
程序进入时会进行服务注册,如下:
// 实例化一个引擎 注册模式 由登陆时进行…… // Receiver.engine(this).registerMore();
// 构造引擎 public static synchronized SipdroidEngine engine(Context context) { // 构造引擎的条件 if (mContext == null || !context.getClass().getName() .contains("ReceiverRestrictedContext")) { mContext = context; } // 为空则构造 if (mSipdroidEngine == null) { mSipdroidEngine = new SipdroidEngine(); // 开始 mSipdroidEngine.StartEngine(); // 开启蓝牙服务 if (Integer.parseInt(Build.VERSION.SDK) >= 8) { Bluetooth.init(); } } else { // 引擎已经存在 mSipdroidEngine.CheckEngine(); } // 开启服务 context.startService(new Intent(context, RegisterService.class)); return mSipdroidEngine; }
而StartEngine()里有开启监听!~
//注册 在此向服务器发送请求。 register(); //实例化一个ExtendedCall 循环监听指定端口 listen();
消息以及协议的通信都是SipProvider类由完成的!~
public void onReceivedMessage(Transport transport, Message msg) { if (pm == null) { pm = (PowerManager) Receiver.mContext.getSystemService(Context.POWER_SERVICE); wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Sipdroid.SipProvider"); } wl.acquire(); // modified //处理接收消息 当程序进行了监听,就会向此类添加一个监听事件标识 //处理消息就是根据标识进行区分的,例如来电、接听、视频等等!~~~ processReceivedMessage(msg); wl.release(); }
/** * Sends a Message, specifing the transport portocol, nexthop address and * port. * 发送一条消息,指定传输协议、地址、以及端口。 */ private ConnectionIdentifier sendMessage(Message msg, String proto, IpAddress dest_ipaddr, int dest_port, int ttl) { if(bDebug) { android.util.Log.i("SipProvider发送消息", "msg:"+msg.toString()); } ConnectionIdentifier conn_id = new ConnectionIdentifier(proto, dest_ipaddr, dest_port); if (log_all_packets || msg.getLength() > MIN_MESSAGE_LENGTH) printLog("Sending message to " + conn_id, LogLevel.MEDIUM); if (transport_udp && proto.equals(PROTO_UDP)) { // UDP // printLog("using UDP",LogLevel.LOW); conn_id = null; try { // if (ttl>0 && multicast_address) do something? udp.sendMessage(msg, dest_ipaddr, dest_port); } catch (IOException e) { printException(e, LogLevel.HIGH); return null; } } else if (transport_tcp && proto.equals(PROTO_TCP)) { // TCP // printLog("using TCP",LogLevel.LOW); if (connections == null || !connections.containsKey(conn_id)) { // modified printLog("no active connection found matching " + conn_id, LogLevel.MEDIUM); printLog("open " + proto + " connection to " + dest_ipaddr + ":" + dest_port, LogLevel.MEDIUM); TcpTransport conn = null; try { conn = new TcpTransport(dest_ipaddr, dest_port, this); } catch (Exception e) { printLog("connection setup FAILED", LogLevel.HIGH); return null; } printLog("connection " + conn + " opened", LogLevel.HIGH); addConnection(conn); if (!msg.isRegister()) Receiver.engine(Receiver.mContext).register(); // modified } else { printLog("active connection found matching " + conn_id, LogLevel.MEDIUM); } ConnectedTransport conn = (ConnectedTransport) connections.get(conn_id); if (conn != null) { printLog("sending data through conn " + conn, LogLevel.MEDIUM); try { conn.sendMessage(msg); conn_id = new ConnectionIdentifier(conn); } catch (IOException e) { printException(e, LogLevel.HIGH); return null; } } else { // this point has not to be reached这一点还没有达到 printLog("ERROR: conn " + conn_id + " not found: abort.", LogLevel.MEDIUM); return null; } } else { // otherwise printWarning("Unsupported protocol (" + proto + "): Message discarded", LogLevel.HIGH); return null; } // logs String dest_addr = dest_ipaddr.toString(); printMessageLog(proto, dest_addr, dest_port, msg.getLength(), msg, "sent"); return conn_id; }
……
回到问题中来,监听电话已经明白了,那是如何实现拨打的?
当用户登陆后就会把自己的信息发送给服务端,服务端记录下来,等用户需要时就返回给他,比如我打banketree电话,我没有他的信息我怎么打呀,是吧!~
拨打电话的关键在UserAgent中,其它的都是封装好处理信息的类!~
public boolean call(String target_url, boolean send_anonymous) { if (Receiver.call_state != UA_STATE_IDLE) { // We can initiate or terminate a call only when // we are in an idle state //只有当我们处于闲置状态,我们可以开始或结束通话 printLog("Call attempted in state" + this.getSessionDescriptor() + " : Failing Request", LogLevel.HIGH); return false; } //挂断 hangup(); // modified //改变状态 changeStatus(UA_STATE_OUTGOING_CALL, target_url); String from_url; if (!send_anonymous) { from_url = user_profile.from_url; } else { from_url = "sip:[email protected]"; } // change start multi codecs 更改启动多编解码器 createOffer(); // change end call = new ExtendedCall(sip_provider, from_url, user_profile.contact_url, user_profile.username, user_profile.realm, user_profile.passwd, this); // in case of incomplete url (e.g. only 'user' is present), try to // complete it //在不完整的URL(例如,“用户”是存在的话)的情况下,尽量去完成它 if (target_url.indexOf("@") < 0) { if (user_profile.realm.equals(Settings.DEFAULT_SERVER)) target_url = "&" + target_url; target_url = target_url + "@" + realm; // modified } // MMTel addition to define MMTel ICSI to be included in INVITE (added // by mandrajg) 要包含在INVITE MMTel除了定义MMTel ICSI String icsi = null; if (user_profile.mmtel == true) { icsi = "\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\""; } //从服务器中获得对方的地址 target_url = sip_provider.completeNameAddress(target_url).toString(); //真的拨打电话 if (user_profile.no_offer) { call.call(target_url); } else { call.call(target_url, local_session, icsi); // modified by mandrajg } return true; }
那它的音频接收以及发送是怎么实现的?
管理音频有一个类,它是JAudioLauncher,当实例化它的时候,它会开启两个线程,一个是RtpStreamReceiver,另一个是RtpStreamSender!~
看标题就知道它们一个是发送的,一个是接收的!~
先来看下接收是怎么实现的!~
public void run() { boolean nodata = PreferenceManager.getDefaultSharedPreferences( Receiver.mContext).getBoolean( org.sipdroid.sipua.ui.Settings.PREF_NODATA, org.sipdroid.sipua.ui.Settings.DEFAULT_NODATA); keepon = PreferenceManager.getDefaultSharedPreferences( Receiver.mContext).getBoolean( org.sipdroid.sipua.ui.Settings.PREF_KEEPON, org.sipdroid.sipua.ui.Settings.DEFAULT_KEEPON); if (rtp_socket == null) { if (DEBUG) { println("ERROR: RTP socket is null(出错:rtp接受套接字出错!)"); } return; } //发送包 缓冲区 byte[] buffer = new byte[BUFFER_SIZE + 12]; //构建包实例 rtp_packet = new RtpPacket(buffer, 0); if (DEBUG) { println("Reading blocks of max (读取快的最大值) = " + buffer.length + " bytes"); } running = true; //开启蓝牙 enableBluetooth(PreferenceManager.getDefaultSharedPreferences( Receiver.mContext).getBoolean( org.sipdroid.sipua.ui.Settings.PREF_BLUETOOTH, org.sipdroid.sipua.ui.Settings.DEFAULT_BLUETOOTH)); restored = false; //设置线程权限 android.os.Process .setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); am = (AudioManager) Receiver.mContext .getSystemService(Context.AUDIO_SERVICE); cr = Receiver.mContext.getContentResolver(); //保存设置 saveSettings(); Settings.System.putInt(cr, Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_NEVER); am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_OFF); am.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION, AudioManager.VIBRATE_SETTING_OFF); if (oldvol == -1) { oldvol = am.getStreamVolume(AudioManager.STREAM_MUSIC); } //初始化模式 initMode(); //设置音频编解码器 setCodec(); //编辑音频 由发送包解码后放到此处 然后调整后放入到声音播放器中 short lin[] = new short[BUFFER_SIZE]; //它是负责清空lin的 short lin2[] = new short[BUFFER_SIZE]; int server, headroom, todo, len = 0, m = 1, expseq, getseq, vm = 1, gap, gseq; ToneGenerator tg = new ToneGenerator( AudioManager.STREAM_VOICE_CALL, (int) (ToneGenerator.MAX_VOLUME * 2 * org.sipdroid.sipua.ui.Settings .getEarGain())); //播放 track.play(); //指示虚拟机运行垃圾收集器,这将是一个好时机。 //请注意,这仅是一个提示。有没有保证,垃圾收集器将实际运行。 System.gc(); //清空 empty(); lockFirst = true; while (running) { lock(true); if (Receiver.call_state == UserAgent.UA_STATE_HOLD) { lock(false); //停止 tg.stopTone(); //暂停 track.pause(); while (running && Receiver.call_state == UserAgent.UA_STATE_HOLD) { try { sleep(1000); } catch (InterruptedException e1) { } } track.play(); System.gc(); timeout = 1; luser = luser2 = -8000 * mu; } try { // 接受数据 rtp_socket.receive(rtp_packet); //超时 if (timeout != 0) { //停止音乐 tg.stopTone(); //暂停声音 track.pause(); //清空 for (int i = maxjitter * 2; i > 0; i -= BUFFER_SIZE) { write(lin2, 0, i > BUFFER_SIZE ? BUFFER_SIZE : i); } cnt += maxjitter * 2; //声音播放 track.play(); empty(); } timeout = 0; } catch (IOException e) { if (timeout == 0 && nodata) { tg.startTone(ToneGenerator.TONE_SUP_RINGTONE); } //异常者 断开 rtp_socket.getDatagramSocket().disconnect(); if (++timeout > 60) { Receiver.engine(Receiver.mContext).rejectcall(); break; } } //在运行 且未超时 if (running && timeout == 0) { //得到数字字符 gseq = rtp_packet.getSequenceNumber(); //得到当前接受包的大小 if (seq == gseq) { m++; continue; } gap = (gseq - seq) & 0xff; if (gap > 240) { continue; } //返回帧中表示播放头位置。 server = track.getPlaybackHeadPosition(); //接受包的总大小 - 当前播放的位置 headroom = user - server; if (headroom > 2 * jitter) { cnt += len; } else { cnt = 0; } if (lserver == server) { cnt2++; } else { cnt2 = 0; } if (cnt <= 500 * mu || cnt2 >= 2 || headroom - jitter < len || p_type.codec.number() != 8 || p_type.codec.number() != 0) { //有效负荷类型||改变类型 if (rtp_packet.getPayloadType() != p_type.number && p_type.change(rtp_packet.getPayloadType())) { //保留声量 saveVolume(); //设置编解码器 setCodec(); //恢复声量 restoreVolume(); //头信息 codec = p_type.codec.getTitle(); } //得到有效负荷长度 len = p_type.codec.decode(buffer, lin, rtp_packet.getPayloadLength()); // Call recording: Save incoming.Data is in buffer lin, from 0 to len. //通话记录:保存传入。数据是在缓冲区林,从0到LEN。 if (call_recorder != null) { //写入 call_recorder.writeIncoming(lin, 0, len); } //声音改变效果显示 if (speakermode == AudioManager.MODE_NORMAL) { calc(lin, 0, len); } else if (gain > 1) { calc2(lin, 0, len); } } // avgheadroom = avgheadroom * 0.99 + (double) headroom * 0.01; if (avgcnt++ > 300) { devheadroom = devheadroom * 0.999 + Math.pow(Math.abs(headroom - avgheadroom), 2) * 0.001; } //头大小 if (headroom < 250 * mu) { late++; newjitter(true); // System.out.println("RTP:underflow " // + (int) Math.sqrt(devheadroom)); todo = jitter - headroom; write(lin2, 0, todo > BUFFER_SIZE ? BUFFER_SIZE : todo); } // if (cnt > 500 * mu && cnt2 < 2) { todo = headroom - jitter; if (todo < len) { write(lin, todo, len - todo); } } else { write(lin, 0, len); } //丢失包统计 if (seq != 0) { getseq = gseq & 0xff; expseq = ++seq & 0xff; if (m == RtpStreamSender.m) { vm = m; } gap = (getseq - expseq) & 0xff; if (gap > 0) { // System.out.println("RTP:lost"); if (gap > 100) { gap = 1; } loss += gap; lost += gap; good += gap - 1; loss2++; } else { if (m < vm) { loss++; loss2++; } } good++; if (good > 110) { good *= 0.99; lost *= 0.99; loss *= 0.99; loss2 *= 0.99; late *= 0.99; } } m = 1; seq = gseq; if (user >= luser + 8000 * mu && (Receiver.call_state == UserAgent.UA_STATE_INCALL || Receiver.call_state == UserAgent.UA_STATE_OUTGOING_CALL)) { if (luser == -8000 * mu || getMode() != speakermode) { //保留声量 saveVolume(); //设置模式 setMode(speakermode); //恢复声量 restoreVolume(); } luser = user; if (user >= luser2 + 160000 * mu) { //抖动 newjitter(false); } } lserver = server; } }
public void run() { //测试音频数据 // testVoiceFile mvoiceFile = new testVoiceFile("voice"); // testVoiceFile mvoiceRtpFile = new testVoiceFile("rtpdate"); //wifi管理 WifiManager wm = (WifiManager) Receiver.mContext .getSystemService(Context.WIFI_SERVICE); long lastscan = 0, lastsent = 0; if (rtp_socket == null) return; int seqn = 0; long time = 0; double p = 0; //改善 boolean improve = PreferenceManager.getDefaultSharedPreferences( Receiver.mContext).getBoolean(Settings.PREF_IMPROVE, Settings.DEFAULT_IMPROVE); //选择wifi boolean selectWifi = PreferenceManager.getDefaultSharedPreferences( Receiver.mContext).getBoolean( org.sipdroid.sipua.ui.Settings.PREF_SELECTWIFI, org.sipdroid.sipua.ui.Settings.DEFAULT_SELECTWIFI); int micgain = 0; long last_tx_time = 0; long next_tx_delay; long now; running = true; m = 1; int dtframesize = 4; //改变线程权限 android.os.Process .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); //解码速率 mu = p_type.codec.samp_rate() / 8000; int min = AudioRecord.getMinBufferSize(p_type.codec.samp_rate(), AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); //最小缓冲大小 if (min == 640) { if (frame_size == 960) frame_size = 320; if (frame_size == 1024) frame_size = 160; min = 4096 * 3 / 2; } else if (min < 4096) { if (min <= 2048 && frame_size == 1024) frame_size /= 2; min = 4096 * 3 / 2; } else if (min == 4096) { min *= 3 / 2; if (frame_size == 960) frame_size = 320; } else { if (frame_size == 960) frame_size = 320; if (frame_size == 1024) frame_size *= 2; } //速率 frame_rate = p_type.codec.samp_rate() / frame_size; long frame_period = 1000 / frame_rate; frame_rate *= 1.5; //发送包缓冲区 byte[] buffer = new byte[frame_size + 12]; // //实例化包 RtpPacket rtp_packet = new RtpPacket(buffer, 0); //设置有效复合 rtp_packet.setPayloadType(p_type.number); //调式输出信息 if (DEBUG) println("Reading blocks of (读取块大小)" + buffer.length + " bytes"); println("Sample rate (采样率) = " + p_type.codec.samp_rate()); println("Buffer size(缓冲区大小) = " + min); //录制 AudioRecord record = null; //获得声音内容变量 short[] lin = new short[frame_size * (frame_rate + 1)]; int num, ring = 0, pos; //随机数 random = new Random(); //输入流 InputStream alerting = null; try { //接受 alerting = Receiver.mContext.getAssets().open("alerting"); } catch (IOException e2) { if (!Sipdroid.release) e2.printStackTrace(); } //初始化音频编码 p_type.codec.init(); if(Sipdroid.VoiceDebug) { Log.i("RtpStreamSender", "音频发送开始啦"); } //循环发送 先录制 然后发送 while (running) { //录制没有 或已经改变 if (changed || record == null) { if (record != null) { //释放操作 record.stop(); record.release(); if (RtpStreamReceiver.samsung) { AudioManager am = (AudioManager) Receiver.mContext .getSystemService(Context.AUDIO_SERVICE); am.setMode(AudioManager.MODE_IN_CALL); am.setMode(AudioManager.MODE_NORMAL); } } //未改变 changed = false; //实例化录制 record = new AudioRecord(MediaRecorder.AudioSource.MIC, p_type.codec.samp_rate(), AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, min); if(Sipdroid.VoiceDebug) { Log.i("RtpStreamSender", "构造音频录制实例"); } //得到录制类型 if (record.getState() != AudioRecord.STATE_INITIALIZED) { //拒接 Receiver.engine(Receiver.mContext).rejectcall(); record = null; break; } //开始录制 record.startRecording(); //计算 micgain = (int) (Settings.getMicGain() * 10); } //静音或挂断 则停止 if (muted || Receiver.call_state == UserAgent.UA_STATE_HOLD) { //挂断 if (Receiver.call_state == UserAgent.UA_STATE_HOLD) { //恢复模式 RtpStreamReceiver.restoreMode(); } if(Sipdroid.VoiceDebug) { Log.i("RtpStreamSender", "挂断电话则停止录制"); } //停止录音 record.stop(); //延时 while (running && (muted || Receiver.call_state == UserAgent.UA_STATE_HOLD)) { try { sleep(1000); } catch (InterruptedException e1) { } } //开始录制 record.startRecording(); } // DTMF change start if (dtmf.length() != 0) { //构造包 byte[] dtmfbuf = new byte[dtframesize + 12]; RtpPacket dt_packet = new RtpPacket(dtmfbuf, 0); //设置有效负荷 dt_packet.setPayloadType(dtmf_payload_type); //设置大小 dt_packet.setPayloadLength(dtframesize); dt_packet.setSscr(rtp_packet.getSscr()); long dttime = time; int duration; for (int i = 0; i < 6; i++) { time += 160; duration = (int) (time - dttime); dt_packet.setSequenceNumber(seqn++); dt_packet.setTimestamp(dttime); dtmfbuf[12] = rtpEventMap.get(dtmf.charAt(0)); dtmfbuf[13] = (byte) 0x0a; dtmfbuf[14] = (byte) (duration >> 8); dtmfbuf[15] = (byte) duration; try { //发送包 声音包 rtp_socket.send(dt_packet); if (Sipdroid.VoiceDebug) { Log.i("RtpStreamSender", "第一次发送声音包 大小" + dt_packet.getLength() + " " + dt_packet.getPacket()); } if(bShowVoiceDecodeData) { // mvoiceRtpFile.write(dt_packet.getPacket()); } sleep(20); } catch (Exception e1) { } } //发送回音? for (int i = 0; i < 3; i++) { duration = (int) (time - dttime); dt_packet.setSequenceNumber(seqn); dt_packet.setTimestamp(dttime); dtmfbuf[12] = rtpEventMap.get(dtmf.charAt(0)); dtmfbuf[13] = (byte) 0x8a; dtmfbuf[14] = (byte) (duration >> 8); dtmfbuf[15] = (byte) duration; try { //发送包 rtp_socket.send(dt_packet); if (Sipdroid.VoiceDebug) { Log.i("RtpStreamSender", "第二次发送声音包 大小" + dt_packet.getLength() + " " + dt_packet.getPacket()); } if(bShowVoiceDecodeData) { // mvoiceRtpFile.write(dt_packet.getPacket()); } } catch (Exception e1) { } } time += 160; seqn++; dtmf = dtmf.substring(1); } // DTMF change end if (frame_size < 480) { now = System.currentTimeMillis(); next_tx_delay = frame_period - (now - last_tx_time); last_tx_time = now; if (next_tx_delay > 0) { try { sleep(next_tx_delay); } catch (InterruptedException e1) { } last_tx_time += next_tx_delay - sync_adj; } } //获得发送的位置 pos = (ring + delay * frame_rate * frame_size) % (frame_size * (frame_rate + 1)); //得到大小 num = record.read(lin, pos, frame_size); if (num <= 0) continue; //是否有效 if (!p_type.codec.isValid()) continue; // Call recording: Save the frame to the CallRecorder. //通话记录:框架保存的CallRecorder的。 新的录制 if (call_recorder != null) { //写入 输出 call_recorder.writeOutgoing(lin, pos, num); } if (RtpStreamReceiver.speakermode == AudioManager.MODE_NORMAL) { calc(lin, pos, num); if (RtpStreamReceiver.nearend != 0 && RtpStreamReceiver.down_time == 0) { noise(lin, pos, num, p / 2); } else if (nearend == 0) { p = 0.9 * p + 0.1 * s; } } else { switch (micgain) { case 1: calc1(lin, pos, num); break; case 2: calc2(lin, pos, num); break; case 10: calc10(lin, pos, num); break; } } iCount++; //通话中 if (Receiver.call_state != UserAgent.UA_STATE_INCALL && Receiver.call_state != UserAgent.UA_STATE_OUTGOING_CALL && alerting != null) { try { if (alerting.available() < num / mu) { alerting.reset(); } alerting.read(buffer, 12, num / mu); } catch (IOException e) { if (!Sipdroid.release) { e.printStackTrace(); } } if (p_type.codec.number() != 8) { G711.alaw2linear(buffer, lin, num, mu); num = p_type.codec.encode(lin, 0, buffer, num); // if(bShowVoiceDecodeData) // { // byte[] sdf = lin.toString().getBytes(); // mvoiceFile.write(sdf); // // String string = ""; // // for(short i:buffer) // string+=i; // // Log.i("p_type.codec.encode",iCount +"编码后的数据(buffer):"+string); // } } } else { num = p_type.codec.encode(lin, ring % (frame_size * (frame_rate + 1)), buffer, num); //进行了编码 // if(bShowVoiceDecodeData) // { // mvoiceFile.write(buffer); // // String string = ""; // // for(short i:buffer) // string+=i; // // Log.i("p_type.codec.encode",iCount +"编码后的数据(buffer):"+string); // } } //大小 ring += frame_size; rtp_packet.setSequenceNumber(seqn++); rtp_packet.setTimestamp(time); rtp_packet.setPayloadLength(num); //记录时间 now = SystemClock.elapsedRealtime(); if (RtpStreamReceiver.timeout == 0 || Receiver.on_wlan || now - lastsent > 500) { try { lastsent = now; rtp_socket.send(rtp_packet); if (m > 1 && (RtpStreamReceiver.timeout == 0 || Receiver.on_wlan)) { for (int i = 1; i < m; i++) rtp_socket.send(rtp_packet); } if (Sipdroid.VoiceDebug) { Log.i("RtpStreamSender", "第三次发送声音包 大小" + rtp_packet.getLength() + " " + rtp_packet.getPacket()); } // if(bShowVoiceDecodeData) // { // mvoiceRtpFile.write(rtp_packet.getPacket()); // } } catch (Exception e) { } } //编码数 if (p_type.codec.number() == 9) { time += frame_size / 2; } else { time += frame_size; } if (RtpStreamReceiver.good != 0 && RtpStreamReceiver.loss2 / RtpStreamReceiver.good > 0.01) { if (selectWifi && Receiver.on_wlan && now - lastscan > 10000) { wm.startScan(); lastscan = now; } if (improve && delay == 0 && (p_type.codec.number() == 0 || p_type.codec.number() == 8 || p_type.codec .number() == 9)) { m = 2; } else { m = 1; } } else { m = 1; } }
好了,进入下一个问题,它的视频又是怎么一回事?此处不谈它的视频编码,直接介绍涉及它的流程!~
涉及视频的类有:VideoCamera、VideoCameraNew、VideoCameraNew2、VideoPreview!~ 而是否使用视频,关键在CallScreen类,CallScreen类是通话的界面!~
如下是否使用视频的代码,该代码在CallScreen中!~
//是否开启视频流程关键地方 必须是在 电话中并且端口等设置正确 if (Receiver.call_state == UserAgent.UA_STATE_INCALL && socket == null && Receiver.engine(mContext).getLocalVideo() != 0 && Receiver.engine(mContext).getRemoteVideo() != 0 && PreferenceManager .getDefaultSharedPreferences(this) .getString(org.sipdroid.sipua.ui.Settings.PREF_SERVER, org.sipdroid.sipua.ui.Settings.DEFAULT_SERVER) .equals(org.sipdroid.sipua.ui.Settings.DEFAULT_SERVER)) { (new Thread() { public void run() { // 视频包 在线包 RtpPacket keepalive = new RtpPacket(new byte[12], 0); RtpPacket videopacket = new RtpPacket(new byte[1000], 0); try { if (intent == null || rtp_socket == null) { // 新建一个套接字 rtp_socket = new RtpSocket( // 初始化套接字 socket = new SipdroidSocket(Receiver .engine(mContext).getLocalVideo()), // 设置网络地址 InetAddress.getByName(Receiver.engine( mContext).getRemoteAddr()), // 接受远程视频 Receiver.engine(mContext).getRemoteVideo()); sleep(3000); } else { // 接受数据 socket = rtp_socket.getDatagramSocket(); } // 接受数据 rtp_socket.getDatagramSocket().setSoTimeout(15000); } catch (Exception e) { if (!Sipdroid.release) { e.printStackTrace(); } return; } // 设置有效载荷类型 keepalive.setPayloadType(126); try { // 发送数据 rtp_socket.send(keepalive); } catch (Exception e1) { return; } for (;;) { try { // 循环接收数据 rtp_socket.receive(videopacket); } catch (IOException e) { // 异常则断开 rtp_socket.getDatagramSocket().disconnect(); try { // 发送在线包 rtp_socket.send(keepalive); } catch (IOException e1) { return; } } // 得到有效负荷长度 if (videopacket.getPayloadLength() > 200) { if (intent != null) { // 发送数据 intent.putExtra("justplay", true); mHandler.sendEmptyMessage(0); } else { // 否则播放 Intent i = new Intent(mContext, org.sipdroid.sipua.ui.VideoCamera.class); i.putExtra("justplay", true); startActivity(i); } return; } } } }).start(); }
yate服务端第一次记录了我的rport为1849,从图中发现它是与服务器通信的端口!~服务器中也把它当做端口记录!~而SipUA客户端是使用了UDP套接字自定义了一个37850端口,这个端口一直到退出才改变,而yate服务端第一次记录了我的rport为2251,也就是说服务器记录的rport的端口是一直在发生改变的!~所以下次用户拨打对方,向服务器索取对方信息时出错,就有可能会直接挂掉!~
rport在Sip中的定义是rport方式主要是对sip信令中Via字头的扩展,不过同时也要求SIP Proxy支持该功能。NAT之后的sip client在发送请求的时候在via字头中添加rport字段,该消息经发出后路由到SIP Proxy,SIP Proxy通过检查消息的源地址和Via字段中的地址,得知该client处于NAT之后,并且基于已有的rport,将消息的真实地址即公网上的地址通过received和rport字段返回给client端,这样client就知道自己真实的公网地址,可以解决信令穿越的问题。
而有网友提出,使用Android模拟器通过路由器时端口会发生变化!~ 不知道这是不是真的!
它又是如何处理登陆超时以及通话出错的?
这就涉及到封装处理Sip协议的类了!~涉及的类不多,感兴趣的童鞋就自己研究了,我累了!~
有些问题需要讨论!~~欢迎高手指点!~