概述:Mediasoup基本框架学习
Mediasoup 的基本结构
mediasoup design
mediasoup 模块
- javaScript层开发的对外接口层,Node.js服务。提供Signal服务(房间服务、SDP、等数据)。 C/C++ 模块,用于媒体数据交换层(ICE, DTLS, RTP and so on),UDP类型数据交换。
两个模块相互通信,但是开发者无需关心C/C++模块,只要关心JavaScript Api使用即可。
mediasoup Work模块架构
- C/C++ Worker模块包括Router和WebRtcTransport。
- Router用于流媒体复制转发,WebRtcTransport维护与客户端的通信。
- Worker:
- 一个Worker代表着一个运行在单核CPU上并处理Router实例的mediasoup C++子进程;
- Router:
- Router用于注入、选择和转发通过Transport实例创建的媒体流;类似于房间功能,一个Router只能在一个worker进程中产生;
- Transport:
- Transport将终端与MediaSoup Router连接起来,并通过在其上创建的Producer和Consumer实例实现双向媒体传输,实 现了下面3种Transport:WebRtcTransport,PlainRtpTransport,PipeTransport.
- 安装
- 安装mediasoup的Node.js模块通过NPM工具
$ npm install mediasoup@3 --save 1
- Mediasoup C/C++模块必须已经编译而且安装完毕,并且在目标服务器上已经可以使用。安装时会自动编译。注意编译报错,并及时解决。
- 安装完成后当前目录下会有 node_modules 文件夹 和package-lock.json文件
- mediasoup重要目录结构
- 进入node_moduels目录下查看mediasoup目录结构
- worker目录:mediasoup的c++模块,用于生产 mediasoup-work进程的二进制文件和源代码 lib目录:mediasoup的Node.js模块,用于对外提供接口,用于创建mediasoup-work进程,并且充当第三方程序和该进程通信的中间层。 test目录:具体的示例代码,可以看看如同启动mediasoup-work模块,如何创建router(room)等
- mediasoup Node.js 模块说明
- Node.js模块主要提供API接口,用于创建mediasoup-work进程,并且提供控制该进程的接口,充当其它进程和mediasoup-work进程通信的桥梁。
- 注:加粗部分是重点模块,需要详细看代码
- AudioLevelObserver.js: 用于检测声音的大小, 通过C++检测音频声音返回应用层,通过Observer接收并展示音频大小 Channel.js:实现于C++模块的通信部分 Consume.js: 消费媒体数据(video audio) EnhancedEventEmitter.js:EventEmitter的封装,C++底层向上层发送事件 Logger.js:日志模块 PipeTransport.js:控制Router之间的转发 PlainRtpTransport.js:控制普通的rtp传输通道,如FFmpeg等不经过浏览器rtp协议的数据传输 Producer.js:生产媒体数据,音频或视频 Router.js:代表一个房间或者一个路由器 Transport.js:所有传输的的基类(父类) WebRtcTransport.js:浏览器使用的传输接口。 Worker.js:用于创建mediasoup-work进程的类,一个房间只能在一个Worker里。 Error.js:错误信息的定义 Index.js:Mediasoup的库,上层引入Mediasoup最先导入的库,也为库的索引。 Ortc.js: 其与SDP相对应,以对象的形式标识SDP,如编解码参数,编解码器,帧 率等,以对象方式去存储。 ScalabilityModes.js:扩容模块,广播等功能可以用到。 SupportedRtpCapabilities.js:对通讯能力的支持,实际上是媒体协商相关的东西,如你支持的帧率, 码率,编解码器是什么等
- 代码调用
- Node.js服务调用mediasoup接口
//your Node.js application: const mediasoup = require("mediasoup"); 12
- Node.js模块通过管道(Unix pipe)和mediasoup-worker进程间通信。所以无法实现管理进程和工作进程分离在不同主机上面。
- worker.js模块调用说明:
//简单的创建一个Worker进程,具体参考test目录里面的示例代码 const os = require('os'); const process = require('process'); const { toBeType } = require('jest-tobetype'); const mediasoup = require('../'); const { createWorker, observer } = mediasoup; const { InvalidStateError } = require('../lib/errors'); expect.extend({ toBeType }); let worker; beforeEach(() => worker && !worker.closed && worker.close()); afterEach(() => worker && !worker.closed && worker.close()); test('createWorker() succeeds', async () => { const onObserverNewWorker = jest.fn(); observer.once('newworker', onObserverNewWorker); worker = await createWorker(); expect(onObserverNewWorker).toHaveBeenCalledTimes(1); expect(onObserverNewWorker).toHaveBeenCalledWith(worker); expect(worker).toBeType('object'); expect(worker.pid).toBeType('number'); expect(worker.closed).toBe(false); worker.close(); expect(worker.closed).toBe(true); // eslint-disable-next-line require-atomic-updates worker = await createWorker( { logLevel : 'debug', logTags : [ 'info' ], rtcMinPort : 0, rtcMaxPort : 9999, dtlsCertificateFile : 'test/data/dtls-cert.pem', dtlsPrivateKeyFile : 'test/data/dtls-key.pem', appData : { bar: 456 } }); expect(worker).toBeType('object'); expect(worker.pid).toBeType('number'); expect(worker.closed).toBe(false); expect(worker.appData).toEqual({ bar: 456 }); worker.close(); expect(worker.closed).toBe(true); }, 2000); 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
- Router 接口调用:
//简单的创建一个Worker进程后下发创建router命令,具体参考test目录里面的示例代码 const { toBeType } = require('jest-tobetype'); const mediasoup = require('../'); const { createWorker } = mediasoup; const { InvalidStateError } = require('../lib/errors'); expect.extend({ toBeType }); let worker; beforeEach(() => worker && !worker.closed && worker.close()); afterEach(() => worker && !worker.closed && worker.close()); const mediaCodecs = [ { kind : 'audio', mimeType : 'audio/opus', clockRate : 48000, channels : 2, parameters : { useinbandfec : 1, foo : 'bar' } }, { kind : 'video', mimeType : 'video/VP8', clockRate : 90000 }, { kind : 'video', mimeType : 'video/H264', clockRate : 90000, parameters : { 'level-asymmetry-allowed' : 1, 'packetization-mode' : 1, 'profile-level-id' : '4d0032' }, rtcpFeedback : [] // Will be ignored. } ]; test('worker.createRouter() succeeds', async () => { worker = await createWorker(); const onObserverNewRouter = jest.fn(); worker.observer.once('newrouter', onObserverNewRouter); const router = await worker.createRouter({ mediaCodecs, appData: { foo: 123 } }); expect(onObserverNewRouter).toHaveBeenCalledTimes(1); expect(onObserverNewRouter).toHaveBeenCalledWith(router); expect(router.id).toBeType('string'); expect(router.closed).toBe(false); expect(router.rtpCapabilities).toBeType('object'); expect(router.rtpCapabilities.codecs).toBeType('array'); expect(router.rtpCapabilities.headerExtensions).toBeType('array'); expect(router.appData).toEqual({ foo: 123 }); await expect(worker.dump()) .resolves .toEqual({ pid: worker.pid, routerIds: [ router.id ] }); await expect(router.dump()) .resolves .toMatchObject( { id : router.id, transportIds : [], rtpObserverIds : [], mapProducerIdConsumerIds : {}, mapConsumerIdProducerId : {}, mapProducerIdObserverIds : {}, mapDataProducerIdDataConsumerIds : {}, mapDataConsumerIdDataProducerId : {} }); // Private API. expect(worker._routers.size).toBe(1); worker.close(); expect(router.closed).toBe(true); // Private API. expect(worker._routers.size).toBe(0); }, 2000); 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
- mediasoup 修改
- Node.js部分可以用go语言重新编写,pip通信也可修改为tcp网络通信,这样可以进行分布式部署,将管理服务和Worker进程分布式部署。
- 待续