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(); }
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; }
int do_rtmp(int port,int protocol,char* playpath_arg,char* host,char* swfVfy_arg,char* tcUrl_arg,char* app_arg,char* pageUrl_arg,char* flashVer_arg,char* conn,char* outfile) { 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 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 retries = 0; int bLiveStream = FALSE; // is it a live stream? then we can't seek/resume int bHashes = FALSE; // display byte counters not hashes by default long int timeout = DEF_TIMEOUT; // timeout connection after 120 seconds uint32_t dStopOffset = 0; RTMP rtmp; 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); signal(SIGHUP, sigIntHandler); signal(SIGPIPE, sigIntHandler); signal(SIGQUIT, sigIntHandler); RTMP_debuglevel = RTMP_LOGINFO; RTMP_Init(&rtmp); STR2AVAL(&swfUrl,swfVfy_arg); swfVfy = 1; if (host) { STR2AVAL(&hostname, host); } if (playpath_arg) { STR2AVAL(&playpath, playpath_arg); } if (tcUrl_arg) { STR2AVAL(&tcUrl, tcUrl_arg); } if (pageUrl_arg) { STR2AVAL(&pageUrl, pageUrl_arg); } if (app_arg) { STR2AVAL(&app, app_arg); } if (flashVer_arg) { STR2AVAL(&flashVer, flashVer_arg); } if (conn) { AVal av; STR2AVAL(&av, conn); if (!RTMP_SetOpt(&rtmp, &av_conn, &av)) { RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", conn); return RD_FAILED; } } flvFile = outfile; file = fopen(flvFile, "w+b"); if (file == 0) { RTMP_LogPrintf("Failed to open file! %s\n", flvFile); return RD_FAILED; } if (port == 0) { if (protocol & RTMP_FEATURE_SSL) port = 443; else if (protocol & RTMP_FEATURE_HTTP) port = 80; else port = 1935; } #ifdef CRYPTO if (swfVfy) { if (RTMP_HashSWF(swfUrl.av_val, &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 RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath, &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout); /* Try to keep the stream moving if it pauses on us */ if (!bLiveStream && !(protocol & RTMP_FEATURE_HTTP)) rtmp.Link.lFlags |= RTMP_LF_BUFX; int first = 1; 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..."); 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, 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) { RTMP_LogPrintf("Download complete\n"); } else if (nStatus == RD_INCOMPLETE) { RTMP_LogPrintf ("Download may be incomplete (downloaded about %.2f%%), try resuming\n", percent); } RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n"); RTMP_Close(&rtmp); if (file != 0) fclose(file); return nStatus; }
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 int len; size_t nRead = 0; 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; char srvhead[] = "\r\nServer: HTTP-RTMP Stream Server " RTMPDUMP_VERSION "\r\n"; // timeout for http requests fd_set fds; struct timeval tv; char *status = "404 Not Found"; server->state = STREAMING_IN_PROGRESS; memcpy(&req, &defaultRTMPRequest, sizeof(RTMP_REQUEST)); 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) { RTMP_Log(RTMP_LOGERROR, "Request timeout/select failed, ignoring request"); goto quit; } else { nRead = recv(sockfd, header, 2047, 0); header[2047] = '\0'; RTMP_Log(RTMP_LOGDEBUG, "%s: header: %s", __FUNCTION__, header); if (strstr(header, "Range: bytes=") != 0) { // TODO check range starts from 0 and asking till the end. RTMP_LogPrintf("%s, Range request not supported\n", __FUNCTION__); len = sprintf(buf, "HTTP/1.0 416 Requested Range Not Satisfiable%s\r\n", srvhead); send(sockfd, buf, len, 0); goto quit; } if (strncmp(header, "GET", 3) == 0 && nRead > 4) { char *p = filename; filename = header + 4; // filter " HTTP/..." from end of request 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) { RTMP_Log(RTMP_LOGDEBUG, "%s: Request header: %s", __FUNCTION__, filename); if (filename[0] == '/') { // if its not empty, is it /? ptr = filename + 1; // parse parameters if (*ptr == '?') { int len; ptr++; len = strlen(ptr); while (len >= 2) { // get position of the next '&' char *temp; char ich = *ptr; unsigned int nArgLen; char *arg; ptr++; if (*ptr != '=') goto filenotfound; // long parameters not (yet) supported ptr++; len -= 2; nArgLen = len; if ((temp = strstr(ptr, "&")) != 0) { nArgLen = temp - ptr; } arg = (char *) malloc((nArgLen + 1) * sizeof(char)); memcpy(arg, ptr, nArgLen * sizeof(char)); arg[nArgLen] = '\0'; //RTMP_Log(RTMP_LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg); http_unescape(arg); RTMP_Log(RTMP_LOGDEBUG, "%s: parameter: %c, arg: %s", __FUNCTION__, ich, arg); ptr += nArgLen + 1; len -= nArgLen + 1; if (!ParseOption(ich, arg, &req)) { status = "400 unknown option"; goto filenotfound; } } } } else { goto filenotfound; } } else { RTMP_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.av_len) { RTMP_Log(RTMP_LOGERROR, "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname"); status = "400 Missing Hostname"; goto filenotfound; } if (req.playpath.av_len == 0) { RTMP_Log(RTMP_LOGERROR, "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath"); status = "400 Missing Playpath"; goto filenotfound;; } if (req.protocol == RTMP_PROTOCOL_UNDEFINED) { RTMP_Log(RTMP_LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP"); req.protocol = RTMP_PROTOCOL_RTMP; } if (req.rtmpport == -1) { RTMP_Log(RTMP_LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port"); req.rtmpport = 0; } if (req.rtmpport == 0) { if (req.protocol & RTMP_FEATURE_SSL) req.rtmpport = 443; else if (req.protocol & RTMP_FEATURE_HTTP) req.rtmpport = 80; else req.rtmpport = 1935; } if (req.tcUrl.av_len == 0) { char str[512] = { 0 }; req.tcUrl.av_len = snprintf(str, 511, "%s://%.*s:%d/%.*s", RTMPProtocolStringsLower[req.protocol], req.hostname.av_len, req.hostname.av_val, req.rtmpport, req.app.av_len, req.app.av_val); req.tcUrl.av_val = (char *) malloc(req.tcUrl.av_len + 1); strcpy(req.tcUrl.av_val, str); } if (req.swfVfy) { #ifdef CRYPTO if (RTMP_HashSWF(req.swfUrl.av_val, &req.swfSize, req.hash, req.swfAge) == 0) { req.swfHash.av_val = (char *)req.hash; req.swfHash.av_len = RTMP_SWF_HASHLEN; } #endif } // after validation of the http request send response header len = sprintf(buf, "HTTP/1.0 200 OK%sContent-Type: video/flv\r\n\r\n", srvhead); send(sockfd, buf, len, 0); // send the packets buffer = (char *) calloc(PACKET_SIZE, 1); // User defined seek offset if (req.dStartOffset > 0) { if (req.bLiveStream) RTMP_Log(RTMP_LOGWARNING, "Can't seek in a live stream, ignoring --seek option"); else dSeek += req.dStartOffset; } if (dSeek != 0) { RTMP_LogPrintf("Starting at TS: %d ms\n", dSeek); } RTMP_Log(RTMP_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, &req.sockshost, &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, dSeek, req.dStopOffset, req.bLiveStream, req.timeout); /* backward compatibility, we always sent this as true before */ if (req.auth.av_len) rtmp.Link.lFlags |= RTMP_LF_AUTH; rtmp.Link.extras = req.extras; rtmp.Link.token = req.token; rtmp.m_read.timestamp = dSeek; RTMP_LogPrintf("Connecting ... port: %d, app: %s\n", req.rtmpport, req.app); if (!RTMP_Connect(&rtmp, NULL)) { RTMP_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; do { nRead = RTMP_Read(&rtmp, buffer, PACKET_SIZE); if (nRead > 0) { if ((nWritten = send(sockfd, buffer, nRead, 0)) < 0) { RTMP_Log(RTMP_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; //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) { percent = ((double) (dSeek + 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); } } #ifdef _DEBUG else { RTMP_Log(RTMP_LOGDEBUG, "zero read!"); } #endif } while (server->state == STREAMING_IN_PROGRESS && nRead > -1 && RTMP_IsConnected(&rtmp) && nWritten >= 0); } cleanup: RTMP_LogPrintf("Closing connection... "); RTMP_Close(&rtmp); 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: RTMP_LogPrintf("%s, %s, %s\n", __FUNCTION__, status, filename); len = sprintf(buf, "HTTP/1.0 %s%s\r\n", status, srvhead); send(sockfd, buf, len, 0); goto quit; }
static int rtmp_setupURL(RTMP *r, const char *uri) { double percent = 0; double duration = 0.0; int nSkipKeyFrames = DEF_SKIPFRM; int bOverrideBufferTime = FALSE; uint32_t dSeek = 0; 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; 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 { AVal parsedHost, parsedApp, parsedPlaypath; unsigned int parsedPort = 0; int parsedProtocol = RTMP_PROTOCOL_UNDEFINED; if (!RTMP_ParseURL (uri, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) { av_log(NULL,AV_LOG_ERROR,"Couldn't parse the specified url (%s)!\n", uri); } 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; } if(parsedApp.av_len && strstr(parsedApp.av_val, "live")) { bLiveStream = TRUE; } } } if (!hostname.av_len && !fullUrl.av_len) { av_log(NULL,AV_LOG_ERROR,"Couldn't parse hostname !\n"); return FALSE; } if (!playpath.av_len && !fullUrl.av_len) { av_log(NULL,AV_LOG_ERROR,"Couldn't parse playpath !\n"); return FALSE; } if (protocol == RTMP_PROTOCOL_UNDEFINED && !fullUrl.av_len) { protocol = RTMP_PROTOCOL_RTMP; } if (port == -1 && !fullUrl.av_len) { 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; } #ifdef CRYPTO if (swfVfy) { if (RTMP_HashSWF(swfUrl.av_val, &swfSize, hash, swfAge) == 0) { swfHash.av_val = (char *)hash; swfHash.av_len = RTMP_SWF_HASHLEN; } } if (swfHash.av_len == 0 && swfSize > 0) { swfSize = 0; } if (swfHash.av_len != 0 && swfSize == 0) { 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 *) av_malloc(tcUrl.av_len); if (!tcUrl.av_val) return FALSE; 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); } if (!fullUrl.av_len) { RTMP_SetupStream(r, protocol, &hostname, port, &sockshost, &playpath, &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout); } else { if (RTMP_SetupURL(r, fullUrl.av_val) == FALSE) { av_log(NULL,AV_LOG_ERROR,"rtmp setupURL failed !\n"); return FALSE; } } #if 0 /* Try to keep the stream moving if it pauses on us */ if (!bLiveStream && !bRealtimeStream && !(protocol & RTMP_FEATURE_HTTP)) r.Link.lFlags |= RTMP_LF_BUFX; #endif //RTMP_SetBufferMS(r, bufferTime); return TRUE; }