跳到主要内容

流媒体服务杂记

阅读量

0

阅读人次

0

在做 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.6.0.tar.gz -O libsrtp-2.6.0.tar.gz

# linux 下
cmake -G Ninja -B build -S . -DENABLE_OPENSSL=true -DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="/opt/Libraries/libsrtp-2.6.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-cCMake/Dependencies/libusrsctp-CMakeLists.txt 文件:

首先注释 usrsctplib\CMakeLists.txt 中的一行:

# set_target_properties(usrsctp PROPERTIES IMPORT_SUFFIX "_import.lib")

然后编译:

cmake -G Ninja -B build -S . -DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON -DCMAKE_BUILD_TYPE=Debug -Dsctp_build_shared_lib=true -Dsctp_build_programs=false -DCMAKE_INSTALL_PREFIX="E:/Projects/Libraries/usrsctp-0.9.5.0_msvc2022_64bit_debug"
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 路径 :

# 查找 srtp 是否安装
# find_package(SRTP QUIET)

set(libSRTP_ROOT /opt/Libraries/libsrtp-2.6.0)
set(SRTP_INCLUDE_DIRS ${libSRTP_ROOT}/include)
set(SRTP_LIBRARIES ${libSRTP_ROOT}/lib/libsrtp2.a)
set(SRTP_FOUND true)


# find_package(SCTP QUIET)
set(SCTP_ROOT E:/Projects/Libraries/usrsctp-0.9.5.0_msvc2022_64bit_debug)
set(SCTP_INCLUDE_DIRS ${SCTP_ROOT}/include)
set(SCTP_LIBRARIES ${SCTP_ROOT}/lib/usrsctp.lib)
set(SCTP_FOUND true)

使用 CMake 进行编译:

cd ZLMediaKit && mkdir build

# linux 下
apt install libssl-dev libsrtp2-dev libusrsctp-dev
cmake -G Ninja -B build -S . -DENABLE_OPENSSL=true -DENABLE_WEBRTC=true -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="/opt/Libraries/ZLMediaKit"

# 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。

config.ini
[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 协议介绍

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)需要信令来建立。

两端想要通信,主要需要三个信令步骤:

  1. 分享会话控制信息;
  2. 交换IP地址和端口等网络信息;
  3. 交换用户的编解码器以及媒体格式

那么为什么通信需要信令呢:

  1. 会话控制信息会控制端到端连接的所有建连、断连、以及发送信息;
  2. IP 以及端口信息用于找到用户网络层位置;
  3. 编解码器以及多媒体格式用于确定用户间建立的分辨率以及多媒体设置;
  4. 这些所有的设置都根据 SDP 协议(Session Description Protocal)来进行交换。

如果两个用户希望P2P通信,那两端之间则需要一个额外的服务器来交换初始数据设置 WebRTC 连接,这个服务器就叫做信令服务器。

WebRTC 建立连接

信令过程结束后,所有多媒体数据都会经过 RTCPeerConnection 端到端交换。

RTCPeerConnection 是浏览器 API 一个比较重要的对象,再建立连接时,有如下步骤:

  1. 创建 RTCPeerConnection 对象。

  2. 调用 RTCPeerConnection.addTransceiver() 指定当前 transceiver 是 sendrecvsendonlyrecvonly

    对于对讲时使用两个 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" });
  3. 如果需要推流,则呼叫者通过 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> 标签进行播放
  4. 调用 RTCPeerConnection.createOffer() 来创建一个提议 (offer)。

  5. 调用 RTCPeerConnection.setLocalDescription() 将提议 (Offer) 设置为本地描述 (即,连接的本地描述).

  6. 通过信令服务器将提议 (offer) 传递至 本次呼叫的接受者。一般使用 WHIP/WHEP 或者流媒体服务器实现的私有信令交换方式。

  7. 受到应答(其中包括对端的 sdp 信息)后,调用 RTCPeerConnection.setRemoteDescription() 将应答设定为远程描述。如此,呼叫者已经获知连接双方的配置了。