新版本的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字段可以看到当前连接支持的所有编码器,如下图:
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 举报,一经查实,本站将立刻删除。