RTMP 视频数据封装

RTMP 协议

与HTTP(超文本传输协议)同样是一个基于TCP的Real Time Messaging Protocol(实时消息传输协议)。由Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的一种开放协议 。在国内被广泛的应用于直 播领域。HTTP默认端口为80,RTMP则为1935
我们通过阅读Adobe的协议规范,通过与服务器建立TCP通信,根据协议格式生成与解析数据即可使用RTMP进行直播。当然我们也可以借助一些实现了RTMP协议的开源库来完成这一过程。

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

RTMPDump

RTMPDump 是一个用来处理RTMP流媒体的开源工具包。它能够单独使用进行RTMP的通信,也可以集成到FFmpeg中通过FFmpeg接口来使用RTMPDump。

RTMPDump源码下载:http://rtmpdump.mplayerhq.hu/download/rtmpdump-2.3.tgz

交叉编译

在Android中可以直接借助NDK在JNI层调用RTMPDump来完成RTMP通信。但是首先必须得进行交叉编译。 RTMPDump源码结构如下:

在根目录下提供了一个 Makefile 与一些 .c 源文件。这里的源文件将会编译出一系列的可执行文件。然后我们需 要的并不是可执行文件,真正的对RTMP的实现都在librtmp子目录中。在这个子目录中同样包含了一个 Makefile 文件。通过阅读 Makefile 发现,它的源码并不多: OBJS=rtmp.o log.o amf.o hashswf.o parseurl.o 。因此我们 不进行预编译,即直接放入AS中借助 CMakeLists.txt 来进行编译。这么做可以让我们方便的对库本身进行调试或 修改(实际上我们确实会稍微修改这个库的源码)。

在AS中复制librtmp置于: src/main/cpp/librtmp ,并为其编写CMakeLists.txt

 #预编译宏
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO" ) 
#所有源文件放入 rtmp_source 变量
file(GLOB rtmp_source *.c)
#编译静态库
add_library(rtmp STATIC ${rtmp_source} )

在 app/CMakeLists.txt 中导入这个CMakeLists.txt

 cmake_minimum_required(VERSION 3.4.1) #导入其他目录cmakelist add_subdirectory(src/main/cpp/librtmp) add_library(XXX SHARED ...) #XXX需要链接rtmp库 target_link_libraries(XXX rtmp ...)

RTMP视频数据

RTMP视频流格式与FLV很相似,通过查看FLV的格式文档,就能够知道RTMP视频数据应该怎么拼接。

RTMP中的数据就是由FLV的TAG中的数据区构成。

FLV tags 结构

 

如上图,第一个字节0x09 表示此段数据为视频,数据大小为0x00,0x00,0x2F即47,时间戳为 0x00,0x00,0x00,时间戳扩展也为0x00。(第二行)流ID:0x00,0x00,0x00。接下来就是视频数据,通过此处的 数据大小字段得知,数据长为47字节。 则从0x17开始,一直到最后一行的0xC0,就是数据区域,而最后的 0x00,0x00,0x00,0x3A 即58,表示的是这个数据块除最后4个字节的总大小。本处为视频数据,那么从0x17 开始,数据内容则为下面的部分。

视频数据

*字段*

*占位*

*描述*

帧类型

4

1: 关键帧2: 普通帧 ......

编码ID

4

7: 高级视频编码 AVC ......

视频数据

n

AVC则需要下面的AVCVIDEOPACKET

AVCVIDEOPACKET

*字段*

*字节*

*描述*

类型

1

0:AVC 序列头(指导播放器如何解码)1:其他单元(其他NALU)

合成时间

3

对于AVC序列头,全为0

数据

n

类型不同,数据不同

视频数据中 0x17 则表示了1: 关键帧与7: 高级视频编码 AVC,如果是普通帧,则此数据为0x27。而类型为: 0x00表示这段数据为AVC序列头(avc sequence header)。最后三个字节为合成时间。而如果类型为AVC序列 头接下来的数据就是下面的内容:

AVC 序列头

在AVCVIDEOPACKET 中如果类型为0,则后续数据为:

*类型*

*字节*

*说明*

版本

1

0x01

编码规格

3

sps[1]+sps[2]+sps[3] (后面说明)

几个字节表示NALU 的长度

1

0xFF,包长为 (0xFF& 3)+ 1,也就是4字节表示

SPS个数

1

0xE1,个数为0xE1 & 0x1F 也就是1

SPS长度

2

整个sps的长度

sps的内容

n

整个sps

pps个数

1

0x01,不用计算就是1

pps长度

2

整个pps长度

pps内容

n

整个pps内容


0x01为版本,后续数据按照上表记录,最后四字节上面说过:为这个数据块除最后4个字节的总大小。其中 SPS与PPS是编码器在编码H.264视频时,在关键帧前会编码出的关于这个关键帧与需要参考该关键帧的B/P 帧如何解码的内容,如:宽、高等信息。

在AVCVIDEOPACKET 中如果类型为1(非AVC 序列头),则后续数据为:

*类型*

*字节*

*说明*

包长

由AVC序列头中定义

后续长度

数据

n

H.264数据

一般情况下,组装的RTMPPacket(RTMPDump中的结构体)为:

这里的sps与pps表示 AVC序列头

所以对于视频的数据封装,AVC序列头为:

    int i = 0;//固定头packet->m_body[i++] = 0x17;//类型packet->m_body[i++] = 0x00;//composition time 0x000000packet->m_body[i++] = 0x00;packet->m_body[i++] = 0x00;packet->m_body[i++] = 0x00;//版本packet->m_body[i++] = 0x01;//编码规格packet->m_body[i++] = sps[1];packet->m_body[i++] = sps[2];packet->m_body[i++] = sps[3];packet->m_body[i++] = 0xFF;//整个spspacket->m_body[i++] = 0xE1;//sps长度packet->m_body[i++] = (spslen >> 8) & 0xff;packet->m_body[i++] = spslen & 0xff;memcpy(&packet->m_body[i], sps, spslen);i += spslen;//ppspacket->m_body[i++] = 0x01;packet->m_body[i++] = (ppslen >> 8) & 0xff;packet->m_body[i++] = (ppslen) & 0xff;memcpy(&packet->m_body[i], pps, ppslen);packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;packet->m_nBodySize = bodySize;packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;//时间戳  sps与pps(不是图像) 没有时间戳packet->m_nTimeStamp = 0;// 使用相对时间packet->m_hasAbsTimestamp = 0;

而对于非AVC序列头,关键字与非关键字,只有第一个字节0x17与0x27的区别:

 packet->m_body[0] = 0x27;//关键帧if (type == NAL_SLICE_IDR) {packet->m_body[0] = 0x17;}//类型packet->m_body[1] = 0x01;//时间戳packet->m_body[2] = 0x00;packet->m_body[3] = 0x00;packet->m_body[4] = 0x00;//数据长度 int 4个字节 相当于把int转成4个字节的byte数组packet->m_body[5] = (i_payload >> 24) & 0xff;packet->m_body[6] = (i_payload >> 16) & 0xff;packet->m_body[7] = (i_payload >> 8) & 0xff;packet->m_body[8] = (i_payload) & 0xff;//图片数据memcpy(&packet->m_body[9], p_payload, i_payload);

H.264数据

H.264码流在网络中传输时实际是以NALU的形式进行传输的。NALU就是NAL UNIT,NAL单元。NAL全称Network Abstract Layer, 即网络抽象层。在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面 (VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头 信息,以保证数据适合各种信道和存储介质上的传输。我们平时的每帧数据就是一个NAL单元。

往RTMP包中填充的就是NAL数据,但不是直接将编码出来的数据填充进去。

一段包含了N个图像的H.264裸数据,每个NAL之间由:

  • 00 00 00 01 或者 00 00 01 进行分割。

在分割符之后的第一个字节,就是表示这个nal的类型。

  • 0x67:sps
  • 0x68: pps
  • 0x65: IDR

在将数据加入RTMPPacket的时候是需要去除分割符的。

所以完整的封包代码为:

 int bodysize = 9 + i_payload;RTMPPacket_Alloc(packet, bodysize);RTMPPacket_Reset(packet);
//    int type = payload[0] & 0x1f;packet->m_body[0] = 0x27;//关键帧if (type == NAL_SLICE_IDR) {packet->m_body[0] = 0x17;}//类型packet->m_body[1] = 0x01;//时间戳packet->m_body[2] = 0x00;packet->m_body[3] = 0x00;packet->m_body[4] = 0x00;//数据长度 int 4个字节 相当于把int转成4个字节的byte数组packet->m_body[5] = (i_payload >> 24) & 0xff;packet->m_body[6] = (i_payload >> 16) & 0xff;packet->m_body[7] = (i_payload >> 8) & 0xff;packet->m_body[8] = (i_payload) & 0xff;//图片数据memcpy(&packet->m_body[9], p_payload, i_payload);

NALU

NALU就是NAL UNIT,nal单元。NAL全称Network Abstract Layer, 即网络抽象层,H.264在网络上传输的结构。 一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice),而装载着这些片(slice)的载体,就是 NALU 了 。

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/345689.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第8章-第6节-Java中字符流的缓冲流

1、在说正题之前,先说一个小细节,不管是字节流还是字符流都要注意这个细节,具体看这篇博文:关于Java的IO流里面的方法read()的小细节 2、字符流的缓冲流: 1)、BufferedWriter 方法名说明void newLine()写…

专业课145+合肥工业大学833信号分析与处理考研经验合工大电子信息通信

今年专业课145也是考研科目中最满意的一门,其他基本相对平平,所以这里我总结一下自己的专业课合肥工业大学833信号分析与处理的复习经验。 我所用的教材是郑君里的《信号与系统》(第三版)和高西全、丁玉美的《数字信号处理》&…

安装rlwrap库出现问题

背景:oracle的sqlplus还是那么难用,不知道为什么不打包解决这个问题,留给用户,内核硬,就是猖狂。废话不多说。下载解压rlwrap-0.46.1.tar.gz;进入/tmp/database/rlwrap-0.46.1源码包,./configure checki…

Apache Doris (六十三): Spark Doris Connector - (3)-配置型及列映射关系

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. Spark 操作Doris配置项

数据结构学习之顺序栈应用的案例(有效的括号)

实例要求: 给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效; 有效字符串需满足的条件: 1、左括号必须用相同类型的右括号闭合; 2、左括号必须…

C++力扣题目98--验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 1: 输入…

AI Table应用程序接口表的格式说明和作用

AI Table 首先全拼不是AI人工智能表,而是Application Interface Table应用程序接口表。此表按照AUTOSAR的格式规范去定义,并且使用此Excel 表格生成相应的应用软件组件Arxml文件。下面就让我们按照AUTOSAR_EXP_AIUserGuide.pdf文档官方解释描述文件去看看…

Python--装饰器

在 Python 中,装饰器是一种特殊类型的函数,它们用于修改或增强其他函数或方法的行为。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。使用装饰器可以在不修改原函数代码的前提下,给函数添加新的…

数据结构实战:变位词侦测

文章目录 一、实战概述二、实战步骤(一)逐个比较法1、编写源程序2、代码解释说明(1)函数逻辑解释(2)主程序部分 3、运行程序,查看结果4、计算时间复杂度 (二)排序比较法1…

《TrollStore巨魔商店》TrollStore2安装使用教程支持IOS14.0-16.6.1

TrollStore(巨魔商店) 简单的说就相当于一个永久的免费证书,它可以给你的iPhone和iPad安装任何你想要安装的App软件,而且不需要越狱,不用担心证书签名过期的问题,不需要个人签名和企业签名。 支持的版本: TrollStore安装和使用教…

时间序列数据库选型: influxdb; netdiscover列出docker实例们的ip,docker管理工具lazydocker、scope

influxdb influxdb: 有收费版本、有开源版本 influxdb 安装、启动(docker) docker run -itd --name influxdb-dev -p 8086:8086 influxdb #influxdb的web客户端(端口8003)被去掉了 #8006是web-service端口#docker exec -it influxdb-dev bashinfluxdb 自带web界面 从后面的…

RK3568平台 温度传感器芯片SD5075

一.SD5075芯片简介 SD5075 是一款高准确度温度传感器芯片内含高精度测温 ADC,在-40C ~100C 范围内典型误差小于0.5C,在-55C~125C 范围内典型误差小于士1.0C。通过两线 IC/SMBus接口可以很方便与其他设备建立通信。设置 A2~A0 的地址线,可支持…