文章目录
一、 基本封装数据格式说明
二、 封装 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 中完整视频标签数据 ;
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 . 标签头 : 前 11 1111 个字节是标签头数据 , 存储有 标签类型 , 标签数据大小 , 时间戳 , 时间戳扩展位 , 流编号 等 11 1111 字节信息 ;
0x00000182 : 09 00 00 2E 00 00 00 00 0x0000018a : 00 00 00
3 . 标签数据 ( 重点 ) : 这就是本篇博客要封装的内容 , 基本上是封装一个格式一模一样的 RTMP 数据包 ,
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 . 数据示例 :
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 长度 , 总共有 10 1010 字节 ;
② 封装 SPS 数据 : SPS 个数 , SPS 长度 , SPS 数据 , 分别有 1 + 2 + s p s L e n 1 + 2 + spsLen1+2+spsLen 字节 ;
③ 封装 PPS 数据 : PPS 个数 , PPS 长度 , PPS 数据 , 分别有 1 + 2 + p p s L e n 1 + 2 + ppsLen1+2+ppsLen 字节 ;
int rtmpPackagesize = 10 + 3 + spsLen + 3 + ppsLen;
三、 封装头数据
向 RTMP 数据包中 , 封装 帧类型 , 数据类型 , 合成时间 , 版本信息 , 编码规则 , NALU 长度 , 总共有 10 1010 字节 ;
// 帧类型数据 : 分为两部分; // 前 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 数据 ;
// 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 数据 ;
// 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 通道 , 时间戳 等信息 ;
// 设置 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 数据封装代码示例
/** * 将 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; }