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, &timestamp, 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