RTMP 是什么
RTMP 握手
- client: 客户端需要发 3 个包。C0,C1,C2
- server: 服务端也需要发同样 3 个包。 S0,S1,S2。
- 客户端在未接受到 S1 之前不能发送 C2 包
- 客户端在未接收到 S2 之前不能发送任何实际数据包
- 服务端在未接受到 C1 之前不能发送 S2.
- 服务端在未接收到 C2 之前不能发送任何实际数据包
- C0+C1
- S0+S1+S2
- C2
C0 && S0
- C0:客户端发送其所支持的 RTMP 版本号:3~31。一般都是写 3。
- S1:服务端返回其所支持的版本号。如果没有客户端的版本号,默认返回 3。
C1 && S1
- time: 发送时间戳,这个其实不是很重要,不过需要记住,不要超出 4B 的范围即可。
- zero: 保留值 0.
- random: 该字段长尾 1528B。主要内容就是随机值,不管你用什么产生都可以。它主要是为了保证此次握手的唯一性,和确定握手的对象。
C2 && S2
- time: 时间戳,同上,也不是很重要
- time2: C1/S1 发送的时间戳。
- random: S1/C1 发送的随机数。长度为 1528B。
Buffer 实操握手
Request Server 搭建
const client = new net.Socket(); client.connect({
port: 1935,
host: "6721.myqcloud.com"},
()=>{
console.log("connected");
}); client.on('data',(data)=>{
client.write('hello');
});
const Emitter = require('mitt')();
Buffer 操作
class C {
constructor() {
this.time;
this.random;
}
C0() {
let buf = Buffer.alloc(1);
buf[0] = 3;
return buf;
}
C1() {
let buf = Buffer.alloc(1536);
return buf;
}
/**
* write C2 package
* @param {Number} time the 4B Number of time
* @param {Buffer} random 1528 byte
*/
produceC2(){
let buf = Buffer.alloc(1536);
// leave empty value as origin time
buf.writeUInt32BE(this.time, 4);
this.random.copy(buf,8,0,1528); return buf;
}
get getC01(){
return Buffer.concat([this.C0(),this.C1()]);
}
get C2(){
return this.produceC2();
}
}
const Client = new net.Socket();
const RTMP_C = new C(); Client.connect({
port: 1935,
host: "6721.liveplay.myqcloud.com"
}, () => {
console.log('connected')
Client.write(RTMP_C.getC01); }); Client.on('data',res=>{
if(!res){
console.warn('received empty Buffer ' + res);
return;
}
// start to decode res package
if(!RTMP_C.S0 && res.length>0){
RTMP_C.S0 = res.readUInt8(0);
res = res.slice(1);
} if(!RTMP_C.S1 && res.length>=1536){
RTMP_C.time = res.readUInt32BE(0);
RTMP_C.random = res.slice(8,1536);
RTMP_C.S1 = true;
res = res.slice(1536);
console.log('send C2');
Client.write(RTMP_C.C2);
} if(!RTMP_C.S2 && res.length >= 1536){
RTMP_C.S2 = true;
res = res.slice(1536);
}
})
RTMP 基本架构
- Header: header 部分用来标识不同的 typeID,告诉客户端相应的 Message 类型。另外,还有个功效就是多路分发。
- Body: Body 内容就是相应发送的数据。这个根据不同的 typeID 来说,格式就完全不一样了。
Header
Basic Header
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
- CS ID: 0 ==> 整个 BH 长为 2B,其中可以表示的 Stream ID 数量为 64-319。例如:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 0 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- CS ID: 1 ==> 整个 BH 长为 3B。可以存储的 Stream ID 数量为 64-65599。例如:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 1 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- CS ID >2 ==> 整个 BH 长为 1B。可以存储的 Stream ID 数量为 3-63。例如:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
Message Header
- timestamp 是绝对时间戳。用来代表当前流编码。
- message length: 3B, 发送 message 的长度。
- type id: 1B
- stream id: 4B, 发送 message stream id 的值。是 little-endian 写入格式!
prevChunk.timeStamp += prevChunk.timeStampDelta || prevChunk.timeStamp;
Message Body
PCM
- message stream ID 为 0
- chunk stream ID 为 2
Set Chunk Size(1)
- 0: 只能设为 0 ,用来表示当前的 PCM 的类型。
- chunk size: 用来表示后面发送正式数据时的大小。范围为 1-16777215。
Abort Message(2)
- chunk stream id: 指定丢弃 chunk message 的 stream
Acknowledgement(3)
- sequence number[4B]: 大小为 4B
Window Acknowledgement Size(5)
Set Peer Bandwidth(6)
- 0: Hard,表示当前带宽需要和当前设置的 window size 匹配
- 1: Soft,将当前宽带设置为该信息定义的 window size,或者已经生效的 window size。主要取决于谁的 window size 更小
- 2: Dynamic,如果前一个 Limit Type 为 Hard 那么,继续使用 Hard 为基准,否则忽略该次协议信息。
UCM
- msg stream ID 为 0
- chunk stream ID 为 2
- Stream Begin: Event Type 为 0。它常常出现在,当客户端和服务端成功 connect 后发送。Event Data 为 4B,内容是已经可以正式用来传输数据的 Stream ID(实际没啥用)。
- Stream EOF: Event Type 为 1。它常常出现在,当音视频流已经全部传输完时。 Event Data 为 4B,用来表示已经发送完音视频流的 Stream ID(实际没啥用)。
- Set Buffer Length: Event Type 为 3。它主要是为了通知服务端,每毫秒用来接收流中 Buffer 的大小。Event Data 的前 4B 表示 stream ID,后面 4B 表示每毫秒 Buffer 的大小。通常为 3000ms
Command Msg
Video Msg
Audio Tag
音视频同步
- 以 Audio 为准,Video 同步 Audio
- 以 Video 为准,Audio 同步 Video
- 以外部时间戳为准,AV 同时同步
// known condition
video.timeStamp && video.perDuration && video.wholeDuration
audio.timeStamp && audio.perDuration // start
refDuration = video.timeStamp + video.wholeDuration
delta = refDuration - audio.timeStamp
audioCount = Math.round(delta/audio.perDuration);
audDemuxArr = this._tmpArr.splice(0,audioCount); // begin to demux
this._remuxVideo(vidDemuxArr);
this._remuxAudio(audDemuxArr);
Command Msg
- netConnection
- netStream
NetConnection
- Command Name[String]: 默认为 connect。表示信息名称
- Transaction ID[Number]: 默认为 1。
- Command Object: 键值对的形式存放相关信息。
- Optional: 可选值。一般没有
- app[String]: 服务端连接应用的名字。这个主要根据你的 RTMP 服务器设定来设置。比如:live。
- flashver[String]: Flash Player 的版本号。一般根据自己设备上的型号来确定即可。也可以设置为默认值:LNX 9,0,124,2。
- tcUrl[String]: 服务端的 URL 地址。简单来说,就是 protocol://host/path。比如:rtmp://6521.liveplay.myqcloud.com/live。
- fpad[Boolean]: 表示是否使用代理。一般为 false。
- audioCodecs[Number]: 客户端支持的音频解码。后续会介绍。默认可以设置为 4071
- videoCodecs[Number]: 客户端支持的视频解码。有自定义的标准。默认可以设置为 252
- videoFunction[Number]: 表明在服务端上调用那种特别的视频函数。默认可以设置为 1
- Command Name[String]: 为 _result 或者 _error。
- Transaction ID[Number]: 默认为 1。
- Command Object: 键值对的形式存放相关信息。
- Information[Object]: 键值对的形式,来描述相关的 response 信息。里面存在的字段有:level,code,description
- Procedure Name[String]: 调用处理程序的名字。
- Transaction ID[Number]: 如果想要有返回,则我们需要制定一个 id。否则为 0。
- Command Object: 键值对的形式存放相关信息。AMF0/3
- Optional: 可选值。一般没有
- Command Name[String]: 根据 call 中 Command Object 参数来决定的。
- Transaction ID[Number]: 如果想要有返回,则我们需要制定一个 id。否则为 0。
- Command Object: 键值对的形式存放相关信息。AMF0/3
- Response[Object]: 响应的结果值
- Procedure Name[String]: 调用处理程序的名字。
- Transaction ID[Number]: 自己制定一个。一般可以设为 2
- Command Object: 键值对的形式存放相关信息。AMF0/3
- Command Name[String]: 根据 call 中 Command Object 参数来决定的。
- Transaction ID[Number]: 如果想要有返回,则我们需要制定一个 id。否则为 0。
- Command Object: 键值对的形式存放相关信息。AMF0/3。一般为 Null
- Stream ID: 返回的 stream ID 值。
NetStream Msg
- Command Name[String]: 根据 call 中 Command Object 参数来决定的。
- Transaction ID[Number]: 默认为 0。也可以设置为其他值
- Command Object: 不需要该字段,在该命令中,默认设为 Null
- Stream Name[String]: 用来指定播放的视频流文件。因为,RTMP 天生是支持 FLV 的,所以针对 FLV 文件来说,并不需要加额外的标识,只需要写明文件名即可。比如:
StreamName: '6721_75994f92ce868a0cd3cc84600a97f75c'
- 不过,如果想要支持其它的文件,那么则需要额外的表示。当然,音频和视频需要不同的支持:
- 如果是播放音频文件,比如 mp3,那么则需要额外的前缀标识符-mp3。例如:mp3:6721_75994f9。
- 如果涉及到视频文件的话,不仅需要前缀,还需要后缀。比如播放的是 MP4 文件,则标识为:mp4:6721_75994f9.mp4。
- start[Number][seconds]: 这个字段其实有点意思。它可以分为 3 类来讲解:-2,-1,>=0。
- -2: 如果是该标识符,服务端会首先寻找是否有对应的 liveStream。没有的话,就找 record_stream。如果还没有的,这次请求会暂时挂起,直到获取到下一次 live_stream。
- -1: 只有 live_stream 才会播放。
- =0: 相当于就是 seek video。它会直接找到 record_stream,并且根据该字段的值来确定播放开始时间。如果没有的话,则播放 list 中的下一个 video。
- duration[Number][seconds]: 用来设置播放时长的。它里面也有几个参数需要讲解一下,-1,0,>0。
- -1: 会一直播放到 live_stream 或者 record_stream 结束。
- 0: 会播放一段一段的 frame。一般不用。
- 0: 会直接播放指定 duration 之内的流。如果超出,则会播放指定时间段内容的 record_stream。
- reset[Boolean]: 该字段没啥用,一般可以忽略。用来表示否是抛弃掉前面的 playlist。
视频技术详解:RTMP H5 直播流技术解析的更多相关文章
-
腾讯技术分享:GIF动图技术详解及手机QQ动态表情压缩技术实践
本文来自腾讯前端开发工程师“ wendygogogo”的技术分享,作者自评:“在Web前端摸爬滚打的码农一枚,对技术充满热情的菜鸟,致力为手Q的建设添砖加瓦.” 1.GIF格式的历史 GIF ( Gr ...
-
「视频直播技术详解」系列之七:直播云 SDK 性能测试模型
关于直播的技术文章不少,成体系的不多.我们将用七篇文章,更系统化地介绍当下大热的视频直播各环节的关键技术,帮助视频直播创业者们更全面.深入地了解视频直播技术,更好地技术选型. 本系列文章大纲如下: ...
-
手游录屏直播技术详解 | 直播 SDK 性能优化实践
在上期<直播推流端弱网优化策略 >中,我们介绍了直播推流端是如何优化的.本期,将介绍手游直播中录屏的实现方式. 直播经过一年左右的快速发展,衍生出越来越丰富的业务形式,也覆盖越来越广的应用 ...
-
Python开发技术详解(视频+源码+文档)
Python, 是一种面向对象.直译式计算机程序设计语言.Python语法简捷而清晰,具有丰富和强大的类库.它常被昵称为胶水语言,它能够很轻松的把用其他语言制作的各种模块(尤其是C/C++)轻松地联结 ...
-
CDN学习笔记二(技术详解)
一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精华放上网.公诸同 ...
-
CDN技术详解及实现原理
CDN技术详解 一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精 ...
-
如何将HLS延时缩短至4秒,HLS+技术详解
在直播应用中,RTMP 和 HLS 是两种较为成熟且广泛应用的流媒体协议,基本上可以覆盖所有客户端.RTMP 是互联网 TCP/IP 五层体系结构中应用层的协议,主要优势就是实时性高,基本可将直播延时 ...
-
《CDN技术详解》 - CDN知多少?
开发时间久了,就会接触到性能和并发方面的问题,如果说,在自己还是菜鸟的时候完全不用理会这种问题或者说有其他的高手去处理这类问题,那么,随着经验的丰富起来,自己必须要独立去处理了.或者,知道思路也行,毕 ...
-
P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
1.内容概述 P2P即点对点通信,或称为对等联网,与传统的服务器客户端模式(如下图"P2P结构模型"所示)有着明显的区别,在即时通讯方案中应用广泛(比如IM应用中的实时音视频通信. ...
随机推荐
-
Web应用多账号系统设计及微信扫码登录实现
Web应用多账号系统设计及微信扫码登录实现 1 前言概述 公司对功能测试,性能测试,安全测试等等都做了比较好的自动化后,急需要一个MIS系统来统一管理这些结果及报表. 此MIS系统特点如下: 仅内 ...
-
svn cleanup failed–previous operation has not finished 解决方法
今天svn遇到一个头疼的问题,最开始更新的时候失败了,因为有文件被锁住了.按照以往的操作,我对父目录进行clean up操作,但是clean up 操作也失败了! svn cleanup failed ...
-
Oracle CASE WHEN 用法介绍[Z]
Oracle CASE WHEN 用法介绍 1. CASE WHEN 表达式有两种形式 --简单Case函数 CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ...
-
asp.net core利用DI实现自定义用户系统,脱离ControllerBase.User
前言 很多时候其实我们并不需要asp.net core自带的那么复杂的用户系统,基于角色,各种概念,还得用EF Core,而且在web应用中都是把信息存储到cookie中进行通讯(我不喜欢放cooki ...
-
tcpip第三章
1,ip协议不可靠.无连接特性介绍 不可靠:计算机A往计算机B发送数据报1,若途径的路由器缓存已满,或者ttl(time to live 生存周期)到了,则路由器直接丢弃数据包1,并产生icmp数据包 ...
-
Jupyter Notebook启动不会自动打开浏览器,每次都要自己打开浏览器输入网址
今天在使用jupyter 时,已启动服务,但每次都需要手动去浏览器 输入网址才可以, 最好找了好久才解决了. 去cmd 命令窗口执行jupyter notebook --generate-config ...
-
C# rtsp 转码rtmp nginx踩下的坑
最近有一个项目, 进行步态识别的时候,同时需要显示摄像的实时画面.对于 Winform 显示画面,之前针对 rtmp 流的时候,是先写一个HTML 网页加载视频流,然后在IIS上发布,将地址直接赋值给 ...
-
List接口特有功能
List 有序的 有整数索引 允许重复使用 特有功能: void add(int index, E element) //指定位置添加元素 E get()int index) ...
-
Linux 进程管理 ps、top、pstree命令
ps命令:查看系统中正在运行的进程 ps 是用来静态地查看系统中正在运行的进程的命令.不过这个命令有些特殊,它的部分选项不能加入"-",比如命令"ps aux" ...
-
Spring 的@@Autowired 和 @Qualifier注释
@Autowired spring2.1中允许用户通过@Autowired注解对Bean的属性变量.属性Setter方法以及构造方法进行标注,配合AutowiredAnnotationBeanProc ...