WebRTC Chrome 动态修改编码器

新版本的WebRTC支持在已经建立连接的情况下,推流端动态修改编码器。在Chrome119版本中可以体验这个功能了。可以在chrome 119版本,通过两个网页进行webrtc通信,使用chrome://webrtc-internals 来查看使用的编码器。

实现方法

核心代码

在RTCPeerConnection建立连接之后,在推流端通过RTPSender.setParameters方法,可以支持动态切换编码器。

let sender = pc_pub.getSenders()[0];
let param = sender.getParameters();
param.encodings[0].codec = codec;
sender.setParameters(param).then(() => {
  console.log("change encoder suc");
}).catch((err) => {
  console.log("change encoder fail");
})

步骤

使用WebRTC建立连接的部分可以参考: 前端使用WebRTC———局域网内单向通信。本文的代码就是在这个代码的基础上增加了切换编码器的逻辑。

1. 获取RTCRtpSender

在RTCPeerConnection建立连接完成以后,可以通过RTCPeerConnection.getSender方法来获取RTCRtpSender对象。因为例子中只有一个发送流,所以在返回的数据中直接取第一个就可以

let sender = pc_pub.getSenders()[0];

2. 获取RTCRtpSendParameters

获取RTCRtpSendParameters 通过 RTCRtpSender.getParameters 方法来获取RTCRtpSendParameters对象。

let param = sender.getParameters();

3. 获取当前支持的编码器 RTCRtpSendParameters.codecs字段可以看到当前连接支持的所有编码器,如下图:

WebRTC Chrome 动态修改编码器

mimeType字段的内容是video/VP8、video/VP9、video/AV1、video/H264这些是编码器。其他的不是编码器。

4. 设置新的编码器

RTCRtpSendParameters.codecs中选择好编码器之后,将编码器赋值param.encodings[0].codec;然后通过RTCRtpSendParameters.setParameters方法将param设置给RTCRtpSender,通过返回的Promise来判断设置是否生效了。

// codec是从RTCRtpSendParameters.codecs中选择好的内容
param.encodings[0].codec = codec;
sender.setParameters(param).then(() => {
  console.log("change encoder suc");
}).catch((err) => {
  console.log("change encoder fail");
})

实际应用

当使用WebRTC进行通信时,有可能在拉流端有些异常的行为,比如解码失败、解码耗时高等情况,此时可以通过以上方法动态的切换编码器,对应的拉流端也会切换到新的解码器进行解码。

代码

可以直接从gittee获取源代码,也可以直接使用下面的代码

服务端

要注意server依赖了nodejs-websocket模块。

var ws = require("nodejs-websocket");


var pub_ws = null;
var sub_ws = null;


function start() {
  var msg = JSON.stringify({ type: "start" });
  pub_ws.send(msg);
}

var server = ws.createServer(function (conn) {
  // 收到websocket连接
  conn.on("text", function (str) {
    if (pub_ws === conn) {
      if (sub_ws) {
        sub_ws.send(str);
      }
    } else if (sub_ws === conn) {
      if (pub_ws) {
        pub_ws.send(str);
      }
    } else {
      let obj = JSON.parse(str);
      if (obj.type === 'publish') {
        pub_ws = conn;
        if (sub_ws) {
          start();
        }
      } else if (obj.type === 'subscribe') {
        sub_ws = conn;
        if (pub_ws) {
          start();
        }
      }
    }
  })

  conn.on("error", function (event) {

  });

  conn.on("close", function (code, reason) {
    if (conn === pub_ws) {
      console.log("remove pub")
      pub_ws = null;
    } else if (conn === sub_ws) {
      console.log("remove sub")
      sub_ws = null;
    }
  })
}).listen(9000);

推流端

推流页面需要用localhost访问,因为获取设备需要https或者localhost(127.0.0.1)才可以。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>推流页面</title>
  </head>

  <body>
    <video id="localStream" style="width: 320px; height: 240px;" autoplay muted></video>
    <select id="encoder_select"></select>
    <script>
      // 获取摄像头返回的MediaStream
      let localStream = null;

      // 显示本地画面的VideoElement
      let localVideo = document.getElementById("localStream");

      let encoder_select = document.getElementById("encoder_select");

      encoder_select.addEventListener('change', () => {
        let codec = JSON.parse(encoder_select.options[encoder_select.selectedIndex].value);
        let sender = pc_pub.getSenders()[0];
        let param = sender.getParameters();
        param.encodings[0].codec = codec;
        sender.setParameters(param).then(() => {
          console.log("change encoder suc");
        }).catch((err) => {
          console.log("change encoder fail");
        })
      })

      // 建立连接按钮
      let startBtn = document.getElementById("startBtn");

      // 推流用的MediaStream
      let pc_pub = new RTCPeerConnection();

      let ws = new WebSocket('ws://127.0.0.1:9000');
      ws.addEventListener('open', () => {
        // 通知server pub已经上线
        ws.send(JSON.stringify({
          type: "publish"
        }))
      })

      ws.addEventListener('message', (event) => {
        let msg = JSON.parse(event.data);
        switch (msg.type) {
          case "start":
            start();
            break;

          case "answer":
            pc_pub.setRemoteDescription(msg).then(() => {

            }).catch((err) => {

            })
            break;

          default:
            pc_pub.addIceCandidate(msg);
            break;
        }
      })

      pc_pub.addEventListener('icecandidate', (event) => {
        if (event.candidate) {
          ws.send(JSON.stringify(event.candidate));
        }
      })

      pc_pub.addEventListener("iceconnectionstatechange", () => {
        if ('connected' == pc_pub.iceConnectionState) {
          encoder_select.options.length = 0;
          let sender = pc_pub.getSenders()[0];
          let param = sender.getParameters();
          console.log(param.codecs)
          for (let i = 0; i < param.codecs.length; ++i) {
            if (param.codecs[i].mimeType === "video/VP8" 
                || param.codecs[i].mimeType === "video/VP9" 
                || param.codecs[i].mimeType === "video/AV1"
                || param.codecs[i].mimeType === "video/H264") {
              let label = param.codecs[i].sdpFmtpLine ? `${param.codecs[i].mimeType} ${param.codecs[i].sdpFmtpLine}` : `${param.codecs[i].mimeType}`;
              encoder_select.add(new Option(label, JSON.stringify(param.codecs[i])))
            }
          }
        }
      })

      function start() {
        getDevice().then((mediaStream) => {
          pc_pub.addTrack(mediaStream.getVideoTracks()[0], mediaStream);
          pc_pub.createOffer().then((offer) => {
            pc_pub.setLocalDescription(offer).then(() => {
              ws.send(JSON.stringify(offer));
            }).catch((err) => {
              console.error('setLocalDescription error', err);
            })
          }).catch((err) => {
            console.error("create offer error", err);
          })
        }).catch((err) => {
          console.error("getDevice error", err);
        })
      }

      function getDevice() {
        return new Promise((resolve, reject) => {
        	navigator.mediaDevices.getUserMedia({ video: true }).then((mediaStream) => {
          	localVideo.srcObject = mediaStream;
            resolve(mediaStream);
          }).catch((err) => {
            reject(err);
          })
      	})
      }
    </script>
  </body>
</html>

订阅端

其中websoket server的ip写的是127.0.0.1,可以自行改成websocket server的局域网ip地址。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>推流页面</title>
  </head>

  <body>
    <video id="remoteStream" style="width: 320px; height: 240px;" autoplay muted></video>
    <button id="deviceBtn">打开本地摄像头</button>
    <button id="startBtn">建立连接</button>
    <script>
      // 显示本地画面的VideoElement
      let remoteStream = document.getElementById("remoteStream");

      // 订阅流用的Peerconnection
      let pc_sub = new RTCPeerConnection();

      let ws = new WebSocket('ws://127.0.0.1:9000');
      ws.addEventListener('open', () => {
        // 通知server pub已经上线
        ws.send(JSON.stringify({
          type: "subscribe"
        }))
      })

      ws.addEventListener('message', (event) => {
        let msg = JSON.parse(event.data);
        switch (msg.type) {
          case "start":
            break;

          case "offer":
            pc_sub.setRemoteDescription(msg).then(() => {
              pc_sub.createAnswer().then((answer) => {
                pc_sub.setLocalDescription(answer).then(() => {
                  ws.send(JSON.stringify(answer));
                }).catch((err) => {

                })
              }).catch((err) => {
                console.error('create answer error', err);
              })
            }).catch((err) => {
              console.error('setRemoteDescription error', err);
            })
            break;

          default:
            pc_sub.addIceCandidate(msg);
            break;
        }
      })

      pc_sub.addEventListener('icecandidate', (event) => {
        if (event.candidate) {
          ws.send(JSON.stringify(event.candidate));
        }
      })

      pc_sub.addEventListener('track', (event) => {
        remoteStream.srcObject = event.streams[0];
      })

    </script>
  </body>

</html>

其他

如果你也是专注前端多媒体或者对前端多媒体感兴趣,可以关注前端多媒体公众号。

作者:豆包啊
链接:https://juejin.cn/post/7296672743617282098
来源:稀土掘金

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论