-
Notifications
You must be signed in to change notification settings - Fork 192
/
Copy pathServer.java
538 lines (454 loc) · 19 KB
/
Server.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
/* ------------------
Server
usage: java Server [RTSP listening port]
---------------------- */
import java.io.*;
import java.net.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;
import java.awt.image.*;
import javax.imageio.*;
import javax.imageio.stream.ImageOutputStream;
public class Server extends JFrame implements ActionListener {
//RTP variables:
//----------------
DatagramSocket RTPsocket; //socket to be used to send and receive UDP packets
DatagramPacket senddp; //UDP packet containing the video frames
InetAddress ClientIPAddr; //Client IP address
int RTP_dest_port = 0; //destination port for RTP packets (given by the RTSP Client)
int RTSP_dest_port = 0;
//GUI:
//----------------
JLabel label;
//Video variables:
//----------------
int imagenb = 0; //image nb of the image currently transmitted
VideoStream video; //VideoStream object used to access video frames
static int MJPEG_TYPE = 26; //RTP payload type for MJPEG video
static int FRAME_PERIOD = 100; //Frame period of the video to stream, in ms
static int VIDEO_LENGTH = 500; //length of the video in frames
Timer timer; //timer used to send the images at the video frame rate
byte[] buf; //buffer used to store the images to send to the client
int sendDelay; //the delay to send images over the wire. Ideally should be
//equal to the frame rate of the video file, but may be
//adjusted when congestion is detected.
//RTSP variables
//----------------
//rtsp states
final static int INIT = 0;
final static int READY = 1;
final static int PLAYING = 2;
//rtsp message types
final static int SETUP = 3;
final static int PLAY = 4;
final static int PAUSE = 5;
final static int TEARDOWN = 6;
final static int DESCRIBE = 7;
static int state; //RTSP Server state == INIT or READY or PLAY
Socket RTSPsocket; //socket used to send/receive RTSP messages
//input and output stream filters
static BufferedReader RTSPBufferedReader;
static BufferedWriter RTSPBufferedWriter;
static String VideoFileName; //video file requested from the client
static String RTSPid = UUID.randomUUID().toString(); //ID of the RTSP session
int RTSPSeqNb = 0; //Sequence number of RTSP messages within the session
//RTCP variables
//----------------
static int RTCP_RCV_PORT = 19001; //port where the client will receive the RTP packets
static int RTCP_PERIOD = 400; //How often to check for control events
DatagramSocket RTCPsocket;
RtcpReceiver rtcpReceiver;
int congestionLevel;
//Performance optimization and Congestion control
ImageTranslator imgTranslator;
CongestionController cc;
final static String CRLF = "\r\n";
//--------------------------------
//Constructor
//--------------------------------
public Server() {
//init Frame
super("RTSP Server");
//init RTP sending Timer
sendDelay = FRAME_PERIOD;
timer = new Timer(sendDelay, this);
timer.setInitialDelay(0);
timer.setCoalesce(true);
//init congestion controller
cc = new CongestionController(600);
//allocate memory for the sending buffer
buf = new byte[20000];
//Handler to close the main window
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
//stop the timer and exit
timer.stop();
rtcpReceiver.stopRcv();
System.exit(0);
}});
//init the RTCP packet receiver
rtcpReceiver = new RtcpReceiver(RTCP_PERIOD);
//GUI:
label = new JLabel("Send frame # ", JLabel.CENTER);
getContentPane().add(label, BorderLayout.CENTER);
//Video encoding and quality
imgTranslator = new ImageTranslator(0.8f);
}
//------------------------------------
//main
//------------------------------------
public static void main(String argv[]) throws Exception
{
//create a Server object
Server server = new Server();
//show GUI:
server.pack();
server.setVisible(true);
server.setSize(new Dimension(400, 200));
//get RTSP socket port from the command line
int RTSPport = Integer.parseInt(argv[0]);
server.RTSP_dest_port = RTSPport;
//Initiate TCP connection with the client for the RTSP session
ServerSocket listenSocket = new ServerSocket(RTSPport);
server.RTSPsocket = listenSocket.accept();
listenSocket.close();
//Get Client IP address
server.ClientIPAddr = server.RTSPsocket.getInetAddress();
//Initiate RTSPstate
state = INIT;
//Set input and output stream filters:
RTSPBufferedReader = new BufferedReader(new InputStreamReader(server.RTSPsocket.getInputStream()) );
RTSPBufferedWriter = new BufferedWriter(new OutputStreamWriter(server.RTSPsocket.getOutputStream()) );
//Wait for the SETUP message from the client
int request_type;
boolean done = false;
while(!done) {
request_type = server.parseRequest(); //blocking
if (request_type == SETUP) {
done = true;
//update RTSP state
state = READY;
System.out.println("New RTSP state: READY");
//Send response
server.sendResponse();
//init the VideoStream object:
server.video = new VideoStream(VideoFileName);
//init RTP and RTCP sockets
server.RTPsocket = new DatagramSocket();
server.RTCPsocket = new DatagramSocket(RTCP_RCV_PORT);
}
}
//loop to handle RTSP requests
while(true) {
//parse the request
request_type = server.parseRequest(); //blocking
if ((request_type == PLAY) && (state == READY)) {
//send back response
server.sendResponse();
//start timer
server.timer.start();
server.rtcpReceiver.startRcv();
//update state
state = PLAYING;
System.out.println("New RTSP state: PLAYING");
}
else if ((request_type == PAUSE) && (state == PLAYING)) {
//send back response
server.sendResponse();
//stop timer
server.timer.stop();
server.rtcpReceiver.stopRcv();
//update state
state = READY;
System.out.println("New RTSP state: READY");
}
else if (request_type == TEARDOWN) {
//send back response
server.sendResponse();
//stop timer
server.timer.stop();
server.rtcpReceiver.stopRcv();
//close sockets
server.RTSPsocket.close();
server.RTPsocket.close();
System.exit(0);
}
else if (request_type == DESCRIBE) {
System.out.println("Received DESCRIBE request");
server.sendDescribe();
}
}
}
//------------------------
//Handler for timer
//------------------------
public void actionPerformed(ActionEvent e) {
byte[] frame;
//if the current image nb is less than the length of the video
if (imagenb < VIDEO_LENGTH) {
//update current imagenb
imagenb++;
try {
//get next frame to send from the video, as well as its size
int image_length = video.getnextframe(buf);
//adjust quality of the image if there is congestion detected
if (congestionLevel > 0) {
imgTranslator.setCompressionQuality(1.0f - congestionLevel * 0.2f);
frame = imgTranslator.compress(Arrays.copyOfRange(buf, 0, image_length));
image_length = frame.length;
System.arraycopy(frame, 0, buf, 0, image_length);
}
//Builds an RTPpacket object containing the frame
RTPpacket rtp_packet = new RTPpacket(MJPEG_TYPE, imagenb, imagenb*FRAME_PERIOD, buf, image_length);
//get to total length of the full rtp packet to send
int packet_length = rtp_packet.getlength();
//retrieve the packet bitstream and store it in an array of bytes
byte[] packet_bits = new byte[packet_length];
rtp_packet.getpacket(packet_bits);
//send the packet as a DatagramPacket over the UDP socket
senddp = new DatagramPacket(packet_bits, packet_length, ClientIPAddr, RTP_dest_port);
RTPsocket.send(senddp);
System.out.println("Send frame #" + imagenb + ", Frame size: " + image_length + " (" + buf.length + ")");
//print the header bitstream
rtp_packet.printheader();
//update GUI
label.setText("Send frame #" + imagenb);
}
catch(Exception ex) {
System.out.println("Exception caught: "+ex);
System.exit(0);
}
}
else {
//if we have reached the end of the video file, stop the timer
timer.stop();
rtcpReceiver.stopRcv();
}
}
//------------------------
//Controls RTP sending rate based on traffic
//------------------------
class CongestionController implements ActionListener {
private Timer ccTimer;
int interval; //interval to check traffic stats
int prevLevel; //previously sampled congestion level
public CongestionController(int interval) {
this.interval = interval;
ccTimer = new Timer(interval, this);
ccTimer.start();
}
public void actionPerformed(ActionEvent e) {
//adjust the send rate
if (prevLevel != congestionLevel) {
sendDelay = FRAME_PERIOD + congestionLevel * (int)(FRAME_PERIOD * 0.1);
timer.setDelay(sendDelay);
prevLevel = congestionLevel;
System.out.println("Send delay changed to: " + sendDelay);
}
}
}
//------------------------
//Listener for RTCP packets sent from client
//------------------------
class RtcpReceiver implements ActionListener {
private Timer rtcpTimer;
private byte[] rtcpBuf;
int interval;
public RtcpReceiver(int interval) {
//set timer with interval for receiving packets
this.interval = interval;
rtcpTimer = new Timer(interval, this);
rtcpTimer.setInitialDelay(0);
rtcpTimer.setCoalesce(true);
//allocate buffer for receiving RTCP packets
rtcpBuf = new byte[512];
}
public void actionPerformed(ActionEvent e) {
//Construct a DatagramPacket to receive data from the UDP socket
DatagramPacket dp = new DatagramPacket(rtcpBuf, rtcpBuf.length);
float fractionLost;
try {
RTCPsocket.receive(dp); // Blocking
RTCPpacket rtcpPkt = new RTCPpacket(dp.getData(), dp.getLength());
System.out.println("[RTCP] " + rtcpPkt);
//set congestion level between 0 to 4
fractionLost = rtcpPkt.fractionLost;
if (fractionLost >= 0 && fractionLost <= 0.01) {
congestionLevel = 0; //less than 0.01 assume negligible
}
else if (fractionLost > 0.01 && fractionLost <= 0.25) {
congestionLevel = 1;
}
else if (fractionLost > 0.25 && fractionLost <= 0.5) {
congestionLevel = 2;
}
else if (fractionLost > 0.5 && fractionLost <= 0.75) {
congestionLevel = 3;
}
else {
congestionLevel = 4;
}
}
catch (InterruptedIOException iioe) {
System.out.println("Nothing to read");
}
catch (IOException ioe) {
System.out.println("Exception caught: "+ioe);
}
}
public void startRcv() {
rtcpTimer.start();
}
public void stopRcv() {
rtcpTimer.stop();
}
}
//------------------------------------
//Translate an image to different encoding or quality
//------------------------------------
class ImageTranslator {
private float compressionQuality;
private ByteArrayOutputStream baos;
private BufferedImage image;
private Iterator<ImageWriter>writers;
private ImageWriter writer;
private ImageWriteParam param;
private ImageOutputStream ios;
public ImageTranslator(float cq) {
compressionQuality = cq;
try {
baos = new ByteArrayOutputStream();
ios = ImageIO.createImageOutputStream(baos);
writers = ImageIO.getImageWritersByFormatName("jpeg");
writer = (ImageWriter)writers.next();
writer.setOutput(ios);
param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(compressionQuality);
} catch (Exception ex) {
System.out.println("Exception caught: "+ex);
System.exit(0);
}
}
public byte[] compress(byte[] imageBytes) {
try {
baos.reset();
image = ImageIO.read(new ByteArrayInputStream(imageBytes));
writer.write(null, new IIOImage(image, null, null), param);
} catch (Exception ex) {
System.out.println("Exception caught: "+ex);
System.exit(0);
}
return baos.toByteArray();
}
public void setCompressionQuality(float cq) {
compressionQuality = cq;
param.setCompressionQuality(compressionQuality);
}
}
//------------------------------------
//Parse RTSP Request
//------------------------------------
private int parseRequest() {
int request_type = -1;
try {
//parse request line and extract the request_type:
String RequestLine = RTSPBufferedReader.readLine();
System.out.println("RTSP Server - Received from Client:");
System.out.println(RequestLine);
StringTokenizer tokens = new StringTokenizer(RequestLine);
String request_type_string = tokens.nextToken();
//convert to request_type structure:
if ((new String(request_type_string)).compareTo("SETUP") == 0)
request_type = SETUP;
else if ((new String(request_type_string)).compareTo("PLAY") == 0)
request_type = PLAY;
else if ((new String(request_type_string)).compareTo("PAUSE") == 0)
request_type = PAUSE;
else if ((new String(request_type_string)).compareTo("TEARDOWN") == 0)
request_type = TEARDOWN;
else if ((new String(request_type_string)).compareTo("DESCRIBE") == 0)
request_type = DESCRIBE;
if (request_type == SETUP) {
//extract VideoFileName from RequestLine
VideoFileName = tokens.nextToken();
}
//parse the SeqNumLine and extract CSeq field
String SeqNumLine = RTSPBufferedReader.readLine();
System.out.println(SeqNumLine);
tokens = new StringTokenizer(SeqNumLine);
tokens.nextToken();
RTSPSeqNb = Integer.parseInt(tokens.nextToken());
//get LastLine
String LastLine = RTSPBufferedReader.readLine();
System.out.println(LastLine);
tokens = new StringTokenizer(LastLine);
if (request_type == SETUP) {
//extract RTP_dest_port from LastLine
for (int i=0; i<3; i++)
tokens.nextToken(); //skip unused stuff
RTP_dest_port = Integer.parseInt(tokens.nextToken());
}
else if (request_type == DESCRIBE) {
tokens.nextToken();
String describeDataType = tokens.nextToken();
}
else {
//otherwise LastLine will be the SessionId line
tokens.nextToken(); //skip Session:
RTSPid = tokens.nextToken();
}
} catch(Exception ex) {
System.out.println("Exception caught: "+ex);
System.exit(0);
}
return(request_type);
}
// Creates a DESCRIBE response string in SDP format for current media
private String describe() {
StringWriter writer1 = new StringWriter();
StringWriter writer2 = new StringWriter();
// Write the body first so we can get the size later
writer2.write("v=0" + CRLF);
writer2.write("m=video " + RTSP_dest_port + " RTP/AVP " + MJPEG_TYPE + CRLF);
writer2.write("a=control:streamid=" + RTSPid + CRLF);
writer2.write("a=mimetype:string;\"video/MJPEG\"" + CRLF);
String body = writer2.toString();
writer1.write("Content-Base: " + VideoFileName + CRLF);
writer1.write("Content-Type: " + "application/sdp" + CRLF);
writer1.write("Content-Length: " + body.length() + CRLF);
writer1.write(body);
return writer1.toString();
}
//------------------------------------
//Send RTSP Response
//------------------------------------
private void sendResponse() {
try {
RTSPBufferedWriter.write("RTSP/1.0 200 OK"+CRLF);
RTSPBufferedWriter.write("CSeq: "+RTSPSeqNb+CRLF);
RTSPBufferedWriter.write("Session: "+RTSPid+CRLF);
RTSPBufferedWriter.flush();
System.out.println("RTSP Server - Sent response to Client.");
} catch(Exception ex) {
System.out.println("Exception caught: "+ex);
System.exit(0);
}
}
private void sendDescribe() {
String des = describe();
try {
RTSPBufferedWriter.write("RTSP/1.0 200 OK"+CRLF);
RTSPBufferedWriter.write("CSeq: "+RTSPSeqNb+CRLF);
RTSPBufferedWriter.write(des);
RTSPBufferedWriter.flush();
System.out.println("RTSP Server - Sent response to Client.");
} catch(Exception ex) {
System.out.println("Exception caught: "+ex);
System.exit(0);
}
}
}