想象一下这样的场景:你的任务是将 Raspberry Pi 或任何服务器(Linux)转换成 IP 摄像机,但有一个问题——唯一可用的连接是不可靠的 3G/4G 网络。这一挑战促使我深入研究 WebRTC,并在本文中分享我的发现。
起初,我使用 Python 来解决这个问题,这是一种适用于 Raspberry Pi 的流行高级语言。然而,当我尝试利用 aioRTC 库(WebRTC 的 Python 实现)时,我遇到了与 CPU 和内存消耗相关的重大问题。这些障碍使得我的项目在目前的状态下不可行。此外,我还需要一种定制策略来监控传输统计数据并自动调整实时分辨率,这一概念被称为动态自适应视频。即使采用了这种量身定制的方法,在特定场景下的性能仍未达到预期。
就在这个时候,我开始考虑另一种选择–Chromium 浏览器。JavaScript 提供了一种更简单的实现方式,而 Chromium 浏览器是用 C++ 构建的,并且经过了高度优化,因此是一种很有前途的选择。此外,该浏览器已经集成了 “内容提示 “功能,提高了视频传输效率和对网络条件的响应速度。在本文中,我们将探讨 WebRTC 和 JavaScript 如何结合成为此类情况下的理想解决方案,在这种情况下,稳定性和高效视频传输是最重要的。
通过浏览器 API 利用 WebRTC
要动态调整视频质量,您有两个选择,contenthint
提供两个选项 :detail
和motion
。如果您的视频包含motion,选择motion
将锁定每秒帧数 (FPS) 并降低图像质量。然而,选择detail
,尤其是在有文本的情况下,可以在保持高分辨率的同时调整 FPS,以适应较差的网络连接。
在本示例中,我排除了Signaling Service,因为它会随 WebSocket、gRPC、HTTP 等解决方案的不同而变化。
1. 使用 JavaScript 在树莓派/服务器上实现
使用 JavaScript 的实现非常简单。请记住,您可以通过修改约束条件来选择要共享的设备,例如:video: {deviceId: {exact: your-device-id}}
。与 linux 上传统的 /dev/video0 ID 不同,浏览器上的设备 ID 结构不同。使用 showDevices() 方法查找要共享的特定摄像头,尤其是在连接了多个摄像头的情况下。
var peer = null;
function createPeer (offerSDP) {
let config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' }
]
};
peer = new RTCPeerConnection(config);
captureCamera(offerSDP);
}
async function showDevices() {
let devices = (await navigator.mediaDevices.enumerateDevices()).filter(i=> i.kind == 'videoinput')
console.log(devices)
}
function captureCamera (sdpOffer) {
let constraints = {
audio: false,
/*
video: {
deviceId: { exact: '4e9606ec398f795755efea4f4b75f206f5b5140f5a9ee8ee51e81154c220f97a' }
}
*/
video: true
};
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
stream.getTracks().forEach(function(track) {
applyContraints(track);
peer.addTrack(track, stream);
});
return createAnswer(sdpOffer);
}, function(err) {
alert('Could not acquire media: ' + err);
});
}
async function createAnswer (sdp) {
let offer = new RTCSessionDescription({sdp: sdp, type: 'offer'})
await peer.setRemoteDescription(offer)
let answer = await peer.createAnswer()
await peer.setLocalDescription(answer)
sendAnswerToBrowser(peer.localDescription.sdp)
}
function applyContraints (videoTrack) {
if (videoTrack) {
const videoConstraints = {
width: { min: 320, max: 1280 },
height: { min: 240, max: 720 },
frameRate: {min: 15, max: 30 }
};
// Apply video track constraints
videoTrack.applyConstraints(videoConstraints)
.then(() => {
console.log("Video track constraints applied successfully");
})
.catch((error) => {
console.error("Error applying video track constraints:", error);
setTimeout(() => {
applyContraints();
}, 5000);//5seg
});
// Set content hint to 'motion' or 'detail'
videoTrack.contentHint = 'motion';
}
}
function sendAnswerToBrowser(answerSDP) {
console.log('sendAnswerToBrowser')
/**
* implement this method with your strategy to signaling
*/
}
如果 Raspberry Pi 没有 X (UI) 服务,请确保已安装 screen 和 xvfb 软件包,以便无缝运行。使用以下命令安装它们:
$ sudo apt-get update
$ sudo apt-get install screen
$ sudo apt-get install xvfb
安装好这些软件包后,就可以通过终端在隔离会话中打开浏览器了。
$ screen -S web xvfb-run chromiu-browser --use-fake-ui-for-media-stream http://localhost:8289/
#without xvfb-run
$ screen -S web chromiu-browser --use-fake-ui-for-media-stream http://localhost:8289/
使用–use-fake-ui-for-media-stream 命令,浏览器就无需请求摄像头权限。
2 在浏览器端(前端)实施 WebRTC
场景: 前端只消费视频,不向服务器发送视频或音频。前端的 JavaScript 代码如下:
var peer = null;
const localVideoTag = document.getElementById('localVideo')
function createPeer () {
console.log("creating a peer connection")
let config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' }
]
};
peer = new RTCPeerConnection(config);
peer.addEventListener('track', function (evt) {
if (evt.track.kind == 'video') {
console.log("receiving a video")
applyContraints( evt.streams[0].getVideoTracks()[0] );
localVideoTag.srcObject = evt.streams[0];
}
});
peer.addTransceiver('audio', {
direction: 'recvonly'
});
peer.addTransceiver('video', {
direction: 'recvonly'
});
}
async function negociate () {
console.log("negociate")
let offer = await peer.createOffer();
await peer.setLocalDescription(offer);
await verifyStateComplete();
sendOfferToServer(peer.localDescription.sdp);
}
async function verifyStateComplete () {
console.log("verifyStateComplete")
return new Promise(function (resolve) {
if (peer.iceGatheringState === 'complete') {
resolve();
} else {
function checkState() {
if (peer.iceGatheringState === 'complete') {
peer.removeEventListener('icegatheringstatechange', checkState);
resolve();
}
}
peer.addEventListener('icegatheringstatechange', checkState);
}
});
}
function applyContraints (videoTrack) {
if (videoTrack) {
const videoConstraints = {
width: { min: 320, max: 1280 },
height: { min: 240, max: 720 },
frameRate: {min: 15, max: 30 }
};
// Apply video track constraints
videoTrack.applyConstraints(videoConstraints)
.then(() => {
console.log("Video track constraints applied successfully");
})
.catch((error) => {
console.error("Error applying video track constraints:", error);
setTimeout(() => {
applyContraints(videoTrack);
}, 5000);//5seg
});
// Set content hint to 'motion' or 'detail'
videoTrack.contentHint = 'motion';
}
}
function sendOfferToServer (offerSDP) {
console.log('sendOfferToServer')
/**
* implement this method with your strategy to signaling
*/
}
/**
* <This method will be called by the signaling solution you choose....>
*/
function setAnswer (sdp) {
console.log('setAnswer')
let answer = {
sdp: sdp,
type: 'answer'
}
peer.setRemoteDescription(answer);
}
//start
function start () {
console.log('start')
createPeer();
negociate();
}
function stop(){
peer.close();
}
结论
使用 chrome://webrtc-internals/
可以轻松分析 WebRTC 流量。
在观察到的指标中,您可能会注意到帧高和帧宽的变化。最初的分辨率为 640×480,但也有波动。它还旨在保持 FPS,这是通过将 “内容提示 “选择为 “运动 “来实现的。这一选择在动态调整分辨率的同时确保了 FPS 的稳定性。
难能可贵的是,你无需创建复杂的自动视频适应逻辑,浏览器开箱即提供了这一功能。它采用了一种智能算法,可分析网络指标并优化视频,以实现卓越的用户体验。
要模拟糟糕的网络条件,可以使用下面的方法故意将比特率降低到 50kbps:
/**
* Call this method when you want to reduce the speed to
* test content hint, simulating situations when you have bad connections
*/
function forceKbps(sdp, speed) {
return sdp.replace(/a=mid:(.*)\r\n/g, 'a=mid:$1\r\nb=AS:' + speed + '\r\n');
}
总之,WebRTC 与浏览器中的 JavaScript 相结合,可以在要求稳定和高效视频传输的场景中改变游戏规则。浏览器的内置优化和 contentHint 功能提供了一个功能强大、用户友好的解决方案。
源代码
我使用 WebSocket 信令服务创建了一个全面的 WebRTC 项目,欢迎大家对这个项目提出宝贵意见。
https://github.com/marcus2vinicius/webrtc-raspberry-js
作者:Marcus Borges
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/35573.html