使用RTP发送h.264的例子代码分析

时间:2022-09-09 09:28:53

首先声明,以下代码为帖子http://topic.csdn.net/u/20090725/11/5FBC75B0-1091-4DD4-9154-3E3D59F9B6D1.htmlttxk的,注释是我加的,对ttxk和jessiepan表示感谢,jessiepan的钻研精神很不错,很负责任的楼主。他遇到的问题估计我也会遇到。

以下是根据rfc3894阅读ttxk的代码的笔记。下一阶段可能是用一个rtp库将rtp传输部分和对应的接受处理部分完成。

 

UINT MediaStreamH264::TransportData(PBYTE pData, UINT dataSize, int pts)
{
//当前缓存头
PBYTE p_buffer = pData;
//当前缓存大小
int i_buffer = dataSize;

//已经发送大小
UINT writeSize = 0;

//寻找第一个头[3 BYTE,0x00,0x00,0x01]
while( i_buffer > 4 && ( p_buffer[0] != 0 || p_buffer[1] != 0 || p_buffer[2] != 1 ) )
{
i_buffer--;
p_buffer++;
}

/* Split nal units */
while( i_buffer > 4 )
{
int i_offset;
int i_size = i_buffer;
int i_skip = i_buffer;

/* search nal end */
for( i_offset = 4; i_offset+2 < i_buffer ; i_offset++)
{
//寻找下一个头来找到当前帧结束位置
if( p_buffer[i_offset] == 0 && p_buffer[i_offset+1] == 0 && p_buffer[i_offset+2] == 1 )
{
/* we found another startcode */
// 判断下一个头是两个还是三个0x00,如果p_buffer[i_offset-1]表明下一个头是三个0x00+0x01,i_size要多减一
i_size = i_offset - ( p_buffer[i_offset-1] == 0 ? 1 : 0);
i_skip = i_offset;
break;

}
/* TODO add STAP-A to remove a lot of overhead with small slice/sei/... */
//发送当前找到的一帧
UINT iWrite = TransportH264Nal(p_buffer, i_size, pts, (i_size >= i_buffer) );
if (iWrite > 0 )
writeSize += iWrite;

i_buffer -= i_skip;
p_buffer += i_skip;
}
return writeSize;
}

//发送一帧264数据,一般一帧以一个RTP包发送,过长帧分为多个RTP包传送。
UINT MediaStreamH264::TransportH264Nal(const PBYTE pNal, UINT nalSize, INT32 pts, BOOL isLast)
{
//应该是对进程加锁
ATLock atlock(&m_tlockRun);

if (m_bRun == FALSE)
return 0;

//每帧最小长度至少为5
if( nalSize < 5 )
return 0;

/* mtu是MTU,应是个全局变量, 如下介绍,在IP网估计可设为1500左右的一个值
聚合包是本荷载规范的NAL单元聚合安排。本计划的引入是反映两个主要目标网络差异巨大的MTU:有线IP网络(MTU 通常被以太网的MTU限制; 大约1500 字节), 基于无线通信系统的IP或非IP (ITU-T H.324/M)网络,它的优先传输最大单元是254或更少。为了阻止连个世界媒体的转换以及避免不必要的打包负担,引入聚合单元安排。
*/
UINT mtu = m_nMTU;

//按照opal的经验可以将i_max设为1400
const int i_max = mtu - RTP_HEADER_SIZE; /* payload max in one packet */
int i_nal_hdr;//NAL的头
int i_nal_type;

i_nal_hdr = pNal[3];
i_nal_type = i_nal_hdr&0x1f;//前两个BYTE中第一个BYTE的最后一个bit和第二个BYTE是type

string sps;
string pps;

//对于nal_unit_type等于7,8 (指示顺序参数集或图像参数集)的NAL单元,H.264编码器应该设置NRI为11 (二进制格式)
if( i_nal_type == 7 || i_nal_type == 8 )
{
/* XXX Why do you want to remove them ? It will break streaming with 
* SPS/PPS change (broadcast) ? */
return 0;
}

/* Skip start code */
PBYTE p_data = pNal;
int i_data = nalSize;

//跳过头部的3个BYTE的h264帧头(0x00 0x00 0x01)
p_data += 3;
i_data -= 3;//NAL+NALU的长度

int writeSize = 0;

//如果此帧数据长度小于一个udp包内容的最大长度直接发送,负责按照FU格式发送一帧发送多个包
if( i_data <= i_max )
//单个NAL单元的传输顺序必须和NAL解码顺序一致。

{
/* Single NAL unit packet */
//writeSize = m_pRtpTransport->SetRtpData(p_data, i_data, pts, isLast);
//发送数据
writeSize = m_pRtpTransport->Write(p_data, i_data, m_nRtpPayloadType, pts, 0, isLast);
if (writeSize <= 0)
return 0;
return writeSize;
}
else
//STAP 类型的包,是STAP-A的方式,由后面的28决定
{
/* FU-A Fragmentation Unit without interleaving */
const int i_count = ( i_data-1 + i_max-2 - 1 ) / (i_max-2);//需要分多少个包
int i;

p_data++;
i_data--;

for( i = 0; i < i_count; i++ )
{
//计算每个NALU DATA的长度,判断是否是最后一包,不是最后一包时都是i_max-2,最后一包是i_data(也即最后一包实际的长度)
const int i_payload = (i_data < (i_max-2)) ? i_data : (i_max-2);
//计算每个NALU的长度
const int nalSize = 2 + i_payload;

m_Packet.ExtendBuffer(nalSize);

/* FU indicator */
/* i_nal_hdr & 0110 0000
   FU指示字节有以下格式:
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+
   FU指示字节的类型域的28,29表示FU-A和FU-B。F的使用在5。3描述。NRI域的值必须根据分片NAL单元的NRI域的值设置。

28表示是FU-A,见rfc3984 5.8,即在此对分片单元的处理方式是FU-A,没有使用交错模式,没有使用DON
*/
m_Packet.m_pData[0] = 0x00 | (i_nal_hdr & 0x60) | 28;
/* FU header */
/*
   FU头的格式如下:
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+
   S: 1 bit
      当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
   即( i == 0 ? 0x80 : 0x00 )

   E: 1 bit
      当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的FU荷载不是分片NAL单元的最后分片,结束位设置为0。
   即 ( (i == i_count-1) ? 0x40 : 0x00 )
   一个分片单元不可以传输在一个FU中; 即, 开始位和结束位不可以被同时设置在同一个FU头中。

   R: 1 bit
      保留位必须设置为0,接收者必须忽略该位。
   没有处理     

   Type: 5 bits
      NAL单元荷载类型定义在[1]的表7-1.
   还是沿用总的type
*/
m_Packet.m_pData[1] = ( i == 0 ? 0x80 : 0x00 ) | ( (i == i_count-1) ? 0x40 : 0x00 ) | i_nal_type;

/* FU payload */
memcpy( &m_Packet.m_pData[2], p_data, i_payload );

m_Packet.m_DataSize = nalSize;

/*
分片只定义于单个NAL单元不用于任何聚合包。NAL单元的一个分片由整数个连续NAL单元字节组成. 每个NAL单元字节必须正好是该NAL单元一个分片的一部分。相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。相似, NAL单元必须按照RTP顺序号的顺序装配
*/
//int iWrite = m_pRtpTransport->SetRtpData(m_Packet.m_pData, m_Packet.m_DataSize, pts, isLast && (i == i_count-1));
int iWrite = m_pRtpTransport->Write(m_Packet.m_pData, m_Packet.m_DataSize, m_nRtpPayloadType, pts, 0, isLast && (i == i_count-1));
/*
如果一个分片单元丢失,接收者应该丢弃后续的所有分片单元对应于相同分片NAL单元的传输顺序的分片。

   终端或MANE中的接收者可以聚合前一个NAL单元的n-1分片到一个(不完全的) NAL单元,甚至分片n没有接收到. 这种情况下,NAL单元的forbidden_zero_bit必须被设置成1指示语法违背.

   接收者包括一个接收缓冲区以补偿传输延迟和抖动。接收者存储进来的包按照接收顺序在接收缓冲区中。包被解封装按照RTP序号的顺序。如果封装包是一个单个NAL单元包,包含在包中的NAL单元直接传递给解码器。如果解封装的包是一个STAP-AI, 包含在包中的NAL单元按照他们在包中的封装顺序传递给解码器。如果解封装包是一个FU-A, 所有的分片NAL单元单分片连接在一起传递给解码器。
  
      信息: 如果解码器支持任意分片顺序,编码的图像片可以按照任意顺序传送给解码器而不管他们的接收传送顺序。

*/

if (iWrite > 0)
writeSize += iWrite;

i_data -= i_payload;
p_data += i_payload;
}
}
return writeSize;
}