flvstreamer/0000755000000000000000000000000011336120110012072 5ustar rootrootflvstreamer/thread.h0000664000000000000000000000226611335073516013542 0ustar rootroot/* Thread compatibility glue * 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #ifndef __THREAD_H__ #define __THREAD_H__ 1 #ifdef WIN32 #include #include #define TFTYPE void #define TFRET() #define THANDLE HANDLE #else #include #define TFTYPE void * #define TFRET() return 0 #define THANDLE pthread_t #endif typedef TFTYPE (thrfunc)(void *arg); THANDLE ThreadCreate(thrfunc *routine, void *args); #endif /* __THREAD_H__ */ flvstreamer/log.h0000664000000000000000000000331111335073516013044 0ustar rootroot/* RTMP Dump * Copyright (C) 2008-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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #ifndef __LOG_H__ #define __LOG_H__ #include #ifdef __cplusplus extern "C" { #endif /* Enable this to get full debugging output */ /* #define _DEBUG */ #ifdef _DEBUG #undef NODEBUG #endif typedef enum { LOGCRIT=0, LOGERROR, LOGWARNING, LOGINFO, LOGDEBUG, LOGDEBUG2, LOGALL } AMF_LogLevel; #define Log AMF_Log #define LogHex AMF_LogHex #define LogHexString AMF_LogHexString #define LogPrintf AMF_LogPrintf #define LogSetOutput AMF_LogSetOutput #define LogStatus AMF_LogStatus #define debuglevel AMF_debuglevel extern AMF_LogLevel debuglevel; void LogSetOutput(FILE *file); void LogPrintf(const char *format, ...); void LogStatus(const char *format, ...); void Log(int level, const char *format, ...); void LogHex(int level, const char *data, unsigned long len); void LogHexString(int level, const char *data, unsigned long len); #ifdef __cplusplus } #endif #endif flvstreamer/rtmp.c0000664000000000000000000017235211336116643013254 0ustar rootroot/* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-2009 Andrej Stepanchuk * Copyright (C) 2009-2010 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include #include #include #include #include "rtmp.h" #include "log.h" #define RTMP_SIG_SIZE 1536 #define RTMP_LARGE_HEADER_SIZE 12 static const int packetSize[] = { 12, 8, 4, 1 }; #define RTMP_PACKET_SIZE_LARGE 0 #define RTMP_PACKET_SIZE_MEDIUM 1 #define RTMP_PACKET_SIZE_SMALL 2 #define RTMP_PACKET_SIZE_MINIMUM 3 bool RTMP_ctrlC; const char RTMPProtocolStrings[][7] = { "RTMP", "RTMPT", "RTMPS", "RTMPE", "RTMPTE", "RTMFP" }; const char RTMPProtocolStringsLower[][7] = { "rtmp", "rtmpt", "rtmps", "rtmpe", "rtmpte", "rtmpfp" }; static bool DumpMetaData(AMFObject * obj); static bool HandShake(RTMP * r, bool FP9HandShake); static bool SocksNegotiate(RTMP * r); static bool SendConnectPacket(RTMP * r, RTMPPacket *cp); static bool SendServerBW(RTMP * r); static bool SendCheckBW(RTMP * r); static bool SendCheckBWResult(RTMP * r, double txn); static bool SendCreateStream(RTMP * r, double dStreamId); static bool SendDeleteStream(RTMP * r, double dStreamId); static bool SendFCSubscribe(RTMP * r, AVal * subscribepath); static bool SendPlay(RTMP * r); static bool SendBytesReceived(RTMP * r); #if 0 /* unused */ static bool SendBGHasStream(RTMP * r, double dId, AVal * playpath); static bool SendSeek(RTMP * r, double dTime); #endif static int HandleInvoke(RTMP * r, const char *body, unsigned int nBodySize); static bool HandleMetadata(RTMP * r, char *body, unsigned int len); static void HandleChangeChunkSize(RTMP * r, const RTMPPacket * packet); static void HandleAudio(RTMP * r, const RTMPPacket * packet); static void HandleVideo(RTMP * r, const RTMPPacket * packet); static void HandleCtrl(RTMP * r, const RTMPPacket * packet); static void HandleServerBW(RTMP * r, const RTMPPacket * packet); static void HandleClientBW(RTMP * r, const RTMPPacket * packet); static int ReadN(RTMP * r, char *buffer, int n); static bool WriteN(RTMP * r, const char *buffer, int n); static void DecodeTEA(AVal *key, AVal *text); uint32_t RTMP_GetTime() { #ifdef _DEBUG return 0; #elif defined(WIN32) return timeGetTime(); #else struct tms t; return times(&t) * 1000 / sysconf(_SC_CLK_TCK); #endif } void RTMPPacket_Reset(RTMPPacket * p) { p->m_headerType = 0; p->m_packetType = 0; p->m_nChannel = 0; p->m_nInfoField1 = 0; p->m_nInfoField2 = 0; p->m_hasAbsTimestamp = false; p->m_nBodySize = 0; p->m_nBytesRead = 0; } bool RTMPPacket_Alloc(RTMPPacket * p, int nSize) { char *ptr = calloc(1, nSize+RTMP_MAX_HEADER_SIZE); if (!ptr) return false; p->m_body = ptr + RTMP_MAX_HEADER_SIZE; p->m_nBytesRead = 0; return true; } void RTMPPacket_Free(RTMPPacket * p) { if (p->m_body) { free(p->m_body-RTMP_MAX_HEADER_SIZE); p->m_body = NULL; } } void RTMPPacket_Dump(RTMPPacket * p) { Log(LOGDEBUG, "RTMP PACKET: packet type: 0x%02x. channel: 0x%02x. info 1: %d info 2: %d. Body size: %lu. body: 0x%02x", p->m_packetType, p->m_nChannel, p->m_nInfoField1, p->m_nInfoField2, p->m_nBodySize, p->m_body ? (unsigned char) p->m_body[0] : 0); } void RTMP_Init(RTMP * r) { int i; for (i = 0; i < RTMP_CHANNELS; i++) { r->m_vecChannelsIn[i] = NULL; r->m_vecChannelsOut[i] = NULL; } RTMP_Close(r); r->m_nBufferMS = 300; r->m_fDuration = 0; r->m_stream_id = -1; r->m_pBufferStart = NULL; r->m_fAudioCodecs = 3191.0; r->m_fVideoCodecs = 252.0; r->m_fEncoding = 0.0; r->m_bTimedout = false; r->m_pausing = 0; r->m_mediaChannel = 0; } double RTMP_GetDuration(RTMP * r) { return r->m_fDuration; } bool RTMP_IsConnected(RTMP * r) { return r->m_socket != 0; } bool RTMP_IsTimedout(RTMP * r) { return r->m_bTimedout; } void RTMP_SetBufferMS(RTMP * r, int size) { r->m_nBufferMS = size; } void RTMP_UpdateBufferMS(RTMP * r) { RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS); } void RTMP_SetupStream(RTMP * r, int protocol, const char *hostname, unsigned int port, const char *sockshost, AVal * playpath, AVal * tcUrl, AVal * swfUrl, AVal * pageUrl, AVal * app, AVal * auth, AVal * swfSHA256Hash, uint32_t swfSize, AVal * flashVer, AVal * subscribepath, double dTime, uint32_t dLength, bool bLiveStream, long int timeout) { assert(protocol < 6); Log(LOGDEBUG, "Protocol : %s", RTMPProtocolStrings[protocol]); Log(LOGDEBUG, "Hostname : %s", hostname); Log(LOGDEBUG, "Port : %d", port); Log(LOGDEBUG, "Playpath : %s", playpath->av_val); if (tcUrl) Log(LOGDEBUG, "tcUrl : %s", tcUrl->av_val); if (swfUrl) Log(LOGDEBUG, "swfUrl : %s", swfUrl->av_val); if (pageUrl) Log(LOGDEBUG, "pageUrl : %s", pageUrl->av_val); if (app) Log(LOGDEBUG, "app : %s", app->av_val); if (auth) Log(LOGDEBUG, "auth : %s", auth->av_val); if (subscribepath) Log(LOGDEBUG, "subscribepath : %s", subscribepath->av_val); if (flashVer) Log(LOGDEBUG, "flashVer : %s", flashVer->av_val); if (dTime > 0) Log(LOGDEBUG, "SeekTime : %.3f sec", (double) dTime / 1000.0); if (dLength > 0) Log(LOGDEBUG, "playLength : %.3f sec", (double) dLength / 1000.0); Log(LOGDEBUG, "live : %s", bLiveStream ? "yes" : "no"); Log(LOGDEBUG, "timeout : %d sec", timeout); if (sockshost) { const char *socksport = strchr(sockshost, ':'); char *hostname = strdup(sockshost); if (socksport) hostname[socksport - sockshost] = '\0'; r->Link.sockshost = hostname; r->Link.socksport = socksport ? atoi(socksport + 1) : 1080; Log(LOGDEBUG, "Connecting via SOCKS proxy: %s:%d", r->Link.sockshost, r->Link.socksport); } else { r->Link.sockshost = NULL; r->Link.socksport = 0; } r->Link.tcUrl = *tcUrl; r->Link.swfUrl = *swfUrl; r->Link.pageUrl = *pageUrl; r->Link.app = *app; r->Link.auth = *auth; r->Link.flashVer = *flashVer; r->Link.subscribepath = *subscribepath; r->Link.seekTime = dTime; r->Link.length = dLength; r->Link.bLiveStream = bLiveStream; r->Link.timeout = timeout; r->Link.protocol = protocol; r->Link.hostname = hostname; r->Link.port = port; r->Link.playpath = *playpath; if (r->Link.port == 0) r->Link.port = 1935; } static bool add_addr_info(struct sockaddr_in *service, const char *hostname, int port) { service->sin_addr.s_addr = inet_addr(hostname); if (service->sin_addr.s_addr == INADDR_NONE) { struct hostent *host = gethostbyname(hostname); if (host == NULL || host->h_addr == NULL) { Log(LOGERROR, "Problem accessing the DNS. (addr: %s)", hostname); return false; } service->sin_addr = *(struct in_addr *) host->h_addr; } service->sin_port = htons(port); return true; } bool RTMP_Connect0(RTMP *r, struct sockaddr *service) { // close any previous connection RTMP_Close(r); r->m_bTimedout = false; r->m_pausing = 0; r->m_fDuration = 0.0; r->m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (r->m_socket != -1) { if (connect (r->m_socket, service, sizeof(struct sockaddr)) < 0) { int err = GetSockError(); Log(LOGERROR, "%s, failed to connect socket. %d (%s)", __FUNCTION__, err, strerror(err)); RTMP_Close(r); return false; } if (r->Link.socksport) { Log(LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__); if (!SocksNegotiate(r)) { Log(LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__); RTMP_Close(r); return false; } } } else { Log(LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__, GetSockError()); return false; } // set timeout SET_RCVTIMEO(tv, r->Link.timeout); if (setsockopt (r->m_socket, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof(tv))) { Log(LOGERROR, "%s, Setting socket timeout to %ds failed!", __FUNCTION__, r->Link.timeout); } int on = 1; setsockopt(r->m_socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); return true; } bool RTMP_Connect1(RTMP *r, RTMPPacket *cp) { Log(LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__); if (!HandShake(r, true)) { Log(LOGERROR, "%s, handshake failed.", __FUNCTION__); RTMP_Close(r); return false; } Log(LOGDEBUG, "%s, handshaked", __FUNCTION__); if (!SendConnectPacket(r, cp)) { Log(LOGERROR, "%s, RTMP connect failed.", __FUNCTION__); RTMP_Close(r); return false; } return true; } bool RTMP_Connect(RTMP *r, RTMPPacket *cp) { struct sockaddr_in service; if (!r->Link.hostname) return false; memset(&service, 0, sizeof(struct sockaddr_in)); service.sin_family = AF_INET; if (r->Link.socksport) { // Connect via SOCKS if (!add_addr_info(&service, r->Link.sockshost, r->Link.socksport)) return false; } else { // Connect directly if (!add_addr_info(&service, r->Link.hostname, r->Link.port)) return false; } if (!RTMP_Connect0(r, (struct sockaddr *)&service)) return false; r->m_bSendCounter = true; return RTMP_Connect1(r, cp); } static bool SocksNegotiate(RTMP * r) { struct sockaddr_in service; memset(&service, 0, sizeof(struct sockaddr_in)); add_addr_info(&service, r->Link.hostname, r->Link.port); unsigned long addr = htonl(service.sin_addr.s_addr); char packet[] = { 4, 1, // SOCKS 4, connect (r->Link.port >> 8) & 0xFF, (r->Link.port) & 0xFF, (char) (addr >> 24) & 0xFF, (char) (addr >> 16) & 0xFF, (char) (addr >> 8) & 0xFF, (char) addr & 0xFF, 0 }; // NULL terminate WriteN(r, packet, sizeof packet); if (ReadN(r, packet, 8) != 8) return false; if (packet[0] == 0 && packet[1] == 90) { return true; } else { Log(LOGERROR, "%s, SOCKS returned error code %d", packet[1]); return false; } } bool RTMP_ConnectStream(RTMP * r, double seekTime, uint32_t dLength) { RTMPPacket packet = { 0 }; if (seekTime >= -2.0) r->Link.seekTime = seekTime; if (dLength >= 0) r->Link.length = dLength; r->m_mediaChannel = 0; while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet)) { if (RTMPPacket_IsReady(&packet)) { if (!packet.m_nBodySize) continue; if ((packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) || (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) || (packet.m_packetType == RTMP_PACKET_TYPE_INFO)) { Log(LOGWARNING, "Received FLV packet before play()! Ignoring."); RTMPPacket_Free(&packet); continue; } RTMP_ClientPacket(r, &packet); RTMPPacket_Free(&packet); } } return r->m_bPlaying; } bool RTMP_ReconnectStream(RTMP * r, int bufferTime, double seekTime, uint32_t dLength) { RTMP_DeleteStream(r); SendCreateStream(r, 2.0); RTMP_SetBufferMS(r, bufferTime); return RTMP_ConnectStream(r, seekTime, dLength); } bool RTMP_ToggleStream(RTMP * r) { bool res; if (!r->m_pausing) { res = RTMP_SendPause(r, true, r->m_pauseStamp); if (!res) return res; r->m_pausing = 1; sleep(1); } res = RTMP_SendPause(r, false, r->m_pauseStamp); r->m_pausing = 3; return res; } void RTMP_DeleteStream(RTMP * r) { if (r->m_stream_id < 0) return; r->m_bPlaying = false; SendDeleteStream(r, r->m_stream_id); } int RTMP_GetNextMediaPacket(RTMP * r, RTMPPacket * packet) { int bHasMediaPacket = 0; while (!bHasMediaPacket && RTMP_IsConnected(r) && RTMP_ReadPacket(r, packet)) { if (!RTMPPacket_IsReady(packet)) { continue; } bHasMediaPacket = RTMP_ClientPacket(r, packet); if (!bHasMediaPacket) { RTMPPacket_Free(packet); } else if (r->m_pausing == 3) { if (packet->m_nTimeStamp <= r->m_mediaStamp) { bHasMediaPacket = 0; #ifdef _DEBUG Log(LOGDEBUG, "Skipped type: %02X, size: %d, TS: %d ms, abs TS: %d, pause: %d ms", packet->m_packetType, packet->m_nBodySize, packet->m_nTimeStamp, packet->m_hasAbsTimestamp, r->m_mediaStamp); #endif continue; } r->m_pausing = 0; } } if (bHasMediaPacket) r->m_bPlaying = true; else if (r->m_bTimedout && !r->m_pausing) r->m_pauseStamp = r->m_channelTimestamp[r->m_mediaChannel]; return bHasMediaPacket; } int RTMP_ClientPacket(RTMP * r, RTMPPacket * packet) { int bHasMediaPacket = 0; switch (packet->m_packetType) { case 0x01: // chunk size HandleChangeChunkSize(r, packet); break; case 0x03: // bytes read report Log(LOGDEBUG, "%s, received: bytes read report", __FUNCTION__); break; case 0x04: // ctrl HandleCtrl(r, packet); break; case 0x05: // server bw HandleServerBW(r, packet); break; case 0x06: // client bw HandleClientBW(r, packet); break; case 0x08: // audio data //Log(LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); HandleAudio(r, packet); bHasMediaPacket = 1; if (!r->m_mediaChannel) r->m_mediaChannel = packet->m_nChannel; if (!r->m_pausing) r->m_mediaStamp = packet->m_nTimeStamp; break; case 0x09: // video data //Log(LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); HandleVideo(r, packet); bHasMediaPacket = 1; if (!r->m_mediaChannel) r->m_mediaChannel = packet->m_nChannel; if (!r->m_pausing) r->m_mediaStamp = packet->m_nTimeStamp; break; case 0x0F: // flex stream send Log(LOGDEBUG, "%s, flex stream send, size %lu bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize); break; case 0x10: // flex shared object Log(LOGDEBUG, "%s, flex shared object, size %lu bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize); break; case 0x11: // flex message { Log(LOGDEBUG, "%s, flex message, size %lu bytes, not fully supported", __FUNCTION__, packet->m_nBodySize); //LogHex(packet.m_body, packet.m_nBodySize); // some DEBUG code /*RTMP_LIB_AMFObject obj; int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1); if(nRes < 0) { Log(LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__); //return; } obj.Dump(); */ if (HandleInvoke(r, packet->m_body + 1, packet->m_nBodySize - 1) == 1) bHasMediaPacket = 2; break; } case 0x12: // metadata (notify) Log(LOGDEBUG, "%s, received: notify %lu bytes", __FUNCTION__, packet->m_nBodySize); if (HandleMetadata(r, packet->m_body, packet->m_nBodySize)) bHasMediaPacket = 1; break; case 0x13: Log(LOGDEBUG, "%s, shared object, not supported, ignoring", __FUNCTION__); break; case 0x14: // invoke Log(LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__, packet->m_nBodySize); //LogHex(packet.m_body, packet.m_nBodySize); if (HandleInvoke(r, packet->m_body, packet->m_nBodySize) == 1) bHasMediaPacket = 2; break; case 0x16: { // go through FLV packets and handle metadata packets unsigned int pos = 0; uint32_t nTimeStamp = packet->m_nTimeStamp; while (pos + 11 < packet->m_nBodySize) { uint32_t dataSize = AMF_DecodeInt24(packet->m_body + pos + 1); // size without header (11) and prevTagSize (4) if (pos + 11 + dataSize + 4 > packet->m_nBodySize) { Log(LOGWARNING, "Stream corrupt?!"); break; } if (packet->m_body[pos] == 0x12) { HandleMetadata(r, packet->m_body + pos + 11, dataSize); } else if (packet->m_body[pos] == 8 || packet->m_body[pos] == 9) { nTimeStamp = AMF_DecodeInt24(packet->m_body + pos + 4); nTimeStamp |= (packet->m_body[pos + 7] << 24); } pos += (11 + dataSize + 4); } if (!r->m_pausing) r->m_mediaStamp = nTimeStamp; // FLV tag(s) //Log(LOGDEBUG, "%s, received: FLV tag(s) %lu bytes", __FUNCTION__, packet.m_nBodySize); bHasMediaPacket = 1; break; } default: Log(LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__, packet->m_packetType); #ifdef _DEBUG LogHex(LOGDEBUG, packet->m_body, packet->m_nBodySize); #endif } return bHasMediaPacket; } #ifdef _DEBUG extern FILE *netstackdump; extern FILE *netstackdump_read; #endif static int ReadN(RTMP * r, char *buffer, int n) { int nOriginalSize = n; char *ptr; r->m_bTimedout = false; #ifdef _DEBUG memset(buffer, 0, n); #endif ptr = buffer; while (n > 0) { int nBytes = 0, nRead; if (r->m_nBufferSize == 0) if (RTMPSockBuf_Fill(&r->m_sb)<1) { if (!r->m_bTimedout) RTMP_Close(r); return 0; } nRead = ((n < r->m_nBufferSize) ? n : r->m_nBufferSize); if (nRead > 0) { memcpy(ptr, r->m_pBufferStart, nRead); r->m_pBufferStart += nRead; r->m_nBufferSize -= nRead; nBytes = nRead; r->m_nBytesIn += nRead; if (r->m_bSendCounter && r->m_nBytesIn > r->m_nBytesInSent + r->m_nClientBW / 2) SendBytesReceived(r); } //Log(LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes); #ifdef _DEBUG fwrite(ptr, 1, nBytes, netstackdump_read); #endif if (nBytes == 0) { Log(LOGDEBUG, "%s, RTMP socket closed by peer", __FUNCTION__); //goto again; RTMP_Close(r); break; } n -= nBytes; ptr += nBytes; } return nOriginalSize - n; } static bool WriteN(RTMP * r, const char *buffer, int n) { const char *ptr = buffer; while (n > 0) { #ifdef _DEBUG fwrite(ptr, 1, n, netstackdump); #endif int nBytes = send(r->m_socket, ptr, n, 0); //Log(LOGDEBUG, "%s: %d\n", __FUNCTION__, nBytes); if (nBytes < 0) { int sockerr = GetSockError(); Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, sockerr, n); if (sockerr == EINTR && !RTMP_ctrlC) continue; RTMP_Close(r); n = 1; break; } if (nBytes == 0) break; n -= nBytes; ptr += nBytes; } return n == 0; } #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(secureToken); SAVC(secureTokenResponse); static bool SendConnectPacket(RTMP *r, RTMPPacket *cp) { RTMPPacket packet; char pbuf[4096], *pend = pbuf+sizeof(pbuf); if (cp) return RTMP_SendPacket(r, cp, true); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_connect); enc = AMF_EncodeNumber(enc, pend, 1.0); *enc++ = AMF_OBJECT; if (r->Link.app.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_app, &r->Link.app); if (!enc) return false; } if (r->Link.flashVer.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_flashVer, &r->Link.flashVer); if (!enc) return false; } if (r->Link.swfUrl.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_swfUrl, &r->Link.swfUrl); if (!enc) return false; } if (r->Link.tcUrl.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_tcUrl, &r->Link.tcUrl); if (!enc) return false; } enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, false); if (!enc) return false; enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0); if (!enc) return false; enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs); if (!enc) return false; enc = AMF_EncodeNamedNumber(enc, pend, &av_videoCodecs, r->m_fVideoCodecs); if (!enc) return false; enc = AMF_EncodeNamedNumber(enc, pend, &av_videoFunction, 1.0); if (!enc) return false; if (r->Link.pageUrl.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_pageUrl, &r->Link.pageUrl); if (!enc) return false; } if (r->m_fEncoding != 0.0 || r->m_bSendEncoding) { enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding); // AMF0, AMF3 not supported yet if (!enc) return false; } if (enc+3 >= pend) return false; *enc++ = 0; *enc++ = 0; // end of object - 0x00 0x00 0x09 *enc++ = AMF_OBJECT_END; // add auth string if (r->Link.auth.av_len) { enc = AMF_EncodeBoolean(enc, pend, r->Link.authflag); if (!enc) return false; enc = AMF_EncodeString(enc, pend, &r->Link.auth); if (!enc) return false; } if (r->Link.extras.o_num) { int i; for (i=0; i < r->Link.extras.o_num; i++) { enc = AMFProp_Encode(&r->Link.extras.o_props[i], enc, pend); if (!enc) return false; } } packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, true); } #if 0 /* unused */ SAVC(bgHasStream); static bool SendBGHasStream(RTMP * r, double dId, AVal * playpath) { RTMPPacket packet; char pbuf[1024], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_bgHasStream); enc = AMF_EncodeNumber(enc, pend, dId); *enc++ = AMF_NULL; enc = AMF_EncodeString(enc, pend, playpath); if (enc == NULL) return false; packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, true); } #endif SAVC(createStream); static bool SendCreateStream(RTMP * r, double dStreamId) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_createStream); enc = AMF_EncodeNumber(enc, pend, dStreamId); *enc++ = AMF_NULL; // NULL packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, true); } SAVC(FCSubscribe); static bool SendFCSubscribe(RTMP * r, AVal * subscribepath) { RTMPPacket packet; char pbuf[512], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; Log(LOGDEBUG, "FCSubscribe: %s", subscribepath->av_val); char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_FCSubscribe); enc = AMF_EncodeNumber(enc, pend, 4.0); *enc++ = AMF_NULL; enc = AMF_EncodeString(enc, pend, subscribepath); if (!enc) return false; packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, true); } SAVC(deleteStream); static bool SendDeleteStream(RTMP * r, double dStreamId) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_deleteStream); enc = AMF_EncodeNumber(enc, pend, 0.0); *enc++ = AMF_NULL; enc = AMF_EncodeNumber(enc, pend, dStreamId); packet.m_nBodySize = enc - packet.m_body; /* no response expected */ return RTMP_SendPacket(r, &packet, false); } SAVC(pause); bool RTMP_SendPause(RTMP * r, bool DoPause, double dTime) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x08; // video channel packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; // invoke packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_pause); enc = AMF_EncodeNumber(enc, pend, 0); *enc++ = AMF_NULL; enc = AMF_EncodeBoolean(enc, pend, DoPause); enc = AMF_EncodeNumber(enc, pend, (double) dTime); packet.m_nBodySize = enc - packet.m_body; Log(LOGDEBUG, "%s, %d, pauseTime=%.2f", __FUNCTION__, DoPause, dTime); return RTMP_SendPacket(r, &packet, true); } #if 0 /* unused */ SAVC(seek); static bool SendSeek(RTMP * r, double dTime) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x08; // video channel packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; // invoke packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_seek); enc = AMF_EncodeNumber(enc, pend, 0); *enc++ = AMF_NULL; enc = AMF_EncodeNumber(enc, pend, dTime); packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, true); } #endif static bool SendServerBW(RTMP * r) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x02; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = 0x05; // Server BW packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; packet.m_nBodySize = 4; AMF_EncodeInt32(packet.m_body, pend, r->m_nServerBW); return RTMP_SendPacket(r, &packet, false); } static bool SendBytesReceived(RTMP * r) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x02; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x03; // bytes in packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; packet.m_nBodySize = 4; AMF_EncodeInt32(packet.m_body, pend, r->m_nBytesIn); // hard coded for now r->m_nBytesInSent = r->m_nBytesIn; //Log(LOGDEBUG, "Send bytes report. 0x%x (%d bytes)", (unsigned int)m_nBytesIn, m_nBytesIn); return RTMP_SendPacket(r, &packet, false); } SAVC(_checkbw); static bool SendCheckBW(RTMP * r) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; /* RTMP_GetTime(); */ packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av__checkbw); enc = AMF_EncodeNumber(enc, pend, 0); *enc++ = AMF_NULL; packet.m_nBodySize = enc - packet.m_body; // triggers _onbwcheck and eventually results in _onbwdone return RTMP_SendPacket(r, &packet, false); } SAVC(_result); static bool SendCheckBWResult(RTMP * r, double txn) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0x16 * r->m_nBWCheckCounter; // temp inc value. till we figure it out. packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av__result); enc = AMF_EncodeNumber(enc, pend, txn); *enc++ = AMF_NULL; enc = AMF_EncodeNumber(enc, pend, (double) r->m_nBWCheckCounter++); packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, false); } SAVC(play); static bool SendPlay(RTMP * r) { RTMPPacket packet; char pbuf[1024], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x08; // we make 8 our stream channel packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField2 = r->m_stream_id; //0x01000000; packet.m_nInfoField1 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_play); enc = AMF_EncodeNumber(enc, pend, 0.0); // stream id?? *enc++ = AMF_NULL; Log(LOGDEBUG, "%s, seekTime=%.2f, dLength=%d, sending play: %s", __FUNCTION__, r->Link.seekTime, r->Link.length, r->Link.playpath.av_val); enc = AMF_EncodeString(enc, pend, &r->Link.playpath); if (!enc) return false; // Optional parameters start and len. // start: -2, -1, 0, positive number // -2: looks for a live stream, then a recorded stream, if not found any open a live stream // -1: plays a live stream // >=0: plays a recorded streams from 'start' milliseconds if (r->Link.bLiveStream) enc = AMF_EncodeNumber(enc, pend, -1000.0); else { if (r->Link.seekTime > 0.0) enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); // resume from here else enc = AMF_EncodeNumber(enc, pend, 0.0); //-2000.0); // recorded as default, -2000.0 is not reliable since that freezes the player if the stream is not found } if (!enc) return false; // len: -1, 0, positive number // -1: plays live or recorded stream to the end (default) // 0: plays a frame 'start' ms away from the beginning // >0: plays a live or recoded stream for 'len' milliseconds //enc += EncodeNumber(enc, -1.0); // len if (r->Link.length) { enc = AMF_EncodeNumber(enc, pend, r->Link.length); // len if (!enc) return false; } packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, true); } static bool SendSecureTokenResponse(RTMP *r, AVal *resp) { RTMPPacket packet; char pbuf[1024], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; /* control channel (invoke) */ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; packet.m_nInfoField2 = 0; packet.m_nInfoField1 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_secureTokenResponse); enc = AMF_EncodeNumber(enc, pend, 0.0); *enc++ = AMF_NULL; enc = AMF_EncodeString(enc, pend, resp); if (!enc) return false; packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, false); } /* from http://jira.red5.org/confluence/display/docs/Ping: Ping is the most mysterious message in RTMP and till now we haven't fully interpreted it yet. In summary, Ping message is used as a special command that are exchanged between client and server. This page aims to document all known Ping messages. Expect the list to grow. The type of Ping packet is 0x4 and contains two mandatory parameters and two optional parameters. The first parameter is the type of Ping and in short integer. The second parameter is the target of the ping. As Ping is always sent in Channel 2 (control channel) and the target object in RTMP header is always 0 which means the Connection object, it's necessary to put an extra parameter to indicate the exact target object the Ping is sent to. The second parameter takes this responsibility. The value has the same meaning as the target object field in RTMP header. (The second value could also be used as other purposes, like RTT Ping/Pong. It is used as the timestamp.) The third and fourth parameters are optional and could be looked upon as the parameter of the Ping packet. Below is an unexhausted list of Ping messages. * type 0: Clear the stream. No third and fourth parameters. The second parameter could be 0. After the connection is established, a Ping 0,0 will be sent from server to client. The message will also be sent to client on the start of Play and in response of a Seek or Pause/Resume request. This Ping tells client to re-calibrate the clock with the timestamp of the next packet server sends. * type 1: Tell the stream to clear the playing buffer. * type 3: Buffer time of the client. The third parameter is the buffer time in millisecond. * type 4: Reset a stream. Used together with type 0 in the case of VOD. Often sent before type 0. * type 6: Ping the client from server. The second parameter is the current time. * type 7: Pong reply from client. The second parameter is the time the server sent with his ping request. * type 26: SWFVerification request * type 27: SWFVerification response */ bool RTMP_SendCtrl(RTMP * r, short nType, unsigned int nObject, unsigned int nTime) { Log(LOGDEBUG, "sending ctrl. type: 0x%04x", (unsigned short) nType); RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x02; // control channel (ping) packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x04; // ctrl packet.m_nInfoField1 = 0; /* RTMP_GetTime(); */ packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; int nSize = (nType == 0x03 ? 10 : 6); // type 3 is the buffer time and requires all 3 parameters. all in all 10 bytes. if (nType == 0x1B) nSize = 44; packet.m_nBodySize = nSize; char *buf = packet.m_body; buf = AMF_EncodeInt16(buf, pend, nType); if (nType == 0x1B) { } else { if (nSize > 2) buf = AMF_EncodeInt32(buf, pend, nObject); if (nSize > 6) buf = AMF_EncodeInt32(buf, pend, nTime); } return RTMP_SendPacket(r, &packet, false); } static void AV_erase(AVal * vals, int *num, int i, bool freeit) { if (freeit) free(vals[i].av_val); (*num)--; for (; i < *num; i++) { vals[i] = vals[i + 1]; } vals[i].av_val = NULL; vals[i].av_len = 0; } static void AV_queue(AVal ** vals, int *num, AVal * av) { char *tmp; if (!(*num & 0x0f)) *vals = realloc(*vals, (*num + 16) * sizeof(AVal)); tmp = malloc(av->av_len + 1); memcpy(tmp, av->av_val, av->av_len); tmp[av->av_len] = '\0'; (*vals)[*num].av_len = av->av_len; (*vals)[(*num)++].av_val = tmp; } static void AV_clear(AVal * vals, int num) { int i; for (i = 0; i < num; i++) free(vals[i].av_val); free(vals); } SAVC(onBWDone); SAVC(onFCSubscribe); SAVC(onFCUnsubscribe); SAVC(_onbwcheck); SAVC(_onbwdone); SAVC(_error); SAVC(close); SAVC(code); SAVC(level); SAVC(onStatus); 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"); // Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' static int HandleInvoke(RTMP * r, const char *body, unsigned int nBodySize) { int ret = 0, nRes; if (body[0] != 0x02) // make sure it is a string method name we start with { Log(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) { Log(LOGERROR, "%s, error decoding invoke packet", __FUNCTION__); return 0; } AMF_Dump(&obj); AVal method; AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method); double txn = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1)); Log(LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.av_val); if (AVMATCH(&method, &av__result)) { AVal methodInvoked = r->m_methodCalls[0]; AV_erase(r->m_methodCalls, &r->m_numCalls, 0, false); Log(LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__, methodInvoked.av_val); if (AVMATCH(&methodInvoked, &av_connect)) { if (r->Link.token.av_len) { AMFObjectProperty p; if (RTMP_FindFirstMatchingProperty(&obj, &av_secureToken, &p)) { DecodeTEA(&r->Link.token, &p.p_vu.p_aval); SendSecureTokenResponse(r, &p.p_vu.p_aval); } } SendServerBW(r); RTMP_SendCtrl(r, 3, 0, 300); SendCreateStream(r, 2.0); /* Send the FCSubscribe if live stream or if subscribepath is set */ if (r->Link.subscribepath.av_len) SendFCSubscribe(r, &r->Link.subscribepath); else if (r->Link.bLiveStream) SendFCSubscribe(r, &r->Link.playpath); } else if (AVMATCH(&methodInvoked, &av_createStream)) { r->m_stream_id = (int) AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); SendPlay(r); RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS); } else if (AVMATCH(&methodInvoked, &av_play)) { r->m_bPlaying = true; } free(methodInvoked.av_val); } else if (AVMATCH(&method, &av_onBWDone)) { SendCheckBW(r); } else if (AVMATCH(&method, &av_onFCSubscribe)) { // SendOnFCSubscribe(); } else if (AVMATCH(&method, &av_onFCUnsubscribe)) { RTMP_Close(r); ret = 1; } else if (AVMATCH(&method, &av__onbwcheck)) { SendCheckBWResult(r, txn); } else if (AVMATCH(&method, &av__onbwdone)) { int i; for (i = 0; i < r->m_numCalls; i++) if (AVMATCH(&r->m_methodCalls[i], &av__checkbw)) { AV_erase(r->m_methodCalls, &r->m_numCalls, i, true); break; } } else if (AVMATCH(&method, &av__error)) { Log(LOGERROR, "rtmp server sent error"); } else if (AVMATCH(&method, &av_close)) { Log(LOGERROR, "rtmp server requested close"); RTMP_Close(r); } 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); Log(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)) { r->m_stream_id = -1; RTMP_Close(r); Log(LOGERROR, "Closing connection: %s", code.av_val); } if (AVMATCH(&code, &av_NetStream_Play_Start)) { int i; r->m_bPlaying = true; for (i = 0; i < r->m_numCalls; i++) { if (AVMATCH(&r->m_methodCalls[i], &av_play)) { AV_erase(r->m_methodCalls, &r->m_numCalls, i, true); break; } } } // Return 1 if this is a Play.Complete or Play.Stop if (AVMATCH(&code, &av_NetStream_Play_Complete) || AVMATCH(&code, &av_NetStream_Play_Stop)) { RTMP_Close(r); ret = 1; } } else { } AMF_Reset(&obj); return ret; } bool RTMP_FindFirstMatchingProperty(AMFObject * obj, const AVal * name, AMFObjectProperty * p) { int n; /* this is a small object search to locate the "duration" property */ for (n = 0; n < obj->o_num; n++) { AMFObjectProperty *prop = AMF_GetProp(obj, NULL, n); if (AVMATCH(&prop->p_name, name)) { *p = *prop; return true; } if (prop->p_type == AMF_OBJECT) { if (RTMP_FindFirstMatchingProperty(&prop->p_vu.p_object, name, p)) return true; } } return false; } static bool DumpMetaData(AMFObject * obj) { AMFObjectProperty *prop; int n; for (n = 0; n < obj->o_num; n++) { prop = AMF_GetProp(obj, NULL, n); if (prop->p_type != AMF_OBJECT) { char str[256] = ""; switch (prop->p_type) { case AMF_NUMBER: snprintf(str, 255, "%.2f", prop->p_vu.p_number); break; case AMF_BOOLEAN: snprintf(str, 255, "%s", prop->p_vu.p_number != 0. ? "TRUE" : "FALSE"); break; case AMF_STRING: snprintf(str, 255, "%.*s", prop->p_vu.p_aval.av_len, prop->p_vu.p_aval.av_val); break; case AMF_DATE: snprintf(str, 255, "timestamp:%.2f", prop->p_vu.p_number); break; default: snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char) prop->p_type); } if (prop->p_name.av_len) { // chomp if (strlen(str) >= 1 && str[strlen(str) - 1] == '\n') str[strlen(str) - 1] = '\0'; LogPrintf(" %-22.*s%s\n", prop->p_name.av_len, prop->p_name.av_val, str); } } else { if (prop->p_name.av_len) LogPrintf("%.*s:\n", prop->p_name.av_len, prop->p_name.av_val); DumpMetaData(&prop->p_vu.p_object); } } return false; } SAVC(onMetaData); SAVC(duration); static bool HandleMetadata(RTMP * r, char *body, unsigned int len) { // allright we get some info here, so parse it and print it // also keep duration or filesize to make a nice progress bar AMFObject obj; AVal metastring; bool ret = false; int nRes = AMF_Decode(&obj, body, len, false); if (nRes < 0) { Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__); return false; } AMF_Dump(&obj); AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &metastring); if (AVMATCH(&metastring, &av_onMetaData)) { AMFObjectProperty prop; // Show metadata LogPrintf("Metadata:\n"); DumpMetaData(&obj); if (RTMP_FindFirstMatchingProperty(&obj, &av_duration, &prop)) { r->m_fDuration = prop.p_vu.p_number; //Log(LOGDEBUG, "Set duration: %.2f", m_fDuration); } ret = true; } AMF_Reset(&obj); return ret; } static void HandleChangeChunkSize(RTMP * r, const RTMPPacket * packet) { if (packet->m_nBodySize >= 4) { r->m_inChunkSize = AMF_DecodeInt32(packet->m_body); Log(LOGDEBUG, "%s, received: chunk size change to %d", __FUNCTION__, r->m_inChunkSize); } } static void HandleAudio(RTMP * r, const RTMPPacket * packet) { } static void HandleVideo(RTMP * r, const RTMPPacket * packet) { } static void HandleCtrl(RTMP * r, const RTMPPacket * packet) { short nType = -1; unsigned int tmp; if (packet->m_body && packet->m_nBodySize >= 2) nType = AMF_DecodeInt16(packet->m_body); Log(LOGDEBUG, "%s, received ctrl. type: %d, len: %d", __FUNCTION__, nType, packet->m_nBodySize); //LogHex(packet.m_body, packet.m_nBodySize); if (packet->m_nBodySize >= 6) { switch (nType) { case 0: tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Stream Begin %d", __FUNCTION__, tmp); break; case 1: tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Stream EOF %d", __FUNCTION__, tmp); if (r->m_pausing == 1) r->m_pausing = 2; break; case 2: tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Stream Dry %d", __FUNCTION__, tmp); break; case 4: tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Stream IsRecorded %d", __FUNCTION__, tmp); break; case 6: // server ping. reply with pong. tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Ping %d", __FUNCTION__, tmp); RTMP_SendCtrl(r, 0x07, tmp, 0); break; case 31: tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Stream BufferEmpty %d", __FUNCTION__, tmp); if (!r->m_pausing) { r->m_pauseStamp = r->m_channelTimestamp[r->m_mediaChannel]; RTMP_SendPause(r, true, r->m_pauseStamp); r->m_pausing = 1; } else if (r->m_pausing == 2) { RTMP_SendPause(r, false, r->m_pauseStamp); r->m_pausing = 3; } break; case 32: tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Stream BufferReady %d", __FUNCTION__, tmp); break; default: tmp = AMF_DecodeInt32(packet->m_body + 2); Log(LOGDEBUG, "%s, Stream xx %d", __FUNCTION__, tmp); break; } } if (nType == 0x1A) { Log(LOGDEBUG, "%s, SWFVerification ping received: ", __FUNCTION__); Log(LOGERROR, "%s: Ignoring SWFVerification request, no CRYPTO support!", __FUNCTION__); } } static void HandleServerBW(RTMP * r, const RTMPPacket * packet) { r->m_nServerBW = AMF_DecodeInt32(packet->m_body); Log(LOGDEBUG, "%s: server BW = %d", __FUNCTION__, r->m_nServerBW); } static void HandleClientBW(RTMP * r, const RTMPPacket * packet) { r->m_nClientBW = AMF_DecodeInt32(packet->m_body); if (packet->m_nBodySize > 4) r->m_nClientBW2 = packet->m_body[4]; else r->m_nClientBW2 = -1; Log(LOGDEBUG, "%s: client BW = %d %d", __FUNCTION__, r->m_nClientBW, r->m_nClientBW2); } static int DecodeInt32LE(const char *data) { unsigned char *c = (unsigned char *)data; unsigned int val; val = (c[3] << 24) | (c[2] << 16) | (c[1] << 8) | c[0]; return val; } static int EncodeInt32LE(char *output, int nVal) { output[0] = nVal; nVal >>= 8; output[1] = nVal; nVal >>= 8; output[2] = nVal; nVal >>= 8; output[3] = nVal; return 4; } bool RTMP_ReadPacket(RTMP * r, RTMPPacket * packet) { char hbuf[RTMP_MAX_HEADER_SIZE] = { 0 }, *header = hbuf; Log(LOGDEBUG2, "%s: fd=%d", __FUNCTION__, r->m_socket); if (ReadN(r, hbuf, 1) == 0) { Log(LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__); return false; } packet->m_headerType = (hbuf[0] & 0xc0) >> 6; packet->m_nChannel = (hbuf[0] & 0x3f); header++; if (packet->m_nChannel == 0) { if (ReadN(r, &hbuf[1], 1) != 1) { Log(LOGERROR, "%s, failed to read RTMP packet header 2nd byte", __FUNCTION__); return false; } packet->m_nChannel = (unsigned) hbuf[1]; packet->m_nChannel += 64; header++; } else if (packet->m_nChannel == 1) { int tmp; if (ReadN(r, &hbuf[1], 2) != 2) { Log(LOGERROR, "%s, failed to read RTMP packet header 3nd byte", __FUNCTION__); return false; } tmp = (((unsigned) hbuf[2]) << 8) + (unsigned) hbuf[1]; packet->m_nChannel = tmp + 64; Log(LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel); header += 2; } int nSize = packetSize[packet->m_headerType], hSize; if (nSize == RTMP_LARGE_HEADER_SIZE) // if we get a full header the timestamp is absolute packet->m_hasAbsTimestamp = true; else if (nSize < RTMP_LARGE_HEADER_SIZE) { // using values from the last message of this channel if (r->m_vecChannelsIn[packet->m_nChannel]) memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel], sizeof(RTMPPacket)); } nSize--; if (nSize > 0 && ReadN(r, header, nSize) != nSize) { Log(LOGERROR, "%s, failed to read RTMP packet header. type: %x", __FUNCTION__, (unsigned int) hbuf[0]); return false; } hSize = nSize+(header-hbuf); if (nSize >= 3) { packet->m_nInfoField1 = AMF_DecodeInt24(header); //Log(LOGDEBUG, "%s, reading RTMP packet chunk on channel %x, headersz %i, timestamp %i, abs timestamp %i", __FUNCTION__, packet.m_nChannel, nSize, packet.m_nInfoField1, packet.m_hasAbsTimestamp); if (nSize >= 6) { packet->m_nBodySize = AMF_DecodeInt24(header + 3); packet->m_nBytesRead = 0; RTMPPacket_Free(packet); if (nSize > 6) { packet->m_packetType = header[6]; if (nSize == 11) packet->m_nInfoField2 = DecodeInt32LE(header + 7); } } if (packet->m_nInfoField1 == 0xffffff) { if (ReadN(r, header+nSize, 4) != 4) { Log(LOGERROR, "%s, failed to read extended timestamp", __FUNCTION__); return false; } packet->m_nInfoField1 = AMF_DecodeInt32(header+nSize); hSize += 4; } } LogHexString(LOGDEBUG2, hbuf, hSize); bool didAlloc = false; if (packet->m_nBodySize > 0 && packet->m_body == NULL) { if (!RTMPPacket_Alloc(packet, packet->m_nBodySize)) { Log(LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__); return false; } didAlloc = true; packet->m_headerType = (hbuf[0] & 0xc0) >> 6; } int nToRead = packet->m_nBodySize - packet->m_nBytesRead; int nChunk = r->m_inChunkSize; if (nToRead < nChunk) nChunk = nToRead; /* Does the caller want the raw chunk? */ if (packet->m_chunk) { packet->m_chunk->c_headerSize = hSize; memcpy(packet->m_chunk->c_header, hbuf, hSize); packet->m_chunk->c_chunk = packet->m_body+packet->m_nBytesRead; packet->m_chunk->c_chunkSize = nChunk; } if (ReadN(r, packet->m_body + packet->m_nBytesRead, nChunk) != nChunk) { Log(LOGERROR, "%s, failed to read RTMP packet body. len: %lu", __FUNCTION__, packet->m_nBodySize); return false; } LogHexString(LOGDEBUG2, packet->m_body+packet->m_nBytesRead, nChunk); packet->m_nBytesRead += nChunk; // keep the packet as ref for other packets on this channel if (!r->m_vecChannelsIn[packet->m_nChannel]) r->m_vecChannelsIn[packet->m_nChannel] = malloc(sizeof(RTMPPacket)); memcpy(r->m_vecChannelsIn[packet->m_nChannel], packet, sizeof(RTMPPacket)); if (RTMPPacket_IsReady(packet)) { packet->m_nTimeStamp = packet->m_nInfoField1; // make packet's timestamp absolute if (!packet->m_hasAbsTimestamp) packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel]; // timestamps seem to be always relative!! r->m_channelTimestamp[packet->m_nChannel] = packet->m_nTimeStamp; // reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel // arrives and requests to re-use some info (small packet header) r->m_vecChannelsIn[packet->m_nChannel]->m_body = NULL; r->m_vecChannelsIn[packet->m_nChannel]->m_nBytesRead = 0; r->m_vecChannelsIn[packet->m_nChannel]->m_hasAbsTimestamp = false; // can only be false if we reuse header } else { packet->m_body = NULL; /* so it won't be erased on free */ } return true; } static bool HandShake(RTMP * r, bool FP9HandShake) { int i; char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf+1; char serversig[RTMP_SIG_SIZE]; clientbuf[0] = 0x03; // not encrypted uint32_t uptime = htonl(RTMP_GetTime()); memcpy(clientsig, &uptime, 4); memset(&clientsig[4], 0, 4); #ifdef _DEBUG for (i = 8; i < RTMP_SIG_SIZE; i++) clientsig[i] = 0xff; #else for (i = 8; i < RTMP_SIG_SIZE; i++) clientsig[i] = (char) (rand() % 256); #endif if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1)) return false; char type; if (ReadN(r, &type, 1) != 1) // 0x03 or 0x06 return false; Log(LOGDEBUG, "%s: Type Answer : %02X", __FUNCTION__, type); if (type != clientbuf[0]) Log(LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d", __FUNCTION__, clientbuf[0], type); if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) return false; // decode server response uint32_t suptime; memcpy(&suptime, serversig, 4); suptime = ntohl(suptime); Log(LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime); Log(LOGDEBUG, "%s: FMS Version : %d.%d.%d.%d", __FUNCTION__, serversig[4], serversig[5], serversig[6], serversig[7]); // 2nd part of handshake if (!WriteN(r, serversig, RTMP_SIG_SIZE)) return false; if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) return false; bool bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0); if (!bMatch) { Log(LOGWARNING, "%s, client signature does not match!", __FUNCTION__); } return true; } static bool SHandShake(RTMP * r) { int i; char serverbuf[RTMP_SIG_SIZE + 1], *serversig = serverbuf+1; char clientsig[RTMP_SIG_SIZE]; uint32_t uptime; if (ReadN(r, serverbuf, 1) != 1) // 0x03 or 0x06 return false; Log(LOGDEBUG, "%s: Type Request : %02X", __FUNCTION__, serverbuf[0]); if (serverbuf[0] != 3) { Log(LOGERROR, "%s: Type unknown: client sent %02X", __FUNCTION__, serverbuf[0]); return false; } uptime = htonl(RTMP_GetTime()); memcpy(serversig, &uptime, 4); memset(&serversig[4], 0, 4); #ifdef _DEBUG for (i = 8; i < RTMP_SIG_SIZE; i++) serversig[i] = 0xff; #else for (i = 8; i < RTMP_SIG_SIZE; i++) serversig[i] = (char) (rand() % 256); #endif if (!WriteN(r, serverbuf, RTMP_SIG_SIZE + 1)) return false; if (ReadN(r, clientsig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) return false; // decode client response memcpy(&uptime, clientsig, 4); uptime = ntohl(uptime); Log(LOGDEBUG, "%s: Client Uptime : %d", __FUNCTION__, uptime); Log(LOGDEBUG, "%s: Player Version: %d.%d.%d.%d", __FUNCTION__, clientsig[4], clientsig[5], clientsig[6], clientsig[7]); // 2nd part of handshake if (!WriteN(r, clientsig, RTMP_SIG_SIZE)) return false; if (ReadN(r, clientsig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) return false; bool bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0); if (!bMatch) { Log(LOGWARNING, "%s, client signature does not match!", __FUNCTION__); } return true; } bool RTMP_SendChunk(RTMP *r, RTMPChunk *chunk) { bool wrote; char hbuf[RTMP_MAX_HEADER_SIZE]; Log(LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_socket, chunk->c_chunkSize); LogHexString(LOGDEBUG2, chunk->c_header, chunk->c_headerSize); if (chunk->c_chunkSize) { char *ptr = chunk->c_chunk - chunk->c_headerSize; LogHexString(LOGDEBUG2, chunk->c_chunk, chunk->c_chunkSize); /* save header bytes we're about to overwrite */ memcpy(hbuf, ptr, chunk->c_headerSize); memcpy(ptr, chunk->c_header, chunk->c_headerSize); wrote = WriteN(r, ptr, chunk->c_headerSize + chunk->c_chunkSize); memcpy(ptr, hbuf, chunk->c_headerSize); } else wrote = WriteN(r, chunk->c_header, chunk->c_headerSize); return wrote; } bool RTMP_SendPacket(RTMP * r, RTMPPacket * packet, bool queue) { const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel]; if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE) { // compress a bit by using the prev packet's attributes if (prevPacket->m_nBodySize == packet->m_nBodySize && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM) packet->m_headerType = RTMP_PACKET_SIZE_SMALL; if (prevPacket->m_nInfoField2 == packet->m_nInfoField2 && packet->m_headerType == RTMP_PACKET_SIZE_SMALL) packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM; } if (packet->m_headerType > 3) // sanity { Log(LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.", (unsigned char) packet->m_headerType); return false; } int nSize = packetSize[packet->m_headerType]; int hSize = nSize, cSize = 0; char *header, *hptr, *hend, hbuf[RTMP_MAX_HEADER_SIZE], c; if (packet->m_body) { header = packet->m_body - nSize; hend = packet->m_body; } else { header = hbuf+6; hend = hbuf+sizeof(hbuf); } if (packet->m_nChannel > 319) cSize = 2; else if (packet->m_nChannel > 63) cSize = 1; if (cSize) { header -= cSize; hSize += cSize; } if (nSize > 1 && packet->m_nInfoField1 >= 0xffffff) { header -= 4; hSize += 4; } hptr = header; c = packet->m_headerType << 6; switch(cSize) { case 0: c |= packet->m_nChannel; break; case 1: break; case 2: c |= 1; break; } *hptr++ = c; if (cSize) { int tmp = packet->m_nChannel - 64; *hptr++ = tmp & 0xff; if (cSize == 2) *hptr++ = tmp >> 8; } if (nSize > 1) { uint32_t t = packet->m_nInfoField1; if (t > 0xffffff) t = 0xffffff; hptr = AMF_EncodeInt24(hptr, hend, t); } if (nSize > 4) { hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize); *hptr++ = packet->m_packetType; } if (nSize > 8) hptr += EncodeInt32LE(hptr, packet->m_nInfoField2); if (nSize > 1 && packet->m_nInfoField1 >= 0xffffff) hptr = AMF_EncodeInt32(hptr, hend, packet->m_nInfoField1); nSize = packet->m_nBodySize; char *buffer = packet->m_body; int nChunkSize = r->m_outChunkSize; Log(LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_socket, nSize); while (nSize+hSize) { int wrote; if (nSize < nChunkSize) nChunkSize = nSize; if (header) { LogHexString(LOGDEBUG2, header, hSize); LogHexString(LOGDEBUG2, buffer, nChunkSize); wrote = WriteN(r, header, nChunkSize + hSize); header = NULL; hSize = 0; } else { LogHexString(LOGDEBUG2, buffer, nChunkSize); wrote = WriteN(r, buffer, nChunkSize); } if (!wrote) return false; nSize -= nChunkSize; buffer += nChunkSize; if (nSize > 0) { header = buffer - 1; hSize = 1; if (cSize) { header -= cSize; hSize += cSize; } *header = (0xc0 | c); if (cSize) { int tmp = packet->m_nChannel - 64; header[1] = tmp & 0xff; if (cSize == 2) header[2] = tmp >> 8; } } } /* we invoked a remote method */ if (packet->m_packetType == 0x14) { AVal method; AMF_DecodeString(packet->m_body + 1, &method); Log(LOGDEBUG, "Invoking %s", method.av_val); /* keep it in call queue till result arrives */ if (queue) AV_queue(&r->m_methodCalls, &r->m_numCalls, &method); } if (!r->m_vecChannelsOut[packet->m_nChannel]) r->m_vecChannelsOut[packet->m_nChannel] = malloc(sizeof(RTMPPacket)); memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket)); return true; } bool RTMP_Serve(RTMP *r) { return SHandShake(r); } void RTMP_Close(RTMP * r) { int i; if (RTMP_IsConnected(r)) closesocket(r->m_socket); r->m_stream_id = -1; r->m_socket = 0; r->m_inChunkSize = RTMP_DEFAULT_CHUNKSIZE; r->m_outChunkSize = RTMP_DEFAULT_CHUNKSIZE; r->m_nBWCheckCounter = 0; r->m_nBytesIn = 0; r->m_nBytesInSent = 0; r->m_nClientBW = 2500000; r->m_nClientBW2 = 2; r->m_nServerBW = 2500000; for (i = 0; i < RTMP_CHANNELS; i++) { if (r->m_vecChannelsIn[i]) { RTMPPacket_Free(r->m_vecChannelsIn[i]); free(r->m_vecChannelsIn[i]); r->m_vecChannelsIn[i] = NULL; } if (r->m_vecChannelsOut[i]) { free(r->m_vecChannelsOut[i]); r->m_vecChannelsOut[i] = NULL; } } AV_clear(r->m_methodCalls, r->m_numCalls); r->m_methodCalls = NULL; r->m_numCalls = 0; r->m_bPlaying = false; r->m_nBufferSize = 0; } int RTMPSockBuf_Fill(RTMPSockBuf *sb) { int nBytes; if (!sb->sb_size) sb->sb_start = sb->sb_buf; while (1) { nBytes = sizeof(sb->sb_buf) - sb->sb_size - (sb->sb_start - sb->sb_buf); nBytes = recv(sb->sb_socket, sb->sb_start+sb->sb_size, nBytes, 0); if (nBytes != -1) { sb->sb_size += nBytes; } else { int sockerr = GetSockError(); Log(LOGDEBUG, "%s, recv returned %d. GetSockError(): %d (%s)", __FUNCTION__, nBytes, sockerr, strerror(sockerr)); if (sockerr == EINTR && !RTMP_ctrlC) continue; if (sockerr == EWOULDBLOCK || sockerr == EAGAIN) { sb->sb_timedout = true; nBytes = 0; } } break; } return nBytes; } #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf)) static void DecodeTEA(AVal *key, AVal *text) { uint32_t *v, k[4] = {0}, u; uint32_t z, y, sum=0, e, DELTA=0x9e3779b9; int32_t p, q; int i, n; unsigned char *ptr, *out; /* prep key: pack 1st 16 chars into 4 LittleEndian ints */ ptr = (unsigned char *)key->av_val; u = 0; n = 0; v = k; p = key->av_len > 16 ? 16:key->av_len; for (i=0; iav_len+7)/8; out = malloc(n*8); ptr = (unsigned char *)text->av_val; v = (uint32_t *)out; for (i=0; i>5)^(y<<2)) + ((y>>3)^(z<<4))) ^ ((sum^y) + (k[(p&3)^e]^z)); z=v[n-1]; y=v[0]; q = 6+52/n ; sum = q*DELTA ; while (sum != 0) { e = sum>>2 & 3; for (p=n-1; p>0; p--) z = v[p-1], y = v[p] -= MX; z = v[n-1]; y = v[0] -= MX; sum -= DELTA; } text->av_len /= 2; memcpy(text->av_val, out, text->av_len); free(out); } flvstreamer/ChangeLog0000664000000000000000000001052411336120055013660 0ustar rootrootFLVStreamer Copyright (C) 2008-2009 Andrej Stepanchuk Copyright (C) 2009-2010 The Flvstreamer Team Copyright (C) 2009-2010 Howard Chu Distributed under the GPL v2 15 Feb 2010, 2.1c1 - Added patch for more problems with rtmp unpause/resume causing corrupted files. (rtmpdump svn r250). 11 Feb 2010, 2.1c - Reforked from rtmpdump 2.1c (and svn-r247) see Changelog.rtmpdump - Specifically fixes problem with rtmp unpause/resume causing corrupted files. 02 Jan 2010, 2.1a - Reforked from rtmpdump 2.1a (and svn-r166) see Changelog.rtmpdump 22 Nov 2009, 1.9a - Applied fixes from, rtmpdump svn r55: - Fix auth encoding, partial revert of r12 (of rtmpdump svn) - flvstreamer bug#107134, don't retry live streams - flvstreamer bug#107134 stop streaming for onFCUnsubscribe 14 Nov 2009, 1.9 - Reforked from rtmpdump svn-r52 which has had all below fixes and features merged into it. - Also see ChangeLog.rtmpdump 24 Oct 2009, 1.8l - Only use absolute timestamps if non-live stream is being streamed - Added MakefileCYGWIN for Win32 cygwin builds - Remove empty object after connect is sent which causes some problems - added in 1.8f 07 Sep 2009, 1.8k - Fixed bug introduced in v1.8i where writing flv dataType did not happen if ctrl-c was pressed - Handle more signals to reduce risk of unresumable/corrupted partially streamed files - Disable disk write buffering to reduce risk of unresumable/corrupted partially streamed files - flush all log writes by default 18 Aug 2009, v1.8j - Allow chunk stream ids upto 3 bytes long - Fixed >2GB file handling 31 Jul 2009, v1.8i - Added --hashes option to show download progress using '#' characters - Sanitized newlines in informational messages to stderr - Fixed minor time offset reporting bug - Always exit as incomplete if Ctrl-C is pressed 30 Jul 2009, v1.8h - Download progress updates less frequently which puts less CPU load an some systems. - Fix to allow win32 to use binary mode on stdout. 28 Jul 2009, v1.8g - Backed-out upstream libRTMP change added in 1.8e which called SendCheckBW when onBWDone was received - Caused 10060 recv error on win32 28 Jul 2009, v1.8f - Added endian detection for PPC MacOSX in bytes.h - New MakefileOSX which will create universal binaries for Mac (intel and PPC) - Don't treat notify packets as media packets - Ensure that flvstreamer always returns a non-zero exit code for <= 99.9% complete non-live streams that report a duration - Fix endian define typo - Add empty object to end of connect request - The --stop option now also uses the play function to specify the length to stream to play. This allows the option to work better for some streams - Added sanity checking for the --start and --stop options - Times are now reported in seconds for readability 12 Jul 2009, v1.8e - Changed the audioCodecs list to match flash player 10 - Merge some upstream changes from libRTMP XBMC linuxport - Fixed small bug in handshake response 10 Jul 2009, v1.8d - Display flv stream metadata in non-verbose modes - Ported flvstreamer fixes and features to streams.cpp 7 Jul 2009, v1.8c - Added --stop and --start options to specify absolute start and stop offset timestamps for a stream - The --stop option can be used to specify the duration of live streaming - Progress additionally displays timestamp in seconds 6 Jul 2009, v1.8b - Exit codes now reflect success of download. - non-zero exit code for incomplete downloads even if stream duration is unknown. - Slight changes in reporting messages. 20 Jun 2009, v1.8a - Added changes adapted from patch from daftcat75 for mlbviewer - Added --subscribe option. Default is to use playpath if --live is specified - Increased memory allocation fo CreateStream and Play packets to 1024 bytes - Added --debug, --verbose and --quiet options - Made default output level LOGINFO 19 Jun 2009, v1.8 - Added FCSubscribe support for live streams to fix hanging live streams - Bumped default flashVer to LNX 10,0,22,87 - Supress 'no-name' from verbose output 22 May 2009, v1.7 - forked from rtmp dump 1.6 - removed all rtmpe and swf verification support - default is now to stream all flv data to stdout - First release flvstreamer/Makefile0000664000000000000000000000317011336117014013546 0ustar rootrootCC=$(CROSS_COMPILE)gcc LD=$(CROSS_COMPILE)ld DEF=-DFLVSTREAMER_VERSION=\"v2.1c1\" OPT=-O2 CFLAGS=-Wall $(XCFLAGS) $(INC) $(DEF) $(OPT) LDFLAGS=-Wall $(XLDFLAGS) LIBS= THREADLIB=-lpthread SLIBS=$(THREADLIB) $(LIBS) EXT= all: @echo 'use "make posix" for a native Linux/Unix build, or' @echo ' "make mingw" for a MinGW32 build' @echo 'use commandline overrides if you want anything else' progs: flvstreamer streams rtmpsrv rtmpsuck posix linux unix osx: @$(MAKE) $(MAKEFLAGS) progs mingw: @$(MAKE) CROSS_COMPILE=mingw32- LIBS="$(LIBS) -lws2_32 -lwinmm -lgdi32" THREADLIB= EXT=.exe $(MAKEFLAGS) progs cygwin: @$(MAKE) XCFLAGS=-static XLDFLAGS="-static-libgcc -static" EXT=.exe $(MAKEFLAGS) progs cross: @$(MAKE) CROSS_COMPILE=armv7a-angstrom-linux-gnueabi- INC=-I/OE/tmp/staging/armv7a-angstrom-linux-gnueabi/usr/include $(MAKEFLAGS) progs clean: rm -f *.o flvstreamer$(EXT) streams$(EXT) rtmpsrv$(EXT) rtmpsuck$(EXT) flvstreamer: log.o rtmp.o amf.o flvstreamer.o parseurl.o $(CC) $(LDFLAGS) $^ -o $@$(EXT) $(LIBS) rtmpsrv: log.o rtmp.o amf.o rtmpsrv.o thread.o $(CC) $(LDFLAGS) $^ -o $@$(EXT) $(SLIBS) rtmpsuck: log.o rtmp.o amf.o rtmpsuck.o thread.o $(CC) $(LDFLAGS) $^ -o $@$(EXT) $(SLIBS) streams: log.o rtmp.o amf.o streams.o parseurl.o thread.o $(CC) $(LDFLAGS) $^ -o $@$(EXT) $(SLIBS) log.o: log.c log.h Makefile parseurl.o: parseurl.c parseurl.h log.h Makefile streams.o: streams.c rtmp.h log.h Makefile rtmp.o: rtmp.c rtmp.h log.h amf.h Makefile amf.o: amf.c amf.h bytes.h log.h Makefile flvstreamer.o: flvstreamer.c rtmp.h log.h amf.h Makefile rtmpsrv.o: rtmpsrv.c rtmp.h log.h amf.h Makefile thread.o: thread.c thread.h flvstreamer/ChangeLog.rtmpdump0000664000000000000000000001436311334755455015554 0ustar rootrootRTMPDump Copyright 2008-2009 Andrej Stepanchuk; Distributed under the GPL v2 Copyright 2009-2010 Howard Chu Copyright 2009 The Flvstreamer Team http://rtmpdump.mplayerhq.hu/ 9 January 2010, v2.1c - cleanup rtmpsrv output - fix crash in 2.1b hashswf - fix parseurl to url-decode PlayPath - fix parseurl to recognize extensions followed by URL params - fix Makefile, inadvertently dropped 'v' from version string - in rtmpdump try Reconnect if ToggleStream doesn't work on timeouts - in rtmpsuck use chunk-based I/O for better latency - in rtmpsuck support lists of streams - in rtmpsuck use raw client connect packet to workaround unsupported features - support arbitrary AMF data appended to connect requests 4 January 2010, v2.1b - fix url matching in .swfinfo lookup - fix resume parsing in rtmpdump - minor code cleanup (CRYPTO dependencies, logging) - add getStreamLength recognition to rtmpsrv - add close processing in rtmpsuck 1 January 2010, v2.1a - fix socket receive timeouts for WIN32 - add streams description to README 29 December 2009, v2.1 - AMF cleanup: bounds checking for all encoders, moved AMF_EncodeNamed* from rtmp.c - added SecureToken support - added automatic SWF hash calculation - added server-side handshake processing - added rtmpsrv stub server example - added rtmpsuck proxy server - tweaks for logging - renamed more functions to cleanup namespace for library use - tweaks for server operation: objectEncoding, chunksize changes 16 December 2009, v2.0 - rewrote everything else in C, reorganized to make it usable again as a library - fixed more portability bugs - plugged memory leaks 2 December 2009, v1.9a - fix auth string typo - handle FCUnsubscribe message - don't try retry on live streams - SIGPIPE portability fix - remove "not supported" comment for RTMPE 13 November 2009, v1.9 - Handle more signals to reduce risk of unresumable/corrupted partially streamed files - Fixed >2GB file handling - Added --hashes option for a hash progress bar instead of byte counter - Fix to allow win32 to use binary mode on stdout. - Added auto-unpause for buffer-limited streams 1 November 2009, v1.7 - added --subscribe option for subscribing to a stream - added --start / --stop options for specifying endpoints of a stream - added --debug / --quiet / --verbose options for controlling output - added SOCKS4 support (by Monsieur Video) - restructured to support auto-restart of timed-out streams - rewritten byteswapping, works on all platforms - fixed errors in command / result parsing - support functions rewritten in C to avoid g++ compiler bugs on ARM - support for 65600 channels instead of just 64 - fixed signature buffer overruns 17 May 2009, v1.6 - big endian alignment fix, should fix sparc64 and others - moved timestamp handling into RTMP protocol innings, all packets have absolute timestamps now, when seeking the stream will start with timestamp 0 even if seeked to a later position! - fixed a timestamp bug (should fix async audio/video problems) 30 Apr 2009, v1.5a - fixed host name resolution bug (caused unexpected crashes if DNS resolution was not available) - also using the hostname in tcUrl instead of the IP turns out to give much better results 27 Apr 2009, v1.5 - RTMPE support (tested on Adobe 3.0.2,3.0.3,3.5.1, Wowza) - SWFVerification (tested on Adobe 3.0.2,3.0.3,3.5.1) - added AMF3 parsing support (experimental feauture, only some primitives, no references) - added -o - option which allows the stream to be dumped to stdout (debug/error messages go to stderr) - added --live option to enable download of live streams - added support for (Free)BSD and Mac (untested, so might need more fixing, especially for PPC/sparc64) - fixed a bug in url parsing - added a useful application: streams, it will start a streaming server and using a request like http://localhost/?r=rtmp://.... you can restream the content to your player over http 11 Mar 2009, v1.4 - fixed resume bug: when the server switches between audio/video packets and FLV chunk packets (why should a server want to do that? some actually do!) and rtmpdump was invoked with --resume the keyframe check prevented rtmpdump from continuing - fixed endianness - added win32 and arm support (you can cross-compile it onto your Windows box or even PDA) - removed libboost dependency, written a small parser for rtmp urls, but it is more of a heuristic one since the rtmp urls can be ambigous in some circumstances. The best way is to supply all prameters using the override options like --play, --app, etc. - fixed stream ids (from XBMC tree) 19 Jan 2009, v1.3b - fixed segfault on Mac OS/BSDdue to times(0) - Makefile rewritten 16 Jan 2009, v1.3a - fixed a bug introduced in v1.3 (wrong report bytes count), downloads won't hang anymore 10 Jan 2009, v1.3 - fixed audio only streams (rtmpdump now recognizes the stream and writes a correct tag, audio, video, audio+video) - improved resume function to wait till a the seek is executed by the server. The server might send playback data before seeking, so we ignore up to e.g. 50 frames and keep waiting for a keyframe with a timestamp of zero. - nevertheless resuming does not always work since the server sometimes doesn't resend the keyframe, seeking in flash is unreliable 02 Jan 2009, v1.2a - fixed non-standard rtmp urls (including characters + < > ; ) - added small script get_hulu which can download hulu.com streams (US only) (many thanks to Richard Ablewhite for the help with hulu.com) 01 Jan 2009, v1.2: - fixed FLV streams (support for resuming extended) - fixed hanging download at the end - several minor bugfixes - changed parameter behaviour: not supplied parameters are omitted from the connect packet, --auth is introduced (was automatically obtained from url before, but it is possible to have an auth in the tcurl/rtmp url only without an additional encoded string in the connect packet) 28 Dec 2008, v1.1a: - fixed warnings, added -Wall to Makefile 28 Dec 2008, v1.1: - fixed stucking downloads (the buffer time is set to the duration now, so the server doesn't wait till the buffer is emptied - added a --resume option to coninue incomplete downloads - added support for AMF_DATE (experimental, no stream to test so far) - fixed AMF parsing and several small bugs (works on 64bit platforms now) 24 Dec 2008, v1.0: - First release flvstreamer/streams.c0000664000000000000000000007733211335073516013752 0ustar rootroot/* HTTP-RTMP Stream 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include #include #include #include #include #include #include "rtmp.h" #include "parseurl.h" #include "thread.h" #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 { int socket; int state; } STREAMING_SERVER; STREAMING_SERVER *httpServer = 0; // server structure pointer STREAMING_SERVER *startStreaming(const char *address, int port); void stopStreaming(STREAMING_SERVER * server); typedef struct { uint32_t dSeek; // seek position in resume mode, 0 otherwise char *hostname; int rtmpport; int protocol; bool bLiveStream; // is it a live stream? then we can't seek/resume long int timeout; // timeout connection afte 300 seconds uint32_t bufferTime; char *rtmpurl; AVal playpath; AVal swfUrl; AVal tcUrl; AVal pageUrl; AVal app; AVal auth; AVal swfHash; AVal flashVer; AVal token; AVal subscribepath; AMFObject extras; int edepth; uint32_t swfSize; int swfAge; int swfVfy; uint32_t dStartOffset; uint32_t dStopOffset; uint32_t nTimeStamp; } RTMP_REQUEST; #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val) int parseAMF(AMFObject *obj, const char *arg, int *depth) { AMFObjectProperty prop = {{0,0}}; int i; char *p; if (arg[1] == ':') { p = (char *)arg+2; switch(arg[0]) { case 'B': prop.p_type = AMF_BOOLEAN; prop.p_vu.p_number = atoi(p); break; case 'S': prop.p_type = AMF_STRING; STR2AVAL(prop.p_vu.p_aval,p); break; case 'N': prop.p_type = AMF_NUMBER; prop.p_vu.p_number = strtod(p, NULL); break; case 'Z': prop.p_type = AMF_NULL; break; case 'O': i = atoi(p); if (i) { prop.p_type = AMF_OBJECT; } else { (*depth)--; return 0; } break; default: return -1; } } else if (arg[2] == ':' && arg[0] == 'N') { p = strchr(arg+3, ':'); if (!p || !*depth) return -1; prop.p_name.av_val = (char *)arg+3; prop.p_name.av_len = p - (arg+3); p++; switch(arg[1]) { case 'B': prop.p_type = AMF_BOOLEAN; prop.p_vu.p_number = atoi(p); break; case 'S': prop.p_type = AMF_STRING; STR2AVAL(prop.p_vu.p_aval,p); break; case 'N': prop.p_type = AMF_NUMBER; prop.p_vu.p_number = strtod(p, NULL); break; case 'O': prop.p_type = AMF_OBJECT; break; default: return -1; } } else return -1; if (*depth) { AMFObject *o2; for (i=0; i<*depth; i++) { o2 = &obj->o_props[obj->o_num-1].p_vu.p_object; obj = o2; } } AMF_AddProp(obj, &prop); if (prop.p_type == AMF_OBJECT) (*depth)++; return 0; } /* this request is formed from the parameters and used to initialize a new request, * thus it is a default settings list. All settings can be overriden by specifying the * parameters in the GET request. */ RTMP_REQUEST defaultRTMPRequest; bool ParseOption(char opt, char *arg, RTMP_REQUEST * req); char DEFAULT_FLASH_VER[] = "LNX 10,0,22,87"; #ifdef _DEBUG uint32_t debugTS = 0; int pnum = 0; FILE *netstackdump = NULL; FILE *netstackdump_read = NULL; #endif /* inplace http unescape. This is possible .. strlen(unescaped_string) <= strlen(esacped_string) */ void http_unescape(char *data) { char hex[3]; char *stp; int src_x = 0; int dst_x = 0; int length = (int) strlen(data); hex[2] = 0; while (src_x < length) { if (strncmp(data + src_x, "%", 1) == 0 && src_x + 2 < length) { // // Since we encountered a '%' we know this is an escaped character // hex[0] = data[src_x + 1]; hex[1] = data[src_x + 2]; data[dst_x] = (char) strtol(hex, &stp, 16); dst_x += 1; src_x += 3; } else if (src_x != dst_x) { // // This doesn't need to be unescaped. If we didn't unescape anything previously // there is no need to copy the string either // data[dst_x] = data[src_x]; src_x += 1; dst_x += 1; } else { // // This doesn't need to be unescaped, however we need to copy the string // src_x += 1; dst_x += 1; } } data[dst_x] = '\0'; } int WriteHeader(char **buf, // target pointer, maybe preallocated unsigned int len // length of buffer if preallocated ) { 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 }; unsigned int size = sizeof(flvHeader); if (size > len) { *buf = (char *) realloc(*buf, size); if (*buf == 0) { Log(LOGERROR, "Couldn't reallocate memory!"); return -1; // fatal error } } memcpy(*buf, flvHeader, sizeof(flvHeader)); return size; } int WriteStream(RTMP * rtmp, char **buf, // target pointer, maybe preallocated unsigned int len, // length of buffer if preallocated uint32_t * nTimeStamp) { uint32_t prevTagSize = 0; int rtnGetNextMediaPacket = 0, ret = -1; RTMPPacket packet = { 0 }; rtnGetNextMediaPacket = RTMP_GetNextMediaPacket(rtmp, &packet); while (rtnGetNextMediaPacket) { char *packetBody = packet.m_body; unsigned int nPacketLen = packet.m_nBodySize; // skip video info/command packets if (packet.m_packetType == 0x09 && nPacketLen == 2 && ((*packetBody & 0xf0) == 0x50)) { ret = 0; break; } if (packet.m_packetType == 0x09 && nPacketLen <= 5) { Log(LOGWARNING, "ignoring too small video packet: size: %d", nPacketLen); ret = 0; break; } if (packet.m_packetType == 0x08 && nPacketLen <= 1) { Log(LOGWARNING, "ignoring too small audio packet: size: %d", nPacketLen); ret = 0; break; } #ifdef _DEBUG Log(LOGDEBUG, "type: %02X, size: %d, TS: %d ms", packet.m_packetType, nPacketLen, packet.m_nTimeStamp); if (packet.m_packetType == 0x09) Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0)); #endif // calculate packet size and reallocate buffer if necessary unsigned int size = nPacketLen + ((packet.m_packetType == 0x08 || packet.m_packetType == 0x09 || packet.m_packetType == 0x12) ? 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) { Log(LOGERROR, "Couldn't reallocate memory!"); ret = -1; // fatal error break; } } char *ptr = *buf, *pend = ptr + size+4; // audio (0x08), video (0x09) or metadata (0x12) packets : // construct 11 byte header then add rtmp packet's data if (packet.m_packetType == 0x08 || packet.m_packetType == 0x09 || packet.m_packetType == 0x12) { // set data type //*dataType |= (((packet.m_packetType == 0x08)<<2)|(packet.m_packetType == 0x09)); (*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 == 0x16) { 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); // set data type //*dataType |= (((*(packetBody+pos) == 0x08)<<2)|(*(packetBody+pos) == 0x09)); if (pos + 11 + dataSize + 4 > nPacketLen) { if (pos + 11 + dataSize > nPacketLen) { Log(LOGERROR, "Wrong data size (%lu), stream corrupted, aborting!", dataSize); ret = -2; break; } Log(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 Log(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 Log(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 != 0x16) { // FLV tag packets contain their own prevTagSize AMF_EncodeInt32(ptr, pend, prevTagSize); //ptr += 4; } // Return 0 if this was completed nicely with invoke message Play.Stop or Play.Complete if (rtnGetNextMediaPacket == 2) { Log(LOGDEBUG, "Got Play.Complete or Play.Stop from server. Assuming stream is complete"); ret = 0; break; } ret = size; break; } RTMPPacket_Free(&packet); return ret; // no more media packets } TFTYPE controlServerThread(void *unused) { char ich; while (1) { ich = getchar(); switch (ich) { case 'q': LogPrintf("Exiting\n"); stopStreaming(httpServer); exit(0); break; default: LogPrintf("Unknown command \'%c\', ignoring\n", ich); } } TFRET(); } /* ssize_t readHTTPLine(int sockfd, char *buffer, size_t length) { size_t i=0; while(i < length-1) { char c; int n = read(sockfd, &c, 1); if(n == 0) break; buffer[i] = c; i++; if(c == '\n') break; } buffer[i]='\0'; i++; return i; } bool isHTTPRequestEOF(char *line, size_t length) { if(length < 2) return true; if(line[0]=='\r' && line[1]=='\n') return true; return false; } */ void processTCPrequest(STREAMING_SERVER * server, // server socket and state (our listening socket) int sockfd // client connection socket ) { char buf[512] = { 0 }; // answer buffer char header[2048] = { 0 }; // request header char *filename = NULL; // GET request: file name //512 not enuf char *buffer = NULL; // stream buffer char *ptr = NULL; // header pointer size_t nRead = 0; char srvhead[] = "\r\nServer:HTTP-RTMP Stream Server \r\nContent-Type: Video/MPEG \r\n\r\n"; server->state = STREAMING_IN_PROGRESS; RTMP rtmp = { 0 }; uint32_t dSeek = 0; // can be used to start from a later point in the stream // reset RTMP options to defaults specified upon invokation of streams RTMP_REQUEST req; memcpy(&req, &defaultRTMPRequest, sizeof(RTMP_REQUEST)); // timeout for http requests fd_set fds; struct timeval tv; memset(&tv, 0, sizeof(struct timeval)); tv.tv_sec = 5; // go through request lines //do { FD_ZERO(&fds); FD_SET(sockfd, &fds); if (select(sockfd + 1, &fds, NULL, NULL, &tv) <= 0) { Log(LOGERROR, "Request timeout/select failed, ignoring request"); goto quit; } else { nRead = recv(sockfd, header, 2047, 0); header[2047] = '\0'; Log(LOGDEBUG, "%s: header: %s", __FUNCTION__, header); if (strstr(header, "Range: bytes=") != 0) { // TODO check range starts from 0 and asking till the end. LogPrintf("%s, Range request not supported\n", __FUNCTION__); sprintf(buf, "HTTP/1.0 416 Requested Range Not Satisfiable%s", srvhead); send(sockfd, buf, (int) strlen(buf), 0); goto quit; } if (strncmp(header, "GET", 3) == 0 && nRead > 4) { filename = header + 4; // filter " HTTP/..." from end of request char *p = filename; while (*p != '\0') { if (*p == ' ') { *p = '\0'; break; } p++; } } } //} while(!isHTTPRequestEOF(header, nRead)); // if we got a filename from the GET method if (filename != NULL) { Log(LOGDEBUG, "%s: Request header: %s", __FUNCTION__, filename); if (filename[0] == '/') { // if its not empty, is it /? ptr = filename + 1; // parse parameters if (*ptr == '?') { ptr++; int len = strlen(ptr); while (len >= 2) { char ich = *ptr; ptr++; if (*ptr != '=') goto filenotfound; // long parameters not (yet) supported ptr++; len -= 2; // get position of the next '&' char *temp; unsigned int nArgLen = len; if ((temp = strstr(ptr, "&")) != 0) { nArgLen = temp - ptr; } char *arg = (char *) malloc((nArgLen + 1) * sizeof(char)); memcpy(arg, ptr, nArgLen * sizeof(char)); arg[nArgLen] = '\0'; //Log(LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg); http_unescape(arg); Log(LOGDEBUG, "%s: parameter: %c, arg: %s", __FUNCTION__, ich, arg); ptr += nArgLen + 1; len -= nArgLen + 1; ParseOption(ich, arg, &req); } } } else { goto filenotfound; } } else { LogPrintf("%s: No request header received/unsupported method\n", __FUNCTION__); } // do necessary checks right here to make sure the combined request of default values and GET parameters is correct if (req.hostname == 0) { Log(LOGERROR, "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname"); goto filenotfound; } if (req.playpath.av_len == 0) { Log(LOGERROR, "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath"); goto filenotfound;; } if (req.rtmpport == -1) { Log(LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port 1935"); req.rtmpport = 1935; } if (req.protocol == RTMP_PROTOCOL_UNDEFINED) { Log(LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP"); req.protocol = RTMP_PROTOCOL_RTMP; } if (req.flashVer.av_len == 0) { STR2AVAL(req.flashVer, DEFAULT_FLASH_VER); } if (req.tcUrl.av_len == 0 && req.app.av_len != 0) { char str[512] = { 0 }; snprintf(str, 511, "%s://%s/%s", RTMPProtocolStringsLower[req.protocol], req.hostname, req.app.av_val); req.tcUrl.av_len = strlen(str); req.tcUrl.av_val = (char *) malloc(req.tcUrl.av_len + 1); strcpy(req.tcUrl.av_val, str); } if (req.rtmpport == 0) req.rtmpport = 1935; // after validation of the http request send response header sprintf(buf, "HTTP/1.0 200 OK%s", srvhead); send(sockfd, buf, (int) strlen(buf), 0); // send the packets buffer = (char *) calloc(PACKET_SIZE, 1); // User defined seek offset if (req.dStartOffset > 0) { if (req.bLiveStream) Log(LOGWARNING, "Can't seek in a live stream, ignoring --seek option"); else dSeek += req.dStartOffset; } if (dSeek != 0) { LogPrintf("Starting at TS: %d ms\n", req.nTimeStamp); } Log(LOGDEBUG, "Setting buffer time to: %dms", req.bufferTime); RTMP_Init(&rtmp); RTMP_SetBufferMS(&rtmp, req.bufferTime); RTMP_SetupStream(&rtmp, req.protocol, req.hostname, req.rtmpport, NULL, // sockshost &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, dSeek, -1, // length req.bLiveStream, req.timeout); /* backward compatibility, we always sent this as true before */ if (req.auth.av_len) rtmp.Link.authflag = true; rtmp.Link.extras = req.extras; rtmp.Link.token = req.token; LogPrintf("Connecting ... port: %d, app: %s\n", req.rtmpport, req.app); if (!RTMP_Connect(&rtmp, NULL)) { LogPrintf("%s, failed to connect!\n", __FUNCTION__); } else { unsigned long size = 0; double percent = 0; double duration = 0.0; int nWritten = 0; int nRead = 0; // write FLV header first nRead = WriteHeader(&buffer, PACKET_SIZE); if (nRead > 0) { nWritten = send(sockfd, buffer, nRead, 0); if (nWritten < 0) { Log(LOGERROR, "%s, sending failed, error: %d", __FUNCTION__, GetSockError()); goto cleanup; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING } size += nRead; } else { Log(LOGERROR, "%s: Couldn't obtain FLV header, exiting!", __FUNCTION__); goto cleanup; } // get the rest of the stream do { nRead = WriteStream(&rtmp, &buffer, PACKET_SIZE, &req.nTimeStamp); if (nRead > 0) { nWritten = send(sockfd, buffer, nRead, 0); //Log(LOGDEBUG, "written: %d", nWritten); if (nWritten < 0) { Log(LOGERROR, "%s, sending failed, error: %d", __FUNCTION__, GetSockError()); goto cleanup; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING } size += nRead; //LogPrintf("write %dbytes (%.1f KB)\n", nRead, nRead/1024.0); if (duration <= 0) // if duration unknown try to get it from the stream (onMetaData) duration = RTMP_GetDuration(&rtmp); if (duration > 0) { percent = ((double) (dSeek + req.nTimeStamp)) / (duration * 1000.0) * 100.0; percent = ((double) (int) (percent * 10.0)) / 10.0; LogStatus("\r%.3f KB / %.2f sec (%.1f%%)", (double) size / 1024.0, (double) (req.nTimeStamp) / 1000.0, percent); } else { LogStatus("\r%.3f KB / %.2f sec", (double) size / 1024.0, (double) (req.nTimeStamp) / 1000.0); } } #ifdef _DEBUG else { Log(LOGDEBUG, "zero read!"); } #endif // Force clean close if a specified stop offset is reached if (req.dStopOffset && req.nTimeStamp >= req.dStopOffset) { LogPrintf("\nStop offset has been reached at %.2f seconds\n", (double) req.dStopOffset / 1000.0); nRead = 0; RTMP_Close(&rtmp); } } while (server->state == STREAMING_IN_PROGRESS && nRead > -1 && RTMP_IsConnected(&rtmp) && nWritten >= 0); } cleanup: LogPrintf("Closing connection... "); RTMP_Close(&rtmp); LogPrintf("done!\n\n"); quit: if (buffer) { free(buffer); buffer = NULL; } if (sockfd) closesocket(sockfd); if (server->state == STREAMING_IN_PROGRESS) server->state = STREAMING_ACCEPTING; return; filenotfound: LogPrintf("%s, File not found, %s\n", __FUNCTION__, filename); sprintf(buf, "HTTP/1.0 404 File Not Found%s", srvhead); send(sockfd, buf, (int) strlen(buf), 0); goto quit; } TFTYPE serverThread(void *arg) { STREAMING_SERVER *server = arg; server->state = STREAMING_ACCEPTING; while (server->state == STREAMING_ACCEPTING) { struct sockaddr_in addr; socklen_t addrlen = sizeof(struct sockaddr_in); int sockfd = accept(server->socket, (struct sockaddr *) &addr, &addrlen); if (sockfd > 0) { // Create a new process and transfer the control to that Log(LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr)); processTCPrequest(server, sockfd); Log(LOGDEBUG, "%s: processed request\n", __FUNCTION__); } else { Log(LOGERROR, "%s: accept failed", __FUNCTION__); } } server->state = STREAMING_STOPPED; TFRET(); } STREAMING_SERVER * startStreaming(const char *address, int port) { struct sockaddr_in addr; int sockfd; STREAMING_SERVER *server; sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd == -1) { Log(LOGERROR, "%s, couldn't create socket", __FUNCTION__); return 0; } 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) { Log(LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__, port); return 0; } if (listen(sockfd, 10) == -1) { Log(LOGERROR, "%s, listen failed", __FUNCTION__); closesocket(sockfd); return 0; } server = (STREAMING_SERVER *) calloc(1, sizeof(STREAMING_SERVER)); server->socket = sockfd; ThreadCreate(serverThread, server); return server; } void stopStreaming(STREAMING_SERVER * server) { assert(server); if (server->state != STREAMING_STOPPED) { if (server->state == STREAMING_IN_PROGRESS) { server->state = STREAMING_STOPPING; // wait for streaming threads to exit while (server->state != STREAMING_STOPPED) msleep(1); } if (closesocket(server->socket)) Log(LOGERROR, "%s: Failed to close listening socket, error %d", GetSockError()); server->state = STREAMING_STOPPED; } } void sigIntHandler(int sig) { RTMP_ctrlC = true; LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig); if (httpServer) stopStreaming(httpServer); signal(SIGINT, SIG_DFL); } // this will parse RTMP related options as needed // excludes the following options: h, d, g // Return values: true (option parsing ok) // false (option not parsed/invalid) bool ParseOption(char opt, char *arg, RTMP_REQUEST * req) { switch (opt) { case 'b': { int32_t bt = atol(arg); if (bt < 0) { Log(LOGERROR, "Buffer time must be greater than zero, ignoring the specified value %d!", bt); } else { req->bufferTime = bt; } break; } case 'v': req->bLiveStream = true; // no seeking or resuming possible! break; case 'd': STR2AVAL(req->subscribepath, arg); break; case 'n': req->hostname = arg; break; case 'c': req->rtmpport = atoi(arg); break; case 'l': { int protocol = atoi(arg); if (protocol != RTMP_PROTOCOL_RTMP && protocol != RTMP_PROTOCOL_RTMPE) { Log(LOGERROR, "Unknown protocol specified: %d, using default", protocol); return false; } else { req->protocol = protocol; } break; } case 'y': STR2AVAL(req->playpath, arg); break; case 'r': { req->rtmpurl = arg; char *parsedHost = 0; unsigned int parsedPort = 0; char *parsedPlaypath = 0; char *parsedApp = 0; int parsedProtocol = RTMP_PROTOCOL_UNDEFINED; if (!ParseUrl (req->rtmpurl, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) { Log(LOGWARNING, "Couldn't parse the specified url (%s)!", arg); } else { if (req->hostname == 0) req->hostname = parsedHost; if (req->rtmpport == -1) req->rtmpport = parsedPort; if (req->playpath.av_len == 0 && parsedPlaypath) { STR2AVAL(req->playpath, parsedPlaypath); } if (req->protocol == RTMP_PROTOCOL_UNDEFINED) req->protocol = parsedProtocol; if (req->app.av_len == 0 && parsedApp) { STR2AVAL(req->app, parsedApp); } } break; } case 's': STR2AVAL(req->swfUrl, arg); break; case 't': STR2AVAL(req->tcUrl, arg); break; case 'p': STR2AVAL(req->pageUrl, arg); break; case 'a': STR2AVAL(req->app, arg); break; case 'f': STR2AVAL(req->flashVer, arg); break; case 'u': STR2AVAL(req->auth, arg); break; case 'C': parseAMF(&req->extras, optarg, &req->edepth); break; case 'm': req->timeout = atoi(arg); break; case 'A': req->dStartOffset = atoi(arg) * 1000; //printf("dStartOffset = %d\n", dStartOffset); break; case 'B': req->dStopOffset = atoi(arg) * 1000; //printf("dStartOffset = %d\n", dStartOffset); break; case 'T': STR2AVAL(req->token, arg); break; case 'q': debuglevel = LOGCRIT; break; case 'V': debuglevel = LOGDEBUG; break; case 'z': debuglevel = LOGALL; break; default: LogPrintf("unknown option: %c, arg: %s\n", opt, arg); break; } return true; } int main(int argc, char **argv) { int nStatus = RD_SUCCESS; // http streaming server char DEFAULT_HTTP_STREAMING_DEVICE[] = "0.0.0.0"; // 0.0.0.0 is any device char *httpStreamingDevice = DEFAULT_HTTP_STREAMING_DEVICE; // streaming device, default 0.0.0.0 int nHttpStreamingPort = 80; // port LogPrintf("HTTP-RTMP Stream Server %s\n", FLVSTREAMER_VERSION); LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n"); // init request memset(&defaultRTMPRequest, 0, sizeof(RTMP_REQUEST)); defaultRTMPRequest.rtmpport = -1; defaultRTMPRequest.protocol = RTMP_PROTOCOL_UNDEFINED; defaultRTMPRequest.bLiveStream = false; // is it a live stream? then we can't seek/resume defaultRTMPRequest.timeout = 300; // timeout connection afte 300 seconds defaultRTMPRequest.bufferTime = 20 * 1000; defaultRTMPRequest.swfAge = 30; int opt; struct option longopts[] = { {"help", 0, NULL, 'h'}, {"host", 1, NULL, 'n'}, {"port", 1, NULL, 'c'}, {"protocol", 1, NULL, 'l'}, {"playpath", 1, NULL, 'y'}, {"rtmp", 1, NULL, 'r'}, {"swfUrl", 1, NULL, 's'}, {"tcUrl", 1, NULL, 't'}, {"pageUrl", 1, NULL, 'p'}, {"app", 1, NULL, 'a'}, {"auth", 1, NULL, 'u'}, {"conn", 1, NULL, 'C'}, {"flashVer", 1, NULL, 'f'}, {"live", 0, NULL, 'v'}, //{"flv", 1, NULL, 'o'}, //{"resume", 0, NULL, 'e'}, {"timeout", 1, NULL, 'm'}, {"buffer", 1, NULL, 'b'}, //{"skip", 1, NULL, 'k'}, {"device", 1, NULL, 'D'}, {"sport", 1, NULL, 'g'}, {"subscribe", 1, NULL, 'd'}, {"start", 1, NULL, 'A'}, {"stop", 1, NULL, 'B'}, {"token", 1, NULL, 'T'}, {"debug", 0, NULL, 'z'}, {"quiet", 0, NULL, 'q'}, {"verbose", 0, NULL, 'V'}, {0, 0, 0, 0} }; signal(SIGINT, sigIntHandler); #ifndef WIN32 signal(SIGPIPE, SIG_IGN); #endif InitSockets(); while ((opt = getopt_long(argc, argv, "hvqVzr:s:t:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:", longopts, NULL)) != -1) { switch (opt) { case 'h': LogPrintf ("\nThis program dumps the media content streamed over rtmp.\n\n"); LogPrintf("--help|-h Prints this help screen.\n"); LogPrintf ("--rtmp|-r url URL (e.g. rtmp//hotname[:port]/path)\n"); LogPrintf ("--host|-n hostname Overrides the hostname in the rtmp url\n"); LogPrintf ("--port|-c port Overrides the port in the rtmp url\n"); LogPrintf ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 3 - RTMPE)\n"); LogPrintf ("--playpath|-y Overrides the playpath parsed from rtmp url\n"); LogPrintf("--swfUrl|-s url URL to player swf file\n"); LogPrintf ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n"); LogPrintf("--pageUrl|-p url Web URL of played programme\n"); LogPrintf("--app|-a app Name of player used\n"); LogPrintf ("--auth|-u string Authentication string to be appended to the connect string\n"); LogPrintf ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n"); LogPrintf (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n"); LogPrintf (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n"); LogPrintf ("--flashVer|-f string Flash version string (default: \"%s\")\n", DEFAULT_FLASH_VER); LogPrintf ("--live|-v Get a live stream, no --resume (seeking) of live strems possible\n"); LogPrintf ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n"); LogPrintf ("--timeout|-m num Timeout connection num seconds (default: %lu)\n", defaultRTMPRequest.timeout); LogPrintf ("--start|-A num Start at num seconds into stream (not valid when using --live)\n"); LogPrintf ("--stop|-B num Stop at num seconds into stream\n"); LogPrintf ("--token|-T key Key for SecureToken response\n"); LogPrintf ("--buffer|-b Buffer time in milliseconds (default: %lu)\n\n", defaultRTMPRequest.bufferTime); LogPrintf ("--device|-D Streaming device ip address (default: %s)\n", DEFAULT_HTTP_STREAMING_DEVICE); LogPrintf ("--sport|-g Streaming port (default: %d)\n\n", nHttpStreamingPort); LogPrintf ("--quiet|-q Supresses all command output.\n"); LogPrintf("--verbose|-x Verbose command output.\n"); LogPrintf("--debug|-z Debug level command output.\n"); LogPrintf ("If you don't pass parameters for swfUrl, pageUrl, app or auth these propertiews will not be included in the connect "); LogPrintf("packet.\n\n"); return RD_SUCCESS; break; // streaming server specific options case 'D': if (inet_addr(optarg) == INADDR_NONE) { Log(LOGERROR, "Invalid binding address (requested address %s), ignoring", optarg); } else { httpStreamingDevice = optarg; } break; case 'g': { int port = atoi(optarg); if (port < 0 || port > 65535) { Log(LOGERROR, "Streaming port out of range (requested port %d), ignoring\n", port); } else { nHttpStreamingPort = port; } break; } default: //LogPrintf("unknown option: %c\n", opt); ParseOption(opt, optarg, &defaultRTMPRequest); break; } } #ifdef _DEBUG netstackdump = fopen("netstackdump", "wb"); netstackdump_read = fopen("netstackdump_read", "wb"); #endif // start text UI ThreadCreate(controlServerThread, 0); // start http streaming if ((httpServer = startStreaming(httpStreamingDevice, nHttpStreamingPort)) == 0) { Log(LOGERROR, "Failed to start HTTP server, exiting!"); return RD_FAILED; } LogPrintf("Streaming on http://%s:%d\n", httpStreamingDevice, nHttpStreamingPort); while (httpServer->state != STREAMING_STOPPED) { sleep(1); } Log(LOGDEBUG, "Done, exiting..."); CleanupSockets(); #ifdef _DEBUG if (netstackdump != 0) fclose(netstackdump); if (netstackdump_read != 0) fclose(netstackdump_read); #endif return nStatus; } flvstreamer/bytes.h0000664000000000000000000000562211335073516013420 0ustar rootroot/* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #ifndef __BYTES_H__ #define __BYTES_H__ #include #ifdef WIN32 // Windows is little endian only #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 #define __BYTE_ORDER __LITTLE_ENDIAN #define __FLOAT_WORD_ORDER __BYTE_ORDER typedef unsigned char uint8_t; #elif (defined(__FreeBSD__) && __FreeBSD_version >= 470000) || defined(__OpenBSD__) || defined(__NetBSD__) // *BSD #include #define __BIG_ENDIAN BIG_ENDIAN #define __LITTLE_ENDIAN LITTLE_ENDIAN #define __BYTE_ORDER BYTE_ORDER #elif (defined(BSD) && (BSD >= 199103)) || defined(__APPLE__) // more BSD #include #define __BIG_ENDIAN BIG_ENDIAN #define __LITTLE_ENDIAN LITTLE_ENDIAN #define __BYTE_ORDER BYTE_ORDER #elif defined(__linux__) //|| defined (__BEOS__) // Linux, BeOS #include #include //typedef __uint64_t uint64_t; //typedef __uint32_t uint32_t; #endif // define missing byte swap macros #ifndef __bswap_32 #define __bswap_32(x) \ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) #endif // define default endianness #ifndef __LITTLE_ENDIAN #define __LITTLE_ENDIAN 1234 #endif #ifndef __BIG_ENDIAN #define __BIG_ENDIAN 4321 #endif #ifndef __BYTE_ORDER #warning "Byte order not defined on your system, assuming little endian!" #define __BYTE_ORDER __LITTLE_ENDIAN #endif // ok, we assume to have the same float word order and byte order if float word order is not defined #ifndef __FLOAT_WORD_ORDER #warning "Float word order not defined, assuming the same as byte order!" #define __FLOAT_WORD_ORDER __BYTE_ORDER #endif #if !defined(__BYTE_ORDER) || !defined(__FLOAT_WORD_ORDER) #error "Undefined byte or float word order!" #endif #if __FLOAT_WORD_ORDER != __BIG_ENDIAN && __FLOAT_WORD_ORDER != __LITTLE_ENDIAN #error "Unknown/unsupported float word order!" #endif #if __BYTE_ORDER != __BIG_ENDIAN && __BYTE_ORDER != __LITTLE_ENDIAN #error "Unknown/unsupported byte order!" #endif #endif flvstreamer/log.c0000664000000000000000000000725711335073516013054 0ustar rootroot/* flvstreamer * Copyright (C) 2008-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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include #include #include #include #include #include "log.h" #define MAX_PRINT_LEN 2048 AMF_LogLevel debuglevel = LOGERROR; static int neednl; static FILE *fmsg; static const char *levels[] = { "CRIT", "ERROR", "WARNING", "INFO", "DEBUG", "DEBUG2" }; void LogSetOutput(FILE *file) { fmsg = file; } void LogPrintf(const char *format, ...) { char str[MAX_PRINT_LEN]=""; int len; va_list args; va_start(args, format); len = vsnprintf(str, MAX_PRINT_LEN-1, format, args); va_end(args); if ( debuglevel==LOGCRIT ) return; if ( !fmsg ) fmsg = stderr; if (neednl) { putc('\n', fmsg); neednl = 0; } if (len > MAX_PRINT_LEN-1) len = MAX_PRINT_LEN-1; fprintf(fmsg, "%s", str); if (str[len-1] == '\n') fflush(fmsg); } void LogStatus(const char *format, ...) { char str[MAX_PRINT_LEN]=""; va_list args; va_start(args, format); vsnprintf(str, MAX_PRINT_LEN-1, format, args); va_end(args); if ( debuglevel==LOGCRIT ) return; if ( !fmsg ) fmsg = stderr; fprintf(fmsg, "%s", str); fflush(fmsg); neednl = 1; } void Log(int level, const char *format, ...) { char str[MAX_PRINT_LEN]=""; va_list args; va_start(args, format); vsnprintf(str, MAX_PRINT_LEN-1, format, args); va_end(args); // Filter out 'no-name' if ( debuglevel debuglevel ) return; for(i=0; i debuglevel ) return; /* in case len is zero */ line[0] = '\n'; line[1] = '\0'; for ( i = 0 ; i < len ; i++ ) { int n = i % 16; unsigned off; if( !n ) { if( i ) LogPrintf( "%s", line ); memset( line, ' ', sizeof(line)-2 ); line[sizeof(line)-2] = '\n'; line[sizeof(line)-1] = '\0'; off = i % 0x0ffffU; line[2] = hexdig[0x0f & (off >> 12)]; line[3] = hexdig[0x0f & (off >> 8)]; line[4] = hexdig[0x0f & (off >> 4)]; line[5] = hexdig[0x0f & off]; line[6] = ':'; } off = BP_OFFSET + n*3 + ((n >= 8)?1:0); line[off] = hexdig[0x0f & ( data[i] >> 4 )]; line[off+1] = hexdig[0x0f & data[i]]; off = BP_GRAPH + n + ((n >= 8)?1:0); if ( isprint( (unsigned char) data[i] )) { line[BP_GRAPH + n] = data[i]; } else { line[BP_GRAPH + n] = '.'; } } LogPrintf( "%s", line ); } flvstreamer/rtmpsrv.c0000664000000000000000000004710511335073516014004 0ustar rootroot/* Simple RTMP 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ /* This is just a stub for an RTMP server. It doesn't do anything * beyond obtaining the connection parameters from the client. */ #include #include #include #include #include #include #include #include "rtmp.h" #include "parseurl.h" #include "thread.h" #ifdef linux #include #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 { int socket; int state; int streamID; char *connect; } STREAMING_SERVER; STREAMING_SERVER *rtmpServer = 0; // server structure pointer STREAMING_SERVER *startStreaming(const char *address, int port); void stopStreaming(STREAMING_SERVER * server); typedef struct { char *hostname; int rtmpport; int protocol; bool bLiveStream; // is it a live stream? then we can't seek/resume long int timeout; // timeout connection afte 300 seconds uint32_t bufferTime; char *rtmpurl; AVal playpath; AVal swfUrl; AVal tcUrl; AVal pageUrl; AVal app; AVal auth; AVal swfHash; AVal flashVer; AVal subscribepath; uint32_t swfSize; uint32_t dStartOffset; uint32_t dStopOffset; uint32_t nTimeStamp; } RTMP_REQUEST; #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val) /* this request is formed from the parameters and used to initialize a new request, * thus it is a default settings list. All settings can be overriden by specifying the * parameters in the GET request. */ RTMP_REQUEST defaultRTMPRequest; #ifdef _DEBUG uint32_t debugTS = 0; int pnum = 0; FILE *netstackdump = NULL; FILE *netstackdump_read = NULL; #endif #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(getStreamLength); SAVC(play); SAVC(fmsVer); SAVC(mode); SAVC(level); SAVC(code); SAVC(description); SAVC(secureToken); static bool SendConnectResult(RTMP *r, double txn) { RTMPPacket packet; char pbuf[384], *pend = pbuf+sizeof(pbuf); AMFObject obj; AMFObjectProperty p, op; AVal av; packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */ packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av__result); enc = AMF_EncodeNumber(enc, pend, txn); *enc++ = AMF_OBJECT; STR2AVAL(av, "FMS/3,5,1,525"); enc = AMF_EncodeNamedString(enc, pend, &av_fmsVer, &av); enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 31.0); enc = AMF_EncodeNamedNumber(enc, pend, &av_mode, 1.0); *enc++ = 0; *enc++ = 0; *enc++ = AMF_OBJECT_END; *enc++ = AMF_OBJECT; STR2AVAL(av, "status"); enc = AMF_EncodeNamedString(enc, pend, &av_level, &av); STR2AVAL(av, "NetConnection.Connect.Success"); enc = AMF_EncodeNamedString(enc, pend, &av_code, &av); STR2AVAL(av, "Connection succeeded."); enc = AMF_EncodeNamedString(enc, pend, &av_description, &av); enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding); #if 0 STR2AVAL(av, "58656322c972d6cdf2d776167575045f8484ea888e31c086f7b5ffbd0baec55ce442c2fb"); enc = AMF_EncodeNamedString(enc, pend, &av_secureToken, &av); #endif STR2AVAL(p.p_name, "version"); STR2AVAL(p.p_vu.p_aval, "3,5,1,525"); p.p_type = AMF_STRING; obj.o_num = 1; obj.o_props = &p; op.p_type = AMF_OBJECT; STR2AVAL(op.p_name, "data"); op.p_vu.p_object = obj; enc = AMFProp_Encode(&op, enc, pend); *enc++ = 0; *enc++ = 0; *enc++ = AMF_OBJECT_END; *enc++ = 0; *enc++ = 0; *enc++ = AMF_OBJECT_END; packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, false); } static bool SendResultNumber(RTMP *r, double txn, double ID) { RTMPPacket packet; char pbuf[256], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */ packet.m_packetType = 0x14; // INVOKE packet.m_nInfoField1 = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av__result); enc = AMF_EncodeNumber(enc, pend, txn); *enc++ = AMF_NULL; enc = AMF_EncodeNumber(enc, pend, ID); packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, false); } static void dumpAMF(AMFObject *obj) { int i; const char opt[] = "NBSO Z"; for (i=0; i < obj->o_num; i++) { AMFObjectProperty *p = &obj->o_props[i]; printf(" -C "); if (p->p_name.av_val) printf("N"); printf("%c:", opt[p->p_type]); if (p->p_name.av_val) printf("%.*s:", p->p_name.av_len, p->p_name.av_val); switch(p->p_type) { case AMF_BOOLEAN: printf("%d", p->p_vu.p_number != 0); break; case AMF_STRING: printf("%.*s", p->p_vu.p_aval.av_len, p->p_vu.p_aval.av_val); break; case AMF_NUMBER: printf("%f", p->p_vu.p_number); break; case AMF_OBJECT: printf("1"); dumpAMF(&p->p_vu.p_object); printf(" -C O:0"); break; case AMF_NULL: break; default: printf("", p->p_type); } } } // Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' int ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int offset) { const char *body; unsigned int nBodySize; int ret = 0, nRes; body = packet->m_body + offset; nBodySize = packet->m_nBodySize - offset; if (body[0] != 0x02) // make sure it is a string method name we start with { Log(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) { Log(LOGERROR, "%s, error decoding invoke packet", __FUNCTION__); return 0; } AMF_Dump(&obj); AVal method; AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method); double txn = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1)); Log(LOGDEBUG, "%s, client invoking <%s>", __FUNCTION__, method.av_val); if (AVMATCH(&method, &av_connect)) { AMFObject cobj; AVal pname, pval; int i; server->connect = packet->m_body; packet->m_body = NULL; AMFProp_GetObject(AMF_GetProp(&obj, NULL, 2), &cobj); for (i=0; iLink.app = pval; pval.av_val = NULL; } else if (AVMATCH(&pname, &av_flashVer)) { r->Link.flashVer = pval; pval.av_val = NULL; } else if (AVMATCH(&pname, &av_swfUrl)) { r->Link.swfUrl = pval; pval.av_val = NULL; } else if (AVMATCH(&pname, &av_tcUrl)) { r->Link.tcUrl = pval; pval.av_val = NULL; } else if (AVMATCH(&pname, &av_pageUrl)) { r->Link.pageUrl = pval; pval.av_val = NULL; } else if (AVMATCH(&pname, &av_audioCodecs)) { r->m_fAudioCodecs = cobj.o_props[i].p_vu.p_number; } else if (AVMATCH(&pname, &av_videoCodecs)) { r->m_fVideoCodecs = cobj.o_props[i].p_vu.p_number; } else if (AVMATCH(&pname, &av_objectEncoding)) { r->m_fEncoding = cobj.o_props[i].p_vu.p_number; } } /* Still have more parameters? Copy them */ if (obj.o_num > 3) { int i = obj.o_num - 3; r->Link.extras.o_num = i; r->Link.extras.o_props = malloc(i*sizeof(AMFObjectProperty)); memcpy(r->Link.extras.o_props, obj.o_props+3, i*sizeof(AMFObjectProperty)); obj.o_num = 3; } SendConnectResult(r, txn); } else if (AVMATCH(&method, &av_createStream)) { SendResultNumber(r, txn, ++server->streamID); } else if (AVMATCH(&method, &av_getStreamLength)) { SendResultNumber(r, txn, 10.0); } else if (AVMATCH(&method, &av_play)) { RTMPPacket pc = {0}; AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &r->Link.playpath); r->Link.seekTime = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 4)); if (obj.o_num > 5) r->Link.length = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 5)); if (r->Link.tcUrl.av_len) { printf("\nflvstreamer -r \"%s\"", r->Link.tcUrl.av_val); if (r->Link.app.av_val) printf(" -a \"%s\"", r->Link.app.av_val); if (r->Link.flashVer.av_val) printf(" -f \"%s\"", r->Link.flashVer.av_val); if (r->Link.swfUrl.av_val) printf(" -W \"%s\"", r->Link.swfUrl.av_val); printf(" -t \"%s\"", r->Link.tcUrl.av_val); if (r->Link.pageUrl.av_val) printf(" -p \"%s\"", r->Link.pageUrl.av_val); if (r->Link.auth.av_val) printf(" -u \"%s\"", r->Link.auth.av_val); if (r->Link.extras.o_num) { dumpAMF(&r->Link.extras); AMF_Reset(&r->Link.extras); } printf(" -y \"%.*s\" -o output.flv\n\n", r->Link.playpath.av_len, r->Link.playpath.av_val); fflush(stdout); } pc.m_body = server->connect; server->connect = NULL; RTMPPacket_Free(&pc); ret = 1; } AMF_Reset(&obj); return ret; } int ServePacket(STREAMING_SERVER *server, RTMP *r, RTMPPacket *packet) { int ret = 0; Log(LOGDEBUG, "%s, received packet type %02X, size %lu bytes", __FUNCTION__, packet->m_packetType, packet->m_nBodySize); switch (packet->m_packetType) { case 0x01: // chunk size // HandleChangeChunkSize(r, packet); break; case 0x03: // bytes read report break; case 0x04: // ctrl // HandleCtrl(r, packet); break; case 0x05: // server bw // HandleServerBW(r, packet); break; case 0x06: // client bw // HandleClientBW(r, packet); break; case 0x08: // audio data //Log(LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); break; case 0x09: // video data //Log(LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); break; case 0x0F: // flex stream send break; case 0x10: // flex shared object break; case 0x11: // flex message { Log(LOGDEBUG, "%s, flex message, size %lu bytes, not fully supported", __FUNCTION__, packet->m_nBodySize); //LogHex(packet.m_body, packet.m_nBodySize); // some DEBUG code /*RTMP_LIB_AMFObject obj; int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1); if(nRes < 0) { Log(LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__); //return; } obj.Dump(); */ ServeInvoke(server, r, packet, 1); break; } case 0x12: // metadata (notify) break; case 0x13: /* shared object */ break; case 0x14: // invoke Log(LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__, packet->m_nBodySize); //LogHex(packet.m_body, packet.m_nBodySize); if (ServeInvoke(server, r, packet, 0)) RTMP_Close(r); break; case 0x16: /* flv */ break; default: Log(LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__, packet->m_packetType); #ifdef _DEBUG LogHex(LOGDEBUG, packet->m_body, packet->m_nBodySize); #endif } return ret; } TFTYPE controlServerThread(void *unused) { char ich; while (1) { ich = getchar(); switch (ich) { case 'q': LogPrintf("Exiting\n"); stopStreaming(rtmpServer); exit(0); break; default: LogPrintf("Unknown command \'%c\', ignoring\n", ich); } } TFRET(); } void doServe(STREAMING_SERVER * server, // server socket and state (our listening socket) int sockfd // client connection socket ) { server->state = STREAMING_IN_PROGRESS; RTMP rtmp = { 0 }; /* our session with the real client */ RTMPPacket packet = { 0 }; // timeout for http requests fd_set fds; struct timeval tv; memset(&tv, 0, sizeof(struct timeval)); tv.tv_sec = 5; FD_ZERO(&fds); FD_SET(sockfd, &fds); if (select(sockfd + 1, &fds, NULL, NULL, &tv) <= 0) { Log(LOGERROR, "Request timeout/select failed, ignoring request"); goto quit; } else { RTMP_Init(&rtmp); rtmp.m_socket = sockfd; if (!RTMP_Serve(&rtmp)) { Log(LOGERROR, "Handshake failed"); goto cleanup; } } while (RTMP_IsConnected(&rtmp) && RTMP_ReadPacket(&rtmp, &packet)) { if (!RTMPPacket_IsReady(&packet)) continue; ServePacket(server, &rtmp, &packet); RTMPPacket_Free(&packet); } cleanup: LogPrintf("Closing connection... "); RTMP_Close(&rtmp); /* Should probably be done by RTMP_Close() ... */ rtmp.Link.playpath.av_val = NULL; rtmp.Link.tcUrl.av_val = NULL; rtmp.Link.swfUrl.av_val = NULL; rtmp.Link.pageUrl.av_val = NULL; rtmp.Link.app.av_val = NULL; rtmp.Link.auth.av_val = NULL; rtmp.Link.flashVer.av_val = NULL; LogPrintf("done!\n\n"); quit: if (server->state == STREAMING_IN_PROGRESS) server->state = STREAMING_ACCEPTING; return; } TFTYPE serverThread(void *arg) { STREAMING_SERVER *server = arg; server->state = STREAMING_ACCEPTING; while (server->state == STREAMING_ACCEPTING) { struct sockaddr_in addr; socklen_t addrlen = sizeof(struct sockaddr_in); 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)); Log(LOGDEBUG, "%s: accepted connection from %s to %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr), destch); #else Log(LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr)); #endif /* Create a new thread and transfer the control to that */ doServe(server, sockfd); Log(LOGDEBUG, "%s: processed request\n", __FUNCTION__); } else { Log(LOGERROR, "%s: accept failed", __FUNCTION__); } } server->state = STREAMING_STOPPED; TFRET(); } 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) { Log(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) { Log(LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__, port); return 0; } if (listen(sockfd, 10) == -1) { Log(LOGERROR, "%s, listen failed", __FUNCTION__); closesocket(sockfd); return 0; } server = (STREAMING_SERVER *) calloc(1, sizeof(STREAMING_SERVER)); server->socket = sockfd; ThreadCreate(serverThread, server); return server; } void stopStreaming(STREAMING_SERVER * server) { assert(server); if (server->state != STREAMING_STOPPED) { if (server->state == STREAMING_IN_PROGRESS) { server->state = STREAMING_STOPPING; // wait for streaming threads to exit while (server->state != STREAMING_STOPPED) msleep(1); } if (closesocket(server->socket)) Log(LOGERROR, "%s: Failed to close listening socket, error %d", GetSockError()); server->state = STREAMING_STOPPED; } } void sigIntHandler(int sig) { RTMP_ctrlC = true; LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig); if (rtmpServer) stopStreaming(rtmpServer); signal(SIGINT, SIG_DFL); } int main(int argc, char **argv) { int nStatus = RD_SUCCESS; // http streaming server char DEFAULT_HTTP_STREAMING_DEVICE[] = "0.0.0.0"; // 0.0.0.0 is any device char *rtmpStreamingDevice = DEFAULT_HTTP_STREAMING_DEVICE; // streaming device, default 0.0.0.0 int nRtmpStreamingPort = 1935; // port LogPrintf("RTMP Server %s\n", FLVSTREAMER_VERSION); LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n"); debuglevel = LOGINFO; if (argc > 1 && !strcmp(argv[1], "-z")) debuglevel = LOGALL; // init request memset(&defaultRTMPRequest, 0, sizeof(RTMP_REQUEST)); defaultRTMPRequest.rtmpport = -1; defaultRTMPRequest.protocol = RTMP_PROTOCOL_UNDEFINED; defaultRTMPRequest.bLiveStream = false; // is it a live stream? then we can't seek/resume defaultRTMPRequest.timeout = 300; // timeout connection afte 300 seconds defaultRTMPRequest.bufferTime = 20 * 1000; 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 ThreadCreate(controlServerThread, 0); // start http streaming if ((rtmpServer = startStreaming(rtmpStreamingDevice, nRtmpStreamingPort)) == 0) { Log(LOGERROR, "Failed to start RTMP server, exiting!"); return RD_FAILED; } LogPrintf("Streaming on rtmp://%s:%d\n", rtmpStreamingDevice, nRtmpStreamingPort); while (rtmpServer->state != STREAMING_STOPPED) { sleep(1); } Log(LOGDEBUG, "Done, exiting..."); CleanupSockets(); #ifdef _DEBUG if (netstackdump != 0) fclose(netstackdump); if (netstackdump_read != 0) fclose(netstackdump_read); #endif return nStatus; } flvstreamer/thread.c0000664000000000000000000000275711335073516013542 0ustar rootroot/* Thread compatibility glue * 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "thread.h" #include "log.h" #ifdef WIN32 #include HANDLE ThreadCreate(thrfunc *routine, void *args) { HANDLE thd; thd = (HANDLE) _beginthread(routine, 0, args); if (thd == -1L) LogPrintf("%s, _beginthread failed with %d\n", __FUNCTION__, errno); return thd; } #else pthread_t ThreadCreate(thrfunc *routine, void *args) { pthread_t id = 0; pthread_attr_t attributes; int ret; pthread_attr_init(&attributes); pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); ret = pthread_create(&id, &attributes, routine, args); if (ret != 0) LogPrintf("%s, pthread_create failed with %d\n", __FUNCTION__, ret); return id; } #endif flvstreamer/rtmpsuck.c0000664000000000000000000007554611335073516014151 0ustar rootroot/* 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, 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 #include #include #include #include #include #include #include "rtmp.h" #include "parseurl.h" #include "thread.h" #ifdef linux #include #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 { Log(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) { Log(LOGERROR, "%s, error decoding invoke packet", __FUNCTION__); return 0; } AMF_Dump(&obj); AVal method; AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method); Log(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); LogPrintf("Processing connect\n"); for (i=0; irc.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)) { 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, '/'); len = r2 - r1; r2 = malloc(len+1); memcpy(r2, r1, len); r2[len] = '\0'; server->rc.Link.hostname = (const char *)r2; r1 = strrchr(server->rc.Link.hostname, ':'); if (r1) { *r1++ = '\0'; server->rc.Link.port = atoi(r1); } else { 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) { server->rc.Link.authflag = AMFProp_GetBoolean(&obj.o_props[3]); 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 = memchr(av.av_val, '?', av.av_len); if (q) 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 = 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 = '_'; 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 = 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); Log(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) 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; Log(LOGDEBUG, "%s, %s sent packet type %02X, size %lu bytes", __FUNCTION__, cst[which], packet->m_packetType, packet->m_nBodySize); switch (packet->m_packetType) { case 0x01: // chunk size // HandleChangeChunkSize(r, packet); break; case 0x03: // bytes read report break; case 0x04: // ctrl // HandleCtrl(r, packet); break; case 0x05: // server bw // HandleServerBW(r, packet); break; case 0x06: // client bw // HandleClientBW(r, packet); break; case 0x08: // audio data //Log(LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); break; case 0x09: // video data //Log(LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); break; case 0x0F: // flex stream send break; case 0x10: // flex shared object break; case 0x11: // flex message { ret = ServeInvoke(server, which, packet, packet->m_body + 1); break; } case 0x12: // metadata (notify) break; case 0x13: /* shared object */ break; case 0x14: // invoke ret = ServeInvoke(server, which, packet, packet->m_body); break; case 0x16: /* flv */ break; default: Log(LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__, packet->m_packetType); #ifdef _DEBUG LogHex(LOGDEBUG, 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 == 0x09 && nPacketLen == 2 && ((*packetBody & 0xf0) == 0x50)) { ret = 0; break; } if (packet->m_packetType == 0x09 && nPacketLen <= 5) { Log(LOGWARNING, "ignoring too small video packet: size: %d", nPacketLen); ret = 0; break; } if (packet->m_packetType == 0x08 && nPacketLen <= 1) { Log(LOGWARNING, "ignoring too small audio packet: size: %d", nPacketLen); ret = 0; break; } #ifdef _DEBUG Log(LOGDEBUG, "type: %02X, size: %d, TS: %d ms", packet->m_packetType, nPacketLen, packet->m_nTimeStamp); if (packet->m_packetType == 0x09) Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0)); #endif // calculate packet size and reallocate buffer if necessary unsigned int size = nPacketLen + ((packet->m_packetType == 0x08 || packet->m_packetType == 0x09 || packet->m_packetType == 0x12) ? 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) { Log(LOGERROR, "Couldn't reallocate memory!"); ret = -1; // fatal error break; } } char *ptr = *buf, *pend = ptr + size+4; // audio (0x08), video (0x09) or metadata (0x12) packets : // construct 11 byte header then add rtmp packet's data if (packet->m_packetType == 0x08 || packet->m_packetType == 0x09 || packet->m_packetType == 0x12) { // set data type //*dataType |= (((packet->m_packetType == 0x08)<<2)|(packet->m_packetType == 0x09)); (*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 == 0x16) { 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); // set data type //*dataType |= (((*(packetBody+pos) == 0x08)<<2)|(*(packetBody+pos) == 0x09)); if (pos + 11 + dataSize + 4 > nPacketLen) { if (pos + 11 + dataSize > nPacketLen) { Log(LOGERROR, "Wrong data size (%lu), stream corrupted, aborting!", dataSize); ret = -2; break; } Log(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 Log(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 Log(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 != 0x16) { // 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 } TFTYPE controlServerThread(void *unused) { char ich; while (1) { ich = getchar(); switch (ich) { case 'q': LogPrintf("Exiting\n"); stopStreaming(rtmpServer); free(rtmpServer); exit(0); break; default: LogPrintf("Unknown command \'%c\', ignoring\n", ich); } } TFRET(); } void doServe(STREAMING_SERVER * server, // server socket and state (our listening socket) int sockfd // client connection socket ) { RTMPPacket pc = { 0 }, ps = { 0 }; RTMPChunk rk = { 0 }; char *buf; unsigned int buflen = 131072; bool paused = false; // 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) { Log(LOGERROR, "Request timeout/select failed, ignoring request"); goto quit; } else { RTMP_Init(&server->rs); RTMP_Init(&server->rc); server->rs.m_socket = sockfd; if (!RTMP_Serve(&server->rs)) { Log(LOGERROR, "Handshake failed"); goto cleanup; } } buf = 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_nBufferSize; sr = server->rs.m_nBufferSize; if (cr || sr) { } else { n = server->rs.m_socket; if (server->rc.m_socket > n) n = server->rc.m_socket; FD_ZERO(&rfds); if (RTMP_IsConnected(&server->rs)) FD_SET(sockfd, &rfds); if (RTMP_IsConnected(&server->rc)) FD_SET(server->rc.m_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; } } Log(LOGERROR, "Request timeout/select failed, ignoring request"); goto cleanup; } if (FD_ISSET(server->rs.m_socket, &rfds)) sr = 1; if (FD_ISSET(server->rc.m_socket, &rfds)) cr = 1; } if (sr) { while (RTMP_ReadPacket(&server->rs, &ps)) if (RTMPPacket_IsReady(&ps)) { /* change chunk size */ if (ps.m_packetType == 0x01) { if (ps.m_nBodySize >= 4) { server->rs.m_inChunkSize = AMF_DecodeInt32(ps.m_body); Log(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 == 0x03) { if (ps.m_nBodySize >= 4) { int count = AMF_DecodeInt32(ps.m_body); Log(LOGDEBUG, "%s, client: bytes received = %d", __FUNCTION__, count); } } /* ctrl */ else if (ps.m_packetType == 0x04) { 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 Log(LOGDEBUG, "%s, client: BufferTime change in stream %d to %d", __FUNCTION__, id, len); } } } else if (ps.m_packetType == 0x11 || ps.m_packetType == 0x14) 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 == 0x01) { if (pc.m_nBodySize >= 4) { server->rc.m_inChunkSize = AMF_DecodeInt32(pc.m_body); Log(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 == 0x04) { short nType = AMF_DecodeInt16(pc.m_body); /* SWFverification */ if (nType == 0x1a) /* The session will certainly fail right after this */ Log(LOGERROR, "%s, server requested SWF verification, need CRYPTO support! ", __FUNCTION__); } else if (server->f_cur && ( pc.m_packetType == 0x08 || pc.m_packetType == 0x09 || pc.m_packetType == 0x12 || pc.m_packetType == 0x16) && 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 == 0x11 || pc.m_packetType == 0x14) { 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: 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() ... */ free((void *)server->rc.Link.hostname); server->rc.Link.hostname = 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; LogPrintf("done!\n\n"); quit: if (server->state == STREAMING_IN_PROGRESS) server->state = STREAMING_ACCEPTING; return; } TFTYPE serverThread(void *arg) { STREAMING_SERVER *server = arg; server->state = STREAMING_ACCEPTING; while (server->state == STREAMING_ACCEPTING) { struct sockaddr_in addr; socklen_t addrlen = sizeof(struct sockaddr_in); 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)); Log(LOGDEBUG, "%s: accepted connection from %s to %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr), destch); #else Log(LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr)); #endif /* Create a new thread and transfer the control to that */ doServe(server, sockfd); Log(LOGDEBUG, "%s: processed request\n", __FUNCTION__); } else { Log(LOGERROR, "%s: accept failed", __FUNCTION__); } } server->state = STREAMING_STOPPED; TFRET(); } 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) { Log(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) { Log(LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__, port); return 0; } if (listen(sockfd, 10) == -1) { Log(LOGERROR, "%s, listen failed", __FUNCTION__); closesocket(sockfd); return 0; } server = (STREAMING_SERVER *) calloc(1, sizeof(STREAMING_SERVER)); server->socket = sockfd; ThreadCreate(serverThread, server); 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)) Log(LOGERROR, "%s: Failed to close listening socket, error %d", GetSockError()); server->state = STREAMING_STOPPED; } } void sigIntHandler(int sig) { RTMP_ctrlC = true; LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig); if (rtmpServer) stopStreaming(rtmpServer); signal(SIGINT, SIG_DFL); } 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 LogPrintf("RTMP Proxy Server %s\n", FLVSTREAMER_VERSION); LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n"); debuglevel = LOGINFO; if (argc > 1 && !strcmp(argv[1], "-z")) debuglevel = 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 ThreadCreate(controlServerThread, 0); // start http streaming if ((rtmpServer = startStreaming(rtmpStreamingDevice, nRtmpStreamingPort)) == 0) { Log(LOGERROR, "Failed to start RTMP server, exiting!"); return RD_FAILED; } LogPrintf("Streaming on rtmp://%s:%d\n", rtmpStreamingDevice, nRtmpStreamingPort); while (rtmpServer->state != STREAMING_STOPPED) { sleep(1); } Log(LOGDEBUG, "Done, exiting..."); free(rtmpServer); CleanupSockets(); #ifdef _DEBUG if (netstackdump != 0) fclose(netstackdump); if (netstackdump_read != 0) fclose(netstackdump_read); #endif return nStatus; } flvstreamer/COPYING0000664000000000000000000004310311334755455013160 0ustar rootroot GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 of the License, 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. flvstreamer/parseurl.c0000664000000000000000000001756311335073516014131 0ustar rootroot/* flvstreamer * 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include #include #include #include #include "log.h" #include "parseurl.h" #define RTMP_PROTOCOL_UNDEFINED -1 #define RTMP_PROTOCOL_RTMP 0 #define RTMP_PROTOCOL_RTMPT 1 // not yet supported #define RTMP_PROTOCOL_RTMPS 2 // not yet supported #define RTMP_PROTOCOL_RTMPE 3 #define RTMP_PROTOCOL_RTMPTE 4 // not yet supported #define RTMP_PROTOCOL_RTMFP 5 // not yet supported char *str2lower(char *str, int len) { char *res = (char *)malloc(len+1); char *p; for(p=res; p= 48) return c-48; else if(c <= 102 && c >= 97) return c-97+10; return -1; } int hex2bin(char *str, char **hex) { if(!str || !hex) return 0; int len = strlen(str); if(len % 2 != 0) return 0; int ret = len/2; *hex = (char *)malloc(ret); if((*hex)==0) return 0; char *hexptr = *hex; char *lwo = str2lower(str, len); char *lw = lwo; len /= 2; while(len) { int d1 = chr2hex(*lw); lw++; int d2 = chr2hex(*lw); lw++; if(d1<0 || d2<0) { free(*hex); free(lwo); *hex=NULL; return -1; } *hexptr = (unsigned char)(d1*16+d2); hexptr++; len--; } free(lwo); return ret; } int ParseUrl(char *url, int *protocol, char **host, unsigned int *port, char **playpath, char **app) { assert(url != 0 && protocol != 0 && host != 0 && port != 0 && playpath != 0 && app != 0); Log(LOGDEBUG, "Parsing..."); *protocol = 0; // default: RTMP // Old School Parsing char *lw = str2lower(url, 6); char *temp; // look for usual :// pattern char *p = strstr(url, "://"); int len = (int)(p-url); if(p == 0) { Log(LOGWARNING, "RTMP URL: No :// in url!"); free(lw); return 0; } if(len == 4 && strncmp(lw, "rtmp", 4)==0) *protocol = RTMP_PROTOCOL_RTMP; else if(len == 5 && strncmp(lw, "rtmpt", 5)==0) *protocol = RTMP_PROTOCOL_RTMPT; else if(len == 5 && strncmp(lw, "rtmps", 5)==0) *protocol = RTMP_PROTOCOL_RTMPS; else if(len == 5 && strncmp(lw, "rtmpe", 5)==0) *protocol = RTMP_PROTOCOL_RTMPE; else if(len == 5 && strncmp(lw, "rtmfp", 5)==0) *protocol = RTMP_PROTOCOL_RTMFP; else if(len == 6 && strncmp(lw, "rtmpte", 6)==0) *protocol = RTMP_PROTOCOL_RTMPTE; else { Log(LOGWARNING, "Unknown protocol!\n"); goto parsehost; } Log(LOGDEBUG, "Parsed protocol: %d", *protocol); parsehost: free(lw); // lets get the hostname p+=3; // check for sudden death if(*p==0) { Log(LOGWARNING, "No hostname in URL!"); return 0; } int iEnd = strlen(p); int iCol = iEnd+1; int iQues = iEnd+1; int iSlash = iEnd+1; if((temp=strstr(p, ":"))!=0) iCol = temp-p; if((temp=strstr(p, "?"))!=0) iQues = temp-p; if((temp=strstr(p, "/"))!=0) iSlash = temp-p; int min = iSlash < iEnd ? iSlash : iEnd+1; min = iQues < min ? iQues : min; int hostlen = iCol < min ? iCol : min; if(min < 256) { *host = (char *)malloc((hostlen+1)*sizeof(char)); strncpy(*host, p, hostlen); (*host)[hostlen]=0; Log(LOGDEBUG, "Parsed host : %s", *host); } else { Log(LOGWARNING, "Hostname exceeds 255 characters!"); } p+=hostlen; iEnd-=hostlen; // get the port number if available if(*p == ':') { p++; iEnd--; int portlen = min-hostlen-1; if(portlen < 6) { char portstr[6]; strncpy(portstr,p,portlen); portstr[portlen]=0; *port = atoi(portstr); if(*port == 0) *port = 1935; Log(LOGDEBUG, "Parsed port : %d", *port); } else { Log(LOGWARNING, "Port number is longer than 5 characters!"); } p+=portlen; iEnd-=portlen; } if(*p != '/') { Log(LOGWARNING, "No application or playpath in URL!"); return 1; } p++; iEnd--; // parse application // // rtmp://host[:port]/app[/appinstance][/...] // application = app[/appinstance] int iSlash2 = iEnd+1; // 2nd slash int iSlash3 = iEnd+1; // 3rd slash if((temp=strstr(p, "/"))!=0) iSlash2 = temp-p; if((temp=strstr(p, "?"))!=0) iQues = temp-p; if(iSlash2 < iEnd) if((temp=strstr(p+iSlash2+1, "/"))!=0) iSlash3 = temp-p; //Log(LOGDEBUG, "p:%s, iEnd: %d\niSlash : %d\niSlash2: %d\niSlash3: %d", p, iEnd, iSlash, iSlash2, iSlash3); int applen = iEnd+1; // ondemand, pass all parameters as app int appnamelen = 8; // ondemand length if(iQues < iEnd && strstr(p, "slist=")) { // whatever it is, the '?' and slist= means we need to use everything as app and parse plapath from slist= appnamelen = iQues; applen = iEnd+1; // pass the parameters as well } else if(strncmp(p, "ondemand/", 9)==0) { // app = ondemand/foobar, only pass app=ondemand applen = 8; } else { // app!=ondemand, so app is app[/appinstance] appnamelen = iSlash2 < iEnd ? iSlash2 : iEnd; if(iSlash3 < iEnd) appnamelen = iSlash3; applen = appnamelen; } *app = (char *)malloc((applen+1)*sizeof(char)); strncpy(*app, p, applen); (*app)[applen]=0; Log(LOGDEBUG, "Parsed app : %s", *app); p += appnamelen; iEnd -= appnamelen; if (*p == '/') { p += 1; iEnd -= 1; } *playpath = ParsePlaypath(p); return 1; } /* * Extracts playpath from RTMP URL. playpath is the file part of the * URL, i.e. the part that comes after rtmp://host:port/app/ * * Returns the stream name in a format understood by FMS. The name is * the playpath part of the URL with formating depending on the stream * type: * * mp4 streams: prepend "mp4:", remove extension * mp3 streams: prepend "mp3:", remove extension * flv streams: remove extension */ char *ParsePlaypath(const char *playpath) { if (!playpath || !*playpath) return NULL; int addMP4 = 0; int addMP3 = 0; int subExt = 0; const char *temp, *q, *ext = NULL; const char *ppstart = playpath; int pplen = strlen(playpath); if ((*ppstart == '?') && (temp=strstr(ppstart, "slist=")) != 0) { ppstart = temp+6; pplen = strlen(ppstart); temp = strchr(ppstart, '&'); if (temp) { pplen = temp-ppstart; } } q = strchr(ppstart, '?'); if (pplen >= 4) { if (q) ext = q-4; else ext = &ppstart[pplen-4]; if ((strncmp(ext, ".f4v", 4) == 0) || (strncmp(ext, ".mp4", 4) == 0)) { addMP4 = 1; subExt = 1; // Only remove .flv from rtmp URL, not slist params } else if ((ppstart == playpath) && (strncmp(ext, ".flv", 4) == 0)) { subExt = 1; } else if (strncmp(ext, ".mp3", 4) == 0) { addMP3 = 1; subExt = 1; } } char *streamname = (char *)malloc((pplen+4+1)*sizeof(char)); if (!streamname) return NULL; char *destptr = streamname, *p; if (addMP4 && (strncmp(ppstart, "mp4:", 4) != 0)) { strcpy(destptr, "mp4:"); destptr += 4; } else if (addMP3 && (strncmp(ppstart, "mp3:", 4) != 0)) { strcpy(destptr, "mp3:"); destptr += 4; } for (p=(char *)ppstart; pplen >0;) { /* skip extension */ if (subExt && p == ext) { p += 4; pplen -= 4; } if (*p == '%') { int c; sscanf(p+1, "%02x", &c); *destptr++ = c; pplen -= 3; p += 3; } else { *destptr++ = *p++; pplen--; } } *destptr = '\0'; return streamname; } flvstreamer/amf.c0000664000000000000000000005646411335073516013042 0ustar rootroot/* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include #include #include #include "amf.h" #include "log.h" #include "bytes.h" static const AMFObjectProperty AMFProp_Invalid = { {0, 0}, AMF_INVALID }; static const AVal AV_empty = { 0, 0 }; /* Data is Big-Endian */ unsigned short AMF_DecodeInt16(const char *data) { unsigned char *c = (unsigned char *) data; unsigned short val; val = (c[0] << 8) | c[1]; return val; } unsigned int AMF_DecodeInt24(const char *data) { unsigned char *c = (unsigned char *) data; unsigned int val; val = (c[0] << 16) | (c[1] << 8) | c[2]; return val; } unsigned int AMF_DecodeInt32(const char *data) { unsigned char *c = (unsigned char *) data; unsigned int val; val = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; return val; } void AMF_DecodeString(const char *data, AVal * bv) { bv->av_len = AMF_DecodeInt16(data); bv->av_val = (bv->av_len > 0) ? (char *) data + 2 : NULL; } double AMF_DecodeNumber(const char *data) { #if __FLOAT_WORD_ORDER == __BYTE_ORDER #if __BYTE_ORDER == __BIG_ENDIAN double dVal; memcpy(&dVal, data, 8); return dVal; #elif __BYTE_ORDER == __LITTLE_ENDIAN double dVal; unsigned char *ci, *co; ci = (unsigned char *) data; co = (unsigned char *) &dVal; co[0] = ci[7]; co[1] = ci[6]; co[2] = ci[5]; co[3] = ci[4]; co[4] = ci[3]; co[5] = ci[2]; co[6] = ci[1]; co[7] = ci[0]; return dVal; #endif #else #if __BYTE_ORDER == __LITTLE_ENDIAN // __FLOAT_WORD_ORER == __BIG_ENDIAN uint32_t in1 = *((uint32_t *) data); uint32_t in2 = *((uint32_t *) (data + 4)); in1 = __bswap_32(in1); in2 = __bswap_32(in2); uint64_t res = ((uint64_t) in2 << 32) | (uint64_t) in1; return *((double *) &res); #else // __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN uint32_t in1 = *((uint32_t *) data); uint32_t in2 = *((uint32_t *) (data + 4)); uint64_t res = ((uint64_t) in1 << 32) | (uint64_t) in2; return *((double *) &res); #endif #endif } bool AMF_DecodeBoolean(const char *data) { return *data != 0; } char * AMF_EncodeInt16(char *output, char *outend, short nVal) { if (output+2 > outend) return NULL; output[1] = nVal & 0xff; output[0] = nVal >> 8; return output+2; } char * AMF_EncodeInt24(char *output, char *outend, int nVal) { if (output+3 > outend) return NULL; output[2] = nVal & 0xff; output[1] = nVal >> 8; output[0] = nVal >> 16; return output+3; } char * AMF_EncodeInt32(char *output, char *outend, int nVal) { if (output+4 > outend) return NULL; output[3] = nVal & 0xff; output[2] = nVal >> 8; output[1] = nVal >> 16; output[0] = nVal >> 24; return output+4; } char * AMF_EncodeString(char *output, char *outend, const AVal * bv) { if (output + 1 + 2 + bv->av_len > outend) return NULL; *output++ = AMF_STRING; output = AMF_EncodeInt16(output, outend, bv->av_len); memcpy(output, bv->av_val, bv->av_len); output += bv->av_len; return output; } char * AMF_EncodeNumber(char *output, char *outend, double dVal) { if (output+1+8 > outend) return NULL; *output++ = AMF_NUMBER; // type: Number #if __FLOAT_WORD_ORDER == __BYTE_ORDER #if __BYTE_ORDER == __BIG_ENDIAN memcpy(output, &dVal, 8); #elif __BYTE_ORDER == __LITTLE_ENDIAN { unsigned char *ci, *co; ci = (unsigned char *) &dVal; co = (unsigned char *) output; co[0] = ci[7]; co[1] = ci[6]; co[2] = ci[5]; co[3] = ci[4]; co[4] = ci[3]; co[5] = ci[2]; co[6] = ci[1]; co[7] = ci[0]; } #endif #else #if __BYTE_ORDER == __LITTLE_ENDIAN /* __FLOAT_WORD_ORER == __BIG_ENDIAN */ { unsigned char *ci, *co; ci = (unsigned char *) &dVal; co = (unsigned char *) output; co[0] = ci[3]; co[1] = ci[2]; co[2] = ci[1]; co[3] = ci[0]; co[4] = ci[7]; co[5] = ci[6]; co[6] = ci[5]; co[7] = ci[4]; } #else /* __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN */ { unsigned char *ci, *co; ci = (unsigned char *) &dVal; co = (unsigned char *) output; co[0] = ci[4]; co[1] = ci[5]; co[2] = ci[6]; co[3] = ci[7]; co[4] = ci[0]; co[5] = ci[1]; co[6] = ci[2]; co[7] = ci[3]; } #endif #endif return output+8; } char * AMF_EncodeBoolean(char *output, char *outend, bool bVal) { if (output+2 > outend) return NULL; *output++ = AMF_BOOLEAN; *output++ = bVal ? 0x01 : 0x00; return output; } char * AMF_EncodeNamedString(char *output, char *outend, const AVal * strName, const AVal * strValue) { if (output+2+strName->av_len > outend) return NULL; output = AMF_EncodeInt16(output, outend, strName->av_len); memcpy(output, strName->av_val, strName->av_len); output += strName->av_len; return AMF_EncodeString(output, outend, strValue); } char * AMF_EncodeNamedNumber(char *output, char *outend, const AVal * strName, double dVal) { if (output+2+strName->av_len > outend) return NULL; output = AMF_EncodeInt16(output, outend, strName->av_len); memcpy(output, strName->av_val, strName->av_len); output += strName->av_len; return AMF_EncodeNumber(output, outend, dVal); } char * AMF_EncodeNamedBoolean(char *output, char *outend, const AVal * strName, bool bVal) { if (output+2+strName->av_len > outend) return NULL; output = AMF_EncodeInt16(output, outend, strName->av_len); memcpy(output, strName->av_val, strName->av_len); output += strName->av_len; return AMF_EncodeBoolean(output, outend, bVal); } void AMFProp_GetName(AMFObjectProperty * prop, AVal * name) { *name = prop->p_name; } void AMFProp_SetName(AMFObjectProperty * prop, AVal * name) { prop->p_name = *name; } AMFDataType AMFProp_GetType(AMFObjectProperty * prop) { return prop->p_type; } double AMFProp_GetNumber(AMFObjectProperty * prop) { return prop->p_vu.p_number; } int AMFProp_GetBoolean(AMFObjectProperty * prop) { return prop->p_vu.p_number != 0; } void AMFProp_GetString(AMFObjectProperty * prop, AVal * str) { *str = prop->p_vu.p_aval; } void AMFProp_GetObject(AMFObjectProperty * prop, AMFObject * obj) { *obj = prop->p_vu.p_object; } int AMFProp_IsValid(AMFObjectProperty * prop) { return prop->p_type != AMF_INVALID; } char * AMFProp_Encode(AMFObjectProperty * prop, char *pBuffer, char *pBufEnd) { if (prop->p_type == AMF_INVALID) return NULL; if (prop->p_type != AMF_NULL && pBuffer + prop->p_name.av_len + 2 + 1 >= pBufEnd) return NULL; if (prop->p_type != AMF_NULL && prop->p_name.av_len) { *pBuffer++ = prop->p_name.av_len >> 8; *pBuffer++ = prop->p_name.av_len & 0xff; memcpy(pBuffer, prop->p_name.av_val, prop->p_name.av_len); pBuffer += prop->p_name.av_len; } switch (prop->p_type) { case AMF_NUMBER: pBuffer = AMF_EncodeNumber(pBuffer, pBufEnd, prop->p_vu.p_number); break; case AMF_BOOLEAN: pBuffer = AMF_EncodeBoolean(pBuffer, pBufEnd, prop->p_vu.p_number != 0); break; case AMF_STRING: pBuffer = AMF_EncodeString(pBuffer, pBufEnd, &prop->p_vu.p_aval); break; case AMF_NULL: if (pBuffer+1 >= pBufEnd) return NULL; *pBuffer++ = AMF_NULL; break; case AMF_OBJECT: pBuffer = AMF_Encode(&prop->p_vu.p_object, pBuffer, pBufEnd); break; default: Log(LOGERROR, "%s, invalid type. %d", __FUNCTION__, prop->p_type); pBuffer = NULL; }; return pBuffer; } #define AMF3_INTEGER_MAX 268435455 #define AMF3_INTEGER_MIN -268435456 int AMF3ReadInteger(const char *data, int32_t * valp) { int i = 0; int32_t val = 0; while (i <= 2) { /* handle first 3 bytes */ if (data[i] & 0x80) { // byte used val <<= 7; // shift up val |= (data[i] & 0x7f); // add bits i++; } else { break; } } if (i > 2) { // use 4th byte, all 8bits val <<= 8; val |= data[3]; // range check if (val > AMF3_INTEGER_MAX) val -= (1 << 29); } else { // use 7bits of last unparsed byte (0xxxxxxx) val <<= 7; val |= data[i]; } *valp = val; return i > 2 ? 4 : i + 1; } int AMF3ReadString(const char *data, AVal * str) { assert(str != 0); int32_t ref = 0; int len = AMF3ReadInteger(data, &ref); data += len; if ((ref & 0x1) == 0) { /* reference: 0xxx */ uint32_t refIndex = (ref >> 1); Log(LOGDEBUG, "%s, string reference, index: %d, not supported, ignoring!", refIndex); return len; } else { uint32_t nSize = (ref >> 1); str->av_val = (char *) data; str->av_len = nSize; return len + nSize; } return len; } int AMF3Prop_Decode(AMFObjectProperty * prop, const char *pBuffer, int nSize, int bDecodeName) { int nOriginalSize = nSize; AMF3DataType type; prop->p_name.av_len = 0; prop->p_name.av_val = NULL; if (nSize == 0 || !pBuffer) { Log(LOGDEBUG, "empty buffer/no buffer pointer!"); return -1; } /* decode name */ if (bDecodeName) { AVal name; int nRes = AMF3ReadString(pBuffer, &name); if (name.av_len <= 0) return nRes; prop->p_name = name; pBuffer += nRes; nSize -= nRes; } /* decode */ type = *pBuffer++; nSize--; switch (type) { case AMF3_UNDEFINED: case AMF3_NULL: prop->p_type = AMF_NULL; break; case AMF3_FALSE: prop->p_type = AMF_BOOLEAN; prop->p_vu.p_number = 0.0; break; case AMF3_TRUE: prop->p_type = AMF_BOOLEAN; prop->p_vu.p_number = 1.0; break; case AMF3_INTEGER: { int32_t res = 0; int len = AMF3ReadInteger(pBuffer, &res); prop->p_vu.p_number = (double) res; prop->p_type = AMF_NUMBER; nSize -= len; break; } case AMF3_DOUBLE: if (nSize < 8) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); prop->p_type = AMF_NUMBER; nSize -= 8; break; case AMF3_STRING: case AMF3_XML_DOC: case AMF3_XML: { int len = AMF3ReadString(pBuffer, &prop->p_vu.p_aval); prop->p_type = AMF_STRING; nSize -= len; break; } case AMF3_DATE: { int32_t res = 0; int len = AMF3ReadInteger(pBuffer, &res); nSize -= len; pBuffer += len; if ((res & 0x1) == 0) { /* reference */ uint32_t nIndex = (res >> 1); Log(LOGDEBUG, "AMF3_DATE reference: %d, not supported!", nIndex); } else { if (nSize < 8) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); nSize -= 8; prop->p_type = AMF_NUMBER; } break; } case AMF3_OBJECT: { int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, true); if (nRes == -1) return -1; nSize -= nRes; prop->p_type = AMF_OBJECT; break; } case AMF3_ARRAY: case AMF3_BYTE_ARRAY: default: Log(LOGDEBUG, "%s - AMF3 unknown/unsupported datatype 0x%02x, @0x%08X", __FUNCTION__, (unsigned char) (*pBuffer), pBuffer); return -1; } return nOriginalSize - nSize; } int AMFProp_Decode(AMFObjectProperty * prop, const char *pBuffer, int nSize, int bDecodeName) { int nOriginalSize = nSize; prop->p_name.av_len = 0; prop->p_name.av_val = NULL; if (nSize == 0 || !pBuffer) { Log(LOGDEBUG, "%s: Empty buffer/no buffer pointer!", __FUNCTION__); return -1; } if (bDecodeName && nSize < 4) { /* at least name (length + at least 1 byte) and 1 byte of data */ Log(LOGDEBUG, "%s: Not enough data for decoding with name, less then 4 bytes!", __FUNCTION__); return -1; } if (bDecodeName) { unsigned short nNameSize = AMF_DecodeInt16(pBuffer); if (nNameSize > nSize - 2) { Log(LOGDEBUG, "%s: Name size out of range: namesize (%d) > len (%d) - 2", __FUNCTION__, nNameSize, nSize); return -1; } AMF_DecodeString(pBuffer, &prop->p_name); nSize -= 2 + nNameSize; pBuffer += 2 + nNameSize; } if (nSize == 0) { return -1; } nSize--; prop->p_type = *pBuffer++; switch (prop->p_type) { case AMF_NUMBER: if (nSize < 8) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); nSize -= 8; break; case AMF_BOOLEAN: if (nSize < 1) return -1; prop->p_vu.p_number = (double) AMF_DecodeBoolean(pBuffer); nSize--; break; case AMF_STRING: { unsigned short nStringSize = AMF_DecodeInt16(pBuffer); if (nSize < (long) nStringSize + 2) return -1; AMF_DecodeString(pBuffer, &prop->p_vu.p_aval); nSize -= (2 + nStringSize); break; } case AMF_OBJECT: { int nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer, nSize, true); if (nRes == -1) return -1; nSize -= nRes; break; } case AMF_MOVIECLIP: { Log(LOGERROR, "AMF_MOVIECLIP reserved!"); return -1; break; } case AMF_NULL: case AMF_UNDEFINED: case AMF_UNSUPPORTED: prop->p_type = AMF_NULL; break; case AMF_REFERENCE: { Log(LOGERROR, "AMF_REFERENCE not supported!"); return -1; break; } case AMF_ECMA_ARRAY: { nSize -= 4; /* next comes the rest, mixed array has a final 0x000009 mark and names, so its an object */ int nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer + 4, nSize, true); if (nRes == -1) return -1; nSize -= nRes; prop->p_type = AMF_OBJECT; break; } case AMF_OBJECT_END: { return -1; break; } case AMF_STRICT_ARRAY: { unsigned int nArrayLen = AMF_DecodeInt32(pBuffer); nSize -= 4; int nRes = AMF_DecodeArray(&prop->p_vu.p_object, pBuffer + 4, nSize, nArrayLen, false); if (nRes == -1) return -1; nSize -= nRes; prop->p_type = AMF_OBJECT; break; } case AMF_DATE: { Log(LOGDEBUG, "AMF_DATE"); if (nSize < 10) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); prop->p_UTCoffset = AMF_DecodeInt16(pBuffer + 8); nSize -= 10; break; } case AMF_LONG_STRING: { unsigned int nStringSize = AMF_DecodeInt32(pBuffer); if (nSize < (long) nStringSize + 4) return -1; AMF_DecodeString(pBuffer, &prop->p_vu.p_aval); nSize -= (4 + nStringSize); prop->p_type = AMF_STRING; break; } case AMF_RECORDSET: { Log(LOGERROR, "AMF_RECORDSET reserved!"); return -1; break; } case AMF_XML_DOC: { Log(LOGERROR, "AMF_XML_DOC not supported!"); return -1; break; } case AMF_TYPED_OBJECT: { Log(LOGERROR, "AMF_TYPED_OBJECT not supported!"); return -1; break; } case AMF_AVMPLUS: { int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, true); if (nRes == -1) return -1; nSize -= nRes; prop->p_type = AMF_OBJECT; break; } default: Log(LOGDEBUG, "%s - unknown datatype 0x%02x, @0x%08X", __FUNCTION__, prop->p_type, pBuffer - 1); return -1; } return nOriginalSize - nSize; } void AMFProp_Dump(AMFObjectProperty * prop) { char strRes[256]; char str[256]; AVal name; if (prop->p_type == AMF_INVALID) { Log(LOGDEBUG, "Property: INVALID"); return; } if (prop->p_type == AMF_NULL) { Log(LOGDEBUG, "Property: NULL"); return; } if (prop->p_name.av_len) { name = prop->p_name; } else { name.av_val = "no-name."; name.av_len = sizeof("no-name.") - 1; } if (name.av_len > 25) name.av_len = 25; snprintf(strRes, 255, "Name: %25.*s, ", name.av_len, name.av_val); if (prop->p_type == AMF_OBJECT) { Log(LOGDEBUG, "Property: <%sOBJECT>", strRes); AMF_Dump(&prop->p_vu.p_object); return; } switch (prop->p_type) { case AMF_NUMBER: snprintf(str, 255, "NUMBER:\t%.2f", prop->p_vu.p_number); break; case AMF_BOOLEAN: snprintf(str, 255, "BOOLEAN:\t%s", prop->p_vu.p_number != 0.0 ? "TRUE" : "FALSE"); break; case AMF_STRING: snprintf(str, 255, "STRING:\t%.*s", prop->p_vu.p_aval.av_len, prop->p_vu.p_aval.av_val); break; case AMF_DATE: snprintf(str, 255, "DATE:\ttimestamp: %.2f, UTC offset: %d", prop->p_vu.p_number, prop->p_UTCoffset); break; default: snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char) prop->p_type); } Log(LOGDEBUG, "Property: <%s%s>", strRes, str); } void AMFProp_Reset(AMFObjectProperty * prop) { if (prop->p_type == AMF_OBJECT) AMF_Reset(&prop->p_vu.p_object); else { prop->p_vu.p_aval.av_len = 0; prop->p_vu.p_aval.av_val = NULL; } prop->p_type = AMF_INVALID; } /* AMFObject */ char * AMF_Encode(AMFObject * obj, char *pBuffer, char *pBufEnd) { int i; if (pBuffer+4 >= pBufEnd) return NULL; *pBuffer++ = AMF_OBJECT; for (i = 0; i < obj->o_num; i++) { char *res = AMFProp_Encode(&obj->o_props[i], pBuffer, pBufEnd); if (res == NULL) { Log(LOGERROR, "AMF_Encode - failed to encode property in index %d", i); break; } else { pBuffer = res; } } if (pBuffer + 3 >= pBufEnd) return NULL; // no room for the end marker pBuffer = AMF_EncodeInt24(pBuffer, pBufEnd, AMF_OBJECT_END); return pBuffer; } int AMF_DecodeArray(AMFObject * obj, const char *pBuffer, int nSize, int nArrayLen, bool bDecodeName) { int nOriginalSize = nSize; bool bError = false; obj->o_num = 0; obj->o_props = NULL; while (nArrayLen > 0) { nArrayLen--; AMFObjectProperty prop; int nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName); if (nRes == -1) bError = true; else { nSize -= nRes; pBuffer += nRes; AMF_AddProp(obj, &prop); } } if (bError) return -1; return nOriginalSize - nSize; } int AMF3_Decode(AMFObject * obj, const char *pBuffer, int nSize, bool bAMFData) { int nOriginalSize = nSize; int32_t ref; int len; obj->o_num = 0; obj->o_props = NULL; if (bAMFData) { if (*pBuffer != AMF3_OBJECT) Log(LOGERROR, "AMF3 Object encapsulated in AMF stream does not start with AMF3_OBJECT!"); pBuffer++; nSize--; } ref = 0; len = AMF3ReadInteger(pBuffer, &ref); pBuffer += len; nSize -= len; if ((ref & 1) == 0) { /* object reference, 0xxx */ uint32_t objectIndex = (ref >> 1); Log(LOGDEBUG, "Object reference, index: %d", objectIndex); } else /* object instance */ { int32_t classRef = (ref >> 1); AMF3ClassDef cd = { {0, 0} }; AMFObjectProperty prop; if ((classRef & 0x1) == 0) { /* class reference */ uint32_t classIndex = (classRef >> 1); Log(LOGDEBUG, "Class reference: %d", classIndex); } else { int32_t classExtRef = (classRef >> 1); int i; cd.cd_externalizable = (classExtRef & 0x1) == 1; cd.cd_dynamic = ((classExtRef >> 1) & 0x1) == 1; cd.cd_num = classExtRef >> 2; // class name len = AMF3ReadString(pBuffer, &cd.cd_name); nSize -= len; pBuffer += len; //std::string str = className; Log(LOGDEBUG, "Class name: %s, externalizable: %d, dynamic: %d, classMembers: %d", cd.cd_name.av_val, cd.cd_externalizable, cd.cd_dynamic, cd.cd_num); for (i = 0; i < cd.cd_num; i++) { AVal memberName; len = AMF3ReadString(pBuffer, &memberName); Log(LOGDEBUG, "Member: %s", memberName.av_val); AMF3CD_AddProp(&cd, &memberName); nSize -= len; pBuffer += len; } } /* add as referencable object */ if (cd.cd_externalizable) { int nRes; AVal name = AVC("DEFAULT_ATTRIBUTE"); Log(LOGDEBUG, "Externalizable, TODO check"); nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, false); if (nRes == -1) Log(LOGDEBUG, "%s, failed to decode AMF3 property!", __FUNCTION__); else { nSize -= nRes; pBuffer += nRes; } AMFProp_SetName(&prop, &name); AMF_AddProp(obj, &prop); } else { int nRes, i; for (i = 0; i < cd.cd_num; i++) /* non-dynamic */ { nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, false); if (nRes == -1) Log(LOGDEBUG, "%s, failed to decode AMF3 property!", __FUNCTION__); AMFProp_SetName(&prop, AMF3CD_GetProp(&cd, i)); AMF_AddProp(obj, &prop); pBuffer += nRes; nSize -= nRes; } if (cd.cd_dynamic) { int len = 0; do { nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, true); AMF_AddProp(obj, &prop); pBuffer += nRes; nSize -= nRes; len = prop.p_name.av_len; } while (len > 0); } } Log(LOGDEBUG, "class object!"); } return nOriginalSize - nSize; } int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize, bool bDecodeName) { int nOriginalSize = nSize; bool bError = false; /* if there is an error while decoding - try to at least find the end mark AMF_OBJECT_END */ obj->o_num = 0; obj->o_props = NULL; while (nSize > 0) { AMFObjectProperty prop; int nRes; if (nSize >=3 && AMF_DecodeInt24(pBuffer) == AMF_OBJECT_END) { nSize -= 3; bError = false; break; } if (bError) { Log(LOGERROR, "DECODING ERROR, IGNORING BYTES UNTIL NEXT KNOWN PATTERN!"); nSize--; pBuffer++; continue; } nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName); if (nRes == -1) bError = true; else { nSize -= nRes; pBuffer += nRes; AMF_AddProp(obj, &prop); } } if (bError) return -1; return nOriginalSize - nSize; } void AMF_AddProp(AMFObject * obj, const AMFObjectProperty * prop) { if (!(obj->o_num & 0x0f)) obj->o_props = realloc(obj->o_props, (obj->o_num + 16) * sizeof(AMFObjectProperty)); obj->o_props[obj->o_num++] = *prop; } int AMF_CountProp(AMFObject * obj) { return obj->o_num; } AMFObjectProperty * AMF_GetProp(AMFObject * obj, const AVal * name, int nIndex) { if (nIndex >= 0) { if (nIndex <= obj->o_num) return &obj->o_props[nIndex]; } else { int n; for (n = 0; n < obj->o_num; n++) { if (AVMATCH(&obj->o_props[n].p_name, name)) return &obj->o_props[n]; } } return (AMFObjectProperty *) & AMFProp_Invalid; } void AMF_Dump(AMFObject * obj) { int n; Log(LOGDEBUG, "(object begin)"); for (n = 0; n < obj->o_num; n++) { AMFProp_Dump(&obj->o_props[n]); } Log(LOGDEBUG, "(object end)"); } void AMF_Reset(AMFObject * obj) { int n; for (n = 0; n < obj->o_num; n++) { AMFProp_Reset(&obj->o_props[n]); } free(obj->o_props); obj->o_props = NULL; obj->o_num = 0; } /* AMF3ClassDefinition */ void AMF3CD_AddProp(AMF3ClassDef * cd, AVal * prop) { if (!(cd->cd_num & 0x0f)) cd->cd_props = realloc(cd->cd_props, (cd->cd_num + 16) * sizeof(AVal)); cd->cd_props[cd->cd_num++] = *prop; } AVal * AMF3CD_GetProp(AMF3ClassDef * cd, int nIndex) { if (nIndex >= cd->cd_num) return (AVal *) & AV_empty; return &cd->cd_props[nIndex]; } flvstreamer/amf.h0000664000000000000000000001240711335073516013034 0ustar rootroot#ifndef __AMF_H__ #define __AMF_H__ /* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include #ifdef __cplusplus extern "C" { #endif typedef enum { AMF_NUMBER = 0, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT, AMF_MOVIECLIP, /* reserved, not used */ AMF_NULL, AMF_UNDEFINED, AMF_REFERENCE, AMF_ECMA_ARRAY, AMF_OBJECT_END, AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING, AMF_UNSUPPORTED, AMF_RECORDSET, /* reserved, not used */ AMF_XML_DOC, AMF_TYPED_OBJECT, AMF_AVMPLUS, /* switch to AMF3 */ AMF_INVALID = 0xff } AMFDataType; typedef enum { AMF3_UNDEFINED = 0, AMF3_NULL, AMF3_FALSE, AMF3_TRUE, AMF3_INTEGER, AMF3_DOUBLE, AMF3_STRING, AMF3_XML_DOC, AMF3_DATE, AMF3_ARRAY, AMF3_OBJECT, AMF3_XML, AMF3_BYTE_ARRAY } AMF3DataType; typedef struct AVal { char *av_val; int av_len; } AVal; #define AVC(str) {str,sizeof(str)-1} #define AVMATCH(a1,a2) ((a1)->av_len == (a2)->av_len && !memcmp((a1)->av_val,(a2)->av_val,(a1)->av_len)) #undef bool #undef true #undef false #define bool int #define true 1 #define false 0 struct AMFObjectProperty; typedef struct AMFObject { int o_num; struct AMFObjectProperty *o_props; } AMFObject; typedef struct AMFObjectProperty { AVal p_name; AMFDataType p_type; union { double p_number; AVal p_aval; AMFObject p_object; } p_vu; int16_t p_UTCoffset; } AMFObjectProperty; char *AMF_EncodeString(char *output, char *outend, const AVal * str); char *AMF_EncodeNumber(char *output, char *outend, double dVal); char *AMF_EncodeInt16(char *output, char *outend, short nVal); char *AMF_EncodeInt24(char *output, char *outend, int nVal); char *AMF_EncodeInt32(char *output, char *outend, int nVal); char *AMF_EncodeBoolean(char *output, char *outend, bool bVal); /* Shortcuts for AMFProp_Encode */ char *AMF_EncodeNamedString(char *output, char *outend, const AVal * name, const AVal * value); char *AMF_EncodeNamedNumber(char *output, char *outend, const AVal * name, double dVal); char *AMF_EncodeNamedBoolean(char *output, char *outend, const AVal * name, bool bVal); unsigned short AMF_DecodeInt16(const char *data); unsigned int AMF_DecodeInt24(const char *data); unsigned int AMF_DecodeInt32(const char *data); void AMF_DecodeString(const char *data, AVal * str); bool AMF_DecodeBoolean(const char *data); double AMF_DecodeNumber(const char *data); char *AMF_Encode(AMFObject * obj, char *pBuffer, char *pBufEnd); int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize, bool bDecodeName); int AMF_DecodeArray(AMFObject * obj, const char *pBuffer, int nSize, int nArrayLen, bool bDecodeName); int AMF3_Decode(AMFObject * obj, const char *pBuffer, int nSize, bool bDecodeName); void AMF_Dump(AMFObject * obj); void AMF_Reset(AMFObject * obj); void AMF_AddProp(AMFObject * obj, const AMFObjectProperty * prop); int AMF_CountProp(AMFObject * obj); AMFObjectProperty *AMF_GetProp(AMFObject * obj, const AVal * name, int nIndex); AMFDataType AMFProp_GetType(AMFObjectProperty * prop); void AMFProp_SetNumber(AMFObjectProperty * prop, double dval); void AMFProp_SetBoolean(AMFObjectProperty * prop, bool bflag); void AMFProp_SetString(AMFObjectProperty * prop, AVal * str); void AMFProp_SetObject(AMFObjectProperty * prop, AMFObject * obj); void AMFProp_GetName(AMFObjectProperty * prop, AVal * name); void AMFProp_SetName(AMFObjectProperty * prop, AVal * name); double AMFProp_GetNumber(AMFObjectProperty * prop); bool AMFProp_GetBoolean(AMFObjectProperty * prop); void AMFProp_GetString(AMFObjectProperty * prop, AVal * str); void AMFProp_GetObject(AMFObjectProperty * prop, AMFObject * obj); bool AMFProp_IsValid(AMFObjectProperty * prop); char *AMFProp_Encode(AMFObjectProperty * prop, char *pBuffer, char *pBufEnd); int AMF3Prop_Decode(AMFObjectProperty * prop, const char *pBuffer, int nSize, bool bDecodeName); int AMFProp_Decode(AMFObjectProperty * prop, const char *pBuffer, int nSize, bool bDecodeName); void AMFProp_Dump(AMFObjectProperty * prop); void AMFProp_Reset(AMFObjectProperty * prop); typedef struct AMF3ClassDef { AVal cd_name; char cd_externalizable; char cd_dynamic; int cd_num; AVal *cd_props; } AMF3ClassDef; void AMF3CD_AddProp(AMF3ClassDef * cd, AVal * prop); AVal *AMF3CD_GetProp(AMF3ClassDef * cd, int idx); #ifdef __cplusplus } #endif #endif /* __AMF_H__ */ flvstreamer/flvstreamer.c0000664000000000000000000014034011335075432014613 0ustar rootroot/* flvstreamer * 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include // to catch Ctrl-C #include #include "rtmp.h" #include "log.h" #include "parseurl.h" #ifdef WIN32 #define fseeko fseeko64 #define ftello ftello64 #include #include #define SET_BINMODE(f) setmode(fileno(f), O_BINARY) #else #define SET_BINMODE(f) #endif #define RD_SUCCESS 0 #define RD_FAILED 1 #define RD_INCOMPLETE 2 // starts sockets bool InitSockets() { #ifdef WIN32 WORD version; WSADATA wsaData; version = MAKEWORD(1, 1); return (WSAStartup(version, &wsaData) == 0); #else return true; #endif } inline void CleanupSockets() { #ifdef WIN32 WSACleanup(); #endif } #ifdef _DEBUG uint32_t debugTS = 0; int pnum = 0; FILE *netstackdump = 0; FILE *netstackdump_read = 0; #endif uint32_t nIgnoredFlvFrameCounter = 0; uint32_t nIgnoredFrameCounter = 0; #define MAX_IGNORED_FRAMES 50 FILE *file = 0; void sigIntHandler(int sig) { RTMP_ctrlC = true; LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig); // ignore all these signals now and let the connection close signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN); #ifndef WIN32 signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); signal(SIGQUIT, SIG_IGN); #endif } int WriteHeader(char **buf, // target pointer, maybe preallocated unsigned int len // length of buffer if preallocated ) { 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 }; unsigned int size = sizeof(flvHeader); if (size > len) { *buf = (char *) realloc(*buf, size); if (*buf == 0) { Log(LOGERROR, "Couldn't reallocate memory!"); return -1; // fatal error } } memcpy(*buf, flvHeader, sizeof(flvHeader)); return size; } static const AVal av_onMetaData = AVC("onMetaData"); static const AVal av_duration = AVC("duration"); // Returns -3 if Play.Close/Stop, -2 if fatal error, -1 if no more media packets, 0 if ignorable error, >0 if there is a media packet int WriteStream(RTMP * rtmp, char **buf, // target pointer, maybe preallocated unsigned int len, // length of buffer if preallocated uint32_t * tsm, // pointer to timestamp, will contain timestamp of last video packet returned bool bResume, // resuming mode, will not write FLV header and compare metaHeader and first kexframe bool bLiveStream, // live mode, will not report absolute timestamps uint32_t nResumeTS, // resume keyframe timestamp char *metaHeader, // pointer to meta header (if bResume == TRUE) uint32_t nMetaHeaderSize, // length of meta header, if zero meta header check omitted (if bResume == TRUE) char *initialFrame, // pointer to initial keyframe (no FLV header or tagSize, raw data) (if bResume == TRUE) uint8_t initialFrameType, // initial frame type (audio or video) uint32_t nInitialFrameSize, // length of initial frame in bytes, if zero initial frame check omitted (if bResume == TRUE) uint8_t * dataType // whenever we get a video/audio packet we set an appropriate flag here, this will be later written to the FLV header ) { static bool bStopIgnoring = false; static bool bFoundKeyframe = false; static bool bFoundFlvKeyframe = false; uint32_t prevTagSize = 0; int rtnGetNextMediaPacket = 0, ret = -1; RTMPPacket packet = { 0 }; rtnGetNextMediaPacket = RTMP_GetNextMediaPacket(rtmp, &packet); while (rtnGetNextMediaPacket) { char *packetBody = packet.m_body; unsigned int nPacketLen = packet.m_nBodySize; // Return -3 if this was completed nicely with invoke message Play.Stop or Play.Complete if (rtnGetNextMediaPacket == 2) { Log(LOGDEBUG, "Got Play.Complete or Play.Stop from server. Assuming stream is complete"); ret = -3; break; } // skip video info/command packets if (packet.m_packetType == 0x09 && nPacketLen == 2 && ((*packetBody & 0xf0) == 0x50)) { ret = 0; break; } if (packet.m_packetType == 0x09 && nPacketLen <= 5) { Log(LOGWARNING, "ignoring too small video packet: size: %d", nPacketLen); ret = 0; break; } if (packet.m_packetType == 0x08 && nPacketLen <= 1) { Log(LOGWARNING, "ignoring too small audio packet: size: %d", nPacketLen); ret = 0; break; } #ifdef _DEBUG Log(LOGDEBUG, "type: %02X, size: %d, TS: %d ms, abs TS: %d", packet.m_packetType, nPacketLen, packet.m_nTimeStamp, packet.m_hasAbsTimestamp); if (packet.m_packetType == 0x09) Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0)); #endif // check the header if we get one if (bResume && packet.m_nTimeStamp == 0) { if (nMetaHeaderSize > 0 && packet.m_packetType == 0x12) { AMFObject metaObj; int nRes = AMF_Decode(&metaObj, packetBody, nPacketLen, false); if (nRes >= 0) { AVal metastring; AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring); if (AVMATCH(&metastring, &av_onMetaData)) { // compare if ((nMetaHeaderSize != nPacketLen) || (memcmp(metaHeader, packetBody, nMetaHeaderSize) != 0)) { ret = -2; } } AMF_Reset(&metaObj); if (ret == -2) break; } } // check first keyframe to make sure we got the right position in the stream! // (the first non ignored frame) if (nInitialFrameSize > 0) { // video or audio data if (packet.m_packetType == initialFrameType && nInitialFrameSize == nPacketLen) { // we don't compare the sizes since the packet can contain several FLV packets, just make // sure the first frame is our keyframe (which we are going to rewrite) if (memcmp(initialFrame, packetBody, nInitialFrameSize) == 0) { Log(LOGDEBUG, "Checked keyframe successfully!"); bFoundKeyframe = true; ret = 0; // ignore it! (what about audio data after it? it is handled by ignoring all 0ms frames, see below) break; } } // hande FLV streams, even though the server resends the keyframe as an extra video packet // it is also included in the first FLV stream chunk and we have to compare it and // filter it out !! // if (packet.m_packetType == 0x16) { // basically we have to find the keyframe with the correct TS being nResumeTS unsigned int pos = 0; uint32_t ts = 0; while (pos + 11 < nPacketLen) { uint32_t dataSize = AMF_DecodeInt24(packetBody + pos + 1); // size without header (11) and prevTagSize (4) ts = AMF_DecodeInt24(packetBody + pos + 4); ts |= (packetBody[pos + 7] << 24); #ifdef _DEBUG Log(LOGDEBUG, "keyframe search: FLV Packet: type %02X, dataSize: %d, timeStamp: %d ms", packetBody[pos], dataSize, ts); #endif // ok, is it a keyframe!!!: well doesn't work for audio! if (packetBody[pos /*6928, test 0 */ ] == initialFrameType /* && (packetBody[11]&0xf0) == 0x10 */ ) { if (ts == nResumeTS) { Log(LOGDEBUG, "Found keyframe with resume-keyframe timestamp!"); if (nInitialFrameSize != dataSize || memcmp(initialFrame, packetBody + pos + 11, nInitialFrameSize) != 0) { Log(LOGERROR, "FLV Stream: Keyframe doesn't match!"); ret = -2; break; } bFoundFlvKeyframe = true; // ok, skip this packet // check whether skipable: if (pos + 11 + dataSize + 4 > nPacketLen) { Log(LOGWARNING, "Non skipable packet since it doesn't end with chunk, stream corrupt!"); ret = -2; break; } packetBody += (pos + 11 + dataSize + 4); nPacketLen -= (pos + 11 + dataSize + 4); goto stopKeyframeSearch; } else if (nResumeTS < ts) { goto stopKeyframeSearch; // the timestamp ts will only increase with further packets, wait for seek } } pos += (11 + dataSize + 4); } if (ts < nResumeTS) { Log(LOGERROR, "First packet does not contain keyframe, all timestamps are smaller than the keyframe timestamp, so probably the resume seek failed?"); } stopKeyframeSearch: ; if (!bFoundFlvKeyframe) { Log(LOGERROR, "Couldn't find the seeked keyframe in this chunk!"); ret = 0; break; } } } } if (bResume && packet.m_nTimeStamp > 0 && (bFoundFlvKeyframe || bFoundKeyframe)) { // another problem is that the server can actually change from 09/08 video/audio packets to an FLV stream // or vice versa and our keyframe check will prevent us from going along with the new stream if we resumed // // in this case set the 'found keyframe' variables to true // We assume that if we found one keyframe somewhere and were already beyond TS > 0 we have written // data to the output which means we can accept all forthcoming data inclusing the change between 08/09 <-> FLV // packets bFoundFlvKeyframe = true; bFoundKeyframe = true; } // skip till we find out keyframe (seeking might put us somewhere before it) if (bResume && !bFoundKeyframe && packet.m_packetType != 0x16) { Log(LOGWARNING, "Stream does not start with requested frame, ignoring data... "); nIgnoredFrameCounter++; if (nIgnoredFrameCounter > MAX_IGNORED_FRAMES) ret = -2; // fatal error, couldn't continue stream else ret = 0; break; } // ok, do the same for FLV streams if (bResume && !bFoundFlvKeyframe && packet.m_packetType == 0x16) { Log(LOGWARNING, "Stream does not start with requested FLV frame, ignoring data... "); nIgnoredFlvFrameCounter++; if (nIgnoredFlvFrameCounter > MAX_IGNORED_FRAMES) ret = -2; else ret = 0; break; } // if bResume, we continue a stream, we have to ignore the 0ms frames since these are the first keyframes, we've got these // so don't mess around with multiple copies sent by the server to us! (if the keyframe is found at a later position // there is only one copy and it will be ignored by the preceding if clause) if (!bStopIgnoring && bResume && packet.m_packetType != 0x16) { // exclude type 0x16 (FLV) since it can conatin several FLV packets if (packet.m_nTimeStamp == 0) { ret = 0; break; } else { bStopIgnoring = true; // stop ignoring packets } } // calculate packet size and reallocate buffer if necessary unsigned int size = nPacketLen + ((packet.m_packetType == 0x08 || packet.m_packetType == 0x09 || packet.m_packetType == 0x12) ? 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) { Log(LOGERROR, "Couldn't reallocate memory!"); ret = -1; // fatal error break; } } char *ptr = *buf, *pend = ptr+size+4; uint32_t nTimeStamp = 0; // use to return timestamp of last processed packet // audio (0x08), video (0x09) or metadata (0x12) packets : // construct 11 byte header then add rtmp packet's data if (packet.m_packetType == 0x08 || packet.m_packetType == 0x09 || packet.m_packetType == 0x12) { // set data type *dataType |= (((packet.m_packetType == 0x08) << 2) | (packet.m_packetType == 0x09)); nTimeStamp = nResumeTS + packet.m_nTimeStamp; prevTagSize = 11 + nPacketLen; *ptr = packet.m_packetType; ptr++; ptr = AMF_EncodeInt24(ptr, pend, nPacketLen); /*if(packet.m_packetType == 0x09) { // video // H264 fix: if((packetBody[0] & 0x0f) == 7) { // CodecId = H264 uint8_t packetType = *(packetBody+1); uint32_t ts = AMF_DecodeInt24(packetBody+2); // composition time int32_t cts = (ts+0xff800000)^0xff800000; Log(LOGDEBUG, "cts : %d\n", cts); nTimeStamp -= cts; // get rid of the composition time CRTMP::EncodeInt24(packetBody+2, 0); } Log(LOGDEBUG, "VIDEO: nTimeStamp: 0x%08X (%d)\n", nTimeStamp, nTimeStamp); } */ 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 == 0x16) { 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); /* CRTMP::EncodeInt24(ptr+pos+4, nTimeStamp); ptr[pos+7] = (nTimeStamp>>24)&0xff;// */ // set data type *dataType |= (((*(packetBody + pos) == 0x08) << 2) | (*(packetBody + pos) == 0x09)); if (pos + 11 + dataSize + 4 > nPacketLen) { if (pos + 11 + dataSize > nPacketLen) { Log(LOGERROR, "Wrong data size (%lu), stream corrupted, aborting!", dataSize); ret = -2; break; } Log(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 Log(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 Log(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 != 0x16) { // FLV tag packets contain their own prevTagSize AMF_EncodeInt32(ptr, pend, prevTagSize); //ptr += 4; } // In non-live this nTimeStamp can contain an absolute TS. // Update ext timestamp with this absolute offset in non-live mode otherwise report the relative one // LogPrintf("\nDEBUG: type: %02X, size: %d, pktTS: %dms, TS: %dms, bLiveStream: %d", packet.m_packetType, nPacketLen, packet.m_nTimeStamp, nTimeStamp, bLiveStream); if (tsm) *tsm = bLiveStream ? packet.m_nTimeStamp : nTimeStamp; ret = size; break; } if (rtnGetNextMediaPacket) RTMPPacket_Free(&packet); return ret; // no more media packets } int OpenResumeFile(const char *flvFile, // file name [in] FILE ** file, // opened file [out] off_t * size, // size of the file [out] char **metaHeader, // meta data read from the file [out] uint32_t * nMetaHeaderSize, // length of metaHeader [out] double *duration) // duration of the stream in ms [out] { size_t bufferSize = 0; char hbuf[16], *buffer = NULL; *nMetaHeaderSize = 0; *size = 0; *file = fopen(flvFile, "r+b"); if (!*file) return RD_SUCCESS; // RD_SUCCESS, because we go to fresh file mode instead of quiting fseek(*file, 0, SEEK_END); *size = ftello(*file); fseek(*file, 0, SEEK_SET); if (*size > 0) { // verify FLV format and read header uint32_t prevTagSize = 0; // check we've got a valid FLV file to continue! if (fread(hbuf, 1, 13, *file) != 13) { Log(LOGERROR, "Couldn't read FLV file header!"); return RD_FAILED; } if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V' || hbuf[3] != 0x01) { Log(LOGERROR, "Invalid FLV file!"); return RD_FAILED; } if ((hbuf[4] & 0x05) == 0) { Log(LOGERROR, "FLV file contains neither video nor audio, aborting!"); return RD_FAILED; } uint32_t dataOffset = AMF_DecodeInt32(hbuf + 5); fseek(*file, dataOffset, SEEK_SET); if (fread(hbuf, 1, 4, *file) != 4) { Log(LOGERROR, "Invalid FLV file: missing first prevTagSize!"); return RD_FAILED; } prevTagSize = AMF_DecodeInt32(hbuf); if (prevTagSize != 0) { Log(LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize); } // go through the file to find the meta data! off_t pos = dataOffset + 4; bool bFoundMetaHeader = false; while (pos < *size - 4 && !bFoundMetaHeader) { fseeko(*file, pos, SEEK_SET); if (fread(hbuf, 1, 4, *file) != 4) break; uint32_t dataSize = AMF_DecodeInt24(hbuf + 1); if (hbuf[0] == 0x12) { if (dataSize > bufferSize) { /* round up to next page boundary */ bufferSize = dataSize + 4095; bufferSize ^= (bufferSize & 4095); free(buffer); buffer = malloc(bufferSize); if (!buffer) return RD_FAILED; } fseeko(*file, pos + 11, SEEK_SET); if (fread(buffer, 1, dataSize, *file) != dataSize) break; AMFObject metaObj; int nRes = AMF_Decode(&metaObj, buffer, dataSize, false); if (nRes < 0) { Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__); break; } AVal metastring; AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring); if (AVMATCH(&metastring, &av_onMetaData)) { AMF_Dump(&metaObj); *nMetaHeaderSize = dataSize; if (*metaHeader) free(*metaHeader); *metaHeader = (char *) malloc(*nMetaHeaderSize); memcpy(*metaHeader, buffer, *nMetaHeaderSize); // get duration AMFObjectProperty prop; if (RTMP_FindFirstMatchingProperty (&metaObj, &av_duration, &prop)) { *duration = AMFProp_GetNumber(&prop); Log(LOGDEBUG, "File has duration: %f", *duration); } bFoundMetaHeader = true; break; } //metaObj.Reset(); //delete obj; } pos += (dataSize + 11 + 4); } free(buffer); if (!bFoundMetaHeader) Log(LOGWARNING, "Couldn't locate meta data!"); } return RD_SUCCESS; } int GetLastKeyframe(FILE * file, // output file [in] int nSkipKeyFrames, // max number of frames to skip when searching for key frame [in] uint32_t * dSeek, // offset of the last key frame [out] char **initialFrame, // content of the last keyframe [out] int *initialFrameType, // initial frame type (audio/video) [out] uint32_t * nInitialFrameSize) // length of initialFrame [out] { const size_t bufferSize = 16; char buffer[bufferSize]; uint8_t dataType; bool bAudioOnly; off_t size; fseek(file, 0, SEEK_END); size = ftello(file); fseek(file, 4, SEEK_SET); if (fread(&dataType, sizeof(uint8_t), 1, file) != 1) return RD_FAILED; bAudioOnly = (dataType & 0x4) && !(dataType & 0x1); Log(LOGDEBUG, "bAudioOnly: %d, size: %llu", bAudioOnly, (unsigned long long) size); // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) //if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames //{ // find the last seekable frame off_t tsize = 0; uint32_t prevTagSize = 0; // go through the file and find the last video keyframe do { int xread; skipkeyframe: if (size - tsize < 13) { Log(LOGERROR, "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0"); return RD_FAILED; } fseeko(file, size - tsize - 4, SEEK_SET); xread = fread(buffer, 1, 4, file); if (xread != 4) { Log(LOGERROR, "Couldn't read prevTagSize from file!"); return RD_FAILED; } prevTagSize = AMF_DecodeInt32(buffer); //Log(LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize); if (prevTagSize == 0) { Log(LOGERROR, "Couldn't find keyframe to resume from!"); return RD_FAILED; } if (prevTagSize < 0 || prevTagSize > size - 4 - 13) { Log(LOGERROR, "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!", prevTagSize); return RD_FAILED; } tsize += prevTagSize + 4; // read header fseeko(file, size - tsize, SEEK_SET); if (fread(buffer, 1, 12, file) != 12) { Log(LOGERROR, "Couldn't read header!"); return RD_FAILED; } //* #ifdef _DEBUG uint32_t ts = AMF_DecodeInt24(buffer + 4); ts |= (buffer[7] << 24); Log(LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts); #endif //*/ // this just continues the loop whenever the number of skipped frames is > 0, // so we look for the next keyframe to continue with // // this helps if resuming from the last keyframe fails and one doesn't want to start // the download from the beginning // if (nSkipKeyFrames > 0 && !(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10))) { #ifdef _DEBUG Log(LOGDEBUG, "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!"); #endif nSkipKeyFrames--; goto skipkeyframe; } } while ((bAudioOnly && buffer[0] != 0x08) || (!bAudioOnly && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10))); // as long as we don't have a keyframe / last audio frame // save keyframe to compare/find position in stream *initialFrameType = buffer[0]; *nInitialFrameSize = prevTagSize - 11; *initialFrame = (char *) malloc(*nInitialFrameSize); fseeko(file, size - tsize + 11, SEEK_SET); if (fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize) { Log(LOGERROR, "Couldn't read last keyframe, aborting!"); return RD_FAILED; } *dSeek = AMF_DecodeInt24(buffer + 4); // set seek position to keyframe tmestamp *dSeek |= (buffer[7] << 24); //} //else // handle audio only, we can seek anywhere we'd like //{ //} if (*dSeek < 0) { Log(LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!"); return RD_FAILED; } Log(LOGDEBUG, "Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek, *nInitialFrameSize, *initialFrameType); /* // now read the timestamp of the frame before the seekable keyframe: fseeko(file, size-tsize-4, SEEK_SET); if(fread(buffer, 1, 4, file) != 4) { Log(LOGERROR, "Couldn't read prevTagSize from file!"); goto start; } uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer); fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET); if(fread(buffer, 1, 4, file) != 4) { Log(LOGERROR, "Couldn't read previous timestamp!"); goto start; } uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer); timestamp |= (buffer[3]<<24); Log(LOGDEBUG, "Previous timestamp: %d ms", timestamp); */ if (*dSeek != 0) { // seek to position after keyframe in our file (we will ignore the keyframes resent by the server // since they are sent a couple of times and handling this would be a mess) fseeko(file, size - tsize + prevTagSize + 4, SEEK_SET); // make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets // (including several meta data headers and the keyframe we seeked to) //bNoHeader = true; if bResume==true this is true anyway } //} return RD_SUCCESS; } int Download(RTMP * rtmp, // connected RTMP object FILE * file, uint32_t dSeek, uint32_t dLength, double duration, bool bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, bool bStdoutMode, bool bLiveStream, bool bHashes, bool bOverrideBufferTime, uint32_t bufferTime, double *percent) // percentage downloaded [out] { uint32_t timestamp = dSeek; int32_t now, lastUpdate; uint8_t dataType = 0; // will be written into the FLV header (position 4) int bufferSize = 1024 * 1024; char *buffer = (char *) malloc(bufferSize); int nRead = 0; off_t size = ftello(file); unsigned long lastPercent = 0; memset(buffer, 0, bufferSize); *percent = 0.0; if (timestamp) { Log(LOGDEBUG, "Continuing at TS: %d ms\n", timestamp); } if (bLiveStream) { LogPrintf("Starting Live Stream\n"); } else { // print initial status // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded if (duration > 0) { if ((double) timestamp >= (double) duration * 999.0) { LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n", (double) timestamp / 1000.0, (double) duration / 1000.0); return RD_SUCCESS; } else { *percent = ((double) timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n", bResume ? "Resuming" : "Starting", (double) size / 1024.0, (double) timestamp / 1000.0, *percent); } } else { LogPrintf("%s download at: %.3f kB\n", bResume ? "Resuming" : "Starting", (double) size / 1024.0); } } if (dLength > 0) LogPrintf("For duration: %.3f sec\n", (double) dLength / 1000.0); // write FLV header if not resuming if (!bResume) { nRead = WriteHeader(&buffer, bufferSize); if (nRead > 0) { if (fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t) nRead) { Log(LOGERROR, "%s: Failed writing FLV header, exiting!", __FUNCTION__); free(buffer); return RD_FAILED; } size += nRead; } else { Log(LOGERROR, "Couldn't obtain FLV header, exiting!"); free(buffer); return RD_FAILED; } } now = RTMP_GetTime(); lastUpdate = now - 1000; do { nRead = WriteStream(rtmp, &buffer, bufferSize, ×tamp, bResume && nInitialFrameSize > 0, bLiveStream, dSeek, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, &dataType); //LogPrintf("nRead: %d\n", nRead); if (nRead > 0) { if (fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t) nRead) { Log(LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__); free(buffer); return RD_FAILED; } size += nRead; //LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0); if (duration <= 0) // if duration unknown try to get it from the stream (onMetaData) duration = RTMP_GetDuration(rtmp); if (duration > 0) { // make sure we claim to have enough buffer time! if (!bOverrideBufferTime && bufferTime < (duration * 1000.0)) { bufferTime = (uint32_t) (duration * 1000.0) + 5000; // extra 5sec to make sure we've got enough Log(LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime); RTMP_SetBufferMS(rtmp, bufferTime); RTMP_UpdateBufferMS(rtmp); } *percent = ((double) timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; if (bHashes) { if (lastPercent + 1 <= *percent) { LogStatus("#"); lastPercent = (unsigned long) *percent; } } else { now = RTMP_GetTime(); if (abs(now - lastUpdate) > 200) { LogStatus("\r%.3f kB / %.2f sec (%.1f%%)", (double) size / 1024.0, (double) (timestamp) / 1000.0, *percent); lastUpdate = now; } } } else { now = RTMP_GetTime(); if (abs(now - lastUpdate) > 200) { if (bHashes) LogStatus("#"); else LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0, (double) (timestamp) / 1000.0); lastUpdate = now; } } } #ifdef _DEBUG else { Log(LOGDEBUG, "zero read!"); } #endif } while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp)); free(buffer); Log(LOGDEBUG, "WriteStream returned: %d", nRead); if (bResume && nRead == -2) { LogPrintf("Couldn't resume FLV file, try --skip %d\n\n", nSkipKeyFrames + 1); return RD_FAILED; } // finalize header by writing the correct dataType (video, audio, video+audio) if (!bResume && dataType != 0x5 && !bStdoutMode) { //Log(LOGDEBUG, "Writing data type: %02X", dataType); fseek(file, 4, SEEK_SET); fwrite(&dataType, sizeof(unsigned char), 1, file); /* resume uses ftell to see where we left off */ fseek(file, 0, SEEK_END); } if (nRead == -3) return RD_SUCCESS; if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0 || RTMP_IsTimedout(rtmp)) { return RD_INCOMPLETE; } return RD_SUCCESS; } #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val) int parseAMF(AMFObject *obj, const char *arg, int *depth) { AMFObjectProperty prop = {{0,0}}; int i; char *p; if (arg[1] == ':') { p = (char *)arg+2; switch(arg[0]) { case 'B': prop.p_type = AMF_BOOLEAN; prop.p_vu.p_number = atoi(p); break; case 'S': prop.p_type = AMF_STRING; STR2AVAL(prop.p_vu.p_aval,p); break; case 'N': prop.p_type = AMF_NUMBER; prop.p_vu.p_number = strtod(p, NULL); break; case 'Z': prop.p_type = AMF_NULL; break; case 'O': i = atoi(p); if (i) { prop.p_type = AMF_OBJECT; } else { (*depth)--; return 0; } break; default: return -1; } } else if (arg[2] == ':' && arg[0] == 'N') { p = strchr(arg+3, ':'); if (!p || !*depth) return -1; prop.p_name.av_val = (char *)arg+3; prop.p_name.av_len = p - (arg+3); p++; switch(arg[1]) { case 'B': prop.p_type = AMF_BOOLEAN; prop.p_vu.p_number = atoi(p); break; case 'S': prop.p_type = AMF_STRING; STR2AVAL(prop.p_vu.p_aval,p); break; case 'N': prop.p_type = AMF_NUMBER; prop.p_vu.p_number = strtod(p, NULL); break; case 'O': prop.p_type = AMF_OBJECT; break; default: return -1; } } else return -1; if (*depth) { AMFObject *o2; for (i=0; i<*depth; i++) { o2 = &obj->o_props[obj->o_num-1].p_vu.p_object; obj = o2; } } AMF_AddProp(obj, &prop); if (prop.p_type == AMF_OBJECT) (*depth)++; return 0; } int main(int argc, char **argv) { extern char *optarg; int nStatus = RD_SUCCESS; double percent = 0; double duration = 0.0; int nSkipKeyFrames = 0; // skip this number of keyframes when resuming bool bOverrideBufferTime = false; // if the user specifies a buffer time override this is true bool bStdoutMode = true; // if true print the stream directly to stdout, messages go to stderr bool bResume = false; // true in resume mode uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise uint32_t bufferTime = 10 * 60 * 60 * 1000; // 10 hours as default // meta header and initial frame for the resume mode (they are read from the file and compared with // the stream we are trying to continue char *metaHeader = 0; uint32_t nMetaHeaderSize = 0; // video keyframe for matching char *initialFrame = 0; uint32_t nInitialFrameSize = 0; int initialFrameType = 0; // tye: audio or video char *hostname = 0; AVal playpath = { 0, 0 }; AVal subscribepath = { 0, 0 }; int port = -1; int protocol = RTMP_PROTOCOL_UNDEFINED; int retries = 0; bool bLiveStream = false; // is it a live stream? then we can't seek/resume bool bHashes = false; // display byte counters not hashes by default long int timeout = 120; // timeout connection after 120 seconds uint32_t dStartOffset = 0; // seek position in non-live mode uint32_t dStopOffset = 0; uint32_t dLength = 0; // length to play from stream - calculated from seek position and dStopOffset char *rtmpurl = 0; AVal swfUrl = { 0, 0 }; AVal tcUrl = { 0, 0 }; AVal pageUrl = { 0, 0 }; AVal app = { 0, 0 }; AVal auth = { 0, 0 }; AVal swfHash = { 0, 0 }; uint32_t swfSize = 0; AVal flashVer = { 0, 0 }; AVal token = { 0, 0 }; char *sockshost = 0; AMFObject extras = {0}; int edepth = 0; char *flvFile = 0; #undef OSS #ifdef WIN32 #define OSS "WIN" #else #define OSS "LNX" #endif char DEFAULT_FLASH_VER[] = OSS " 10,0,22,87"; signal(SIGINT, sigIntHandler); signal(SIGTERM, sigIntHandler); #ifndef WIN32 signal(SIGHUP, sigIntHandler); signal(SIGPIPE, sigIntHandler); signal(SIGQUIT, sigIntHandler); #endif // Check for --quiet option before printing any output int index = 0; while (index < argc) { if (strcmp(argv[index], "--quiet") == 0 || strcmp(argv[index], "-q") == 0) debuglevel = LOGCRIT; index++; } LogPrintf("FLVStreamer %s\n", FLVSTREAMER_VERSION); LogPrintf ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n"); if (!InitSockets()) { Log(LOGERROR, "Couldn't load sockets support on your platform, exiting!"); return RD_FAILED; } /* sleep(30); */ int opt; struct option longopts[] = { {"help", 0, NULL, 'h'}, {"host", 1, NULL, 'n'}, {"port", 1, NULL, 'c'}, {"socks", 1, NULL, 'S'}, {"protocol", 1, NULL, 'l'}, {"playpath", 1, NULL, 'y'}, {"rtmp", 1, NULL, 'r'}, {"swfUrl", 1, NULL, 's'}, {"tcUrl", 1, NULL, 't'}, {"pageUrl", 1, NULL, 'p'}, {"app", 1, NULL, 'a'}, {"auth", 1, NULL, 'u'}, {"conn", 1, NULL, 'C'}, {"flashVer", 1, NULL, 'f'}, {"live", 0, NULL, 'v'}, {"flv", 1, NULL, 'o'}, {"resume", 0, NULL, 'e'}, {"timeout", 1, NULL, 'm'}, {"buffer", 1, NULL, 'b'}, {"skip", 1, NULL, 'k'}, {"subscribe", 1, NULL, 'd'}, {"start", 1, NULL, 'A'}, {"stop", 1, NULL, 'B'}, {"token", 1, NULL, 'T'}, {"hashes", 0, NULL, '#'}, {"debug", 0, NULL, 'z'}, {"quiet", 0, NULL, 'q'}, {"verbose", 0, NULL, 'V'}, {0, 0, 0, 0} }; while ((opt = getopt_long(argc, argv, "hVveqzr:s:t:p:a:b:f:o:u:C:n:c:l:y:m:k:d:A:B:T:w:x:W:X:S:#", longopts, NULL)) != -1) { switch (opt) { case 'h': LogPrintf ("\nThis program dumps the media content streamed over rtmp.\n\n"); LogPrintf("--help|-h Prints this help screen.\n"); LogPrintf ("--rtmp|-r url URL (e.g. rtmp//hotname[:port]/path)\n"); LogPrintf ("--host|-n hostname Overrides the hostname in the rtmp url\n"); LogPrintf ("--port|-c port Overrides the port in the rtmp url\n"); LogPrintf ("--socks|-S host:port Use the specified SOCKS proxy\n"); LogPrintf ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 3 - RTMPE)\n"); LogPrintf ("--playpath|-y Overrides the playpath parsed from rtmp url\n"); LogPrintf("--swfUrl|-s url URL to player swf file\n"); LogPrintf ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n"); LogPrintf("--pageUrl|-p url Web URL of played programme\n"); LogPrintf("--app|-a app Name of player used\n"); LogPrintf ("--auth|-u string Authentication string to be appended to the connect string\n"); LogPrintf ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n"); LogPrintf (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n"); LogPrintf (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n"); LogPrintf ("--flashVer|-f string Flash version string (default: \"%s\")\n", DEFAULT_FLASH_VER); LogPrintf ("--live|-v Save a live stream, no --resume (seeking) of live streams possible\n"); LogPrintf ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n"); LogPrintf ("--flv|-o string FLV output file name, if the file name is - print stream to stdout\n"); LogPrintf ("--resume|-e Resume a partial RTMP download\n"); LogPrintf ("--timeout|-m num Timeout connection num seconds (default: %lu)\n", timeout); LogPrintf ("--start|-A num Start at num seconds into stream (not valid when using --live)\n"); LogPrintf ("--stop|-B num Stop at num seconds into stream\n"); LogPrintf ("--token|-T key Key for SecureToken response\n"); LogPrintf ("--hashes|-# Display progress with hashes, not with the byte counter\n"); LogPrintf ("--buffer|-b Buffer time in milliseconds (default: %lu), this option makes only sense in stdout mode (-o -)\n", bufferTime); LogPrintf ("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n", nSkipKeyFrames); LogPrintf ("--quiet|-q Supresses all command output.\n"); LogPrintf("--verbose|-V Verbose command output.\n"); LogPrintf("--debug|-z Debug level command output.\n"); LogPrintf ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect "); LogPrintf("packet.\n\n"); return RD_SUCCESS; case 'k': nSkipKeyFrames = atoi(optarg); if (nSkipKeyFrames < 0) { Log(LOGERROR, "Number of keyframes skipped must be greater or equal zero, using zero!"); nSkipKeyFrames = 0; } else { Log(LOGDEBUG, "Number of skipped key frames for resume: %d", nSkipKeyFrames); } break; case 'b': { int32_t bt = atol(optarg); if (bt < 0) { Log(LOGERROR, "Buffer time must be greater than zero, ignoring the specified value %d!", bt); } else { bufferTime = bt; bOverrideBufferTime = true; } break; } case 'v': bLiveStream = true; // no seeking or resuming possible! break; case 'd': STR2AVAL(subscribepath, optarg); break; case 'n': hostname = optarg; break; case 'c': port = atoi(optarg); break; case 'l': protocol = atoi(optarg); if (protocol != RTMP_PROTOCOL_RTMP && protocol != RTMP_PROTOCOL_RTMPE) { Log(LOGERROR, "Unknown protocol specified: %d", protocol); return RD_FAILED; } break; case 'y': STR2AVAL(playpath, optarg); break; case 'r': { rtmpurl = optarg; char *parsedHost = 0; unsigned int parsedPort = 0; char *parsedPlaypath = 0; char *parsedApp = 0; int parsedProtocol = RTMP_PROTOCOL_UNDEFINED; if (!ParseUrl (rtmpurl, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) { Log(LOGWARNING, "Couldn't parse the specified url (%s)!", optarg); } else { if (hostname == 0) hostname = parsedHost; if (port == -1) port = parsedPort; if (playpath.av_len == 0 && parsedPlaypath) { STR2AVAL(playpath, parsedPlaypath); } if (protocol == RTMP_PROTOCOL_UNDEFINED) protocol = parsedProtocol; if (app.av_len == 0 && parsedApp) { STR2AVAL(app, parsedApp); } } break; } case 's': STR2AVAL(swfUrl, optarg); break; case 't': STR2AVAL(tcUrl, optarg); break; case 'p': STR2AVAL(pageUrl, optarg); break; case 'a': STR2AVAL(app, optarg); break; case 'f': STR2AVAL(flashVer, optarg); break; case 'o': flvFile = optarg; if (strcmp(flvFile, "-")) bStdoutMode = false; break; case 'e': bResume = true; break; case 'u': STR2AVAL(auth, optarg); break; case 'C': if (parseAMF(&extras, optarg, &edepth)) { Log(LOGERROR, "Invalid AMF parameter: %s", optarg); return RD_FAILED; } break; case 'm': timeout = atoi(optarg); break; case 'A': dStartOffset = (int) (atof(optarg) * 1000.0); break; case 'B': dStopOffset = (int) (atof(optarg) * 1000.0); break; case 'T': STR2AVAL(token, optarg); break; case '#': bHashes = true; break; case 'q': debuglevel = LOGCRIT; break; case 'V': debuglevel = LOGDEBUG; break; case 'z': debuglevel = LOGALL; break; case 'S': sockshost = optarg; break; default: LogPrintf("unknown option: %c\n", opt); break; } } if (hostname == 0) { Log(LOGERROR, "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname"); return RD_FAILED; } if (playpath.av_len == 0) { Log(LOGERROR, "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath"); return RD_FAILED; } if (port == -1) { Log(LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port 1935"); port = 1935; } if (port == 0) { port = 1935; } if (protocol == RTMP_PROTOCOL_UNDEFINED) { Log(LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP"); protocol = RTMP_PROTOCOL_RTMP; } if (flvFile == 0) { Log(LOGWARNING, "You haven't specified an output file (-o filename), using stdout"); bStdoutMode = true; } if (bStdoutMode && bResume) { Log(LOGWARNING, "Can't resume in stdout mode, ignoring --resume option"); bResume = false; } if (bLiveStream && bResume) { Log(LOGWARNING, "Can't resume live stream, ignoring --resume option"); bResume = false; } if (flashVer.av_len == 0) { STR2AVAL(flashVer, DEFAULT_FLASH_VER); } if (tcUrl.av_len == 0 && app.av_len != 0) { char str[512] = { 0 }; snprintf(str, 511, "%s://%s:%d/%s", RTMPProtocolStringsLower[protocol], hostname, port, app.av_val); tcUrl.av_len = strlen(str); tcUrl.av_val = (char *) malloc(tcUrl.av_len + 1); strcpy(tcUrl.av_val, str); } int first = 1; // User defined seek offset if (dStartOffset > 0) { // Live stream if (bLiveStream) { Log(LOGWARNING, "Can't seek in a live stream, ignoring --start option"); dStartOffset = 0; } } RTMP rtmp = { 0 }; RTMP_Init(&rtmp); RTMP_SetupStream(&rtmp, protocol, hostname, port, sockshost, &playpath, &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, &flashVer, &subscribepath, dSeek, 0, bLiveStream, timeout); /* backward compatibility, we always sent this as true before */ if (auth.av_len) rtmp.Link.authflag = true; rtmp.Link.extras = extras; rtmp.Link.token = token; off_t size = 0; // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) if (bResume) { nStatus = OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize, &duration); if (nStatus == RD_FAILED) goto clean; if (!file) { // file does not exist, so go back into normal mode bResume = false; // we are back in fresh file mode (otherwise finalizing file won't be done) } else { nStatus = GetLastKeyframe(file, nSkipKeyFrames, &dSeek, &initialFrame, &initialFrameType, &nInitialFrameSize); if (nStatus == RD_FAILED) { Log(LOGDEBUG, "Failed to get last keyframe."); goto clean; } if (dSeek == 0) { Log(LOGDEBUG, "Last keyframe is first frame in stream, switching from resume to normal mode!"); bResume = false; } } } if (!file) { if (bStdoutMode) { file = stdout; SET_BINMODE(file); } else { file = fopen(flvFile, "w+b"); if (file == 0) { LogPrintf("Failed to open file! %s\n", flvFile); return RD_FAILED; } } } #ifdef _DEBUG netstackdump = fopen("netstackdump", "wb"); netstackdump_read = fopen("netstackdump_read", "wb"); #endif while (!RTMP_ctrlC) { Log(LOGDEBUG, "Setting buffer time to: %dms", bufferTime); RTMP_SetBufferMS(&rtmp, bufferTime); if (first) { first = 0; LogPrintf("Connecting ...\n"); if (!RTMP_Connect(&rtmp, NULL)) { nStatus = RD_FAILED; break; } Log(LOGINFO, "Connected..."); // User defined seek offset if (dStartOffset > 0) { // Don't need the start offset if resuming an existing file if (bResume) { Log(LOGWARNING, "Can't seek a resumed stream, ignoring --start option"); dStartOffset = 0; } else { dSeek = dStartOffset; } } // Calculate the length of the stream to still play if (dStopOffset > 0) { dLength = dStopOffset - dSeek; // Quit if start seek is past required stop offset if (dLength <= 0) { LogPrintf("Already Completed\n"); nStatus = RD_SUCCESS; break; } } if (!RTMP_ConnectStream(&rtmp, dSeek, dLength)) { nStatus = RD_FAILED; break; } } else { nInitialFrameSize = 0; if (retries) { Log(LOGERROR, "Failed to resume the stream\n\n"); if (!RTMP_IsTimedout(&rtmp)) nStatus = RD_FAILED; else nStatus = RD_INCOMPLETE; break; } Log(LOGINFO, "Connection timed out, trying to resume.\n\n"); /* Did we already try pausing, and it still didn't work? */ if (rtmp.m_pausing == 3) { /* Only one try at reconnecting... */ retries = 1; dSeek = rtmp.m_pauseStamp; if (dStopOffset > 0) { dLength = dStopOffset - dSeek; if (dLength <= 0) { LogPrintf("Already Completed\n"); nStatus = RD_SUCCESS; break; } } if (!RTMP_ReconnectStream(&rtmp, bufferTime, dSeek, dLength)) { Log(LOGERROR, "Failed to resume the stream\n\n"); if (!RTMP_IsTimedout(&rtmp)) nStatus = RD_FAILED; else nStatus = RD_INCOMPLETE; break; } } else if (!RTMP_ToggleStream(&rtmp)) { Log(LOGERROR, "Failed to resume the stream\n\n"); if (!RTMP_IsTimedout(&rtmp)) nStatus = RD_FAILED; else nStatus = RD_INCOMPLETE; break; } bResume = true; } nStatus = Download(&rtmp, file, dSeek, dLength, duration, bResume, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, nSkipKeyFrames, bStdoutMode, bLiveStream, bHashes, bOverrideBufferTime, bufferTime, &percent); free(initialFrame); initialFrame = NULL; /* If we succeeded, we're done. */ if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream) break; } if (nStatus == RD_SUCCESS) { LogPrintf("Download complete\n"); } else if (nStatus == RD_INCOMPLETE) { LogPrintf ("Download may be incomplete (downloaded about %.2f%%), try resuming\n", percent); } clean: Log(LOGDEBUG, "Closing connection.\n"); RTMP_Close(&rtmp); if (file != 0) fclose(file); CleanupSockets(); #ifdef _DEBUG if (netstackdump != 0) fclose(netstackdump); if (netstackdump_read != 0) fclose(netstackdump_read); #endif return nStatus; } flvstreamer/rtmp.h0000664000000000000000000001531311335073516013252 0ustar rootroot#ifndef __RTMP_H__ #define __RTMP_H__ /* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-2009 Andrej Stepanchuk * Copyright (C) 2009-2010 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #ifdef WIN32 #include #define GetSockError() WSAGetLastError() #define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e) #define EWOULDBLOCK WSAETIMEDOUT /* we don't use nonblocking, but we do use timeouts */ #define sleep(n) Sleep(n*1000) #define msleep(n) Sleep(n) #define socklen_t int #define SET_RCVTIMEO(tv,s) int tv = s*1000 #else #include #include #include #include #include #include #include #include #define GetSockError() errno #define closesocket(s) close(s) #define msleep(n) usleep(n*1000) #define SET_RCVTIMEO(tv,s) struct timeval tv = {s,0} #endif #include #include #include "log.h" #include "amf.h" #define RTMP_PROTOCOL_UNDEFINED -1 #define RTMP_PROTOCOL_RTMP 0 #define RTMP_PROTOCOL_RTMPT 1 // not yet supported #define RTMP_PROTOCOL_RTMPS 2 // not yet supported #define RTMP_PROTOCOL_RTMPE 3 #define RTMP_PROTOCOL_RTMPTE 4 // not yet supported #define RTMP_PROTOCOL_RTMFP 5 // not yet supported #define RTMP_DEFAULT_CHUNKSIZE 128 #define RTMP_BUFFER_CACHE_SIZE (16*1024) // needs to fit largest number of bytes recv() may return #define RTMP_CHANNELS 65600 extern const char RTMPProtocolStringsLower[][7]; extern bool RTMP_ctrlC; uint32_t RTMP_GetTime(); #define RTMP_PACKET_TYPE_AUDIO 0x08 #define RTMP_PACKET_TYPE_VIDEO 0x09 #define RTMP_PACKET_TYPE_INFO 0x12 #define RTMP_MAX_HEADER_SIZE 18 typedef unsigned char BYTE; typedef struct RTMPChunk { int c_headerSize; int c_chunkSize; char *c_chunk; char c_header[RTMP_MAX_HEADER_SIZE]; } RTMPChunk; typedef struct RTMPPacket { BYTE m_headerType; BYTE m_packetType; BYTE m_hasAbsTimestamp; // timestamp absolute or relative? int m_nChannel; uint32_t m_nInfoField1; // 3 first bytes int32_t m_nInfoField2; // last 4 bytes in a long header, absolute timestamp for long headers, relative timestamp for short headers uint32_t m_nTimeStamp; // absolute timestamp uint32_t m_nBodySize; uint32_t m_nBytesRead; RTMPChunk *m_chunk; char *m_body; } RTMPPacket; typedef struct RTMPSockBuf { int sb_socket; int sb_size; /* number of unprocessed bytes in buffer */ char *sb_start; /* pointer into sb_pBuffer of next byte to process */ char sb_buf[RTMP_BUFFER_CACHE_SIZE]; /* data read from socket */ bool sb_timedout; } RTMPSockBuf; void RTMPPacket_Reset(RTMPPacket *p); void RTMPPacket_Dump(RTMPPacket *p); bool RTMPPacket_Alloc(RTMPPacket *p, int nSize); void RTMPPacket_Free(RTMPPacket *p); #define RTMPPacket_IsReady(a) ((a)->m_nBytesRead == (a)->m_nBodySize) typedef struct RTMP_LNK { const char *hostname; unsigned int port; int protocol; AVal playpath; AVal tcUrl; AVal swfUrl; AVal pageUrl; AVal app; AVal auth; AVal flashVer; AVal subscribepath; AVal token; bool authflag; AMFObject extras; double seekTime; uint32_t length; bool bLiveStream; long int timeout; // number of seconds before connection times out const char *sockshost; unsigned short socksport; } RTMP_LNK; typedef struct RTMP { int m_inChunkSize; int m_outChunkSize; int m_nBWCheckCounter; int m_nBytesIn; int m_nBytesInSent; int m_nBufferMS; int m_stream_id; // returned in _result from invoking createStream int m_mediaChannel; uint32_t m_mediaStamp; uint32_t m_pauseStamp; int m_pausing; int m_nServerBW; int m_nClientBW; uint8_t m_nClientBW2; bool m_bPlaying; bool m_bSendEncoding; bool m_bSendCounter; AVal *m_methodCalls; /* remote method calls queue */ int m_numCalls; RTMP_LNK Link; RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS]; RTMPPacket *m_vecChannelsOut[RTMP_CHANNELS]; int m_channelTimestamp[RTMP_CHANNELS]; // abs timestamp of last packet double m_fAudioCodecs; // audioCodecs for the connect packet double m_fVideoCodecs; // videoCodecs for the connect packet double m_fEncoding; /* AMF0 or AMF3 */ double m_fDuration; // duration of stream in seconds RTMPSockBuf m_sb; #define m_socket m_sb.sb_socket #define m_nBufferSize m_sb.sb_size #define m_pBufferStart m_sb.sb_start #define m_pBuffer m_sb.sb_buf #define m_bTimedout m_sb.sb_timedout } RTMP; void RTMP_SetBufferMS(RTMP *r, int size); void RTMP_UpdateBufferMS(RTMP *r); void RTMP_SetupStream(RTMP *r, int protocol, const char *hostname, unsigned int port, const char *sockshost, AVal *playpath, AVal *tcUrl, AVal *swfUrl, AVal *pageUrl, AVal *app, AVal *auth, AVal *swfSHA256Hash, uint32_t swfSize, AVal *flashVer, AVal *subscribepath, double dTime, uint32_t dLength, bool bLiveStream, long int timeout); bool RTMP_Connect(RTMP *r, RTMPPacket *cp); bool RTMP_Connect0(RTMP *r, struct sockaddr *svc); bool RTMP_Connect1(RTMP *r, RTMPPacket *cp); bool RTMP_Serve(RTMP *r); bool RTMP_ReadPacket(RTMP * r, RTMPPacket * packet); bool RTMP_SendPacket(RTMP * r, RTMPPacket * packet, bool queue); bool RTMP_SendChunk(RTMP * r, RTMPChunk *chunk); bool RTMP_IsConnected(RTMP *r); bool RTMP_IsTimedout(RTMP *r); double RTMP_GetDuration(RTMP *r); bool RTMP_ToggleStream(RTMP *r); bool RTMP_ConnectStream(RTMP *r, double seekTime, uint32_t dLength); bool RTMP_ReconnectStream(RTMP *r, int bufferTime, double seekTime, uint32_t dLength); void RTMP_DeleteStream(RTMP *r); int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet); int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet); void RTMP_Init(RTMP *r); void RTMP_Close(RTMP *r); bool RTMP_SendCtrl(RTMP * r, short nType, unsigned int nObject, unsigned int nTime); bool RTMP_SendPause(RTMP *r, bool DoPause, double dTime); bool RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name, AMFObjectProperty *p); bool RTMPSockBuf_Fill(RTMPSockBuf *sb); #endif flvstreamer/README0000664000000000000000000001341411335073415012775 0ustar rootrootFLVStreamer v2.1c (C) 2009 Andrej Stepanchuk (C) 2009-2010 Howard Chu (C) 2009-2010 The FLVstreamer Team License: GPLv2 To compile type "make" with your platform name, e.g. (for osx, linux, or cygwin) $ make posix For mingw: $ make mingw Please read the Makefile to see what other make variables are used. Connect Parameters ------------------ Some servers expect additional custom parameters to be attached to the RTMP connect request. The "--auth" option handles a specific case, where a boolean TRUE followed by the given string are added to the request. Other servers may require completely different parameters, so the new "--conn" option has been added. This option can be set multiple times on the command line, adding one parameter each time. The argument to the option must take the form : where type can be B for boolean, S for string, N for number, and O for object. For booleans the value must be 0 or 1. Also, for objects the value must be 1 to start a new object, or 0 to end the current object. Examples: --conn B:0 --conn S:hello --conn N:3.14159 Named parameters can be specified by prefixing 'N' to the type. Then the name should come next, and finally the value: --conn NB:myflag:1 --conn NS:category:something --conn NN:pi:3.14159 Objects may be added sequentially: -C O:1 -C NB:flag:1 -C NS:status:success -C O:0 -C O:1 -C NN:time:12.30 -C O:0 or nested: -C O:1 -C NS:code:hello -C NO:extra:1 -C NS:data:stuff -C O:0 -C O:0 Credit goes to team boxee for the XBMC RTMP code originally used in RTMPDumper. The current code is based on the XBMC code but rewritten in C by Howard Chu. Example Servers --------------- Three different types of servers are also present in this distribution: rtmpsrv - a stub server rtmpsuck - a transparent proxy streams - an RTMP to HTTP gateway rtmpsrv - Note that this is very incomplete code, and I haven't yet decided whether or not to finish it. In its current form it is useful for obtaining all the parameters that a real Flash client would send to an RTMP server, so that they can be used with flvstreamer. rtmpsuck - proxy server. See below... All you need to do is redirect your Flash clients to the machine running this server and it will dump out all the connect / play parameters that the Flash client sent. The simplest way to cause the redirect is by editing /etc/hosts when you know the hostname of the RTMP server, and point it to localhost while running rtmpsrv on your machine. (This approach should work on any OS; on Windows you would edit %SystemRoot%\system32\drivers\etc\hosts.) On Linux you can also use iptables to redirect all outbound RTMP traffic. You can do this as root: iptables -t nat -A OUTPUT -p tcp --dport 1935 -j REDIRECT In my original plan I would have the transparent proxy running as a special user (e.g. user "proxy"), and regular Flash clients running as any other user. In that case the proxy would make the connection to the real RTMP server. The iptables rule would look like this: iptables -t nat -A OUTPUT -p tcp --dport 1935 -m owner \! --uid-owner proxy \ -j REDIRECT A rule like the above will be needed to use rtmpsuck. Using it in this mode takes advantage of the Linux support for IP redirects; in particular it uses a special getsockopt() call to retrieve the original destination address of the connection. That way the proxy can create the real outbound connection without any other help from the user. The equivalent functionality may exist on other OSs but needs more investigation. (Based on reading the BSD ipfw manpage, these rules ought to work on BSD: ipfw add 40 fwd 127.0.0.1 1935 tcp from any to any 1935 ipfw add 40 fwd 127.0.0.1 1935 tcp from any to any 1935 not uid proxy Some confirmation from any BSD users would be nice.) (We have a solution for Windows based on a TDI driver; this is known to work on Win2K and WinXP but is assumed to not work on Vista or Win7 as the TDI is no longer used on those OS versions. Also, none of the known solutions are available as freeware.) The rtmpsuck command has only one option: "-z" to turn on debug logging. It listens on port 1935 for RTMP sessions, but you can also redirect other ports to it as needed (read the iptables docs). It first performs an RTMP handshake with the client, then waits for the client to send a connect request. It parses and prints the connect parameters, then makes an outbound connection to the real RTMP server. It performs an RTMP handshake with that server, forwards the connect request, and from that point on it just relays packets back and forth between the two endpoints. It also checks for a few packets that it treats specially: a play packet from the client will get parsed so that the playpath can be displayed. It also handles SWF Verification requests from the server, without forwarding them to the client. (There would be no point, since the response is tied to each session's handshake.) Once the play command is processed, all subsequent audio/video data received from the server will be written to a file, as well as being delivered back to the client. The point of all this, instead of just using a sniffer, is that since rtmpsuck has performed real handshakes with both the client and the server, it can negotiate whatever encryption keys are needed and so record the unencrypted data. streams - HTTP gateway: this is an HTTP server that accepts requests that consist of flvstreamer parameters. It then connects to the specified RTMP server and returns the retrieved data in the HTTP response. The only valid HTTP request is "GET /" but additional options can be provided in normal URL-encoded fashion. E.g. GET /?r=rtmp:%2f%2fserver%2fmyapp&y=somefile HTTP/1.0 is equivalent the flvstreamer parameters "-r rtmp://server/myapp -y somefile". Note that only the shortform (single letter) flvstreamer options are supported. flvstreamer/parseurl.h0000664000000000000000000000217611335073516014130 0ustar rootroot#ifndef _PARSEURL_H_ #define _PARSEURL_H_ /* flvstreamer * 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 flvstreamer; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #ifdef __cplusplus extern "C" { #endif int hex2bin(char *str, char **hex); int ParseUrl(char *url, int *protocol, char **host, unsigned int *port, char **playpath, char **app); char *ParsePlaypath(const char *playpath); #ifdef __cplusplus } #endif #endif