1 /** |
|
2 * Java RTP Library (jlibrtp) |
|
3 * Copyright (C) 2006 Arne Kepp |
|
4 * |
|
5 * This library is free software; you can redistribute it and/or |
|
6 * modify it under the terms of the GNU Lesser General Public |
|
7 * License as published by the Free Software Foundation; either |
|
8 * version 2.1 of the License, or (at your option) any later version. |
|
9 * |
|
10 * This library is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 * Lesser General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU Lesser General Public |
|
16 * License along with this library; if not, write to the Free Software |
|
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
18 */ |
|
19 package jlibrtp; |
|
20 |
|
21 import java.net.DatagramSocket; |
|
22 import java.net.MulticastSocket; |
|
23 import java.net.DatagramPacket; |
|
24 import java.net.InetAddress; |
|
25 import java.net.InetSocketAddress; |
|
26 import java.util.Iterator; |
|
27 import java.util.concurrent.locks.*; |
|
28 import java.util.Random; |
|
29 import java.util.Enumeration; |
|
30 /** |
|
31 * The RTPSession object is the core of jlibrtp. |
|
32 * |
|
33 * One should be instantiated for every communication channel, i.e. if you send voice and video, you should create one for each. |
|
34 * |
|
35 * The instance holds a participant database, as well as other information about the session. When the application registers with the session, the necessary threads for receiving and processing RTP packets are spawned. |
|
36 * |
|
37 * RTP Packets are sent synchronously, all other operations are asynchronous. |
|
38 * |
|
39 * @author Arne Kepp |
|
40 */ |
|
41 public class RTPSession { |
|
42 /** |
|
43 * The debug level is final to avoid compilation of if-statements.</br> |
|
44 * 0 provides no debugging information, 20 provides everything </br> |
|
45 * Debug output is written to System.out</br> |
|
46 * Debug level for RTP related things. |
|
47 */ |
|
48 final static public int rtpDebugLevel = 0; |
|
49 /** |
|
50 * The debug level is final to avoid compilation of if-statements.</br> |
|
51 * 0 provides no debugging information, 20 provides everything </br> |
|
52 * Debug output is written to System.out</br> |
|
53 * Debug level for RTCP related things. |
|
54 */ |
|
55 final static public int rtcpDebugLevel = 0; |
|
56 |
|
57 /** RTP unicast socket */ |
|
58 protected DatagramSocket rtpSock = null; |
|
59 /** RTP multicast socket */ |
|
60 protected MulticastSocket rtpMCSock = null; |
|
61 /** RTP multicast group */ |
|
62 protected InetAddress mcGroup = null; |
|
63 |
|
64 // Internal state |
|
65 /** Whether this session is a multicast session or not */ |
|
66 protected boolean mcSession = false; |
|
67 /** Current payload type, can be changed by application */ |
|
68 protected int payloadType = 0; |
|
69 /** SSRC of this session */ |
|
70 protected long ssrc; |
|
71 /** The last timestamp when we sent something */ |
|
72 protected long lastTimestamp = 0; |
|
73 /** Current sequence number */ |
|
74 protected int seqNum = 0; |
|
75 /** Number of packets sent by this session */ |
|
76 protected int sentPktCount = 0; |
|
77 /** Number of octets sent by this session */ |
|
78 protected int sentOctetCount = 0; |
|
79 |
|
80 /** The random seed */ |
|
81 protected Random random = null; |
|
82 |
|
83 /** Session bandwidth in BYTES per second */ |
|
84 protected int bandwidth = 8000; |
|
85 |
|
86 /** By default we do not return packets from strangers in unicast mode */ |
|
87 protected boolean naiveReception = false; |
|
88 |
|
89 /** Should the library attempt frame reconstruction? */ |
|
90 protected boolean frameReconstruction = true; |
|
91 |
|
92 /** Maximum number of packets used for reordering */ |
|
93 protected int pktBufBehavior = 3; |
|
94 |
|
95 /** Participant database */ |
|
96 protected ParticipantDatabase partDb = new ParticipantDatabase(this); |
|
97 /** Handle to application interface for RTP */ |
|
98 protected RTPAppIntf appIntf = null; |
|
99 /** Handle to application interface for RTCP (optional) */ |
|
100 protected RTCPAppIntf rtcpAppIntf = null; |
|
101 /** Handle to application interface for AVPF, RFC 4585 (optional) */ |
|
102 protected RTCPAVPFIntf rtcpAVPFIntf = null; |
|
103 /** Handle to application interface for debugging */ |
|
104 protected DebugAppIntf debugAppIntf = null; |
|
105 |
|
106 /** The RTCP session associated with this RTP Session */ |
|
107 protected RTCPSession rtcpSession = null; |
|
108 /** The thread for receiving RTP packets */ |
|
109 protected RTPReceiverThread recvThrd = null; |
|
110 /** The thread for invoking callbacks for RTP packets */ |
|
111 protected AppCallerThread appCallerThrd = null; |
|
112 |
|
113 /** Lock to protect the packet buffers */ |
|
114 final protected Lock pktBufLock = new ReentrantLock(); |
|
115 /** Condition variable, to tell the */ |
|
116 final protected Condition pktBufDataReady = pktBufLock.newCondition(); |
|
117 |
|
118 /** Enough is enough, set to true when you want to quit. */ |
|
119 protected boolean endSession = false; |
|
120 /** Only one registered application, please */ |
|
121 protected boolean registered = false; |
|
122 /** We're busy resolving a SSRC conflict, please try again later */ |
|
123 protected boolean conflict = false; |
|
124 /** Number of conflicts observed, exessive number suggests loop in network */ |
|
125 protected int conflictCount = 0; |
|
126 |
|
127 /** SDES CNAME */ |
|
128 protected String cname = null; |
|
129 /** SDES The participant's real name */ |
|
130 public String name = null; |
|
131 /** SDES The participant's email */ |
|
132 public String email = null; |
|
133 /** SDES The participant's phone number */ |
|
134 public String phone = null; |
|
135 /** SDES The participant's location*/ |
|
136 public String loc = null; |
|
137 /** SDES The tool the participants is using */ |
|
138 public String tool = null; |
|
139 /** SDES A note */ |
|
140 public String note = null; |
|
141 /** SDES A priv string, loosely defined */ |
|
142 public String priv = null; |
|
143 |
|
144 // RFC 4585 stuff. This should live on RTCPSession, but we need to have this |
|
145 // infromation ready by the time the RTCP Session starts |
|
146 // 0 = RFC 3550 , -1 = ACK , 1 = Immediate feedback, 2 = Early RTCP, |
|
147 protected int rtcpMode = 0; |
|
148 protected int fbEarlyThreshold = -1; // group size, immediate -> early transition point |
|
149 protected int fbRegularThreshold = -1; // group size, early -> regular transition point |
|
150 protected int minInterval = 5000; // minimum interval |
|
151 protected int fbMaxDelay = 1000; // how long the information is useful |
|
152 // RTCP bandwidth |
|
153 protected int rtcpBandwidth = -1; |
|
154 |
|
155 |
|
156 /** |
|
157 * Returns an instance of a <b>unicast</b> RTP session. |
|
158 * Following this you should adjust any settings and then register your application. |
|
159 * |
|
160 * The sockets should have external ip addresses, else your CNAME automatically |
|
161 * generated CNAMe will be bad. |
|
162 * |
|
163 * @param rtpSocket UDP socket to receive RTP communication on |
|
164 * @param rtcpSocket UDP socket to receive RTCP communication on, null if none. |
|
165 */ |
|
166 public RTPSession(DatagramSocket rtpSocket, DatagramSocket rtcpSocket) { |
|
167 mcSession = false; |
|
168 rtpSock = rtpSocket; |
|
169 this.generateCNAME(); |
|
170 this.generateSsrc(); |
|
171 this.rtcpSession = new RTCPSession(this,rtcpSocket); |
|
172 |
|
173 // The sockets are not always imediately available? |
|
174 try { Thread.sleep(1); } catch (InterruptedException e) { System.out.println("RTPSession sleep failed"); } |
|
175 } |
|
176 |
|
177 /** |
|
178 * Returns an instance of a <b>multicast</b> RTP session. |
|
179 * Following this you should register your application. |
|
180 * |
|
181 * The sockets should have external ip addresses, else your CNAME automatically |
|
182 * generated CNAMe will be bad. |
|
183 * |
|
184 * @param rtpSock a multicast socket to receive RTP communication on |
|
185 * @param rtcpSock a multicast socket to receive RTP communication on |
|
186 * @param multicastGroup the multicast group that we want to communicate with. |
|
187 */ |
|
188 public RTPSession(MulticastSocket rtpSock, MulticastSocket rtcpSock, InetAddress multicastGroup) throws Exception { |
|
189 mcSession = true; |
|
190 rtpMCSock =rtpSock; |
|
191 mcGroup = multicastGroup; |
|
192 rtpMCSock.joinGroup(mcGroup); |
|
193 rtcpSock.joinGroup(mcGroup); |
|
194 this.generateCNAME(); |
|
195 this.generateSsrc(); |
|
196 this.rtcpSession = new RTCPSession(this,rtcpSock,mcGroup); |
|
197 |
|
198 // The sockets are not always imediately available? |
|
199 try { Thread.sleep(1); } catch (InterruptedException e) { System.out.println("RTPSession sleep failed"); } |
|
200 } |
|
201 |
|
202 /** |
|
203 * Registers an application (RTPAppIntf) with the RTP session. |
|
204 * The session will call receiveData() on the supplied instance whenever data has been received. |
|
205 * |
|
206 * Following this you should set the payload type and add participants to the session. |
|
207 * |
|
208 * @param rtpApp an object that implements the RTPAppIntf-interface |
|
209 * @param rtcpApp an object that implements the RTCPAppIntf-interface (optional) |
|
210 * @return -1 if this RTPSession-instance already has an application registered. |
|
211 */ |
|
212 public int RTPSessionRegister(RTPAppIntf rtpApp, RTCPAppIntf rtcpApp, DebugAppIntf debugApp) { |
|
213 if(registered) { |
|
214 System.out.println("RTPSessionRegister(): Can\'t register another application!"); |
|
215 return -1; |
|
216 } else { |
|
217 registered = true; |
|
218 generateSeqNum(); |
|
219 if(RTPSession.rtpDebugLevel > 0) { |
|
220 System.out.println("-> RTPSessionRegister"); |
|
221 } |
|
222 this.appIntf = rtpApp; |
|
223 this.rtcpAppIntf = rtcpApp; |
|
224 this.debugAppIntf = debugApp; |
|
225 |
|
226 recvThrd = new RTPReceiverThread(this); |
|
227 appCallerThrd = new AppCallerThread(this, rtpApp); |
|
228 recvThrd.start(); |
|
229 appCallerThrd.start(); |
|
230 rtcpSession.start(); |
|
231 return 0; |
|
232 } |
|
233 } |
|
234 |
|
235 /** |
|
236 * Send data to all participants registered as receivers, using the current timeStamp, |
|
237 * dynamic sequence number and the current payload type specified for the session. |
|
238 * |
|
239 * @param buf A buffer of bytes, less than 1496 bytes |
|
240 * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise |
|
241 */ |
|
242 public long[] sendData(byte[] buf) { |
|
243 byte[][] tmp = {buf}; |
|
244 long[][] ret = this.sendData(tmp, null, null, -1, null); |
|
245 |
|
246 if(ret != null) |
|
247 return ret[0]; |
|
248 |
|
249 return null; |
|
250 } |
|
251 |
|
252 /** |
|
253 * Send data to all participants registered as receivers, using the specified timeStamp, |
|
254 * sequence number and the current payload type specified for the session. |
|
255 * |
|
256 * @param buf A buffer of bytes, less than 1496 bytes |
|
257 * @param rtpTimestamp the RTP timestamp to be used in the packet |
|
258 * @param seqNum the sequence number to be used in the packet |
|
259 * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise |
|
260 */ |
|
261 public long[] sendData(byte[] buf, long rtpTimestamp, long seqNum) { |
|
262 byte[][] tmp = {buf}; |
|
263 long[][] ret = this.sendData(tmp, null, null, -1, null); |
|
264 |
|
265 if(ret != null) |
|
266 return ret[0]; |
|
267 |
|
268 return null; |
|
269 } |
|
270 |
|
271 /** |
|
272 * Send data to all participants registered as receivers, using the current timeStamp and |
|
273 * payload type. The RTP timestamp will be the same for all the packets. |
|
274 * |
|
275 * @param buffers A buffer of bytes, should not bed padded and less than 1500 bytes on most networks. |
|
276 * @param csrcArray an array with the SSRCs of contributing sources |
|
277 * @param markers An array indicating what packets should be marked. Rarely anything but the first one |
|
278 * @param rtpTimestamp The RTP timestamp to be applied to all packets |
|
279 * @param seqNumbers An array with the sequence number associated with each byte[] |
|
280 * @return null if there was a problem sending the packets, 2-dim array with {RTP Timestamp, Sequence number} |
|
281 */ |
|
282 public long[][] sendData(byte[][] buffers, long[] csrcArray, boolean[] markers, long rtpTimestamp, long[] seqNumbers) { |
|
283 if(RTPSession.rtpDebugLevel > 5) { |
|
284 System.out.println("-> RTPSession.sendData(byte[])"); |
|
285 } |
|
286 |
|
287 // Same RTP timestamp for all |
|
288 if(rtpTimestamp < 0) |
|
289 rtpTimestamp = System.currentTimeMillis(); |
|
290 |
|
291 // Return values |
|
292 long[][] ret = new long[buffers.length][2]; |
|
293 |
|
294 for(int i=0; i<buffers.length; i++) { |
|
295 byte[] buf = buffers[i]; |
|
296 |
|
297 boolean marker = false; |
|
298 if(markers != null) |
|
299 marker = markers[i]; |
|
300 |
|
301 if(buf.length > 1500) { |
|
302 System.out.println("RTPSession.sendData() called with buffer exceeding 1500 bytes ("+buf.length+")"); |
|
303 } |
|
304 |
|
305 // Get the return values |
|
306 ret[i][0] = rtpTimestamp; |
|
307 if(seqNumbers == null) { |
|
308 ret[i][1] = getNextSeqNum(); |
|
309 } else { |
|
310 ret[i][1] = seqNumbers[i]; |
|
311 } |
|
312 // Create a new RTP Packet |
|
313 RtpPkt pkt = new RtpPkt(rtpTimestamp,this.ssrc,(int) ret[i][1],this.payloadType,buf); |
|
314 |
|
315 if(csrcArray != null) |
|
316 pkt.setCsrcs(csrcArray); |
|
317 |
|
318 pkt.setMarked(marker); |
|
319 |
|
320 // Creates a raw packet |
|
321 byte[] pktBytes = pkt.encode(); |
|
322 |
|
323 //System.out.println(Integer.toString(StaticProcs.bytesToUIntInt(pktBytes, 2))); |
|
324 |
|
325 // Pre-flight check, are resolving an SSRC conflict? |
|
326 if(this.conflict) { |
|
327 System.out.println("RTPSession.sendData() called while trying to resolve conflict."); |
|
328 return null; |
|
329 } |
|
330 |
|
331 |
|
332 if(this.mcSession) { |
|
333 DatagramPacket packet = null; |
|
334 |
|
335 |
|
336 try { |
|
337 packet = new DatagramPacket(pktBytes,pktBytes.length,this.mcGroup,this.rtpMCSock.getPort()); |
|
338 } catch (Exception e) { |
|
339 System.out.println("RTPSession.sendData() packet creation failed."); |
|
340 e.printStackTrace(); |
|
341 return null; |
|
342 } |
|
343 |
|
344 try { |
|
345 rtpMCSock.send(packet); |
|
346 //Debug |
|
347 if(this.debugAppIntf != null) { |
|
348 this.debugAppIntf.packetSent(1, (InetSocketAddress) packet.getSocketAddress(), |
|
349 new String("Sent multicast RTP packet of size " + packet.getLength() + |
|
350 " to " + packet.getSocketAddress().toString() + " via " |
|
351 + rtpMCSock.getLocalSocketAddress().toString())); |
|
352 } |
|
353 } catch (Exception e) { |
|
354 System.out.println("RTPSession.sendData() multicast failed."); |
|
355 e.printStackTrace(); |
|
356 return null; |
|
357 } |
|
358 |
|
359 } else { |
|
360 // Loop over recipients |
|
361 Iterator<Participant> iter = partDb.getUnicastReceivers(); |
|
362 while(iter.hasNext()) { |
|
363 InetSocketAddress receiver = iter.next().rtpAddress; |
|
364 DatagramPacket packet = null; |
|
365 |
|
366 if(RTPSession.rtpDebugLevel > 15) { |
|
367 System.out.println(" Sending to " + receiver.toString()); |
|
368 } |
|
369 |
|
370 try { |
|
371 packet = new DatagramPacket(pktBytes,pktBytes.length,receiver); |
|
372 } catch (Exception e) { |
|
373 System.out.println("RTPSession.sendData() packet creation failed."); |
|
374 e.printStackTrace(); |
|
375 return null; |
|
376 } |
|
377 |
|
378 //Actually send the packet |
|
379 try { |
|
380 rtpSock.send(packet); |
|
381 //Debug |
|
382 if(this.debugAppIntf != null) { |
|
383 this.debugAppIntf.packetSent(0, (InetSocketAddress) packet.getSocketAddress(), |
|
384 new String("Sent unicast RTP packet of size " + packet.getLength() + |
|
385 " to " + packet.getSocketAddress().toString() + " via " |
|
386 + rtpSock.getLocalSocketAddress().toString())); |
|
387 } |
|
388 } catch (Exception e) { |
|
389 System.out.println("RTPSession.sendData() unicast failed."); |
|
390 e.printStackTrace(); |
|
391 return null; |
|
392 } |
|
393 } |
|
394 } |
|
395 |
|
396 //Update our stats |
|
397 this.sentPktCount++; |
|
398 this.sentOctetCount++; |
|
399 |
|
400 if(RTPSession.rtpDebugLevel > 5) { |
|
401 System.out.println("<- RTPSession.sendData(byte[]) " + pkt.getSeqNumber()); |
|
402 } |
|
403 } |
|
404 |
|
405 return ret; |
|
406 } |
|
407 |
|
408 /** |
|
409 * Send RTCP App packet to receiver specified by ssrc |
|
410 * |
|
411 * |
|
412 * |
|
413 * Return values: |
|
414 * 0 okay |
|
415 * -1 no RTCP session established |
|
416 * -2 name is not byte[4]; |
|
417 * -3 data is not byte[x], where x = 4*y for syme y |
|
418 * -4 type is not a 5 bit unsigned integer |
|
419 * |
|
420 * Note that a return value of 0 does not guarantee delivery. |
|
421 * The participant must also exist in the participant database, |
|
422 * otherwise the message will eventually be deleted. |
|
423 * |
|
424 * @param ssrc of the participant you want to reach |
|
425 * @param type the RTCP App packet subtype, default 0 |
|
426 * @param name the ASCII (in byte[4]) representation |
|
427 * @param data the data itself |
|
428 * @return 0 if okay, negative value otherwise (see above) |
|
429 */ |
|
430 |
|
431 public int sendRTCPAppPacket(long ssrc, int type, byte[] name, byte[] data) { |
|
432 if(this.rtcpSession == null) |
|
433 return -1; |
|
434 |
|
435 if(name.length != 4) |
|
436 return -2; |
|
437 |
|
438 if(data.length % 4 != 0) |
|
439 return -3; |
|
440 |
|
441 if(type > 63 || type < 0 ) |
|
442 return -4; |
|
443 |
|
444 RtcpPktAPP pkt = new RtcpPktAPP(ssrc, type, name, data); |
|
445 this.rtcpSession.addToAppQueue(ssrc, pkt); |
|
446 |
|
447 return 0; |
|
448 } |
|
449 /** |
|
450 * Add a participant object to the participant database. |
|
451 * |
|
452 * If packets have already been received from this user, we will try to update the automatically inserted participant with the information provided here. |
|
453 * |
|
454 * @param p A participant. |
|
455 */ |
|
456 public int addParticipant(Participant p) { |
|
457 //For now we make all participants added this way persistent |
|
458 p.unexpected = false; |
|
459 return this.partDb.addParticipant(0, p); |
|
460 } |
|
461 |
|
462 /** |
|
463 * Remove a participant from the database. All buffered packets will be destroyed. |
|
464 * |
|
465 * @param p A participant. |
|
466 */ |
|
467 public void removeParticipant(Participant p) { |
|
468 partDb.removeParticipant(p); |
|
469 } |
|
470 |
|
471 public Iterator<Participant> getUnicastReceivers() { |
|
472 return partDb.getUnicastReceivers(); |
|
473 } |
|
474 |
|
475 public Enumeration<Participant> getParticipants() { |
|
476 return partDb.getParticipants(); |
|
477 } |
|
478 |
|
479 /** |
|
480 * End the RTP Session. This will halt all threads and send bye-messages to other participants. |
|
481 * |
|
482 * RTCP related threads may require several seconds to wake up and terminate. |
|
483 */ |
|
484 public void endSession() { |
|
485 this.endSession = true; |
|
486 |
|
487 // No more RTP packets, please |
|
488 if(this.mcSession) { |
|
489 this.rtpMCSock.close(); |
|
490 } else { |
|
491 this.rtpSock.close(); |
|
492 } |
|
493 |
|
494 // Signal the thread that pushes data to application |
|
495 this.pktBufLock.lock(); |
|
496 try { this.pktBufDataReady.signalAll(); } finally { |
|
497 this.pktBufLock.unlock(); |
|
498 } |
|
499 // Interrupt what may be sleeping |
|
500 this.rtcpSession.senderThrd.interrupt(); |
|
501 |
|
502 // Give things a chance to cool down. |
|
503 try { Thread.sleep(50); } catch (Exception e){ }; |
|
504 |
|
505 this.appCallerThrd.interrupt(); |
|
506 |
|
507 // Give things a chance to cool down. |
|
508 try { Thread.sleep(50); } catch (Exception e){ }; |
|
509 |
|
510 if(this.rtcpSession != null) { |
|
511 // No more RTP packets, please |
|
512 if(this.mcSession) { |
|
513 this.rtcpSession.rtcpMCSock.close(); |
|
514 } else { |
|
515 this.rtcpSession.rtcpSock.close(); |
|
516 } |
|
517 } |
|
518 } |
|
519 |
|
520 |
|
521 /** |
|
522 * Check whether this session is ending. |
|
523 * |
|
524 * @return true if session and associated threads are terminating. |
|
525 */ |
|
526 boolean isEnding() { |
|
527 return this.endSession; |
|
528 } |
|
529 |
|
530 /** |
|
531 * Overrides CNAME, used for outgoing RTCP packets. |
|
532 * |
|
533 * @param cname a string, e.g. username@hostname. Must be unique for session. |
|
534 */ |
|
535 public void CNAME(String cname) { |
|
536 this.cname = cname; |
|
537 } |
|
538 |
|
539 /** |
|
540 * Get the current CNAME, used for outgoing SDES packets |
|
541 */ |
|
542 public String CNAME() { |
|
543 return this.cname; |
|
544 } |
|
545 |
|
546 public long getSsrc() { |
|
547 return this.ssrc; |
|
548 } |
|
549 |
|
550 private void generateCNAME() { |
|
551 String hostname; |
|
552 |
|
553 if(this.mcSession) { |
|
554 hostname = this.rtpMCSock.getLocalAddress().getCanonicalHostName(); |
|
555 } else { |
|
556 hostname = this.rtpSock.getLocalAddress().getCanonicalHostName(); |
|
557 } |
|
558 |
|
559 //if(hostname.equals("0.0.0.0") && System.getenv("HOSTNAME") != null) { |
|
560 // hostname = System.getenv("HOSTNAME"); |
|
561 //} |
|
562 |
|
563 cname = System.getProperty("user.name") + "@" + hostname; |
|
564 } |
|
565 |
|
566 /** |
|
567 * Change the RTP socket of the session. |
|
568 * Peers must be notified through SIP or other signalling protocol. |
|
569 * Only valid if this is a unicast session to begin with. |
|
570 * |
|
571 * @param newSock integer for new port number, check it is free first. |
|
572 */ |
|
573 public int updateRTPSock(DatagramSocket newSock) { |
|
574 if(!mcSession) { |
|
575 rtpSock = newSock; |
|
576 return 0; |
|
577 } else { |
|
578 System.out.println("Can't switch from multicast to unicast."); |
|
579 return -1; |
|
580 } |
|
581 } |
|
582 |
|
583 /** |
|
584 * Change the RTCP socket of the session. |
|
585 * Peers must be notified through SIP or other signalling protocol. |
|
586 * Only valid if this is a unicast session to begin with. |
|
587 * |
|
588 * @param newSock the new unicast socket for RTP communication. |
|
589 */ |
|
590 public int updateRTCPSock(DatagramSocket newSock) { |
|
591 if(!mcSession) { |
|
592 this.rtcpSession.rtcpSock = newSock; |
|
593 return 0; |
|
594 } else { |
|
595 System.out.println("Can't switch from multicast to unicast."); |
|
596 return -1; |
|
597 } |
|
598 } |
|
599 |
|
600 /** |
|
601 * Change the RTP multicast socket of the session. |
|
602 * Peers must be notified through SIP or other signalling protocol. |
|
603 * Only valid if this is a multicast session to begin with. |
|
604 * |
|
605 * @param newSock the new multicast socket for RTP communication. |
|
606 */ |
|
607 public int updateRTPSock(MulticastSocket newSock) { |
|
608 if(mcSession) { |
|
609 this.rtpMCSock = newSock; |
|
610 return 0; |
|
611 } else { |
|
612 System.out.println("Can't switch from unicast to multicast."); |
|
613 return -1; |
|
614 } |
|
615 } |
|
616 |
|
617 /** |
|
618 * Change the RTCP multicast socket of the session. |
|
619 * Peers must be notified through SIP or other signalling protocol. |
|
620 * Only valid if this is a multicast session to begin with. |
|
621 * |
|
622 * @param newSock the new multicast socket for RTCP communication. |
|
623 */ |
|
624 public int updateRTCPSock(MulticastSocket newSock) { |
|
625 if(mcSession) { |
|
626 this.rtcpSession.rtcpMCSock = newSock; |
|
627 return 0; |
|
628 } else { |
|
629 System.out.println("Can't switch from unicast to multicast."); |
|
630 return -1; |
|
631 } |
|
632 } |
|
633 |
|
634 /** |
|
635 * Update the payload type used for the session. It is represented as a 7 bit integer, whose meaning must be negotiated elsewhere (see IETF RFCs <a href="http://www.ietf.org/rfc/rfc3550.txt">3550</a> and <a href="http://www.ietf.org/rfc/rfc3550.txt">3551</a>) |
|
636 * |
|
637 * @param payloadT an integer representing the payload type of any subsequent packets that are sent. |
|
638 */ |
|
639 public int payloadType(int payloadT) { |
|
640 if(payloadT > 128 || payloadT < 0) { |
|
641 return -1; |
|
642 } else { |
|
643 this.payloadType = payloadT; |
|
644 return this.payloadType; |
|
645 } |
|
646 } |
|
647 |
|
648 /** |
|
649 * Get the payload type that is currently used for outgoing RTP packets. |
|
650 * |
|
651 * @return payload type as integer |
|
652 */ |
|
653 public int payloadType() { |
|
654 return this.payloadType; |
|
655 } |
|
656 |
|
657 /** |
|
658 * Should packets from unknown participants be returned to the application? This can be dangerous. |
|
659 * |
|
660 * @param doAccept packets from participants not added by the application. |
|
661 */ |
|
662 public void naivePktReception(boolean doAccept) { |
|
663 naiveReception = doAccept; |
|
664 } |
|
665 |
|
666 /** |
|
667 * Are packets from unknown participants returned to the application? |
|
668 * |
|
669 * @return whether we accept packets from participants not added by the application. |
|
670 */ |
|
671 public boolean naivePktReception() { |
|
672 return naiveReception; |
|
673 } |
|
674 |
|
675 /** |
|
676 * Set the number of RTP packets that should be buffered when a packet is |
|
677 * missing or received out of order. Setting this number high increases |
|
678 * the chance of correctly reordering packets, but increases latency when |
|
679 * a packet is dropped by the network. |
|
680 * |
|
681 * Packets that arrive in order are not affected, they are passed straight |
|
682 * to the application. |
|
683 * |
|
684 * The maximum delay is numberofPackets * packet rate , where the packet rate |
|
685 * depends on the codec and profile used by the sender. |
|
686 * |
|
687 * Valid values: |
|
688 * >0 - The maximum number of packets (based on RTP Timestamp) that may accumulate |
|
689 * 0 - All valid packets received in order will be given to the application |
|
690 * -1 - All valid packets will be given to the application |
|
691 * |
|
692 * @param behavior the be |
|
693 * @return the behavior set, unchanged in the case of a erroneous value |
|
694 */ |
|
695 public int packetBufferBehavior(int behavior) { |
|
696 if(behavior > -2) { |
|
697 this.pktBufBehavior = behavior; |
|
698 // Signal the thread that pushes data to application |
|
699 this.pktBufLock.lock(); |
|
700 try { this.pktBufDataReady.signalAll(); } finally { |
|
701 this.pktBufLock.unlock(); |
|
702 } |
|
703 return this.pktBufBehavior; |
|
704 } else { |
|
705 return this.pktBufBehavior; |
|
706 } |
|
707 } |
|
708 |
|
709 /** |
|
710 * The number of RTP packets that should be buffered when a packet is |
|
711 * missing or received out of order. A high number increases the chance |
|
712 * of correctly reordering packets, but increases latency when a packet is |
|
713 * dropped by the network. |
|
714 * |
|
715 * A negative value disables the buffering, out of order packets will simply be dropped. |
|
716 * |
|
717 * @return the maximum number of packets that can accumulate before the first is returned |
|
718 */ |
|
719 public int packetBufferBehavior() { |
|
720 return this.pktBufBehavior; |
|
721 } |
|
722 |
|
723 /** |
|
724 * Set whether the stack should operate in RFC 4585 mode. |
|
725 * |
|
726 * This will automatically call adjustPacketBufferBehavior(-1), |
|
727 * i.e. disable all RTP packet buffering in jlibrtp, |
|
728 * and disable frame reconstruction |
|
729 * |
|
730 * @param rtcpAVPFIntf the in |
|
731 */ |
|
732 public int registerAVPFIntf(RTCPAVPFIntf rtcpAVPFIntf, int maxDelay, int earlyThreshold, int regularThreshold ) { |
|
733 if(this.rtcpSession != null) { |
|
734 this.packetBufferBehavior(-1); |
|
735 this.frameReconstruction = false; |
|
736 this.rtcpAVPFIntf = rtcpAVPFIntf; |
|
737 this.fbEarlyThreshold = earlyThreshold; |
|
738 this.fbRegularThreshold = regularThreshold; |
|
739 return 0; |
|
740 } else { |
|
741 return -1; |
|
742 } |
|
743 } |
|
744 |
|
745 /** |
|
746 * Unregisters the RTCP AVPF interface, thereby going from |
|
747 * RFC 4585 mode to RFC 3550 |
|
748 * |
|
749 * You still have to adjust packetBufferBehavior() and |
|
750 * frameReconstruction. |
|
751 * |
|
752 */ |
|
753 public void unregisterAVPFIntf() { |
|
754 this.fbEarlyThreshold = -1; |
|
755 this.fbRegularThreshold = -1; |
|
756 this.rtcpAVPFIntf = null; |
|
757 } |
|
758 |
|
759 /** |
|
760 * Enable / disable frame reconstruction in the packet buffers. |
|
761 * This is only relevant if getPacketBufferBehavior > 0; |
|
762 * |
|
763 * Default is true. |
|
764 */ |
|
765 public void frameReconstruction(boolean toggle) { |
|
766 this.frameReconstruction = toggle; |
|
767 } |
|
768 |
|
769 /** |
|
770 * Whether the packet buffer will attempt to reconstruct |
|
771 * packet automatically. |
|
772 * |
|
773 * @return the status |
|
774 */ |
|
775 public boolean frameReconstruction() { |
|
776 return this.frameReconstruction; |
|
777 } |
|
778 |
|
779 /** |
|
780 * The bandwidth currently allocated to the session, |
|
781 * in bytes per second. The default is 8000. |
|
782 * |
|
783 * This value is not enforced and currently only |
|
784 * used to calculate the RTCP interval to ensure the |
|
785 * control messages do not exceed 5% of the total bandwidth |
|
786 * described here. |
|
787 * |
|
788 * Since the actual value may change a conservative |
|
789 * estimate should be used to avoid RTCP flooding. |
|
790 * |
|
791 * see rtcpBandwidth(void) |
|
792 * |
|
793 * @return current bandwidth setting |
|
794 */ |
|
795 public int sessionBandwidth() { |
|
796 return this.bandwidth; |
|
797 } |
|
798 |
|
799 /** |
|
800 * Set the bandwidth of the session. |
|
801 * |
|
802 * See sessionBandwidth(void) for details. |
|
803 * |
|
804 * @param bandwidth the new value requested, in bytes per second |
|
805 * @return the actual value set |
|
806 */ |
|
807 public int sessionBandwidth(int bandwidth) { |
|
808 if(bandwidth < 1) { |
|
809 this.bandwidth = 8000; |
|
810 } else { |
|
811 this.bandwidth = bandwidth; |
|
812 } |
|
813 return this.bandwidth; |
|
814 } |
|
815 |
|
816 |
|
817 /** |
|
818 * RFC 3550 dictates that 5% of the total bandwidth, |
|
819 * as set by sessionBandwidth, should be dedicated |
|
820 * to RTCP traffic. This |
|
821 * |
|
822 * This should normally not be done, but is permissible in |
|
823 * conjunction with feedback (RFC 4585) and possibly |
|
824 * other profiles. |
|
825 * |
|
826 * Also see sessionBandwidth(void) |
|
827 * |
|
828 * @return current RTCP bandwidth setting, -1 means not in use |
|
829 */ |
|
830 public int rtcpBandwidth() { |
|
831 return this.rtcpBandwidth; |
|
832 } |
|
833 |
|
834 /** |
|
835 * Set the RTCP bandwidth, see rtcpBandwidth(void) for details. |
|
836 * |
|
837 * This function must be |
|
838 * |
|
839 * @param bandwidth the new value requested, in bytes per second or -1 to disable |
|
840 * @return the actual value set |
|
841 */ |
|
842 public int rtcpBandwidth(int bandwidth) { |
|
843 if(bandwidth < -1) { |
|
844 this.rtcpBandwidth = -1; |
|
845 } else { |
|
846 this.rtcpBandwidth = bandwidth; |
|
847 } |
|
848 return this.rtcpBandwidth; |
|
849 } |
|
850 |
|
851 /********************************************* Feedback message stuff ***************************************/ |
|
852 |
|
853 /** |
|
854 * Adds a Picture Loss Indication to the feedback queue |
|
855 * |
|
856 * @param ssrcMediaSource |
|
857 * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant |
|
858 */ |
|
859 public int fbPictureLossIndication(long ssrcMediaSource) { |
|
860 int ret = 0; |
|
861 |
|
862 if(this.rtcpAVPFIntf == null) |
|
863 return -1; |
|
864 |
|
865 RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); |
|
866 pkt.makePictureLossIndication(); |
|
867 ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); |
|
868 if(ret == 0) |
|
869 this.rtcpSession.wakeSenderThread(ssrcMediaSource); |
|
870 return ret; |
|
871 } |
|
872 |
|
873 /** |
|
874 * Adds a Slice Loss Indication to the feedback queue |
|
875 * |
|
876 * @param ssrcMediaSource |
|
877 * @param sliFirst macroblock (MB) address of the first lost macroblock |
|
878 * @param sliNumber number of lost macroblocks |
|
879 * @param sliPictureId six least significant bits of the codec-specific identif |
|
880 * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant |
|
881 */ |
|
882 public int fbSlicLossIndication(long ssrcMediaSource, int[] sliFirst, int[] sliNumber, int[] sliPictureId) { |
|
883 int ret = 0; |
|
884 if(this.rtcpAVPFIntf == null) |
|
885 return -1; |
|
886 |
|
887 RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); |
|
888 pkt.makeSliceLossIndication(sliFirst, sliNumber, sliPictureId); |
|
889 |
|
890 ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); |
|
891 if(ret == 0) |
|
892 this.rtcpSession.wakeSenderThread(ssrcMediaSource); |
|
893 return ret; |
|
894 } |
|
895 |
|
896 /** |
|
897 * Adds a Reference Picture Selection Indication to the feedback queue |
|
898 * |
|
899 * @param ssrcMediaSource |
|
900 * @param bitPadding number of padded bits at end of bitString |
|
901 * @param payloadType RTP payload type for codec |
|
902 * @param bitString RPSI information as natively defined by the video codec |
|
903 * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant |
|
904 */ |
|
905 public int fbRefPictureSelIndic(long ssrcMediaSource, int bitPadding, int payloadType, byte[] bitString) { |
|
906 int ret = 0; |
|
907 |
|
908 if(this.rtcpAVPFIntf == null) |
|
909 return -1; |
|
910 |
|
911 RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); |
|
912 pkt.makeRefPictureSelIndic(bitPadding, payloadType, bitString); |
|
913 ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); |
|
914 if(ret == 0) |
|
915 this.rtcpSession.wakeSenderThread(ssrcMediaSource); |
|
916 return ret; |
|
917 } |
|
918 |
|
919 /** |
|
920 * Adds a Picture Loss Indication to the feedback queue |
|
921 * |
|
922 * @param ssrcMediaSource |
|
923 * @param bitString the original application message |
|
924 * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant |
|
925 */ |
|
926 public int fbAppLayerFeedback(long ssrcMediaSource, byte[] bitString) { |
|
927 int ret = 0; |
|
928 |
|
929 if(this.rtcpAVPFIntf == null) |
|
930 return -1; |
|
931 |
|
932 RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource); |
|
933 pkt.makeAppLayerFeedback(bitString); |
|
934 ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); |
|
935 if(ret == 0) |
|
936 this.rtcpSession.wakeSenderThread(ssrcMediaSource); |
|
937 return ret; |
|
938 } |
|
939 |
|
940 |
|
941 /** |
|
942 * Adds a RTP Feedback packet to the feedback queue. |
|
943 * |
|
944 * These are mostly used for NACKs. |
|
945 * |
|
946 * @param ssrcMediaSource |
|
947 * @param FMT the Feedback Message Subtype |
|
948 * @param PID RTP sequence numbers of lost packets |
|
949 * @param BLP bitmask of following lost packets, shared index with PID |
|
950 * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant |
|
951 */ |
|
952 public int fbPictureLossIndication(long ssrcMediaSource, int FMT, int[] PID, int[] BLP) { |
|
953 int ret = 0; |
|
954 |
|
955 if(this.rtcpAVPFIntf == null) |
|
956 return -1; |
|
957 |
|
958 RtcpPktRTPFB pkt = new RtcpPktRTPFB(this.ssrc, ssrcMediaSource, FMT, PID, BLP); |
|
959 ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt); |
|
960 if(ret == 0) |
|
961 this.rtcpSession.wakeSenderThread(ssrcMediaSource); |
|
962 return ret; |
|
963 } |
|
964 |
|
965 /** |
|
966 * Fetches the next sequence number for RTP packets. |
|
967 * @return the next sequence number |
|
968 */ |
|
969 private int getNextSeqNum() { |
|
970 seqNum++; |
|
971 // 16 bit number |
|
972 if(seqNum > 65536) { |
|
973 seqNum = 0; |
|
974 } |
|
975 return seqNum; |
|
976 } |
|
977 |
|
978 /** |
|
979 * Initializes a random variable |
|
980 * |
|
981 */ |
|
982 private void createRandom() { |
|
983 this.random = new Random(System.currentTimeMillis() + Thread.currentThread().getId() |
|
984 - Thread.currentThread().hashCode() + this.cname.hashCode()); |
|
985 } |
|
986 |
|
987 |
|
988 /** |
|
989 * Generates a random sequence number |
|
990 */ |
|
991 private void generateSeqNum() { |
|
992 if(this.random == null) |
|
993 createRandom(); |
|
994 |
|
995 seqNum = this.random.nextInt(); |
|
996 if(seqNum < 0) |
|
997 seqNum = -seqNum; |
|
998 while(seqNum > 65535) { |
|
999 seqNum = seqNum / 10; |
|
1000 } |
|
1001 } |
|
1002 |
|
1003 /** |
|
1004 * Generates a random SSRC |
|
1005 */ |
|
1006 private void generateSsrc() { |
|
1007 if(this.random == null) |
|
1008 createRandom(); |
|
1009 |
|
1010 // Set an SSRC |
|
1011 this.ssrc = this.random.nextInt(); |
|
1012 if(this.ssrc < 0) { |
|
1013 this.ssrc = this.ssrc * -1; |
|
1014 } |
|
1015 } |
|
1016 |
|
1017 /** |
|
1018 * Resolve an SSRC conflict. |
|
1019 * |
|
1020 * Also increments the SSRC conflict counter, after 5 conflicts |
|
1021 * it is assumed there is a loop somewhere and the session will |
|
1022 * terminate. |
|
1023 * |
|
1024 */ |
|
1025 protected void resolveSsrcConflict() { |
|
1026 System.out.println("!!!!!!! Beginning SSRC conflict resolution !!!!!!!!!"); |
|
1027 this.conflictCount++; |
|
1028 |
|
1029 if(this.conflictCount < 5) { |
|
1030 //Don't send any more regular packets out until we have this sorted out. |
|
1031 this.conflict = true; |
|
1032 |
|
1033 //Send byes |
|
1034 rtcpSession.sendByes(); |
|
1035 |
|
1036 //Calculate the next delay |
|
1037 rtcpSession.calculateDelay(); |
|
1038 |
|
1039 //Generate a new Ssrc for ourselves |
|
1040 generateSsrc(); |
|
1041 |
|
1042 //Get the SDES packets out faster |
|
1043 rtcpSession.initial = true; |
|
1044 |
|
1045 this.conflict = false; |
|
1046 System.out.println("SSRC conflict resolution complete"); |
|
1047 |
|
1048 } else { |
|
1049 System.out.println("Too many conflicts. There is probably a loop in the network."); |
|
1050 this.endSession(); |
|
1051 } |
|
1052 } |
|
1053 } |
|