RTMP学习(十二)rtmpdump源码阅读(6)数据接收

数据接收



    这一篇很重要,下面将结合chunk的结构来分析,数据是怎么样从网络上读取的,以及如何解析它们。


chunk的结构

	// rtmp消息块
	typedef struct RTMPPacket
	{
		// chunk basic header(大部分情况是一个字节)
		uint8_t m_headerType;

		// Message type ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)
		uint8_t m_packetType;

		// 是否含有Extend timeStamp字段
		uint8_t m_hasAbsTimestamp;	/* timestamp absolute or relative? */

		// channel 即 stream id字段
		int m_nChannel;

		// 时间戳
		uint32_t m_nTimeStamp;	/* timestamp */

		// message stream id
		int32_t m_nInfoField2;	/* last 4 bytes in a long header */

		// chunk体的长度
		uint32_t m_nBodySize;
		uint32_t m_nBytesRead;
		RTMPChunk *m_chunk; // 原始rtmp消息块
		char *m_body;
	} RTMPPacket;

其他更多信息 http://blog.csdn.net/nb_vol_1/article/details/58603868



读取一个chunk



    入口函数是RTMP_ReadPacket,它的运行流程如下:

    1、读取一个字节,这个字节代表chunk basic header(大部分情况下,basic header只占用一个字节)
    2、解析basic header的fmt字段(m_headerType)和stream id字段(m_nChannel),其中fmt字段占2bit,stream id字段占6bit
    3、计算调整stream id字段
    4、根据fmt字段(m_headerType)计算chunk basic header+ chunk Msg header的长度(通过查表得到),下面是计算规则:
                fmt=0,那么chunk Msg header长度是11
                fmt=1,那么chunk Msg header长度是7
                fmt=2,那么chunk Msg header长度是3
                fmt=3,那么chunk Msg header长度是0
    5、如果chunk basic header+ chunk Msg header的长度等于12字节,那么表示这是一个”大“的头部,所有的chunk信息都可以从网络上读取得,到并且存在Extend timestamp字段
    6、如果chunk basic header+ chunk Msg header的长度小于12字节,那么表示它不是一个”大“的头部,有一些chunk信息不能从网络上读取到,这些不能读取到的信息从上一个chunk中得到。
    7、读取chunk Msg header
    8、根据chunk Msg header的长度,分别解析出时间戳、message length、message type id、message stream id等字段
    9、如果时间戳字段等于0xffffff,那么表示存在Extend timestamp字段,从网上读取它
    10、读取chunk body
    11、其他的一些操作!

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

	// 先读取一个字节,这一个字节是chunk basic header
	if (ReadN(r, (char *)hbuf, 1) == 0)
	{
		RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);
		return FALSE;
	}

	// chunk basic header中的fmt字段,2bit
	packet->m_headerType = (hbuf[0] & 0xc0) >> 6;
	
	// chunk basic header中的stream id字段,6bit
	packet->m_nChannel = (hbuf[0] & 0x3f);
	header++;
	if (packet->m_nChannel == 0)
	{
		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)
	{
		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的fmt字段计算basic header + chunk msg header的长度
	nSize = packetSize[packet->m_headerType];

	// RTMP_LARGE_HEADER_SIZE 是完整的basic header 加上 chunk Msg header的长度
	// 如果是”大“的头部
	if (nSize == RTMP_LARGE_HEADER_SIZE)	/* if we get a full header the timestamp is absolute */
		packet->m_hasAbsTimestamp = TRUE;

	// 如果不是”大“的头部,那么复用上一个chunk的头部信息
	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));
	}

	// 减去1,表示chunk msg header的长度
	nSize--;

	// 读取chunk msg 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 = nSize + (header - (char *)hbuf);

	// 如果chunk Msg header的长度大于3字节,表示存在Timestamp字段
	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)
		{
			// 得到message的长度
			packet->m_nBodySize = AMF_DecodeInt24(header + 3);
			packet->m_nBytesRead = 0;
			
			// 清空旧的数据
			RTMPPacket_Free(packet);

			if (nSize > 6)
			{
				// 这里表示message type id
				packet->m_packetType = header[6];

				// message stream id字段
				if (nSize == 11)
					packet->m_nInfoField2 = DecodeInt32LE(header + 7);
			}
		}
		// 如果时间戳字段等于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);

	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)
			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;
}



底层的读函数



ReadN



    ReadN的作用是读取指定数量的字节,全部读取完毕才返回,否则就一直阻塞
    它的操作如下:
        1、如果套接字使用了HTTP进行封装,那么先发送HTTP请求,然后调用RTMPSockBuf_Fill函数读取数据
        2、如果套接字没有经过包装,那么直接调用RTMPSockBuf_Fill函数读取数据


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;
		
		// 如果底层套接字使用了HTTP进行包装
		if (r->Link.protocol & RTMP_FEATURE_HTTP)
		{
			while (!r->m_resplen)
			{
				if (r->m_sb.sb_size < 144)
				{
					// 先用HTTP向服务器进行请求:post
					if (!r->m_unackd)
						HTTP_Post(r, RTMPT_IDLE, "", 1);
					
					// 读取数据
					if (RTMPSockBuf_Fill(&r->m_sb) < 1)
					{
						if (!r->m_sb.sb_timedout)
							RTMP_Close(r);
						return 0;
					}
				}
				
				// 读取HTTP的反馈
				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 < r->m_resplen))
				if (RTMPSockBuf_Fill(&r->m_sb) < 0)
					if (!r->m_sb.sb_timedout)
						RTMP_Close(r);
			avail = r->m_sb.sb_size;
			if (avail > r->m_resplen)
				avail = r->m_resplen;
		}
		// 如果底层使用原始的套接字(tcp)
		else
		{
			avail = r->m_sb.sb_size;
			if (avail == 0)
			{
				// 直接使用套接字读取数据
				if (RTMPSockBuf_Fill(&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;

#ifdef CRYPTO
		if (r->Link.rc4keyIn)
		{
			RC4_encrypt(r->Link.rc4keyIn, nBytes, ptr);
		}
#endif

		n -= nBytes;
		ptr += nBytes;
	}

	return nOriginalSize - n;
}



RTMPSockBuf_Fill


    RTMPSockBuf_Fill的作用是从网上读取数据,填充到套接字缓冲区中,只要读取到数据就可以返回,不必等到所有数据都读取完成,返回值是读取到的字节数

int
RTMPSockBuf_Fill(RTMPSockBuf *sb)
{
	int nBytes;

	if (!sb->sb_size)
		sb->sb_start = sb->sb_buf;

	while (1)
	{
		// 计算还需要读取的字节数
		nBytes = sizeof(sb->sb_buf) - sb->sb_size - (sb->sb_start - sb->sb_buf);
#if defined(CRYPTO) && !defined(NO_SSL)
		if (sb->sb_ssl)
		{
			nBytes = TLS_read(sb->sb_ssl, sb->sb_start + sb->sb_size, nBytes);
		}
		else
#endif
		{
			// 读取数据
			nBytes = recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0);
		}
		
		// 读取成功
		if (nBytes != -1)
		{
			// 计算已经读取到的字节数
			sb->sb_size += nBytes;
		}
		// 读取失败,出错!
		else
		{
			int sockerr = GetSockError();
			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;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: rtmpdump是一个开源的命令行工具,用于从流媒体服务器上下载或录制RTMP流(Real-Time Messaging Protocol)。vc工程是指使用VC++编写的工程项目。将两者结合起来,意味着在VC++环境下使用rtmpdump工具进行流媒体下载或录制。 要在VC++中使用rtmpdump工具,首先需要下载并安装rtmpdump源代码。然后在VC++项目中添加rtmpdump的相关文件,并在代码中引用相关的头文件。接下来,就可以使用rtmpdump提供的函数来实现对RTMP流的下载或录制。 具体而言,可以使用rtmpdumprtmpdump()函数指定要下载或录制的RTMP流的URL,并设置保存的文件路径。通过调用该函数,rtmpdump将连接到流媒体服务器并获取流媒体数据,并将其写入指定的文件中。在下载或录制过程中,还可以使用rtmpdump的其他函数来处理数据流,例如解密、分段等操作。 在VC++中使用rtmpdump工程可以为流媒体下载或录制提供更灵活和定制化的功能。通过使用VC++的编程特性,可以进行更高级的数据处理和界面设计,实现更复杂的功能需求。同时,rtmpdump的开源性质也使得用户能够根据自己的需求对其进行修改和扩展。 总之,通过在VC++中使用rtmpdump工程,可以方便地实现对RTMP流的下载或录制,并根据需要进行定制化的功能扩展。这为开发者提供了更大的灵活性和自由度,以满足不同的流媒体处理需求。 ### 回答2: rtmpdump是一款用于从RTMP流媒体服务器下载或录制音频/视频内容的开源工具。vc工程是指使用Visual C++编程语言和开发环境创建的软件项目。 要创建一个rtmpdump的vc工程,首先需要下载rtmpdump的源代码,然后使用Visual C++打开相应的工程文件。接下来需要配置工程的编译环境,确保所需的头文件和库文件路径正确。 在工程中,会有一些源代码文件,这些文件负责实现rtmpdump的各种功能,例如建立与RTMP服务器的连接、发送请求、接收数据数据解析等等。这些文件会被编译成可执行的二进制文件。 在编译期间可能会遇到一些依赖项问题,例如需要额外链接一些库文件以支持某些功能。开发人员需要根据编译过程中的错误信息,对工程进行适当的配置和调整,以确保成功编译。 一旦vc工程成功编译完成,就可以生成一个可执行文件,该文件可以在命令行中运行。通过指定RTMP服务器的URL和相关参数,可以让rtmpdump工具连接到服务器并下载或录制所需的音频/视频内容。 总的来说,创建一个rtmpdump的vc工程需要下载源代码、配置编译环境、解决依赖项问题,并根据需要添加自定义功能。通过编译和运行工程生成的可执行文件,就能实现从RTMP流媒体服务器下载或录制音频/视频内容的操作。 ### 回答3: rtmpdump是一个用于下载和保存RTMP流的工具。它提供了用于处理RTMP协议的源代码,可以自定义编译和构建,获得一个可以在不同平台上运行的RTMP下载工具。 RTMP是一种用于流媒体传输的协议,它是Adobe公司开发的一种实时通信协议,常用于视频直播、在线游戏和音频通信等领域。rtmpdump工具可以连接到RTMP服务器,读取和接收RTMP流,并将其保存为本地文件。 vc工程是指使用Visual C++编译器创建的工程。通过将rtmpdump的源代码加入到Visual C++工程中,我们可以使用VC++的功能和特性来构建和调试rtmpdump工具。这样,我们就可以根据自己的需求进行修改和定制,使其能够更好地满足我们的需求。 在vc工程中,我们可以使用rtmpdump的源代码来构建一个命令行界面的工具,以便用户可以通过命令行输入相关参数来下载RTMP流。我们可以利用Visual C++的调试功能来排查和修复可能存在的问题,提高工具的稳定性和性能。 除了在vc工程中进行开发和调试,rtmpdump还可以在不同的平台上进行编译和构建,如Linux、Mac和Android等。这使得rtmpdump工具具有更广泛的适用性和可移植性。 总结来说,rtmpdump vc工程是指使用Visual C++编译器创建的工程,并集成了rtmpdump的源代码,使其能够在Windows平台上运行和定制化,从而实现对RTMP流进行下载和保存的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值