[rtmpdump] r405 - in trunk: librtmp/Makefile librtmp/handshake.h librtmp/rtmp.c librtmp/rtmp.h rtmpdump.c rtmpgw.c rtmpsuck.c
hyc
subversion at mplayerhq.hu
Sat Mar 27 06:35:05 CET 2010
Author: hyc
Date: Sat Mar 27 06:35:04 2010
New Revision: 405
Log:
Option restructuring, allow most options to be passed along
with the RTMP URL, clean up
Modified:
trunk/librtmp/Makefile
trunk/librtmp/handshake.h
trunk/librtmp/rtmp.c
trunk/librtmp/rtmp.h
trunk/rtmpdump.c
trunk/rtmpgw.c
trunk/rtmpsuck.c
Modified: trunk/librtmp/Makefile
==============================================================================
--- trunk/librtmp/Makefile Fri Mar 26 22:07:03 2010 (r404)
+++ trunk/librtmp/Makefile Sat Mar 27 06:35:04 2010 (r405)
@@ -32,7 +32,7 @@ log.o: log.c log.h Makefile
rtmp.o: rtmp.c rtmp.h rtmp_sys.h handshake.h dh.h log.h amf.h Makefile
amf.o: amf.c amf.h bytes.h log.h Makefile
hashswf.o: hashswf.c http.h rtmp.h rtmp_sys.h Makefile
-parseurl.o: parseurl.c rtmp_sys.h log.h Makefile
+parseurl.o: parseurl.c rtmp.h rtmp_sys.h log.h Makefile
librtmp.pc: librtmp.pc.in Makefile
sed -e "s;@prefix@;$(prefix);" -e "s;@VERSION@;$(VERSION);" \
Modified: trunk/librtmp/handshake.h
==============================================================================
--- trunk/librtmp/handshake.h Fri Mar 26 22:07:03 2010 (r404)
+++ trunk/librtmp/handshake.h Sat Mar 27 06:35:04 2010 (r405)
@@ -346,7 +346,7 @@ HandShake(RTMP * r, bool FP9HandShake)
char type;
getoff *getdh, *getdig;
- if (encrypted || r->Link.SWFHash.av_len)
+ if (encrypted || r->Link.SWFSize)
FP9HandShake = true;
else
FP9HandShake = false;
@@ -504,7 +504,7 @@ HandShake(RTMP * r, bool FP9HandShake)
dhposServer);
/* generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, key are the last 32 bytes of the server handshake) */
- if (r->Link.SWFHash.av_len)
+ if (r->Link.SWFSize)
{
const char swfVerify[] = { 0x01, 0x01 };
char *vend = r->Link.SWFVerificationResponse+sizeof(r->Link.SWFVerificationResponse);
@@ -512,7 +512,7 @@ HandShake(RTMP * r, bool FP9HandShake)
memcpy(r->Link.SWFVerificationResponse, swfVerify, 2);
AMF_EncodeInt32(&r->Link.SWFVerificationResponse[2], vend, r->Link.SWFSize);
AMF_EncodeInt32(&r->Link.SWFVerificationResponse[6], vend, r->Link.SWFSize);
- HMACsha256(r->Link.SWFHash.av_val, SHA256_DIGEST_LENGTH,
+ HMACsha256(r->Link.SWFHash, SHA256_DIGEST_LENGTH,
&serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
SHA256_DIGEST_LENGTH, &r->Link.SWFVerificationResponse[10]);
}
@@ -866,7 +866,7 @@ SHandShake(RTMP * r)
dhposClient);
/* generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, key are the last 32 bytes of the server handshake) */
- if (r->Link.SWFHash.av_len)
+ if (r->Link.SWFSize)
{
const char swfVerify[] = { 0x01, 0x01 };
char *vend = r->Link.SWFVerificationResponse+sizeof(r->Link.SWFVerificationResponse);
@@ -874,7 +874,7 @@ SHandShake(RTMP * r)
memcpy(r->Link.SWFVerificationResponse, swfVerify, 2);
AMF_EncodeInt32(&r->Link.SWFVerificationResponse[2], vend, r->Link.SWFSize);
AMF_EncodeInt32(&r->Link.SWFVerificationResponse[6], vend, r->Link.SWFSize);
- HMACsha256(r->Link.SWFHash.av_val, SHA256_DIGEST_LENGTH,
+ HMACsha256(r->Link.SWFHash, SHA256_DIGEST_LENGTH,
&serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
SHA256_DIGEST_LENGTH, &r->Link.SWFVerificationResponse[10]);
}
Modified: trunk/librtmp/rtmp.c
==============================================================================
--- trunk/librtmp/rtmp.c Fri Mar 26 22:07:03 2010 (r404)
+++ trunk/librtmp/rtmp.c Sat Mar 27 06:35:04 2010 (r405)
@@ -276,7 +276,8 @@ RTMP_UpdateBufferMS(RTMP *r)
#else
#define OSS "GNU"
#endif
-static const char DEFAULT_FLASH_VER[] = OSS " 10,0,32,18";
+#define DEF_VERSTR OSS " 10,0,32,18"
+static const char DEFAULT_FLASH_VER[] = DEF_VERSTR;
const AVal RTMP_DefaultFlashVer =
{ (char *)DEFAULT_FLASH_VER, sizeof(DEFAULT_FLASH_VER) - 1 };
@@ -296,8 +297,8 @@ RTMP_SetupStream(RTMP *r,
uint32_t swfSize,
AVal *flashVer,
AVal *subscribepath,
- double dTime,
- uint32_t dLength, bool bLiveStream, long int timeout)
+ double dStart,
+ double dStop, bool bLiveStream, long int timeout)
{
RTMP_Log(RTMP_LOGDEBUG, "Protocol : %s", RTMPProtocolStrings[protocol&7]);
RTMP_Log(RTMP_LOGDEBUG, "Hostname : %.*s", host->av_len, host->av_val);
@@ -318,10 +319,10 @@ RTMP_SetupStream(RTMP *r,
RTMP_Log(RTMP_LOGDEBUG, "subscribepath : %s", subscribepath->av_val);
if (flashVer && flashVer->av_val)
RTMP_Log(RTMP_LOGDEBUG, "flashVer : %s", flashVer->av_val);
- if (dTime > 0)
- RTMP_Log(RTMP_LOGDEBUG, "SeekTime : %.3f sec", (double)dTime / 1000.0);
- if (dLength > 0)
- RTMP_Log(RTMP_LOGDEBUG, "playLength : %.3f sec", (double)dLength / 1000.0);
+ if (dStart > 0)
+ RTMP_Log(RTMP_LOGDEBUG, "StartTime : %.3f sec", dStart);
+ if (dStop > 0)
+ RTMP_Log(RTMP_LOGDEBUG, "StopTime : %.3f sec", dStop);
RTMP_Log(RTMP_LOGDEBUG, "live : %s", bLiveStream ? "yes" : "no");
RTMP_Log(RTMP_LOGDEBUG, "timeout : %d sec", timeout);
@@ -329,16 +330,14 @@ RTMP_SetupStream(RTMP *r,
#ifdef CRYPTO
if (swfSHA256Hash != NULL && swfSize > 0)
{
- r->Link.SWFHash = *swfSHA256Hash;
+ memcpy(r->Link.SWFHash, swfSHA256Hash->av_val, sizeof(r->Link.SWFHash));
r->Link.SWFSize = swfSize;
RTMP_Log(RTMP_LOGDEBUG, "SWFSHA256:");
- RTMP_LogHex(RTMP_LOGDEBUG, r->Link.SWFHash.av_val, 32);
+ RTMP_LogHex(RTMP_LOGDEBUG, r->Link.SWFHash, sizeof(r->Link.SWFHash));
RTMP_Log(RTMP_LOGDEBUG, "SWFSize : %lu", r->Link.SWFSize);
}
else
{
- r->Link.SWFHash.av_len = 0;
- r->Link.SWFHash.av_val = NULL;
r->Link.SWFSize = 0;
}
#endif
@@ -373,15 +372,18 @@ RTMP_SetupStream(RTMP *r,
if (app && app->av_len)
r->Link.app = *app;
if (auth && auth->av_len)
- r->Link.auth = *auth;
+ {
+ r->Link.auth = *auth;
+ r->Link.authflag = true;
+ }
if (flashVer && flashVer->av_len)
r->Link.flashVer = *flashVer;
else
r->Link.flashVer = RTMP_DefaultFlashVer;
if (subscribepath && subscribepath->av_len)
r->Link.subscribepath = *subscribepath;
- r->Link.seekTime = dTime;
- r->Link.length = dLength;
+ r->Link.seekTime = dStart;
+ r->Link.stopTime = dStop;
r->Link.bLiveStream = bLiveStream;
r->Link.timeout = timeout;
@@ -401,6 +403,251 @@ RTMP_SetupStream(RTMP *r,
}
}
+enum { OPT_STR=0, OPT_INT, OPT_BOOL, OPT_CONN };
+static const char *optinfo[] = {
+ "string", "integer", "boolean", "AMF" };
+
+#define OFF(x) offsetof(struct RTMP,x)
+
+static struct urlopt {
+ AVal name;
+ off_t off;
+ int otype;
+ char *use;
+} options[] = {
+ { AVC("socks"), OFF(Link.sockshost), OPT_STR,
+ "Use the specified SOCKS proxy" },
+ { AVC("app"), OFF(Link.app), OPT_STR,
+ "Name of target app on server" },
+ { AVC("tcUrl"), OFF(Link.tcUrl), OPT_STR,
+ "URL to played stream" },
+ { AVC("pageUrl"), OFF(Link.pageUrl), OPT_STR,
+ "URL of played media's web page" },
+ { AVC("swfUrl"), OFF(Link.swfUrl), OPT_STR,
+ "URL to player SWF file" },
+ { AVC("flashver"), OFF(Link.flashVer), OPT_STR,
+ "Flash version string (default " DEF_VERSTR ")" },
+ { AVC("conn"), OFF(Link.extras), OPT_CONN,
+ "Append arbitrary AMF data to Connect message" },
+ { AVC("playpath"), OFF(Link.playpath), OPT_STR,
+ "Path to target media on server" },
+ { AVC("live"), OFF(Link.bLiveStream), OPT_BOOL,
+ "Stream is live, no seeking possible" },
+ { AVC("subscribe"), OFF(Link.subscribepath), OPT_STR,
+ "Stream to subscribe to" },
+ { AVC("token"), OFF(Link.token), OPT_STR,
+ "Key for SecureToken response" },
+ { AVC("swfVfy"), OFF(Link.swfVfy), OPT_BOOL,
+ "Perform SWF Verification" },
+ { AVC("swfAge"), OFF(Link.swfAge), OPT_INT,
+ "Number of days to use cached SWF hash" },
+ { AVC("start"), OFF(Link.seekTime), OPT_INT,
+ "Stream start position in milliseconds" },
+ { AVC("stop"), OFF(Link.stopTime), OPT_INT,
+ "Stream stop position in milliseconds" },
+ { AVC("buffer"), OFF(m_nBufferMS), OPT_INT,
+ "Buffer time in milliseconds" },
+ { AVC("timeout"), OFF(Link.timeout), OPT_INT,
+ "Session timeout in seconds" },
+ { {NULL,0}, 0, 0}
+};
+
+static const AVal truth[] = {
+ AVC("1"),
+ AVC("on"),
+ AVC("yes"),
+ AVC("true"),
+ {0,0}
+};
+
+static void RTMP_OptUsage()
+{
+ int i;
+
+ RTMP_LogPrintf("Valid RTMP options are:\n");
+ for (i=0; options[i].name.av_len; i++) {
+ RTMP_LogPrintf("%10s %-7s %s\n", options[i].name.av_val,
+ optinfo[options[i].otype], options[i].use);
+ }
+}
+
+static int
+parseAMF(AMFObject *obj, AVal *av, int *depth)
+{
+ AMFObjectProperty prop = {{0,0}};
+ int i;
+ char *p, *arg = av->av_val;
+
+ if (arg[1] == ':')
+ {
+ p = (char *)arg+2;
+ switch(arg[0])
+ {
+ case 'B':
+ prop.p_type = AMF_BOOLEAN;
+ prop.p_vu.p_number = atoi(p);
+ break;
+ case 'S':
+ prop.p_type = AMF_STRING;
+ prop.p_vu.p_aval.av_val = p;
+ prop.p_vu.p_aval.av_len = av->av_len - (p-arg);
+ break;
+ case 'N':
+ prop.p_type = AMF_NUMBER;
+ prop.p_vu.p_number = strtod(p, NULL);
+ break;
+ case 'Z':
+ prop.p_type = AMF_NULL;
+ break;
+ case 'O':
+ i = atoi(p);
+ if (i)
+ {
+ prop.p_type = AMF_OBJECT;
+ }
+ else
+ {
+ (*depth)--;
+ return 0;
+ }
+ break;
+ default:
+ return -1;
+ }
+ }
+ else if (arg[2] == ':' && arg[0] == 'N')
+ {
+ p = strchr(arg+3, ':');
+ if (!p || !*depth)
+ return -1;
+ prop.p_name.av_val = (char *)arg+3;
+ prop.p_name.av_len = p - (arg+3);
+
+ p++;
+ switch(arg[1])
+ {
+ case 'B':
+ prop.p_type = AMF_BOOLEAN;
+ prop.p_vu.p_number = atoi(p);
+ break;
+ case 'S':
+ prop.p_type = AMF_STRING;
+ prop.p_vu.p_aval.av_val = p;
+ prop.p_vu.p_aval.av_len = av->av_len - (p-arg);
+ break;
+ case 'N':
+ prop.p_type = AMF_NUMBER;
+ prop.p_vu.p_number = strtod(p, NULL);
+ break;
+ case 'O':
+ prop.p_type = AMF_OBJECT;
+ break;
+ default:
+ return -1;
+ }
+ }
+ else
+ return -1;
+
+ if (*depth)
+ {
+ AMFObject *o2;
+ for (i=0; i<*depth; i++)
+ {
+ o2 = &obj->o_props[obj->o_num-1].p_vu.p_object;
+ obj = o2;
+ }
+ }
+ AMF_AddProp(obj, &prop);
+ if (prop.p_type == AMF_OBJECT)
+ (*depth)++;
+ return 0;
+}
+
+bool RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg)
+{
+ int i;
+ void *v;
+
+ for (i=0; options[i].name.av_len; i++) {
+ if (opt->av_len != options[i].name.av_len) continue;
+ if (strcasecmp(opt->av_val, options[i].name.av_val)) continue;
+ v = (char *)r + options[i].off;
+ switch(options[i].otype) {
+ case OPT_STR: {
+ AVal *aptr = v;
+ *aptr = *arg; }
+ break;
+ case OPT_INT: {
+ long l = strtol(arg->av_val, NULL, 0);
+ *(int *)v = l; }
+ break;
+ case OPT_BOOL: {
+ int j;
+ bool b = false;
+ for (j=0; truth[j].av_len; j++) {
+ if (arg->av_len != truth[j].av_len) continue;
+ if (strcasecmp(arg->av_val, truth[j].av_val)) continue;
+ b = true; break; }
+ *(bool *)v = b;
+ }
+ break;
+ case OPT_CONN:
+ if (parseAMF(&r->Link.extras, arg, &r->Link.edepth))
+ return false;
+ break;
+ }
+ break;
+ }
+ if (!options[i].name.av_len) {
+ RTMP_Log(RTMP_LOGERROR, "Unknown option %s", opt->av_val);
+ RTMP_OptUsage();
+ return false;
+ }
+
+ return true;
+}
+
+bool RTMP_SetupURL(RTMP *r, char *url)
+{
+ AVal opt, arg;
+ char *p1, *p2, *ptr = strchr(url, ' ');
+ bool ret;
+ unsigned int port = 0;
+
+ while (ptr) {
+ *ptr++ = '\0';
+ p1 = ptr;
+ p2 = strchr(p1, '=');
+ if (!p2)
+ break;
+ opt.av_val = p1;
+ opt.av_len = p2 - p1;
+ *p2++ = '\0';
+ arg.av_val = p2;
+ ptr = strchr(p2, ' ');
+ if (ptr) {
+ *ptr = '\0';
+ arg.av_len = ptr - p2;
+ } else {
+ arg.av_len = strlen(p2);
+ }
+ ret = RTMP_SetOpt(r, &opt, &arg);
+ if (!ret)
+ return ret;
+ }
+ ret = RTMP_ParseURL(url, &r->Link.protocol, &r->Link.hostname,
+ &port, &r->Link.playpath0, &r->Link.app);
+ if (!ret)
+ return ret;
+ r->Link.port = port;
+ r->Link.playpath = r->Link.playpath0;
+ if (r->Link.swfVfy && r->Link.swfUrl.av_len)
+ RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize,
+ (unsigned char *)r->Link.SWFHash, r->Link.swfAge);
+ return true;
+}
+
static bool
add_addr_info(struct sockaddr_in *service, AVal *host, int port)
{
@@ -606,15 +853,12 @@ SocksNegotiate(RTMP *r)
}
bool
-RTMP_ConnectStream(RTMP *r, double seekTime, uint32_t dLength)
+RTMP_ConnectStream(RTMP *r, double seekTime)
{
RTMPPacket packet = { 0 };
if (seekTime >= -2.0)
r->Link.seekTime = seekTime;
- if (dLength >= 0)
- r->Link.length = dLength;
-
r->m_mediaChannel = 0;
while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet))
@@ -641,16 +885,13 @@ RTMP_ConnectStream(RTMP *r, double seekT
}
bool
-RTMP_ReconnectStream(RTMP *r, int bufferTime, double seekTime,
- uint32_t dLength)
+RTMP_ReconnectStream(RTMP *r, double seekTime)
{
RTMP_DeleteStream(r);
RTMP_SendCreateStream(r);
- RTMP_SetBufferMS(r, bufferTime);
-
- return RTMP_ConnectStream(r, seekTime, dLength);
+ return RTMP_ConnectStream(r, seekTime);
}
bool
@@ -681,6 +922,7 @@ RTMP_DeleteStream(RTMP *r)
r->m_bPlaying = false;
SendDeleteStream(r, r->m_stream_id);
+ r->m_stream_id = -1;
}
int
@@ -1607,8 +1849,8 @@ SendPlay(RTMP *r)
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_NULL;
- RTMP_Log(RTMP_LOGDEBUG, "%s, seekTime=%.2f, dLength=%d, sending play: %s",
- __FUNCTION__, r->Link.seekTime, r->Link.length,
+ RTMP_Log(RTMP_LOGDEBUG, "%s, seekTime=%.2f, stopTime=%.2f, sending play: %s",
+ __FUNCTION__, r->Link.seekTime, r->Link.stopTime,
r->Link.playpath.av_val);
enc = AMF_EncodeString(enc, pend, &r->Link.playpath);
if (!enc)
@@ -1637,9 +1879,9 @@ SendPlay(RTMP *r)
// 0: plays a frame 'start' ms away from the beginning
// >0: plays a live or recoded stream for 'len' milliseconds
//enc += EncodeNumber(enc, -1.0); // len
- if (r->Link.length)
+ if (r->Link.stopTime)
{
- enc = AMF_EncodeNumber(enc, pend, r->Link.length); // len
+ enc = AMF_EncodeNumber(enc, pend, r->Link.stopTime - r->Link.seekTime);
if (!enc)
return false;
}
@@ -2230,7 +2472,7 @@ HandleCtrl(RTMP *r, const RTMPPacket *pa
//RTMP_LogHex(packet.m_body, packet.m_nBodySize);
// respond with HMAC SHA256 of decompressed SWF, key is the 30byte player key, also the last 30 bytes of the server handshake are applied
- if (r->Link.SWFHash.av_len)
+ if (r->Link.SWFSize)
{
RTMP_SendCtrl(r, 0x1B, 0, 0);
}
@@ -2866,6 +3108,9 @@ RTMP_Close(RTMP *r)
r->m_resplen = 0;
r->m_unackd = 0;
+ free(r->Link.playpath0.av_val);
+ r->Link.playpath0.av_val = NULL;
+
#ifdef CRYPTO
if (r->Link.dh)
{
Modified: trunk/librtmp/rtmp.h
==============================================================================
--- trunk/librtmp/rtmp.h Fri Mar 26 22:07:03 2010 (r404)
+++ trunk/librtmp/rtmp.h Sat Mar 27 06:35:04 2010 (r405)
@@ -76,8 +76,6 @@ extern "C"
#define RTMP_PACKET_SIZE_SMALL 2
#define RTMP_PACKET_SIZE_MINIMUM 3
- typedef unsigned char BYTE;
-
typedef struct RTMPChunk
{
int c_headerSize;
@@ -88,9 +86,9 @@ extern "C"
typedef struct RTMPPacket
{
- BYTE m_headerType;
- BYTE m_packetType;
- BYTE m_hasAbsTimestamp; // timestamp absolute or relative?
+ uint8_t m_headerType;
+ uint8_t m_packetType;
+ uint8_t m_hasAbsTimestamp; // timestamp absolute or relative?
int m_nChannel;
uint32_t m_nTimeStamp; // timestamp
int32_t m_nInfoField2; // last 4 bytes in a long header
@@ -120,10 +118,10 @@ extern "C"
typedef struct RTMP_LNK
{
AVal hostname;
- unsigned int port;
- int protocol;
+ AVal sockshost;
- AVal playpath;
+ AVal playpath0; /* parsed from URL */
+ AVal playpath; /* passed in explicitly */
AVal tcUrl;
AVal swfUrl;
AVal pageUrl;
@@ -132,26 +130,30 @@ extern "C"
AVal flashVer;
AVal subscribepath;
AVal token;
- AVal playpath0;
AMFObject extras;
+ int edepth;
+
+ int seekTime;
+ int stopTime;
- double seekTime;
- uint32_t length;
bool authflag;
bool bLiveStream;
+ bool swfVfy;
+ int swfAge;
+ int protocol;
int timeout; // number of seconds before connection times out
- AVal sockshost;
unsigned short socksport;
+ unsigned short port;
#ifdef CRYPTO
void *dh; // for encryption
void *rc4keyIn;
void *rc4keyOut;
- AVal SWFHash;
uint32_t SWFSize;
+ char SWFHash[32];
char SWFVerificationResponse[42];
#endif
} RTMP_LNK;
@@ -236,10 +238,13 @@ extern "C"
bool RTMP_ParseURL(const char *url, int *protocol, AVal *host,
unsigned int *port, AVal *playpath, AVal *app);
+
void RTMP_ParsePlaypath(AVal *in, AVal *out);
void RTMP_SetBufferMS(RTMP *r, int size);
void RTMP_UpdateBufferMS(RTMP *r);
+ bool RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg);
+ bool RTMP_SetupURL(RTMP *r, char *url);
void RTMP_SetupStream(RTMP *r, int protocol,
AVal *hostname,
unsigned int port,
@@ -254,8 +259,8 @@ extern "C"
uint32_t swfSize,
AVal *flashVer,
AVal *subscribepath,
- double dTime,
- uint32_t dLength, bool bLiveStream, long int timeout);
+ double dStart,
+ double dStop, bool bLiveStream, long int timeout);
bool RTMP_Connect(RTMP *r, RTMPPacket *cp);
struct sockaddr;
@@ -271,9 +276,8 @@ extern "C"
double RTMP_GetDuration(RTMP *r);
bool RTMP_ToggleStream(RTMP *r);
- bool RTMP_ConnectStream(RTMP *r, double seekTime, uint32_t dLength);
- bool RTMP_ReconnectStream(RTMP *r, int bufferTime, double seekTime,
- uint32_t dLength);
+ bool RTMP_ConnectStream(RTMP *r, double seekTime);
+ bool RTMP_ReconnectStream(RTMP *r, double seekTime);
void RTMP_DeleteStream(RTMP *r);
int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet);
int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet);
Modified: trunk/rtmpdump.c
==============================================================================
--- trunk/rtmpdump.c Fri Mar 26 22:07:03 2010 (r404)
+++ trunk/rtmpdump.c Sat Mar 27 06:35:04 2010 (r405)
@@ -46,6 +46,10 @@
#define RD_FAILED 1
#define RD_INCOMPLETE 2
+#define DEF_TIMEOUT 30 /* seconds */
+#define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */
+#define DEF_SKIPFRM 0
+
// starts sockets
bool
InitSockets()
@@ -119,6 +123,8 @@ int hex2bin(char *str, char **hex)
static const AVal av_onMetaData = AVC("onMetaData");
static const AVal av_duration = AVC("duration");
+static const AVal av_conn = AVC("conn");
+static const AVal av_token = AVC("token");
int
OpenResumeFile(const char *flvFile, // file name [in]
@@ -431,7 +437,7 @@ GetLastKeyframe(FILE * file, // output f
int
Download(RTMP * rtmp, // connected RTMP object
- FILE * file, uint32_t dSeek, uint32_t dLength, double duration, bool bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, bool bStdoutMode, bool bLiveStream, bool bHashes, bool bOverrideBufferTime, uint32_t bufferTime, double *percent) // percentage downloaded [out]
+ FILE * file, uint32_t dSeek, uint32_t dStopOffset, double duration, bool bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, bool bStdoutMode, bool bLiveStream, bool bHashes, bool bOverrideBufferTime, uint32_t bufferTime, double *percent) // percentage downloaded [out]
{
int32_t now, lastUpdate;
int bufferSize = 64 * 1024;
@@ -484,8 +490,8 @@ Download(RTMP * rtmp, // connected RTMP
}
}
- if (dLength > 0)
- RTMP_LogPrintf("For duration: %.3f sec\n", (double) dLength / 1000.0);
+ if (dStopOffset > 0)
+ RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0);
if (bResume && nInitialFrameSize > 0)
rtmp->m_read.flags |= RTMP_READ_RESUME;
@@ -620,95 +626,81 @@ Download(RTMP * rtmp, // connected RTMP
#define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
-int
-parseAMF(AMFObject *obj, const char *arg, int *depth)
+void usage(char *prog)
{
- AMFObjectProperty prop = {{0,0}};
- int i;
- char *p;
-
- if (arg[1] == ':')
- {
- p = (char *)arg+2;
- switch(arg[0])
- {
- case 'B':
- prop.p_type = AMF_BOOLEAN;
- prop.p_vu.p_number = atoi(p);
- break;
- case 'S':
- prop.p_type = AMF_STRING;
- STR2AVAL(prop.p_vu.p_aval,p);
- break;
- case 'N':
- prop.p_type = AMF_NUMBER;
- prop.p_vu.p_number = strtod(p, NULL);
- break;
- case 'Z':
- prop.p_type = AMF_NULL;
- break;
- case 'O':
- i = atoi(p);
- if (i)
- {
- prop.p_type = AMF_OBJECT;
- }
- else
- {
- (*depth)--;
- return 0;
- }
- break;
- default:
- return -1;
- }
- }
- else if (arg[2] == ':' && arg[0] == 'N')
- {
- p = strchr(arg+3, ':');
- if (!p || !*depth)
- return -1;
- prop.p_name.av_val = (char *)arg+3;
- prop.p_name.av_len = p - (arg+3);
-
- p++;
- switch(arg[1])
- {
- case 'B':
- prop.p_type = AMF_BOOLEAN;
- prop.p_vu.p_number = atoi(p);
- break;
- case 'S':
- prop.p_type = AMF_STRING;
- STR2AVAL(prop.p_vu.p_aval,p);
- break;
- case 'N':
- prop.p_type = AMF_NUMBER;
- prop.p_vu.p_number = strtod(p, NULL);
- break;
- case 'O':
- prop.p_type = AMF_OBJECT;
- break;
- default:
- return -1;
- }
- }
- else
- return -1;
-
- if (*depth)
- {
- AMFObject *o2;
- for (i=0; i<*depth; i++)
- {
- o2 = &obj->o_props[obj->o_num-1].p_vu.p_object;
- obj = o2;
- }
- }
- AMF_AddProp(obj, &prop);
- if (prop.p_type == AMF_OBJECT)
- (*depth)++;
- return 0;
+ RTMP_LogPrintf
+ ("\n%s: This program dumps the media content streamed over RTMP.\n\n", prog);
+ RTMP_LogPrintf("--help|-h Prints this help screen.\n");
+ RTMP_LogPrintf
+ ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
+ RTMP_LogPrintf
+ ("--host|-n hostname Overrides the hostname in the rtmp url\n");
+ RTMP_LogPrintf
+ ("--port|-c port Overrides the port in the rtmp url\n");
+ RTMP_LogPrintf
+ ("--socks|-S host:port Use the specified SOCKS proxy\n");
+ RTMP_LogPrintf
+ ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
+ RTMP_LogPrintf
+ ("--playpath|-y Overrides the playpath parsed from rtmp url\n");
+ RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
+ RTMP_LogPrintf
+ ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
+ RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
+ RTMP_LogPrintf("--app|-a app Name of target app on server\n");
+#ifdef CRYPTO
+ RTMP_LogPrintf
+ ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
+ RTMP_LogPrintf
+ ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
+ RTMP_LogPrintf
+ ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
+ RTMP_LogPrintf
+ ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
+#endif
+ RTMP_LogPrintf
+ ("--auth|-u string Authentication string to be appended to the connect string\n");
+ RTMP_LogPrintf
+ ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
+ RTMP_LogPrintf
+ (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
+ RTMP_LogPrintf
+ (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
+ RTMP_LogPrintf
+ ("--flashVer|-f string Flash version string (default: \"%s\")\n",
+ RTMP_DefaultFlashVer.av_val);
+ RTMP_LogPrintf
+ ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
+ RTMP_LogPrintf
+ ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
+ RTMP_LogPrintf
+ ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
+ RTMP_LogPrintf
+ ("--resume|-e Resume a partial RTMP download\n");
+ RTMP_LogPrintf
+ ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
+ DEF_TIMEOUT);
+ RTMP_LogPrintf
+ ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
+ RTMP_LogPrintf
+ ("--stop|-B num Stop at num seconds into stream\n");
+ RTMP_LogPrintf
+ ("--token|-T key Key for SecureToken response\n");
+ RTMP_LogPrintf
+ ("--hashes|-# Display progress with hashes, not with the byte counter\n");
+ RTMP_LogPrintf
+ ("--buffer|-b Buffer time in milliseconds (default: %lu)\n",
+ DEF_BUFTIME);
+ RTMP_LogPrintf
+ ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
+ DEF_SKIPFRM);
+ RTMP_LogPrintf
+ ("--quiet|-q Suppresses all command output.\n");
+ RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
+ RTMP_LogPrintf("--debug|-z Debug level command output.\n");
+ RTMP_LogPrintf
+ ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
+ RTMP_LogPrintf("packet.\n\n");
}
int
@@ -720,13 +712,13 @@ main(int argc, char **argv)
double percent = 0;
double duration = 0.0;
- int nSkipKeyFrames = 0; // skip this number of keyframes when resuming
+ int nSkipKeyFrames = DEF_SKIPFRM; // skip this number of keyframes when resuming
bool bOverrideBufferTime = false; // if the user specifies a buffer time override this is true
bool bStdoutMode = true; // if true print the stream directly to stdout, messages go to stderr
bool bResume = false; // true in resume mode
uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise
- uint32_t bufferTime = 10 * 60 * 60 * 1000; // 10 hours as default
+ uint32_t bufferTime = DEF_BUFTIME;
// meta header and initial frame for the resume mode (they are read from the file and compared with
// the stream we are trying to continue
@@ -747,12 +739,11 @@ main(int argc, char **argv)
bool bLiveStream = false; // is it a live stream? then we can't seek/resume
bool bHashes = false; // display byte counters not hashes by default
- long int timeout = 120; // timeout connection after 120 seconds
+ long int timeout = DEF_TIMEOUT; // timeout connection after 120 seconds
uint32_t dStartOffset = 0; // seek position in non-live mode
uint32_t dStopOffset = 0;
- uint32_t dLength = 0; // length to play from stream - calculated from seek position and dStopOffset
+ RTMP rtmp = { 0 };
- char *rtmpurl = 0;
AVal swfUrl = { 0, 0 };
AVal tcUrl = { 0, 0 };
AVal pageUrl = { 0, 0 };
@@ -761,10 +752,7 @@ main(int argc, char **argv)
AVal swfHash = { 0, 0 };
uint32_t swfSize = 0;
AVal flashVer = { 0, 0 };
- AVal token = { 0, 0 };
AVal sockshost = { 0, 0 };
- AMFObject extras = {0};
- int edepth = 0;
#ifdef CRYPTO
int swfAge = 30; /* 30 days for SWF cache by default */
@@ -805,6 +793,8 @@ main(int argc, char **argv)
/* sleep(30); */
+ RTMP_Init(&rtmp);
+
int opt;
struct option longopts[] = {
{"help", 0, NULL, 'h'},
@@ -852,79 +842,7 @@ main(int argc, char **argv)
switch (opt)
{
case 'h':
- RTMP_LogPrintf
- ("\nThis program dumps the media content streamed over RTMP.\n\n");
- RTMP_LogPrintf("--help|-h Prints this help screen.\n");
- RTMP_LogPrintf
- ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
- RTMP_LogPrintf
- ("--host|-n hostname Overrides the hostname in the rtmp url\n");
- RTMP_LogPrintf
- ("--port|-c port Overrides the port in the rtmp url\n");
- RTMP_LogPrintf
- ("--socks|-S host:port Use the specified SOCKS proxy\n");
- RTMP_LogPrintf
- ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
- RTMP_LogPrintf
- ("--playpath|-y Overrides the playpath parsed from rtmp url\n");
- RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
- RTMP_LogPrintf
- ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
- RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
- RTMP_LogPrintf("--app|-a app Name of target app on server\n");
-#ifdef CRYPTO
- RTMP_LogPrintf
- ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
- RTMP_LogPrintf
- ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
- RTMP_LogPrintf
- ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
- RTMP_LogPrintf
- ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
-#endif
- RTMP_LogPrintf
- ("--auth|-u string Authentication string to be appended to the connect string\n");
- RTMP_LogPrintf
- ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
- RTMP_LogPrintf
- (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
- RTMP_LogPrintf
- (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
- RTMP_LogPrintf
- ("--flashVer|-f string Flash version string (default: \"%s\")\n",
- RTMP_DefaultFlashVer.av_val);
- RTMP_LogPrintf
- ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n");
- RTMP_LogPrintf
- ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
- RTMP_LogPrintf
- ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n");
- RTMP_LogPrintf
- ("--resume|-e Resume a partial RTMP download\n");
- RTMP_LogPrintf
- ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
- timeout);
- RTMP_LogPrintf
- ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
- RTMP_LogPrintf
- ("--stop|-B num Stop at num seconds into stream\n");
- RTMP_LogPrintf
- ("--token|-T key Key for SecureToken response\n");
- RTMP_LogPrintf
- ("--hashes|-# Display progress with hashes, not with the byte counter\n");
- RTMP_LogPrintf
- ("--buffer|-b Buffer time in milliseconds (default: %lu), this option makes only sense in stdout mode (-o -)\n",
- bufferTime);
- RTMP_LogPrintf
- ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
- nSkipKeyFrames);
- RTMP_LogPrintf
- ("--quiet|-q Suppresses all command output.\n");
- RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
- RTMP_LogPrintf("--debug|-z Debug level command output.\n");
- RTMP_LogPrintf
- ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
- RTMP_LogPrintf("packet.\n\n");
+ usage(argv[0]);
return RD_SUCCESS;
#ifdef CRYPTO
case 'w':
@@ -1014,8 +932,7 @@ main(int argc, char **argv)
break;
case 'l':
protocol = atoi(optarg);
- if (protocol != RTMP_PROTOCOL_RTMP
- && protocol != RTMP_PROTOCOL_RTMPE)
+ if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPS)
{
RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol);
return RD_FAILED;
@@ -1030,9 +947,8 @@ main(int argc, char **argv)
unsigned int parsedPort = 0;
int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
- rtmpurl = optarg;
if (!RTMP_ParseURL
- (rtmpurl, &parsedProtocol, &parsedHost, &parsedPort,
+ (optarg, &parsedProtocol, &parsedHost, &parsedPort,
&parsedPlaypath, &parsedApp))
{
RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
@@ -1084,13 +1000,16 @@ main(int argc, char **argv)
case 'u':
STR2AVAL(auth, optarg);
break;
- case 'C':
- if (parseAMF(&extras, optarg, &edepth))
- {
- RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg);
- return RD_FAILED;
- }
- break;
+ case 'C': {
+ AVal av;
+ STR2AVAL(av, optarg);
+ if (!RTMP_SetOpt(&rtmp, &av_conn, &av))
+ {
+ RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg);
+ return RD_FAILED;
+ }
+ }
+ break;
case 'm':
timeout = atoi(optarg);
break;
@@ -1100,8 +1019,11 @@ main(int argc, char **argv)
case 'B':
dStopOffset = (int) (atof(optarg) * 1000.0);
break;
- case 'T':
+ case 'T': {
+ AVal token;
STR2AVAL(token, optarg);
+ RTMP_SetOpt(&rtmp, &av_token, &token);
+ }
break;
case '#':
bHashes = true;
@@ -1120,6 +1042,7 @@ main(int argc, char **argv)
break;
default:
RTMP_LogPrintf("unknown option: %c\n", opt);
+ usage(argv[0]);
return RD_FAILED;
break;
}
@@ -1231,18 +1154,10 @@ main(int argc, char **argv)
}
}
- RTMP rtmp = { 0 };
- RTMP_Init(&rtmp);
RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
&tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
- &flashVer, &subscribepath, dSeek, 0, bLiveStream, timeout);
-
- /* backward compatibility, we always sent this as true before */
- if (auth.av_len)
- rtmp.Link.authflag = true;
+ &flashVer, &subscribepath, dSeek, dStopOffset, bLiveStream, timeout);
- rtmp.Link.extras = extras;
- rtmp.Link.token = token;
off_t size = 0;
// ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
@@ -1339,10 +1254,8 @@ main(int argc, char **argv)
// Calculate the length of the stream to still play
if (dStopOffset > 0)
{
- dLength = dStopOffset - dSeek;
-
// Quit if start seek is past required stop offset
- if (dLength <= 0)
+ if (dStopOffset <= dSeek)
{
RTMP_LogPrintf("Already Completed\n");
nStatus = RD_SUCCESS;
@@ -1350,7 +1263,7 @@ main(int argc, char **argv)
}
}
- if (!RTMP_ConnectStream(&rtmp, dSeek, dLength))
+ if (!RTMP_ConnectStream(&rtmp, dSeek))
{
nStatus = RD_FAILED;
break;
@@ -1378,15 +1291,14 @@ main(int argc, char **argv)
dSeek = rtmp.m_pauseStamp;
if (dStopOffset > 0)
{
- dLength = dStopOffset - dSeek;
- if (dLength <= 0)
+ if (dStopOffset <= dSeek)
{
RTMP_LogPrintf("Already Completed\n");
nStatus = RD_SUCCESS;
break;
}
}
- if (!RTMP_ReconnectStream(&rtmp, bufferTime, dSeek, dLength))
+ if (!RTMP_ReconnectStream(&rtmp, dSeek))
{
RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
if (!RTMP_IsTimedout(&rtmp))
@@ -1408,7 +1320,7 @@ main(int argc, char **argv)
bResume = true;
}
- nStatus = Download(&rtmp, file, dSeek, dLength, duration, bResume,
+ nStatus = Download(&rtmp, file, dSeek, dStopOffset, duration, bResume,
metaHeader, nMetaHeaderSize, initialFrame,
initialFrameType, nInitialFrameSize,
nSkipKeyFrames, bStdoutMode, bLiveStream, bHashes,
Modified: trunk/rtmpgw.c
==============================================================================
--- trunk/rtmpgw.c Fri Mar 26 22:07:03 2010 (r404)
+++ trunk/rtmpgw.c Sat Mar 27 06:35:04 2010 (r405)
@@ -343,7 +343,6 @@ void processTCPrequest(STREAMING_SERVER
RTMP rtmp = { 0 };
uint32_t dSeek = 0; // can be used to start from a later point in the stream
- int32_t dLength = -1;
// reset RTMP options to defaults specified upon invokation of streams
RTMP_REQUEST req;
@@ -548,16 +547,11 @@ void processTCPrequest(STREAMING_SERVER
RTMP_LogPrintf("Starting at TS: %d ms\n", dSeek);
}
- if (req.dStopOffset > 0)
- {
- dLength = req.dStopOffset - dSeek;
- }
-
RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", req.bufferTime);
RTMP_Init(&rtmp);
RTMP_SetBufferMS(&rtmp, req.bufferTime);
RTMP_SetupStream(&rtmp, req.protocol, &req.hostname, req.rtmpport, &req.sockshost,
- &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, dSeek, dLength,
+ &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, dSeek, req.dStopOffset,
req.bLiveStream, req.timeout);
/* backward compatibility, we always sent this as true before */
if (req.auth.av_len)
@@ -937,11 +931,11 @@ ParseOption(char opt, char *arg, RTMP_RE
req->timeout = atoi(arg);
break;
case 'A':
- req->dStartOffset = atoi(arg) * 1000;
+ req->dStartOffset = (int)(atof(arg) * 1000.0);
//printf("dStartOffset = %d\n", dStartOffset);
break;
case 'B':
- req->dStopOffset = atoi(arg) * 1000;
+ req->dStopOffset = (int)(atof(arg) * 1000.0);
//printf("dStartOffset = %d\n", dStartOffset);
break;
case 'T':
Modified: trunk/rtmpsuck.c
==============================================================================
--- trunk/rtmpsuck.c Fri Mar 26 22:07:03 2010 (r404)
+++ trunk/rtmpsuck.c Sat Mar 27 06:35:04 2010 (r405)
@@ -95,9 +95,6 @@ typedef struct
Flist *f_head, *f_tail;
Flist *f_cur;
-#ifdef CRYPTO
- unsigned char hash[HASHLEN];
-#endif
} STREAMING_SERVER;
STREAMING_SERVER *rtmpServer = 0; // server structure pointer
@@ -215,11 +212,9 @@ ServeInvoke(STREAMING_SERVER *server, in
else if (AVMATCH(&pname, &av_swfUrl))
{
#ifdef CRYPTO
- if (pval.av_val && RTMP_HashSWF(pval.av_val, &server->rc.Link.SWFSize, server->hash, 30) == 0)
- {
- server->rc.Link.SWFHash.av_val = (char *)server->hash;
- server->rc.Link.SWFHash.av_len = HASHLEN;
- }
+ if (pval.av_val)
+ RTMP_HashSWF(pval.av_val, &server->rc.Link.SWFSize,
+ (unsigned char *)server->rc.Link.SWFHash, 30);
#endif
server->rc.Link.swfUrl = pval;
pval.av_val = NULL;
@@ -907,16 +902,16 @@ void doServe(STREAMING_SERVER * server,
short nType = AMF_DecodeInt16(pc.m_body);
/* SWFverification */
if (nType == 0x1a)
- #ifdef CRYPTO
- if (server->rc.Link.SWFHash.av_len)
+#ifdef CRYPTO
+ if (server->rc.Link.SWFSize)
{
RTMP_SendCtrl(&server->rc, 0x1b, 0, 0);
sendit = 0;
}
- #else
+#else
/* The session will certainly fail right after this */
RTMP_Log(RTMP_LOGERROR, "%s, server requested SWF verification, need CRYPTO support! ", __FUNCTION__);
- #endif
+#endif
}
else if (server->f_cur && (
pc.m_packetType == 0x08 ||
@@ -974,9 +969,6 @@ cleanup:
server->rc.Link.app.av_val = NULL;
server->rc.Link.auth.av_val = NULL;
server->rc.Link.flashVer.av_val = NULL;
-#ifdef CRYPTO
- server->rc.Link.SWFHash.av_val = NULL;
-#endif
RTMP_LogPrintf("done!\n\n");
quit:
More information about the rtmpdump
mailing list