Spec-Zone.ru › Web APIs

Использование кодированных преобразований WebRTC

Ограниченная доступность

Эта функция не относится к Baseline, так как она не работает во всех наиболее широко используемых браузерах.

  • Узнать больше
  • Полная совместимость
  • Отправить отзыв

Кодированные преобразования WebRTC предоставляют механизм для вставки высокопроизводительного API потоков для изменения кодированных видео- и аудиокадров входящих и исходящих потоков WebRTC. Это позволяет использовать такие сценарии, как шифрование кодированных кадров сторонним кодом.

API определяет объекты для основного потока и потока-рабочего. Интерфейс основного потока — это экземпляр RTCRtpScriptTransform, который при создании определяет Worker, который должен реализовать код преобразователя. Преобразование, выполняемое в рабочем потоке, вставляется в поток WebRTC, входящий или исходящий, путем добавления RTCRtpScriptTransform в RTCRtpReceiver.transform или RTCRtpSender.transform соответственно.

Соответствующий объект RTCRtpScriptTransformer создается в рабочем потоке, который имеет свойство ReadableStream readable, свойство WritableStream writable, и объект options, переданный из конструктора связанного RTCRtpScriptTransform. Кодированные видеокадры (RTCEncodedVideoFrame) или аудиокадры (RTCEncodedAudioFrame) из потока WebRTC добавляются в очередь readable для обработки.

RTCRtpScriptTransformer доступен коду как свойство transformer события rtctransform, которое срабатывает в глобальном пространстве имен рабочего потока всякий раз, когда кодированный кадр добавляется в очередь для обработки (и первоначально при создании соответствующего RTCRtpScriptTransform). Код рабочего потока должен реализовать обработчик события, который считывает кодированные кадры из transformer.readable, модифицирует их по необходимости и записывает их в transformer.writable в том же порядке и без дублирования.

Хотя интерфейс не накладывает никаких других ограничений на реализацию, естественным способом преобразования кадров является создание цепочки обработки потоков, которая отправляет кадры, помещенные в очередь в потоке event.transformer.readable через TransformStream в поток event.transformer.writable. Мы можем использовать свойство event.transformer.options для настройки любого кода преобразования, который зависит от того, входящие кадры из пакетизатора или исходящие кадры из кодека отправляются в очередь преобразованием.

Интерфейс RTCRtpScriptTransformer также предоставляет методы, которые могут быть использованы при отправке кодированного видео для получения кодека, генерирующего «ключевой» кадр, и при получении видео для запроса отправки нового ключевого кадра. Это может быть полезно, чтобы позволить получателю начать просмотр видео быстрее, если, например, они присоединяются к конференц-звонку, когда отправляются дельта-кадры.

В следующих примерах приведены более конкретные примеры использования фреймворка с использованием реализации на основе TransformStream.

Проверка поддержки кодированных преобразований

Проверьте поддержку кодированных преобразований, проверив существование RTCRtpSender.transform (или RTCRtpReceiver.transform):

const supportsEncodedTransforms =
  window.RTCRtpSender && "transform" in RTCRtpSender.prototype;

Добавление преобразования для исходящих кадров

Преобразование, выполняемое в рабочем потоке, вставляется в исходящий поток WebRTC путем назначения его соответствующего RTCRtpScriptTransform свойству RTCRtpSender.transform для исходящей дорожки.

Этот пример демонстрирует, как можно транслировать видео с веб-камеры пользователя по WebRTC, добавив кодированное преобразование WebRTC для изменения исходящих потоков. Код предполагает, что есть объект RTCPeerConnection с именем peerConnection, который уже подключен к удаленному участнику.

Сначала мы получаем MediaStreamTrack, используя getUserMedia() для получения видео MediaStream с устройства ввода, а затем метод MediaStream.getTracks() для получения первой MediaStreamTrack в потоке.

Дорожка добавляется к подключению к peer с помощью addTrack(), что запускает её трансляцию удаленному участнику. Метод addTrack() возвращает RTCRtpSender, используемый для отправки дорожки.

// Get Video stream and MediaTrack
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getTracks();
const videoSender = peerConnection.addTrack(track, stream);

Затем создаётся RTCRtpScriptTransform, принимающий скрипт рабочего потока, который определяет преобразование, и необязательный объект, который можно использовать для передачи произвольных сообщений в рабочий поток (в этом случае мы использовали свойство name со значением "senderTransform", чтобы указать рабочему потоку, что это преобразование будет добавлено в исходящий поток). Мы добавляем преобразование в исходящий поток, назначив его свойству RTCRtpSender.transform.

// Create a worker containing a TransformStream
const worker = new Worker("worker.js");
videoSender.transform = new RTCRtpScriptTransform(worker, {
  name: "senderTransform",
});

Раздел Использование отдельных преобразований отправителя и получателя ниже показывает, как name можно использовать в рабочем потоке.

Обратите внимание, что вы можете добавить преобразование в любое время, но добавив его сразу после вызова addTrack(), преобразование получит первый кодированный кадр, который отправляется.

Добавление преобразования для входящих кадров

Преобразование, выполняемое в рабочем потоке, вставляется во входящий поток WebRTC путем назначения его соответствующего RTCRtpScriptTransform свойству RTCRtpReceiver.transform для входящей дорожки.

Этот пример демонстрирует, как добавить преобразование для изменения входящего потока. Код предполагает, что есть объект RTCPeerConnection с именем peerConnection, который уже подключен к удаленному участнику.

Сначала мы добавляем обработчик события track события для захвата события, когда peer начинает принимать новую дорожку. В обработчике мы создаем RTCRtpScriptTransform и добавляем его к event.receiver.transform (event.receiver — это RTCRtpReceiver). Как и в предыдущем разделе, конструктор принимает объект со свойством name, но здесь мы используем receiverTransform в качестве значения, чтобы указать рабочему потоку, что кадры являются входящими.

peerConnection.ontrack = (event) => {
  const worker = new Worker("worker.js");
  event.receiver.transform = new RTCRtpScriptTransform(worker, {
    name: "receiverTransform",
  });
  received_video.srcObject = event.streams[0];
};

Обратите внимание, что вы можете добавить поток преобразования в любое время. Однако добавление его в обработчике события track гарантирует, что поток преобразования получит первый кодированный кадр для дорожки.

Реализация в рабочем потоке

Скрипт рабочего потока должен реализовать обработчик события rtctransform, создавая цепочку обработки потоков, которая передает поток event.transformer.readable (ReadableStream) через TransformStream в поток event.transformer.writable (WritableStream).

Рабочий поток может поддерживать преобразование входящих или исходящих кодированных кадров, или обоих, и преобразование может быть жестко закодировано или настроено в процессе выполнения с использованием информации, переданной веб-приложением.

Базовое кодированное преобразование WebRTC

Пример ниже демонстрирует базовое кодированное преобразование WebRTC, которое инвертирует все биты в очередях кадров. Оно не использует и не нуждается в параметрах, переданных из основного потока, так как тот же алгоритм может быть использован в потоке отправителя для инверсии битов и в потоке получателя для их восстановления.

Код реализует обработчик события rtctransform. Он создает TransformStream, затем передает его с помощью ReadableStream.pipeThrough(), и, наконец, передает в event.transformer.writable с помощью ReadableStream.pipeTo().

addEventListener("rtctransform", (event) => {
  const transform = new TransformStream({
    start() {}, // Called on startup.
    flush() {}, // Called when the stream is about to be closed.
    async transform(encodedFrame, controller) {
      // Reconstruct the original frame.
      const view = new DataView(encodedFrame.data);

      // Construct a new buffer
      const newData = new ArrayBuffer(encodedFrame.data.byteLength);
      const newView = new DataView(newData);

      // Negate all bits in the incoming frame
      for (let i = 0; i < encodedFrame.data.byteLength; ++i) {
        newView.setInt8(i, ~view.getInt8(i));
      }

      encodedFrame.data = newData;
      controller.enqueue(encodedFrame);
    },
  });
  event.transformer.readable
    .pipeThrough(transform)
    .pipeTo(event.transformer.writable);
});

Реализация кодированного преобразования WebRTC похожа на «общий» TransformStream, но с некоторыми важными отличиями. Как и в общем потоке, её конструктор принимает объект, определяющий необязательный метод start(), который вызывается при создании, метод flush(), который вызывается, когда поток закрывается, и метод transform(), который вызывается каждый раз, когда есть пакет для обработки. В отличие от обычного конструктора, любые свойства writableStrategy или readableStrategy объекта конструктора игнорируются, и стратегия очереди полностью управляется агентом пользователя.

Метод transform() также отличается тем, что получает либо RTCEncodedVideoFrame, либо RTCEncodedAudioFrame, а не общий «пакет». Фактический код, показанный здесь для метода, не является примечательным, за исключением того, что он демонстрирует, как преобразовать кадр в форму, в которой вы можете его изменить и добавить в очередь впоследствии в поток.

Использование отдельных преобразований отправителя и получателя

Предыдущий пример работает, если функция преобразования одинакова при отправке и приёме, но во многих случаях алгоритмы будут отличаться. Можно использовать отдельные скрипты обработчика для отправителя и получателя, или обработать оба случая в одном обработчике, как показано ниже.

Если обработчик используется для отправителя и получателя, ему нужно знать, является ли текущая закодированная кадр исходящей от кодека или входящей от пакетника. Эту информацию можно указать, используя второй вариант в RTCRtpScriptTransform конструкторе. Например, мы можем определить отдельный RTCRtpScriptTransform для отправителя и получателя, передавая один и тот же обработчик и объект опций с свойством name, которое указывает, используется ли преобразование в отправителе или получателе (как показано в предыдущих разделах выше). Затем эта информация будет доступна в обработчике в event.transformer.options.

В этом примере мы реализуем обработчик события onrtctransform в глобальном объекте области видимости выделенного обработчика. Значение свойства name используется для определения, какой TransformStream следует создать (фактические методы конструктора не показаны).

// Code to instantiate transform and attach them to sender/receiver pipelines.
onrtctransform = (event) => {
  let transform;
  if (event.transformer.options.name == "senderTransform")
    transform = createSenderTransform(); // returns a TransformStream
  else if (event.transformer.options.name == "receiverTransform")
    transform = createReceiverTransform(); // returns a TransformStream
  else return;
  event.transformer.readable
    .pipeThrough(transform)
    .pipeTo(event.transformer.writable);
};

Обратите внимание, что код для создания цепочки каналов такой же, как и в предыдущем примере.

Динамическое взаимодействие с преобразователем

В RTCRtpScriptTransform конструкторе можно передавать опции и передаваемые объекты в обработчик. В предыдущем примере мы передавали статическую информацию, но иногда вам может потребоваться изменить алгоритм преобразования в обработчике во время выполнения или получить информацию обратно от обработчика. Например, звонок WebRTC, поддерживающий шифрование, может потребоваться добавить новый ключ к алгоритму, используемому преобразователем.

Хотя можно обмениваться информацией между обработчиком, выполняющим код преобразования, и основным потоком, используя Worker.postMessage(), проще использовать общий канал MessageChannel в качестве опции RTCRtpScriptTransform конструктора, так как контекст канала будет непосредственно доступен в event.transformer.options при обработке нового закодированного кадра.

Код ниже создаёт общий канал MessageChannel и передаёт его второй порт в обработчик. После этого основной поток и преобразователь могут обмениваться сообщениями с использованием первого и второго портов.

// Create a worker containing a TransformStream
const worker = new Worker("worker.js");

// Create a channel
// Pass channel.port2 to the transform as a constructor option
// and also transfer it to the worker
const channel = new MessageChannel();
const transform = new RTCRtpScriptTransform(
  worker,
  { purpose: "encrypt", port: channel.port2 },
  [channel.port2],
);

// Use the port1 to send a string.
// (we can send and transfer basic types/objects).
channel.port1.postMessage("A message for the worker");
channel.port1.start();

В обработчике порт доступен как event.transformer.options.port. Код ниже демонстрирует, как можно прослушивать событие message порта для получения сообщений из основного потока. Также можно использовать порт для отправки сообщений обратно в основной поток.

event.transformer.options.port.onmessage = (event) => {
  // The message payload is in 'event.data';
  console.log(event.data);
};

Выполнение ключевого кадра

Необработанное видео редко отправляется или хранится, так как для представления каждого кадра как полного изображения требуется много места и пропускной способности. Вместо этого кодеки периодически генерируют «ключевой кадр», содержащий достаточно информации для построения полного изображения, а между ключевыми кадрами отправляют «кадры-приращения», содержащие только изменения с момента последнего кадра-приращения. Хотя это намного эффективнее, чем отправка необработанного видео, это означает, что для отображения изображения, связанного с конкретным кадром-приращением, требуется последний ключевой кадр и все последующие кадры-приращения.

Это может вызвать задержку для новых пользователей, присоединяющихся к приложению WebRTC-конференции, так как они не могут отображать видео, пока не получат свой первый ключевой кадр. Аналогично, если для шифрования кадров использовалось закодированное преобразование, получатель не сможет отобразить видео, пока не получит первый ключевой кадр, зашифрованный его ключом.

Для того, чтобы новый ключевой кадр мог быть отправлен как можно раньше при необходимости, объект RTCRtpScriptTransformer в event.transformer имеет два метода: RTCRtpScriptTransformer.generateKeyFrame(), который заставляет кодек генерировать ключевой кадр, и RTCRtpScriptTransformer.sendKeyFrameRequest(), который получатель может использовать для запроса ключевого кадра у отправителя.

Пример ниже показывает, как основной поток может передать ключ шифрования в преобразователь отправителя и запустить кодек для генерации ключевого кадра. Обратите внимание, что основной поток не имеет прямого доступа к объекту RTCRtpScriptTransformer, поэтому ему нужно передать ключ и идентификатор ограничения («rid») в обработчик («rid» — это идентификатор потока, который указывает кодер, который должен сгенерировать ключевой кадр). Здесь мы делаем это с помощью MessageChannel, используя ту же схему, что и в предыдущем разделе. Код предполагает, что уже есть соединение с партнёром и что videoSender — это RTCRtpSender.

const worker = new Worker("worker.js");
const channel = new MessageChannel();

videoSender.transform = new RTCRtpScriptTransform(
  worker,
  { name: "senderTransform", port: channel.port2 },
  [channel.port2],
);

// Post rid and new key to the sender
channel.port1.start();
channel.port1.postMessage({
  rid: "1",
  key: "93ae0927a4f8e527f1gce6d10bc6ab6c",
});

Обработчик события rtctransform в обработчике получает порт и использует его для прослушивания событий message из основного потока. При получении события он получает rid и key, а затем вызывает generateKeyFrame().

event.transformer.options.port.onmessage = (event) => {
  const { rid, key } = event.data;
  // key is used by the transformer to encrypt frames (not shown)

  // Get codec to generate a new key frame using the rid
  // Here 'rcEvent' is the rtctransform event.
  rcEvent.transformer.generateKeyFrame(rid);
};

Код для получателя для запроса нового ключевого кадра будет почти идентичным, за исключением того, что «rid» не указан. Вот код только для обработчика сообщений порта:

event.transformer.options.port.onmessage = (event) => {
  const { key } = event.data;
  // key is used by the transformer to decrypt frames (not shown)

  // Request sender to emit a key frame.
  transformer.sendKeyFrameRequest();
};

Совместимость с браузерами

Рабочие столы Мобильные устройства
Chrome Edge Firefox Opera Safari Chrome для Android Firefox для Android Opera для Android Safari для iOS Samsung Internet WebView для Android
Using_Encoded_Transforms Нет Нет 117 Нет 15.4 Нет 117 Нет 15.4 Нет Нет

См. также

  • RTCRtpScriptTransform
  • RTCRtpReceiver.transform
  • RTCRtpSender.transform
  • rtctransform событие
  • RTCTransformEvent
  • RTCRtpScriptTransformer
  • RTCEncodedVideoFrame
  • RTCEncodedAudioFrame

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Using_Encoded_Transforms

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API