2 * Copyright (C) 2009 Andrej Stepanchuk
3 * Copyright (C) 2009 Howard Chu
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)
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.
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
23 #define _FILE_OFFSET_BITS 64
30 #include <signal.h> // to catch Ctrl-C
33 #include "librtmp/rtmp_sys.h"
34 #include "librtmp/log.h"
37 #define fseeko fseeko64
38 #define ftello ftello64
41 #define SET_BINMODE(f) setmode(fileno(f), O_BINARY)
43 #define SET_BINMODE(f)
48 #define RD_INCOMPLETE 2
50 #define DEF_TIMEOUT 30 /* seconds */
51 #define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */
62 version
= MAKEWORD(1, 1);
63 return (WSAStartup(version
, &wsaData
) == 0);
81 FILE *netstackdump
= 0;
82 FILE *netstackdump_read
= 0;
85 uint32_t nIgnoredFlvFrameCounter
= 0;
86 uint32_t nIgnoredFrameCounter
= 0;
87 #define MAX_IGNORED_FRAMES 50
92 sigIntHandler(int sig
)
95 RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig
);
96 // ignore all these signals now and let the connection close
97 signal(SIGINT
, SIG_IGN
);
98 signal(SIGTERM
, SIG_IGN
);
100 signal(SIGHUP
, SIG_IGN
);
101 signal(SIGPIPE
, SIG_IGN
);
102 signal(SIGQUIT
, SIG_IGN
);
106 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
107 int hex2bin(char *str
, char **hex
)
110 int i
, l
= strlen(str
);
121 *ptr
++ = (HEX2BIN(str
[i
]) << 4) | HEX2BIN(str
[i
+1]);
125 static const AVal av_onMetaData
= AVC("onMetaData");
126 static const AVal av_duration
= AVC("duration");
127 static const AVal av_conn
= AVC("conn");
128 static const AVal av_token
= AVC("token");
129 static const AVal av_playlist
= AVC("playlist");
130 static const AVal av_true
= AVC("true");
133 OpenResumeFile(const char *flvFile
, // file name [in]
134 FILE ** file
, // opened file [out]
135 off_t
* size
, // size of the file [out]
136 char **metaHeader
, // meta data read from the file [out]
137 uint32_t * nMetaHeaderSize
, // length of metaHeader [out]
138 double *duration
) // duration of the stream in ms [out]
140 size_t bufferSize
= 0;
141 char hbuf
[16], *buffer
= NULL
;
143 *nMetaHeaderSize
= 0;
146 *file
= fopen(flvFile
, "r+b");
148 return RD_SUCCESS
; // RD_SUCCESS, because we go to fresh file mode instead of quiting
150 fseek(*file
, 0, SEEK_END
);
151 *size
= ftello(*file
);
152 fseek(*file
, 0, SEEK_SET
);
156 // verify FLV format and read header
157 uint32_t prevTagSize
= 0;
159 // check we've got a valid FLV file to continue!
160 if (fread(hbuf
, 1, 13, *file
) != 13)
162 RTMP_Log(RTMP_LOGERROR
, "Couldn't read FLV file header!");
165 if (hbuf
[0] != 'F' || hbuf
[1] != 'L' || hbuf
[2] != 'V'
168 RTMP_Log(RTMP_LOGERROR
, "Invalid FLV file!");
172 if ((hbuf
[4] & 0x05) == 0)
174 RTMP_Log(RTMP_LOGERROR
,
175 "FLV file contains neither video nor audio, aborting!");
179 uint32_t dataOffset
= AMF_DecodeInt32(hbuf
+ 5);
180 fseek(*file
, dataOffset
, SEEK_SET
);
182 if (fread(hbuf
, 1, 4, *file
) != 4)
184 RTMP_Log(RTMP_LOGERROR
, "Invalid FLV file: missing first prevTagSize!");
187 prevTagSize
= AMF_DecodeInt32(hbuf
);
188 if (prevTagSize
!= 0)
190 RTMP_Log(RTMP_LOGWARNING
,
191 "First prevTagSize is not zero: prevTagSize = 0x%08X",
195 // go through the file to find the meta data!
196 off_t pos
= dataOffset
+ 4;
197 int bFoundMetaHeader
= FALSE
;
199 while (pos
< *size
- 4 && !bFoundMetaHeader
)
201 fseeko(*file
, pos
, SEEK_SET
);
202 if (fread(hbuf
, 1, 4, *file
) != 4)
205 uint32_t dataSize
= AMF_DecodeInt24(hbuf
+ 1);
209 if (dataSize
> bufferSize
)
211 /* round up to next page boundary */
212 bufferSize
= dataSize
+ 4095;
213 bufferSize
^= (bufferSize
& 4095);
215 buffer
= malloc(bufferSize
);
220 fseeko(*file
, pos
+ 11, SEEK_SET
);
221 if (fread(buffer
, 1, dataSize
, *file
) != dataSize
)
225 int nRes
= AMF_Decode(&metaObj
, buffer
, dataSize
, FALSE
);
228 RTMP_Log(RTMP_LOGERROR
, "%s, error decoding meta data packet",
234 AMFProp_GetString(AMF_GetProp(&metaObj
, NULL
, 0), &metastring
);
236 if (AVMATCH(&metastring
, &av_onMetaData
))
240 *nMetaHeaderSize
= dataSize
;
243 *metaHeader
= (char *) malloc(*nMetaHeaderSize
);
244 memcpy(*metaHeader
, buffer
, *nMetaHeaderSize
);
247 AMFObjectProperty prop
;
248 if (RTMP_FindFirstMatchingProperty
249 (&metaObj
, &av_duration
, &prop
))
251 *duration
= AMFProp_GetNumber(&prop
);
252 RTMP_Log(RTMP_LOGDEBUG
, "File has duration: %f", *duration
);
255 bFoundMetaHeader
= TRUE
;
261 pos
+= (dataSize
+ 11 + 4);
265 if (!bFoundMetaHeader
)
266 RTMP_Log(RTMP_LOGWARNING
, "Couldn't locate meta data!");
273 GetLastKeyframe(FILE * file
, // output file [in]
274 int nSkipKeyFrames
, // max number of frames to skip when searching for key frame [in]
275 uint32_t * dSeek
, // offset of the last key frame [out]
276 char **initialFrame
, // content of the last keyframe [out]
277 int *initialFrameType
, // initial frame type (audio/video) [out]
278 uint32_t * nInitialFrameSize
) // length of initialFrame [out]
280 const size_t bufferSize
= 16;
281 char buffer
[bufferSize
];
286 fseek(file
, 0, SEEK_END
);
289 fseek(file
, 4, SEEK_SET
);
290 if (fread(&dataType
, sizeof(uint8_t), 1, file
) != 1)
293 bAudioOnly
= (dataType
& 0x4) && !(dataType
& 0x1);
295 RTMP_Log(RTMP_LOGDEBUG
, "bAudioOnly: %d, size: %llu", bAudioOnly
,
296 (unsigned long long) size
);
298 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
300 //if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
302 // find the last seekable frame
304 uint32_t prevTagSize
= 0;
306 // go through the file and find the last video keyframe
311 if (size
- tsize
< 13)
313 RTMP_Log(RTMP_LOGERROR
,
314 "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
317 fseeko(file
, size
- tsize
- 4, SEEK_SET
);
318 xread
= fread(buffer
, 1, 4, file
);
321 RTMP_Log(RTMP_LOGERROR
, "Couldn't read prevTagSize from file!");
325 prevTagSize
= AMF_DecodeInt32(buffer
);
326 //RTMP_Log(RTMP_LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);
328 if (prevTagSize
== 0)
330 RTMP_Log(RTMP_LOGERROR
, "Couldn't find keyframe to resume from!");
334 if (prevTagSize
< 0 || prevTagSize
> size
- 4 - 13)
336 RTMP_Log(RTMP_LOGERROR
,
337 "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!",
341 tsize
+= prevTagSize
+ 4;
344 fseeko(file
, size
- tsize
, SEEK_SET
);
345 if (fread(buffer
, 1, 12, file
) != 12)
347 RTMP_Log(RTMP_LOGERROR
, "Couldn't read header!");
352 uint32_t ts
= AMF_DecodeInt24(buffer
+ 4);
353 ts
|= (buffer
[7] << 24);
354 RTMP_Log(RTMP_LOGDEBUG
, "%02X: TS: %d ms", buffer
[0], ts
);
357 // this just continues the loop whenever the number of skipped frames is > 0,
358 // so we look for the next keyframe to continue with
360 // this helps if resuming from the last keyframe fails and one doesn't want to start
361 // the download from the beginning
363 if (nSkipKeyFrames
> 0
365 && (buffer
[0] != 0x09 || (buffer
[11] & 0xf0) != 0x10)))
368 RTMP_Log(RTMP_LOGDEBUG
,
369 "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
376 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 // save keyframe to compare/find position in stream
379 *initialFrameType
= buffer
[0];
380 *nInitialFrameSize
= prevTagSize
- 11;
381 *initialFrame
= (char *) malloc(*nInitialFrameSize
);
383 fseeko(file
, size
- tsize
+ 11, SEEK_SET
);
384 if (fread(*initialFrame
, 1, *nInitialFrameSize
, file
) != *nInitialFrameSize
)
386 RTMP_Log(RTMP_LOGERROR
, "Couldn't read last keyframe, aborting!");
390 *dSeek
= AMF_DecodeInt24(buffer
+ 4); // set seek position to keyframe tmestamp
391 *dSeek
|= (buffer
[7] << 24);
393 //else // handle audio only, we can seek anywhere we'd like
399 RTMP_Log(RTMP_LOGERROR
,
400 "Last keyframe timestamp is negative, aborting, your file is corrupt!");
403 RTMP_Log(RTMP_LOGDEBUG
, "Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek
,
404 *nInitialFrameSize
, *initialFrameType
);
407 // now read the timestamp of the frame before the seekable keyframe:
408 fseeko(file, size-tsize-4, SEEK_SET);
409 if(fread(buffer, 1, 4, file) != 4) {
410 RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!");
413 uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer);
414 fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET);
415 if(fread(buffer, 1, 4, file) != 4) {
416 RTMP_Log(RTMP_LOGERROR, "Couldn't read previous timestamp!");
419 uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
420 timestamp |= (buffer[3]<<24);
422 RTMP_Log(RTMP_LOGDEBUG, "Previous timestamp: %d ms", timestamp);
427 // seek to position after keyframe in our file (we will ignore the keyframes resent by the server
428 // since they are sent a couple of times and handling this would be a mess)
429 fseeko(file
, size
- tsize
+ prevTagSize
+ 4, SEEK_SET
);
431 // make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets
432 // (including several meta data headers and the keyframe we seeked to)
433 //bNoHeader = TRUE; if bResume==true this is true anyway
442 Download(RTMP
* rtmp
, // connected RTMP object
443 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 bHashes
, int bOverrideBufferTime
, uint32_t bufferTime
, double *percent
) // percentage downloaded [out]
445 int32_t now
, lastUpdate
;
446 int bufferSize
= 64 * 1024;
447 char *buffer
= (char *) malloc(bufferSize
);
449 off_t size
= ftello(file
);
450 unsigned long lastPercent
= 0;
452 rtmp
->m_read
.timestamp
= dSeek
;
456 if (rtmp
->m_read
.timestamp
)
458 RTMP_Log(RTMP_LOGDEBUG
, "Continuing at TS: %d ms\n", rtmp
->m_read
.timestamp
);
463 RTMP_LogPrintf("Starting Live Stream\n");
467 // print initial status
468 // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
471 if ((double) rtmp
->m_read
.timestamp
>= (double) duration
* 999.0)
473 RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",
474 (double) rtmp
->m_read
.timestamp
/ 1000.0,
475 (double) duration
/ 1000.0);
480 *percent
= ((double) rtmp
->m_read
.timestamp
) / (duration
* 1000.0) * 100.0;
481 *percent
= ((double) (int) (*percent
* 10.0)) / 10.0;
482 RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
483 bResume
? "Resuming" : "Starting",
484 (double) size
/ 1024.0, (double) rtmp
->m_read
.timestamp
/ 1000.0,
490 RTMP_LogPrintf("%s download at: %.3f kB\n",
491 bResume
? "Resuming" : "Starting",
492 (double) size
/ 1024.0);
497 RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset
- dSeek
) / 1000.0);
499 if (bResume
&& nInitialFrameSize
> 0)
500 rtmp
->m_read
.flags
|= RTMP_READ_RESUME
;
501 rtmp
->m_read
.initialFrameType
= initialFrameType
;
502 rtmp
->m_read
.nResumeTS
= dSeek
;
503 rtmp
->m_read
.metaHeader
= metaHeader
;
504 rtmp
->m_read
.initialFrame
= initialFrame
;
505 rtmp
->m_read
.nMetaHeaderSize
= nMetaHeaderSize
;
506 rtmp
->m_read
.nInitialFrameSize
= nInitialFrameSize
;
508 now
= RTMP_GetTime();
509 lastUpdate
= now
- 1000;
512 nRead
= RTMP_Read(rtmp
, buffer
, bufferSize
);
513 //RTMP_LogPrintf("nRead: %d\n", nRead);
516 if (fwrite(buffer
, sizeof(unsigned char), nRead
, file
) !=
519 RTMP_Log(RTMP_LOGERROR
, "%s: Failed writing, exiting!", __FUNCTION__
);
525 //RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
526 if (duration
<= 0) // if duration unknown try to get it from the stream (onMetaData)
527 duration
= RTMP_GetDuration(rtmp
);
531 // make sure we claim to have enough buffer time!
532 if (!bOverrideBufferTime
&& bufferTime
< (duration
* 1000.0))
534 bufferTime
= (uint32_t) (duration
* 1000.0) + 5000; // extra 5sec to make sure we've got enough
536 RTMP_Log(RTMP_LOGDEBUG
,
537 "Detected that buffer time is less than duration, resetting to: %dms",
539 RTMP_SetBufferMS(rtmp
, bufferTime
);
540 RTMP_UpdateBufferMS(rtmp
);
542 *percent
= ((double) rtmp
->m_read
.timestamp
) / (duration
* 1000.0) * 100.0;
543 *percent
= ((double) (int) (*percent
* 10.0)) / 10.0;
546 if (lastPercent
+ 1 <= *percent
)
549 lastPercent
= (unsigned long) *percent
;
554 now
= RTMP_GetTime();
555 if (abs(now
- lastUpdate
) > 200)
557 RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
558 (double) size
/ 1024.0,
559 (double) (rtmp
->m_read
.timestamp
) / 1000.0, *percent
);
566 now
= RTMP_GetTime();
567 if (abs(now
- lastUpdate
) > 200)
572 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size
/ 1024.0,
573 (double) (rtmp
->m_read
.timestamp
) / 1000.0);
581 RTMP_Log(RTMP_LOGDEBUG
, "zero read!");
586 while (!RTMP_ctrlC
&& nRead
> -1 && RTMP_IsConnected(rtmp
) && !RTMP_IsTimedout(rtmp
));
589 nRead
= rtmp
->m_read
.status
;
591 /* Final status update */
596 *percent
= ((double) rtmp
->m_read
.timestamp
) / (duration
* 1000.0) * 100.0;
597 *percent
= ((double) (int) (*percent
* 10.0)) / 10.0;
598 RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
599 (double) size
/ 1024.0,
600 (double) (rtmp
->m_read
.timestamp
) / 1000.0, *percent
);
604 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size
/ 1024.0,
605 (double) (rtmp
->m_read
.timestamp
) / 1000.0);
609 RTMP_Log(RTMP_LOGDEBUG
, "RTMP_Read returned: %d", nRead
);
611 if (bResume
&& nRead
== -2)
613 RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
621 if ((duration
> 0 && *percent
< 99.9) || RTMP_ctrlC
|| nRead
< 0
622 || RTMP_IsTimedout(rtmp
))
624 return RD_INCOMPLETE
;
630 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
632 void usage(char *prog
)
635 ("\n%s: This program dumps the media content streamed over RTMP.\n\n", prog
);
636 RTMP_LogPrintf("--help|-h Prints this help screen.\n");
638 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
640 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
642 ("--port|-c port Overrides the port in the rtmp url\n");
644 ("--socks|-S host:port Use the specified SOCKS proxy\n");
646 ("--protocol|-l num Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
648 ("--playpath|-y path Overrides the playpath parsed from rtmp url\n");
650 ("--playlist|-Y Set playlist before playing\n");
651 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
653 ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
654 RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
655 RTMP_LogPrintf("--app|-a app Name of target app on server\n");
658 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
660 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
662 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
664 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
667 ("--auth|-u string Authentication string to be appended to the connect string\n");
669 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
671 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
673 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
675 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
676 RTMP_DefaultFlashVer
.av_val
);
678 ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
680 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
682 ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
684 ("--resume|-e Resume a partial RTMP download\n");
686 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
689 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
691 ("--stop|-B num Stop at num seconds into stream\n");
693 ("--token|-T key Key for SecureToken response\n");
695 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
697 ("--hashes|-# Display progress with hashes, not with the byte counter\n");
699 ("--buffer|-b Buffer time in milliseconds (default: %lu)\n",
702 ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
705 ("--quiet|-q Suppresses all command output.\n");
706 RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
707 RTMP_LogPrintf("--debug|-z Debug level command output.\n");
709 ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
710 RTMP_LogPrintf("packet.\n\n");
714 main(int argc
, char **argv
)
718 int nStatus
= RD_SUCCESS
;
720 double duration
= 0.0;
722 int nSkipKeyFrames
= DEF_SKIPFRM
; // skip this number of keyframes when resuming
724 int bOverrideBufferTime
= FALSE
; // if the user specifies a buffer time override this is true
725 int bStdoutMode
= TRUE
; // if true print the stream directly to stdout, messages go to stderr
726 int bResume
= FALSE
; // true in resume mode
727 uint32_t dSeek
= 0; // seek position in resume mode, 0 otherwise
728 uint32_t bufferTime
= DEF_BUFTIME
;
730 // meta header and initial frame for the resume mode (they are read from the file and compared with
731 // the stream we are trying to continue
732 char *metaHeader
= 0;
733 uint32_t nMetaHeaderSize
= 0;
735 // video keyframe for matching
736 char *initialFrame
= 0;
737 uint32_t nInitialFrameSize
= 0;
738 int initialFrameType
= 0; // tye: audio or video
740 AVal hostname
= { 0, 0 };
741 AVal playpath
= { 0, 0 };
742 AVal subscribepath
= { 0, 0 };
743 AVal usherToken
= { 0, 0 }; //Justin.tv auth token
745 int protocol
= RTMP_PROTOCOL_UNDEFINED
;
747 int bLiveStream
= FALSE
; // is it a live stream? then we can't seek/resume
748 int bHashes
= FALSE
; // display byte counters not hashes by default
750 long int timeout
= DEF_TIMEOUT
; // timeout connection after 120 seconds
751 uint32_t dStartOffset
= 0; // seek position in non-live mode
752 uint32_t dStopOffset
= 0;
755 AVal swfUrl
= { 0, 0 };
756 AVal tcUrl
= { 0, 0 };
757 AVal pageUrl
= { 0, 0 };
759 AVal auth
= { 0, 0 };
760 AVal swfHash
= { 0, 0 };
761 uint32_t swfSize
= 0;
762 AVal flashVer
= { 0, 0 };
763 AVal sockshost
= { 0, 0 };
766 int swfAge
= 30; /* 30 days for SWF cache by default */
768 unsigned char hash
[RTMP_SWF_HASHLEN
];
773 signal(SIGINT
, sigIntHandler
);
774 signal(SIGTERM
, sigIntHandler
);
776 signal(SIGHUP
, sigIntHandler
);
777 signal(SIGPIPE
, sigIntHandler
);
778 signal(SIGQUIT
, sigIntHandler
);
781 RTMP_debuglevel
= RTMP_LOGINFO
;
783 // Check for --quiet option before printing any output
787 if (strcmp(argv
[index
], "--quiet") == 0
788 || strcmp(argv
[index
], "-q") == 0)
789 RTMP_debuglevel
= RTMP_LOGCRIT
;
793 RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION
);
795 ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
799 RTMP_Log(RTMP_LOGERROR
,
800 "Couldn't load sockets support on your platform, exiting!");
809 struct option longopts
[] = {
810 {"help", 0, NULL
, 'h'},
811 {"host", 1, NULL
, 'n'},
812 {"port", 1, NULL
, 'c'},
813 {"socks", 1, NULL
, 'S'},
814 {"protocol", 1, NULL
, 'l'},
815 {"playpath", 1, NULL
, 'y'},
816 {"playlist", 0, NULL
, 'Y'},
817 {"rtmp", 1, NULL
, 'r'},
818 {"swfUrl", 1, NULL
, 's'},
819 {"tcUrl", 1, NULL
, 't'},
820 {"pageUrl", 1, NULL
, 'p'},
821 {"app", 1, NULL
, 'a'},
822 {"auth", 1, NULL
, 'u'},
823 {"conn", 1, NULL
, 'C'},
825 {"swfhash", 1, NULL
, 'w'},
826 {"swfsize", 1, NULL
, 'x'},
827 {"swfVfy", 1, NULL
, 'W'},
828 {"swfAge", 1, NULL
, 'X'},
830 {"flashVer", 1, NULL
, 'f'},
831 {"live", 0, NULL
, 'v'},
832 {"flv", 1, NULL
, 'o'},
833 {"resume", 0, NULL
, 'e'},
834 {"timeout", 1, NULL
, 'm'},
835 {"buffer", 1, NULL
, 'b'},
836 {"skip", 1, NULL
, 'k'},
837 {"subscribe", 1, NULL
, 'd'},
838 {"start", 1, NULL
, 'A'},
839 {"stop", 1, NULL
, 'B'},
840 {"token", 1, NULL
, 'T'},
841 {"hashes", 0, NULL
, '#'},
842 {"debug", 0, NULL
, 'z'},
843 {"quiet", 0, NULL
, 'q'},
844 {"verbose", 0, NULL
, 'V'},
845 {"jtv", 1, NULL
, 'j'},
850 getopt_long(argc
, argv
,
851 "hVveqzr: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:",
852 longopts
, NULL
)) != -1)
862 int res
= hex2bin(optarg
, &swfHash
.av_val
);
863 if (res
!= RTMP_SWF_HASHLEN
)
865 swfHash
.av_val
= NULL
;
866 RTMP_Log(RTMP_LOGWARNING
,
867 "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN
);
869 swfHash
.av_len
= RTMP_SWF_HASHLEN
;
874 int size
= atoi(optarg
);
877 RTMP_Log(RTMP_LOGERROR
, "SWF Size must be at least 1, ignoring\n");
886 STR2AVAL(swfUrl
, optarg
);
891 int num
= atoi(optarg
);
894 RTMP_Log(RTMP_LOGERROR
, "SWF Age must be non-negative, ignoring\n");
904 nSkipKeyFrames
= atoi(optarg
);
905 if (nSkipKeyFrames
< 0)
907 RTMP_Log(RTMP_LOGERROR
,
908 "Number of keyframes skipped must be greater or equal zero, using zero!");
913 RTMP_Log(RTMP_LOGDEBUG
, "Number of skipped key frames for resume: %d",
919 int32_t bt
= atol(optarg
);
922 RTMP_Log(RTMP_LOGERROR
,
923 "Buffer time must be greater than zero, ignoring the specified value %d!",
929 bOverrideBufferTime
= TRUE
;
934 bLiveStream
= TRUE
; // no seeking or resuming possible!
937 STR2AVAL(subscribepath
, optarg
);
940 STR2AVAL(hostname
, optarg
);
946 protocol
= atoi(optarg
);
947 if (protocol
< RTMP_PROTOCOL_RTMP
|| protocol
> RTMP_PROTOCOL_RTMPTS
)
949 RTMP_Log(RTMP_LOGERROR
, "Unknown protocol specified: %d", protocol
);
954 STR2AVAL(playpath
, optarg
);
957 RTMP_SetOpt(&rtmp
, &av_playlist
, (AVal
*)&av_true
);
961 AVal parsedHost
, parsedApp
, parsedPlaypath
;
962 unsigned int parsedPort
= 0;
963 int parsedProtocol
= RTMP_PROTOCOL_UNDEFINED
;
966 (optarg
, &parsedProtocol
, &parsedHost
, &parsedPort
,
967 &parsedPlaypath
, &parsedApp
))
969 RTMP_Log(RTMP_LOGWARNING
, "Couldn't parse the specified url (%s)!",
974 if (!hostname
.av_len
)
975 hostname
= parsedHost
;
978 if (playpath
.av_len
== 0 && parsedPlaypath
.av_len
)
980 playpath
= parsedPlaypath
;
982 if (protocol
== RTMP_PROTOCOL_UNDEFINED
)
983 protocol
= parsedProtocol
;
984 if (app
.av_len
== 0 && parsedApp
.av_len
)
992 STR2AVAL(swfUrl
, optarg
);
995 STR2AVAL(tcUrl
, optarg
);
998 STR2AVAL(pageUrl
, optarg
);
1001 STR2AVAL(app
, optarg
);
1004 STR2AVAL(flashVer
, optarg
);
1008 if (strcmp(flvFile
, "-"))
1009 bStdoutMode
= FALSE
;
1016 STR2AVAL(auth
, optarg
);
1020 STR2AVAL(av
, optarg
);
1021 if (!RTMP_SetOpt(&rtmp
, &av_conn
, &av
))
1023 RTMP_Log(RTMP_LOGERROR
, "Invalid AMF parameter: %s", optarg
);
1029 timeout
= atoi(optarg
);
1032 dStartOffset
= (int) (atof(optarg
) * 1000.0);
1035 dStopOffset
= (int) (atof(optarg
) * 1000.0);
1039 STR2AVAL(token
, optarg
);
1040 RTMP_SetOpt(&rtmp
, &av_token
, &token
);
1047 RTMP_debuglevel
= RTMP_LOGCRIT
;
1050 RTMP_debuglevel
= RTMP_LOGDEBUG
;
1053 RTMP_debuglevel
= RTMP_LOGALL
;
1056 STR2AVAL(sockshost
, optarg
);
1059 STR2AVAL(usherToken
, optarg
);
1062 RTMP_LogPrintf("unknown option: %c\n", opt
);
1069 if (!hostname
.av_len
)
1071 RTMP_Log(RTMP_LOGERROR
,
1072 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
1075 if (playpath
.av_len
== 0)
1077 RTMP_Log(RTMP_LOGERROR
,
1078 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
1082 if (protocol
== RTMP_PROTOCOL_UNDEFINED
)
1084 RTMP_Log(RTMP_LOGWARNING
,
1085 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
1086 protocol
= RTMP_PROTOCOL_RTMP
;
1090 RTMP_Log(RTMP_LOGWARNING
,
1091 "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
1096 if (protocol
& RTMP_FEATURE_SSL
)
1098 else if (protocol
& RTMP_FEATURE_HTTP
)
1106 RTMP_Log(RTMP_LOGWARNING
,
1107 "You haven't specified an output file (-o filename), using stdout");
1111 if (bStdoutMode
&& bResume
)
1113 RTMP_Log(RTMP_LOGWARNING
,
1114 "Can't resume in stdout mode, ignoring --resume option");
1118 if (bLiveStream
&& bResume
)
1120 RTMP_Log(RTMP_LOGWARNING
, "Can't resume live stream, ignoring --resume option");
1127 if (RTMP_HashSWF(swfUrl
.av_val
, &swfSize
, hash
, swfAge
) == 0)
1129 swfHash
.av_val
= (char *)hash
;
1130 swfHash
.av_len
= RTMP_SWF_HASHLEN
;
1134 if (swfHash
.av_len
== 0 && swfSize
> 0)
1136 RTMP_Log(RTMP_LOGWARNING
,
1137 "Ignoring SWF size, supply also the hash with --swfhash");
1141 if (swfHash
.av_len
!= 0 && swfSize
== 0)
1143 RTMP_Log(RTMP_LOGWARNING
,
1144 "Ignoring SWF hash, supply also the swf size with --swfsize");
1146 swfHash
.av_val
= NULL
;
1150 if (tcUrl
.av_len
== 0)
1152 char str
[512] = { 0 };
1154 tcUrl
.av_len
= snprintf(str
, 511, "%s://%.*s:%d/%.*s",
1155 RTMPProtocolStringsLower
[protocol
], hostname
.av_len
,
1156 hostname
.av_val
, port
, app
.av_len
, app
.av_val
);
1157 tcUrl
.av_val
= (char *) malloc(tcUrl
.av_len
+ 1);
1158 strcpy(tcUrl
.av_val
, str
);
1163 // User defined seek offset
1164 if (dStartOffset
> 0)
1169 RTMP_Log(RTMP_LOGWARNING
,
1170 "Can't seek in a live stream, ignoring --start option");
1175 RTMP_SetupStream(&rtmp
, protocol
, &hostname
, port
, &sockshost
, &playpath
,
1176 &tcUrl
, &swfUrl
, &pageUrl
, &app
, &auth
, &swfHash
, swfSize
,
1177 &flashVer
, &subscribepath
, &usherToken
, dSeek
, dStopOffset
, bLiveStream
, timeout
);
1179 /* Try to keep the stream moving if it pauses on us */
1180 if (!bLiveStream
&& !(protocol
& RTMP_FEATURE_HTTP
))
1181 rtmp
.Link
.lFlags
|= RTMP_LF_BUFX
;
1185 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1189 OpenResumeFile(flvFile
, &file
, &size
, &metaHeader
, &nMetaHeaderSize
,
1191 if (nStatus
== RD_FAILED
)
1196 // file does not exist, so go back into normal mode
1197 bResume
= FALSE
; // we are back in fresh file mode (otherwise finalizing file won't be done)
1201 nStatus
= GetLastKeyframe(file
, nSkipKeyFrames
,
1202 &dSeek
, &initialFrame
,
1203 &initialFrameType
, &nInitialFrameSize
);
1204 if (nStatus
== RD_FAILED
)
1206 RTMP_Log(RTMP_LOGDEBUG
, "Failed to get last keyframe.");
1212 RTMP_Log(RTMP_LOGDEBUG
,
1213 "Last keyframe is first frame in stream, switching from resume to normal mode!");
1228 file
= fopen(flvFile
, "w+b");
1231 RTMP_LogPrintf("Failed to open file! %s\n", flvFile
);
1238 netstackdump
= fopen("netstackdump", "wb");
1239 netstackdump_read
= fopen("netstackdump_read", "wb");
1244 RTMP_Log(RTMP_LOGDEBUG
, "Setting buffer time to: %dms", bufferTime
);
1245 RTMP_SetBufferMS(&rtmp
, bufferTime
);
1250 RTMP_LogPrintf("Connecting ...\n");
1252 if (!RTMP_Connect(&rtmp
, NULL
))
1254 nStatus
= RD_FAILED
;
1258 RTMP_Log(RTMP_LOGINFO
, "Connected...");
1260 // User defined seek offset
1261 if (dStartOffset
> 0)
1263 // Don't need the start offset if resuming an existing file
1266 RTMP_Log(RTMP_LOGWARNING
,
1267 "Can't seek a resumed stream, ignoring --start option");
1272 dSeek
= dStartOffset
;
1276 // Calculate the length of the stream to still play
1277 if (dStopOffset
> 0)
1279 // Quit if start seek is past required stop offset
1280 if (dStopOffset
<= dSeek
)
1282 RTMP_LogPrintf("Already Completed\n");
1283 nStatus
= RD_SUCCESS
;
1288 if (!RTMP_ConnectStream(&rtmp
, dSeek
))
1290 nStatus
= RD_FAILED
;
1296 nInitialFrameSize
= 0;
1300 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1301 if (!RTMP_IsTimedout(&rtmp
))
1302 nStatus
= RD_FAILED
;
1304 nStatus
= RD_INCOMPLETE
;
1307 RTMP_Log(RTMP_LOGINFO
, "Connection timed out, trying to resume.\n\n");
1308 /* Did we already try pausing, and it still didn't work? */
1309 if (rtmp
.m_pausing
== 3)
1311 /* Only one try at reconnecting... */
1313 dSeek
= rtmp
.m_pauseStamp
;
1314 if (dStopOffset
> 0)
1316 if (dStopOffset
<= dSeek
)
1318 RTMP_LogPrintf("Already Completed\n");
1319 nStatus
= RD_SUCCESS
;
1323 if (!RTMP_ReconnectStream(&rtmp
, dSeek
))
1325 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1326 if (!RTMP_IsTimedout(&rtmp
))
1327 nStatus
= RD_FAILED
;
1329 nStatus
= RD_INCOMPLETE
;
1333 else if (!RTMP_ToggleStream(&rtmp
))
1335 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1336 if (!RTMP_IsTimedout(&rtmp
))
1337 nStatus
= RD_FAILED
;
1339 nStatus
= RD_INCOMPLETE
;
1345 nStatus
= Download(&rtmp
, file
, dSeek
, dStopOffset
, duration
, bResume
,
1346 metaHeader
, nMetaHeaderSize
, initialFrame
,
1347 initialFrameType
, nInitialFrameSize
,
1348 nSkipKeyFrames
, bStdoutMode
, bLiveStream
, bHashes
,
1349 bOverrideBufferTime
, bufferTime
, &percent
);
1351 initialFrame
= NULL
;
1353 /* If we succeeded, we're done.
1355 if (nStatus
!= RD_INCOMPLETE
|| !RTMP_IsTimedout(&rtmp
) || bLiveStream
)
1359 if (nStatus
== RD_SUCCESS
)
1361 RTMP_LogPrintf("Download complete\n");
1363 else if (nStatus
== RD_INCOMPLETE
)
1366 ("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1371 RTMP_Log(RTMP_LOGDEBUG
, "Closing connection.\n");
1380 if (netstackdump
!= 0)
1381 fclose(netstackdump
);
1382 if (netstackdump_read
!= 0)
1383 fclose(netstackdump_read
);