📷
WebRTC (旧)Insertable Streams と ScriptTransform の相互通信実験
WebRTC Insertable Stream とは
WebRTCで映像や音声のエンコード済みのデータを取得、加工できる仕組み。エンコード後、パケット分割前のデータを操作することができるため、主にEnd-to-End Encryptionの用途で使われる。
- 参考: webrtcHacks True End-to-End Encryption with WebRTC Insertable Streams
非標準の(旧)Insertabe Streamと、現在標準化プロセスに乗っている(現)Insertable Streamである「WebRTC Encoded Transform」がある。
(旧)Insertable Stream のおさらい
Chromeでサポートされている。
- 2023年9月現在この仕様は標準化のプロセスから外れていて、Chrome独自機能として残っている
- 仕様のドラフトや、Explainerは残っていない
- W3C WebRTC WG Meeting の資料が残っている(2021年2月)
使い方
(1) RTCPeerConnection のインスタンスを作る際に、オプションを指定
let peer = new RTCPeerConnection({ encodedInsertableStreams: true });
(2) 送信側の対応
- RTCRtpSender.createEncodedStreams() で ReadableStream と WritableStreamを取り出す
- 変換処理を挟み込む
function setupSenderTransform(sender) {
const senderStreams = sender.createEncodedStreams();
const readableStream = senderStreams.readable;
const writableStream = senderStreams.writable;
const transformStream = new TransformStream({
transform: encodeFunction, // 用意した変換関数を指定
});
// 変換を挟んで、readableStreamとwritableStreamを接続
readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream);
}
// RTCRtpSenderを取得し、変換関数をセットアップする
peer.getSenders().forEach(setupSenderTransform);
(3) 受信側の指定
- RTCRtpReceiver.createEncodedStreams() で ReadableStream と WritableStreamを取り出す
- 変換処理を挟み込む
function setupReceiverTransform(receiver) {
const receiverStreams = receiver.createEncodedStreams();
const readableStream = receiverStreams.readable;
const writableStream = receiverStreams.writable;
const transformStream = new TransformStream({
transform: decodeFunction, // 用意した逆変換関数を指定
});
// 逆変換を挟んで、readableStreamとwritableStreamを接続
readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream);
}
// RTCPeerConnection.ontrack()イベント等でRTCRtpReceiverを取得し、変換関数をセットアップする
peer.ontrack = function (evt) {
setupReceiverTransform(evt.receiver);
}
Workerの利用
- 変換処理をメインスレッドで行うことも可能
- 変換処理をWorkerスレッドで行うことも可能
- 重い処理の場合は、Workerスレッドで行うことが推奨
- メインスレッドで readableStream, writableStreamを取得し、workerスレッドに渡して利用する
GitHub Pagesで試す
- Chrome m86以上で、https://mganeko.github.io/webrtc_insertable_demo/insertable_stream.html にアクセス
- [Start Video]ボタンをクリックし、カメラから映像を取得
- 左に映像が表示される
- [use Audio]がチェックされていると、マイクの音声も取得
- [Connect]ボタンをクリック
- ブラウザの単一タブ内で2つのPeerConnectionの通信が確立
- 右に受信した映像が表示される
- ストリームデータの加工
- 左の[XOR Sender data]をチェックすると、送信側でストリームのデータを加工
- 右の[XOR Receiver data]をチェックすると、受信側でストリームのデータを逆加工
- どちらも加工しない、あるいは加工する場合のみ、正常に右の映像が表示できる
- ※映像の乱れや回復が反映されるまで、時間がかかることがあります
Chromeの例
参考
ScriptTransform とは
2023年9月現在、WebRTCの仕様の標準化検討中の仕様で、Safari 15.4〜、Firefox 117〜 でサポート。
- Explainer - WebRTC Insertable Streams
- 仕様のドラフト: WebRTC Encoded Transform
旧Insertable Streamとは異なり、Workerの利用が前提となっている。
使い方
(1) RTCPeerConnection のインスタンスを作る際に、オプションを指定
let peer = new RTCPeerConnection({ encodedInsertableStreams: true });
(2) 送信側の対応
- 変換処理を行う、workerを用意する
- RTCRtpScriptTransformのインスタンスを生成し、RTCRtpSenderに設定する
// --- workerを読み込む ---
const worker = new Worker('ワーカーのjsファイル名', {name: '適切な名前'});
function setupSenderTransform(sender) {
sender.transform = new RTCRtpScriptTransform(worker, {operation: 'encode'}); // workerのイベントを呼び出す
return;
}
// RTCRtpSenderを取得し、変換関数をセットアップする
peer.getSenders().forEach(setupSenderTransform);
workerの例
// ScriptTransform利用時のイベント
onrtctransform = (event) => {
const transformer = event.transformer;
const readable = transformer.readable;
const writable = transformer.writable;
if (transformer.options.operation === "encode") {
const transformStream = new TransformStream({
transform: encodeFunction, // 用意した変換関数を指定
});
// 変換を挟んで、readableStreamとwritableStreamを接続
readable
.pipeThrough(transformStream)
.pipeTo(writable);
}
else if (transformer.options.operation === "decode") {
const transformStream = new TransformStream({
transform: decodeFunction, // 用意した逆変換関数を指定
});
// 逆変換を挟んで、readableStreamとwritableStreamを接続
readable
.pipeThrough(transformStream)
.pipeTo(writable);
}
}
(3) 受信側の指定
- 変換処理を行う、workerを用意する
- RTCRtpScriptTransformのインスタンスを生成し、RTCRtpReceiverに設定する
// --- workerを読み込む ---
const worker = new Worker('ワーカーのjsファイル名', {name: '適切な名前'});
function setupReceiverTransform(sender) {
sender.transform = new RTCRtpScriptTransform(worker, {operation: 'decode'}); // workerのイベントを呼び出す
return;
}
// RTCPeerConnection.ontrack()イベント等でRTCRtpReceiverを取得し、変換関数をセットアップする
peer.ontrack = function (evt) {
setupReceiverTransform(evt.receiver);
}
GitHub Pagesで試す
- Safari 16.xか、Firefox117以上で https://mganeko.github.io/webrtc_insertable_demo/script_transform.html にアクセス
- [Start Video]ボタンをクリックし、カメラから映像を取得
- 左に映像が表示される
- [use Audio]がチェックされていると、マイクの音声も取得
- [Connect]ボタンをクリック
- ブラウザの単一タブ内で2つのPeerConnectionの通信が確立
- 右に受信した映像が表示される
- ストリームデータの加工
- 左の[XOR Sender data]をチェックすると、送信側でストリームのデータを加工
- 右の[XOR Receiver data]をチェックすると、受信側でストリームのデータを逆加工
- どちらも加工しない、あるいは加工する場合のみ、正常に右の映像が表示できる
- ※映像の乱れや回復が反映されるまで、時間がかかることがあります
Safariの例
相互通信のテスト
新旧Insertable StreamはAPIは違うものの、やっていることは同等なので、通信の互換性はあるはず。そこで相互通信のテストを実施。
概要
- 通信方式 ... P2P
- シグナリング ... Ayame-Laboを利用
- sender ... 送信側。カメラ映像を取得して送信
- receiver ... 受信側。映像を受信して送信
- 簡易暗号化 ... データをXORでビット反転
- チェックボックスで、ビット反転をon/offする
- sender/receiverの双方がon、または双方がoffの場合に正常に通信できる
GitHub Pagesで試す
ScriptTransformがサポートされている場合はそちらを利用(Safari/Firefoxの場合)、createEncodedStreams(旧Insertable Stream)がサポートされている場合はそれを利用する(Chromeの場合)。
- 送信側 sender をブラウザで開く
- RoomID を指定
- [Start Video]ボタンをクリックし、カメラから映像を取得
映像が表示される- [use Audio]がチェックされていると、マイクの音声も取得
- [Connect]ボタンをクリック
- Ayame-Laboに接続、シグナリング待ち
- 受信側 receiver
- RoomID を指定
- [Connect]ボタンをクリック
- Ayame-Laboに接続、シグナリング開始
- P2P通信が確立し、受信した映像が表示される
- ストリームデータの加工
- 送信側の[Sender XOR Simple Encryption]をチェックすると、送信側でストリームのデータを加工
- 受信側の[Receiver XOR Simple Decryption]をチェックすると、受信側でストリームのデータを逆加工
- どちらも加工しない、あるいは加工する場合のみ、正常に受信映像が表示できる
Sender:Safari → Reciever:Chrome の例
GitHub でコードを見る
リポジトリ mganeko/webrtc_insertable_demo
Discussion