r405 - in trunk: librtmp/Makefile librtmp/handshake.h librtmp/rtmp.c librtmp/rtmp.h rtmpdump.c rtmpgw.c rtmpsuck.c
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:
hyc wrote:
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
This moves the option parsing I wrote for ffmpeg / mplayer into librtmp. This will make it easier to support option parsing in 3rd party code, they will just need to link against a current librtmp to automatically get support for whatever new options come along. The approach is pretty simple - the options are appended to the main URL, separated by spaces. I'll probably shift rtmpdump and rtmpgw over to this new approach too. So instead of rtmpdump --rtmp rtmp://server/app/path --swfurl http://server/path --pageurl http://server/path You could use rtmpdump -r "rtmp://server/app/path swfurl=http://server/path pageurl=http://server/path" Ideally we can unify all of this so that a single syntax works identically for rtmpdump, rtmpgw, and all the other apps.
How exactly would one quote a value? What if a value contains spaces? ie. flashVer, a connect string, or an output filename? (Not counting URLs which can be encoded, although URLs with unencoded spaces may theorhetically be required by a server.) Also, why not just drop the -r and have all non-switch arguments be treated as -r values? Shifting rtmpdump to this sounds fine, but please don't eliminate support for the old-style switches. :-) On Sat, Mar 27, 2010 at 3:39 PM, Howard Chu <hyc@highlandsun.com> wrote:
hyc wrote:
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
This moves the option parsing I wrote for ffmpeg / mplayer into librtmp. This will make it easier to support option parsing in 3rd party code, they will just need to link against a current librtmp to automatically get support for whatever new options come along.
The approach is pretty simple - the options are appended to the main URL, separated by spaces. I'll probably shift rtmpdump and rtmpgw over to this new approach too.
So instead of rtmpdump --rtmp rtmp://server/app/path --swfurl http://server/path --pageurl http://server/path
You could use rtmpdump -r "rtmp://server/app/path swfurl=http://server/path pageurl=http://server/path"
Ideally we can unify all of this so that a single syntax works identically for rtmpdump, rtmpgw, and all the other apps. _______________________________________________ rtmpdump mailing list rtmpdump@mplayerhq.hu https://lists.mplayerhq.hu/mailman/listinfo/rtmpdump
NhJm wrote:
How exactly would one quote a value? What if a value contains spaces? ie. flashVer, a connect string, or an output filename? (Not counting URLs which can be encoded, although URLs with unencoded spaces may theorhetically be required by a server.)
The output filename is not handled by these changes. I'm only talking about options that directly affect the RTMP negotiation, since they will be needed universally. (E.g., in XBMC, mplayer, or any other app that wants to access an RTMP stream, independent of what they do with the stream once they connect to it.) Servers cannot require unencoded spaces. Unencoded spaces are illegal in URLs. So, for spaces in values, just URLencode them, e.g. flashVer=LNX%2010.0.3.22
Also, why not just drop the -r and have all non-switch arguments be treated as -r values?
Because there are options that are only relevant to a specific application (e.g. rtmpdump) and those are separate from options that are relevant to the RTMP session.
Shifting rtmpdump to this sounds fine, but please don't eliminate support for the old-style switches. :-)
Right. At least, not yet. Down the road there should be no need to maintain both styles.
On Sat, Mar 27, 2010 at 3:39 PM, Howard Chu<hyc@highlandsun.com> wrote:
hyc wrote:
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
This moves the option parsing I wrote for ffmpeg / mplayer into librtmp. This will make it easier to support option parsing in 3rd party code, they will just need to link against a current librtmp to automatically get support for whatever new options come along.
The approach is pretty simple - the options are appended to the main URL, separated by spaces. I'll probably shift rtmpdump and rtmpgw over to this new approach too.
So instead of rtmpdump --rtmp rtmp://server/app/path --swfurl http://server/path --pageurl http://server/path
You could use rtmpdump -r "rtmp://server/app/path swfurl=http://server/path pageurl=http://server/path"
Ideally we can unify all of this so that a single syntax works identically for rtmpdump, rtmpgw, and all the other apps. _______________________________________________ rtmpdump mailing list rtmpdump@mplayerhq.hu https://lists.mplayerhq.hu/mailman/listinfo/rtmpdump
_______________________________________________ rtmpdump mailing list rtmpdump@mplayerhq.hu https://lists.mplayerhq.hu/mailman/listinfo/rtmpdump
-- -- Howard Chu CTO, Symas Corp. http://www.symas.com Director, Highland Sun http://highlandsun.com/hyc/ Chief Architect, OpenLDAP http://www.openldap.org/project/
Howard Chu wrote:
NhJm wrote:
How exactly would one quote a value? What if a value contains spaces? ie. flashVer, a connect string, or an output filename? (Not counting URLs which can be encoded, although URLs with unencoded spaces may theorhetically be required by a server.)
The output filename is not handled by these changes. I'm only talking about options that directly affect the RTMP negotiation, since they will be needed universally. (E.g., in XBMC, mplayer, or any other app that wants to access an RTMP stream, independent of what they do with the stream once they connect to it.)
Servers cannot require unencoded spaces. Unencoded spaces are illegal in URLs. So, for spaces in values, just URLencode them, e.g. flashVer=LNX%2010.0.3.22
URL encoding here would be a big mistake. We'll have to use some other escaping or substitution instead. Any method will work as long as it doesn't duplicate URL encoding. How about: replace spaces with TAB+Ctrl-A, (0x08 0x01) and replace TABs with TAB+Ctrl-B (0x08 0x02). Simple and probably low impact.
On Sun, Mar 28, 2010 at 2:52 AM, Howard Chu <hyc@highlandsun.com> wrote:
Howard Chu wrote:
NhJm wrote:
How exactly would one quote a value? What if a value contains spaces? ie. flashVer, a connect string, or an output filename? (Not counting URLs which can be encoded, although URLs with unencoded spaces may theorhetically be required by a server.)
The output filename is not handled by these changes. I'm only talking about options that directly affect the RTMP negotiation, since they will be needed universally. (E.g., in XBMC, mplayer, or any other app that wants to access an RTMP stream, independent of what they do with the stream once they connect to it.)
Servers cannot require unencoded spaces. Unencoded spaces are illegal in URLs. So, for spaces in values, just URLencode them, e.g. flashVer=LNX%2010.0.3.22
URL encoding here would be a big mistake. We'll have to use some other escaping or substitution instead. Any method will work as long as it doesn't duplicate URL encoding.
How about: replace spaces with TAB+Ctrl-A, (0x08 0x01) and replace TABs with TAB+Ctrl-B (0x08 0x02). Simple and probably low impact.
But a user wouldn't be able to easily enter these charcters. Backslashes would be a common and expected solution, requiring less explanation.
NhJm wrote:
On Sun, Mar 28, 2010 at 2:52 AM, Howard Chu<hyc@highlandsun.com> wrote:
How about: replace spaces with TAB+Ctrl-A, (0x08 0x01) and replace TABs with TAB+Ctrl-B (0x08 0x02). Simple and probably low impact.
But a user wouldn't be able to easily enter these charcters. Backslashes would be a common and expected solution, requiring less explanation.
OK. Although parsing simple backslashes is sometimes a pain, if you have several in a row. Lets go with "\XX" and hex encode the real character. (So it's like URLencoding, but using \ instead of % .)
participants (3)
-
Howard Chu -
hyc -
NhJm