最近在做关于rtmp直播推流的项目,本身对rtmp也是一窍不通的,关于连接封装方面的资料也是看了很多,软件实现还是没有什么头绪,在看了雷神的基于librtmp的例子后(https://blog.csdn.net/leixiaohua1020/article/details/42105049) 在大神的轮子上修改,增加了一些东西,简单实现了一个摄像头rtmp实时推流的功能。
在看这篇文章之前,我还是建议大家先去上文给的雷神的博文中看看,里面还有大神写的源代码,本文实现的推流功能在关于librtmp库(rtmp的初始化,连接及发送接口)的运用方面都是参照雷神的来写的,没有什么区别,关于这部分代码的详解我会另起一篇或几篇文章来写(有关RTMP的东西实在太多),这篇文章主要介绍的是我跟雷神写的不同的地方,看过雷神的例子应该都知道,雷神的例子是推一个H264的视频文件,而在直播中,码流数据大多时候是不会存在本地(摄像头本身可用内存就少)而是直接推送到流媒体服务器上,推完之后缓冲就会被清除,所以在关于码流数据的循环读取和拆分部分需要重写,主要是三个函数ReadFirstNaluFromBuf(),ReadNaluFromBuf(),FindNaluFromNextPacket()。
在分析了海思,RealTek等厂家的编码芯片出来的码流可以发现,视频开始的第一个数据包通常都由SPS,PPS,I帧构成,接下来的几个数据包大多都是P帧,然后又会有一个包含了SPS,PPS,I帧的数据包出现,这样往复循环。而RTMP是不能直接推h264码流的,必须把264码流封装成FLV格式才能推出去。所以,这个264的RTMP的主要实现思想就是,把每包数据以NALU为单位拆分开来,并对每一个NALU进行FLV格式封装,封装好后调用librtmp的发送接口发送出去。
现在上代码
1.ReadFirstNaluFromBuf()分离码流包第一个Nalu,当头下标nalhead_pos为0时调用
int ReadFirstNaluFromBuf(NaluUnit *nalu,unsigned char *buf, unsigned int buf_size)
{
int naltail_pos=nalhead_pos;
if(buf_tmp != NULL){
free(buf_tmp);
}
buf_tmp=(unsigned char*)malloc(buf_size);
memset(buf_tmp,0,buf_size);
//Nalu头标志为00000001,头下标找到第一个Nalu头标志
while(nalhead_pos<buf_size)
{
if(buf[nalhead_pos++] == 0x00 &&
buf[nalhead_pos++] == 0x00)
{
if(buf[nalhead_pos++] == 0x00 &&
buf[nalhead_pos++] == 0x01 ){
goto gotnal_head;
}
else
continue;
}
else
continue;
gotnal_head:
//尾下标找到下一个Nalu头标志
naltail_pos = nalhead_pos;
while (naltail_pos<buf_size)
{
if(buf[naltail_pos++] == 0x00 &&
buf[naltail_pos++] == 0x00 )
{
if(buf[naltail_pos++] == 0x00 &&
buf[naltail_pos++] == 0x01)
{
nalu->size = (naltail_pos-4)-nalhead_pos;
break;
}
}
}
//在此码流包找不到下一个Nalu头标志,说明在下一个包还有此Nalu数据
if(nalhead_pos<buf_size && naltail_pos>=buf_size ){
nalu->size = buf_size - nalhead_pos;
nalu->type = buf[nalhead_pos]&0x1f;
memcpy(buf_tmp,buf+nalhead_pos,nalu->size+1);//先把这个包的Nalu数据保存起来
nalu->size = FindNaluFromNextPacket(nalu->size,buf_size);//不断将这个包的NALU剩余数据放到buf_tmp中,以便一起发送
nalhead_pos = 0;//FindNaluFromNextPacket()函数运行后读指针必定指向新的码流包,所以置0重新调用本函数。
if(nalu->size == FALSE){
printf("FindNaluFromNextPacket file \n");
return FALSE;
}
}
//在此码流找到下一个Nalu头标志
if(nalhead_pos<buf_size && naltail_pos<buf_size){
nalu->type = buf[nalhead_pos]&0x1f;
memcpy(buf_tmp,buf+nalhead_pos,nalu->size+1);
nalhead_pos=naltail_pos;
}
nalu->data=buf_tmp;
return TRUE;
}
return TRUE;
}
2.ReadNaluFromBuf()分离码流包中Nalu,当头下标nalhead_pos不为0时调用
int ReadNaluFromBuf(NaluUnit *nalu,unsigned char *buf, unsigned int buf_size)
{
int naltail_pos=nalhead_pos;
int nalustart;
if(buf_tmp != NULL){
free(buf_tmp);
}
buf_tmp=(unsigned char*)malloc(buf_size);
memset(buf_tmp,0,buf_size);
nalu->size=0;
while(naltail_pos<buf_size)
{
if(buf[naltail_pos++] == 0x00 &&
buf[naltail_pos++] == 0x00)
{
if(buf[naltail_pos++] == 0x00 &&
buf[naltail_pos++] == 0x01)
{
nalustart=4;
goto gotnal;
}
else
continue;
}
else
continue;
gotnal:
nalu->type = buf[nalhead_pos]&0x1f;
nalu->size=naltail_pos-nalhead_pos-nalustart;
if(nalu->type==0x06)
{
nalhead_pos=naltail_pos;
continue;
}
memcpy(buf_tmp,buf+nalhead_pos,nalu->size+1);
nalu->data=buf_tmp;
nalhead_pos=naltail_pos;
return TRUE;
}
//此包找不到下一NALU
if(naltail_pos>=buf_size && nalhead_pos<buf_size)
{
nalu->size = buf_size - nalhead_pos;
nalu->type = buf[nalhead_pos]&0x1f;
memcpy(buf_tmp,buf+nalhead_pos,nalu->size+1);
nalu->size = FindNaluFromNextPacket(nalu->size,buf_size);
nalhead_pos = 0;
if(nalu->size == FALSE){
printf("FindNaluFromNextPacket file \n");
return FALSE;
}
return TRUE;
}
if(naltail_pos>=buf_size && nalhead_pos>=buf_size){
nalhead_pos = 0;
return FALSE;
}
return FALSE;
}
3.FindNaluFromNextPacket()更新数据包直至找到下一NALU为止
int FindNaluFromNextPacket(int size,unsigned int buf_size){
int nal_pos = 0;
int nalustate = TRUE;
do{//找到下一NALU头为止
nal_pos = 0;
struct rts_av_buffer *pdata=NULL;
memset(&stData,0,sizeof (stData));
//usleep(1000);
if (rts_av_poll(h264flag))//检查 Channel 中是否有数据可读(获取码流数据底层接口)
continue;
if (rts_av_recv(h264flag, &pdata))//接收数据放入buffer(获取码流数据底层接口)
continue;
if(stDataPacket != NULL){
free(stDataPacket);
//printf("free buffer \n");
}
stData.pSize = pdata->bytesused;
stDataPacket=(unsigned char*)malloc(pdata->bytesused);
if(stDataPacket)
//printf("malloc buffer success \n");
memset(stDataPacket,0,pdata->bytesused);
memcpy(stDataPacket, (unsigned char *)pdata->vm_addr,pdata->bytesused);
stData.pData = stDataPacket;
rts_av_put_buffer(pdata);//减少 buffer 的引用计数
/*if (stDataPacket) {
fwrite(stData.pData, 1,
stData.pSize, pfile);
}*/
while(nal_pos<stData.pSize) { //跳出此循环说明新数据包遍历完毕或找到nalu头
if(stData.pData[nal_pos++] == 0x00 &&
stData.pData[nal_pos++] == 0x00) {
if(stData.pData[nal_pos++] == 0x00 &&
stData.pData[nal_pos++] == 0x01 ){
nalustate = FALSE;
break;
}
else
continue;
}
else
continue;
}
//未找到下一nalu头但到了此包尾,将buf_tmp扩大容量以放入上一NALU剩余数据
if(nalustate && nal_pos>=stData.pSize){
if((buf_tmp = (unsigned char*)realloc(buf_tmp,nal_pos + buf_size)) == NULL){
return FALSE;
}
memcpy(buf_tmp+size,stData.pData,nal_pos+1);
size = size + nal_pos;//记录NALU长度
buf_size = nal_pos + buf_size;//记录BUFFER长度用以扩大容量
}
}while(nalustate);
return size;
}
这三个函数在成功取到一个NALU的相关数据后就会将这些数据存到定义的NALU结构体里面,将这个结构体里面的数据进行封装并发送就可以实现实时的rtmp推流了。这里把完整的demo放上来https://download.csdn.net/download/weixin_36983723/10753424 ,有兴趣的可以看一下。