这篇博客是写给新手的,我就不介绍rtmp流的格式了,因为是直接使用rtmp的库的,rtmp的格式对我的程序没有什么影响。对于h264视频流和aac音频格式简要的介绍一下。
h264视频格式:
下图是h264的视频序列,但是这样描述的不太好(对我而言)
sps pps I 帧 B帧 ……P帧
sps pps I 帧 B帧 ……P帧
sps pps I 帧 B帧 ……P帧
就像上面一样,只不过B帧和P帧之间可能有其他的不同数目的B帧和P帧,但是一定是I帧开始,P帧结束,然后再来一个类似的结构,然后就是区分不同的帧的类型了。
首先说一下我的rtmp封包的处理原则(所谓的处理原则其实就是rtmp包体前封装不同的数据)对于未列举的数据帧不太重要,就不用考虑其封包规则。
对于每个NAL块的标识头都去掉,因为这部分信息对rtmp流服务器没用
对sps、pps单独处理,把sps、pps封成一个rtmp包发送出去,这部分需要添加AVCDecodeConfigurationRecord,这个的具体含义,有兴趣的可以去查查。
对于I帧区别对待,因为I帧是关健帧(如果I帧丢失,I帧后面的都要丢掉,直到下一个I帧,但是这部分我们不用管)
其他的数据就按照普通数据的处理
知道了结构我们就可以对H264视频流进行封包了
首先对RTMPserver初始化。
int RTMP264_Connect(const char *push_url)
{
//由于摄像头的timestamp是一直在累加,需要每次得到相对时间戳
//分配与初始化
rtmp = RTMP_Alloc();
RTMP_Init(rtmp);
//设置URL
if (RTMP_SetupURL(rtmp, push_url) < 0)
{
//log(LOG_ERR, "RTMP_SetupURL() failed!");
RTMP_Free(rtmp);
return -1;
}
//设置可写,即发布流,这个函数必须在连接前使用,否则无效
RTMP_EnableWrite(rtmp);
//连接服务器
if (RTMP_Connect(rtmp, NULL) < 0)
{
//log(LOG_ERR, "RTMP_Connect() failed!");
RTMP_Free(rtmp);
return -1;
}
//连接流
if (RTMP_ConnectStream(rtmp, 0) < 0)
{
//log(LOG_ERR, "RTMP_ConnectStream() failed!");
RTMP_Close(rtmp);
RTMP_Free(rtmp);
return -1;
}
return 0;
}
初始化之后对h264数据封包(代码基本固定)
int SendVideoSpsPps(unsigned char *sps, int sps_len, unsigned char *pps, int pps_len, int timestamp, const char *push_url)
{
RTMPPacket *packet = NULL; //rtmp packet
unsigned char *body = NULL;
int i;
packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + 1024);
memset(packet, 0, RTMP_HEAD_SIZE + 1024);
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
body = (unsigned char *)packet->m_body;
if(sps[2] == 0x00)
{
sps += 4;
sps_len -= 4;
} else if (sps[2] == 0x01)
{
sps += 3;
sps_len -= 3;
}
if(pps[2] == 0x00)
{
pps += 4;
pps_len -= 4;
} else if (pps[2] == 0x01)
{
pps += 3;
pps_len -= 3;
}
i = 0;
body[i++] = 0x17;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
// AVCDecodeConfigurationRecord
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;
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nBodySize = i;
packet->m_nChannel = 0x04;
packet->m_nTimeStamp = timestamp & 0xffffff;
packet->m_hasAbsTimestamp = 0;
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet->m_nInfoField2 = rtmp->m_stream_id;
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("sps pps trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("sps pps trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("sps pps trying to connect to rtmp server...\n");
}
//调用发送接口
int nRet = RTMP_SendPacket(rtmp, packet, true);
free(packet);
return nRet;
}
处理普通视频数据
int send_rtmp_video(unsigned char* buf, int len, int timestamp, const char *push_url)
{
int type;
long timeoffset;
RTMPPacket *packet;
unsigned char *body;
timeoffset = timestamp;
//去掉帧界定符
if(buf[2] == 0x00)
{
buf += 4;
len -= 4;
} else if (buf[2] == 0x01)
{
buf += 3;
len -= 3;
}
type = buf[0]&0x1f;
packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + len + 9);
memset(packet, 0, RTMP_HEAD_SIZE);
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
packet->m_nBodySize = len +9;
// send video packet
body = (unsigned char *)packet->m_body;
memset(body, 0, len + 9);
// key frame
body[0] = 0x27;
//if (type == NAL_SLICE_IDR) type = 5是关键帧
// 在此处还可以判断是否是SEI帧,如果是丢掉,否则播放的时候会报错(虽然不影响播放效果)
if (type == 5)
{
body[0] = 0x17;
}else if (type == 6){
free(packet);
return 0;
}
body[1] = 0x01; //nal unit
body[2] = 0x00;
body[3] = 0x00;
body[4] = 0x00;
body[5] = (len >> 24) & 0xff;
body[6] = (len >> 16) & 0xff;
body[7] = (len >> 8) & 0xff;
body[8] = (len) & 0xff;
// copy data
memcpy(&body[9], buf, len);
packet->m_hasAbsTimestamp = 0;
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nInfoField2 = rtmp->m_stream_id;
packet->m_nChannel = 0x04;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nTimeStamp = timeoffset & 0xffffff;
// 调用发送接口
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("video normal data trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("video normal data trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("video normal data trying to connect to rtmp server...\n");
if (!RTMP_IsConnected(*rtmp))
return -1;
}
int nRet = RTMP_SendPacket(rtmp, packet, true);
free(packet);
return nRet;
}
接下来是针对音频数据了
aac发送须知
发送音频包之前要先发送AudioDecoderSpecificInfo
音频包分隔单元前7个字节是没有用的,直接去掉
在封装到rtmp流时,发送AudioDecoderSpecificInfo时,rtmp包主体前要添加0xAF 00,普通数据包体前添加0xAF 01,我就当时没注意,搞了好长时间都没声音,还是同事发现的。
发送aac AudioDecoderSpecificInfo
int rtmp_sendaac_spec(unsigned char *spec_buf,int spec_len, int timestamp, const char *push_url)
{
//if (NULL == spec_buf)
//{
// printf("get spec_buf badly\n");
// return -1;
//}
RTMPPacket * packet;
unsigned char * body;
int len;
len = spec_len; /*spec data长度,一般是2*/
packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+len+2);
memset(packet,0,RTMP_HEAD_SIZE);
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
body = (unsigned char *)packet->m_body;
/*AF 00 + AAC RAW data*/
body[0] = 0xAF;
body[1] = 0x00;
memcpy(&body[2],spec_buf,len); /*spec_buf是AAC sequence header数据*/
//memcpy(&body[2], spec_buf, spec_len);
packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet->m_nBodySize = len + 2;
packet->m_nChannel = 0x04;
packet->m_hasAbsTimestamp = 0;
packet->m_nTimeStamp = timestamp&0xffffff;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nInfoField2 = rtmp->m_stream_id;
/*调用发送接口*/
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("audio spec trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("audio spec trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf("audio spec trying to connect to rtmp server...\n");
if (!RTMP_IsConnected(*rtmp))
return -1;
}
int nRet = RTMP_SendPacket(rtmp, packet, true);
free(packet);
return nRet;
}
其中有必要解释一下spec_buf和spec_len就是AudioDecoderSpecificInfo
可以通过faacEncGetDecoderSpecificInfo得到。对于一般情况44100Hz双声道,这个值是0x1210
char *buf;
int len;
faacEncGetDecoderSpecificInfo(fh,&buf,&len);
memcpy(spec_buf,buf,len);
spec_len = len;
/释放系统内存/
free(buf);
对于其他普通的音频数据
int send_aac_audio(unsigned char *buf, int len, int timestamp, const char *push_url)
{
// 去掉前七个无用字节
buf = buf + 7;
len = len - 7;
long timeoffset;
RTMPPacket *packet;
unsigned char *body;
timeoffset = timestamp;
packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + len + 2);
memset(packet, 0, RTMP_HEAD_SIZE);
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
body = (unsigned char *)packet->m_body;
memset(body, 0, len + 2);
body[0] = 0xAF;
body[1] = 0x01;
memcpy(&body[2], buf, len);
packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
packet->m_nBodySize = len + 2;
packet->m_hasAbsTimestamp = 0;
packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet->m_nInfoField2 = rtmp->m_stream_id;
packet->m_nChannel = 0x04;
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet->m_nTimeStamp = timeoffset & 0xffffff;
// 调用发送接口
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf(" audio trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf(" audio trying to connect to rtmp server...\n");
}
if (!RTMP_IsConnected(rtmp))
{
RTMP264_Connect(push_url);
printf(" audio trying to connect to rtmp server...\n");
}
int nRet = RTMP_SendPacket(rtmp, packet, true);
free(packet);
return nRet;
}
在此有必要说一下rtmp服务器不清楚是否支持线程安全,所以最好采用队列发送,另外如果出现一下错误,一定要记得检查一下时间戳的问题
ERROR: WriteN, RTMP send error 32 (136 bytes)
ERROR: WriteN, RTMP send error 32 (39 bytes)
ERROR: WriteN, RTMP send error 9 (42 bytes)
音视频在一个队列里面发送也会出现时间戳的问题,音视频的时间戳相差时间不要太大,要不然就会报以上错误。
最后说一下g711a转码的问题,我是直接调用的转码库,大家可以搜一下,估计都比我的要好,因为我的库预测是内部人员写的,到现在还出现内存泄露的问题,估计到时候我还要改一下。
参考文章
RTMP中FLV流到标准h264、aac的转换
灵魂守卫 » 使用librtmp进行H264与AAC直播
RTMP直播到FMS中的AAC音频直播
————————————————
版权声明:本文为优快云博主「l1114598932」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/l1114598932/article/details/43797695