相關連接
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
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");
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);
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);
-
mediasoup 修改
Node.js部分可以用go語言重新編寫,pip通信也可修改爲tcp網絡通信,這樣可以進行分佈式部署,將管理服務和Worker進程分佈式部署。