LibRTMP源代码分析6:建立网络流

建立了RTMP网络连接之后,客户端发送createStream 命令到服务器端为消息连接创建一个逻辑通道。 NetStream 定义了这个传输逻辑通道,通过这个通道,音频流、视频流以及数据消息流可以通过连接客户端和服务端的 NetConnection 传输。NetConnection是默认的通信通道,流ID为 0。
RTMP_SendCreateStream( )函数用来发送createStream 命令, 由于各种命令的构成及发送比较类似,相关代码的分析放在后续的专门章节中讲解。RTMP_ConnectStream( )函数主要用于在NetConnection基础上建立一个NetStream。

/**
 * @brief 创建流(NetStream)
 */
int RTMP_ConnectStream(RTMP *r, int seekTime)
{
RTMPPacket packet = { 0 };

/* seekTime was already set by SetupStream / SetupURL.
   This is only needed by ReconnectStream. */
if (seekTime > 0)
r->Link.seekTime = seekTime;

r->m_mediaChannel = 0;

// 没有正在播放,RTMP连接上,从socket读取到消息(message)包
// 接收到的实际上是块(Chunk),而不是消息(Message),因为消息在网上传输的时候要分割成块.
while (!r->m_bPlaying && RTMP_IsConnected(r) &&  RTMP_ReadPacket(r, &packet))
{
// 一个消息可能被封装成多个块(Chunk),只有当所有块读取完才处理这个消息包
if (RTMPPacket_IsReady(&packet))
{
if (!packet.m_nBodySize)
continue;

// 读取到flv数据包,则继续读取下一个包
if  ( ( packet.m_packetType == RTMP_PACKET_TYPE_AUDIO ) ||
( packet.m_packetType == RTMP_PACKET_TYPE_VIDEO ) ||
( packet.m_packetType == RTMP_PACKET_TYPE_INFO ) )
{
RTMP_Log(RTMP_LOGWARNING, "Received FLV packet before play()! Ignoring.");
RTMPPacket_Free(&packet);
continue;
}

RTMP_ClientPacket(r, &packet); // 处理收到的数据包
RTMPPacket_Free(&packet); // 处理完毕,清除数据
}
}

return r->m_bPlaying;
}

       初看起来这个函数的代码量好像挺少的,实际上不然,其复杂度还是挺高的。我觉得比RTMP_Connect()要复杂不少。其关键就在于这个While()循环。首先,循环的三个条件都满足,就能进行循环。只有出错或者建立网络流(NetStream)的步骤完成后,才能跳出循环。
       在这个函数中有两个函数尤为重要:RTMP_ReadPacket( ) 和 RTMP_ClientPacket( )。 第一个函数的作用是读取通过Socket接收下来的消息(Message)包,但是不做任何处理。我们会在下面详细分析该函数代码。第二个函数则是处理消息(Message),并做出响应。该函数代码比较长,而且涉及很多子函数的调用,所以该函数代码在后续一篇单独的文章中加以详细分析。 这两个函数结合,就可以完成接收消息然后响应消息的步骤。

/**
 * @brief 读取接收到的消息块(Chunk),存放在packet中. 对接收到的消息不做任何处理。 块的格式为:
 *
 *   | basic header(1-3字节)| chunk msg header(0/3/7/11字节) | Extended Timestamp(0/4字节) | chunk data |
 *
 *   其中 basic header还可以分解为:| fmt(2位) | cs id (3 <= id <= 65599) |
 *   RTMP协议支持65597种流,ID从3-65599。ID 0、1、2作为保留。
 *      id = 0,表示ID的范围是64-319(第二个字节 + 64);
 *      id = 1,表示ID范围是64-65599(第三个字节*256 + 第二个字节 + 64);
 *      id = 2,表示低层协议消息。
 *   没有其他的字节来表示流ID。3 -- 63表示完整的流ID。
 *
 *    一个完整的chunk msg header 还可以分解为 :
 *     | timestamp(3字节) | msg length(3字节) | msg type id(1字节,小端) | msg stream id(4字节) |
 */
int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)
{
uint8_t hbuf[RTMP_MAX_HEADER_SIZE] = { 0 }; // Chunk Header长度最大值为3 + 11 + 4 = 18
char *header = (char *)hbuf; // header指向从socket接收到的数据
int   nSize, hSize, nToRead, nChunk; // nSize是块消息头长度,hSize是块头长度
int   didAlloc = FALSE;

RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d", __FUNCTION__, r->m_sb.sb_socket);

// 读取1个字节存入 hbuf[0]
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; // 块类型fmt
packet->m_nChannel    = (hbuf[0] & 0x3f); // 块流ID(2 - 63)
header++;

// 块流ID第一个字节为0,表示块流ID占2个字节,表示ID的范围是64-319(第二个字节 + 64)
if (packet->m_nChannel == 0)
        {
// 读取接下来的1个字节存放在hbuf[1]中
if (ReadN(r, (char *)&hbuf[1], 1) != 1)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte", __FUNCTION__);
return FALSE;
}

// 块流ID = 第二个字节 + 64 = hbuf[1] + 64
packet->m_nChannel = hbuf[1];
packet->m_nChannel += 64;
header++;
 }
// 块流ID第一个字节为1,表示块流ID占3个字节,表示ID范围是64 -- 65599(第三个字节*256 + 第二个字节 + 64)
else if (packet->m_nChannel == 1)
        {
int tmp;

// 读取2个字节存放在hbuf[1]和hbuf[2]中
if (ReadN(r, (char *)&hbuf[1], 2) != 2)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte", __FUNCTION__);
return FALSE;
}

// 块流ID = 第三个字节*256 + 第二个字节 + 64
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;
        }

// 块消息头(ChunkMsgHeader)有四种类型,大小分别为11、7、3、0,每个值加1 就得到该数组的值
// 块头 = BasicHeader(1-3字节) + ChunkMsgHeader + ExtendTimestamp(0或4字节)
nSize = packetSize[packet->m_headerType];

// 块类型fmt为0的块,在一个块流的开始和时间戳返回的时候必须有这种块
// 块类型fmt为1、2、3的块使用与先前块相同的数据
// 关于块类型的定义,可参考官方协议:流的分块 --- 6.1.2节
if (nSize == RTMP_LARGE_HEADER_SIZE) /* if we get a full header the timestamp is absolute */
{
packet->m_hasAbsTimestamp = TRUE; // 11个字节的完整ChunkMsgHeader的TimeStamp是绝对时间戳
}
else if (nSize < RTMP_LARGE_HEADER_SIZE)
        {
/* using values from the last message of this channel */
if (r->m_vecChannelsIn[packet->m_nChannel])
memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel], sizeof(RTMPPacket));
        }

nSize--; // 真实的ChunkMsgHeader的大小,此处减1是因为前面获取包类型的时候多加了1

// 读取nSize个字节存入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;
}

// 目前已经读取的字节数 = chunk msg header + basic header
hSize = nSize + (header - (char *)hbuf);

// chunk msg header为11、7、3字节,fmt类型值为0、1、2
if (nSize >= 3)
        {
// 首部前3个字节为timestamp
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); */

// chunk msg header为11或7字节,fmt类型值为0或1
if (nSize >= 6)
{
packet->m_nBodySize = AMF_DecodeInt24(header + 3);
packet->m_nBytesRead = 0;
RTMPPacket_Free(packet);

if (nSize > 6)
{
packet->m_packetType = header[6]; // msg type id
if (nSize == 11)
packet->m_nInfoField2 = DecodeInt32LE(header + 7); // msg stream id,小端字节序
}
}

// Extend Tiemstamp,占4个字节
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);

// 如果消息长度非0,且消息数据缓冲区为空,则为之申请空间
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;
nChunk = r->m_inChunkSize;
if (nToRead < nChunk)
nChunk = nToRead;

/* Does the caller want the 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: %u",
 __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));

// 包读取完毕
if (RTMPPacket_IsReady(packet))
{
/* make packet's timestamp absolute,绝对时间戳 = 上一次绝对时间戳 + 时间戳增量 */
if (!packet->m_hasAbsTimestamp) /* timestamps seem to be always relative!! */
packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel];

// 当前绝对时间戳保存起来,供下一个包转换时间戳使用
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;
}

在RTMP_ReadPacket()函数里完成从Socket中读取数据的函数是ReadN(),继续看看它的源代码:

/**
 * @brief 从HTTP或SOCKET中读取n个数据存放在buffer中.
 */
static int ReadN(RTMP *r, char *buffer, int n)
{
int  nOriginalSize = n;
int  avail;
char *ptr;

r->m_sb.sb_timedout = FALSE;
#ifdef _DEBUG
memset(buffer, 0, n);
#endif

ptr = buffer;
while (n > 0)
        {
int nBytes = 0, nRead;
if (r->Link.protocol & RTMP_FEATURE_HTTP)
               {
while (!r->m_resplen)
{
if (r->m_sb.sb_size < 144)
{
if (!r->m_unackd)
HTTP_Post(r, RTMPT_IDLE, "", 1);
if (RTMPSockBuf_Fill(r, &r->m_sb) < 1)
{
if (!r->m_sb.sb_timedout)
RTMP_Close(r);
return 0;
}
}

if (HTTP_read(r, 0) == -1)
{
RTMP_Log(RTMP_LOGDEBUG, "%s, No valid HTTP response found", __FUNCTION__);
RTMP_Close(r);
return 0;
}
}

if (r->m_resplen && !r->m_sb.sb_size)
RTMPSockBuf_Fill(r, &r->m_sb);

avail = r->m_sb.sb_size;
if (avail > r->m_resplen)
avail = r->m_resplen;
}
else
               {
avail = r->m_sb.sb_size;
if (avail == 0)
{
if (RTMPSockBuf_Fill(r, &r->m_sb) < 1)
{
if (!r->m_sb.sb_timedout)
RTMP_Close(r);
return 0;
}
avail = r->m_sb.sb_size;
}
}

nRead = ((n < avail) ? n : avail);
if (nRead > 0)
{
memcpy(ptr, r->m_sb.sb_start, nRead);
r->m_sb.sb_start += nRead;
r->m_sb.sb_size -= nRead;
nBytes = nRead;
r->m_nBytesIn += nRead;
if (r->m_bSendCounter && r->m_nBytesIn > ( r->m_nBytesInSent + r->m_nClientBW / 10))
if (!SendBytesReceived(r))
return FALSE;
}
/*RTMP_Log(RTMP_LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes); */
//#ifdef _DEBUG
//      fwrite(ptr, 1, nBytes, netstackdump_read);
//#endif

if (nBytes == 0)
{
RTMP_Log(RTMP_LOGDEBUG, "%s, RTMP socket closed by peer", __FUNCTION__);
/*goto again; */
RTMP_Close(r);
break;
}

if (r->Link.protocol & RTMP_FEATURE_HTTP)
r->m_resplen -= nBytes;
n -= nBytes;
ptr += nBytes;
}

return nOriginalSize - n;
}

ReadN()中实现从Socket中接收数据的函数是RTMPSockBuf_Fill(),看看代码吧(又是层层调用)。

/**
 * @brief 调用Socket编程中的recv()函数,接收数据
 */
int RTMPSockBuf_Fill(RTMP *r, RTMPSockBuf *sb)
{
int nBytes;
if  (!sb->sb_size)
sb->sb_start = sb->sb_buf;

while (1)
{
// 缓冲区长度:总长-未处理字节-已处理字节  
// |-----已处理--------|-----未处理--------|---------缓冲区----------|  
// sb_buf        sb_start    sb_size  
nBytes = sizeof(sb->sb_buf) - sb->sb_size - (sb->sb_start - sb->sb_buf);
{
// int recv( SOCKET s, char * buf, int len, int flags);  
// s    :一个标识已连接套接口的描述字。  
// buf  :用于接收数据的缓冲区。   
// len  :缓冲区长度。  
// flags:指定调用方式。  
// 从sb_start(待处理的下一字节) + sb_size()还未处理的字节开始buffer为空,可以存储
nBytes = r->m_sock.recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0);
}

if (nBytes != -1)
{
// 未处理的字节又多了
sb->sb_size += nBytes;
}
else
{
int sockerr = r->m_sock.getsockerr();
RTMP_Log(RTMP_LOGDEBUG, "%s, recv returned %d. GetSockError(): %d (%s)", 
__FUNCTION__, nBytes, sockerr, strerror(sockerr));
if (sockerr == EINTR && !RTMP_ctrlC)
continue;

if (sockerr == EWOULDBLOCK || sockerr == EAGAIN)
{
sb->sb_timedout = TRUE;
nBytes = 0;
}
}
break;
}
return nBytes;
}

从RTMPSockBuf_Fill()代码中可以看出,调用了系统Socket的recv()函数接收RTMP连接传输过来的数据。
接下来我们用一张图来描述以上函数的大致流程:
LibRTMP源代码分析6:建立网络流(NetStream) - nkwavelet - 小波的世界
 
下图简单描述RTMP_ReadPacket函数的流程:
LibRTMP源代码分析6:建立网络流(NetStream) - nkwavelet - 小波的世界

LibRTMP源代码分析6:建立网络流(NetStream) - nkwavelet - 小波的世界

猜你喜欢

转载自blog.csdn.net/wongainia158158/article/details/48626615