1 /*
2 * main.cxx
3 *
4 * OPAL application source file for playing RTP from a PCAP file
5 *
6 * Main program entry point.
7 *
8 * Copyright (c) 2007 Equivalence Pty. Ltd.
9 *
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
14 *
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * The Original Code is Portable Windows Library.
21 *
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
23 *
24 * Contributor(s): ______________________________________.
25 *
26 * $Revision: 27859 $
27 * $Author: rjongbloed $
28 * $Date: 2012-06-19 01:12:22 -0500 (Tue, 19 Jun 2012) $
29 */
30
31 #include "precompile.h"
32 #include "main.h"
33
34 #include <rtp/pcapfile.h>
35 #include <ptlib/vconvert.h>
36 #include <ptlib/pipechan.h>
37
38
39 PCREATE_PROCESS(PlayRTP);
40
41
42 const int g_extraHeight = 35;
43
44
45 ///////////////////////////////////////////////////////////////////////
46
PlayRTP()47 PlayRTP::PlayRTP()
48 : PProcess("OPAL Audio/Video Codec Tester", "PlayRTP", 1, 0, ReleaseCode, 0)
49 , m_transcoder(NULL)
50 , m_player(NULL)
51 , m_display(NULL)
52 {
53 }
54
55
~PlayRTP()56 PlayRTP::~PlayRTP()
57 {
58 delete m_transcoder;
59 delete m_player;
60 }
61
62
Main()63 void PlayRTP::Main()
64 {
65 PArgList & args = GetArguments();
66
67 args.Parse("h-help."
68 "m-mapping:"
69 "S-src-ip:"
70 "D-dst-ip:"
71 "s-src-port:"
72 "d-dst-port:"
73 "A-audio-driver:"
74 "a-audio-device:"
75 "V-video-driver:"
76 "v-video-device:"
77 "p-singlestep."
78 "P-payload-file:"
79 "i-info."
80 "f-find."
81 "Y-video-file:"
82 "E:"
83 "T:"
84 "X."
85 "O:"
86 "-session:"
87 "-nodelay."
88 #if PTRACING
89 "o-output:" "-no-output."
90 "t-trace." "-no-trace."
91 #endif
92 , FALSE);
93
94 #if PTRACING
95 PTrace::Initialise(args.GetOptionCount('t'),
96 args.HasOption('o') ? (const char *)args.GetOptionString('o') : NULL,
97 PTrace::Blocks | PTrace::Timestamp | PTrace::Thread | PTrace::FileAndLine);
98 #endif
99
100 if (args.HasOption('h') || args.GetCount() == 0) {
101 PError << "usage: " << GetFile().GetTitle() << " [ options ] filename [ filename ... ]\n"
102 "\n"
103 "Available options are:\n"
104 " --help : print this help message.\n"
105 " -m or --mapping N=fmt : Set mapping of payload type to format, eg 101=H.264\n"
106 " -S or --src-ip addr : Source IP address, default is any\n"
107 " -D or --dst-ip addr : Destination IP address, default is any\n"
108 " -s or --src-port N : Source UDP port, default is any\n"
109 " -d or --dst-port N : Destination UDP port, default is any\n"
110 " -A or --audio-driver drv : Audio player driver.\n"
111 " -a or --audio-device dev : Audio player device.\n"
112 " -V or --video-driver drv : Video display driver to use.\n"
113 " -v or --video-device dev : Video display device to use.\n"
114 " -p or --singlestep : Single step through input data.\n"
115 " -i or --info : Display per-frame information.\n"
116 " -f or --find : find and display list of RTP sessions.\n"
117 " -P or --payload-file fn : write RTP payload to file\n"
118 " -Y file : write decoded video to file\n"
119 " -E file : write event log to file\n"
120 " -T title : put text in extra video information\n"
121 " -X : enable extra video information\n"
122 " -O --option fmt:opt=val : Set codec option (may be used multiple times)\n"
123 " : fmt is name of codec, eg \"H.261\"\n"
124 " : opt is name of option, eg \"Target Bit Rate\"\n"
125 " : val is value of option, eg \"48000\"\n"
126 " --session num : automatically select session num\n"
127 " --nodelay : do not delay as per timestamps\n"
128 #if PTRACING
129 " -o or --output file : file name for output of log messages\n"
130 " -t or --trace : degree of verbosity in error log (more times for more detail)\n"
131 #endif
132 "\n"
133 "e.g. " << GetFile().GetTitle() << " conversation.pcap\n\n";
134 return;
135 }
136
137 m_extendedInfo = args.HasOption('X') || args.HasOption('T');
138 m_noDelay = args.HasOption("nodelay");
139
140 PStringArray options = args.GetOptionString('O').Lines();
141 for (PINDEX i = 0; i < options.GetSize(); i++) {
142 const PString & optionDescription = options[i];
143 PINDEX colon = optionDescription.Find(':');
144 PINDEX equal = optionDescription.Find('=', colon+2);
145 if (colon == P_MAX_INDEX || equal == P_MAX_INDEX) {
146 cerr << "Invalid option description \"" << optionDescription << '"' << endl;
147 continue;
148 }
149 OpalMediaFormat mediaFormat = optionDescription.Left(colon);
150 if (mediaFormat.IsEmpty()) {
151 cerr << "Invalid media format in option description \"" << optionDescription << '"' << endl;
152 continue;
153 }
154 PString optionName = optionDescription(colon+1, equal-1);
155 if (!mediaFormat.HasOption(optionName)) {
156 cerr << "Invalid option name in description \"" << optionDescription << '"' << endl;
157 continue;
158 }
159 PString valueStr = optionDescription.Mid(equal+1);
160 if (!mediaFormat.SetOptionValue(optionName, valueStr)) {
161 cerr << "Invalid option value in description \"" << optionDescription << '"' << endl;
162 continue;
163 }
164 OpalMediaFormat::SetRegisteredMediaFormat(mediaFormat);
165 cout << "Set option \"" << optionName << "\" to \"" << valueStr << "\" in \"" << mediaFormat << '"' << endl;
166 }
167
168 OpalPCAPFile pcap;
169 if (!pcap.Open(args[0]))
170 return;
171
172 if (args.HasOption('f')) {
173 OpalPCAPFile::DiscoveredRTPMap discoveredRTPMap;
174 if (!pcap.DiscoverRTP(discoveredRTPMap)) {
175 cerr << "error: no RTP sessions found" << endl;
176 return;
177 }
178 cout << "Found " << discoveredRTPMap.size() << " sessions:\n" << discoveredRTPMap << endl;
179 return;
180 }
181
182 PStringArray mappings = args.GetOptionString('m').Lines();
183 for (PINDEX i = 0; i < mappings.GetSize(); i++) {
184 const PString & mapping = mappings[i];
185 PINDEX equal = mapping.Find('=');
186 if (equal == P_MAX_INDEX) {
187 cout << "Invalid syntax for mapping \"" << mapping << '"' << endl;
188 continue;
189 }
190
191 RTP_DataFrame::PayloadTypes pt = (RTP_DataFrame::PayloadTypes)mapping.Left(equal).AsUnsigned();
192 if (pt > RTP_DataFrame::MaxPayloadType) {
193 cout << "Invalid payload type for mapping \"" << mapping << '"' << endl;
194 continue;
195 }
196
197 OpalMediaFormat mf = mapping.Mid(equal+1);
198 if (!mf.IsTransportable()) {
199 cout << "Invalid media format for mapping \"" << mapping << '"' << endl;
200 continue;
201 }
202
203 pcap.SetPayloadMap(pt, mf);
204 }
205
206 if (args.HasOption('S') || args.HasOption('D') || args.HasOption('s') || args.HasOption('d')) {
207 pcap.SetFilterSrcIP(args.GetOptionString('S', PIPSocket::GetDefaultIpAny().AsString()));
208 pcap.SetFilterDstIP(args.GetOptionString('D', PIPSocket::GetDefaultIpAny().AsString()));
209 pcap.SetFilterSrcPort(PIPSocket::GetPortByService("udp", args.GetOptionString('s')));
210 pcap.SetFilterDstPort(PIPSocket::GetPortByService("udp", args.GetOptionString('d', "5000")));
211 }
212 else {
213 OpalPCAPFile::DiscoveredRTPMap discoveredRTPMap;
214 if (!pcap.DiscoverRTP(discoveredRTPMap)) {
215 cerr << "error: no RTP sessions found - please use -S/-D/-s/-d option to specify session manually" << endl;
216 return;
217 }
218
219 if (args.HasOption("session")) {
220 if (!pcap.SetFilters(discoveredRTPMap, args.GetOptionString("session").AsUnsigned())) {
221 cout << "Preselected session " << args.GetOptionString("session") << " not valid" << endl;
222 return;
223 }
224 }
225 else {
226 cout << "Select one of the following sessions:\n" << discoveredRTPMap << endl;
227 for (;;) {
228 cout << "Select (1-" << discoveredRTPMap.size()*2 << ") ? " << flush;
229 size_t selected;
230 cin >> selected;
231 if (pcap.SetFilters(discoveredRTPMap, selected))
232 break;
233 cout << "Session " << selected << " is not valid" << endl;
234 }
235 }
236 }
237
238 if (args.HasOption('E')) {
239 if (!m_eventLog.Open(args.GetOptionString('E'), PFile::WriteOnly)) {
240 cerr << "cannot open event log file '" << args.GetOptionString('E') << "'" << endl;
241 return;
242 }
243 m_eventLog << "Event log created " << PTime().AsString() << " from " << args[0] << endl;
244 }
245
246 m_singleStep = args.HasOption('p');
247 m_info = args.GetOptionCount('i');
248
249 if (args.HasOption('P')) {
250 if (!m_payloadFile.Open(args.GetOptionString('P'), PFile::ReadWrite)) {
251 cerr << "Cannot create \"" << m_payloadFile.GetFilePath() << '\"' << endl;
252 return;
253 }
254 cout << "Dumping RTP payload to \"" << m_payloadFile.GetFilePath() << '\"' << endl;
255 }
256
257 if (args.HasOption('Y')) {
258 PFilePath yuvFileName = args.GetOptionString('Y');
259 m_extraText = yuvFileName.GetFileName();
260
261 if (yuvFileName.GetType() != ".yuv") {
262 m_encodedFileName = yuvFileName;
263 yuvFileName += ".yuv";
264 }
265
266 if (!m_yuvFile.Open(yuvFileName, PFile::ReadWrite)) {
267 cerr << "Cannot create '" << yuvFileName << "'" << endl;
268 return;
269 }
270 cout << "Writing video to \"" << yuvFileName << '\"' << endl;
271 }
272
273 if (m_extendedInfo) {
274 if (args.HasOption('T'))
275 m_extraText = args.GetOptionString('T').Trim();
276 m_extraHeight = g_extraHeight + ((m_extraText.GetLength() == 0) ? 0 : 17);
277 }
278
279 // Audio player
280 {
281 PString driverName = args.GetOptionString('A');
282 PString deviceName = args.GetOptionString('a');
283 m_player = PSoundChannel::CreateOpenedChannel(driverName, deviceName, PSoundChannel::Player);
284 if (m_player == NULL) {
285 PStringList devices = PSoundChannel::GetDriversDeviceNames("*", PSoundChannel::Player);
286 if (devices.IsEmpty()) {
287 cerr << "No audio devices in the system!" << endl;
288 return;
289 }
290
291 if (!driverName.IsEmpty() || !deviceName.IsEmpty()) {
292 cerr << "Cannot use ";
293 if (driverName.IsEmpty() && deviceName.IsEmpty())
294 cerr << "default ";
295 cerr << "audio player";
296 if (!driverName)
297 cerr << ", driver \"" << driverName << '"';
298 if (!deviceName)
299 cerr << ", device \"" << deviceName << '"';
300 cerr << ", must be one of:\n";
301 for (PINDEX i = 0; i < devices.GetSize(); i++)
302 cerr << " " << devices[i] << '\n';
303 cerr << endl;
304 return;
305 }
306
307 PStringList::iterator it = devices.begin();
308 while ((m_player = PSoundChannel::CreateOpenedChannel(driverName, *it, PSoundChannel::Player)) == NULL) {
309 cerr << "Cannot use audio device \"" << *it << '"' << endl;
310 if (++it == devices.end()) {
311 cerr << "Unable to find an available sound device." << endl;
312 return;
313 }
314 }
315 }
316
317 cout << "Audio Player ";
318 if (!driverName.IsEmpty())
319 cout << "driver \"" << driverName << "\" and ";
320 cout << "device \"" << m_player->GetName() << "\" opened." << endl;
321 }
322
323 // Video display
324 PString driverName = args.GetOptionString('V');
325 PString deviceName = args.GetOptionString('v');
326 m_display = PVideoOutputDevice::CreateOpenedDevice(driverName, deviceName, FALSE);
327 if (m_display == NULL) {
328 cerr << "Cannot use ";
329 if (driverName.IsEmpty() && deviceName.IsEmpty())
330 cerr << "default ";
331 cerr << "video display";
332 if (!driverName)
333 cerr << ", driver \"" << driverName << '"';
334 if (!deviceName)
335 cerr << ", device \"" << deviceName << '"';
336 cerr << ", must be one of:\n";
337 PStringList devices = PVideoOutputDevice::GetDriversDeviceNames("*");
338 for (PINDEX i = 0; i < devices.GetSize(); i++)
339 cerr << " " << devices[i] << '\n';
340 cerr << endl;
341 return;
342 }
343
344 m_display->SetColourFormatConverter(OpalYUV420P);
345
346 cout << "Display ";
347 if (!driverName.IsEmpty())
348 cout << "driver \"" << driverName << "\" and ";
349 cout << "device \"" << m_display->GetDeviceName() << "\" opened." << endl;
350
351 Play(pcap);
352
353 for (PINDEX i = 1; i < args.GetCount(); i++) {
354 if (pcap.Open(args[i]))
355 Play(pcap);
356 }
357 }
358
359
DrawText(unsigned x,unsigned y,unsigned frameWidth,unsigned frameHeight,BYTE * frame,const char * text)360 static void DrawText(unsigned x, unsigned y, unsigned frameWidth, unsigned frameHeight, BYTE * frame, const char * text)
361 {
362 BYTE * output = frame + ((y * frameWidth) + x);
363 unsigned uoffs = (frameWidth * frameHeight) / 4;
364
365 while (*text != '\0') {
366 const PVideoFont::LetterData * letter = PVideoFont::GetLetterData(*text++);
367 if (letter != NULL) {
368 for (PINDEX y = 0; y < PVideoFont::MAX_L_HEIGHT; ++y) {
369 BYTE * Y = output + y * frameWidth;
370 const char * line = letter->line[y];
371 {
372 int UVwidth = (strlen(line)+2)/2;
373 memset(Y + uoffs*2, 0x80, UVwidth);
374 memset(Y + uoffs*2, 0x80, UVwidth);
375 }
376 while (*line != '\0')
377 *Y++ = (*line++ == ' ') ? 16 : 240;
378 }
379 output += 1 + strlen(letter->line[0]);
380 }
381 }
382 }
383
384
Play(OpalPCAPFile & pcap)385 void PlayRTP::Play(OpalPCAPFile & pcap)
386 {
387 cout << "Playing " << pcap.GetFilePath() << endl;
388
389 RTP_DataFrame::PayloadTypes rtpStreamPayloadType = RTP_DataFrame::IllegalPayloadType;
390 RTP_DataFrame::PayloadTypes lastUnsupportedPayloadType = RTP_DataFrame::IllegalPayloadType;
391 DWORD lastTimeStamp = 0;
392
393 PINDEX totalBytes = 0;
394 unsigned intraFrames = 0;
395 unsigned fragmentationCount = 0;
396 unsigned nextSequenceNumber = 0;
397 unsigned missingPackets = 0;
398 m_packetCount = 0;
399
400 RTP_DataFrame extendedData;
401 m_videoError = false;
402 m_videoFrames = 0;
403
404 bool isAudio = false;
405 bool needInfoHeader = true;
406
407 PTimeInterval playStartTick = PTimer::Tick();
408
409 while (!pcap.IsEndOfFile()) {
410 RTP_DataFrame rtp;
411 if (pcap.GetRTP(rtp) < 0)
412 continue;
413
414 unsigned thisSequenceNumber = rtp.GetSequenceNumber();
415 if (nextSequenceNumber != 0 && thisSequenceNumber != nextSequenceNumber) {
416 cout << "Received SN=" << thisSequenceNumber << ", expected SN=" << nextSequenceNumber << endl;
417 if (thisSequenceNumber > nextSequenceNumber)
418 missingPackets += (thisSequenceNumber - nextSequenceNumber);
419 }
420 nextSequenceNumber = thisSequenceNumber+1;
421
422 if (rtpStreamPayloadType != rtp.GetPayloadType()) {
423 if (rtpStreamPayloadType != RTP_DataFrame::IllegalPayloadType) {
424 cout << "Payload type changed in mid file at sequence number " << thisSequenceNumber
425 << " in file\"" << pcap.GetFilePath() << '"' << endl;
426 continue;
427 }
428 rtpStreamPayloadType = rtp.GetPayloadType();
429 }
430
431 if (m_transcoder == NULL) {
432 OpalMediaFormat srcFmt = pcap.GetMediaFormat(rtp);
433 if (!srcFmt.IsValid()) {
434 if (lastUnsupportedPayloadType != rtpStreamPayloadType) {
435 cout << "Unsupported Payload Type " << rtpStreamPayloadType << " in file \"" << pcap.GetFilePath() << '"' << endl;
436 lastUnsupportedPayloadType = rtpStreamPayloadType;
437 }
438 rtpStreamPayloadType = RTP_DataFrame::IllegalPayloadType;
439 continue;
440 }
441
442 OpalMediaFormat dstFmt;
443 if (srcFmt.GetMediaType() == OpalMediaType::Audio()) {
444 dstFmt = OpalPCM16;
445 m_noDelay = true; // Will be paced by output device.
446 isAudio = true;
447 unsigned frame = srcFmt.GetFrameTime();
448 if (frame < 160)
449 frame = 160;
450 m_player->SetBuffers(frame*2, 2000/frame); // 250ms of buffering of Vista goes funny
451 }
452 else if (srcFmt.GetMediaType() == OpalMediaType::Video()) {
453 dstFmt = OpalYUV420P;
454 m_display->Start();
455 }
456 else {
457 cout << "Unsupported Media Type " << srcFmt.GetMediaType() << " in file \"" << pcap.GetFilePath() << '"' << endl;
458 return;
459 }
460
461 m_transcoder = OpalTranscoder::Create(srcFmt, dstFmt);
462 if (m_transcoder == NULL) {
463 cout << "No transcoder for " << srcFmt << " in file \"" << pcap.GetFilePath() << '"' << endl;
464 return;
465 }
466
467 cout << "Decoding " << srcFmt << " from file \"" << pcap.GetFilePath() << '"' << endl;
468 m_transcoder->SetCommandNotifier(PCREATE_NOTIFIER(OnTranscoderCommand));
469 lastTimeStamp = rtp.GetTimestamp();
470 }
471
472 const OpalMediaFormat & inputFmt = m_transcoder->GetInputFormat();
473
474 if (inputFmt == "H.264") {
475 static BYTE const StartCode[4] = { 0, 0, 0, 1 };
476 m_payloadFile.Write(StartCode, sizeof(StartCode));
477 }
478 m_payloadFile.Write(rtp.GetPayloadPtr(), rtp.GetPayloadSize());
479
480 if (!m_noDelay) {
481 if (rtp.GetTimestamp() != lastTimeStamp) {
482 unsigned msecs = (rtp.GetTimestamp() - lastTimeStamp)/inputFmt.GetTimeUnits();
483 if (msecs < 3000)
484 PThread::Sleep(msecs);
485 else
486 cout << "ignoring timestamp jump > 3 seconds" << endl;
487 lastTimeStamp = rtp.GetTimestamp();
488 }
489 }
490
491 if (pcap.IsFragmentated())
492 ++fragmentationCount;
493
494 if (m_info > 0) {
495 if (needInfoHeader) {
496 needInfoHeader = false;
497 if (m_info > 0) {
498 if (m_info > 1)
499 cout << "Packet,RealTime,CaptureTime,";
500 cout << "SSRC,SequenceNumber,TimeStamp";
501 if (m_info > 1) {
502 cout << ",Marker,PayloadType,payloadSize";
503 if (isAudio)
504 cout << ",DecodedSize";
505 else
506 cout << ",Width,Height";
507 if (m_info > 2)
508 cout << ",Data";
509 }
510 cout << '\n';
511 }
512 }
513
514 if (m_info > 1)
515 cout << m_packetCount << ','
516 << PTimer::Tick().GetMilliSeconds() << ','
517 << pcap.GetPacketTime().AsString(PTime::EpochTime) << ',';
518 cout << "0x" << hex << rtp.GetSyncSource() << dec << ',' << rtp.GetSequenceNumber() << ',' << rtp.GetTimestamp();
519 if (m_info > 1)
520 cout << ',' << rtp.GetMarker() << ',' << rtp.GetPayloadType() << ',' << rtp.GetPayloadSize();
521 }
522
523 if (m_singleStep)
524 cout << "Input packet of length " << rtp.GetPayloadSize() << (rtp.GetMarker() ? " with MARKER" : "") << " -> ";
525
526 totalBytes += rtp.GetPayloadSize();
527
528 m_vfu = false;
529 RTP_DataFrameList output;
530 if (!m_transcoder->ConvertFrames(rtp, output)) {
531 cout << "Error decoding file \"" << pcap.GetFilePath() << '"' << endl;
532 return;
533 }
534
535 if (output.GetSize() == 0) {
536 if (m_singleStep)
537 cout << "no frame" << endl;
538 }
539 else {
540 if (m_singleStep) {
541 cout << output.GetSize() << " packets" << endl;
542 char ch;
543 cin >> ch;
544 if (ch == 'c')
545 m_singleStep = false;
546 }
547
548 for (PINDEX i = 0; i < output.GetSize(); i++) {
549 const RTP_DataFrame & data = output[i];
550 if (isAudio) {
551 m_player->Write(data.GetPayloadPtr(), data.GetPayloadSize());
552 if (m_info > 1)
553 cout << ',' << data.GetPayloadSize();
554 }
555 else {
556 ++m_videoFrames;
557
558 OpalVideoTranscoder * video = (OpalVideoTranscoder *)m_transcoder;
559 if (video->WasLastFrameIFrame()) {
560 ++intraFrames;
561 m_eventLog << "Frame " << m_videoFrames << ": I-Frame received";
562 if (m_videoError)
563 m_eventLog << " - decode error cleared";
564 m_eventLog << endl;
565 m_videoError = false;
566 }
567
568 OpalVideoTranscoder::FrameHeader * frame = (OpalVideoTranscoder::FrameHeader *)data.GetPayloadPtr();
569 m_yuvFile.SetFrameSize(frame->width, frame->height + (m_extendedInfo ? m_extraHeight : 0));
570
571 if (!m_extendedInfo) {
572
573 m_display->SetFrameSize(frame->width, frame->height);
574 m_display->SetFrameData(frame->x, frame->y,
575 frame->width, frame->height,
576 OPAL_VIDEO_FRAME_DATA_PTR(frame), data.GetMarker());
577 m_yuvFile.WriteFrame(OPAL_VIDEO_FRAME_DATA_PTR(frame));
578 }
579 else {
580 int extendedHeight = frame->height + m_extraHeight;
581 extendedData.SetSize(data.GetHeaderSize() + sizeof(OpalVideoTranscoder::FrameHeader) + (frame->width * extendedHeight * 3 / 2));
582 memcpy(extendedData.GetPointer(), (const BYTE *)data, data.GetHeaderSize());
583 OpalVideoTranscoder::FrameHeader * extendedFrame = (OpalVideoTranscoder::FrameHeader *)extendedData.GetPayloadPtr();
584 *extendedFrame = *frame;
585 extendedFrame->height = extendedHeight;
586
587 PColourConverter::FillYUV420P(0, 0, extendedFrame->width, extendedFrame->height,
588 extendedFrame->width, extendedFrame->height, OPAL_VIDEO_FRAME_DATA_PTR(extendedFrame),
589 0, 0, 0);
590
591 char text[60];
592 sprintf(text, "Seq:%08u Ts:%08u",
593 rtp.GetSequenceNumber(),
594 rtp.GetTimestamp());
595 DrawText(4, 4, extendedFrame->width, extendedFrame->height, OPAL_VIDEO_FRAME_DATA_PTR(extendedFrame), text);
596
597 sprintf(text, "TC:%06u %c %c %c",
598 m_videoFrames,
599 m_vfu ? 'V' : ' ',
600 video->WasLastFrameIFrame() ? 'I' : ' ',
601 m_videoError ? 'E' : ' ');
602 DrawText(4, 20, extendedFrame->width, extendedFrame->height, OPAL_VIDEO_FRAME_DATA_PTR(extendedFrame), text);
603
604 if (m_extraText.GetLength() > 0)
605 DrawText(4, 37, extendedFrame->width, extendedFrame->height, OPAL_VIDEO_FRAME_DATA_PTR(extendedFrame), m_extraText);
606
607 PColourConverter::CopyYUV420P(0, 0, frame->width, frame->height, frame->width, frame->height, OPAL_VIDEO_FRAME_DATA_PTR(frame),
608 0, m_extraHeight, frame->width, frame->height, extendedFrame->width, extendedFrame->height, OPAL_VIDEO_FRAME_DATA_PTR(extendedFrame),
609 PVideoFrameInfo::eCropTopLeft);
610
611 m_display->SetFrameSize(extendedFrame->width, extendedFrame->height);
612 m_display->SetFrameData(extendedFrame->x, extendedFrame->y,
613 extendedFrame->width, extendedFrame->height,
614 OPAL_VIDEO_FRAME_DATA_PTR(extendedFrame), data.GetMarker());
615
616 m_yuvFile.WriteFrame(OPAL_VIDEO_FRAME_DATA_PTR(extendedFrame));
617 }
618
619 if (m_info > 1)
620 cout << ',' << frame->width << ',' << frame->height;
621 }
622 }
623 }
624
625 if (m_info > 0) {
626 if (m_info > 2) {
627 PINDEX psz = rtp.GetPayloadSize();
628 if (m_info == 2 && psz > 30)
629 psz = 30;
630 cout << ',' << hex << setfill('0');
631 const BYTE * ptr = rtp.GetPayloadPtr();
632 for (PINDEX i = 0; i < psz; ++i)
633 cout << setw(2) << (unsigned)*ptr++;
634 cout << dec << setfill(' ');
635 }
636 cout << '\n';
637 }
638
639 ++m_packetCount;
640
641 if (m_packetCount % 250 == 0)
642 m_eventLog << "Packet " << m_packetCount << ": still processing" << endl;
643 }
644
645 m_eventLog << "Packet " << m_packetCount << ": completed" << endl;
646
647 // Output final stats.
648 cout << '\n';
649
650 if (m_yuvFile.IsOpen())
651 cout << "Written file : " << m_yuvFile.GetFilePath() << '\n';
652
653 cout << "Payload Type : " << rtpStreamPayloadType;
654 if (m_transcoder != NULL)
655 cout << " (" << m_transcoder->GetInputFormat() << ')';
656 cout << "\n"
657 "Source : " << pcap.GetFilterSrcIP() << ':' << pcap.GetFilterSrcPort() << "\n"
658 "Destination : " << pcap.GetFilterDstIP() << ':' << pcap.GetFilterDstPort() << "\n"
659 "Total packets : " << m_packetCount << "\n"
660 "Total bytes : " << totalBytes << '\n';
661
662 PTimeInterval playTime = PTimer::Tick() - playStartTick;
663 if (!m_yuvFile.IsOpen() && playTime > 0) {
664 cout << "Duration : " << playTime << " seconds\n"
665 << "Bit rate : " << fixed << setprecision(1)
666 << (totalBytes*8/playTime.GetMilliSeconds()) << "kbps\n";
667 }
668
669 if (m_videoFrames > 0) {
670 cout << "Resolution : " << m_yuvFile.GetFrameWidth() << "x" << m_yuvFile.GetFrameHeight() << "\n"
671 "Video frames : " << m_videoFrames << "\n"
672 "Intra Frames : " << intraFrames << '\n';
673 if (!m_yuvFile.IsOpen() && playTime > 0)
674 cout << "Frame rate : " << fixed << setprecision(1)
675 << (m_videoFrames*1000.0/playTime.GetMilliSeconds()) << "fps\n";
676 }
677 else {
678 if (playTime > 0)
679 cout << "Avg frame time : " << fixed << setprecision(1)
680 << (playTime.GetMilliSeconds()/m_packetCount) << "ms\n";
681 }
682
683 cout << "Missing packets: " << missingPackets << "\n"
684 "IP fragments : " << fragmentationCount
685 << endl;
686
687 if (!m_encodedFileName.IsEmpty()) {
688 PStringStream args;
689 args << "ffmpeg -r 10 -y -s " << m_yuvFile.GetFrameWidth() << 'x' << m_yuvFile.GetFrameHeight()
690 << " -i '" << m_yuvFile.GetFilePath() << "' '" << m_encodedFileName << '\'';
691 cout << "Executing command \"" << args << '\"' << endl;
692 PPipeChannel cmd;
693 if (!cmd.Open(args, PPipeChannel::ReadWriteStd, true))
694 cout << "failed";
695 cmd.Execute();
696 cmd.WaitForTermination();
697 cout << "done" << endl;
698 }
699
700 // Clean up
701 delete m_transcoder;
702 m_transcoder = NULL;
703 }
704
705
OnTranscoderCommand(OpalMediaCommand & command,INT)706 void PlayRTP::OnTranscoderCommand(OpalMediaCommand & command, INT /*extra*/)
707 {
708 if (PIsDescendant(&command, OpalVideoUpdatePicture)) {
709 PStringStream msg;
710 msg << "Packet " << m_packetCount << ", frame " << m_videoFrames << ": decoding error";
711
712 OpalVideoPictureLoss * loss = dynamic_cast<OpalVideoPictureLoss *>(&command);
713 if (loss != NULL)
714 msg << " for sn=" << loss->GetSequenceNumber() << ", ts=" << loss->GetTimestamp();
715 m_eventLog << msg << endl;
716 cout << msg << endl;
717 m_videoError = m_vfu = true;
718 }
719 }
720
721
722 // End of File ///////////////////////////////////////////////////////////////
723