第一次接触WebRTC信令时,我盯着SDP那一大串文本看了半天,完全不明白这些a=、v=开头的字符串有什么用。后来在实际项目中踩过几次坑才明白,这其实是媒体协商的关键。简单来说,信令交互就像两个陌生人在相亲前先交换基本信息:你擅长什么(支持的编解码)、你喜欢什么方式沟通(传输协议)、你的联系方式是什么(IP和端口)。
WebRTC的信令过程必须解决三个核心问题:
SRS作为信令服务器的优势在于,它把复杂的ICE协商过程封装成了两个简单的HTTP API。开发者只需要关注:
/rtc/v1/publish/发布本地媒体流/rtc/v1/play/请求远端媒体流在Vue项目中实现推流时,有几个关键参数容易出错。首先是streamurl的格式,必须严格按照webrtc://{ip}:{port}/{stream}的格式填写,其中8000是SRS默认的WebRTC UDP端口。我曾因为漏写端口号导致连续两小时排查失败。
完整的推流请求示例:
javascript复制const data = {
api: "http://192.168.5.104:1985/rtc/v1/publish/",
streamurl: "webrtc://192.168.5.104:8000/live/room1",
sdp: pc.localDescription.sdp // 从RTCPeerConnection获取
}
服务器响应中的关键字段:
code=0表示成功sdp包含服务器端的媒体协商结果sessionid用于后续的信令跟踪拉流时最容易踩的坑是收发器(transceiver)的方向设置。在创建RTCPeerConnection后,必须正确配置transceiver的direction参数:
javascript复制pc.addTransceiver("audio", {direction: "recvonly"});
pc.addTransceiver("video", {direction: "recvonly"});
如果忘记设置recvonly,会导致SRS服务器误判为双向流传输,消耗额外带宽。我在生产环境就遇到过因为这个问题导致服务器负载飙升的情况。
一个完整的SDP包含多个层级的信息:
code复制v=0 // 协议版本
o=- 0 0 IN IP4 127.0.0.1 // 会话标识
s=- // 会话名称
t=0 0 // 时间戳
a=group:BUNDLE 0 1 // 媒体流绑定
m=audio 9 UDP/TLS/RTP/SAVPF 111 // 音频媒体行
a=rtpmap:111 opus/48000/2 // 编解码详情
a=candidate:1 1 udp 2130706431 192.168.1.100 5000 typ host // 网络候选地址
实际项目中,我建议重点关注三个部分:
m=开头的媒体行,确认音视频是否都正常协商a=rtpmap验证编解码器是否支持a=candidate检查NAT穿透地址是否正确调试时最常遇到的三种SDP错误:
通过Wireshark抓包分析时,可以重点关注:
在vue.config.js中需要添加WebRTC相关的polyfill配置:
javascript复制module.exports = {
configureWebpack: {
resolve: {
fallback: {
'http': false,
'https': false,
'zlib': false
}
}
}
}
推流功能的完整实现包含以下步骤:
javascript复制const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true
},
video: {
width: { ideal: 1280 },
height: { ideal: 720 }
}
});
javascript复制const pc = new RTCPeerConnection();
stream.getTracks().forEach(track => {
pc.addTrack(track);
});
javascript复制const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const response = await fetch('http://192.168.5.104:1985/rtc/v1/publish/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api: "http://192.168.5.104:1985/rtc/v1/publish/",
streamurl: "webrtc://192.168.5.104:8000/live/demo",
sdp: offer.sdp
})
});
const answer = await response.json();
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer.sdp })
);
在开发过程中,我总结出几个实用的调试方法:
javascript复制pc.addEventListener('iceconnectionstatechange', () => {
console.log('ICE状态:', pc.iceConnectionState);
});
setInterval(async () => {
const stats = await pc.getStats();
stats.forEach(report => {
if(report.type === 'outbound-rtp') {
console.log('当前码率:', report.bitrate);
}
});
}, 1000);
在SRS的配置文件中,这几个参数对性能影响最大:
conf复制rtc_server {
enabled on;
listen 8000;
candidate $CANDIDATE_IP;
}
rtc {
twcc on; # 启用传输层拥塞控制
min_port 10000;
max_port 20000;
}
其中candidate配置特别重要,必须设置为服务器的公网IP或域名。我在阿里云环境测试时,错误配置内网IP导致外网用户完全无法连接。
根据实际项目经验,推荐以下优化措施:
实现代码示例:
javascript复制// 动态调整视频参数
function adaptVideoQuality(bitrate) {
const sender = pc.getSenders().find(s => s.track.kind === 'video');
const parameters = sender.getParameters();
if(bitrate < 500) {
parameters.encodings[0].scaleResolutionDownBy = 2.0;
parameters.encodings[0].maxBitrate = 300000;
} else {
parameters.encodings[0].scaleResolutionDownBy = 1.0;
parameters.encodings[0].maxBitrate = 1000000;
}
sender.setParameters(parameters);
}
当遇到ICE协商失败时,建议按以下步骤排查:
测试STUN服务的命令示例:
bash复制nmap -sU -p 3478 stun.l.google.com
音频卡顿通常由以下原因导致:
解决方案:
javascript复制// 启用opus的FEC(前向纠错)
const audioSender = pc.getSenders().find(s => s.track.kind === 'audio');
const parameters = audioSender.getParameters();
parameters.encodings[0].codecPayloadType = 111; // opus负载类型
parameters.codecs = [
{
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2,
parameters: {
useinbandfec: 1, // 开启FEC
usedtx: 1 // 启用静音压缩
}
}
];
audioSender.setParameters(parameters);