Import rtmpdump v1.4.
authordiego <diego@400ebc74-4327-4243-bc38-086b20814532>
Fri, 9 Oct 2009 19:11:36 +0000 (19:11 +0000)
committerdiego <diego@400ebc74-4327-4243-bc38-086b20814532>
Fri, 9 Oct 2009 19:11:36 +0000 (19:11 +0000)
git-svn-id: svn://svn.mplayerhq.hu/rtmpdump@2 400ebc74-4327-4243-bc38-086b20814532

14 files changed:
AMFObject.cpp
ChangeLog
Makefile
README
bytes.cpp [new file with mode: 0644]
bytes.h [new file with mode: 0644]
log.h
parseurl.cpp [new file with mode: 0644]
parseurl.h [new file with mode: 0644]
rtmp.cpp
rtmp.h
rtmpdump.cpp
rtmppacket.cpp
rtmppacket.h

index b41de3b..8965521 100644 (file)
  */
 
 #include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <time.h>
-#include <arpa/inet.h>
+//#include <stdlib.h>
+//#include <stdio.h>
+//#include <time.h>
+//#include <arpa/inet.h>
+
+#ifdef WIN32
+#include <winsock.h> // for htons
+#endif
 
 #include "AMFObject.h"
 #include "log.h"
 #include "rtmp.h"
+#include "bytes.h"
 
 RTMP_LIB::AMFObjectProperty RTMP_LIB::AMFObject::m_invalidProp;
 
@@ -201,7 +206,7 @@ int RTMP_LIB::AMFObjectProperty::Decode(const char * pBuffer, int nSize, bool bD
     case 0x00: //AMF_NUMBER:
       if (nSize < (int)sizeof(double))
         return -1;
-      m_dNumVal = RTMP_LIB::CRTMP::ReadNumber(pBuffer+1);
+      m_dNumVal = ReadNumber(pBuffer+1);
       nSize -= sizeof(double);
       m_type = AMF_NUMBER;
       break;
@@ -266,7 +271,7 @@ int RTMP_LIB::AMFObjectProperty::Decode(const char * pBuffer, int nSize, bool bD
       if (nSize < 10)
               return -1;
 
-      m_dNumVal = RTMP_LIB::CRTMP::ReadNumber(pBuffer+1);
+      m_dNumVal = ReadNumber(pBuffer+1);
       m_nUTCOffset = RTMP_LIB::CRTMP::ReadInt16(pBuffer+9);
 
       m_type = AMF_DATE;
index 89f2897..97613df 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,24 @@
 RTMPDump
 Copyright 2008-2009 Andrej Stepanchuk; Distributed under the GPL v2
 
+11 Mar 2009, v1.4
+
+- fixed resume bug: when the server switches between audio/video packets and FLV
+chunk packets (why should a server want to do that? some actually do!) and rtmpdump 
+was invoked with --resume the keyframe check prevented rtmpdump from continuing
+
+- fixed endianness
+
+- added win32 and arm support (you can cross-compile it onto your Windows box
+or even PDA)
+
+- removed libboost dependency, written a small parser for rtmp urls, but it is
+more of a heuristic one since the rtmp urls can be ambigous in some
+circumstances. The best way is to supply all prameters using the override
+options like --play, --app, etc.
+
+- fixed stream ids (from XBMC tree)
+
 04 Feb 2009, v1.3d
 
 - fixed missing terminating character in url parsing
index d218c21..e6f5e26 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,11 @@
+CROSS=
+CC=$(CROSS)gcc
+CXX=$(CROSS)g++
+LD=$(CROSS)ld
+
+#CC=arm-linux-gcc
+#CXX=arm-linux-g++
+#LD=arm-linux-ld
 CFLAGS=-Wall
 CXXFLAGS=-Wall
 LDFLAGS=-Wall
@@ -5,14 +13,19 @@ LDFLAGS=-Wall
 all: rtmpdump
 
 clean:
-       rm -f rtmpdump *.o
+       rm -f rtmpdump rtmpdump.exe *.o
+
+rtmpdump: bytes.o log.o rtmp.o AMFObject.o rtmppacket.o rtmpdump.o parseurl.o
+       $(CXX) $(LDFLAGS) $^ -o $@
 
-rtmpdump: log.o rtmp.o AMFObject.o rtmppacket.o rtmpdump.o
-       g++ $(LDFLAGS) $^ -o $@ -lboost_regex
+win32: bytes.o log.o rtmp.o AMFObject.o rtmppacket.o rtmpdump.o parseurl.o
+       $(CXX) $(LDFLAGS) $^ -o rtmpdump.exe -lws2_32 -lwinmm
 
+bytes.o: bytes.cpp bytes.h Makefile
 log.o: log.cpp log.h Makefile
 rtmp.o: rtmp.cpp rtmp.h log.h AMFObject.h Makefile
 AMFObject.o: AMFObject.cpp AMFObject.h log.h rtmp.h Makefile
 rtmppacket.o: rtmppacket.cpp rtmppacket.h log.h Makefile
 rtmpdump.o: rtmpdump.cpp rtmp.h log.h AMFObject.h Makefile
+parseurl.o: parseurl.cpp parseurl.h log.h Makefile
 
diff --git a/README b/README
index 488d823..359ad58 100644 (file)
--- a/README
+++ b/README
@@ -1,21 +1,26 @@
-RTMP Dumper v1.3d
+RTMP Dumper v1.4
 (C) 2009 Andrej Stepanchuk
 License: GPLv2
 
-To compile you need the boost libraries, just type
+To compile just type
 
   $ make
 
-To download an rtmp stream use
+or to cross compile
+
+ $ make [win32] CROSS=platform
 
-  $ ./get_iplayer --get 1 --vmode flashhigh --rtmp --rtmpdump "./rtmpdump"
+For example:
+$ make win32 CROSS=mingw32-
+$ make CROSS=arm-linux-
 
-Or for hulu.com (US only)
 
-  $ ./get_hulu url quality
+To download an rtmp stream use
+
+  $ ./get_iplayer --raw --force --get 1 --vmode flashhigh --rtmp --rtmpdump "./rtmpdump"
 
-The url is just a programme url like "http://www.hulu.com/watch/48466/30-rock-christmas-special"
-and quality is a 0, 1 or 2 to select one of the three available streams, use 1 for high quality rtmp.
+rtmpdump still supports hulu (only the non RTMPE streams for now) if you can invoke it with
+a correct rtmp url.
 
 Credit goes to team boxee for the XBMC RTMP code used in RTMPDumper.
 
diff --git a/bytes.cpp b/bytes.cpp
new file mode 100644 (file)
index 0000000..8aab46a
--- /dev/null
+++ b/bytes.cpp
@@ -0,0 +1,113 @@
+/*  RTMPDump
+ *  Copyright (C) 2008-2009 Andrej Stepanchuk
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <string.h>
+#include <sys/types.h>
+//#include <endian.h>
+//#include <byteswap.h>
+#include "bytes.h"
+
+// write dVal as 64bit little endian double
+void WriteNumber(char *data, double dVal)
+{
+       uint64_t res;
+
+#if __FLOAT_WORD_ORDER == __BYTE_ORDER
+#if __BYTE_ORDER == __BIG_ENDIAN
+       res = dVal;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+        uint64_t in  = *((uint64_t*)&dVal);
+        res = __bswap_64(in);
+#endif
+#else
+#if __BYTE_ORDER == __LITTLE_ENDIAN // __FLOAT_WORD_ORER == __BIG_ENDIAN
+        uint32_t in1 = *((uint32_t*)&dVal);
+        uint32_t in2 = *((uint32_t*)((char *)&dVal+4));
+        
+        in1 = __bswap_32(in1);
+        in2 = __bswap_32(in2);
+
+        res = ((uint64_t)in2<<32) | (uint64_t)in1;
+#else // __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN
+        uint32_t in1 = *((uint32_t*)&dVal);
+        uint32_t in2 = *((uint32_t*)((char*)&dVal+4));
+
+        res = ((uint64_t)in1<<32) | (uint64_t)in2;
+#endif
+#endif
+       memcpy(data, &res, 8);
+}
+
+// reads a little endian 64bit double from data
+double ReadNumber(const char *data)
+{
+#if __FLOAT_WORD_ORDER == __BYTE_ORDER
+#if __BYTE_ORDER == __BIG_ENDIAN
+       return *((double *)data);
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+       uint64_t in  = *((uint64_t*)data);
+       uint64_t res = __bswap_64(in);
+       return *((double *)&res);
+#endif
+#else
+#if __BYTE_ORDER == __LITTLE_ENDIAN // __FLOAT_WORD_ORER == __BIG_ENDIAN
+       uint32_t in1 = *((uint32_t*)data);
+       uint32_t in2 = *((uint32_t*)(data+4));
+       
+       in1 = __bswap_32(in1);
+       in2 = __bswap_32(in2);
+
+       uint64_t res = ((uint64_t)in2<<32) | (uint64_t)in1;
+       return *((double *)&res);
+#else // __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN
+       uint32_t in1 = *((uint32_t*)data);
+        uint32_t in2 = *((uint32_t*)(data+4));
+        
+        uint64_t res = ((uint64_t)in1<<32) | (uint64_t)in2;
+        return *((double *)&res);
+#endif
+#endif
+}
+
+// read little-endian 32bit integer
+int ReadInt32LE(const char *data)
+{
+  int val;
+  memcpy(&val, data, sizeof(int));
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+  val = __bswap_32(val);
+#endif
+
+  return val;
+}
+
+// write little-endian 32bit integer
+int EncodeInt32LE(char *output, int nVal)
+{
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+  nVal = __bswap_32(nVal);
+#endif
+
+  memcpy(output, &nVal, sizeof(int));
+  return sizeof(int);
+}
+
diff --git a/bytes.h b/bytes.h
new file mode 100644 (file)
index 0000000..e7ca3ed
--- /dev/null
+++ b/bytes.h
@@ -0,0 +1,63 @@
+#ifndef __BYTES_H__
+#define __BYTES_H__
+
+#include <stdint.h>
+
+#ifdef WIN32
+#define __LITTLE_ENDIAN 1234
+#define __BIG_ENDIAN    4321
+#define __BYTE_ORDER __LITTLE_ENDIAN
+#define __FLOAT_WORD_ORDER __BYTE_ORDER
+
+/*typedef signed char int8_t;
+typedef signed short int16_t;
+typedef signed long int int32_t;
+typedef signed long long int int64_t;
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned long int uint32_t;
+typedef unsigned long long int uint64_t;
+*/
+
+#define __bswap_32(x) \
+     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |               \
+     (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))
+
+#define __bswap_64(x) \
+     ((((x) & 0xff00000000000000ull) >> 56)                                   \
+      | (((x) & 0x00ff000000000000ull) >> 40)                                 \
+      | (((x) & 0x0000ff0000000000ull) >> 24)                                 \
+      | (((x) & 0x000000ff00000000ull) >> 8)                                  \
+      | (((x) & 0x00000000ff000000ull) << 8)                                  \
+      | (((x) & 0x0000000000ff0000ull) << 24)                                 \
+      | (((x) & 0x000000000000ff00ull) << 40)                                 \
+      | (((x) & 0x00000000000000ffull) << 56))
+
+#else
+#include <endian.h>
+#include <byteswap.h>
+
+typedef __uint64_t uint64_t;
+typedef __uint32_t uint32_t;
+#endif
+
+#if !defined(__BYTE_ORDER) || !defined(__FLOAT_WORD_ORDER)
+#error "Undefined byte and float word order!"
+#endif
+
+#if __FLOAT_WORD_ORDER != __BIG_ENDIAN && __FLOAT_WORD_ORDER != __LITTLE_ENDIAN
+#error "Unknown float word order!"
+#endif
+
+#if __BYTE_ORDER != __BIG_ENDIAN && __BYTE_ORDER != __LITTLE_ENDIAN
+#error "Unknown float word order!"
+#endif
+
+void WriteNumber(char *data, double dVal);
+double ReadNumber(const char *data);
+
+int ReadInt32LE(const char *data);
+int EncodeInt32LE(char *output, int nVal);
+
+#endif
+
diff --git a/log.h b/log.h
index 05ee142..9076d39 100644 (file)
--- a/log.h
+++ b/log.h
 #ifndef __LOG_H__
 #define __LOG_H__
 
-//#define _DEBUG
+#define _DEBUG
+
+#ifdef _DEBUG
+#undef NODEBUG
+#endif
 
 #define LOGDEBUG        0
 #define LOGERROR        1
diff --git a/parseurl.cpp b/parseurl.cpp
new file mode 100644 (file)
index 0000000..096da82
--- /dev/null
@@ -0,0 +1,389 @@
+/*  RTMPDump
+ *  Copyright (C) 2009 Andrej Stepanchuk
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <assert.h>
+
+#include "log.h"
+#include "parseurl.h"
+
+#include "rtmp.h"
+/*
+#define RTMP_PROTOCOL_RTMP      0
+#define RTMP_PROTOCOL_RTMPT     1 // not yet supported
+#define RTMP_PROTOCOL_RTMPS     2 // not yet supported
+#define RTMP_PROTOCOL_RTMPE     3 // not yet supported
+#define RTMP_PROTOCOL_RTMPTE    4 // not yet supported
+#define RTMP_PROTOCOL_RTMFP     5 // not yet supported
+*/
+char *str2lower(char *str, int len)
+{
+       char *res = (char *)malloc(len+1);
+       char *p;
+
+       for(p=res; p<res+len; p++, str++) {
+               *p = tolower(*str);
+       }
+
+       *p = 0;
+
+       return res;
+}
+
+bool IsUrlValid(char *url)
+{
+       return true;
+       /*boost::regex re("^rtmp:\\/\\/[a-zA-Z0-9_\\.\\-]+((:(\\d)+\\/)|\\/)(([0-9a-zA-Z_:;\\+\\-\\.\\!\\\"\\$\\%\\&\\/\\(\\)\\=\\?\\<\\>\\s]*)$|$)");
+
+       if (!boost::regex_match(url, re))
+               return false;
+       
+       return true;*/
+}
+
+bool ParseUrl(char *url, int *protocol, char **host, unsigned int *port, char **playpath, char **app)
+{
+       assert(url != 0 && protocol != 0 && host != 0 && port != 0 && playpath != 0 && app != 0);
+
+       Log(LOGDEBUG, "parsing...");
+
+       *protocol = 0; // default: RTMP
+
+       // Old School Parsing
+       char *lw = str2lower(url, 6);
+
+       char *p = strstr(url, "://");
+       int len = (int)(p-url);
+       if(p == 0) {
+               Log(LOGWARNING, "RTMP URL: No :// in url!");
+               free(lw);
+               return false;
+       }
+
+       if(len == 4 && strncmp(lw, "rtmp", 4)==0)
+               *protocol = RTMP_PROTOCOL_RTMP;
+       else if(len == 5 && strncmp(lw, "rtmpt", 5)==0)
+               *protocol = RTMP_PROTOCOL_RTMPT;
+       else if(len == 5 && strncmp(lw, "rtmps", 5)==0)
+               *protocol = RTMP_PROTOCOL_RTMPS;
+       else if(len == 5 && strncmp(lw, "rtmpe", 5)==0)
+               *protocol = RTMP_PROTOCOL_RTMPE;
+       else if(len == 5 && strncmp(lw, "rtmfp", 5)==0)
+               *protocol = RTMP_PROTOCOL_RTMFP;
+       else if(len == 6 && strncmp(lw, "rtmpte", 6)==0)
+               *protocol = RTMP_PROTOCOL_RTMPTE;
+       else {
+               Log(LOGWARNING, "Unknown protocol!\n");
+               goto parsehost;
+       }
+
+       Log(LOGDEBUG, "Parsed protocol: %d", *protocol);
+
+parsehost:
+       free(lw);
+
+       // lets get the hostname
+       p+=3;
+
+       char *temp;
+
+       int iEnd   = strlen(p)-1;
+       int iCol   = iEnd+1; 
+       int iQues  = iEnd+1;
+       int iSlash = iEnd+1;
+
+       if((temp=strstr(p, ":"))!=0)
+               iCol = temp-p;
+       if((temp=strstr(p, "?"))!=0)
+               iQues = temp-p;
+       if((temp=strstr(p, "/"))!=0)
+               iSlash = temp-p;
+
+       int min = iSlash < iEnd ? iSlash : iEnd;
+       min = iQues   < min ? iQues   : min;
+
+       int hostlen = iCol < min ? iCol : min;
+
+       if(min < 256) {
+               *host = (char *)malloc((hostlen+1)*sizeof(char));
+               strncpy(*host, p, hostlen);
+               (*host)[hostlen]=0;
+
+               Log(LOGDEBUG, "Parsed host    : %s", *host);
+       } else {
+               Log(LOGWARNING, "Hostname exceeds 255 characters!");
+       }
+
+       p+=hostlen; iEnd-=hostlen;
+
+       // get the port number if available
+       if(*p == ':') {
+               p++; iEnd--;
+
+               int portlen = min-hostlen-1;
+               if(portlen < 6) {
+                       char portstr[6];
+                       strncpy(portstr,p,portlen);
+                       portstr[portlen]=0;
+
+                       *port = atoi(portstr);
+                       if(*port == 0)
+                               *port = 1935;
+
+                       Log(LOGDEBUG, "Parsed port    : %d", *port);
+               } else {
+                       Log(LOGWARNING, "Port number is longer than 5 characters!");
+               }
+
+               p+=portlen; iEnd-=portlen;
+       }
+
+       if(*p != '/') {
+               Log(LOGWARNING, "No application or playpath in URL!");
+               return true;
+       }
+       p++; iEnd--;
+
+       // parse application
+       //
+       // rtmp://host[:port]/app[/appinstance][/...]
+       // application = app[/appinstance]
+       int iSlash2 = iEnd+1; // 2nd slash
+        int iSlash3 = iEnd+1; // 3rd slash
+
+        if((temp=strstr(p, "/"))!=0)
+               iSlash2 = temp-p;
+       
+       if((temp=strstr(p, "?"))!=0)
+               iQues = temp-p;
+
+       if(iSlash2 < iEnd)
+               if((temp=strstr(p+iSlash2+1, "/"))!=0)
+                       iSlash3 = temp-p;
+
+       //Log(LOGDEBUG, "p:%s, iEnd: %d\niSlash : %d\niSlash2: %d\niSlash3: %d", p, iEnd, iSlash, iSlash2, iSlash3);
+       
+       int applen = iEnd+1; // ondemand, pass all parameters as app
+       int appnamelen = 8; // ondemand length
+
+       if(iQues < iEnd) { // whatever it is, the '?' means we need to use everything as app
+               appnamelen = iQues;
+               applen = iEnd+1; // pass the parameters as well
+       } 
+       else if(strncmp(p, "ondemand/", 9)==0) {
+                // app = ondemand/foobar, only pass app=ondemand
+                applen = 8;
+        }
+       else { // app!=ondemand, so app is app[/appinstance]
+               appnamelen = iSlash2 < iEnd ? iSlash2 : iEnd;
+               if(iSlash3 < iEnd)
+                       appnamelen = iSlash3;
+       
+               applen = appnamelen;
+       }
+
+       *app = (char *)malloc((applen+1)*sizeof(char));
+       strncpy(*app, p, applen);
+       (*app)[applen]=0;
+       Log(LOGDEBUG, "Parsed app     : %s", *app);
+
+       p += appnamelen; 
+       iEnd -= appnamelen;
+
+       // parse playpath
+       int iPlaypathPos = -1;
+       int iPlaypathLen = -1;
+
+       if(*p=='?' && (temp=strstr(p, "slist="))!=0) {
+               iPlaypathPos = temp-p+6;
+
+               int iAnd = iEnd+1;
+               if((temp=strstr(p+iPlaypathPos, "&"))!=0)
+                       iAnd = temp-p;
+               if(iAnd < iEnd)
+                       iPlaypathLen = iAnd-iPlaypathPos;
+               else
+                       iPlaypathLen = iEnd-iPlaypathPos+1;
+       } else { // no slist parameter, so take string after applen 
+               if(iEnd > 0) {
+                       iPlaypathPos = 1;
+                       iPlaypathLen = iEnd-iPlaypathPos+1;
+                       
+                       // filter .flv from playpath specified with slashes: rtmp://host/app/path.flv
+                       if(iPlaypathLen >=4 && strncmp(&p[iPlaypathPos+iPlaypathLen-4], ".flv", 4)==0) {
+                               iPlaypathLen-=4;
+                       }
+               } else {
+                       Log(LOGERROR, "No playpath found!");
+               }
+       }
+
+       if(iPlaypathLen > -1) {
+               *playpath = (char *)malloc((iPlaypathLen+1)*sizeof(char));
+               strncpy(*playpath, &p[iPlaypathPos], iPlaypathLen);
+               (*playpath)[iPlaypathLen]=0;
+
+               Log(LOGDEBUG, "Parsed playpath: %s", *playpath);
+       } else {
+               Log(LOGWARNING, "No playpath in URL!");
+       }
+
+        return true;
+       
+
+       //boost::cmatch matches;
+       /*boost::regex re1("^rtmp:\\/\\/([a-zA-Z0-9_\\.\\-]+)((:([0-9]+)\\/)|\\/)[0-9a-zA-Z_:;\\+\\-\\.\\!\\\"\\$\\%\\&\\/\\(\\)\\=\\?\\<\\>\\s]+");
+       if(boost::regex_match(url, matches, re1))
+       {
+               if(matches[1].second-matches[1].first < 512) {
+                       *host = (char *)malloc(512*sizeof(char));
+                       memcpy(*host, matches[1].first, matches[1].second-matches[1].first);
+                       (*host)[matches[1].second-matches[1].first]=0x00;
+                       Log(LOGDEBUG, "Hostname: %s", *host);
+               } else {
+                       Log(LOGWARNING, "Hostname too long: must not be longer than 255 characters!");
+               }
+
+               char portstr[6];
+               if(matches[4].second-matches[4].first < 6) {
+                       strncpy(portstr, matches[4].first, matches[4].second-matches[4].first);
+                       portstr[matches[4].second-matches[4].first]=0x00;
+                       *port = atoi(portstr);
+               } else {
+                       Log(LOGWARNING, "Port too long: must not be longer than 5 digits!");
+               }
+               
+               std::string strPlay;
+               // use slist parameter, if there is one
+               std::string surl = std::string(url);
+               int nPos = surl.find("slist=");
+               if (nPos > 0)
+                       strPlay = surl.substr(nPos+6, surl.size()-(nPos+6));
+
+               if (strPlay.empty()) {
+                       // or use last piece of URL, if there's more than one level
+                       std::string::size_type pos_slash = surl.find_last_of("/");
+                       if ( pos_slash != std::string::npos )
+                               strPlay = surl.substr(pos_slash+1, surl.size()-(pos_slash+1));
+               }
+
+               if(!strPlay.empty()){
+                       *playpath = (char *)malloc(1024);
+                       if(strlen(strPlay.c_str()) < 1024)
+                               strcpy(*playpath, strPlay.c_str());
+                       else
+                               Log(LOGWARNING, "Playpath too long: must not be longer than 1023 characters!");
+               }
+               return true;
+       }*/
+       return false;
+}
+
+/*
+boost::cmatch matches;
+
+  boost::regex re1("^rtmp:\\/\\/([a-zA-Z0-9_\\.\\-]+)((:([0-9]+)\\/)|\\/)[0-9a-zA-Z_:;\\+\\-\\.\\!\\\"\\$\\%\\&\\/\\(\\)\\=\\?\\<\\>\\s]+");
+  if(!boost::regex_match(url, matches, re1))
+  {
+        Log(LOGERROR, "RTMP Connect: Regex for url doesn't match (error in programme)!");
+        return false;
+  }
+  //for(int i=0; i<matches.size(); i++) {
+  //      Log(LOGDEBUG, "matches[%d]: %s, %s", i, matches[i].first, matches[i].second);
+  //}
+
+  if(matches[1].second-matches[1].first > 255) {
+        Log(LOGERROR, "Hostname must not be longer than 255 characters!");
+        return false;
+  }
+  strncpy(Link.hostname, matches[1].first, matches[1].second-matches[1].first);
+
+  Log(LOGDEBUG, "Hostname: %s", Link.hostname);
+
+  char portstr[256];
+  if(matches[4].second-matches[4].first > 5) {
+          Log(LOGERROR, "Port must not be longer than 5 digits!");
+          return false;
+  }
+  strncpy(portstr, matches[4].first, matches[4].second-matches[4].first);
+  Link.port = atoi(portstr);
+
+*/
+// obtain auth string if available
+  /*
+  boost::regex re2("^.*auth\\=([0-9a-zA-Z_:;\\-\\.\\!\\\"\\$\\%\\/\\(\\)\\=\\s]+)((&.+$)|$)");
+  boost::cmatch matches2;
+  
+  if(boost::regex_match(url, matches2, re2)) {
+    int len = matches2[1].second-matches2[1].first;
+    if(len > 255) {
+        Log(LOGERROR, "Auth string must not be longer than 255 characters!");
+    }
+    Link.auth = (char *)malloc((len+1)*sizeof(char));
+    strncpy(Link.auth, matches2[1].first, len);
+    Link.auth[len]=0;
+
+    Log(LOGDEBUG, "Auth: %s", Link.auth);
+  } else { Link.auth = 0; }
+  //*/
+/*
+// use m_strPlayPath
+  std::string strPlay;// = m_strPlayPath;
+  //if (strPlay.empty())
+  //{
+    // or use slist parameter, if there is one
+    std::string url = std::string(Link.url);
+    int nPos = url.find("slist=");
+    if (nPos > 0)
+      strPlay = url.substr(nPos+6, url.size()-(nPos+6)); //Mid(nPos + 6);
+
+                if (strPlay.empty())
+                {
+                        // or use last piece of URL, if there's more than one level
+                        std::string::size_type pos_slash = url.find_last_of("/");
+                        if ( pos_slash != std::string::npos )
+                                strPlay = url.substr(pos_slash+1, url.size()-(pos_slash+1)); //Mid(pos_slash+1);
+                }
+
+                if (strPlay.empty()){
+                        Log(LOGERROR, "%s, no name to play!", __FUNCTION__);
+                        return false;
+                }
+  //}
+*/
+
+
+//CURL url(m_strLink);
+  /*std::string app = std::string(Link.url);
+
+  std::string::size_type slistPos = std::string(Link.url).find("slist=");
+  if ( slistPos == std::string::npos ){
+    // no slist parameter. send the path as the app
+    // if URL path contains a slash, use the part up to that as the app
+    // as we'll send the part after the slash as the thing to play
+    std::string::size_type pos_slash = app.find_last_of("/");
+    if( pos_slash != std::string::npos ){
+      app = app.substr(0,pos_slash);
+    }
+  }*/
+
diff --git a/parseurl.h b/parseurl.h
new file mode 100644 (file)
index 0000000..7290366
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef _PARSEURL_H_
+#define _PARSEURL_H_
+/*  RTMPDump
+ *  Copyright (C) 2009 Andrej Stepanchuk
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+bool ParseUrl(char *url, int *protocol, char **host, unsigned int *port, char **playpath, char **app);
+
+#endif
+
index 5c22e76..6286e6c 100644 (file)
--- a/rtmp.cpp
+++ b/rtmp.cpp
  *
  */
 
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/time.h>
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <winsock.h>
+#define close(x)       closesocket(x)
+#else
 #include <sys/times.h>
-#include <time.h>
 #include <errno.h>
-#include <boost/regex.hpp>
+#endif
 
 #include "rtmp.h"
 #include "AMFObject.h"
 #include "log.h"
+#include "bytes.h"
 
 #define RTMP_SIG_SIZE 1536
 #define RTMP_LARGE_HEADER_SIZE 12
 
-#define RTMP_BUFFER_CACHE_SIZE (16*1024)
+#define RTMP_BUFFER_CACHE_SIZE (16*1024) // needs to fit largest number of bytes recv() may return
 
 using namespace RTMP_LIB;
 using namespace std;
@@ -47,12 +52,36 @@ static const int packetSize[] = { 12, 8, 4, 1 };
 #define RTMP_PACKET_SIZE_SMALL    2
 #define RTMP_PACKET_SIZE_MINIMUM  3
 
-clock_t GetTime()
+inline int GetSockError() {
+#ifdef WIN32
+       return WSAGetLastError();
+#else
+       return errno;
+#endif
+}
+
+int32_t GetTime()
 {
+#ifdef _DEBUG
+       return 0;
+#elif defined(WIN32)
+       return timeGetTime();
+#else
        struct tms t;
        return times(&t);
+#endif
 }
 
+char RTMPProtocolStrings[][7] =
+{
+       "RTMP",
+       "RTMPT",
+       "RTMPS",
+       "RTMPE",
+       "RTMPTE",
+       "RTMFP"
+};
+
 CRTMP::CRTMP() : m_socket(0)
 {
   Close();
@@ -74,86 +103,53 @@ void CRTMP::SetBufferMS(int size)
 {
   m_nBufferMS = size;
 }
-//*
+
 void CRTMP::UpdateBufferMS()
 {
   SendPing(3, 1, m_nBufferMS);
 }
-//*/
-bool CRTMP::Connect(char *url, char *tcUrl, char *player, char *pageUrl, char *app, char *auth, char *flashVer, double dTime)
-{
-  // check url formatting
-  boost::regex re("^rtmp:\\/\\/[a-zA-Z0-9_\\.\\-]+((:(\\d)+\\/)|\\/)(([0-9a-zA-Z_:;\\+\\-\\.\\!\\\"\\$\\%\\&\\/\\(\\)\\=\\?\\<\\>\\s]*)$|$)");
 
-  if (!boost::regex_match(url, re))
-  {
-       Log(LOGERROR, "RTMP Connect: invalid url!");
-       return false;
-  }
+bool CRTMP::Connect(int protocol, char *hostname, unsigned int port, char *playpath, char *tcUrl, char *swfUrl, char *pageUrl, char *app, char *auth, char *flashVer, double dTime)
+{
+  assert(protocol < 6);
+
+  Log(LOGDEBUG, "Protocol: %s", RTMPProtocolStrings[protocol]);
+  Log(LOGDEBUG, "Hostname: %s", hostname);
+  Log(LOGDEBUG, "Port    : %d", port);
+  Log(LOGDEBUG, "Playpath: %s", playpath);
+
+  if(tcUrl)
+       Log(LOGDEBUG, "tcUrl   : %s", tcUrl);
+  if(swfUrl)
+       Log(LOGDEBUG, "swfUrl  : %s", swfUrl);
+  if(pageUrl)
+       Log(LOGDEBUG, "pageUrl : %s", pageUrl);
+  if(app)
+       Log(LOGDEBUG, "app     : %s", app);
+  if(auth)
+       Log(LOGDEBUG, "auth    : %s", auth);
+  if(flashVer)
+       Log(LOGDEBUG, "flashVer: %s", flashVer);
+  if(dTime > 0)
+       Log(LOGDEBUG, "SeekTime: %lf", dTime);
 
-  Link.url = url;
   Link.tcUrl = tcUrl;
-  Link.player = player;
+  Link.swfUrl = swfUrl;
   Link.pageUrl = pageUrl;
   Link.app = app;
   Link.auth = auth;
   Link.flashVer = flashVer;
   Link.seekTime = dTime;
 
-  boost::cmatch matches;
-
-  boost::regex re1("^rtmp:\\/\\/([a-zA-Z0-9_\\.\\-]+)((:([0-9]+)\\/)|\\/)[0-9a-zA-Z_:;\\+\\-\\.\\!\\\"\\$\\%\\&\\/\\(\\)\\=\\?\\<\\>\\s]+");
-  if(!boost::regex_match(url, matches, re1))
-  {
-       Log(LOGERROR, "RTMP Connect: Regex for url doesn't match (error in programme)!");
-       return false;
-  }
-  /*for(int i=0; i<matches.size(); i++) {
-       Log(LOGDEBUG, "matches[%d]: %s, %s", i, matches[i].first, matches[i].second);
-  }*/
-
-  if(matches[1].second-matches[1].first > 255) {
-       Log(LOGERROR, "Hostname must not be longer than 255 characters!");
-       return false;
-  }
-  strncpy(Link.hostname, matches[1].first, matches[1].second-matches[1].first);
-  Link.hostname[matches[1].second-matches[1].first]=0x00;
-
-  Log(LOGDEBUG, "Hostname: %s", Link.hostname);
-
-  char portstr[6];
-  if(matches[4].second-matches[4].first > 5) {
-          Log(LOGERROR, "Port must not be longer than 5 digits!");
-         return false;
-  }
-  strncpy(portstr, matches[4].first, matches[4].second-matches[4].first);
-  portstr[matches[4].second-matches[4].first]=0x00;
-
-  Link.port = atoi(portstr);
+  Link.protocol = protocol;
+  Link.hostname = hostname;
+  Link.port = port;
+  Link.playpath = playpath;
 
   if (Link.port == 0)
     Link.port = 1935;
   
-  Log(LOGDEBUG, "Port: %d", Link.port);
-
-  // obtain auth string if available
-  /*
-  boost::regex re2("^.*auth\\=([0-9a-zA-Z_:;\\-\\.\\!\\\"\\$\\%\\/\\(\\)\\=\\s]+)((&.+$)|$)");
-  boost::cmatch matches2;
-  
-  if(boost::regex_match(url, matches2, re2)) {
-    int len = matches2[1].second-matches2[1].first;
-    if(len > 255) {
-       Log(LOGERROR, "Auth string must not be longer than 255 characters!");
-    }
-    Link.auth = (char *)malloc((len+1)*sizeof(char));
-    strncpy(Link.auth, matches2[1].first, len);
-    Link.auth[len]=0;
-
-    Log(LOGDEBUG, "Auth: %s", Link.auth);
-  } else { Link.auth = 0; }
-  //*/
-
+  // close any previous connection
   Close();
 
   sockaddr_in service;
@@ -173,11 +169,11 @@ bool CRTMP::Connect(char *url, char *tcUrl, char *player, char *pageUrl, char *a
 
   service.sin_port = htons(Link.port);
   m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-  if (m_socket != )
+  if (m_socket != -1)
   {
     if (connect(m_socket, (sockaddr*) &service, sizeof(struct sockaddr)) < 0)
     {
-      Log(LOGERROR, "%s, failed to connect.", __FUNCTION__);
+      Log(LOGERROR, "%s, failed to connect socket. Error: %d", __FUNCTION__, GetSockError());
       Close();
       return false;
     }
@@ -193,22 +189,21 @@ bool CRTMP::Connect(char *url, char *tcUrl, char *player, char *pageUrl, char *a
     Log(LOGDEBUG, "handshaked");
     if (!Connect())
     {
-      Log(LOGERROR, "%s, connect failed.", __FUNCTION__);
+      Log(LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
       Close();
       return false;
     }
     // set timeout
-      struct timeval tv;
-      memset(&tv, 0, sizeof(tv));
-      tv.tv_sec = 300;
-      if (setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof(tv)))
-      {
-       Log(LOGERROR,"Setting timeout failed!");
-      }
+    struct timeval tv;
+    memset(&tv, 0, sizeof(tv));
+    tv.tv_sec = 300;
+    if (setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof(tv))) {
+       Log(LOGERROR,"%s, Setting socket timeout to %ds failed!", __FUNCTION__, tv.tv_sec);
+    }
   }
   else
   {
-    Log(LOGERROR, "%s, failed to create socket.", __FUNCTION__);
+    Log(LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__, GetSockError());
     return false;
   }
 
@@ -223,8 +218,7 @@ bool CRTMP::GetNextMediaPacket(RTMPPacket &packet)
     if (!packet.IsReady())
     {
       packet.FreePacket();
-      usleep(5000); // 5ms
-      //sleep(0.1);//30);
+      //usleep(5000); // 5ms
       continue;
     }
 
@@ -271,14 +265,14 @@ bool CRTMP::GetNextMediaPacket(RTMPPacket &packet)
 
       case 0x12:
         // metadata (notify)
-        Log(LOGDEBUG, "%s, received: notify", __FUNCTION__);
+        Log(LOGDEBUG, "%s, received: notify %lu bytes", __FUNCTION__, packet.m_nBodySize);
         HandleMetadata(packet.m_body, packet.m_nBodySize);
         bHasMediaPacket = true;
         break;
 
       case 0x14:
         // invoke
-       Log(LOGDEBUG, "%s, received: invoke", __FUNCTION__);
+       Log(LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__, packet.m_nBodySize);
         HandleInvoke(packet);
         break;
 
@@ -293,7 +287,7 @@ bool CRTMP::GetNextMediaPacket(RTMPPacket &packet)
         break;
 
       default:
-        Log(LOGDEBUG, "unknown packet type received: 0x%02x", packet.m_packetType);
+        Log(LOGDEBUG, "i%s, unknown packet type received: 0x%02x", __FUNCTION__, packet.m_packetType);
     }
 
     if (!bHasMediaPacket) { 
@@ -310,31 +304,30 @@ bool CRTMP::GetNextMediaPacket(RTMPPacket &packet)
 int CRTMP::ReadN(char *buffer, int n)
 {
   int nOriginalSize = n;
+  
+  #ifdef _DEBUG
   memset(buffer, 0, n);
+  #endif
+
   char *ptr = buffer;
   while (n > 0)
   {
     int nBytes = 0;
-    // for dumping we don't need buffering, so we won't get stuck in the end!
-    /*if (m_bPlaying)
-    {
-      if (m_nBufferSize < n)
-        FillBuffer();
-
-      int nRead = ((n<m_nBufferSize)?n:m_nBufferSize);
-      if (nRead > 0)
-      {
-        memcpy(buffer, m_pBuffer, nRead);
-        memmove(m_pBuffer, m_pBuffer + nRead, m_nBufferSize - nRead); // crunch buffer
-        m_nBufferSize -= nRead;
-        nBytes = nRead;
-        m_nBytesIn += nRead;
-        if (m_nBytesIn > m_nBytesInSent + (600*1024) ) // report every 600K
-          SendBytesReceived();
-      }
-    }
-    else*/
-      nBytes = recv(m_socket, ptr, n, 0);
+// todo, test this code:
+/*
+    if(m_nBufferSize == 0)
+       FillBuffer();
+    int nRead = ((n<m_nBufferSize)?n:m_nBufferSize;
+    if(nRead > 0) {
+       memcpy(ptr, m_pBufferStart, nRead);
+       m_pBufferStart += nRead;
+       m_nBufferSize -= nRead;
+       nBytes = nRead;
+       m_nBytesIn += nRead;
+       if(m_nBytesIn > m_nBytesInSent + (600*1024)) // report every 600K
+               SendBytesReceived();
+    }//*/
+    nBytes = recv(m_socket, ptr, n, 0);
 
     if(m_bPlaying) {
         m_nBytesIn += nBytes;
@@ -344,7 +337,7 @@ int CRTMP::ReadN(char *buffer, int n)
  
     if (nBytes == -1)
     {
-      Log(LOGERROR, "%s, RTMP recv error %d", __FUNCTION__, errno);
+      Log(LOGERROR, "%s, RTMP recv error %d", __FUNCTION__, GetSockError());
       Close();
       return false;
     }
@@ -363,15 +356,22 @@ int CRTMP::ReadN(char *buffer, int n)
   return nOriginalSize - n;
 }
 
+#ifdef _DEBUG
+extern FILE *netstackdump;
+#endif
+
 bool CRTMP::WriteN(const char *buffer, int n)
 {
   const char *ptr = buffer;
   while (n > 0)
   {
+#ifdef _DEBUG
+       fwrite(ptr, 1, n, netstackdump);
+#endif
     int nBytes = send(m_socket, ptr, n, 0);
     if (nBytes < 0)
     {
-      Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, errno, n);
+      Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, GetSockError(), n);
       Close();
       return false;
     }
@@ -415,8 +415,8 @@ bool CRTMP::SendConnectPacket()
        enc += EncodeString(enc, "app", Link.app);
   if(Link.flashVer)
        enc += EncodeString(enc, "flashVer", Link.flashVer);
-  if(Link.player)
-       enc += EncodeString(enc, "swfUrl", Link.player);
+  if(Link.swfUrl)
+       enc += EncodeString(enc, "swfUrl", Link.swfUrl);
   if(Link.tcUrl)
        enc += EncodeString(enc, "tcUrl", Link.tcUrl);
   
@@ -444,6 +444,27 @@ bool CRTMP::SendConnectPacket()
   return SendRTMP(packet);
 }
 
+bool CRTMP::SendBGHasStream(double dId, char *playpath)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // INVOKE
+
+  packet.AllocPacket(256); // should be enough
+  char *enc = packet.m_body;
+  enc += EncodeString(enc, "bgHasStream");
+  enc += EncodeNumber(enc, dId);
+  *enc = 0x05; // NULL
+  enc++;
+
+  enc += EncodeString(enc, playpath);
+
+  packet.m_nBodySize = enc-packet.m_body;
+
+  return SendRTMP(packet);
+}
+
 bool CRTMP::SendCreateStream(double dStreamId)
 {
   RTMPPacket packet;
@@ -566,7 +587,7 @@ bool CRTMP::SendCheckBWResult()
   packet.AllocPacket(256); // should be enough
   char *enc = packet.m_body;
   enc += EncodeString(enc, "_result");
-  enc += EncodeNumber(enc, (double)time(NULL)); // temp
+  enc += EncodeNumber(enc, (double)GetTime()); // temp
   *enc = 0x05; // NULL
   enc++;
   enc += EncodeNumber(enc, (double)m_nBWCheckCounter++); 
@@ -582,7 +603,7 @@ bool CRTMP::SendPlay()
   packet.m_nChannel = 0x08;   // we make 8 our stream channel
   packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
   packet.m_packetType = 0x14; // INVOKE
-  packet.m_nInfoField2 = 0x01000000;
+  packet.m_nInfoField2 = m_stream_id; //0x01000000;
 
   packet.AllocPacket(256); // should be enough
   char *enc = packet.m_body;
@@ -590,33 +611,30 @@ bool CRTMP::SendPlay()
   enc += EncodeNumber(enc, 0.0);
   *enc = 0x05; // NULL
   enc++;
-  // use m_strPlayPath
-  std::string strPlay;// = m_strPlayPath;
-  //if (strPlay.empty())
-  //{
-    // or use slist parameter, if there is one
-    std::string url = std::string(Link.url);
-    int nPos = url.find("slist=");
-    if (nPos > 0)
-      strPlay = url.substr(nPos+6, url.size()-(nPos+6)); //Mid(nPos + 6);
-               
-               if (strPlay.empty())
-               {
-                       // or use last piece of URL, if there's more than one level
-                       std::string::size_type pos_slash = url.find_last_of("/");
-                       if ( pos_slash != std::string::npos )
-                               strPlay = url.substr(pos_slash+1, url.size()-(pos_slash+1)); //Mid(pos_slash+1);
-               }
-
-               if (strPlay.empty()){
-                       Log(LOGERROR, "%s, no name to play!", __FUNCTION__);
-                       return false;
-               }
-  //}
 
-  Log(LOGDEBUG, "Sending play: %s", strPlay.c_str());
-  enc += EncodeString(enc, strPlay.c_str());
-  enc += EncodeNumber(enc, 0.0);
+  Log(LOGDEBUG, "%s, sending play: %s", __FUNCTION__, Link.playpath);
+  enc += EncodeString(enc, Link.playpath);
+ /* 
+  *enc = 0x00; enc++;
+  *enc = 0xc0; enc++;
+  *enc = 0x8f; enc++;
+  *enc = 0x40; enc++;
+  *enc = 0x00; enc++;
+  *enc = 0x00; enc++;
+  *enc = 0x00; enc++;
+  *enc = 0x00; enc++;
+  *enc = 0x00; enc++;
+
+  double d= ReadNumber(enc-8);
+  
+  Log(LOGERROR, "dd: %lf", d);
+  exit(1);
+  // */
+  enc += EncodeNumber(enc, 0.0); // tincan
+  //////enc += EncodeNumber(enc, -1.0); // seems to work for all so far
+  //enc += EncodeNumber(enc, -1000.0); // k-tv.at
+  
+  //enc += EncodeNumber(enc, 4.0); // well is this the duration, lets see
 
   packet.m_nBodySize = enc - packet.m_body;
 
@@ -682,10 +700,13 @@ void CRTMP::HandleInvoke(const RTMPPacket &packet)
     {
       SendServerBW();
       SendPing(3, 0, 300);
+
       SendCreateStream(2.0);
     }
     else if (methodInvoked == "createStream")
     {
+      m_stream_id = (int)obj.GetProperty(3).GetNumber();
+
       SendPlay();
       if(Link.seekTime > 0) {
        Log(LOGDEBUG, "%s, sending seek: %f ms", __FUNCTION__, Link.seekTime);
@@ -726,6 +747,8 @@ void CRTMP::HandleInvoke(const RTMPPacket &packet)
     ||  code == "NetStream.Play.Stop")
       Close();
 
+    //if (code == "NetStream.Play.Complete")
+
     /*if(Link.seekTime > 0) {
        if(code == "NetStream.Seek.Notify") { // seeked successfully, can play now!
                bSeekedSuccessfully = true;
@@ -822,13 +845,13 @@ void CRTMP::HandleVideo(const RTMPPacket &packet)
 void CRTMP::HandlePing(const RTMPPacket &packet)
 {
   short nType = -1;
-  if (packet.m_body && packet.m_nBodySize >= sizeof(short))
+  if (packet.m_body && packet.m_nBodySize >= 2)
     nType = ReadInt16(packet.m_body);
-  Log(LOGDEBUG, "server sent ping. type: %d", nType);
+  Log(LOGDEBUG, "%s, received ping. type: %d", __FUNCTION__, nType);
 
   if (nType == 0x06 && packet.m_nBodySize >= 6) // server ping. reply with pong.
   {
-    unsigned int nTime = ReadInt32(packet.m_body + sizeof(short));
+    unsigned int nTime = ReadInt32(packet.m_body + 2);
     SendPing(0x07, nTime);
   }
 }
@@ -875,7 +898,7 @@ bool CRTMP::ReadPacket(RTMPPacket &packet)
     packet.m_packetType = header[6];
 
   if (nSize == 11)
-    packet.m_nInfoField2 = ReadInt32(header+7);
+    packet.m_nInfoField2 = ReadInt32LE(header+7);
 
   if (packet.m_nBodySize > 0 && packet.m_body == NULL && !packet.AllocPacket(packet.m_nBodySize))
   {
@@ -929,6 +952,7 @@ int  CRTMP::ReadInt24(const char *data)
   return ntohl(val);
 }
 
+// big-endian 32bit integer
 int  CRTMP::ReadInt32(const char *data)
 {
   int val;
@@ -956,19 +980,6 @@ bool CRTMP::ReadBool(const char *data)
   return *data == 0x01;
 }
 
-double CRTMP::ReadNumber(const char *data)
-{
-  double val;
-  char *dPtr = (char *)&val;
-  for (int i=7;i>=0;i--)
-  {
-    *dPtr = data[i];
-    dPtr++;
-  }
-
-  return val;
-}
-
 int CRTMP::EncodeString(char *output, const std::string &strName, const std::string &strValue)
 {
   char *buf = output;
@@ -999,6 +1010,7 @@ int CRTMP::EncodeInt24(char *output, int nVal)
   return 3;
 }
 
+// big-endian 32bit integer
 int CRTMP::EncodeInt32(char *output, int nVal)
 {
   nVal = htonl(nVal);
@@ -1058,13 +1070,7 @@ int CRTMP::EncodeNumber(char *output, double dVal)
   *buf = 0x00; // type: Number
   buf++;
 
-  char *dPtr = (char *)&dVal;
-  for (int i=7;i>=0;i--)
-  {
-    buf[i] = *dPtr;
-    dPtr++;
-  }
-
+  WriteNumber(buf, dVal);
   buf += 8;
 
   return buf - output;
@@ -1085,29 +1091,70 @@ int CRTMP::EncodeBoolean(char *output, bool bVal)
 
 bool CRTMP::HandShake()
 {
+  bool encrypted = 0;//Link.protocol == RTMP_PROTOCOL_RTMPE || Link.protocol == RTMP_PROTOCOL_RTMPTE;
+
   char clientsig[RTMP_SIG_SIZE+1];
   char serversig[RTMP_SIG_SIZE];
 
-  //Log(LOGDEBUG, "HandShake: ");
-  clientsig[0] = 0x3;
+  if(encrypted)
+       clientsig[0] = 0x06;
+  else
+       clientsig[0] = 0x03;
+  
   uint32_t uptime = htonl(GetTime());
   memcpy(clientsig + 1, &uptime, 4);
-  memset(clientsig + 5, 0, 4);
 
-  for (int i=9; i<=RTMP_SIG_SIZE; i++)
-    clientsig[i] = (char)(rand() % 256);
+  /* TODO RTMPE ;), its just RC4 with diffie-hellman
+  // set version to 9.0.124.0
+  clientsig[5] = 0x09;
+  clientsig[6] = 0x00;
+  clientsig[7] = 0x7C;
+  clientsig[8] = 0x00;
+  */
+  memset(&clientsig[5], 0, 4);
+
+#ifdef _DEBUG
+    for (int i=9; i<=RTMP_SIG_SIZE; i++) 
+      clientsig[i] = 0xff;
+#else
+    for (int i=9; i<=RTMP_SIG_SIZE; i++)
+      clientsig[i] = (char)(rand() % 256);
+#endif
 
   if (!WriteN(clientsig, RTMP_SIG_SIZE + 1))
     return false;
 
-  char dummy;
-  if (ReadN(&dummy, 1) != 1) // 0x03
+  char type;
+  if (ReadN(&type, 1) != 1) // 0x03 or 0x06
     return false;
 
+  Log(LOGDEBUG, "%s: Type Answer   : %02X", __FUNCTION__, type);
   
+  if(type != clientsig[0])
+       Log(LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d", __FUNCTION__, clientsig[0], type);
+
   if (ReadN(serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
     return false;
 
+  // decode server response
+  uint32_t suptime;
+
+  memcpy(&suptime, serversig, 4);
+  suptime = ntohl(suptime);
+
+  Log(LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime);
+  Log(LOGDEBUG, "%s: FMS Version   : %d.%d.%d.%d", __FUNCTION__, serversig[4], serversig[5], serversig[6], serversig[7]);
+
+  /*printf("Server signature:\n");
+  for(int i=0; i<RTMP_SIG_SIZE; i++) {
+       printf("%02X ", serversig[i]);
+  }
+  printf("\n");*/
+
+  // do Diffie-Hellmann Key exchange for encrypted RTMP
+  // ....
+
+  // 2nd part of handshake
   char resp[RTMP_SIG_SIZE];
   if (ReadN(resp, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
     return false;
@@ -1157,11 +1204,11 @@ bool CRTMP::SendRTMP(RTMPPacket &packet)
   }
 
   if (nSize > 8)
-    EncodeInt32(header+8, packet.m_nInfoField2);
+    EncodeInt32LE(header+8, packet.m_nInfoField2);
 
   if (!WriteN(header, nSize))
   {
-    Log(LOGWARNING, "couldnt send rtmp header");
+    Log(LOGWARNING, "couldn't send rtmp header");
     return false;
   }
 
@@ -1221,18 +1268,18 @@ void CRTMP::Close()
 
 bool CRTMP::FillBuffer()
 {
-  time_t now = time(NULL);
-  while (m_nBufferSize < RTMP_BUFFER_CACHE_SIZE && time(NULL) - now < 4)
-  {
-    int nBytes = recv(m_socket, m_pBuffer + m_nBufferSize, RTMP_BUFFER_CACHE_SIZE - m_nBufferSize, 0);
-    if (nBytes != -1)
-      m_nBufferSize += nBytes;
+    assert(m_nBufferSize == 0); // only fill buffer when it's empty
+    int nBytes = recv(m_socket, m_pBuffer, RTMP_BUFFER_CACHE_SIZE, 0);
+    if(nBytes != -1) {
+       m_nBufferSize += nBytes;
+       m_pBufferStart = m_pBuffer;
+    }
     else
     {
-      Log(LOGDEBUG, "%s, read buffer returned %d. errno: %d", __FUNCTION__, nBytes, errno);
-      break;
+      Log(LOGDEBUG, "%s, recv returned %d. GetSockError(): %d", __FUNCTION__, nBytes, GetSockError());
+      Close();
+      return false;
     }
-  }
 
   return true;
 }
diff --git a/rtmp.h b/rtmp.h
index 7394394..1bc3100 100644 (file)
--- a/rtmp.h
+++ b/rtmp.h
  *
  */
 
-#include <string>
-#include <vector>
+//#include <string>
+//#include <vector>
 
-#include <sys/types.h>
-#include <sys/socket.h>
+#ifdef WIN32
+#else
+//#include <sys/types.h>
+//#include <sys/socket.h>
 #include <netdb.h>
 #include <arpa/inet.h>
-#include <unistd.h>
-#include <netinet/in.h>
+//#include <unistd.h>
+//#include <netinet/in.h>
+#endif
 
 #include "AMFObject.h"
 #include "rtmppacket.h"
 
+#define RTMP_PROTOCOL_UNDEFINED        -1
+#define RTMP_PROTOCOL_RTMP      0
+#define RTMP_PROTOCOL_RTMPT     1 // not yet supported
+#define RTMP_PROTOCOL_RTMPS     2 // not yet supported
+#define RTMP_PROTOCOL_RTMPE     3 // not yet supported
+#define RTMP_PROTOCOL_RTMPTE    4 // not yet supported
+#define RTMP_PROTOCOL_RTMFP     5 // not yet supported
+
 namespace RTMP_LIB
 {
 
 typedef struct
 {
-        char hostname[256];
+        char *hostname;
         unsigned int port;
+       int protocol;
+       char *playpath;
 
-        char *url;
         char *tcUrl;
-        char *player;
+        char *swfUrl;
         char *pageUrl;
         char *app;
         char *auth;
@@ -61,13 +73,22 @@ class CRTMP
       CRTMP();
       virtual ~CRTMP();
 
-      //void SetPlayer(const std::string &strPlayer);
-      //void SetPageUrl(const std::string &strPageUrl);
-      //void SetPlayPath(const std::string &strPlayPath);
       void SetBufferMS(int size);
       void UpdateBufferMS();
 
-      bool Connect(char *url, char *tcUrl, char *player, char *pageUrl, char *app, char *auth, char *flashVer, double dTime);
+      bool Connect(
+       int protocol, 
+       char *hostname, 
+       unsigned int port, 
+       char *playpath, 
+       char *tcUrl, 
+       char *swfUrl, 
+       char *pageUrl, 
+       char *app, 
+       char *auth, 
+       char *flashVer, 
+       double dTime);
+
       bool IsConnected(); 
       double GetDuration();
 
@@ -100,6 +121,7 @@ class CRTMP
       bool SendCheckBW();
       bool SendCheckBWResult();
       bool SendPing(short nType, unsigned int nObject, unsigned int nTime = 0);
+      bool SendBGHasStream(double dId, char *playpath);
       bool SendCreateStream(double dStreamId);
       bool SendPlay();
       bool SendPause();
@@ -132,6 +154,7 @@ class CRTMP
       int  m_nBytesInSent;
       bool m_bPlaying;
       int  m_nBufferMS;
+      int  m_stream_id; // returned in _result from invoking createStream
 
       //std::string m_strPlayer;
       //std::string m_strPageUrl;
@@ -141,8 +164,9 @@ class CRTMP
       std::vector<std::string> m_methodCalls; //remote method calls queue
 
       LNK Link;
-      char *m_pBuffer;
-      int  m_nBufferSize;
+      char *m_pBuffer;      // data read from socket
+      char *m_pBufferStart; // pointer into m_pBuffer of next byte to process
+      int  m_nBufferSize;   // number of unprocessed bytes in buffer
       RTMPPacket m_vecChannelsIn[64];
       RTMPPacket m_vecChannelsOut[64];
 
index dcd2cf4..d7e28a7 100644 (file)
  *  http://www.gnu.org/copyleft/gpl.html
  *
  */
-#include <string>
-#include <stdio.h>
+
 #include <stdlib.h>
 #include <string.h>
 #include <math.h>
-#include <signal.h> // to catch Ctrl-C
 
+#include <signal.h> // to catch Ctrl-C
 #include <getopt.h>
 
+#ifdef WIN32
+#include <winsock.h>
+#endif
+
 #include "rtmp.h"
 #include "log.h"
 #include "AMFObject.h"
+#include "parseurl.h"
 
 using namespace RTMP_LIB;
 
-#define RTMPDUMP_VERSION       "v1.3d"
+#define RTMPDUMP_VERSION       "v1.4"
 
 #define RD_SUCCESS             0
 #define RD_FAILED              1
 #define RD_INCOMPLETE          2
 
+inline void InitSockets() {
+#ifdef WIN32
+        WORD version;
+        WSADATA wsaData;
+
+        version = MAKEWORD(1,1);
+        WSAStartup(version, &wsaData);
+#endif
+}
+
+inline void CleanupSockets() {
+#ifdef WIN32
+        WSACleanup();
+#endif
+}
+
 uint32_t nTimeStamp = 0;
 
 #ifdef _DEBUG
 uint32_t debugTS = 0;
 int pnum=0;
+
+FILE *netstackdump = 0;
 #endif
 
 uint32_t nIgnoredFlvFrameCounter = 0;
@@ -99,7 +121,7 @@ int WriteStream(
                }
 #ifdef _DEBUG
                debugTS += packet.m_nInfoField1;
-               Log(LOGDEBUG, "type: %d, size: %d, TS: %d ms, sent TS: %d ms", packet.m_packetType, nPacketLen, debugTS, packet.m_nInfoField1);
+               Log(LOGDEBUG, "type: %02X, size: %d, TS: %d ms, sent TS: %d ms", packet.m_packetType, nPacketLen, debugTS, packet.m_nInfoField1);
                if(packet.m_packetType == 0x09)
                        Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0));
 #endif
@@ -141,11 +163,11 @@ int WriteStream(
                                // hande FLV streams, even though the server resends the keyframe as an extra video packet
                                // it is also included in the first FLV stream chunk and we have to compare it and
                                // filter it out !!
+                               //
                                if(packet.m_packetType == 0x16) {
                                        // basically we have to find the keyframe with the correct TS being nTimeStamp
                                        unsigned int pos=0;
                                        uint32_t ts = 0;
-                                       //bool bFound = false;
 
                                        while(pos+11 < nPacketLen) {
                                                uint32_t dataSize = CRTMP::ReadInt24(packetBody+pos+1); // size without header (11) and prevTagSize (4)
@@ -177,8 +199,9 @@ int WriteStream(
 
                                                                goto stopKeyframeSearch;
 
-                                                       } else if(nTimeStamp < ts)
+                                                       } else if(nTimeStamp < ts) {
                                                                goto stopKeyframeSearch; // the timestamp ts will only increase with further packets, wait for seek
+                                                       }
                                                } 
                                                pos += (11+dataSize+4);
                                        }
@@ -195,6 +218,18 @@ stopKeyframeSearch:
                                }
                        }
                }
+               
+               if(bNoHeader && packet.m_nInfoField1 > 0 && (bFoundFlvKeyframe || bFoundKeyframe)) {
+                       // another problem is that the server can actually change from 09/08 video/audio packets to an FLV stream
+                       // or vice versa and our keyframe check will prevent us from going along with the new stream if we resumed
+                       //
+                       // in this case set the 'found keyframe' variables to true
+                       // We assume that if we found one keyframe somewhere and were already beyond TS > 0 we have written
+                       // data to the output which means we can accept all forthcoming data inclusing the change between 08/09 <-> FLV
+                       // packets
+                       bFoundFlvKeyframe = true;
+                       bFoundKeyframe = true;
+               }
 
                 // skip till we find out keyframe (seeking might put us somewhere before it)
                if(bNoHeader && !bFoundKeyframe && packet.m_packetType != 0x16) {
@@ -351,8 +386,8 @@ FILE *file = 0;
 bool bCtrlC = false;
 
 void sigIntHandler(int sig) {
-       printf("Catched signal: %d, cleaning up, just a second...\n", sig);
        bCtrlC = true;
+       printf("Caught signal: %d, cleaning up, just a second...\n", sig);
        signal(SIGINT, SIG_DFL);
 }
 
@@ -385,7 +420,12 @@ int main(int argc, char **argv)
        uint32_t nInitialFrameSize = 0;
        int initialFrameType = 0; // tye: audio or video
 
-       char *url = 0;
+       char *hostname = 0;
+       char *playpath = 0;
+       int port = -1;
+       int protocol = RTMP_PROTOCOL_UNDEFINED;
+
+       char *rtmpurl = 0;
        char *swfUrl = 0;
        char *tcUrl = 0;
        char *pageUrl = 0;
@@ -403,6 +443,10 @@ int main(int argc, char **argv)
        int opt;
        struct option longopts[] = {
                {"help",    0, NULL, 'h'},
+               {"host",    1, NULL, 'n'},
+               {"port",    1, NULL, 'c'},
+               {"protocol",1, NULL, 'l'},
+               {"playpath",1, NULL, 'y'},
                {"rtmp",    1, NULL, 'r'},
                {"swfUrl",  1, NULL, 's'},
                {"tcUrl",   1, NULL, 't'},
@@ -417,12 +461,16 @@ int main(int argc, char **argv)
 
        signal(SIGINT, sigIntHandler);
 
-       while((opt = getopt_long(argc, argv, "hr:s:t:p:a:f:o:u:", longopts, NULL)) != -1) {
+       while((opt = getopt_long(argc, argv, "hr:s:t:p:a:f:o:u:n:c:l:y:", longopts, NULL)) != -1) {
                switch(opt) {
                        case 'h':
-                               printf("\nThis program dumps the media contnt streamed over rtmp.\n\n");
+                               printf("\nThis program dumps the media content streamed over rtmp.\n\n");
                                printf("--help|-h\t\tPrints this help screen.\n");
                                printf("--rtmp|-r url\t\tURL (e.g. rtmp//hotname[:port]/path)\n");
+                               printf("--host|-n hostname\t\tOverrides the hostname in the rtmp url\n");
+                               printf("--port|-c port\t\tOverrides the port in the rtmp url\n");
+                               printf("--protocol|-l\t\tOverrides the protocol in the rtmp url (0 - RTMP)\n");
+                               printf("--playpath|-y\t\tOverrides the playpath parsed from rtmp url\n");
                                printf("--swfUrl|-s url\t\tURL to player swf file\n");
                                printf("--tcUrl|-t url\t\tURL to played stream\n");
                                printf("--pageUrl|-p url\tWeb URL of played programme\n");
@@ -434,9 +482,54 @@ int main(int argc, char **argv)
                                printf("If you don't pass parameters for swfUrl, tcUrl, pageUrl, app or auth these propertiews will not be included in the connect ");
                                printf("packet.\n\n");
                                return RD_SUCCESS;
+                       case 'n':
+                               hostname = optarg;
+                               break;
+                       case 'c':
+                               port = atoi(optarg);
+                               break;
+                       case 'l':
+                               protocol = atoi(optarg);
+                               if(protocol != 0) {
+                                       Log(LOGERROR, "Unknown protocol specified: %d", protocol);
+                                       return RD_FAILED;
+                               }
+                               break;
+                       case 'y':
+                               playpath = optarg;
+                               break;
                        case 'r':
-                               url = optarg;
+                       {
+                               rtmpurl = optarg;
+
+                               /*if(!IsUrlValid(optarg)) {
+                                       Log(LOGWARNING, "The specified url (%s) is invalid!", optarg);
+                               } 
+                               else 
+                               {*/
+                                       char *parsedHost = 0;
+                                       unsigned int parsedPort = 0;
+                                       char *parsedPlaypath = 0;
+                                       char *parsedApp = 0;
+                                       int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
+
+                                       if(!ParseUrl(rtmpurl, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) {
+                                               Log(LOGWARNING, "Couldn't parse the specified url (%s)!", optarg);
+                                       } else {
+                                               if(hostname == 0)
+                                                       hostname = parsedHost;
+                                               if(port == -1)
+                                                       port = parsedPort;
+                                               if(playpath == 0)
+                                                       playpath = parsedPlaypath;
+                                               if(protocol == RTMP_PROTOCOL_UNDEFINED)
+                                                       protocol = parsedProtocol;
+                                               if(app == 0)
+                                                       app = parsedApp;
+                                       }
+                               //}
                                break;
+                       }
                        case 's':
                                swfUrl = optarg;
                                break;
@@ -467,10 +560,23 @@ int main(int argc, char **argv)
                }
        }
 
-       if(url == 0) {
-               printf("ERROR: You must specify a url (-r \"rtmp://host[:port]/playpath\" )\n");
+       if(hostname == 0) {
+               Log(LOGERROR, "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
+               return RD_FAILED;
+       }
+       if(playpath == 0) {
+               Log(LOGERROR, "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
                return RD_FAILED;
        }
+               
+       if(port == -1) {
+               Log(LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
+               port = 1935;
+       }
+       if(protocol == RTMP_PROTOCOL_UNDEFINED) {
+               Log(LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
+               protocol = RTMP_PROTOCOL_RTMP;
+       }
        if(flvFile == 0) {
                printf("ERROR: You must specify an output flv file (-o filename)\n");
                return RD_FAILED;
@@ -479,6 +585,17 @@ int main(int argc, char **argv)
        if(flashVer == 0)
                flashVer = DEFAULT_FLASH_VER;
 
+       if(tcUrl == 0 && app != 0) {
+               //if(rtmpurl != 0)
+               //      tcUrl = rtmpurl;
+               //else {
+                       char str[256]={0};
+                       sprintf(str, "%s://%s/%s", "rtmp", hostname, app);
+                       tcUrl = (char *)malloc(strlen(str)+1);
+                       strcpy(tcUrl, str);
+               //}
+       }
+
        int bufferSize = 1024*1024;
        char *buffer = (char *)malloc(bufferSize);
         int nRead = 0;
@@ -708,7 +825,11 @@ start:
                 }
        }
         
-       printf("Connecting to %s ...\n", url);
+       #ifdef _DEBUG
+       netstackdump = fopen("netstackdump", "wb");
+       #endif
+
+       printf("Connecting ...\n");
 /*
 #ifdef _DEBUG_TEST_PLAYSTOP
        // DEBUG!!!! seek to end if duration known!
@@ -717,7 +838,8 @@ start:
        if(duration > 0)
                dSeek = (duration-5.0)*1000.0;
 #endif*/
-       if (!rtmp->Connect(url, tcUrl, swfUrl, pageUrl, app, auth, flashVer, dSeek)) {
+       InitSockets();
+       if (!rtmp->Connect(protocol, hostname, port, playpath, tcUrl, swfUrl, pageUrl, app, auth, flashVer, dSeek)) {
                printf("Failed to connect!\n");
                return RD_FAILED;
        }
@@ -807,18 +929,25 @@ start:
                fseek(file, 4, SEEK_SET);
                fwrite(&dataType, sizeof(unsigned char), 1, file);
        }
-       if((duration > 0 && percent < 100.0) || bCtrlC || nRead != (-1)) {
+       if((duration > 0 && percent < 99.9) || bCtrlC || nRead != (-1)) {
                Log(LOGWARNING, "Download may be incomplete, try --resume!");
                nStatus = RD_INCOMPLETE;
        }
 
-       fclose(file);
-
 clean:
        printf("Closing connection... ");
        rtmp->Close();
        printf("done!\n\n");
 
+       if(file != 0)
+               fclose(file);
+
+       CleanupSockets();
+
+#ifdef _DEBUG
+       if(netstackdump != 0)
+               fclose(netstackdump);
+#endif
        return nStatus;
 }
 
index 6053874..b15b793 100644 (file)
  *
  */
 
-#include <stdlib.h>
 #include <string.h>
-#include <time.h>
-#include <math.h>
-#include <arpa/inet.h>
 
 #include "rtmppacket.h"
 #include "log.h"
index 12a3ea1..49d2d67 100644 (file)
 
 typedef unsigned char BYTE;
 
+typedef struct
+{
+       BYTE Type; // 0x03 RTMP, 0x06 RTMPE
+
+} HANDSHAKE_SIGNATURE;
+
 namespace RTMP_LIB
 {
   class RTMPPacket