前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android RTMP】RTMPDump 封装 RTMPPacket 数据包 ( 关键帧数据格式 | 非关键帧数据格式 | x264 编码后的数据处理 | 封装 H.264 视频数据帧 )

【Android RTMP】RTMPDump 封装 RTMPPacket 数据包 ( 关键帧数据格式 | 非关键帧数据格式 | x264 编码后的数据处理 | 封装 H.264 视频数据帧 )

作者头像
韩曙亮
发布2023-03-27 21:29:27
6300
发布2023-03-27 21:29:27
举报
文章被收录于专栏:韩曙亮的移动开发专栏

文章目录

一、 x264 编码后的 H.264 数据帧


1 . x264 编码操作 : 调用 x264 库的 x264_encoder_encode 方法 , 将图像数据编码成 H.264 数据帧后 ;

① 编码后的数据 : 编码后的 H.264 数据保存在 pp_nal[i].p_payload 中 ;

② 编码后的数据长度 : 编码的 H.264 数据长度为 pp_nal[i].i_payload ;

2 . 数据间隔 :

① 数据间隔分类 : pp_nal[i].p_payload 数据时编码后的数据, 前四位默认是 00 00 00 01 , 或 00 00 01 ;

② 数据间隔处理 : 这个数据间隔在封装 RTMPPacket 数据包时 , 是不需要的 , 这些数据需要剔除 ;

③ 剔除数据间隔方法 : 首先计算数据时 , 要将数据大小 pp_nal[i].i_payload 减去间隔长度 , 另外数据取值时 , 需要越过 3 / 4 位数据间隔再取值 ;

代码语言:javascript
复制
// 4 字节分隔符是 x264 编码后生成的 H.264 数据中的数据, 这里需要剔除该数据
spsLen = pp_nal[i].i_payload - 4;
// 拷贝 H.264 数据时, 需要越过 4 字节 间隔数据
memcpy(sps, pp_nal[i].p_payload + 4, spsLen);

二、 RTMP 协议中 关键帧 / 非关键帧 数据格式 说明


1 . RTMP 协议中 H.264 数据帧格式 :

① 帧类型 : 1 字节, 关键帧 17, 非关键帧 27 ;

② 包类型 : 1 字节, 1 表示数据帧 ( 关键帧 / 非关键帧 ), 0 表示 AVC 序列头数据 ;

③ 合成时间 : 3 字节, 一般情况下设置 00 00 00 ;

④ 数据长度 : 4 字节, 即真实的数据帧画面的数据大小 ;

2 . 计算出数据帧的个数 : 上述 帧类型 , 包类型 , 合成时间 , 数据长度 , 总共有 9 字节 , 再加上实际的 H.264 数据帧长度 , 即最终打包的 RTMPPacket 数据帧大小 ;

代码语言:javascript
复制
int rtmpPackagesize = 9 + payload;

三、 判定 H.264 帧数据分隔符


1 . 不同数据帧的分隔符描述 :

① AVC 序列头 : 如果是 SPS PPS 数据帧 , 可以判定分隔符就是 00 00 00 01 四字节 ;

② H.264 视频帧 : 对于视频数据帧 , 不确定当前的 H.264 数据的分隔符是 00 00 00 01 还是 00 00 01 , 需要开发者进行判定 ;

2 . 判定方法 : 根据 第 2 位 的值判定 ;

① 四位分隔符判定 : 如果 第 2 位 值为 01, 说明分隔符是 00 00 01 ;

② 三位分隔符判定 : 如果 第 2 位 值为 00, 说明分隔符是 00 00 00 01 ;

3 . 分割符处理方法 :

① 数据大小处理 : 数据大小计算时 , 减去分隔符长度 , 3 或 4 ;

② 数据指针处理 : 数据取出时 , 跳过你分隔符数据 ;

4 . 分隔符处理代码 :

代码语言:javascript
复制
    // 判定分隔符是 00 00 00 01 还是 00 00 01
    // 根据 第 2 位 的值判定
    // 如果 第 2 位 值为 01, 说明分隔符是 00 00 01
    // 如果 第 2 位 值为 00, 说明分隔符是 00 00 00 01
    if (p_payload[2] == 0x00){
        // 识别出分隔符是 00 00 00 01
        // 要将 x264 编码出的数据个数减去 4, 只统计实际的数据帧个数
        payload -= 4;
        // 从 x264 编码后的数据向外拿数据时, 越过开始的 00 00 00 01 数据
        p_payload += 4;
    } else if(p_payload[2] == 0x01){
        // 识别出分隔符是 00 00 01
        // 要将 x264 编码出的数据个数减去 3, 只统计实际的数据帧个数
        payload -= 3;
        // 从 x264 编码后的数据向外拿数据时, 越过开始的 00 00 01 数据
        p_payload += 3;
    }

四、 初始化 RTMPPacket


调用 RTMPPacket_Alloc 方法 , 为 RTMP 数据包分配内存 , 之后调用 RTMPPacket_Reset 方法重置 RTMP 数据包 ;

代码语言:javascript
复制
    // 为 RTMP 数据包分配内存
    RTMPPacket_Alloc(rtmpPacket, rtmpPackagesize);
    // 重置 RTMP 数据包
    RTMPPacket_Reset(rtmpPacket);

五、 设置包头数据


包头数据设置 :

① 帧类型设置 : 如果是关键帧 , 设置 17 , 如果是非关键帧 , 设置 27 ; 这里需要判断该 H.264 视频帧是关键帧还是非关键帧 ;

② 包类型设置 : 01 是数据帧, 00 是 AVC 序列头封装 SPS PPS 数据 ;

③ 合成时间戳 : 默认设置 00 00 00 ;

④ 设置数据长度 : 位运算计算 4 字节中每一位的值 , 然后给四个字节数据赋值 ;

代码语言:javascript
复制
    // 设置帧类型, 非关键帧类型 27, 关键帧类型 17
    rtmpPacket->m_body[0] = 0x27;
    if (type == NAL_SLICE_IDR) {
        rtmpPacket->m_body[0] = 0x17;
    }

    // 设置包类型, 01 是数据帧, 00 是 AVC 序列头封装 SPS PPS 数据
    rtmpPacket->m_body[1] = 0x01;
    // 合成时间戳, AVC 数据直接赋值 00 00 00
    rtmpPacket->m_body[2] = 0x00;
    rtmpPacket->m_body[3] = 0x00;
    rtmpPacket->m_body[4] = 0x00;

    // 数据长度, 需要使用 4 位表示
    rtmpPacket->m_body[5] = (payload >> 24) & 0xFF;
    rtmpPacket->m_body[6] = (payload >> 16) & 0xFF;
    rtmpPacket->m_body[7] = (payload >> 8) & 0xFF;
    rtmpPacket->m_body[8] = (payload) & 0xFF;

六、 设置 H.264 数据帧数据


将 H.264 数据帧数据拷贝到 rtmpPacket->m_body[9] 对应的地址中 , 前面存放了 9 字节的包头数据 , 这里直接从索引 9 位置开始存放 H.264 视频帧数据 ;

代码语言:javascript
复制
    // H.264 数据帧数据
    memcpy(&rtmpPacket->m_body[9], p_payload, payload);

七、 设置其它数据


设置 RTMP 包类型 , RTMP 包长度 , RTMP 通道 , 时间戳 等信息 ;

代码语言:javascript
复制
    // 设置 RTMP 包类型, 视频类型数据
    rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
    // 设置 RTMP 包长度
    rtmpPacket->m_nBodySize = rtmpPackagesize;
    // 分配 RTMP 通道, 随意分配
    rtmpPacket->m_nChannel = 10;
    // 设置绝对时间, 对于 SPS PPS 赋值 0 即可
    rtmpPacket->m_hasAbsTimestamp = 0;
    // 设置头类型, 随意设置一个
    rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;

八、 RTMPDump 封装视频帧数据代码示例


代码语言:javascript
复制
/**
 * 封装视频帧 , 关键帧 和 非关键帧
 * @param type  视频帧类型
 * @param payload   视频帧大小
 * @param p_payload 视频帧数据
 */
void VedioChannel::sendFrameToRtmpServer(int type, int payload, uint8_t *p_payload) {
    // 判定分隔符是 00 00 00 01 还是 00 00 01
    // 根据 第 2 位 的值判定
    // 如果 第 2 位 值为 01, 说明分隔符是 00 00 01
    // 如果 第 2 位 值为 00, 说明分隔符是 00 00 00 01
    if (p_payload[2] == 0x00){
        // 识别出分隔符是 00 00 00 01
        // 要将 x264 编码出的数据个数减去 4, 只统计实际的数据帧个数
        payload -= 4;
        // 从 x264 编码后的数据向外拿数据时, 越过开始的 00 00 00 01 数据
        p_payload += 4;
    } else if(p_payload[2] == 0x01){
        // 识别出分隔符是 00 00 01
        // 要将 x264 编码出的数据个数减去 3, 只统计实际的数据帧个数
        payload -= 3;
        // 从 x264 编码后的数据向外拿数据时, 越过开始的 00 00 01 数据
        p_payload += 3;
    }

    // 创建 RTMP 数据包
    RTMPPacket *rtmpPacket = new RTMPPacket;

    /*
        计算 RTMP 数据包大小

        帧类型 : 1 字节, 关键帧 17, 非关键帧 27
        包类型 : 1 字节, 1 表示数据帧 ( 关键帧 / 非关键帧 ), 0 表示 AVC 序列头
        合成时间 : 3 字节, 设置 00 00 00
        数据长度 : 4 字节, 赋值 payload 代表的数据长度

     */
    int rtmpPackagesize = 9 + payload;

    // 为 RTMP 数据包分配内存
    RTMPPacket_Alloc(rtmpPacket, rtmpPackagesize);
    // 重置 RTMP 数据包
    RTMPPacket_Reset(rtmpPacket);

    // 设置帧类型, 非关键帧类型 27, 关键帧类型 17
    rtmpPacket->m_body[0] = 0x27;
    if (type == NAL_SLICE_IDR) {
        rtmpPacket->m_body[0] = 0x17;
    }

    // 设置包类型, 01 是数据帧, 00 是 AVC 序列头封装 SPS PPS 数据
    rtmpPacket->m_body[1] = 0x01;
    // 合成时间戳, AVC 数据直接赋值 00 00 00
    rtmpPacket->m_body[2] = 0x00;
    rtmpPacket->m_body[3] = 0x00;
    rtmpPacket->m_body[4] = 0x00;

    // 数据长度, 需要使用 4 位表示
    rtmpPacket->m_body[5] = (payload >> 24) & 0xFF;
    rtmpPacket->m_body[6] = (payload >> 16) & 0xFF;
    rtmpPacket->m_body[7] = (payload >> 8) & 0xFF;
    rtmpPacket->m_body[8] = (payload) & 0xFF;

    // H.264 数据帧数据
    memcpy(&rtmpPacket->m_body[9], p_payload, payload);

    // 设置 RTMP 包类型, 视频类型数据
    rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
    // 设置 RTMP 包长度
    rtmpPacket->m_nBodySize = rtmpPackagesize;
    // 分配 RTMP 通道, 随意分配
    rtmpPacket->m_nChannel = 10;
    // 设置绝对时间, 对于 SPS PPS 赋值 0 即可
    rtmpPacket->m_hasAbsTimestamp = 0;
    // 设置头类型, 随意设置一个
    rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;

    // 调用回调接口, 将该封装好的 RTMPPacket 数据包放入 native-lib 类中的 线程安全队列中
    // 这是个 RTMPPacketPackUpCallBack 类型的函数指针
    rtmpPacketPackUpCallBack(rtmpPacket);
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 一、 x264 编码后的 H.264 数据帧
  • 二、 RTMP 协议中 关键帧 / 非关键帧 数据格式 说明
  • 三、 判定 H.264 帧数据分隔符
  • 四、 初始化 RTMPPacket
  • 五、 设置包头数据
  • 六、 设置 H.264 数据帧数据
  • 七、 设置其它数据
  • 八、 RTMPDump 封装视频帧数据代码示例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档