Author: hyc Date: Fri Oct 30 20:12:27 2009 New Revision: 12 Log: Partial merge from RTMPDump-YLE: fix buffer overruns, fix client reporting interval, attempt auto-resume Modified: handshake.cpp rtmp.cpp rtmp.h rtmpdump.cpp Modified: handshake.cpp ============================================================================== --- handshake.cpp Fri Oct 30 02:47:01 2009 (r11) +++ handshake.cpp Fri Oct 30 20:12:27 2009 (r12) @@ -526,10 +526,10 @@ bool CRTMP::HandShake(bool FP9HandShake) // generate signed answer char clientResp[RTMP_SIG_SIZE]; #ifdef _DEBUG - for(int i=0; i<=RTMP_SIG_SIZE; i++) + for(int i=0; i<RTMP_SIG_SIZE; i++) clientResp[i] = 0;//(char)(rand() % 256);//0xff; #else - for(int i=0; i<=RTMP_SIG_SIZE; i++) + for(int i=0; i<RTMP_SIG_SIZE; i++) clientResp[i] = (char)(rand() % 256); #endif Modified: rtmp.cpp ============================================================================== --- rtmp.cpp Fri Oct 30 02:47:01 2009 (r11) +++ rtmp.cpp Fri Oct 30 20:12:27 2009 (r12) @@ -23,6 +23,7 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> @@ -56,6 +57,8 @@ static const int packetSize[] = { 12, 8, #define RTMP_PACKET_SIZE_SMALL 2 #define RTMP_PACKET_SIZE_MINIMUM 3 +extern bool bCtrlC; + int32_t GetTime() { #ifdef _DEBUG @@ -64,7 +67,7 @@ int32_t GetTime() return timeGetTime(); #else struct tms t; - return times(&t); + return times(&t)*1000/sysconf(_SC_CLK_TCK); #endif } @@ -94,6 +97,11 @@ CRTMP::CRTMP() : m_socket(0) m_pBuffer = new char[RTMP_BUFFER_CACHE_SIZE]; m_nBufferMS = 300; m_fDuration = 0; + m_stream_id = -1; + m_pBufferStart = NULL; + m_fAudioCodecs = 3191.0; + m_fVideoCodecs = 252.0; + m_bTimedout = false; } CRTMP::~CRTMP() @@ -104,6 +112,7 @@ CRTMP::~CRTMP() double CRTMP::GetDuration() { return m_fDuration; } bool CRTMP::IsConnected() { return m_socket != 0; } +bool CRTMP::IsTimedout() { return m_bTimedout; } void CRTMP::SetBufferMS(int size) { @@ -115,20 +124,20 @@ void CRTMP::UpdateBufferMS() SendPing(3, 1, m_nBufferMS); } -bool CRTMP::Connect( +void CRTMP::SetupStream( int protocol, - char *hostname, + const char *hostname, unsigned int port, - char *playpath, - char *tcUrl, - char *swfUrl, - char *pageUrl, - char *app, - char *auth, - char *swfSHA256Hash, + const char *playpath, + const char *tcUrl, + const char *swfUrl, + const char *pageUrl, + const char *app, + const char *auth, + const char *swfSHA256Hash, uint32_t swfSize, - char *flashVer, - char *subscribepath, + const char *flashVer, + const char *subscribepath, double dTime, bool bLiveStream, long int timeout @@ -190,10 +199,18 @@ bool CRTMP::Connect( if (Link.port == 0) Link.port = 1935; - +} + +bool CRTMP::Connect() { + if (!Link.hostname) + return false; + // close any previous connection Close(); + m_bTimedout = false; + m_fDuration = 0.0; + sockaddr_in service; memset(&service, 0, sizeof(sockaddr_in)); service.sin_family = AF_INET; @@ -215,7 +232,9 @@ bool CRTMP::Connect( { if (connect(m_socket, (sockaddr*) &service, sizeof(struct sockaddr)) < 0) { - Log(LOGERROR, "%s, failed to connect socket. Error: %d", __FUNCTION__, GetSockError()); + int err = GetSockError(); + Log(LOGERROR, "%s, failed to connect socket. %d (%s)", __FUNCTION__, + err, strerror(err)); Close(); return false; } @@ -229,7 +248,7 @@ bool CRTMP::Connect( } Log(LOGDEBUG, "%s, handshaked", __FUNCTION__); - if (!Connect()) + if (!RTMPConnect()) { Log(LOGERROR, "%s, RTMP connect failed.", __FUNCTION__); Close(); @@ -254,6 +273,56 @@ bool CRTMP::Connect( return true; } +bool CRTMP::ConnectStream(double seekTime) { + if (seekTime >= -2.0) + Link.seekTime = seekTime; + + RTMPPacket packet; + while (!m_bPlaying && IsConnected() && ReadPacket(packet)) { + if (!packet.IsReady()) + { + packet.FreePacket(); + continue; + } + + if ((packet.m_packetType == 0x8) || \ + (packet.m_packetType == 0x9) || \ + (packet.m_packetType == 0x16)) + { + Log(LOGDEBUG, "%s, received FLV packet before play()!", __FUNCTION__); + break; + } + + HandlePacket(packet); + } + + return m_bPlaying; +} + +bool CRTMP::ReconnectStream(int bufferTime, double seekTime) { + DeleteStream(); + + SendCreateStream(2.0); + + SetBufferMS(bufferTime); + UpdateBufferMS(); + + return ConnectStream(seekTime); +} + +void CRTMP::DeleteStream() { + if (m_stream_id < 0) + return; + + m_bPlaying = false; + + SendDeleteStream(m_stream_id); + + // No response expected for deleteStream + if (m_methodCalls.back() == "deleteStream") + m_methodCalls.erase(m_methodCalls.end()); +} + int CRTMP::GetNextMediaPacket(RTMPPacket &packet) { int bHasMediaPacket = 0; @@ -266,6 +335,21 @@ int CRTMP::GetNextMediaPacket(RTMPPacket continue; } + bHasMediaPacket = HandlePacket(packet); + + if (!bHasMediaPacket) { + packet.FreePacket(); + } + } + + if (bHasMediaPacket) + m_bPlaying = true; + + return bHasMediaPacket; +} + +int CRTMP::HandlePacket(RTMPPacket &packet) { + int bHasMediaPacket = 0; switch (packet.m_packetType) { case 0x01: @@ -285,14 +369,12 @@ int CRTMP::GetNextMediaPacket(RTMPPacket case 0x05: // server bw - Log(LOGDEBUG, "%s, received: server BW", __FUNCTION__); - //LogHex(packet.m_body, packet.m_nBodySize); + HandleServerBW(packet); break; case 0x06: // client bw - Log(LOGDEBUG, "%s, received: client BW", __FUNCTION__); - //LogHex(packet.m_body, packet.m_nBodySize); + HandleClientBW(packet); break; case 0x08: @@ -386,14 +468,6 @@ int CRTMP::GetNextMediaPacket(RTMPPacket #endif } - if (!bHasMediaPacket) { - packet.FreePacket(); - } - } - - if (bHasMediaPacket) - m_bPlaying = true; - return bHasMediaPacket; } @@ -406,6 +480,8 @@ int CRTMP::ReadN(char *buffer, int n) { int nOriginalSize = n; + m_bTimedout = false; + #ifdef _DEBUG memset(buffer, 0, n); #endif @@ -415,10 +491,11 @@ int CRTMP::ReadN(char *buffer, int n) { int nBytes = 0; if(m_nBufferSize == 0) - if (!FillBuffer()) { - Close(); - return 0; - } + if (!FillBuffer()) { + if (!m_bTimedout) + Close(); + return 0; + } int nRead = ((n<m_nBufferSize)?n:m_nBufferSize); if(nRead > 0) { memcpy(ptr, m_pBufferStart, nRead); @@ -426,7 +503,7 @@ int CRTMP::ReadN(char *buffer, int n) m_nBufferSize -= nRead; nBytes = nRead; m_nBytesIn += nRead; - if(m_nBytesIn > m_nBytesInSent + (600*1024)) // report every 600K + if(m_nBytesIn > m_nBytesInSent + m_nClientBW/2 ) SendBytesReceived(); } @@ -483,7 +560,12 @@ bool CRTMP::WriteN(const char *buffer, i if (nBytes < 0) { - Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, GetSockError(), n); + int sockerr = GetSockError(); + Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, sockerr, n); + + if (sockerr == EINTR && !bCtrlC) + continue; + Close(); if(encrypted) @@ -504,7 +586,7 @@ bool CRTMP::WriteN(const char *buffer, i return n == 0; } -bool CRTMP::Connect() +bool CRTMP::RTMPConnect() { if (!SendConnectPacket()) { @@ -540,8 +622,8 @@ bool CRTMP::SendConnectPacket() enc += EncodeBoolean(enc, "fpad", false); enc += EncodeNumber(enc, "capabilities", 15.0); - enc += EncodeNumber(enc, "audioCodecs", 3191.0); - enc += EncodeNumber(enc, "videoCodecs", 252.0); + enc += EncodeNumber(enc, "audioCodecs", m_fAudioCodecs); + enc += EncodeNumber(enc, "videoCodecs", m_fVideoCodecs); enc += EncodeNumber(enc, "videoFunction", 1.0); if(Link.pageUrl) enc += EncodeString(enc, "pageUrl", Link.pageUrl); @@ -558,8 +640,8 @@ bool CRTMP::SendConnectPacket() // add auth string if(Link.auth) { - *enc = 0x01; enc++; - *enc = 0x01; enc++; +// *enc = 0x01; enc++; +// *enc = 0x01; enc++; enc += EncodeString(enc, Link.auth); } @@ -608,7 +690,7 @@ bool CRTMP::SendCreateStream(double dStr return SendRTMP(packet); } -bool CRTMP::SendFCSubscribe(char *subscribepath) +bool CRTMP::SendFCSubscribe(const char *subscribepath) { RTMPPacket packet; packet.m_nChannel = 0x03; // control channel (invoke) @@ -700,7 +782,7 @@ bool CRTMP::SendServerBW() packet.AllocPacket(4); packet.m_nBodySize = 4; - EncodeInt32(packet.m_body, 0x001312d0); // hard coded for now + EncodeInt32(packet.m_body, m_nServerBW); return SendRTMP(packet); } @@ -780,18 +862,14 @@ bool CRTMP::SendPlay() Log(LOGDEBUG, "%s, sending play: %s", __FUNCTION__, Link.playpath); enc += EncodeString(enc, Link.playpath); + // Optional parameters start and len. + // start: -2, -1, 0, positive number // -2: looks for a live stream, then a recorded stream, if not found any open a live stream // -1: plays a live stream // >=0: plays a recorded streams from 'start' milliseconds - if(Link.bLiveStream) - enc += EncodeNumber(enc, -1000.0); - else { - if(Link.seekTime > 0.0) - enc += EncodeNumber(enc, Link.seekTime); // resume from here - else - enc += EncodeNumber(enc, 0.0);//-2000.0); // recorded as default, -2000.0 is not reliable since that freezes the player if the stream is not found - } + if(Link.seekTime > 0.0) + enc += EncodeNumber(enc, Link.seekTime); // resume from here // len: -1, 0, positive number // -1: plays live or recorded stream to the end (default) @@ -913,6 +991,7 @@ int CRTMP::HandleInvoke(const char *body } else if (CSCMP(methodInvoked,"play")) { + m_bPlaying = true; SendPlay(); } } @@ -952,8 +1031,16 @@ int CRTMP::HandleInvoke(const char *body if (code == "NetStream.Failed" || code == "NetStream.Play.Failed" || code == "NetStream.Play.StreamNotFound" - || code == "NetConnection.Connect.InvalidApp") + || code == "NetConnection.Connect.InvalidApp") { + m_stream_id = -1; Close(); + } + + if (code == "NetStream.Play.Start") { + m_bPlaying = true; + if (m_methodCalls[0] == "play") + m_methodCalls.erase(m_methodCalls.begin()); + } // Return 1 if this is a Play.Complete or Play.Stop if (code == "NetStream.Play.Complete" @@ -1132,6 +1219,20 @@ void CRTMP::HandlePing(const RTMPPacket } } +void CRTMP::HandleServerBW(const RTMPPacket &packet) { + m_nServerBW = ReadInt32(packet.m_body); + Log(LOGDEBUG, "%s: server BW = %d", __FUNCTION__, m_nServerBW); +} + +void CRTMP::HandleClientBW(const RTMPPacket &packet) { + m_nClientBW = ReadInt32(packet.m_body); + if (packet.m_nBodySize > 4) + m_nClientBW2 = packet.m_body[4]; + else + m_nClientBW2 = -1; + Log(LOGDEBUG, "%s: client BW = %d %d", __FUNCTION__, m_nClientBW, m_nClientBW2); +} + bool CRTMP::ReadPacket(RTMPPacket &packet) { char type; @@ -1420,10 +1521,10 @@ bool CRTMP::HandShake(bool FP9HandShake) memset(&clientsig[5], 0, 4); #ifdef _DEBUG - for (int i=9; i<=RTMP_SIG_SIZE; i++) + for (int i=9; i<RTMP_SIG_SIZE; i++) clientsig[i] = 0xff; #else - for (int i=9; i<=RTMP_SIG_SIZE; i++) + for (int i=9; i<RTMP_SIG_SIZE; i++) clientsig[i] = (char)(rand() % 256); #endif @@ -1543,8 +1644,10 @@ bool CRTMP::SendRTMP(RTMPPacket &packet) #endif } - if (packet.m_packetType == 0x14) // we invoked a remote method, keep it in call queue till result arrives + if (packet.m_packetType == 0x14) { // we invoked a remote method, keep it in call queue till result arrives m_methodCalls.push_back(ReadString(packet.m_body + 1)); + Log(LOGDEBUG, "Invoking %s", ReadString(packet.m_body + 1).c_str()); + } if (!m_vecChannelsOut[packet.m_nChannel]) m_vecChannelsOut[packet.m_nChannel] = new RTMPPacket; @@ -1558,11 +1661,15 @@ void CRTMP::Close() if (IsConnected()) close(m_socket); + m_stream_id = -1; m_socket = 0; m_chunkSize = 128; m_nBWCheckCounter = 0; m_nBytesIn = 0; m_nBytesInSent = 0; + m_nClientBW = 2500000; + m_nClientBW2 = 2; + m_nServerBW = 2500000; for (int i=0; i<65600; i++) { @@ -1575,6 +1682,7 @@ void CRTMP::Close() m_vecChannelsOut[i] = NULL; } } + m_methodCalls.clear(); m_bPlaying = false; m_nBufferSize = 0; @@ -1583,15 +1691,25 @@ void CRTMP::Close() bool CRTMP::FillBuffer() { assert(m_nBufferSize == 0); // only fill buffer when it's empty - int nBytes = recv(m_socket, m_pBuffer, RTMP_BUFFER_CACHE_SIZE, 0); + int nBytes; + +again: + nBytes = recv(m_socket, m_pBuffer, RTMP_BUFFER_CACHE_SIZE, 0); if(nBytes != -1) { m_nBufferSize += nBytes; m_pBufferStart = m_pBuffer; } else { - Log(LOGDEBUG, "%s, recv returned %d. GetSockError(): %d", __FUNCTION__, nBytes, GetSockError()); - Close(); + int sockerr = GetSockError(); + Log(LOGDEBUG, "%s, recv returned %d. GetSockError(): %d", __FUNCTION__, nBytes, sockerr); + if (sockerr == EINTR && !bCtrlC) + goto again; + + if (sockerr == EWOULDBLOCK || sockerr == EAGAIN) + m_bTimedout = true; + else + Close(); return false; } Modified: rtmp.h ============================================================================== --- rtmp.h Fri Oct 30 02:47:01 2009 (r11) +++ rtmp.h Fri Oct 30 20:12:27 2009 (r12) @@ -71,20 +71,20 @@ namespace RTMP_LIB typedef struct { - char *hostname; + const char *hostname; unsigned int port; int protocol; - char *playpath; + const char *playpath; - char *tcUrl; - char *swfUrl; - char *pageUrl; - char *app; - char *auth; - char *SWFHash; + const char *tcUrl; + const char *swfUrl; + const char *pageUrl; + const char *app; + const char *auth; + const char *SWFHash; uint32_t SWFSize; - char *flashVer; - char *subscribepath; + const char *flashVer; + const char *subscribepath; double seekTime; bool bLiveStream; @@ -111,27 +111,33 @@ class CRTMP void SetBufferMS(int size); void UpdateBufferMS(); - bool Connect( + void SetupStream( int protocol, - char *hostname, + const char *hostname, unsigned int port, - char *playpath, - char *tcUrl, - char *swfUrl, - char *pageUrl, - char *app, - char *auth, - char *swfSHA256Hash, + const char *playpath, + const char *tcUrl, + const char *swfUrl, + const char *pageUrl, + const char *app, + const char *auth, + const char *swfSHA256Hash, uint32_t swfSize, - char *flashVer, - char *subscribepath, + const char *flashVer, + const char *subscribepath, double dTime, bool bLiveStream, long int timeout=300); + bool Connect(); + bool IsConnected(); + bool IsTimedout(); double GetDuration(); + bool ConnectStream(double seekTime=-10.0); + bool ReconnectStream(int bufferTime, double seekTime=-10.0); + void DeleteStream(); int GetNextMediaPacket(RTMPPacket &packet); void Close(); @@ -156,7 +162,7 @@ class CRTMP protected: bool HandShake(bool FP9HandShake=true); - bool Connect(); + bool RTMPConnect(); bool SendConnectPacket(); bool SendServerBW(); @@ -166,17 +172,20 @@ class CRTMP bool SendBGHasStream(double dId, char *playpath); bool SendCreateStream(double dStreamId); bool SendDeleteStream(double dStreamId); - bool SendFCSubscribe(char *subscribepath); + bool SendFCSubscribe(const char *subscribepath); bool SendPlay(); bool SendSeek(double dTime); bool SendBytesReceived(); + int HandlePacket(RTMPPacket &packet); int HandleInvoke(const char *body, unsigned int nBodySize); bool HandleMetadata(char *body, unsigned int len); void HandleChangeChunkSize(const RTMPPacket &packet); void HandleAudio(const RTMPPacket &packet); void HandleVideo(const RTMPPacket &packet); void HandlePing(const RTMPPacket &packet); + void HandleServerBW(const RTMPPacket &packet); + void HandleClientBW(const RTMPPacket &packet); int EncodeString(char *output, const std::string &strName, const std::string &strValue); int EncodeNumber(char *output, const std::string &strName, double dVal); @@ -199,6 +208,10 @@ class CRTMP bool m_bPlaying; int m_nBufferMS; int m_stream_id; // returned in _result from invoking createStream + bool m_bTimedout; + int m_nClientBW; + uint8_t m_nClientBW2; + int m_nServerBW; //std::string m_strPlayer; //std::string m_strPageUrl; @@ -215,6 +228,9 @@ class CRTMP RTMPPacket *m_vecChannelsOut[65600]; int m_channelTimestamp[65600]; // abs timestamp of last packet + double m_fAudioCodecs; // audioCodecs for the connect packet + double m_fVideoCodecs; // videoCodecs for the connect packet + double m_fDuration; // duration of stream in seconds }; }; Modified: rtmpdump.cpp ============================================================================== --- rtmpdump.cpp Fri Oct 30 02:47:01 2009 (r11) +++ rtmpdump.cpp Fri Oct 30 20:12:27 2009 (r12) @@ -78,6 +78,15 @@ uint32_t nIgnoredFlvFrameCounter = 0; uint32_t nIgnoredFrameCounter = 0; #define MAX_IGNORED_FRAMES 50 +FILE *file = 0; +bool bCtrlC = false; + +void sigIntHandler(int sig) { + bCtrlC = true; + LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig); + signal(SIGINT, SIG_DFL); +} + int WriteHeader( char **buf, // target pointer, maybe preallocated unsigned int len // length of buffer if preallocated @@ -414,13 +423,402 @@ stopKeyframeSearch: return -1; // no more media packets } -FILE *file = 0; -bool bCtrlC = false; +int OpenResumeFile(const char *flvFile, // file name [in] + FILE **file, // opened file [out] + unsigned long *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] +{ + const size_t bufferSize = 1024; + char buffer[bufferSize]; -void sigIntHandler(int sig) { - bCtrlC = true; - LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig); - signal(SIGINT, SIG_DFL); + *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 = ftell(*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(buffer, 1, 13, *file) != 13) { + Log(LOGERROR, "Couldn't read FLV file header!"); + return RD_FAILED; + } + if(buffer[0] != 'F' || buffer[1] != 'L' || buffer[2] != 'V' || buffer[3] != 0x01) { + Log(LOGERROR, "Inavlid FLV file!"); + return RD_FAILED; + } + + if((buffer[4]&0x05) == 0) { + Log(LOGERROR, "FLV file contains neither video nor audio, aborting!"); + return RD_FAILED; + } + + uint32_t dataOffset = RTMP_LIB::CRTMP::ReadInt32(buffer+5); + fseek(*file, dataOffset, SEEK_SET); + + if(fread(buffer, 1, 4, *file) != 4) { + Log(LOGERROR, "Invalid FLV file: missing first prevTagSize!"); + return RD_FAILED; + } + prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer); + if(prevTagSize != 0) { + Log(LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize); + } + + // go through the file to find the meta data! + uint32_t pos = dataOffset+4; + bool bFoundMetaHeader = false; + + while(pos < *size-4 && !bFoundMetaHeader) { + fseek(*file, pos, SEEK_SET); + if(fread(buffer, 1, 4, *file)!=4) + break; + + uint32_t dataSize = RTMP_LIB::CRTMP::ReadInt24(buffer+1); + + if(buffer[0] == 0x12) { + if (dataSize > bufferSize) { + Log(LOGERROR, "%s: dataSize (%d) > bufferSize (%d)", __FUNCTION__, dataSize, bufferSize); + return RD_FAILED; + } + + fseek(*file, pos+11, SEEK_SET); + if(fread(buffer, 1, dataSize, *file) != dataSize) + break; + + RTMP_LIB::AMFObject metaObj; + int nRes = metaObj.Decode(buffer, dataSize); + if(nRes < 0) { + Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__); + break; + } + + std::string metastring = metaObj.GetProperty(0).GetString(); + + if(metastring == "onMetaData") { + metaObj.Dump(); + + *nMetaHeaderSize = dataSize; + if (*metaHeader) free(*metaHeader); + *metaHeader = (char *)malloc(*nMetaHeaderSize); + memcpy(*metaHeader, buffer, *nMetaHeaderSize); + + // get duration + AMFObjectProperty prop; + if(RTMP_LIB::CRTMP::FindFirstMatchingProperty(metaObj, "duration", prop)) { + *duration = prop.GetNumber(); + Log(LOGDEBUG, "File has duration: %f", *duration); + } + + bFoundMetaHeader = true; + break; + } + //metaObj.Reset(); + //delete obj; + } + pos += (dataSize+11+4); + } + + if(!bFoundMetaHeader) + Log(LOGWARNING, "Couldn't locate meta data!"); + } + + return RD_SUCCESS; +} + +int GetLastKeyframe(FILE *file, // output file [in] + int nSkipKeyFrames, // max number of frames to skip when searching for key frame [in] + uint32_t *dSeek, // offset of the last key frame [out] + char **initialFrame, // content of the last keyframe [out] + int *initialFrameType, // initial frame type (audio/video) [out] + uint32_t *nInitialFrameSize) // length of initialFrame [out] +{ + const size_t bufferSize = 16; + char buffer[bufferSize]; + uint8_t dataType; + bool bAudioOnly; + unsigned long size; + + fseek(file, 0, SEEK_END); + size = ftell(file); + + fseek(file, 4, SEEK_SET); + fread(&dataType, sizeof(uint8_t), 1, file); + bAudioOnly = (dataType & 0x4) && !(dataType & 0x1); + + Log(LOGDEBUG, "bAudioOnly: %d, size: %d", bAudioOnly, 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 + uint32_t tsize = 0; + uint32_t prevTagSize = 0; + + // go through the file and find the last video keyframe + do { + int xread; +skipkeyframe: + if(size-tsize < 13) { + Log(LOGERROR, "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0"); + return RD_FAILED; + } + fseek(file, size-tsize-4, SEEK_SET); + xread = fread(buffer, 1, 4, file); + if(xread != 4) { + Log(LOGERROR, "Couldn't read prevTagSize from file!"); + return RD_FAILED; + } + + prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer); + //Log(LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize); + + if(prevTagSize == 0) { + Log(LOGERROR, "Couldn't find keyframe to resume from!"); + return RD_FAILED; + } + + if(prevTagSize < 0 || prevTagSize > size-4-13) { + Log(LOGERROR, "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!", prevTagSize); + return RD_FAILED; + } + tsize += prevTagSize+4; + + // read header + fseek(file, size-tsize, SEEK_SET); + if(fread(buffer, 1, 12, file) != 12) { + Log(LOGERROR, "Couldn't read header!"); + return RD_FAILED; + } + //* + #ifdef _DEBUG + uint32_t ts = RTMP_LIB::CRTMP::ReadInt24(buffer+4); + ts |= (buffer[7]<<24); + Log(LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts); + #endif //*/ + + // this just continues the loop whenever the number of skipped frames is > 0, + // so we look for the next keyframe to continue with + // + // this helps if resuming from the last keyframe fails and one doesn't want to start + // the download from the beginning + // + if(nSkipKeyFrames > 0 && !(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))) { + #ifdef _DEBUG + Log(LOGDEBUG, "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!"); + #endif + nSkipKeyFrames--; + goto skipkeyframe; + } + + } while( + (bAudioOnly && buffer[0] != 0x08) || + (!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10)) + ); // as long as we don't have a keyframe / last audio frame + + // save keyframe to compare/find position in stream + *initialFrameType = buffer[0]; + *nInitialFrameSize = prevTagSize-11; + *initialFrame = (char *)malloc(*nInitialFrameSize); + + fseek(file, size-tsize+11, SEEK_SET); + if(fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize) { + Log(LOGERROR, "Couldn't read last keyframe, aborting!"); + return RD_FAILED; + } + + *dSeek = RTMP_LIB::CRTMP::ReadInt24(buffer+4); // set seek position to keyframe tmestamp + *dSeek |= (buffer[7]<<24); + //} + //else // handle audio only, we can seek anywhere we'd like + //{ + //} + + if(*dSeek < 0) { + Log(LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!"); + return RD_FAILED; + } + Log(LOGDEBUG,"Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek, *nInitialFrameSize, *initialFrameType); + + /* + // now read the timestamp of the frame before the seekable keyframe: + fseek(file, size-tsize-4, SEEK_SET); + if(fread(buffer, 1, 4, file) != 4) { + Log(LOGERROR, "Couldn't read prevTagSize from file!"); + goto start; + } + uint32_t prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer); + fseek(file, size-tsize-4-prevTagSize+4, SEEK_SET); + if(fread(buffer, 1, 4, file) != 4) { + Log(LOGERROR, "Couldn't read previous timestamp!"); + goto start; + } + uint32_t timestamp = RTMP_LIB::CRTMP::ReadInt24(buffer); + timestamp |= (buffer[3]<<24); + + Log(LOGDEBUG, "Previuos 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) + fseek(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(CRTMP *rtmp, // connected CRTMP object + FILE *file, + uint32_t dSeek, + uint32_t dStopOffset, + double duration, + bool bResume, + char *metaHeader, + uint32_t nMetaHeaderSize, + char *initialFrame, + int initialFrameType, + uint32_t nInitialFrameSize, + int nSkipKeyFrames, + bool bStdoutMode, + bool bLiveStream, + bool bOverrideBufferTime, + uint32_t bufferTime, + double *percent) // percentage downloaded [out] +{ + uint32_t timestamp = dSeek; + int32_t now, lastUpdate; + uint8_t dataType = 0; // will be written into the FLV header (position 4) + int bufferSize = 1024*1024; + char *buffer = (char *)malloc(bufferSize); + int nRead = 0; + unsigned long size = ftell(file); + + memset(buffer, 0, bufferSize); + + *percent = 0.0; + + if(timestamp != 0) { + LogPrintf("Continuing at TS: %d ms\n", timestamp); + } + + // print initial status + LogPrintf("Starting download at "); + if(duration > 0) { + *percent = ((double)timestamp) / (duration*1000.0)*100.0; + *percent = round(*percent*10.0)/10.0; + LogPrintf("%.3f kB (%.1f%%)\n", (double)size/1024.0, *percent); + } else { + LogPrintf("%.3f kB\n", (double)size/1024.0); + } + + // write FLV header if not resuming + if(!bResume) { + nRead = WriteHeader(&buffer, bufferSize); + if(nRead > 0) { + if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) { + Log(LOGERROR, "%s: Failed writing FLV header, exiting!", __FUNCTION__); + free(buffer); + return RD_FAILED; + } + size += nRead; + } else { + Log(LOGERROR, "Couldn't obtain FLV header, exiting!"); + free(buffer); + return RD_FAILED; + } + } + + now = GetTime(); + lastUpdate = now-1000; + do + { + nRead = WriteStream(rtmp, &buffer, bufferSize, ×tamp, bResume, dSeek, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, &dataType); + + //LogPrintf("nRead: %d\n", nRead); + if(nRead > 0) { + if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) { + Log(LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__); + free(buffer); + return RD_FAILED; + } + size += nRead; + + //LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0); + if(duration <= 0) // if duration unknown try to get it from the stream (onMetaData) + duration = rtmp->GetDuration(); + + if(duration > 0) { + // make sure we claim to have enough buffer time! + if(!bOverrideBufferTime && bufferTime < (duration*1000.0)) { + bufferTime = (uint32_t)(duration*1000.0)+5000; // extra 5sec to make sure we've got enough + + Log(LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime); + rtmp->SetBufferMS(bufferTime); + rtmp->UpdateBufferMS(); + } + *percent = ((double)timestamp) / (duration*1000.0)*100.0; + *percent = round(*percent*10.0)/10.0; + now = GetTime(); + if (abs(now - lastUpdate) > 200) { + LogPrintf("\r%.3f kB (%.1f%%)", (double)size/1024.0, *percent); + lastUpdate = now; + } + } else { + now = GetTime(); + if (abs(now - lastUpdate) > 200) { + LogPrintf("\r%.3f kB", (double)size/1024.0); + lastUpdate = now; + } + } + } + #ifdef _DEBUG + else { Log(LOGDEBUG, "zero read!"); } + #endif + + // Force clean close if a specified stop offset is reached + if (dStopOffset && timestamp >= dStopOffset) { + LogPrintf("\nStop offset has been reached at %.2f seconds\n", (double)dStopOffset/1000.0); + nRead = 0; + rtmp->Close(); + } + + } while(!bCtrlC && nRead > -1 && rtmp->IsConnected()); + free(buffer); + + if(bResume && nRead == -2) { + LogPrintf("Couldn't resume FLV file, try --skip %d\n\n", nSkipKeyFrames+1); + return RD_FAILED; + } + + // finalize header by writing the correct dataType (video, audio, video+audio) + if(!bResume && dataType != 0x5 && !bStdoutMode) { + //Log(LOGDEBUG, "Writing data type: %02X", dataType); + fseek(file, 4, SEEK_SET); + fwrite(&dataType, sizeof(unsigned char), 1, file); + } + if((duration > 0 && *percent < 99.9) || bCtrlC || nRead < 0 || rtmp->IsTimedout()) { + return RD_INCOMPLETE; + } + + return RD_SUCCESS; } //#define _DEBUG_TEST_PLAYSTOP @@ -436,15 +834,12 @@ int main(int argc, char **argv) double percent = 0; double duration = 0.0; - uint8_t dataType = 0; // will be written into the FLV header (position 4) int nSkipKeyFrames = 0; // skip this number of keyframes when resuming bool bOverrideBufferTime = false; // if the user specifies a buffer time override this is true bool bStdoutMode = false;// if true print the stream directly to stdout, messages go to stderr bool bResume = false; // true in resume mode - //bool bNoHeader = false; // in resume mode this will tell not to write an FLV header again - bool bAudioOnly = false; // when resuming this will tell whether its an audio only stream uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise uint32_t bufferTime = 10*60*60*1000; // 10 hours as default @@ -465,7 +860,7 @@ int main(int argc, char **argv) int protocol = RTMP_PROTOCOL_UNDEFINED; bool bLiveStream = false; // is it a live stream? then we can't seek/resume - long int timeout = 300; // timeout connection after 300 seconds + long int timeout = 120; // timeout connection after 120 seconds uint32_t dStartOffset = 0; // seek position in non-live mode uint32_t dStopOffset = 0; @@ -560,7 +955,7 @@ int main(int argc, char **argv) LogPrintf("--skip|-k num Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n", nSkipKeyFrames); LogPrintf("--quiet|-q Supresses all command output.\n"); - LogPrintf("--verbose|-X Verbose command output.\n"); + LogPrintf("--verbose|-V Verbose command output.\n"); LogPrintf("--debug|-z Debug level command output.\n"); LogPrintf("If you don't pass parameters for swfUrl, pageUrl, app or auth these propertiews will not be included in the connect "); LogPrintf("packet.\n\n"); @@ -774,259 +1169,53 @@ int main(int argc, char **argv) strcpy(tcUrl, str); } - int bufferSize = 1024*1024; char *buffer = (char *)malloc(bufferSize); - int nRead = 0, count = 0; - + int first = 1; memset(buffer, 0, bufferSize); CRTMP *rtmp = new CRTMP(); - - Log(LOGDEBUG, "Setting buffer time to: %dms", bufferTime); - rtmp->SetBufferMS(bufferTime); + rtmp->SetupStream(protocol, hostname, port, playpath, tcUrl, swfUrl, + pageUrl, app, auth, swfHash, swfSize, flashVer, subscribepath, + dSeek, bLiveStream, timeout); unsigned long size = 0; uint32_t timestamp = 0; + int tries = 2; /* 1 retry */ // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) if(bResume) { - file = fopen(flvFile, "r+b"); - if(file == 0) { - bResume = false; // we are back in fresh file mode (otherwise finalizing file won't be done) - goto start; // file does not exist, so go back into normal mode - } - - fseek(file, 0, SEEK_END); - size = ftell(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(buffer, 1, 13, file) != 13) { - Log(LOGERROR, "Couldn't read FLV file header!"); - nStatus = RD_FAILED; - goto clean; - } - if(buffer[0] != 'F' || buffer[1] != 'L' || buffer[2] != 'V' || buffer[3] != 0x01) { - Log(LOGERROR, "Invalid FLV file!"); - nStatus = RD_FAILED; - goto clean; - } - - if((buffer[4]&0x05) == 0) { - Log(LOGERROR, "FLV file contains neither video nor audio, aborting!"); - nStatus = RD_FAILED; - goto clean; - } - bAudioOnly = (buffer[4] & 0x4) && !(buffer[4] & 0x1); - if(bAudioOnly) - Log(LOGDEBUG, "Resuming audio only stream!"); - - uint32_t dataOffset = RTMP_LIB::CRTMP::ReadInt32(buffer+5); - fseek(file, dataOffset, SEEK_SET); - - if(fread(buffer, 1, 4, file) != 4) { - Log(LOGERROR, "Invalid FLV file: missing first prevTagSize!"); - nStatus = RD_FAILED; - goto clean; - } - prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer); - if(prevTagSize != 0) { - Log(LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize); - } - - // go through the file to find the meta data! - uint32_t pos = dataOffset+4; - bool bFoundMetaHeader = false; - - while(pos < size-4 && !bFoundMetaHeader) { - fseek(file, pos, SEEK_SET); - if(fread(buffer, 1, 4, file)!=4) - break; - - uint32_t dataSize = RTMP_LIB::CRTMP::ReadInt24(buffer+1); - - if(buffer[0] == 0x12) { - fseek(file, pos+11, SEEK_SET); - if(fread(buffer, 1, dataSize, file) != dataSize) - break; - - RTMP_LIB::AMFObject metaObj; - int nRes = metaObj.Decode(buffer, dataSize); - if(nRes < 0) { - Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__); - break; - } - - std::string metastring = metaObj.GetProperty(0).GetString(); - - if(metastring == "onMetaData") { - metaObj.Dump(); - - nMetaHeaderSize = dataSize; - metaHeader = (char *)malloc(nMetaHeaderSize); - memcpy(metaHeader, buffer, nMetaHeaderSize); - - // get duration - AMFObjectProperty prop; - if(RTMP_LIB::CRTMP::FindFirstMatchingProperty(metaObj, "duration", prop)) { - duration = prop.GetNumber(); - Log(LOGDEBUG, "File has duration: %f", duration); - } - - bFoundMetaHeader = true; - break; - } - //metaObj.Reset(); - //delete obj; - } - pos += (dataSize+11+4); - } - - if(!bFoundMetaHeader) - Log(LOGWARNING, "Couldn't locate meta data!"); - - //if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames - //{ - // find the last seekable frame - uint32_t tsize = 0; - - // go through the file and find the last video keyframe - do { -skipkeyframe: - if(size-tsize < 13) { - Log(LOGERROR, "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0"); - nStatus = RD_FAILED; goto clean; - } - - fseek(file, size-tsize-4, SEEK_SET); - if(fread(buffer, 1, 4, file) != 4) { - Log(LOGERROR, "Couldn't read prevTagSize from file!"); - nStatus = RD_FAILED; goto clean; - } - - prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer); - //Log(LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize); - - if(prevTagSize == 0) { - Log(LOGERROR, "Couldn't find keyframe to resume from!"); - nStatus = RD_FAILED; goto clean; - } - - if(prevTagSize < 0 || prevTagSize > size-4-13) { - Log(LOGERROR, "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!", prevTagSize); - nStatus = RD_FAILED; goto clean; - } - tsize += prevTagSize+4; - - // read header - fseek(file, size-tsize, SEEK_SET); - if(fread(buffer, 1, 12, file) != 12) { - Log(LOGERROR, "Couldn't read header!"); - nStatus=RD_FAILED; goto clean; - } - //* - #ifdef _DEBUG - uint32_t ts = RTMP_LIB::CRTMP::ReadInt24(buffer+4); - ts |= (buffer[7]<<24); - Log(LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts); - #endif //*/ - - // this just continues the loop whenever the number of skipped frames is > 0, - // so we look for the next keyframe to continue with - // - // this helps if resuming from the last keyframe fails and one doesn't want to start - // the download from the beginning - // - if(nSkipKeyFrames > 0 && !(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))) { - #ifdef _DEBUG - Log(LOGDEBUG, "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!"); - #endif - nSkipKeyFrames--; - goto skipkeyframe; - } - - } while( - (bAudioOnly && buffer[0] != 0x08) || - (!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10)) - ); // as long as we don't have a keyframe / last audio frame - - // save keyframe to compare/find position in stream - initialFrameType = buffer[0]; - nInitialFrameSize = prevTagSize-11; - initialFrame = (char *)malloc(nInitialFrameSize); - - fseek(file, size-tsize+11, SEEK_SET); - if(fread(initialFrame, 1, nInitialFrameSize, file) != nInitialFrameSize) { - Log(LOGERROR, "Couldn't read last keyframe, aborting!"); - nStatus=RD_FAILED; - goto clean; - } - - dSeek = RTMP_LIB::CRTMP::ReadInt24(buffer+4); // set seek position to keyframe tmestamp - dSeek |= (buffer[7]<<24); - //} - //else // handle audio only, we can seek anywhere we'd like - //{ - //} + nStatus = OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize, &duration); + if (nStatus == RD_FAILED) + goto clean; - if(dSeek < 0) { - Log(LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!"); - nStatus=RD_FAILED; + if (!file) { + // file does not exist, so go back into normal mode + bResume = false; // we are back in fresh file mode (otherwise finalizing file won't be done) + } else { + nStatus = GetLastKeyframe(file, nSkipKeyFrames, + &dSeek, &initialFrame, + &initialFrameType, + &nInitialFrameSize); + if (nStatus == RD_FAILED) { + Log(LOGDEBUG, "Failed to get last keyframe."); goto clean; } - Log(LOGDEBUG,"Last keyframe found at: %d ms, size: %d, type: %02X", dSeek, nInitialFrameSize, initialFrameType); - /* - // now read the timestamp of the frame before the seekable keyframe: - fseek(file, size-tsize-4, SEEK_SET); - if(fread(buffer, 1, 4, file) != 4) { - Log(LOGERROR, "Couldn't read prevTagSize from file!"); - goto start; - } - uint32_t prevTagSize = RTMP_LIB::CRTMP::ReadInt32(buffer); - fseek(file, size-tsize-4-prevTagSize+4, SEEK_SET); - if(fread(buffer, 1, 4, file) != 4) { - Log(LOGERROR, "Couldn't read previous timestamp!"); - goto start; - } - uint32_t timestamp = RTMP_LIB::CRTMP::ReadInt24(buffer); - timestamp |= (buffer[3]<<24); - - Log(LOGDEBUG, "Previuos timestamp: %d ms", timestamp); - */ - - if(dSeek == 0) { - Log(LOGDEBUG, "Last keyframe is first frame in stream, switching from resume to normal mode!"); - bResume = false; - goto start; - } - else - { - // 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) - fseek(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 + if (dSeek == 0) { + Log(LOGDEBUG, "Last keyframe is first frame in stream, switching from resume to normal mode!"); + bResume = false; } } - } else { -start: - if(file != 0) - fclose(file); + } + if (!file) { if(bStdoutMode) file = stdout; else { - file = fopen(flvFile, "w"); + file = fopen(flvFile, "w+b"); if(file == 0) { LogPrintf("Failed to open file! %s\n", flvFile); return RD_FAILED; @@ -1039,8 +1228,6 @@ start: netstackdump_read = fopen("netstackdump_read", "wb"); #endif - LogPrintf("Connecting ...\n"); - // User defined seek offset if (dStartOffset > 0) { if (bLiveStream) @@ -1052,151 +1239,75 @@ start: dSeek += dStartOffset; } - //{ // here we decrease the seek time by 10ms to make sure the server starts with the next keyframe - //double dFindSeek = dSeek; + while (tries > 0 && !bCtrlC) { + tries--; - //if(!bAudioOnly && dFindSeek >= 10.0) - // dFindSeek-=10.0; + Log(LOGDEBUG, "Setting buffer time to: %dms", bufferTime); + rtmp->SetBufferMS(bufferTime); - if (!rtmp->Connect(protocol, hostname, port, playpath, tcUrl, swfUrl, pageUrl, app, auth, swfHash, swfSize, flashVer, subscribepath, dSeek, bLiveStream, timeout)) { - LogPrintf("Failed to connect!\n"); - return RD_FAILED; - } - Log(LOGINFO, "Connected..."); - //} - - /*#ifdef _DEBUG - debugTS = dSeek; - #endif*/ + if (first) { + first = 0; + LogPrintf("Connecting ...\n"); - timestamp = dSeek; - if(dSeek != 0) { - LogPrintf("Continuing at TS: %d ms\n", timestamp); - } + if (!rtmp->Connect()) { + break; + } - // print initial status - // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded - if( duration > 0 && (double)timestamp >= (double)duration*999.0 ) { - LogPrintf("Already Completed at: TS=%.1f Duration=%.1f\n", (double)timestamp, (double)duration); - goto clean; - } else if(duration > 0) { - percent = ((double)timestamp) / (duration*1000.0)*100.0; - percent = round(percent*10.0)/10.0; - LogPrintf("Starting download at %.3f kB (%.1f%%)\n", (double)size/1024.0, percent); - } else { - LogPrintf("Starting download at %.3f kB\n", (double)size/1024.0); - } + Log(LOGINFO, "Connected..."); + + timestamp = dSeek; + if(dSeek != 0) { + LogPrintf("Continuing at TS: %d ms\n", timestamp); + } - // write FLV header if not resuming - if(!bResume) { - nRead = WriteHeader(&buffer, bufferSize); - if(nRead > 0) { - if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) { - Log(LOGERROR, "%s: Failed writing FLV header, exiting!", __FUNCTION__); - nStatus=RD_FAILED; - goto clean; - } - size += nRead; - } else { - Log(LOGERROR, "Couldn't obtain FLV header, exiting!"); - nStatus=RD_FAILED; - goto clean; + if (!rtmp->ConnectStream(dSeek)) { + break; } } - do - { - nRead = WriteStream(rtmp, &buffer, bufferSize, ×tamp, bResume, dSeek, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, &dataType); -#define PRINT_CHUNK 32768 + nStatus = Download(rtmp, file, dSeek, dStopOffset, duration, bResume, + metaHeader, nMetaHeaderSize, initialFrame, + initialFrameType, nInitialFrameSize, + nSkipKeyFrames, bStdoutMode, bLiveStream, + bOverrideBufferTime, bufferTime, &percent); + free(initialFrame); + initialFrame = NULL; - //LogPrintf("nRead: %d\n", nRead); - if(nRead > 0) { - if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) { - Log(LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__); - nStatus=RD_FAILED; - goto clean; - } - size += nRead; - count += nRead; + /* If we succeeded, we're done. If writing to stdout + * we can't seek and retry. + */ + if (nStatus != RD_INCOMPLETE || bStdoutMode || !rtmp->IsTimedout()) + break; - //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(); - - if(duration > 0) { - // make sure we claim to have enough buffer time! - if(!bOverrideBufferTime && bufferTime < (duration*1000.0)) { - bufferTime = (uint32_t)(duration*1000.0)+5000; // extra 5sec to make sure we've got enough - - Log(LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime); - rtmp->SetBufferMS(bufferTime); - rtmp->UpdateBufferMS(); - } - percent = ((double)timestamp) / (duration*1000.0)*100.0; - percent = round(percent*10.0)/10.0; - if (count > PRINT_CHUNK && debuglevel >= LOGINFO) { - LogPrintf("\r%.3f KB (%.1f%%)", (double)size/1024.0, percent); - count = 0; - } - } else { - if (count > PRINT_CHUNK && debuglevel >= LOGINFO) { - LogPrintf("\r%.3f KB", (double)size/1024.0); - count = 0; - } - } - } - #ifdef _DEBUG - else { Log(LOGDEBUG, "zero read!"); } - #endif - - // Force clean close if a specified stop offset is reached - if (dStopOffset && timestamp >= dStopOffset) { - LogPrintf("\nStop offset has been reached at %.2f seconds\n", (double)dStopOffset/1000.0); - nRead = 0; - rtmp->Close(); - } - - } while(!bCtrlC && nRead > -1 && rtmp->IsConnected()); + nInitialFrameSize = 0; - // finalize header by writing the correct dataType (video, audio, video+audio) - if(!bResume && dataType != 0x5 && !bStdoutMode) { - Log(LOGDEBUG, "Writing data type: %02X", dataType); - fseek(file, 4, SEEK_SET); - fwrite(&dataType, sizeof(unsigned char), 1, file); - } + Log(LOGINFO, "Connection timed out, trying to reconnect.\n\n"); + + nStatus = GetLastKeyframe(file, nSkipKeyFrames, + &dSeek, &initialFrame, + &initialFrameType, + &nInitialFrameSize); + if (nStatus != RD_SUCCESS) { + Log(LOGDEBUG, "Failed to get last keyframe."); + break; + } - if(bResume && nRead == -2) { - LogPrintf("\rCouldn't resume FLV file, try --skip %d\n\n", nSkipKeyFrames+1); - nStatus = RD_FAILED; - goto clean; - } - - // If duration is available then assume the download is complete if > 99.9% - if (bLiveStream == false) { - if (duration > 0 && percent > 99.9) { - LogPrintf("\nDownload complete\n"); - nStatus = RD_SUCCESS; - goto clean; - //} else if ( bCtrlC || nRead != (-1) ) { - } else { - LogPrintf("\nDownload may be incomplete (downloaded about %.2f%%), try --resume\n", percent); - nStatus = RD_INCOMPLETE; - goto clean; - } + if (!rtmp->ReconnectStream(bufferTime, dSeek)) { + Log(LOGERROR, "Failed to resume the stream\n\n"); + if (!rtmp->IsTimedout()) { + nStatus = RD_FAILED; + break; + } + } + bResume = true; } - // If nRead is zero then assume complete - if(nRead == 0) { - LogPrintf("\nDownload complete\n"); - nStatus = RD_SUCCESS; - goto clean; + if (nStatus == RD_SUCCESS) { + LogPrintf("\nDownload complete\n"); + } else if (nStatus == RD_INCOMPLETE) { + LogPrintf("\nDownload may be incomplete (downloaded about %.2f%%), try --resume\n", percent); } - - // Ensure we have a non-zero exit code where WriteStream has failed - if (nRead < 0) - nStatus = RD_INCOMPLETE; - //Log(LOGDEBUG, "nStatus: %d, nRead: %d", nStatus, nRead); clean: LogPrintf("\rClosing connection.\n"); rtmp->Close();
hyc wrote:
Author: hyc Date: Fri Oct 30 20:12:27 2009 New Revision: 12
Log: Partial merge from RTMPDump-YLE: fix buffer overruns, fix client reporting interval, attempt auto-resume
Modified: handshake.cpp rtmp.cpp rtmp.h rtmpdump.cpp
Downloads from hulu still hang for me, despite the setting of the BufferTime. Typically I receive a ping type 31 when the stream stops. The reconnect code used here doesn't work either, I get a ping type 32, then a 1, then a 31 and then it hangs again. Anyone have any idea what these type 31 and 32 messages are, and what response (if any) should be sent? -- hyc
Howard Chu wrote:
Downloads from hulu still hang for me, despite the setting of the BufferTime. Typically I receive a ping type 31 when the stream stops. The reconnect code used here doesn't work either, I get a ping type 32, then a 1, then a 31 and then it hangs again. Anyone have any idea what these type 31 and 32 messages are, and what response (if any) should be sent?
Looks like we can ignore them; I've got the auto-reconnect working now.
-- hyc
participants (2)
-
Howard Chu -
hyc