说实话,第一次接到“用 Golang 做视频直播”这个需求的时候,我脑袋里是有点懵的,毕竟 Go 语言平时写写 API、搞搞微服务挺顺手,但视频流这玩意儿——听起来就是 C++ 或者 Python 的活儿,对吧?但真上手做了之后,我发现 Go 的并发模型和标准库在直播这块居然挺能打,今天就把我摸索出来的路子掰开了揉碎了跟你聊聊。
直播到底在传输什么?先别急着写代码
视频直播说白了就是三件事:采集、编码、传输,你手机摄像头拍到的是原始数据,很大很大,如果不压缩根本传不出去,所以要把这些数据编码成 H.264 或者 H.265 这类标准格式,然后通过 RTMP、HLS 或者 WebRTC 这些协议扔到服务器上,再分发到观众那边。
为什么选 Golang 而不是其他语言?
有人会说,C++ 性能多好啊,Python 轮子多啊,但 Golang 有几个点让我觉得真香:
- 并发是写死的:直播里要同时处理多个视频流、多个观众连接,Go 的 goroutine 用起来就跟呼吸一样自然。
- 部署太友好了:编译出来一个二进制文件,扔到服务器上就能跑,不用装奇怪的解码器依赖。
- 标准库的网络处理:
net/http和io包,跟流式数据处理简直是天生一对。
但你也别指望拿 Go 去写一个 FFmpeg 出来,那是纯属找虐,咱们的策略是:Go 做胶水层,FFmpeg 做编解码引擎。
第一步:采集和编码,得抱大腿
在 Go 里直接调用摄像头?有库,github.com/gen2brain/malgo 或者 gocv.io/x/gocv,但说真的,这条路走着走着就发现全是坑,我试过用 gocv 采集摄像头画面,然后转成字节流,结果延迟高得离谱。
最靠谱的做法:用 Go 拉起一个外部进程跑 FFmpeg。
package main
import (
"bytes"
"io"
"log"
"os/exec"
)
func startCapture() (io.ReadCloser, error) {
cmd := exec.Command("ffmpeg",
"-f", "avfoundation", // macOS 摄像头设备
"-i", "0:0",
"-c:v", "libx264",
"-preset", "ultrafast",
"-f", "flv",
"pipe:1",
)
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
return stdout, nil
}
你看看,Go 负责管道管理,FFmpeg 负责干活。-preset ultrafast 这个参数能大幅降低延迟,如果你做的是实时直播,这个参数几乎必须加。
第二步:用 Golang 搭建 RTMP 服务器,其实没那么难
RTMP 是 Adobe 搞的老协议了,但它依然是推流届的扛把子,你可以在远端部署一个 Nginx 加 RTMP 模块来接收流,但如果你想要纯 Go 的轻量级方案,我推荐用开源库 github.com/nareix/joy4。

简单起一个 RTMP 服务器
package main
import (
"github.com/nareix/joy4/av"
"github.com/nareix/joy4/av/rtmp"
"log"
)
func main() {
server := &rtmp.Server{}
server.HandlePlay = func(conn *rtmp.Conn) {
log.Println("观众连接过来了")
// 这里把视频包转发给观众
conn.WritePacket(av.Packet{})
}
server.HandlePublish = func(conn *rtmp.Conn) {
log.Println("主播开始推流")
for {
pkt, err := conn.ReadPacket()
if err != nil {
return
}
// 收到一帧数据,存起来或者广播出去
_ = pkt
}
}
log.Fatal(server.ListenAndServe())
}
你看,核心逻辑就这么多。joy4 帮我们搞定了 RTMP 协议的解析和打包,剩下的就是数据搬运,但这里有个细节:HandlePublish 和 HandlePlay 是两个不同的 goroutine 在执行,你需要在它们之间建立一个管道或者缓冲队列来传递视频包。
一个简单的广播机制
type StreamHub struct {
streams map[string]chan av.Packet
mu sync.RWMutex
}
func (h *StreamHub) Publish(key string, packets <-chan av.Packet) {
// 把数据广播给所有订阅了这个 key 的观众
}
是的,map + chan + sync.RWMutex 就搞定了多路并发的问题,Go 的并发原语在这里特别顺手。
第三步:HLS 和 WebRTC,两种不同的玩法
RTMP 在移动端支持不太好,所以现在主流是往 HLS 或 WebRTC 转。
| 协议 | 延迟 | 适合场景 | Go 实现方式 |
|---|---|---|---|
| RTMP | 2-5秒 | 传统推流 | joy4 接收,转发 |
| HLS | 10-30秒 | 点播、大规模分发 | 切片成 .ts 文件,写 .m3u8 |
| WebRTC | <1秒 | 视频通话、互动直播 | 用 pion/webrtc 库 |
把 RTMP 转成 HLS
这块我直接用了 FFmpeg 命令行处理,Go 里只做文件名字管理和状态同步。
func convertToHLS(inputFile string, outputDir string) {
cmd := exec.Command("ffmpeg",
"-i", inputFile,
"-c:v", "copy",
"-c:a", "aac",
"-f", "hls",
"-hls_time", "4",
"-hls_list_size", "10",
outputDir+"/index.m3u8",
)
cmd.Run()
}
-hls_time 4 意思是每 4 秒切一个 .ts 分片,延迟高的原因就在这——你必须等一个切片完整生成才能播放,所以HLS 不建议用在实时互动场景,但大规模分发真的很稳。
WebRTC 上点硬货
如果你想要毫秒级延迟,WebRTC 是唯一选择,Go 里用 github.com/pion/webrtc/v3 库可以纯 Go 实现信令和媒体通道。
func createWebRTCSession() (*webrtc.PeerConnection, error) {
m := webrtc.MediaEngine{}
m.RegisterCodec(webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000))
m.RegisterCodec(webrtc.NewRTPH264Codec(webrtc.DefaultPayloadTypeH264, 90000))
api := webrtc.NewAPI(webrtc.WithMediaEngine(&m))
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{URLs: []string{"stun:stun.l.google.com:19302"}},
},
}
peerConnection, err := api.NewPeerConnection(config)
if err != nil {
return nil, err
}
return peerConnection, nil
}
信令交换(SDP 和 ICE candidate)得你自己写,一般用 WebSocket 在客户端和服务器之间传,这块代码量会大一些,但是效果真的非常好。
第四步:处理卡顿和性能优化,痛过才知道
直播最怕什么?卡顿,我踩过的坑列出来当个警示牌:
- 缓冲队列不要设太大:我一开始设了 100 帧缓冲,结果延迟飙到 8 秒,现在改成 10 帧,舒服多了。
- 使用
runtime.GOMAXPROCS:默认 Go 用的是 CPU 核心数,但你处理视频转码时,可以适当限制一下,防止跟 FFmpeg 抢 CPU。 - 及时丢弃旧帧:如果观众端消费能力跟不上,别硬撑,直接把老的包丢弃,用
select加default实现非阻塞发送。
select {
case ch <- pkt:
default:
// 观众消费太慢,丢掉旧包
}
一个性能对照表
| 操作 | 没优化 | 优化后 | 改善点 |
|---|---|---|---|
| 接收 RTMP 流 | 12% CPU | 6% CPU | 改用 io.Copy 替代逐字节读写 |
| 转 HLS 切片 | 75% CPU | 40% CPU | 限制 FFmpeg 的 -threads 参数 |
| 广播到 100 个观众 | 掉帧严重 | 稳定 30fps | 用 sync.Pool 复用缓冲对象 |
第五步:部署和运维,一个二进制搞定
我最后把整个服务器编译成一个 Go 二进制文件,加上 FFmpeg 的静态编译版本,打包扔到一台 2 核 4G 的云服务器上,启动命令超级简单:
./live-server --port 1935 --hls-dir /data/hls
日志用 Go 的 log 包,监控用 /debug/vars 暴露内部状态,一切从简,但是很稳,有一次跑了一周多没重启。
当然你可以用 Prometheus 收集更详细的指标,但我自己用的方案更土一点:每 10 秒往日志里打印一次当前活跃连接数和内存占用,够用了,真的够用了。
写在最后
用 Golang 做视频直播,从技术选型来看,它不是万能钥匙,但你把它放在合适的位置——作为调度中心、数据管道、协议服务器——它真的特别出彩,FFmpeg 负责脏活累活,Go 负责把一切黏合得妥妥当当。
我到现在还是每隔两周翻一次 FFmpeg 文档,也偶尔会为 WebRTC 的信令逻辑抓狂,但这不就是写代码的日常嘛,改一行跑一下,慢慢就往前走了。
本文来自作者[kyadmin]投稿,不代表中国·AC米兰(Milan)体育官方网站-Official Website立场,如若转载,请注明出处:http://www.graee.com/fc/963.html
评论列表(4条)
我是中国·AC米兰(Milan)体育官方网站-Official Website的签约作者“kyadmin”!
希望本篇文章《如何用 Golang 做视频直播?我踩过的坑和真香时刻》能对你有所帮助!
本站[中国·AC米兰(Milan)体育官方网站-Official Website]内容主要涵盖:AC米兰,ac米兰官网,AC米兰官网
本文概览:说实话,第一次接到“用Golang做视频直播”这个需求的时候,我脑袋里是有点懵的,毕竟Go语言平时写写API、搞搞微服务挺顺手...