libx264 版本是 128
libfaac 版本是 1.28 服务器
对于 H.264 而言每帧的界定符为 00 00 00 01 或者 00 00 01。 多线程
好比下面的 h264 文件片段这就包含三帧数据: ide
00 00 00 01 67 42 C0 28 DA 01 E0 08 9F 96 10 00
00 03 00 10 00 00 03 01 48 F1 83 2A 00 00 00 01
68 CE 3C 80 00 00 01 06 05 FF FF 5D DC 45 E9 BD
E6 D9 48 B7 96 2C D8 20 D9 23 EE EF … 函数
第一帧是 00 00 00 01 67 42 C0 28 DA 01 E0 08 9F 96 10 00 00 03 00 10 00 00 03 01 48 F1 83 2A
第二帧是 00 00 00 01 68 CE 3C 80
第三帧是 00 00 01 06 05 FF FF 5D DC 45 E9 BD E6 D9 48 B7 96 2C D8 20 D9 23 EE EF .. 编码
帧类型有:
NAL_SLICE = 1
NAL_SLICE_DPA = 2
NAL_SLICE_DPB = 3
NAL_SLICE_DPC = 4
NAL_SLICE_IDR = 5
NAL_SEI = 6
NAL_SPS = 7
NAL_PPS = 8
NAL_AUD = 9
NAL_FILLER = 12, url
咱们发送 RTMP 数据时只须要知道四种帧类型,其它类型我都把它规类成非关键帧。分别是
NAL_SPS(7), sps 帧
NAL_PPS(8), pps 帧
NAL_SLICE_IDR(5), 关键帧
NAL_SLICE(1) 非关键帧 spa
帧类型的方式判断为界面符后首字节的低四位。
第一帧的帧类型为: 0x67 & 0x1F = 7,这是一个 SPS 帧
第二帧的帧类型为: 0x68 & 0x1F = 8,这是一个 PPS 帧
第三帧的帧类型为: 0x06 & 0x1F = 6,这是一个 SEI 帧 .net
以上是咱们利用帧界定符划分帧,并能够判断每个帧的类型。 线程
注意:若是是压缩图像成 H264 帧,咱们就可没必要进行帧界定,由于每一次压缩的输出都明确了该帧的大小(包括界定符),每一次的压缩的结果可能包函多帧。一会具体讨论。 code
对于 AAC 帧它的界定符是 FF F1
这里我就不举例了,可经过查看 AAC 的二进制文件能够看到以下的帧结构:
FF F1 50 80 24 9F FD DE 04 00 00 6C 69 62 66 61 61 63 20 31 2E 32 38 00 00 42 15 95 ..
注意:那么对于 AAC 而言加上界定符每一帧的前 7 字节是帧的描述信息,也就是说 AAC 的祼数据是除去前面的 7 个字节的,在发送 RTMP 时,咱们要去掉这 7 个字节。一样,若是咱们是一边压缩一边发送 RTMP,咱们一样不须要界定帧,由于 libfaac 每次压缩完成的输出就是一个完整的帧数据,咱们只须要将该帧打包发送便可。
综合上面的所述,若是咱们只是一边压缩一边将压缩结果发送到 RTMP 服务器,那咱们就能够不用对帧进行界定,若是咱们是发送 H264 与 AAC 文件,那咱们就要对帧进行界定。
若是咱们只是简答的将压缩数据打包发送给 RTMP 服务器,那么 RTMP 服务器是不能够对数据进行解码和播放的,在这以前咱们要将音视频的视频的编码信息发送给 RTMP 服务器。不少人可能苦于寻找下面的三个编码参数而不得要领。其实要想获得也是很简单的。
对于 H264 而言,SPS 就是编码后的第一帧。若是是读取 H264 文件,就是第一个帧界定符与第二帧界定符中间的数据长度是 4。
对于 H264 而言,PPS 就是编码后的第二帧。若是是读取 H264 文件,就是第二个帧界定符与第三帧界定符中间的数据,长度不固定。
这个长度为 2 个字节,能够经过计算或者调用函数获取。建议经过调用faacEncGetDecoderSpecificInfo(fh,&spec,&len); 获取。
通常状况双声道 44100 采样下,该值是 0x1210
/*分配与初始化*/ rtmp = RTMP_Alloc(); RTMP_Init(rtmp); /*设置URL*/ if (RTMP_SetupURL(rtmp,rtmp_url) == FALSE) { log(LOG_ERR,"RTMP_SetupURL() failed!"); RTMP_Free(rtmp); return -1; } /*设置可写,即发布流,这个函数必须在链接前使用,不然无效*/ RTMP_EnableWrite(rtmp); /*链接服务器*/ if (RTMP_Connect(rtmp, NULL) == FALSE) { log(LOG_ERR,"RTMP_Connect() failed!"); RTMP_Free(rtmp); return -1; } /*链接流*/ if (RTMP_ConnectStream(rtmp,0) == FALSE) { log(LOG_ERR,"RTMP_ConnectStream() failed!"); RTMP_Close(rtmp); RTMP_Free(rtmp); return -1; }
/*定义包头长度,RTMP_MAX_HEADER_SIZE为rtmp.h中定义值为18*/ #define RTMP_HEAD_SIZE (sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE) RTMPPacket * packet; unsigned char * body; /*分配包内存和初始化,len为包体长度*/ packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+len); memset(packet,0,RTMP_HEAD_SIZE); /*包体内存*/ packet->m_body = (char *)packet + RTMP_HEAD_SIZE; body = (unsigned char *)packet->m_body; packet->m_nBodySize = 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; /*发送*/ if (RTMP_IsConnected(rtmp)) { ret = RTMP_SendPacket(rtmp,packet,TRUE); /*TRUE为放进发送队列,FALSE是不放进发送队列,直接发送*/ } /*释放内存*/ free(packet);
/*关闭与释放*/ RTMP_Close(rtmp); RTMP_Free(rtmp);
H.264 的编码信息帧是发送给 RTMP 服务器称为 AVC sequence header,RTMP 服务器只有收到 AVC sequence header 中的 sps, pps 才能解析后续发送的 H264 帧。
int send_video_sps_pps() { RTMPPacket * packet; unsigned char * body; int i; packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+1024); memset(packet,0,RTMP_HEAD_SIZE); packet->m_body = (char *)packet + RTMP_HEAD_SIZE; body = (unsigned char *)packet->m_body; memcpy(winsys->pps,buf,len); winsys->pps_len = len; 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; packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; packet->m_nBodySize = i; packet->m_nChannel = 0x04; packet->m_nTimeStamp = 0; packet->m_hasAbsTimestamp = 0; packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet->m_nInfoField2 = rtmp->m_stream_id; /*调用发送接口*/ RTMP_SendPacket(rtmp,packet,TRUE); free(packet); return 0; }
sps 与 pps 怎么获取到呢?
在前面已经说过,H264 的第 1 帧是 sps 帧, pps 是第 2 帧。
咱们在编码时会调用以下接口
size = x264_encoder_encode(cx->hd,&nal,&n,pic,&pout); int i,last; for (i = 0,last = 0;i < n;i++) { if (nal[i].i_type == NAL_SPS) { sps_len = nal[i].i_payload-4; memcpy(sps,nal[i].p_payload+4,sps_len); } else if (nal[i].i_type == NAL_PPS) { pps_len = nal[i].i_payload-4; memcpy(pps,nal[i].p_payload+4,pps_len); /*发送sps pps*/ send_video_sps_pps(); } else { /*发送普通帧*/ send_rtmp_video(nal[i].p_payload,nal[i].i_payload); } last += nal[i].i_payload; }
我彻底能够不用知道 sps, pps 的具体意义:)
int send_rtmp_video(unsigned char * buf,int len) { int type; long timeoffset; RTMPPacket * packet; unsigned char * body; timeoffset = GetTickCount() - start_time; /*start_time为开始直播时的时间戳*/ /*去掉帧界定符*/ if (buf[2] == 0x00) { /*00 00 00 01*/ buf += 4; len -= 4; } else if (buf[2] == 0x01){ /*00 00 01*/ buf += 3; len -= 3; } type = buf[0]&0x1f; packet = (RTMPPacket *)base_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) { body[0] = 0x17; } 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 = winsys->rtmp->m_stream_id; packet->m_nChannel = 0x04; packet->m_headerType = RTMP_PACKET_SIZE_LARGE; packet->m_nTimeStamp = timeoffset; /*调用发送接口*/ RTMP_SendPacket(rtmp,packet,TRUE); free(packet); }
这里要说明一下:
好比说 x264_encoder_encode 输出了 6 帧。
分别是 sps 帧, pps 帧,关键帧,非关键帧,非关键帧,非关键帧。
发送结果应该是, sps,pps 合成为一帧调用发送函数,剩下 4 帧,除去每一个 nal 的界定符,分别发送每个 nal。
在 libx264 中每一次调用 x264_encoder_encode 输出了 n 个帧,咱们要从这 n 个帧里找出 sps 和 pps,剩下的分次所有发送 nal,sps 与 pps 的帧界定符都是 00 00 00 01,而普通帧多是 00 00 00 01 也有可能 00 00 01。
若是 x264_encoder_encode 里没有 sps 帧与 pps 帧,则结果除去第一帧的界定符因此帧作为一个总体调用发送函数,它们的类型是由第一帧类型决定。
另外,H264 的流的第 1 帧必定是 sps 帧(包含帧界定符为 8 个字节),第 2 帧必定是 pps帧。
int cap_rtmp_sendaac_spec(unsigned char *spec_buf,int spec_len) { RTMPPacket * packet; unsigned char * body; int len; len = spec_len; /*spec data长度,通常是2*/ packet = (RTMPPacket *)base_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数据*/ packet->m_packetType = RTMP_PACKET_TYPE_AUDIO; packet->m_nBodySize = len+2; packet->m_nChannel = 0x04; packet->m_nTimeStamp = 0; packet->m_hasAbsTimestamp = 0; packet->m_headerType = RTMP_PACKET_SIZE_LARGE; packet->m_nInfoField2 = rtmp->m_stream_id; /*调用发送接口*/ RTMP_SendPacket(rtmp,packet,TRUE); return TRUE; }
对于音频解码参数 AAC sequence header 是经过
下面是得到 AAC sequence header 的方法
char *buf; int len; faacEncGetDecoderSpecificInfo(fh,&buf,&len); memcpy(spec_buf,buf,len); spec_len = len; /*释放系统内存*/ free(buf);
另外若是你是打开 aac 文件进行发送,那么你能够尝试本身计算这个值,其实也很简单,打开faac 源代码看一下 faacEncGetDecoderSpecificInfo 的实现,也就是几个移位的事:)。
对于通常状况 44100Hz 双声道,这个值是 0x1210,偷懒就是直接用这个值吧。
如前面所述,发送 AAC 的普通数据要改造一下,由于 AAC 的前 7 个字节(包括帧界定符)对于 RTMP 服务器来讲是无用的。
void * cap_dialog_send_audio(unsigned char * buf,int len) { long timeoffset; timeoffset = GetTickCount() - start_time; buf += 7; len += 7; if (len > 0) { RTMPPacket * packet; unsigned char * body; 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 01 + AAC RAW data*/ body[0] = 0xAF; body[1] = 0x01; memcpy(&body[2],buf,len); packet->m_packetType = RTMP_PACKET_TYPE_AUDIO; packet->m_nBodySize = len+2; packet->m_nChannel = 0x04; packet->m_nTimeStamp = timeoffset; packet->m_hasAbsTimestamp = 0; packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet->m_nInfoField2 = rtmp->m_stream_id; /*调用发送接口*/ RTMP_SendPacket(rtmp,packet,TRUE); free(packet); } return 0; }
至此全部流程均结束了。
要注意的几件事:
libRTMP 多线程发送有时候可能会出现问题,不知道是什么问题,最好改为队列发送。将填充好的 packet 经过消息或者其它方式发送给其它线程,发送线程统一发送便可。