RTMPdump 源代码分析 :RTMP_ConnectStream建立网络流连接

------------------------本文主要作用记录自己对照协议阅读RTMP源代码并作出详细批准----------------------
注: 大致浏览一遍协议即可,不要深入阅读,带着代码去阅读协议更容易理解

协议

分块

握手之后,连接开始复用一个或多个块流。每个块流承载来自一个消息流的一类消息。每个被创建的块都关联到一个唯一的块流ID。所有的块都通过网络传输。在传输过程中,必须一个块发送完之后再发送下一个块。在接收端,每个块都根据块ID被收集成消息。
  分块使高层协议的大消息分割成小的消息,保证大的低优先级消息不阻塞小的高优先级消息。
  分块把原本应该消息中包含的信息压缩在块头中减少了小块消息发送的开销。
  块大小是可配置的。这个可以在7.1节中描述的块消息中完成。最大块是65535字节,最小块是128字节。块越大CPU使用率越低,但是也导致大的写入,在低带宽下产生其他内容的延迟。块大小对每个方向都保持独立。
1、 块格式
  块由头和数据组成。块头由三部分组成:
在这里插入图片描述
  块基本头:1到3 字节
    本字段包含块流ID和块类型。块类型决定编码的消息头的格式。长度取决于块流ID。块流ID是可变长字段。
  块消息头:0,3,7或11字节。
    本字段编码要发送的消息的信息。本字段的长度,取决于块头中指定的块类型。
  扩展时间戳:0个或4字节
    本字段必须在发送普通时间戳(普通时间戳是指块消息头中的时间戳)设置为0xffffff时发送,正常时间戳为其他值时都不应发送本值。当普通时间戳的值小于0xffffff时,本字段不用出现,而应当使用正常时间戳字段。
1.1 块基本头
  块基本头编码块流ID和块类型(在下图中用fmt表示)。块类型决定编码消息头的
格式。块基本头字段可能是1,2或3个字节。这取决于块流ID。
  一个实现应该用最少的数据来表示ID?。
  本协议支持65597种流,ID从3-65599。ID 0、1、2作为保留。0,表示ID的范围是64-319(第二个字节+64);1,表示ID范围是64-65599(第三个字节256+第二个字节+64);2表示低层协议消息。没有其他的字节来表示流ID。3-63表示完整的流ID。3-63之间的值表示完整的流ID。没有其他的字节表示流ID。
  0-5(不显著的)位表示块流ID。
  块流ID 2-63可以用1字节的字段表示
  在这里插入图片描述
  块流ID 64-319可以用2-字节表示。ID计算是(第二个字节+64)
  在这里插入图片描述
  块流ID64-65599可以表示为3个字节 。ID计算为第三个字节
255+第二个字节+64
在这里插入图片描述
  Cs id:6位
    本字段表示范围在2-63的块流ID。值0和1 表示本字段的2或3 字节版本
   Fmt:2位
    本字段标识块消息头的4种格式。每种流类型的块消息头在下一节中表示。
  Cs id-64:8-16位
    本字段包含块流ID减去64 的值。例如365,应 在cs id中表示1,而用这里的1 6位表示301。
  块流ID在64-319 范围之内,可以用2个字节版本表示,也可以用3字节版本表示。
1.2 块消息头
  有四种格式的块消息ID,供块流基本头中的fmt字段选择。
  一个实现应该使用最紧致的方式来表示块消息头。
1.2.1 类型0
  0类型的块长度为11字节。在一个块流的开始和时间戳返回的时候必须有这种块。
在这里插入图片描述
  时间戳:3字节
  对于0类型的块。消息的绝对时间戳在这里发送。如果时间戳大于或等于16777215(16进制0x00ffffff),该值必须为16777215,并且扩展时间戳必须出现。否则该值就是整个的时间戳。
1.2.2. 类型1
   类型1的块占7个字节长。消息流 ID不包含在本块中。块的消息流ID与先前的块相同。具有可变大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
在这里插入图片描述
1.2.3. 类型2
  类型2的块占3个字节。既不包含流ID也不包含消息长度。本块使用的流ID和消息长度与先前的块相同。具有固定大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
在这里插入图片描述
1.2.4 类型3
  类型3的块没有头。流ID,消息长度,时间戳都不出现。这种类型的块使用与先前块相同的数据。当一个消息被分成多个块,除了第一块以外,所有的块都应使用这种类型。示例可参考6.2.2节中的例2 。由相同大小,流ID,和时间间隔的流在类型2的块之后应使用这种块。示例可参考6.2.1节中的例1 。
  如果第一个消息和第二个消息的时间增量与第一个消息的时间戳相同,那么0类型的块之后必须是3类型的块而,不需要类型2的块来注册时间增量。如果类型3的块在类型0的块之后,那么类型3的时间戳增量与0类型的块的时间戳相同。
   时间戳增量:3字节
   对于类型1的块和类型2的块,本字段表示先前块的时间戳与当前块的时间戳的差值。如果增量大于等于1677215(16进制0x00ffffff),这个值必须是16777215 ,并且扩展时间戳必须出现。否则这个值就是整个的增量。
  消息长度:3字节
  对于类型0或类型1的块本字段表示消息的长度。
注意,这个值通常与负载长度是不相同的。The chunk payload length is the maximum chunk size for all but the last chunk, and the remainder (which may be the entire length, for small messages) for the last chunk.
消息类型ID:1字节
     对于0类型和1类型的块,本字段发送消息类型。
消息流ID:4 字节
    对于0类型的块,本字段存储消息流ID。通常,在一个块流中的消息来自于同一个消息流。虽然,由于不同的消息可能复用到一个块流中而使头压缩无法有效实施。但是,如果一个消息流关闭而另一个消息流才打开,那么通过发送一个新的0类型的块重复使用一个存在的块流也不是不可以。
1.3. 扩展时间戳
  只有当块消息头中的普通时间戳设置为0x00ffffff时,本字段才被传送。如果普通时间戳的值小于0x00ffffff,那么本字段一定不能出现。如果时间戳字段不出现本字段也一定不能出现。类型3的块一定不能含有本字段。本字段在块消息头之后,块时间之前。

涉及结构体解析

  typedef struct RTMPPacket
  {
    uint8_t m_headerType;	/* zx块类型,块类型决定了消息头大小 */
    uint8_t m_packetType;	/* zx消息类型 */
    uint8_t m_hasAbsTimestamp;	/* zx True表示绝对时间 */	
    int m_nChannel;			/* zx块流id */
    uint32_t m_nTimeStamp;	/* timestamp */
    int32_t m_nInfoField2;	/* last 4 bytes in a long header消息流id */
    uint32_t m_nBodySize;	/* zx 消息长度 */
    uint32_t m_nBytesRead;	/* zx 表示已读字节数 */
    RTMPChunk *m_chunk;		
    char *m_body;			/* zx 申请内存存放消息内容,头数据18字节之后的地址 */
  } RTMPPacket;

源代码

RTMP_ConnectStream实现: RTMP_ReadPacket循环的读取一个消息中的所有块数据,RTMPPacket_IsReady判断读取完成之后,调用RTMP_ClientPacket解析数据。

bool 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;

	/* zx RTMP_ReadPacket读取socket传过来的数据但是不做任何处理 */
	while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet))
	{
		if (RTMPPacket_IsReady(&packet))
		{
			if (!packet.m_nBodySize)
				continue;
			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;
			}

			/* zx开始处理接收到的数据,并作出响应 */
			RTMP_ClientPacket(r, &packet);
			RTMPPacket_Free(&packet);		/* zx处理完之后释放数据 */
		}
	}

	return r->m_bPlaying;
}

RTMP_ReadPacket:


/* zx 函数作用:读取接收下来的chunk
 * 参数packet:用来保存读到的数据
 */
bool RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)
{
	char hbuf[RTMP_MAX_HEADER_SIZE] = { 0 }, *header = hbuf;
	int nSize, hSize, nToRead, nChunk;
	bool didAlloc = false;

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

	if (ReadN(r, hbuf, 1) == 0)
	{
		RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);
		return false;
	}

	packet->m_headerType = (hbuf[0] & 0xc0) >> 6;	/* zx 块类型 */
	packet->m_nChannel = (hbuf[0] & 0x3f);			/* zx 块流id */
	header++;

	/* zx 下面根据协议计算块流id */
	if (packet->m_nChannel == 0)	/* zx 等于0是一个字节 */
	{
		if (ReadN(r, &hbuf[1], 1) != 1)
		{
			RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte", __FUNCTION__);
			return false;
		}
		packet->m_nChannel = (unsigned)hbuf[1];
		packet->m_nChannel += 64;
		header++;
	}
	else if (packet->m_nChannel == 1)
	{
		int tmp;
		if (ReadN(r, &hbuf[1], 2) != 2)
		{
			RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte", __FUNCTION__);
			return false;
		}
		tmp = (((unsigned)hbuf[2]) << 8) + (unsigned)hbuf[1];
		packet->m_nChannel = tmp + 64;
		RTMP_Log(RTMP_LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);
		header += 2;
	}

	/* zx 根据块类型得到后面块消息长度 */
	nSize = packetSize[packet->m_headerType];

	if (nSize == RTMP_LARGE_HEADER_SIZE)	/* if we get a full header the timestamp is absolute */
	{
		packet->m_hasAbsTimestamp = true;	/* zx 11字节的完整块,时间戳是绝对时间,3和7都是相对于之前的时间的 */
	}		
	else if (nSize < RTMP_LARGE_HEADER_SIZE) /* zx 小于12表示不是消息的第一个块,使用上一个块的消息头,只修改差异部分 */
	{				/* 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--;

	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 = nSize + (header - hbuf);

	if (nSize >= 3)
	{
		/* zx 无论消息头占几个字节,前三个字节都是时间戳,类型0是绝对时间戳,类型1和2是相对于上一次的时间戳 */
		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)
		{
			packet->m_nBodySize = AMF_DecodeInt24(header + 3);	/* zx 消息长度 */
			packet->m_nBytesRead = 0;
			RTMPPacket_Free(packet);

			if (nSize > 6)
			{
				packet->m_packetType = header[6];				/* zx 此消息的消息类型 */

				if (nSize == 11)
					packet->m_nInfoField2 = DecodeInt32LE(header + 7);	/* zx 消息流id */
			}
		}
		/* zx 扩展时间戳,只要当块消息头中的普通时间戳为0xffffff时,才会有扩展时间戳 */
		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);

	/* zx 消息长度大于0时,申请内存存放消息内容+18字节最大头数据 */
	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;		/* zx 这个是默认值128 */
	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;
	}

	/* zx 累加的往后读取数据块内容,packet->m_nBytesRead第一次为0 */
	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;

	/* zx 保存packet,供其他作用在这个通道的packet使用 */
	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));

	/* zx 判断是否读取完成,读取完成之后,读取完的字节数packet->m_nBytesR等于块消息头中读取到的字节数 */
	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
	{
		/* zx 没读完不进行拷贝,下一次进来之后会memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel], sizeof(RTMPPacket)); */
		packet->m_body = NULL;	/* so it won't be erased on free */
	}

	return true;
	}

猜你喜欢

转载自blog.csdn.net/weixin_37921201/article/details/90679009
今日推荐