http://my.oschina.net/jerikc/blog/501948#OSC_h3_13

使用librtmp进行H264与AAC直播

发表于5个月前(2015-09-06 23:19)   阅读(1276) | 评论(0) 11人收藏此文章, 我要收藏
赞0

目录[-]

  • 1、帧的划分
  • 1.1 H.264 帧
  • 1.2 AAC 帧
  • 2.视频与音频的编码信息
  • 2.1 (H264)SPS
  • 2.2 (H264)PPS
  • 2.3 (AAC)AudioDecoderSpecificInfo
  • 3.librtmp 的使用
  • 4.包类型
  • 4.1 H.264编码信息帧
  • 4.2 H.264关键帧
  • 4.3 H.264非关键帧
  • 4.4 AAC编码信息
  • 4.5 AAC普通数据
  • 5.参考文档

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 …

第一帧是 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 的使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*分配与初始化*/
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;
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*定义包头长度,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);

?
1
2
3
/*关闭与释放*/
RTMP_Close(rtmp);
RTMP_Free(rtmp);

4.包类型

4.1 H.264编码信息帧

H.264 的编码信息帧是发送给 RTMP 服务器称为 AVC sequence header,RTMP 服务器只有收到 AVC sequence header 中的 sps, pps 才能解析后续发送的 H264 帧。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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 帧。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 的具体意义:)

4.2 H.264关键帧

4.3 H.264非关键帧

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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帧。

4.4 AAC编码信息

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 的方法

?
1
2
3
4
5
6
7
8
9
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 服务器来说是无用的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 通过消息或者其它方式发送给其它线程,发送线程统一发送即可。

5.参考文档

  • 《Video File Format Specification Version 10》 http://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf
  • 《H264视频通过RTMP直播》 http://blog.csdn.net/firehood_/article/details/8783589
  • rtmpdump-2.3

使用librtmp进行H264与AAC直播相关推荐

  1. php librtmp,使用librtmp进行H264与AAC直播

    libx264 版本是 128 libfaac 版本是 1.28 1.帧的划分 1.1 H.264 帧 对于 H.264 而言每帧的界定符为 00 00 00 01或者 00 00 01. 比如下面的 ...

  2. 1小时学会:最简单的iOS直播推流(六)h264、aac、flv介绍

    最简单的iOS 推流代码,视频捕获,软编码(faac,x264),硬编码(aac,h264),美颜,flv编码,rtmp协议,陆续更新代码解析,你想学的知识这里都有,愿意懂直播技术的同学快来看!! 源 ...

  3. Android直播开发之旅(17):使用FFmpeg提取MP4中的H264和AAC

    最近在开发中遇到了一个问题,即无法提取到MP4中H264流的关键帧进行处理,且保存到本地的AAC音频也无法正常播放.经过调试分析发现,这是由于解封装MP4得到的H264和AAC是ES流,它们缺失解码时 ...

  4. H264和AAC合成FLV案例

    H264和AAC合成FLV案例 目录 FFmpeg合成流程 FFmpeg函数:avformat_write_header FFmpeg结构体:avformat_alloc_output_context ...

  5. 【Android音视频开发】【032】Android从RTMP流中提取H264和AAC数据进行播放

    前篇 在上篇博客中,我们已经讲解过,如何从RTMP流中提取H264和AAC数据,并保存为FLV,AAC,H264等文件 这篇博客我们讲解,怎么通过Android多媒体框架播放这些数据 上篇博客的重点在 ...

  6. RTP协议封装H264/H265/AAC

    <RTSP实时音视频传输介绍> 目录 一.前言 二.RTP基本格式介绍 1.RTP 固定头 2.RTP 扩展头 3.RTP 载荷 三.RTP封装H264 1.封装包类型 四.RTP封装H2 ...

  7. ffmpeg h264和aac封装为mp4文件

    1.I帧/P帧/B帧 I帧:帧内编码图像帧,也叫关键帧,包含一幅完整的图像信息,不含运动矢量,在解码时不需要参考其它帧图像.在闭合式GOP(画面组)中,每个GOP的开始是IDR帧,且当前GOP的数据不 ...

  8. 视频系统 流媒体 rtsp hls h264 h265 aac 高并发 低延时 系统 设计 录像 视频合成 转发 点播 快进 快退 单步播放 分布式集群

    系统改名为:,升级包改使用jpg图像封装,从2.124版本开始,1.*的升级包停止使用 系统工具            下载地址(2019-04-19) :https://pan.baidu.com/ ...

  9. FFmpeg将H264和AAC合成为MP4

    前言 之前的内容提到使用Mp4V2合成Mp4文件失败,只能换方案使用FFmpeg去尝试了. 一.下载编译FFmpeg 在网上下载到了FFmpeg源码库,一开始使用的是 ffmpeg-4.1.3 版本, ...

最新文章

  1. 对下载的包进行修改--python
  2. FILE 结构体的定义,inode ,软链接和硬链接的理解
  3. 美国互联网巨头在华发展10大败因
  4. 【个人申明】主要发表平台迁至简书和公众号
  5. UVA - 10934 Dropping water balloons(装满水的气球)(dp)
  6. php byte stringbuffer,重拾java基础(十三):String姐妹StringBuffer、StringBuilder总结
  7. 二元相图软件_Materials Studio 领先的材料模拟软件
  8. SpringSecurity + JWT实现单点登录
  9. NKOJ2317 英语四六级考试
  10. [Bzoj1003][ZJOI2006]物流运输(spfa+dp)
  11. 程序员如何转型项目经理?
  12. Win11设置人离开后电脑自动锁屏教程
  13. php 不报notice错误,PHP新手NOTICE错误常见解决方法_PHP教程
  14. Shell--点名器脚本,实现点名去重。
  15. 微信社群怎么做?-螳螂SCRM系统社群营销轻松裂变
  16. APP Designer 制作简易英汉词典的回调函数书写
  17. 单片机软件设计架构(C语言)
  18. 不得不看的经典软件测试面试问题
  19. 怎么把qlv格式转成mp4?附裁剪qlv视频尺寸技巧
  20. python中的怎么打出来_如何在Python中打印上标?

热门文章

  1. php中ci框架分页,Codeigniter(CI)框架分页函数及相关知识
  2. kafka创建topic_ELK-基础系列(六)-ELK加入消息队列-Kafka部署
  3. linux 打开 protel文件格式,Protel快捷键大全
  4. java8中class怎么用_Java8中你可能不知道的一些地方之方法引用实战
  5. bash 脚本_实用的 bash 自定义脚本,快速提效工作流程
  6. 避免将 props 的值复制给 state!
  7. Hooks解决了什么问题?
  8. java list to byte_java – 将ArrayList转换为byte []
  9. 解读ES6 Promise
  10. 设备 esp32_低功耗ESP32手持式袖珍显示屏