【Android RTMP】RTMPDump 封装 RTMPPacket 数据包 ( 封装 SPS / PPS 数据包 )

2023-03-27 21:28:53 浏览数 (2)

文章目录

  • 一、 基本封装数据格式说明
  • 二、 封装 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 . 标签头 :

11

个字节是标签头数据 , 存储有 标签类型 , 标签数据大小 , 时间戳 , 时间戳扩展位 , 流编号 等

11

字节信息 ;

代码语言: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 字节
代码语言:javascript复制
                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 数据
代码语言:javascript复制
                            68 E9 7B 2C
0x000001ba	:   8B
  • 后面的 00 00 00 39 是视频标签的总长度 , 这里在 RTMP 标签中可以不用封装 ;

2 . 计算整个 SPS 和 PPS 数据的大小 :

① 封装头 : 帧类型 , 数据类型 , 合成时间 , 版本信息 , 编码规则 , NALU 长度 , 总共有

10

字节 ;

② 封装 SPS 数据 : SPS 个数 , SPS 长度 , SPS 数据 , 分别有

1 2 spsLen

字节 ;

③ 封装 PPS 数据 : PPS 个数 , PPS 长度 , PPS 数据 , 分别有

1 2 ppsLen

字节 ;

代码语言:javascript复制
int rtmpPackagesize = 10   3   spsLen   3   ppsLen;

三、 封装头数据


向 RTMP 数据包中 , 封装 帧类型 , 数据类型 , 合成时间 , 版本信息 , 编码规则 , NALU 长度 , 总共有

10

字节 ;

代码语言: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;
}

0 人点赞