1 /* flvstreamer
2 * Copyright (C) 2009 Andrej Stepanchuk
3 * Copyright (C) 2009 Howard Chu
4 *
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.
9 *
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.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with flvstreamer; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 */
21
22 #define _FILE_OFFSET_BITS 64
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <math.h>
27 #include <stdio.h>
28
29 #include <signal.h> // to catch Ctrl-C
30 #include <getopt.h>
31
32 #include "rtmp.h"
33 #include "log.h"
34 #include "parseurl.h"
35
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
45
46 #define RD_SUCCESS 0
47 #define RD_FAILED 1
48 #define RD_INCOMPLETE 2
49
50 // starts sockets
51 bool
InitSockets()52 InitSockets()
53 {
54 #ifdef WIN32
55 WORD version;
56 WSADATA wsaData;
57
58 version = MAKEWORD(1, 1);
59 return (WSAStartup(version, &wsaData) == 0);
60 #else
61 return true;
62 #endif
63 }
64
65 inline void
CleanupSockets()66 CleanupSockets()
67 {
68 #ifdef WIN32
69 WSACleanup();
70 #endif
71 }
72
73 #ifdef _DEBUG
74 uint32_t debugTS = 0;
75 int pnum = 0;
76
77 FILE *netstackdump = 0;
78 FILE *netstackdump_read = 0;
79 #endif
80
81 uint32_t nIgnoredFlvFrameCounter = 0;
82 uint32_t nIgnoredFrameCounter = 0;
83 #define MAX_IGNORED_FRAMES 50
84
85 FILE *file = 0;
86
87 void
sigIntHandler(int sig)88 sigIntHandler(int sig)
89 {
90 RTMP_ctrlC = true;
91 LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
92 // ignore all these signals now and let the connection close
93 signal(SIGINT, SIG_IGN);
94 signal(SIGTERM, SIG_IGN);
95 #ifndef WIN32
96 signal(SIGHUP, SIG_IGN);
97 signal(SIGPIPE, SIG_IGN);
98 signal(SIGQUIT, SIG_IGN);
99 #endif
100 }
101
102 int
WriteHeader(char ** buf,unsigned int len)103 WriteHeader(char **buf, // target pointer, maybe preallocated
104 unsigned int len // length of buffer if preallocated
105 )
106 {
107 char flvHeader[] = { 'F', 'L', 'V', 0x01,
108 0x05, // video + audio, we finalize later if the value is different
109 0x00, 0x00, 0x00, 0x09,
110 0x00, 0x00, 0x00, 0x00 // first prevTagSize=0
111 };
112
113 unsigned int size = sizeof(flvHeader);
114
115 if (size > len)
116 {
117 *buf = (char *) realloc(*buf, size);
118 if (*buf == 0)
119 {
120 Log(LOGERROR, "Couldn't reallocate memory!");
121 return -1; // fatal error
122 }
123 }
124 memcpy(*buf, flvHeader, sizeof(flvHeader));
125 return size;
126 }
127
128 static const AVal av_onMetaData = AVC("onMetaData");
129 static const AVal av_duration = AVC("duration");
130
131 // Returns -3 if Play.Close/Stop, -2 if fatal error, -1 if no more media packets, 0 if ignorable error, >0 if there is a media packet
132 int
WriteStream(RTMP * rtmp,char ** buf,unsigned int len,uint32_t * tsm,bool bResume,bool bLiveStream,uint32_t nResumeTS,char * metaHeader,uint32_t nMetaHeaderSize,char * initialFrame,uint8_t initialFrameType,uint32_t nInitialFrameSize,uint8_t * dataType)133 WriteStream(RTMP * rtmp, char **buf, // target pointer, maybe preallocated
134 unsigned int len, // length of buffer if preallocated
135 uint32_t * tsm, // pointer to timestamp, will contain timestamp of last video packet returned
136 bool bResume, // resuming mode, will not write FLV header and compare metaHeader and first kexframe
137 bool bLiveStream, // live mode, will not report absolute timestamps
138 uint32_t nResumeTS, // resume keyframe timestamp
139 char *metaHeader, // pointer to meta header (if bResume == TRUE)
140 uint32_t nMetaHeaderSize, // length of meta header, if zero meta header check omitted (if bResume == TRUE)
141 char *initialFrame, // pointer to initial keyframe (no FLV header or tagSize, raw data) (if bResume == TRUE)
142 uint8_t initialFrameType, // initial frame type (audio or video)
143 uint32_t nInitialFrameSize, // length of initial frame in bytes, if zero initial frame check omitted (if bResume == TRUE)
144 uint8_t * dataType // whenever we get a video/audio packet we set an appropriate flag here, this will be later written to the FLV header
145 )
146 {
147 static bool bStopIgnoring = false;
148 static bool bFoundKeyframe = false;
149 static bool bFoundFlvKeyframe = false;
150
151 uint32_t prevTagSize = 0;
152 int rtnGetNextMediaPacket = 0, ret = -1;
153 RTMPPacket packet = { 0 };
154
155 rtnGetNextMediaPacket = RTMP_GetNextMediaPacket(rtmp, &packet);
156 while (rtnGetNextMediaPacket)
157 {
158 char *packetBody = packet.m_body;
159 unsigned int nPacketLen = packet.m_nBodySize;
160
161 // Return -3 if this was completed nicely with invoke message Play.Stop or Play.Complete
162 if (rtnGetNextMediaPacket == 2)
163 {
164 Log(LOGDEBUG,
165 "Got Play.Complete or Play.Stop from server. Assuming stream is complete");
166 ret = -3;
167 break;
168 }
169
170 // skip video info/command packets
171 if (packet.m_packetType == 0x09 &&
172 nPacketLen == 2 && ((*packetBody & 0xf0) == 0x50))
173 {
174 ret = 0;
175 break;
176 }
177
178 if (packet.m_packetType == 0x09 && nPacketLen <= 5)
179 {
180 Log(LOGWARNING, "ignoring too small video packet: size: %d",
181 nPacketLen);
182 ret = 0;
183 break;
184 }
185 if (packet.m_packetType == 0x08 && nPacketLen <= 1)
186 {
187 Log(LOGWARNING, "ignoring too small audio packet: size: %d",
188 nPacketLen);
189 ret = 0;
190 break;
191 }
192 #ifdef _DEBUG
193 Log(LOGDEBUG, "type: %02X, size: %d, TS: %d ms, abs TS: %d",
194 packet.m_packetType, nPacketLen, packet.m_nTimeStamp,
195 packet.m_hasAbsTimestamp);
196 if (packet.m_packetType == 0x09)
197 Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0));
198 #endif
199
200 // check the header if we get one
201 if (bResume && packet.m_nTimeStamp == 0)
202 {
203 if (nMetaHeaderSize > 0 && packet.m_packetType == 0x12)
204 {
205
206 AMFObject metaObj;
207 int nRes = AMF_Decode(&metaObj, packetBody, nPacketLen, false);
208 if (nRes >= 0)
209 {
210 AVal metastring;
211 AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0),
212 &metastring);
213
214 if (AVMATCH(&metastring, &av_onMetaData))
215 {
216 // compare
217 if ((nMetaHeaderSize != nPacketLen) ||
218 (memcmp(metaHeader, packetBody, nMetaHeaderSize) !=
219 0))
220 {
221 ret = -2;
222 }
223 }
224 AMF_Reset(&metaObj);
225 if (ret == -2)
226 break;
227 }
228 }
229
230 // check first keyframe to make sure we got the right position in the stream!
231 // (the first non ignored frame)
232 if (nInitialFrameSize > 0)
233 {
234
235 // video or audio data
236 if (packet.m_packetType == initialFrameType
237 && nInitialFrameSize == nPacketLen)
238 {
239 // we don't compare the sizes since the packet can contain several FLV packets, just make
240 // sure the first frame is our keyframe (which we are going to rewrite)
241 if (memcmp(initialFrame, packetBody, nInitialFrameSize) ==
242 0)
243 {
244 Log(LOGDEBUG, "Checked keyframe successfully!");
245 bFoundKeyframe = true;
246 ret = 0; // ignore it! (what about audio data after it? it is handled by ignoring all 0ms frames, see below)
247 break;
248 }
249 }
250
251 // hande FLV streams, even though the server resends the keyframe as an extra video packet
252 // it is also included in the first FLV stream chunk and we have to compare it and
253 // filter it out !!
254 //
255 if (packet.m_packetType == 0x16)
256 {
257 // basically we have to find the keyframe with the correct TS being nResumeTS
258 unsigned int pos = 0;
259 uint32_t ts = 0;
260
261 while (pos + 11 < nPacketLen)
262 {
263 uint32_t dataSize = AMF_DecodeInt24(packetBody + pos + 1); // size without header (11) and prevTagSize (4)
264 ts = AMF_DecodeInt24(packetBody + pos + 4);
265 ts |= (packetBody[pos + 7] << 24);
266
267 #ifdef _DEBUG
268 Log(LOGDEBUG,
269 "keyframe search: FLV Packet: type %02X, dataSize: %d, timeStamp: %d ms",
270 packetBody[pos], dataSize, ts);
271 #endif
272 // ok, is it a keyframe!!!: well doesn't work for audio!
273 if (packetBody[pos /*6928, test 0 */ ] ==
274 initialFrameType
275 /* && (packetBody[11]&0xf0) == 0x10 */ )
276 {
277 if (ts == nResumeTS)
278 {
279 Log(LOGDEBUG,
280 "Found keyframe with resume-keyframe timestamp!");
281 if (nInitialFrameSize != dataSize
282 || memcmp(initialFrame,
283 packetBody + pos + 11,
284 nInitialFrameSize) != 0)
285 {
286 Log(LOGERROR,
287 "FLV Stream: Keyframe doesn't match!");
288 ret = -2;
289 break;
290 }
291 bFoundFlvKeyframe = true;
292
293 // ok, skip this packet
294 // check whether skipable:
295 if (pos + 11 + dataSize + 4 > nPacketLen)
296 {
297 Log(LOGWARNING,
298 "Non skipable packet since it doesn't end with chunk, stream corrupt!");
299 ret = -2;
300 break;
301 }
302 packetBody += (pos + 11 + dataSize + 4);
303 nPacketLen -= (pos + 11 + dataSize + 4);
304
305 goto stopKeyframeSearch;
306
307 }
308 else if (nResumeTS < ts)
309 {
310 goto stopKeyframeSearch; // the timestamp ts will only increase with further packets, wait for seek
311 }
312 }
313 pos += (11 + dataSize + 4);
314 }
315 if (ts < nResumeTS)
316 {
317 Log(LOGERROR,
318 "First packet does not contain keyframe, all timestamps are smaller than the keyframe timestamp, so probably the resume seek failed?");
319 }
320 stopKeyframeSearch:
321 ;
322 if (!bFoundFlvKeyframe)
323 {
324 Log(LOGERROR,
325 "Couldn't find the seeked keyframe in this chunk!");
326 ret = 0;
327 break;
328 }
329 }
330 }
331 }
332
333 if (bResume && packet.m_nTimeStamp > 0
334 && (bFoundFlvKeyframe || bFoundKeyframe))
335 {
336 // another problem is that the server can actually change from 09/08 video/audio packets to an FLV stream
337 // or vice versa and our keyframe check will prevent us from going along with the new stream if we resumed
338 //
339 // in this case set the 'found keyframe' variables to true
340 // We assume that if we found one keyframe somewhere and were already beyond TS > 0 we have written
341 // data to the output which means we can accept all forthcoming data inclusing the change between 08/09 <-> FLV
342 // packets
343 bFoundFlvKeyframe = true;
344 bFoundKeyframe = true;
345 }
346
347 // skip till we find out keyframe (seeking might put us somewhere before it)
348 if (bResume && !bFoundKeyframe && packet.m_packetType != 0x16)
349 {
350 Log(LOGWARNING,
351 "Stream does not start with requested frame, ignoring data... ");
352 nIgnoredFrameCounter++;
353 if (nIgnoredFrameCounter > MAX_IGNORED_FRAMES)
354 ret = -2; // fatal error, couldn't continue stream
355 else
356 ret = 0;
357 break;
358 }
359 // ok, do the same for FLV streams
360 if (bResume && !bFoundFlvKeyframe && packet.m_packetType == 0x16)
361 {
362 Log(LOGWARNING,
363 "Stream does not start with requested FLV frame, ignoring data... ");
364 nIgnoredFlvFrameCounter++;
365 if (nIgnoredFlvFrameCounter > MAX_IGNORED_FRAMES)
366 ret = -2;
367 else
368 ret = 0;
369 break;
370 }
371
372 // if bResume, we continue a stream, we have to ignore the 0ms frames since these are the first keyframes, we've got these
373 // so don't mess around with multiple copies sent by the server to us! (if the keyframe is found at a later position
374 // there is only one copy and it will be ignored by the preceding if clause)
375 if (!bStopIgnoring && bResume && packet.m_packetType != 0x16)
376 { // exclude type 0x16 (FLV) since it can conatin several FLV packets
377 if (packet.m_nTimeStamp == 0)
378 {
379 ret = 0;
380 break;
381 }
382 else
383 {
384 bStopIgnoring = true; // stop ignoring packets
385 }
386 }
387
388 // calculate packet size and reallocate buffer if necessary
389 unsigned int size = nPacketLen
390 +
391 ((packet.m_packetType == 0x08 || packet.m_packetType == 0x09
392 || packet.m_packetType ==
393 0x12) ? 11 : 0) + (packet.m_packetType != 0x16 ? 4 : 0);
394
395 if (size + 4 > len)
396 { // the extra 4 is for the case of an FLV stream without a last prevTagSize (we need extra 4 bytes to append it)
397 *buf = (char *) realloc(*buf, size + 4);
398 if (*buf == 0)
399 {
400 Log(LOGERROR, "Couldn't reallocate memory!");
401 ret = -1; // fatal error
402 break;
403 }
404 }
405 char *ptr = *buf, *pend = ptr+size+4;
406
407 uint32_t nTimeStamp = 0; // use to return timestamp of last processed packet
408
409 // audio (0x08), video (0x09) or metadata (0x12) packets :
410 // construct 11 byte header then add rtmp packet's data
411 if (packet.m_packetType == 0x08 || packet.m_packetType == 0x09
412 || packet.m_packetType == 0x12)
413 {
414 // set data type
415 *dataType |=
416 (((packet.m_packetType == 0x08) << 2) | (packet.m_packetType ==
417 0x09));
418
419 nTimeStamp = nResumeTS + packet.m_nTimeStamp;
420 prevTagSize = 11 + nPacketLen;
421
422 *ptr = packet.m_packetType;
423 ptr++;
424 ptr = AMF_EncodeInt24(ptr, pend, nPacketLen);
425
426 /*if(packet.m_packetType == 0x09) { // video
427
428 // H264 fix:
429 if((packetBody[0] & 0x0f) == 7) { // CodecId = H264
430 uint8_t packetType = *(packetBody+1);
431
432 uint32_t ts = AMF_DecodeInt24(packetBody+2); // composition time
433 int32_t cts = (ts+0xff800000)^0xff800000;
434 Log(LOGDEBUG, "cts : %d\n", cts);
435
436 nTimeStamp -= cts;
437 // get rid of the composition time
438 CRTMP::EncodeInt24(packetBody+2, 0);
439 }
440 Log(LOGDEBUG, "VIDEO: nTimeStamp: 0x%08X (%d)\n", nTimeStamp, nTimeStamp);
441 } */
442
443 ptr = AMF_EncodeInt24(ptr, pend, nTimeStamp);
444 *ptr = (char) ((nTimeStamp & 0xFF000000) >> 24);
445 ptr++;
446
447 // stream id
448 ptr = AMF_EncodeInt24(ptr, pend, 0);
449 }
450
451 memcpy(ptr, packetBody, nPacketLen);
452 unsigned int len = nPacketLen;
453
454 // correct tagSize and obtain timestamp if we have an FLV stream
455 if (packet.m_packetType == 0x16)
456 {
457 unsigned int pos = 0;
458
459 while (pos + 11 < nPacketLen)
460 {
461 uint32_t dataSize = AMF_DecodeInt24(packetBody + pos + 1); // size without header (11) and without prevTagSize (4)
462 nTimeStamp = AMF_DecodeInt24(packetBody + pos + 4);
463 nTimeStamp |= (packetBody[pos + 7] << 24);
464
465 /*
466 CRTMP::EncodeInt24(ptr+pos+4, nTimeStamp);
467 ptr[pos+7] = (nTimeStamp>>24)&0xff;// */
468
469 // set data type
470 *dataType |=
471 (((*(packetBody + pos) ==
472 0x08) << 2) | (*(packetBody + pos) == 0x09));
473
474 if (pos + 11 + dataSize + 4 > nPacketLen)
475 {
476 if (pos + 11 + dataSize > nPacketLen)
477 {
478 Log(LOGERROR,
479 "Wrong data size (%lu), stream corrupted, aborting!",
480 dataSize);
481 ret = -2;
482 break;
483 }
484 Log(LOGWARNING, "No tagSize found, appending!");
485
486 // we have to append a last tagSize!
487 prevTagSize = dataSize + 11;
488 AMF_EncodeInt32(ptr + pos + 11 + dataSize, pend, prevTagSize);
489 size += 4;
490 len += 4;
491 }
492 else
493 {
494 prevTagSize =
495 AMF_DecodeInt32(packetBody + pos + 11 + dataSize);
496
497 #ifdef _DEBUG
498 Log(LOGDEBUG,
499 "FLV Packet: type %02X, dataSize: %lu, tagSize: %lu, timeStamp: %lu ms",
500 (unsigned char) packetBody[pos], dataSize, prevTagSize,
501 nTimeStamp);
502 #endif
503
504 if (prevTagSize != (dataSize + 11))
505 {
506 #ifdef _DEBUG
507 Log(LOGWARNING,
508 "Tag and data size are not consitent, writing tag size according to dataSize+11: %d",
509 dataSize + 11);
510 #endif
511
512 prevTagSize = dataSize + 11;
513 AMF_EncodeInt32(ptr + pos + 11 + dataSize, pend, prevTagSize);
514 }
515 }
516
517 pos += prevTagSize + 4; //(11+dataSize+4);
518 }
519 }
520 ptr += len;
521
522 if (packet.m_packetType != 0x16)
523 { // FLV tag packets contain their own prevTagSize
524 AMF_EncodeInt32(ptr, pend, prevTagSize);
525 //ptr += 4;
526 }
527
528 // In non-live this nTimeStamp can contain an absolute TS.
529 // Update ext timestamp with this absolute offset in non-live mode otherwise report the relative one
530 // LogPrintf("\nDEBUG: type: %02X, size: %d, pktTS: %dms, TS: %dms, bLiveStream: %d", packet.m_packetType, nPacketLen, packet.m_nTimeStamp, nTimeStamp, bLiveStream);
531 if (tsm)
532 *tsm = bLiveStream ? packet.m_nTimeStamp : nTimeStamp;
533
534
535 ret = size;
536 break;
537 }
538
539 if (rtnGetNextMediaPacket)
540 RTMPPacket_Free(&packet);
541 return ret; // no more media packets
542 }
543
544 int
OpenResumeFile(const char * flvFile,FILE ** file,off_t * size,char ** metaHeader,uint32_t * nMetaHeaderSize,double * duration)545 OpenResumeFile(const char *flvFile, // file name [in]
546 FILE ** file, // opened file [out]
547 off_t * size, // size of the file [out]
548 char **metaHeader, // meta data read from the file [out]
549 uint32_t * nMetaHeaderSize, // length of metaHeader [out]
550 double *duration) // duration of the stream in ms [out]
551 {
552 size_t bufferSize = 0;
553 char hbuf[16], *buffer = NULL;
554
555 *nMetaHeaderSize = 0;
556 *size = 0;
557
558 *file = fopen(flvFile, "r+b");
559 if (!*file)
560 return RD_SUCCESS; // RD_SUCCESS, because we go to fresh file mode instead of quiting
561
562 fseek(*file, 0, SEEK_END);
563 *size = ftello(*file);
564 fseek(*file, 0, SEEK_SET);
565
566 if (*size > 0)
567 {
568 // verify FLV format and read header
569 uint32_t prevTagSize = 0;
570
571 // check we've got a valid FLV file to continue!
572 if (fread(hbuf, 1, 13, *file) != 13)
573 {
574 Log(LOGERROR, "Couldn't read FLV file header!");
575 return RD_FAILED;
576 }
577 if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V'
578 || hbuf[3] != 0x01)
579 {
580 Log(LOGERROR, "Invalid FLV file!");
581 return RD_FAILED;
582 }
583
584 if ((hbuf[4] & 0x05) == 0)
585 {
586 Log(LOGERROR,
587 "FLV file contains neither video nor audio, aborting!");
588 return RD_FAILED;
589 }
590
591 uint32_t dataOffset = AMF_DecodeInt32(hbuf + 5);
592 fseek(*file, dataOffset, SEEK_SET);
593
594 if (fread(hbuf, 1, 4, *file) != 4)
595 {
596 Log(LOGERROR, "Invalid FLV file: missing first prevTagSize!");
597 return RD_FAILED;
598 }
599 prevTagSize = AMF_DecodeInt32(hbuf);
600 if (prevTagSize != 0)
601 {
602 Log(LOGWARNING,
603 "First prevTagSize is not zero: prevTagSize = 0x%08X",
604 prevTagSize);
605 }
606
607 // go through the file to find the meta data!
608 off_t pos = dataOffset + 4;
609 bool bFoundMetaHeader = false;
610
611 while (pos < *size - 4 && !bFoundMetaHeader)
612 {
613 fseeko(*file, pos, SEEK_SET);
614 if (fread(hbuf, 1, 4, *file) != 4)
615 break;
616
617 uint32_t dataSize = AMF_DecodeInt24(hbuf + 1);
618
619 if (hbuf[0] == 0x12)
620 {
621 if (dataSize > bufferSize)
622 {
623 /* round up to next page boundary */
624 bufferSize = dataSize + 4095;
625 bufferSize ^= (bufferSize & 4095);
626 free(buffer);
627 buffer = malloc(bufferSize);
628 if (!buffer)
629 return RD_FAILED;
630 }
631
632 fseeko(*file, pos + 11, SEEK_SET);
633 if (fread(buffer, 1, dataSize, *file) != dataSize)
634 break;
635
636 AMFObject metaObj;
637 int nRes = AMF_Decode(&metaObj, buffer, dataSize, false);
638 if (nRes < 0)
639 {
640 Log(LOGERROR, "%s, error decoding meta data packet",
641 __FUNCTION__);
642 break;
643 }
644
645 AVal metastring;
646 AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring);
647
648 if (AVMATCH(&metastring, &av_onMetaData))
649 {
650 AMF_Dump(&metaObj);
651
652 *nMetaHeaderSize = dataSize;
653 if (*metaHeader)
654 free(*metaHeader);
655 *metaHeader = (char *) malloc(*nMetaHeaderSize);
656 memcpy(*metaHeader, buffer, *nMetaHeaderSize);
657
658 // get duration
659 AMFObjectProperty prop;
660 if (RTMP_FindFirstMatchingProperty
661 (&metaObj, &av_duration, &prop))
662 {
663 *duration = AMFProp_GetNumber(&prop);
664 Log(LOGDEBUG, "File has duration: %f", *duration);
665 }
666
667 bFoundMetaHeader = true;
668 break;
669 }
670 //metaObj.Reset();
671 //delete obj;
672 }
673 pos += (dataSize + 11 + 4);
674 }
675
676 free(buffer);
677 if (!bFoundMetaHeader)
678 Log(LOGWARNING, "Couldn't locate meta data!");
679 }
680
681 return RD_SUCCESS;
682 }
683
684 int
GetLastKeyframe(FILE * file,int nSkipKeyFrames,uint32_t * dSeek,char ** initialFrame,int * initialFrameType,uint32_t * nInitialFrameSize)685 GetLastKeyframe(FILE * file, // output file [in]
686 int nSkipKeyFrames, // max number of frames to skip when searching for key frame [in]
687 uint32_t * dSeek, // offset of the last key frame [out]
688 char **initialFrame, // content of the last keyframe [out]
689 int *initialFrameType, // initial frame type (audio/video) [out]
690 uint32_t * nInitialFrameSize) // length of initialFrame [out]
691 {
692 const size_t bufferSize = 16;
693 char buffer[bufferSize];
694 uint8_t dataType;
695 bool bAudioOnly;
696 off_t size;
697
698 fseek(file, 0, SEEK_END);
699 size = ftello(file);
700
701 fseek(file, 4, SEEK_SET);
702 if (fread(&dataType, sizeof(uint8_t), 1, file) != 1)
703 return RD_FAILED;
704
705 bAudioOnly = (dataType & 0x4) && !(dataType & 0x1);
706
707 Log(LOGDEBUG, "bAudioOnly: %d, size: %llu", bAudioOnly,
708 (unsigned long long) size);
709
710 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
711
712 //if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
713 //{
714 // find the last seekable frame
715 off_t tsize = 0;
716 uint32_t prevTagSize = 0;
717
718 // go through the file and find the last video keyframe
719 do
720 {
721 int xread;
722 skipkeyframe:
723 if (size - tsize < 13)
724 {
725 Log(LOGERROR,
726 "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
727 return RD_FAILED;
728 }
729 fseeko(file, size - tsize - 4, SEEK_SET);
730 xread = fread(buffer, 1, 4, file);
731 if (xread != 4)
732 {
733 Log(LOGERROR, "Couldn't read prevTagSize from file!");
734 return RD_FAILED;
735 }
736
737 prevTagSize = AMF_DecodeInt32(buffer);
738 //Log(LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);
739
740 if (prevTagSize == 0)
741 {
742 Log(LOGERROR, "Couldn't find keyframe to resume from!");
743 return RD_FAILED;
744 }
745
746 if (prevTagSize < 0 || prevTagSize > size - 4 - 13)
747 {
748 Log(LOGERROR,
749 "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!",
750 prevTagSize);
751 return RD_FAILED;
752 }
753 tsize += prevTagSize + 4;
754
755 // read header
756 fseeko(file, size - tsize, SEEK_SET);
757 if (fread(buffer, 1, 12, file) != 12)
758 {
759 Log(LOGERROR, "Couldn't read header!");
760 return RD_FAILED;
761 }
762 //*
763 #ifdef _DEBUG
764 uint32_t ts = AMF_DecodeInt24(buffer + 4);
765 ts |= (buffer[7] << 24);
766 Log(LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts);
767 #endif //*/
768
769 // this just continues the loop whenever the number of skipped frames is > 0,
770 // so we look for the next keyframe to continue with
771 //
772 // this helps if resuming from the last keyframe fails and one doesn't want to start
773 // the download from the beginning
774 //
775 if (nSkipKeyFrames > 0
776 && !(!bAudioOnly
777 && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10)))
778 {
779 #ifdef _DEBUG
780 Log(LOGDEBUG,
781 "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
782 #endif
783 nSkipKeyFrames--;
784 goto skipkeyframe;
785 }
786
787 }
788 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
789
790 // save keyframe to compare/find position in stream
791 *initialFrameType = buffer[0];
792 *nInitialFrameSize = prevTagSize - 11;
793 *initialFrame = (char *) malloc(*nInitialFrameSize);
794
795 fseeko(file, size - tsize + 11, SEEK_SET);
796 if (fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize)
797 {
798 Log(LOGERROR, "Couldn't read last keyframe, aborting!");
799 return RD_FAILED;
800 }
801
802 *dSeek = AMF_DecodeInt24(buffer + 4); // set seek position to keyframe tmestamp
803 *dSeek |= (buffer[7] << 24);
804 //}
805 //else // handle audio only, we can seek anywhere we'd like
806 //{
807 //}
808
809 if (*dSeek < 0)
810 {
811 Log(LOGERROR,
812 "Last keyframe timestamp is negative, aborting, your file is corrupt!");
813 return RD_FAILED;
814 }
815 Log(LOGDEBUG, "Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek,
816 *nInitialFrameSize, *initialFrameType);
817
818 /*
819 // now read the timestamp of the frame before the seekable keyframe:
820 fseeko(file, size-tsize-4, SEEK_SET);
821 if(fread(buffer, 1, 4, file) != 4) {
822 Log(LOGERROR, "Couldn't read prevTagSize from file!");
823 goto start;
824 }
825 uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer);
826 fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET);
827 if(fread(buffer, 1, 4, file) != 4) {
828 Log(LOGERROR, "Couldn't read previous timestamp!");
829 goto start;
830 }
831 uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
832 timestamp |= (buffer[3]<<24);
833
834 Log(LOGDEBUG, "Previous timestamp: %d ms", timestamp);
835 */
836
837 if (*dSeek != 0)
838 {
839 // seek to position after keyframe in our file (we will ignore the keyframes resent by the server
840 // since they are sent a couple of times and handling this would be a mess)
841 fseeko(file, size - tsize + prevTagSize + 4, SEEK_SET);
842
843 // make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets
844 // (including several meta data headers and the keyframe we seeked to)
845 //bNoHeader = true; if bResume==true this is true anyway
846 }
847
848 //}
849
850 return RD_SUCCESS;
851 }
852
853 int
Download(RTMP * rtmp,FILE * file,uint32_t dSeek,uint32_t dLength,double duration,bool bResume,char * metaHeader,uint32_t nMetaHeaderSize,char * initialFrame,int initialFrameType,uint32_t nInitialFrameSize,int nSkipKeyFrames,bool bStdoutMode,bool bLiveStream,bool bHashes,bool bOverrideBufferTime,uint32_t bufferTime,double * percent)854 Download(RTMP * rtmp, // connected RTMP object
855 FILE * file, uint32_t dSeek, uint32_t dLength, double duration, bool bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, bool bStdoutMode, bool bLiveStream, bool bHashes, bool bOverrideBufferTime, uint32_t bufferTime, double *percent) // percentage downloaded [out]
856 {
857 uint32_t timestamp = dSeek;
858 int32_t now, lastUpdate;
859 uint8_t dataType = 0; // will be written into the FLV header (position 4)
860 int bufferSize = 1024 * 1024;
861 char *buffer = (char *) malloc(bufferSize);
862 int nRead = 0;
863 off_t size = ftello(file);
864 unsigned long lastPercent = 0;
865
866 memset(buffer, 0, bufferSize);
867
868 *percent = 0.0;
869
870 if (timestamp)
871 {
872 Log(LOGDEBUG, "Continuing at TS: %d ms\n", timestamp);
873 }
874
875 if (bLiveStream)
876 {
877 LogPrintf("Starting Live Stream\n");
878 }
879 else
880 {
881 // print initial status
882 // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
883 if (duration > 0)
884 {
885 if ((double) timestamp >= (double) duration * 999.0)
886 {
887 LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",
888 (double) timestamp / 1000.0,
889 (double) duration / 1000.0);
890 return RD_SUCCESS;
891 }
892 else
893 {
894 *percent = ((double) timestamp) / (duration * 1000.0) * 100.0;
895 *percent = ((double) (int) (*percent * 10.0)) / 10.0;
896 LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
897 bResume ? "Resuming" : "Starting",
898 (double) size / 1024.0, (double) timestamp / 1000.0,
899 *percent);
900 }
901 }
902 else
903 {
904 LogPrintf("%s download at: %.3f kB\n",
905 bResume ? "Resuming" : "Starting",
906 (double) size / 1024.0);
907 }
908 }
909
910 if (dLength > 0)
911 LogPrintf("For duration: %.3f sec\n", (double) dLength / 1000.0);
912
913 // write FLV header if not resuming
914 if (!bResume)
915 {
916 nRead = WriteHeader(&buffer, bufferSize);
917 if (nRead > 0)
918 {
919 if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
920 (size_t) nRead)
921 {
922 Log(LOGERROR, "%s: Failed writing FLV header, exiting!",
923 __FUNCTION__);
924 free(buffer);
925 return RD_FAILED;
926 }
927 size += nRead;
928 }
929 else
930 {
931 Log(LOGERROR, "Couldn't obtain FLV header, exiting!");
932 free(buffer);
933 return RD_FAILED;
934 }
935 }
936
937 now = RTMP_GetTime();
938 lastUpdate = now - 1000;
939 do
940 {
941 nRead = WriteStream(rtmp, &buffer, bufferSize, ×tamp, bResume
942 && nInitialFrameSize > 0, bLiveStream, dSeek,
943 metaHeader, nMetaHeaderSize, initialFrame,
944 initialFrameType, nInitialFrameSize, &dataType);
945
946 //LogPrintf("nRead: %d\n", nRead);
947 if (nRead > 0)
948 {
949 if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
950 (size_t) nRead)
951 {
952 Log(LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
953 free(buffer);
954 return RD_FAILED;
955 }
956 size += nRead;
957
958 //LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
959 if (duration <= 0) // if duration unknown try to get it from the stream (onMetaData)
960 duration = RTMP_GetDuration(rtmp);
961
962 if (duration > 0)
963 {
964 // make sure we claim to have enough buffer time!
965 if (!bOverrideBufferTime && bufferTime < (duration * 1000.0))
966 {
967 bufferTime = (uint32_t) (duration * 1000.0) + 5000; // extra 5sec to make sure we've got enough
968
969 Log(LOGDEBUG,
970 "Detected that buffer time is less than duration, resetting to: %dms",
971 bufferTime);
972 RTMP_SetBufferMS(rtmp, bufferTime);
973 RTMP_UpdateBufferMS(rtmp);
974 }
975 *percent = ((double) timestamp) / (duration * 1000.0) * 100.0;
976 *percent = ((double) (int) (*percent * 10.0)) / 10.0;
977 if (bHashes)
978 {
979 if (lastPercent + 1 <= *percent)
980 {
981 LogStatus("#");
982 lastPercent = (unsigned long) *percent;
983 }
984 }
985 else
986 {
987 now = RTMP_GetTime();
988 if (abs(now - lastUpdate) > 200)
989 {
990 LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
991 (double) size / 1024.0,
992 (double) (timestamp) / 1000.0, *percent);
993 lastUpdate = now;
994 }
995 }
996 }
997 else
998 {
999 now = RTMP_GetTime();
1000 if (abs(now - lastUpdate) > 200)
1001 {
1002 if (bHashes)
1003 LogStatus("#");
1004 else
1005 LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
1006 (double) (timestamp) / 1000.0);
1007 lastUpdate = now;
1008 }
1009 }
1010 }
1011 #ifdef _DEBUG
1012 else
1013 {
1014 Log(LOGDEBUG, "zero read!");
1015 }
1016 #endif
1017
1018 }
1019 while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp));
1020 free(buffer);
1021
1022 Log(LOGDEBUG, "WriteStream returned: %d", nRead);
1023
1024 if (bResume && nRead == -2)
1025 {
1026 LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
1027 nSkipKeyFrames + 1);
1028 return RD_FAILED;
1029 }
1030
1031 // finalize header by writing the correct dataType (video, audio, video+audio)
1032 if (!bResume && dataType != 0x5 && !bStdoutMode)
1033 {
1034 //Log(LOGDEBUG, "Writing data type: %02X", dataType);
1035 fseek(file, 4, SEEK_SET);
1036 fwrite(&dataType, sizeof(unsigned char), 1, file);
1037 /* resume uses ftell to see where we left off */
1038 fseek(file, 0, SEEK_END);
1039 }
1040
1041 if (nRead == -3)
1042 return RD_SUCCESS;
1043
1044 if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
1045 || RTMP_IsTimedout(rtmp))
1046 {
1047 return RD_INCOMPLETE;
1048 }
1049
1050 return RD_SUCCESS;
1051 }
1052
1053 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
1054
1055 int
parseAMF(AMFObject * obj,const char * arg,int * depth)1056 parseAMF(AMFObject *obj, const char *arg, int *depth)
1057 {
1058 AMFObjectProperty prop = {{0,0}};
1059 int i;
1060 char *p;
1061
1062 if (arg[1] == ':')
1063 {
1064 p = (char *)arg+2;
1065 switch(arg[0])
1066 {
1067 case 'B':
1068 prop.p_type = AMF_BOOLEAN;
1069 prop.p_vu.p_number = atoi(p);
1070 break;
1071 case 'S':
1072 prop.p_type = AMF_STRING;
1073 STR2AVAL(prop.p_vu.p_aval,p);
1074 break;
1075 case 'N':
1076 prop.p_type = AMF_NUMBER;
1077 prop.p_vu.p_number = strtod(p, NULL);
1078 break;
1079 case 'Z':
1080 prop.p_type = AMF_NULL;
1081 break;
1082 case 'O':
1083 i = atoi(p);
1084 if (i)
1085 {
1086 prop.p_type = AMF_OBJECT;
1087 }
1088 else
1089 {
1090 (*depth)--;
1091 return 0;
1092 }
1093 break;
1094 default:
1095 return -1;
1096 }
1097 }
1098 else if (arg[2] == ':' && arg[0] == 'N')
1099 {
1100 p = strchr(arg+3, ':');
1101 if (!p || !*depth)
1102 return -1;
1103 prop.p_name.av_val = (char *)arg+3;
1104 prop.p_name.av_len = p - (arg+3);
1105
1106 p++;
1107 switch(arg[1])
1108 {
1109 case 'B':
1110 prop.p_type = AMF_BOOLEAN;
1111 prop.p_vu.p_number = atoi(p);
1112 break;
1113 case 'S':
1114 prop.p_type = AMF_STRING;
1115 STR2AVAL(prop.p_vu.p_aval,p);
1116 break;
1117 case 'N':
1118 prop.p_type = AMF_NUMBER;
1119 prop.p_vu.p_number = strtod(p, NULL);
1120 break;
1121 case 'O':
1122 prop.p_type = AMF_OBJECT;
1123 break;
1124 default:
1125 return -1;
1126 }
1127 }
1128 else
1129 return -1;
1130
1131 if (*depth)
1132 {
1133 AMFObject *o2;
1134 for (i=0; i<*depth; i++)
1135 {
1136 o2 = &obj->o_props[obj->o_num-1].p_vu.p_object;
1137 obj = o2;
1138 }
1139 }
1140 AMF_AddProp(obj, &prop);
1141 if (prop.p_type == AMF_OBJECT)
1142 (*depth)++;
1143 return 0;
1144 }
1145
1146 int
main(int argc,char ** argv)1147 main(int argc, char **argv)
1148 {
1149 extern char *optarg;
1150
1151 int nStatus = RD_SUCCESS;
1152 double percent = 0;
1153 double duration = 0.0;
1154
1155 int nSkipKeyFrames = 0; // skip this number of keyframes when resuming
1156
1157 bool bOverrideBufferTime = false; // if the user specifies a buffer time override this is true
1158 bool bStdoutMode = true; // if true print the stream directly to stdout, messages go to stderr
1159 bool bResume = false; // true in resume mode
1160 uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise
1161 uint32_t bufferTime = 10 * 60 * 60 * 1000; // 10 hours as default
1162
1163 // meta header and initial frame for the resume mode (they are read from the file and compared with
1164 // the stream we are trying to continue
1165 char *metaHeader = 0;
1166 uint32_t nMetaHeaderSize = 0;
1167
1168 // video keyframe for matching
1169 char *initialFrame = 0;
1170 uint32_t nInitialFrameSize = 0;
1171 int initialFrameType = 0; // tye: audio or video
1172
1173 char *hostname = 0;
1174 AVal playpath = { 0, 0 };
1175 AVal subscribepath = { 0, 0 };
1176 int port = -1;
1177 int protocol = RTMP_PROTOCOL_UNDEFINED;
1178 int retries = 0;
1179 bool bLiveStream = false; // is it a live stream? then we can't seek/resume
1180 bool bHashes = false; // display byte counters not hashes by default
1181
1182 long int timeout = 120; // timeout connection after 120 seconds
1183 uint32_t dStartOffset = 0; // seek position in non-live mode
1184 uint32_t dStopOffset = 0;
1185 uint32_t dLength = 0; // length to play from stream - calculated from seek position and dStopOffset
1186
1187 char *rtmpurl = 0;
1188 AVal swfUrl = { 0, 0 };
1189 AVal tcUrl = { 0, 0 };
1190 AVal pageUrl = { 0, 0 };
1191 AVal app = { 0, 0 };
1192 AVal auth = { 0, 0 };
1193 AVal swfHash = { 0, 0 };
1194 uint32_t swfSize = 0;
1195 AVal flashVer = { 0, 0 };
1196 AVal token = { 0, 0 };
1197 char *sockshost = 0;
1198 AMFObject extras = {0};
1199 int edepth = 0;
1200
1201 char *flvFile = 0;
1202
1203 #undef OSS
1204 #ifdef WIN32
1205 #define OSS "WIN"
1206 #else
1207 #define OSS "LNX"
1208 #endif
1209
1210 char DEFAULT_FLASH_VER[] = OSS " 10,0,22,87";
1211
1212 signal(SIGINT, sigIntHandler);
1213 signal(SIGTERM, sigIntHandler);
1214 #ifndef WIN32
1215 signal(SIGHUP, sigIntHandler);
1216 signal(SIGPIPE, sigIntHandler);
1217 signal(SIGQUIT, sigIntHandler);
1218 #endif
1219
1220 // Check for --quiet option before printing any output
1221 int index = 0;
1222 while (index < argc)
1223 {
1224 if (strcmp(argv[index], "--quiet") == 0
1225 || strcmp(argv[index], "-q") == 0)
1226 debuglevel = LOGCRIT;
1227 index++;
1228 }
1229
1230 LogPrintf("FLVStreamer %s\n", FLVSTREAMER_VERSION);
1231 LogPrintf
1232 ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
1233
1234 if (!InitSockets())
1235 {
1236 Log(LOGERROR,
1237 "Couldn't load sockets support on your platform, exiting!");
1238 return RD_FAILED;
1239 }
1240
1241 /* sleep(30); */
1242
1243 int opt;
1244 struct option longopts[] = {
1245 {"help", 0, NULL, 'h'},
1246 {"host", 1, NULL, 'n'},
1247 {"port", 1, NULL, 'c'},
1248 {"socks", 1, NULL, 'S'},
1249 {"protocol", 1, NULL, 'l'},
1250 {"playpath", 1, NULL, 'y'},
1251 {"rtmp", 1, NULL, 'r'},
1252 {"swfUrl", 1, NULL, 's'},
1253 {"tcUrl", 1, NULL, 't'},
1254 {"pageUrl", 1, NULL, 'p'},
1255 {"app", 1, NULL, 'a'},
1256 {"auth", 1, NULL, 'u'},
1257 {"conn", 1, NULL, 'C'},
1258 {"flashVer", 1, NULL, 'f'},
1259 {"live", 0, NULL, 'v'},
1260 {"flv", 1, NULL, 'o'},
1261 {"resume", 0, NULL, 'e'},
1262 {"timeout", 1, NULL, 'm'},
1263 {"buffer", 1, NULL, 'b'},
1264 {"skip", 1, NULL, 'k'},
1265 {"subscribe", 1, NULL, 'd'},
1266 {"start", 1, NULL, 'A'},
1267 {"stop", 1, NULL, 'B'},
1268 {"token", 1, NULL, 'T'},
1269 {"hashes", 0, NULL, '#'},
1270 {"debug", 0, NULL, 'z'},
1271 {"quiet", 0, NULL, 'q'},
1272 {"verbose", 0, NULL, 'V'},
1273 {0, 0, 0, 0}
1274 };
1275
1276 while ((opt =
1277 getopt_long(argc, argv,
1278 "hVveqzr:s:t:p:a:b:f:o:u:C:n:c:l:y:m:k:d:A:B:T:w:x:W:X:S:#",
1279 longopts, NULL)) != -1)
1280 {
1281 switch (opt)
1282 {
1283 case 'h':
1284 LogPrintf
1285 ("\nThis program dumps the media content streamed over rtmp.\n\n");
1286 LogPrintf("--help|-h Prints this help screen.\n");
1287 LogPrintf
1288 ("--rtmp|-r url URL (e.g. rtmp//hotname[:port]/path)\n");
1289 LogPrintf
1290 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
1291 LogPrintf
1292 ("--port|-c port Overrides the port in the rtmp url\n");
1293 LogPrintf
1294 ("--socks|-S host:port Use the specified SOCKS proxy\n");
1295 LogPrintf
1296 ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 3 - RTMPE)\n");
1297 LogPrintf
1298 ("--playpath|-y Overrides the playpath parsed from rtmp url\n");
1299 LogPrintf("--swfUrl|-s url URL to player swf file\n");
1300 LogPrintf
1301 ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
1302 LogPrintf("--pageUrl|-p url Web URL of played programme\n");
1303 LogPrintf("--app|-a app Name of player used\n");
1304 LogPrintf
1305 ("--auth|-u string Authentication string to be appended to the connect string\n");
1306 LogPrintf
1307 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
1308 LogPrintf
1309 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
1310 LogPrintf
1311 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
1312 LogPrintf
1313 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
1314 DEFAULT_FLASH_VER);
1315 LogPrintf
1316 ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
1317 LogPrintf
1318 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
1319 LogPrintf
1320 ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
1321 LogPrintf
1322 ("--resume|-e Resume a partial RTMP download\n");
1323 LogPrintf
1324 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
1325 timeout);
1326 LogPrintf
1327 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
1328 LogPrintf
1329 ("--stop|-B num Stop at num seconds into stream\n");
1330 LogPrintf
1331 ("--token|-T key Key for SecureToken response\n");
1332 LogPrintf
1333 ("--hashes|-# Display progress with hashes, not with the byte counter\n");
1334 LogPrintf
1335 ("--buffer|-b Buffer time in milliseconds (default: %lu), this option makes only sense in stdout mode (-o -)\n",
1336 bufferTime);
1337 LogPrintf
1338 ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
1339 nSkipKeyFrames);
1340 LogPrintf
1341 ("--quiet|-q Supresses all command output.\n");
1342 LogPrintf("--verbose|-V Verbose command output.\n");
1343 LogPrintf("--debug|-z Debug level command output.\n");
1344 LogPrintf
1345 ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
1346 LogPrintf("packet.\n\n");
1347 return RD_SUCCESS;
1348 case 'k':
1349 nSkipKeyFrames = atoi(optarg);
1350 if (nSkipKeyFrames < 0)
1351 {
1352 Log(LOGERROR,
1353 "Number of keyframes skipped must be greater or equal zero, using zero!");
1354 nSkipKeyFrames = 0;
1355 }
1356 else
1357 {
1358 Log(LOGDEBUG, "Number of skipped key frames for resume: %d",
1359 nSkipKeyFrames);
1360 }
1361 break;
1362 case 'b':
1363 {
1364 int32_t bt = atol(optarg);
1365 if (bt < 0)
1366 {
1367 Log(LOGERROR,
1368 "Buffer time must be greater than zero, ignoring the specified value %d!",
1369 bt);
1370 }
1371 else
1372 {
1373 bufferTime = bt;
1374 bOverrideBufferTime = true;
1375 }
1376 break;
1377 }
1378 case 'v':
1379 bLiveStream = true; // no seeking or resuming possible!
1380 break;
1381 case 'd':
1382 STR2AVAL(subscribepath, optarg);
1383 break;
1384 case 'n':
1385 hostname = optarg;
1386 break;
1387 case 'c':
1388 port = atoi(optarg);
1389 break;
1390 case 'l':
1391 protocol = atoi(optarg);
1392 if (protocol != RTMP_PROTOCOL_RTMP
1393 && protocol != RTMP_PROTOCOL_RTMPE)
1394 {
1395 Log(LOGERROR, "Unknown protocol specified: %d", protocol);
1396 return RD_FAILED;
1397 }
1398 break;
1399 case 'y':
1400 STR2AVAL(playpath, optarg);
1401 break;
1402 case 'r':
1403 {
1404 rtmpurl = optarg;
1405
1406 char *parsedHost = 0;
1407 unsigned int parsedPort = 0;
1408 char *parsedPlaypath = 0;
1409 char *parsedApp = 0;
1410 int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
1411
1412 if (!ParseUrl
1413 (rtmpurl, &parsedProtocol, &parsedHost, &parsedPort,
1414 &parsedPlaypath, &parsedApp))
1415 {
1416 Log(LOGWARNING, "Couldn't parse the specified url (%s)!",
1417 optarg);
1418 }
1419 else
1420 {
1421 if (hostname == 0)
1422 hostname = parsedHost;
1423 if (port == -1)
1424 port = parsedPort;
1425 if (playpath.av_len == 0 && parsedPlaypath)
1426 {
1427 STR2AVAL(playpath, parsedPlaypath);
1428 }
1429 if (protocol == RTMP_PROTOCOL_UNDEFINED)
1430 protocol = parsedProtocol;
1431 if (app.av_len == 0 && parsedApp)
1432 {
1433 STR2AVAL(app, parsedApp);
1434 }
1435 }
1436 break;
1437 }
1438 case 's':
1439 STR2AVAL(swfUrl, optarg);
1440 break;
1441 case 't':
1442 STR2AVAL(tcUrl, optarg);
1443 break;
1444 case 'p':
1445 STR2AVAL(pageUrl, optarg);
1446 break;
1447 case 'a':
1448 STR2AVAL(app, optarg);
1449 break;
1450 case 'f':
1451 STR2AVAL(flashVer, optarg);
1452 break;
1453 case 'o':
1454 flvFile = optarg;
1455 if (strcmp(flvFile, "-"))
1456 bStdoutMode = false;
1457
1458 break;
1459 case 'e':
1460 bResume = true;
1461 break;
1462 case 'u':
1463 STR2AVAL(auth, optarg);
1464 break;
1465 case 'C':
1466 if (parseAMF(&extras, optarg, &edepth))
1467 {
1468 Log(LOGERROR, "Invalid AMF parameter: %s", optarg);
1469 return RD_FAILED;
1470 }
1471 break;
1472 case 'm':
1473 timeout = atoi(optarg);
1474 break;
1475 case 'A':
1476 dStartOffset = (int) (atof(optarg) * 1000.0);
1477 break;
1478 case 'B':
1479 dStopOffset = (int) (atof(optarg) * 1000.0);
1480 break;
1481 case 'T':
1482 STR2AVAL(token, optarg);
1483 break;
1484 case '#':
1485 bHashes = true;
1486 break;
1487 case 'q':
1488 debuglevel = LOGCRIT;
1489 break;
1490 case 'V':
1491 debuglevel = LOGDEBUG;
1492 break;
1493 case 'z':
1494 debuglevel = LOGALL;
1495 break;
1496 case 'S':
1497 sockshost = optarg;
1498 break;
1499 default:
1500 LogPrintf("unknown option: %c\n", opt);
1501 break;
1502 }
1503 }
1504
1505 if (hostname == 0)
1506 {
1507 Log(LOGERROR,
1508 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
1509 return RD_FAILED;
1510 }
1511 if (playpath.av_len == 0)
1512 {
1513 Log(LOGERROR,
1514 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
1515 return RD_FAILED;
1516 }
1517
1518 if (port == -1)
1519 {
1520 Log(LOGWARNING,
1521 "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
1522 port = 1935;
1523 }
1524 if (port == 0)
1525 {
1526 port = 1935;
1527 }
1528 if (protocol == RTMP_PROTOCOL_UNDEFINED)
1529 {
1530 Log(LOGWARNING,
1531 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
1532 protocol = RTMP_PROTOCOL_RTMP;
1533 }
1534 if (flvFile == 0)
1535 {
1536 Log(LOGWARNING,
1537 "You haven't specified an output file (-o filename), using stdout");
1538 bStdoutMode = true;
1539 }
1540
1541 if (bStdoutMode && bResume)
1542 {
1543 Log(LOGWARNING,
1544 "Can't resume in stdout mode, ignoring --resume option");
1545 bResume = false;
1546 }
1547
1548 if (bLiveStream && bResume)
1549 {
1550 Log(LOGWARNING, "Can't resume live stream, ignoring --resume option");
1551 bResume = false;
1552 }
1553
1554 if (flashVer.av_len == 0)
1555 {
1556 STR2AVAL(flashVer, DEFAULT_FLASH_VER);
1557 }
1558
1559
1560 if (tcUrl.av_len == 0 && app.av_len != 0)
1561 {
1562 char str[512] = { 0 };
1563
1564 snprintf(str, 511, "%s://%s:%d/%s", RTMPProtocolStringsLower[protocol],
1565 hostname, port, app.av_val);
1566 tcUrl.av_len = strlen(str);
1567 tcUrl.av_val = (char *) malloc(tcUrl.av_len + 1);
1568 strcpy(tcUrl.av_val, str);
1569 }
1570
1571 int first = 1;
1572
1573 // User defined seek offset
1574 if (dStartOffset > 0)
1575 {
1576 // Live stream
1577 if (bLiveStream)
1578 {
1579 Log(LOGWARNING,
1580 "Can't seek in a live stream, ignoring --start option");
1581 dStartOffset = 0;
1582 }
1583 }
1584
1585 RTMP rtmp = { 0 };
1586 RTMP_Init(&rtmp);
1587 RTMP_SetupStream(&rtmp, protocol, hostname, port, sockshost, &playpath,
1588 &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
1589 &flashVer, &subscribepath, dSeek, 0, bLiveStream, timeout);
1590
1591 /* backward compatibility, we always sent this as true before */
1592 if (auth.av_len)
1593 rtmp.Link.authflag = true;
1594
1595 rtmp.Link.extras = extras;
1596 rtmp.Link.token = token;
1597 off_t size = 0;
1598
1599 // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
1600 if (bResume)
1601 {
1602 nStatus =
1603 OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize,
1604 &duration);
1605 if (nStatus == RD_FAILED)
1606 goto clean;
1607
1608 if (!file)
1609 {
1610 // file does not exist, so go back into normal mode
1611 bResume = false; // we are back in fresh file mode (otherwise finalizing file won't be done)
1612 }
1613 else
1614 {
1615 nStatus = GetLastKeyframe(file, nSkipKeyFrames,
1616 &dSeek, &initialFrame,
1617 &initialFrameType, &nInitialFrameSize);
1618 if (nStatus == RD_FAILED)
1619 {
1620 Log(LOGDEBUG, "Failed to get last keyframe.");
1621 goto clean;
1622 }
1623
1624 if (dSeek == 0)
1625 {
1626 Log(LOGDEBUG,
1627 "Last keyframe is first frame in stream, switching from resume to normal mode!");
1628 bResume = false;
1629 }
1630 }
1631 }
1632
1633 if (!file)
1634 {
1635 if (bStdoutMode)
1636 {
1637 file = stdout;
1638 SET_BINMODE(file);
1639 }
1640 else
1641 {
1642 file = fopen(flvFile, "w+b");
1643 if (file == 0)
1644 {
1645 LogPrintf("Failed to open file! %s\n", flvFile);
1646 return RD_FAILED;
1647 }
1648 }
1649 }
1650
1651 #ifdef _DEBUG
1652 netstackdump = fopen("netstackdump", "wb");
1653 netstackdump_read = fopen("netstackdump_read", "wb");
1654 #endif
1655
1656 while (!RTMP_ctrlC)
1657 {
1658 Log(LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
1659 RTMP_SetBufferMS(&rtmp, bufferTime);
1660
1661 if (first)
1662 {
1663 first = 0;
1664 LogPrintf("Connecting ...\n");
1665
1666 if (!RTMP_Connect(&rtmp, NULL))
1667 {
1668 nStatus = RD_FAILED;
1669 break;
1670 }
1671
1672 Log(LOGINFO, "Connected...");
1673
1674 // User defined seek offset
1675 if (dStartOffset > 0)
1676 {
1677 // Don't need the start offset if resuming an existing file
1678 if (bResume)
1679 {
1680 Log(LOGWARNING,
1681 "Can't seek a resumed stream, ignoring --start option");
1682 dStartOffset = 0;
1683 }
1684 else
1685 {
1686 dSeek = dStartOffset;
1687 }
1688 }
1689
1690 // Calculate the length of the stream to still play
1691 if (dStopOffset > 0)
1692 {
1693 dLength = dStopOffset - dSeek;
1694
1695 // Quit if start seek is past required stop offset
1696 if (dLength <= 0)
1697 {
1698 LogPrintf("Already Completed\n");
1699 nStatus = RD_SUCCESS;
1700 break;
1701 }
1702 }
1703
1704 if (!RTMP_ConnectStream(&rtmp, dSeek, dLength))
1705 {
1706 nStatus = RD_FAILED;
1707 break;
1708 }
1709 }
1710 else
1711 {
1712 nInitialFrameSize = 0;
1713
1714 if (retries)
1715 {
1716 Log(LOGERROR, "Failed to resume the stream\n\n");
1717 if (!RTMP_IsTimedout(&rtmp))
1718 nStatus = RD_FAILED;
1719 else
1720 nStatus = RD_INCOMPLETE;
1721 break;
1722 }
1723 Log(LOGINFO, "Connection timed out, trying to resume.\n\n");
1724 /* Did we already try pausing, and it still didn't work? */
1725 if (rtmp.m_pausing == 3)
1726 {
1727 /* Only one try at reconnecting... */
1728 retries = 1;
1729 dSeek = rtmp.m_pauseStamp;
1730 if (dStopOffset > 0)
1731 {
1732 dLength = dStopOffset - dSeek;
1733 if (dLength <= 0)
1734 {
1735 LogPrintf("Already Completed\n");
1736 nStatus = RD_SUCCESS;
1737 break;
1738 }
1739 }
1740 if (!RTMP_ReconnectStream(&rtmp, bufferTime, dSeek, dLength))
1741 {
1742 Log(LOGERROR, "Failed to resume the stream\n\n");
1743 if (!RTMP_IsTimedout(&rtmp))
1744 nStatus = RD_FAILED;
1745 else
1746 nStatus = RD_INCOMPLETE;
1747 break;
1748 }
1749 }
1750 else if (!RTMP_ToggleStream(&rtmp))
1751 {
1752 Log(LOGERROR, "Failed to resume the stream\n\n");
1753 if (!RTMP_IsTimedout(&rtmp))
1754 nStatus = RD_FAILED;
1755 else
1756 nStatus = RD_INCOMPLETE;
1757 break;
1758 }
1759 bResume = true;
1760 }
1761
1762 nStatus = Download(&rtmp, file, dSeek, dLength, duration, bResume,
1763 metaHeader, nMetaHeaderSize, initialFrame,
1764 initialFrameType, nInitialFrameSize,
1765 nSkipKeyFrames, bStdoutMode, bLiveStream, bHashes,
1766 bOverrideBufferTime, bufferTime, &percent);
1767 free(initialFrame);
1768 initialFrame = NULL;
1769
1770 /* If we succeeded, we're done.
1771 */
1772 if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
1773 break;
1774 }
1775
1776 if (nStatus == RD_SUCCESS)
1777 {
1778 LogPrintf("Download complete\n");
1779 }
1780 else if (nStatus == RD_INCOMPLETE)
1781 {
1782 LogPrintf
1783 ("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
1784 percent);
1785 }
1786
1787 clean:
1788 Log(LOGDEBUG, "Closing connection.\n");
1789 RTMP_Close(&rtmp);
1790
1791 if (file != 0)
1792 fclose(file);
1793
1794 CleanupSockets();
1795
1796 #ifdef _DEBUG
1797 if (netstackdump != 0)
1798 fclose(netstackdump);
1799 if (netstackdump_read != 0)
1800 fclose(netstackdump_read);
1801 #endif
1802 return nStatus;
1803 }
1804