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 bRealtimeStream
, 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);
496 RTMP_LogPrintf(" in approximately realtime (disabled BUFX speedup hack)\n");
500 RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset
- dSeek
) / 1000.0);
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
;
511 buffer
= (char *) malloc(bufferSize
);
513 now
= RTMP_GetTime();
514 lastUpdate
= now
- 1000;
517 nRead
= RTMP_Read(rtmp
, buffer
, bufferSize
);
518 //RTMP_LogPrintf("nRead: %d\n", nRead);
521 if (fwrite(buffer
, sizeof(unsigned char), nRead
, file
) !=
524 RTMP_Log(RTMP_LOGERROR
, "%s: Failed writing, exiting!", __FUNCTION__
);
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
);
536 // make sure we claim to have enough buffer time!
537 if (!bOverrideBufferTime
&& bufferTime
< (duration
* 1000.0))
539 bufferTime
= (uint32_t) (duration
* 1000.0) + 5000; // extra 5sec to make sure we've got enough
541 RTMP_Log(RTMP_LOGDEBUG
,
542 "Detected that buffer time is less than duration, resetting to: %dms",
544 RTMP_SetBufferMS(rtmp
, bufferTime
);
545 RTMP_UpdateBufferMS(rtmp
);
547 *percent
= ((double) rtmp
->m_read
.timestamp
) / (duration
* 1000.0) * 100.0;
548 *percent
= ((double) (int) (*percent
* 10.0)) / 10.0;
551 if (lastPercent
+ 1 <= *percent
)
554 lastPercent
= (unsigned long) *percent
;
559 now
= RTMP_GetTime();
560 if (abs(now
- lastUpdate
) > 200)
562 RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
563 (double) size
/ 1024.0,
564 (double) (rtmp
->m_read
.timestamp
) / 1000.0, *percent
);
571 now
= RTMP_GetTime();
572 if (abs(now
- lastUpdate
) > 200)
577 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size
/ 1024.0,
578 (double) (rtmp
->m_read
.timestamp
) / 1000.0);
586 RTMP_Log(RTMP_LOGDEBUG
, "zero read!");
588 if (rtmp
->m_read
.status
== RTMP_READ_EOF
)
593 while (!RTMP_ctrlC
&& nRead
> -1 && RTMP_IsConnected(rtmp
) && !RTMP_IsTimedout(rtmp
));
596 nRead
= rtmp
->m_read
.status
;
598 /* Final status update */
603 *percent
= ((double) rtmp
->m_read
.timestamp
) / (duration
* 1000.0) * 100.0;
604 *percent
= ((double) (int) (*percent
* 10.0)) / 10.0;
605 RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
606 (double) size
/ 1024.0,
607 (double) (rtmp
->m_read
.timestamp
) / 1000.0, *percent
);
611 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size
/ 1024.0,
612 (double) (rtmp
->m_read
.timestamp
) / 1000.0);
616 RTMP_Log(RTMP_LOGDEBUG
, "RTMP_Read returned: %d", nRead
);
618 if (bResume
&& nRead
== -2)
620 RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
628 if ((duration
> 0 && *percent
< 99.9) || RTMP_ctrlC
|| nRead
< 0
629 || RTMP_IsTimedout(rtmp
))
631 return RD_INCOMPLETE
;
637 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
639 void usage(char *prog
)
642 ("\n%s: This program dumps the media content streamed over RTMP.\n\n", prog
);
643 RTMP_LogPrintf("--help|-h Prints this help screen.\n");
645 ("--url|-i url URL with options included (e.g. rtmp://host[:port]/path swfUrl=url tcUrl=url)\n");
647 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
649 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
651 ("--port|-c port Overrides the port in the rtmp url\n");
653 ("--socks|-S host:port Use the specified SOCKS proxy\n");
655 ("--protocol|-l num Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
657 ("--playpath|-y path Overrides the playpath parsed from rtmp url\n");
659 ("--playlist|-Y Set playlist before playing\n");
660 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
662 ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
663 RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
664 RTMP_LogPrintf("--app|-a app Name of target app on server\n");
667 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
669 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
671 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
673 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
676 ("--auth|-u string Authentication string to be appended to the connect string\n");
678 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
680 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
682 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
684 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
685 RTMP_DefaultFlashVer
.av_val
);
687 ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
689 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
691 ("--realtime|-R Don't attempt to speed up download via the Pause/Unpause BUFX hack\n");
693 ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
695 ("--resume|-e Resume a partial RTMP download\n");
697 ("--timeout|-m num Timeout connection num seconds (default: %u)\n",
700 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
702 ("--stop|-B num Stop at num seconds into stream\n");
704 ("--token|-T key Key for SecureToken response\n");
706 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
708 ("--hashes|-# Display progress with hashes, not with the byte counter\n");
710 ("--buffer|-b Buffer time in milliseconds (default: %u)\n",
713 ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
716 ("--quiet|-q Suppresses all command output.\n");
717 RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
718 RTMP_LogPrintf("--debug|-z Debug level command output.\n");
720 ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
721 RTMP_LogPrintf("packet.\n\n");
725 main(int argc
, char **argv
)
729 int nStatus
= RD_SUCCESS
;
731 double duration
= 0.0;
733 int nSkipKeyFrames
= DEF_SKIPFRM
; // skip this number of keyframes when resuming
735 int bOverrideBufferTime
= FALSE
; // if the user specifies a buffer time override this is true
736 int bStdoutMode
= TRUE
; // if true print the stream directly to stdout, messages go to stderr
737 int bResume
= FALSE
; // true in resume mode
738 uint32_t dSeek
= 0; // seek position in resume mode, 0 otherwise
739 uint32_t bufferTime
= DEF_BUFTIME
;
741 // meta header and initial frame for the resume mode (they are read from the file and compared with
742 // the stream we are trying to continue
743 char *metaHeader
= 0;
744 uint32_t nMetaHeaderSize
= 0;
746 // video keyframe for matching
747 char *initialFrame
= 0;
748 uint32_t nInitialFrameSize
= 0;
749 int initialFrameType
= 0; // tye: audio or video
751 AVal hostname
= { 0, 0 };
752 AVal playpath
= { 0, 0 };
753 AVal subscribepath
= { 0, 0 };
754 AVal usherToken
= { 0, 0 }; //Justin.tv auth token
756 int protocol
= RTMP_PROTOCOL_UNDEFINED
;
758 int bLiveStream
= FALSE
; // is it a live stream? then we can't seek/resume
759 int bRealtimeStream
= FALSE
; // If true, disable the BUFX hack (be patient)
760 int bHashes
= FALSE
; // display byte counters not hashes by default
762 long int timeout
= DEF_TIMEOUT
; // timeout connection after 120 seconds
763 uint32_t dStartOffset
= 0; // seek position in non-live mode
764 uint32_t dStopOffset
= 0;
767 AVal fullUrl
= { 0, 0 };
768 AVal swfUrl
= { 0, 0 };
769 AVal tcUrl
= { 0, 0 };
770 AVal pageUrl
= { 0, 0 };
772 AVal auth
= { 0, 0 };
773 AVal swfHash
= { 0, 0 };
774 uint32_t swfSize
= 0;
775 AVal flashVer
= { 0, 0 };
776 AVal sockshost
= { 0, 0 };
779 int swfAge
= 30; /* 30 days for SWF cache by default */
781 unsigned char hash
[RTMP_SWF_HASHLEN
];
786 signal(SIGINT
, sigIntHandler
);
787 signal(SIGTERM
, sigIntHandler
);
789 signal(SIGHUP
, sigIntHandler
);
790 signal(SIGPIPE
, sigIntHandler
);
791 signal(SIGQUIT
, sigIntHandler
);
794 RTMP_debuglevel
= RTMP_LOGINFO
;
796 // Check for --quiet option before printing any output
800 if (strcmp(argv
[index
], "--quiet") == 0
801 || strcmp(argv
[index
], "-q") == 0)
802 RTMP_debuglevel
= RTMP_LOGCRIT
;
806 RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION
);
808 ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
812 RTMP_Log(RTMP_LOGERROR
,
813 "Couldn't load sockets support on your platform, exiting!");
822 struct option longopts
[] = {
823 {"help", 0, NULL
, 'h'},
824 {"host", 1, NULL
, 'n'},
825 {"port", 1, NULL
, 'c'},
826 {"socks", 1, NULL
, 'S'},
827 {"protocol", 1, NULL
, 'l'},
828 {"playpath", 1, NULL
, 'y'},
829 {"playlist", 0, NULL
, 'Y'},
830 {"url", 1, NULL
, 'i'},
831 {"rtmp", 1, NULL
, 'r'},
832 {"swfUrl", 1, NULL
, 's'},
833 {"tcUrl", 1, NULL
, 't'},
834 {"pageUrl", 1, NULL
, 'p'},
835 {"app", 1, NULL
, 'a'},
836 {"auth", 1, NULL
, 'u'},
837 {"conn", 1, NULL
, 'C'},
839 {"swfhash", 1, NULL
, 'w'},
840 {"swfsize", 1, NULL
, 'x'},
841 {"swfVfy", 1, NULL
, 'W'},
842 {"swfAge", 1, NULL
, 'X'},
844 {"flashVer", 1, NULL
, 'f'},
845 {"live", 0, NULL
, 'v'},
846 {"realtime", 0, NULL
, 'R'},
847 {"flv", 1, NULL
, 'o'},
848 {"resume", 0, NULL
, 'e'},
849 {"timeout", 1, NULL
, 'm'},
850 {"buffer", 1, NULL
, 'b'},
851 {"skip", 1, NULL
, 'k'},
852 {"subscribe", 1, NULL
, 'd'},
853 {"start", 1, NULL
, 'A'},
854 {"stop", 1, NULL
, 'B'},
855 {"token", 1, NULL
, 'T'},
856 {"hashes", 0, NULL
, '#'},
857 {"debug", 0, NULL
, 'z'},
858 {"quiet", 0, NULL
, 'q'},
859 {"verbose", 0, NULL
, 'V'},
860 {"jtv", 1, NULL
, 'j'},
865 getopt_long(argc
, argv
,
866 "hVveqzRr:s:t:i:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:",
867 longopts
, NULL
)) != -1)
877 int res
= hex2bin(optarg
, &swfHash
.av_val
);
878 if (res
!= RTMP_SWF_HASHLEN
)
880 swfHash
.av_val
= NULL
;
881 RTMP_Log(RTMP_LOGWARNING
,
882 "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN
);
884 swfHash
.av_len
= RTMP_SWF_HASHLEN
;
889 int size
= atoi(optarg
);
892 RTMP_Log(RTMP_LOGERROR
, "SWF Size must be at least 1, ignoring\n");
901 STR2AVAL(swfUrl
, optarg
);
906 int num
= atoi(optarg
);
909 RTMP_Log(RTMP_LOGERROR
, "SWF Age must be non-negative, ignoring\n");
919 nSkipKeyFrames
= atoi(optarg
);
920 if (nSkipKeyFrames
< 0)
922 RTMP_Log(RTMP_LOGERROR
,
923 "Number of keyframes skipped must be greater or equal zero, using zero!");
928 RTMP_Log(RTMP_LOGDEBUG
, "Number of skipped key frames for resume: %d",
934 int32_t bt
= atol(optarg
);
937 RTMP_Log(RTMP_LOGERROR
,
938 "Buffer time must be greater than zero, ignoring the specified value %d!",
944 bOverrideBufferTime
= TRUE
;
949 bLiveStream
= TRUE
; // no seeking or resuming possible!
952 bRealtimeStream
= TRUE
; // seeking and resuming is still possible
955 STR2AVAL(subscribepath
, optarg
);
958 STR2AVAL(hostname
, optarg
);
964 protocol
= atoi(optarg
);
965 if (protocol
< RTMP_PROTOCOL_RTMP
|| protocol
> RTMP_PROTOCOL_RTMPTS
)
967 RTMP_Log(RTMP_LOGERROR
, "Unknown protocol specified: %d", protocol
);
972 STR2AVAL(playpath
, optarg
);
975 RTMP_SetOpt(&rtmp
, &av_playlist
, (AVal
*)&av_true
);
979 AVal parsedHost
, parsedApp
, parsedPlaypath
;
980 unsigned int parsedPort
= 0;
981 int parsedProtocol
= RTMP_PROTOCOL_UNDEFINED
;
984 (optarg
, &parsedProtocol
, &parsedHost
, &parsedPort
,
985 &parsedPlaypath
, &parsedApp
))
987 RTMP_Log(RTMP_LOGWARNING
, "Couldn't parse the specified url (%s)!",
992 if (!hostname
.av_len
)
993 hostname
= parsedHost
;
996 if (playpath
.av_len
== 0 && parsedPlaypath
.av_len
)
998 playpath
= parsedPlaypath
;
1000 if (protocol
== RTMP_PROTOCOL_UNDEFINED
)
1001 protocol
= parsedProtocol
;
1002 if (app
.av_len
== 0 && parsedApp
.av_len
)
1010 STR2AVAL(fullUrl
, optarg
);
1013 STR2AVAL(swfUrl
, optarg
);
1016 STR2AVAL(tcUrl
, optarg
);
1019 STR2AVAL(pageUrl
, optarg
);
1022 STR2AVAL(app
, optarg
);
1025 STR2AVAL(flashVer
, optarg
);
1029 if (strcmp(flvFile
, "-"))
1030 bStdoutMode
= FALSE
;
1037 STR2AVAL(auth
, optarg
);
1041 STR2AVAL(av
, optarg
);
1042 if (!RTMP_SetOpt(&rtmp
, &av_conn
, &av
))
1044 RTMP_Log(RTMP_LOGERROR
, "Invalid AMF parameter: %s", optarg
);
1050 timeout
= atoi(optarg
);
1053 dStartOffset
= (int) (atof(optarg
) * 1000.0);
1056 dStopOffset
= (int) (atof(optarg
) * 1000.0);
1060 STR2AVAL(token
, optarg
);
1061 RTMP_SetOpt(&rtmp
, &av_token
, &token
);
1068 RTMP_debuglevel
= RTMP_LOGCRIT
;
1071 RTMP_debuglevel
= RTMP_LOGDEBUG
;
1074 RTMP_debuglevel
= RTMP_LOGALL
;
1077 STR2AVAL(sockshost
, optarg
);
1080 STR2AVAL(usherToken
, optarg
);
1083 RTMP_LogPrintf("unknown option: %c\n", opt
);
1090 if (!hostname
.av_len
&& !fullUrl
.av_len
)
1092 RTMP_Log(RTMP_LOGERROR
,
1093 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
1096 if (playpath
.av_len
== 0 && !fullUrl
.av_len
)
1098 RTMP_Log(RTMP_LOGERROR
,
1099 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
1103 if (protocol
== RTMP_PROTOCOL_UNDEFINED
&& !fullUrl
.av_len
)
1105 RTMP_Log(RTMP_LOGWARNING
,
1106 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
1107 protocol
= RTMP_PROTOCOL_RTMP
;
1109 if (port
== -1 && !fullUrl
.av_len
)
1111 RTMP_Log(RTMP_LOGWARNING
,
1112 "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
1115 if (port
== 0 && !fullUrl
.av_len
)
1117 if (protocol
& RTMP_FEATURE_SSL
)
1119 else if (protocol
& RTMP_FEATURE_HTTP
)
1127 RTMP_Log(RTMP_LOGWARNING
,
1128 "You haven't specified an output file (-o filename), using stdout");
1132 if (bStdoutMode
&& bResume
)
1134 RTMP_Log(RTMP_LOGWARNING
,
1135 "Can't resume in stdout mode, ignoring --resume option");
1139 if (bLiveStream
&& bResume
)
1141 RTMP_Log(RTMP_LOGWARNING
, "Can't resume live stream, ignoring --resume option");
1148 if (RTMP_HashSWF(swfUrl
.av_val
, &swfSize
, hash
, swfAge
) == 0)
1150 swfHash
.av_val
= (char *)hash
;
1151 swfHash
.av_len
= RTMP_SWF_HASHLEN
;
1155 if (swfHash
.av_len
== 0 && swfSize
> 0)
1157 RTMP_Log(RTMP_LOGWARNING
,
1158 "Ignoring SWF size, supply also the hash with --swfhash");
1162 if (swfHash
.av_len
!= 0 && swfSize
== 0)
1164 RTMP_Log(RTMP_LOGWARNING
,
1165 "Ignoring SWF hash, supply also the swf size with --swfsize");
1167 swfHash
.av_val
= NULL
;
1171 if (tcUrl
.av_len
== 0)
1173 tcUrl
.av_len
= strlen(RTMPProtocolStringsLower
[protocol
]) +
1174 hostname
.av_len
+ app
.av_len
+ sizeof("://:65535/");
1175 tcUrl
.av_val
= (char *) malloc(tcUrl
.av_len
);
1178 tcUrl
.av_len
= snprintf(tcUrl
.av_val
, tcUrl
.av_len
, "%s://%.*s:%d/%.*s",
1179 RTMPProtocolStringsLower
[protocol
], hostname
.av_len
,
1180 hostname
.av_val
, port
, app
.av_len
, app
.av_val
);
1185 // User defined seek offset
1186 if (dStartOffset
> 0)
1191 RTMP_Log(RTMP_LOGWARNING
,
1192 "Can't seek in a live stream, ignoring --start option");
1197 if (!fullUrl
.av_len
)
1199 RTMP_SetupStream(&rtmp
, protocol
, &hostname
, port
, &sockshost
, &playpath
,
1200 &tcUrl
, &swfUrl
, &pageUrl
, &app
, &auth
, &swfHash
, swfSize
,
1201 &flashVer
, &subscribepath
, &usherToken
, dSeek
, dStopOffset
, bLiveStream
, timeout
);
1205 if (RTMP_SetupURL(&rtmp
, fullUrl
.av_val
) == FALSE
)
1207 RTMP_Log(RTMP_LOGERROR
, "Couldn't parse URL: %s", fullUrl
.av_val
);
1212 /* Try to keep the stream moving if it pauses on us */
1213 if (!bLiveStream
&& !bRealtimeStream
&& !(protocol
& RTMP_FEATURE_HTTP
))
1214 rtmp
.Link
.lFlags
|= RTMP_LF_BUFX
;
1218 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1222 OpenResumeFile(flvFile
, &file
, &size
, &metaHeader
, &nMetaHeaderSize
,
1224 if (nStatus
== RD_FAILED
)
1229 // file does not exist, so go back into normal mode
1230 bResume
= FALSE
; // we are back in fresh file mode (otherwise finalizing file won't be done)
1234 nStatus
= GetLastKeyframe(file
, nSkipKeyFrames
,
1235 &dSeek
, &initialFrame
,
1236 &initialFrameType
, &nInitialFrameSize
);
1237 if (nStatus
== RD_FAILED
)
1239 RTMP_Log(RTMP_LOGDEBUG
, "Failed to get last keyframe.");
1245 RTMP_Log(RTMP_LOGDEBUG
,
1246 "Last keyframe is first frame in stream, switching from resume to normal mode!");
1261 file
= fopen(flvFile
, "w+b");
1264 RTMP_LogPrintf("Failed to open file! %s\n", flvFile
);
1271 netstackdump
= fopen("netstackdump", "wb");
1272 netstackdump_read
= fopen("netstackdump_read", "wb");
1277 RTMP_Log(RTMP_LOGDEBUG
, "Setting buffer time to: %dms", bufferTime
);
1278 RTMP_SetBufferMS(&rtmp
, bufferTime
);
1283 RTMP_LogPrintf("Connecting ...\n");
1285 if (!RTMP_Connect(&rtmp
, NULL
))
1287 nStatus
= RD_NO_CONNECT
;
1291 RTMP_Log(RTMP_LOGINFO
, "Connected...");
1293 // User defined seek offset
1294 if (dStartOffset
> 0)
1296 // Don't need the start offset if resuming an existing file
1299 RTMP_Log(RTMP_LOGWARNING
,
1300 "Can't seek a resumed stream, ignoring --start option");
1305 dSeek
= dStartOffset
;
1309 // Calculate the length of the stream to still play
1310 if (dStopOffset
> 0)
1312 // Quit if start seek is past required stop offset
1313 if (dStopOffset
<= dSeek
)
1315 RTMP_LogPrintf("Already Completed\n");
1316 nStatus
= RD_SUCCESS
;
1321 if (!RTMP_ConnectStream(&rtmp
, dSeek
))
1323 nStatus
= RD_FAILED
;
1329 nInitialFrameSize
= 0;
1333 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1334 if (!RTMP_IsTimedout(&rtmp
))
1335 nStatus
= RD_FAILED
;
1337 nStatus
= RD_INCOMPLETE
;
1340 RTMP_Log(RTMP_LOGINFO
, "Connection timed out, trying to resume.\n\n");
1341 /* Did we already try pausing, and it still didn't work? */
1342 if (rtmp
.m_pausing
== 3)
1344 /* Only one try at reconnecting... */
1346 dSeek
= rtmp
.m_pauseStamp
;
1347 if (dStopOffset
> 0)
1349 if (dStopOffset
<= dSeek
)
1351 RTMP_LogPrintf("Already Completed\n");
1352 nStatus
= RD_SUCCESS
;
1356 if (!RTMP_ReconnectStream(&rtmp
, dSeek
))
1358 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1359 if (!RTMP_IsTimedout(&rtmp
))
1360 nStatus
= RD_FAILED
;
1362 nStatus
= RD_INCOMPLETE
;
1366 else if (!RTMP_ToggleStream(&rtmp
))
1368 RTMP_Log(RTMP_LOGERROR
, "Failed to resume the stream\n\n");
1369 if (!RTMP_IsTimedout(&rtmp
))
1370 nStatus
= RD_FAILED
;
1372 nStatus
= RD_INCOMPLETE
;
1378 nStatus
= Download(&rtmp
, file
, dSeek
, dStopOffset
, duration
, bResume
,
1379 metaHeader
, nMetaHeaderSize
, initialFrame
,
1380 initialFrameType
, nInitialFrameSize
, nSkipKeyFrames
,
1381 bStdoutMode
, bLiveStream
, bRealtimeStream
, bHashes
,
1382 bOverrideBufferTime
, bufferTime
, &percent
);
1384 initialFrame
= NULL
;
1386 /* If we succeeded, we're done.
1388 if (nStatus
!= RD_INCOMPLETE
|| !RTMP_IsTimedout(&rtmp
) || bLiveStream
)
1392 if (nStatus
== RD_SUCCESS
)
1394 RTMP_LogPrintf("Download complete\n");
1396 else if (nStatus
== RD_INCOMPLETE
)
1399 ("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1404 RTMP_Log(RTMP_LOGDEBUG
, "Closing connection.\n");
1413 if (netstackdump
!= 0)
1414 fclose(netstackdump
);
1415 if (netstackdump_read
!= 0)
1416 fclose(netstackdump_read
);