반응형

 이전 포스팅에서는 Janus-Gateway 의 API에 대해서 개략적으로 알아보았다. 이번 포스팅에서는 Janus-Gateway 의 Audiobridge Plugin을 이용하는 방법에 대해서 설명한다.


 Janus-Gateway 는 WebRTC 기반 서비스 또는 기능은 Plugin 을 통해서 제공한다. 기본적으로 제공하는 Plugin 중 audiobridge 는 음성 그룹 채팅이 가능한 plugin 이다. 그리고 MCU의 특성, 다시 말해 음성 그룹 채팅에 참여하는 Peer 의 음성을 Mixing 해서 전달한다. 


 Mixing 기능을 제공하기 때문에 서버의 CPU를 많이 소모하며, 참여자가 늘어날 수록 CPU 사용율이 Linear 하게 증가되게 된다. Plugin 의 API는 janus 홈페이지에서는 확인이 불가능하고 실제 plugin 코드를 보거나, 데모 웹을 통해서 동작 순서를 분석해야 한다. 


1. configuration

 janus audiobridge plugin의 config 파일은 /usr/local/etc/janus/janus.plugin.audiobridge.cfg 이다. 이 파일을 열어보면 default room 정보가 들어 있고 [general] 항목에 admin_key를 설정하는 항목이 있다. admin_key를 설정하면 room의 create api에 admin_key를 설정해서 보내야 한다.


2. create room

 audiobridge에 mixing room 을 생성한다. 다음은 create room 파라미터 설명이다.

 {

        "request" : "create",

        "room" : <unique numeric ID, optional, chosen by plugin if missing>,

        "permanent" : <true|false, whether the room should be saved in the config file, default false>,

        "description" : "<pretty name of the room, optional>",

        "secret" : "<password required to edit/destroy the room, optional>",

        "pin" : "<password required to join the room, optional>",

        "is_private" : <true|false, whether the room should appear in a list request>,

        "allowed" : [ array of string tokens users can use to join this room, optional],

        "sampling" : <sampling rate of the room, optional, 16000 by default>,

        "audiolevel_ext" : <true|false, whether the ssrc-audio-level RTP extension must be negotiated for new joins, default true>,

        "record" : <true|false, whether to record the room or not, default false>,

        "record_file" : "</path/to/the/recording.wav, optional>",

}

 다음은 websocket 으로 create 를 호출하는 코드이다.

janus.plugin={};

janus.plugin.audiobridge={};

janus.plugin.audiobridge.create=(ws,session_id,handle_id,roomopt)=>{

        let trxid=getTrxId();

        let request={};

        request.janus='message';

        request.transaction=trxid;

        request.session_id=session_id;

        request.handle_id=handle_id;

        request.body={};

        request.body.request='create';

        request.body.room=roomopt.room;

        request.body.permanent=roomopt.permanent;

        if(roomopt.description) request.body.permanent=roomopt.description;

        if(roomopt.secret) request.body.permanent=roomopt.secret;

        if(roomopt.pin) request.body.permanent=roomopt.pin;

        if(roomopt.is_private) request.body.permanent=roomopt.is_private;

        if(roomopt.allowed) request.body.permanent=roomopt.allowed;

        if(roomopt.sampling) request.body.permanent=roomopt.sampling;

        if(roomopt.audiolevel_ext) request.body.permanent=roomopt.audiolevel_ext;

        if(roomopt.record) request.body.permanent=roomopt.record;

        if(roomopt.record_file) request.body.permanent=roomopt.record_file;

        if(roomopt.admin_key) request.body.admin_key=roomopt.admin_key;

        console.log('janus.plugin.audiobridge.create msg:'+JSON.stringify(request));

        ws.send(JSON.stringify(request));

};

 다음과 같이 호출할 경우 요청-응답은 다음과 같다.

 호출코드> 

        let roomopt={};

        roomopt.room=5555;

        roomopt.permanent=false;

        roomopt.admin_key='janushello';

        janus.plugin.audiobridge.create(ws,janusSessionId,handleId,roomopt);

 요청>

{"janus":"message","transaction":"Ik7z2RcMbxgO","session_id":273910267729177,

"handle_id":6812743015768869,"body":{"request":"create","room":5555,"permanent":false,

"admin_key":"janushello"}}

 응답>

{"janus":"success","session_id":273910267729177,"sender":6812743015768869,

"transaction":"Ik7z2RcMbxgO","plugindata":{"plugin":"janus.plugin.audiobridge","data":{"audiobridge":"created","room":5555}}}


3. destroy room

 생성한 audiobridge room 을 파괴한다. 다음은 destroy room 파라미터에 대한 설명이다.

{

        "request" : "destroy",

        "room" : <unique numeric ID of the room to destroy>,

        "secret" : "<room secret, mandatory if configured>",

        "permanent" : <true|false, whether the room should be also removed from the config file, default false>

 다음은 websocket으로 destroy 를 호출하는 코드이다.

janus.plugin.audiobridge.destroy=(ws,session_id,handle_id,roomopt)=>{

        let trxid=getTrxId();

        let request={};

        request.janus='message';

        request.transaction=trxid;

        request.session_id=session_id;

        request.handle_id=handle_id;

        request.body={};

        request.body.request='destroy';

        request.body.room=roomopt.room;

        if(roomopt.admin_key) request.body.admin_key=roomopt.admin_key;

        console.log('janus.plugin.audiobridge.create msg:'+JSON.stringify(request));

        ws.send(JSON.stringify(request));

};

 다음과 같이 호출할 경우 요청-응답은 다음과 같다.

 호출코드>

        let roomopt={};

        roomopt.room=5555;

        roomopt.admin_key='janushello';

        janus.plugin.audiobridge.destroy(ws,janusSessionId,handleId,roomopt);

 요청>

{"janus":"message","transaction":"9qLWxeUm2XqH","session_id":4108642482605178,

"handle_id":3115692898921259,"body":{"request":"destroy","room":5555,"admin_key":"janushello"}}

  응답>

{"janus":"success","session_id":4108642482605178,"sender":3115692898921259,

"transaction":"9qLWxeUm2XqH","plugindata":{"plugin":"janus.plugin.audiobridge","data":{"audiobridge":"destroyed","room":5555}}}


4. join room

 생성되어져 있는 room 에 join 한다.

{

        "request" : "join",

        "room" : <numeric ID of the room to join>,

        "id" : <unique ID to assign to the participant; optional, assigned by the plugin if missing>,

        "pin" : "<password required to join the room, if any; optional>",

        "display" : "<display name to have in the room; optional>",

        "token" : "<invitation token, in case the room has an ACL; optional>",

        "muted" : <true|false, whether to start unmuted or muted>,

        "quality" : <0-10,Opus-related complexity to use, lower is higher quality; optional, default is 4>,

        "volume" : <percent value, <100 reduces volume, >100 increases volume; optional, default is 100 (no volume change)>

}

 다음은 websocket으로 join 을 호출하는 코드이다.

janus.plugin.audiobridge.join=(ws,session_id,handle_id,roomopt)=>{

        let trxid=getTrxId();

        let request={};

        request.janus='message';

        request.transaction=trxid;

        request.session_id=session_id;

        request.handle_id=handle_id;

        request.body={};

        request.body.request='join';

        request.body.room=roomopt.room;

        if(roomopt.id) request.body.id;

        if(roomopt.pin) request.body.pin;

        if(roomopt.display) request.body.display;

        if(roomopt.token) request.body.token;

        if(roomopt.muted) request.body.muted;

        if(roomopt.quality) request.body.quality;

        if(roomopt.volume) request.body.volume;

        console.log('janus.plugin.audiobridge.join msg:'+JSON.stringify(request));

        ws.send(JSON.stringify(request));

        return trxid;

}; 

 다음과 같이 호출할 경우 요청-응답은 다음과 같다.

호출코드> 

        let roomopt={};

        roomopt.room=5555;

        joinTransaction=janus.plugin.audiobridge.join(ws,janusSessionId,handleId,roomopt);

요청>

{"janus":"message","transaction":"bnkSWjbJlc85","session_id":6043633968408612,

"handle_id":3174356917265656,"body":{"request":"join","room":5555}}

응답>

{"janus":"event","session_id":6043633968408612,"sender":3174356917265656,

"transaction":"bnkSWjbJlc85","plugindata":{"plugin":"janus.plugin.audiobridge","data":{"audiobridge":"joined","room":5555,"id":1426105010077627,"participants":[{"id":6900479519878206,"muted":false}]}}}


5. leave room

 Join한 Room에서 나간다. 참여하고 있는 다른 Client에 Leave하는 client의 ID를 event notification으로 받는다.

{

        "request" : "leave"

}

 다음은 websocket으로 leave를 호출하는 코드이다.

janus.plugin.audiobridge.leave=(ws,session_id,handle_id,roomopt)=>{

        let trxid=getTrxId();           

        let request={};                 

        request.janus='message';

        request.transaction=trxid;

        request.session_id=session_id;

        request.handle_id=handle_id;

        request.body={};

        request.body.request='leave';

        ws.send(JSON.stringify(request));

        return trxid;

};

 호출시 다음의 요청-응답을 보인다.

 요청>

 {"janus":"message","transaction":"ZPhUC0UnQ0b4","session_id":6924157701703087,

"handle_id":154472435867945,"body":{"request":"leave"}}

 응답>

{"janus":"event","session_id":6924157701703087,"sender":154472435867945,

"transaction":"ZPhUC0UnQ0b4","plugindata":{"plugin":"janus.plugin.audiobridge","data":{"audiobridge":"left","room":5555,"id":3424150840600147}}}


6. configure

 Join 이후에 WebRTC Channel 생성을 위한 jsep 메시지(offer)를 전달한다. plugin code상에는 다음처럼 기술되어 있으나, websocket 으로 연동시 jsep 메시지를 첨가해야 한다. 즉 configure 메시지를 보내기 전에 local의 sdp 가 나와야 한다는 것이다.

{

        "request" : "configure",

        "muted" : <true|false, whether to unmute or mute>,

        "display" : "<new display name to have in the room>",

        "quality" : <0-10, Opus-related complexity to use, lower is higher quality; optional, default is 4>,

        "volume" : <percent value, <100 reduces volume, >100 increases volume; optional, default is 100 (no volume change)>,

        "record": <true|false, whether to record this user's contribution to a .mjr file (mixer not involved),

        "filename": "<basename of the file to record to, -audio.mjr will be added by the plugin>"

 다음은 websocket으로 configure를 호출하는 코드이다.

janus.plugin.audiobridge.configure=(ws,session_id,handle_id,description,roomopt)=>{

        let trxid=getTrxId();

        let jsep={

                type:description.type,

                sdp: description.sdp

        };      

        let request={

                "janus":"message",

                "transaction":trxid,

                "session_id":session_id,

                "handle_id":handle_id,

                "jsep":jsep

        };      

        request.body={};

        request.body.request='configure';

        request.body.muted=roomopt.muted;

        if(roomopt.display) request.body.display=roomopt.display;

        if(roomopt.quality) request.body.quality=roomopt.quality;

        if(roomopt.volume) request.body.volume=roomopt.volume;

        if(roomopt.record) request.body.record=roomopt.record;

        if(roomopt.filename) request.body.filename=roomopt.filename;

        ws.send(JSON.stringify(request));

        return trxid;

};

 다음은 이를 호출하는 코드 및 요청-응답이다. 호출 코드는 peerconnection의 createOffer 함수 상에서 진행된다. configure에 대한 응답으로, answer가 온다. 따라서 peerconnection의 setLocalDescription 이후 configure로 offer를 보내고 이에 대한 응답을 받아서 setRemoteDescription 을 설정하면 webrtc channel이 생성되는 것이다.

        pc.createOffer(pc_constraints).then(function (offer) {

                return pc.setLocalDescription(offer);

        }).then(function () {

                let roomopt={};

                roomopt.muted=false;                      offerToTransaction=janus.plugin.audiobridge.configure(ws,janusSessionId,handleId,pc.localDescription,roomopt);

        }).catch(logError);

 요청>
{"janus":"message","transaction":"FkFWawuOYpQh","session_id":5685216088527405,
"handle_id":4526015082420804,"body":{"request":"configure","muted":false},
"jsep":{"type":"offer","sdp":"v=0\r\no=- 6251931514972413769 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0
\r\na=group:BUNDLE audio
\r\na=msid-semantic: WMS i5Cn6h0gKzQ8fQ2F90UCKNPdaVZgNRWyzlej
\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:pWcr
\r\na=ice-pwd:ss1s/Hdf/FDXp0gr/+rMz5rE\r\na=fingerprint:sha-256 F5:88:59:11:9A:5D:F2:17:5C:6B:65:23:C5:AA:0D:63:AD:7D:2C:46:8F:83:47:5E:B3:D0:96:2F:85:68:81:53
\r\na=setup:actpass\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=sendrecv\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:112 telephone-event/32000\r\na=rtpmap:113 telephone-event/16000\r\na=rtpmap:126 telephone-event/8000\r\na=ssrc:1620416225 cname:PuvGbj3ECo88axm1\r\na=ssrc:1620416225 msid:i5Cn6h0gKzQ8fQ2F90UCKNPdaVZgNRWyzlej 3720a2b9-2600-46af-b55a-dc598d6476fe\r\na=ssrc:1620416225 mslabel:i5Cn6h0gKzQ8fQ2F90UCKNPdaVZgNRWyzlej\r\na=ssrc:1620416225 label:3720a2b9-2600-46af-b55a-dc598d6476fe\r\n"}}

 응답>
{"janus":"event","session_id":5685216088527405,"sender":4526015082420804,
"transaction":"FkFWawuOYpQh","plugindata":{"plugin":"janus.plugin.audiobridge",
"data":{"audiobridge":"event","result":"ok"}},"jsep":{"type":"answer","sdp":"v=0
\r\no=- 6251931514972413769 2 IN IP4 192.168.0.55\r\ns=Room 5555
\r\nt=0 0\r\na=group:BUNDLE audio\r\na=msid-semantic: WMS janus
\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 192.168.0.55\r\na=sendrecv
\r\na=mid:audio\r\na=rtcp-mux\r\na=ice-ufrag:f8OH
\r\na=ice-pwd:IFTLbtfQ2bLAVpClAqWwE2\r\na=ice-options:trickle\r\na=fingerprint:sha-256 D2:B9:31:8F:DF:24:D8:0E:ED:D2:EF:25:9E:AF:6F:B8:34:AE:53:9C:E6:F3:8F:F2:64:15:FA:E8:7F:53:2D:38

\r\na=setup:active\r\na=rtpmap:111 opus/48000/2\r\na=fmtp:111 maxplaybackrate=16000; stereo=0; sprop-stereo=0; useinbandfec=0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=ssrc:3268582270 cname:janusaudio\r\na=ssrc:3268582270 msid:janus janusa0\r\na=ssrc:3268582270 mslabel:janus\r\na=ssrc:3268582270 label:janusa0\r\na=candidate:1 1 udp 2013266431 192.168.0.55 47767 typ host\r\n"}}


반응형
Posted by alias
,