如何用WHIP协议WebRTC推流到NodeMediaServer
NMS v3.21 WHIP协议WebRTC推流指南
一、协议实现
NMS v3.21基于WHIP协议(WebRTC-HTTP Ingestion Protocol)重新实现WebRTC推流功能。
二、功能特性
- 客户端开放:支持自定义实现
- 流采集能力:
- 摄像头/麦克风捕获
- 桌面/应用窗口采集
- Canvas/Video元素捕获
- 支持视频滤镜、AI特效等
- 完整WebRTC功能支持(参考WebRTC Samples)
三、配置说明
[webrtc] # 使用whip和whep标准协议实现webrtc直接推流和播放 # ip 服务部署在公网,设置公网IP地址,内网就填内网ip,使用此IP地址直接进行通讯, 如不设置使用stun识别 ip = # port 监听端口, 设置后开启服务 port = 10000 # ice_tcp ice是否通过tcp传输, Chrome支持\OBS不支持 ice_tcp = 0 # stun 服务器地址, 多个服务器地址用 | 分割 stun = stun:stun.l.google.com:19302
关键参数说明:
- ip:当明确设置固定公网ip地址时,WebRTC使用1:1 的NAT通讯,像RTMP等协议一样进行快速直连。
- port:WebRTC的通讯端口,目前直接使用TCP传输,在公网通讯中,不掉包、不乱序、不花屏、不卡顿。
- stun:stun服务器,如果不配置固定公网ip,则通过stun识别公网地址。
- 注意:WHIP是以HTTP POST形式进行SDP信令交换,因此HTTP或HTTPS端口必须打开。流地址使用HTTP、HTTPS的端口,WebRTC作为内部通讯端口并不在流地址中表现。
四、流地址格式
HTTPS场景(需有效证书)
https://server_name:8443/live/stream.whip
- 大多数web应用采用域名访问网站时,打开摄像头和麦克风需要网站以https访问,证书需要机构颁发,否则需要预先同意使用自签名证书。当web以https访问是,推流地址也必须是https地址,采用域名访问,NMS需要正确配置证书。
HTTP场景(本地测试)
http://server_ip:8000/live/stream.whip
- 还有一种情况,当web以127.0.0.1或localhost访问时,不需要https就可以采集摄像头,因此推流地址可以是http协议的ip地址。
五、推流控制
- 结束推流:按照协议定义,当post 本机 SDP信息后,服务器会在body里响应服务端SDP,在header中会返回一个location字段,值为资源id,以HTTP DELETE请求该地址时,就可以立即结束推流。
- 超时机制:如果不进行该操作,服务端也会根据ICE状态判断流断开超时,不是及时的,一般为10秒左右。
六、集成示例
1. 定义video标签预览摄像头
Start Call Hang Up2. 创建媒体流
const startButton = document.getElementById('startButton'); const callButton = document.getElementById('callButton'); const hangupButton = document.getElementById('hangupButton'); callButton.disabled = true; hangupButton.disabled = true; startButton.addEventListener('click', start); callButton.addEventListener('click', call); hangupButton.addEventListener('click', hangup); const localVideo = document.getElementById('localVideo'); localVideo.addEventListener('loadedmetadata', function () { console.log(`Local video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`); }); let localStream; let pc1; let whipLocation; const offerOptions = { offerToReceiveAudio: 1, offerToReceiveVideo: 1 }; const configuration = { iceServers: [ { urls: "stun:stun.l.google.com:19302" } ] }; async function start() { console.log('Requesting local stream'); startButton.disabled = true; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: { width: 1280, height: 720, frameRate: 30 } }); console.log('Received local stream'); localVideo.srcObject = stream; localStream = stream; callButton.disabled = false; } catch (e) { alert(`getUserMedia() error: ${e.name}`); } }
3. 发起推流
async function call() { callButton.disabled = true; hangupButton.disabled = false; console.log('Starting call'); const videoTracks = localStream.getVideoTracks(); const audioTracks = localStream.getAudioTracks(); if (videoTracks.length > 0) { console.log(`Using video device: ${videoTracks[0].label}`); } if (audioTracks.length > 0) { console.log(`Using audio device: ${audioTracks[0].label}`); } console.log('RTCPeerConnection configuration:', configuration); pc1 = new RTCPeerConnection(configuration); console.log('Created local peer connection object pc1'); // pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e)); pc1.onicegatheringstatechange = gatheringStateChange; pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e)); localStream.getTracks().forEach(track => pc1.addTrack(track, localStream)); console.log('Added local stream to pc1'); try { console.log('pc1 createOffer start'); const offer = await pc1.createOffer(offerOptions); await pc1.setLocalDescription(offer); const answer = await postSDPOffer("https://192.168.0.2:8443/live/demo.whip", pc1.localDescription.sdp); await pc1.setRemoteDescription(answer); whipLocation = answer.location; // await onCreateOfferSuccess(offer); } catch (e) { onCreateSessionDescriptionError(e); } } async function postSDPOffer(url, sdp) { let result = { status: 0, type: 'answer', }; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/sdp', "video-bitrate": 2_000_000, "video-keyint": 3, "audio-bitrate": 64_000 }, body: sdp }); result.status = response.status; result.sdp = await response.text(); result.location = response.headers.get("Location"); } catch (error) { result.error = error; } return result; }
4. 结束推流
function hangup() { console.log('Ending call'); pc1.close(); pc1 = null; hangupButton.disabled = true; callButton.disabled = false; fetch(whipLocation, { method: "DELETE" }); }
七、OBS推流配置
注意:
- 注意,此处端口仍然是http端口,而非配置中的webrtc端口。8000端口负责交换信令(显式),10000端口用于媒体传输(隐式)。
八、在线演示推流DEMO
NodeMedia Webrtc WHIP 推流 Demo(右键可查看完整源码)
- 注意,此处端口仍然是http端口,而非配置中的webrtc端口。8000端口负责交换信令(显式),10000端口用于媒体传输(隐式)。
- 还有一种情况,当web以127.0.0.1或localhost访问时,不需要https就可以采集摄像头,因此推流地址可以是http协议的ip地址。
- 大多数web应用采用域名访问网站时,打开摄像头和麦克风需要网站以https访问,证书需要机构颁发,否则需要预先同意使用自签名证书。当web以https访问是,推流地址也必须是https地址,采用域名访问,NMS需要正确配置证书。
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。