文章目录
- 一、 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);
}