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 RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr)); processTCPrequest(server, sockfd); RTMP_Log(RTMP_LOGDEBUG, "%s: processed request\n", __FUNCTION__); } else { RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__); } } server->state = STREAMING_STOPPED; TFRET(); }
void RTMP_LogHex(int level, const uint8_t *data, unsigned long len) { unsigned long i; char line[50], *ptr; if ( level > RTMP_debuglevel ) return; ptr = line; for(i=0; i<len; i++) { *ptr++ = hexdig[0x0f & (data[i] >> 4)]; *ptr++ = hexdig[0x0f & data[i]]; if ((i & 0x0f) == 0x0f) { *ptr = '\0'; ptr = line; RTMP_Log(level, "%s", line); } else { *ptr++ = ' '; } } if (i & 0x0f) { *ptr = '\0'; RTMP_Log(level, "%s", line); } }
int ExecuteRPCResult(RTMP *r, AMFObject *obj, YLENGStream *yle, int *redirected) { AVal rpcKind; AVal mediaxml; char *playurl, *tvpayOnly; AVal parsedHost = {NULL, 0}; AVal parsedPlaypath = {NULL, 0}; AVal parsedApp = {NULL, 0}; *redirected = FALSE; AMFProp_GetString(AMF_GetProp(obj, NULL, 3), &rpcKind); if (!AVMATCH(&rpcKind, &av_e0)) return TRUE; AMFProp_GetString(AMF_GetProp(obj, NULL, 4), &mediaxml); RTMP_Log(RTMP_LOGDEBUG, "clip data:\n%.*s", mediaxml.av_len, mediaxml.av_val); playurl = GetXMLNodeContent(mediaxml.av_val, "url"); if (!playurl) return FALSE; if (!ParseYLEPlaypath(playurl, &parsedHost, &parsedApp, &parsedPlaypath)) { RTMP_Log(RTMP_LOGERROR, "Couldn't parse stream url %s!", playurl); free(playurl); return FALSE; } // FIXME: old r->Link.playpath may be leaked r->Link.playpath.av_len = parsedPlaypath.av_len; r->Link.playpath.av_val = malloc(parsedPlaypath.av_len*sizeof(char)); strncpy(r->Link.playpath.av_val, parsedPlaypath.av_val, r->Link.playpath.av_len); RTMP_Log(RTMP_LOGDEBUG, "New playpath : %.*s", r->Link.playpath.av_len, r->Link.playpath.av_val); if (!AVMATCH(&parsedHost, &r->Link.hostname)) { RTMP_Log(RTMP_LOGDEBUG, "Redirected to another server: %.*s", parsedHost.av_len, parsedHost.av_val); // FIXME: old value may be leaked r->Link.hostname.av_val = malloc(parsedHost.av_len*sizeof(char)); r->Link.hostname.av_len = parsedHost.av_len; memcpy(r->Link.hostname.av_val, parsedHost.av_val, parsedHost.av_len); *redirected = TRUE; } tvpayOnly = GetXMLNodeContent(mediaxml.av_val, "tvpayOnly"); if (tvpayOnly) { yle->tvFeeRequired = (strcmp(tvpayOnly, "false")!=0); free(tvpayOnly); } free(playurl); return RTMP_SendCreateStream(r); }
void RTMP_LogHexString ( int level, const uint8_t *data, unsigned long len ) { #define BP_OFFSET 9 #define BP_GRAPH 60 #define BP_LEN 80 char line[BP_LEN]; unsigned long i; if ( !data || level > RTMP_debuglevel ) { return; } /* in case len is zero */ line[0] = '\0'; for ( i = 0 ; i < len ; i++ ) { int n = i % 16; unsigned off; if ( !n ) { if ( i ) { RTMP_Log ( level, "%s", line ); } memset ( line, ' ', sizeof ( line ) - 2 ); line[sizeof ( line ) - 2] = '\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 ( data[i] ) ) { line[BP_GRAPH + n] = data[i]; } else { line[BP_GRAPH + n] = '.'; } } RTMP_Log ( level, "%s", line ); }
void QRtmp::setSwfAge(int age) { if(age < 0) RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n"); else m_swfAge = age; }
void QRtmp::setConnData(const QByteArray &data) { m_connData = data; AVal connData = aval(m_connData); if(!RTMP_SetOpt(m_rtmp, &av_conn, &connData)) RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter"); }
void QRtmp::setSwfSize(uint32_t swfSize) { if(swfSize <= 0) RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n"); else m_swfSize = swfSize; }
STREAMING_SERVER * startStreaming(const char *address, int port) { struct sockaddr_in addr; int sockfd, tmp; STREAMING_SERVER *server; sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd == -1) { RTMP_Log(RTMP_LOGERROR, "%s, couldn't create socket", __FUNCTION__); return 0; } tmp = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&tmp, sizeof(tmp)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(address); //htonl(INADDR_ANY); addr.sin_port = htons(port); if (bind(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1) { RTMP_Log(RTMP_LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__, port); return 0; } if (listen(sockfd, 10) == -1) { RTMP_Log(RTMP_LOGERROR, "%s, listen failed", __FUNCTION__); closesocket(sockfd); return 0; } server = (STREAMING_SERVER *)calloc(1, sizeof(STREAMING_SERVER)); server->socket = sockfd; //ThreadCreate(serverThread, server); std::thread theThread(serverThread, server); theThread.join(); return server; }
void QRtmp::setSwfHash(const QByteArray &hash, bool isHex) { QByteArray tmp = isHex ? QByteArray::fromHex(hash) : hash; if(tmp.size() != RTMP_SWF_HASHLEN) RTMP_Log(RTMP_LOGWARNING, "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN); else m_swfHash = tmp; }
void QRtmp::setSkipKeyFrames(int nSkipKeyFrames) { if(nSkipKeyFrames < 0) { RTMP_Log(RTMP_LOGERROR, "Number of keyframes skipped must be greater or equal zero, using zero!"); m_nSkipKeyFrames = 0; } else m_nSkipKeyFrames = nSkipKeyFrames; }
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)); RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s to %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr), destch); #else RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr)); #endif /* Create a new thread and transfer the control to that */ doServe(server, sockfd); RTMP_Log(RTMP_LOGDEBUG, "%s: processed request\n", __FUNCTION__); } else { RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__); } } server->state = STREAMING_STOPPED; TFRET(); }
void QRtmp::stopBlocking(int timeoutSec) { if(isRunning()) { QEventLoop waiter; connect(this, SIGNAL(finished()), &waiter, SLOT(quit())); QTimer::singleShot(timeoutSec * 1000, &waiter, SLOT(quit())); m_stop = true; waiter.exec(); if(isRunning()) // timeout exceeded RTMP_Log(RTMP_LOGWARNING, "Thread has not finished while timeout has exceeded"); } }
int ExecuteAuthenticationDetails(RTMP *r, AMFObject *proplist, YLENGStream *yle) { long authResult = -1; int i; for (i=0; i<AMF_CountProp(proplist); i++) { AVal name; AMFObjectProperty *prop = AMF_GetProp(proplist, NULL, i); AMFProp_GetName(prop, &name); if (AVMATCH(&name, &av_locatedInBroadcastTerritory)) { yle->locatedInBroadcastTerritory = AMFProp_GetBoolean(prop); } else if (AVMATCH(&name, &av_randomAuth)) { authResult = ((long)AMFProp_GetNumber(prop) + 447537687) % 6834253; } else if (AVMATCH(&name, &av_tvFeeActivated)) { yle->tvFeeActivated = AMFProp_GetBoolean(prop); } } if (authResult != -1) { RTMPPacket packet; char pbuf[128], *pend = pbuf+sizeof(pbuf); packet.m_nChannel = 0x03; // control channel packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = 0x11; // FLEX MESSAGE packet.m_nTimeStamp = RTMP_GetTime(); packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; *enc++ = 0x00; // Unknown enc = AMF_EncodeString(enc, pend, &av_authenticateRandomNumber); enc = AMF_EncodeNumber(enc, pend, 0); *enc++ = AMF_NULL; enc = AMF_EncodeNumber(enc, pend, (double)authResult); packet.m_nBodySize = enc-packet.m_body; RTMP_Log(RTMP_LOGDEBUG, "sending authenticateRandomNumber"); return RTMP_SendPacket(r, &packet, FALSE); } return FALSE; }
int RequestData(RTMP *r, YLENGStream *yle) { RTMPPacket packet; char pbuf[128], *pend = pbuf+sizeof(pbuf); AVal clipID; packet.m_nChannel = 0x03; // control channel packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = 0x11; // FLEX MESSAGE packet.m_nTimeStamp = RTMP_GetTime(); packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; char *enc = packet.m_body; *enc++ = 0x00; // Unknown enc = AMF_EncodeString(enc, pend, &av_requestData); enc = AMF_EncodeNumber(enc, pend, 0); *enc++ = AMF_NULL; enc = AMF_EncodeString(enc, pend, &av_e0); if ((r->Link.lFlags & RTMP_LF_LIVE) != 0) { char *tmp = malloc(yle->clipID.av_len+12); strcpy(tmp, "streams/fi/"); strncat(tmp, yle->clipID.av_val, yle->clipID.av_len); STR2AVAL(clipID, tmp); enc = AMF_EncodeString(enc, pend, &clipID); free(tmp); } else { char *tmp = malloc(yle->clipID.av_len+2); strcpy(tmp, "/"); strncat(tmp, yle->clipID.av_val, yle->clipID.av_len); STR2AVAL(clipID, tmp); enc = AMF_EncodeString(enc, pend, &clipID); free(tmp); } if (!enc) { RTMP_Log(RTMP_LOGERROR, "Buffer too short in RequestData"); return FALSE; } packet.m_nBodySize = enc-packet.m_body; return RTMP_SendPacket(r, &packet, FALSE); }
int ExecuteInvokedMethod(RTMP *r, const AVal *method, AMFObject *obj, void *ctx) { struct YLENGStream *yle = (struct YLENGStream *)ctx; int redirected = FALSE; if (!yle || yle->yleAuth == 0) return RTMP_CB_NOT_HANDLED; if (AVMATCH(method, &av_authenticationDetails)) { AMFObject list; AMFProp_GetObject(AMF_GetProp(obj, NULL, 3), &list); if (!ExecuteAuthenticationDetails(r, &list, yle)) return RTMP_CB_ERROR_STOP; if (yle->clipID.av_len) { if (!RequestData(r, yle)) return RTMP_CB_ERROR_STOP; } else if (!RTMP_SendCreateStream(r)) { return RTMP_CB_ERROR_STOP; } return RTMP_CB_SUCCESS; } else if (AVMATCH(method, &av_randomNumberAuthenticated)) { ExecuteRandomNumberAuthenticated(r); return RTMP_CB_SUCCESS; } else if (AVMATCH(method, &av_rpcResult)) { if (!ExecuteRPCResult(r, obj, yle, &redirected)) return RTMP_CB_ERROR_STOP; //if (redirected && !ConnectRedirected(r, r->Link.seekTime, yle)) // return RTMP_CB_ERROR_STOP; return RTMP_CB_SUCCESS; } else if (AVMATCH(method, &av_rpcError)) { RTMP_Log(RTMP_LOGERROR, "RTMP server returned RPC error"); return RTMP_CB_ERROR_STOP; } return RTMP_CB_NOT_HANDLED; }
void QRtmp::start(const QString &rtmpString) { m_error.clear(); m_stop = false; if(dStartOffset > 0 && m_bLiveStream) { RTMP_Log(RTMP_LOGWARNING, "Can't seek in a live stream, ignoring --start option"); dStartOffset = 0; } m_rtmpString = rtmpString.toLocal8Bit(); RTMP_SetupURL(m_rtmp, m_rtmpString.data()); /* Try to keep the stream moving if it pauses on us */ if(!m_bLiveStream && !(rtmpProto(m_proto) & RTMP_FEATURE_HTTP)) m_rtmp->Link.lFlags |= RTMP_LF_BUFX; QThread::start(); }
void QRtmp::setRtmpUrl(const QString &url) { AVal parsedHost, parsedApp, parsedPlaypath; unsigned int parsedPort = 0; int parsedProtocol = RTMP_PROTOCOL_UNDEFINED; if(!RTMP_ParseURL(url.toLocal8Bit().data(), &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url!"); else { // use constructor instead of QByteArray::fromRawData to perform a deep copy if(m_hostname.isEmpty()) m_hostname = QByteArray(parsedHost.av_val, parsedHost.av_len); if(m_port == -1) m_port = parsedPort; if(m_playpath.isEmpty()) m_playpath = QByteArray(parsedPlaypath.av_val, parsedPlaypath.av_len); if(m_app.isEmpty()) m_app = QByteArray(parsedApp.av_val, parsedApp.av_len); if(m_proto == Undefined) m_proto = qrtmpProto(parsedProtocol); } }
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)) RTMP_Log(RTMP_LOGERROR, "%s: Failed to close listening socket, error %d", GetSockError()); server->state = STREAMING_STOPPED; } }
HTTPResult HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb) { char *host, *path; char *p1, *p2; char hbuf[256]; int port = 80; #ifdef CRYPTO int ssl = 0; #endif int hlen, flen = 0; int rc, i; int len_known; HTTPResult ret = HTTPRES_OK; struct sockaddr_in sa; RTMPSockBuf sb = {0}; http->status = -1; memset(&sa, 0, sizeof(struct sockaddr_in)); sa.sin_family = AF_INET; /* we only handle http here */ if (strncasecmp(url, "http", 4)) return HTTPRES_BAD_REQUEST; if (url[4] == 's') { #ifdef CRYPTO ssl = 1; port = 443; if (!RTMP_TLS_ctx) RTMP_TLS_Init(); #else return HTTPRES_BAD_REQUEST; #endif } p1 = strchr(url + 4, ':'); if (!p1 || strncmp(p1, "://", 3)) return HTTPRES_BAD_REQUEST; host = p1 + 3; path = strchr(host, '/'); hlen = path - host; strncpy(hbuf, host, hlen); hbuf[hlen] = '\0'; host = hbuf; p1 = strrchr(host, ':'); if (p1) { *p1++ = '\0'; port = atoi(p1); } sa.sin_addr.s_addr = inet_addr(host); if (sa.sin_addr.s_addr == INADDR_NONE) { struct hostent *hp = gethostbyname(host); if (!hp || !hp->h_addr) return HTTPRES_LOST_CONNECTION; sa.sin_addr = *(struct in_addr *) hp->h_addr; } sa.sin_port = htons(port); sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sb.sb_socket == -1) return HTTPRES_LOST_CONNECTION; i = sprintf(sb.sb_buf, "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\nReferer: %.*s\r\n", path, AGENT, host, (int) (path - url + 1), url); if (http->date[0]) i += sprintf(sb.sb_buf + i, "If-Modified-Since: %s\r\n", http->date); i += sprintf(sb.sb_buf + i, "\r\n"); if (connect (sb.sb_socket, (struct sockaddr *) &sa, sizeof(struct sockaddr)) < 0) { ret = HTTPRES_LOST_CONNECTION; goto leave; } #ifdef CRYPTO if (ssl) { #ifdef NO_SSL RTMP_Log(RTMP_LOGERROR, "%s, No SSL/TLS support", __FUNCTION__); ret = HTTPRES_BAD_REQUEST; goto leave; #else TLS_client(RTMP_TLS_ctx, sb.sb_ssl); TLS_setfd(sb.sb_ssl, sb.sb_socket); if (TLS_connect(sb.sb_ssl) < 0) { RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__); ret = HTTPRES_LOST_CONNECTION; goto leave; } #endif } #endif RTMPSockBuf_Send(&sb, sb.sb_buf, i); /* set timeout */ #define HTTP_TIMEOUT 5 { SET_RCVTIMEO(tv, HTTP_TIMEOUT); if (setsockopt (sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof(tv))) { RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!", __FUNCTION__, HTTP_TIMEOUT); } } sb.sb_size = 0; sb.sb_timedout = FALSE; if (RTMPSockBuf_Fill(&sb) < 1) { ret = HTTPRES_LOST_CONNECTION; goto leave; } if (strncmp(sb.sb_buf, "HTTP/1", 6)) { ret = HTTPRES_BAD_REQUEST; goto leave; } p1 = strchr(sb.sb_buf, ' '); rc = atoi(p1 + 1); http->status = rc; if (rc >= 300) { if (rc == 304) { ret = HTTPRES_OK_NOT_MODIFIED; goto leave; } else if (rc == 404) ret = HTTPRES_NOT_FOUND; else if (rc >= 500) ret = HTTPRES_SERVER_ERROR; else if (rc >= 400) ret = HTTPRES_BAD_REQUEST; else ret = HTTPRES_REDIRECTED; } p1 = memchr(sb.sb_buf, '\n', sb.sb_size); if (!p1) { ret = HTTPRES_BAD_REQUEST; goto leave; } sb.sb_start = p1 + 1; sb.sb_size -= sb.sb_start - sb.sb_buf; while ((p2 = memchr(sb.sb_start, '\r', sb.sb_size))) { if (*sb.sb_start == '\r') { sb.sb_start += 2; sb.sb_size -= 2; break; } else if (!strncasecmp (sb.sb_start, "Content-Length: ", sizeof("Content-Length: ") - 1)) { flen = atoi(sb.sb_start + sizeof("Content-Length: ") - 1); } else if (!strncasecmp (sb.sb_start, "Last-Modified: ", sizeof("Last-Modified: ") - 1)) { *p2 = '\0'; strcpy(http->date, sb.sb_start + sizeof("Last-Modified: ") - 1); } p2 += 2; sb.sb_size -= p2 - sb.sb_start; sb.sb_start = p2; if (sb.sb_size < 1) { if (RTMPSockBuf_Fill(&sb) < 1) { ret = HTTPRES_LOST_CONNECTION; goto leave; } } } len_known = flen > 0; while ((!len_known || flen > 0) && (sb.sb_size > 0 || RTMPSockBuf_Fill(&sb) > 0)) { cb(sb.sb_start, 1, sb.sb_size, http->data); if (len_known) flen -= sb.sb_size; http->size += sb.sb_size; sb.sb_size = 0; } if (flen > 0) ret = HTTPRES_LOST_CONNECTION; leave: RTMPSockBuf_Close(&sb); return ret; }
int RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash, int age) { FILE *f = NULL; char *path, date[64], cctim[64]; long pos = 0; time_t ctim = -1, cnow; int i, got = 0, ret = 0; unsigned int hlen; struct info in = {0}; struct HTTP_ctx http = {0}; HTTPResult httpres; z_stream zs = {0}; AVal home, hpre; date[0] = '\0'; #ifdef _WIN32 #ifdef XBMC4XBOX hpre.av_val = "Q:"; hpre.av_len = 2; home.av_val = "\\UserData"; #else hpre.av_val = getenv("HOMEDRIVE"); hpre.av_len = strlen(hpre.av_val); home.av_val = getenv("HOMEPATH"); #endif #define DIRSEP "\\" #else /* !_WIN32 */ hpre.av_val = ""; hpre.av_len = 0; home.av_val = getenv("HOME"); #define DIRSEP "/" #endif if (!home.av_val) home.av_val = "."; home.av_len = strlen(home.av_val); /* SWF hash info is cached in a fixed-format file. * url: <url of SWF file> * ctim: HTTP datestamp of when we last checked it. * date: HTTP datestamp of the SWF's last modification. * size: SWF size in hex * hash: SWF hash in hex * * These fields must be present in this order. All fields * besides URL are fixed size. */ path = malloc(hpre.av_len + home.av_len + sizeof(DIRSEP ".swfinfo")); sprintf(path, "%s%s" DIRSEP ".swfinfo", hpre.av_val, home.av_val); f = fopen(path, "r+"); while (f) { char buf[4096], *file, *p; file = strchr(url, '/'); if (!file) break; file += 2; file = strchr(file, '/'); if (!file) break; file++; hlen = file - url; p = strrchr(file, '/'); if (p) file = p; else file--; while (fgets(buf, sizeof(buf), f)) { char *r1; got = 0; if (strncmp(buf, "url: ", 5)) continue; if (strncmp(buf + 5, url, hlen)) continue; r1 = strrchr(buf, '/'); i = strlen(r1); r1[--i] = '\0'; if (strncmp(r1, file, i)) continue; pos = ftell(f); while (got < 4 && fgets(buf, sizeof(buf), f)) { if (!strncmp(buf, "size: ", 6)) { *size = strtol(buf + 6, NULL, 16); got++; } else if (!strncmp(buf, "hash: ", 6)) { unsigned char *ptr = hash, *in = (unsigned char *) buf + 6; int l = strlen((char *) in) - 1; for (i = 0; i < l; i += 2) *ptr++ = (HEX2BIN(in[i]) << 4) | HEX2BIN(in[i + 1]); got++; } else if (!strncmp(buf, "date: ", 6)) { buf[strlen(buf) - 1] = '\0'; strncpy(date, buf + 6, sizeof(date)); got++; } else if (!strncmp(buf, "ctim: ", 6)) { buf[strlen(buf) - 1] = '\0'; ctim = make_unix_time(buf + 6); got++; } else if (!strncmp(buf, "url: ", 5)) break; } break; } break; } cnow = time(NULL); /* If we got a cache time, see if it's young enough to use directly */ if (age && ctim > 0) { ctim = cnow - ctim; ctim /= 3600 * 24; /* seconds to days */ if (ctim < age) /* ok, it's new enough */ goto out; } in.first = 1; HMAC_setup(in.ctx, "Genuine Adobe Flash Player 001", 30); inflateInit(&zs); in.zs = &zs; http.date = date; http.data = ∈ httpres = HTTP_get(&http, url, swfcrunch); inflateEnd(&zs); if (httpres != HTTPRES_OK && httpres != HTTPRES_OK_NOT_MODIFIED) { ret = -1; if (httpres == HTTPRES_LOST_CONNECTION) RTMP_Log(RTMP_LOGERROR, "%s: connection lost while downloading swfurl %s", __FUNCTION__, url); else if (httpres == HTTPRES_NOT_FOUND) RTMP_Log(RTMP_LOGERROR, "%s: swfurl %s not found", __FUNCTION__, url); else RTMP_Log(RTMP_LOGERROR, "%s: couldn't contact swfurl %s (HTTP error %d)", __FUNCTION__, url, http.status); } else { if (got && pos) fseek(f, pos, SEEK_SET); else { char *q; if (!f) f = fopen(path, "w"); if (!f) { int err = errno; RTMP_Log(RTMP_LOGERROR, "%s: couldn't open %s for writing, errno %d (%s)", __FUNCTION__, path, err, strerror(err)); ret = -1; goto out; } fseek(f, 0, SEEK_END); q = strchr(url, '?'); if (q) i = q - url; else i = strlen(url); fprintf(f, "url: %.*s\n", i, url); } strtime(&cnow, cctim); fprintf(f, "ctim: %s\n", cctim); if (!in.first) { HMAC_finish(in.ctx, hash, hlen); *size = in.size; fprintf(f, "date: %s\n", date); fprintf(f, "size: %08x\n", in.size); fprintf(f, "hash: "); for (i = 0; i < SHA256_DIGEST_LENGTH; i++) fprintf(f, "%02x", hash[i]); fprintf(f, "\n"); } } HMAC_close(in.ctx); out: free(path); if (f) fclose(f); return ret; }
int ServePacket(STREAMING_SERVER *server, RTMP *r, RTMPPacket *packet) { int ret = 0; RTMP_Log(RTMP_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 //RTMP_Log(RTMP_LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); break; case 0x09: // video data //RTMP_Log(RTMP_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 { RTMP_Log(RTMP_LOGDEBUG, "%s, flex message, size %lu bytes, not fully supported", __FUNCTION__, packet->m_nBodySize); //RTMP_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) { RTMP_Log(RTMP_LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__); //return; } obj.Dump(); */ if (ServeInvoke(server, r, packet, 1)) RTMP_Close(r); break; } case 0x12: // metadata (notify) break; case 0x13: /* shared object */ break; case 0x14: // invoke RTMP_Log(RTMP_LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__, packet->m_nBodySize); //RTMP_LogHex(packet.m_body, packet.m_nBodySize); if (ServeInvoke(server, r, packet, 0)) RTMP_Close(r); break; case 0x16: /* flv */ break; default: RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__, packet->m_packetType); #ifdef _DEBUG RTMP_LogHex(RTMP_LOGDEBUG, packet->m_body, packet->m_nBodySize); #endif } return ret; }
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) { RTMP_Log(RTMP_LOGERROR, "Request timeout/select failed, ignoring request"); goto quit; } else { RTMP_Init(&rtmp); rtmp.m_sb.sb_socket = sockfd; if (!RTMP_Serve(&rtmp)) { RTMP_Log(RTMP_LOGERROR, "Handshake failed"); goto cleanup; } } server->arglen = 0; while (RTMP_IsConnected(&rtmp) && RTMP_ReadPacket(&rtmp, &packet)) { if (!RTMPPacket_IsReady(&packet)) continue; ServePacket(server, &rtmp, &packet); RTMPPacket_Free(&packet); } cleanup: RTMP_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.flashVer.av_val = NULL; RTMP_LogPrintf("done!\n\n"); quit: if (server->state == STREAMING_IN_PROGRESS) server->state = STREAMING_ACCEPTING; return; }
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) { RTMP_Log(RTMP_LOGERROR, "Couldn't read FLV file header!"); return RD_FAILED; } if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V' || hbuf[3] != 0x01) { RTMP_Log(RTMP_LOGERROR, "Invalid FLV file!"); return RD_FAILED; } if ((hbuf[4] & 0x05) == 0) { RTMP_Log(RTMP_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) { RTMP_Log(RTMP_LOGERROR, "Invalid FLV file: missing first prevTagSize!"); return RD_FAILED; } prevTagSize = AMF_DecodeInt32(hbuf); if (prevTagSize != 0) { RTMP_Log(RTMP_LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize); } // go through the file to find the meta data! off_t pos = dataOffset + 4; int 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 = (char *)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) { RTMP_Log(RTMP_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); RTMP_Log(RTMP_LOGDEBUG, "File has duration: %f", *duration); } bFoundMetaHeader = TRUE; break; } //metaObj.Reset(); //delete obj; } pos += (dataSize + 11 + 4); } free(buffer); if (!bFoundMetaHeader) RTMP_Log(RTMP_LOGWARNING, "Couldn't locate meta data!"); } return RD_SUCCESS; }
int QRtmp::download() { qint32 now, lastUpdate; int bufferSize = 64 * 1024; char *buffer = (char *) malloc(bufferSize); int nRead = 0; off_t size = 0; m_rtmp->m_read.timestamp = dSeek; m_percent = 0.0; if(m_rtmp->m_read.timestamp) RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", m_rtmp->m_read.timestamp); if(m_bLiveStream) RTMP_LogPrintf("Starting Live Stream\n"); else { // print initial status // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded if(m_duration > 0) { if((double) m_rtmp->m_read.timestamp >= (double) m_duration * 999.0) { RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n", (double) m_rtmp->m_read.timestamp / 1000.0, (double) m_duration / 1000.0); return RD_SUCCESS; } else { m_percent = ((double) m_rtmp->m_read.timestamp) / (m_duration * 1000.0) * 100.0; m_percent = ((double) (int) (m_percent * 10.0)) / 10.0; RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n", m_bResume ? "Resuming" : "Starting", (double) size / 1024.0, (double) m_rtmp->m_read.timestamp / 1000.0, m_percent); } } else { RTMP_LogPrintf("%s download at: %.3f kB\n", m_bResume ? "Resuming" : "Starting", (double) size / 1024.0); } } if(dStopOffset > 0) RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0); m_rtmp->m_read.nResumeTS = dSeek; now = RTMP_GetTime(); lastUpdate = now - 1000; do { nRead = RTMP_Read(m_rtmp, buffer, bufferSize); //RTMP_LogPrintf("nRead: %d\n", nRead); if(nRead > 0) { if(m_destFile.isOpen()) { if(m_destFile.write(buffer, nRead) != nRead) { setError(QString("Can't write to %1 - %2").arg(m_destFile.fileName()).arg(m_destFile.errorString())); m_stop = true; } } else emit readData(QByteArray(buffer, nRead)); m_destFile.flush(); size += nRead; setStreamIsRunning(true); //RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0); if(m_duration <= 0) // if duration unknown try to get it from the stream (onMetaData) m_duration = RTMP_GetDuration(m_rtmp); if(m_duration > 0) { // make sure we claim to have enough buffer time! if(!m_bOverrideBufferTime && m_bufferTime < (m_duration * 1000.0)) { m_bufferTime = (quint32) (m_duration * 1000.0) + 5000; // extra 5sec to make sure we've got enough RTMP_Log(RTMP_LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", m_bufferTime); RTMP_SetBufferMS(m_rtmp, m_bufferTime); RTMP_UpdateBufferMS(m_rtmp); } m_percent = ((double) m_rtmp->m_read.timestamp) / (m_duration * 1000.0) * 100.0; m_percent = ((double) (int) (m_percent * 10.0)) / 10.0; now = RTMP_GetTime(); if(abs(now - lastUpdate) > 200) { RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)", (double) size / 1024.0, (double) (m_rtmp->m_read.timestamp) / 1000.0, m_percent); lastUpdate = now; } } else { now = RTMP_GetTime(); if(abs(now - lastUpdate) > 200) { RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0, (double) (m_rtmp->m_read.timestamp) / 1000.0); lastUpdate = now; } } } }while (!m_stop && nRead > -1 && RTMP_IsConnected(m_rtmp) && !RTMP_IsTimedout(m_rtmp)); free(buffer); if (nRead < 0) nRead = m_rtmp->m_read.status; /* Final status update */ if(m_duration > 0) { m_percent = ((double) m_rtmp->m_read.timestamp) / (m_duration * 1000.0) * 100.0; m_percent = ((double) (int) (m_percent * 10.0)) / 10.0; RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)", (double) size / 1024.0, (double) (m_rtmp->m_read.timestamp) / 1000.0, m_percent); } else { RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0, (double) (m_rtmp->m_read.timestamp) / 1000.0); } RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead); if(m_bResume && nRead == -2) { RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n", m_nSkipKeyFrames + 1); return RD_FAILED; } if(nRead == -3) return RD_SUCCESS; if((m_duration > 0 && m_percent < 99.9) || m_stop || nRead < 0 || RTMP_IsTimedout(m_rtmp)) { return RD_INCOMPLETE; } return RD_SUCCESS; }
void QRtmp::run() { bool first = true; int retries = 0; if(!m_destFile.fileName().isEmpty()) if(!m_destFile.open(QIODevice::WriteOnly)) { setError(QString("Can't open %1 for writing").arg(m_destFile.fileName())); return; } while(!m_stop) { RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", m_bufferTime); RTMP_SetBufferMS(m_rtmp, m_bufferTime); if(first) { first = false; RTMP_LogPrintf("Connecting ...\n"); if(!RTMP_Connect(m_rtmp, NULL)) { setError("RTMP_Connect failed"); break; } RTMP_Log(RTMP_LOGINFO, "Connected..."); // User defined seek offset if(dStartOffset > 0) { // Don't need the start offset if resuming an existing file if(m_bResume) { RTMP_Log(RTMP_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) { // Quit if start seek is past required stop offset if(dStopOffset <= dSeek) { RTMP_LogPrintf("Already Completed\n"); break; } } if(!RTMP_ConnectStream(m_rtmp, dSeek)) { setError("RTMP_ConnectStream failed"); break; } } else { if(retries) { RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n"); if(!RTMP_IsTimedout(m_rtmp)) setError("RTMP_IsTimedout failed"); else setError("RTMP_IsTimedout RD_INCOMPLETE"); break; } RTMP_Log(RTMP_LOGINFO, "Connection timed out, trying to resume.\n\n"); /* Did we already try pausing, and it still didn't work? */ if(m_rtmp->m_pausing == 3) { /* Only one try at reconnecting... */ retries = 1; dSeek = m_rtmp->m_pauseStamp; if(dStopOffset > 0) { if(dStopOffset <= dSeek) { RTMP_LogPrintf("Already Completed\n"); break; } } if(!RTMP_ReconnectStream(m_rtmp, dSeek)) { RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n"); if(!RTMP_IsTimedout(m_rtmp)) setError("RTMP_IsTimedout failed"); else setError("RTMP_IsTimedout RD_INCOMPLETE"); break; } } else if(!RTMP_ToggleStream(m_rtmp)) { RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n"); if(!RTMP_IsTimedout(m_rtmp)) setError("RTMP_IsTimedout failed"); else setError("RTMP_IsTimedout RD_INCOMPLETE"); break; } m_bResume = true; } int nStatus = download(); if(nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(m_rtmp) || m_bLiveStream) break; } RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n"); RTMP_Close(m_rtmp); if(m_destFile.isOpen()) m_destFile.close(); setStreamIsRunning(false); }
void QRtmp::start() { m_error.clear(); m_stop = false; if(m_hostname.isEmpty()) { setError("You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname"); return; } if(m_playpath.isEmpty()) { setError("You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath"); return; } if(m_proto == Undefined) { RTMP_Log(RTMP_LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP"); m_proto = RTMP; } if(m_port == -1) { RTMP_Log(RTMP_LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port"); m_port = 0; } int protocol = rtmpProto(m_proto); if(m_port == 0) { if(protocol & RTMP_FEATURE_SSL) m_port = 443; else if(protocol & RTMP_FEATURE_HTTP) m_port = 80; else m_port = 1935; } // calculate hash { unsigned char hash[RTMP_SWF_HASHLEN]; if(RTMP_HashSWF(m_swfUrl.data(), &m_swfSize, hash, m_swfAge) == 0) m_swfHash = QByteArray::fromRawData(reinterpret_cast<const char*>(hash), sizeof(hash)); } if(m_swfHash.isEmpty() && m_swfSize > 0) { RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF size, supply also the hash with --swfhash"); m_swfSize = 0; } if(!m_swfHash.isEmpty() && m_swfSize == 0) { RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF hash, supply also the swf size with --swfsize"); m_swfHash.clear(); } if(m_tcUrl.isEmpty()) m_tcUrl = QString("%1://%2:%3/%4") .arg(RTMPProtocolStringsLower[protocol]) .arg(QString(m_hostname)) .arg(m_port) .arg(QString(m_app)) .toLocal8Bit(); if(dStartOffset > 0 && m_bLiveStream) { RTMP_Log(RTMP_LOGWARNING, "Can't seek in a live stream, ignoring --start option"); dStartOffset = 0; } // usherToken is unavailable for librtmp 2.3 AVal hostname = aval(m_hostname), proxy = aval(m_proxy), playpath = aval(m_playpath), tcUrl = aval(m_tcUrl), swfUrl = aval(m_swfUrl), pageUrl = aval(m_pageUrl), app = aval(m_app), auth = aval(m_auth), swfHash = aval(m_swfHash), flashVer = aval(m_flashVer), subscribePath = aval(m_subscribepath); RTMP_SetupStream(m_rtmp, protocol, &hostname, m_port, &proxy, &playpath, &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, m_swfSize, &flashVer, &subscribePath, /*&aval(m_usherToken),*/ dSeek, dStopOffset, m_bLiveStream, m_timeout); /* Try to keep the stream moving if it pauses on us */ if(!m_bLiveStream && !(protocol & RTMP_FEATURE_HTTP)) m_rtmp->Link.lFlags |= RTMP_LF_BUFX; QThread::start(); }
void QRtmp::setError(const QString &errorDesc) { m_error = errorDesc; RTMP_Log(RTMP_LOGERROR, errorDesc.toLocal8Bit().data()); emit error(m_error); }
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; int 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); RTMP_Log(RTMP_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) { RTMP_Log(RTMP_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) { RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!"); return RD_FAILED; } prevTagSize = AMF_DecodeInt32(buffer); //RTMP_Log(RTMP_LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize); if (prevTagSize == 0) { RTMP_Log(RTMP_LOGERROR, "Couldn't find keyframe to resume from!"); return RD_FAILED; } if (prevTagSize < 0 || prevTagSize > size - 4 - 13) { RTMP_Log(RTMP_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) { RTMP_Log(RTMP_LOGERROR, "Couldn't read header!"); return RD_FAILED; } //* #ifdef _DEBUG uint32_t ts = AMF_DecodeInt24(buffer + 4); ts |= (buffer[7] << 24); RTMP_Log(RTMP_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 RTMP_Log(RTMP_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) { RTMP_Log(RTMP_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) { RTMP_Log(RTMP_LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!"); return RD_FAILED; } RTMP_Log(RTMP_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) { RTMP_Log(RTMP_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) { RTMP_Log(RTMP_LOGERROR, "Couldn't read previous timestamp!"); goto start; } uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer); timestamp |= (buffer[3]<<24); RTMP_Log(RTMP_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 dStopOffset, double duration, int bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, int bStdoutMode, int bLiveStream, int bRealtimeStream, int bHashes, int bOverrideBufferTime, uint32_t bufferTime, double *percent) // percentage downloaded [out] { int32_t now, lastUpdate; int bufferSize = 64 * 1024; char *buffer = (char *) malloc(bufferSize); int nRead = 0; off_t size = ftello(file); unsigned long lastPercent = 0; rtmp->m_read.timestamp = dSeek; *percent = 0.0; if (rtmp->m_read.timestamp) { RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp); } if (bLiveStream) { RTMP_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) rtmp->m_read.timestamp >= (double) duration * 999.0) { RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n", (double) rtmp->m_read.timestamp / 1000.0, (double) duration / 1000.0); return RD_SUCCESS; } else { *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n", bResume ? "Resuming" : "Starting", (double) size / 1024.0, (double) rtmp->m_read.timestamp / 1000.0, *percent); } } else { RTMP_LogPrintf("%s download at: %.3f kB\n", bResume ? "Resuming" : "Starting", (double) size / 1024.0); } if (bRealtimeStream) RTMP_LogPrintf(" in approximately realtime (disabled BUFX speedup hack)\n"); } if (dStopOffset > 0) RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0); if (bResume && nInitialFrameSize > 0) rtmp->m_read.flags |= RTMP_READ_RESUME; rtmp->m_read.initialFrameType = initialFrameType; rtmp->m_read.nResumeTS = dSeek; rtmp->m_read.metaHeader = metaHeader; rtmp->m_read.initialFrame = initialFrame; rtmp->m_read.nMetaHeaderSize = nMetaHeaderSize; rtmp->m_read.nInitialFrameSize = nInitialFrameSize; buffer = (char *) malloc(bufferSize); now = RTMP_GetTime(); lastUpdate = now - 1000; do { nRead = RTMP_Read(rtmp, buffer, bufferSize); //RTMP_LogPrintf("nRead: %d\n", nRead); if (nRead > 0) { if (fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t) nRead) { RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__); free(buffer); return RD_FAILED; } size += nRead; //RTMP_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 RTMP_Log(RTMP_LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime); RTMP_SetBufferMS(rtmp, bufferTime); RTMP_UpdateBufferMS(rtmp); } *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; if (bHashes) { if (lastPercent + 1 <= *percent) { RTMP_LogStatus("#"); lastPercent = (unsigned long) *percent; } } else { now = RTMP_GetTime(); if (abs(now - lastUpdate) > 200) { RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)", (double) size / 1024.0, (double) (rtmp->m_read.timestamp) / 1000.0, *percent); lastUpdate = now; } } } else { now = RTMP_GetTime(); if (abs(now - lastUpdate) > 200) { if (bHashes) RTMP_LogStatus("#"); else RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0, (double) (rtmp->m_read.timestamp) / 1000.0); lastUpdate = now; } } } else { #ifdef _DEBUG RTMP_Log(RTMP_LOGDEBUG, "zero read!"); #endif if (rtmp->m_read.status == RTMP_READ_EOF) break; } } while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp)); free(buffer); if (nRead < 0) nRead = rtmp->m_read.status; /* Final status update */ if (!bHashes) { if (duration > 0) { *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)", (double) size / 1024.0, (double) (rtmp->m_read.timestamp) / 1000.0, *percent); } else { RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0, (double) (rtmp->m_read.timestamp) / 1000.0); } } RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead); if (bResume && nRead == -2) { RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n", nSkipKeyFrames + 1); return RD_FAILED; } 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; }
int main(int argc, char **argv) { extern char *optarg; int nStatus = RD_SUCCESS; double percent = 0; double duration = 0.0; int nSkipKeyFrames = DEF_SKIPFRM; // skip this number of keyframes when resuming int bOverrideBufferTime = FALSE; // if the user specifies a buffer time override this is true int bStdoutMode = TRUE; // if true print the stream directly to stdout, messages go to stderr int bResume = FALSE; // true in resume mode uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise uint32_t bufferTime = DEF_BUFTIME; // 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 AVal hostname = { 0, 0 }; AVal playpath = { 0, 0 }; AVal subscribepath = { 0, 0 }; AVal usherToken = { 0, 0 }; //Justin.tv auth token int port = -1; int protocol = RTMP_PROTOCOL_UNDEFINED; int retries = 0; int bLiveStream = FALSE; // is it a live stream? then we can't seek/resume int bRealtimeStream = FALSE; // If true, disable the BUFX hack (be patient) int bHashes = FALSE; // display byte counters not hashes by default long int timeout = DEF_TIMEOUT; // timeout connection after 120 seconds uint32_t dStartOffset = 0; // seek position in non-live mode uint32_t dStopOffset = 0; RTMP rtmp = { 0 }; FILE *pLogFile; AVal fullUrl = { 0, 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 sockshost = { 0, 0 }; #ifdef CRYPTO int swfAge = 30; /* 30 days for SWF cache by default */ int swfVfy = 0; unsigned char hash[RTMP_SWF_HASHLEN]; #endif char *flvFile = 0; signal(SIGINT, sigIntHandler); signal(SIGTERM, sigIntHandler); #ifndef WIN32 signal(SIGHUP, sigIntHandler); signal(SIGPIPE, sigIntHandler); signal(SIGQUIT, sigIntHandler); #endif RTMP_debuglevel = RTMP_LOGALL; //pLogFile = fopen("log.txt", "w"); //RTMP_LogSetOutput(pLogFile); // 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) RTMP_debuglevel = RTMP_LOGCRIT; index++; } #define RTMPDUMP_VERSION "2.4" RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION); RTMP_LogPrintf ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n"); if (!InitSockets()) { RTMP_Log(RTMP_LOGERROR, "Couldn't load sockets support on your platform, exiting!"); return RD_FAILED; } /* sleep(30); */ RTMP_Init(&rtmp); 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'}, {"playlist", 0, 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'}, #ifdef CRYPTO {"swfhash", 1, NULL, 'w'}, {"swfsize", 1, NULL, 'x'}, {"swfVfy", 1, NULL, 'W'}, {"swfAge", 1, NULL, 'X'}, #endif {"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:Ym:k:d:A:B:T:w:x:W:X:S:#"/*, longopts, NULL*/)) != -1) { switch (opt) { case 'h': usage(argv[0]); return RD_SUCCESS; #ifdef CRYPTO case 'w': { int res = hex2bin(optarg, &swfHash.av_val); if (res != RTMP_SWF_HASHLEN) { swfHash.av_val = NULL; RTMP_Log(RTMP_LOGWARNING, "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN); } swfHash.av_len = RTMP_SWF_HASHLEN; break; } case 'x': { int size = atoi(optarg); if (size <= 0) { RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n"); } else { swfSize = size; } break; } case 'W': STR2AVAL(swfUrl, optarg); swfVfy = 1; break; case 'X': { int num = atoi(optarg); if (num < 0) { RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n"); } else { swfAge = num; } } break; #endif case 'k': nSkipKeyFrames = atoi(optarg); if (nSkipKeyFrames < 0) { RTMP_Log(RTMP_LOGERROR, "Number of keyframes skipped must be greater or equal zero, using zero!"); nSkipKeyFrames = 0; } else { RTMP_Log(RTMP_LOGDEBUG, "Number of skipped key frames for resume: %d", nSkipKeyFrames); } break; case 'b': { int32_t bt = atol(optarg); if (bt < 0) { RTMP_Log(RTMP_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 'R': bRealtimeStream = TRUE; // seeking and resuming is still possible break; case 'd': STR2AVAL(subscribepath, optarg); break; case 'n': STR2AVAL(hostname, optarg); break; case 'c': port = atoi(optarg); break; case 'l': protocol = atoi(optarg); if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS) { RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol); return RD_FAILED; } break; case 'y': STR2AVAL(playpath, optarg); break; case 'Y': RTMP_SetOpt(&rtmp, &av_playlist, (AVal *)&av_true); break; case 'r': { AVal parsedHost, parsedApp, parsedPlaypath; unsigned int parsedPort = 0; int parsedProtocol = RTMP_PROTOCOL_UNDEFINED; if (!RTMP_ParseURL (optarg, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) { RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!", optarg); } else { if (!hostname.av_len) hostname = parsedHost; if (port == -1) port = parsedPort; if (playpath.av_len == 0 && parsedPlaypath.av_len) { playpath = parsedPlaypath; } if (protocol == RTMP_PROTOCOL_UNDEFINED) protocol = parsedProtocol; if (app.av_len == 0 && parsedApp.av_len) { app = parsedApp; } } break; } case 'i': STR2AVAL(fullUrl, optarg); 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': { AVal av; STR2AVAL(av, optarg); if (!RTMP_SetOpt(&rtmp, &av_conn, &av)) { RTMP_Log(RTMP_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': { AVal token; STR2AVAL(token, optarg); RTMP_SetOpt(&rtmp, &av_token, &token); } break; case '#': bHashes = TRUE; break; case 'q': RTMP_debuglevel = RTMP_LOGCRIT; break; case 'V': RTMP_debuglevel = RTMP_LOGDEBUG; break; case 'z': RTMP_debuglevel = RTMP_LOGALL; break; case 'S': STR2AVAL(sockshost, optarg); break; case 'j': STR2AVAL(usherToken, optarg); break; default: RTMP_LogPrintf("unknown option: %c\n", opt); usage(argv[0]); return RD_FAILED; break; } } if (!hostname.av_len && !fullUrl.av_len) { RTMP_Log(RTMP_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 && !fullUrl.av_len) { RTMP_Log(RTMP_LOGERROR, "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath"); return RD_FAILED; } if (protocol == RTMP_PROTOCOL_UNDEFINED && !fullUrl.av_len) { RTMP_Log(RTMP_LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP"); protocol = RTMP_PROTOCOL_RTMP; } if (port == -1 && !fullUrl.av_len) { RTMP_Log(RTMP_LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port 1935"); port = 0; } if (port == 0 && !fullUrl.av_len) { if (protocol & RTMP_FEATURE_SSL) port = 443; else if (protocol & RTMP_FEATURE_HTTP) port = 80; else port = 1935; } if (flvFile == 0) { RTMP_Log(RTMP_LOGWARNING, "You haven't specified an output file (-o filename), using stdout"); bStdoutMode = TRUE; } if (bStdoutMode && bResume) { RTMP_Log(RTMP_LOGWARNING, "Can't resume in stdout mode, ignoring --resume option"); bResume = FALSE; } if (bLiveStream && bResume) { RTMP_Log(RTMP_LOGWARNING, "Can't resume live stream, ignoring --resume option"); bResume = FALSE; } #ifdef CRYPTO if (swfVfy) { if (RTMP_HashSWF(swfUrl.av_val, (unsigned int *)&swfSize, hash, swfAge) == 0) { swfHash.av_val = (char *)hash; swfHash.av_len = RTMP_SWF_HASHLEN; } } if (swfHash.av_len == 0 && swfSize > 0) { RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF size, supply also the hash with --swfhash"); swfSize = 0; } if (swfHash.av_len != 0 && swfSize == 0) { RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF hash, supply also the swf size with --swfsize"); swfHash.av_len = 0; swfHash.av_val = NULL; } #endif if (tcUrl.av_len == 0) { tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) + hostname.av_len + app.av_len + sizeof("://:65535/"); tcUrl.av_val = (char *) malloc(tcUrl.av_len); if (!tcUrl.av_val) return RD_FAILED; tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s", RTMPProtocolStringsLower[protocol], hostname.av_len, hostname.av_val, port, app.av_len, app.av_val); } int first = 1; // User defined seek offset if (dStartOffset > 0) { // Live stream if (bLiveStream) { RTMP_Log(RTMP_LOGWARNING, "Can't seek in a live stream, ignoring --start option"); dStartOffset = 0; } } if (!fullUrl.av_len) { RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath, &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout); } else { if (RTMP_SetupURL(&rtmp, fullUrl.av_val) == FALSE) { RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", fullUrl.av_val); return RD_FAILED; } } /* Try to keep the stream moving if it pauses on us */ if (!bLiveStream && !bRealtimeStream && !(protocol & RTMP_FEATURE_HTTP)) rtmp.Link.lFlags |= RTMP_LF_BUFX; 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) { RTMP_Log(RTMP_LOGDEBUG, "Failed to get last keyframe."); goto clean; } if (dSeek == 0) { RTMP_Log(RTMP_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) { RTMP_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) { RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime); RTMP_SetBufferMS(&rtmp, bufferTime); if (first) { first = 0; RTMP_LogPrintf("Connecting ...\n"); if (!RTMP_Connect(&rtmp, NULL)) { nStatus = RD_NO_CONNECT; break; } RTMP_Log(RTMP_LOGINFO, "Connected..."); // User defined seek offset if (dStartOffset > 0) { // Don't need the start offset if resuming an existing file if (bResume) { RTMP_Log(RTMP_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) { // Quit if start seek is past required stop offset if (dStopOffset <= dSeek) { RTMP_LogPrintf("Already Completed\n"); nStatus = RD_SUCCESS; break; } } if (!RTMP_ConnectStream(&rtmp, dSeek)) { nStatus = RD_FAILED; break; } } else { nInitialFrameSize = 0; if (retries) { RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n"); if (!RTMP_IsTimedout(&rtmp)) nStatus = RD_FAILED; else nStatus = RD_INCOMPLETE; break; } RTMP_Log(RTMP_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) { if (dStopOffset <= dSeek) { RTMP_LogPrintf("Already Completed\n"); nStatus = RD_SUCCESS; break; } } if (!RTMP_ReconnectStream(&rtmp, dSeek)) { RTMP_Log(RTMP_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)) { RTMP_Log(RTMP_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, dStopOffset, duration, bResume, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, nSkipKeyFrames, bStdoutMode, bLiveStream, bRealtimeStream, 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) { RTMP_LogPrintf("Download complete\n"); } else if (nStatus == RD_INCOMPLETE) { RTMP_LogPrintf ("Download may be incomplete (downloaded about %.2f%%), try resuming\n", percent); } clean: RTMP_Log(RTMP_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 fclose(pLogFile); return nStatus; }