• OWT(Open WebRTC Toolkit) Server信令分析 (上)


    OWT(Open WebRTC Toolkit) Server信令分析 (上)


    目录

    1. OWT Server信令分析
    2. OWT Server信令交互过程解析

    信令分析因为包含一些代码和格式,文章很长,所以分成上下两篇记录,OWT(Open WebRTC Toolkit)相关文章
    相关文章:

    1. Ubuntu环境安装OWT Server[Open WebRTC Toolkit]
    2. Docker环境安装OWT Server[Open WebRTC Toolkit]
    3. OWT Server整体架构分析 [Open WebRTC Toolkit]
    4. OWT Server信令分析 (上) [Open WebRTC Toolkit]
    5. OWT Server信令分析 (下) [Open WebRTC Toolkit]
    6. OWT Server进程结构和JS代码处理流程 [Open WebRTC Toolkit]
    7. OWT Server REST API

    1. OWT Server信令分析

    1. OWT信令协议分为RESTful API和SocketIO长连接两部分,RESTful API由Management API提供,SocketIO长连接则由WebRTC Portal提供。
    2. 不过不是直接调用Management API,而是调用Conference Sample Server的接口,它是对Management API的一个封装,源码在owt-client-javascript https://github.com/open-webrtc-toolkit/owt-client-javascript/tree/master/src/samples/conference项目中。
    3. OWT Server WebRTC的信令交互过程如下:
    A POST /tokens/
    A SocketIO connect
    A SocketIO login
    A SocketIO publish
    A SocketIO soac offer
    A SocketIO soac candidate
    Portal SocketIO soac answer
    
    B POST /tokens/
    B SocketIO connect
    B SocketIO login
    B SocketIO subscribe
    B SocketIO soac offer
    B SocketIO soac candidate
    Portal SocketIO soac answer
    
    SocketIO logout
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. OWT Server信令交互过程解析

    1. 以owt-client-javascript作为客户端,owt-server作为服务端为例。
    2. owt server安装完成后,可以在https://localhost:3004或者https://ip:3004看到通话界面。
    3. owt-client-javascript入口函数为src/samples/conference/public/scripts/index.js的window.onload(通话界面按F12可见),简化代码如下:
        window.onload = function() {
            var simulcast = getParameterByName('simulcast') || false;
            var shareScreen = getParameterByName('screen') || false;
            myRoom = getParameterByName('room');
            var isHttps = (location.protocol === 'https:');
            var mediaUrl = getParameterByName('url');
            var isPublish = getParameterByName('publish');
            createToken(myRoom, 'user', 'presenter', function(response) {
                var token = response;
                conference.join(token).then(resp => {
                    ...
                }, function(err) {
                    ...
                });
            }, serverUrlBase);
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 可以看到,客户端首先会创建token,拿到token后再加入会议室。

    1. HTTP POST请求创建token

    1. 用户加入房间时,会发送POST请求让服务端返回token字符串。
    2. POST ${host}/v1/rooms/{roomId}/tokens
    3. request body:
    object(TokenRequest):
    {
        room: string,     // 可选,为空时会传入事先创建好的房间名为sampleRoom的roomId
        preference: object(Preference),     // Preference of this token would be used to connect through, refers to Data Model.
        user: string,     // Participant's user defined ID
        role: string      // Participant's role
    }
    object(Preference):
    {
        isp: string,
        region: string
    }
    
    示例:
    {
      "user": "user_a",
      "role": "presenter",
      "room": "5dca71a45778c64ff39d9485"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 其中:
      1. user字段为用户id。
      2. role字段的取值含义由服务端配置,表明用户能否发布和订阅音视频流,在source/data_access/defaults.js中定义了如下默认的role:
        1. 使用presenter,表示音频和视频都能发布、订阅。
    const DEFAULT_ROLES = [
      {
        role: 'presenter',
      publish: { audio: true, video: true },
      subscribe: { audio: true, video: true }
      },
      {
        role: 'viewer',
      publish: {audio: false, video: false },
      subscribe: {audio: true, video: true }
      },
      {
        role: 'audio_only_presenter',
      publish: {audio: true, video: false },
      subscribe: {audio: true, video: false }
      },
      {
        role: 'video_only_viewer',
      publish: {audio: false, video: false },
      subscribe: {audio: false, video: true }
      },
      {
        role: 'sip',
      publish: { audio: true, video: true },
      subscribe: { audio: true, video: true }
      }
    ];
    
    • 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
    1. response body为加密后的token字符串,示例:
    eyJ0b2tlbklkIjoiNjM2MjE2YWExNmY5ZWI0MjkxNjMwY2RiIiwiaG9zdCI6ImludHJhLXJ0Yy5zbWFydGVkdS5sZW5vdm8uY29tOjgwODAiLCJzZWN1cmUiOnRydWUsInNpZ25hdHVyZSI6Ik56YzBNamRsWWpNMk5EQXdOV0ZpWkRNME1HUXdPRE16TjJFek1XUTFNMlU0TVRjeU5UQTFaall6TnpGaU1UQXdNVFJsTkRRMU1ESXhaV05tTm1NMVpBPT0ifQ==
    
    • 1

    1. 客户端相关代码
    1. 客户端相关代码:
      1. 位置:src/samples/conference/public/scripts/rest-sample.js
    var createToken = function (room, user, role, callback, host) {
        var body = {
            room: room,
            user: user,
            role: role
        };
        send('POST', '/tokens/', body, callback, host);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 客户端发送的POST请求不是直接调用Management API,而是调用Conference Sample Server的接口,它是对Management API的一个封装,源码在owt-client-javascript https://github.com/open-webrtc-toolkit/owt-client-javascript/tree/master/src/samples/conference项目中。
    2. Conference Sample Server相关代码:
      1. 位置:src/samples/conference/samplertcservice.js
    app.post('/tokens', function(req, res) { 
      'use strict'; 
      var room = req.body.room || sampleRoom, // sampleRoom会自动创建,见下文
        user = req.body.user,
        role = req.body.role;
    
      //Note: The actual *ISP* and *region* information should be retrieved from the *req* object and filled in the following 'preference' data.
      var preference = {isp: 'isp', region: 'region'};
      icsREST.API.createToken(room, user, role, preference, function(token) {
        res.send(token);
      }, function(err) {
        res.status(401).send(err);
      });
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 会调用createToken方法,将请求转发给Management API:
      1. 位置:src/sdk/rest/API.js
      /**
         * @function createToken
         * @desc This function creates a new token when a new participant to a room needs to be added.
         * @memberOf OWT_REST.API
         * @param {string} room                          -Room ID
         * @param {string} user                          -Participant's user ID
         * @param {string} role                          -Participant's role
         * @param {object} preference                    -Preference of this token would be used to connect through
         * @param {function} callback                    -Callback function on success
         * @param {function} callbackError               -Callback function on error
         * @example
      var roomId = '51c10d86909ad1f939000001';
      var user = 'user-id@company.com';
      var role = 'guest';
      // Only isp and region are supported in preference currently, please see server's document for details.
      var preference = {isp: 'isp', region: 'region'};
      OWT_REST.API.createToken(roomId, user, role, preference, function(token) {
        console.log ('Token created:' token);
      }, function(status, error) {
        // HTTP status and error
        console.log(status, error);
      });
         */
      var createToken = function(room, user, role, preference, callback, callbackError) {
        if (typeof room !== 'string' || typeof user !== 'string' || typeof role !== 'string') {
          if (typeof callbackError === 'function')
            callbackError(400, 'Invalid argument.');
          return;
        }
        send('POST', 'rooms/' + room + '/tokens/', {preference: preference, user: user, role: role}, callback, callbackError);
      };
    
    • 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

    2. 服务端相关代码
    1. owt server服务端中Management API接收请求:
      2. 位置:source/management_api/resource/v1/index.js
    //Create token.
    router.post('/rooms/:room/tokens', tokensResource.create);
    
    /*
     * Post Token. Creates a new token for a determined room of a service.
     */
    exports.create = function (req, res, next) {
        var authData = req.authData;
        authData.user = (req.authData.user || (req.body && req.body.user));
        authData.role = (req.authData.role || (req.body && req.body.role));
        var origin = ((req.body && req.body.preference) || {isp: 'isp', region: 'region'});
    
        generateToken(req.params.room, authData, origin, function (tokenS) {
            if (tokenS === undefined) {
                log.info('Name and role?');
                return next(new e.BadRequestError('Name or role not valid'));
            }
            if (tokenS === 'error') {
                log.info('RequestHandler does not respond');
                return next(new e.CloudError('Failed to get portal'));
            }
            log.debug('Created token for room ', req.params.room, 'and service ', authData.service._id);
            res.send(tokenS);
        });
    };
    
    • 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
    1. 会调用generateToken函数生成token字符串,代码如下:
    /*
     * Generates new token.
     * The format of a token is:
     * {tokenId: id, host: erizoController host, signature: signature of the token};
     */
    var generateToken = function(currentRoom, authData, origin, callback) {
        const databaseGenerateToken = function(token) {
            return new Promise((resolve, reject) => {
                dataAccess.token.create(token, (id) => {
                    if (id) {
                        resolve(id);
                    } else {
                        reject(new Error('Failed to get token ID.'));
                    }
                });
            }).then(id => {
                return getTokenString(id, token);
            });
        };
    
        var currentService = authData.service,
            user = authData.user,
            role = authData.role,
            r,
            tr,
            token,
            tokenS;
    
        if (user === undefined || user === '') {
            callback(undefined);
            return;
        }
    
        if (!authData.room.roles.find((r) => (r.role === role))) {
            callback(undefined);
            return;
        }
    
        token = {};
        token.user = user;
        token.room = currentRoom;
        token.role = role;
        token.service = currentService._id;
        token.creationDate = new Date();
        token.origin = origin;
        token.code = Math.floor(Math.random() * 100000000000) + '';
    
        // Values to be filled from the erizoController
        token.secure = false;
    
        requestHandler.schedulePortal (token.code, origin, function (ec) {
            if (ec === 'timeout') {
                callback('error');
                return;
            }
    
            if(ec.via_host !== '') {
                if(ec.via_host.indexOf('https') == 0) {
                    token.secure = true;
                    token.host = ec.via_host.substr(8);
                } else {
                    token.secure = false;
                    token.host = ec.via_host.substr(7);
                }
    
            } else {
                token.secure = ec.ssl;
                if (ec.hostname !== '') {
                    token.host = ec.hostname;
                } else {
                    token.host = ec.ip;
                }
    
                token.host += ':' + ec.port;
            }
    
    /* 相关对象示例,方便理解: 
    token:  {
      user: 'user',
      room: '62b95bd27ff8054480cbb73a',
      role: 'presenter',
      service: '62b95b9166c14841ed16f845',
      creationDate: '2022-11-04T09:09:11.650Z',
      origin: { isp: 'isp', region: 'region' },
      code: '37024002700',
      secure: true,
      host: '52.81.xxx.190:8080'
    } , 
    ec:  {
      ip: '52.81.xxx.190',
      hostname: '',
      port: 8080,
      via_host: '',
      ssl: true,
      state: 2,
      max_load: 0.85,
      capacity: { isps: [], regions: [] }
    }
    */
            if (!global.config.server.enableWebTransport){
                databaseGenerateToken(token).then(tokenS => {
                    callback(tokenS);
                });
            } else {
                // TODO: Schedule QUIC agent and portal parallelly.
                requestHandler.scheduleQuicAgent(token.code, origin, info => {
                    if (info !== 'timeout') {
                        let hostname = info.hostname;
                        if (!hostname) {
                            hostname = info.ip;
                        }
                        // TODO: Rename "echo".
                        token.webTransportUrl = 'https://' + hostname + ':' + info.port + '/';
                    }
                    databaseGenerateToken(token).then(tokenS => {
                        callback(tokenS);
                    });
                });
            }
        });
    };
    
    • 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
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    1. generateToken函数中,token对象包含内容有:
      1. 用户名:user
      2. 房间id:roomId
      3. 权限:role
      4. serviceId:serviceid
      5. 创建时间戳:creationData
      6. 偏好:origin
      7. 随机数:code
    2. requestHandler.schedulePortal函数对WebRTC Portal进行调度,提供客户端的请求数据(互联网服务提供商ISP和地域),要求分配一个WebRTC Portal的地址。
      1. 调度逻辑后续分析。
    3. 生成token字符串代码逻辑如下:
      1. 位置:source/management_api/resource/v1/tokensResource.js
    var getTokenString = function (id, token) {
        return dataAccess.token.key().then(function(serverKey) {
            var toSign = id + ',' + token.host + ',' + token.webTransportUrl,
                hex = crypto.createHmac('sha256', serverKey).update(toSign).digest('hex'),
                signed = Buffer.from(hex).toString('base64'),
    
                tokenJ = {
                    tokenId: id,
                    host: token.host,
                    secure: token.secure,
                    webTransportUrl: token.webTransportUrl,
                    signature: signed
                },
                tokenS = Buffer.from(JSON.stringify(tokenJ)).toString('base64');
    
            return tokenS;
    
        }).catch(function(err) {
            log.error('Get serverKey error:', err);
        });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. token示例,方便理解:
    token:
    {
      user: 'user',
      room: '62b95bd27ff8054480cbb73a',
      role: 'presenter',
      service: '62b95b9166c14841ed16f845',
      creationDate: '2022-11-04T09:09:11.650Z',
      origin: { isp: 'isp', region: 'region' },
      code: '37024002700',
      secure: true,
      host: '52.81.201.190:8080'
    } 
    
    tokenJ:
    {
      tokenId: '6364d6b7fd05c63e3b648f4c',
      host: '52.81.201.190:8080',
      secure: true,
      webTransportUrl: 'undefined',
      signature: 'OWZiYWY3NGY5ZWZjZWIxZjlkN2Y0MWMwMzliY2E1YTc1N2VjNzU3ZmQ5Y2QzNGJmNDIzNmIxYzFkOTU3NjIxZA=='
    }
    
    tokenS:
    eyJ0b2tlbklkIjoiNjM2NGQ2YjdmZDA1YzYzZTNiNjQ4ZjRjIiwiaG9zdCI6IjUyLjgxLjIwMS4xOTA6ODA4MCIsInNlY3VyZSI6dHJ1ZSwic2lnbmF0dXJlIjoiT1daaVlXWTNOR1k1WldaalpXSXhaamxrTjJZME1XTXdNemxpWTJFMVlUYzFOMlZqTnpVM1ptUTVZMlF6TkdKbU5ESXpObUl4WXpGa09UVTNOakl4WkE9PSJ9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3. 关于sampleRoom
    1. Conference Sample Server接收tokens请求时,如果request body没有携带roomId,会将默认的sampleRoom的roomId赋予。
      1. 位置:src/samples/conference/samplertcservice.js
    app.post('/tokens', function(req, res) { 
      'use strict'; 
      var room = req.body.room || sampleRoom, // sampleRoom会自动创建,见下文
        user = req.body.user,
        role = req.body.role;
    
      //Note: The actual *ISP* and *region* information should be retrieved from the *req* object and filled in the following 'preference' data.
      var preference = {isp: 'isp', region: 'region'};
      icsREST.API.createToken(room, user, role, preference, function(token) {
        res.send(token);
      }, function(err) {
        res.status(401).send(err);
      });
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 在启动时就会自动创建一个房间名为"sampleRoom"的房间,相关代码:
    var sampleRoom;
    var pageOption = { page: 1, per_page: 100 };
    (function initSampleRoom () {
      // 获取room列表,判断room列表中是否有room name为sampleRoom,有则获取roomId
      icsREST.API.getRooms(pageOption, function(rooms) {
        console.log(rooms.length + ' rooms in this service.');
        for (var i = 0; i < rooms.length; i++) {
          if (sampleRoom === undefined && rooms[i].name === 'sampleRoom') {
            sampleRoom = rooms[i]._id;
            console.log('sampleRoom Id:', sampleRoom);
          }
          if (sampleRoom !== undefined) {
            break;
          }
        }
        // 没有sampleRoom,则创建,创建成功后返回roomId
        var tryCreate = function(room, callback) {
          var options = {};
          icsREST.API.createRoom(room.name, options, function(roomId) {
            console.log('Created room:', roomId._id);
            callback(roomId._id);
          }, function(status, err) {
            console.log('Error in creating room:', err, '[Retry]');
            setTimeout(function() {
              tryCreate(room, options, callback);
            }, 100);
          }, room);
        };
    
        var room;
        if (!sampleRoom) {
          room = {
            name: 'sampleRoom'
          };
          tryCreate(room, function(Id) {
            sampleRoom = Id;
            console.log('sampleRoom Id:', sampleRoom);
          });
        }
      }, function(stCode, msg) {
        console.log('getRooms failed(', stCode, '):', msg);
      });
    })();
    
    • 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
    1. 获取房间列表接口为:GET ${host}/v1/rooms
      1. 见:
    2. request body:null
    3. response body:
    object(Room):
    {
      _id: string,
      name: string,                        // name of the room
      participantLimit: number,            // -1 means no limit
      inputLimit: number,                  // the limit for input stream
      roles: [ object(Role) ],             // role definition
      views: [ object(View) ],             // view definition, represents a mixed stream, empty list means no mixing
      mediaIn: object(MediaIn),            // the input media constraints allowed for processing
      mediaOut: object(MediaOut),          // the output media constraints
      transcoding: object(Transcoding),    // the transcoding control
      notifying: object(Notifying),        // notification control
      selectActiveAudio: boolean,          // select 3 most active audio streams for the room
      sip: object(Sip)                     // SIP configuration
    }
    
    object(Role): {
      role: string,        // name of the role
      publish: {
        video: boolean,    // whether the role can publish video
        audio: boolean     // whether the role can publish audio
      },
      subscribe: {
        video: boolean,    // whether the role can subscribe video
        audio: boolean     // whether the role can subscribe audio
      }
    }
    
    object(View): {
      label: string,
      audio: object(ViewAudio),           // audio setting for the view
      video: object(ViewVideo) | false    // video setting for the view
    }
    
    object(ViewAudio): {
      format: {                // object(AudioFormat)
        codec: string,         // "opus", "pcmu", "pcma", "aac", "ac3", "nellymoser"
        sampleRate: number,    // "opus/48000/2", "isac/16000/2", "isac/32000/2", "g722/16000/1"
        channelNum: number     // E.g "opus/48000/2", "opus" is codec, 48000 is sampleRate, 2 is channelNum
      },
      vad: boolean             // whether enable Voice Activity Detection for mixed audio
    }
    
    object(ViewVideo):{
      format: {                                   // object(VideoFormat)
        codec: string,                            // "h264", "vp8", "h265", "vp9"
        profile: string                           // For "h264" output only, CB", "B", "M", "H"
      },
      parameters: {
        resolution: object(Resolution),           // valid resolutions see [media constraints](#6-Media-Constraints)
        framerate: number,                        // valid values in [6, 12, 15, 24, 30, 48, 60]
        bitrate: number,                          // Kbps
        keyFrameInterval: number,                 // valid values in [100, 30, 5, 2, 1]
      },
      maxInput: number,                           // input limit for the view, positive integer
      bgColor: {
        r: 0 ~ 255,
        g: 0 ~ 255,
        b: 0 ~ 255
      },
      motionFactor: number,                       // float, affact the bitrate
      keepActiveInputPrimary: boolean,            // keep active audio's related video in primary region in the view
      layout: {
        fitPolicy: string,                        // "letterbox" or "crop".
        templates: {
          base: string,                           // template base, valid values ["fluid", "lecture", "void"].
          custom: [                               // user customized layout applied on base
            { region: [ object(Region) ] },       // a region list of length K represents a K-region-layout
            { region: [ object(Region) ] }        // detail of Region refer to the object(Region)
          ]
         }
      }
    }
    
    object(Resolution): {
      width: number,    // resolution width
      height: number    // resolution height
    }
    
    object(Region): {
      id: string,
      shape: string,      // "rectangle"
      area: {
        left: number,     // the left corner ratio of the region, [0, 1]
        top: number,      // the top corner ratio of the region, [0, 1]
        width: number,    // the width ratio of the region, [0, 1]
        height: number    // the height ratio of the region, [0, 1]
      }
    }
    
    object(MediaIn): {
      audio: [ object(AudioFormat) ]// Refers to the AudioFormat above.
      video: [ object(VideoFormat) ]      // Refers to the VideoFormat above.
    }
    
    object(MediaOut): {
      audio: [ object(AudioFormat) ],       // Refers to the AudioFormat above.
      video: {
        format: [ object(VideoFormat) ],    // Refers to the VideoFormat above.
        parameters: {
          resolution: [ string ],           // Array of resolution.E.g. ["x3/4", "x2/3", ... "cif"]
          framerate: [ number ],            // Array of framerate.E.g. [5, 15, 24, 30, 48, 60]
          bitrate: [ number ],              // Array of bitrate.E.g. [500, 1000, ... ]
          keyFrameInterval: [ number ]      // Array of keyFrameInterval.E.g. [100, 30, 5, 2, 1]
        }
      }
    }
    
    object(Transcoding): {
      audio: boolean,                  // if allow transcoding format(opus, pcmu, ...) for audio
      video: {
        parameters: {
          resolution: boolean,         // if allow transcoding resolution for video
          framerate: boolean,          // if allow transcoding framerate for video
          bitrate: boolean,            // if allow transcoding bitrate for video
          keyFrameInterval: boolean    // if allow transcoding KFI for video
        },
        format: boolean                // if allow transcoding format(vp8, h264, ...) for video
      }
    }
    
    object(Notifying): {
      participantActivities: boolean,    // whether enable notification for participantActivities
      streamChange: boolean              // whether enable notification for streamChange
    }
    
    object(Sip): {
      sipServer: string,    // host or IP address for the SIP server
      username: string,     // username of SIP account
      password: string      // password of SIP account
    }
    
    • 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
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    1. object(Room)示例:
    [
        {
            "mediaIn": {
                "audio": [
                    {
                        "codec": "opus",
                        "sampleRate": 48000,
                        "channelNum": 2
                    },
                    {
                        "codec": "isac",
                        "sampleRate": 16000
                    },
                    {
                        "codec": "isac",
                        "sampleRate": 32000
                    },
                    {
                        "codec": "g722",
                        "sampleRate": 16000,
                        "channelNum": 1
                    },
                    {
                        "codec": "pcma"
                    },
                    {
                        "codec": "pcmu"
                    },
                    {
                        "codec": "aac"
                    },
                    {
                        "codec": "ac3"
                    },
                    {
                        "codec": "nellymoser"
                    },
                    {
                        "codec": "ilbc"
                    }
                ],
                "video": [
                    {
                        "codec": "h264"
                    },
                    {
                        "codec": "vp8"
                    },
                    {
                        "codec": "vp9"
                    }
                ]
            },
            "mediaOut": {
                "video": {
                    "parameters": {
                        "resolution": [
                            "x3/4",
                            "x2/3",
                            "x1/2",
                            "x1/3",
                            "x1/4",
                            "hd1080p",
                            "hd720p",
                            "svga",
                            "vga",
                            "qvga",
                            "cif"
                        ],
                        "framerate": [
                            6,
                            12,
                            15,
                            24,
                            30,
                            48,
                            60
                        ],
                        "bitrate": [
                            "x0.8",
                            "x0.6",
                            "x0.4",
                            "x0.2"
                        ],
                        "keyFrameInterval": [
                            100,
                            30,
                            5,
                            2,
                            1
                        ]
                    },
                    "format": [
                        {
                            "codec": "vp8"
                        },
                        {
                            "codec": "h264",
                            "profile": "CB"
                        },
                        {
                            "codec": "h264",
                            "profile": "B"
                        },
                        {
                            "codec": "vp9"
                        }
                    ]
                },
                "audio": [
                    {
                        "codec": "opus",
                        "sampleRate": 48000,
                        "channelNum": 2
                    },
                    {
                        "codec": "isac",
                        "sampleRate": 16000
                    },
                    {
                        "codec": "isac",
                        "sampleRate": 32000
                    },
                    {
                        "codec": "g722",
                        "sampleRate": 16000,
                        "channelNum": 1
                    },
                    {
                        "codec": "pcma"
                    },
                    {
                        "codec": "pcmu"
                    },
                    {
                        "codec": "aac",
                        "sampleRate": 48000,
                        "channelNum": 2
                    },
                    {
                        "codec": "ac3"
                    },
                    {
                        "codec": "nellymoser"
                    },
                    {
                        "codec": "ilbc"
                    }
                ]
            },
            "transcoding": {
                "video": {
                    "parameters": {
                        "resolution": true,
                        "framerate": true,
                        "bitrate": true,
                        "keyFrameInterval": true
                    },
                    "format": true
                },
                "audio": true
            },
            "notifying": {
                "participantActivities": true,
                "streamChange": true
            },
            "inputLimit": -1,
            "participantLimit": -1,
            "selectActiveAudio": false,
            "roles": [
                {
                    "role": "presenter",
                    "publish": {
                        "audio": true,
                        "video": true
                    },
                    "subscribe": {
                        "audio": true,
                        "video": true
                    }
                },
                {
                    "role": "viewer",
                    "publish": {
                        "audio": false,
                        "video": false
                    },
                    "subscribe": {
                        "audio": true,
                        "video": true
                    }
                },
                {
                    "role": "audio_only_presenter",
                    "publish": {
                        "audio": true,
                        "video": false
                    },
                    "subscribe": {
                        "audio": true,
                        "video": false
                    }
                },
                {
                    "role": "video_only_viewer",
                    "publish": {
                        "audio": false,
                        "video": false
                    },
                    "subscribe": {
                        "audio": false,
                        "video": true
                    }
                },
                {
                    "role": "sip",
                    "publish": {
                        "audio": true,
                        "video": true
                    },
                    "subscribe": {
                        "audio": true,
                        "video": true
                    }
                }
            ],
            "_id": "62b95bd27ff8054480cbb73a",
            "name": "sampleRoom",
            "views": [
                {
                    "audio": {
                        "format": {
                            "codec": "opus",
                            "sampleRate": 48000,
                            "channelNum": 2
                        },
                        "vad": true
                    },
                    "video": {
                        "parameters": {
                            "resolution": {
                                "width": 640,
                                "height": 480
                            },
                            "framerate": 24,
                            "keyFrameInterval": 100
                        },
                        "bgColor": {
                            "r": 0,
                            "g": 0,
                            "b": 0
                        },
                        "layout": {
                            "templates": {
                                "base": "fluid",
                                "custom": []
                            },
                            "fitPolicy": "letterbox"
                        },
                        "format": {
                            "codec": "vp8"
                        },
                        "maxInput": 16,
                        "motionFactor": 0.8,
                        "keepActiveInputPrimary": false
                    },
                    "label": "common"
                }
            ],
            "__v": 0,
            "id": "62b95bd27ff8054480cbb73a"
        }
    ]
    
    • 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
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    1. 生成房间接口:POST ${host}/v1/rooms
    2. request body:
    object(RoomConfig):          // Configuration used to create room
    {
        name:name,               // Name of the room, required
        options: object(Room)    // Subset of the object(Room) definition above, optional.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. object(RoomConfig) 示例:
    {
      "name": "sampleRoom"
    }
    
    • 1
    • 2
    • 3
    1. response body:同object(Room)。
  • 相关阅读:
    Ubuntu QtCreator不能输入中文,可以从其他位置复制中文
    系统集成|第二十一章(笔记)
    Java当中:this关键字
    C/S架构学习之TCP的三次握手和四次挥手
    react18【系列实用教程】useEffect —— 副作用操作 (2024最新版)
    openEuler embedded编译镜像报错You can set INITRAMFS_MAXSIZE a larger value
    游戏公会在去中心化游戏中的未来
    区块链应用:椭圆曲线数字签名算法ECDSA
    QT社团管理系统
    【云原生 | Kubernetes 系列】---altermanager消息配置和pushgateway
  • 原文地址:https://blog.csdn.net/weixin_41910694/article/details/127694349