SRS 流媒体服务器 / WebRTC / 视频编码
SRS 流媒体服务器
SRS 简介
SRS(Simple Realtime Server)是一个开源的、高性能的流媒体服务器,采用 C++ 开发,支持丰富的流媒体协议和直播/转码/录播等场景。其定位为"云原生流媒体服务器",广泛应用于直播平台、在线教育、视频监控、实时通信等领域。
SRS 的功能矩阵:
| 协议/功能 | 支持情况 | 典型场景 |
|---|---|---|
| RTMP | 推流/拉流 | 直播推流、Flash 播放 |
| HLS | 分片输出 | 苹果生态、点播/直播 |
| FLV | HTTP-FLV | 低延迟直播 |
| WebRTC | 推流/拉流/转码 | 实时通信、连麦 |
| SRT | 推流/拉流 | 公网传输抗丢包 |
| GB28181 | 接入/转发 | 安防监控 |
| HTTP-TS | 输出 | 通用播放 |
| DASH | 输出 | MPEG-DASH 标准 |
SRS 支持多种架构模式:单机模式、Origin + Edge 集群模式、以及基于 Docker/K8s 的云原生部署。
Docker 部署
使用 Docker 可以快速启动 SRS 实例。以下为基本部署步骤。
拉取镜像并启动:
docker run -d --restart=always \
--name srs \
-p 1935:1935 \
-p 1985:1985 \
-p 8080:8080 \
-p 8000:8000/udp \
-p 10080:10080/udp \
registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5 \
./objs/srs -c conf/docker.conf端口说明:
| 端口 | 协议 | 用途 |
|---|---|---|
| 1935 | TCP | RTMP 推流/拉流 |
| 1985 | TCP | HTTP API / WebSocket |
| 8080 | TCP | HTTP Live 播放 / WebRTC 页面 |
| 8000 | UDP | WebRTC 媒体传输 (UDP) |
| 10080 | UDP | WebRTC 媒体传输 (UDP) 扩展 |
使用自定义配置文件:
创建 srs.conf 配置文件:
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000;
protocol udp;
}
vhost __defaultVhost__ {
rtc {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
hls {
enabled on;
}
}启动自定义配置:
docker run -d --restart=always \
--name srs \
-v /path/to/srs.conf:/usr/local/srs/conf/srs.conf \
-p 1935:1935 -p 1985:1985 -p 8080:8080 -p 8000:8000/udp \
registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5集群部署:Origin + Edge 模式
SRS 集群采用 Origin(源站)+ Edge(边缘)架构,Edge 节点从 Origin 拉取流并分发到终端用户,支持水平扩展和负载均衡。
架构原理:
+----------+ +----------+
| Origin1 | | Origin2 |
+----------+ +----------+
^ ^
| |
+----+-------------------+----+
| Edge 集群 |
| Edge1 Edge2 Edge3 ... |
+----+-------------------+----+
| |
+----+----+ +----+----+
| 用户 1 | | 用户 2 |
+---------+ +---------+- Origin 节点:接收推流(RTMP/WebRTC/GB28181),负责转码和录制。集群内多 Origin 可做故障转移。
- Edge 节点:接受播放请求,从 Origin 回源拉流并缓存转发。Edge 无状态,可横向扩展。
- 负载均衡:在 Edge 层前部署 Nginx/HAProxy 做流量分发。
Origin 节点配置:
listen 1935;
max_connections 2000;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
}
rtc_server {
enabled on;
listen 8000;
protocol udp;
}
vhost __defaultVhost__ {
rtc { enabled on; }
hls { enabled on; }
http_remux { enabled on; mount [vhost]/[app]/[stream].flv; }
# 开启录制
dvr {
enabled on;
dvr_path ./objs/nginx/html/record/[app]/[stream].[timestamp].flv;
dvr_plan segment;
dvr_duration 300;
}
}Edge 节点配置:
listen 1935;
max_connections 5000;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
}
rtc_server {
enabled on;
listen 8000;
protocol udp;
}
vhost __defaultVhost__ {
rtc { enabled on; }
http_remux { enabled on; mount [vhost]/[app]/[stream].flv; }
# 回源配置
mode remote;
origin 192.168.1.100:1935 192.168.1.101:1935; # Origin 地址列表
origin_failretry 30; # 回源失败重试间隔(秒)
origin_fasthttp on; # 启用 HTTP 快速回源
}Nginx 负载均衡配置:
upstream srs_edge {
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
server_name live.example.com;
location /live/ {
proxy_pass http://srs_edge;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /rtc/ {
proxy_pass http://srs_edge;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}SRS HTTP Callback 事件回调
SRS 支持通过 HTTP Callback 机制在关键事件发生时调用外部 API,用于鉴权、统计、日志等场景。
支持的事件类型:
| 事件 | 触发时机 | 鉴权用途 |
|---|---|---|
| on_connect | 客户端连接服务器 | 连接鉴权 |
| on_close | 客户端断开连接 | 在线统计 |
| on_publish | 推流开始 | 推流鉴权 |
| on_unpublish | 推流结束 | 录制完成通知 |
| on_play | 播放开始 | 播放鉴权 |
| on_stop | 播放结束 | 播放统计 |
| on_dvr | 录制文件生成 | 录制完成回调 |
| on_hls | HLS 切片完成 | HLS 通知 |
配置文件启用回调:
vhost __defaultVhost__ {
http_hooks {
enabled on;
on_connect http://192.168.1.200:9000/api/srs/on_connect;
on_close http://192.168.1.200:9000/api/srs/on_close;
on_publish http://192.168.1.200:9000/api/srs/on_publish;
on_unpublish http://192.168.1.200:9000/api/srs/on_unpublish;
on_play http://192.168.1.200:9000/api/srs/on_play;
on_stop http://192.168.1.200:9000/api/srs/on_stop;
on_dvr http://192.168.1.200:9000/api/srs/on_dvr;
on_hls http://192.168.1.200:9000/api/srs/on_hls;
}
}回调请求格式(POST JSON):
// POST /api/srs/on_publish
{
"server_id": "vid-0x1234",
"action": "on_publish",
"client_id": "4321",
"ip": "192.168.1.50",
"vhost": "__defaultVhost__",
"app": "live",
"stream": "stream_abc",
"param": "?token=abc123&uid=1001",
"pageUrl": "http://example.com/publisher.html"
}鉴权服务示例(Node.js):
const express = require('express');
const app = express();
app.use(express.json());
// 推流鉴权
app.post('/api/srs/on_publish', (req, res) => {
const { stream, param } = req.body;
// 解析 URL 参数中的 token
const params = new URLSearchParams(param);
const token = params.get('token');
const uid = params.get('uid');
// 验证 token(示例逻辑)
if (validateToken(uid, token)) {
// 鉴权通过,返回 0 允许推流
res.json({ code: 0 });
} else {
// 鉴权失败,返回非 0 拒绝推流
res.json({ code: 1, msg: 'Invalid token' });
}
});
// 播放鉴权
app.post('/api/srs/on_play', (req, res) => {
const { stream, param } = req.body;
const params = new URLSearchParams(param);
const token = params.get('token');
if (validatePlayToken(stream, token)) {
res.json({ code: 0 });
} else {
res.json({ code: 1, msg: 'Unauthorized' });
}
});
app.listen(9000, () => {
console.log('SRS callback server running on port 9000');
});WebRTC 基础
WebRTC 整体架构
WebRTC(Web Real-Time Communication)是由 W3C 和 IETF 标准化的一套实时通信协议和 API 集合。其核心架构分为以下层次:
+-----------------------------------------------------+
| Web Application |
+-----------------------------------------------------+
| Web API (W3C standard) |
+------------------+------------------+---------------+
| MediaStream | RTCPeerConnection| RTCDataChannel|
| (getUserMedia) | (P2P 连接管理) | (数据通道) |
+------------------+------------------+---------------+
+-----------------------------------------------------+
| WebRTC Native C++ API (浏览器层) |
+------------------+------------------+---------------+
| 音频引擎 | 视频引擎 | 传输层 |
| - Opus/iLBC | - VP8/VP9/H.264 | - ICE/STUN |
| - 回声消除(AEC) | - 抖动缓冲(JB) | - TURN |
| - 降噪(NS) | - 丢包隐藏(PLC) | - DTLS/SRTP |
| - 自动增益(AGC) | - 带宽自适应 | - SCTP |
+------------------+------------------+---------------+各层次说明:
| 层次 | 职责 |
|---|---|
| Web API | 浏览器暴露给 JavaScript 的接口:MediaStream、RTCPeerConnection、RTCDataChannel |
| Native C++ API | 浏览器内部的实现层,各浏览器厂商(Chrome/Firefox/Safari)各自实现 |
| 音频引擎 | 音频采集/渲染、回声消除(AEC)、降噪(NS)、自动增益控制(AGC)、Opus 编解码 |
| 视频引擎 | 视频采集/渲染、VP8/VP9/H.264 编解码、抖动缓冲(Jitter Buffer)、丢包隐藏(PLC) |
| 传输层 | ICE 连接建立(STUN/TURN)、DTLS 加密、SRTP 媒体加密、SCTP 数据通道 |
获取媒体流:getUserMedia
navigator.mediaDevices.getUserMedia() 是获取用户媒体设备(摄像头、麦克风、屏幕)的核心 API。
摄像头与麦克风:
async function getCameraAndMic() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 },
facingMode: 'user' // 'user'前置 / 'environment'后置
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 48000,
channelCount: 2
}
});
// 将媒体流绑定到 <video> 标签
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = stream;
return stream;
} catch (err) {
console.error('getUserMedia 失败:', err.name, err.message);
}
}MediaStream 约束参数:
const constraints = {
video: {
// 分辨率约束:ideal 首选值,exact 强制值,min/max 范围
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 },
frameRate: { ideal: 30, max: 60 },
aspectRatio: { ideal: 16 / 9 },
// 设备选择
deviceId: { exact: 'camera-device-id' },
// 编码偏好
// Chrome 不支持直接在 constraints 中指定编码器
},
audio: {
// 音频处理
echoCancellation: true, // 回声消除
noiseSuppression: true, // 降噪
autoGainControl: true, // 自动增益
// 采样参数
sampleRate: { ideal: 48000 },
channelCount: { ideal: 2 },
// 设备选择
deviceId: { exact: 'mic-device-id' }
}
};屏幕共享:
屏幕共享使用 getDisplayMedia API,区别于 getUserMedia:
async function startScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: 'always', // 显示鼠标指针
displaySurface: 'monitor' // monitor / window / browser
},
audio: {
echoCancellation: true,
noiseSuppression: true
}
});
// 停止屏幕共享处理
const [videoTrack] = stream.getVideoTracks();
videoTrack.onended = () => {
console.log('屏幕共享已停止');
// 切换回摄像头
switchToCamera();
};
return stream;
} catch (err) {
console.error('屏幕共享失败:', err);
}
}MediaStream 高级操作:
// 获取视频/音频轨道列表
const videoTracks = stream.getVideoTracks();
const audioTracks = stream.getAudioTracks();
// 启用/禁用轨道(静音/关闭摄像头)
audioTracks[0].enabled = false; // 静音
videoTracks[0].enabled = false; // 关闭摄像头
// 轨道替换(切换前后摄像头)
async function switchCamera(stream) {
const newStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
});
const [newVideoTrack] = newStream.getVideoTracks();
const [oldVideoTrack] = stream.getVideoTracks();
stream.removeTrack(oldVideoTrack);
stream.addTrack(newVideoTrack);
oldVideoTrack.stop();
}
// 停止整个流
stream.getTracks().forEach(track => track.stop());RTCPeerConnection
RTCPeerConnection 是 WebRTC 的核心对象,负责管理对等连接(P2P),包括媒体协商、连接建立、数据通道等。
关键 API:
| API | 功能 |
|---|---|
createOffer() | 创建 SDP Offer(发起方调用) |
createAnswer() | 创建 SDP Answer(接收方调用) |
setLocalDescription(sdp) | 设置本地 SDP |
setRemoteDescription(sdp) | 设置远端 SDP |
addTrack(track, stream) | 添加媒体轨道 |
ontrack | 收到远端媒体流时触发 |
onicecandidate | ICE 候选收集完成时触发 |
oniceconnectionstatechange | ICE 连接状态变更 |
addIceCandidate(candidate) | 添加远端 ICE 候选 |
createDataChannel(label) | 创建数据通道 |
close() | 关闭连接 |
完整连接流程示例(发起方):
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'password'
}
],
iceCandidatePoolSize: 10,
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require'
};
const pc = new RTCPeerConnection(configuration);
// 添加本地媒体流
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
// 接收远端媒体流
pc.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// ICE 候选收集
pc.onicecandidate = (event) => {
if (event.candidate) {
// 通过信令服务器发送到远端
signalingServer.send({
type: 'candidate',
candidate: event.candidate
});
}
};
// 连接状态变化
pc.oniceconnectionstatechange = () => {
console.log('ICE 状态:', pc.iceConnectionState);
if (pc.iceConnectionState === 'connected') {
console.log('P2P 连接已建立');
} else if (pc.iceConnectionState === 'disconnected') {
console.log('连接断开');
}
};
// 创建 Offer
const offer = await pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await pc.setLocalDescription(offer);
// 通过信令服务器发送 Offer
signalingServer.send({
type: 'offer',
sdp: offer.sdp
});完整连接流程示例(接收方):
// 接收 Offer
signalingServer.onmessage = async (msg) => {
if (msg.type === 'offer') {
const pc = new RTCPeerConnection(configuration);
pc.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
pc.onicecandidate = (event) => {
if (event.candidate) {
signalingServer.send({
type: 'candidate',
candidate: event.candidate
});
}
};
// 添加本地流
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
// 设置远端 Offer 并创建 Answer
await pc.setRemoteDescription(new RTCSessionDescription({
type: 'offer',
sdp: msg.sdp
}));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
signalingServer.send({
type: 'answer',
sdp: answer.sdp
});
// 处理接收到的 ICE 候选
signalingServer.on('candidate', (candidateMsg) => {
pc.addIceCandidate(new RTCIceCandidate(candidateMsg.candidate));
});
}
};SDP 协商流程
SDP(Session Description Protocol)协商是 WebRTC 建立连接的核心过程,涉及媒体能力交换、编解码器协商和网络候选收集。
完整协商流程:
发起方 A 信令服务器 接收方 B
| | |
|--- createOffer() -------- | |
|--- setLocalDescription(offer) |
| | |
|--{ type: 'offer', sdp }-->|--{ type: 'offer' }------>|
| | |
| |--- setRemoteDescription(offer)
| |--- createAnswer()
| |--- setLocalDescription(answer)
| | |
|<--{ type: 'answer' }-----|<--{ type: 'answer' }------|
| | |
|--- setRemoteDescription(answer) |
| | |
|-- 开始 ICE 候选收集 -- | -- 开始 ICE 候选收集 -- |
| | |
|<== ICE 候选交换 (candidate) ==> |
| | |
|<<================ P2P 连接建立 ====================>>|Offer/Answer 示例(SDP片段):
// Offer(发起方)
v=0
o=- 1234567890 2 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS stream1
// 音频媒体行
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:abc123
a=ice-pwd:def456
a=fingerprint:sha-256 XX:XX:...:XX
a=setup:actpass
a=mid:0
a=sendrecv
a=rtpmap:111 opus/48000/2
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=ssrc:123456789 cname:test
// 视频媒体行
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:abc123
a=ice-pwd:def456
a=fingerprint:sha-256 XX:XX:...:XX
a=setup:actpass
a=mid:1
a=sendrecv
a=rtpmap:96 H264/90000
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:97 rtx/90000
a=rtpmap:98 VP8/90000
a=rtpmap:99 VP9/90000
a=rtpmap:100 AV1/90000
a=ssrc:987654321 cname:testICE 候选示例:
// ICE 候选格式
{
"candidate": "candidate:1 1 UDP 2122252543 192.168.1.10 53421 typ host",
"sdpMid": "0",
"sdpMLineIndex": 0
}
// 候选类型说明
// host: 本地局域网候选(优先级最高)
// srflx: STUN 反射候选(NAT 穿透)
// relay: TURN 中继候选(优先级最低,但最可靠)
// ICE 候选优先级示例
// 2122252543 -> 主机候选 (host)
// 1685923071 -> 反射候选 (srflx)
// 92216831 -> 中继候选 (relay)WebRTC 信令服务器
信令作用
WebRTC 本身只负责 P2P 媒体传输,连接的建立需要信令服务器来协调。信令服务器的主要职责:
| 功能 | 说明 |
|---|---|
| 房间管理 | 创建/加入/离开房间,维护房间内用户列表 |
| 用户列表 | 通知房间内用户变化(加入/离开) |
| Offer/Answer 转发 | 转发 SDP 协商消息 |
| ICE Candidate 转发 | 转发 ICE 候选信息 |
| 状态同步 | 通知房间状态变化(静音、举手等) |
信令服务器不传输媒体数据,只传输控制信令,因此本身负载较轻。
实现方案
方案对比:
| 方案 | 延迟 | 双向通信 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| WebSocket | 低 | 是 | 中 | 实时性要求高的场景 |
| Socket.IO | 低 | 是 | 低 | 快速原型、兼容性要求高 |
| HTTP 轮询 | 高 | 否 | 低 | 非实时场景、简单 Demo |
推荐方案: 生产环境优先选择 WebSocket 或 Socket.IO。Socket.IO 在 WebSocket 基础上增加了自动重连、房间管理、事件广播等能力,适合信令场景。
信令协议设计
信令消息类型定义:
// 信令消息类型定义
type SignalMessage =
// 房间管理
| { type: 'join'; roomId: string; userId: string }
| { type: 'leave'; roomId: string; userId: string }
| { type: 'room-info'; roomId: string; users: UserInfo[] }
// SDP 交换
| { type: 'offer'; roomId: string; from: string; to: string; sdp: string }
| { type: 'answer'; roomId: string; from: string; to: string; sdp: string }
// ICE 交换
| { type: 'candidate'; roomId: string; from: string; to: string;
candidate: RTCIceCandidate }
// 媒体控制
| { type: 'mute'; roomId: string; userId: string; kind: 'audio' | 'video' }
| { type: 'unmute'; roomId: string; userId: string; kind: 'audio' | 'video' }
// 房间状态
| { type: 'user-joined'; roomId: string; user: UserInfo }
| { type: 'user-left'; roomId: string; userId: string }
| { type: 'error'; code: number; message: string };Socket.IO 信令服务器实现:
// server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: '*' }
});
// 房间管理
const rooms = new Map(); // roomId -> Set<socketId>
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
// 加入房间
socket.on('join', ({ roomId, userId }) => {
socket.join(roomId);
socket.data.roomId = roomId;
socket.data.userId = userId;
if (!rooms.has(roomId)) {
rooms.set(roomId, new Map());
}
rooms.get(roomId).set(socket.id, { userId, joinedAt: Date.now() });
// 通知房间内其他用户
socket.to(roomId).emit('user-joined', {
roomId,
user: { id: userId, socketId: socket.id }
});
// 返回当前用户列表
const users = Array.from(rooms.get(roomId).entries()).map(([sid, info]) => ({
socketId: sid,
userId: info.userId
}));
socket.emit('room-info', { roomId, users });
});
// 转发 Offer
socket.on('offer', ({ roomId, to, sdp }) => {
socket.to(to).emit('offer', {
from: socket.id,
sdp
});
});
// 转发 Answer
socket.on('answer', ({ roomId, to, sdp }) => {
socket.to(to).emit('answer', {
from: socket.id,
sdp
});
});
// 转发 ICE Candidate
socket.on('candidate', ({ roomId, to, candidate }) => {
socket.to(to).emit('candidate', {
from: socket.id,
candidate
});
});
// 离开房间
socket.on('leave', ({ roomId }) => {
handleLeave(socket, roomId);
});
// 断开连接
socket.on('disconnect', () => {
const roomId = socket.data.roomId;
if (roomId) {
handleLeave(socket, roomId);
}
});
});
function handleLeave(socket, roomId) {
socket.leave(roomId);
if (rooms.has(roomId)) {
rooms.get(roomId).delete(socket.id);
if (rooms.get(roomId).size === 0) {
rooms.delete(roomId);
}
}
socket.to(roomId).emit('user-left', {
roomId,
userId: socket.data.userId
});
}
server.listen(3000, () => {
console.log('Signaling server running on port 3000');
});客户端信令通信:
// client.js
const socket = io('http://localhost:3000');
// 加入房间
socket.emit('join', { roomId: 'room-001', userId: 'user-abc' });
// 接收房间信息
socket.on('room-info', ({ users }) => {
console.log('房间内用户:', users);
// 如果房间内有其他人,发起连接
if (users.length > 1) {
const otherUser = users.find(u => u.socketId !== socket.id);
if (otherUser) {
createOffer(otherUser.socketId);
}
}
});
// 收到 Offer
socket.on('offer', async ({ from, sdp }) => {
await peerConnection.setRemoteDescription(new RTCSessionDescription({
type: 'offer', sdp
}));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit('answer', { roomId: 'room-001', to: from, sdp: answer.sdp });
});
// 收到 Answer
socket.on('answer', async ({ from, sdp }) => {
await peerConnection.setRemoteDescription(new RTCSessionDescription({
type: 'answer', sdp
}));
});
// 收到 ICE Candidate
socket.on('candidate', async ({ from, candidate }) => {
await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
});视频编码基础
H.264 / AVC
H.264(Advanced Video Coding)是目前应用最广泛的视频编码标准,由 ITU-T VCEG 和 ISO/IEC MPEG 联合制定。
编码原理:
原始帧序列:
I B B P B B P B B I ...
| | | | | | | | | |
| +--+--+ +--+--+ +--+--+
| 预测 预测 预测
| (前向+ (前向+ (前向+
| 后向) 后向) 后向)
|
I帧: 关键帧(帧内编码,可独立解码)
P帧: 前向预测帧(参考前面的 I/P 帧)
B帧: 双向预测帧(参考前后的 I/P 帧)帧类型说明:
| 帧类型 | 编码方式 | 压缩率 | 解码依赖 | 作用 |
|---|---|---|---|---|
| I 帧 | 帧内预测(类似 JPEG) | 最低 | 无 | 随机访问点、GOP 起点 |
| P 帧 | 前向帧间预测 | 中 | 依赖前一个 I/P 帧 | 压缩连续画面 |
| B 帧 | 双向帧间预测 | 最高 | 依赖前后帧 | 最高压缩率 |
| IDR 帧 | 特殊 I 帧 | 同 I 帧 | 无 | 刷新解码器参考帧队列 |
GOP (Group of Pictures) 与 IDR:
- GOP:一组连续的画面组,从 IDR 帧开始到下一个 IDR 帧之前结束。
- IDR 帧:Instantaneous Decoder Refresh,解码器遇到 IDR 帧时清空参考帧缓存,确保从此处开始可独立解码。
- GOP 大小:决定随机访问粒度。GOP 越大压缩率越高,但 seek 响应越慢。常见 GOP 大小:直播场景 2 秒(60 帧),点播场景 5-10 秒。
Profile 与 Level:
| Profile | 特性 | 适用场景 |
|---|---|---|
| Baseline | 仅 I/P 帧、CAVLC 熵编码 | 视频会议、移动端低延迟 |
| Main | I/P/B 帧、CAVLC/CABAC | 标清电视广播、存储 |
| High | I/P/B 帧、CABAC、8x8 变换 | 蓝光、高清视频、流媒体 |
| Level | 最大分辨率 | 最大码率 (High Profile) | 典型应用 |
|---|---|---|---|
| 3.0 | 720x576@30 | 10 Mbps | SD 标清 |
| 3.1 | 1280x720@30 | 14 Mbps | 720p |
| 4.0 | 1920x1080@30 | 20 Mbps | 1080p |
| 4.1 | 1920x1080@60 | 50 Mbps | 1080p60 |
| 5.0 | 2560x1920@30 | 135 Mbps | 2K |
| 5.1 | 3840x2160@30 | 240 Mbps | 4K |
NAL Unit 类型:
H.264 码流由一系列 NAL Unit(Network Abstraction Layer Unit)组成:
| NAL 类型 | 名称 | 说明 |
|---|---|---|
| 1 | 非 IDR 图像片 | 非关键帧数据 |
| 5 | IDR 图像片 | 关键帧数据 |
| 6 | SEI | 补充增强信息 |
| 7 | SPS | 序列参数集(分辨率、Profile、Level) |
| 8 | PPS | 图像参数集(熵编码模式、切片组) |
H.264 码流结构:
[Start Code] [NAL Header] [NAL Payload]
0x00000001 0x67 (SPS 数据)
0x00000001 0x68 (PPS 数据)
0x00000001 0x65 (IDR 帧数据)
0x00000001 0x41 (P 帧数据)
0x00000001 0x01 (B 帧数据)H.265 / HEVC
H.265(High Efficiency Video Coding,也称 HEVC)是 H.264 的继任标准,在同等画质下码率降低约 50%。
CTU / CU / CB / TB 树形块划分:
H.265 引入了更灵活的树形块划分结构,取代了 H.264 的固定 16x16 宏块:
CTU (Coding Tree Unit, 编码树单元)
64x64 / 32x32 / 16x16
|
+-- CU (Coding Unit, 编码单元)
| 递归四叉树划分
| 64x64 -> 32x32 -> 16x16 -> 8x8
| |
| +-- CB (Coding Block, 编码块)
| 亮度 CB + 色度 CB
|
+-- PU (Prediction Unit, 预测单元)
| 帧内: 2Nx2N / NxN
| 帧间: 对称 (2Nx2N/2NxN/Nx2N) + 非对称 (2NxnU/2NxnD/nLx2N/nRx2N)
|
+-- TU (Transform Unit, 变换单元)
递归四叉树划分
32x32 -> 16x16 -> 8x8 -> 4x4
TB (Transform Block, 变换块)H.265 与 H.264 对比:
| 特性 | H.264 | H.265 |
|---|---|---|
| 宏块/CTU 大小 | 最大 16x16 | 最大 64x64 |
| 块划分方式 | 固定宏块 | 递归四叉树 |
| 帧内预测模式 | 9 种 (4x4) / 4 种 (16x16) | 35 种 |
| 运动补偿精度 | 1/4 像素 | 1/4 像素 + DCT 插值 |
| 变换尺寸 | 4x4 / 8x8 | 4x4~32x32 |
| 熵编码 | CAVLC / CABAC | CABAC(改进) |
| 去块滤波 | 简单 | 自适应环路滤波 (SAO) |
| 编码效率 | 基准 | 提升约 50% |
兼容性:
H.265 的兼容性相较于 H.264 仍有差距:
- 软件解码:较新的浏览器(Chrome 105+、Safari 11+)支持
- 硬件解码:2015 年后主流 GPU/Mobile SoC 支持
- 浏览器支持:Safari 原生支持,Chrome/Edge 需 HEVC 扩展或平台支持
- 流媒体协议:HLS 支持 H.265,RTMP 需自定义扩展
AV1
AV1 是由开放媒体联盟(AOM,Alliance for Open Media)开发的开源、免专利费的视频编码标准,成员包括 Google、Mozilla、Microsoft、Netflix、Amazon 等。
核心特点:
| 特性 | 说明 |
|---|---|
| 专利权 | 开源免专利费,无需许可 |
| 压缩率 | 比 H.264 高约 50%,比 H.265 高约 20-30% |
| 编码器 | 官方 aom(libaom)、Rav1e、SVT-AV1 |
| 浏览器支持 | Chrome 70+、Firefox 67+、Edge 75+ |
| 硬件编码 | 2021 年后 GPU 开始支持(Intel Arc、NVIDIA RTX 40 系列) |
编码器对比:
| 编码器 | 开发方 | 语言 | 特点 |
|---|---|---|---|
| libaom (aom) | AOM / Google | C | 参考实现,压缩率最高但编码极慢 |
| Rav1e | Xiph / Mozilla | Rust | 关注编码速度和安全,适合实时场景 |
| SVT-AV1 | Intel / Netflix | C/ASM | 可扩展多线程,兼顾速度和质量 |
压缩率与编码耗时对比:
测试条件:1080p 视频,CRF 模式,目标 VMAF 90+:
| 编码器 | 码率 (kbps) | 相对 H.264 码率节省 | 编码速度 | 编码耗时 (相对) |
|---|---|---|---|---|
| H.264 (x264) | 3500 | 基准 | fast | 1x |
| H.265 (x265) | 2000 | -43% | medium | 3-5x |
| AV1 (SVT-AV1) | 1600 | -54% | medium | 5-10x |
| AV1 (rav1e) | 1550 | -56% | fast-medium | 8-15x |
| AV1 (libaom) | 1500 | -57% | veryslow | 50-100x |
注意:AV1 软件编码耗时远高于 H.264/H.265,硬件编码器正在快速追赶。
OpenH264 / Cisco 开源实现
OpenH264 是 Cisco 开源的 H.264 编码器实现,采用 BSD 许可证,主要用于 WebRTC 场景。
核心特性:
| 特性 | 说明 |
|---|---|
| 许可证 | BSD 2-Clause(免专利费,Cisco 提供专利保护) |
| 编码能力 | Baseline + Main Profile,最高 Level 5.2 |
| 解码能力 | Baseline + Main + High Profile |
| 编码器尺寸 | 约 50KB(极小,适合浏览器插件) |
| 应用场景 | WebRTC H.264 编码、Firefox H.264 支持 |
使用方式:
#include "codec_api.h"
// 初始化编码器
ISVCEncoder* encoder = nullptr;
WelsCreateSVCEncoder(&encoder);
SEncParamExt param;
encoder->GetDefaultParams(¶m);
param.iUsageType = CAMERA_VIDEO_REALTIME;
param.fMaxFrameRate = 30;
param.iPicWidth = 1280;
param.iPicHeight = 720;
param.iTargetBitrate = 2000000; // 2 Mbps
param.iMaxBitrate = 2500000;
param.iRCMode = RC_BITRATE_MODE;
param.bEnableDenoise = false;
encoder->InitializeExt(¶m);
// 编码帧
SFrameBSInfo info;
SSourcePicture pic;
pic.iColorFormat = videoFormatI420;
pic.iPicWidth = 1280;
pic.iPicHeight = 720;
pic.iStride[0] = 1280;
pic.iStride[1] = 640;
pic.iStride[2] = 640;
pic.pData[0] = yData;
pic.pData[1] = uData;
pic.pData[2] = vData;
encoder->EncodeFrame(&pic, &info);
for (int i = 0; i < info.iLayerNum; i++) {
SLayerBSInfo* layer = &info.sLayerInfo[i];
// 输出编码数据: layer->pBsBuf, layer->iNalCount, layer->iBsLen
}
// 释放编码器
encoder->Uninitialize();
WelsDestroySVCEncoder(encoder);WebRTC 中集成 OpenH264:
在 Chrome 中,H.264 编码通过操作系统平台编码器实现;在 Firefox 中,通过 OpenH264 插件实现。SRS 在 WebRTC 转码场景中也可以集成 OpenH264 提供服务端 H.264 编码能力。
转码实践
FFmpeg 转码命令
FFmpeg 是业界最强大的多媒体处理工具,支持几乎所有视频编码格式的转码。
H.264 编码:
# 基础转码:H.264 编码
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 128k output.mp4
# 直播推流转码(RTMP 输出)
ffmpeg -i rtmp://origin/live/stream \
-c:v libx264 -preset veryfast -tune zerolatency -crf 25 \
-c:a aac -b:a 96k \
-f flv rtmp://edge/live/stream
# H.264 高级参数
ffmpeg -i input.mp4 \
-c:v libx264 \
-profile:v high \ # Profile: baseline/main/high
-level:v 4.1 \ # Level: 4.1 (1080p)
-preset slow \ # 编码预设: ultrafast~veryslow
-crf 23 \ # CRF 质量: 0-51 (越小质量越好)
-x264-params "keyint=60:min-keyint=60:scenecut=0" \ # GOP=60帧
-c:a copy \
output.mp4H.265 / HEVC 编码:
# 基础转码:H.265 编码
ffmpeg -i input.mp4 -c:v libx265 -preset medium -crf 26 -c:a aac -b:a 128k output.mp4
# H.265 参数优化
ffmpeg -i input.mp4 \
-c:v libx265 \
-preset medium \
-crf 26 \
-x265-params "keyint=60:min-keyint=60:no-scenecut=1:bframes=4:aq-mode=3" \
-c:a copy \
output.mp4
# 对比:H.265 同等质量下码率约为 H.264 的 50-60%
# CRF: H.264 23 ≈ H.265 26 (同等主观质量)AV1 编码:
# 使用 SVT-AV1 编码器(推荐,速度较快)
ffmpeg -i input.mp4 -c:v libsvtav1 -preset 8 -crf 35 -c:a aac -b:a 128k output.mp4
# 使用 libaom 编码器(最慢,压缩率最高)
ffmpeg -i input.mp4 -c:v libaom-av1 \
-cpu-used 4 \
-crf 30 \
-row-mt 1 \
-tiles 2x2 \
-c:a aac -b:a 128k \
output.mp4
# AV1 SVT-AV1 参数说明
# -preset: 0-13 (0=最慢/质量最高, 13=最快/质量最低)
# 推荐: preset 8 (速度与质量的平衡点)
# -crf: 20-50 (推荐 30-40,AV1 的 CRF 范围与 H.264 不同)视频尺寸缩放:
# 固定尺寸缩放
ffmpeg -i input.mp4 -vf "scale=1280:720" -c:v libx264 -crf 23 output_720p.mp4
# 保持宽高比缩放
ffmpeg -i input.mp4 -vf "scale=1280:-1" -c:v libx264 -crf 23 output_720p.mp4
# 多分辨率输出(直播转码场景)
ffmpeg -i input.mp4 \
-vf "scale=1920:1080" -c:v libx264 -b:v 5000k -preset veryfast -c:a copy \
-vf "scale=1280:720" -c:v libx264 -b:v 2500k -preset veryfast -c:a copy \
-vf "scale=854:480" -c:v libx264 -b:v 1000k -preset veryfast -c:a copy \
-f tee -map 0:v -map 0:a \
"[f=mp4]output_1080p.mp4|[f=mp4]output_720p.mp4|[f=mp4]output_480p.mp4"
# 注意:多路输出需要使用 tee muxer 或 filter_complex码率控制模式:
# CRF(恒定质量)——推荐点播场景
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
# CRF 值参考: 18=视觉无损, 23=高质量, 28=普通, 35=低质量
# CBR(恒定码率)——推荐直播/低延迟场景
ffmpeg -i input.mp4 -c:v libx264 -b:v 2000k -minrate 2000k -maxrate 2000k -bufsize 4000k output.mp4
# VBR(可变码率)——两遍编码,质量最优
ffmpeg -i input.mp4 -c:v libx264 -b:v 2000k -pass 1 -f mp4 /dev/null
ffmpeg -i input.mp4 -c:v libx264 -b:v 2000k -pass 2 -c:a aac -b:a 128k output.mp4
# Capped VBR(有界可变码率)——推荐流媒体
ffmpeg -i input.mp4 -c:v libx264 -b:v 2500k -maxrate 5000k -bufsize 5000k output.mp4GPU 转码加速
NVIDIA NVENC 硬编码:
# 查看可用的 NVENC 编码器
ffmpeg -encoders | grep nvenc
# H.264 NVENC 编码
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v h264_nvenc \
-preset p4 \ # p1~p7 (p1=最快, p7=最慢质量最高)
-tune hq \ # hq=高质量, ll=低延迟, ull=超低延迟
-rc vbr \ # 码率控制: cbr/vbr/constqp
-b:v 2500k \ # 目标码率
-maxrate 5000k \ # 最大码率
-bufsize 5000k \ # VBV 缓存
-cq 23 \ # 质量等级(类似 CRF,0-51)
-c:a copy \
output.mp4
# H.265 NVENC 编码
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v hevc_nvenc \
-preset p4 \
-rc vbr \
-b:v 2000k \
-cq 25 \
output.mp4
# AV1 NVENC(RTX 40 系列及以上)
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v av1_nvenc \
-preset p4 \
-rc vbr \
-b:v 1500k \
-cq 28 \
output.mp4Intel QSV 硬编码:
# 查看 QSV 设备
ffmpeg -v debug -init_hw_device qsv=qsv -f lavfi -i null -f null -
# H.264 QSV 编码
ffmpeg -hwaccel qsv -i input.mp4 \
-c:v h264_qsv \
-preset medium \ # veryfast/faster/medium/slower/veryslow
-global_quality 23 \ # 质量 (1-51)
-b:v 2500k \
-c:a copy \
output.mp4
# H.265 QSV 编码
ffmpeg -hwaccel qsv -i input.mp4 \
-c:v hevc_qsv \
-preset medium \
-global_quality 25 \
-b:v 2000k \
output.mp4AMD AMF 硬编码:
# H.264 AMF 编码
ffmpeg -i input.mp4 \
-c:v h264_amf \
-quality quality \ # speed/balanced/quality
-rc cbr \ # cbr/vbr/qvbr
-b:v 2500k \
-c:a copy \
output.mp4
# H.265 AMF 编码
ffmpeg -i input.mp4 \
-c:v hevc_amf \
-quality quality \
-rc vbr \
-b:v 2000k \
output.mp4硬件转码延迟和吞吐量对比
编码延迟对比(1080p -> 720p 实时转码):
| 编码器 | 编码延迟 | 延迟特性 | 适用场景 |
|---|---|---|---|
| x264 (CPU, ultrafast) | 5-10ms | 低,受 CPU 负载影响 | 软件转码,无需 GPU |
| x264 (CPU, medium) | 30-50ms | 中等 | 点播转码 |
| h264_nvenc (p4) | 2-5ms | 极低,稳定 | 直播转码、实时通信 |
| h264_qsv | 3-8ms | 低 | 直播转码 |
| h264_amf | 3-8ms | 低 | 直播转码 |
| x265 (CPU, medium) | 100-200ms | 高 | 点播转码 |
| hevc_nvenc | 3-6ms | 极低 | 直播 HDR 转码 |
吞吐量对比(单路 1080p 转码, 同时支持的并发路数):
| GPU/方案 | H.264 编码 | H.265 编码 | 说明 |
|---|---|---|---|
| NVIDIA T4 | 8-12 路 | 6-8 路 | 云服务器常见 |
| NVIDIA A10 | 16-20 路 | 12-16 路 | 中端云 GPU |
| NVIDIA A100 | 24-32 路 | 18-24 路 | 高端云 GPU |
| NVIDIA RTX 4090 | 6-8 路 | 4-6 路 | 桌面 GPU |
| Intel Arc A770 | 8-12 路 | 6-10 路 | Intel 独立 GPU |
| Intel UHD 770 (核显) | 2-4 路 | 1-2 路 | 桌面 CPU 核显 |
| AMD Radeon Pro W5700 | 4-6 路 | 3-5 路 | AMD 工作站 GPU |
| x264 (CPU 16核) | 2-4 路 | - | 纯软件编码 |
注意:上述数据为近似值,实际性能受码率、分辨率、GOP 大小、编码预设等因素影响。GPU 编码的优势在于低延迟 + 高并发,适合直播和实时通信场景。CPU 软件编码在同等码率下画质更优,适合点播转码(非实时)。