Remove the generated pkg-config file on make clean
[rtmpdump.git] / rtmpdump.c
blob89c053ab3de628b42a5625e4def377e5e15d0e5a
1 /* RTMPDump
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)
8 * any later version.
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
25 #include <stdlib.h>
26 #include <string.h>
27 #include <math.h>
28 #include <stdio.h>
30 #include <signal.h> // to catch Ctrl-C
31 #include <getopt.h>
33 #include "librtmp/rtmp_sys.h"
34 #include "librtmp/log.h"
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
46 #define RD_SUCCESS 0
47 #define RD_FAILED 1
48 #define RD_INCOMPLETE 2
50 #define DEF_TIMEOUT 30 /* seconds */
51 #define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */
52 #define DEF_SKIPFRM 0
54 // starts sockets
55 int
56 InitSockets()
58 #ifdef WIN32
59 WORD version;
60 WSADATA wsaData;
62 version = MAKEWORD(1, 1);
63 return (WSAStartup(version, &wsaData) == 0);
64 #else
65 return TRUE;
66 #endif
69 inline void
70 CleanupSockets()
72 #ifdef WIN32
73 WSACleanup();
74 #endif
77 #ifdef _DEBUG
78 uint32_t debugTS = 0;
79 int pnum = 0;
81 FILE *netstackdump = 0;
82 FILE *netstackdump_read = 0;
83 #endif
85 uint32_t nIgnoredFlvFrameCounter = 0;
86 uint32_t nIgnoredFrameCounter = 0;
87 #define MAX_IGNORED_FRAMES 50
89 FILE *file = 0;
91 void
92 sigIntHandler(int sig)
94 RTMP_ctrlC = TRUE;
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);
99 #ifndef WIN32
100 signal(SIGHUP, SIG_IGN);
101 signal(SIGPIPE, SIG_IGN);
102 signal(SIGQUIT, SIG_IGN);
103 #endif
106 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
107 int hex2bin(char *str, char **hex)
109 char *ptr;
110 int i, l = strlen(str);
112 if (l & 1)
113 return 0;
115 *hex = malloc(l/2);
116 ptr = *hex;
117 if (!ptr)
118 return 0;
120 for (i=0; i<l; i+=2)
121 *ptr++ = (HEX2BIN(str[i]) << 4) | HEX2BIN(str[i+1]);
122 return l/2;
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;
144 *size = 0;
146 *file = fopen(flvFile, "r+b");
147 if (!*file)
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);
154 if (*size > 0)
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!");
163 return RD_FAILED;
165 if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V'
166 || hbuf[3] != 0x01)
168 RTMP_Log(RTMP_LOGERROR, "Invalid FLV file!");
169 return RD_FAILED;
172 if ((hbuf[4] & 0x05) == 0)
174 RTMP_Log(RTMP_LOGERROR,
175 "FLV file contains neither video nor audio, aborting!");
176 return RD_FAILED;
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!");
185 return RD_FAILED;
187 prevTagSize = AMF_DecodeInt32(hbuf);
188 if (prevTagSize != 0)
190 RTMP_Log(RTMP_LOGWARNING,
191 "First prevTagSize is not zero: prevTagSize = 0x%08X",
192 prevTagSize);
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)
203 break;
205 uint32_t dataSize = AMF_DecodeInt24(hbuf + 1);
207 if (hbuf[0] == 0x12)
209 if (dataSize > bufferSize)
211 /* round up to next page boundary */
212 bufferSize = dataSize + 4095;
213 bufferSize ^= (bufferSize & 4095);
214 free(buffer);
215 buffer = malloc(bufferSize);
216 if (!buffer)
217 return RD_FAILED;
220 fseeko(*file, pos + 11, SEEK_SET);
221 if (fread(buffer, 1, dataSize, *file) != dataSize)
222 break;
224 AMFObject metaObj;
225 int nRes = AMF_Decode(&metaObj, buffer, dataSize, FALSE);
226 if (nRes < 0)
228 RTMP_Log(RTMP_LOGERROR, "%s, error decoding meta data packet",
229 __FUNCTION__);
230 break;
233 AVal metastring;
234 AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring);
236 if (AVMATCH(&metastring, &av_onMetaData))
238 AMF_Dump(&metaObj);
240 *nMetaHeaderSize = dataSize;
241 if (*metaHeader)
242 free(*metaHeader);
243 *metaHeader = (char *) malloc(*nMetaHeaderSize);
244 memcpy(*metaHeader, buffer, *nMetaHeaderSize);
246 // get duration
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;
256 break;
258 //metaObj.Reset();
259 //delete obj;
261 pos += (dataSize + 11 + 4);
264 free(buffer);
265 if (!bFoundMetaHeader)
266 RTMP_Log(RTMP_LOGWARNING, "Couldn't locate meta data!");
269 return RD_SUCCESS;
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];
282 uint8_t dataType;
283 int bAudioOnly;
284 off_t size;
286 fseek(file, 0, SEEK_END);
287 size = ftello(file);
289 fseek(file, 4, SEEK_SET);
290 if (fread(&dataType, sizeof(uint8_t), 1, file) != 1)
291 return RD_FAILED;
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
303 off_t tsize = 0;
304 uint32_t prevTagSize = 0;
306 // go through the file and find the last video keyframe
309 int xread;
310 skipkeyframe:
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");
315 return RD_FAILED;
317 fseeko(file, size - tsize - 4, SEEK_SET);
318 xread = fread(buffer, 1, 4, file);
319 if (xread != 4)
321 RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!");
322 return RD_FAILED;
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!");
331 return RD_FAILED;
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!",
338 prevTagSize);
339 return RD_FAILED;
341 tsize += prevTagSize + 4;
343 // read header
344 fseeko(file, size - tsize, SEEK_SET);
345 if (fread(buffer, 1, 12, file) != 12)
347 RTMP_Log(RTMP_LOGERROR, "Couldn't read header!");
348 return RD_FAILED;
351 #ifdef _DEBUG
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);
355 #endif //*/
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
364 && !(!bAudioOnly
365 && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10)))
367 #ifdef _DEBUG
368 RTMP_Log(RTMP_LOGDEBUG,
369 "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
370 #endif
371 nSkipKeyFrames--;
372 goto skipkeyframe;
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!");
387 return RD_FAILED;
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
397 if (*dSeek < 0)
399 RTMP_Log(RTMP_LOGERROR,
400 "Last keyframe timestamp is negative, aborting, your file is corrupt!");
401 return RD_FAILED;
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!");
411 goto start;
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!");
417 goto start;
419 uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
420 timestamp |= (buffer[3]<<24);
422 RTMP_Log(RTMP_LOGDEBUG, "Previous timestamp: %d ms", timestamp);
425 if (*dSeek != 0)
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
438 return RD_SUCCESS;
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);
448 int nRead = 0;
449 off_t size = ftello(file);
450 unsigned long lastPercent = 0;
452 rtmp->m_read.timestamp = dSeek;
454 *percent = 0.0;
456 if (rtmp->m_read.timestamp)
458 RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp);
461 if (bLiveStream)
463 RTMP_LogPrintf("Starting Live Stream\n");
465 else
467 // print initial status
468 // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
469 if (duration > 0)
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);
476 return RD_SUCCESS;
478 else
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,
485 *percent);
488 else
490 RTMP_LogPrintf("%s download at: %.3f kB\n",
491 bResume ? "Resuming" : "Starting",
492 (double) size / 1024.0);
496 if (dStopOffset > 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);
514 if (nRead > 0)
516 if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
517 (size_t) nRead)
519 RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
520 free(buffer);
521 return RD_FAILED;
523 size += nRead;
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);
529 if (duration > 0)
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",
538 bufferTime);
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;
544 if (bHashes)
546 if (lastPercent + 1 <= *percent)
548 RTMP_LogStatus("#");
549 lastPercent = (unsigned long) *percent;
552 else
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);
560 lastUpdate = now;
564 else
566 now = RTMP_GetTime();
567 if (abs(now - lastUpdate) > 200)
569 if (bHashes)
570 RTMP_LogStatus("#");
571 else
572 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
573 (double) (rtmp->m_read.timestamp) / 1000.0);
574 lastUpdate = now;
578 #ifdef _DEBUG
579 else
581 RTMP_Log(RTMP_LOGDEBUG, "zero read!");
583 #endif
586 while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
587 free(buffer);
588 if (nRead < 0)
589 nRead = rtmp->m_read.status;
591 /* Final status update */
592 if (!bHashes)
594 if (duration > 0)
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);
602 else
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",
614 nSkipKeyFrames + 1);
615 return RD_FAILED;
618 if (nRead == -3)
619 return RD_SUCCESS;
621 if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
622 || RTMP_IsTimedout(rtmp))
624 return RD_INCOMPLETE;
627 return RD_SUCCESS;
630 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
632 void usage(char *prog)
634 RTMP_LogPrintf
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");
637 RTMP_LogPrintf
638 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
639 RTMP_LogPrintf
640 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
641 RTMP_LogPrintf
642 ("--port|-c port Overrides the port in the rtmp url\n");
643 RTMP_LogPrintf
644 ("--socks|-S host:port Use the specified SOCKS proxy\n");
645 RTMP_LogPrintf
646 ("--protocol|-l num Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
647 RTMP_LogPrintf
648 ("--playpath|-y path Overrides the playpath parsed from rtmp url\n");
649 RTMP_LogPrintf
650 ("--playlist|-Y Set playlist before playing\n");
651 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
652 RTMP_LogPrintf
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");
656 #ifdef CRYPTO
657 RTMP_LogPrintf
658 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
659 RTMP_LogPrintf
660 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
661 RTMP_LogPrintf
662 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
663 RTMP_LogPrintf
664 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
665 #endif
666 RTMP_LogPrintf
667 ("--auth|-u string Authentication string to be appended to the connect string\n");
668 RTMP_LogPrintf
669 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
670 RTMP_LogPrintf
671 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
672 RTMP_LogPrintf
673 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
674 RTMP_LogPrintf
675 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
676 RTMP_DefaultFlashVer.av_val);
677 RTMP_LogPrintf
678 ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
679 RTMP_LogPrintf
680 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
681 RTMP_LogPrintf
682 ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
683 RTMP_LogPrintf
684 ("--resume|-e Resume a partial RTMP download\n");
685 RTMP_LogPrintf
686 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
687 DEF_TIMEOUT);
688 RTMP_LogPrintf
689 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
690 RTMP_LogPrintf
691 ("--stop|-B num Stop at num seconds into stream\n");
692 RTMP_LogPrintf
693 ("--token|-T key Key for SecureToken response\n");
694 RTMP_LogPrintf
695 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
696 RTMP_LogPrintf
697 ("--hashes|-# Display progress with hashes, not with the byte counter\n");
698 RTMP_LogPrintf
699 ("--buffer|-b Buffer time in milliseconds (default: %lu)\n",
700 DEF_BUFTIME);
701 RTMP_LogPrintf
702 ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
703 DEF_SKIPFRM);
704 RTMP_LogPrintf
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");
708 RTMP_LogPrintf
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)
716 extern char *optarg;
718 int nStatus = RD_SUCCESS;
719 double percent = 0;
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
744 int port = -1;
745 int protocol = RTMP_PROTOCOL_UNDEFINED;
746 int retries = 0;
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;
753 RTMP rtmp = { 0 };
755 AVal swfUrl = { 0, 0 };
756 AVal tcUrl = { 0, 0 };
757 AVal pageUrl = { 0, 0 };
758 AVal app = { 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 };
765 #ifdef CRYPTO
766 int swfAge = 30; /* 30 days for SWF cache by default */
767 int swfVfy = 0;
768 unsigned char hash[RTMP_SWF_HASHLEN];
769 #endif
771 char *flvFile = 0;
773 signal(SIGINT, sigIntHandler);
774 signal(SIGTERM, sigIntHandler);
775 #ifndef WIN32
776 signal(SIGHUP, sigIntHandler);
777 signal(SIGPIPE, sigIntHandler);
778 signal(SIGQUIT, sigIntHandler);
779 #endif
781 RTMP_debuglevel = RTMP_LOGINFO;
783 // Check for --quiet option before printing any output
784 int index = 0;
785 while (index < argc)
787 if (strcmp(argv[index], "--quiet") == 0
788 || strcmp(argv[index], "-q") == 0)
789 RTMP_debuglevel = RTMP_LOGCRIT;
790 index++;
793 RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION);
794 RTMP_LogPrintf
795 ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
797 if (!InitSockets())
799 RTMP_Log(RTMP_LOGERROR,
800 "Couldn't load sockets support on your platform, exiting!");
801 return RD_FAILED;
804 /* sleep(30); */
806 RTMP_Init(&rtmp);
808 int opt;
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'},
824 #ifdef CRYPTO
825 {"swfhash", 1, NULL, 'w'},
826 {"swfsize", 1, NULL, 'x'},
827 {"swfVfy", 1, NULL, 'W'},
828 {"swfAge", 1, NULL, 'X'},
829 #endif
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'},
846 {0, 0, 0, 0}
849 while ((opt =
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)
854 switch (opt)
856 case 'h':
857 usage(argv[0]);
858 return RD_SUCCESS;
859 #ifdef CRYPTO
860 case 'w':
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;
870 break;
872 case 'x':
874 int size = atoi(optarg);
875 if (size <= 0)
877 RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
879 else
881 swfSize = size;
883 break;
885 case 'W':
886 STR2AVAL(swfUrl, optarg);
887 swfVfy = 1;
888 break;
889 case 'X':
891 int num = atoi(optarg);
892 if (num < 0)
894 RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
896 else
898 swfAge = num;
901 break;
902 #endif
903 case 'k':
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!");
909 nSkipKeyFrames = 0;
911 else
913 RTMP_Log(RTMP_LOGDEBUG, "Number of skipped key frames for resume: %d",
914 nSkipKeyFrames);
916 break;
917 case 'b':
919 int32_t bt = atol(optarg);
920 if (bt < 0)
922 RTMP_Log(RTMP_LOGERROR,
923 "Buffer time must be greater than zero, ignoring the specified value %d!",
924 bt);
926 else
928 bufferTime = bt;
929 bOverrideBufferTime = TRUE;
931 break;
933 case 'v':
934 bLiveStream = TRUE; // no seeking or resuming possible!
935 break;
936 case 'd':
937 STR2AVAL(subscribepath, optarg);
938 break;
939 case 'n':
940 STR2AVAL(hostname, optarg);
941 break;
942 case 'c':
943 port = atoi(optarg);
944 break;
945 case 'l':
946 protocol = atoi(optarg);
947 if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS)
949 RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol);
950 return RD_FAILED;
952 break;
953 case 'y':
954 STR2AVAL(playpath, optarg);
955 break;
956 case 'Y':
957 RTMP_SetOpt(&rtmp, &av_playlist, (AVal *)&av_true);
958 break;
959 case 'r':
961 AVal parsedHost, parsedApp, parsedPlaypath;
962 unsigned int parsedPort = 0;
963 int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
965 if (!RTMP_ParseURL
966 (optarg, &parsedProtocol, &parsedHost, &parsedPort,
967 &parsedPlaypath, &parsedApp))
969 RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
970 optarg);
972 else
974 if (!hostname.av_len)
975 hostname = parsedHost;
976 if (port == -1)
977 port = parsedPort;
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)
986 app = parsedApp;
989 break;
991 case 's':
992 STR2AVAL(swfUrl, optarg);
993 break;
994 case 't':
995 STR2AVAL(tcUrl, optarg);
996 break;
997 case 'p':
998 STR2AVAL(pageUrl, optarg);
999 break;
1000 case 'a':
1001 STR2AVAL(app, optarg);
1002 break;
1003 case 'f':
1004 STR2AVAL(flashVer, optarg);
1005 break;
1006 case 'o':
1007 flvFile = optarg;
1008 if (strcmp(flvFile, "-"))
1009 bStdoutMode = FALSE;
1011 break;
1012 case 'e':
1013 bResume = TRUE;
1014 break;
1015 case 'u':
1016 STR2AVAL(auth, optarg);
1017 break;
1018 case 'C': {
1019 AVal av;
1020 STR2AVAL(av, optarg);
1021 if (!RTMP_SetOpt(&rtmp, &av_conn, &av))
1023 RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg);
1024 return RD_FAILED;
1027 break;
1028 case 'm':
1029 timeout = atoi(optarg);
1030 break;
1031 case 'A':
1032 dStartOffset = (int) (atof(optarg) * 1000.0);
1033 break;
1034 case 'B':
1035 dStopOffset = (int) (atof(optarg) * 1000.0);
1036 break;
1037 case 'T': {
1038 AVal token;
1039 STR2AVAL(token, optarg);
1040 RTMP_SetOpt(&rtmp, &av_token, &token);
1042 break;
1043 case '#':
1044 bHashes = TRUE;
1045 break;
1046 case 'q':
1047 RTMP_debuglevel = RTMP_LOGCRIT;
1048 break;
1049 case 'V':
1050 RTMP_debuglevel = RTMP_LOGDEBUG;
1051 break;
1052 case 'z':
1053 RTMP_debuglevel = RTMP_LOGALL;
1054 break;
1055 case 'S':
1056 STR2AVAL(sockshost, optarg);
1057 break;
1058 case 'j':
1059 STR2AVAL(usherToken, optarg);
1060 break;
1061 default:
1062 RTMP_LogPrintf("unknown option: %c\n", opt);
1063 usage(argv[0]);
1064 return RD_FAILED;
1065 break;
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");
1073 return RD_FAILED;
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");
1079 return RD_FAILED;
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;
1088 if (port == -1)
1090 RTMP_Log(RTMP_LOGWARNING,
1091 "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
1092 port = 0;
1094 if (port == 0)
1096 if (protocol & RTMP_FEATURE_SSL)
1097 port = 443;
1098 else if (protocol & RTMP_FEATURE_HTTP)
1099 port = 80;
1100 else
1101 port = 1935;
1104 if (flvFile == 0)
1106 RTMP_Log(RTMP_LOGWARNING,
1107 "You haven't specified an output file (-o filename), using stdout");
1108 bStdoutMode = TRUE;
1111 if (bStdoutMode && bResume)
1113 RTMP_Log(RTMP_LOGWARNING,
1114 "Can't resume in stdout mode, ignoring --resume option");
1115 bResume = FALSE;
1118 if (bLiveStream && bResume)
1120 RTMP_Log(RTMP_LOGWARNING, "Can't resume live stream, ignoring --resume option");
1121 bResume = FALSE;
1124 #ifdef CRYPTO
1125 if (swfVfy)
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");
1138 swfSize = 0;
1141 if (swfHash.av_len != 0 && swfSize == 0)
1143 RTMP_Log(RTMP_LOGWARNING,
1144 "Ignoring SWF hash, supply also the swf size with --swfsize");
1145 swfHash.av_len = 0;
1146 swfHash.av_val = NULL;
1148 #endif
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);
1161 int first = 1;
1163 // User defined seek offset
1164 if (dStartOffset > 0)
1166 // Live stream
1167 if (bLiveStream)
1169 RTMP_Log(RTMP_LOGWARNING,
1170 "Can't seek in a live stream, ignoring --start option");
1171 dStartOffset = 0;
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;
1183 off_t size = 0;
1185 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1186 if (bResume)
1188 nStatus =
1189 OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize,
1190 &duration);
1191 if (nStatus == RD_FAILED)
1192 goto clean;
1194 if (!file)
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)
1199 else
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.");
1207 goto clean;
1210 if (dSeek == 0)
1212 RTMP_Log(RTMP_LOGDEBUG,
1213 "Last keyframe is first frame in stream, switching from resume to normal mode!");
1214 bResume = FALSE;
1219 if (!file)
1221 if (bStdoutMode)
1223 file = stdout;
1224 SET_BINMODE(file);
1226 else
1228 file = fopen(flvFile, "w+b");
1229 if (file == 0)
1231 RTMP_LogPrintf("Failed to open file! %s\n", flvFile);
1232 return RD_FAILED;
1237 #ifdef _DEBUG
1238 netstackdump = fopen("netstackdump", "wb");
1239 netstackdump_read = fopen("netstackdump_read", "wb");
1240 #endif
1242 while (!RTMP_ctrlC)
1244 RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
1245 RTMP_SetBufferMS(&rtmp, bufferTime);
1247 if (first)
1249 first = 0;
1250 RTMP_LogPrintf("Connecting ...\n");
1252 if (!RTMP_Connect(&rtmp, NULL))
1254 nStatus = RD_FAILED;
1255 break;
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
1264 if (bResume)
1266 RTMP_Log(RTMP_LOGWARNING,
1267 "Can't seek a resumed stream, ignoring --start option");
1268 dStartOffset = 0;
1270 else
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;
1284 break;
1288 if (!RTMP_ConnectStream(&rtmp, dSeek))
1290 nStatus = RD_FAILED;
1291 break;
1294 else
1296 nInitialFrameSize = 0;
1298 if (retries)
1300 RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1301 if (!RTMP_IsTimedout(&rtmp))
1302 nStatus = RD_FAILED;
1303 else
1304 nStatus = RD_INCOMPLETE;
1305 break;
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... */
1312 retries = 1;
1313 dSeek = rtmp.m_pauseStamp;
1314 if (dStopOffset > 0)
1316 if (dStopOffset <= dSeek)
1318 RTMP_LogPrintf("Already Completed\n");
1319 nStatus = RD_SUCCESS;
1320 break;
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;
1328 else
1329 nStatus = RD_INCOMPLETE;
1330 break;
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;
1338 else
1339 nStatus = RD_INCOMPLETE;
1340 break;
1342 bResume = TRUE;
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);
1350 free(initialFrame);
1351 initialFrame = NULL;
1353 /* If we succeeded, we're done.
1355 if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
1356 break;
1359 if (nStatus == RD_SUCCESS)
1361 RTMP_LogPrintf("Download complete\n");
1363 else if (nStatus == RD_INCOMPLETE)
1365 RTMP_LogPrintf
1366 ("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1367 percent);
1370 clean:
1371 RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
1372 RTMP_Close(&rtmp);
1374 if (file != 0)
1375 fclose(file);
1377 CleanupSockets();
1379 #ifdef _DEBUG
1380 if (netstackdump != 0)
1381 fclose(netstackdump);
1382 if (netstackdump_read != 0)
1383 fclose(netstackdump_read);
1384 #endif
1385 return nStatus;