libx264 版本是 128
libfaac 版本是 1.28

1、帧的划分

1.1 H.264 帧

对于 H.264 而言每帧的界定符为 00 00 00 01 或者 00 00 01

比如下面的 h264 文件片断这就包含三帧数据:

第一帧是 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,

我们发送 RTMP 数据时只需要知道四种帧类型,其它类型我都把它规类成非关键帧。分别是
NAL_SPS(7), sps 帧
NAL_PPS(8), pps 帧
NAL_SLICE_IDR(5), 关键帧
NAL_SLICE(1) 非关键帧

帧类型的方式判断为界面符后首字节的低四位。
第一帧的帧类型为: 0x67 & 0x1F = 7,这是一个 SPS 帧
第二帧的帧类型为: 0x68 & 0x1F = 8,这是一个 PPS 帧
第三帧的帧类型为: 0x06 & 0x1F = 6,这是一个 SEI 帧

以上是我们利用帧界定符划分帧,并可以判断每一个帧的类型。

注意:如果是压缩图像成 H264 帧,我们就可不必进行帧界定,因为每一次压缩的输出都明确了该帧的大小(包括界定符),每一次的压缩的结果可能包函多帧。一会具体讨论。

1.2 AAC 帧

对于 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 文件,那我们就要对帧进行界定。

2.视频与音频的编码信息

如果我们只是简答的将压缩数据打包发送给 RTMP 服务器,那么 RTMP 服务器是不可以对数据进行解码和播放的,在这之前我们要将音视频的视频的编码信息发送给 RTMP 服务器。很多人可能苦于寻找下面的三个编码参数而不得要领。其实要想得到也是很简单的。

2.1 (H264)SPS

对于 H264 而言,SPS 就是编码后的第一帧。如果是读取 H264 文件,就是第一个帧界定符与第二帧界定符中间的数据长度是 4。

2.2 (H264)PPS

对于 H264 而言,PPS 就是编码后的第二帧。如果是读取 H264 文件,就是第二个帧界定符与第三帧界定符中间的数据,长度不固定。

2.3 (AAC)AudioDecoderSpecificInfo

这个长度为 2 个字节,可以通过计算或者调用函数获取。建议通过调用faacEncGetDecoderSpecificInfo(fh,&spec,&len); 获取。
一般情况双声道 44100 采样下,该值是 0x1210

3.librtmp 的使用

/*分配与初始化*/
rtmp = RTMP_Alloc();
RTMP_Init(rtmp); /*设置URL*/
if (RTMP_SetupURL(rtmp,rtmp_url) == FALSE) {
log(LOG_ERR,"RTMP_SetupURL() failed!");
RTMP_Free(rtmp);
return -;
} /*设置可写,即发布流,这个函数必须在连接前使用,否则无效*/
RTMP_EnableWrite(rtmp); /*连接服务器*/
if (RTMP_Connect(rtmp, NULL) == FALSE) {
log(LOG_ERR,"RTMP_Connect() failed!");
RTMP_Free(rtmp);
return -;
} /*连接流*/
if (RTMP_ConnectStream(rtmp,) == FALSE) {
log(LOG_ERR,"RTMP_ConnectStream() failed!");
RTMP_Close(rtmp);
RTMP_Free(rtmp);
return -;
}

4.包类型

4.1 H.264编码信息帧

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+);
memset(packet,,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 = ;
body[i++] = 0x17;
body[i++] = 0x00; body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00; /*AVCDecoderConfigurationRecord*/
body[i++] = 0x01;
body[i++] = sps[];
body[i++] = sps[];
body[i++] = sps[];
body[i++] = 0xff; /*sps*/
body[i++] = 0xe1;
body[i++] = (sps_len >> ) & 0xff;
body[i++] = sps_len & 0xff;
memcpy(&body[i],sps,sps_len);
i += sps_len; /*pps*/
body[i++] = 0x01;
body[i++] = (pps_len >> ) & 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 = ;
packet->m_hasAbsTimestamp = ;
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet->m_nInfoField2 = rtmp->m_stream_id; /*调用发送接口*/
RTMP_SendPacket(rtmp,packet,TRUE);
free(packet); return ;
}

sps 与 pps 怎么获取到呢?

在前面已经说过,H264 的第 1 帧是 sps 帧, pps 是第 2 帧。

我们在编码时会调用如下接口

size = x264_encoder_encode(cx->hd,&nal,&n,pic,&pout);

int i,last;
for (i = ,last = ;i < n;i++) {
if (nal[i].i_type == NAL_SPS) {
sps_len = nal[i].i_payload-;
memcpy(sps,nal[i].p_payload+,sps_len);
} else if (nal[i].i_type == NAL_PPS) {
pps_len = nal[i].i_payload-;
memcpy(pps,nal[i].p_payload+,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 的具体意义:)

4.2 H.264关键帧

4.3 H.264非关键帧

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[] == 0x00) { /*00 00 00 01*/
buf += ;
len -= ;
} else if (buf[] == 0x01){ /*00 00 01*/
buf += ;
len -= ;
}
type = buf[]&0x1f; packet = (RTMPPacket *)base_malloc(RTMP_HEAD_SIZE+len+);
memset(packet,,RTMP_HEAD_SIZE); packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
packet->m_nBodySize = len + ; /*send video packet*/
body = (unsigned char *)packet->m_body;
memset(body,,len+); /*key frame*/
body[] = 0x27;
if (type == NAL_SLICE_IDR) {
body[] = 0x17;
} body[] = 0x01; /*nal unit*/
body[] = 0x00;
body[] = 0x00;
body[] = 0x00; body[] = (len >> ) & 0xff;
body[] = (len >> ) & 0xff;
body[] = (len >> ) & 0xff;
body[] = (len ) & 0xff; /*copy data*/
memcpy(&body[],buf,len); packet->m_hasAbsTimestamp = ;
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帧。

4.4 AAC编码信息

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+);
memset(packet,,RTMP_HEAD_SIZE); packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
body = (unsigned char *)packet->m_body; /*AF 00 + AAC RAW data*/
body[] = 0xAF;
body[] = 0x00;
memcpy(&body[],spec_buf,len); /*spec_buf是AAC sequence header数据*/ packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet->m_nBodySize = len+;
packet->m_nChannel = 0x04;
packet->m_nTimeStamp = ;
packet->m_hasAbsTimestamp = ;
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,偷懒就是直接用这个值吧。

4.5 AAC普通数据

如前面所述,发送 AAC 的普通数据要改造一下,因为 AAC 的前 7 个字节(包括帧界定符)对于 RTMP 服务器来说是无用的。

void * cap_dialog_send_audio(unsigned char * buf,int len)
{
long timeoffset;
timeoffset = GetTickCount() - start_time; buf += ;
len += ; if (len > ) {
RTMPPacket * packet;
unsigned char * body; packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+len+);
memset(packet,,RTMP_HEAD_SIZE); packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
body = (unsigned char *)packet->m_body; /*AF 01 + AAC RAW data*/
body[] = 0xAF;
body[] = 0x01;
memcpy(&body[],buf,len); packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet->m_nBodySize = len+;
packet->m_nChannel = 0x04;
packet->m_nTimeStamp = timeoffset;
packet->m_hasAbsTimestamp = ;
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet->m_nInfoField2 = rtmp->m_stream_id; /*调用发送接口*/
RTMP_SendPacket(rtmp,packet,TRUE);
free(packet);
}
return ;
}

至此所有流程均结束了。

要注意的几件事:
libRTMP 多线程发送有时候可能会出现问题,不知道是什么问题,最好改成队列发送。将填充好的 packet 通过消息或者其它方式发送给其它线程,发送线程统一发送即可。

转自:https://my.oschina.net/jerikc/blog/501948

使用librtmp进行H264与AAC直播的更多相关文章

  1. rtmp发送H264及aac的音视频

    RTMP推送的音视频流的封装形式和FLV格式相似,由此可知,向FMS推送H264和AAC直播流,需要首先发送"AVC sequence header"和"AAC sequ ...

  2. rtmp发送H264及aac的音视频 (转)

    RTMP推送的音视频流的封装形式和FLV格式相似,由此可知,向FMS推送H264和AAC直播流,需要首先发送"AVC sequence header"和"AAC sequ ...

  3. js对flv提取h264、aac音视频流

    FLV提取里面的h264视频流 FLV和MP4支持的编码 流媒体和媒体文件的区别 流媒体是指将一连串的多媒体资料压缩后,经过互联网分段发送资料,在互联网上即时传输影音以供观赏的一种技术与过程,此技术使 ...

  4. 解密H264、AAC硬件解码的关键扩展数据处理

    通过上一篇文章,我们用ffmpeg分离出一个多媒体容器中的音视频数据,但是很可能这些数据是不能被正确解码的.为什么呢?因为在解码这些数据之前,需要对解码器做一些配置,典型的就是目前流行的高清编码“黄金 ...

  5. (原)从mp4,flv文件中解析出h264和aac,送解码器解码失败

    转载请注明出处:http://www.cnblogs.com/lihaiping/p/5285166.html 今天在做本地文件解码测试,发现从mp4,flv文件中读出来的帧数据,h264和aac帧直 ...

  6. (转) 解密H264、AAC硬件解码的关键扩展数据处理

    出自:http://blog.itpub.net/30168498/viewspace-1576794/       通过上一篇文章,我们用ffmpeg分离出一个多媒体容器中的音视频数据,但是很可能这 ...

  7. (转)RTMP中FLV流到标准h264、aac的转换

    这段时间,工作上的需要,在RTMP上做了flv流到标准h264.AAC的转换,服务器是开源项目CRTMPSERVER,客户端flex编写,视频编码h264,音频编码AAC,现将一些协议相关的东西记录如 ...

  8. 将h264和aac码流合成flv文件

    在视频应用中,经常需要将接收到h264和aac数据保存成文件. 本来想用mp4格式,但是mp4在没有正常关闭的情况下会导致文件打不开,而在实际应用中经常会出现设备直接拔电,程序不是正常结束的情况.于是 ...

  9. H264与AAC ES打包成MP4

    注意 设置图像的sps pps MP4AddH264SequenceParameterSet(file,video,sps,sizeof(sps)); MP4AddH264PictureParamet ...

随机推荐

  1. go ftp通信

    main.go package main import ( "strings" ftp4go "github.com/shenshouer/ftp4go" &q ...

  2. 关于Unity中红外线瞄准的效果实现

    今天做一个FPS游戏的时候,由于我做的是第三人称的射击,所以需要一个枪的红外线瞄准的效果. 一开始我在枪上挂一个很细很长的聚光灯,瞄准远处物体的时候,看起来有点红外线的样子,但是靠近之后光线就变成一个 ...

  3. Spring JMS 官方文档学习

    最后部分的XML懒得写了,因为个人更倾向于JavaConfig形式. 为知笔记版本见这里,带格式~ 做了一个小demo,放到码云上了,有兴趣的点我. 说明:需要先了解下JMS的基础知识. 1.介绍 S ...

  4. Spring系列(一):Spring的基本概念及其核心

    一.Spring是什么 Spring是一种多层的J2EE应用程序框架,其核心就是提供一种新的机制管理业务对象及其依赖关系. 二.为什么要使用Spring 1. 降低组件之间的耦合度,实现软件各层之间的 ...

  5. e587. Filling Basic Shapes

    There are two ways to fill basic shapes like lines and rectangles. The first is to use specific draw ...

  6. Storm概念、原理详解及其应用(一)BaseStorm

    本文借鉴官文,添加了一些解释和看法,其中有些理解,写的比较粗糙,有问题的地方希望大家指出.写这篇文章,是想把一些官文和资料中基础.重点拿出来,能总结出便于大家理解的话语.与大多数“wordcount” ...

  7. 如何解析oracle执行计划

    要执行任何SQL语句,Oracle 必须推导出一个“执行计划”.查询的执行计划是 Oracle 将如何实现数据的检索,以满足给定 SQL 语句的描述.它只不过是其中包含的步骤及它们之间关系的顺序树.执 ...

  8. 单例模式简介以及C++版本的实现

        本篇博文主要内容参考 C++的单例模式一文,在此,为原作者耐心细致的分析讲解,表示感谢.本文将结合此篇文章,给出自己做实验后的理解以及代码,作为今天学习的小结.     单例模式,它的意图是保 ...

  9. 内存管理 初始化(二)bootmem位图分配器建立 及 使用

    本地的笔记有点长,先把bootmem位图分配器的建立 及  使用过程做下梳理. 都是代码,上面做了标注.开始的汇编部分省略了(涉及的内容不多,除了swapper_pg_dir的分配). 该记录不会再添 ...

  10. Python中MongoDB使用

    MongoDB的层级为 database -->collection --> document 安装MongoDB,启动mongo服务 PyMongo模块是Python对MongoDB操作 ...