文章目录
- 一、 基本封装数据格式说明
- 二、 封装 SPS PPS 数据总体说明
- 三、 封装头数据
- 四、 封装 SPS 数据
- 五、 封装 PPS 数据
- 六、 设置 RTMP 数据包其它参数
- 七、 SPS PPS 数据封装代码示例
Android 直播推流流程 : 手机采集视频 / 音频数据 , 视频数据使用 H.264 编码 , 音频数据使用 AAC 编码 , 最后将音视频数据都打包到 RTMP 数据包中 , 使用 RTMP 协议上传到 RTMP 服务器中 ;
Android 端中主要完成手机端采集视频数据操作 , 并将视频数据传递给 JNI , 在 NDK 中使用 x264 将图像转为 H.264 格式的视频 , 最后将 H.264 格式的视频打包到 RTMP 数据包中 , 上传到 RTMP 服务器中 ;
本篇博客中介绍如下内容 , Java 层将 Camera 采集的 NV21 格式的数据传入 JNI 层 , 在 JNI 中使用 x264 编码器将 NV21 图像数据编码为 H.264 视频数据 ;
本篇博客中主要封装 AVC 序列头数据 , 将 帧类型 , AVC 数据类型 , 合成时间 , 版本信息 , 编码规格 , NALU 长度 , SPS 个数 , SPS 长度 , SPS 数据 , PPS 个数 , PPS 长度 , PPS 数据 , 封装到 RTMP 包中 ;
一、 基本封装数据格式说明
1 . 这是完整的视频标签数据内容 : 这是 FLV 中完整视频标签数据 ;
代码语言:javascript复制0x00000182 : 09 00 00 2E 00 00 00 00
0x0000018a : 00 00 00 17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
2 . 标签头 : 前
个字节是标签头数据 , 存储有 标签类型 , 标签数据大小 , 时间戳 , 时间戳扩展位 , 流编号 等
字节信息 ;
代码语言:javascript复制0x00000182 : 09 00 00 2E 00 00 00 00
0x0000018a : 00 00 00
3 . 标签数据 ( 重点 ) : 这就是本篇博客要封装的内容 , 基本上是封装一个格式一模一样的 RTMP 数据包 ,
代码语言:javascript复制 17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
参考博客 : 参考之前的两篇分析 RTMP 数据格式的博客 , 分析了与 RTMP 格式几乎一致的 FLV 视频数据格式 ;
- 【Android RTMP】RTMP 数据格式 ( FLV 视频格式分析 | 文件头 Header 分析 | 标签 Tag 分析 | 视频标签 Tag 数据分析 )
- 【Android RTMP】RTMP 数据格式 ( FLV 视频格式分析 | AVC 序列头格式解析 )
这两篇博客一定要 , 并且明白 FLV 视频标签数据格式 , 才能看懂今天写的 RTMP 数据包封装的内容 ;
二、 封装 SPS PPS 数据总体说明
1 . 数据示例 :
代码语言:javascript复制 17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
- 17 帧类型, 1 字节
- 00 数据类型, 1 字节
- 00 00 00 合成时间, 3 字节
- 01 版本信息, 1 字节
- 64 00 32 编码规则, 3 字节
- FF NALU 长度, 1 字节
- E1 SPS 个数, 1 字节
- 00 19 SPS 长度, 2 字节
截止到当前位置有 13 字节数据
- spsLen 字节数据, 这里是 25 字节
67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68
- 01 PPS 个数, 1 字节
- 00 05 PPS 长度, 2 字节
- ppsLen 字节的 PPS 数据
68 E9 7B 2C
0x000001ba : 8B
- 后面的 00 00 00 39 是视频标签的总长度 , 这里在 RTMP 标签中可以不用封装 ;
2 . 计算整个 SPS 和 PPS 数据的大小 :
① 封装头 : 帧类型 , 数据类型 , 合成时间 , 版本信息 , 编码规则 , NALU 长度 , 总共有
字节 ;
② 封装 SPS 数据 : SPS 个数 , SPS 长度 , SPS 数据 , 分别有
字节 ;
③ 封装 PPS 数据 : PPS 个数 , PPS 长度 , PPS 数据 , 分别有
字节 ;
代码语言:javascript复制int rtmpPackagesize = 10 3 spsLen 3 ppsLen;
三、 封装头数据
向 RTMP 数据包中 , 封装 帧类型 , 数据类型 , 合成时间 , 版本信息 , 编码规则 , NALU 长度 , 总共有
字节 ;
代码语言:javascript复制 // 帧类型数据 : 分为两部分;
// 前 4 位表示帧类型, 1 表示关键帧, 2 表示普通帧
// 后 4 位表示编码类型, 7 表示 AVC 视频编码
rtmpPacket->m_body[nextPosition ] = 0x17;
// 数据类型, 00 表示 AVC 序列头
rtmpPacket->m_body[nextPosition ] = 0x00;
// 合成时间, 一般设置 00 00 00
rtmpPacket->m_body[nextPosition ] = 0x00;
rtmpPacket->m_body[nextPosition ] = 0x00;
rtmpPacket->m_body[nextPosition ] = 0x00;
// 版本信息
rtmpPacket->m_body[nextPosition ] = 0x01;
// 编码规格
rtmpPacket->m_body[nextPosition ] = sps[1];
rtmpPacket->m_body[nextPosition ] = sps[2];
rtmpPacket->m_body[nextPosition ] = sps[3];
// NALU 长度
rtmpPacket->m_body[nextPosition ] = 0xFF;
四、 封装 SPS 数据
将 SPS 数据封装到 RTMP 数据包中 , 包含 SPS 个数 , SPS 长度 , SPS 数据 ;
代码语言:javascript复制 // SPS 个数
rtmpPacket->m_body[nextPosition ] = 0xE1;
// SPS 长度, 占 2 字节
// 设置长度的高位
rtmpPacket->m_body[nextPosition ] = (spsLen >> 8) & 0xFF;
// 设置长度的低位
rtmpPacket->m_body[nextPosition ] = spsLen & 0xFF;
// 拷贝 SPS 数据
// 将 SPS 数据拷贝到 rtmpPacket->m_body[nextPosition] 地址中
memcpy(&rtmpPacket->m_body[nextPosition], sps, spsLen);
// 累加 SPS 长度信息
nextPosition = spsLen;
五、 封装 PPS 数据
将 PPS 数据封装到 RTMP 数据包中 , 包含 PPS 个数 , PPS 长度 , PPS 数据 ;
代码语言:javascript复制 // PPS 个数
rtmpPacket->m_body[nextPosition ] = 0x01;
// PPS 数据的长度, 占 2 字节
// 设置长度的高位
rtmpPacket->m_body[nextPosition ] = (ppsLen >> 8) & 0xFF;
// 设置长度的低位
rtmpPacket->m_body[nextPosition ] = (ppsLen) & 0xFF;
// 拷贝 SPS 数据
memcpy(&rtmpPacket->m_body[nextPosition], pps, ppsLen);
六、 设置 RTMP 数据包其它参数
设置 RTMP 包类型 , RTMP 包长度 , RTMP 通道 , 时间戳 等信息 ;
代码语言:javascript复制 // 设置 RTMP 包类型, 视频类型数据
rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
// 设置 RTMP 包长度
rtmpPacket->m_nBodySize = rtmpPackagesize;
// 分配 RTMP 通道, 随意分配
rtmpPacket->m_nChannel = 10;
// 设置视频时间戳, 如果是 SPP PPS 数据, 没有时间戳
rtmpPacket->m_nTimeStamp = 0;
// 设置绝对时间, 对于 SPS PPS 赋值 0 即可
rtmpPacket->m_hasAbsTimestamp = 0;
// 设置头类型, 随意设置一个
rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
七、 SPS PPS 数据封装代码示例
代码语言:javascript复制/**
* 将 SPS / PPS 数据发送到 RTMP 服务器端
* @param sps SPS 数据
* @param pps PPS 数据
* @param spsLen SPS 长度
* @param ppsLen PPS 长度
*/
void VedioChannel::sendSpsPpsToRtmpServer(uint8_t *sps, uint8_t *pps, int spsLen, int ppsLen) {
// 创建 RTMP 数据包, 将数据都存入该 RTMP 数据包中
RTMPPacket *rtmpPacket = new RTMPPacket;
/*
计算整个 SPS 和 PPS 数据的大小
数据示例 :
17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
17 帧类型, 1 字节
00 数据类型, 1 字节
00 00 00 合成时间, 3 字节
01 版本信息, 1 字节
64 00 32 编码规则, 3 字节
FF NALU 长度, 1 字节
E1 SPS 个数, 1 字节
00 19 SPS 长度, 2 字节
截止到当前位置有 13 字节数据
spsLen 字节数据, 这里是 25 字节
67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68
01 PPS 个数, 1 字节
00 05 PPS 长度, 2 字节
ppsLen 字节的 PPS 数据
68 E9 7B 2C
0x000001ba : 8B
后面的 00 00 00 39 是视频标签的总长度
这里再 RTMP 标签中可以不用封装
*/
int rtmpPackagesize = 10 3 spsLen 3 ppsLen;
// 为 RTMP 数据包分配内存
RTMPPacket_Alloc(rtmpPacket, rtmpPackagesize);
// 记录下一个要写入数据的索引位置
int nextPosition = 0;
// 帧类型数据 : 分为两部分;
// 前 4 位表示帧类型, 1 表示关键帧, 2 表示普通帧
// 后 4 位表示编码类型, 7 表示 AVC 视频编码
rtmpPacket->m_body[nextPosition ] = 0x17;
// 数据类型, 00 表示 AVC 序列头
rtmpPacket->m_body[nextPosition ] = 0x00;
// 合成时间, 一般设置 00 00 00
rtmpPacket->m_body[nextPosition ] = 0x00;
rtmpPacket->m_body[nextPosition ] = 0x00;
rtmpPacket->m_body[nextPosition ] = 0x00;
// 版本信息
rtmpPacket->m_body[nextPosition ] = 0x01;
// 编码规格
rtmpPacket->m_body[nextPosition ] = sps[1];
rtmpPacket->m_body[nextPosition ] = sps[2];
rtmpPacket->m_body[nextPosition ] = sps[3];
// NALU 长度
rtmpPacket->m_body[nextPosition ] = 0xFF;
// SPS 个数
rtmpPacket->m_body[nextPosition ] = 0xE1;
// SPS 长度, 占 2 字节
// 设置长度的高位
rtmpPacket->m_body[nextPosition ] = (spsLen >> 8) & 0xFF;
// 设置长度的低位
rtmpPacket->m_body[nextPosition ] = spsLen & 0xFF;
// 拷贝 SPS 数据
// 将 SPS 数据拷贝到 rtmpPacket->m_body[nextPosition] 地址中
memcpy(&rtmpPacket->m_body[nextPosition], sps, spsLen);
// 累加 SPS 长度信息
nextPosition = spsLen;
// PPS 个数
rtmpPacket->m_body[nextPosition ] = 0x01;
// PPS 数据的长度, 占 2 字节
// 设置长度的高位
rtmpPacket->m_body[nextPosition ] = (ppsLen >> 8) & 0xFF;
// 设置长度的低位
rtmpPacket->m_body[nextPosition ] = (ppsLen) & 0xFF;
// 拷贝 SPS 数据
memcpy(&rtmpPacket->m_body[nextPosition], pps, ppsLen);
// 设置 RTMP 包类型, 视频类型数据
rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
// 设置 RTMP 包长度
rtmpPacket->m_nBodySize = rtmpPackagesize;
// 分配 RTMP 通道, 随意分配
rtmpPacket->m_nChannel = 10;
// 设置视频时间戳, 如果是 SPP PPS 数据, 没有时间戳
rtmpPacket->m_nTimeStamp = 0;
// 设置绝对时间, 对于 SPS PPS 赋值 0 即可
rtmpPacket->m_hasAbsTimestamp = 0;
// 设置头类型, 随意设置一个
rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
}