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

2023-03-27 21:29:27 浏览数 (1)

文章目录

  • 一、 x264 编码后的 H.264 数据帧
  • 二、 RTMP 协议中 关键帧 / 非关键帧 数据格式 说明
  • 三、 判定 H.264 帧数据分隔符
  • 四、 初始化 RTMPPacket
  • 五、 设置包头数据
  • 六、 设置 H.264 数据帧数据
  • 七、 设置其它数据
  • 八、 RTMPDump 封装视频帧数据代码示例

一、 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);
}

0 人点赞