设想主持人在直播节目进行到一半时断开连接。接下来播出的,可以是一段备用录音,可以是音乐播放列表里的曲目,也可以是一片寂静。Liquidsoap 是一门用于组织媒体流的开源语言,这项选择可以直接写进程序结构。沿着下一帧音频的行进过程,依次查看音源、备用切换规则和时钟,就能理解系统如何维持电台的播出。[1][3]
理解这套架构,可以从分工入手。在常见的网络广播配置中,Liquidsoap 负责编排节目并编码,Icecast 负责将音频流分发给听众,听众的播放器再把收到的数据变成声音。听众增加,主要增加的是分发环节的工作量;决定他们听到什么,则是上游的职责。[1] 本文示例依据 Liquidsoap 的 2.4.5 版文档,法国广播电台(Radio France)的公开部署则作为单独的运维实例。
输出端请求下一帧
Liquidsoap 脚本会把多个音源(source)连接成一张网络。音源逐帧供应媒体数据,并按需要附带元数据和曲目边界。因此,播放列表、直播输入,以及组合其他音源的算子,都能参与同一套系统。[3]
时钟每跳动一次,输出端就向自己的音源请求数据。请求沿着相连的算子传递,直到抵达能够供应采样数据的环节。例如,output.icecast 可以从 fallback 获取数据,再由后者选中一个播放列表。脚本描述了这些连接关系,播出期间,数据请求会反复沿着它们传递。[3]
有些输入即使尚未播出,也要持续运行。音源文档将用于接收传入直播流的 input.harbor 列为主动音源:即使当前选中了另一个音源,它仍须接收数据。[3] 因而,备用输入也有自己的运行过程,在听众听到它之前,就已经开始工作。
文件名还要经过处理才能变成声音
录制素材经由另一层抽象进入系统:请求(request)。request.create 接受一个 URI,创建请求之后,还要由 playlist 或 single 这类基于请求的音源解析它,取得能够解码的素材,才能播放。[4]
解析过程可以包含多个步骤。自定义协议先把媒体库中的标识符转换成 URL,HTTP 处理器再下载文件,随后还要有解码器能够读取它。即使已经取得本地文件,内容不合要求也会阻碍后续处理。[4] 节目单上有一条记录,与音源已经准备好播出,是两种不同的状态。
元数据同样有到达时限。Liquidsoap 的 annotate: 协议可以在请求解析期间传入 cue_in 和 cue_out。这些指令必须在播放前抵达解码器;到了更下游才附加,便无法追溯改变解码器已经读取的内容。[4] 这里体现了一条有用的架构原则:信息应当在仍能影响处理结果的环节到位。
可用的音频也会是一片静音
Liquidsoap 会区分可失效音源与预期持续可用的音源。普通播放列表属于可失效音源,因为其中的文件会有无效或无法访问的情况。默认情况下,输出端要求输入音源持续可用。如果 fallback 的最后一项是 single("standby.ogg"),且它指向一段有效的本地录音,就能用来保证音频持续供应。另一种做法是使用 mksafe,在输入失效时供应静音。[1]
这里的 infallible(持续可用)有意限定在很窄的范围内,描述的是流媒体模型中的音源可用性。至于节目内容是否有用,或机器断电后还能否播出,都超出了这个词所能保证的范围。
演播室的连接可以一直保持着,同时持续传来静音。Liquidsoap 用另一个算子 blank.strip 处理这种情况:静音持续过久,就把该输入标记为不可用,外层的备用切换算子随后可以选用其他音源。文档将 max_blank=5. 与 track_sensitive=false 配合使用:输入静音达到五秒后,插播内容接替播出;声音恢复时,即使插播内容尚未到达曲目边界,也可以切回直播音源。[5]
这项设置也表达了一项编辑决定。长时间停顿,既有技术故障的情形,也有戏剧朗诵或作品有意收尾的情形。检测器测量的是音频状态,电台则决定这种状态持续到何时应当介入。规则写得清楚,就能在真正播出之前讨论并演练这次中断与切换。
交叉淡化需要提前取得音频
音源选择决定音频从哪里来,时钟决定系统以多快的速度消耗它。每个音源都归属于一个时钟,Liquidsoap 通常会自动确定这种归属。[6]
录制好的音乐可以提前读取。crossfade(交叉淡化)需要这种余地,因为两首曲目重叠时,系统须暂时以高于输出流播放速度的速率消耗素材。直播输入若按自身时序运行,就未必有同样的余地。时钟文档给出一个例子:SRT 输入与需要掌握处理节奏的交叉淡化算子相连时,系统拒绝了这项连接。[6]
buffer() 可以在不同的时钟域之间建立缓冲队列,让媒体数据从一个时钟域流向另一个。队列容量有限,速率差异足够大,就会耗尽或填满队列。因此,缓冲也带来了容量与时序上的取舍,对设备间节奏差异的调节能力有其限度。[6] 对负责接入演播室信号的人来说,实际要弄清的是哪个组件掌握节奏,以及其他组件各自能够容忍多长的等待。
广播机构在音源网络之外增加了什么
法国广播电台公开的配置,让这些抽象概念有了具体形态。其 README 描述了多路 SRT 输入:它们经由不同的网络路径传送同一电台的音频。一个 Liquidsoap 进程从中选择,生成 radio_prod,随后按多组参数编码,再经 Icecast 和 HLS 分发。[7]
配置还公开了 preferred_output、real_output 和 is_output_blank。三者分别表示配置设定或手动指定的首选输出、备用切换系统实际选中的输出,以及当前选中项是否正在输出静音。监测项目还包括输入是否就绪、音频电平和缓冲区状态。[7] 进程正在运行,只是判断播出是否正常的初步依据。
Liquidsoap 在更大的电台系统中承担这类职责,已有很长一段历史。Nathan Willis 在 2012 年发表于 LWN 的 Airtime 报道中,将它描述为广播管理应用底层的音频流生成器。[8] 这份独立报道也解释了,一门小型语言为何能影响到编写脚本的人之外:媒体处理组件之上,还可以搭建排期与节目呈现界面。
依我的理解,这套设计的长处在于让持续播出的安排有据可查。对小型电台来说,这仍然要求有人维护脚本,并测试文件失效、主持人断线、输入静音及恢复播出等情况。对于规模更大的机构,法国广播电台的实践展示了另一层价值:观察实际选中的音源及其状态。[7] 备用录音响起时,让人满意的还有对整个过程的理解:它为何启动,此刻正在播出什么,以及满足哪些条件后直播节目会回来。
来源
- Liquidsoap 2.4.5 文档,“Quickstart”——音频流的生成与分发、音源失效、本地备用录音及静音后备方案。
- Edison McCullen,“Maison de la Radio & Pont Rouelle”,2018 年 5 月 17 日,Wikimedia Commons——原始照片及 CC BY-SA 4.0 署名信息。
- Liquidsoap 2.4.5 文档,“Understanding Sources in Liquidsoap”——帧、音源组合、由输出端驱动的执行过程及主动输入。
- Liquidsoap 2.4.5 文档,“Understanding Requests”——URI 解析、解码,以及播放起止点元数据应当传入的时机。
- Liquidsoap 2.4.5 文档,“Blank detection”——将静音输入标记为不可用,以及声音恢复后的切回操作。
- Liquidsoap 2.4.5 文档,“Clocks in Liquidsoap”——时钟域、交叉淡化的约束、SRT 时序及缓冲区。
- 法国广播电台,“Radio France's Liquidsoap scripts”,公开仓库 README——冗余输入路径、输出编码参数、选择状态及监测。
- Nathan Willis,“Radio station management with Airtime”,LWN,2012 年 2 月 15 日——关于 Liquidsoap 在电台自动化系统中所处位置的独立历史报道。