WebRTC + NDI——广播业的福音?

在疫情蔓延的这几个月里,许多人的日常生活、公司的运作模式发生了巨大变化。我们大多数人被迫呆在家里工作有很长一段时间了,很多行业,特别是整个广播电视行业深受其害。比如由于嘉宾(甚至主持人)无法来到演播室现场,新闻节目、脱口秀中的采访或任何形式的现场互动只能作罢,制作团队只好寻找其他能让节目正常运行的解决方案。

对于我们这些从事实时业务的人来说,答案好像很简单——利用实时技术即可。但说起来容易做起来难,问题的关键在于制作团队如何使用他们需要编辑和播出的媒体流以及所采用的工具。一如既往,我又受到那些在Twitter上与我们频繁互动的用户的启发,尤其是来自Nimble Ape的Dan Jenkins(同时他也是绝妙的CommCon活动的策划人,推荐大家去看看其中刚刚结束的那场线上会议)提出了一个与BBC新闻有关的新奇发现。另外,Sam Machin的回答也很有启发性,他向我介绍了一种新“玩法”。

“Skype很受欢迎,因为它配备了NDI插件,可以让它在视觉混合器中作为摄像机源出现,我还没找到其他能提供这个功能的应用。”

——Sam Machin (@sammachin) 2020年4月24日

这是我第一次听说NDI,却有醍醐灌顶的感觉。它提醒我用户是否选择你的服务,往往基于其是否能帮助用户更顺畅地工作。我觉得这很有趣并参与到讨论中来,因为我想知道执行RTP到NDI的翻译有多容易(或多难)。

“我没想过用NDI实现。我需要确定生成这样的流/设备要花多大功夫。在Janus中,我们有办法把WebRTC流转为普通的RTP流,以便在其他地方使用,所以或许也有办法实现RTP到NDI。”

——Lorenzo Miniero (@elminiero) 2020年4月24日

提起OBS,CoSMo软件公司确实实现了一个能够与WebRTC对话的OBS版本。但之后被证实是个虚假消息。正如Alex博士澄清的那样,OBS-WebRTC不支持WebRTC输入,若该项支持成立,OBS是可以实现将输入流翻译成NDI的操作的。

“OBS的NDI插件既可入也可出。在OBS-Studio-Webrtc中,我们只负责原生的webrtc OUT。webrtc IN由browser source来处理(其内部使用CEF)。”

——Alex.Gouaillard博士 (@ag. Gouaillard )2020年5月8日

在交流之后,我迫不及待地想研究NDI相关技术(尤其是我现在对它一无所知),但后来由于琐事繁多,我就把它抛之脑后了。

但几周前和Dan的一次闲聊又激发了我的斗志。他提到他认识的一家视频制作公司正苦恼如何能从网上获取适当的实时推送用于其日常制作中。他也再一次提到,现在NDI几乎是该行业的事实标准了。另外他也提及引入WebRTC流的适当方法少之又少,这迫使编辑们只能像黑客一样去盗取源材料了。

因此我决定开始研究NDI。我想弄清楚RTP到NDI翻译是否真的可行。

首先, NDI 是什么?

维基百科上写道:

网络设备接口(NDI)是NewTek开发的一种免费软件标准,它能使支持视频的产品以高质量、低延迟的方式通信、传输和接收高质量的视频,该方式可精确到帧,适合在直播环境中进行切换。

简而言之,NDI允许在同一局域网内进行多通道和非压缩媒体流的实时交换,它利用mDNS进行服务发现。这使得节目制作人可以在同一局域网内利用不同来源的媒体,也就是说制作人可以不受限于实际连接到其设置的设备去制作节目。例如下图(来自他们的宣传资料),可以从视觉上让整个过程更加清晰。

显然,这项技术背后真正的优势是他们提供的免费SDK,以及可以与NDI媒体实际交互的一套工具。一旦产生了流媒体,无论其来源如何,我们都可以很容易地对其进行切割、操作或处理,以便将其嵌入到更复杂的制作环境中。正因为如此,很多媒体制作公司都在使用NDI。

这就引出了一个关键问题:如果远程采访也采取这样的制作方式,那制作者很可能无所不用其极获取可以直接使用的NDI源。显然,WebRTC对他们来说还是很难用的。如果没有办法直接拉来WebRTC源,他们只能用些备选方案,比如捕捉渲染远程视频的浏览器窗口,然后用NDI录音机裁剪出他们需要的东西,最终产出成品。而对于音频制作来说,这可能意味着制作人员会录制系统音频输出或者采用类似的操作。

当然,这是一个次优的解决方案,需要制作人员花费更多心血。此外,如果渲染流媒体的网页有动态接口,它可能会擅自移动你感兴趣的部分,甚至限制页面功能(比如不支持屏幕共享),这就更令人头痛了。这正是我开始研究如何让RTP或多或少地直接翻译成NDI的原因,希望能让整个过程更容易操作。

RTP/NDI 网关

我首先去查找了市面上是否已经有一些RTP/NDI的实现,结果模棱两可。我找到的第一个项目是一个开源插件,叫gst-plugin-ndi。这个插件值得研究,但它只涵盖了NDI的接收,没有交付。稍后我会详细解释,该插件对证实我的成果相当有帮助,但并不是我想要的项目。之后我快速研究了FFmpeg的集成,找到了这篇文章,作者在文中解释了FFmpeg之前是如何支持NDI的,但后来由于Newtek主张其存在版权侵权问题,就放弃了对其支持。因此,FFmpeg也不是我的选择。

下一步,我开始寻找自己生成NDI内容的方法,这很简单。因为事实上NDI SDK是免费的,可以在官网上免费下载(下载链接会发送到你提供的邮件地址里)。下载好SDK后,你就可以访问一个很强大的文档,包含大量例子。我浏览了一些展示了如何发送音频和视频的例子,看起来很简单。事实上,对于视频,NDI支持以几种不同的支持格式发送未压缩的帧;对于音频,未压缩的帧可以是16pp或FLTP的(浮点会更好)。

考虑到WebRTC媒体的性质,我决定写一个小型的RTP接收器应用程序(我更喜欢称其为rtp2ndi),该程序可以将音频和视频数据包解包或解码成NDI支持的格式。具体来说,我使用libopus来解码音频数据包,以及libavcodec来解码视频(为了操作简单只支持VP8格式)。至于NDI格式,我选择UYVY视频帧,48khz 16pp音频(虽然FLTP是首选,但NDI SDK还是支持上述格式的)。

测试该应用需要两种不同工具:

  1. 能通过RTP把音视频帧传送到我应用上的工具;

  2. 能通过NDI接收翻译过的流媒体的工具。

对于第一种工具,我选择了自己经常在测试中会用到的一个流媒体脚本——一个简单的gstreamer管道。该管道会接收一个预先录制的文件,再通过RTP发送该文件。对于第二种工具,我选择了上面提到的gst-plugin-ndi项目,它提供了一种在窗口中渲染输入feed的简单方法。

准备就绪后,我先启动了rtp2ndi工具,将其绑定到几个端口来接收RTP数据包,同时给NDI流指定一个名称。

RTP接收器就位后,我就开始把数据包输入到脚本里。

这样我的RTP接收器就开始照我的预想来接收和解码数据包了,同时在这个过程中把数据包翻译成NDI。

之后我要做的就是启动gstreamer管道来接收并显示NDI数据流。

当当!这时我们就能看到下图了,这是我最喜欢的一个SNL片段!

现在我们有了一个好的开端,下一步就是尝试用WebRTC进行画面操作。我一如既往选择了Janus来完成这项工作。

WebRTC (和 Janus )进行画面操作

除发送SNL片段的脚本外,另一个测试WebRTC源的方法可以是常用的RTP转发器,之前在很多场合(包括几天前我在CommCon的演讲)我都推荐过这个方法,也亲自用过很多次了。该方法用在这里也不会出错。步骤很简单:创建一个VideoRoom发布者,RTP把源转发到rtp2ndi工具就好了。但出于其他考虑(下文会详述),我想要开发更集成、能独立运行的工具。

所以我写了一个新的Janus插件,允许WebRTC用户进行以下操作:

  1. 用Janus通过WebRTC发布音频或视频:

  2. 解码发布的流;

  3. 将它们翻译成NDI。

简而言之,我想结合gstreamer管道提供的注入部分,利用Janus核心中集成的RTP服务器功能来接收数据包,并在新插件中嵌入rtp2ndi的RTP到NDI过程。这是一个相当简单的操作。我设计了一个简单的演示页面,允许网络用户给NDI发送者取一个名字,然后像其他几个插件所做的的那样,与Janus建立一个只进行发送的PeerConnection。

我们需要协商一个新的PeerConnection来指示插件创建相关资源,即一个Opus解码器、一个VP8解码器,以及一个使用提供名称的NDI发送器。建立PeerConnection后,演示界面会变成下图这个样子:

显而易见,这个操作没什么特别的地方。我们只是简单展示了浏览器捕捉到的本地流并将其发送给Janus。然后一如往常,Janus核心会将未加密的音视频数据包传递给插件。我们配置Janus NDI插件对数据包进行解码,并通过NDI转发未压缩的帧(和之前rtp2ndi做的完全一样)。这意味着通过WebRTC发源的流现在也可以被NDI接收者使用,我们可以通过gstreamer NDI插件再次确认其可行性。

到这里基本算大功告成了,但这仍然不是我真正想达到的目的。事实上,如果你仔细想想,该操作确实允许Janus作为WebRTC/NDI网关来运行。但如果处理你感兴趣对话的Janus服务器实际是在互联网,而不是在你的局域网里(几乎总是这样)的话,这个操作意义不大。具体一点,如果Janus处于互联网中的某个位置,比如在AWS,你就无法对创建于此的NDI流做什么。因为只有当所有的流都在你的LAN网络范围内交换时,NDI才能发挥作用。

因此,我决定再进一步。具体来说就是我决定尝试让两个Janus实例互相对话,即:

  1. 一个在互联网上的Janus(比如上文在线演示的那个Janus)。

  2. 一个在我局域网里的Janus。

目的很简单——通过WebRTC,使用我局域网中的Janus接收一个实际由公共互联网上的Janus提供服务的流,这样本地的Janus实例就可以将远程流翻译成我可以在本地访问的NDI流。具体来说就是用我的本地Janus实例作为WebRTC 的”客户端”接收来自远程Janus实例的媒体,并将其网关到NDI,如下图所示。

虽然这听起来很费解而且确实有点不合常规,但其实让Janus实例之间这样对话并不稀奇。之前有人使用这个技巧通过WebRTC把一个插件的媒体加载到另一个插件中。要让这一招奏效,通常只需在双方协调Janus API调用,使其中一个(泄露时可能还有其他候选)的SDP提议传递给另一个,相应的SDP应答也会发送回来,让两个Janus实例相互协商PeerConnection。这就是使用WebRTC这种标准的好处!

我决定进行实操。具体来说,我订阅了Janus在线demo上默认的Streaming挂载点(Meetecho点),并利用相关产品与我的Janus NDI插件协商,形成一个新的PeerConnection。事实上,流媒体插件订阅在功能上等同于Janus中的VideoRoom(SFU)订阅。它们都属于简单而又有效的验证部署方式。

之后,再加上编排代码就大功告成了。

当当!该在线演示中远程流媒体挂载点的视频被作为订阅者的本地Janus实例通过WebRTC成功拉取,经过gstreamer NDI接收者再次确认,该媒体流也被正确翻译成NDI了。

大功告成

希望大家喜欢这篇我对于该新机遇的简短介绍。当然,这篇文章中我主要分享了朝这个方向努力的初步进展,考虑到该操作还没有在实际生产环境中测试过,也没有用gstreamer NDI插件以外的任何其他NDI工具测试过,实际上我们还有很多工作要做。我做的一些假设可能需要调整。如果将来该操作需求较大,我也会考虑开源代码。

更重要的是,虽然我概念验证的方法有用,但这并不是最理想的验证方式。事实上正如我们在上文所看到的那样,假设处理你感兴趣的对话/流的Janus实例和负责NDI翻译的Janus实例是不同的(这种情况很常见),也就是你要让两个Janus实例在协调器的帮助下建立一个或多个PeerConnections。这个操作是可行的,但仅仅是为了NDI翻译过程就在生产局域网中多一个Janus实例,可能会被视为一种矫枉过正了。

可能更好的替代方案是创建一个新的临时客户应用程序,其能够执行几乎相同的操作,即:

  1. 建立一个PeerConnection,以便通过WebRTC接收音频和/或视频;

  2. 解包和解码传入流;

  3. 将解码后的数据流翻译成NDI。

这样操作的复杂性在于WebRTC堆栈,这就是为什么在我的概念验证中我坚持使用Janus,因为它能提供很多免费工具。也就是说在开源世界中,我能找到很多工具完成这个目的。此外,我们自己的Jattack测试应用程序基本上就是这样的。通过一些更改,它也可以用来支持额外的NDI翻译过程。

不管采用什么方法,对该机遇感兴趣的人只需开发一个客户端应用程序,将其指向需要媒体的服务器,基本上就可以让他们对整个过程拥有更高控制权。例如该应用可以被配置成只连接一个房间,并自动订阅所有发现;或者指示它挑选来自不同来源的流;总之就是以一种更集成的方式获取我们之前设想的通用外部协商机制所提供的东西。开发一项这样专门的应用程序也将为客户端的额外定制打开大门。例如与其在服务器上运行一个CLI应用程序,这个应用程序可以设定自己的用户界面,并在客户的桌面上运行。

如果上述这方面的需求大,下一步我会重点关注该领域的。虽然不一定不是UI的部分(我真的不太擅长这一块!),但打造一个专用的客户端还是大有可为的。

文章地址:https://www.meetecho.com/blog/webrtc-ndi/

原文作者:Lorenzo Miniero