[rtmpdump] r12 - handshake.cpp rtmp.cpp rtmp.h rtmpdump.cpp
hyc
subversion at mplayerhq.hu
Fri Oct 30 20:12:27 CET 2009
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();
More information about the rtmpdump
mailing list