1 /*  RTMPDump
2  *  Copyright (C) 2009 Andrej Stepanchuk
3  *  Copyright (C) 2009 Howard Chu
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program 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
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with RTMPDump; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  *  Boston, MA  02110-1301, USA.
19  *  http://www.gnu.org/copyleft/gpl.html
20  *
21  */
22 
23 #define _FILE_OFFSET_BITS	64
24 
25 #include <stdlib.h>
26 #include <string.h>
27 #include <math.h>
28 #include <stdio.h>
29 
30 #include <signal.h>		// to catch Ctrl-C
31 #include <getopt.h>
32 
33 #include "librtmp/rtmp_sys.h"
34 #include "librtmp/log.h"
35 
36 #ifdef WIN32
37 #define fseeko fseeko64
38 #define ftello ftello64
39 #include <io.h>
40 #include <fcntl.h>
41 #define	SET_BINMODE(f)	setmode(fileno(f), O_BINARY)
42 #else
43 #define	SET_BINMODE(f)
44 #endif
45 
46 #define RD_SUCCESS		0
47 #define RD_FAILED		1
48 #define RD_INCOMPLETE		2
49 #define RD_NO_CONNECT		3
50 
51 #define DEF_TIMEOUT	30	/* seconds */
52 #define DEF_BUFTIME	(10 * 60 * 60 * 1000)	/* 10 hours default */
53 #define DEF_SKIPFRM	0
54 
55 // starts sockets
56 int
InitSockets()57 InitSockets()
58 {
59 #ifdef WIN32
60   WORD version;
61   WSADATA wsaData;
62 
63   version = MAKEWORD(1, 1);
64   return (WSAStartup(version, &wsaData) == 0);
65 #else
66   return TRUE;
67 #endif
68 }
69 
70 inline void
CleanupSockets()71 CleanupSockets()
72 {
73 #ifdef WIN32
74   WSACleanup();
75 #endif
76 }
77 
78 #ifdef _DEBUG
79 uint32_t debugTS = 0;
80 int pnum = 0;
81 
82 FILE *netstackdump = 0;
83 FILE *netstackdump_read = 0;
84 #endif
85 
86 uint32_t nIgnoredFlvFrameCounter = 0;
87 uint32_t nIgnoredFrameCounter = 0;
88 #define MAX_IGNORED_FRAMES	50
89 
90 FILE *file = 0;
91 
92 void
sigIntHandler(int sig)93 sigIntHandler(int sig)
94 {
95   RTMP_ctrlC = TRUE;
96   RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
97   // ignore all these signals now and let the connection close
98   signal(SIGINT, SIG_IGN);
99   signal(SIGTERM, SIG_IGN);
100 #ifndef WIN32
101   signal(SIGHUP, SIG_IGN);
102   signal(SIGPIPE, SIG_IGN);
103   signal(SIGQUIT, SIG_IGN);
104 #endif
105 }
106 
107 #define HEX2BIN(a)      (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
hex2bin(char * str,char ** hex)108 int hex2bin(char *str, char **hex)
109 {
110   char *ptr;
111   int i, l = strlen(str);
112 
113   if (l & 1)
114   	return 0;
115 
116   *hex = malloc(l/2);
117   ptr = *hex;
118   if (!ptr)
119     return 0;
120 
121   for (i=0; i<l; i+=2)
122     *ptr++ = (HEX2BIN(str[i]) << 4) | HEX2BIN(str[i+1]);
123   return l/2;
124 }
125 
126 static const AVal av_onMetaData = AVC("onMetaData");
127 static const AVal av_duration = AVC("duration");
128 static const AVal av_conn = AVC("conn");
129 static const AVal av_token = AVC("token");
130 static const AVal av_playlist = AVC("playlist");
131 static const AVal av_true = AVC("true");
132 
133 int
OpenResumeFile(const char * flvFile,FILE ** file,off_t * size,char ** metaHeader,uint32_t * nMetaHeaderSize,double * duration)134 OpenResumeFile(const char *flvFile,	// file name [in]
135 	       FILE ** file,	// opened file [out]
136 	       off_t * size,	// size of the file [out]
137 	       char **metaHeader,	// meta data read from the file [out]
138 	       uint32_t * nMetaHeaderSize,	// length of metaHeader [out]
139 	       double *duration)	// duration of the stream in ms [out]
140 {
141   size_t bufferSize = 0;
142   char hbuf[16], *buffer = NULL;
143 
144   *nMetaHeaderSize = 0;
145   *size = 0;
146 
147   *file = fopen(flvFile, "r+b");
148   if (!*file)
149     return RD_SUCCESS;		// RD_SUCCESS, because we go to fresh file mode instead of quiting
150 
151   fseek(*file, 0, SEEK_END);
152   *size = ftello(*file);
153   fseek(*file, 0, SEEK_SET);
154 
155   if (*size > 0)
156     {
157       // verify FLV format and read header
158       uint32_t prevTagSize = 0;
159 
160       // check we've got a valid FLV file to continue!
161       if (fread(hbuf, 1, 13, *file) != 13)
162 	{
163 	  RTMP_Log(RTMP_LOGERROR, "Couldn't read FLV file header!");
164 	  return RD_FAILED;
165 	}
166       if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V'
167 	  || hbuf[3] != 0x01)
168 	{
169 	  RTMP_Log(RTMP_LOGERROR, "Invalid FLV file!");
170 	  return RD_FAILED;
171 	}
172 
173       if ((hbuf[4] & 0x05) == 0)
174 	{
175 	  RTMP_Log(RTMP_LOGERROR,
176 	      "FLV file contains neither video nor audio, aborting!");
177 	  return RD_FAILED;
178 	}
179 
180       uint32_t dataOffset = AMF_DecodeInt32(hbuf + 5);
181       fseek(*file, dataOffset, SEEK_SET);
182 
183       if (fread(hbuf, 1, 4, *file) != 4)
184 	{
185 	  RTMP_Log(RTMP_LOGERROR, "Invalid FLV file: missing first prevTagSize!");
186 	  return RD_FAILED;
187 	}
188       prevTagSize = AMF_DecodeInt32(hbuf);
189       if (prevTagSize != 0)
190 	{
191 	  RTMP_Log(RTMP_LOGWARNING,
192 	      "First prevTagSize is not zero: prevTagSize = 0x%08X",
193 	      prevTagSize);
194 	}
195 
196       // go through the file to find the meta data!
197       off_t pos = dataOffset + 4;
198       int bFoundMetaHeader = FALSE;
199 
200       while (pos < *size - 4 && !bFoundMetaHeader)
201 	{
202 	  fseeko(*file, pos, SEEK_SET);
203 	  if (fread(hbuf, 1, 4, *file) != 4)
204 	    break;
205 
206 	  uint32_t dataSize = AMF_DecodeInt24(hbuf + 1);
207 
208 	  if (hbuf[0] == 0x12)
209 	    {
210 	      if (dataSize > bufferSize)
211 		{
212                   /* round up to next page boundary */
213                   bufferSize = dataSize + 4095;
214 		  bufferSize ^= (bufferSize & 4095);
215 		  free(buffer);
216                   buffer = malloc(bufferSize);
217                   if (!buffer)
218 		    return RD_FAILED;
219 		}
220 
221 	      fseeko(*file, pos + 11, SEEK_SET);
222 	      if (fread(buffer, 1, dataSize, *file) != dataSize)
223 		break;
224 
225 	      AMFObject metaObj;
226 	      int nRes = AMF_Decode(&metaObj, buffer, dataSize, FALSE);
227 	      if (nRes < 0)
228 		{
229 		  RTMP_Log(RTMP_LOGERROR, "%s, error decoding meta data packet",
230 		      __FUNCTION__);
231 		  break;
232 		}
233 
234 	      AVal metastring;
235 	      AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring);
236 
237 	      if (AVMATCH(&metastring, &av_onMetaData))
238 		{
239 		  AMF_Dump(&metaObj);
240 
241 		  *nMetaHeaderSize = dataSize;
242 		  if (*metaHeader)
243 		    free(*metaHeader);
244 		  *metaHeader = (char *) malloc(*nMetaHeaderSize);
245 		  memcpy(*metaHeader, buffer, *nMetaHeaderSize);
246 
247 		  // get duration
248 		  AMFObjectProperty prop;
249 		  if (RTMP_FindFirstMatchingProperty
250 		      (&metaObj, &av_duration, &prop))
251 		    {
252 		      *duration = AMFProp_GetNumber(&prop);
253 		      RTMP_Log(RTMP_LOGDEBUG, "File has duration: %f", *duration);
254 		    }
255 
256 		  bFoundMetaHeader = TRUE;
257 		  break;
258 		}
259 	      //metaObj.Reset();
260 	      //delete obj;
261 	    }
262 	  pos += (dataSize + 11 + 4);
263 	}
264 
265       free(buffer);
266       if (!bFoundMetaHeader)
267 	RTMP_Log(RTMP_LOGWARNING, "Couldn't locate meta data!");
268     }
269 
270   return RD_SUCCESS;
271 }
272 
273 int
GetLastKeyframe(FILE * file,int nSkipKeyFrames,uint32_t * dSeek,char ** initialFrame,int * initialFrameType,uint32_t * nInitialFrameSize)274 GetLastKeyframe(FILE * file,	// output file [in]
275 		int nSkipKeyFrames,	// max number of frames to skip when searching for key frame [in]
276 		uint32_t * dSeek,	// offset of the last key frame [out]
277 		char **initialFrame,	// content of the last keyframe [out]
278 		int *initialFrameType,	// initial frame type (audio/video) [out]
279 		uint32_t * nInitialFrameSize)	// length of initialFrame [out]
280 {
281   const size_t bufferSize = 16;
282   char buffer[bufferSize];
283   uint8_t dataType;
284   int bAudioOnly;
285   off_t size;
286 
287   fseek(file, 0, SEEK_END);
288   size = ftello(file);
289 
290   fseek(file, 4, SEEK_SET);
291   if (fread(&dataType, sizeof(uint8_t), 1, file) != 1)
292     return RD_FAILED;
293 
294   bAudioOnly = (dataType & 0x4) && !(dataType & 0x1);
295 
296   RTMP_Log(RTMP_LOGDEBUG, "bAudioOnly: %d, size: %llu", bAudioOnly,
297       (unsigned long long) size);
298 
299   // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
300 
301   //if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
302   //{
303   // find the last seekable frame
304   off_t tsize = 0;
305   uint32_t prevTagSize = 0;
306 
307   // go through the file and find the last video keyframe
308   do
309     {
310       int xread;
311     skipkeyframe:
312       if (size - tsize < 13)
313 	{
314 	  RTMP_Log(RTMP_LOGERROR,
315 	      "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
316 	  return RD_FAILED;
317 	}
318       fseeko(file, size - tsize - 4, SEEK_SET);
319       xread = fread(buffer, 1, 4, file);
320       if (xread != 4)
321 	{
322 	  RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!");
323 	  return RD_FAILED;
324 	}
325 
326       prevTagSize = AMF_DecodeInt32(buffer);
327       //RTMP_Log(RTMP_LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);
328 
329       if (prevTagSize == 0)
330 	{
331 	  RTMP_Log(RTMP_LOGERROR, "Couldn't find keyframe to resume from!");
332 	  return RD_FAILED;
333 	}
334 
335       if (prevTagSize < 0 || prevTagSize > size - 4 - 13)
336 	{
337 	  RTMP_Log(RTMP_LOGERROR,
338 	      "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!",
339 	      prevTagSize);
340 	  return RD_FAILED;
341 	}
342       tsize += prevTagSize + 4;
343 
344       // read header
345       fseeko(file, size - tsize, SEEK_SET);
346       if (fread(buffer, 1, 12, file) != 12)
347 	{
348 	  RTMP_Log(RTMP_LOGERROR, "Couldn't read header!");
349 	  return RD_FAILED;
350 	}
351       //*
352 #ifdef _DEBUG
353       uint32_t ts = AMF_DecodeInt24(buffer + 4);
354       ts |= (buffer[7] << 24);
355       RTMP_Log(RTMP_LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts);
356 #endif //*/
357 
358       // this just continues the loop whenever the number of skipped frames is > 0,
359       // so we look for the next keyframe to continue with
360       //
361       // this helps if resuming from the last keyframe fails and one doesn't want to start
362       // the download from the beginning
363       //
364       if (nSkipKeyFrames > 0
365 	  && !(!bAudioOnly
366 	       && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10)))
367 	{
368 #ifdef _DEBUG
369 	  RTMP_Log(RTMP_LOGDEBUG,
370 	      "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
371 #endif
372 	  nSkipKeyFrames--;
373 	  goto skipkeyframe;
374 	}
375 
376     }
377   while ((bAudioOnly && buffer[0] != 0x08) || (!bAudioOnly && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10)));	// as long as we don't have a keyframe / last audio frame
378 
379   // save keyframe to compare/find position in stream
380   *initialFrameType = buffer[0];
381   *nInitialFrameSize = prevTagSize - 11;
382   *initialFrame = (char *) malloc(*nInitialFrameSize);
383 
384   fseeko(file, size - tsize + 11, SEEK_SET);
385   if (fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize)
386     {
387       RTMP_Log(RTMP_LOGERROR, "Couldn't read last keyframe, aborting!");
388       return RD_FAILED;
389     }
390 
391   *dSeek = AMF_DecodeInt24(buffer + 4);	// set seek position to keyframe tmestamp
392   *dSeek |= (buffer[7] << 24);
393   //}
394   //else // handle audio only, we can seek anywhere we'd like
395   //{
396   //}
397 
398   if (*dSeek < 0)
399     {
400       RTMP_Log(RTMP_LOGERROR,
401 	  "Last keyframe timestamp is negative, aborting, your file is corrupt!");
402       return RD_FAILED;
403     }
404   RTMP_Log(RTMP_LOGDEBUG, "Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek,
405       *nInitialFrameSize, *initialFrameType);
406 
407   /*
408      // now read the timestamp of the frame before the seekable keyframe:
409      fseeko(file, size-tsize-4, SEEK_SET);
410      if(fread(buffer, 1, 4, file) != 4) {
411      RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!");
412      goto start;
413      }
414      uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer);
415      fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET);
416      if(fread(buffer, 1, 4, file) != 4) {
417      RTMP_Log(RTMP_LOGERROR, "Couldn't read previous timestamp!");
418      goto start;
419      }
420      uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
421      timestamp |= (buffer[3]<<24);
422 
423      RTMP_Log(RTMP_LOGDEBUG, "Previous timestamp: %d ms", timestamp);
424    */
425 
426   if (*dSeek != 0)
427     {
428       // seek to position after keyframe in our file (we will ignore the keyframes resent by the server
429       // since they are sent a couple of times and handling this would be a mess)
430       fseeko(file, size - tsize + prevTagSize + 4, SEEK_SET);
431 
432       // make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets
433       // (including several meta data headers and the keyframe we seeked to)
434       //bNoHeader = TRUE; if bResume==true this is true anyway
435     }
436 
437   //}
438 
439   return RD_SUCCESS;
440 }
441 
442 int
Download(RTMP * rtmp,FILE * file,uint32_t dSeek,uint32_t dStopOffset,double duration,int bResume,char * metaHeader,uint32_t nMetaHeaderSize,char * initialFrame,int initialFrameType,uint32_t nInitialFrameSize,int nSkipKeyFrames,int bStdoutMode,int bLiveStream,int bRealtimeStream,int bHashes,int bOverrideBufferTime,uint32_t bufferTime,double * percent)443 Download(RTMP * rtmp,		// connected RTMP object
444 	 FILE * file, uint32_t dSeek, uint32_t dStopOffset, double duration, int bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, int bStdoutMode, int bLiveStream, int bRealtimeStream, int bHashes, int bOverrideBufferTime, uint32_t bufferTime, double *percent)	// percentage downloaded [out]
445 {
446   int32_t now, lastUpdate;
447   int bufferSize = 64 * 1024;
448   char *buffer;
449   int nRead = 0;
450   off_t size = ftello(file);
451   unsigned long lastPercent = 0;
452 
453   rtmp->m_read.timestamp = dSeek;
454 
455   *percent = 0.0;
456 
457   if (rtmp->m_read.timestamp)
458     {
459       RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp);
460     }
461 
462   if (bLiveStream)
463     {
464       RTMP_LogPrintf("Starting Live Stream\n");
465     }
466   else
467     {
468       // print initial status
469       // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
470       if (duration > 0)
471 	{
472 	  if ((double) rtmp->m_read.timestamp >= (double) duration * 999.0)
473 	    {
474 	      RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",
475 			(double) rtmp->m_read.timestamp / 1000.0,
476 			(double) duration / 1000.0);
477 	      return RD_SUCCESS;
478 	    }
479 	  else
480 	    {
481 	      *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
482 	      *percent = ((double) (int) (*percent * 10.0)) / 10.0;
483 	      RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
484 			bResume ? "Resuming" : "Starting",
485 			(double) size / 1024.0, (double) rtmp->m_read.timestamp / 1000.0,
486 			*percent);
487 	    }
488 	}
489       else
490 	{
491 	  RTMP_LogPrintf("%s download at: %.3f kB\n",
492 		    bResume ? "Resuming" : "Starting",
493 		    (double) size / 1024.0);
494 	}
495       if (bRealtimeStream)
496 	RTMP_LogPrintf("  in approximately realtime (disabled BUFX speedup hack)\n");
497     }
498 
499   if (dStopOffset > 0)
500     RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0);
501 
502   if (bResume && nInitialFrameSize > 0)
503     rtmp->m_read.flags |= RTMP_READ_RESUME;
504   rtmp->m_read.initialFrameType = initialFrameType;
505   rtmp->m_read.nResumeTS = dSeek;
506   rtmp->m_read.metaHeader = metaHeader;
507   rtmp->m_read.initialFrame = initialFrame;
508   rtmp->m_read.nMetaHeaderSize = nMetaHeaderSize;
509   rtmp->m_read.nInitialFrameSize = nInitialFrameSize;
510 
511   buffer = (char *) malloc(bufferSize);
512 
513   now = RTMP_GetTime();
514   lastUpdate = now - 1000;
515   do
516     {
517       nRead = RTMP_Read(rtmp, buffer, bufferSize);
518       //RTMP_LogPrintf("nRead: %d\n", nRead);
519       if (nRead > 0)
520 	{
521 	  if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
522 	      (size_t) nRead)
523 	    {
524 	      RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
525 	      free(buffer);
526 	      return RD_FAILED;
527 	    }
528 	  size += nRead;
529 
530 	  //RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
531 	  if (duration <= 0)	// if duration unknown try to get it from the stream (onMetaData)
532 	    duration = RTMP_GetDuration(rtmp);
533 
534 	  if (duration > 0)
535 	    {
536 	      // make sure we claim to have enough buffer time!
537 	      if (!bOverrideBufferTime && bufferTime < (duration * 1000.0))
538 		{
539 		  bufferTime = (uint32_t) (duration * 1000.0) + 5000;	// extra 5sec to make sure we've got enough
540 
541 		  RTMP_Log(RTMP_LOGDEBUG,
542 		      "Detected that buffer time is less than duration, resetting to: %dms",
543 		      bufferTime);
544 		  RTMP_SetBufferMS(rtmp, bufferTime);
545 		  RTMP_UpdateBufferMS(rtmp);
546 		}
547 	      *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
548 	      *percent = ((double) (int) (*percent * 10.0)) / 10.0;
549 	      if (bHashes)
550 		{
551 		  if (lastPercent + 1 <= *percent)
552 		    {
553 		      RTMP_LogStatus("#");
554 		      lastPercent = (unsigned long) *percent;
555 		    }
556 		}
557 	      else
558 		{
559 		  now = RTMP_GetTime();
560 		  if (abs(now - lastUpdate) > 200)
561 		    {
562 		      RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
563 				(double) size / 1024.0,
564 				(double) (rtmp->m_read.timestamp) / 1000.0, *percent);
565 		      lastUpdate = now;
566 		    }
567 		}
568 	    }
569 	  else
570 	    {
571 	      now = RTMP_GetTime();
572 	      if (abs(now - lastUpdate) > 200)
573 		{
574 		  if (bHashes)
575 		    RTMP_LogStatus("#");
576 		  else
577 		    RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
578 			      (double) (rtmp->m_read.timestamp) / 1000.0);
579 		  lastUpdate = now;
580 		}
581 	    }
582 	}
583       else
584 	{
585 #ifdef _DEBUG
586 	  RTMP_Log(RTMP_LOGDEBUG, "zero read!");
587 #endif
588 	  if (rtmp->m_read.status == RTMP_READ_EOF)
589 	    break;
590 	}
591 
592     }
593   while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
594   free(buffer);
595   if (nRead < 0)
596     nRead = rtmp->m_read.status;
597 
598   /* Final status update */
599   if (!bHashes)
600     {
601       if (duration > 0)
602 	{
603 	  *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
604 	  *percent = ((double) (int) (*percent * 10.0)) / 10.0;
605 	  RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
606 	    (double) size / 1024.0,
607 	    (double) (rtmp->m_read.timestamp) / 1000.0, *percent);
608 	}
609       else
610 	{
611 	  RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
612 	    (double) (rtmp->m_read.timestamp) / 1000.0);
613 	}
614     }
615 
616   RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead);
617 
618   if (bResume && nRead == -2)
619     {
620       RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
621 		nSkipKeyFrames + 1);
622       return RD_FAILED;
623     }
624 
625   if (nRead == -3)
626     return RD_SUCCESS;
627 
628   if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
629       || RTMP_IsTimedout(rtmp))
630     {
631       return RD_INCOMPLETE;
632     }
633 
634   return RD_SUCCESS;
635 }
636 
637 #define STR2AVAL(av,str)	av.av_val = str; av.av_len = strlen(av.av_val)
638 
usage(char * prog)639 void usage(char *prog)
640 {
641 	  RTMP_LogPrintf
642 	    ("\n%s: This program dumps the media content streamed over RTMP.\n\n", prog);
643 	  RTMP_LogPrintf("--help|-h               Prints this help screen.\n");
644 	  RTMP_LogPrintf
645 	    ("--url|-i url            URL with options included (e.g. rtmp://host[:port]/path swfUrl=url tcUrl=url)\n");
646 	  RTMP_LogPrintf
647 	    ("--rtmp|-r url           URL (e.g. rtmp://host[:port]/path)\n");
648 	  RTMP_LogPrintf
649 	    ("--host|-n hostname      Overrides the hostname in the rtmp url\n");
650 	  RTMP_LogPrintf
651 	    ("--port|-c port          Overrides the port in the rtmp url\n");
652 	  RTMP_LogPrintf
653 	    ("--socks|-S host:port    Use the specified SOCKS proxy\n");
654 	  RTMP_LogPrintf
655 	    ("--protocol|-l num       Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
656 	  RTMP_LogPrintf
657 	    ("--playpath|-y path      Overrides the playpath parsed from rtmp url\n");
658 	  RTMP_LogPrintf
659 	    ("--playlist|-Y           Set playlist before playing\n");
660 	  RTMP_LogPrintf("--swfUrl|-s url         URL to player swf file\n");
661 	  RTMP_LogPrintf
662 	    ("--tcUrl|-t url          URL to played stream (default: \"rtmp://host[:port]/app\")\n");
663 	  RTMP_LogPrintf("--pageUrl|-p url        Web URL of played programme\n");
664 	  RTMP_LogPrintf("--app|-a app            Name of target app on server\n");
665 #ifdef CRYPTO
666 	  RTMP_LogPrintf
667 	    ("--swfhash|-w hexstring  SHA256 hash of the decompressed SWF file (32 bytes)\n");
668 	  RTMP_LogPrintf
669 	    ("--swfsize|-x num        Size of the decompressed SWF file, required for SWFVerification\n");
670 	  RTMP_LogPrintf
671 	    ("--swfVfy|-W url         URL to player swf file, compute hash/size automatically\n");
672 	  RTMP_LogPrintf
673 	    ("--swfAge|-X days        Number of days to use cached SWF hash before refreshing\n");
674 #endif
675 	  RTMP_LogPrintf
676 	    ("--auth|-u string        Authentication string to be appended to the connect string\n");
677 	  RTMP_LogPrintf
678 	    ("--conn|-C type:data     Arbitrary AMF data to be appended to the connect string\n");
679 	  RTMP_LogPrintf
680 	    ("                        B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
681 	  RTMP_LogPrintf
682 	    ("                        Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
683 	  RTMP_LogPrintf
684 	    ("--flashVer|-f string    Flash version string (default: \"%s\")\n",
685 	     RTMP_DefaultFlashVer.av_val);
686 	  RTMP_LogPrintf
687 	    ("--live|-v               Save a live stream, no --resume (seeking) of live streams possible\n");
688 	  RTMP_LogPrintf
689 	    ("--subscribe|-d string   Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
690 	  RTMP_LogPrintf
691 	    ("--realtime|-R           Don't attempt to speed up download via the Pause/Unpause BUFX hack\n");
692 	  RTMP_LogPrintf
693 	    ("--flv|-o string         FLV output file name, if the file name is - print stream to stdout\n");
694 	  RTMP_LogPrintf
695 	    ("--resume|-e             Resume a partial RTMP download\n");
696 	  RTMP_LogPrintf
697 	    ("--timeout|-m num        Timeout connection num seconds (default: %u)\n",
698 	     DEF_TIMEOUT);
699 	  RTMP_LogPrintf
700 	    ("--start|-A num          Start at num seconds into stream (not valid when using --live)\n");
701 	  RTMP_LogPrintf
702 	    ("--stop|-B num           Stop at num seconds into stream\n");
703 	  RTMP_LogPrintf
704 	    ("--token|-T key          Key for SecureToken response\n");
705 	  RTMP_LogPrintf
706 	    ("--jtv|-j JSON           Authentication token for Justin.tv legacy servers\n");
707 	  RTMP_LogPrintf
708 	    ("--hashes|-#             Display progress with hashes, not with the byte counter\n");
709 	  RTMP_LogPrintf
710 	    ("--buffer|-b             Buffer time in milliseconds (default: %u)\n",
711 	     DEF_BUFTIME);
712 	  RTMP_LogPrintf
713 	    ("--skip|-k num           Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
714 	     DEF_SKIPFRM);
715 	  RTMP_LogPrintf
716 	    ("--quiet|-q              Suppresses all command output.\n");
717 	  RTMP_LogPrintf("--verbose|-V            Verbose command output.\n");
718 	  RTMP_LogPrintf("--debug|-z              Debug level command output.\n");
719 	  RTMP_LogPrintf
720 	    ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
721 	  RTMP_LogPrintf("packet.\n\n");
722 }
723 
724 int
main(int argc,char ** argv)725 main(int argc, char **argv)
726 {
727   extern char *optarg;
728 
729   int nStatus = RD_SUCCESS;
730   double percent = 0;
731   double duration = 0.0;
732 
733   int nSkipKeyFrames = DEF_SKIPFRM;	// skip this number of keyframes when resuming
734 
735   int bOverrideBufferTime = FALSE;	// if the user specifies a buffer time override this is true
736   int bStdoutMode = TRUE;	// if true print the stream directly to stdout, messages go to stderr
737   int bResume = FALSE;		// true in resume mode
738   uint32_t dSeek = 0;		// seek position in resume mode, 0 otherwise
739   uint32_t bufferTime = DEF_BUFTIME;
740 
741   // meta header and initial frame for the resume mode (they are read from the file and compared with
742   // the stream we are trying to continue
743   char *metaHeader = 0;
744   uint32_t nMetaHeaderSize = 0;
745 
746   // video keyframe for matching
747   char *initialFrame = 0;
748   uint32_t nInitialFrameSize = 0;
749   int initialFrameType = 0;	// tye: audio or video
750 
751   AVal hostname = { 0, 0 };
752   AVal playpath = { 0, 0 };
753   AVal subscribepath = { 0, 0 };
754   AVal usherToken = { 0, 0 }; //Justin.tv auth token
755   int port = -1;
756   int protocol = RTMP_PROTOCOL_UNDEFINED;
757   int retries = 0;
758   int bLiveStream = FALSE;	// is it a live stream? then we can't seek/resume
759   int bRealtimeStream = FALSE;  // If true, disable the BUFX hack (be patient)
760   int bHashes = FALSE;		// display byte counters not hashes by default
761 
762   long int timeout = DEF_TIMEOUT;	// timeout connection after 120 seconds
763   uint32_t dStartOffset = 0;	// seek position in non-live mode
764   uint32_t dStopOffset = 0;
765   RTMP rtmp = { 0 };
766 
767   AVal fullUrl = { 0, 0 };
768   AVal swfUrl = { 0, 0 };
769   AVal tcUrl = { 0, 0 };
770   AVal pageUrl = { 0, 0 };
771   AVal app = { 0, 0 };
772   AVal auth = { 0, 0 };
773   AVal swfHash = { 0, 0 };
774   uint32_t swfSize = 0;
775   AVal flashVer = { 0, 0 };
776   AVal sockshost = { 0, 0 };
777 
778 #ifdef CRYPTO
779   int swfAge = 30;	/* 30 days for SWF cache by default */
780   int swfVfy = 0;
781   unsigned char hash[RTMP_SWF_HASHLEN];
782 #endif
783 
784   char *flvFile = 0;
785 
786   signal(SIGINT, sigIntHandler);
787   signal(SIGTERM, sigIntHandler);
788 #ifndef WIN32
789   signal(SIGHUP, sigIntHandler);
790   signal(SIGPIPE, sigIntHandler);
791   signal(SIGQUIT, sigIntHandler);
792 #endif
793 
794   RTMP_debuglevel = RTMP_LOGINFO;
795 
796   // Check for --quiet option before printing any output
797   int index = 0;
798   while (index < argc)
799     {
800       if (strcmp(argv[index], "--quiet") == 0
801 	  || strcmp(argv[index], "-q") == 0)
802 	RTMP_debuglevel = RTMP_LOGCRIT;
803       index++;
804     }
805 
806   RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION);
807   RTMP_LogPrintf
808     ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
809 
810   if (!InitSockets())
811     {
812       RTMP_Log(RTMP_LOGERROR,
813 	  "Couldn't load sockets support on your platform, exiting!");
814       return RD_FAILED;
815     }
816 
817   /* sleep(30); */
818 
819   RTMP_Init(&rtmp);
820 
821   int opt;
822   struct option longopts[] = {
823     {"help", 0, NULL, 'h'},
824     {"host", 1, NULL, 'n'},
825     {"port", 1, NULL, 'c'},
826     {"socks", 1, NULL, 'S'},
827     {"protocol", 1, NULL, 'l'},
828     {"playpath", 1, NULL, 'y'},
829     {"playlist", 0, NULL, 'Y'},
830     {"url", 1, NULL, 'i'},
831     {"rtmp", 1, NULL, 'r'},
832     {"swfUrl", 1, NULL, 's'},
833     {"tcUrl", 1, NULL, 't'},
834     {"pageUrl", 1, NULL, 'p'},
835     {"app", 1, NULL, 'a'},
836     {"auth", 1, NULL, 'u'},
837     {"conn", 1, NULL, 'C'},
838 #ifdef CRYPTO
839     {"swfhash", 1, NULL, 'w'},
840     {"swfsize", 1, NULL, 'x'},
841     {"swfVfy", 1, NULL, 'W'},
842     {"swfAge", 1, NULL, 'X'},
843 #endif
844     {"flashVer", 1, NULL, 'f'},
845     {"live", 0, NULL, 'v'},
846     {"realtime", 0, NULL, 'R'},
847     {"flv", 1, NULL, 'o'},
848     {"resume", 0, NULL, 'e'},
849     {"timeout", 1, NULL, 'm'},
850     {"buffer", 1, NULL, 'b'},
851     {"skip", 1, NULL, 'k'},
852     {"subscribe", 1, NULL, 'd'},
853     {"start", 1, NULL, 'A'},
854     {"stop", 1, NULL, 'B'},
855     {"token", 1, NULL, 'T'},
856     {"hashes", 0, NULL, '#'},
857     {"debug", 0, NULL, 'z'},
858     {"quiet", 0, NULL, 'q'},
859     {"verbose", 0, NULL, 'V'},
860     {"jtv", 1, NULL, 'j'},
861     {0, 0, 0, 0}
862   };
863 
864   while ((opt =
865 	  getopt_long(argc, argv,
866 		      "hVveqzRr:s:t:i:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:",
867 		      longopts, NULL)) != -1)
868     {
869       switch (opt)
870 	{
871 	case 'h':
872 	  usage(argv[0]);
873 	  return RD_SUCCESS;
874 #ifdef CRYPTO
875 	case 'w':
876 	  {
877 	    int res = hex2bin(optarg, &swfHash.av_val);
878 	    if (res != RTMP_SWF_HASHLEN)
879 	      {
880 		swfHash.av_val = NULL;
881 		RTMP_Log(RTMP_LOGWARNING,
882 		    "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN);
883 	      }
884 	    swfHash.av_len = RTMP_SWF_HASHLEN;
885 	    break;
886 	  }
887 	case 'x':
888 	  {
889 	    int size = atoi(optarg);
890 	    if (size <= 0)
891 	      {
892 		RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
893 	      }
894 	    else
895 	      {
896 		swfSize = size;
897 	      }
898 	    break;
899 	  }
900         case 'W':
901 	  STR2AVAL(swfUrl, optarg);
902 	  swfVfy = 1;
903           break;
904         case 'X':
905 	  {
906 	    int num = atoi(optarg);
907 	    if (num < 0)
908 	      {
909 		RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
910 	      }
911 	    else
912 	      {
913 		swfAge = num;
914 	      }
915 	  }
916           break;
917 #endif
918 	case 'k':
919 	  nSkipKeyFrames = atoi(optarg);
920 	  if (nSkipKeyFrames < 0)
921 	    {
922 	      RTMP_Log(RTMP_LOGERROR,
923 		  "Number of keyframes skipped must be greater or equal zero, using zero!");
924 	      nSkipKeyFrames = 0;
925 	    }
926 	  else
927 	    {
928 	      RTMP_Log(RTMP_LOGDEBUG, "Number of skipped key frames for resume: %d",
929 		  nSkipKeyFrames);
930 	    }
931 	  break;
932 	case 'b':
933 	  {
934 	    int32_t bt = atol(optarg);
935 	    if (bt < 0)
936 	      {
937 		RTMP_Log(RTMP_LOGERROR,
938 		    "Buffer time must be greater than zero, ignoring the specified value %d!",
939 		    bt);
940 	      }
941 	    else
942 	      {
943 		bufferTime = bt;
944 		bOverrideBufferTime = TRUE;
945 	      }
946 	    break;
947 	  }
948 	case 'v':
949 	  bLiveStream = TRUE;	// no seeking or resuming possible!
950 	  break;
951 	case 'R':
952 	  bRealtimeStream = TRUE; // seeking and resuming is still possible
953 	  break;
954 	case 'd':
955 	  STR2AVAL(subscribepath, optarg);
956 	  break;
957 	case 'n':
958 	  STR2AVAL(hostname, optarg);
959 	  break;
960 	case 'c':
961 	  port = atoi(optarg);
962 	  break;
963 	case 'l':
964 	  protocol = atoi(optarg);
965 	  if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS)
966 	    {
967 	      RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol);
968 	      return RD_FAILED;
969 	    }
970 	  break;
971 	case 'y':
972 	  STR2AVAL(playpath, optarg);
973 	  break;
974 	case 'Y':
975 	  RTMP_SetOpt(&rtmp, &av_playlist, (AVal *)&av_true);
976 	  break;
977 	case 'r':
978 	  {
979 	    AVal parsedHost, parsedApp, parsedPlaypath;
980 	    unsigned int parsedPort = 0;
981 	    int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
982 
983 	    if (!RTMP_ParseURL
984 		(optarg, &parsedProtocol, &parsedHost, &parsedPort,
985 		 &parsedPlaypath, &parsedApp))
986 	      {
987 		RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
988 		    optarg);
989 	      }
990 	    else
991 	      {
992 		if (!hostname.av_len)
993 		  hostname = parsedHost;
994 		if (port == -1)
995 		  port = parsedPort;
996 		if (playpath.av_len == 0 && parsedPlaypath.av_len)
997 		  {
998 		    playpath = parsedPlaypath;
999 		  }
1000 		if (protocol == RTMP_PROTOCOL_UNDEFINED)
1001 		  protocol = parsedProtocol;
1002 		if (app.av_len == 0 && parsedApp.av_len)
1003 		  {
1004 		    app = parsedApp;
1005 		  }
1006 	      }
1007 	    break;
1008 	  }
1009 	case 'i':
1010 	  STR2AVAL(fullUrl, optarg);
1011           break;
1012 	case 's':
1013 	  STR2AVAL(swfUrl, optarg);
1014 	  break;
1015 	case 't':
1016 	  STR2AVAL(tcUrl, optarg);
1017 	  break;
1018 	case 'p':
1019 	  STR2AVAL(pageUrl, optarg);
1020 	  break;
1021 	case 'a':
1022 	  STR2AVAL(app, optarg);
1023 	  break;
1024 	case 'f':
1025 	  STR2AVAL(flashVer, optarg);
1026 	  break;
1027 	case 'o':
1028 	  flvFile = optarg;
1029 	  if (strcmp(flvFile, "-"))
1030 	    bStdoutMode = FALSE;
1031 
1032 	  break;
1033 	case 'e':
1034 	  bResume = TRUE;
1035 	  break;
1036 	case 'u':
1037 	  STR2AVAL(auth, optarg);
1038 	  break;
1039 	case 'C': {
1040 	  AVal av;
1041 	  STR2AVAL(av, optarg);
1042 	  if (!RTMP_SetOpt(&rtmp, &av_conn, &av))
1043 	    {
1044 	      RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg);
1045 	      return RD_FAILED;
1046 	    }
1047 	  }
1048 	  break;
1049 	case 'm':
1050 	  timeout = atoi(optarg);
1051 	  break;
1052 	case 'A':
1053 	  dStartOffset = (int) (atof(optarg) * 1000.0);
1054 	  break;
1055 	case 'B':
1056 	  dStopOffset = (int) (atof(optarg) * 1000.0);
1057 	  break;
1058 	case 'T': {
1059 	  AVal token;
1060 	  STR2AVAL(token, optarg);
1061 	  RTMP_SetOpt(&rtmp, &av_token, &token);
1062 	  }
1063 	  break;
1064 	case '#':
1065 	  bHashes = TRUE;
1066 	  break;
1067 	case 'q':
1068 	  RTMP_debuglevel = RTMP_LOGCRIT;
1069 	  break;
1070 	case 'V':
1071 	  RTMP_debuglevel = RTMP_LOGDEBUG;
1072 	  break;
1073 	case 'z':
1074 	  RTMP_debuglevel = RTMP_LOGALL;
1075 	  break;
1076 	case 'S':
1077 	  STR2AVAL(sockshost, optarg);
1078 	  break;
1079 	case 'j':
1080 	  STR2AVAL(usherToken, optarg);
1081 	  break;
1082 	default:
1083 	  RTMP_LogPrintf("unknown option: %c\n", opt);
1084 	  usage(argv[0]);
1085 	  return RD_FAILED;
1086 	  break;
1087 	}
1088     }
1089 
1090   if (!hostname.av_len && !fullUrl.av_len)
1091     {
1092       RTMP_Log(RTMP_LOGERROR,
1093 	  "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
1094       return RD_FAILED;
1095     }
1096   if (playpath.av_len == 0 && !fullUrl.av_len)
1097     {
1098       RTMP_Log(RTMP_LOGERROR,
1099 	  "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
1100       return RD_FAILED;
1101     }
1102 
1103   if (protocol == RTMP_PROTOCOL_UNDEFINED && !fullUrl.av_len)
1104     {
1105       RTMP_Log(RTMP_LOGWARNING,
1106 	  "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
1107       protocol = RTMP_PROTOCOL_RTMP;
1108     }
1109   if (port == -1 && !fullUrl.av_len)
1110     {
1111       RTMP_Log(RTMP_LOGWARNING,
1112 	  "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
1113       port = 0;
1114     }
1115   if (port == 0 && !fullUrl.av_len)
1116     {
1117       if (protocol & RTMP_FEATURE_SSL)
1118 	port = 443;
1119       else if (protocol & RTMP_FEATURE_HTTP)
1120 	port = 80;
1121       else
1122 	port = 1935;
1123     }
1124 
1125   if (flvFile == 0)
1126     {
1127       RTMP_Log(RTMP_LOGWARNING,
1128 	  "You haven't specified an output file (-o filename), using stdout");
1129       bStdoutMode = TRUE;
1130     }
1131 
1132   if (bStdoutMode && bResume)
1133     {
1134       RTMP_Log(RTMP_LOGWARNING,
1135 	  "Can't resume in stdout mode, ignoring --resume option");
1136       bResume = FALSE;
1137     }
1138 
1139   if (bLiveStream && bResume)
1140     {
1141       RTMP_Log(RTMP_LOGWARNING, "Can't resume live stream, ignoring --resume option");
1142       bResume = FALSE;
1143     }
1144 
1145 #ifdef CRYPTO
1146   if (swfVfy)
1147     {
1148       if (RTMP_HashSWF(swfUrl.av_val, &swfSize, hash, swfAge) == 0)
1149         {
1150           swfHash.av_val = (char *)hash;
1151           swfHash.av_len = RTMP_SWF_HASHLEN;
1152         }
1153     }
1154 
1155   if (swfHash.av_len == 0 && swfSize > 0)
1156     {
1157       RTMP_Log(RTMP_LOGWARNING,
1158 	  "Ignoring SWF size, supply also the hash with --swfhash");
1159       swfSize = 0;
1160     }
1161 
1162   if (swfHash.av_len != 0 && swfSize == 0)
1163     {
1164       RTMP_Log(RTMP_LOGWARNING,
1165 	  "Ignoring SWF hash, supply also the swf size  with --swfsize");
1166       swfHash.av_len = 0;
1167       swfHash.av_val = NULL;
1168     }
1169 #endif
1170 
1171   if (tcUrl.av_len == 0)
1172     {
1173 	  tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) +
1174 	  	hostname.av_len + app.av_len + sizeof("://:65535/");
1175       tcUrl.av_val = (char *) malloc(tcUrl.av_len);
1176 	  if (!tcUrl.av_val)
1177 	    return RD_FAILED;
1178       tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s",
1179 	  	   RTMPProtocolStringsLower[protocol], hostname.av_len,
1180 		   hostname.av_val, port, app.av_len, app.av_val);
1181     }
1182 
1183   int first = 1;
1184 
1185   // User defined seek offset
1186   if (dStartOffset > 0)
1187     {
1188       // Live stream
1189       if (bLiveStream)
1190 	{
1191 	  RTMP_Log(RTMP_LOGWARNING,
1192 	      "Can't seek in a live stream, ignoring --start option");
1193 	  dStartOffset = 0;
1194 	}
1195     }
1196 
1197   if (!fullUrl.av_len)
1198     {
1199       RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
1200 		       &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
1201 		       &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout);
1202     }
1203   else
1204     {
1205       if (RTMP_SetupURL(&rtmp, fullUrl.av_val) == FALSE)
1206         {
1207           RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", fullUrl.av_val);
1208           return RD_FAILED;
1209 	}
1210     }
1211 
1212   /* Try to keep the stream moving if it pauses on us */
1213   if (!bLiveStream && !bRealtimeStream && !(protocol & RTMP_FEATURE_HTTP))
1214     rtmp.Link.lFlags |= RTMP_LF_BUFX;
1215 
1216   off_t size = 0;
1217 
1218   // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1219   if (bResume)
1220     {
1221       nStatus =
1222 	OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize,
1223 		       &duration);
1224       if (nStatus == RD_FAILED)
1225 	goto clean;
1226 
1227       if (!file)
1228 	{
1229 	  // file does not exist, so go back into normal mode
1230 	  bResume = FALSE;	// we are back in fresh file mode (otherwise finalizing file won't be done)
1231 	}
1232       else
1233 	{
1234 	  nStatus = GetLastKeyframe(file, nSkipKeyFrames,
1235 				    &dSeek, &initialFrame,
1236 				    &initialFrameType, &nInitialFrameSize);
1237 	  if (nStatus == RD_FAILED)
1238 	    {
1239 	      RTMP_Log(RTMP_LOGDEBUG, "Failed to get last keyframe.");
1240 	      goto clean;
1241 	    }
1242 
1243 	  if (dSeek == 0)
1244 	    {
1245 	      RTMP_Log(RTMP_LOGDEBUG,
1246 		  "Last keyframe is first frame in stream, switching from resume to normal mode!");
1247 	      bResume = FALSE;
1248 	    }
1249 	}
1250     }
1251 
1252   if (!file)
1253     {
1254       if (bStdoutMode)
1255 	{
1256 	  file = stdout;
1257 	  SET_BINMODE(file);
1258 	}
1259       else
1260 	{
1261 	  file = fopen(flvFile, "w+b");
1262 	  if (file == 0)
1263 	    {
1264 	      RTMP_LogPrintf("Failed to open file! %s\n", flvFile);
1265 	      return RD_FAILED;
1266 	    }
1267 	}
1268     }
1269 
1270 #ifdef _DEBUG
1271   netstackdump = fopen("netstackdump", "wb");
1272   netstackdump_read = fopen("netstackdump_read", "wb");
1273 #endif
1274 
1275   while (!RTMP_ctrlC)
1276     {
1277       RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
1278       RTMP_SetBufferMS(&rtmp, bufferTime);
1279 
1280       if (first)
1281 	{
1282 	  first = 0;
1283 	  RTMP_LogPrintf("Connecting ...\n");
1284 
1285 	  if (!RTMP_Connect(&rtmp, NULL))
1286 	    {
1287 	      nStatus = RD_NO_CONNECT;
1288 	      break;
1289 	    }
1290 
1291 	  RTMP_Log(RTMP_LOGINFO, "Connected...");
1292 
1293 	  // User defined seek offset
1294 	  if (dStartOffset > 0)
1295 	    {
1296 	      // Don't need the start offset if resuming an existing file
1297 	      if (bResume)
1298 		{
1299 		  RTMP_Log(RTMP_LOGWARNING,
1300 		      "Can't seek a resumed stream, ignoring --start option");
1301 		  dStartOffset = 0;
1302 		}
1303 	      else
1304 		{
1305 		  dSeek = dStartOffset;
1306 		}
1307 	    }
1308 
1309 	  // Calculate the length of the stream to still play
1310 	  if (dStopOffset > 0)
1311 	    {
1312 	      // Quit if start seek is past required stop offset
1313 	      if (dStopOffset <= dSeek)
1314 		{
1315 		  RTMP_LogPrintf("Already Completed\n");
1316 		  nStatus = RD_SUCCESS;
1317 		  break;
1318 		}
1319 	    }
1320 
1321 	  if (!RTMP_ConnectStream(&rtmp, dSeek))
1322 	    {
1323 	      nStatus = RD_FAILED;
1324 	      break;
1325 	    }
1326 	}
1327       else
1328 	{
1329 	  nInitialFrameSize = 0;
1330 
1331           if (retries)
1332             {
1333 	      RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1334 	      if (!RTMP_IsTimedout(&rtmp))
1335 	        nStatus = RD_FAILED;
1336 	      else
1337 	        nStatus = RD_INCOMPLETE;
1338 	      break;
1339             }
1340 	  RTMP_Log(RTMP_LOGINFO, "Connection timed out, trying to resume.\n\n");
1341           /* Did we already try pausing, and it still didn't work? */
1342           if (rtmp.m_pausing == 3)
1343             {
1344               /* Only one try at reconnecting... */
1345               retries = 1;
1346               dSeek = rtmp.m_pauseStamp;
1347               if (dStopOffset > 0)
1348                 {
1349                   if (dStopOffset <= dSeek)
1350                     {
1351                       RTMP_LogPrintf("Already Completed\n");
1352 		      nStatus = RD_SUCCESS;
1353 		      break;
1354                     }
1355                 }
1356               if (!RTMP_ReconnectStream(&rtmp, dSeek))
1357                 {
1358 	          RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1359 	          if (!RTMP_IsTimedout(&rtmp))
1360 		    nStatus = RD_FAILED;
1361 	          else
1362 		    nStatus = RD_INCOMPLETE;
1363 	          break;
1364                 }
1365             }
1366 	  else if (!RTMP_ToggleStream(&rtmp))
1367 	    {
1368 	      RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1369 	      if (!RTMP_IsTimedout(&rtmp))
1370 		nStatus = RD_FAILED;
1371 	      else
1372 		nStatus = RD_INCOMPLETE;
1373 	      break;
1374 	    }
1375 	  bResume = TRUE;
1376 	}
1377 
1378       nStatus = Download(&rtmp, file, dSeek, dStopOffset, duration, bResume,
1379 			 metaHeader, nMetaHeaderSize, initialFrame,
1380 			 initialFrameType, nInitialFrameSize, nSkipKeyFrames,
1381 			 bStdoutMode, bLiveStream, bRealtimeStream, bHashes,
1382 			 bOverrideBufferTime, bufferTime, &percent);
1383       free(initialFrame);
1384       initialFrame = NULL;
1385 
1386       /* If we succeeded, we're done.
1387        */
1388       if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
1389 	break;
1390     }
1391 
1392   if (nStatus == RD_SUCCESS)
1393     {
1394       RTMP_LogPrintf("Download complete\n");
1395     }
1396   else if (nStatus == RD_INCOMPLETE)
1397     {
1398       RTMP_LogPrintf
1399 	("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1400 	 percent);
1401     }
1402 
1403 clean:
1404   RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
1405   RTMP_Close(&rtmp);
1406 
1407   if (file != 0)
1408     fclose(file);
1409 
1410   CleanupSockets();
1411 
1412 #ifdef _DEBUG
1413   if (netstackdump != 0)
1414     fclose(netstackdump);
1415   if (netstackdump_read != 0)
1416     fclose(netstackdump_read);
1417 #endif
1418   return nStatus;
1419 }
1420