再谈SipDroid

研究了SipDroid2.7,自己对它的理解也渐渐的清晰了。

那它是怎样实现电话拨打以及电话监听的?它的音频接收以及发送是怎么实现的?它的视频又是怎么一回事?它在模拟器上的端口为什么总是变化的?它又是如何处理登陆超时以及通话出错的?

带着这些疑问进入它的代码思想境界!

使用yate搭配服务器,然后使用了一个yate与SipDroid客户端进行通话!~至于怎么搭配服务器以及SipDroid的配置设置,此处就不讨论了!~ 

登陆后的标识效果如图:


然后我使用了yate客户端程序拨打了SipDroid程序段上的帐号(banketree),如图:

再谈SipDroid_第1张图片

再谈SipDroid_第2张图片

接通后的效果图像如图:

再谈SipDroid_第3张图片

好了,进入我们的主题了!~

它是怎样实现电话拨打以及电话监听的?

程序进入时会进行服务注册,如下:

		// 实例化一个引擎 注册模式    由登陆时进行……
	//	Receiver.engine(this).registerMore();

我们知道Receiver.engine(this) 进行了SipUA引擎的实例化,并开启SipUA引擎,关键就SipUA引擎了!~

	// 构造引擎
	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();

至于里面是怎么实现的,那就涉及到了SipUA封装的一些类以及消息了!~ 

消息以及协议的通信都是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;
	}

消息的信息格式如下:

再谈SipDroid_第4张图片

……

回到问题中来,监听电话已经明白了,那是如何实现拨打的?

当用户登陆后就会把自己的信息发送给服务端,服务端记录下来,等用户需要时就返回给他,比如我打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();
		}

之后就进入到涉及视频的类VideoCamera中了!~
它在模拟器上的端口为什么总是变化的?
抓包分析下,如图:

再谈SipDroid_第5张图片

再谈SipDroid_第6张图片

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协议的类了!~涉及的类不多,感兴趣的童鞋就自己研究了,我累了!~

有些问题需要讨论!~~欢迎高手指点!~




你可能感兴趣的:(再谈SipDroid)