Análisis de señalización del servidor OWT (Parte 1) [Open WebRTC Toolkit]

Análisis de señalización del servidor OWT (Open WebRTC Toolkit) (Parte 1)


Tabla de contenido

  1. Análisis de señalización del servidor OWT
  2. Análisis del proceso de interacción de señalización del servidor OWT

Debido a que el análisis de señalización contiene algunos códigos y formatos, el artículo es muy extenso, por lo que se divide en dos registros, artículos relacionados con OWT (Open WebRTC Toolkit) Artículos relacionados
:

  1. Instale el servidor OWT [Open WebRTC Toolkit] en el entorno Ubuntu
  2. Instale el servidor OWT [Open WebRTC Toolkit] en el entorno Docker
  3. Análisis de la arquitectura general del servidor OWT [Open WebRTC Toolkit]
  4. Análisis de señalización del servidor OWT (Parte 1) [Open WebRTC Toolkit]
  5. Análisis de señalización del servidor OWT (Parte 2) [Open WebRTC Toolkit]
  6. Estructura del proceso del servidor OWT y flujo de procesamiento del código JS[Open WebRTC Toolkit]
  7. API REST del servidor OWT

1. Análisis de señalización del servidor OWT

  1. El protocolo de señalización OWT se divide en dos partes: la API RESTful y la conexión persistente SocketIO.La API RESTful la proporciona la API de administración y la conexión persistente SocketIO la proporciona WebRTC Portal.
  2. Pero en lugar de llamar directamente a la API de administración, llama a la interfaz del servidor de muestras de conferencias, que es un encapsulado de la API de administración. El código fuente está en owt-client-javascript https://github.com/open-webrtc- toolkit/owt-client-javascript/tree/master/src/samples/conference project.
  3. El proceso de interacción de señalización de OWT Server WebRTC es el siguiente:
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

2. Análisis del proceso de interacción de señalización del servidor OWT

  1. Tome owt-client-javascript como cliente y owt-server como servidor como ejemplo.
  2. Después de instalar el servidor owt, puede ver la interfaz de llamada en https://localhost:3004 o https://ip:3004 .
  3. La función de entrada de owt-client-javascript es window.onload de src/samples/conference/public/scripts/index.js (presione F12 en la interfaz de llamada para verlo), el código simplificado es el siguiente:
    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. Se puede ver que el cliente primero creará un token y luego se unirá a la sala de reuniones después de obtener el token.

1. Solicitud HTTP POST para crear un token

  1. Cuando un usuario se une a una sala, se envía una solicitud POST al servidor para devolver una cadena de token.
  2. POST ${host}/v1/rooms/{roomId}/tokens
  3. cuerpo de la solicitud:
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. en:
    1. El campo de usuario es la identificación del usuario.
    2. El servidor configura el significado del valor del campo de rol, que indica si el usuario puede publicar y suscribirse a flujos de audio y video. Los siguientes roles predeterminados se definen en source/data_access/defaults.js:
      1. Usar el presentador significa que tanto el audio como el video se pueden publicar y suscribir.
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. El cuerpo de la respuesta es la cadena de token cifrada, por ejemplo:
eyJ0b2tlbklkIjoiNjM2MjE2YWExNmY5ZWI0MjkxNjMwY2RiIiwiaG9zdCI6ImludHJhLXJ0Yy5zbWFydGVkdS5sZW5vdm8uY29tOjgwODAiLCJzZWN1cmUiOnRydWUsInNpZ25hdHVyZSI6Ik56YzBNamRsWWpNMk5EQXdOV0ZpWkRNME1HUXdPRE16TjJFek1XUTFNMlU0TVRjeU5UQTFaall6TnpGaU1UQXdNVFJsTkRRMU1ESXhaV05tTm1NMVpBPT0ifQ==

1. Código relacionado con el cliente

  1. Código relacionado con el cliente:
    1. Ejemplo: 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. La solicitud POST enviada por el cliente no llama directamente a la API de administración, sino que llama a la interfaz del servidor de muestras de conferencias, que es un encapsulado de la API de administración, y el código fuente está en owt-client-javascript https:// github .com/open-webrtc-toolkit/owt-client-javascript/tree/master/src/samples/conference project.
  2. Código relacionado con el servidor de ejemplo de conferencia:
    1. Ejemplo: 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. Se llamará al método createToken para reenviar la solicitud a la API de administración:
    1. Ubicación: 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 = '[email protected]';
  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);
  };

2. Código relacionado con el servidor

  1. La API de administración en el servidor owt recibe solicitudes:
    2. Ubicación: 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. Se llamará a la función generateToken para generar una cadena de token, el código es el siguiente:
/*
 * 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. En la función generateToken, el objeto token contiene:
    1. Nombre de usuario: usuario
    2. ID de la habitación: roomId
    3. permiso: rol
    4. ID de servicio: ID de servicio
    5. Marca de tiempo de creación: CreationData
    6. Preferencia: origen
    7. número aleatorio: código
  2. La función requestHandler.schedulePortal programa el portal WebRTC, proporciona los datos de solicitud del cliente (proveedor de servicios de Internet ISP y región) y requiere la asignación de una dirección de portal WebRTC.
    1. Análisis de seguimiento de la lógica de programación.
  3. La lógica del código para generar la cadena del token es la siguiente:
    1. Ubicación: fuente/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. Ejemplo de token para una fácil comprensión:
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

3. Acerca de la sala de muestras

  1. Cuando Conference Sample Server recibe la solicitud de tokens, si el cuerpo de la solicitud no lleva roomId, asignará el roomId predeterminado de sampleRoom.
    1. Ejemplo: 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. Una sala llamada "sampleRoom" se creará automáticamente al inicio, el código correspondiente:
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. La interfaz para obtener la lista de salas es: GET ${host}/v1/rooms
    1. Ver:
  2. cuerpo de la solicitud: nulo
  3. cuerpo de respuesta:
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. Ejemplo de objeto (Habitación):
[
    {
    
    
        "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. Generar interfaz de sala: POST ${host}/v1/rooms
  2. cuerpo de la solicitud:
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. ejemplo de objeto (RoomConfig):
{
    
    
  "name": "sampleRoom"
}
  1. cuerpo de respuesta:同objeto(Habitación)。

Supongo que te gusta

Origin blog.csdn.net/weixin_41910694/article/details/127694349
Recomendado
Clasificación