/* RTMP Proxy Server
* Copyright (C) 2009 Andrej Stepanchuk
* Copyright (C) 2009 Howard Chu
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RTMPDump; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
/* This is a Proxy Server that displays the connection parameters from a
* client and then saves any data streamed to the client.
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <signal.h>
#include "librtmp/getopt.h"
#include <assert.h>
#include "librtmp/rtmp_sys.h"
#include "librtmp/log.h"
#include "thread.h"
#ifdef linux
#include <linux/netfilter_ipv4.h>
#endif
#define RD_SUCCESS 0
#define RD_FAILED 1
#define RD_INCOMPLETE 2
#define PACKET_SIZE 1024*1024
#ifdef WIN32
#define InitSockets() {\
WORD version; \
WSADATA wsaData; \
\
version = MAKEWORD(1,1); \
WSAStartup(version, &wsaData); }
#define CleanupSockets() WSACleanup()
#else
#define InitSockets()
#define CleanupSockets()
#endif
enum
{
STREAMING_ACCEPTING,
STREAMING_IN_PROGRESS,
STREAMING_STOPPING,
STREAMING_STOPPED
};
typedef struct Flist
{
struct Flist *f_next;
FILE *f_file;
AVal f_path;
} Flist;
typedef struct Plist
{
struct Plist *p_next;
RTMPPacket p_pkt;
} Plist;
typedef struct
{
int socket;
int state;
uint32_t stamp;
RTMP rs;
RTMP rc;
Plist *rs_pkt[2]; /* head, tail */
Plist *rc_pkt[2]; /* head, tail */
Flist *f_head, *f_tail;
Flist *f_cur;
} STREAMING_SERVER;
STREAMING_SERVER *rtmpServer = 0; // server structure pointer
STREAMING_SERVER *startStreaming(const char *address, int port);
void stopStreaming(STREAMING_SERVER * server);
#define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
#ifdef _DEBUG
uint32_t debugTS = 0;
int pnum = 0;
FILE *netstackdump = NULL;
FILE *netstackdump_read = NULL;
#endif
#define BUFFERTIME (4*60*60*1000) /* 4 hours */
#define SAVC(x) static const AVal av_##x = AVC(#x)
SAVC(app);
SAVC(connect);
SAVC(flashVer);
SAVC(swfUrl);
SAVC(pageUrl);
SAVC(tcUrl);
SAVC(fpad);
SAVC(capabilities);
SAVC(audioCodecs);
SAVC(videoCodecs);
SAVC(videoFunction);
SAVC(objectEncoding);
SAVC(_result);
SAVC(createStream);
SAVC(play);
SAVC(closeStream);
SAVC(fmsVer);
SAVC(mode);
SAVC(level);
SAVC(code);
SAVC(secureToken);
SAVC(onStatus);
SAVC(close);
static const AVal av_NetStream_Failed = AVC("NetStream.Failed");
static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed");
static const AVal av_NetStream_Play_StreamNotFound =
AVC("NetStream.Play.StreamNotFound");
static const AVal av_NetConnection_Connect_InvalidApp =
AVC("NetConnection.Connect.InvalidApp");
static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start");
static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete");
static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop");
static const char *cst[] = { "client", "server" };
// Returns 0 for OK/Failed/error, 1 for 'Stop or Complete'
int
ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *body)
{
int ret = 0, nRes;
int nBodySize = pack->m_nBodySize;
if (body > pack->m_body)
nBodySize--;
if (body[0] != 0x02) // make sure it is a string method name we start with
{
RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet",
__FUNCTION__);
return 0;
}
AMFObject obj;
nRes = AMF_Decode(&obj, body, nBodySize, FALSE);
if (nRes < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, error decoding invoke packet", __FUNCTION__);
return 0;
}
AMF_Dump(&obj);
AVal method;
AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method);
RTMP_Log(RTMP_LOGDEBUG, "%s, %s invoking <%s>", __FUNCTION__, cst[which], method.av_val);
if (AVMATCH(&method, &av_connect))
{
AMFObject cobj;
AVal pname, pval;
int i;
AMFProp_GetObject(AMF_GetProp(&obj, NULL, 2), &cobj);
RTMP_LogPrintf("Processing connect\n");
for (i=0; i<cobj.o_num; i++)
{
pname = cobj.o_props[i].p_name;
pval.av_val = NULL;
pval.av_len = 0;
if (cobj.o_props[i].p_type == AMF_STRING)
{
pval = cobj.o_props[i].p_vu.p_aval;
RTMP_LogPrintf("%.*s: %.*s\n", pname.av_len, pname.av_val, pval.av_len, pval.av_val);
}
if (AVMATCH(&pname, &av_app))
{
server->rc.Link.app = pval;
pval.av_val = NULL;
}
else if (AVMATCH(&pname, &av_flashVer))
{
server->rc.Link.flashVer = pval;
pval.av_val = NULL;
}
else if (AVMATCH(&pname, &av_swfUrl))
{
#ifdef CRYPTO
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;
}
else if (AVMATCH(&pname, &av_tcUrl))
{
char *r1 = NULL, *r2;
int len;
server->rc.Link.tcUrl = pval;
if ((pval.av_val[0] | 0x40) == 'r' &&
(pval.av_val[1] | 0x40) == 't' &&
(pval.av_val[2] | 0x40) == 'm' &&
(pval.av_val[3] | 0x40) == 'p')
{
if (pval.av_val[4] == ':')
{
server->rc.Link.protocol = RTMP_PROTOCOL_RTMP;
r1 = pval.av_val+7;
}
else if ((pval.av_val[4] | 0x40) == 'e' && pval.av_val[5] == ':')
{
server->rc.Link.protocol = RTMP_PROTOCOL_RTMPE;
r1 = pval.av_val+8;
}
r2 = strchr(r1, '/');
if (r2)
len = r2 - r1;
else
len = pval.av_len - (r1 - pval.av_val);
r2 = (char*)malloc(len+1);
memcpy(r2, r1, len);
r2[len] = '\0';
server->rc.Link.hostname.av_val = r2;
r1 = strrchr(r2, ':');
if (r1)
{
server->rc.Link.hostname.av_len = r1 - r2;
*r1++ = '\0';
server->rc.Link.port = atoi(r1);
}
else
{
server->rc.Link.hostname.av_len = len;
server->rc.Link.port = 1935;
}
}
pval.av_val = NULL;
}
else if (AVMATCH(&pname, &av_pageUrl))
{
server->rc.Link.pageUrl = pval;
pval.av_val = NULL;
}
else if (AVMATCH(&pname, &av_audioCodecs))
{
server->rc.m_fAudioCodecs = cobj.o_props[i].p_vu.p_number;
}
else if (AVMATCH(&pname, &av_videoCodecs))
{
server->rc.m_fVideoCodecs = cobj.o_props[i].p_vu.p_number;
}
else if (AVMATCH(&pname, &av_objectEncoding))
{
server->rc.m_fEncoding = cobj.o_props[i].p_vu.p_number;
server->rc.m_bSendEncoding = TRUE;
}
/* Dup'd a string we didn't recognize? */
if (pval.av_val)
free(pval.av_val);
}
if (obj.o_num > 3)
{
if (AMFProp_GetBoolean(&obj.o_props[3]))
server->rc.Link.lFlags |= RTMP_LF_AUTH;
if (obj.o_num > 4)
{
AMFProp_GetString(&obj.o_props[4], &server->rc.Link.auth);
}
}
if (!RTMP_Connect(&server->rc, pack))
{
/* failed */
return 1;
}
server->rc.m_bSendCounter = FALSE;
}
else if (AVMATCH(&method, &av_play))
{
Flist *fl;
AVal av;
FILE *out;
char *file, *p, *q;
char flvHeader[] = { 'F', 'L', 'V', 0x01,
0x05, // video + audio, we finalize later if the value is different
0x00, 0x00, 0x00, 0x09,
0x00, 0x00, 0x00, 0x00 // first prevTagSize=0
};
int count = 0, flen;
server->rc.m_stream_id = pack->m_nInfoField2;
AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &av);
server->rc.Link.playpath = av;
if (!av.av_val)
goto out;
/* check for duplicates */
for (fl = server->f_head; fl; fl=fl->f_next)
{
if (AVMATCH(&av, &fl->f_path))
count++;
}
/* strip trailing URL parameters */
q = (char*)memchr(av.av_val, '?', av.av_len);
if (q)
{
if (q == av.av_val)
{
av.av_val++;
av.av_len--;
}
else
{
av.av_len = q - av.av_val;
}
}
/* strip leading slash components */
for (p=av.av_val+av.av_len-1; p>=av.av_val; p--)
if (*p == '/')
{
p++;
av.av_len -= p - av.av_val;
av.av_val = p;
break;
}
/* skip leading dot */
if (av.av_val[0] == '.')
{
av.av_val++;
av.av_len--;
}
flen = av.av_len;
/* hope there aren't more than 255 dups */
if (count)
flen += 2;
file = (char*)malloc(flen+1);
memcpy(file, av.av_val, av.av_len);
if (count)
sprintf(file+av.av_len, "%02x", count);
else
file[av.av_len] = '\0';
for (p=file; *p; p++)
if (*p == ':')
*p = '_';
RTMP_LogPrintf("Playpath: %.*s\nSaving as: %s\n",
server->rc.Link.playpath.av_len, server->rc.Link.playpath.av_val,
file);
out = fopen(file, "wb");
free(file);
if (!out)
ret = 1;
else
{
fwrite(flvHeader, 1, sizeof(flvHeader), out);
av = server->rc.Link.playpath;
fl = (Flist*)malloc(sizeof(Flist)+av.av_len+1);
fl->f_file = out;
fl->f_path.av_len = av.av_len;
fl->f_path.av_val = (char *)(fl+1);
memcpy(fl->f_path.av_val, av.av_val, av.av_len);
fl->f_path.av_val[av.av_len] = '\0';
fl->f_next = NULL;
if (server->f_tail)
server->f_tail->f_next = fl;
else
server->f_head = fl;
server->f_tail = fl;
}
}
else if (AVMATCH(&method, &av_onStatus))
{
AMFObject obj2;
AVal code, level;
AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val);
if (AVMATCH(&code, &av_NetStream_Failed)
|| AVMATCH(&code, &av_NetStream_Play_Failed)
|| AVMATCH(&code, &av_NetStream_Play_StreamNotFound)
|| AVMATCH(&code, &av_NetConnection_Connect_InvalidApp))
{
ret = 1;
}
if (AVMATCH(&code, &av_NetStream_Play_Start))
{
/* set up the next stream */
if (server->f_cur)
{
if (server->f_cur->f_next)
server->f_cur = server->f_cur->f_next;
}
else
{
for (server->f_cur = server->f_head; server->f_cur &&
!server->f_cur->f_file; server->f_cur = server->f_cur->f_next) ;
}
server->rc.m_bPlaying = TRUE;
}
// Return 1 if this is a Play.Complete or Play.Stop
if (AVMATCH(&code, &av_NetStream_Play_Complete)
|| AVMATCH(&code, &av_NetStream_Play_Stop))
{
ret = 1;
}
}
else if (AVMATCH(&method, &av_closeStream))
{
ret = 1;
}
else if (AVMATCH(&method, &av_close))
{
RTMP_Close(&server->rc);
ret = 1;
}
out:
AMF_Reset(&obj);
return ret;
}
int
ServePacket(STREAMING_SERVER *server, int which, RTMPPacket *packet)
{
int ret = 0;
RTMP_Log(RTMP_LOGDEBUG, "%s, %s sent packet type %02X, size %u bytes", __FUNCTION__,
cst[which], packet->m_packetType, packet->m_nBodySize);
switch (packet->m_packetType)
{
case RTMP_PACKET_TYPE_CHUNK_SIZE:
// chunk size
// HandleChangeChunkSize(r, packet);
break;
case RTMP_PACKET_TYPE_BYTES_READ_REPORT:
// bytes read report
break;
case RTMP_PACKET_TYPE_CONTROL:
// ctrl
// HandleCtrl(r, packet);
break;
case RTMP_PACKET_TYPE_SERVER_BW:
// server bw
// HandleServerBW(r, packet);
break;
case RTMP_PACKET_TYPE_CLIENT_BW:
// client bw
// HandleClientBW(r, packet);
break;
case RTMP_PACKET_TYPE_AUDIO:
// audio data
RTMP_Log(RTMP_LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet->m_nBodySize);
break;
case RTMP_PACKET_TYPE_VIDEO:
// video data
RTMP_Log(RTMP_LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet->m_nBodySize);
break;
case RTMP_PACKET_TYPE_FLEX_STREAM_SEND:
// flex stream send
break;
case RTMP_PACKET_TYPE_FLEX_SHARED_OBJECT:
// flex shared object
break;
case RTMP_PACKET_TYPE_FLEX_MESSAGE:
// flex message
{
ret = ServeInvoke(server, which, packet, packet->m_body + 1);
break;
}
case RTMP_PACKET_TYPE_INFO:
// metadata (notify)
break;
case RTMP_PACKET_TYPE_SHARED_OBJECT:
/* shared object */
break;
case RTMP_PACKET_TYPE_INVOKE:
// invoke
ret = ServeInvoke(server, which, packet, packet->m_body);
break;
case RTMP_PACKET_TYPE_FLASH_VIDEO:
/* flv */
break;
default:
RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__,
packet->m_packetType);
#ifdef _DEBUG
RTMP_LogHex(RTMP_LOGDEBUG, (uint8_t*)packet->m_body, packet->m_nBodySize);
#endif
}
return ret;
}
int
WriteStream(char **buf, // target pointer, maybe preallocated
unsigned int *plen, // length of buffer if preallocated
uint32_t *nTimeStamp,
RTMPPacket *packet)
{
uint32_t prevTagSize = 0;
int ret = -1, len = *plen;
while (1)
{
char *packetBody = packet->m_body;
unsigned int nPacketLen = packet->m_nBodySize;
// skip video info/command packets
if (packet->m_packetType == RTMP_PACKET_TYPE_VIDEO &&
nPacketLen == 2 && ((*packetBody & 0xf0) == 0x50))
{
ret = 0;
break;
}
if (packet->m_packetType == RTMP_PACKET_TYPE_VIDEO && nPacketLen <= 5)
{
RTMP_Log(RTMP_LOGWARNING, "ignoring too small video packet: size: %d",
nPacketLen);
ret = 0;
break;
}
if (packet->m_packetType == RTMP_PACKET_TYPE_AUDIO && nPacketLen <= 1)
{
RTMP_Log(RTMP_LOGWARNING, "ignoring too small audio packet: size: %d",
nPacketLen);
ret = 0;
break;
}
#ifdef _DEBUG
RTMP_Log(RTMP_LOGDEBUG, "type: %02X, size: %d, TS: %d ms", packet->m_packetType,
nPacketLen, packet->m_nTimeStamp);
if (packet->m_packetType == RTMP_PACKET_TYPE_VIDEO)
RTMP_Log(RTMP_LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0));
#endif
// calculate packet size and reallocate buffer if necessary
unsigned int size = nPacketLen
+
((packet->m_packetType == RTMP_PACKET_TYPE_AUDIO
|| packet->m_packetType == RTMP_PACKET_TYPE_VIDEO
|| packet->m_packetType == RTMP_PACKET_TYPE_INFO) ? 11 : 0)
+ (packet->m_packetType != 0x16 ? 4 : 0);
if (size + 4 > len)
{
/* The extra 4 is for the case of an FLV stream without a last
* prevTagSize (we need extra 4 bytes to append it). */
*buf = (char *) realloc(*buf, size + 4);
if (*buf == 0)
{
RTMP_Log(RTMP_LOGERROR, "Couldn't reallocate memory!");
ret = -1; // fatal error
break;
}
}
char *ptr = *buf, *pend = ptr + size+4;
/* audio (RTMP_PACKET_TYPE_AUDIO), video (RTMP_PACKET_TYPE_VIDEO)
* or metadata (RTMP_PACKET_TYPE_INFO) packets: construct 11 byte
* header then add rtmp packet's data. */
if (packet->m_packetType == RTMP_PACKET_TYPE_AUDIO
|| packet->m_packetType == RTMP_PACKET_TYPE_VIDEO
|| packet->m_packetType == RTMP_PACKET_TYPE_INFO)
{
// set data type
//*dataType |= (((packet->m_packetType == RTMP_PACKET_TYPE_AUDIO)<<2)|(packet->m_packetType == RTMP_PACKET_TYPE_VIDEO));
(*nTimeStamp) = packet->m_nTimeStamp;
prevTagSize = 11 + nPacketLen;
*ptr++ = packet->m_packetType;
ptr = AMF_EncodeInt24(ptr, pend, nPacketLen);
ptr = AMF_EncodeInt24(ptr, pend, *nTimeStamp);
*ptr = (char) (((*nTimeStamp) & 0xFF000000) >> 24);
ptr++;
// stream id
ptr = AMF_EncodeInt24(ptr, pend, 0);
}
memcpy(ptr, packetBody, nPacketLen);
unsigned int len = nPacketLen;
// correct tagSize and obtain timestamp if we have an FLV stream
if (packet->m_packetType == RTMP_PACKET_TYPE_FLASH_VIDEO)
{
unsigned int pos = 0;
while (pos + 11 < nPacketLen)
{
uint32_t dataSize = AMF_DecodeInt24(packetBody + pos + 1); // size without header (11) and without prevTagSize (4)
*nTimeStamp = AMF_DecodeInt24(packetBody + pos + 4);
*nTimeStamp |= (packetBody[pos + 7] << 24);
#if 0
/* set data type */
*dataType |= (((*(packetBody+pos) == RTMP_PACKET_TYPE_AUDIO) << 2)
| (*(packetBody+pos) == RTMP_PACKET_TYPE_VIDEO));
#endif
if (pos + 11 + dataSize + 4 > nPacketLen)
{
if (pos + 11 + dataSize > nPacketLen)
{
RTMP_Log(RTMP_LOGERROR,
"Wrong data size (%u), stream corrupted, aborting!",
dataSize);
ret = -2;
break;
}
RTMP_Log(RTMP_LOGWARNING, "No tagSize found, appending!");
// we have to append a last tagSize!
prevTagSize = dataSize + 11;
AMF_EncodeInt32(ptr + pos + 11 + dataSize, pend, prevTagSize);
size += 4;
len += 4;
}
else
{
prevTagSize =
AMF_DecodeInt32(packetBody + pos + 11 + dataSize);
#ifdef _DEBUG
RTMP_Log(RTMP_LOGDEBUG,
"FLV Packet: type %02X, dataSize: %lu, tagSize: %lu, timeStamp: %lu ms",
(unsigned char) packetBody[pos], dataSize, prevTagSize,
*nTimeStamp);
#endif
if (prevTagSize != (dataSize + 11))
{
#ifdef _DEBUG
RTMP_Log(RTMP_LOGWARNING,
"Tag and data size are not consitent, writing tag size according to dataSize+11: %d",
dataSize + 11);
#endif
prevTagSize = dataSize + 11;
AMF_EncodeInt32(ptr + pos + 11 + dataSize, pend, prevTagSize);
}
}
pos += prevTagSize + 4; //(11+dataSize+4);
}
}
ptr += len;
if (packet->m_packetType != RTMP_PACKET_TYPE_FLASH_VIDEO)
{ // FLV tag packets contain their own prevTagSize
AMF_EncodeInt32(ptr, pend, prevTagSize);
//ptr += 4;
}
ret = size;
break;
}
if (len > *plen)
*plen = len;
return ret; // no more media packets
}
DWORD WINAPI
controlServerThread(void *unused)
{
char ich;
while (1)
{
ich = getchar();
switch (ich)
{
case 'q':
RTMP_LogPrintf("Exiting\n");
stopStreaming(rtmpServer);
free(rtmpServer);
exit(0);
break;
default:
RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich);
}
}
TFRET();
}
DWORD WINAPI doServe(void *arg) // server socket and state (our listening socket)
{
STREAMING_SERVER *server = (STREAMING_SERVER*)arg;
RTMPPacket pc = { 0 }, ps = { 0 };
RTMPChunk rk = { 0 };
char *buf = NULL;
unsigned int buflen = 131072;
int paused = FALSE;
int sockfd = server->socket;
// timeout for http requests
fd_set rfds;
struct timeval tv;
server->state = STREAMING_IN_PROGRESS;
memset(&tv, 0, sizeof(struct timeval));
tv.tv_sec = 5;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
if (select(sockfd + 1, &rfds, NULL, NULL, &tv) <= 0)
{
RTMP_Log(RTMP_LOGERROR, "Request timeout/select failed, ignoring request");
goto quit;
}
else
{
RTMP_Init(&server->rs);
RTMP_Init(&server->rc);
server->rs.m_sb.sb_socket = sockfd;
if (!RTMP_Serve(&server->rs))
{
RTMP_Log(RTMP_LOGERROR, "Handshake failed");
goto cleanup;
}
}
buf = (char*)malloc(buflen);
/* Just process the Connect request */
while (RTMP_IsConnected(&server->rs) && RTMP_ReadPacket(&server->rs, &ps))
{
if (!RTMPPacket_IsReady(&ps))
continue;
ServePacket(server, 0, &ps);
RTMPPacket_Free(&ps);
if (RTMP_IsConnected(&server->rc))
break;
}
pc.m_chunk = &rk;
/* We have our own timeout in select() */
server->rc.Link.timeout = 10;
server->rs.Link.timeout = 10;
while (RTMP_IsConnected(&server->rs) || RTMP_IsConnected(&server->rc))
{
int n;
int sr, cr;
cr = server->rc.m_sb.sb_size;
sr = server->rs.m_sb.sb_size;
if (cr || sr)
{
}
else
{
n = server->rs.m_sb.sb_socket;
if (server->rc.m_sb.sb_socket > n)
n = server->rc.m_sb.sb_socket;
FD_ZERO(&rfds);
if (RTMP_IsConnected(&server->rs))
FD_SET(sockfd, &rfds);
if (RTMP_IsConnected(&server->rc))
FD_SET(server->rc.m_sb.sb_socket, &rfds);
/* give more time to start up if we're not playing yet */
tv.tv_sec = server->f_cur ? 30 : 60;
tv.tv_usec = 0;
if (select(n + 1, &rfds, NULL, NULL, &tv) <= 0)
{
if (server->f_cur && server->rc.m_mediaChannel && !paused)
{
server->rc.m_pauseStamp = server->rc.m_channelTimestamp[server->rc.m_mediaChannel];
if (RTMP_ToggleStream(&server->rc))
{
paused = TRUE;
continue;
}
}
RTMP_Log(RTMP_LOGERROR, "Request timeout/select failed, ignoring request");
goto cleanup;
}
if (server->rs.m_sb.sb_socket > 0 &&
FD_ISSET(server->rs.m_sb.sb_socket, &rfds))
sr = 1;
if (server->rc.m_sb.sb_socket > 0 &&
FD_ISSET(server->rc.m_sb.sb_socket, &rfds))
cr = 1;
}
if (sr)
{
while (RTMP_ReadPacket(&server->rs, &ps))
if (RTMPPacket_IsReady(&ps))
{
/* change chunk size */
if (ps.m_packetType == RTMP_PACKET_TYPE_CHUNK_SIZE)
{
if (ps.m_nBodySize >= 4)
{
server->rs.m_inChunkSize = AMF_DecodeInt32(ps.m_body);
RTMP_Log(RTMP_LOGDEBUG, "%s, client: chunk size change to %d", __FUNCTION__,
server->rs.m_inChunkSize);
server->rc.m_outChunkSize = server->rs.m_inChunkSize;
}
}
/* bytes received */
else if (ps.m_packetType == RTMP_PACKET_TYPE_BYTES_READ_REPORT)
{
if (ps.m_nBodySize >= 4)
{
int count = AMF_DecodeInt32(ps.m_body);
RTMP_Log(RTMP_LOGDEBUG, "%s, client: bytes received = %d", __FUNCTION__,
count);
}
}
/* ctrl */
else if (ps.m_packetType == RTMP_PACKET_TYPE_CONTROL)
{
short nType = AMF_DecodeInt16(ps.m_body);
/* UpdateBufferMS */
if (nType == 0x03)
{
char *ptr = ps.m_body+2;
int id;
int len;
id = AMF_DecodeInt32(ptr);
/* Assume the interesting media is on a non-zero stream */
if (id)
{
len = AMF_DecodeInt32(ptr+4);
#if 1
/* request a big buffer */
if (len < BUFFERTIME)
{
AMF_EncodeInt32(ptr+4, ptr+8, BUFFERTIME);
}
#endif
RTMP_Log(RTMP_LOGDEBUG, "%s, client: BufferTime change in stream %d to %d", __FUNCTION__,
id, len);
}
}
}
else if (ps.m_packetType == RTMP_PACKET_TYPE_FLEX_MESSAGE
|| ps.m_packetType == RTMP_PACKET_TYPE_INVOKE)
{
if (ServePacket(server, 0, &ps) && server->f_cur)
{
fclose(server->f_cur->f_file);
server->f_cur->f_file = NULL;
server->f_cur = NULL;
}
}
RTMP_SendPacket(&server->rc, &ps, FALSE);
RTMPPacket_Free(&ps);
break;
}
}
if (cr)
{
while (RTMP_ReadPacket(&server->rc, &pc))
{
int sendit = 1;
if (RTMPPacket_IsReady(&pc))
{
if (paused)
{
if (pc.m_nTimeStamp <= server->rc.m_mediaStamp)
continue;
paused = 0;
server->rc.m_pausing = 0;
}
/* change chunk size */
if (pc.m_packetType == RTMP_PACKET_TYPE_CHUNK_SIZE)
{
if (pc.m_nBodySize >= 4)
{
server->rc.m_inChunkSize = AMF_DecodeInt32(pc.m_body);
RTMP_Log(RTMP_LOGDEBUG, "%s, server: chunk size change to %d", __FUNCTION__,
server->rc.m_inChunkSize);
server->rs.m_outChunkSize = server->rc.m_inChunkSize;
}
}
else if (pc.m_packetType == RTMP_PACKET_TYPE_CONTROL)
{
short nType = AMF_DecodeInt16(pc.m_body);
/* SWFverification */
if (nType == 0x1a)
#ifdef CRYPTO
if (server->rc.Link.SWFSize)
{
RTMP_SendCtrl(&server->rc, 0x1b, 0, 0);
sendit = 0;
}
#else
/* The session will certainly fail right after this */
RTMP_Log(RTMP_LOGERROR, "%s, server requested SWF verification, need CRYPTO support! ", __FUNCTION__);
#endif
}
else if (server->f_cur && (
pc.m_packetType == RTMP_PACKET_TYPE_AUDIO ||
pc.m_packetType == RTMP_PACKET_TYPE_VIDEO ||
pc.m_packetType == RTMP_PACKET_TYPE_INFO ||
pc.m_packetType == RTMP_PACKET_TYPE_FLASH_VIDEO) &&
RTMP_ClientPacket(&server->rc, &pc))
{
int len = WriteStream(&buf, &buflen, &server->stamp, &pc);
if (len > 0 && fwrite(buf, 1, len, server->f_cur->f_file) != len)
goto cleanup;
}
else if (pc.m_packetType == RTMP_PACKET_TYPE_FLEX_MESSAGE ||
pc.m_packetType == RTMP_PACKET_TYPE_INVOKE)
{
if (ServePacket(server, 1, &pc) && server->f_cur)
{
fclose(server->f_cur->f_file);
server->f_cur->f_file = NULL;
server->f_cur = NULL;
}
}
}
if (sendit && RTMP_IsConnected(&server->rs))
RTMP_SendChunk(&server->rs, &rk);
if (RTMPPacket_IsReady(&pc))
RTMPPacket_Free(&pc);
break;
}
}
if (!RTMP_IsConnected(&server->rs) && RTMP_IsConnected(&server->rc)
&& !server->f_cur)
RTMP_Close(&server->rc);
}
cleanup:
RTMP_LogPrintf("Closing connection... ");
RTMP_Close(&server->rs);
RTMP_Close(&server->rc);
while (server->f_head)
{
Flist *fl = server->f_head;
server->f_head = fl->f_next;
if (fl->f_file)
fclose(fl->f_file);
free(fl);
}
server->f_tail = NULL;
server->f_cur = NULL;
free(buf);
/* Should probably be done by RTMP_Close() ... */
server->rc.Link.hostname.av_val = NULL;
server->rc.Link.tcUrl.av_val = NULL;
server->rc.Link.swfUrl.av_val = NULL;
server->rc.Link.pageUrl.av_val = NULL;
server->rc.Link.app.av_val = NULL;
server->rc.Link.auth.av_val = NULL;
server->rc.Link.flashVer.av_val = NULL;
RTMP_LogPrintf("done!\n\n");
quit:
if (server->state == STREAMING_IN_PROGRESS)
server->state = STREAMING_ACCEPTING;
return 1;
}
DWORD WINAPI
serverThread(void *arg)
{
STREAMING_SERVER *server = (STREAMING_SERVER*)arg;
server->state = STREAMING_ACCEPTING;
while (server->state == STREAMING_ACCEPTING)
{
struct sockaddr_in addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
STREAMING_SERVER *srv2 = (STREAMING_SERVER*)malloc(sizeof(STREAMING_SERVER));
int sockfd =
accept(server->socket, (struct sockaddr *) &addr, &addrlen);
if (sockfd > 0)
{
#ifdef linux
struct sockaddr_in dest;
char destch[16];
socklen_t destlen = sizeof(struct sockaddr_in);
getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, &dest, &destlen);
strcpy(destch, inet_ntoa(dest.sin_addr));
RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s to %s\n", __FUNCTION__,
inet_ntoa(addr.sin_addr), destch);
#else
RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__,
inet_ntoa(addr.sin_addr));
#endif
*srv2 = *server;
srv2->socket = sockfd;
/* Create a new thread and transfer the control to that */
CreateThread(0,0,doServe, srv2,0,0);
RTMP_Log(RTMP_LOGDEBUG, "%s: processed request\n", __FUNCTION__);
}
else
{
RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__);
}
}
server->state = STREAMING_STOPPED;
return 1;
}
STREAMING_SERVER *
startStreaming(const char *address, int port)
{
struct sockaddr_in addr;
int sockfd, tmp;
STREAMING_SERVER *server;
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd == -1)
{
RTMP_Log(RTMP_LOGERROR, "%s, couldn't create socket", __FUNCTION__);
return 0;
}
tmp = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
(char *) &tmp, sizeof(tmp) );
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(address); //htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) ==
-1)
{
RTMP_Log(RTMP_LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__,
port);
return 0;
}
if (listen(sockfd, 10) == -1)
{
RTMP_Log(RTMP_LOGERROR, "%s, listen failed", __FUNCTION__);
closesocket(sockfd);
return 0;
}
server = (STREAMING_SERVER *) calloc(1, sizeof(STREAMING_SERVER));
server->socket = sockfd;
::CreateThread(0,0,serverThread, server,0,0);
return server;
}
void
stopStreaming(STREAMING_SERVER * server)
{
assert(server);
if (server->state != STREAMING_STOPPED)
{
int fd = server->socket;
server->socket = 0;
if (server->state == STREAMING_IN_PROGRESS)
{
server->state = STREAMING_STOPPING;
// wait for streaming threads to exit
while (server->state != STREAMING_STOPPED)
msleep(1);
}
if (fd && closesocket(fd))
RTMP_Log(RTMP_LOGERROR, "%s: Failed to close listening socket, error %d",
__FUNCTION__, GetSockError());
server->state = STREAMING_STOPPED;
}
}
void
sigIntHandler(int sig)
{
RTMP_ctrlC = TRUE;
RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
if (rtmpServer)
stopStreaming(rtmpServer);
signal(SIGINT, SIG_DFL);
}
char RTMPDUMP_VERSION[25]="1";
int
main(int argc, char **argv)
{
int nStatus = RD_SUCCESS;
// rtmp streaming server
char DEFAULT_RTMP_STREAMING_DEVICE[] = "0.0.0.0"; // 0.0.0.0 is any device
char *rtmpStreamingDevice = DEFAULT_RTMP_STREAMING_DEVICE; // streaming device, default 0.0.0.0
int nRtmpStreamingPort = 1935; // port
RTMP_LogPrintf("RTMP Proxy Server %s\n", RTMPDUMP_VERSION);
RTMP_LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n");
RTMP_debuglevel = RTMP_LOGINFO;
if (argc > 1 && !strcmp(argv[1], "-z"))
RTMP_debuglevel = RTMP_LOGALL;
signal(SIGINT, sigIntHandler);
#ifndef WIN32
signal(SIGPIPE, SIG_IGN);
#endif
#ifdef _DEBUG
netstackdump = fopen("netstackdump", "wb");
netstackdump_read = fopen("netstackdump_read", "wb");
#endif
InitSockets();
// start text UI
::CreateThread(0,0,controlServerThread, 0,0,0);
// start http streaming
if ((rtmpServer =
startStreaming(rtmpStreamingDevice, nRtmpStreamingPort)) == 0)
{
RTMP_Log(RTMP_LOGERROR, "Failed to start RTMP server, exiting!");
return RD_FAILED;
}
RTMP_LogPrintf("Streaming on rtmp://%s:%d\n", rtmpStreamingDevice,
nRtmpStreamingPort);
while (rtmpServer->state != STREAMING_STOPPED)
{
sleep(1);
}
RTMP_Log(RTMP_LOGDEBUG, "Done, exiting...");
free(rtmpServer);
CleanupSockets();
#ifdef _DEBUG
if (netstackdump != 0)
fclose(netstackdump);
if (netstackdump_read != 0)
fclose(netstackdump_read);
#endif
return nStatus;
}