自建视频流媒体平台:从OBS推流到SRS分发的完整实践指南
2026/9/16 8:17:46 网站建设 项目流程

搞视频自建这套东西,前前后后折腾了三四年,中间踩过的坑比我写过的代码都多。这次想把手里的一个项目完整拆开聊聊,项目名就叫LunaTV,一个自建视频流媒体服务平台。Luna是月神的名字,取这个名倒没什么深意,就是希望它安静、稳定、夜里有光。现在我已经把它跑在公网VPS上,给一个小圈子里的朋友做直播和点播用,效果相当稳。

LunaTV不是什么商业产品,也不是开源大作,它就是一套完全自管理的视频直播与点播方案,核心链路是:OBS推流 -> SRS媒体服务器接收 -> RTMP/WebRTC入流 -> HLS/HTTP-FLV分发 -> 浏览器播放器拉流观看。你可以把它理解成一个私有化的“小电视台”,内容完全归自己控制。适合谁参考?如果你是一个想要给社群做稳定直播的技术博主,或者纯粹想把家里的监控、窗外风景、宠物画面做成一个长期直播链接分享给朋友,又或者需要一个可归档、可重看的私有视频存储系统,这篇东西应该能帮你省掉好几周摸索的时间。

我做这套东西的时候,市面上不是没有现成的方案。B站直播、视频号、YouTube都能一键开播,为什么还要自己搭一套?答案很简单:控制权和数据隐私。平台直播有审核规则、有延时、有广告插播,最要命的是内容不由你决定去留。LunaTV这套方案里,流是自己收的,片子是自己存的,推流密码自己管,连播放器样式都能改成自己想要的。说白了,这是一个折腾过的人才写得出来的完整过程记录,包含我踩过的大部分坑和最终跑通的配置。

1. 内容整体设计与思路拆解

1.1 这个方案到底要解决什么问题

在决定自建之前,我先列了一下痛点,写得很直白。第一,平台直播的延迟不可控,交互性强的场景根本没法用。第二,平台内容审核机制不可控,不过审就全白干。第三,直播结束之后想保存一个高质量回放,平台会压缩画面,还会在某个时间自动清理。第四,我需要同时服务固定的一批人,不想让第三方平台每开一场直播就生成一个新链接,我想给一个固定的入口,内容持续归档。

LunaTV的核心设计目标因此定成五个词:低延迟、高可控、可归档、免维护、能看回放。低延迟我走了WebRTC和HTTP-FLV两条线,本地测试的时候端到端延迟压到1到3秒;高可控体现在所有配置都在一个web管理面板里完成;可归档靠的是SRS的录制回放功能;免维护意思是不需要我在直播现场盯着看,跑定时脚本加探活就够了;回放功能就绑定在录制文件上,一台服务器全搞定。

围绕这五个目标,我放弃了几个看起来很美的方案。比如一开始考虑过用Nginx搭配RTMP模块,经典但老气,性能瓶颈明显。也考虑过直接上GStreamer搭建全链路,灵活是灵活,但运维成本太高,不适合长期跑。最后选了SRS,不是因为它功能最全,而是因为它干的事情足够聚焦,设计上就是一个为直播和流媒体分发而生的服务器,RTC、RTMP、HLS、HTTP-FLV、SRT全都支持,社区也活跃,踩坑时能搜到答案。

1.2 方案选型的具体考量和取舍逻辑

先给一个我当时做选型的对比表,这份表多少能反映出我在做技术决策时的思考方式:

方案延迟表现部署难度运维成本协议支持适合场景
Nginx + RTMP模块中(3-5秒)RTMP为主,HLS需额外插件轻量直播,临时跑一跑
GStreamer全链路低(1-2秒)任意,全靠自己搭研究实验,不适合长期生产
SRS低(1-3秒)RTC/RTMP/HLS/HTTP-FLV/SRT全支持个人与中小规模生产
商业云直播极低极低全面,但封闭有预算且不想自己管

我当时毫不犹豫选了SRS。一个核心理由是它的部署友好度:一个二进制文件就够,配置文件是扁平化的,官方文档写得非常实用,每一项参数的默认值都经过生产验证,不需要我再去猜。另外一个理由是它的HLS切片能力在个人服务器上性能表现很好,实测4M码率的1080p直播流,切片在普通机械硬盘上也能跑得动,没有任何卡顿,这就给回放功能打好了底子。

对比之下Nginx加RTMP组合的问题在于,Nginx本身不是为流媒体设计的,RTMP模块的生态也停更了很久。虽然网上教程最多,但它解决问题的路径往往是“要HLS就再配一个ffmpeg切片”,整体链路拉得很长,出问题的时候排查也麻烦。如果你只是想偶尔开一次直播,Nginx方案勉强能用;但如果你像我一样想要一个长期稳定、带录制归档的私有电视台,直接上SRS一天就能跑通,没必要走弯路。

1.3 整体架构与数据流向

LunaTV整个架构可以拆成四层。采集层,我用OBS Studio做推流客户端,它负责把摄像头画面、屏幕内容、音频混合成一个输出流。接入层,SRS监听1935端口接收RTMP推流,同时监听1985端口提供HTTP API和HTTP-FLV服务,此外还启动了一个WebRTC端口做低延迟实时交互。分发层,SRS内部把RTMP流转封装成HLS切片,切成一个个几秒钟的.ts文件,同时通过播放器拉取m3u8索引来观看;HTTP-FLV则是推流端直接可用的低延迟分发协议。存储层,SRS开启了录像功能,直接把收到的RTMP流写成flv文件,放在指定目录,方便后续处理或者直接当点播资源。

数据流向比想得简单,OBS把流推到rtmp://你的服务器地址:1935/live/lunatv,SRS接收后同时做三件事:一是转成HLS切片文件放到web目录,二是作为HTTP-FLV源直接喂给网页播放器,三是以flv文件形式落盘存档。浏览器端播放器的逻辑是,优先用HTTP-FLV做直播,延迟低;如果浏览器不支持,比如Safari,就走HLS。这套分流设计让我在电脑Chrome上延迟只有2秒左右,手机Safari也能稳定播放。

2. 核心细节解析与实操要点

2.1 视频流的三个关键环节

整个链路里最值得花时间理解的是三个环节:采集与推流、服务端转封装、播放端拉流。

采集与推流,核心是不要再传一遍无损画面。很多人第一次用OBS推流,直接选“无损”或者极高码率,结果带宽全被吃光,服务器瞬间傻眼,播放端反而卡成幻灯片。实际做法是根据你上行带宽和服务器下行带宽来定码率,个人场景1080p推流给3000到5000kbps够了,画面清晰度完全够用;720p的话2000到3000kbps更稳。为什么是这个范围?因为1080p的H.264编码在3000kbps以上已经能保证大部分动态画面不出现明显马赛克,再低就会糊得没法看,再高则对服务端转码和客户端解码都带来压力。

服务端转封装,这里的“转封装”不是转码。SRS默认不改变流的编码格式,它只是把RTMP协议封装里的数据重新打包成HLS切片或者FLV标签。这意味着H.264/H.265的视频流、AAC的音频流,都需要你的推流端先把原始画面和声音压缩好。理解这一层很重要,因为很多人在服务器上找转码参数找不到,其实SRS默认就没有转码引擎。如果你确实需要服务器对视频重新编码,得单独引入FFmpeg作为转码中间层,但个人场景一般不需要,推流端编码就够了。

播放端拉流,这一层最大的坑是“浏览器兼容性”。PC端Chrome、Firefox对HTTP-FLV支持不错,Safari则就走HLS;移动端iOS Safari原生支持HLS,安卓Chrome则更依赖HTTP-FLV。为了不做一个只兼容一半的播放器,我在前端集成了两个核心解析器:mpegts.js处理HTTP-FLV,hls.js处理HLS。这样一套页面在几乎所有主流浏览器里都能稳定播放,用户根本不用手动装插件。

2.2 转码与码率参数的“为什么”

很多教程上来就让你把参数照抄,但参数背后的逻辑才是真正值钱的部分,照抄不一定是适合你的场景。

先讲码率。公式其实很简单:码率等于单帧数据量乘以帧率,单位是kbps或Mbps。画面分辨率定下来后,编码复杂度越高,单帧数据量越大。所以1080p60的视频,比1080p30需要更高码率才能保持同样画质。我当时按3个场景做了三个档位:

场景分辨率帧率码率关键帧间隔
屏幕分享/教程1920x1080303500kbps2秒
唱歌/聊天类直播1280x720302500kbps2秒
户外长时间挂机直播854x480241000kbps4秒

关键帧间隔这一点特别容易被忽略,但它在低延迟播放里作用非常大。HTTP-FLV和HLS本质上是边下载边播放,播放器只有拿到关键帧才能开始解码,如果关键帧间隔太长,观众打开页面的时候就要等好几秒才能出画面。设成2秒意味着每2秒一个关键帧,观众基本是秒开体验。代价是这个关键帧体积比较大,会多一些带宽消耗,实际测算下来,关键帧在总码率里占的比例不超过10%,这点开销换低延迟绝对划算。

再说音频部分,个人项目里音频编码用AAC-LC,采样率44100Hz,码率128kbps足够。如果做纯音乐类直播,可以上256kbps,但一般128就够了。音频参数上很多人犯的错是跟着视频码率一起往上抬,纯属浪费带宽。128kbps的AAC-LC在听感上已经超过大部分人的日常需求,把它当成固定值就行。

2.3 播放协议选型与延迟实测

选播放协议这事,其实可以一句话总结:直播要低延迟就HTTP-FLV加WebRTC,回放和兼容面优先就HLS。但这背后有一些实验数据支撑,这里直接放我自己的实测结果:

协议端到端延迟浏览器兼容首帧耗时适用场景
HTTP-FLV1-3秒Chrome/FF优秀,Safari较差<500msPC端低延迟直播
HLS5-10秒几乎所有浏览器1-3秒跨端兼容直播/回放
WebRTC<800ms现代浏览器均支持<300ms互动连线、低延迟对谈

我在LunaTV里默认开了HTTP-FLV和HLS两种协议。网页播放器先探测浏览器是否支持FLV,如果支持就拉HTTP-FLV流,不支持就自动切HLS。这个切换逻辑是整个播放器最核心的部分,一开始我没做自动切换,结果一半观众流畅一半观众卡,最后才发现是浏览器兼容问题。

有一点必须提醒,WebRTC虽然延迟最低,但它在服务器端对带宽和CPU消耗都更大,一个WebRTC观众另外还需要单独维护一条连接,不像HTTP-FLV那样走普通HTTP就能多路复用。所以我的策略是默认场景不用WebRTC,只在做互动问答之类的场景才临时切换,既保证体验又控制成本。

3. 实操过程与核心环节实现

3.1 服务器选型与环境初始化

搭建LunaTV首先得有一台公网可达的服务器。我选的是2核4G内存的云主机,带宽30Mbps,系统Ubuntu 22.04 LTS。为什么这个配置?2核主要给SRS和可能的FFmpeg转码进程用,4G内存足够跑SRS加录制模块,30Mbps带宽在个人直播场景下可以容纳约10个1080p观众同时观看,性价比合适。如果你主要做小圈子直播,2核2G带宽10M就能跑,但录制回放和切片会稍微吃力一点,内存不足时HLS切片会有延迟。

装好系统后,先更新基础环境:

sudo apt update && sudo apt upgrade -y sudo apt install -y git wget curl build-essential

然后下载SRS。我直接用官方release版本,没有从源码编译,省时间也稳定:

cd /opt wget https://github.com/ossrs/srs/releases/download/v5.0.5/srs-5.0.5-linux-amd64.tar.gz tar xf srs-5.0.5-linux-amd64.tar.gz mv srs-5.0.5-linux-amd64 srs cd srs

SRS的二进制包解压就能用,这点是真的友好。我用的是v5版本,配置文件在conf/srs.conf,不过我没有直接改默认配置,而是新建了一个lunatv.conf,保持默认配置可用作回滚。

3.2 SRS配置与启动

这份配置是我经过数次调整后一直在用的,每一个参数都有明确意图:

listen 1935; max_connections 1000; daemon on; pid ./objs/srs.pid; http_api { enabled on; listen 1985; } http_server { enabled on; listen 8080; dir ./objs/nginx/html; } rtc_server { enabled on; listen 8000; candidate $CANDIDATE; } vhost __defaultVhost__ { hls { enabled on; hls_path ./objs/nginx/html; hls_fragment 4; hls_window 60; } http_remux { enabled on; mount [vhost]/[app]/[stream].flv; } dvr { enabled on; dvr_path ./objs/nginx/html/record/[stream].[timestamp].flv; dvr_plan session; } play { gop_cache on; queue_length 10; } }

解释几个关键项。hls_fragment设成4秒,意思是每个切片4秒长,适合快速起播;hls_window设成60秒,表示播放器最多往回看60秒的直播内容。这是直播的滑动窗口,超过了就会被清理,不用担心存储爆炸。dvr_plan设成session,意思是每次推流开始到结束,落盘成一个完整的FLV文件,对回放归档最友好。gop_cache开起来是为了让观众进入直播间时能快速首帧,代价是多缓存一个关键帧组,这个缓存机制实测让秒开体验提升非常明显。

启动命令:

export CANDIDATE=你的服务器公网IP ./objs/srs -c conf/lunatv.conf

如果不设CANDIDATE变量,WebRTC会拿不到正确IP,这是SRS里最容易翻车的地方之一。启动后确认一下日志,看到“start server successfully”就说明起来了。

3.3 OBS推流参数设置

服务端就绪后,客户端用OBS推流。在“设置-直播”里,服务选择“自定义”,服务器填:

rtmp://你的服务器IP:1935/live/

串流密钥填一个你想用的流名称,比如lunatv。这样完整推流地址就是rtmp://你的服务器IP:1935/live/lunatv

OBS的“输出”设置里,建议按我之前给你的参数来:视频编码器用硬件编码器NVENC或AMD,码率按场景选择,CPU编码也行但会占用较多机器资源,可能导致画面掉帧。音频编码器选AAC,采样率44100,码率128。视频设置里,基础分辨率和输出分辨率保持一致,不要开自动缩放,缩放会带来额外的CPU和画质损失。

一个很重要的小细节是OBS里的“关键帧间隔”。它默认是0(自动),但自动模式下间隔可能过大,我会手动设成2秒,配合SRS的gop_cache,观众进入直播间时基本能做到秒开。另一个小细节是把“麦克风/辅助音频”的采样率设成44.1kHz,避免音频采样率不匹配导致的声音发闷。

OBS开始推流后,可以先去服务端验证一下是否收到了流:

curl http://127.0.0.1:1985/api/v1/streams/

如果返回里能看到app=livename=lunatv这样的信息,就说明流已经进来了。

3.4 网页播放器接入

播放页面是最直接面对观众的部分。我写了一个简化版播放器页面,集成了HTTP-FLV和HLS两个解析器。

首先是引入依赖:

<script src="https://cdn.jsdelivr.net/npm/mpegts.js@1.2.1/dist/mpegts.js"></script> <script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7/dist/hls.min.js"></script>

然后核心播放逻辑:

const streamName = 'lunatv'; const baseUrl = '你的服务器IP:8080'; function initPlayer() { const videoElement = document.getElementById('video'); // 优先尝试HTTP-FLV if (mpegts.isSupported()) { const flvUrl = `http://${baseUrl}/live/${streamName}.flv`; const player = mpegts.createPlayer({ type: 'flv', isLive: true, url: flvUrl }); player.attachMediaElement(videoElement); player.load(); player.play(); } else if (Hls.isSupported()) { // Safari等不支持FLV时,走HLS const hlsUrl = `http://${baseUrl}/live/${streamName}.m3u8`; const hls = new Hls(); hls.loadSource(hlsUrl); hls.attachMedia(videoElement); hls.on(Hls.Events.MANIFEST_PARSED, () => videoElement.play()); } } initPlayer();

这个页面的核心就是先探测支持哪个协议,再选择对应的解析器。mpegts.js支持HTTP-FLV直接用浏览器原生Media Source Extensions解码,hls.js同理但走的是HLS。实测下来Chrome首选FLV首帧700毫秒左右,Safari走HLS大概2秒出画面,整体体验非常顺滑。

当时写这个播放器页面时,有个地方反复改了几次。就是mpegts.js的参数里,isLive: true必须显式设置,不然默认按点播逻辑去缓冲,直播延迟能飙到十几秒。改完之后,延迟立刻从12秒降到2秒左右,这个细节强烈建议注意。

3.5 点播回放与录制归档

SRS的dvr录制模块会让每次推流自动生成一个FLV文件,存放在配置里指定的record目录。但FLV格式不适合直接做点播,浏览器原生播放不了,所以我在LunaTV的回放模块里加了一步:用FFmpeg把FLV转成MP4,然后放到Nginx静态目录供点播。

转换命令很直接:

ffmpeg -i /path/to/record/xxx.flv -c copy /path/to/archive/xxx.mp4

-c copy是直接复制编码数据,不重新编码,所以速度极快,一个1小时的直播文件大概1分钟内就能转完,画质零损失。然后我就写了个小脚本,每天凌晨扫描record目录,把前一天新生成的FLV转成MP4并移入点播目录,同时清理老的FLV源文件。这样直播和点播就形成了完整的闭环。

点播播放就更简单了,HTML5的<video>标签原生支持MP4,不需要额外写播放器逻辑。放一个视频列表页,扫一下目录里的文件,列成链接就能看回放。

4. 常见问题与排查技巧实录

4.1 播放黑屏却显示流在推

这个问题我遇到过好几次,特征是OBS显示推流稳定,服务器也能看到流,但网页播放器黑屏。排查思路要顺着协议链路走。

先确认服务器端的流状态,用SRS的API查询:

curl http://127.0.0.1:1985/api/v1/streams/ | jq

如果流确实存在,再看播放器拉的是哪个地址。HTTP-FLV的问题通常是端口或者路径写错,比如8080端口没开放,或者flv路径里的vhost、app、stream没对齐。HLS黑屏则最常见于切片目录下没有生成.m3u8文件,而此时能生成说明hls模块配置没启用。我手头遇到最多的场景是改配置后忘了重启SRS,改完hls_fragment之类的参数不重启的话根本不生效。

4.2 推流画面延迟越来越大

正常SRS配置下直播延迟应该在2到5秒内。如果你发现观众看到的画面比实际时间慢很多,甚至越来越慢,十有八九是播放器缓存和缓冲设置的问题。

mpegts.js默认会用较大的缓冲来抗网络抖动,这在点播场景很好,但在直播场景是灾难。需要把参数调成低延时模式:

const player = mpegts.createPlayer({ type: 'flv', isLive: true, url: flvUrl, lazyLoad: false, liveBufferLatencyChasing: true, liveBufferLatencyMaxLatency: 3, liveBufferLatencyMinRemain: 1 });

这几个参数的意义在于,告诉播放器不要无限堆积缓冲区,当延迟超过3秒就开始追帧,最小保留1秒缓冲保证流畅。加了这套配置后,我的直播延迟稳定在2秒左右,观感像是真的在“直播”。

服务器端也能配合调整,gop_cache on为观众提供快速起播,但前提是缓存窗口不要开太大。如果SRS配置了很大的gop缓存,新观众进入时会先加载缓存的关键帧组,反而会感觉到较大的延迟,所以配置窗口要克制,4秒一个切片、缓存60秒就够。

4.3 公网访问不通与安全防护

跑公网服务最常见的问题是端口没开。LunaTV依赖3个端口:1935(RTMP推流端口)、1985(HTTP API端口)、8080(HTTP播放端口),另外WebRTC用8000/UDP。云服务商的安全组和服务器iptables都要放行这些端口。当时我排查了很久才发现是云平台安全组里默认不开放1935,推流一直报连接超时。

安全方面有几个优先级很高的建议:

  • 播放和推流地址不要让外网随便猜。串流密钥用一个足够长的随机字符串,比如lunatv_9f8d7c6b5a4e这种,不要用testlive这种弱密钥。
  • SRS的HTTP API端口最好绑定在内网或者限制IP,不然任何人调用API都能看到你的流列表。
  • 录制文件目录不要直接暴露到公网,除非你有意做公开点播,否则配置一个访问鉴权会更安全。
  • 如果长期跑,建议在Nginx反代层开启Basic Auth或者token验证,保护播放页面。

4.4 观众并发上来后卡顿

个人直播通常不会遇到特别大的并发,但难免有朋友拉好几个平台转播的情况。如果200人在线,30Mbps带宽就会被打满。这里可以做个简单的估算:每人观看1080p流,大约需要3.5Mbps带宽。30Mbps理论上只够8个人流畅观看,一旦观众再多,就开始出现缓冲。

优化手段有几个:

  • 降码率分发。在SRS前面加一层转码,给观众提供一个720p的档位,每人只占2Mbps左右,能多容纳近一半观众。
  • 走CDN分发。自建边缘节点成本高,个人项目直接买云直播CDN按量付费最划算。
  • 限制单路流并发。如果只是给几个朋友看的直播,在Nginx层面限制连接数就行,避免服务器被拖垮。

我当时的选择是直接限制并发,超过8个观众就暂时拒绝额外连接,宁可少数人看流畅,也不要几百个人卡成PPT。

5. 后续扩展与长期维护体会

5.1 可扩展的方向

LunaTV跑通之后,可延展的空间其实很大。我最想分享的几个方向是:

多清晰度切换。在SRS前面挂一个FFmpeg转码服务,把收到的1080p流转成720p和480p两路,然后播放器根据网速自动切换档位。这是现成视频平台的基本功能,自建也能做到,只是需要额外跑转码进程,对服务器CPU要求会高一截。

弹幕系统。给直播页面接一个轻量级WebSocket服务,观众发弹幕实时显示,配合HTTP-FLV低延迟直播,互动体验可以做得非常像商业产品。

多机位直播。SRS天然支持多路流同时推入不同app或stream名,网页端用JavaScript控制切换播放源,就能实现简单的导播台效果,适合一些简单的演唱会或者课程直播。

录制文件自动归档到对象存储。本地硬盘满了以后,把录制的flv转MP4再传到对象存储,等于做了一个私有长视频库,永远不用担心存储空间。

5.2 我在实际运行中的几个体会

第一,能不转码就不转码。在个人服务器上,FFmpeg转码非常吃CPU,跑一路1080p转720p的转码进程,CPU占用率轻松上到60%以上。推流端编码是免费的,服务端转码却要花钱买CPU,所以个人项目尽量在OBS里直接输出目标码率,少做服务端转码。

第二,录制文件的存储规划要趁早做。我的常驻直播流是24小时开着的,一个晚上下来FLV录制文件能到10G以上。一个月就是300G,这个增长速度远超我的预估。合理做法是录制文件只保留最近一周,点播归档后同步删除原始文件。

第三,名片式的推广页面很重要。LunaTV做出来以后,我给朋友分享的不是一个裸播放器地址,而是一个简单的落地页,上面有直播画面、节目时间表、回放列表和推流状态。这看起来只是很小的前端工作,但从观众体验角度来说,它的价值可能比后端技术本身还要大。一个统一入口的“私人电视台”形象,一下子就能立起来。

最后说一个运维层面的小技巧。SRS长时间运行后内存会有缓慢增长,我写了一个定时探活脚本,每10分钟请求一次API,连续3次失败就自动重启SRS进程。同时配了一个磁盘清理任务,超过15天的录像文件自动删除。有了这两个脚本,LunaTV基本达到了“部署一次后两周不用管”的状态,我只需要偶尔看一眼监控面板确认它在正常转,其他时间完全可以安心睡觉。

如果你也想折腾一套类似的东西,我的建议是别一上来就追求功能全,先把最核心的链路跑通:推流、播放、录制。这三件事通了,后面加东西都只是锦上添花。踩坑是难免的,但每一步踩坑之后换来的稳定运行,才是这套自建方案真正值钱的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询