Fix missing log message parameter
[rtmpdump.git] / rtmpgw.c
blob733e105b28424b5d62e99778941bfad0f76684b7
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)
8 * any later version.
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
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
27 #include <signal.h>
28 #include <getopt.h>
30 #include <assert.h>
32 #include "librtmp/rtmp_sys.h"
33 #include "librtmp/log.h"
35 #include "thread.h"
37 #define RD_SUCCESS 0
38 #define RD_FAILED 1
39 #define RD_INCOMPLETE 2
41 #define PACKET_SIZE 1024*1024
43 #ifdef WIN32
44 #define InitSockets() {\
45 WORD version; \
46 WSADATA wsaData; \
48 version = MAKEWORD(1,1); \
49 WSAStartup(version, &wsaData); }
51 #define CleanupSockets() WSACleanup()
52 #else
53 #define InitSockets()
54 #define CleanupSockets()
55 #endif
57 enum
59 STREAMING_ACCEPTING,
60 STREAMING_IN_PROGRESS,
61 STREAMING_STOPPING,
62 STREAMING_STOPPED
65 typedef struct
67 int socket;
68 int state;
70 } STREAMING_SERVER;
72 STREAMING_SERVER *httpServer = 0; // server structure pointer
74 STREAMING_SERVER *startStreaming(const char *address, int port);
75 void stopStreaming(STREAMING_SERVER * server);
77 typedef struct
79 AVal hostname;
80 int rtmpport;
81 int protocol;
82 int bLiveStream; // is it a live stream? then we can't seek/resume
84 long int timeout; // timeout connection after 120 seconds
85 uint32_t bufferTime;
87 char *rtmpurl;
88 AVal playpath;
89 AVal swfUrl;
90 AVal tcUrl;
91 AVal pageUrl;
92 AVal app;
93 AVal auth;
94 AVal swfHash;
95 AVal flashVer;
96 AVal token;
97 AVal subscribepath;
98 AVal usherToken; //Justin.tv auth token
99 AVal sockshost;
100 AMFObject extras;
101 int edepth;
102 uint32_t swfSize;
103 int swfAge;
104 int swfVfy;
106 uint32_t dStartOffset;
107 uint32_t dStopOffset;
109 #ifdef CRYPTO
110 unsigned char hash[RTMP_SWF_HASHLEN];
111 #endif
112 } RTMP_REQUEST;
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}};
120 int i;
121 char *p;
123 if (arg[1] == ':')
125 p = (char *)arg+2;
126 switch(arg[0])
128 case 'B':
129 prop.p_type = AMF_BOOLEAN;
130 prop.p_vu.p_number = atoi(p);
131 break;
132 case 'S':
133 prop.p_type = AMF_STRING;
134 STR2AVAL(prop.p_vu.p_aval,p);
135 break;
136 case 'N':
137 prop.p_type = AMF_NUMBER;
138 prop.p_vu.p_number = strtod(p, NULL);
139 break;
140 case 'Z':
141 prop.p_type = AMF_NULL;
142 break;
143 case 'O':
144 i = atoi(p);
145 if (i)
147 prop.p_type = AMF_OBJECT;
149 else
151 (*depth)--;
152 return 0;
154 break;
155 default:
156 return -1;
159 else if (arg[2] == ':' && arg[0] == 'N')
161 p = strchr(arg+3, ':');
162 if (!p || !*depth)
163 return -1;
164 prop.p_name.av_val = (char *)arg+3;
165 prop.p_name.av_len = p - (arg+3);
167 p++;
168 switch(arg[1])
170 case 'B':
171 prop.p_type = AMF_BOOLEAN;
172 prop.p_vu.p_number = atoi(p);
173 break;
174 case 'S':
175 prop.p_type = AMF_STRING;
176 STR2AVAL(prop.p_vu.p_aval,p);
177 break;
178 case 'N':
179 prop.p_type = AMF_NUMBER;
180 prop.p_vu.p_number = strtod(p, NULL);
181 break;
182 case 'O':
183 prop.p_type = AMF_OBJECT;
184 break;
185 default:
186 return -1;
189 else
190 return -1;
192 if (*depth)
194 AMFObject *o2;
195 for (i=0; i<*depth; i++)
197 o2 = &obj->o_props[obj->o_num-1].p_vu.p_object;
198 obj = o2;
201 AMF_AddProp(obj, &prop);
202 if (prop.p_type == AMF_OBJECT)
203 (*depth)++;
204 return 0;
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);
214 #ifdef _DEBUG
215 uint32_t debugTS = 0;
217 int pnum = 0;
219 FILE *netstackdump = NULL;
220 FILE *netstackdump_read = NULL;
221 #endif
223 /* inplace http unescape. This is possible .. strlen(unescaped_string) <= strlen(esacped_string) */
224 void
225 http_unescape(char *data)
227 char hex[3];
228 char *stp;
229 int src_x = 0;
230 int dst_x = 0;
232 int length = (int) strlen(data);
233 hex[2] = 0;
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);
245 dst_x += 1;
246 src_x += 3;
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];
255 src_x += 1;
256 dst_x += 1;
258 else
261 // This doesn't need to be unescaped, however we need to copy the string
263 src_x += 1;
264 dst_x += 1;
267 data[dst_x] = '\0';
270 TFTYPE
271 controlServerThread(void *unused)
273 char ich;
274 while (1)
276 ich = getchar();
277 switch (ich)
279 case 'q':
280 RTMP_LogPrintf("Exiting\n");
281 stopStreaming(httpServer);
282 exit(0);
283 break;
284 default:
285 RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich);
288 TFRET();
292 ssize_t readHTTPLine(int sockfd, char *buffer, size_t length)
294 size_t i=0;
296 while(i < length-1) {
297 char c;
298 int n = read(sockfd, &c, 1);
300 if(n == 0)
301 break;
303 buffer[i] = c;
304 i++;
306 if(c == '\n')
307 break;
309 buffer[i]='\0';
310 i++;
312 return i;
315 int isHTTPRequestEOF(char *line, size_t length)
317 if(length < 2)
318 return TRUE;
320 if(line[0]=='\r' && line[1]=='\n')
321 return TRUE;
323 return FALSE;
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
336 int len;
338 size_t nRead = 0;
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;
346 RTMP rtmp = { 0 };
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
350 RTMP_REQUEST req;
351 memcpy(&req, &defaultRTMPRequest, sizeof(RTMP_REQUEST));
353 // timeout for http requests
354 fd_set fds;
355 struct timeval tv;
357 memset(&tv, 0, sizeof(struct timeval));
358 tv.tv_sec = 5;
360 // go through request lines
361 //do {
362 FD_ZERO(&fds);
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");
368 goto quit;
370 else
372 nRead = recv(sockfd, header, 2047, 0);
373 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",
382 srvhead);
383 send(sockfd, buf, len, 0);
384 goto quit;
387 if (strncmp(header, "GET", 3) == 0 && nRead > 4)
389 filename = header + 4;
391 // filter " HTTP/..." from end of request
392 char *p = filename;
393 while (*p != '\0')
395 if (*p == ' ')
397 *p = '\0';
398 break;
400 p++;
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 /?
412 ptr = filename + 1;
414 // parse parameters
415 if (*ptr == '?')
417 ptr++;
418 int len = strlen(ptr);
420 while (len >= 2)
422 char ich = *ptr;
423 ptr++;
424 if (*ptr != '=')
425 goto filenotfound; // long parameters not (yet) supported
427 ptr++;
428 len -= 2;
430 // get position of the next '&'
431 char *temp;
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));
441 arg[nArgLen] = '\0';
443 //RTMP_Log(RTMP_LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg);
444 http_unescape(arg);
446 RTMP_Log(RTMP_LOGDEBUG, "%s: parameter: %c, arg: %s", __FUNCTION__,
447 ich, arg);
449 ptr += nArgLen + 1;
450 len -= nArgLen + 1;
452 if (!ParseOption(ich, arg, &req))
454 status = "400 unknown option";
455 goto filenotfound;
460 else
462 goto filenotfound;
465 else
467 RTMP_LogPrintf("%s: No request header received/unsupported method\n",
468 __FUNCTION__);
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";
477 goto filenotfound;
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";
484 goto filenotfound;;
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");
497 req.rtmpport = 0;
499 if (req.rtmpport == 0)
501 if (req.protocol & RTMP_FEATURE_SSL)
502 req.rtmpport = 443;
503 else if (req.protocol & RTMP_FEATURE_HTTP)
504 req.rtmpport = 80;
505 else
506 req.rtmpport = 1935;
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);
519 if (req.swfVfy)
521 #ifdef CRYPTO
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;
527 #endif
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);
534 // send the packets
535 buffer = (char *) calloc(PACKET_SIZE, 1);
537 // User defined seek offset
538 if (req.dStartOffset > 0)
540 if (req.bLiveStream)
541 RTMP_Log(RTMP_LOGWARNING,
542 "Can't seek in a live stream, ignoring --seek option");
543 else
544 dSeek += req.dStartOffset;
547 if (dSeek != 0)
549 RTMP_LogPrintf("Starting at TS: %d ms\n", dSeek);
552 RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", req.bufferTime);
553 RTMP_Init(&rtmp);
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 */
559 if (req.auth.av_len)
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__);
571 else
573 unsigned long size = 0;
574 double percent = 0;
575 double duration = 0.0;
577 int nWritten = 0;
578 int nRead = 0;
582 nRead = RTMP_Read(&rtmp, buffer, PACKET_SIZE);
584 if (nRead > 0)
586 if ((nWritten = send(sockfd, buffer, nRead, 0)) < 0)
588 RTMP_Log(RTMP_LOGERROR, "%s, sending failed, error: %d", __FUNCTION__,
589 GetSockError());
590 goto cleanup; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING
593 size += nRead;
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);
599 if (duration > 0)
601 percent =
602 ((double) (dSeek + rtmp.m_read.timestamp)) / (duration *
603 1000.0) * 100.0;
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);
609 else
611 RTMP_LogStatus("\r%.3f KB / %.2f sec", (double) size / 1024.0,
612 (double) (rtmp.m_read.timestamp) / 1000.0);
615 #ifdef _DEBUG
616 else
618 RTMP_Log(RTMP_LOGDEBUG, "zero read!");
620 #endif
622 while (server->state == STREAMING_IN_PROGRESS && nRead > -1
623 && RTMP_IsConnected(&rtmp) && nWritten >= 0);
625 cleanup:
626 RTMP_LogPrintf("Closing connection... ");
627 RTMP_Close(&rtmp);
628 RTMP_LogPrintf("done!\n\n");
630 quit:
631 if (buffer)
633 free(buffer);
634 buffer = NULL;
637 if (sockfd)
638 closesocket(sockfd);
640 if (server->state == STREAMING_IN_PROGRESS)
641 server->state = STREAMING_ACCEPTING;
643 return;
645 filenotfound:
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);
649 goto quit;
652 TFTYPE
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);
662 int sockfd =
663 accept(server->socket, (struct sockaddr *) &addr, &addrlen);
665 if (sockfd > 0)
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__);
673 else
675 RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__);
678 server->state = STREAMING_STOPPED;
679 TFRET();
682 STREAMING_SERVER *
683 startStreaming(const char *address, int port)
685 struct sockaddr_in addr;
686 int sockfd;
687 STREAMING_SERVER *server;
689 sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
690 if (sockfd == -1)
692 RTMP_Log(RTMP_LOGERROR, "%s, couldn't create socket", __FUNCTION__);
693 return 0;
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__,
704 port);
705 return 0;
708 if (listen(sockfd, 10) == -1)
710 RTMP_Log(RTMP_LOGERROR, "%s, listen failed", __FUNCTION__);
711 closesocket(sockfd);
712 return 0;
715 server = (STREAMING_SERVER *) calloc(1, sizeof(STREAMING_SERVER));
716 server->socket = sockfd;
718 ThreadCreate(serverThread, server);
720 return server;
723 void
724 stopStreaming(STREAMING_SERVER * server)
726 assert(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)
736 msleep(1);
739 if (closesocket(server->socket))
740 RTMP_Log(RTMP_LOGERROR, "%s: Failed to close listening socket, error %d",
741 GetSockError());
743 server->state = STREAMING_STOPPED;
748 void
749 sigIntHandler(int sig)
751 RTMP_ctrlC = TRUE;
752 RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
753 if (httpServer)
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)
761 char *ptr;
762 int i, l = strlen(str);
764 if (l & 1)
765 return 0;
767 *hex = malloc(l/2);
768 ptr = *hex;
769 if (!ptr)
770 return 0;
772 for (i=0; i<l; i+=2)
773 *ptr++ = (HEX2BIN(str[i]) << 4) | HEX2BIN(str[i+1]);
774 return l/2;
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)
785 switch (opt)
787 #ifdef CRYPTO
788 case 'w':
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;
798 break;
800 case 'x':
802 int size = atoi(arg);
803 if (size <= 0)
805 RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
807 else
809 req->swfSize = size;
811 break;
813 case 'W':
815 STR2AVAL(req->swfUrl, arg);
816 req->swfVfy = 1;
818 break;
819 case 'X':
821 int num = atoi(arg);
822 if (num < 0)
824 RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
826 else
828 req->swfAge = num;
830 break;
832 #endif
833 case 'b':
835 int32_t bt = atol(arg);
836 if (bt < 0)
838 RTMP_Log(RTMP_LOGERROR,
839 "Buffer time must be greater than zero, ignoring the specified value %d!",
840 bt);
842 else
844 req->bufferTime = bt;
846 break;
848 case 'v':
849 req->bLiveStream = TRUE; // no seeking or resuming possible!
850 break;
851 case 'd':
852 STR2AVAL(req->subscribepath, arg);
853 break;
854 case 'n':
855 STR2AVAL(req->hostname, arg);
856 break;
857 case 'c':
858 req->rtmpport = atoi(arg);
859 break;
860 case 'l':
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",
866 protocol);
867 return FALSE;
869 else
871 req->protocol = protocol;
873 break;
875 case 'y':
876 STR2AVAL(req->playpath, arg);
877 break;
878 case 'r':
880 req->rtmpurl = arg;
882 AVal parsedHost, parsedPlaypath, parsedApp;
883 unsigned int parsedPort = 0;
884 int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
886 if (!RTMP_ParseURL
887 (req->rtmpurl, &parsedProtocol, &parsedHost, &parsedPort,
888 &parsedPlaypath, &parsedApp))
890 RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!", arg);
892 else
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;
909 break;
911 case 's':
912 STR2AVAL(req->swfUrl, arg);
913 break;
914 case 't':
915 STR2AVAL(req->tcUrl, arg);
916 break;
917 case 'p':
918 STR2AVAL(req->pageUrl, arg);
919 break;
920 case 'a':
921 STR2AVAL(req->app, arg);
922 break;
923 case 'f':
924 STR2AVAL(req->flashVer, arg);
925 break;
926 case 'u':
927 STR2AVAL(req->auth, arg);
928 break;
929 case 'C':
930 parseAMF(&req->extras, optarg, &req->edepth);
931 break;
932 case 'm':
933 req->timeout = atoi(arg);
934 break;
935 case 'A':
936 req->dStartOffset = (int)(atof(arg) * 1000.0);
937 //printf("dStartOffset = %d\n", dStartOffset);
938 break;
939 case 'B':
940 req->dStopOffset = (int)(atof(arg) * 1000.0);
941 //printf("dStartOffset = %d\n", dStartOffset);
942 break;
943 case 'T':
944 STR2AVAL(req->token, arg);
945 break;
946 case 'S':
947 STR2AVAL(req->sockshost, arg);
948 case 'q':
949 RTMP_debuglevel = RTMP_LOGCRIT;
950 break;
951 case 'V':
952 RTMP_debuglevel = RTMP_LOGDEBUG;
953 break;
954 case 'z':
955 RTMP_debuglevel = RTMP_LOGALL;
956 break;
957 case 'j':
958 STR2AVAL(req->usherToken, arg);
959 break;
960 default:
961 RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt, arg);
962 return FALSE;
964 return TRUE;
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");
981 // init request
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;
993 int opt;
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'},
1006 #ifdef CRYPTO
1007 {"swfhash", 1, NULL, 'w'},
1008 {"swfsize", 1, NULL, 'x'},
1009 {"swfVfy", 1, NULL, 'W'},
1010 {"swfAge", 1, NULL, 'X'},
1011 #endif
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'},
1031 {0, 0, 0, 0}
1034 signal(SIGINT, sigIntHandler);
1035 #ifndef WIN32
1036 signal(SIGPIPE, SIG_IGN);
1037 #endif
1039 InitSockets();
1041 while ((opt =
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,
1044 NULL)) != -1)
1046 switch (opt)
1048 case 'h':
1049 RTMP_LogPrintf
1050 ("\nThis program serves media content streamed from RTMP onto HTTP.\n\n");
1051 RTMP_LogPrintf("--help|-h Prints this help screen.\n");
1052 RTMP_LogPrintf
1053 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
1054 RTMP_LogPrintf
1055 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
1056 RTMP_LogPrintf
1057 ("--port|-c port Overrides the port in the rtmp url\n");
1058 RTMP_LogPrintf
1059 ("--socks|-S host:port Use the specified SOCKS proxy\n");
1060 RTMP_LogPrintf
1061 ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
1062 RTMP_LogPrintf
1063 ("--playpath|-y Overrides the playpath parsed from rtmp url\n");
1064 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
1065 RTMP_LogPrintf
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");
1069 #ifdef CRYPTO
1070 RTMP_LogPrintf
1071 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
1072 RTMP_LogPrintf
1073 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
1074 RTMP_LogPrintf
1075 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
1076 RTMP_LogPrintf
1077 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
1078 #endif
1079 RTMP_LogPrintf
1080 ("--auth|-u string Authentication string to be appended to the connect string\n");
1081 RTMP_LogPrintf
1082 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
1083 RTMP_LogPrintf
1084 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
1085 RTMP_LogPrintf
1086 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
1087 RTMP_LogPrintf
1088 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
1089 RTMP_DefaultFlashVer.av_val);
1090 RTMP_LogPrintf
1091 ("--live|-v Get a live stream, no --resume (seeking) of live streams possible\n");
1092 RTMP_LogPrintf
1093 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specified)\n");
1094 RTMP_LogPrintf
1095 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
1096 defaultRTMPRequest.timeout);
1097 RTMP_LogPrintf
1098 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
1099 RTMP_LogPrintf
1100 ("--stop|-B num Stop at num seconds into stream\n");
1101 RTMP_LogPrintf
1102 ("--token|-T key Key for SecureToken response\n");
1103 RTMP_LogPrintf
1104 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
1105 RTMP_LogPrintf
1106 ("--buffer|-b Buffer time in milliseconds (default: %lu)\n\n",
1107 defaultRTMPRequest.bufferTime);
1109 RTMP_LogPrintf
1110 ("--device|-D Streaming device ip address (default: %s)\n",
1111 DEFAULT_HTTP_STREAMING_DEVICE);
1112 RTMP_LogPrintf
1113 ("--sport|-g Streaming port (default: %d)\n\n",
1114 nHttpStreamingPort);
1115 RTMP_LogPrintf
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");
1119 RTMP_LogPrintf
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");
1122 return RD_SUCCESS;
1123 break;
1124 // streaming server specific options
1125 case 'D':
1126 if (inet_addr(optarg) == INADDR_NONE)
1128 RTMP_Log(RTMP_LOGERROR,
1129 "Invalid binding address (requested address %s), ignoring",
1130 optarg);
1132 else
1134 httpStreamingDevice = optarg;
1136 break;
1137 case 'g':
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",
1144 port);
1146 else
1148 nHttpStreamingPort = port;
1150 break;
1152 default:
1153 //RTMP_LogPrintf("unknown option: %c\n", opt);
1154 if (!ParseOption(opt, optarg, &defaultRTMPRequest))
1155 return RD_FAILED;
1156 break;
1160 #ifdef _DEBUG
1161 netstackdump = fopen("netstackdump", "wb");
1162 netstackdump_read = fopen("netstackdump_read", "wb");
1163 #endif
1165 // start text UI
1166 ThreadCreate(controlServerThread, 0);
1168 // start http streaming
1169 if ((httpServer =
1170 startStreaming(httpStreamingDevice, nHttpStreamingPort)) == 0)
1172 RTMP_Log(RTMP_LOGERROR, "Failed to start HTTP server, exiting!");
1173 return RD_FAILED;
1175 RTMP_LogPrintf("Streaming on http://%s:%d\n", httpStreamingDevice,
1176 nHttpStreamingPort);
1178 while (httpServer->state != STREAMING_STOPPED)
1180 sleep(1);
1182 RTMP_Log(RTMP_LOGDEBUG, "Done, exiting...");
1184 CleanupSockets();
1186 #ifdef _DEBUG
1187 if (netstackdump != 0)
1188 fclose(netstackdump);
1189 if (netstackdump_read != 0)
1190 fclose(netstackdump_read);
1191 #endif
1192 return nStatus;