Simplify the logic for freeing reused strings in RTMP_Close
[rtmpdump.git] / rtmpdump.c
blob13741a7de427269a23d98502428f10dc88c0058d
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
49 #define RD_NO_CONNECT 3
51 #define DEF_TIMEOUT 30 /* seconds */
52 #define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */
53 #define DEF_SKIPFRM 0
55 // starts sockets
56 int
57 InitSockets()
59 #ifdef WIN32
60 WORD version;
61 WSADATA wsaData;
63 version = MAKEWORD(1, 1);
64 return (WSAStartup(version, &wsaData) == 0);
65 #else
66 return TRUE;
67 #endif
70 inline void
71 CleanupSockets()
73 #ifdef WIN32
74 WSACleanup();
75 #endif
78 #ifdef _DEBUG
79 uint32_t debugTS = 0;
80 int pnum = 0;
82 FILE *netstackdump = 0;
83 FILE *netstackdump_read = 0;
84 #endif
86 uint32_t nIgnoredFlvFrameCounter = 0;
87 uint32_t nIgnoredFrameCounter = 0;
88 #define MAX_IGNORED_FRAMES 50
90 FILE *file = 0;
92 void
93 sigIntHandler(int sig)
95 RTMP_ctrlC = TRUE;
96 RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
97 // ignore all these signals now and let the connection close
98 signal(SIGINT, SIG_IGN);
99 signal(SIGTERM, SIG_IGN);
100 #ifndef WIN32
101 signal(SIGHUP, SIG_IGN);
102 signal(SIGPIPE, SIG_IGN);
103 signal(SIGQUIT, SIG_IGN);
104 #endif
107 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
108 int hex2bin(char *str, char **hex)
110 char *ptr;
111 int i, l = strlen(str);
113 if (l & 1)
114 return 0;
116 *hex = malloc(l/2);
117 ptr = *hex;
118 if (!ptr)
119 return 0;
121 for (i=0; i<l; i+=2)
122 *ptr++ = (HEX2BIN(str[i]) << 4) | HEX2BIN(str[i+1]);
123 return l/2;
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;
145 *size = 0;
147 *file = fopen(flvFile, "r+b");
148 if (!*file)
149 return RD_SUCCESS; // RD_SUCCESS, because we go to fresh file mode instead of quiting
151 fseek(*file, 0, SEEK_END);
152 *size = ftello(*file);
153 fseek(*file, 0, SEEK_SET);
155 if (*size > 0)
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!");
164 return RD_FAILED;
166 if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V'
167 || hbuf[3] != 0x01)
169 RTMP_Log(RTMP_LOGERROR, "Invalid FLV file!");
170 return RD_FAILED;
173 if ((hbuf[4] & 0x05) == 0)
175 RTMP_Log(RTMP_LOGERROR,
176 "FLV file contains neither video nor audio, aborting!");
177 return RD_FAILED;
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!");
186 return RD_FAILED;
188 prevTagSize = AMF_DecodeInt32(hbuf);
189 if (prevTagSize != 0)
191 RTMP_Log(RTMP_LOGWARNING,
192 "First prevTagSize is not zero: prevTagSize = 0x%08X",
193 prevTagSize);
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)
204 break;
206 uint32_t dataSize = AMF_DecodeInt24(hbuf + 1);
208 if (hbuf[0] == 0x12)
210 if (dataSize > bufferSize)
212 /* round up to next page boundary */
213 bufferSize = dataSize + 4095;
214 bufferSize ^= (bufferSize & 4095);
215 free(buffer);
216 buffer = malloc(bufferSize);
217 if (!buffer)
218 return RD_FAILED;
221 fseeko(*file, pos + 11, SEEK_SET);
222 if (fread(buffer, 1, dataSize, *file) != dataSize)
223 break;
225 AMFObject metaObj;
226 int nRes = AMF_Decode(&metaObj, buffer, dataSize, FALSE);
227 if (nRes < 0)
229 RTMP_Log(RTMP_LOGERROR, "%s, error decoding meta data packet",
230 __FUNCTION__);
231 break;
234 AVal metastring;
235 AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring);
237 if (AVMATCH(&metastring, &av_onMetaData))
239 AMF_Dump(&metaObj);
241 *nMetaHeaderSize = dataSize;
242 if (*metaHeader)
243 free(*metaHeader);
244 *metaHeader = (char *) malloc(*nMetaHeaderSize);
245 memcpy(*metaHeader, buffer, *nMetaHeaderSize);
247 // get duration
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;
257 break;
259 //metaObj.Reset();
260 //delete obj;
262 pos += (dataSize + 11 + 4);
265 free(buffer);
266 if (!bFoundMetaHeader)
267 RTMP_Log(RTMP_LOGWARNING, "Couldn't locate meta data!");
270 return RD_SUCCESS;
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];
283 uint8_t dataType;
284 int bAudioOnly;
285 off_t size;
287 fseek(file, 0, SEEK_END);
288 size = ftello(file);
290 fseek(file, 4, SEEK_SET);
291 if (fread(&dataType, sizeof(uint8_t), 1, file) != 1)
292 return RD_FAILED;
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
304 off_t tsize = 0;
305 uint32_t prevTagSize = 0;
307 // go through the file and find the last video keyframe
310 int xread;
311 skipkeyframe:
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");
316 return RD_FAILED;
318 fseeko(file, size - tsize - 4, SEEK_SET);
319 xread = fread(buffer, 1, 4, file);
320 if (xread != 4)
322 RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!");
323 return RD_FAILED;
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!");
332 return RD_FAILED;
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!",
339 prevTagSize);
340 return RD_FAILED;
342 tsize += prevTagSize + 4;
344 // read header
345 fseeko(file, size - tsize, SEEK_SET);
346 if (fread(buffer, 1, 12, file) != 12)
348 RTMP_Log(RTMP_LOGERROR, "Couldn't read header!");
349 return RD_FAILED;
352 #ifdef _DEBUG
353 uint32_t ts = AMF_DecodeInt24(buffer + 4);
354 ts |= (buffer[7] << 24);
355 RTMP_Log(RTMP_LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts);
356 #endif //*/
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
365 && !(!bAudioOnly
366 && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10)))
368 #ifdef _DEBUG
369 RTMP_Log(RTMP_LOGDEBUG,
370 "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
371 #endif
372 nSkipKeyFrames--;
373 goto skipkeyframe;
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!");
388 return RD_FAILED;
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
398 if (*dSeek < 0)
400 RTMP_Log(RTMP_LOGERROR,
401 "Last keyframe timestamp is negative, aborting, your file is corrupt!");
402 return RD_FAILED;
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!");
412 goto start;
414 uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer);
415 fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET);
416 if(fread(buffer, 1, 4, file) != 4) {
417 RTMP_Log(RTMP_LOGERROR, "Couldn't read previous timestamp!");
418 goto start;
420 uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
421 timestamp |= (buffer[3]<<24);
423 RTMP_Log(RTMP_LOGDEBUG, "Previous timestamp: %d ms", timestamp);
426 if (*dSeek != 0)
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
439 return RD_SUCCESS;
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;
448 char *buffer;
449 int nRead = 0;
450 off_t size = ftello(file);
451 unsigned long lastPercent = 0;
453 rtmp->m_read.timestamp = dSeek;
455 *percent = 0.0;
457 if (rtmp->m_read.timestamp)
459 RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp);
462 if (bLiveStream)
464 RTMP_LogPrintf("Starting Live Stream\n");
466 else
468 // print initial status
469 // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
470 if (duration > 0)
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);
477 return RD_SUCCESS;
479 else
481 *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
482 *percent = ((double) (int) (*percent * 10.0)) / 10.0;
483 RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
484 bResume ? "Resuming" : "Starting",
485 (double) size / 1024.0, (double) rtmp->m_read.timestamp / 1000.0,
486 *percent);
489 else
491 RTMP_LogPrintf("%s download at: %.3f kB\n",
492 bResume ? "Resuming" : "Starting",
493 (double) size / 1024.0);
495 if (bRealtimeStream)
496 RTMP_LogPrintf(" in approximately realtime (disabled BUFX speedup hack)\n");
499 if (dStopOffset > 0)
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);
519 if (nRead > 0)
521 if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
522 (size_t) nRead)
524 RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
525 free(buffer);
526 return RD_FAILED;
528 size += nRead;
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);
534 if (duration > 0)
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",
543 bufferTime);
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;
549 if (bHashes)
551 if (lastPercent + 1 <= *percent)
553 RTMP_LogStatus("#");
554 lastPercent = (unsigned long) *percent;
557 else
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);
565 lastUpdate = now;
569 else
571 now = RTMP_GetTime();
572 if (abs(now - lastUpdate) > 200)
574 if (bHashes)
575 RTMP_LogStatus("#");
576 else
577 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
578 (double) (rtmp->m_read.timestamp) / 1000.0);
579 lastUpdate = now;
583 else
585 #ifdef _DEBUG
586 RTMP_Log(RTMP_LOGDEBUG, "zero read!");
587 #endif
588 if (rtmp->m_read.status == RTMP_READ_EOF)
589 break;
593 while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
594 free(buffer);
595 if (nRead < 0)
596 nRead = rtmp->m_read.status;
598 /* Final status update */
599 if (!bHashes)
601 if (duration > 0)
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);
609 else
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",
621 nSkipKeyFrames + 1);
622 return RD_FAILED;
625 if (nRead == -3)
626 return RD_SUCCESS;
628 if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
629 || RTMP_IsTimedout(rtmp))
631 return RD_INCOMPLETE;
634 return RD_SUCCESS;
637 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
639 void usage(char *prog)
641 RTMP_LogPrintf
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");
644 RTMP_LogPrintf
645 ("--url|-i url URL with options included (e.g. rtmp://host[:port]/path swfUrl=url tcUrl=url)\n");
646 RTMP_LogPrintf
647 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
648 RTMP_LogPrintf
649 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
650 RTMP_LogPrintf
651 ("--port|-c port Overrides the port in the rtmp url\n");
652 RTMP_LogPrintf
653 ("--socks|-S host:port Use the specified SOCKS proxy\n");
654 RTMP_LogPrintf
655 ("--protocol|-l num Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
656 RTMP_LogPrintf
657 ("--playpath|-y path Overrides the playpath parsed from rtmp url\n");
658 RTMP_LogPrintf
659 ("--playlist|-Y Set playlist before playing\n");
660 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
661 RTMP_LogPrintf
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");
665 #ifdef CRYPTO
666 RTMP_LogPrintf
667 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
668 RTMP_LogPrintf
669 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
670 RTMP_LogPrintf
671 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
672 RTMP_LogPrintf
673 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
674 #endif
675 RTMP_LogPrintf
676 ("--auth|-u string Authentication string to be appended to the connect string\n");
677 RTMP_LogPrintf
678 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
679 RTMP_LogPrintf
680 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
681 RTMP_LogPrintf
682 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
683 RTMP_LogPrintf
684 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
685 RTMP_DefaultFlashVer.av_val);
686 RTMP_LogPrintf
687 ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
688 RTMP_LogPrintf
689 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
690 RTMP_LogPrintf
691 ("--realtime|-R Don't attempt to speed up download via the Pause/Unpause BUFX hack\n");
692 RTMP_LogPrintf
693 ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
694 RTMP_LogPrintf
695 ("--resume|-e Resume a partial RTMP download\n");
696 RTMP_LogPrintf
697 ("--timeout|-m num Timeout connection num seconds (default: %u)\n",
698 DEF_TIMEOUT);
699 RTMP_LogPrintf
700 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
701 RTMP_LogPrintf
702 ("--stop|-B num Stop at num seconds into stream\n");
703 RTMP_LogPrintf
704 ("--token|-T key Key for SecureToken response\n");
705 RTMP_LogPrintf
706 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
707 RTMP_LogPrintf
708 ("--hashes|-# Display progress with hashes, not with the byte counter\n");
709 RTMP_LogPrintf
710 ("--buffer|-b Buffer time in milliseconds (default: %u)\n",
711 DEF_BUFTIME);
712 RTMP_LogPrintf
713 ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
714 DEF_SKIPFRM);
715 RTMP_LogPrintf
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");
719 RTMP_LogPrintf
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)
727 extern char *optarg;
729 int nStatus = RD_SUCCESS;
730 double percent = 0;
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
755 int port = -1;
756 int protocol = RTMP_PROTOCOL_UNDEFINED;
757 int retries = 0;
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;
765 RTMP rtmp = { 0 };
767 AVal fullUrl = { 0, 0 };
768 AVal swfUrl = { 0, 0 };
769 AVal tcUrl = { 0, 0 };
770 AVal pageUrl = { 0, 0 };
771 AVal app = { 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 };
778 #ifdef CRYPTO
779 int swfAge = 30; /* 30 days for SWF cache by default */
780 int swfVfy = 0;
781 unsigned char hash[RTMP_SWF_HASHLEN];
782 #endif
784 char *flvFile = 0;
786 signal(SIGINT, sigIntHandler);
787 signal(SIGTERM, sigIntHandler);
788 #ifndef WIN32
789 signal(SIGHUP, sigIntHandler);
790 signal(SIGPIPE, sigIntHandler);
791 signal(SIGQUIT, sigIntHandler);
792 #endif
794 RTMP_debuglevel = RTMP_LOGINFO;
796 // Check for --quiet option before printing any output
797 int index = 0;
798 while (index < argc)
800 if (strcmp(argv[index], "--quiet") == 0
801 || strcmp(argv[index], "-q") == 0)
802 RTMP_debuglevel = RTMP_LOGCRIT;
803 index++;
806 RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION);
807 RTMP_LogPrintf
808 ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
810 if (!InitSockets())
812 RTMP_Log(RTMP_LOGERROR,
813 "Couldn't load sockets support on your platform, exiting!");
814 return RD_FAILED;
817 /* sleep(30); */
819 RTMP_Init(&rtmp);
821 int opt;
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'},
838 #ifdef CRYPTO
839 {"swfhash", 1, NULL, 'w'},
840 {"swfsize", 1, NULL, 'x'},
841 {"swfVfy", 1, NULL, 'W'},
842 {"swfAge", 1, NULL, 'X'},
843 #endif
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'},
861 {0, 0, 0, 0}
864 while ((opt =
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)
869 switch (opt)
871 case 'h':
872 usage(argv[0]);
873 return RD_SUCCESS;
874 #ifdef CRYPTO
875 case 'w':
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;
885 break;
887 case 'x':
889 int size = atoi(optarg);
890 if (size <= 0)
892 RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
894 else
896 swfSize = size;
898 break;
900 case 'W':
901 STR2AVAL(swfUrl, optarg);
902 swfVfy = 1;
903 break;
904 case 'X':
906 int num = atoi(optarg);
907 if (num < 0)
909 RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
911 else
913 swfAge = num;
916 break;
917 #endif
918 case 'k':
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!");
924 nSkipKeyFrames = 0;
926 else
928 RTMP_Log(RTMP_LOGDEBUG, "Number of skipped key frames for resume: %d",
929 nSkipKeyFrames);
931 break;
932 case 'b':
934 int32_t bt = atol(optarg);
935 if (bt < 0)
937 RTMP_Log(RTMP_LOGERROR,
938 "Buffer time must be greater than zero, ignoring the specified value %d!",
939 bt);
941 else
943 bufferTime = bt;
944 bOverrideBufferTime = TRUE;
946 break;
948 case 'v':
949 bLiveStream = TRUE; // no seeking or resuming possible!
950 break;
951 case 'R':
952 bRealtimeStream = TRUE; // seeking and resuming is still possible
953 break;
954 case 'd':
955 STR2AVAL(subscribepath, optarg);
956 break;
957 case 'n':
958 STR2AVAL(hostname, optarg);
959 break;
960 case 'c':
961 port = atoi(optarg);
962 break;
963 case 'l':
964 protocol = atoi(optarg);
965 if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS)
967 RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol);
968 return RD_FAILED;
970 break;
971 case 'y':
972 STR2AVAL(playpath, optarg);
973 break;
974 case 'Y':
975 RTMP_SetOpt(&rtmp, &av_playlist, (AVal *)&av_true);
976 break;
977 case 'r':
979 AVal parsedHost, parsedApp, parsedPlaypath;
980 unsigned int parsedPort = 0;
981 int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
983 if (!RTMP_ParseURL
984 (optarg, &parsedProtocol, &parsedHost, &parsedPort,
985 &parsedPlaypath, &parsedApp))
987 RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
988 optarg);
990 else
992 if (!hostname.av_len)
993 hostname = parsedHost;
994 if (port == -1)
995 port = parsedPort;
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)
1004 app = parsedApp;
1007 break;
1009 case 'i':
1010 STR2AVAL(fullUrl, optarg);
1011 break;
1012 case 's':
1013 STR2AVAL(swfUrl, optarg);
1014 break;
1015 case 't':
1016 STR2AVAL(tcUrl, optarg);
1017 break;
1018 case 'p':
1019 STR2AVAL(pageUrl, optarg);
1020 break;
1021 case 'a':
1022 STR2AVAL(app, optarg);
1023 break;
1024 case 'f':
1025 STR2AVAL(flashVer, optarg);
1026 break;
1027 case 'o':
1028 flvFile = optarg;
1029 if (strcmp(flvFile, "-"))
1030 bStdoutMode = FALSE;
1032 break;
1033 case 'e':
1034 bResume = TRUE;
1035 break;
1036 case 'u':
1037 STR2AVAL(auth, optarg);
1038 break;
1039 case 'C': {
1040 AVal av;
1041 STR2AVAL(av, optarg);
1042 if (!RTMP_SetOpt(&rtmp, &av_conn, &av))
1044 RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg);
1045 return RD_FAILED;
1048 break;
1049 case 'm':
1050 timeout = atoi(optarg);
1051 break;
1052 case 'A':
1053 dStartOffset = (int) (atof(optarg) * 1000.0);
1054 break;
1055 case 'B':
1056 dStopOffset = (int) (atof(optarg) * 1000.0);
1057 break;
1058 case 'T': {
1059 AVal token;
1060 STR2AVAL(token, optarg);
1061 RTMP_SetOpt(&rtmp, &av_token, &token);
1063 break;
1064 case '#':
1065 bHashes = TRUE;
1066 break;
1067 case 'q':
1068 RTMP_debuglevel = RTMP_LOGCRIT;
1069 break;
1070 case 'V':
1071 RTMP_debuglevel = RTMP_LOGDEBUG;
1072 break;
1073 case 'z':
1074 RTMP_debuglevel = RTMP_LOGALL;
1075 break;
1076 case 'S':
1077 STR2AVAL(sockshost, optarg);
1078 break;
1079 case 'j':
1080 STR2AVAL(usherToken, optarg);
1081 break;
1082 default:
1083 RTMP_LogPrintf("unknown option: %c\n", opt);
1084 usage(argv[0]);
1085 return RD_FAILED;
1086 break;
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");
1094 return RD_FAILED;
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");
1100 return RD_FAILED;
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");
1113 port = 0;
1115 if (port == 0 && !fullUrl.av_len)
1117 if (protocol & RTMP_FEATURE_SSL)
1118 port = 443;
1119 else if (protocol & RTMP_FEATURE_HTTP)
1120 port = 80;
1121 else
1122 port = 1935;
1125 if (flvFile == 0)
1127 RTMP_Log(RTMP_LOGWARNING,
1128 "You haven't specified an output file (-o filename), using stdout");
1129 bStdoutMode = TRUE;
1132 if (bStdoutMode && bResume)
1134 RTMP_Log(RTMP_LOGWARNING,
1135 "Can't resume in stdout mode, ignoring --resume option");
1136 bResume = FALSE;
1139 if (bLiveStream && bResume)
1141 RTMP_Log(RTMP_LOGWARNING, "Can't resume live stream, ignoring --resume option");
1142 bResume = FALSE;
1145 #ifdef CRYPTO
1146 if (swfVfy)
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");
1159 swfSize = 0;
1162 if (swfHash.av_len != 0 && swfSize == 0)
1164 RTMP_Log(RTMP_LOGWARNING,
1165 "Ignoring SWF hash, supply also the swf size with --swfsize");
1166 swfHash.av_len = 0;
1167 swfHash.av_val = NULL;
1169 #endif
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);
1176 if (!tcUrl.av_val)
1177 return RD_FAILED;
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);
1183 int first = 1;
1185 // User defined seek offset
1186 if (dStartOffset > 0)
1188 // Live stream
1189 if (bLiveStream)
1191 RTMP_Log(RTMP_LOGWARNING,
1192 "Can't seek in a live stream, ignoring --start option");
1193 dStartOffset = 0;
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);
1203 else
1205 if (RTMP_SetupURL(&rtmp, fullUrl.av_val) == FALSE)
1207 RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", fullUrl.av_val);
1208 return RD_FAILED;
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;
1216 off_t size = 0;
1218 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1219 if (bResume)
1221 nStatus =
1222 OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize,
1223 &duration);
1224 if (nStatus == RD_FAILED)
1225 goto clean;
1227 if (!file)
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)
1232 else
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.");
1240 goto clean;
1243 if (dSeek == 0)
1245 RTMP_Log(RTMP_LOGDEBUG,
1246 "Last keyframe is first frame in stream, switching from resume to normal mode!");
1247 bResume = FALSE;
1252 if (!file)
1254 if (bStdoutMode)
1256 file = stdout;
1257 SET_BINMODE(file);
1259 else
1261 file = fopen(flvFile, "w+b");
1262 if (file == 0)
1264 RTMP_LogPrintf("Failed to open file! %s\n", flvFile);
1265 return RD_FAILED;
1270 #ifdef _DEBUG
1271 netstackdump = fopen("netstackdump", "wb");
1272 netstackdump_read = fopen("netstackdump_read", "wb");
1273 #endif
1275 while (!RTMP_ctrlC)
1277 RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
1278 RTMP_SetBufferMS(&rtmp, bufferTime);
1280 if (first)
1282 first = 0;
1283 RTMP_LogPrintf("Connecting ...\n");
1285 if (!RTMP_Connect(&rtmp, NULL))
1287 nStatus = RD_NO_CONNECT;
1288 break;
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
1297 if (bResume)
1299 RTMP_Log(RTMP_LOGWARNING,
1300 "Can't seek a resumed stream, ignoring --start option");
1301 dStartOffset = 0;
1303 else
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;
1317 break;
1321 if (!RTMP_ConnectStream(&rtmp, dSeek))
1323 nStatus = RD_FAILED;
1324 break;
1327 else
1329 nInitialFrameSize = 0;
1331 if (retries)
1333 RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
1334 if (!RTMP_IsTimedout(&rtmp))
1335 nStatus = RD_FAILED;
1336 else
1337 nStatus = RD_INCOMPLETE;
1338 break;
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... */
1345 retries = 1;
1346 dSeek = rtmp.m_pauseStamp;
1347 if (dStopOffset > 0)
1349 if (dStopOffset <= dSeek)
1351 RTMP_LogPrintf("Already Completed\n");
1352 nStatus = RD_SUCCESS;
1353 break;
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;
1361 else
1362 nStatus = RD_INCOMPLETE;
1363 break;
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;
1371 else
1372 nStatus = RD_INCOMPLETE;
1373 break;
1375 bResume = TRUE;
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);
1383 free(initialFrame);
1384 initialFrame = NULL;
1386 /* If we succeeded, we're done.
1388 if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
1389 break;
1392 if (nStatus == RD_SUCCESS)
1394 RTMP_LogPrintf("Download complete\n");
1396 else if (nStatus == RD_INCOMPLETE)
1398 RTMP_LogPrintf
1399 ("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1400 percent);
1403 clean:
1404 RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
1405 RTMP_Close(&rtmp);
1407 if (file != 0)
1408 fclose(file);
1410 CleanupSockets();
1412 #ifdef _DEBUG
1413 if (netstackdump != 0)
1414 fclose(netstackdump);
1415 if (netstackdump_read != 0)
1416 fclose(netstackdump_read);
1417 #endif
1418 return nStatus;