参考连接:
基于libRTMP的流媒体直播之 AAC、H264 推送:
http://billhoo.blog.51cto.com/2337751/1557646/
使用librtmp进行H264与AAC直播
http://www.codeman.net/2014/01/439.html
雷博CSDN博客
下面是我最近了解PUST H264流到RTMP服务器上的一些笔记,参考了上面的链接,看别人的不如自己动手实践一下,以下便是实验过程记录。
关于FLV的结构,雷博的博文已经详细说明了,在这里可以参考一张图片
由上面的图片可以看出,FLV的整体结构为Header + Body. 其中Body则由 Tag + PreviousTagSizeN的方式组成,每一个Tag由Tag Header + Tag Data组成。
其中,在Video_File_Format_Specification_V10.pdf中,对每一个TAG的Tag Data有了更详细的说明(暂时只关注视频tag):
关于上面对Tag Data结构的说明,以前在做本地FLV视频PUSH到RTMP的测试中,并没有关注,只是直接将Tag Data封包进RTMP Packet里面发送出去即可。
H264的码流结构主要由SPS、 PPS、 IDR 帧(包含一个或多个 I-Slice)、 P 帧(包含一个或多个P-Slice)、 B 帧(包含一个或多个 B-Slice)等部分组成。
关于SPS和PPS,引用以下:
- 将一个视频序列 (从 IDR 帧开始到下一个 IDR 帧之前的数据称为一个视频序列) 全部图像的共同特征抽取出来,放在 SPS 语法单元中。
- 将各个图像的典型特征抽取出来,放在 PPS 语法单元中。
- 只有视频序列之间才能切换 SPS,即只有 IDR 帧的第一个 slice 可以切换 SPS。
- 只有图像之间才能切换 PPS,即只有每帧图像的第一个 slice 才能切换 PPS
H.264 的所有语法结构最终都被封装成 nalu。码流中的 nalu单元必须定义合适的分隔符,否则无法区分,因此采用前缀码“00 00 01”或者“00 00 00 01”来区分每一个nalu单元。在每个前缀码后面紧跟的一个字节为nalu的语法结构,由三部分组成forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)。
forbidden_bit:禁止位。
nal_reference_bit:当前NAL的优先级,值越大,该NAL越重要。
nal_unit_type :NAL类型。
其中,我们着重需要关注的是nal_unit_type ,因为它代表了这个NALU的具体荷载数据的类型,我们可以通过 nal_unit_type & 0x1f的方式来获取其类型,具体定义如下:
在PUSH FLV到RTMP服务器的时候,我们只需将FLV 的Tag Data封装进RTMP Packet,然后调用RTMP_SendPacket()函数将数据发送出去。
因此,在将H264 PUSH给RTMP服务器的时候,我们也需要按照FLV的格式将H264数据进行封包。因此我们需要构造视频Tag,并且我们在发送第一包数据前,需要构造“AVC Sequence Header”,用于告诉RTMP服务器解码相关的信息。
再次呈上上面的两幅图:
上面两幅图为FLV 每一个TAG 中的Tag Data里面的组成,因此我们需要根据上面的结构构造视频同步包 和 H264码流包
视频同步包的构造,其实是需要我们将H264中的SPS PPS按照一定的格式发给RTMP服务器,供RTMP服务器解码器进行解码。根据上面的图片可知,当构造视频同步包的时候,各个结构如下:
VIDEODATA
FrameType == 1
CodecID == 7
VideoData == AVCVIDEOPACKET
AVCVIDEOPACKET
AVCPacketType == 0x00
CompositionTime == 0x000000
Data == AVCDecoderConfigurationRecord
接下来继续构造AVCDecoderConfigurationRecord,参考上面链接,AVCDecoderConfigurationRecord相关结构如下:
因此我们需要将H264中的SPS PPS信息按照上面的结构进行填充,代码如下:
int send_sps_pps(unsigned char* index_body,int index_body_len)
{
unsigned char *sps,*pps;
int sps_temp,pps_temp;
int sps_len,pps_len;
int i,temp;
unsigned char body[1024];
memset(body,0,1024);
/* find *sps *pps */
temp = find_sps_pps(index_body,index_body_len,&sps_temp,&pps_temp,&sps_len,&pps_len);
if(temp < 0)
return -1;
sps = index_body + sps_temp;
pps = index_body + pps_temp;
/*去掉帧界定符*/
sps += 4;
pps += 4;
sps_len -= 4;
pps_len -= 4;
/*AVC head*/
i = 0;
body[i++] = 0x17;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
/*AVCDecoderConfigurationRecord*/
body[i++] = 0x01;
body[i++] = *(sps+1);
body[i++] = *(sps+2);
body[i++] = *(sps+3);
body[i++] = 0xff;
/*sps*/
body[i++] = 0xe1;
body[i++] = (sps_len >> 8) & 0xff;
body[i++] = sps_len & 0xff;
memcpy(&body[i],sps,sps_len);
i += sps_len;
/*pps*/
body[i++] = 0x01;
body[i++] = (pps_len >> 8) & 0xff;
body[i++] = (pps_len) & 0xff;
memcpy(&body[i],pps,pps_len);
i += pps_len;
/*send rtmp packet*/
if(rtmp_packet_send(body,i,RTMP_PACKET_TYPE_VIDEO,0) < 0)
return -2;
return temp;
}
构建了上面的视频同步包后,我们需要将H264的荷载的数据即H264的I P B等数据封包进去。根据上面的图片,结构信息如下:
VIDEODATA
FrameType ==(I frame ? 1 :2)
CodecID == 7
VideoData == AVCVIDEOPACKET
AVCVIDEOPACKET
AVCPacketType == 0x01
CompositionTime == 0x000000
Data == NALU
上面的Data == H264 NALU Size + NALU Raw Data
代码如下:
int send_rtmp_video(unsigned char* index_body,int index_body_len,unsigned int timestamp)
{
unsigned char *body;
int type;
/*去掉帧界定符*/
if (index_body[2] == 0x00) { /*00 00 00 01*/
index_body += 4;
index_body_len -= 4;
} else if (index_body[2] == 0x01){ /*00 00 01*/
index_body += 3;
index_body_len -= 3;
}
/*申请发送数据空间*/
body = (unsigned char*)malloc(index_body_len+9);
/*send video packet*/
memset(body,0,index_body_len+9);
/*key frame*/
body[0] = 0x27;
type = index_body[0]&0x1f;
if (type == NAL_SLICE_IDR)
body[0] = 0x17;
/*nal unit*/
body[1] = 0x01;
body[2] = 0x00;
body[3] = 0x00;
body[4] = 0x00;
//data length
body[5] = (index_body_len >> 24) & 0xff;
body[6] = (index_body_len >> 16) & 0xff;
body[7] = (index_body_len >> 8) & 0xff;
body[8] = (index_body_len ) & 0xff;
/*copy data*/
memcpy(&body[9],index_body,index_body_len);
/*send rtmp packet*/
if(rtmp_packet_send(body,index_body_len + 9,RTMP_PACKET_TYPE_VIDEO,timestamp) < 0)
return -1;
/*释放发送数据空间*/
free(body);
return 0;
}
在刚开始接触H264 PUSH到RTMP时,一头雾水,不理解如何进行操作,然后通过查阅资料,得到了初步的了解,将H264推流到RTMP服务器,关键的也就是视频同步包的构造,和H264普通数据包的构造,以及在将这些数据封包进RTMP Packet时候,要注意时间戳之间的关系。除了这些,其他方面例如RTMP的初始化,建立连接等方面则和RTMPDUMP中的流程一致。