]> granicus.if.org Git - rtmpdump/blob - rtmpdump.c
Add option --realtime (-R) to rtmpdump to disable the BUFX hack
[rtmpdump] / rtmpdump.c
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
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
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
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))
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
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
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
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 #ifdef _DEBUG
584       else
585         {
586           RTMP_Log(RTMP_LOGDEBUG, "zero read!");
587         }
588 #endif
589
590     }
591   while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
592   free(buffer);
593   if (nRead < 0)
594     nRead = rtmp->m_read.status;
595
596   /* Final status update */
597   if (!bHashes)
598     {
599       if (duration > 0)
600         {
601           *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
602           *percent = ((double) (int) (*percent * 10.0)) / 10.0;
603           RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
604             (double) size / 1024.0,
605             (double) (rtmp->m_read.timestamp) / 1000.0, *percent);
606         }
607       else
608         {
609           RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
610             (double) (rtmp->m_read.timestamp) / 1000.0);
611         }
612     }
613
614   RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead);
615
616   if (bResume && nRead == -2)
617     {
618       RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
619                 nSkipKeyFrames + 1);
620       return RD_FAILED;
621     }
622
623   if (nRead == -3)
624     return RD_SUCCESS;
625
626   if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
627       || RTMP_IsTimedout(rtmp))
628     {
629       return RD_INCOMPLETE;
630     }
631
632   return RD_SUCCESS;
633 }
634
635 #define STR2AVAL(av,str)        av.av_val = str; av.av_len = strlen(av.av_val)
636
637 void usage(char *prog)
638 {
639           RTMP_LogPrintf
640             ("\n%s: This program dumps the media content streamed over RTMP.\n\n", prog);
641           RTMP_LogPrintf("--help|-h               Prints this help screen.\n");
642           RTMP_LogPrintf
643             ("--rtmp|-r url           URL (e.g. rtmp://host[:port]/path)\n");
644           RTMP_LogPrintf
645             ("--host|-n hostname      Overrides the hostname in the rtmp url\n");
646           RTMP_LogPrintf
647             ("--port|-c port          Overrides the port in the rtmp url\n");
648           RTMP_LogPrintf
649             ("--socks|-S host:port    Use the specified SOCKS proxy\n");
650           RTMP_LogPrintf
651             ("--protocol|-l num       Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
652           RTMP_LogPrintf
653             ("--playpath|-y path      Overrides the playpath parsed from rtmp url\n");
654           RTMP_LogPrintf
655             ("--playlist|-Y           Set playlist before playing\n");
656           RTMP_LogPrintf("--swfUrl|-s url         URL to player swf file\n");
657           RTMP_LogPrintf
658             ("--tcUrl|-t url          URL to played stream (default: \"rtmp://host[:port]/app\")\n");
659           RTMP_LogPrintf("--pageUrl|-p url        Web URL of played programme\n");
660           RTMP_LogPrintf("--app|-a app            Name of target app on server\n");
661 #ifdef CRYPTO
662           RTMP_LogPrintf
663             ("--swfhash|-w hexstring  SHA256 hash of the decompressed SWF file (32 bytes)\n");
664           RTMP_LogPrintf
665             ("--swfsize|-x num        Size of the decompressed SWF file, required for SWFVerification\n");
666           RTMP_LogPrintf
667             ("--swfVfy|-W url         URL to player swf file, compute hash/size automatically\n");
668           RTMP_LogPrintf
669             ("--swfAge|-X days        Number of days to use cached SWF hash before refreshing\n");
670 #endif
671           RTMP_LogPrintf
672             ("--auth|-u string        Authentication string to be appended to the connect string\n");
673           RTMP_LogPrintf
674             ("--conn|-C type:data     Arbitrary AMF data to be appended to the connect string\n");
675           RTMP_LogPrintf
676             ("                        B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
677           RTMP_LogPrintf
678             ("                        Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
679           RTMP_LogPrintf
680             ("--flashVer|-f string    Flash version string (default: \"%s\")\n",
681              RTMP_DefaultFlashVer.av_val);
682           RTMP_LogPrintf
683             ("--live|-v               Save a live stream, no --resume (seeking) of live streams possible\n");
684           RTMP_LogPrintf
685             ("--subscribe|-d string   Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
686           RTMP_LogPrintf
687             ("--realtime|-R           Don't attempt to speed up download via the Pause/Unpause BUFX hack\n");
688           RTMP_LogPrintf
689             ("--flv|-o string         FLV output file name, if the file name is - print stream to stdout\n");
690           RTMP_LogPrintf
691             ("--resume|-e             Resume a partial RTMP download\n");
692           RTMP_LogPrintf
693             ("--timeout|-m num        Timeout connection num seconds (default: %u)\n",
694              DEF_TIMEOUT);
695           RTMP_LogPrintf
696             ("--start|-A num          Start at num seconds into stream (not valid when using --live)\n");
697           RTMP_LogPrintf
698             ("--stop|-B num           Stop at num seconds into stream\n");
699           RTMP_LogPrintf
700             ("--token|-T key          Key for SecureToken response\n");
701           RTMP_LogPrintf
702             ("--jtv|-j JSON           Authentication token for Justin.tv legacy servers\n");
703           RTMP_LogPrintf
704             ("--hashes|-#             Display progress with hashes, not with the byte counter\n");
705           RTMP_LogPrintf
706             ("--buffer|-b             Buffer time in milliseconds (default: %u)\n",
707              DEF_BUFTIME);
708           RTMP_LogPrintf
709             ("--skip|-k num           Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
710              DEF_SKIPFRM);
711           RTMP_LogPrintf
712             ("--quiet|-q              Suppresses all command output.\n");
713           RTMP_LogPrintf("--verbose|-V            Verbose command output.\n");
714           RTMP_LogPrintf("--debug|-z              Debug level command output.\n");
715           RTMP_LogPrintf
716             ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
717           RTMP_LogPrintf("packet.\n\n");
718 }
719
720 int
721 main(int argc, char **argv)
722 {
723   extern char *optarg;
724
725   int nStatus = RD_SUCCESS;
726   double percent = 0;
727   double duration = 0.0;
728
729   int nSkipKeyFrames = DEF_SKIPFRM;     // skip this number of keyframes when resuming
730
731   int bOverrideBufferTime = FALSE;      // if the user specifies a buffer time override this is true
732   int bStdoutMode = TRUE;       // if true print the stream directly to stdout, messages go to stderr
733   int bResume = FALSE;          // true in resume mode
734   uint32_t dSeek = 0;           // seek position in resume mode, 0 otherwise
735   uint32_t bufferTime = DEF_BUFTIME;
736
737   // meta header and initial frame for the resume mode (they are read from the file and compared with
738   // the stream we are trying to continue
739   char *metaHeader = 0;
740   uint32_t nMetaHeaderSize = 0;
741
742   // video keyframe for matching
743   char *initialFrame = 0;
744   uint32_t nInitialFrameSize = 0;
745   int initialFrameType = 0;     // tye: audio or video
746
747   AVal hostname = { 0, 0 };
748   AVal playpath = { 0, 0 };
749   AVal subscribepath = { 0, 0 };
750   AVal usherToken = { 0, 0 }; //Justin.tv auth token
751   int port = -1;
752   int protocol = RTMP_PROTOCOL_UNDEFINED;
753   int retries = 0;
754   int bLiveStream = FALSE;      // is it a live stream? then we can't seek/resume
755   int bRealtimeStream = FALSE;  // If true, disable the BUFX hack (be patient)
756   int bHashes = FALSE;          // display byte counters not hashes by default
757
758   long int timeout = DEF_TIMEOUT;       // timeout connection after 120 seconds
759   uint32_t dStartOffset = 0;    // seek position in non-live mode
760   uint32_t dStopOffset = 0;
761   RTMP rtmp = { 0 };
762
763   AVal swfUrl = { 0, 0 };
764   AVal tcUrl = { 0, 0 };
765   AVal pageUrl = { 0, 0 };
766   AVal app = { 0, 0 };
767   AVal auth = { 0, 0 };
768   AVal swfHash = { 0, 0 };
769   uint32_t swfSize = 0;
770   AVal flashVer = { 0, 0 };
771   AVal sockshost = { 0, 0 };
772
773 #ifdef CRYPTO
774   int swfAge = 30;      /* 30 days for SWF cache by default */
775   int swfVfy = 0;
776   unsigned char hash[RTMP_SWF_HASHLEN];
777 #endif
778
779   char *flvFile = 0;
780
781   signal(SIGINT, sigIntHandler);
782   signal(SIGTERM, sigIntHandler);
783 #ifndef WIN32
784   signal(SIGHUP, sigIntHandler);
785   signal(SIGPIPE, sigIntHandler);
786   signal(SIGQUIT, sigIntHandler);
787 #endif
788
789   RTMP_debuglevel = RTMP_LOGINFO;
790
791   // Check for --quiet option before printing any output
792   int index = 0;
793   while (index < argc)
794     {
795       if (strcmp(argv[index], "--quiet") == 0
796           || strcmp(argv[index], "-q") == 0)
797         RTMP_debuglevel = RTMP_LOGCRIT;
798       index++;
799     }
800
801   RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION);
802   RTMP_LogPrintf
803     ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
804
805   if (!InitSockets())
806     {
807       RTMP_Log(RTMP_LOGERROR,
808           "Couldn't load sockets support on your platform, exiting!");
809       return RD_FAILED;
810     }
811
812   /* sleep(30); */
813
814   RTMP_Init(&rtmp);
815
816   int opt;
817   struct option longopts[] = {
818     {"help", 0, NULL, 'h'},
819     {"host", 1, NULL, 'n'},
820     {"port", 1, NULL, 'c'},
821     {"socks", 1, NULL, 'S'},
822     {"protocol", 1, NULL, 'l'},
823     {"playpath", 1, NULL, 'y'},
824     {"playlist", 0, NULL, 'Y'},
825     {"rtmp", 1, NULL, 'r'},
826     {"swfUrl", 1, NULL, 's'},
827     {"tcUrl", 1, NULL, 't'},
828     {"pageUrl", 1, NULL, 'p'},
829     {"app", 1, NULL, 'a'},
830     {"auth", 1, NULL, 'u'},
831     {"conn", 1, NULL, 'C'},
832 #ifdef CRYPTO
833     {"swfhash", 1, NULL, 'w'},
834     {"swfsize", 1, NULL, 'x'},
835     {"swfVfy", 1, NULL, 'W'},
836     {"swfAge", 1, NULL, 'X'},
837 #endif
838     {"flashVer", 1, NULL, 'f'},
839     {"live", 0, NULL, 'v'},
840     {"realtime", 0, NULL, 'R'},
841     {"flv", 1, NULL, 'o'},
842     {"resume", 0, NULL, 'e'},
843     {"timeout", 1, NULL, 'm'},
844     {"buffer", 1, NULL, 'b'},
845     {"skip", 1, NULL, 'k'},
846     {"subscribe", 1, NULL, 'd'},
847     {"start", 1, NULL, 'A'},
848     {"stop", 1, NULL, 'B'},
849     {"token", 1, NULL, 'T'},
850     {"hashes", 0, NULL, '#'},
851     {"debug", 0, NULL, 'z'},
852     {"quiet", 0, NULL, 'q'},
853     {"verbose", 0, NULL, 'V'},
854     {"jtv", 1, NULL, 'j'},
855     {0, 0, 0, 0}
856   };
857
858   while ((opt =
859           getopt_long(argc, argv,
860                       "hVveqzRr:s:t:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:",
861                       longopts, NULL)) != -1)
862     {
863       switch (opt)
864         {
865         case 'h':
866           usage(argv[0]);
867           return RD_SUCCESS;
868 #ifdef CRYPTO
869         case 'w':
870           {
871             int res = hex2bin(optarg, &swfHash.av_val);
872             if (res != RTMP_SWF_HASHLEN)
873               {
874                 swfHash.av_val = NULL;
875                 RTMP_Log(RTMP_LOGWARNING,
876                     "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN);
877               }
878             swfHash.av_len = RTMP_SWF_HASHLEN;
879             break;
880           }
881         case 'x':
882           {
883             int size = atoi(optarg);
884             if (size <= 0)
885               {
886                 RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
887               }
888             else
889               {
890                 swfSize = size;
891               }
892             break;
893           }
894         case 'W':
895           STR2AVAL(swfUrl, optarg);
896           swfVfy = 1;
897           break;
898         case 'X':
899           {
900             int num = atoi(optarg);
901             if (num < 0)
902               {
903                 RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
904               }
905             else
906               {
907                 swfAge = num;
908               }
909           }
910           break;
911 #endif
912         case 'k':
913           nSkipKeyFrames = atoi(optarg);
914           if (nSkipKeyFrames < 0)
915             {
916               RTMP_Log(RTMP_LOGERROR,
917                   "Number of keyframes skipped must be greater or equal zero, using zero!");
918               nSkipKeyFrames = 0;
919             }
920           else
921             {
922               RTMP_Log(RTMP_LOGDEBUG, "Number of skipped key frames for resume: %d",
923                   nSkipKeyFrames);
924             }
925           break;
926         case 'b':
927           {
928             int32_t bt = atol(optarg);
929             if (bt < 0)
930               {
931                 RTMP_Log(RTMP_LOGERROR,
932                     "Buffer time must be greater than zero, ignoring the specified value %d!",
933                     bt);
934               }
935             else
936               {
937                 bufferTime = bt;
938                 bOverrideBufferTime = TRUE;
939               }
940             break;
941           }
942         case 'v':
943           bLiveStream = TRUE;   // no seeking or resuming possible!
944           break;
945         case 'R':
946           bRealtimeStream = TRUE; // seeking and resuming is still possible
947           break;
948         case 'd':
949           STR2AVAL(subscribepath, optarg);
950           break;
951         case 'n':
952           STR2AVAL(hostname, optarg);
953           break;
954         case 'c':
955           port = atoi(optarg);
956           break;
957         case 'l':
958           protocol = atoi(optarg);
959           if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS)
960             {
961               RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol);
962               return RD_FAILED;
963             }
964           break;
965         case 'y':
966           STR2AVAL(playpath, optarg);
967           break;
968         case 'Y':
969           RTMP_SetOpt(&rtmp, &av_playlist, (AVal *)&av_true);
970           break;
971         case 'r':
972           {
973             AVal parsedHost, parsedApp, parsedPlaypath;
974             unsigned int parsedPort = 0;
975             int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
976
977             if (!RTMP_ParseURL
978                 (optarg, &parsedProtocol, &parsedHost, &parsedPort,
979                  &parsedPlaypath, &parsedApp))
980               {
981                 RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
982                     optarg);
983               }
984             else
985               {
986                 if (!hostname.av_len)
987                   hostname = parsedHost;
988                 if (port == -1)
989                   port = parsedPort;
990                 if (playpath.av_len == 0 && parsedPlaypath.av_len)
991                   {
992                     playpath = parsedPlaypath;
993                   }
994                 if (protocol == RTMP_PROTOCOL_UNDEFINED)
995                   protocol = parsedProtocol;
996                 if (app.av_len == 0 && parsedApp.av_len)
997                   {
998                     app = parsedApp;
999                   }
1000               }
1001             break;
1002           }
1003         case 's':
1004           STR2AVAL(swfUrl, optarg);
1005           break;
1006         case 't':
1007           STR2AVAL(tcUrl, optarg);
1008           break;
1009         case 'p':
1010           STR2AVAL(pageUrl, optarg);
1011           break;
1012         case 'a':
1013           STR2AVAL(app, optarg);
1014           break;
1015         case 'f':
1016           STR2AVAL(flashVer, optarg);
1017           break;
1018         case 'o':
1019           flvFile = optarg;
1020           if (strcmp(flvFile, "-"))
1021             bStdoutMode = FALSE;
1022
1023           break;
1024         case 'e':
1025           bResume = TRUE;
1026           break;
1027         case 'u':
1028           STR2AVAL(auth, optarg);
1029           break;
1030         case 'C': {
1031           AVal av;
1032           STR2AVAL(av, optarg);
1033           if (!RTMP_SetOpt(&rtmp, &av_conn, &av))
1034             {
1035               RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg);
1036               return RD_FAILED;
1037             }
1038           }
1039           break;
1040         case 'm':
1041           timeout = atoi(optarg);
1042           break;
1043         case 'A':
1044           dStartOffset = (int) (atof(optarg) * 1000.0);
1045           break;
1046         case 'B':
1047           dStopOffset = (int) (atof(optarg) * 1000.0);
1048           break;
1049         case 'T': {
1050           AVal token;
1051           STR2AVAL(token, optarg);
1052           RTMP_SetOpt(&rtmp, &av_token, &token);
1053           }
1054           break;
1055         case '#':
1056           bHashes = TRUE;
1057           break;
1058         case 'q':
1059           RTMP_debuglevel = RTMP_LOGCRIT;
1060           break;
1061         case 'V':
1062           RTMP_debuglevel = RTMP_LOGDEBUG;
1063           break;
1064         case 'z':
1065           RTMP_debuglevel = RTMP_LOGALL;
1066           break;
1067         case 'S':
1068           STR2AVAL(sockshost, optarg);
1069           break;
1070         case 'j':
1071           STR2AVAL(usherToken, optarg);
1072           break;
1073         default:
1074           RTMP_LogPrintf("unknown option: %c\n", opt);
1075           usage(argv[0]);
1076           return RD_FAILED;
1077           break;
1078         }
1079     }
1080
1081   if (!hostname.av_len)
1082     {
1083       RTMP_Log(RTMP_LOGERROR,
1084           "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
1085       return RD_FAILED;
1086     }
1087   if (playpath.av_len == 0)
1088     {
1089       RTMP_Log(RTMP_LOGERROR,
1090           "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
1091       return RD_FAILED;
1092     }
1093
1094   if (protocol == RTMP_PROTOCOL_UNDEFINED)
1095     {
1096       RTMP_Log(RTMP_LOGWARNING,
1097           "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
1098       protocol = RTMP_PROTOCOL_RTMP;
1099     }
1100   if (port == -1)
1101     {
1102       RTMP_Log(RTMP_LOGWARNING,
1103           "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
1104       port = 0;
1105     }
1106   if (port == 0)
1107     {
1108       if (protocol & RTMP_FEATURE_SSL)
1109         port = 443;
1110       else if (protocol & RTMP_FEATURE_HTTP)
1111         port = 80;
1112       else
1113         port = 1935;
1114     }
1115
1116   if (flvFile == 0)
1117     {
1118       RTMP_Log(RTMP_LOGWARNING,
1119           "You haven't specified an output file (-o filename), using stdout");
1120       bStdoutMode = TRUE;
1121     }
1122
1123   if (bStdoutMode && bResume)
1124     {
1125       RTMP_Log(RTMP_LOGWARNING,
1126           "Can't resume in stdout mode, ignoring --resume option");
1127       bResume = FALSE;
1128     }
1129
1130   if (bLiveStream && bResume)
1131     {
1132       RTMP_Log(RTMP_LOGWARNING, "Can't resume live stream, ignoring --resume option");
1133       bResume = FALSE;
1134     }
1135
1136 #ifdef CRYPTO
1137   if (swfVfy)
1138     {
1139       if (RTMP_HashSWF(swfUrl.av_val, &swfSize, hash, swfAge) == 0)
1140         {
1141           swfHash.av_val = (char *)hash;
1142           swfHash.av_len = RTMP_SWF_HASHLEN;
1143         }
1144     }
1145
1146   if (swfHash.av_len == 0 && swfSize > 0)
1147     {
1148       RTMP_Log(RTMP_LOGWARNING,
1149           "Ignoring SWF size, supply also the hash with --swfhash");
1150       swfSize = 0;
1151     }
1152
1153   if (swfHash.av_len != 0 && swfSize == 0)
1154     {
1155       RTMP_Log(RTMP_LOGWARNING,
1156           "Ignoring SWF hash, supply also the swf size  with --swfsize");
1157       swfHash.av_len = 0;
1158       swfHash.av_val = NULL;
1159     }
1160 #endif
1161
1162   if (tcUrl.av_len == 0)
1163     {
1164           tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) +
1165                 hostname.av_len + app.av_len + sizeof("://:65535/");
1166       tcUrl.av_val = (char *) malloc(tcUrl.av_len);
1167           if (!tcUrl.av_val)
1168             return RD_FAILED;
1169       tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s",
1170                    RTMPProtocolStringsLower[protocol], hostname.av_len,
1171                    hostname.av_val, port, app.av_len, app.av_val);
1172     }
1173
1174   int first = 1;
1175
1176   // User defined seek offset
1177   if (dStartOffset > 0)
1178     {
1179       // Live stream
1180       if (bLiveStream)
1181         {
1182           RTMP_Log(RTMP_LOGWARNING,
1183               "Can't seek in a live stream, ignoring --start option");
1184           dStartOffset = 0;
1185         }
1186     }
1187
1188   RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
1189                    &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
1190                    &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout);
1191
1192   /* Try to keep the stream moving if it pauses on us */
1193   if (!bLiveStream && !bRealtimeStream && !(protocol & RTMP_FEATURE_HTTP))
1194     rtmp.Link.lFlags |= RTMP_LF_BUFX;
1195
1196   off_t size = 0;
1197
1198   // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1199   if (bResume)
1200     {
1201       nStatus =
1202         OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize,
1203                        &duration);
1204       if (nStatus == RD_FAILED)
1205         goto clean;
1206
1207       if (!file)
1208         {
1209           // file does not exist, so go back into normal mode
1210           bResume = FALSE;      // we are back in fresh file mode (otherwise finalizing file won't be done)
1211         }
1212       else
1213         {
1214           nStatus = GetLastKeyframe(file, nSkipKeyFrames,
1215                                     &dSeek, &initialFrame,
1216                                     &initialFrameType, &nInitialFrameSize);
1217           if (nStatus == RD_FAILED)
1218             {
1219               RTMP_Log(RTMP_LOGDEBUG, "Failed to get last keyframe.");
1220               goto clean;
1221             }
1222
1223           if (dSeek == 0)
1224             {
1225               RTMP_Log(RTMP_LOGDEBUG,
1226                   "Last keyframe is first frame in stream, switching from resume to normal mode!");
1227               bResume = FALSE;
1228             }
1229         }
1230     }
1231
1232   if (!file)
1233     {
1234       if (bStdoutMode)
1235         {
1236           file = stdout;
1237           SET_BINMODE(file);
1238         }
1239       else
1240         {
1241           file = fopen(flvFile, "w+b");
1242           if (file == 0)
1243             {
1244               RTMP_LogPrintf("Failed to open file! %s\n", flvFile);
1245               return RD_FAILED;
1246             }
1247         }
1248     }
1249
1250 #ifdef _DEBUG
1251   netstackdump = fopen("netstackdump", "wb");
1252   netstackdump_read = fopen("netstackdump_read", "wb");
1253 #endif
1254
1255   while (!RTMP_ctrlC)
1256     {
1257       RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
1258       RTMP_SetBufferMS(&rtmp, bufferTime);
1259
1260       if (first)
1261         {
1262           first = 0;
1263           RTMP_LogPrintf("Connecting ...\n");
1264
1265           if (!RTMP_Connect(&rtmp, NULL))
1266             {
1267               nStatus = RD_NO_CONNECT;
1268               break;
1269             }
1270
1271           RTMP_Log(RTMP_LOGINFO, "Connected...");
1272
1273           // User defined seek offset
1274           if (dStartOffset > 0)
1275             {
1276               // Don't need the start offset if resuming an existing file
1277               if (bResume)
1278                 {
1279                   RTMP_Log(RTMP_LOGWARNING,
1280                       "Can't seek a resumed stream, ignoring --start option");
1281                   dStartOffset = 0;
1282                 }
1283               else
1284                 {
1285                   dSeek = dStartOffset;
1286                 }
1287             }
1288
1289           // Calculate the length of the stream to still play
1290           if (dStopOffset > 0)
1291             {
1292               // Quit if start seek is past required stop offset
1293               if (dStopOffset <= dSeek)
1294                 {
1295                   RTMP_LogPrintf("Already Completed\n");
1296                   nStatus = RD_SUCCESS;
1297                   break;
1298                 }
1299             }
1300
1301           if (!RTMP_ConnectStream(&rtmp, dSeek))
1302             {
1303               nStatus = RD_FAILED;
1304               break;
1305             }
1306         }
1307       else
1308         {
1309           nInitialFrameSize = 0;
1310
1311           if (retries)
1312             {
1313               RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1314               if (!RTMP_IsTimedout(&rtmp))
1315                 nStatus = RD_FAILED;
1316               else
1317                 nStatus = RD_INCOMPLETE;
1318               break;
1319             }
1320           RTMP_Log(RTMP_LOGINFO, "Connection timed out, trying to resume.\n\n");
1321           /* Did we already try pausing, and it still didn't work? */
1322           if (rtmp.m_pausing == 3)
1323             {
1324               /* Only one try at reconnecting... */
1325               retries = 1;
1326               dSeek = rtmp.m_pauseStamp;
1327               if (dStopOffset > 0)
1328                 {
1329                   if (dStopOffset <= dSeek)
1330                     {
1331                       RTMP_LogPrintf("Already Completed\n");
1332                       nStatus = RD_SUCCESS;
1333                       break;
1334                     }
1335                 }
1336               if (!RTMP_ReconnectStream(&rtmp, dSeek))
1337                 {
1338                   RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1339                   if (!RTMP_IsTimedout(&rtmp))
1340                     nStatus = RD_FAILED;
1341                   else
1342                     nStatus = RD_INCOMPLETE;
1343                   break;
1344                 }
1345             }
1346           else if (!RTMP_ToggleStream(&rtmp))
1347             {
1348               RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1349               if (!RTMP_IsTimedout(&rtmp))
1350                 nStatus = RD_FAILED;
1351               else
1352                 nStatus = RD_INCOMPLETE;
1353               break;
1354             }
1355           bResume = TRUE;
1356         }
1357
1358       nStatus = Download(&rtmp, file, dSeek, dStopOffset, duration, bResume,
1359                          metaHeader, nMetaHeaderSize, initialFrame,
1360                          initialFrameType, nInitialFrameSize, nSkipKeyFrames,
1361                          bStdoutMode, bLiveStream, bRealtimeStream, bHashes,
1362                          bOverrideBufferTime, bufferTime, &percent);
1363       free(initialFrame);
1364       initialFrame = NULL;
1365
1366       /* If we succeeded, we're done.
1367        */
1368       if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
1369         break;
1370     }
1371
1372   if (nStatus == RD_SUCCESS)
1373     {
1374       RTMP_LogPrintf("Download complete\n");
1375     }
1376   else if (nStatus == RD_INCOMPLETE)
1377     {
1378       RTMP_LogPrintf
1379         ("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1380          percent);
1381     }
1382
1383 clean:
1384   RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
1385   RTMP_Close(&rtmp);
1386
1387   if (file != 0)
1388     fclose(file);
1389
1390   CleanupSockets();
1391
1392 #ifdef _DEBUG
1393   if (netstackdump != 0)
1394     fclose(netstackdump);
1395   if (netstackdump_read != 0)
1396     fclose(netstackdump_read);
1397 #endif
1398   return nStatus;
1399 }