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 0x000000
    packet->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;

    //整个sps
    packet->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;

    //pps
    packet->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)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 安卓开发:Kivy、BeeWare、Flet、Flutter

kivy:https://github.com/kivy python-for-android :https://python-for-android.readthedocs.io/en/latest/ BeeWare:https://docs.beeware.org/en/latest/ Flet:https://github.com/flet-dev/flet 把 PySide6 移植到安卓上去&a…

如何使用网络测试仪构造特殊流量

为什么要仿真特殊流量 在现网中,网络流量时常伴随着突发,突发流量可能会造成网络的拥塞,从而产生丢包、抖动和时延,导致网络服务质量整体下降。面对宏观上的突发,通常采用在网络设备入向限速或者流量整形功能来消除突…

kotlin运行

1.使用android studio 由于我本身是做android的,android studio本身有内置kotlin的插件。但若只是想跑kotlin的程序,并不像和android程序绑在一起,可以创建一个kt文件,在里面写一个main函数,就可以直接运行kotlin程序…

MySQL第二次

作业要求: 作业代码实现: create database db_04 default charsetutf8mb4;use db_04;create table if not exists t_hero(id int primary key auto_increment,name varchar(20) not null unique,nickname varchar(50) not null unique,address varchar…

Vue面试之v-if与v-show的区别

Vue面试之v-if与v-show的区别 DOM渲染初始渲染性能切换开销标签配合源码实现 最近在整理一些前端面试中经常被问到的问题,分为vue相关、react相关、js相关、react相关等等专题,可持续关注后续内容,会不断进行整理~ 作为Vue中两种条件性渲染元…

Python 最新版本 3.12.1 环境配置(windows)

文章目录 python 3.12.1环境安装3.12.1 网盘下载3.12.1 官网下载 python 安装完成测试第一个 python 程序Hello Python python 3.12.1环境安装 3.12.1 网盘下载 python 3.12.1 百度网盘地址:https://pan.baidu.com/s/1SAcH_uH0T3DiERn6AZeQlg?pwd4242 提取码&a…

跟着cherno手搓游戏引擎【4】窗口抽象、GLFW配置、窗口事件

引入GLFW: 在vendor里创建GLFW文件夹: 在github上下载,把包下载到GLFW包下。 GitHub - TheCherno/glfw: A multi-platform library for OpenGL, OpenGL ES, Vulkan, window and input修改SRC/premake5.lua的配置:12、13、15、36…

阿里云服务器ECS介绍_高性能云服务器_为了无法计算的价值

阿里云高性能云服务器60%单实例最大性能提升,35Gbps内网带宽,网络增强&通用型云服务器、本地SSD型云服务器、大数据型云服务器、GPU异构型云服务器,阿里云百科aliyunbaike.com分享阿里云高性能云服务器: 阿里云高性能云服务器…

修改权限控制(chmod命令、chown命令)

1.chmod命令 功能:修改文件、文件夹权限(注意,只有文件、文件夹的所属用户或root用户可以修改) 语法:chmod [-R] 权限 参数 权限,要设置的权限,比如755,表示:rwxr-xr-x…

Python入门-面向对象

1.类和对象 是不是很熟悉?和Java一样,在Python中,都可以把万物看成(封装成)对象。它俩都是面向对象编程 1.1 查看对象数据类型 a 10 b 9.8 c helloprint(type(a)) print(type(b)) print(type(c))运行结果: D:\Python_Home\v…

自定义数据实现SA3D

SA3D:Segment Anything in 3D with NeRFs 实现了3D目标分割 原理是利用SAM(segment anything) 模型和Nerf分割渲染3D目标, SAM只能分块,是没有语义标签的,如何做到语义连续? SA3D中用了self-prompt, 根据前一帧的mask…

基于Python的汽车信息爬取与可视化分析系统

介绍 这款汽车信息网站是基于多项技术和框架设计的全面的汽车信息展示及查询系统。其中,采用了Python Django框架和Scrapy爬虫技术实现数据的抓取和处理,结合MySQL数据库进行数据存储和管理,利用Vue3、Element-Plus、ECharts以及Pinia等前端…

MFC为资源对话框添加消息处理函数和初始化控件

现在我VC6新建了一个对话框工程;又在资源添加了一个新的对话框,并为新的对话框添加了名为CTestDlg的类; 在主对话框的cpp文件包含#include "TestDlg.h"; 在主对话框的cpp文件的OnInitDialog()成员函数中,添…

leetcode 2645. 构造有效字符串的最少插入数-python

题目: 给你一个字符串 word ,你可以向其中任何位置插入 “a”、“b” 或 “c” 任意次,返回使 word 有效 需要插入的最少字母数。 如果字符串可以由 “abc” 串联多次得到,则认为该字符串 有效 。 解题方法 1.先判断字符串是否…

快速排序的背后——深入理解时间复杂度

时间复杂度的概念衡量算法性能的重要标准,是算法设计和性能优化中的关键概念,对于编写高效、稳定和可扩展的程序至关重要。但是,初学者对于如何理解和应用时间复杂度则显得较为困难,本文以快速排序为例进一步加深对时间复杂度的理…

云服务器ECS_云主机_服务器托管_计算-阿里云

阿里云服务器ECS英文全程Elastic Compute Service,云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务,阿里云提供多种云服务器ECS实例规格,如经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等,阿里云百科aliyunbai…

Logstash配置详解

一、配置文件 Logstash配置文件位于Logstash安装目录下bin/logstash.conf 启动命令: logstash -f logstash.conf文件描述logstash.yml配置Logstash的yml。pipelines.yml包含在单个Logstash实例中运行多个管道的框架和说明。jvm.options配置Logstash的JVM,使用此文…

Unity图片导入趣事随笔

像这样的png格式的图片,直接导入unity时unity会把没有像素的部分用黑色填充,并根据填充部分自动生成alpha通道。看起来alpha通道是不能手动覆盖的,即使在ps中手动添加一个alpha通道,并添加覆盖值。 导出后也会发现这没有任何意义&…

环信服务端下载消息文件---菜鸟教程

前言 在服务端,下载消息文件是一个重要的功能。它允许您从服务器端获取并保存聊天消息、文件等数据,以便在本地进行进一步的处理和分析。本指南将指导您完成环信服务端下载消息文件的步骤。 环信服务端下载消息文件是指在环信服务端上,通过调…

Self-Attention

前置知识:RNN,Attention机制 在一般任务的Encoder-Decoder框架中,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attent…