ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NetStream 全解:从 RTMP 低延迟原理到直播实战与踩坑记录

NetStream 全解:从 RTMP 低延迟原理到直播实战与踩坑记录 先抛个问题十几年前想在网页上看直播打开的是一个插件页面没有 HLS没有 WebRTC只有那个灰底白字的 Flash 播放器。那时候直播不卡靠的是一套叫 NetStream 的 API在 ActionScript 里负责把远端的音视频流拉回来或者把手边的画面推上去。很多从那个年代走过来的流媒体开发者提起 RTMP 和 NetStream都会把它当成理解“流传输”的入门必修课。这篇文章我把 NetStream 的前因后果、工作原理、实战参数和踩坑记录都摊开讲一遍适合刚入行的流媒体开发、产品经理以及想搞懂“浏览器直播底层到底发生了什么”的人。我会按实际项目里的经验来写不堆概念只讲能落地的内容。1. NetStream 到底是个啥1.1 先回到 Flash Player 的时代NetStream 不是一个网络协议也不是一台服务器它是 Adobe Flash Player 和 Flex/ActionScript 里提供的一套客户端流媒体 API。它解决的问题很具体浏览器不是电影播放器你要怎么把一个正在录制或者正在编码的视频流实时地拉到用户屏幕上那时候浏览器也支持 video 标签但 HTML5 的视频解码能力和直播能力非常有限。MP4 点播能播但“推流-拉流-低延迟”这条路几乎没有替代方案。微软的 Silverlight 有小众的方案RealPlayer 也在但网页直播的第一选择基本就是 Flash。于是 NetStream 成了事实标准。当时的直播网站、视频聊天室、摄像头监控后台播放器核心十有八九是同一个套路创建一个 NetConnection连上一个 RTMP 地址然后把 NetStream 挂上去再调用 play 或 publish。很多人一听 NetStream 就以为是 Flash 专属觉得跟今天的流媒体没有关系。但实际上现在很多直播系统的服务端还是保留着 RTMP 入口只是播放端从 Flash 换成了 HTML5 或原生 App。你打开 OBS 推流到云直播服务时大多数推流地址填写的那串 rtmp:// 协议仍然需要接收端有一套类似 NetStream 的逻辑来承接。所以理解 NetStream并不只是为了考古而是为了看懂直播链路里那些至今还在用的底层思路。1.2 NetConnection、NetStream、Camera/Microphone 的分工第一次写 NetStream 的时候很容易把三个概念搞混NetConnection、NetStream、Camera/Microphone。用电话类比就清楚了NetConnection 是“线路”负责建立和维护客户端与流媒体服务器之间的连接。它以 rtmp:// 开头好比你拨号给服务器。没有线路一切免谈。NetStream 是“线路上的数据流”一个连接上可以挂多个流播放端用 play推流端用 publish同一份画面可以被多人各自独立播放。Camera 和 Microphone 是“信号源”采集摄像头和麦克风再交给推流的 NetStreamattachCamera 和 attachMicrophone 就是把它们绑上去。这三个角色搞清楚了代码里就不会犯一些低级错误。比如有人 new 了 NetConnection 却一直不触发 Success那可能是连接地址不对也可能是服务端没开对应端口。而有人永远卡在“连上了却没画面”那就要去看 NetStream 这个环节是否真的拿到了流名是否设置了 client 回调是否缓冲一直没填满。排查的时候先看连接再看流再查采集基本上能定位大多数问题。角色类比职责关键方法NetConnection线路建立并维护 RTMP 会话connect、closeNetStream线路上的数据流播放或发布音视频数据play、publish、pause、resumeCamera/Microphone信号源采集画面与声音Camera.getCamera、attachCamera2. 核心原理NetStream 是怎么把画面送出去的2.1 为什么 RTMP 能做到低延迟要理解 NetStream必须捎带理解它背后最常见的传输协议 RTMP。RTMP 是 Adobe 为流媒体设计的实时消息协议默认走 TCP 1935 端口。相比 HTTP 的一次请求一次响应RTMP 在握手成功后建立一条长连接客户端和服务器可以在这条连接上双向、持续地交换数据。这条长连接最大的价值不只是省掉几次握手而是让服务器可以随时把新增的音视频数据推给播放器不需要播放器反复发 HTTP 请求来轮询。RTMP 的另一个关键设计是消息分块也就是 chunk。音视频数据被切成小块在一条 TCP 连接上交错传输音频、视频、命令消息互相穿插接收端再按 chunk stream id 重新拼装。这是一个非常贴合实时场景的设计视频帧再大也能被切成小块不至于让音频消息一直排队等着发送。正是“长连接 小块交错传输 AMF 命令即时交互”这三个特性组合在一起才让 RTMP 能把直播延迟做到 0.5 到 3 秒。在 2008 年左右的网页直播里这几乎是唯一真正可用的低延迟方案。顺带说一句TCP 本身是有拥塞控制和重传机制的网络抖动剧烈时延迟依然会上升。NetStream 不是在传输层上做过什么神奇的事情它更多是在传输之上建立了一套“怎么描述一条流、怎么控制一条流、怎么感知一条流”的客户端模型。这也是为什么后来 Flash 没了这套模型的很多思想还是留了下来。2.2 缓冲、状态机与播放策略NetStream 播放画面时并不是数据一到就立即显示。它内部有一个播放缓冲客户端会先把头几个片段收进来再开始播放。所以千万别以为“连接成功”就等于“画面出现”两者之间隔着一个缓冲期。有两个参数必须分清楚bufferTime 是你给播放器预设的缓冲目标单位是秒bufferLength 是当前实际缓冲的时长单位也是秒。NetStream 会尽力让 bufferLength 逼近 bufferTime。当 bufferLength 掉到很低长时间收不到新的可解码数据就会触发 NetStream.Buffer.Empty。缓冲补足重新开始播放时会触发 NetStream.Unpause.Notify 之类的事件。bufferTime 怎么调取决于业务场景。网络抖动大的环境比如跨运营商直播、无线网络为主的场景bufferTime 建议设到 2 到 3 秒。强实时场景比如连麦、互动答题bufferTime 可以压到 0.5 到 1 秒。但要注意这个参数不能无限缩小设得太小网络一抖动就触发 Buffer.Empty画面就会一直卡。这里面的本质是抖动容忍度和端到端延迟的权衡没有绝对正确的值只有适合你业务的区间。2.3 音视频数据混流如何区分一条 NetStream 上既有视频又有音频还可能夹带 onMetaData、onPlayStatus 等数据消息。RTMP 内部通过消息类型 ID 区分音频消息、视频消息和数据消息各有各的 message type然后在同一条连接上交错传输。播放器收到后拆包视频交给 Video 对象解码显示音频交给 Sound 系统播放数据消息则触发回调方法。在 AS3 里接收数据消息通常要给 NetStream 设置一个 client 对象然后实现 onMetaData、onPlayStatus 等方法。这一点很多新手都会忽略。如果不设置 client就会出现“什么都能播就是拿不到视频时长、分辨率、关键帧位置这些信息”的怪问题。我当时做点播列表的时候需要读取 onMetaData 里的 duration 来显示视频总时长结果一直拿不到值后来才发现是少了 ns.client this 这一步。3. 动手实战用 NetStream 做一个播放器和推流端3.1 播放端从 connect 到 play纸上谈兵不如直接写代码。下面是 AS3 里一个最基础的播放器流程创建 NetConnection监听状态事件连接成功后创建 NetStream设置缓冲和 client最后播放一条流。import flash.net.NetConnection; import flash.net.NetStream; import flash.events.NetStatusEvent; import flash.media.Video; var nc:NetConnection new NetConnection(); nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); nc.connect(rtmp://192.168.1.10/live); function onNetStatus(e:NetStatusEvent):void { if (e.info.code NetConnection.Connect.Success) { var ns:NetStream new NetStream(nc); ns.client this; ns.bufferTime 1.5; video.attachNetStream(ns); ns.play(stream1); } }这里的 video 是一个放在显示列表里的 Video 对象这一步不能漏。很多人看到 NetStream 连接成功也调用了 play但播放器没有画面排查了半天才发现 Video 对象没有添加到舞台上或者没有执行 attachNetStream。Video 和 NetStream 之间是绑定关系你可以在一个 Video 上切换不同的 NetStream也可以换用不同的 Video 展示同一条流。这个灵活性在后来写画中画、多路预览时非常有用。接收 onMetaData 也很简单定义同名方法即可function onMetaData(info:Object):void { trace(分辨率: info.width x info.height); }注意命名Flash 运行时是按方法名去回调的。如果写成 onMetadata、onMetaDataInfo 这种名字它是不会正常触发的也不会报错你只会觉得莫名其妙。3.2 推流端publish 完成一次直播发布播放端只是网络直播的一半另一半是推流。把摄像头和麦克风采集到的数据推给服务器用 NetStream 写起来也很直接var cam:Camera Camera.getCamera(); var mic:Microphone Microphone.getMicrophone(); if (cam null) { trace(没有检测到摄像头); return; } var ns:NetStream new NetStream(nc); ns.attachCamera(cam); ns.attachMicrophone(mic); ns.publish(stream1, live);publish 的第二个参数有三种常见值live 表示直播不落盘record 表示边推边录服务器会把流录制到文件中append 表示在已有录制文件上追加。对直播业务绝大多数都使用 live。录像回放业务才会用 record 或 append。有一次我们在测试环境里推流后一直看服务器没有录像排查半天才发现写的是 live改成 record 后文件就正常生成了。这个参数看起来简单但放错位置会让业务行为完全不一样。另外attachCamera 之前最好先调用 Camera.getCamera() 返回对象并做非空判断。在真实浏览器环境里如果用户没有插摄像头或者摄像头被其他程序占用Camera.getCamera() 会返回 null不处理的话后面 attachCamera 就会直接抛错或静默失败。3.3 关键事件状态码速查表NetStream 把几乎所有关键状态变化都通过 NetStatusEvent 回调告诉你事件里有一个 info.code 字符串。真实的项目里我遇到过不下十种状态码下面这几个是最常用的。code出现时机处理建议NetConnection.Connect.Success连接成功建立 NetStream继续 play/publishNetConnection.Connect.Rejected连接被拒检查鉴权参数、地址、服务端配置NetStream.Play.Start流开始播放等待首帧画面可关闭加载提示NetStream.Play.Stop播放结束或流结束点播场景正常结束直播场景可能断流NetStream.Play.StreamNotFound找不到流名检查流名是否匹配服务端配置NetStream.Buffer.Empty缓冲耗尽等待缓冲或检查网络必要时调大 bufferTimeNetStream.Buffer.Flush缓冲填满说明当前缓冲充足可以维持播放NetStream.Publish.Start推流成功表示直播推流已开始NetStream.Publish.BadName流名被占用或被拒绝换流名或清理旧连接建议不要把状态码字符串到处散落在业务代码里最好定义成常量类统一管理。否则别人维护代码的时候看到一段魔法字符串还以为写错了。4. 真实项目里的坑与排查思路4.1 有画面没声音或者有声音没画面这类问题在直播开发里太常遇见了。有画面没声音最常见的第一个猜测是“播放端静音了”但多数时候不是。发布端麦克风没有 attach或者音频编码格式播放端不支持才是真正原因。那个年代 Flash 最低支持的音频编码一般是 AAC 和 Speex如果服务器转码后输出其他格式或者发布端使用了低码率 NellyMoser 编码播放端可能直接丢弃音频。排查步骤可以固定下来先看发布端是否有麦克风权限Microphone.getMicrophone() 是否返回 null再用 NetStream 的 info 属性检查有没有音频字节流最后看服务端日志里音频轨的编码格式。有声音没画面也是一样先看 Camera 是否被占用再看视频编码是否是服务端和播放端都支持的格式比如 H.264 基线或主配置。4.2 Buffer.Empty 反复触发直播播放最烦的事情就是播放几秒卡一次。现象上表现为 NetStream.Buffer.Empty 反复出现画面断断续续。原因通常是三类第一客户端网络带宽不足数据接收速度跟不上播放速度第二服务端发送策略有问题关键帧间隔太大播放器要等下一个关键帧才能恢复画面第三bufferTime 设置太小网络稍微抖动就直接触发缓冲事件。针对这个场景我建议先做一个可量化的调整把 bufferTime 暂时调到 3 秒观察 Buffer.Empty 触发频率。如果明显减少说明是网络抖动或缓冲设置问题。如果依然频繁触发就要看服务端关键帧间隔最好控制在 1 到 2 秒。有些开源流媒体服务器默认配置的关键帧间隔很大播放器在丢帧后要等很久才能找回画面体感就是一直在缓冲。4.3 延迟越来越大直播系统跑一段时间后延迟从 1 秒慢慢爬到 5 秒甚至更多这是 NetStream 项目里很典型的问题。原因不难理解NetStream 在直播场景里不会因为本地缓冲超量而主动丢帧。如果推流端码率和播放端处理速度有一点点差距bufferLength 会缓慢上涨延迟也就在不知不觉中变大。当时我常用的应急手段是开一个定时器监测 bufferLength 和 bufferTime 的关系当 bufferLength 比 bufferTime 多出一定阈值时执行一遍 pause 再马上 resume利用缓冲重建把多余的数据丢弃把画面跳回接近实时。var timer:Timer new Timer(5000); timer.addEventListener(TimerEvent.TIMER, function():void { if (ns.bufferLength ns.bufferTime 2) { ns.pause(); ns.resume(); } }); timer.start();这个方法不算优雅也有可能在跳帧瞬间出现轻微画面跳跃但至少能把延迟控制在一个可接受的范围内。更彻底的做法是在服务端把缓存队列压小只保留少量缓冲让播放器拿到的数据始终接近实时。真正要解决延迟问题必须客户端和服务端一起调单靠一边很费劲。4.4 NetStream.info 不是摆设很多人不知道 NetStream 自带一个 info 属性用它可以直接看到接收字节数、当前帧率、丢帧情况等关键数据。排查问题的时候这些参数比猜要靠谱得多。setInterval(function():void { trace(接收字节: ns.info.bytesReceived 当前FPS: ns.info.currentFPS 丢帧: ns.info.droppedFrames); }, 5000);bytesReceived 每隔几秒稳定增长说明数据链路是通的。currentFPS 长时间低于设定的推流帧率说明推流端或服务端没有按预期输出。droppedFrames 突然飙升说明解码端处理不过来或者网络丢包导致到达的数据不完整。这几个值组合起来看能判断问题是出在推流端、传输链路还是播放端。我在线上环境里定位过一个问题播放器画面卡服务端却说数据正常最后从播放器的 currentFPS 和 droppedFrames 数值对比中看出是解码性能不足跟网络无关。5. NetStream 和现代流媒体技术的关系5.1 从 RTMP 到 HTTP-FLV、HLS、WebRTCFlash 退场之后NetStream 这个 API 慢慢没人写了但它背后的思路并没有消失。HTTP-FLV 是最直接的后继者它把 RTMP 里的 FLV tag 封装进 HTTP 响应体里持续下发播放端用 MediaSource Extensions 边接收边播放。协议从 RTMP 换成了 HTTP但流的内部结构还是 FLV缓冲和状态机的思路也和 NetStream 一脉相承。HLS 则走了另一条路把连续的视频流切成一个个 TS 或 fmp4 分片再用 m3u8 索引文件描述分片顺序。它的延迟偏高但对标准 CDN 非常友好因为是纯静态文件分发。WebRTC 又把延迟压到了毫秒级底层用 UDP 和 SRTP 做实时传输配合 ICE 协商建立连接。虽然技术栈完全不同但你会看到类似的连接状态管理、缓冲层级和丢帧策略。方案传输层延迟核心思想RTMP/NetStreamTCP 长连接低长连接 chunk 交错传输HTTP-FLVHTTP 持久连接低流结构仍是 FLV靠 MSE 播放HLSHTTP 分片中高切片 索引文件 CDN 友好WebRTCUDP/DTLS/SRTP极低P2P RTP 状态协商5.2 NetStream 留下的流媒体基本功学 NetStream 最值钱的不是 AS3 语法而是三个概念连接与流分离、状态机驱动、缓冲调优。现在你在 WebRTC 里也会看到连接状态变化在 MSE 里也会看到 SourceBuffer 的 update 状态和 buffer 水位监控。换个壳内核依然是“什么时候开始播放什么时候缓冲什么时候丢帧或跳帧”。我后来带人做播放器开发第一堂内部课不是讲播放器框架而是拿一个用 NetStream 写的老播放器做案例分析。把里面的 connection、stream、buffer、event code 讲透新人再去看现代播放器的源码理解速度会快很多。这套基础知识跨协议、跨语言价值不会随着 Flash 退场而消失。5.3 现在还要不要学 NetStream纯做新项目的 Web 播放器当然不需要再去写 AS3。但如果要维护老直播系统、阅读开源流媒体服务器里的 RTMP 模块、理解 OBS 推流日志和流名规则NetStream 和 RTMP 的知识仍然有用。它更像一门流媒体老古董课但胜在逻辑简单直接反而适合作为入门教材。如果你刚接触流媒体我建议不要把时间浪费在纠结“要不要学已经过时的技术”上。把 NetStream 状态机理解清楚再对比着学 WebRTC 或 MSE你会发现“流的工作方式”在本质上没怎么变。有了这个底子后面接触任何新协议都能很快上手。最后说点私人经验。我后来负责把一套 Flash 直播系统迁移到 HTML5/MSE 时没有急着替换播放器而是先让团队把老系统里 NetStream 的状态机、缓冲策略、流名规则整理出来然后在现代播放器里一一对号入座。这种迁移方式最后比直接重写稳得多因为业务层面的问题早就被老系统暴露过一遍了。如果你现在才开始接触流媒体不要觉得 NetStream 过时就跳过它把它当成理解“流”这门手艺的入门课是很划算的选择。
返回列表