
WebRTC音视频即时通讯通信【免费下载链接】webrtcPure Go implementation of the WebRTC API项目地址https://gitcode.com/gh_mirrors/we/webrtc点击查看免费下载导读本文围绕 Pion WebRTC 仓库中的 examples/ortc-media 示例 展开讲解如何绕过传统的 Session Description ProtocolSDP直接使用 ORTC 风格的底层传输 APIICE、DTLS、RTP来搭建一个文件到文件的视频推流通道一端从 IVF 文件读取 VP8/VP9/AV1 视频流另一端接收并打印收到的 RTP 包。读完本文你将掌握 ORTC 与 WebRTC 的关系、基于 JSONBase64 的自定义信令协议设计以及如何把 IVF 容器与 RTP/RTCP 管线衔接起来并能完整复现一个可运行的媒体示例。ORTC 是什么为什么要绕开 SDPORTCObject Real-Time Communications见 ortc.org的核心思想是不再用 SDP 文本来描述媒体会话而是直接暴露一组可编程的对象与 API。在examples/ortc-media/README.md中对此有明确表述Instead of using the Session Description Protocol to configure and communicate, ORTC provides APIs. Users then can implement signaling with whatever protocol they wish. ORTC can then be used to implement WebRTC. A ORTC implementation can parse/emit Session Description and act as a WebRTC implementation.翻译过来就是三层含义信令协议完全自由SDP 只是 WebRTC 生态中一种约定俗成的会话描述格式ORTC 场景下你可以用 JSON、Protobuf、纯文本甚至自定义二进制协议来交换会话参数。ORTC 可以反过来实现 WebRTC一个 ORTC 实现既可以解析/生成 Session Description也可以扮演完整的 WebRTC 实现——这说明 ORTC 与 WebRTC 并非对立而是更底层的积木。媒体能力由对象而非文本承载编解码器协商、ICE 参数、DTLS 指纹、RTP 发送/接收参数都通过类型化的 Go 结构体传递编译期即可发现类型错误这是相对 SDP 字符串解析的一大优势。在 examples/ortc/README.md 中同一个 ORTC 思路被用于 DataChannel 场景而本示例ortc-media则聚焦在媒体传输RTP上两者合起来覆盖了 ORTC 的数据与媒体两条主线。示例概览两个进程 一个自定义 JSON 信令协议ortc-media示例由两个 Go 进程组成分别扮演 offerer 与 answerer角色启动方式职责offerer发送方ortc-media -offer打开output.ivf创建本地媒体轨Track启动 RTP Sender并开启 HTTP 服务等待接收对方的信令answerer接收方echo base64 \| ortc-media通过 stdin 接收对方信令创建 RTP Receiver接收媒体并打印首个 RTP 包的 SSRC两端之间交换的会话参数被定义为一个名为Signal的 Go 结构体见 examples/ortc-media/main.go// Signal is used to exchange signaling info. // This is not part of the ORTC spec. You are free // to exchange this information any way you want. type Signal struct { ICECandidates []webrtc.ICECandidate json:iceCandidates ICEParameters webrtc.ICEParameters json:iceParameters DTLSParameters webrtc.DTLSParameters json:dtlsParameters RTPSendParameters webrtc.RTPSendParameters json:rtpSendParameters }注意代码注释特别强调这个结构体并不是 ORTC 规范的一部分你可以用任何方式交换这些信息。这正是 ORTC 设计哲学的体现——规范只负责定义会话参数对象不负责定义如何传输它们。该结构体随后通过json.Marshal再叠加base64.StdEncoding编码成一个字符串见 main.go 的 encode/decode 函数方便在命令行、stdin、HTTP body 之间无差别搬运。逐步实操从零跑通媒体传输以下步骤完整继承自 examples/ortc-media/README.md并补充了源码层面的解释。第 1 步用 ffmpeg 生成 IVF 测试文件示例读取的是名为output.ivf的 IVFInterchange Video File Format文件且必须包含VP8/VP9/AV1其中一种编码的视频轨。生成命令如下ffmpeg -i $INPUT_FILE -g 30 -b:v 2M output.ivf参数含义-i $INPUT_FILE任意输入视频文件-g 30GOP关键帧间隔为 30 帧-b:v 2M视频目标码率 2 兆比特每秒。README 特别提示-b:v 2M只是一个够用就好的默认值用来换取不错的画质。如果遇到丢帧等问题可以调低这个值。更完整的取值语法请查阅 ffmpeg 官方文档的 Options 一节。值得强调的是示例并不限定编码格式——源码 fourCCToTrack 函数 会根据 IVF 文件头部的 FourCC 字段自动选择对应的 MIME 类型switch fourCC { case AV01: trackCodec webrtc.MimeTypeAV1 case VP90: trackCodec webrtc.MimeTypeVP9 case VP80: trackCodec webrtc.MimeTypeVP8 default: panic(fmt.Sprintf(Unable to handle FourCC %s, fourCC)) }也就是说你生成的output.ivf是 VP8、VP9 还是 AV1程序都能自动适配无需修改代码。第 2 步安装 ortc-media 命令go install github.com/pion/webrtc/v4/examples/ortc-medialatest这会利用 Go 的模块工具链把示例编译成可执行文件安装到$GOBIN。当前仓库的模块声明为github.com/pion/webrtc/v4见 go.mod因此latest会拉取最新发布版本。第 3 步启动 offerer拿到第一段 Base64 信令ortc-media -offer程序会打印一段 Base64 字符串把它复制到剪贴板。这段字符串内部就是前面Signal结构体的 JSON 编码本地 ICE 候选、ICE 参数、DTLS 参数以及 RTP 发送参数。结合源码看 offerer 端的启动流程main.go用os.Open打开output.ivf用ivfreader.NewWith解析 IVF 文件头根据header.FourCC创建对应的TrackLocalStaticSample本地媒体轨通过api.NewRTPSender(trackLocal, dtls)创建 RTP Sender调用rtpSender.Send(rtpSendParameters)开始发送启动 goroutinewriteFileToTrack把文件帧写入轨道。第 4 步启动 answerer回传第二段 Base64 信令把刚才复制的消息通过 stdin 传给第二个进程echo BASE64_MESSAGE_YOU_COPIED | ortc-mediaanswerer 会打印另一段 Base64 字符串同样复制它。answerer 端的流程对应 main.goif rtpReceiver, err api.NewRTPReceiver(webrtc.RTPCodecTypeVideo, dtls); err ! nil { panic(err) }它只创建了一个面向视频的RTPReceiver不打开任何文件——因为它只负责收。第 5 步用 curl 把第二段信令送回 offerercurl localhost:8080 -d BASE64_MESSAGE_YOU_COPIEDlocalhost:8080正是 offerer 内部启动的 HTTP 服务器对应源码中的httpSDPServer(*port)main.go。这个 HTTP 服务非常简单把请求 body 原样塞进一个 channel然后由主流程取出来解码。默认监听端口是 8080可通过命令行参数覆盖ortc-media -offer -port 9090对应 main.go 的 flag 定义。第 6 步观察结果接收方answerer在拿到首个 RTP 包后会打印类似这样的信息Got RTP Packet with SSRC 3097857772每次运行的 SSRC 都不同因为 SSRC 是会话建立时随机生成的。媒体包会持续流动直到文件所有帧都被读取完毕发送方打印All video frames parsed and sent后进程退出见 writeFileToTrack。源码深挖一次完整的 ORTC 媒体会话是如何搭起来的1. 自底向上的传输对象组装与使用PeerConnection的一站式用法不同ORTC 风格要求你手动组装传输栈。main.go中这段代码完整呈现了从 ICE 到 RTP 的每一层// Prepare ICE gathering options iceOptions : webrtc.ICEGatherOptions{ ICEServers: []webrtc.ICEServer{ {URLs: []string{stun:stun.l.google.com:19302}}, }, } // Use default Codecs mediaEngine : webrtc.MediaEngine{} if err : mediaEngine.RegisterDefaultCodecs(); err ! nil { panic(err) } // Create an API object api : webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine)) // Create the ICE gatherer gatherer, err : api.NewICEGatherer(iceOptions) // Construct the ICE transport ice : api.NewICETransport(gatherer) // Construct the DTLS transport dtls, err : api.NewDTLSTransport(ice, nil)逐层说明ICEGatherericegatherer.go 的api.NewICEGatherer负责收集本地 ICE 候选。示例使用了一个公开的 Google STUN 服务器stun:stun.l.google.com:19302来获取公网候选地址。注意这里通过WithMediaEngine定制了 API默认NewAPI()也会自动注册默认编解码器见 api.go 的NewAPI实现此处显式传入是为了强调 MediaEngine 在 ORTC 中的可配置地位。ICETransport承载候选对candidate pair的连通性检查负责 NAT 穿透。DTLSTransport在 ICE 之上建立 DTLS 加密通道第二个参数nil表示使用自动生成的证书可通过传入[]Certificate自定义。2. 媒体方向的分叉Sender 与 Receiver组装完传输层后程序按角色分叉main.goofferer创建RTPSender把本地TrackLocalStaticSample绑定到 DTLS 通道上发送answerer创建RTPReceiver用RTPCodecTypeVideo指定接收视频流。之后双方都要执行候选收集与参数导出main.gogatherFinished : make(chan struct{}) gatherer.OnLocalCandidate(func(candidate *webrtc.ICECandidate) { if candidate nil { close(gatherFinished) } }) if err gatherer.Gather(); err ! nil { ... } -gatherFinished iceCandidates, err : gatherer.GetLocalCandidates() iceParams, err : gatherer.GetLocalParameters() dtlsParams, err : dtls.GetLocalParameters()这里OnLocalCandidate回调中candidate nil表示收集结束gathering complete随后统一取出本地候选、ICE 参数与 DTLS 参数打包进Signal发送给对方。3. 角色与 ICE 控制方协商值得注意的一个细节是 ICE 角色的选择main.goiceRole : webrtc.ICERoleControlled if *isOffer { iceRole webrtc.ICERoleControlling }默认先假设自己是Controlled方如果是 offerer 再切换为Controlling。这是因为在 ICE 标准中必须一方 controlling、一方 controlled而本示例用-offer标志来消解这个歧义。4. 连接启动ICE → DTLS → RTP信息交换完成后双方向启动连接main.goif err ice.SetRemoteCandidates(remoteSignal.ICECandidates); err ! nil { ... } if err ice.Start(nil, remoteSignal.ICEParameters, iceRole); err ! nil { ... } if err dtls.Start(remoteSignal.DTLSParameters); err ! nil { ... }answerer 还会用远程的 RTP 发送参数配置自己的接收端并从rtpReceiver.Track()取出TrackRemote读取 RTP 包if err rtpReceiver.Receive(webrtc.RTPReceiveParameters{ Encodings: []webrtc.RTPDecodingParameters{ { RTPCodingParameters: remoteSignal.RTPSendParameters.Encodings[0].RTPCodingParameters, }, }, }); err ! nil { ... } remoteTrack : rtpReceiver.Track() pkt, _, err : remoteTrack.ReadRTP() fmt.Printf(Got RTP Packet with SSRC %d \n, pkt.SSRC)RTPReceiver.Receive是接收侧的关键入口见 rtpreceiver.go它先configureReceive再startReceive把TrackRemote挂到接收管线里Track()方法在只有一个轨道时直接返回该轨道rtpreceiver.go。5. IVF 文件是怎么变成 RTP 包的发送端 goroutinewriteFileToTrackmain.go承担了文件 → 媒体采样的搬运工作ticker : time.NewTicker( time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000), ) for ; true; -ticker.C { frame, _, err : ivf.ParseNextFrame() if errors.Is(err, io.EOF) { fmt.Printf(All video frames parsed and sent) os.Exit(0) } if err track.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err ! nil { ... } }它按照 IVF 头部声明的时间基准timebase构造一个time.Ticker以接近实时播放的节奏逐帧读取并写入媒体轨模拟真实推流行为。底层的解析逻辑位于 pkg/media/ivfreader/ivfreader.go文件头32 字节以DKIF魔数开头ivfFileHeaderSignature DKIF包含 FourCC、宽高、timebase 分子/分母、帧数等信息对应IVFFileHeader结构体ivfreader.go帧头每帧 12 字节含FrameSize与时间戳IVFFrameHeaderNewWith解析文件头并校验签名、版本与 timebase 合法性ivfreader.goParseNextFrame逐帧读取负载读到文件末尾返回io.EOF并给出空结果ivfreader.go。而TrackLocalStaticSample.WriteSample会把media.Sample交给内部 Payloader 打包成 RTP 包再经 SRTP 加密发送这正是TrackLocalStaticSample类见 track_local_static.go 的注释If you wish to send a media.Sample use TrackLocalStaticSample与TrackLocalStaticRTP的区别所在——前者面向媒体采样后者直接接受现成的 RTP 包。信令交换链路全景把整个流程串起来两端的信令交互是这样的offerer: Gather → 导出 Signal#1(ICE/DTLS/RTP参数) → 打印 Base64#1 answerer: 读取 Base64#1 → Gather → 导出 Signal#2 → 打印 Base64#2 offerer: curl POST Base64#2 → 解码 → SetRemoteCandidates → ICE.Start → DTLS.Start answerer: SetRemoteCandidates → ICE.Start → DTLS.Start → RTPReceiver.Receive → ReadRTP两个方向分别走不同的信令通道offerer → answerer通过终端复制粘贴stdinanswerer → offerer通过 offerer 上运行的 HTTP 服务默认 8080 端口。这种两头不对称的设计恰好演示了 ORTC 的优势信令通道完全由你定义。你可以把两段 Base64 换成 WebSocket、消息队列、甚至纸条都不影响媒体面的正常工作。与 DataChannel 版 ortc 示例的对照仓库中还有 examples/ortc 示例两者共享同一套 ORTC 组装思路NewICEGatherer → NewICETransport → NewDTLSTransport区别在于ortc数据面在 DTLS 之上继续叠加NewSCTPTransport通过api.NewDataChannel创建 DataChannel信令载荷为SCTPCapabilities媒体内容为随机生成的文本消息ortc-media媒体面在 DTLS 之上直接创建RTPSender/RTPReceiver信令载荷为RTPSendParameters媒体内容为 IVF 文件中的视频帧。对照两个 main.go 可以看到ORTC 把传输栈和会话参数拆成了清晰的层次无论是跑数据通道还是媒体通道ICE/DTLS 部分的代码几乎可以原样复用——这正是 ORTC 面向工程复用的价值所在。小结与下一步ortc-media示例证明了在 Pion WebRTC 中你可以完全不依赖 SDP仅凭类型化的 ORTC 对象ICEGatherer、ICETransport、DTLSTransport、RTPSender/RTPReceiver就搭建出一条完整的视频传输链路并且信令协议可以按需自定义。对希望把 WebRTC 能力嵌入自有系统、需要精细控制传输栈或希望规避 SDP 解析复杂性的开发者来说这是一个理想的起点。如果你想进一步探索阅读 examples/ortc看同样的 ORTC 栈如何承载 DataChannel阅读 pkg/media/ivfreader/ivfreader.go 与 pkg/media/ivfwriter了解 IVF 容器读写的完整实现参考 examples/ortc-media/main.go 的完整源码尝试把信令通道替换为 WebSocket 或 MQTT体会信令与媒体解耦的设计自由。赞分享WebRTC音视频即时通讯通信【免费下载链接】webrtcPure Go implementation of the WebRTC API项目地址https://gitcode.com/gh_mirrors/we/webrtc点击查看免费下载相关推荐【免费下载】 Python-aiortc基于asyncio的WebRTC和ORTC实现Python aiortc基于asyncio的WebRTC和ORTC实现 此仓库包含了一个使用Python的asyncio库实现的WebRTC和ORTC的资源音视频探索Pion构建WebRTC应用的卓越框架与示例探索Pion构建WebRTC应用的卓越框架与示例 项目简介 则是一个特别有价值的资源它包含了多个Pion WebRTC应用程序的实例帮助开发者快速理解并着上一篇DBX 开源指南25 MB 轻量级跨平台数据库客户端内置 AI 助手与 MCP Server 全解析下一篇chezmoi 实战在 chezmoi init 阶段用 hooks.read-source-state.pre 自动安装密码管理器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考