研究了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:[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,另一個是RtpStreamReceiver!~
看標題就知道它們一個是發送的,一個是接收的!~
先來看下接收是怎麼實現的!~
- 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協議的類了!~涉及的類不多,感興趣的童鞋就自己研究了,我累了!~
有些問題需要討論!~~歡迎高手指點!~