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