流媒体服务杂记
在做 WebRTC 的笔记之前,我想记录一下ICE。
git clone https://webrtc.googlesource.com/src
WebRTC 源码依赖 Abseil 库。
提取音频3A处理相关模块
https://www.cnblogs.com/mod109/p/5827918.html:这篇博客记录了提取编译2012年左右的 WebRTC 源码中相关的音频3A处理模块。
Amazon Kinesis Video Streams C WebRTC SDK 是一个为嵌入式设备设计的 WebRTC Client 库,值得注意的是:该库将 Opus 编解码器的采样率被限制为 48 kHz。这一限制是为了确保音频质量、兼容性和性能。如果需要处理其他采样率的音频数据,需要在传递给 Opus 编解码器之前进行采样率转换。
编译 libsrtp:
wget https://github.com/cisco/libsrtp/archive/refs/tags/v2.7.0.tar.gz -O libsrtp-2.7.0.tar.gz
# linux 下
cmake -G Ninja -B build -S . -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
-DENABLE_OPENSSL=true -DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/opt/aarch64-v01c01-linux-gnu-gcc/lib/libsrtp-2.7.0 \
-DOPENSSL_ROOT_DIR=/opt/aarch64-v01c01-linux-gnu-gcc/lib/openssl-3.5.0
# windows 下
cmake -G Ninja -B build -S . -DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_BUILD_TYPE=Debug -DLIBSRTP_TEST_APPS=false -DBUILD_SHARED_LIBS=true -DCMAKE_INSTALL_PREFIX="E:/Projects/Libraries/libsrtp-2.6.0_msvc2022_64bit_debug" -DENABLE_OPENSSL=true -DOPENSSL_ROOT_DIR="D:/Qt/Tools/OpenSSLv3/Win_x64"
cmake --build build --target all
cmake --install build
编译 libwebsockets:
# linux交叉编译 需要先屏蔽pkg-config找到主机的ssl库
export PKG_CONFIG=/opt/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/lib/openssl-3.3.1/lib/pkgconfig
# Windows下先将 lib/core-net/client/connect3.c 文件编码转换为 UTF-8 with BOM
cmake -G Ninja -B build -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_INSTALL_PREFIX="E:/Projects/Libraries/libwebsockets-4.3.3_msvc2022_64bit_debug" -DLWS_WITHOUT_TESTAPPS=true -DOPENSSL_ROOT_DIR="D:/Qt/Tools/OpenSSLv3/Win_x64"
cmake --build build --target all
cmake --install build
编译 usrsctp,参考 amazon-kinesis-video-streams-webrtc-sdk-c
的 CMake/Dependencies/libusrsctp-CMakeLists.txt
文件:
首先注释 usrsctplib\CMakeLists.txt
中的一行:
# set_target_properties(usrsctp PROPERTIES IMPORT_SUFFIX "_import.lib")
然后编译:
cmake -G Ninja -B build -S . -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
-DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON -DCMAKE_BUILD_TYPE=Release \
-Dsctp_build_shared_lib=true -Dsctp_build_programs=false \
-DCMAKE_INSTALL_PREFIX="/opt/aarch64-v01c01-linux-gnu-gcc/lib/usrsctp-0.9.5.0"
cmake --build build --target all
cmake --install build
下载 amazon-kinesis-video-streams-webrtc-sdk-c
源代码:
git clone --recursive https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c.git
修改 CMakeLists.txt
文件:
# 将 find_library(SRTP_LIBRARIES srtp2 REQUIRED PATHS ${OPEN_SRC_INSTALL_PREFIX}) 修改为:
set(LIBSRTP_ROOT E:/Projects/Libraries/libsrtp-2.6.0_msvc2022_64bit_debug)
set(LIBSRTP_INCLUDE_DIRS ${LIBSRTP_ROOT}/include)
find_library(SRTP_LIBRARIES srtp2 REQUIRED PATHS ${LIBSRTP_ROOT}/lib)
set(LIBWEBSOCKETS_ROOT E:/Projects/Libraries/libwebsockets-4.3.3_msvc2022_64bit_debug)
set(LIBWEBSOCKETS_INCLUDE_DIRS ${LIBWEBSOCKETS_ROOT}/include)
set(LIBWEBSOCKETS_LIBRARY_DIRS ${LIBWEBSOCKETS_ROOT}/lib)
#将
#find_library(
# Usrsctp
# NAMES ${USRSCTP_LIBNAME} usrsctp REQUIRED
# PATHS ${OPEN_SRC_INSTALL_PREFIX}/lib ${OPEN_SRC_INSTALL_PREFIX}/lib64)
#
#set(OPEN_SRC_INCLUDE_DIRS ${OPEN_SRC_INCLUDE_DIRS} ${LIBSRTP_INCLUDE_DIRS}
# ${CURL_INCLUDE_DIRS} ${LIBWEBSOCKETS_INCLUDE_DIRS})
#改为
set(USRSCTP_ROOT E:/Projects/Libraries/usrsctp-0.9.5.0_msvc2022_64bit_debug)
find_library(
Usrsctp
NAMES ${USRSCTP_LIBNAME} usrsctp REQUIRED
PATHS ${USRSCTP_ROOT}/lib)
set(OPEN_SRC_INCLUDE_DIRS ${OPEN_SRC_INCLUDE_DIRS} ${LIBSRTP_INCLUDE_DIRS}
${CURL_INCLUDE_DIRS} ${LIBWEBSOCKETS_INCLUDE_DIRS} ${USRSCTP_ROOT}/include)
# 交叉编译需要加入CMAKE_TOOLCHAIN_FILE
set(BUILD_ARGS
-DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain.cmake
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DUSE_OPENSSL=${USE_OPENSSL}
-DUSE_MBEDTLS=${USE_MBEDTLS}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS})
build_dependency(kvsCommonLws ${BUILD_ARGS})
# 交叉编译需要加入如下信息
list(APPEND EXTRA_DEPS dl)
set(OPENSSL_ROOT /opt/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/lib/openssl-3.3.1)
set(OPENSSL_SSL_LIBRARY ${OPENSSL_ROOT}/lib/libssl.a)
set(OPENSSL_CRYPTO_LIBRARY ${OPENSSL_ROOT}/lib/libcrypto.a)
然后开始构建:
# linux下交叉编译 需要先屏蔽pkg-config找到主机的ssl、libwebsockets库
export PKG_CONFIG_PATH=/opt/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/lib/libwebsockets-4.3.3/lib/pkgconfig:/opt/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/lib/openssl-3.3.1/lib/pkgconfig:$PKG_CONFIG_PATH
export OPENSSL_ROOT_DIR=/opt/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/lib/openssl-3.3.1
set OPENSSL_ROOT_DIR=D:/Qt/Tools/OpenSSLv3/Win_x64
cmake -G Ninja -B build -S . -DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="E:/Projects/Libraries/amazon-kinesis-video-streams-webrtc-sdk-c_msvc2022_64bit_debug" -DOPEN_SRC_INSTALL_PREFIX="E:/Projects/Libraries/amazon-kinesis-video-streams-webrtc-sdk-c_msvc2022_64bit_debug" -DBUILD_DEPENDENCIES=false -DBUILD_SAMPLE=false
# linux下交叉编译建议加上:
# -DBUILD_STATIC_LIBS=ON
cmake --build build --target all
cmake --install build
ZLMediaKit
ZLMediaKit 是一个基于 C++11 的流媒体服务框架。
编译
下载 ZLMediaKit 源代码:
git clone --recursive https://github.com/ZLMediaKit/ZLMediaKit.git
修改 webrtc/CMakeLists.txt
,自行设置 libSRTP、SCTP 路径 :
# find_package(SCTP QUIET)
set(SCTP_ROOT /opt/aarch64-v01c01-linux-gnu-gcc/lib/usrsctp-0.9.5.0)
set(SCTP_INCLUDE_DIRS ${SCTP_ROOT}/include)
set(SCTP_LIBRARIES ${SCTP_ROOT}/lib/libusrsctp.so)
set(SCTP_FOUND true)
使用 CMake 进行编译:
cd ZLMediaKit && mkdir build
# linux 下
apt install libssl-dev libsrtp2-dev libusrsctp-dev
cmake -G Ninja -B build -S . -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
-DCMAKE_INSTALL_PREFIX=/opt/aarch64-v01c01-linux-gnu-gcc/lib/ZLMediaKit \
-DOPENSSL_ROOT_DIR=/opt/aarch64-v01c01-linux-gnu-gcc/lib/openssl-3.5.0 \
-DSRTP_PREFIX=/opt/aarch64-v01c01-linux-gnu-gcc/lib/libsrtp-2.7.0 \
-DDISABLE_REPORT=ON \
-DCMAKE_BUILD_TYPE=Release
# windows 下
cmake -G Ninja -B build -S . -DENABLE_OPENSSL=true -DENABLE_WEBRTC=true -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="E:/Projects/Libraries/ZLMediaKit_msvc2022_64bit_debug" -DOPENSSL_ROOT_DIR="D:/Qt/Tools/OpenSSLv3/Win_x64"
cmake --build build --target all
cmake --install build
简单使用
修改 rtc.externIP
使得 WebRTC 客户端能够访问 ZLMediaKit。
[rtc]
datachannel_echo=1
externIP=192.168.3.3
然后运行
./MediaServer -s ./default.pem -c ./config.ini
FFmpeg使用推流至ZLMediaKit:
ffmpeg.exe -re -i "E:\test.mp4" -vcodec h264 -acodec aac -f rtsp -rtsp_transport tcp rtsp://127.0.0.1/live/test
ffplay.exe rtsp://127.0.0.1/live/test # 使用 ffplay 播放 rtsp 流
# 使用rtmp推流,测试 webrtc 比较友好
ffmpeg.exe -re -i "E:\test.mp4" -vcodec h264 -acodec aac -f flv rtmp://127.0.0.1/live/test
ffmpeg -re -i "E:\test.mp4" -vcodec h264 -acodec aac -f rtp_mpegts rtp://127.0.0.1:10000
ffplay.exe rtp://127.0.0.1:10000 # 使用 ffplay 播放 rtp 流
WebRTC 协议介绍
音视频数据通过 SRTP/SRTCP(基于 DTLS 加密的 RTP 协议)传输。
自定义应用数据通过 SCTP传输,由 DTLS 提供加密保障。
SCTP/DTLS
WebRTC 中的 SCTP(流控制传输协议)和 DTLS(数据报传输层安全协议)是支撑实时通信安全性与可靠性的两大核心协议。它们分别负责不同层面的功能,并通过协作实现端到端的高效数据传输。
DTLS 是 TLS(传输层安全协议)的 UDP 版本,用于在不可靠的 UDP 传输层上建立加密信道,它解决了 TLS 在 UDP 环境下无法处理丢包和乱序的问题。WebRTC 规范要求所有传输(包括音视频和自定义数据)必须通过 DTLS 加密,且只能在 HTTPS 或本地环境运行
SCTP 是专为 WebRTC 的 DataChannel
设计的协议,支持端到端的任意二进制数据传输(如文件、游戏指令等),特点包括:
- 多流机制:允许在单一连接内并行传输多个独立数据流,避免“队头阻塞”问题
- 可靠性控制:开发者可配置传输模式为可靠传输(类似 TCP)或低延迟的半可靠传输(类似 UDP)
- 有序交付:确保数据包按发送顺序到达接收端(可选配置)
ICE
交互式连接创建(Interactive Connectivity Establishment),一种综合性的NAT穿透技术。 交互式连接创建是由IETF的MMUSIC工作组开发出来的一种Framework,可集成各种NAT穿透技术,如STUN、TURN(Traversal Using Relay NAT,中继NAT实现的穿透)、RSIP(Realm Specific IP,特定域IP)等。该Framework可以让WebRTC客户端利用各种NAT穿透方式打穿远程的防火墙。
STUN
STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序)是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间创建UDP通信。
总而言之,STUN就是为了先NAT穿透提供的服务。多说一句,目前的NAT穿透,广泛使用的UDP方式,因此也叫UDP打洞。
TURN
TURN(全名Traversal Using Relay NAT),是一种数据传输协议(data-transfer protocol)。允许在TCP或UDP的连接跨越NAT或防火墙。 TURN是一个client-server协议。TURN的NAT穿透方法与STUN类似,都是通过获取应用层中的公有地址达到NAT穿透。但实现TURN client的终端必须在通信开始前与TURN server进行交互,并要求TURN server产生"relay port",也就是relayed-transport-address。这时TURN server会创建peer,即远程端点(remote endpoints),开始进行中继(relay)的动作,TURN client利用relay port将数据发送至peer,再由peer转传到另一方的TURN client。 TURN不仅提供了发现隐藏于NAT网络后的内网设备的能力,更提供了当对端无法进行NAT穿透时,再为其提供数据中继的能力。
所以本质上,ICE服务器与WebRTC是没有强关联的,它只是起到将WebRTC客户端能够发现对端的作用。
SDP
会话描述协议Session Description Protocol (SDP) 是一个描述多媒体连接内容的协议,例如分辨率,格式,编码,加密算法等。所以在数据传输时两端都能够理解彼此的数据。本质上,这些描述内容的元数据并不是媒体流本身。
从技术上讲,SDP 并不是一个真正的协议,而是一种数据格式,用于描述在设备之间共享媒体的连接。
Signal
信令(Signal)是配置、控制、以及结束用户间通信会话的过程。端到端通信(即P2P)需要信令来建立。
两端想要通信,主要需要三个信令步骤:
- 分享会话控制信息;
- 交换IP地址和端口等网络信息;
- 交换用户的编解码器以及媒体格式
那么为什么通信需要信令呢:
- 会话控制信息会控制端到端连接的所有建连、断连、以及发送信息;
- IP 以及端口信息用于找到用户网络层位置;
- 编解码器以及多媒体格式用于确定用户间建立的分辨率以及多媒体设置;
- 这些所有的设置都根据 SDP 协议(Session Description Protocal)来进行交换。
如果两个用户希望P2P通信,那两端之间则需要一个额外的服务器来交换初始数据设置 WebRTC 连接,这个服务器就叫做信令服务器。
WebRTC 建立连接
信令过程结束后,所有多媒体数据都会经过 RTCPeerConnection 端到端交换。
RTCPeerConnection 是浏览器 API 一个比较重要的对象,再建立连接时,有如下步骤:
-
创建 RTCPeerConnection 对象。
-
调用
RTCPeerConnection.addTransceiver()
指定当前 transceiver 是sendrecv
、sendonly
、recvonly
。对于对讲时使用两个 RTCPeerConnection 的情况,用于推流的 RTCPeerConnection 应该指定为
sendonly
,用于拉流的 RTCPeerConnection 应该指定为recvonly
:// 推流
peerConnection.addTransceiver("audio", { direction: "sendonly" });
peerConnection.addTransceiver("video", { direction: "sendonly" });
// 拉流
peerConnection.addTransceiver("audio", { direction: "recvonly" });
peerConnection.addTransceiver("video", { direction: "recvonly" }); -
如果需要推流,则呼叫者通过
navigator.mediaDevices.getUserMedia()
捕捉本地媒体,然后调用RTCPeerConnection.addTrack()
。// Navigator是一个表示浏览器的状态和身份的全局对象。
navigator.mediaDevices.getUserMedia({ audio: true, video: true, }).then(localStream => {
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream)
}
);
});如果需要拉流,则使用 track 回调:
let stream = new MediaStream();
peerConnection.ontrack = (event) => {
stream.addTrack(event.track);
};
// 然后可以将 stream 设置给 <video> 标签进行播放 -
调用
RTCPeerConnection.createOffer()
来创建一个提议 (offer)。 -
调用
RTCPeerConnection.setLocalDescription()
将提议 (Offer) 设置为本地描述 (即,连接的本地描述). -
通过信令服务器将提议 (offer) 传递至 本次呼叫的接受者。一般使用 WHIP/WHEP 或者流媒体服务器实现的私有信令交换方式。
-
受到应答(其中包括对端的 sdp 信息)后,调用
RTCPeerConnection.setRemoteDescription()
将应答设定为远程描述。如此,呼叫者已经获知连接双方的配置了。
libdatachannel
libdatachannel 是一个轻量级的 C/C++ 网络库,专注于实现 WebRTC 数据通道(Data Channels)、媒体传输(Media Transport)及 WebSocket 协议。
跨平台支持、轻量化设计、标准兼容性,完全兼容 W3C 和 IETF 的 WebRTC 标准,支持与 Firefox、Chromium 等浏览器进行实时点对点通信。
rtc::Track
类与WebRTC的MediaStreamTrack
功能类似,是WebRTC中表示单一类型媒体源的抽象单元。例如,摄像头采集的视频流或麦克风采集的音频流均通过独立的Track表示。每个Track对应一个“源”(如设备、文件或屏幕共享),并封装了原始媒体数据和控制接口。
rtc::H264RtpPacketizer
:将 H.264 编码的视频数据封装为 RTP 包。
rtc::RtcpSrReporter
:生成并发送 RTCP Sender Report (SR) 包,提供发送端统计信息。
rtc::RtcpNackResponder
:响应接收端发送的 NACK(否定确认) 请求,重传丢失的 RTP 包。