文章目录
- librtmp 库相关结构体
- AMF(Action Message Format): 动作消息格式
- 推流
- 拉流
- 参考文献
librtmp 库相关结构体
RTMP 结构体
表示一个rtmp连接
typedef struct RTMP
{
int m_inChunkSize; //拉流流分块大小:初始化默认128字节 RTMP_DEFAULT_CHUNKSIZE 128
int m_outChunkSize; //推流分块大小:初始化默认128字节 RTMP_DEFAULT_CHUNKSIZE 128
int m_nBWCheckCounter;
int m_nBytesIn;
int m_nBytesInSent;
int m_nBufferMS;
int m_stream_id; /* returned in _result from createStream */
int m_mediaChannel;
uint32_t m_mediaStamp;
uint32_t m_pauseStamp;
int m_pausing;
int m_nServerBW;
int m_nClientBW;
uint8_t m_nClientBW2;
uint8_t m_bPlaying;
uint8_t m_bSendEncoding;
uint8_t m_bSendCounter;
int m_numInvokes;
int m_numCalls;
RTMP_METHOD *m_methodCalls; /* remote method calls queue */
RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS];
RTMPPacket *m_vecChannelsOut[RTMP_CHANNELS];
int m_channelTimestamp[RTMP_CHANNELS]; /* abs timestamp of last packet */
double m_fAudioCodecs; /* audioCodecs for the connect packet */
double m_fVideoCodecs; /* videoCodecs for the connect packet */
double m_fEncoding; /* AMF0 or AMF3 */
double m_fDuration; /* duration of stream in seconds */
int m_msgCounter; /* RTMPT stuff */
int m_polling;
int m_resplen;
int m_unackd;
AVal m_clientID;
RTMP_READ m_read;
RTMPPacket m_write;
RTMPSockBuf m_sb;
RTMP_LNK Link;
} RTMP;
RTMP_LNK 结构体
typedef struct RTMP_LNK
{
AVal hostname;
AVal sockshost;
AVal playpath0; /* parsed from URL */
AVal playpath; /* passed in explicitly */
AVal tcUrl;
AVal swfUrl;
AVal pageUrl;
AVal app;
AVal auth;
AVal flashVer;
AVal subscribepath;
AVal token;
AMFObject extras;
int edepth;
int seekTime;
int stopTime;
#define RTMP_LF_AUTH 0x0001 /* using auth param */
#define RTMP_LF_LIVE 0x0002 /* stream is live */
#define RTMP_LF_SWFV 0x0004 /* do SWF verification */
#define RTMP_LF_PLST 0x0008 /* send playlist before play */
#define RTMP_LF_BUFX 0x0010 /* toggle stream on BufferEmpty msg */
#define RTMP_LF_FTCU 0x0020 /* free tcUrl on close */
int lFlags;
int swfAge;
int protocol;
int timeout; /* connection timeout in seconds */
unsigned short socksport;
unsigned short port;
#ifdef CRYPTO
#define RTMP_SWF_HASHLEN 32
void *dh; /* for encryption */
void *rc4keyIn;
void *rc4keyOut;
uint32_t SWFSize;
uint8_t SWFHash[RTMP_SWF_HASHLEN];
char SWFVerificationResponse[RTMP_SWF_HASHLEN+10];
#endif
} RTMP_LNK;
RTMPPacket 结构体:描述实时消息协议的分块
//块由头和数据组成,原始的rtmp消息块表示其中的数据都是没有经过解析的,是原始的字节流
#define RTMP_MAX_HEADER_SIZE 18
typedef struct RTMPChunk
{
int c_headerSize; //头部的长度
int c_chunkSize; //chunk的大小
char *c_chunk; //数据
char c_header[RTMP_MAX_HEADER_SIZE]; //chunk头部
} RTMPChunk;
typedef struct RTMPPacket
{
uint8_t m_headerType; //basic header 中的type头字节,值为(0,1,2,3)表示ChunkMsgHeader的类型(4种)
uint8_t m_packetType; //Chunk Msg Header中msg type 1字节:消息类型id(8: audio;9:video;18:AMF0编码的元数据)
uint8_t m_hasAbsTimestamp; //bool值,是否是绝对时间戳(类型1时为true)
int m_nChannel; //块流ID ,通过设置ChannelID来设置Basic stream id的长度和值
uint32_t m_nTimeStamp; //时间戳,消息头前三字节
int32_t m_nInfoField2; //Chunk Msg Header中msg StreamID 4字节:消息流id
uint32_t m_nBodySize; //Chunk Msg Header中msg length 4字节:消息长度
uint32_t m_nBytesRead; //已读取的数据
RTMPChunk *m_chunk; //raw chunk结构体指针,把RTMPPacket的真实头部和数据段拷贝进来
char *m_body; //数据段指针
} RTMPPacket;
RTMPPacket_Alloc() RTMPPacket_Alloc() RTMPPacket_Free() RTMPPacket_Dump()
void RTMPPacket_Reset(RTMPPacket *p)
{
p->m_headerType = 0;
p->m_packetType = 0;
p->m_nChannel = 0;
p->m_nTimeStamp = 0;
p->m_nInfoField2 = 0;
p->m_hasAbsTimestamp = FALSE;
p->m_nBodySize = 0;
p->m_nBytesRead = 0;
}
int RTMPPacket_Alloc(RTMPPacket *p, int nSize)
{
char *ptr = calloc(1, nSize + RTMP_MAX_HEADER_SIZE);
if (!ptr)
return FALSE;
p->m_body = ptr + RTMP_MAX_HEADER_SIZE;
p->m_nBytesRead = 0;
return TRUE;
}
void RTMPPacket_Free(RTMPPacket *p)
{
if (p->m_body)
{
free(p->m_body - RTMP_MAX_HEADER_SIZE);
p->m_body = NULL;
}
}
void RTMPPacket_Dump(RTMPPacket *p)
{
RTMP_Log(RTMP_LOGDEBUG,
"RTMP PACKET: packet type: 0x%02x. channel: 0x%02x. info 1: %d info 2: %d. Body size: %lu. body: 0x%02x",
p->m_packetType, p->m_nChannel, p->m_nTimeStamp, p->m_nInfoField2,
p->m_nBodySize, p->m_body ? (unsigned char)p->m_body[0] : 0);
}
AMF(Action Message Format): 动作消息格式
它是一种二进制的数据格式,++AMF数据采用Big-Endian(大端模式,高字节存储在内存的低地址端)++。它的设计是为了把actionscript里面的数据(包括Object,Array,Boolean,Number等)序列化成二进制数据,然后把这段数据随意发送给其他接收方程序,比如发给远程的服务器,在远程服务器那边,可以把这段数据给还原出来,以此达到一个数据传输的作用。
AVal结构体:自定义字符串
// AMF自定义的字符串;
typedef struct AVal
{
char *av_val; //字符串指针
int av_len; //字符串长度
} AVal;
// AVal的快速初始化;
#define AVC(str) {str, sizeof(str)-1}
// 比较AVal字符串;
#define AVMATCH(a1,a2) ((a1)->av_len == (a2)->av_len && !memcmp((a1)->av_val,(a2)->av_val,(a1)->av_len))
推流
InitSockets() 初始化Socket
int InitSockets()
{
#ifdef WIN32
WORD version;
WSADATA wsaData;
version = MAKEWORD(1, 1);
return (WSAStartup(version, &wsaData) == 0);
#else
return TRUE;
#endif
}
RTMP_Alloc() 用于创建一个RTMP的结构体
RTMP* RTMP_Alloc()
{
return calloc(1, sizeof(RTMP));
}
RTMP_Init() 初始化结构体
void RTMP_Init(RTMP *r)
{
#ifdef CRYPTO
if (!RTMP_TLS_ctx)
RTMP_TLS_Init();
#endif
memset(r, 0, sizeof(RTMP));
r->m_sb.sb_socket = -1;
r->m_inChunkSize = RTMP_DEFAULT_CHUNKSIZE;
r->m_outChunkSize = RTMP_DEFAULT_CHUNKSIZE;
r->m_nBufferMS = 30000;
r->m_nClientBW = 2500000;
r->m_nClientBW2 = 2;
r->m_nServerBW = 2500000;
r->m_fAudioCodecs = 3191.0;
r->m_fVideoCodecs = 252.0;
r->Link.timeout = 30;
r->Link.swfAge = 30;
}
RTMP_SetupURL() 设置推拉流的URL
内部调用了RTMP_ParseURL主要用于解析url
RTMP_ParseURL() 解析输入URL
RTMP_EnableWrite() 设置为推流状态
//是否推流,默认不推流
void RTMP_EnableWrite(RTMP *r)
{
r->Link.protocol |= RTMP_FEATURE_WRITE;
}
RTMP_Connect() 建立NetConnection
主要分为RTMP_Connect0()函数(第0次连接,建立socket连接)+ RTMP_Connect1函数(第1次连接,建立真正的rtmp连接。主要分为HandShake握手、SendConnectPacket发送命令请求)
RTMP_Connect0() 第0次连接,建立socket连接
int RTMP_Connect0(RTMP *r, struct sockaddr * service)
{
int on = 1;
r->m_sb.sb_timedout = FALSE;
r->m_pausing = 0;
r->m_fDuration = 0.0;
r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (r->m_sb.sb_socket != -1)
{
if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0)
{
int err = GetSockError();
RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)",
__FUNCTION__, err, strerror(err));
RTMP_Close(r);
return FALSE;
}
if (r->Link.socksport)
{
RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__);
if (!SocksNegotiate(r))
{
RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
}
}
else
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__,
GetSockError());
return FALSE;
}
/* set timeout */
{
SET_RCVTIMEO(tv, r->Link.timeout);
if (setsockopt
(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)))
{
RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
__FUNCTION__, r->Link.timeout);
}
}
setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
return TRUE;
}
RTMP_Connect1() 第1次连接,建立真正的rtmp连接
主要分为HandShake握手、SendConnectPacket发送命令请求)
HandShake() 握手过程实现(C0+C1, S0+S1+S2, C2)
- C0 和 S0消息格式:C0和S0是单独的一个字节,表示版本信息
在C0中这个字段表示客户端要求的RTMP版本 。在S0中这个字段表示服务器选择的RTMP版本。本规范所定义的版本是3;0-2是早期产品所用的,已被丢弃;4-31保留在未来使用 ;32-255不允许使用 (为了区分其他以某一字符开始的文本协议)。如果服务无法识别客户端请求的版本,应该返回3 。客户端可以选择减到版本3或选择取消握手
- C1 和 S1消息格式:C1和S1消息有1536字节长
- 时间:4字节:本字段包含时间戳。该时间戳应该是发送这个数据块的端点的后续块的时间起始点。可以是0,或其他的任何值。为了同步多个流,端点可能发送其块流的当前值。
- 零:4字节:本字段必须是全零。
- 随机数据:1528字节。本字段可以包含任何值。因为每个端点必须用自己初始化的握手和对端初始化的握手来区分身份,所以这个数据应有充分的随机性。但是并不需要加密安全的随机值,或者动态值
- C2 和 S2 消息格式:C2和S2消息有1536字节长,只是S1和C1的回复
- 时间:4字节:本字段必须包含对等段发送的时间(对C2来说是S1,对S2来说是C1)。
- 时间2:4字节:本字段必须包含先前发送的并被对端读取的包的时间戳。
- 随机回复:1528字节:本字段必须包含对端发送的随机数据字段(对C2来说是S1,对S2来说是C1)。每个对等端可以用时间和时间2字段中的时间戳来快速地估计带宽和延迟。但这样做可能并不实用。
//不使用加密
#ifndef CRYPTO
static int HandShake(RTMP *r, int FP9HandShake)
{
int i;
uint32_t uptime, suptime;
int bMatch;
char type;
//#define RTMP_SIG_SIZE 1536:C1和S1消息有1536字节长
char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1;//C0和C1同时发送,因此多一字节C0
char serversig[RTMP_SIG_SIZE];
//0x03代表RTMP协议的版本(客户端要求的)
clientbuf[0] = 0x03; /* not encrypted */
//当前时间,填充C1前4字节
uptime = htonl(RTMP_GetTime());
memcpy(clientsig, &uptime, 4);
//4字节0填充
memset(&clientsig[4], 0, 4);
#ifdef _DEBUG
for (i = 8; i < RTMP_SIG_SIZE; i++)
clientsig[i] = 0xff;
#else
//随机数填充
for (i = 8; i < RTMP_SIG_SIZE; i++)
clientsig[i] = (char)(rand() % 256);
#endif
//同时发送C0+C1
if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1))
return FALSE;
//读取1字节服务端返回值S0填充到type
if (ReadN(r, &type, 1) != 1) /* 0x03 or 0x06 */
return FALSE;
RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer : %02X", __FUNCTION__, type);
if (type != clientbuf[0])
RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d",
__FUNCTION__, clientbuf[0], type);
//读取1536字节的S1到serversig
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
/* decode server response */
//获取服务端时间
memcpy(&suptime, serversig, 4);
suptime = ntohl(suptime);
RTMP_Log(RTMP_LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime);
RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version : %d.%d.%d.%d", __FUNCTION__,
serversig[4], serversig[5], serversig[6], serversig[7]);
/* 2nd part of handshake */
if (!WriteN(r, serversig, RTMP_SIG_SIZE))
return FALSE;
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0);
if (!bMatch)
{
RTMP_Log(RTMP_LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
}
return TRUE;
}
SendConnectPacket() 发送命令请求
发送"connect"等各种字符串到服务端
#define SAVC(x) static const AVal av_##x = AVC(#x) //可知av_connect==》SAVC(connect)
#define AVC(str) {str,sizeof(str)-1}
SAVC(app);
SAVC(connect);
SAVC(flashVer);
SAVC(swfUrl);
SAVC(pageUrl);
SAVC(tcUrl);
SAVC(fpad);
SAVC(capabilities);
SAVC(audioCodecs);
SAVC(videoCodecs);
SAVC(videoFunction);
SAVC(objectEncoding);
SAVC(secureToken);
SAVC(secureTokenResponse);
SAVC(type);
SAVC(nonprivate);
static int SendConnectPacket(RTMP *r, RTMPPacket *cp)
{
RTMPPacket packet;
char pbuf[4096], *pend = pbuf + sizeof(pbuf);
char *enc;
if (cp)
return RTMP_SendPacket(r, cp, TRUE);
//块流ID
packet.m_nChannel = 0x03; /* control channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
//消息类型为20的用AMF0编码,这些消息用于在远端实现连接,创建流,发布,播放和暂停等操作
packet.m_packetType = 0x14; /* INVOKE */
packet.m_nTimeStamp = 0;
//流ID需要设置为0
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
enc = packet.m_body;
//connect使用##进行字符串化连接,此处编码connect字符串
enc = AMF_EncodeString(enc, pend, &av_connect);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_OBJECT;
enc = AMF_EncodeNamedString(enc, pend, &av_app, &r->Link.app);
if (!enc)
return FALSE;
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
enc = AMF_EncodeNamedString(enc, pend, &av_type, &av_nonprivate);
if (!enc)
return FALSE;
}
if (r->Link.flashVer.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_flashVer, &r->Link.flashVer);
if (!enc)
return FALSE;
}
if (r->Link.swfUrl.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_swfUrl, &r->Link.swfUrl);
if (!enc)
return FALSE;
}
if (r->Link.tcUrl.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_tcUrl, &r->Link.tcUrl);
if (!enc)
return FALSE;
}
if (!(r->Link.protocol & RTMP_FEATURE_WRITE))
{
enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, FALSE);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_videoCodecs, r->m_fVideoCodecs);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_videoFunction, 1.0);
if (!enc)
return FALSE;
if (r->Link.pageUrl.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_pageUrl, &r->Link.pageUrl);
if (!enc)
return FALSE;
}
}
if (r->m_fEncoding != 0.0 || r->m_bSendEncoding)
{ /* AMF0, AMF3 not fully supported yet */
enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding);
if (!enc)
return FALSE;
}
if (enc + 3 >= pend)
return FALSE;
*enc++ = 0;
*enc++ = 0; /* end of object - 0x00 0x00 0x09 */
*enc++ = AMF_OBJECT_END;
/* add auth string */
if (r->Link.auth.av_len)
{
enc = AMF_EncodeBoolean(enc, pend, r->Link.lFlags & RTMP_LF_AUTH);
if (!enc)
return FALSE;
enc = AMF_EncodeString(enc, pend, &r->Link.auth);
if (!enc)
return FALSE;
}
if (r->Link.extras.o_num)
{
int i;
for (i = 0; i < r->Link.extras.o_num; i++)
{
enc = AMFProp_Encode(&r->Link.extras.o_props[i], enc, pend);
if (!enc)
return FALSE;
}
}
packet.m_nBodySize = enc - packet.m_body;
return RTMP_SendPacket(r, &packet, TRUE);
}
RTMP_ConnectStream() 建立NetStream
RTMP_ClientPacket() 处理收到的消息
消息类型解析
int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)
{
int bHasMediaPacket = 0;
switch (packet->m_packetType)
{
/* 协议控制消息:RTMP 块流使用类型为 1、2、3、5 和 6 的消息用于协议控制消息。
这些消息包含 RTMP块流协议需要的信息。这个协议控制消息必须(MUST)使用 ID 为 0 消息流
并且用 ID 为 2 块流发送。协议控制消息在接收到时尽快处理。
*/
case 0x01:
/* chunk size */
HandleChangeChunkSize(r, packet);
break;
case 0x03:
/* bytes read report */
RTMP_Log(RTMP_LOGDEBUG, "%s, received: bytes read report", __FUNCTION__);
break;
/*用户控制消息:RTMP 使用类型 ID 为 4 的消息做为用户控制消息,
用户控制消息应该(SHOULD)消息流 ID 0(称为控制流),
并且通过 RTMP 块流发送,使用块流 ID 为 2。
*/
case 0x04:
/* ctrl */
HandleCtrl(r, packet);
break;
case 0x05:
/* server bw */
HandleServerBW(r, packet);
break;
case 0x06:
/* client bw */
HandleClientBW(r, packet);
break;
//类型为 8 的消息保留给音频消息
case 0x08:
/* audio data */
/*RTMP_Log(RTMP_LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); */
HandleAudio(r, packet);
bHasMediaPacket = 1;
if (!r->m_mediaChannel)
r->m_mediaChannel = packet->m_nChannel;
if (!r->m_pausing)
r->m_mediaStamp = packet->m_nTimeStamp;
break;
//类型为 9 的消息保留给视频消息
case 0x09:
/* video data */
/*RTMP_Log(RTMP_LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); */
HandleVideo(r, packet);
bHasMediaPacket = 1;
if (!r->m_mediaChannel)
r->m_mediaChannel = packet->m_nChannel;
if (!r->m_pausing)
r->m_mediaStamp = packet->m_nTimeStamp;
break;
case 0x0F: /* flex stream send */
RTMP_Log(RTMP_LOGDEBUG,
"%s, flex stream send, size %lu bytes, not supported, ignoring",
__FUNCTION__, packet->m_nBodySize);
break;
case 0x10: /* flex shared object */
RTMP_Log(RTMP_LOGDEBUG,
"%s, flex shared object, size %lu bytes, not supported, ignoring",
__FUNCTION__, packet->m_nBodySize);
break;
case 0x11: /* flex message */
{
RTMP_Log(RTMP_LOGDEBUG,
"%s, flex message, size %lu bytes, not fully supported",
__FUNCTION__, packet->m_nBodySize);
/*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */
/* some DEBUG code */
#if 0
RTMP_LIB_AMFObject obj;
int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1);
if(nRes < 0) {
RTMP_Log(RTMP_LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__);
/*return; */
}
obj.Dump();
#endif
if (HandleInvoke(r, packet->m_body + 1, packet->m_nBodySize - 1) == 1)
bHasMediaPacket = 2;
break;
}
/*元数据消息:客户端用这个消息向对端发送 Metadata 或者任意的用户数据。
Metadata 包函了数据(音频、视频)的详细信息,像创建时间,时长,主题等等。
这些消息使用消息类型 18 表示 AMF0,用消息类型 15 来表示 AMF3。
*/
case 0x12:
/* metadata (notify) */
RTMP_Log(RTMP_LOGDEBUG, "%s, received: notify %lu bytes", __FUNCTION__,
packet->m_nBodySize);
if (HandleMetadata(r, packet->m_body, packet->m_nBodySize))
bHasMediaPacket = 1;
break;
/*共享对象:共享对象是一个 Flash 对象(一个键值对的集合),用来同步多个客户端,应用实例等等。
消息类型为 19 表示使用 AMF0,16 保留用作 AMF3 编码共享事件。
*/
case 0x13:
RTMP_Log(RTMP_LOGDEBUG, "%s, shared object, not supported, ignoring",
__FUNCTION__);
break;
/*命令消息,消息类型为17或20)发送端发送时会带有命令的名字,
如connect,TransactionID表示此次命令的标识,Command Object表示相关参数。
*/
case 0x14:
/* invoke */
RTMP_Log(RTMP_LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__,
packet->m_nBodySize);
/*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */
if (HandleInvoke(r, packet->m_body, packet->m_nBodySize) == 1)
bHasMediaPacket = 2;
break;
//处理flv元数据
case 0x16:
{
/* go through FLV packets and handle metadata packets */
unsigned int pos = 0;
uint32_t nTimeStamp = packet->m_nTimeStamp;
while (pos + 11 < packet->m_nBodySize)
{
//从flv头部获取数据长度
uint32_t dataSize = AMF_DecodeInt24(packet->m_body + pos + 1); /* size without header (11) and prevTagSize (4) */
if (pos + 11 + dataSize + 4 > packet->m_nBodySize)
{
RTMP_Log(RTMP_LOGWARNING, "Stream corrupt?!");
break;
}
//类型为18,处理元数据
if (packet->m_body[pos] == 0x12)
{
HandleMetadata(r, packet->m_body + pos + 11, dataSize);
}
//类型为8表示音频数据,9表示视频数据
else if (packet->m_body[pos] == 8 || packet->m_body[pos] == 9)
{
nTimeStamp = AMF_DecodeInt24(packet->m_body + pos + 4);
nTimeStamp |= (packet->m_body[pos + 7] << 24);
}
pos += (11 + dataSize + 4);//指向下一个flv数据块头部
}
if (!r->m_pausing)
r->m_mediaStamp = nTimeStamp;
/* FLV tag(s) */
/*RTMP_Log(RTMP_LOGDEBUG, "%s, received: FLV tag(s) %lu bytes", __FUNCTION__, packet.m_nBodySize); */
bHasMediaPacket = 1;
break;
}
default:
RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__,
packet->m_packetType);
#ifdef _DEBUG
RTMP_LogHex(RTMP_LOGDEBUG, packet->m_body, packet->m_nBodySize);
#endif
}
return bHasMediaPacket;
}
RTMP_SendPacket() 消息流发送,内含分块处理
int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)
{
/*获取m_vecChannelsOut数组接收对应块流的包并保存待使用;#define RTMP_CHANNELS 65600
本协议支持65597种块流,ID从3-65599.ID 0,1,2作为保留。
0表示ID范围64-319(第二字节+64)
1表示范围是64-65599(第三字节*256 + 第二字节+64)
2表示低层协议消息
3-63
#define RTMP_CHANNELS 65600
RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS];
RTMPPacket *m_vecChannelsOut[RTMP_CHANNELS];
*/
const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
uint32_t last = 0;//上一次相对时间戳
int nSize;//实际头部大小
int hSize, cSize;
//header头指针指向头部,hend块尾指针指向body头部
//hbuf表示头部最大18(3字节最大块基本头+11字节最大快消息头+4字节扩展时间戳)缓冲数组
char *header, *hptr, *hend, hbuf[RTMP_MAX_HEADER_SIZE], c;
uint32_t t;
//
char *buffer, *tbuf = NULL, *toff = NULL;
int nChunkSize;
int tlen;
//根据m_headerType值判断消息类型(0,1,2,3)块基本头的高两位
if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
{
/* compress a bit by using the prev packet's attributes */
if (prevPacket->m_nBodySize == packet->m_nBodySize
&& prevPacket->m_packetType == packet->m_packetType
&& packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)
packet->m_headerType = RTMP_PACKET_SIZE_SMALL;//块类型判断为2
if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp
&& packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;//块类型判断为2
last = prevPacket->m_nTimeStamp;
}
if (packet->m_headerType > 3) /* sanity */
{
RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.",
(unsigned char)packet->m_headerType);
return FALSE;
}
//static const int packetSize[] = { 12, 8, 4, 1 };块消息头加上一字节块基本头
nSize = packetSize[packet->m_headerType];
hSize = nSize; cSize = 0;
t = packet->m_nTimeStamp - last;//相对时间
if (packet->m_body)
{
header = packet->m_body - nSize;
hend = packet->m_body;
}
else //消息体为空
{
header = hbuf + 6;//header指向消息长度,最长块基本头3+块消息头3字节时间戳
hend = hbuf + sizeof(hbuf);
}
//由块流ID判断块类型
if (packet->m_nChannel > 319)
cSize = 2;//块流id所占字节
else if (packet->m_nChannel > 63)
cSize = 1;
if (cSize)
{
header -= cSize;
hSize += cSize;
}
//时间大于0xffffff增加4字节扩展时间戳大小
if (nSize > 1 && t >= 0xffffff)
{
header -= 4;
hSize += 4;
}
hptr = header;
c = packet->m_headerType << 6;//块类型
switch (cSize)
{
case 0:
c |= packet->m_nChannel;
break;
case 1:
break;
case 2:
c |= 1;
break;
}
*hptr++ = c;
if (cSize)
{
int tmp = packet->m_nChannel - 64;
*hptr++ = tmp & 0xff;
if (cSize == 2)
*hptr++ = tmp >> 8;
}
if (nSize > 1)
{
hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
}
if (nSize > 4)
{
hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);
*hptr++ = packet->m_packetType;
}
if (nSize > 8)
hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
if (nSize > 1 && t >= 0xffffff)
hptr = AMF_EncodeInt32(hptr, hend, t);
// 到此为止 已经将块头填写好了;
// 此时nSize表示负载数据的长度 buffer是指向负载数据区的指针;
nSize = packet->m_nBodySize; //数据大小
buffer = packet->m_body; //数据头部指针
nChunkSize = r->m_outChunkSize; //输出块大小,默认是128字节
RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket,
nSize);
/* send all chunks in one HTTP request */
if (r->Link.protocol & RTMP_FEATURE_HTTP)
{
int chunks = (nSize+nChunkSize-1) / nChunkSize;//分块数
if (chunks > 1)
{
tlen = chunks * (cSize + 1) + nSize + hSize;
tbuf = malloc(tlen);
if (!tbuf)
return FALSE;
toff = tbuf;
}
}
//
while (nSize + hSize)
{
int wrote;
if (nSize < nChunkSize)//最后一个块大小
nChunkSize = nSize;
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)header, hSize);
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)buffer, nChunkSize);
if (tbuf)
{
// 将从Chunk头开始的nChunkSize + hSize个字节拷贝至toff中;
// 这些拷贝的数据包括块头数据(hSize字节)和nChunkSize个负载数据;
memcpy(toff, header, nChunkSize + hSize);
toff += nChunkSize + hSize;
}
else
{
wrote = WriteN(r, header, nChunkSize + hSize);
if (!wrote)
return FALSE;
}
nSize -= nChunkSize;
buffer += nChunkSize;
hSize = 0;
if (nSize > 0)
{
header = buffer - 1;
hSize = 1;
if (cSize)
{
header -= cSize;
hSize += cSize;
}
*header = (0xc0 | c);
if (cSize)
{
int tmp = packet->m_nChannel - 64;
header[1] = tmp & 0xff;
if (cSize == 2)
header[2] = tmp >> 8;
}
}
}
if (tbuf)
{
int wrote = WriteN(r, tbuf, toff-tbuf);
free(tbuf);
tbuf = NULL;
if (!wrote)
return FALSE;
}
/* we invoked a remote method */
if (packet->m_packetType == 0x14)//消息流类型:0x14(20)
{
AVal method;
char *ptr;
ptr = packet->m_body + 1;
AMF_DecodeString(ptr, &method);
RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);
/* keep it in call queue till result arrives */
if (queue) {
int txn;
ptr += 3 + method.av_len;
txn = (int)AMF_DecodeNumber(ptr);
AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);
}
}
//m_vecChannelsOut数组添加传入的packet
if (!r->m_vecChannelsOut[packet->m_nChannel])
r->m_vecChannelsOut[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
return TRUE;
}
RTMP_Close() 关闭连接
CleanupSockets() 清理Socket
inline void CleanupSockets()
{
#ifdef WIN32
WSACleanup();
#endif
}
拉流
RTMP_ReadPacket() 读取RTMPPacket包
int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)
{
uint8_t hbuf[RTMP_MAX_HEADER_SIZE] = { 0 };//按最大字节初始化
char *header = (char *)hbuf;
int nSize, hSize, nToRead, nChunk;
int didAlloc = FALSE;
RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d", __FUNCTION__, r->m_sb.sb_socket);
//解析块基本头(1-3字节)
if (ReadN(r, (char *)hbuf, 1) == 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);
return FALSE;
}
packet->m_headerType = (hbuf[0] & 0xc0) >> 6;//获取块类型
//3-63 之间的值表示完整的流ID
packet->m_nChannel = (hbuf[0] & 0x3f);//获取块流id:0x3f首字节后6比特位
header++;
//ID 0、1作为保留:
if (packet->m_nChannel == 0)
{
//0,表示ID 的范围是64-319(需要2个字节得出块id:第二个字节+64)
if (ReadN(r, (char *)&hbuf[1], 1) != 1)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte",
__FUNCTION__);
return FALSE;
}
packet->m_nChannel = hbuf[1];
packet->m_nChannel += 64;
header++;
}
else if (packet->m_nChannel == 1)
{
//1,表示ID 范围是64-65599(需要3个字节得出块id:第三个字节*256+第二个字节+64)
int tmp;
if (ReadN(r, (char *)&hbuf[1], 2) != 2)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte",
__FUNCTION__);
return FALSE;
}
tmp = (hbuf[2] << 8) + hbuf[1];
packet->m_nChannel = tmp + 64;
RTMP_Log(RTMP_LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);
header += 2;
}
//basic header 中的type头字节,值为(0,1,2,3)表示ChunkMsgHeader的类型(11,7,3,0)字节长度
//static const int packetSize[] = { 12, 8, 4, 1 };
nSize = packetSize[packet->m_headerType];//获取块消息头字节大小
//判断是否为绝对时间戳:对于0 类型的块,消息的绝对时间戳在这里发送
if (nSize == RTMP_LARGE_HEADER_SIZE) /* if we get a full header the timestamp is absolute */
packet->m_hasAbsTimestamp = TRUE;
else if (nSize < RTMP_LARGE_HEADER_SIZE)
{ /* using values from the last message of this channel */ //使用此通道上一条消息中的值
//根据块流id号从RTMPPacket *的m_vecChannelsIn数组中获取目标分块(用块流id对应m_vecChannelsIn数组下标)
if (r->m_vecChannelsIn[packet->m_nChannel])
memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel],
sizeof(RTMPPacket));
}
//块消息头值,(块基本头最少一个字节)
nSize--;
//解析块消息头
//块基本头大于1字节时,读取块消息头,header指向消息头地址
if (nSize > 0 && ReadN(r, header, nSize) != nSize)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header. type: %x",
__FUNCTION__, (unsigned int)hbuf[0]);
return FALSE;
}
//hSize大小指向数据段地址起始
hSize = nSize + (header - (char *)hbuf);
//非3类型
if (nSize >= 3)
{
//前3字节填充时间戳,块消息头前三字节
packet->m_nTimeStamp = AMF_DecodeInt24(header);
/*RTMP_Log(RTMP_LOGDEBUG, "%s, reading RTMP packet chunk on channel %x, headersz %i, timestamp %i, abs timestamp %i", __FUNCTION__, packet.m_nChannel, nSize, packet.m_nTimeStamp, packet.m_hasAbsTimestamp); */
if (nSize >= 6)
{
//第4-6字节填充块消息长度
packet->m_nBodySize = AMF_DecodeInt24(header + 3);
packet->m_nBytesRead = 0;
RTMPPacket_Free(packet);
if (nSize > 6)
{
//第7字节填充消息类型
packet->m_packetType = header[6];
//第8-11字节填充消息id
if (nSize == 11)
packet->m_nInfoField2 = DecodeInt32LE(header + 7);
}
}
if (packet->m_nTimeStamp == 0xffffff)//时间戳满,启用扩展时间戳
{
if (ReadN(r, header + nSize, 4) != 4)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read extended timestamp",
__FUNCTION__);
return FALSE;
}
packet->m_nTimeStamp = AMF_DecodeInt32(header + nSize);
hSize += 4;
}
}
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)hbuf, hSize);
//分配数据空间
if (packet->m_nBodySize > 0 && packet->m_body == NULL)
{
if (!RTMPPacket_Alloc(packet, packet->m_nBodySize))
{
RTMP_Log(RTMP_LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__);
return FALSE;
}
didAlloc = TRUE;
packet->m_headerType = (hbuf[0] & 0xc0) >> 6;
}
//将要读取的数据
nToRead = packet->m_nBodySize - packet->m_nBytesRead;
//rtmp流输入块大小
nChunk = r->m_inChunkSize;
// 剩下的消息数据长度如果比块尺寸大,则需要分块,否则块尺寸就等于剩下的消息数据长度
if (nToRead < nChunk)
nChunk = nToRead;
/* Does the caller want the raw chunk? */
//根据解析的数据重新封装一个RTMPChunk *对象,即获得raw chunk
if (packet->m_chunk)
{
packet->m_chunk->c_headerSize = hSize;
memcpy(packet->m_chunk->c_header, hbuf, hSize);
packet->m_chunk->c_chunk = packet->m_body + packet->m_nBytesRead;
packet->m_chunk->c_chunkSize = nChunk;
}
if (ReadN(r, packet->m_body + packet->m_nBytesRead, nChunk) != nChunk)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet body. len: %lu",
__FUNCTION__, packet->m_nBodySize);
return FALSE;
}
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)packet->m_body + packet->m_nBytesRead, nChunk);
// 更新已读数据字节个数
packet->m_nBytesRead += nChunk;
/* keep the packet as ref for other packets on this channel */
if (!r->m_vecChannelsIn[packet->m_nChannel])
r->m_vecChannelsIn[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
memcpy(r->m_vecChannelsIn[packet->m_nChannel], packet, sizeof(RTMPPacket));
//数据准备完毕,清除r内存
if (RTMPPacket_IsReady(packet))
{
/* make packet's timestamp absolute *//*绝对时间戳 = 上一次绝对时间戳 + 时间戳增量 */
if (!packet->m_hasAbsTimestamp)
packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel]; /* timestamps seem to be always relative!! */
// 当前绝对时间戳保存起来,供下一个包转换时间戳使用
r->m_channelTimestamp[packet->m_nChannel] = packet->m_nTimeStamp;
/* reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel */
/* arrives and requests to re-use some info (small packet header) */
//重置保存的包。保留块头数据,因为通道中新到来的包(更短的块头)可能需要使用前面块头的信息.
r->m_vecChannelsIn[packet->m_nChannel]->m_body = NULL;
r->m_vecChannelsIn[packet->m_nChannel]->m_nBytesRead = 0;
r->m_vecChannelsIn[packet->m_nChannel]->m_hasAbsTimestamp = FALSE; /* can only be false if we reuse header */
}
else
{
packet->m_body = NULL; /* so it won't be erased on free */
}
return TRUE;
}
Download() 下载函数
int Download(RTMP * rtmp, // connected RTMP object
FILE * file, uint32_t dSeek, uint32_t dStopOffset, double duration, int bResume,
char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType,
uint32_t nInitialFrameSize, int nSkipKeyFrames, int bStdoutMode, int bLiveStream,
int bHashes, int bOverrideBufferTime, uint32_t bufferTime, double *percent) // percentage downloaded [out]
{
int32_t now, lastUpdate;
int bufferSize = 64 * 1024;
char *buffer = (char *) malloc(bufferSize);
int nRead = 0;
//long ftell(FILE *stream);
//返回当前文件指针
RTMP_LogPrintf("开始下载!\n");
off_t size = ftello(file);
unsigned long lastPercent = 0;
//时间戳
rtmp->m_read.timestamp = dSeek;
*percent = 0.0;
if (rtmp->m_read.timestamp)
{
RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp);
}
//是直播
if (bLiveStream)
{
RTMP_LogPrintf("直播流\n");
}
else
{
// print initial status
// Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
if (duration > 0)
{
if ((double) rtmp->m_read.timestamp >= (double) duration * 999.0)
{
RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",
(double) rtmp->m_read.timestamp / 1000.0,
(double) duration / 1000.0);
return RD_SUCCESS;
}
else
{
*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
*percent = ((double) (int) (*percent * 10.0)) / 10.0;
RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
bResume ? "Resuming" : "Starting",
(double) size / 1024.0, (double) rtmp->m_read.timestamp / 1000.0,
*percent);
}
}
else
{
RTMP_LogPrintf("%s download at: %.3f kB\n",
bResume ? "Resuming" : "Starting",
(double) size / 1024.0);
}
}
if (dStopOffset > 0)
RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0);
//各种设置参数到rtmp连接
if (bResume && nInitialFrameSize > 0)
rtmp->m_read.flags |= RTMP_READ_RESUME;
rtmp->m_read.initialFrameType = initialFrameType;
rtmp->m_read.nResumeTS = dSeek;
rtmp->m_read.metaHeader = metaHeader;
rtmp->m_read.initialFrame = initialFrame;
rtmp->m_read.nMetaHeaderSize = nMetaHeaderSize;
rtmp->m_read.nInitialFrameSize = nInitialFrameSize;
now = RTMP_GetTime();
lastUpdate = now - 1000;
do
{
//从rtmp中把bufferSize(64k)个数据读入buffer
nRead = RTMP_Read(rtmp, buffer, bufferSize);
//RTMP_LogPrintf("nRead: %d\n", nRead);
if (nRead > 0)
{
//函数:size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream);
//向文件读入写入一个数据块。返回值:返回实际写入的数据块数目
//(1)buffer:是一个指针,对fwrite来说,是要输出数据的地址。
//(2)size:要写入内容的单字节数;
//(3)count:要进行写入size字节的数据项的个数;
//(4)stream:目标文件指针。
//(5)返回实际写入的数据项个数count。
//关键。把buffer里面的数据写成文件
if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
(size_t) nRead)
{
RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
free(buffer);
return RD_FAILED;
}
//记录已经写入的字节数
size += nRead;
//RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
if (duration <= 0) // if duration unknown try to get it from the stream (onMetaData)
duration = RTMP_GetDuration(rtmp);
if (duration > 0)
{
// make sure we claim to have enough buffer time!
if (!bOverrideBufferTime && bufferTime < (duration * 1000.0))
{
bufferTime = (uint32_t) (duration * 1000.0) + 5000; // 再加5s以确保buffertime足够长
RTMP_Log(RTMP_LOGDEBUG,
"Detected that buffer time is less than duration, resetting to: %dms",
bufferTime);
//重设Buffer长度
RTMP_SetBufferMS(rtmp, bufferTime);
//给服务器发送UserControl消息通知Buffer改变
RTMP_UpdateBufferMS(rtmp);
}
//计算百分比
*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
*percent = ((double) (int) (*percent * 10.0)) / 10.0;
if (bHashes)
{
if (lastPercent + 1 <= *percent)
{
RTMP_LogStatus("#");
lastPercent = (unsigned long) *percent;
}
}
else
{
//设置显示数据的更新间隔200ms
now = RTMP_GetTime();
if (abs(now - lastUpdate) > 200)
{
RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
(double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0, *percent);
lastUpdate = now;
}
}
}
else
{
//现在距离开机的毫秒数
now = RTMP_GetTime();
//每间隔200ms刷新一次数据
if (abs(now - lastUpdate) > 200)
{
if (bHashes)
RTMP_LogStatus("#");
else
//size为已写入文件的字节数
RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0);
lastUpdate = now;
}
}
}
#ifdef _DEBUG
else
{
RTMP_Log(RTMP_LOGDEBUG, "zero read!");
}
#endif
}
while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
free(buffer);
if (nRead < 0)
//nRead是读取情况
nRead = rtmp->m_read.status;
/* Final status update */
if (!bHashes)
{
if (duration > 0)
{
*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
*percent = ((double) (int) (*percent * 10.0)) / 10.0;
//输出
RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
(double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0, *percent);
}
else
{
RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0);
}
}
RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead);
//读取错误
if (bResume && nRead == -2)
{
RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
nSkipKeyFrames + 1);
return RD_FAILED;
}
//读取正确
if (nRead == -3)
return RD_SUCCESS;
//没读完...
if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
|| RTMP_IsTimedout(rtmp))
{
return RD_INCOMPLETE;
}
return RD_SUCCESS;
}