文章目录
介绍
SRT(Secure Reliable Transport)安全 可靠 传输协议 SRT协议基于UDT(UDP-based Data
Transfer)协议,继承了UDP开销低、速度快的优点,并结合了TCP的可靠性特性,为视频流媒体传输提供了全新的解决方案。
详细说明
https://github.com/Haivision/srt
特点
- 安全性:
- SRT支持AES加密,保障端到端的视频传输安全。可以选择AES-128或AES-256位加密,确保数据在传输过程中不被窃取或篡改。
- 握手过程支持出站连接,无需在防火墙中打开永久外部端口,维护公司安全策略。
- 可靠性:
- SRT使用ARQ(Automatic Repeat Request)技术和FEC(前向纠错)技术来确保数据的可靠传输。ARQ通过反馈循环来检测和纠正数据包的丢失和损坏,FEC则通过奇偶校验编码来恢复丢失的数据包。
- 支持多种流类型,包括多个并发流和不同的媒体流,如多个摄像机角度或可选音频轨道。
- 低延迟:
- SRT通过优化数据包大小和发送时间来降低传输延迟。其基于UDP的传输机制减少了协议开销,使得传输速度更快、延迟更低。
- 提供了灵活的流控制机制和带宽自适应功能,可以根据网络状况动态调整传输速率,确保最佳的传输性能。
- 灵活性:
- 支持点对点直接传输数据,也可借助网关或服务器实现点对多端数据传输。
- 支持多种传输模式,如UDP、TCP、HTTP等,用户可以根据需求选择合适的传输模式。
协议概述
协议常用URL格式
推流
srt://host:port?streamid=#!::r=live/test,m=publish
ffmpeg -re -stream_loop -1 -i "test.ts" -c copy -f mpegts 'srt://127.0.0.1:1234?streamid=#!::r=srtlive/test,m=publish'
SRT Stream ID 的格式
#!:::这是一个特殊的标记,表示 SRT 协议中的 Stream ID。streamid 参数通常使用 #!:: 作为前缀,但这不是强制性的。你可以直接使用数字或其他标识符 比如1234
srt://127.0.0.1:1234?streamid=1234:r=srtlive/test,m=publish’
r=:指定资源名称,用于标识流的来源。
m=publish:指定模式为 publish。
播放、拉流
ffplay -i 'srt://ip:port?streamid=#!::r=live/test,m=request'
SRT协议通过简单的握手、固定的延时量、ARQ(自动重传请求)、FEC(前向纠错)以及ACK、NACK等策略解决TCP协议在流媒体业务上存在的问题。
协议工作流程
核心流程包括
- 握手
客户端和服务器之间进行握手以建立连接。握手过程中,双方交换必要的信息,如版本号、身份验证信息等
- 能力协商
-双方协商传输能力,确定最佳的传输参数,如最大带宽、加密方式 是否使用FEC、ARQ、拥塞控制策略、传输包大小、最大重传次数等。
- 身份验证
非必须,证书交换:如果启用了安全模式,双方可能会交换证书以进行身份验证。 密钥交换:协商共享密钥,用于加密和解密数据
- 数据传输(数据分组 前向纠错 ARQ 拥塞控制 加密)
1 数据分组:数据被分割成较小的数据包(称为分组或帧),每个分组包含一部分原始数据。
2 前向纠错 (FEC):为了提高可靠性,SRT使用前向纠错技术,在每个数据分组中添加冗余信息。如果某些分组丢失,接收方可以利用冗余信息重建丢失的数据。
3 自动请求重传(ARQ):如果接收方检测到数据包丢失或损坏,可以请求发送方重传丢失的数据包。
4 拥塞控制:SRT实施拥塞控制机制,根据网络状况动态调整发送速率,以避免网络拥塞。
5 加密:SRT 支持端到端加密,确保数据在传输过程中的安全性。可以使用AES-128 或 AES-256 加密算法。
- 流量监控
- 速率控制:SRT 通过速率控制来调整发送速率,确保网络资源得到充分利用,同时避免过度使用带宽。 缓冲管理:SRT 使用缓冲区来存储数据包,以应对网络延迟和抖动
- 错误处理
丢包检测:SRT 可以检测数据包丢失,并通过 FEC 和 ARQ 机制进行修复。 错误检测:SRT 使用校验和等技术来检测数据包中的错误。
重传请求:如果检测到数据包丢失或损坏,接收方可以请求发送方重传丢失的数据包
- 关闭连接
SRT 协议中最常用的工作模式为“呼叫 - 监听”(Caller-Listener)模式,监听方(Listener)会持续监听本方的固定
UDP 端口,呼叫方(Caller)通过访问监听方的公网 IP 地址和该固定端口来建立 SRT 连接。 呼叫和监听的角色主要在 SRT
协议握手阶段起作用,无论是编码端还是解码端都可以担任呼叫者或监听者的角色。
协议包格式
SRT协议中包含两类数据包:数据包(Data Packet)和控制包(Control Packet)
数据包和控制包
他们通过SRT首部的最高位(标志位)来区分,0代表信息数据包,1代表控制数据包。控制数据包又包含了 握手(Handshake)、肯定应答(ACK)、否定应答(NAK)、对肯定应答的应答(ACKACK),保持连接(Keepalive)、关闭连接(Shutdown) 等多种类型
数据包
解析
- 最高字节 0 标识数据包 1 是控制包
- 数据包序列号:数据包序号的初始值在握手时确定,之后每发一个数据包,数据包序号就会加1;
- PP:占2位,数据包位置标志位。“10”代表第一个数据包,10”代表第一个数据包,“00”代表中间数据包,“01”代表最后一个数据包,“11”代表单个数据包。
- O:占1位,顺序标志位。是否按顺序发送 一般为1
- KK:占2位,加密密钥标志位。“00”代表不用加密,“01”代表偶数密钥,“10”代表奇数密钥。
- R:占1位,重传包标志位。
- 报文序号:报文序号从0开始,之后每发一个数据包,报文序号就会加1,报文序号前4个标志位功能如图3所示;数据包序列号对每个数据包进行独立计数,而报文序列号则是在报文范围内进行计数,比如一帧数据
- 时间戳:以建立时间为基准,单位为微秒;
- 目的地端口套接字ID:在多路复用的时候可以用来区分不同的SRT流。
SRT通过数据包序列号和报文序号,能明确那些数据包已经被发送出去,通过接收端发送过来的ACK控制指令就知道发送出去的哪些数据已经被成功接收了。如果出现丢包等情况,接收端通过NAK控制指令(如图5所示)通知发送端重传数据
控制包
- 最高字节 0 标识数据包 1 是控制包
- 控制类型
不同控制类型有不同结构
以ACK和NACK举例说明
ACK
ACK用于表示数据包到达确认。所有ACK包可能会携带额外信息,比如RTT、链路容量、传输速率。ACK控制包结构如下:
通过RTT时间和链路带宽等参数,SRT就可以估计整个网络状态,从而实现自适应的调整
NACK
Negative Acknowlegement(NAK)也是属于控制包,用于表示否定应答(没有收到数据包)。与TCP的NACK否定应答不同的是,SRT采用NAK表示否定应答。NAK控制包结构如下
开源协议栈
以下是一些常用的 SRT 协议栈:
libSRT
libSRT 是最著名的 SRT 协议实现之一,由 Haivision Systems Inc. 开发并维护。它是一个跨平台的库,
- 支持多种操作系统,包括 Windows、Linux 和 macOS。
- 支持 SRT 协议的所有核心功能,包括低延迟、高可靠性、安全性和拥塞控制。
- 提供了丰富的 API 接口,方便开发者集成 SRT 功能。
- 支持 RTMP、RTSP、HLS 等其他流媒体协议的转换
- https://github.com/Haivision/libSRT
FFmpeg
ffmpeg 已经集成了 SRT 支持,可以通过简单的命令行参数启用 SRT。此外GStreamer 也集成了srt
注意 ffmpeg 可以用来发送和接收 SRT 流,接受的srt流主要作为录制或者转码。如果要实现一个完整的 SRT 服务器,对众多客户端提供服务,需要其他专门的软件比如srs配合或者自己开发
推流
ffmpeg -re -i input.mp4 -vcodec copy -acodec copy -f srt srt://localhost:1234
接受srt保存为文件
ffmpeg -i srt://localhost:1234 -c copy output.mp4
VLC Media Player
VLC Media Player 是一个流行的多媒体播放器,支持多种编解码器和传输协议,包括 SRT,可以作为客户端或服务器,支持 SRT 流的播放和传输。
SRT Alliance
SRT Alliance 是一个社区驱动的组织,致力于推广和发展 SRT 协议。它不仅提供 SRT 协议的规范和技术支持,还汇集了多个支持 SRT 的开源项目
SRS
SRS (Simple-RTMP-Server): 这是一个高性能的实时流媒体服务器,支持 RTMP、HLS、HTTP-FLV、WebSocket-FLV、GB28181 和 SRT 等多种协议。
测试
确保ffmpeg支持srt 如果不支持需要重新编译
ffmpeg -protocols
使用ffmpeg+srs
- 使用ffmpeg将文件采用srt协议推到srt服务器
- 使用 srs接收 SRT 流,
- 并通过 FFplay 播放 SRT 流,
推流端
ffmpeg -re -i input.mp4 -vcodec copy -acodec copy -f srt srt://localhost:1234
参数解释
- -re:使 FFmpeg 以实时模式工作。
- -i input.mp4:指定输入文件为 input.mp4。
- -c copy:复制输入流而不进行重新编码。
- -f srt:指定输出格式为 SRT。
- srt://localhost:1234:指定目标地址为本地主机的端口 1234。
接收端
ffmpeg -hide_banner -re -i srt://:1234 -c copy -f matroska pipe:1
- -hide_banner:隐藏 FFmpeg 的启动横幅。
- -re:使 FFmpeg 以实时模式工作。
- -i srt://:1234:指定输入为 SRT 协议,监听端口 1234。
- -c copy:复制输入流而不进行重新编码。
- -f matroska pipe:1:将输出格式设置为 Matroska 并通过管道输出到标准输出(stdout)。
还可以转发到udp服务器
ffmpeg -hide_banner -re -i srt://:1234 -c copy -f mpegts udp://localhost:1235
- -f mpegts:指定输出格式为 MPEG-TS。
- udp://localhost:1235:指定 UDP 输出的地址和端口
播放端
注意 确保FFmpeg已经集成了libsrt库,否则ffplay无法播放srt链接
ffplay srt://localhost:1234
如果需要从标准输入读取使用下面命令
打开第三个终端窗口,执行以下命令
ffplay -
将标准输出的输出重定向到 FFplay:
ffmpeg -hide_banner -re -i srt://:1234 -c copy -f matroska pipe:1 | ffplay -
srs配置
https://ossrs.net/lts/zh-cn/docs/v5/doc/srt
srs中srt介绍
使用 libSRT
发送端
#include <srt/srt.h>
#include <string.h>
int main() {
SRTSOCKET sock;
char *url = "srt://localhost:1234";
sock = srt_socket(NULL, SRT_SO_REUSEADDR, 0);
if (sock == SRT_INVALID_SOCK) {
fprintf(stderr, "Failed to create socket\n");
return 1;
}
int ret = srt_connect(sock, url, strlen(url));
if (ret < 0) {
fprintf(stderr, "Failed to connect to %s\n", url);
return 1;
}
const char *data = "Hello, SRT!";
int len = strlen(data);
ret = srt_send(sock, (const unsigned char *)data, len, 0);
if (ret != len) {
fprintf(stderr, "Failed to send data\n");
return 1;
}
srt_close(sock);
return 0;
}
接收端
#include <srt/srt.h>
#include <string.h>
int main() {
SRTSOCKET sock;
char *url = "srt://*:1234";
sock = srt_socket(NULL, SRT_SO_REUSEADDR, 0);
if (sock == SRT_INVALID_SOCK) {
fprintf(stderr, "Failed to create socket\n");
return 1;
}
int ret = srt_bind(sock, url, strlen(url));
if (ret < 0) {
fprintf(stderr, "Failed to bind to %s\n", url);
return 1;
}
ret = srt_listen(sock, 5);
if (ret < 0) {
fprintf(stderr, "Failed to listen\n");
return 1;
}
SRTSOCKET client = srt_accept(sock, NULL, NULL);
if (client == SRT_INVALID_SOCK) {
fprintf(stderr, "Failed to accept connection\n");
return 1;
}
char buffer[1024];
int len = sizeof(buffer);
ret = srt_recv(client, (unsigned char *)buffer, len, 0);
if (ret > 0) {
buffer[ret] = '\0';
printf("Received: %s\n", buffer);
} else {
fprintf(stderr, "Failed to receive data\n");
return 1;
}
srt_close(client);
srt_close(sock);
return 0;
}