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
49 #define RD_NO_CONNECT 3
51 #define DEF_TIMEOUT 30 /* seconds */
52 #define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */
63 version
= MAKEWORD(1, 1);
64 return (WSAStartup(version
, &wsaData
) == 0);
82 FILE *netstackdump
= 0;
83 FILE *netstackdump_read
= 0;
86 uint32_t nIgnoredFlvFrameCounter
= 0;
87 uint32_t nIgnoredFrameCounter
= 0;
88 #define MAX_IGNORED_FRAMES 50
93 sigIntHandler(int sig
)
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
);
101 signal(SIGHUP
, SIG_IGN
);
102 signal(SIGPIPE
, SIG_IGN
);
103 signal(SIGQUIT
, SIG_IGN
);
107 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
108 int hex2bin(char *str
, char **hex
)
111 int i
, l
= strlen(str
);
122 *ptr
++ = (HEX2BIN(str
[i
]) << 4) | HEX2BIN(str
[i
+1]);
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");
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]
141 size_t bufferSize
= 0;
142 char hbuf
[16], *buffer
= NULL
;
144 *nMetaHeaderSize
= 0;
147 *file
= fopen(flvFile
, "r+b");
149 return RD_SUCCESS
; // RD_SUCCESS, because we go to fresh file mode instead of quiting
151 fseek(*file
, 0, SEEK_END
);
152 *size
= ftello(*file
);
153 fseek(*file
, 0, SEEK_SET
);
157 // verify FLV format and read header
158 uint32_t prevTagSize
= 0;
160 // check we've got a valid FLV file to continue!
161 if (fread(hbuf
, 1, 13, *file
) != 13)
163 RTMP_Log(RTMP_LOGERROR
, "Couldn't read FLV file header!");
166 if (hbuf
[0] != 'F' || hbuf
[1] != 'L' || hbuf
[2] != 'V'
169 RTMP_Log(RTMP_LOGERROR
, "Invalid FLV file!");
173 if ((hbuf
[4] & 0x05) == 0)
175 RTMP_Log(RTMP_LOGERROR
,
176 "FLV file contains neither video nor audio, aborting!");
180 uint32_t dataOffset
= AMF_DecodeInt32(hbuf
+ 5);
181 fseek(*file
, dataOffset
, SEEK_SET
);
183 if (fread(hbuf
, 1, 4, *file
) != 4)
185 RTMP_Log(RTMP_LOGERROR
, "Invalid FLV file: missing first prevTagSize!");
188 prevTagSize
= AMF_DecodeInt32(hbuf
);
189 if (prevTagSize
!= 0)
191 RTMP_Log(RTMP_LOGWARNING
,
192 "First prevTagSize is not zero: prevTagSize = 0x%08X",
196 // go through the file to find the meta data!
197 off_t pos
= dataOffset
+ 4;
198 int bFoundMetaHeader
= FALSE
;
200 while (pos
< *size
- 4 && !bFoundMetaHeader
)
202 fseeko(*file
, pos
, SEEK_SET
);
203 if (fread(hbuf
, 1, 4, *file
) != 4)
206 uint32_t dataSize
= AMF_DecodeInt24(hbuf
+ 1);
210 if (dataSize
> bufferSize
)
212 /* round up to next page boundary */
213 bufferSize
= dataSize
+ 4095;
214 bufferSize
^= (bufferSize
& 4095);
216 buffer
= malloc(bufferSize
);
221 fseeko(*file
, pos
+ 11, SEEK_SET
);
222 if (fread(buffer
, 1, dataSize
, *file
) != dataSize
)
226 int nRes
= AMF_Decode(&metaObj
, buffer
, dataSize
, FALSE
);
229 RTMP_Log(RTMP_LOGERROR
, "%s, error decoding meta data packet",
235 AMFProp_GetString(AMF_GetProp(&metaObj
, NULL
, 0), &metastring
);
237 if (AVMATCH(&metastring
, &av_onMetaData
))
241 *nMetaHeaderSize
= dataSize
;
244 *metaHeader
= (char *) malloc(*nMetaHeaderSize
);
245 memcpy(*metaHeader
, buffer
, *nMetaHeaderSize
);
248 AMFObjectProperty prop
;
249 if (RTMP_FindFirstMatchingProperty
250 (&metaObj
, &av_duration
, &prop
))
252 *duration
= AMFProp_GetNumber(&prop
);
253 RTMP_Log(RTMP_LOGDEBUG
, "File has duration: %f", *duration
);
256 bFoundMetaHeader
= TRUE
;
262 pos
+= (dataSize
+ 11 + 4);
266 if (!bFoundMetaHeader
)
267 RTMP_Log(RTMP_LOGWARNING
, "Couldn't locate meta data!");
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]
281 const size_t bufferSize
= 16;
282 char buffer
[bufferSize
];
287 fseek(file
, 0, SEEK_END
);
290 fseek(file
, 4, SEEK_SET
);
291 if (fread(&dataType
, sizeof(uint8_t), 1, file
) != 1)
294 bAudioOnly
= (dataType
& 0x4) && !(dataType
& 0x1);
296 RTMP_Log(RTMP_LOGDEBUG
, "bAudioOnly: %d, size: %llu", bAudioOnly
,
297 (unsigned long long) size
);
299 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
301 //if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
303 // find the last seekable frame
305 uint32_t prevTagSize
= 0;
307 // go through the file and find the last video keyframe
312 if (size
- tsize
< 13)
314 RTMP_Log(RTMP_LOGERROR
,
315 "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
318 fseeko(file
, size
- tsize
- 4, SEEK_SET
);
319 xread
= fread(buffer
, 1, 4, file
);
322 RTMP_Log(RTMP_LOGERROR
, "Couldn't read prevTagSize from file!");
326 prevTagSize
= AMF_DecodeInt32(buffer
);
327 //RTMP_Log(RTMP_LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);
329 if (prevTagSize
== 0)
331 RTMP_Log(RTMP_LOGERROR
, "Couldn't find keyframe to resume from!");
335 if (prevTagSize
< 0 || prevTagSize
> size
- 4 - 13)
337 RTMP_Log(RTMP_LOGERROR
,
338 "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!",
342 tsize
+= prevTagSize
+ 4;
345 fseeko(file
, size
- tsize
, SEEK_SET
);
346 if (fread(buffer
, 1, 12, file
) != 12)
348 RTMP_Log(RTMP_LOGERROR
, "Couldn't read header!");
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
);
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
361 // this helps if resuming from the last keyframe fails and one doesn't want to start
362 // the download from the beginning
364 if (nSkipKeyFrames
> 0
366 && (buffer
[0] != 0x09 || (buffer
[11] & 0xf0) != 0x10)))
369 RTMP_Log(RTMP_LOGDEBUG
,
370 "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
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
379 // save keyframe to compare/find position in stream
380 *initialFrameType
= buffer
[0];
381 *nInitialFrameSize
= prevTagSize
- 11;
382 *initialFrame
= (char *) malloc(*nInitialFrameSize
);
384 fseeko(file
, size
- tsize
+ 11, SEEK_SET
);
385 if (fread(*initialFrame
, 1, *nInitialFrameSize
, file
) != *nInitialFrameSize
)
387 RTMP_Log(RTMP_LOGERROR
, "Couldn't read last keyframe, aborting!");
391 *dSeek
= AMF_DecodeInt24(buffer
+ 4); // set seek position to keyframe tmestamp
392 *dSeek
|= (buffer
[7] << 24);
394 //else // handle audio only, we can seek anywhere we'd like
400 RTMP_Log(RTMP_LOGERROR
,
401 "Last keyframe timestamp is negative, aborting, your file is corrupt!");
404 RTMP_Log(RTMP_LOGDEBUG
, "Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek
,
405 *nInitialFrameSize
, *initialFrameType
);
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!");
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!");
420 uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
421 timestamp |= (buffer[3]<<24);
423 RTMP_Log(RTMP_LOGDEBUG, "Previous timestamp: %d ms", timestamp);
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
);
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
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 bHashes
, int bOverrideBufferTime
, uint32_t bufferTime
, double *percent
) // percentage downloaded [out]
446 int32_t now
, lastUpdate
;
447 int bufferSize
= 64 * 1024;
450 off_t size
= ftello(file
);
451 unsigned long lastPercent
= 0;
453 rtmp
->m_read
.timestamp
= dSeek
;
457 if (rtmp
->m_read
.timestamp
)
459 RTMP_Log(RTMP_LOGDEBUG
, "Continuing at TS: %d ms\n", rtmp
->m_read
.timestamp
);
464 RTMP_LogPrintf("Starting Live Stream\n");
468 // print initial status
469 // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
472 if ((double) rtmp
->m_read
.timestamp
>= (double) duration
* 999.0)
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);
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,
491 RTMP_LogPrintf("%s download at: %.3f kB\n",
492 bResume
? "Resuming" : "Starting",
493 (double) size
/ 1024.0);
498 RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset
- dSeek
) / 1000.0);
500 if (bResume
&& nInitialFrameSize
> 0)
501 rtmp
->m_read
.flags
|= RTMP_READ_RESUME
;
502 rtmp
->m_read
.initialFrameType
= initialFrameType
;
503 rtmp
->m_read
.nResumeTS
= dSeek
;
504 rtmp
->m_read
.metaHeader
= metaHeader
;
505 rtmp
->m_read
.initialFrame
= initialFrame
;
506 rtmp
->m_read
.nMetaHeaderSize
= nMetaHeaderSize
;
507 rtmp
->m_read
.nInitialFrameSize
= nInitialFrameSize
;
509 buffer
= (char *) malloc(bufferSize
);
511 now
= RTMP_GetTime();
512 lastUpdate
= now
- 1000;
515 nRead
= RTMP_Read(rtmp
, buffer
, bufferSize
);
516 //RTMP_LogPrintf("nRead: %d\n", nRead);
519 if (fwrite(buffer
, sizeof(unsigned char), nRead
, file
) !=
522 RTMP_Log(RTMP_LOGERROR
, "%s: Failed writing, exiting!", __FUNCTION__
);
528 //RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
529 if (duration
<= 0) // if duration unknown try to get it from the stream (onMetaData)
530 duration
= RTMP_GetDuration(rtmp
);
534 // make sure we claim to have enough buffer time!
535 if (!bOverrideBufferTime
&& bufferTime
< (duration
* 1000.0))
537 bufferTime
= (uint32_t) (duration
* 1000.0) + 5000; // extra 5sec to make sure we've got enough
539 RTMP_Log(RTMP_LOGDEBUG
,
540 "Detected that buffer time is less than duration, resetting to: %dms",
542 RTMP_SetBufferMS(rtmp
, bufferTime
);
543 RTMP_UpdateBufferMS(rtmp
);
545 *percent
= ((double) rtmp
->m_read
.timestamp
) / (duration
* 1000.0) * 100.0;
546 *percent
= ((double) (int) (*percent
* 10.0)) / 10.0;
549 if (lastPercent
+ 1 <= *percent
)
552 lastPercent
= (unsigned long) *percent
;
557 now
= RTMP_GetTime();
558 if (abs(now
- lastUpdate
) > 200)
560 RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
561 (double) size
/ 1024.0,
562 (double) (rtmp
->m_read
.timestamp
) / 1000.0, *percent
);
569 now
= RTMP_GetTime();
570 if (abs(now
- lastUpdate
) > 200)
575 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size
/ 1024.0,
576 (double) (rtmp
->m_read
.timestamp
) / 1000.0);
584 RTMP_Log(RTMP_LOGDEBUG
, "zero read!");
589 while (!RTMP_ctrlC
&& nRead
> -1 && RTMP_IsConnected(rtmp
) && !RTMP_IsTimedout(rtmp
));
592 nRead
= rtmp
->m_read
.status
;
594 /* Final status update */
599 *percent
= ((double) rtmp
->m_read
.timestamp
) / (duration
* 1000.0) * 100.0;
600 *percent
= ((double) (int) (*percent
* 10.0)) / 10.0;
601 RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
602 (double) size
/ 1024.0,
603 (double) (rtmp
->m_read
.timestamp
) / 1000.0, *percent
);
607 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size
/ 1024.0,
608 (double) (rtmp
->m_read
.timestamp
) / 1000.0);
612 RTMP_Log(RTMP_LOGDEBUG
, "RTMP_Read returned: %d", nRead
);
614 if (bResume
&& nRead
== -2)
616 RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
624 if ((duration
> 0 && *percent
< 99.9) || RTMP_ctrlC
|| nRead
< 0
625 || RTMP_IsTimedout(rtmp
))
627 return RD_INCOMPLETE
;
633 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
635 void usage(char *prog
)
638 ("\n%s: This program dumps the media content streamed over RTMP.\n\n", prog
);
639 RTMP_LogPrintf("--help|-h Prints this help screen.\n");
641 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
643 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
645 ("--port|-c port Overrides the port in the rtmp url\n");
647 ("--socks|-S host:port Use the specified SOCKS proxy\n");
649 ("--protocol|-l num Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
651 ("--playpath|-y path Overrides the playpath parsed from rtmp url\n");
653 ("--playlist|-Y Set playlist before playing\n");
654 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
656 ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
657 RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
658 RTMP_LogPrintf("--app|-a app Name of target app on server\n");
661 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
663 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
665 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
667 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
670 ("--auth|-u string Authentication string to be appended to the connect string\n");
672 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
674 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
676 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
678 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
679 RTMP_DefaultFlashVer
.av_val
);
681 ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
683 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
685 ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
687 ("--resume|-e Resume a partial RTMP download\n");
689 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
692 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
694 ("--stop|-B num Stop at num seconds into stream\n");
696 ("--token|-T key Key for SecureToken response\n");
698 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
700 ("--hashes|-# Display progress with hashes, not with the byte counter\n");
702 ("--buffer|-b Buffer time in milliseconds (default: %lu)\n",
705 ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
708 ("--quiet|-q Suppresses all command output.\n");
709 RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
710 RTMP_LogPrintf("--debug|-z Debug level command output.\n");
712 ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
713 RTMP_LogPrintf("packet.\n\n");
717 main(int argc
, char **argv
)
721 int nStatus
= RD_SUCCESS
;
723 double duration
= 0.0;
725 int nSkipKeyFrames
= DEF_SKIPFRM
; // skip this number of keyframes when resuming
727 int bOverrideBufferTime
= FALSE
; // if the user specifies a buffer time override this is true
728 int bStdoutMode
= TRUE
; // if true print the stream directly to stdout, messages go to stderr
729 int bResume
= FALSE
; // true in resume mode
730 uint32_t dSeek
= 0; // seek position in resume mode, 0 otherwise
731 uint32_t bufferTime
= DEF_BUFTIME
;
733 // meta header and initial frame for the resume mode (they are read from the file and compared with
734 // the stream we are trying to continue
735 char *metaHeader
= 0;
736 uint32_t nMetaHeaderSize
= 0;
738 // video keyframe for matching
739 char *initialFrame
= 0;
740 uint32_t nInitialFrameSize
= 0;
741 int initialFrameType
= 0; // tye: audio or video
743 AVal hostname
= { 0, 0 };
744 AVal playpath
= { 0, 0 };
745 AVal subscribepath
= { 0, 0 };
746 AVal usherToken
= { 0, 0 }; //Justin.tv auth token
748 int protocol
= RTMP_PROTOCOL_UNDEFINED
;
750 int bLiveStream
= FALSE
; // is it a live stream? then we can't seek/resume
751 int bHashes
= FALSE
; // display byte counters not hashes by default
753 long int timeout
= DEF_TIMEOUT
; // timeout connection after 120 seconds
754 uint32_t dStartOffset
= 0; // seek position in non-live mode
755 uint32_t dStopOffset
= 0;
758 AVal swfUrl
= { 0, 0 };
759 AVal tcUrl
= { 0, 0 };
760 AVal pageUrl
= { 0, 0 };
762 AVal auth
= { 0, 0 };
763 AVal swfHash
= { 0, 0 };
764 uint32_t swfSize
= 0;
765 AVal flashVer
= { 0, 0 };
766 AVal sockshost
= { 0, 0 };
769 int swfAge
= 30; /* 30 days for SWF cache by default */
771 unsigned char hash
[RTMP_SWF_HASHLEN
];
776 signal(SIGINT
, sigIntHandler
);
777 signal(SIGTERM
, sigIntHandler
);
779 signal(SIGHUP
, sigIntHandler
);
780 signal(SIGPIPE
, sigIntHandler
);
781 signal(SIGQUIT
, sigIntHandler
);
784 RTMP_debuglevel
= RTMP_LOGINFO
;
786 // Check for --quiet option before printing any output
790 if (strcmp(argv
[index
], "--quiet") == 0
791 || strcmp(argv
[index
], "-q") == 0)
792 RTMP_debuglevel
= RTMP_LOGCRIT
;
796 RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION
);
798 ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
802 RTMP_Log(RTMP_LOGERROR
,
803 "Couldn't load sockets support on your platform, exiting!");
812 struct option longopts
[] = {
813 {"help", 0, NULL
, 'h'},
814 {"host", 1, NULL
, 'n'},
815 {"port", 1, NULL
, 'c'},
816 {"socks", 1, NULL
, 'S'},
817 {"protocol", 1, NULL
, 'l'},
818 {"playpath", 1, NULL
, 'y'},
819 {"playlist", 0, NULL
, 'Y'},
820 {"rtmp", 1, NULL
, 'r'},
821 {"swfUrl", 1, NULL
, 's'},
822 {"tcUrl", 1, NULL
, 't'},
823 {"pageUrl", 1, NULL
, 'p'},
824 {"app", 1, NULL
, 'a'},
825 {"auth", 1, NULL
, 'u'},
826 {"conn", 1, NULL
, 'C'},
828 {"swfhash", 1, NULL
, 'w'},
829 {"swfsize", 1, NULL
, 'x'},
830 {"swfVfy", 1, NULL
, 'W'},
831 {"swfAge", 1, NULL
, 'X'},
833 {"flashVer", 1, NULL
, 'f'},
834 {"live", 0, NULL
, 'v'},
835 {"flv", 1, NULL
, 'o'},
836 {"resume", 0, NULL
, 'e'},
837 {"timeout", 1, NULL
, 'm'},
838 {"buffer", 1, NULL
, 'b'},
839 {"skip", 1, NULL
, 'k'},
840 {"subscribe", 1, NULL
, 'd'},
841 {"start", 1, NULL
, 'A'},
842 {"stop", 1, NULL
, 'B'},
843 {"token", 1, NULL
, 'T'},
844 {"hashes", 0, NULL
, '#'},
845 {"debug", 0, NULL
, 'z'},
846 {"quiet", 0, NULL
, 'q'},
847 {"verbose", 0, NULL
, 'V'},
848 {"jtv", 1, NULL
, 'j'},
853 getopt_long(argc
, argv
,
854 "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:",
855 longopts
, NULL
)) != -1)
865 int res
= hex2bin(optarg
, &swfHash
.av_val
);
866 if (res
!= RTMP_SWF_HASHLEN
)
868 swfHash
.av_val
= NULL
;
869 RTMP_Log(RTMP_LOGWARNING
,
870 "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN
);
872 swfHash
.av_len
= RTMP_SWF_HASHLEN
;
877 int size
= atoi(optarg
);
880 RTMP_Log(RTMP_LOGERROR
, "SWF Size must be at least 1, ignoring\n");
889 STR2AVAL(swfUrl
, optarg
);
894 int num
= atoi(optarg
);
897 RTMP_Log(RTMP_LOGERROR
, "SWF Age must be non-negative, ignoring\n");
907 nSkipKeyFrames
= atoi(optarg
);
908 if (nSkipKeyFrames
< 0)
910 RTMP_Log(RTMP_LOGERROR
,
911 "Number of keyframes skipped must be greater or equal zero, using zero!");
916 RTMP_Log(RTMP_LOGDEBUG
, "Number of skipped key frames for resume: %d",
922 int32_t bt
= atol(optarg
);
925 RTMP_Log(RTMP_LOGERROR
,
926 "Buffer time must be greater than zero, ignoring the specified value %d!",
932 bOverrideBufferTime
= TRUE
;
937 bLiveStream
= TRUE
; // no seeking or resuming possible!
940 STR2AVAL(subscribepath
, optarg
);
943 STR2AVAL(hostname
, optarg
);
949 protocol
= atoi(optarg
);
950 if (protocol
< RTMP_PROTOCOL_RTMP
|| protocol
> RTMP_PROTOCOL_RTMPTS
)
952 RTMP_Log(RTMP_LOGERROR
, "Unknown protocol specified: %d", protocol
);
957 STR2AVAL(playpath
, optarg
);
960 RTMP_SetOpt(&rtmp
, &av_playlist
, (AVal
*)&av_true
);
964 AVal parsedHost
, parsedApp
, parsedPlaypath
;
965 unsigned int parsedPort
= 0;
966 int parsedProtocol
= RTMP_PROTOCOL_UNDEFINED
;
969 (optarg
, &parsedProtocol
, &parsedHost
, &parsedPort
,
970 &parsedPlaypath
, &parsedApp
))
972 RTMP_Log(RTMP_LOGWARNING
, "Couldn't parse the specified url (%s)!",
977 if (!hostname
.av_len
)
978 hostname
= parsedHost
;
981 if (playpath
.av_len
== 0 && parsedPlaypath
.av_len
)
983 playpath
= parsedPlaypath
;
985 if (protocol
== RTMP_PROTOCOL_UNDEFINED
)
986 protocol
= parsedProtocol
;
987 if (app
.av_len
== 0 && parsedApp
.av_len
)
995 STR2AVAL(swfUrl
, optarg
);
998 STR2AVAL(tcUrl
, optarg
);
1001 STR2AVAL(pageUrl
, optarg
);
1004 STR2AVAL(app
, optarg
);
1007 STR2AVAL(flashVer
, optarg
);
1011 if (strcmp(flvFile
, "-"))
1012 bStdoutMode
= FALSE
;
1019 STR2AVAL(auth
, optarg
);
1023 STR2AVAL(av
, optarg
);
1024 if (!RTMP_SetOpt(&rtmp
, &av_conn
, &av
))
1026 RTMP_Log(RTMP_LOGERROR
, "Invalid AMF parameter: %s", optarg
);
1032 timeout
= atoi(optarg
);
1035 dStartOffset
= (int) (atof(optarg
) * 1000.0);
1038 dStopOffset
= (int) (atof(optarg
) * 1000.0);
1042 STR2AVAL(token
, optarg
);
1043 RTMP_SetOpt(&rtmp
, &av_token
, &token
);
1050 RTMP_debuglevel
= RTMP_LOGCRIT
;
1053 RTMP_debuglevel
= RTMP_LOGDEBUG
;
1056 RTMP_debuglevel
= RTMP_LOGALL
;
1059 STR2AVAL(sockshost
, optarg
);
1062 STR2AVAL(usherToken
, optarg
);
1065 RTMP_LogPrintf("unknown option: %c\n", opt
);
1072 if (!hostname
.av_len
)
1074 RTMP_Log(RTMP_LOGERROR
,
1075 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
1078 if (playpath
.av_len
== 0)
1080 RTMP_Log(RTMP_LOGERROR
,
1081 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
1085 if (protocol
== RTMP_PROTOCOL_UNDEFINED
)
1087 RTMP_Log(RTMP_LOGWARNING
,
1088 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
1089 protocol
= RTMP_PROTOCOL_RTMP
;
1093 RTMP_Log(RTMP_LOGWARNING
,
1094 "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
1099 if (protocol
& RTMP_FEATURE_SSL
)
1101 else if (protocol
& RTMP_FEATURE_HTTP
)
1109 RTMP_Log(RTMP_LOGWARNING
,
1110 "You haven't specified an output file (-o filename), using stdout");
1114 if (bStdoutMode
&& bResume
)
1116 RTMP_Log(RTMP_LOGWARNING
,
1117 "Can't resume in stdout mode, ignoring --resume option");
1121 if (bLiveStream
&& bResume
)
1123 RTMP_Log(RTMP_LOGWARNING
, "Can't resume live stream, ignoring --resume option");
1130 if (RTMP_HashSWF(swfUrl
.av_val
, &swfSize
, hash
, swfAge
) == 0)
1132 swfHash
.av_val
= (char *)hash
;
1133 swfHash
.av_len
= RTMP_SWF_HASHLEN
;
1137 if (swfHash
.av_len
== 0 && swfSize
> 0)
1139 RTMP_Log(RTMP_LOGWARNING
,
1140 "Ignoring SWF size, supply also the hash with --swfhash");
1144 if (swfHash
.av_len
!= 0 && swfSize
== 0)
1146 RTMP_Log(RTMP_LOGWARNING
,
1147 "Ignoring SWF hash, supply also the swf size with --swfsize");
1149 swfHash
.av_val
= NULL
;
1153 if (tcUrl
.av_len
== 0)
1155 tcUrl
.av_len
= strlen(RTMPProtocolStringsLower
[protocol
]) +
1156 hostname
.av_len
+ app
.av_len
+ sizeof("://:65535/");
1157 tcUrl
.av_val
= (char *) malloc(tcUrl
.av_len
);
1160 tcUrl
.av_len
= snprintf(tcUrl
.av_val
, tcUrl
.av_len
, "%s://%.*s:%d/%.*s",
1161 RTMPProtocolStringsLower
[protocol
], hostname
.av_len
,
1162 hostname
.av_val
, port
, app
.av_len
, app
.av_val
);
1167 // User defined seek offset
1168 if (dStartOffset
> 0)
1173 RTMP_Log(RTMP_LOGWARNING
,
1174 "Can't seek in a live stream, ignoring --start option");
1179 RTMP_SetupStream(&rtmp
, protocol
, &hostname
, port
, &sockshost
, &playpath
,
1180 &tcUrl
, &swfUrl
, &pageUrl
, &app
, &auth
, &swfHash
, swfSize
,
1181 &flashVer
, &subscribepath
, &usherToken
, dSeek
, dStopOffset
, bLiveStream
, timeout
);
1183 /* Try to keep the stream moving if it pauses on us */
1184 if (!bLiveStream
&& !(protocol
& RTMP_FEATURE_HTTP
))
1185 rtmp
.Link
.lFlags
|= RTMP_LF_BUFX
;
1189 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1193 OpenResumeFile(flvFile
, &file
, &size
, &metaHeader
, &nMetaHeaderSize
,
1195 if (nStatus
== RD_FAILED
)
1200 // file does not exist, so go back into normal mode
1201 bResume
= FALSE
; // we are back in fresh file mode (otherwise finalizing file won't be done)
1205 nStatus
= GetLastKeyframe(file
, nSkipKeyFrames
,
1206 &dSeek
, &initialFrame
,
1207 &initialFrameType
, &nInitialFrameSize
);
1208 if (nStatus
== RD_FAILED
)
1210 RTMP_Log(RTMP_LOGDEBUG
, "Failed to get last keyframe.");
1216 RTMP_Log(RTMP_LOGDEBUG
,
1217 "Last keyframe is first frame in stream, switching from resume to normal mode!");
1232 file
= fopen(flvFile
, "w+b");
1235 RTMP_LogPrintf("Failed to open file! %s\n", flvFile
);
1242 netstackdump
= fopen("netstackdump", "wb");
1243 netstackdump_read
= fopen("netstackdump_read", "wb");
1248 RTMP_Log(RTMP_LOGDEBUG
, "Setting buffer time to: %dms", bufferTime
);
1249 RTMP_SetBufferMS(&rtmp
, bufferTime
);
1254 RTMP_LogPrintf("Connecting ...\n");
1256 if (!RTMP_Connect(&rtmp
, NULL
))
1258 nStatus
= RD_NO_CONNECT
;
1262 RTMP_Log(RTMP_LOGINFO
, "Connected...");
1264 // User defined seek offset
1265 if (dStartOffset
> 0)
1267 // Don't need the start offset if resuming an existing file
1270 RTMP_Log(RTMP_LOGWARNING
,
1271 "Can't seek a resumed stream, ignoring --start option");
1276 dSeek
= dStartOffset
;
1280 // Calculate the length of the stream to still play
1281 if (dStopOffset
> 0)
1283 // Quit if start seek is past required stop offset
1284 if (dStopOffset
<= dSeek
)
1286 RTMP_LogPrintf("Already Completed\n");
1287 nStatus
= RD_SUCCESS
;
1292 if (!RTMP_ConnectStream(&rtmp
, dSeek
))
1294 nStatus
= RD_FAILED
;
1300 nInitialFrameSize
= 0;
1304 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1305 if (!RTMP_IsTimedout(&rtmp
))
1306 nStatus
= RD_FAILED
;
1308 nStatus
= RD_INCOMPLETE
;
1311 RTMP_Log(RTMP_LOGINFO
, "Connection timed out, trying to resume.\n\n");
1312 /* Did we already try pausing, and it still didn't work? */
1313 if (rtmp
.m_pausing
== 3)
1315 /* Only one try at reconnecting... */
1317 dSeek
= rtmp
.m_pauseStamp
;
1318 if (dStopOffset
> 0)
1320 if (dStopOffset
<= dSeek
)
1322 RTMP_LogPrintf("Already Completed\n");
1323 nStatus
= RD_SUCCESS
;
1327 if (!RTMP_ReconnectStream(&rtmp
, dSeek
))
1329 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1330 if (!RTMP_IsTimedout(&rtmp
))
1331 nStatus
= RD_FAILED
;
1333 nStatus
= RD_INCOMPLETE
;
1337 else if (!RTMP_ToggleStream(&rtmp
))
1339 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1340 if (!RTMP_IsTimedout(&rtmp
))
1341 nStatus
= RD_FAILED
;
1343 nStatus
= RD_INCOMPLETE
;
1349 nStatus
= Download(&rtmp
, file
, dSeek
, dStopOffset
, duration
, bResume
,
1350 metaHeader
, nMetaHeaderSize
, initialFrame
,
1351 initialFrameType
, nInitialFrameSize
,
1352 nSkipKeyFrames
, bStdoutMode
, bLiveStream
, bHashes
,
1353 bOverrideBufferTime
, bufferTime
, &percent
);
1355 initialFrame
= NULL
;
1357 /* If we succeeded, we're done.
1359 if (nStatus
!= RD_INCOMPLETE
|| !RTMP_IsTimedout(&rtmp
) || bLiveStream
)
1363 if (nStatus
== RD_SUCCESS
)
1365 RTMP_LogPrintf("Download complete\n");
1367 else if (nStatus
== RD_INCOMPLETE
)
1370 ("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1375 RTMP_Log(RTMP_LOGDEBUG
, "Closing connection.\n");
1384 if (netstackdump
!= 0)
1385 fclose(netstackdump
);
1386 if (netstackdump_read
!= 0)
1387 fclose(netstackdump_read
);