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
98 AVal usherToken
; //Justin.tv auth token
106 uint32_t dStartOffset
;
107 uint32_t dStopOffset
;
110 unsigned char hash
[RTMP_SWF_HASHLEN
];
114 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
117 parseAMF(AMFObject
*obj
, const char *arg
, int *depth
)
119 AMFObjectProperty prop
= {{0,0}};
129 prop
.p_type
= AMF_BOOLEAN
;
130 prop
.p_vu
.p_number
= atoi(p
);
133 prop
.p_type
= AMF_STRING
;
134 STR2AVAL(prop
.p_vu
.p_aval
,p
);
137 prop
.p_type
= AMF_NUMBER
;
138 prop
.p_vu
.p_number
= strtod(p
, NULL
);
141 prop
.p_type
= AMF_NULL
;
147 prop
.p_type
= AMF_OBJECT
;
159 else if (arg
[2] == ':' && arg
[0] == 'N')
161 p
= strchr(arg
+3, ':');
164 prop
.p_name
.av_val
= (char *)arg
+3;
165 prop
.p_name
.av_len
= p
- (arg
+3);
171 prop
.p_type
= AMF_BOOLEAN
;
172 prop
.p_vu
.p_number
= atoi(p
);
175 prop
.p_type
= AMF_STRING
;
176 STR2AVAL(prop
.p_vu
.p_aval
,p
);
179 prop
.p_type
= AMF_NUMBER
;
180 prop
.p_vu
.p_number
= strtod(p
, NULL
);
183 prop
.p_type
= AMF_OBJECT
;
195 for (i
=0; i
<*depth
; i
++)
197 o2
= &obj
->o_props
[obj
->o_num
-1].p_vu
.p_object
;
201 AMF_AddProp(obj
, &prop
);
202 if (prop
.p_type
== AMF_OBJECT
)
207 /* this request is formed from the parameters and used to initialize a new request,
208 * thus it is a default settings list. All settings can be overriden by specifying the
209 * parameters in the GET request. */
210 RTMP_REQUEST defaultRTMPRequest
;
212 int ParseOption(char opt
, char *arg
, RTMP_REQUEST
* req
);
215 uint32_t debugTS
= 0;
219 FILE *netstackdump
= NULL
;
220 FILE *netstackdump_read
= NULL
;
223 /* inplace http unescape. This is possible .. strlen(unescaped_string) <= strlen(esacped_string) */
225 http_unescape(char *data
)
232 int length
= (int) strlen(data
);
235 while (src_x
< length
)
237 if (strncmp(data
+ src_x
, "%", 1) == 0 && src_x
+ 2 < length
)
240 // Since we encountered a '%' we know this is an escaped character
242 hex
[0] = data
[src_x
+ 1];
243 hex
[1] = data
[src_x
+ 2];
244 data
[dst_x
] = (char) strtol(hex
, &stp
, 16);
248 else if (src_x
!= dst_x
)
251 // This doesn't need to be unescaped. If we didn't unescape anything previously
252 // there is no need to copy the string either
254 data
[dst_x
] = data
[src_x
];
261 // This doesn't need to be unescaped, however we need to copy the string
271 controlServerThread(void *unused
)
280 RTMP_LogPrintf("Exiting\n");
281 stopStreaming(httpServer
);
285 RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich
);
292 ssize_t readHTTPLine(int sockfd, char *buffer, size_t length)
296 while(i < length-1) {
298 int n = read(sockfd, &c, 1);
315 int isHTTPRequestEOF(char *line, size_t length)
320 if(line[0]=='\r' && line[1]=='\n')
327 void processTCPrequest(STREAMING_SERVER
* server
, // server socket and state (our listening socket)
328 int sockfd
// client connection socket
331 char buf
[512] = { 0 }; // answer buffer
332 char header
[2048] = { 0 }; // request header
333 char *filename
= NULL
; // GET request: file name //512 not enuf
334 char *buffer
= NULL
; // stream buffer
335 char *ptr
= NULL
; // header pointer
340 char srvhead
[] = "\r\nServer: HTTP-RTMP Stream Server " RTMPDUMP_VERSION
"\r\n";
342 char *status
= "404 Not Found";
344 server
->state
= STREAMING_IN_PROGRESS
;
347 uint32_t dSeek
= 0; // can be used to start from a later point in the stream
349 // reset RTMP options to defaults specified upon invokation of streams
351 memcpy(&req
, &defaultRTMPRequest
, sizeof(RTMP_REQUEST
));
353 // timeout for http requests
357 memset(&tv
, 0, sizeof(struct timeval
));
360 // go through request lines
363 FD_SET(sockfd
, &fds
);
365 if (select(sockfd
+ 1, &fds
, NULL
, NULL
, &tv
) <= 0)
367 RTMP_Log(RTMP_LOGERROR
, "Request timeout/select failed, ignoring request");
372 nRead
= recv(sockfd
, header
, 2047, 0);
375 RTMP_Log(RTMP_LOGDEBUG
, "%s: header: %s", __FUNCTION__
, header
);
377 if (strstr(header
, "Range: bytes=") != 0)
379 // TODO check range starts from 0 and asking till the end.
380 RTMP_LogPrintf("%s, Range request not supported\n", __FUNCTION__
);
381 len
= sprintf(buf
, "HTTP/1.0 416 Requested Range Not Satisfiable%s\r\n",
383 send(sockfd
, buf
, len
, 0);
387 if (strncmp(header
, "GET", 3) == 0 && nRead
> 4)
389 filename
= header
+ 4;
391 // filter " HTTP/..." from end of request
404 //} while(!isHTTPRequestEOF(header, nRead));
406 // if we got a filename from the GET method
407 if (filename
!= NULL
)
409 RTMP_Log(RTMP_LOGDEBUG
, "%s: Request header: %s", __FUNCTION__
, filename
);
410 if (filename
[0] == '/')
411 { // if its not empty, is it /?
418 int len
= strlen(ptr
);
425 goto filenotfound
; // long parameters not (yet) supported
430 // get position of the next '&'
433 unsigned int nArgLen
= len
;
434 if ((temp
= strstr(ptr
, "&")) != 0)
436 nArgLen
= temp
- ptr
;
439 char *arg
= (char *) malloc((nArgLen
+ 1) * sizeof(char));
440 memcpy(arg
, ptr
, nArgLen
* sizeof(char));
443 //RTMP_Log(RTMP_LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg);
446 RTMP_Log(RTMP_LOGDEBUG
, "%s: parameter: %c, arg: %s", __FUNCTION__
,
452 if (!ParseOption(ich
, arg
, &req
))
454 status
= "400 unknown option";
467 RTMP_LogPrintf("%s: No request header received/unsupported method\n",
471 // do necessary checks right here to make sure the combined request of default values and GET parameters is correct
472 if (!req
.hostname
.av_len
)
474 RTMP_Log(RTMP_LOGERROR
,
475 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
476 status
= "400 Missing Hostname";
479 if (req
.playpath
.av_len
== 0)
481 RTMP_Log(RTMP_LOGERROR
,
482 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
483 status
= "400 Missing Playpath";
487 if (req
.protocol
== RTMP_PROTOCOL_UNDEFINED
)
489 RTMP_Log(RTMP_LOGWARNING
,
490 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
491 req
.protocol
= RTMP_PROTOCOL_RTMP
;
493 if (req
.rtmpport
== -1)
495 RTMP_Log(RTMP_LOGWARNING
,
496 "You haven't specified a port (--port) or rtmp url (-r), using default port");
499 if (req
.rtmpport
== 0)
501 if (req
.protocol
& RTMP_FEATURE_SSL
)
503 else if (req
.protocol
& RTMP_FEATURE_HTTP
)
509 if (req
.tcUrl
.av_len
== 0)
511 char str
[512] = { 0 };
512 req
.tcUrl
.av_len
= snprintf(str
, 511, "%s://%.*s:%d/%.*s",
513 RTMPProtocolStringsLower
[req
.protocol
], req
.hostname
.av_len
,
514 req
.hostname
.av_val
, req
.rtmpport
, req
.app
.av_len
, req
.app
.av_val
);
515 req
.tcUrl
.av_val
= (char *) malloc(req
.tcUrl
.av_len
+ 1);
516 strcpy(req
.tcUrl
.av_val
, str
);
522 if (RTMP_HashSWF(req
.swfUrl
.av_val
, &req
.swfSize
, req
.hash
, req
.swfAge
) == 0)
524 req
.swfHash
.av_val
= (char *)req
.hash
;
525 req
.swfHash
.av_len
= RTMP_SWF_HASHLEN
;
530 // after validation of the http request send response header
531 len
= sprintf(buf
, "HTTP/1.0 200 OK%sContent-Type: video/flv\r\n\r\n", srvhead
);
532 send(sockfd
, buf
, len
, 0);
535 buffer
= (char *) calloc(PACKET_SIZE
, 1);
537 // User defined seek offset
538 if (req
.dStartOffset
> 0)
541 RTMP_Log(RTMP_LOGWARNING
,
542 "Can't seek in a live stream, ignoring --seek option");
544 dSeek
+= req
.dStartOffset
;
549 RTMP_LogPrintf("Starting at TS: %d ms\n", dSeek
);
552 RTMP_Log(RTMP_LOGDEBUG
, "Setting buffer time to: %dms", req
.bufferTime
);
554 RTMP_SetBufferMS(&rtmp
, req
.bufferTime
);
555 RTMP_SetupStream(&rtmp
, req
.protocol
, &req
.hostname
, req
.rtmpport
, &req
.sockshost
,
556 &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
,
557 req
.bLiveStream
, req
.timeout
);
558 /* backward compatibility, we always sent this as true before */
560 rtmp
.Link
.lFlags
|= RTMP_LF_AUTH
;
562 rtmp
.Link
.extras
= req
.extras
;
563 rtmp
.Link
.token
= req
.token
;
564 rtmp
.m_read
.timestamp
= dSeek
;
566 RTMP_LogPrintf("Connecting ... port: %d, app: %s\n", req
.rtmpport
, req
.app
);
567 if (!RTMP_Connect(&rtmp
, NULL
))
569 RTMP_LogPrintf("%s, failed to connect!\n", __FUNCTION__
);
573 unsigned long size
= 0;
575 double duration
= 0.0;
582 nRead
= RTMP_Read(&rtmp
, buffer
, PACKET_SIZE
);
586 if ((nWritten
= send(sockfd
, buffer
, nRead
, 0)) < 0)
588 RTMP_Log(RTMP_LOGERROR
, "%s, sending failed, error: %d", __FUNCTION__
,
590 goto cleanup
; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING
595 //RTMP_LogPrintf("write %dbytes (%.1f KB)\n", nRead, nRead/1024.0);
596 if (duration
<= 0) // if duration unknown try to get it from the stream (onMetaData)
597 duration
= RTMP_GetDuration(&rtmp
);
602 ((double) (dSeek
+ rtmp
.m_read
.timestamp
)) / (duration
*
604 percent
= ((double) (int) (percent
* 10.0)) / 10.0;
605 RTMP_LogStatus("\r%.3f KB / %.2f sec (%.1f%%)",
606 (double) size
/ 1024.0,
607 (double) (rtmp
.m_read
.timestamp
) / 1000.0, percent
);
611 RTMP_LogStatus("\r%.3f KB / %.2f sec", (double) size
/ 1024.0,
612 (double) (rtmp
.m_read
.timestamp
) / 1000.0);
618 RTMP_Log(RTMP_LOGDEBUG
, "zero read!");
622 while (server
->state
== STREAMING_IN_PROGRESS
&& nRead
> -1
623 && RTMP_IsConnected(&rtmp
) && nWritten
>= 0);
626 RTMP_LogPrintf("Closing connection... ");
628 RTMP_LogPrintf("done!\n\n");
640 if (server
->state
== STREAMING_IN_PROGRESS
)
641 server
->state
= STREAMING_ACCEPTING
;
646 RTMP_LogPrintf("%s, %s, %s\n", __FUNCTION__
, status
, filename
);
647 len
= sprintf(buf
, "HTTP/1.0 %s%s\r\n", status
, srvhead
);
648 send(sockfd
, buf
, len
, 0);
653 serverThread(void *arg
)
655 STREAMING_SERVER
*server
= arg
;
656 server
->state
= STREAMING_ACCEPTING
;
658 while (server
->state
== STREAMING_ACCEPTING
)
660 struct sockaddr_in addr
;
661 socklen_t addrlen
= sizeof(struct sockaddr_in
);
663 accept(server
->socket
, (struct sockaddr
*) &addr
, &addrlen
);
667 // Create a new process and transfer the control to that
668 RTMP_Log(RTMP_LOGDEBUG
, "%s: accepted connection from %s\n", __FUNCTION__
,
669 inet_ntoa(addr
.sin_addr
));
670 processTCPrequest(server
, sockfd
);
671 RTMP_Log(RTMP_LOGDEBUG
, "%s: processed request\n", __FUNCTION__
);
675 RTMP_Log(RTMP_LOGERROR
, "%s: accept failed", __FUNCTION__
);
678 server
->state
= STREAMING_STOPPED
;
683 startStreaming(const char *address
, int port
)
685 struct sockaddr_in addr
;
687 STREAMING_SERVER
*server
;
689 sockfd
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
692 RTMP_Log(RTMP_LOGERROR
, "%s, couldn't create socket", __FUNCTION__
);
696 addr
.sin_family
= AF_INET
;
697 addr
.sin_addr
.s_addr
= inet_addr(address
); //htonl(INADDR_ANY);
698 addr
.sin_port
= htons(port
);
700 if (bind(sockfd
, (struct sockaddr
*) &addr
, sizeof(struct sockaddr_in
)) ==
703 RTMP_Log(RTMP_LOGERROR
, "%s, TCP bind failed for port number: %d", __FUNCTION__
,
708 if (listen(sockfd
, 10) == -1)
710 RTMP_Log(RTMP_LOGERROR
, "%s, listen failed", __FUNCTION__
);
715 server
= (STREAMING_SERVER
*) calloc(1, sizeof(STREAMING_SERVER
));
716 server
->socket
= sockfd
;
718 ThreadCreate(serverThread
, server
);
724 stopStreaming(STREAMING_SERVER
* server
)
728 if (server
->state
!= STREAMING_STOPPED
)
730 if (server
->state
== STREAMING_IN_PROGRESS
)
732 server
->state
= STREAMING_STOPPING
;
734 // wait for streaming threads to exit
735 while (server
->state
!= STREAMING_STOPPED
)
739 if (closesocket(server
->socket
))
740 RTMP_Log(RTMP_LOGERROR
, "%s: Failed to close listening socket, error %d",
743 server
->state
= STREAMING_STOPPED
;
749 sigIntHandler(int sig
)
752 RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig
);
754 stopStreaming(httpServer
);
755 signal(SIGINT
, SIG_DFL
);
758 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
759 int hex2bin(char *str
, char **hex
)
762 int i
, l
= strlen(str
);
773 *ptr
++ = (HEX2BIN(str
[i
]) << 4) | HEX2BIN(str
[i
+1]);
777 // this will parse RTMP related options as needed
778 // excludes the following options: h, d, g
780 // Return values: true (option parsing ok)
781 // false (option not parsed/invalid)
783 ParseOption(char opt
, char *arg
, RTMP_REQUEST
* req
)
790 int res
= hex2bin(arg
, &req
->swfHash
.av_val
);
791 if (!res
|| res
!= RTMP_SWF_HASHLEN
)
793 req
->swfHash
.av_val
= NULL
;
794 RTMP_Log(RTMP_LOGWARNING
,
795 "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN
);
797 req
->swfHash
.av_len
= RTMP_SWF_HASHLEN
;
802 int size
= atoi(arg
);
805 RTMP_Log(RTMP_LOGERROR
, "SWF Size must be at least 1, ignoring\n");
815 STR2AVAL(req
->swfUrl
, arg
);
824 RTMP_Log(RTMP_LOGERROR
, "SWF Age must be non-negative, ignoring\n");
835 int32_t bt
= atol(arg
);
838 RTMP_Log(RTMP_LOGERROR
,
839 "Buffer time must be greater than zero, ignoring the specified value %d!",
844 req
->bufferTime
= bt
;
849 req
->bLiveStream
= TRUE
; // no seeking or resuming possible!
852 STR2AVAL(req
->subscribepath
, arg
);
855 STR2AVAL(req
->hostname
, arg
);
858 req
->rtmpport
= atoi(arg
);
862 int protocol
= atoi(arg
);
863 if (protocol
< RTMP_PROTOCOL_RTMP
|| protocol
> RTMP_PROTOCOL_RTMPTS
)
865 RTMP_Log(RTMP_LOGERROR
, "Unknown protocol specified: %d, using default",
871 req
->protocol
= protocol
;
876 STR2AVAL(req
->playpath
, arg
);
882 AVal parsedHost
, parsedPlaypath
, parsedApp
;
883 unsigned int parsedPort
= 0;
884 int parsedProtocol
= RTMP_PROTOCOL_UNDEFINED
;
887 (req
->rtmpurl
, &parsedProtocol
, &parsedHost
, &parsedPort
,
888 &parsedPlaypath
, &parsedApp
))
890 RTMP_Log(RTMP_LOGWARNING
, "Couldn't parse the specified url (%s)!", arg
);
894 if (!req
->hostname
.av_len
)
895 req
->hostname
= parsedHost
;
896 if (req
->rtmpport
== -1)
897 req
->rtmpport
= parsedPort
;
898 if (req
->playpath
.av_len
== 0 && parsedPlaypath
.av_len
)
900 req
->playpath
= parsedPlaypath
;
902 if (req
->protocol
== RTMP_PROTOCOL_UNDEFINED
)
903 req
->protocol
= parsedProtocol
;
904 if (req
->app
.av_len
== 0 && parsedApp
.av_len
)
906 req
->app
= parsedApp
;
912 STR2AVAL(req
->swfUrl
, arg
);
915 STR2AVAL(req
->tcUrl
, arg
);
918 STR2AVAL(req
->pageUrl
, arg
);
921 STR2AVAL(req
->app
, arg
);
924 STR2AVAL(req
->flashVer
, arg
);
927 STR2AVAL(req
->auth
, arg
);
930 parseAMF(&req
->extras
, optarg
, &req
->edepth
);
933 req
->timeout
= atoi(arg
);
936 req
->dStartOffset
= (int)(atof(arg
) * 1000.0);
937 //printf("dStartOffset = %d\n", dStartOffset);
940 req
->dStopOffset
= (int)(atof(arg
) * 1000.0);
941 //printf("dStartOffset = %d\n", dStartOffset);
944 STR2AVAL(req
->token
, arg
);
947 STR2AVAL(req
->sockshost
, arg
);
949 RTMP_debuglevel
= RTMP_LOGCRIT
;
952 RTMP_debuglevel
= RTMP_LOGDEBUG
;
955 RTMP_debuglevel
= RTMP_LOGALL
;
958 STR2AVAL(req
->usherToken
, arg
);
961 RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt
, arg
);
968 main(int argc
, char **argv
)
970 int nStatus
= RD_SUCCESS
;
972 // http streaming server
973 char DEFAULT_HTTP_STREAMING_DEVICE
[] = "0.0.0.0"; // 0.0.0.0 is any device
975 char *httpStreamingDevice
= DEFAULT_HTTP_STREAMING_DEVICE
; // streaming device, default 0.0.0.0
976 int nHttpStreamingPort
= 80; // port
978 RTMP_LogPrintf("HTTP-RTMP Stream Gateway %s\n", RTMPDUMP_VERSION
);
979 RTMP_LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n");
982 memset(&defaultRTMPRequest
, 0, sizeof(RTMP_REQUEST
));
984 defaultRTMPRequest
.rtmpport
= -1;
985 defaultRTMPRequest
.protocol
= RTMP_PROTOCOL_UNDEFINED
;
986 defaultRTMPRequest
.bLiveStream
= FALSE
; // is it a live stream? then we can't seek/resume
988 defaultRTMPRequest
.timeout
= 120; // timeout connection after 120 seconds
989 defaultRTMPRequest
.bufferTime
= 20 * 1000;
991 defaultRTMPRequest
.swfAge
= 30;
994 struct option longopts
[] = {
995 {"help", 0, NULL
, 'h'},
996 {"host", 1, NULL
, 'n'},
997 {"port", 1, NULL
, 'c'},
998 {"socks", 1, NULL
, 'S'},
999 {"protocol", 1, NULL
, 'l'},
1000 {"playpath", 1, NULL
, 'y'},
1001 {"rtmp", 1, NULL
, 'r'},
1002 {"swfUrl", 1, NULL
, 's'},
1003 {"tcUrl", 1, NULL
, 't'},
1004 {"pageUrl", 1, NULL
, 'p'},
1005 {"app", 1, NULL
, 'a'},
1007 {"swfhash", 1, NULL
, 'w'},
1008 {"swfsize", 1, NULL
, 'x'},
1009 {"swfVfy", 1, NULL
, 'W'},
1010 {"swfAge", 1, NULL
, 'X'},
1012 {"auth", 1, NULL
, 'u'},
1013 {"conn", 1, NULL
, 'C'},
1014 {"flashVer", 1, NULL
, 'f'},
1015 {"live", 0, NULL
, 'v'},
1016 //{"flv", 1, NULL, 'o'},
1017 //{"resume", 0, NULL, 'e'},
1018 {"timeout", 1, NULL
, 'm'},
1019 {"buffer", 1, NULL
, 'b'},
1020 //{"skip", 1, NULL, 'k'},
1021 {"device", 1, NULL
, 'D'},
1022 {"sport", 1, NULL
, 'g'},
1023 {"subscribe", 1, NULL
, 'd'},
1024 {"start", 1, NULL
, 'A'},
1025 {"stop", 1, NULL
, 'B'},
1026 {"token", 1, NULL
, 'T'},
1027 {"debug", 0, NULL
, 'z'},
1028 {"quiet", 0, NULL
, 'q'},
1029 {"verbose", 0, NULL
, 'V'},
1030 {"jtv", 1, NULL
, 'j'},
1034 signal(SIGINT
, sigIntHandler
);
1036 signal(SIGPIPE
, SIG_IGN
);
1042 getopt_long(argc
, argv
,
1043 "hvqVzr:s:t:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:", longopts
,
1050 ("\nThis program serves media content streamed from RTMP onto HTTP.\n\n");
1051 RTMP_LogPrintf("--help|-h Prints this help screen.\n");
1053 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
1055 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
1057 ("--port|-c port Overrides the port in the rtmp url\n");
1059 ("--socks|-S host:port Use the specified SOCKS proxy\n");
1061 ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
1063 ("--playpath|-y Overrides the playpath parsed from rtmp url\n");
1064 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
1066 ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
1067 RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
1068 RTMP_LogPrintf("--app|-a app Name of target app in server\n");
1071 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
1073 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
1075 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
1077 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
1080 ("--auth|-u string Authentication string to be appended to the connect string\n");
1082 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
1084 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
1086 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
1088 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
1089 RTMP_DefaultFlashVer
.av_val
);
1091 ("--live|-v Get a live stream, no --resume (seeking) of live streams possible\n");
1093 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specified)\n");
1095 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
1096 defaultRTMPRequest
.timeout
);
1098 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
1100 ("--stop|-B num Stop at num seconds into stream\n");
1102 ("--token|-T key Key for SecureToken response\n");
1104 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
1106 ("--buffer|-b Buffer time in milliseconds (default: %lu)\n\n",
1107 defaultRTMPRequest
.bufferTime
);
1110 ("--device|-D Streaming device ip address (default: %s)\n",
1111 DEFAULT_HTTP_STREAMING_DEVICE
);
1113 ("--sport|-g Streaming port (default: %d)\n\n",
1114 nHttpStreamingPort
);
1116 ("--quiet|-q Suppresses all command output.\n");
1117 RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
1118 RTMP_LogPrintf("--debug|-z Debug level command output.\n");
1120 ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
1121 RTMP_LogPrintf("packet.\n\n");
1124 // streaming server specific options
1126 if (inet_addr(optarg
) == INADDR_NONE
)
1128 RTMP_Log(RTMP_LOGERROR
,
1129 "Invalid binding address (requested address %s), ignoring",
1134 httpStreamingDevice
= optarg
;
1139 int port
= atoi(optarg
);
1140 if (port
< 0 || port
> 65535)
1142 RTMP_Log(RTMP_LOGERROR
,
1143 "Streaming port out of range (requested port %d), ignoring\n",
1148 nHttpStreamingPort
= port
;
1153 //RTMP_LogPrintf("unknown option: %c\n", opt);
1154 if (!ParseOption(opt
, optarg
, &defaultRTMPRequest
))
1161 netstackdump
= fopen("netstackdump", "wb");
1162 netstackdump_read
= fopen("netstackdump_read", "wb");
1166 ThreadCreate(controlServerThread
, 0);
1168 // start http streaming
1170 startStreaming(httpStreamingDevice
, nHttpStreamingPort
)) == 0)
1172 RTMP_Log(RTMP_LOGERROR
, "Failed to start HTTP server, exiting!");
1175 RTMP_LogPrintf("Streaming on http://%s:%d\n", httpStreamingDevice
,
1176 nHttpStreamingPort
);
1178 while (httpServer
->state
!= STREAMING_STOPPED
)
1182 RTMP_Log(RTMP_LOGDEBUG
, "Done, exiting...");
1187 if (netstackdump
!= 0)
1188 fclose(netstackdump
);
1189 if (netstackdump_read
!= 0)
1190 fclose(netstackdump_read
);