docs
Cbrts 消费器补充

Cbrts 消费器补充

入门

cbrts 消费器(consumer)是对 avformat 消费器的一层封装。因此,你必须先理解 avformat 消费器。更多信息参见 ConsumerAvformat:

例如,下面是一个 SD PAL MPEG-2 10 Mbps CBR 的示例:

$ melt -profile dv_pal ... -consumer avformat:test.ts f=mpegts muxrate=10240000 \
vcodec=mpeg2video bufsize=1835008 vb=7000k minrate=7000k maxrate=7000k g=15 bf=2 \
acodec=mp2 ab=160k threads=2

(MLT 默认使用立体声 48 KHz 音频)

在 cbrts 消费器上设置的所有属性(property)都会传递给 avformat 消费器——无需特殊前缀——除了少数关键运行属性,例如 real_time 和 terminate_on_pause。虽然某些 avformat 属性可以接受 “k” 后缀,但 cbrts 消费器使用 muxrate 且不识别 “k” 后缀:

$ melt -profile dv_pal ... -consumer cbrts muxrate=10240000 \
vcodec=mpeg2video bufsize=1835008 vb=7000k minrate=7000k maxrate=7000k g=15 bf=2 \
acodec=mp2 ab=160k threads=2

码流被发送到 STDOUT,但没有任何码率控制!MLT 会以 STDOUT 和 CPU 所允许的最快速度写入。如果 STDOUT 被重定向到磁盘,那就好比转码一个文件或渲染/编码一个 MLT 合成(composition)。因此,如果你编写一个程序读取 STDOUT 并写入 UDP 套接字,那么你就需要依据自己的 muxrate 对写入套接字进行计量(节流),以确保正确投递。另外,你可能需要对数据做一些缓冲,以保证包间时序的一致性。下面是一个简单的 Python 程序,可以帮助你入门:它从 STDIN 读取并写入一个组播套接字,没有任何缓冲或码率控制:

#!/usr/bin/env python
import sys, socket, signal

filename = sys.stdin
address = '224.30.30.4'
port = 1234

def sigpipe_handler():
	sys.exit(0)
signal.signal( signal.SIGPIPE, sigpipe_handler)

f = sys.stdin
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, '\xFF')

while True:
	packet = f.read(188 * 7)
	if len(packet) == 0:
		break
	s.sendto(packet, (address, port))

s.close()

HD

如果你此时想转向 HD,请务必把码率适当调高,尤其是 VBV(bufsize)。否则你可能给 MPEG-2 Video 编码器造成过大负担,使它无法跟上实时处理。上面使用的数值对应于更典型的表示方式:把 112 换算为比特:112 * 2048 * 8。112 是 DVD Video 的标准,但我认为 DVB 对单独的 SD 允许两倍的空间,所以对于 HD 要把它提高不少,但我不知道标准具体规定是多少。请自行试验。下面是一个示例:

melt -profile atsc_1080i_50 ~/tmp/videos/* -consumer cbrts muxrate=20000000 \
vcodec=mpeg2video bufsize=5505024 vb=13000k minrate=13000k maxrate=13000k g=15 bf=2 \
acodec=mp2 ab=256k threads=3 real_time=-2

DVB PSI/SI 段

接下来,我们可以进入 DVB 的 SI 插入部分。对于这些内容,我们使用 [[http://www.avalpa.com/the-key-values/15-free-software/33-opencaster][OpenCaster (opens in a new tab)]] Python 库 “dvbobjects” 来生成二进制段文件。按照约定,我们给这些文件使用 “.sec” 扩展名。dvbobjects 库非常全面,如果有缺失的东西,由于它是 Python 实现的,扩展起来也很容易。即使你不了解 Python 也不必担心,因为其绝大部分代码只是简单的 Python 对象表示语法(可以想象成 JSON,但用的是 Python 语法)。所以它更多是声明式的,而不是编程式的。参见文件 !OCTutorials/SPTS/firstconfig.py,那是一个很好的入门示例。

如果你选择重写 PMT 段,那么你需要了解 libavformat 如何在基本流上设置 PID。目前,视频流是 256(0x100),音频流从 257 开始,从此依次递增。

把 .sec 文件传给消费器有一些属性命名约定。约定为

  • si.<section>.file=<filename.sec>
  • si.<section>.pid=<number>
  • si.<section>.time=<milliseconds> <section> 实际上可以是任意名称,但通常是:pat、sdt、nit、eit 等。“pat”“pmt” 和 “sdt” 是特殊的:当提供它们时,libavformat 对应的段会被过滤掉,以你的输入替代。你应当始终对 NIT 使用 PID 16,对 SDT 使用 PID 17,当然 PAT 使用 0;PMT 可以是任意值,但 libavformat 使用 4095(0xfff)。

time 属性表示插入该段的频率——每 N 毫秒一次。这些值在 DVB 的某处有规定,我相信。下面是一个示例:

$ cd OCTutorials
$ ./firstconfig.py
$ melt -profile dv_pal ~/tmp/videos/* -consumer cbrts muxrate=10240000 \
vcodec=mpeg2video bufsize=1835008 vb=7000k minrate=7000k maxrate=7000k g=15 bf=2 \
acodec=mp2 ab=160k threads=2 \
si.sdt.file=sdt.sec si.sdt.pid=17 si.sdt.time=500 \
si.pat.file=pat.sec si.pat.pid=0 si.pat.time=100 \
si.pmt.file=pmt.sec si.pmt.pid=4095 si.pmt.time=100 \
si.nit.file=nit.sec si.nit.pid=16 si.nit.time=600

多音轨

多音频轨或多流涉及两个方面。第一,在输入/生产器(producer)侧,若要把多条音频轨合并为一个多声道束,需在 producer 对象上把 “audio_index” 设为 “all”。第二,在输出/消费器侧,你必须为每条轨/流设置声道数。在此之前有一个公共的 “channels” 属性,但对于多条轨,你要追加一个从 0 开始的轨索引:channels.0、channels.1 等。如果你要插入 PMT,别忘了更新你的 Python 脚本以包含额外的流。音频 PID 从 257(0x101)开始并从此顺序递增。延续我们的示例,下面的命令输出 2 条立体声轨(例如英语和法语):

$ melt -profile dv_pal ~/tmp/videos/* audio_index=all \
-consumer cbrts muxrate=10240000 \
vcodec=mpeg2video bufsize=1835008 vb=7000k minrate=7000k maxrate=7000k g=15 bf=2 \
acodec=mp2 ab=160k threads=2 \
si.sdt.file=sdt.sec si.sdt.pid=17 si.sdt.time=500 \
si.pat.file=pat.sec si.pat.pid=0 si.pat.time=100 \
si.pmt.file=pmt.sec si.pmt.pid=4095 si.pmt.time=100 \
si.nit.file=nit.sec si.nit.pid=16 si.nit.time=600 \
channels.0=2 channels.1=2

如果输入没有 2 条音频轨,那么它会在第二条输出轨上输出静音。

音频声道映射

你可以在 producer 对象上设置属性,把它们的音频声道重新映射到输出中不同的声道和流。这实际上是 avformat 消费器的一个特性。这些属性名为 meta.map.audio.{N}.channels 和 meta.map.audio.{N}.start,两者都取整数值,{N} 是从 0 开始的顺序递增整数。在试图理解这些属性时,心里默念或说出下面这句话最有帮助:从输入中读取 X 个声道,并输出到输出中从第 Y 个声道 开始 的位置。基于上面多音轨的示例,我们可以交换轨序,先输出法语再输出英语:

$ melt -profile dv_pal ~/tmp/videos/* audio_index=all \
meta.map.audio.0.channels=2 meta.map.audio.0.start=2 \
meta.map.audio.1.channels=2 meta.map.audio.1.start=0 \
-consumer cbrts muxrate=10240000 \
vcodec=mpeg2video bufsize=1835008 vb=7000k minrate=7000k maxrate=7000k g=15 bf=2 \
acodec=mp2 ab=160k threads=2 \
si.sdt.file=sdt.sec si.sdt.pid=17 si.sdt.time=500 \
si.pat.file=pat.sec si.pat.pid=0 si.pat.time=100 \
si.pmt.file=pmt.sec si.pmt.pid=4095 si.pmt.time=100 \
si.nit.file=nit.sec si.nit.pid=16 si.nit.time=600 \
channels.0=2 channels.1=2

如果你需要在输出中跳过某些声道,必须添加一些额外的 meta.map.audio 属性来指示它这样做。如果我们想在输出的第一条轨上增加一条德语(Deutsch)立体声轨(Deutsch、法语、英语),但我们的源片段不包含它,可以按如下方式跳过它:

$ melt -profile dv_pal ~/tmp/videos/* audio_index=all \
meta.map.audio.0.channels=2 meta.map.audio.0.start=2 \
meta.map.audio.1.channels=2 meta.map.audio.1.start=4 \
meta.map.audio.2.channels=2 meta.map.audio.2.start=0 \
-consumer cbrts muxrate=10240000 \
vcodec=mpeg2video bufsize=1835008 vb=7000k minrate=7000k maxrate=7000k g=15 bf=2 \
acodec=mp2 ab=160k threads=2 \
si.sdt.file=sdt.sec si.sdt.pid=17 si.sdt.time=500 \
si.pat.file=pat.sec si.pat.pid=0 si.pat.time=100 \
si.pmt.file=pmt.sec si.pmt.pid=4095 si.pmt.time=100 \
si.nit.file=nit.sec si.nit.pid=16 si.nit.time=600 \
channels.0=2 channels.1=2 channels.2=2

注意我还给消费器添加了 channels.2 属性。重要的是,要把用于跳过输出声道的 meta.map.audio 属性添加在实际使用的声道 之后。

AC-3 音频

按照 !StreamXpert 的判断,AC-3 音频对我是可用的。在 Tandberg IRD 上没有显示错误,但我还没有弄清楚如何“听到”它。以下是使其工作的步骤:

  1. 使用 avformat 属性 acodec=ac3 代替 acodec=mp2。
  2. 基于 A/52 标准,我在 OpenCaster/libs/dvbobjects/dvbobjects/MPEG/Descriptors.py 的末尾添加了以下 Python 类:
class AC3_audio_stream_descriptor(Descriptor):

	descriptor_tag = 0x81

	def bytes(self):
		fmt = "!BBBBBB%dsB%s%s" % (
			len(self.text),
			self.language_flag == 1 and '3s' or '',
			self.language_flag_2 == 1 and '3s' or '',
		)
		return pack(fmt,
			((self.sample_rate_code & 0x7) << 5 |
			(self.bsid & 0x1f)),
			((self.bit_rate_code & 0x3f) << 2 |
			(self.surround_mode & 0x3)),
			((self.bsmod & 0x7) << 5 |
			(self.num_channels & 0xf) << 1 |
			(self.full_svc & 0x1)),
			0xFF, # langcod is deprecated per A/52
			(self.bsmod < 2) and (
				(self.mainid & 0x7) << 5 |
				(self.priority & 0x3) << 3 |
				0x7 # reserved
			) or (
				self.asvcflags
			),
			((len(self.text) & 0x7f) |
			(self.text_code & 0x1)),
			self.text,
			((self.language_flag & 0x1) << 7 |
			(self.language_flag_2 & 0x1) << 6 |
			0x3f) #reserved
# languages are not working yet - set the flags to zero
#			self.language_flag == 1 and self.language or (
#				self.language_flag_2 == 1 and self.language_2 or None)
		)
  1. 像下面这个示例一样,把一个 AC3 描述符添加到 PMT 段文件:
		stream_loop_item(
			stream_type = 0x81,
			elementary_PID = 0x101,
			element_info_descriptor_loop = [
				AC3_audio_stream_descriptor(
					sample_rate_code = 0, # 48 KHz
					bsid = 8, # see libavformat source code
					bit_rate_code = 12, # 256 kb/s, see A/52
					surround_mode = 1, # = 0 not indicated, per libavformat in stereo mode; 1 = NOT; 2 = surround
					bsmod = 0, # see libavformat source code
					num_channels = 2,
					full_svc = 1,
					mainid = 0,
					priority = 1, # primary
					asvcflags = 1, # associated with service 0
					text = '',
					text_code = 1, # Latin-1
					language_flag = 0,
					language_flag_2 = 0
				)
			]
		),