1 /* HTTP-RTMP Stream Gateway
2 * Copyright (C) 2009 Andrej Stepanchuk
3 * Copyright (C) 2009-2010 Howard Chu
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with RTMPDump; see the file COPYING. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
32 #include "librtmp/rtmp_sys.h"
33 #include "librtmp/log.h"
39 #define RD_INCOMPLETE 2
41 #define PACKET_SIZE 1024*1024
44 #define InitSockets() {\
48 version = MAKEWORD(1,1); \
49 WSAStartup(version, &wsaData); }
51 #define CleanupSockets() WSACleanup()
54 #define CleanupSockets()
60 STREAMING_IN_PROGRESS
,
72 STREAMING_SERVER
*httpServer
= 0; // server structure pointer
74 STREAMING_SERVER
*startStreaming(const char *address
, int port
);
75 void stopStreaming(STREAMING_SERVER
* server
);
82 int bLiveStream
; // is it a live stream? then we can't seek/resume
84 long int timeout
; // timeout connection after 120 seconds
99 AVal usherToken
; //Justin.tv auth token
107 uint32_t dStartOffset
;
108 uint32_t dStopOffset
;
111 unsigned char hash
[RTMP_SWF_HASHLEN
];
115 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
118 parseAMF(AMFObject
*obj
, const char *arg
, int *depth
)
120 AMFObjectProperty prop
= {{0,0}};
130 prop
.p_type
= AMF_BOOLEAN
;
131 prop
.p_vu
.p_number
= atoi(p
);
134 prop
.p_type
= AMF_STRING
;
135 STR2AVAL(prop
.p_vu
.p_aval
,p
);
138 prop
.p_type
= AMF_NUMBER
;
139 prop
.p_vu
.p_number
= strtod(p
, NULL
);
142 prop
.p_type
= AMF_NULL
;
148 prop
.p_type
= AMF_OBJECT
;
160 else if (arg
[2] == ':' && arg
[0] == 'N')
162 p
= strchr(arg
+3, ':');
165 prop
.p_name
.av_val
= (char *)arg
+3;
166 prop
.p_name
.av_len
= p
- (arg
+3);
172 prop
.p_type
= AMF_BOOLEAN
;
173 prop
.p_vu
.p_number
= atoi(p
);
176 prop
.p_type
= AMF_STRING
;
177 STR2AVAL(prop
.p_vu
.p_aval
,p
);
180 prop
.p_type
= AMF_NUMBER
;
181 prop
.p_vu
.p_number
= strtod(p
, NULL
);
184 prop
.p_type
= AMF_OBJECT
;
196 for (i
=0; i
<*depth
; i
++)
198 o2
= &obj
->o_props
[obj
->o_num
-1].p_vu
.p_object
;
202 AMF_AddProp(obj
, &prop
);
203 if (prop
.p_type
== AMF_OBJECT
)
208 /* this request is formed from the parameters and used to initialize a new request,
209 * thus it is a default settings list. All settings can be overriden by specifying the
210 * parameters in the GET request. */
211 RTMP_REQUEST defaultRTMPRequest
;
213 int ParseOption(char opt
, char *arg
, RTMP_REQUEST
* req
);
216 uint32_t debugTS
= 0;
220 FILE *netstackdump
= NULL
;
221 FILE *netstackdump_read
= NULL
;
224 /* inplace http unescape. This is possible .. strlen(unescaped_string) <= strlen(esacped_string) */
226 http_unescape(char *data
)
233 int length
= (int) strlen(data
);
236 while (src_x
< length
)
238 if (strncmp(data
+ src_x
, "%", 1) == 0 && src_x
+ 2 < length
)
241 // Since we encountered a '%' we know this is an escaped character
243 hex
[0] = data
[src_x
+ 1];
244 hex
[1] = data
[src_x
+ 2];
245 data
[dst_x
] = (char) strtol(hex
, &stp
, 16);
249 else if (src_x
!= dst_x
)
252 // This doesn't need to be unescaped. If we didn't unescape anything previously
253 // there is no need to copy the string either
255 data
[dst_x
] = data
[src_x
];
262 // This doesn't need to be unescaped, however we need to copy the string
272 controlServerThread(void *unused
)
281 RTMP_LogPrintf("Exiting\n");
282 stopStreaming(httpServer
);
286 RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich
);
293 ssize_t readHTTPLine(int sockfd, char *buffer, size_t length)
297 while(i < length-1) {
299 int n = read(sockfd, &c, 1);
316 int isHTTPRequestEOF(char *line, size_t length)
321 if(line[0]=='\r' && line[1]=='\n')
328 void processTCPrequest(STREAMING_SERVER
* server
, // server socket and state (our listening socket)
329 int sockfd
// client connection socket
332 char buf
[512] = { 0 }; // answer buffer
333 char header
[2048] = { 0 }; // request header
334 char *filename
= NULL
; // GET request: file name //512 not enuf
335 char *buffer
= NULL
; // stream buffer
336 char *ptr
= NULL
; // header pointer
341 char srvhead
[] = "\r\nServer: HTTP-RTMP Stream Server " RTMPDUMP_VERSION
"\r\n";
343 char *status
= "404 Not Found";
345 server
->state
= STREAMING_IN_PROGRESS
;
348 uint32_t dSeek
= 0; // can be used to start from a later point in the stream
350 // reset RTMP options to defaults specified upon invokation of streams
352 memcpy(&req
, &defaultRTMPRequest
, sizeof(RTMP_REQUEST
));
354 // timeout for http requests
358 memset(&tv
, 0, sizeof(struct timeval
));
361 // go through request lines
364 FD_SET(sockfd
, &fds
);
366 if (select(sockfd
+ 1, &fds
, NULL
, NULL
, &tv
) <= 0)
368 RTMP_Log(RTMP_LOGERROR
, "Request timeout/select failed, ignoring request");
373 nRead
= recv(sockfd
, header
, 2047, 0);
376 RTMP_Log(RTMP_LOGDEBUG
, "%s: header: %s", __FUNCTION__
, header
);
378 if (strstr(header
, "Range: bytes=") != 0)
380 // TODO check range starts from 0 and asking till the end.
381 RTMP_LogPrintf("%s, Range request not supported\n", __FUNCTION__
);
382 len
= sprintf(buf
, "HTTP/1.0 416 Requested Range Not Satisfiable%s\r\n",
384 send(sockfd
, buf
, len
, 0);
388 if (strncmp(header
, "GET", 3) == 0 && nRead
> 4)
390 filename
= header
+ 4;
392 // filter " HTTP/..." from end of request
405 //} while(!isHTTPRequestEOF(header, nRead));
407 // if we got a filename from the GET method
408 if (filename
!= NULL
)
410 RTMP_Log(RTMP_LOGDEBUG
, "%s: Request header: %s", __FUNCTION__
, filename
);
411 if (filename
[0] == '/')
412 { // if its not empty, is it /?
419 int len
= strlen(ptr
);
426 goto filenotfound
; // long parameters not (yet) supported
431 // get position of the next '&'
434 unsigned int nArgLen
= len
;
435 if ((temp
= strstr(ptr
, "&")) != 0)
437 nArgLen
= temp
- ptr
;
440 char *arg
= (char *) malloc((nArgLen
+ 1) * sizeof(char));
441 memcpy(arg
, ptr
, nArgLen
* sizeof(char));
444 //RTMP_Log(RTMP_LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg);
447 RTMP_Log(RTMP_LOGDEBUG
, "%s: parameter: %c, arg: %s", __FUNCTION__
,
453 if (!ParseOption(ich
, arg
, &req
))
455 status
= "400 unknown option";
468 RTMP_LogPrintf("%s: No request header received/unsupported method\n",
472 // do necessary checks right here to make sure the combined request of default values and GET parameters is correct
473 if (!req
.hostname
.av_len
&& !req
.fullUrl
.av_len
)
475 RTMP_Log(RTMP_LOGERROR
,
476 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
477 status
= "400 Missing Hostname";
480 if (req
.playpath
.av_len
== 0 && !req
.fullUrl
.av_len
)
482 RTMP_Log(RTMP_LOGERROR
,
483 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
484 status
= "400 Missing Playpath";
488 if (req
.protocol
== RTMP_PROTOCOL_UNDEFINED
&& !req
.fullUrl
.av_len
)
490 RTMP_Log(RTMP_LOGWARNING
,
491 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
492 req
.protocol
= RTMP_PROTOCOL_RTMP
;
494 if (req
.rtmpport
== -1 && !req
.fullUrl
.av_len
)
496 RTMP_Log(RTMP_LOGWARNING
,
497 "You haven't specified a port (--port) or rtmp url (-r), using default port");
500 if (req
.rtmpport
== 0 && !req
.fullUrl
.av_len
)
502 if (req
.protocol
& RTMP_FEATURE_SSL
)
504 else if (req
.protocol
& RTMP_FEATURE_HTTP
)
510 if (req
.tcUrl
.av_len
== 0)
512 char str
[512] = { 0 };
513 req
.tcUrl
.av_len
= snprintf(str
, 511, "%s://%.*s:%d/%.*s",
514 RTMPProtocolStringsLower
[req
.protocol
], req
.hostname
.av_len
,
515 req
.hostname
.av_val
, req
.rtmpport
, req
.app
.av_len
, req
.app
.av_val
);
516 req
.tcUrl
.av_val
= (char *) malloc(req
.tcUrl
.av_len
+ 1);
517 strcpy(req
.tcUrl
.av_val
, str
);
523 if (RTMP_HashSWF(req
.swfUrl
.av_val
, &req
.swfSize
, req
.hash
, req
.swfAge
) == 0)
525 req
.swfHash
.av_val
= (char *)req
.hash
;
526 req
.swfHash
.av_len
= RTMP_SWF_HASHLEN
;
531 // after validation of the http request send response header
532 len
= sprintf(buf
, "HTTP/1.0 200 OK%sContent-Type: video/flv\r\n\r\n", srvhead
);
533 send(sockfd
, buf
, len
, 0);
536 buffer
= (char *) calloc(PACKET_SIZE
, 1);
538 // User defined seek offset
539 if (req
.dStartOffset
> 0)
542 RTMP_Log(RTMP_LOGWARNING
,
543 "Can't seek in a live stream, ignoring --seek option");
545 dSeek
+= req
.dStartOffset
;
550 RTMP_LogPrintf("Starting at TS: %d ms\n", dSeek
);
553 RTMP_Log(RTMP_LOGDEBUG
, "Setting buffer time to: %dms", req
.bufferTime
);
555 RTMP_SetBufferMS(&rtmp
, req
.bufferTime
);
556 if (!req
.fullUrl
.av_len
)
558 RTMP_SetupStream(&rtmp
, req
.protocol
, &req
.hostname
, req
.rtmpport
, &req
.sockshost
,
559 &req
.playpath
, &req
.tcUrl
, &req
.swfUrl
, &req
.pageUrl
, &req
.app
, &req
.auth
, &req
.swfHash
, req
.swfSize
, &req
.flashVer
, &req
.subscribepath
, &req
.usherToken
, dSeek
, req
.dStopOffset
,
560 req
.bLiveStream
, req
.timeout
);
564 if (RTMP_SetupURL(&rtmp
, req
.fullUrl
.av_val
) == FALSE
)
566 RTMP_Log(RTMP_LOGERROR
, "Couldn't parse URL: %s", req
.fullUrl
.av_val
);
570 /* backward compatibility, we always sent this as true before */
572 rtmp
.Link
.lFlags
|= RTMP_LF_AUTH
;
574 rtmp
.Link
.extras
= req
.extras
;
575 rtmp
.Link
.token
= req
.token
;
576 rtmp
.m_read
.timestamp
= dSeek
;
578 RTMP_LogPrintf("Connecting ... port: %d, app: %s\n", req
.rtmpport
, req
.app
.av_val
);
579 if (!RTMP_Connect(&rtmp
, NULL
))
581 RTMP_LogPrintf("%s, failed to connect!\n", __FUNCTION__
);
585 unsigned long size
= 0;
587 double duration
= 0.0;
594 nRead
= RTMP_Read(&rtmp
, buffer
, PACKET_SIZE
);
598 if ((nWritten
= send(sockfd
, buffer
, nRead
, 0)) < 0)
600 RTMP_Log(RTMP_LOGERROR
, "%s, sending failed, error: %d", __FUNCTION__
,
602 goto cleanup
; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING
607 //RTMP_LogPrintf("write %dbytes (%.1f KB)\n", nRead, nRead/1024.0);
608 if (duration
<= 0) // if duration unknown try to get it from the stream (onMetaData)
609 duration
= RTMP_GetDuration(&rtmp
);
614 ((double) (dSeek
+ rtmp
.m_read
.timestamp
)) / (duration
*
616 percent
= ((double) (int) (percent
* 10.0)) / 10.0;
617 RTMP_LogStatus("\r%.3f KB / %.2f sec (%.1f%%)",
618 (double) size
/ 1024.0,
619 (double) (rtmp
.m_read
.timestamp
) / 1000.0, percent
);
623 RTMP_LogStatus("\r%.3f KB / %.2f sec", (double) size
/ 1024.0,
624 (double) (rtmp
.m_read
.timestamp
) / 1000.0);
630 RTMP_Log(RTMP_LOGDEBUG
, "zero read!");
634 while (server
->state
== STREAMING_IN_PROGRESS
&& nRead
> -1
635 && RTMP_IsConnected(&rtmp
) && nWritten
>= 0);
638 RTMP_LogPrintf("Closing connection... ");
640 RTMP_LogPrintf("done!\n\n");
652 if (server
->state
== STREAMING_IN_PROGRESS
)
653 server
->state
= STREAMING_ACCEPTING
;
658 RTMP_LogPrintf("%s, %s, %s\n", __FUNCTION__
, status
, filename
);
659 len
= sprintf(buf
, "HTTP/1.0 %s%s\r\n", status
, srvhead
);
660 send(sockfd
, buf
, len
, 0);
665 serverThread(void *arg
)
667 STREAMING_SERVER
*server
= arg
;
668 server
->state
= STREAMING_ACCEPTING
;
670 while (server
->state
== STREAMING_ACCEPTING
)
672 struct sockaddr_in addr
;
673 socklen_t addrlen
= sizeof(struct sockaddr_in
);
675 accept(server
->socket
, (struct sockaddr
*) &addr
, &addrlen
);
679 // Create a new process and transfer the control to that
680 RTMP_Log(RTMP_LOGDEBUG
, "%s: accepted connection from %s\n", __FUNCTION__
,
681 inet_ntoa(addr
.sin_addr
));
682 processTCPrequest(server
, sockfd
);
683 RTMP_Log(RTMP_LOGDEBUG
, "%s: processed request\n", __FUNCTION__
);
687 RTMP_Log(RTMP_LOGERROR
, "%s: accept failed", __FUNCTION__
);
690 server
->state
= STREAMING_STOPPED
;
695 startStreaming(const char *address
, int port
)
697 struct sockaddr_in addr
;
699 STREAMING_SERVER
*server
;
701 sockfd
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
704 RTMP_Log(RTMP_LOGERROR
, "%s, couldn't create socket", __FUNCTION__
);
708 addr
.sin_family
= AF_INET
;
709 addr
.sin_addr
.s_addr
= inet_addr(address
); //htonl(INADDR_ANY);
710 addr
.sin_port
= htons(port
);
712 if (bind(sockfd
, (struct sockaddr
*) &addr
, sizeof(struct sockaddr_in
)) ==
715 RTMP_Log(RTMP_LOGERROR
, "%s, TCP bind failed for port number: %d", __FUNCTION__
,
720 if (listen(sockfd
, 10) == -1)
722 RTMP_Log(RTMP_LOGERROR
, "%s, listen failed", __FUNCTION__
);
727 server
= (STREAMING_SERVER
*) calloc(1, sizeof(STREAMING_SERVER
));
728 server
->socket
= sockfd
;
730 ThreadCreate(serverThread
, server
);
736 stopStreaming(STREAMING_SERVER
* server
)
740 if (server
->state
!= STREAMING_STOPPED
)
742 if (server
->state
== STREAMING_IN_PROGRESS
)
744 server
->state
= STREAMING_STOPPING
;
746 // wait for streaming threads to exit
747 while (server
->state
!= STREAMING_STOPPED
)
751 if (closesocket(server
->socket
))
752 RTMP_Log(RTMP_LOGERROR
, "%s: Failed to close listening socket, error %d",
753 __FUNCTION__
, GetSockError());
755 server
->state
= STREAMING_STOPPED
;
761 sigIntHandler(int sig
)
764 RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig
);
766 stopStreaming(httpServer
);
767 signal(SIGINT
, SIG_DFL
);
770 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
771 int hex2bin(char *str
, char **hex
)
774 int i
, l
= strlen(str
);
785 *ptr
++ = (HEX2BIN(str
[i
]) << 4) | HEX2BIN(str
[i
+1]);
789 // this will parse RTMP related options as needed
790 // excludes the following options: h, d, g
792 // Return values: true (option parsing ok)
793 // false (option not parsed/invalid)
795 ParseOption(char opt
, char *arg
, RTMP_REQUEST
* req
)
802 int res
= hex2bin(arg
, &req
->swfHash
.av_val
);
803 if (!res
|| res
!= RTMP_SWF_HASHLEN
)
805 req
->swfHash
.av_val
= NULL
;
806 RTMP_Log(RTMP_LOGWARNING
,
807 "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN
);
809 req
->swfHash
.av_len
= RTMP_SWF_HASHLEN
;
814 int size
= atoi(arg
);
817 RTMP_Log(RTMP_LOGERROR
, "SWF Size must be at least 1, ignoring\n");
827 STR2AVAL(req
->swfUrl
, arg
);
836 RTMP_Log(RTMP_LOGERROR
, "SWF Age must be non-negative, ignoring\n");
847 int32_t bt
= atol(arg
);
850 RTMP_Log(RTMP_LOGERROR
,
851 "Buffer time must be greater than zero, ignoring the specified value %d!",
856 req
->bufferTime
= bt
;
861 req
->bLiveStream
= TRUE
; // no seeking or resuming possible!
864 STR2AVAL(req
->subscribepath
, arg
);
867 STR2AVAL(req
->hostname
, arg
);
870 req
->rtmpport
= atoi(arg
);
874 int protocol
= atoi(arg
);
875 if (protocol
< RTMP_PROTOCOL_RTMP
|| protocol
> RTMP_PROTOCOL_RTMPTS
)
877 RTMP_Log(RTMP_LOGERROR
, "Unknown protocol specified: %d, using default",
883 req
->protocol
= protocol
;
888 STR2AVAL(req
->playpath
, arg
);
894 AVal parsedHost
, parsedPlaypath
, parsedApp
;
895 unsigned int parsedPort
= 0;
896 int parsedProtocol
= RTMP_PROTOCOL_UNDEFINED
;
899 (req
->rtmpurl
, &parsedProtocol
, &parsedHost
, &parsedPort
,
900 &parsedPlaypath
, &parsedApp
))
902 RTMP_Log(RTMP_LOGWARNING
, "Couldn't parse the specified url (%s)!", arg
);
906 if (!req
->hostname
.av_len
)
907 req
->hostname
= parsedHost
;
908 if (req
->rtmpport
== -1)
909 req
->rtmpport
= parsedPort
;
910 if (req
->playpath
.av_len
== 0 && parsedPlaypath
.av_len
)
912 req
->playpath
= parsedPlaypath
;
914 if (req
->protocol
== RTMP_PROTOCOL_UNDEFINED
)
915 req
->protocol
= parsedProtocol
;
916 if (req
->app
.av_len
== 0 && parsedApp
.av_len
)
918 req
->app
= parsedApp
;
924 STR2AVAL(req
->fullUrl
, arg
);
927 STR2AVAL(req
->swfUrl
, arg
);
930 STR2AVAL(req
->tcUrl
, arg
);
933 STR2AVAL(req
->pageUrl
, arg
);
936 STR2AVAL(req
->app
, arg
);
939 STR2AVAL(req
->flashVer
, arg
);
942 STR2AVAL(req
->auth
, arg
);
945 parseAMF(&req
->extras
, arg
, &req
->edepth
);
948 req
->timeout
= atoi(arg
);
951 req
->dStartOffset
= (int)(atof(arg
) * 1000.0);
952 //printf("dStartOffset = %d\n", dStartOffset);
955 req
->dStopOffset
= (int)(atof(arg
) * 1000.0);
956 //printf("dStartOffset = %d\n", dStartOffset);
959 STR2AVAL(req
->token
, arg
);
962 STR2AVAL(req
->sockshost
, arg
);
964 RTMP_debuglevel
= RTMP_LOGCRIT
;
967 RTMP_debuglevel
= RTMP_LOGDEBUG
;
970 RTMP_debuglevel
= RTMP_LOGALL
;
973 STR2AVAL(req
->usherToken
, arg
);
976 RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt
, arg
);
983 main(int argc
, char **argv
)
985 int nStatus
= RD_SUCCESS
;
987 // http streaming server
988 char DEFAULT_HTTP_STREAMING_DEVICE
[] = "0.0.0.0"; // 0.0.0.0 is any device
990 char *httpStreamingDevice
= DEFAULT_HTTP_STREAMING_DEVICE
; // streaming device, default 0.0.0.0
991 int nHttpStreamingPort
= 80; // port
993 RTMP_LogPrintf("HTTP-RTMP Stream Gateway %s\n", RTMPDUMP_VERSION
);
994 RTMP_LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n");
997 memset(&defaultRTMPRequest
, 0, sizeof(RTMP_REQUEST
));
999 defaultRTMPRequest
.rtmpport
= -1;
1000 defaultRTMPRequest
.protocol
= RTMP_PROTOCOL_UNDEFINED
;
1001 defaultRTMPRequest
.bLiveStream
= FALSE
; // is it a live stream? then we can't seek/resume
1003 defaultRTMPRequest
.timeout
= 120; // timeout connection after 120 seconds
1004 defaultRTMPRequest
.bufferTime
= 20 * 1000;
1006 defaultRTMPRequest
.swfAge
= 30;
1009 struct option longopts
[] = {
1010 {"help", 0, NULL
, 'h'},
1011 {"url", 1, NULL
, 'i'},
1012 {"host", 1, NULL
, 'n'},
1013 {"port", 1, NULL
, 'c'},
1014 {"socks", 1, NULL
, 'S'},
1015 {"protocol", 1, NULL
, 'l'},
1016 {"playpath", 1, NULL
, 'y'},
1017 {"rtmp", 1, NULL
, 'r'},
1018 {"swfUrl", 1, NULL
, 's'},
1019 {"tcUrl", 1, NULL
, 't'},
1020 {"pageUrl", 1, NULL
, 'p'},
1021 {"app", 1, NULL
, 'a'},
1023 {"swfhash", 1, NULL
, 'w'},
1024 {"swfsize", 1, NULL
, 'x'},
1025 {"swfVfy", 1, NULL
, 'W'},
1026 {"swfAge", 1, NULL
, 'X'},
1028 {"auth", 1, NULL
, 'u'},
1029 {"conn", 1, NULL
, 'C'},
1030 {"flashVer", 1, NULL
, 'f'},
1031 {"live", 0, NULL
, 'v'},
1032 //{"flv", 1, NULL, 'o'},
1033 //{"resume", 0, NULL, 'e'},
1034 {"timeout", 1, NULL
, 'm'},
1035 {"buffer", 1, NULL
, 'b'},
1036 //{"skip", 1, NULL, 'k'},
1037 {"device", 1, NULL
, 'D'},
1038 {"sport", 1, NULL
, 'g'},
1039 {"subscribe", 1, NULL
, 'd'},
1040 {"start", 1, NULL
, 'A'},
1041 {"stop", 1, NULL
, 'B'},
1042 {"token", 1, NULL
, 'T'},
1043 {"debug", 0, NULL
, 'z'},
1044 {"quiet", 0, NULL
, 'q'},
1045 {"verbose", 0, NULL
, 'V'},
1046 {"jtv", 1, NULL
, 'j'},
1050 signal(SIGINT
, sigIntHandler
);
1052 signal(SIGPIPE
, SIG_IGN
);
1058 getopt_long(argc
, argv
,
1059 "hvqVzr:s:t:i:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:", longopts
,
1066 ("\nThis program serves media content streamed from RTMP onto HTTP.\n\n");
1067 RTMP_LogPrintf("--help|-h Prints this help screen.\n");
1069 ("--url|-i url URL with options included (e.g. rtmp://host[:port]/path swfUrl=url tcUrl=url)\n");
1071 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
1073 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
1075 ("--port|-c port Overrides the port in the rtmp url\n");
1077 ("--socks|-S host:port Use the specified SOCKS proxy\n");
1079 ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
1081 ("--playpath|-y Overrides the playpath parsed from rtmp url\n");
1082 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
1084 ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
1085 RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
1086 RTMP_LogPrintf("--app|-a app Name of target app in server\n");
1089 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
1091 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
1093 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
1095 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
1098 ("--auth|-u string Authentication string to be appended to the connect string\n");
1100 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
1102 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
1104 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
1106 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
1107 RTMP_DefaultFlashVer
.av_val
);
1109 ("--live|-v Get a live stream, no --resume (seeking) of live streams possible\n");
1111 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specified)\n");
1113 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
1114 defaultRTMPRequest
.timeout
);
1116 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
1118 ("--stop|-B num Stop at num seconds into stream\n");
1120 ("--token|-T key Key for SecureToken response\n");
1122 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
1124 ("--buffer|-b Buffer time in milliseconds (default: %u)\n\n",
1125 defaultRTMPRequest
.bufferTime
);
1128 ("--device|-D Streaming device ip address (default: %s)\n",
1129 DEFAULT_HTTP_STREAMING_DEVICE
);
1131 ("--sport|-g Streaming port (default: %d)\n\n",
1132 nHttpStreamingPort
);
1134 ("--quiet|-q Suppresses all command output.\n");
1135 RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
1136 RTMP_LogPrintf("--debug|-z Debug level command output.\n");
1138 ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
1139 RTMP_LogPrintf("packet.\n\n");
1142 // streaming server specific options
1144 if (inet_addr(optarg
) == INADDR_NONE
)
1146 RTMP_Log(RTMP_LOGERROR
,
1147 "Invalid binding address (requested address %s), ignoring",
1152 httpStreamingDevice
= optarg
;
1157 int port
= atoi(optarg
);
1158 if (port
< 0 || port
> 65535)
1160 RTMP_Log(RTMP_LOGERROR
,
1161 "Streaming port out of range (requested port %d), ignoring\n",
1166 nHttpStreamingPort
= port
;
1171 //RTMP_LogPrintf("unknown option: %c\n", opt);
1172 if (!ParseOption(opt
, optarg
, &defaultRTMPRequest
))
1179 netstackdump
= fopen("netstackdump", "wb");
1180 netstackdump_read
= fopen("netstackdump_read", "wb");
1184 ThreadCreate(controlServerThread
, 0);
1186 // start http streaming
1188 startStreaming(httpStreamingDevice
, nHttpStreamingPort
)) == 0)
1190 RTMP_Log(RTMP_LOGERROR
, "Failed to start HTTP server, exiting!");
1193 RTMP_LogPrintf("Streaming on http://%s:%d\n", httpStreamingDevice
,
1194 nHttpStreamingPort
);
1196 while (httpServer
->state
!= STREAMING_STOPPED
)
1200 RTMP_Log(RTMP_LOGDEBUG
, "Done, exiting...");
1205 if (netstackdump
!= 0)
1206 fclose(netstackdump
);
1207 if (netstackdump_read
!= 0)
1208 fclose(netstackdump_read
);