*/
#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;
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;
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;
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
+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
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
-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.
--- /dev/null
+/* 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);
+}
+
--- /dev/null
+#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
+
#ifndef __LOG_H__
#define __LOG_H__
-//#define _DEBUG
+#define _DEBUG
+
+#ifdef _DEBUG
+#undef NODEBUG
+#endif
#define LOGDEBUG 0
#define LOGERROR 1
--- /dev/null
+/* 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);
+ }
+ }*/
+
--- /dev/null
+#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
+
*
*/
-#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;
#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();
{
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;
service.sin_port = htons(Link.port);
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (m_socket != 0 )
+ 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;
}
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;
}
if (!packet.IsReady())
{
packet.FreePacket();
- usleep(5000); // 5ms
- //sleep(0.1);//30);
+ //usleep(5000); // 5ms
continue;
}
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;
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) {
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;
if (nBytes == -1)
{
- Log(LOGERROR, "%s, RTMP recv error %d", __FUNCTION__, errno);
+ Log(LOGERROR, "%s, RTMP recv error %d", __FUNCTION__, GetSockError());
Close();
return false;
}
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;
}
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);
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;
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++);
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;
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;
{
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);
|| 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;
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);
}
}
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))
{
return ntohl(val);
}
+// big-endian 32bit integer
int CRTMP::ReadInt32(const char *data)
{
int val;
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;
return 3;
}
+// big-endian 32bit integer
int CRTMP::EncodeInt32(char *output, int nVal)
{
nVal = htonl(nVal);
*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;
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;
}
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;
}
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;
}
*
*/
-#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;
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();
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();
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;
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];
* 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;
}
#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
// 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)
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);
}
}
}
}
+
+ 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) {
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);
}
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;
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'},
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");
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;
}
}
- 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;
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;
}
}
- 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!
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;
}
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;
}
*
*/
-#include <stdlib.h>
#include <string.h>
-#include <time.h>
-#include <math.h>
-#include <arpa/inet.h>
#include "rtmppacket.h"
#include "log.h"
typedef unsigned char BYTE;
+typedef struct
+{
+ BYTE Type; // 0x03 RTMP, 0x06 RTMPE
+
+} HANDSHAKE_SIGNATURE;
+
namespace RTMP_LIB
{
class RTMPPacket