Alby's blog

世上没有巧合,只有巧合的假象。

0%

mediasoup 3.9.10 worker 源码初步梳理

一、概述

Mediasoup 主要提供了 3 个库和 1 个 demo。

库名 说明
mediasoup 主要包含三部分。一是 worker 可执行程序,由 C++ 实现,是本系列分析的重点;二是 Node 库,由 TypeScript 实现;三是 Rust 库,和 Node 的主要不同在于它没有以进程方式而是以静态库方式使用 mediasoup-worker。
mediasoup-client Web 客户端库。TypeScript 实现。
libmediasoupclient Native 客户端库。C++ 实现。
mediasoup-demo 官方 Demo。
Examples 各种示例。

网络上对 mediasoup 的 Node.js 层——准确说是对官方的 mediasoup-demo 的源码分析比较多,对于 mediasoup-clientmediasoup-worker (之后简 worker) 等的源码详细分析相对较少。本人之前有将 GB28281 集成进 mediasoup 的想法并验证了可行性,以及使用 .Net 重新实现过 Node.js 层(含 mediasoup-client 和 mediasoup-demo),对 worker 的源码进行过比较粗略地浏览。最近基于想要弥补一些比较模糊的认知,并且 mediaoup 本身也在进化,故就再做了一次源码的梳理。

至于 mediasoup 是什么、能做什么、与其他 SFU 相较而言的优缺点、Demo 如何运行、为什么不用单一语言来实现等等讨论不是本系列关注的重点。

二、名词/概念

mediasoup 定义和抽象了一些名词和概念,为了方便描述,做整理如下表:

名词/概念 说明
Settings 设置。用于读取命令行配置、将配置输出到日志,以及运行时更新配置。另外用 线程本地存储 保存配置以确保每个线程都有一个 Settings 配置对象。当然,以 worker 进程方式运行不存在这个问题。设置的选项(option: getopt.h)具体包括:logLevellogTagsrtcMinPortrtcMaxPortdtlsCertificateFiledtlsPrivateKeyFile 这六项,分别表示日志等级、日志标签(如ice、dtls等,控制哪些类的日志才会传出)、最小和最大 rtc 端口,以及 dtls 证书文件和私钥的物理路径。这些参数都是可选的,如果未提供则会使用默认值或自动生成。
Logger 日志。有 debugwarnerrornone 四个等级。日志通过 ChannelSocket 传输到父进程,如果 ChannelSocket 尚未创建则会在标准输出中打印日志。mediasoup 给很多方法都加了 Trace 日志,打开 MS_LOG_TRACE 编译宏开关并且设置日志等级为 debug 则会输出。类似于 Settings,Logger 也用线程本地存储保存 Buffer 和 ChannelSocket 以确保每个线程都有一个对象。
ChannelSocket 通道 Socket。抽象了 worker 进程和父进程的通信。得益于 libuv,对于进程间通信开发者不用关心操作系统是用的 Linux/UNIX 的 UNIX Domain Socket 还是 Windows 的 IPC 等。而 mediasoup 的这层 ChannelSocket 抽象,也为后来支持的 以库的方式使用 worker 打下基础。ChannelSocket 是双向的,主要向父进程传输日志;接受父进程的如创建 TransportProducer 等指令请求;向父进程传输指令请求的执行结果;向父进程传输 Producer 暂停关闭、SCTP 发送 Buffer 已满之类的通知。所有消息都是基于文本的。
PayloadChannelSocket 负载通道 Socket。类似于 ChannelSocket,不同的是 PayloadChannelSocket 用于 DirectTransport 向父进程发送 RTP 或 RTCP 数据包等。消息是基于文本和二进制的。
Worker 工作者。注意这里并非指 worker 进程,但某种意义下可以指代 worker 进程。ChannelSocket 和 PayloadChannelSocket 只负责消息的接收和发送,而 Worker 是具体消息的处理入口。它根据消息的类别进行处理,对于自身可以负责的请求比如获取资源使用率、创建及关闭 Router、dump 获取本 Worker 下的 Router 的 Id 集合等则直接处理,其余的就交给 Router 来处理。遵循谁创建谁销毁的原则,Router 的销毁工作也由 Worker 负责。
Router 路由。Router 是比较重要的概念,不准确地说可以将 Router 和房间对应起来。其保存了 Transport、Producer、Consumer、Producer下对应的 Consumer 以及 DataProducerDataConsumer 相关的集合。如上文所述,Worker 将其处理不了的工作交给 Router,Router 能够处创建和关闭 Transport 以及和 RtpObserver 相关的几个操作请求,其余的就交给 Transport 处理。另外,Router 只负责 Transport 和 RtpObserver 的销毁工作,对于 Producer、Consumer 等则由创建这些对象的 Transport 负责。
Transport 传输通道。Transport 是抽象类,具体类包括:WebRtcTransportPlainTransportDirectTransportPipeTransport
WebRtcTransport WebRtc 传输通道。WebRtcTransport 包含 Socket 服务端,将接收到的数据包尝试转换为 StunPacketRtcp PacketRtpPacketDtls 数据 中的一种。StunPacket 由 IceServer 处理,Rtcp Packet 会经过处理后发送给 Consumer, RtpPacket 由相应的 Producer 处理,Dtls 数据当然是先解密再处理;也会通过 Sokcet 将数据发送到本 Transport 下对应的 Consumer 中去。
PlainTransport Plain 传输通道。类似于 WebRtcTransport,不同的是不会收到 StunPacket 。
PipeTransport Pipe 传输通道。用于跨 Router/worker Rtp 包传输。
DirectTransport Direct 传输通道。DirectTransport 不包含 Socket 服务端,将接收到的数据直接通过 PayloadChannel 发送给 Consumer。
Producer 生产者。类型有四类:SIMPLE、SIMULCAST、SVC 和 PIPE。
Consumer 消费者。Consumer 是抽象类,具体类包括:SimpleConsumerSimulcastConsumerSvcConsumerPipeConsumer。如果是 PipeTranspor 进行消费,则 type 为 “pipe”,否则为 Producer 的 type。
SimpleConsumer 单体消费者。
SimulcastConsumer Simulcast 消费者。
SvcConsumer Svc 消费者。
PipeConsumer Pipe 消费者。
DataProducer Data 生产者。类型有两类:SCTP 和 DIRECT。只有传递 enableSctp 为 true 的参数才能创建类型为 SCTP 的 DataProducer;只能在 DirectTransport 之上创建类型为 DIRECT 的 DataProducer。
DataConsumer Data 消费者。只有传递 enableSctp 为 true 的参数才能创建类型为 SCTP 的 DataProducer;只能在 DirectTransport 之上创建类型为 DIRECT 的 DataConsumer。
RtpObserver Rtp 观察者。RtpObserver 是抽象类,具体类包括:AudioLevelObserverActiveSpeakerObserver
AudioLevelObserver 音量观察者。收到音频 Rtp 包后累计音量。比如每1秒间隔计算触发一次计算,如果对应 Producer 收集了 10 个及以上的包则计算音频的平均值,如果值大于默认 -80dB 则将 Producder 的 Id 收集起来,最多收集指定参数 maxEntries 那么多个。如果存在符合条件的 Producer 则发出 volumes 事件,否则发出 silence 事件。事件会发送给父进程,而父进程会发送给相应的客户端。
ActiveSpeakerObserver 说话人观察者。ActiveSpeakerObserver 是 3.8.0 新增的。具体算法不算太简单,大致看并不是谁说话就算,而是计算出会议中说话人中谁是主导者。如果存在符合条件的唯一 Producer 则发出 dominantspeaker 事件。事件会发送给父进程,而父进程会发送给相应的客户端。

父对象通常会监听子对象的相关事件,比如 Worker 会监听 ChannelSocket 和 PayloadChannelSocket 的相关事件(Router没有监听器),Router 会监听 Transport 的相关事件, Transport 会监听 Producer 、 Consumer 等的相关事件。
如果要扩展其他协议,比如支持 GB28181 的 PS 流,可以创建自定义 Transport 。
如果要集成一些 AI 功能,比如目标识别,手势识别,语音指令识别等,可以创建自定义 RtpObserver 。

三、ChannelSocket 消息类型

ChannelRequest::MethodId 枚举定义了各种消息。其命名有一定规律,除 *.closeRtpObServer 相关的消息外,第一个下划线前的第一个单词通常标明了消息由谁来处理。比如 TRANSPORT_PRODUCE,表示 Transport 需要处理 Produce 消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// File: worker/src/Channel/ChannelRequest.cpp
absl::flat_hash_map<std::string, ChannelRequest::MethodId> ChannelRequest::string2MethodId =
{
// 关闭 Worker 。通常发生在 worker 进程被关闭时,由 Worker 的析构函数触发。
{ "worker.close", ChannelRequest::MethodId::WORKER_CLOSE },
// 获取 Worker 转储。返回值包含 worker 进程 Id 和本 Worker 下的 Router 的 routerIds 集合。
{ "worker.dump", ChannelRequest::MethodId::WORKER_DUMP },
// 获取 Worker 资源使用率。结构见 http://docs.libuv.org/en/v1.x/misc.html#c.uv_rusage_t 或 mediasoup-client 库。
{ "worker.getResourceUsage", ChannelRequest::MethodId::WORKER_GET_RESOURCE_USAGE },
// 更新设置。具体是更新日志等级(logLevel)和日志标签(logTags)。返回值无具体内容。
{ "worker.updateSettings", ChannelRequest::MethodId::WORKER_UPDATE_SETTINGS },
// 创建 Router。请求参数的 routerId 有调用方提供。返回值无具体内容。
{ "worker.createRouter", ChannelRequest::MethodId::WORKER_CREATE_ROUTER },
// 关闭 Router。该消息由 Worker 处理。返回值无具体内容。
{ "router.close", ChannelRequest::MethodId::ROUTER_CLOSE },
// 获取 Router 转储。返回值包含本 Router 的 id、transportIds、rtpObserverIds、mapProducerIdConsumerIds、mapConsumerIdProducerId、mapProducerIdObserverIds、mapDataProducerIdDataConsumerIds 和 mapDataConsumerIdDataProducerId。
{ "router.dump", ChannelRequest::MethodId::ROUTER_DUMP },
// 创建 WebRtcTransport。请求参数主要主要有监听 IP 和 Port(旧版可能无法指定端口而只能使用允许范围内的随机端口);是否启用 UDP 和(或) TCP;是否首选 UDP 和(或) TCP。返回值包含客户端创建 Transport 所需参数,暂略。
{ "router.createWebRtcTransport", ChannelRequest::MethodId::ROUTER_CREATE_WEBRTC_TRANSPORT },
// 创建 PlainTransport。请求参数类似有创建 WebRtcTransport,但无需指定使用 UDP 还是 TCP,因为只支持 UDP。额外的 comedia 表示是否需要预先连接 Transport;rtcpMux 表示是否 RTCP 复用 RTP 端口;enableSrtp 表示是否使用 Srtp,true 则需要提供 srtpCryptoSuite 参数。返回值包含客户端创建 Transport 所需参数,暂略。
{ "router.createPlainTransport", ChannelRequest::MethodId::ROUTER_CREATE_PLAIN_TRANSPORT },
// 创建 PipeTransport。请求参数类似有创建 WebRtcTransport,但无需指定使用 UDP 还是 TCP,因为只支持 UDP。额外的 enableRtx 是否启用 Rtx 重传。返回值包含打通管道所需参数,暂略。
{ "router.createPipeTransport", ChannelRequest::MethodId::ROUTER_CREATE_PIPE_TRANSPORT },
// 创建 DirectTransport。请求参数不能将 enableSctp 设置为 true,因为没必要。也无需提供监听 IP 和 Port 等信息。返回值包含直连需参数,暂略。
{ "router.createDirectTransport", ChannelRequest::MethodId::ROUTER_CREATE_DIRECT_TRANSPORT },
// 创建 ActiveSpeakerObserver。 请求参数必须提供 interval 。返回会议中发言人的音量。
{ "router.createActiveSpeakerObserver", ChannelRequest::MethodId::ROUTER_CREATE_ACTIVE_SPEAKER_OBSERVER },
// 创建 AudioLevelObserver。请求参数必须提供 interval,maxEntries 和 threshold 。返回会议中唯一主导说话者。
{ "router.createAudioLevelObserver", ChannelRequest::MethodId::ROUTER_CREATE_AUDIO_LEVEL_OBSERVER },
// 关闭 Transport。该消息由 Router 处理。Transport 的关闭会触发连锁反应,以确保其上的 Consumer、Producer,其他人消费本 Transport 上的 Producer 的 Consumer(有点绕) 得以关闭。返回值无具体内容。
{ "transport.close", ChannelRequest::MethodId::TRANSPORT_CLOSE },
// 获取 Transport 转储。返回值包含本 Transport 的 Id、是否是 DirectTransport 的 direct 等。更多返回参数详见:Transport.cpp 的 void Transport::FillJson(json& jsonObject) const 方法。
{ "transport.dump", ChannelRequest::MethodId::TRANSPORT_DUMP },
// 获取 Transport 的状态值。返回值见:Transport.cpp 的 void Transport::FillJsonStats(json& jsonArray) 方法。
{ "transport.getStats", ChannelRequest::MethodId::TRANSPORT_GET_STATS },
// 连接 Transport。DirectTransport 不会发送该请求,创建 PipeTransport 时如果参数 comedia 为 true 也无需发送该请求。返回值暂略。
{ "transport.connect", ChannelRequest::MethodId::TRANSPORT_CONNECT },
// 设置最大传入码率。用于拥塞控制。返回值无具体内容。
{ "transport.setMaxIncomingBitrate", ChannelRequest::MethodId::TRANSPORT_SET_MAX_INCOMING_BITRATE },
// 设置最大传出码率。用于拥塞控制。返回值无具体内容。
{ "transport.setMaxOutgoingBitrate", ChannelRequest::MethodId::TRANSPORT_SET_MAX_OUTGOING_BITRATE },
// 重启 ICE。 仅 WebRtcTransport 会发送该请求。
{ "transport.restartIce", ChannelRequest::MethodId::TRANSPORT_RESTART_ICE },
// 创建 Producer。返回值暂略。
{ "transport.produce", ChannelRequest::MethodId::TRANSPORT_PRODUCE },
// 创建 Consumer。返回值暂略。
{ "transport.consume", ChannelRequest::MethodId::TRANSPORT_CONSUME },
// 创建 DataProducer。返回值暂略。
{ "transport.produceData", ChannelRequest::MethodId::TRANSPORT_PRODUCE_DATA },
// 创建 DataConsumer。返回值暂略。
{ "transport.consumeData", ChannelRequest::MethodId::TRANSPORT_CONSUME_DATA },
// 启用 Transport 的跟踪事件。可选事件包括:probation 和 bwe。返回值无具体内容。
{ "transport.enableTraceEvent", ChannelRequest::MethodId::TRANSPORT_ENABLE_TRACE_EVENT },
// 关闭 Producer。该消息由 Transport 处理。Producer 的关闭会触发连锁反应,以确消费本 Producer 的 Consumer 和监听本 Producer 的 RtpObserver 得以关闭。返回值无具体内容。
{ "producer.close", ChannelRequest::MethodId::PRODUCER_CLOSE },
// 获取 Producer 转储。返回值暂略。
{ "producer.dump", ChannelRequest::MethodId::PRODUCER_DUMP },
// 获取 Producer 状态。返回值暂略。
{ "producer.getStats", ChannelRequest::MethodId::PRODUCER_GET_STATS },
// 暂停 Producer 的生产。返回值无具体内容。
{ "producer.pause", ChannelRequest::MethodId::PRODUCER_PAUSE },
// 恢复 Producer 的生产。返回值无具体内容。
{ "producer.resume" , ChannelRequest::MethodId::PRODUCER_RESUME },
// 启用 Producer 的跟踪事件。可选事件包括:rtp、keyframe、nack、pli 和 fir。返回值无具体内容。
{ "producer.enableTraceEvent", ChannelRequest::MethodId::PRODUCER_ENABLE_TRACE_EVENT },
// 关闭 Consumer。该消息由 Transport 处理。返回值无具体内容。
{ "consumer.close", ChannelRequest::MethodId::CONSUMER_CLOSE },
// 获取 Consumer 转储。返回值暂略。
{ "consumer.dump", ChannelRequest::MethodId::CONSUMER_DUMP },
// 获取 Consumer 状态。返回值暂略。
{ "consumer.getStats", ChannelRequest::MethodId::CONSUMER_GET_STATS },
// 暂停 Consumer 的消费。返回值无具体内容。
{ "consumer.pause", ChannelRequest::MethodId::CONSUMER_PAUSE },
// 暂停 Consumer 的消费。返回值无具体内容。
{ "consumer.resume", ChannelRequest::MethodId::CONSUMER_RESUME },
// 设置 Consumer 的首选 Layers。仅对 SimulcastConsumer 和 SvcConsumer 有效。返回值无具体内容。
{ "consumer.setPreferredLayers", ChannelRequest::MethodId::CONSUMER_SET_PREFERRED_LAYERS },
// 设置 Consumer 的码率优先级。返回值无具体内容。
{ "consumer.setPriority", ChannelRequest::MethodId::CONSUMER_SET_PRIORITY },
// Consumer 请求关键帧。返回值无具体内容。
{ "consumer.requestKeyFrame", ChannelRequest::MethodId::CONSUMER_REQUEST_KEY_FRAME },
// 启用 Consumer 的跟踪事件。可选事件包括:rtp、keyframe、nack、pli 和 fir。返回值无具体内容。
{ "consumer.enableTraceEvent", ChannelRequest::MethodId::CONSUMER_ENABLE_TRACE_EVENT },
// 如下 DataProducer 和 DataProducer 的请求类似于 Producer 和 Consumer
{ "dataProducer.close", ChannelRequest::MethodId::DATA_PRODUCER_CLOSE },
{ "dataProducer.dump", ChannelRequest::MethodId::DATA_PRODUCER_DUMP },
{ "dataProducer.getStats", ChannelRequest::MethodId::DATA_PRODUCER_GET_STATS },
{ "dataConsumer.close", ChannelRequest::MethodId::DATA_CONSUMER_CLOSE },
{ "dataConsumer.dump", ChannelRequest::MethodId::DATA_CONSUMER_DUMP },
{ "dataConsumer.getStats", ChannelRequest::MethodId::DATA_CONSUMER_GET_STATS },
// 获取尚未发出的 Sctp buffer 数量。
{ "dataConsumer.getBufferedAmount", ChannelRequest::MethodId::DATA_CONSUMER_GET_BUFFERED_AMOUNT },
// 降低 Sctp buffer 阈值。
{ "dataConsumer.setBufferedAmountLowThreshold", ChannelRequest::MethodId::DATA_CONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD },
// 关闭 RtpObserver。该消息由 Router 处理。返回值无具体内容。
{ "rtpObserver.close", ChannelRequest::MethodId::RTP_OBSERVER_CLOSE },
// 暂停 RtpObserver 的监听。该消息由 Router 处理。返回值无具体内容。
{ "rtpObserver.pause", ChannelRequest::MethodId::RTP_OBSERVER_PAUSE },
// 恢复 RtpObserver 的监听。该消息由 Router 处理。返回值无具体内容。
{ "rtpObserver.resume", ChannelRequest::MethodId::RTP_OBSERVER_RESUME },
// 向 RtpObserver 新增 Producer。返回值无具体内容。
{ "rtpObserver.addProducer", ChannelRequest::MethodId::RTP_OBSERVER_ADD_PRODUCER },
// 从 RtpObserver 移除 Producer。返回值无具体内容。
{ "rtpObserver.removeProducer", ChannelRequest::MethodId::RTP_OBSERVER_REMOVE_PRODUCER }
};

四、PayloadChannelSocket 消息类型

PayloadChannelRequest::MethodId 枚举定义一种消息。

1
2
3
4
5
6
// File: worker/src/PayloadChannel/PayloadChannelRequest.cpp
absl::flat_hash_map<std::string, PayloadChannelRequest::MethodId> PayloadChannelRequest::string2MethodId =
{
// DataConsumer 发送
{ "dataConsumer.send", PayloadChannelRequest::MethodId::DATA_CONSUMER_SEND },
};

五、ChannelSocket 通知类型

为了方便使用, mediasoup 定义了 ChannelNotifier 类用于 worker 向父进程发送通知。通知类型整理如下:

Sender 类型 说明
Workder running worker 运行中。
ActiveSpeakerObserver dominantspeaker 当前主导发言者。
AudioLevelObserver volumes 当前发言者的音量。
- silence 之前发言者已静音。
Consumer producerpause 生产者已暂停。
- producerresume 生产者已恢复。
- producerclose 生产者已关闭
- trace 跟踪消费者信息。类型包括:rtp、keyframe、nack、pli 和 fir。
DataConsumer bufferedamountlow 数据消费者 Sctp Buffer 低。
- dataproducerclose 数据生产者已关闭。
PlainTransport tuple TransportTuple。 当 comedia 为 true 时,通过收到的数据提取到 TransportTuple。
- rtcptuple TransportTuple。 当 comedia 为 true 且 rtcpMux 为 false, 通过收到的数据提取到 TransportTuple。
Producer videoorientationchange 视频方向发生改变。
- score Rtp 流的质量评分。
- trace 跟踪生产者信息。类型包括:rtp、keyframe、nack、pli 和 fir。
SctpAssociation sctpsendbufferfull DataConsumer 的 Sctp 发送 Buffer 已满。
SimpleConsumer score Rtp 流的质量评分。
SimulcastConsumer score Rtp 流的质量评分。包含自身的和 Producer 的。
- layerschange Layers 已改变。
SvcConsumer score Rtp 流的质量评分。包含自身的和 Producer 的。
- layerschange Layers 已改变。
Transport trace 跟踪 Transport 信息。类型包括:probation 和 bwe。
- sctpstatechange Sctp 状态已改变。
WebRtcTransport iceselectedtuplechange Ice 选择的 TransportTuple 已改变。
- icestatechange Ice 状态已改变。
- dtlsstatechange Dtls 状态已改变。 状态包括:connecting、connected、completed、disconnected、closed 和 failed。

六、PayloadChannelSocket 通知类型

为了方便使用, mediasoup 定义了 PayloadChannelNotifier 类用于 worker 向父进程发送通知。通知类型整理如下:

Sender 类型 说明
DirectTransport rtp Rtp 包。
- rtcp Rtcp 包。
- message 消息。

七、worker 进程的启动

worker 是可执行程序,(一般来说)需要通过其他进程来启动。
启动时首先需要设置环境变量 MEDIASOUP_VERSION,通常设置为 mediaoup 真实的版本号。当然,如果非要设置成其他内容也没关系。

1
2
3
4
5
6
7
8
// File: worker/src/main.cpp
// Ensure we are called by our Node library.
if (!std::getenv("MEDIASOUP_VERSION"))
{
MS_ERROR_STD("you don't seem to be my real father!");

std::_Exit(EXIT_FAILURE);
}

还可以以命令行参数的方式设置 logLevellogTagsrtcMinPortrtcMaxPortdtlsCertificateFiledtlsPrivateKeyFile,分别表示日志等级、日志标签(如ice、dtls等,控制哪些类的日志才会传出)、最小和最大 rtc 端口,以及 dtls 证书文件和私钥的物理路径。这些参数都是可选的,如果未提供则会使用默认值或自动生成。

因为 worker 是独立进程,需要和父进程通信。以 Linux 为例,在 fork 子进程后会将父进程的文件描述符传递到子进程中。具体来说 Node.js 程序 fork worker 进程之前,会创建几个 libuv 概念下而非 Linux 概念下的抽象意义上的 pipe,在 Linux 中使用的是 UNIX Domain Socket。fork 进程后,会在子进程将要使用的文件描述符重定向。这里子进程期望持有的文件描述符是 3-6,而实际上父进程创建的可能是 11-15,fork 之后子进程得到的还是 11-15,只要在子进程中使用 fcntl 系统调用重定向即可。通过合理的数量和顺序上的约定能确定重定向为 3-6。最终在子进程中 exec mediasoup-worker(见:uv__process_child_init)。
mediasoup 抽象出叫做 ChannelSocketPayloadChannelSocket 的概念来表示 worker 和父进程的通信信道。

mediasoup 最初只使用了 1 个文件描述符,后来改为了 2 个,再后来改为了 4 个。

八、以库的方式使用 worker

在较长的一段时间,worker 都是以进程的方式运行,不过直到 2021 年 3 月发布的 3.7.0 开始, 准确地说是从 1a805366 的这次提交开始,已经支持以库的方式使用。

如果要像 mediasoup-sfu-cpp 那样用纯 C++ 实现整个 mediasoup 服务端,则可以直接调用 mediasoup_worker_run 方法。当然,也就不用设置环境 MEDIASOUP_VERSION 变量了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// File: worker/src/lib.hpp
extern "C" int mediasoup_worker_run(
int argc,
char* argv[],
const char* version,
int consumerChannelFd,
int producerChannelFd,
int payloadConsumeChannelFd,
int payloadProduceChannelFd,
ChannelReadFn channelReadFn,
ChannelReadCtx channelReadCtx,
ChannelWriteFn channelWriteFn,
ChannelWriteCtx channelWriteCtx,
PayloadChannelReadFn payloadChannelReadFn,
PayloadChannelReadCtx payloadChannelReadCtx,
PayloadChannelWriteFn payloadChannelWriteFn,
PayloadChannelWriteCtx payloadChannelWriteCtx);

以进程方式运行时进程间通所使用的是文件描述符(consumerChannelFd等),以库的方式运行则可以将函数指针传递给 mediasoup_worker_run 的其他参数(PayloadChannelReadFn等),都能够构造出 ChannelSocket 和 PayloadChannelSocket。

还可以用多个线程执行 mediasoup_worker_run,以达到多个 worker 进程的效果。

备注:mediasoup-sfu-cpp 是基于旧版 mediasoup,尚未更新至可以使用 mediasoup_worker_run 方法的版本。

参考资料

现代C++和Mediasoup的WebRTC集群服务实践
纯 C++ 实现的 Mediasoup 闫华
【流媒体】Mediasoup库的架构(C++部分)
libuv
mediasoup v3 Design
mediasoup v3 API
mediasoup-client v3 API