当前位置:首页 > 赛程 > 正文

用Go语言做视频直播?这事儿我从踩坑到跑通的全过程

  • 赛程
  • 2026-07-25 16:11:27
  • 33
摘要: 为什么突然想用Go搞直播?上周有个朋友问我:“你们程序员能不能自己搭个直播间,不用第三方平台那种?”我第一反应是“那得用ffmp...

为什么突然想用Go搞直播?

上周有个朋友问我:“你们程序员能不能自己搭个直播间,不用第三方平台那种?”我第一反应是“那得用ffmpeg吧”,但转念一想,Go语言的并发模型和网络库天生适合做流媒体处理,为什么不试试?

说实话,直播这东西听起来高大上,拆开来看无非就是采集→编码→传输→解码→播放五个环节,而Go语言在中间的传输环节,简直像为它量身定做的——goroutine处理并发连接,channel管理数据流,标准库里的net/httpnet包又能快速搭建信令服务。

第一步:搞清楚直播到底在传什么

别被“视频流”这个词唬住,视频直播本质上是在连续不断地传图片,只不过每秒钟要传24-30张(帧),还得带上声音,常见的协议有:

  • RTMP:Adobe家的老牌协议,推流端常用
  • HLS:苹果发明的,把视频切成小片段,适合播放端
  • WebRTC:点对点,延迟最低但复杂度高

我选的是RTMP + HLS的组合——用Go写一个RTMP服务器接收推流,同时转成HLS分发给播放器,这样推流端用OBS(免费软件)就能推,播放端用浏览器直接看。

第二步:Go语言怎么处理视频流?

这里必须坦白:Go本身不直接处理视频编解码,真正干苦力活的是C语言的FFmpeg库,但Go的优势在于调度——你可以用os/exec包调用FFmpeg命令行,也可以用cgo直接调FFmpeg的C接口。

我选了更“Go风格”的做法:用库 joy4(一个纯Go实现的音视频处理库),虽然它不如FFmpeg成熟,但胜在不用装外部依赖,核心代码就几行:

// 伪代码示意,别直接复制用
rtmpServer := &rtmp.Server{}
rtmpServer.HandlePlay = func(conn *rtmp.Conn) {
    // 收到推流后,启动协程转码
    go transcodeToHLS(conn.Stream)
}

但这玩意儿有个坑:joy4的HLS切片实现不太稳定,我实际生产环境还是换成了调用系统FFmpeg,用Go封装FFmpeg的做法很普遍,比如这样:

cmd := exec.Command("ffmpeg", 
    "-i", "rtmp://localhost/live/stream",
    "-c:v", "libx264",
    "-hls_time", "2",
    "-hls_list_size", "10",
    "output.m3u8")
cmd.Run()

第三步:信令服务器——直播的门卫

直播不只是传视频,还得控制谁在看、谁在推,这部分Go就舒服了,用net/http写个简单的REST API:

接口路径 作用 响应示例
/create 创建直播间 {"room":"abc123"}
/push 获取推流地址 rtmp://server/live/abc123
/play 获取播放地址 http://server/hls/abc123.m3u8

我用了gorilla/websocket做实时信令,观众打开页面时,服务器通过WebSocket通知他当前推流状态,这样观众既不用反复刷页面,也能看到“直播已结束”的提示。

第四步:播放器端——用浏览器看Go传的流

播放器我选了video.jsHls.js插件,原因是兼容性好,页面代码大概长这样:

<video id="player" controls></video>
<script>
var video = document.getElementById('player');
if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('http://localhost:8080/hls/stream.m3u8');
    hls.attachMedia(video);
}
</script>

注意:必须通过HTTP服务把.m3u8.ts文件暴露出去,我直接在Go里用http.FileServer挂载了HLS输出目录:

http.Handle("/hls/", http.StripPrefix("/hls/", http.FileServer(http.Dir("./hls_output"))))

第五步:踩过的三个大坑

  1. 延迟问题:HLS默认有15-30秒延迟,解决办法是缩短切片时长,比如切成2秒一个的片段,并且把hls_list_size设小,但代价是服务器压力变大。
  2. 断流重连:推流端网络不稳时,FFmpeg会报错退出,我用了个监督协程:每隔5秒检查FFmpeg进程是否存活,挂了就自动重启。
  3. 内存泄漏:早期版本每个推流连接都开goroutine,但没做清理,后来加上了context.WithCancel到超时或推流结束自动释放资源
ctx, cancel := context.WithTimeout(context.Background(), 24*time.Hour)
defer cancel()
go handleStream(ctx, conn)
// 用ctx.Done()判断是否该退出

最后跑起来的样子

OBS里推流地址填rtmp://localhost:1935/live/test,浏览器打开http://localhost:8080/player?room=test还真能看到画面了,虽然只有640x480分辨率,延迟大概5秒,但全程没用第三方平台。

说实话用Go做直播不是最主流的选择——Python有更成熟的框架,Node.js有socket.io生态,但Go的优势在你需要高并发、低资源消耗时特别明显,比如同时有100个观众,Go服务器只开几个goroutine就能扛住,换成其他语言可能已经CPU飙升了。

如果你想试着跑起来,建议先装好FFmpeg,然后用Go调用它——别像我一开始那样执着于纯Go实现,毕竟工具是拿来用的,不是拿来信仰的

下次再有人问我“程序员能不能自己搭直播间”,我会说:“能,但你要先准备好踩坑。”

用Go语言做视频直播?这事儿我从踩坑到跑通的全过程