研究了SipDroid2.7,自己对它的理解也渐渐的清晰了。
那它是怎样实现电话拨打以及电话监听的?它的音频接收以及发送是怎么实现的?它的视频又是怎么一回事?它在模拟器上的端口为什么总是变化的?它又是如何处理登陆超时以及通话出错的?
带着这些疑问进入它的代码思想境界!
使用yate搭配服务器,然后使用了一个yate与SipDroid客户端进行通话!~至于怎么搭配服务器以及SipDroid的配置设置,此处就不讨论了!~
登陆后的标识效果如图:
然后我使用了yate客户端程序拨打了SipDroid程序段上的帐号(banketree),如图:
接通后的效果图像如图:
好了,进入我们的主题了!~
它是怎样实现电话拨打以及电话监听的?
程序进入时会进行服务注册,如下:
// 实例化一个引擎 注册模式 由登陆时进行……
// 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;
}
消息的信息格式如下:
……
回到问题中来,监听电话已经明白了,那是如何实现拨打的?
当用户登陆后就会把自己的信息发送给服务端,服务端记录下来,等用户需要时就返回给他,比如我打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:anonymous@anonymous.com";
}
// 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中了!~
它在模拟器上的端口为什么总是变化的?
抓包分析下,如图:
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协议的类了!~涉及的类不多,感兴趣的童鞋就自己研究了,我累了!~
有些问题需要讨论!~~欢迎高手指点!~