live-common: Kaltura 媒体框架通用 NGINX 模块
安装
您可以在任何基于 RHEL 的发行版中安装此模块,包括但不限于:
- RedHat Enterprise Linux 7、8、9 和 10
- CentOS 7、8、9
- AlmaLinux 8、9
- Rocky Linux 8、9
- Amazon Linux 2 和 Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-live-common
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-live-common
通过在 /etc/nginx/nginx.conf 顶部添加以下内容来启用该模块:
load_module modules/ngx_http_api_module.so;
本文档描述了 nginx-module-live-common v2.0.6,于 2025 年 11 月 13 日发布。
一个用于实时视频流的分布式框架。该系统由多个组件组成,每个组件负责特定功能。
这些组件可以在单个服务器上部署以进行小规模部署/测试,但建议将它们分开部署以实现更优化的资源利用。例如,转码器可以利用 GPU,因此在启用 GPU 的服务器上部署转码器会更具成本效益,而其他组件则可以在没有 GPU 的服务器上运行。
媒体在不同组件之间通过自定义协议进行内部传输 - 1. Kaltura 媒体协议 (KMP) - 一种基于 TCP 的协议,用于传输流媒体,概念上类似于单个 fMP4/MPEG-TS 的轨道 2. Kaltura 分段媒体协议 (KSMP) - 一种基于 HTTP 的协议,用于分段传输媒体,概念上是 LLHLS/DASH 的超集
不同媒体组件的编排由“控制器”执行。控制器的主要职责是构建媒体管道的拓扑,并在发生故障时进行更新。控制器通过 HTTP-POST 从媒体组件获取 JSON 事件。此外,所有媒体处理组件都公开一个 基于 JSON 的 REST API,控制器使用该 API 获取最新状态并采取行动。有关一体化服务器的示例控制器实现,请参见 conf 文件夹。
主要特性
- 发布协议:RTMP、MPEGTS(通过 SRT/HTTP/TCP)
- 播放协议:HLS/LLHLS、DASH
- 实时推送/中继协议:RTMP
- 视频/音频转码 - 包括基于 ffmpeg API 的 GPU 支持
- 持久性 - 在 S3(或兼容)对象存储中
- 自适应比特率传输
- 字幕支持 - 包括将 608/708 转换为 WebVTT
- 替代音频
- 媒体加密和 DRM
- 视频帧捕获
入门
conf 文件夹包含运行一体化服务器的示例代码和配置。
术语表
- 频道 - 表示实时流的容器,可能包含轨道、变体、时间线等。
- 轨道 - 单个视频/音频/字幕的呈现。例如,一个频道可能有 3 个视频轨道:1080p、720p、540p。
- 变体 - 用于打包的轨道分组。变体决定在使用复用段时,哪个音频轨道将与每个视频轨道配对。 一个变体可以指向多个轨道,但每种媒体类型最多只能有一个轨道。 轨道必须与变体关联,以便通过 HLS/DASH 进行传输。
- 段 - 特定轨道的一组帧。段始终是独立的 - 视频段总是以关键帧/IDR 帧开始。
- 段索引 - 标识与特定时间间隔关联的不同轨道的段的编号。
- 周期 - 一组可以连续播放的段索引。
- 时间线 - 一组周期。可以创建多个时间线,每个时间线都有自己的一组周期。
时间线可以用于实现“预览模式” - 发布者消费一个时间线,而观众消费另一个。
发布者的时间线始终是
active,而观众的时间线在发布者的决定下激活。
示例拓扑
以下图示展示了使用媒体框架组件可以创建的一些示例拓扑。
简单 RTMP 直通
flowchart LR;
enc(编码器);
ingest(nginx-rtmp-kmp-module);
live(nginx-live-module);
pckg(nginx-pckg-module);
play(播放器);
enc-->|RTMP|ingest;
ingest-->|KMP|live;
live-->|KSMP|pckg;
pckg-->|LLHLS/DASH|play;
直通 + S3 持久性
flowchart LR;
enc(编码器);
ingest(nginx-rtmp-kmp-module);
live(nginx-live-module);
pckg(nginx-pckg-module);
s3(Amazon S3);
play(播放器);
enc-->|RTMP|ingest;
ingest-->|KMP|live;
live-->|HTTP|s3;
s3-->|HTTP|live;
live-->|KSMP|pckg;
pckg-->|LLHLS/DASH|play;
SRT 输入 + 视频转码
flowchart LR;
enc(编码器);
ingest(nginx-mpegts-kmp-module);
srt(nginx-srt-module);
trans(转码器);
live(nginx-live-module);
pckg(nginx-pckg-module);
play(播放器);
enc-->|SRT|srt;
srt-->|MPEG-TS|ingest;
ingest-->|KMP 视频|trans;
trans-->|KMP 视频|live;
ingest-->|KMP 音频|live;
live-->|KSMP|pckg;
pckg-->|LLHLS/DASH|play;
隐藏字幕解码
flowchart LR;
enc(编码器);
ingest(nginx-rtmp-kmp-module);
cc(nginx-cc-module);
live(nginx-live-module);
pckg(nginx-pckg-module);
play(播放器);
enc-->|RTMP|ingest;
ingest-->|KMP 视频|cc;
cc-->|KMP 字幕|live;
ingest-->|KMP 视频|live;
ingest-->|KMP 音频|live;
live-->|KSMP|pckg;
pckg-->|LLHLS/DASH|play;
转码 + RTMP 推送
flowchart LR;
enc(编码器);
ingest(nginx-mpegts-kmp-module);
trans(转码器);
live(nginx-live-module);
pckg(nginx-pckg-module);
push(nginx-kmp-rtmp-module);
yt(YouTube);
play(播放器);
enc-->|MPEG-TS/HTTP|ingest;
ingest-->|KMP|trans;
trans-->|KMP|live;
trans-->|KMP|push;
push-->|RTMP|yt;
live-->|KSMP|pckg;
pckg-->|LLHLS/DASH|play;
组件概述
媒体组件
-
nginx-rtmp-kmp-module - 实时媒体摄取,输入:RTMP,输出:KMP x N
-
nginx-mpegts-kmp-module - 实时媒体摄取,输入:MPEG-TS 通过 TCP/HTTP,输出:KMP x N
-
转码器 - 视频/音频转码,输入:KMP,输出:KMP x N
-
nginx-live-module - 实时媒体分段器,输入:KMP x N,输出:KSMP
附加功能:持久性、填充、时间线支持。
-
nginx-pckg-module - 实时媒体打包器(无状态),输入:KSMP,输出:HLS/LLHLS, DASH
附加功能:自适应比特率、字幕、替代音频、媒体加密 / DRM、视频帧捕获
-
nginx-kmp-cc-module - 隐藏字幕解码器,输入:KMP 视频 (h264/5),输出:KMP 字幕 (WebVTT) x N
-
nginx-kmp-rtmp-module - 实时媒体中继,输入:KMP x N,输出:RTMP
重要:所有有状态的基于 nginx 的组件(=除 nginx-pckg-module 外的所有组件)必须在单个进程的 nginx 服务器上部署(worker_processes 1;)。
模块状态是按进程保持的,当使用多个进程时,无法控制哪个进程将接收请求。
例如,创建分段器上的频道的请求可能到达工作进程 1,而与实际媒体的 KMP 连接将命中工作进程 2。
在使用容器的部署中,这不应该是问题 - 可以在单个服务器上部署多个容器,而不是使用多个 nginx 进程。
另一种可能性是使用像 arut 的 per-worker listener 这样的补丁,
但它可能需要更新以适用于 stream 连接。
调试选项
一些媒体框架组件支持可选的预处理器宏以用于调试 -
- NGX_LBA_SKIP (nginx-common) - 跳过使用“大缓冲区数组”(LBA)模块。当启用时,LBA 分配将路由到 ngx_alloc / ngx_free。
- NGX_RTMP_VERBOSE (nginx-rtmp-module) - 启用额外的调试日志消息
- NGX_LIVE_VALIDATIONS (nginx-live-module) - 启用对内部数据结构的运行时一致性检查,使用 --with-debug 时默认启用
- NGX_BLOCK_POOL_SKIP (nginx-live-module) - 跳过使用块池。当启用时,块池分配将路由到 ngx_palloc / ngx_pfree。
为了使用 valgrind 测试模块,建议应用 no-pool-nginx 补丁,
并使用 --with-cc-opt="-O0 -DNGX_BLOCK_POOL_SKIP -DNGX_LBA_SKIP" 和 --with-debug 配置 nginx。
Kaltura 媒体协议 (KMP)
Kaltura 媒体协议是一种简单的基于数据包的协议,用于通过 TCP 流传输媒体。 KMP 连接可以传输单个视频/音频/字幕轨道的媒体 - 当需要多个轨道时,将建立多个 TCP 连接。
每个数据包以包含以下字段的头部开始(每个 32 位) - - 类型 - 数据包的类型。数据包类型是四个字符的代码,下面是当前定义的类型列表。 - 头部大小 - 数据包头部的大小,必须介于 sizeof(kmp_packet_header_t) 和 64KB 之间。 解析器必须使用头部大小来访问数据包的数据,这使得可以在不破坏现有解析器的情况下向数据包头部添加新字段。 - 数据大小 - 数据包数据的大小,必须介于 0 和 16MB 之间。 - 保留 - 保留供将来使用,必须设置为 0。
KMP 中使用的结构和常量可以在 ngx_live_kmp.h 中找到。
KMP 帧 ID
帧 ID 是一个 64 位整数,唯一标识一个输入帧。
摄取模块(nginx-rtmp-kmp-module / nginx-mpegts-kmp-module)根据服务器时钟(以时间尺度单位)分配初始帧 ID,
当创建输出轨道时。
为了避免在每个发送的帧上发送帧 ID,KMP 中的帧 ID 是连续的 -
第 N 个通过 KMP 连接发送的帧的 ID 为 initial_frame_id + N。
如果输入连接(例如 RTMP)掉线并重新建立,将分配新的 KMP 帧 ID。 由于默认时间尺度较高(90kHz),而帧速率不太可能超过 60fps,即使在短时间流媒体后重新连接, 初始帧 ID 也将显著高于上一个连接中最后发送的帧 ID。 因此,由于重新连接而与任何先前使用的帧 ID 发生冲突的可能性极小。
帧 ID 用于: - 确定在 KMP ack 数据包中被确认的帧 - 如果重新建立 KMP 连接,跳过先前处理的帧
转码器为帧 ID 的管理增加了一些复杂性 -
- 由于转码器可能会改变输入帧速率或丢弃帧,因此转码器输入中的帧 ID,
不一定与转码器输出中的帧 ID 相同。
如果转码器被重启,它需要知道要发送的上游服务器的 initial_frame_id 的值(通常是 nginx-live-module)。
upstream_frame_id 字段用于此目的。
- 转码器可能被配置为改变音频轨道的采样率,在这种情况下,转码后的帧与输入帧不对齐。
为了处理这种情况,转码器需要能够仅确认输入帧的一部分。
这就是 offset 字段的目的 - 它可以存储,例如,应该在帧内确认的音频样本数量。
offset 字段的确切含义由 KMP 接收器确定 -
接收器在返回的 ack 帧中设置 offset,并在重新连接时在连接数据包的 initial_offset 字段中返回。
发布者 KMP 数据包
以下部分列出了 KMP 发布者可以发送的 KMP 数据包。
连接 (cnct)
在 KMP TCP 连接建立后立即发送。
头部包含以下字段:
- channel_id - 字符串,正在发布的频道 ID。最大允许长度为 32 个字符。
- track_id - 字符串,正在发布的轨道 ID。最大允许长度为 32 个字符。
- initial_frame_id - 整数,发送的第一帧的 ID。
- initial_upstream_frame_id - 整数,应该发送给上游服务器的初始帧 ID(由转码器使用)
- initial_offset - 整数,初始帧内的起始偏移量。
- flags - 整数,标志的位掩码,目前定义了一个标志 -
consistent - 当 KMP 发布者生成一致(比特精确)输出时设置该标志,给定相同的输入。
nginx-rtmp-kmp-module 和 nginx-mpegts-kmp-module 是一致的发布者的例子。
而 转码器 则不是。consistent 标志在重新连接时由 LL 分段器使用。
当发布者一致时,LL 分段器可以从它停止的地方继续。
当发布者不一致时,LL 分段器只能从下一个 GOP 继续 -
它不能将断开连接前的部分 GOP 与断开连接后接收到的 GOP 混合。
连接数据包的数据是可选的,数据的预期格式由特定的 KMP 接收器定义。
媒体信息 (minf)
包含媒体的参数。 头部中的某些字段由所有媒体类型共享,而其他字段仅为特定类型定义(联合)。
共享的头部字段包括:
- media_type - 整数,媒体类型 - video / audio / subtitle,使用 KMP_MEDIA_XXX 常量。
- codec_id - 整数,编码器 ID,使用 KMP_CODEC_XXX 常量。
- timescale - 整数,帧数据包中的 dts / pts_delay / created 字段中使用的单位,以 Hz 为单位。
- bitrate - 整数,比特率,以每秒比特数为单位。
视频特定的头部字段包括:
- width - 整数,视频宽度,以像素为单位。
- height - 整数,视频高度,以像素为单位。
- frame_rate - 有理数,视频的帧速率,以每秒帧数为单位。
- cea_captions - 布尔值,当视频轨道包含 EIA-608 / CTA-708 字幕时设置为 1。
音频特定的头部字段包括:
- channels - 整数,音频通道的数量。
- bits_per_sample - 整数,音频样本的大小,以比特为单位。
- sample_rate - 整数,音频的采样率,以每秒样本数为单位。
- channel_layout - 整数,通道位置的位掩码,使用 KMP_CH_XXX 常量。
媒体信息数据包的数据包含编码器的私有/额外数据。
例如,当使用 h264 编码器时,数据包含 avcC MP4 盒子的主体。
KMP 接收器应处理媒体信息的变化,例如,视频分辨率的变化。 然而,在 KMP 连接中发送的媒体类型(视频/音频/字幕)不得更改。
KMP 接收器应忽略与先前接收到的媒体信息数据包相同的媒体信息数据包。
帧 (fram)
表示单个视频帧/音频帧/字幕提示。
帧头部包含以下字段:
- created - 整数,帧被管道中的第一个媒体框架模块接收的时间,以时间尺度单位表示。
- dts - 整数,帧的解码时间戳,以时间尺度单位表示。
当媒体类型为 subtitle 时,保存提示的开始时间戳。
- flags - 整数,目前仅定义一个标志 -
key - 在视频关键帧上启用。
- pts_delay - 整数,帧的呈现时间戳与解码时间戳之间的差异,以时间尺度单位表示。
当媒体类型为 subtitle 时,保存提示的持续时间 - end_pts - start_pts。
当媒体类型为视频/音频时,帧数据包的数据包含压缩的媒体。
当媒体类型为字幕且编码器为 WebVTT 时,帧的数据遵循 WebVTT 样本格式,如 ISO/IEC 14496-30 中所规定
(通常,在这种情况下,样本是一个 vttc 盒子,包含一个 payl 盒子)。
空 (null)
发送以表示“实时性”,并防止空闲计时器过期。 空数据包不携带任何数据,除了基本的 KMP 头部。 解析器必须忽略空数据包。
流结束 (eost)
用于表示发布会话的优雅终止。 流结束数据包不携带任何数据,除了基本的 KMP 头部。
接收器 KMP 数据包
以下部分列出了 KMP 接收器可以发送的 KMP 数据包。
确认帧 (ackf)
确认接收的帧。
KMP 接收器决定发送确认数据包的适当时机。 例如,当启用持久性时,分段器仅在包含帧的段保存到存储后发送确认。
某些接收器根本不发送确认,在这种情况下,KMP 生产者必须配置为在发送后丢弃帧(使用 resume_from 设置)
数据包头部包含以下字段:
- frame_id - 整数,如果连接掉线,则应重新发送的第一帧的 ID。
换句话说,确认帧数据包确认所有 ID 小于 frame_id 的帧。
如果重新建立 KMP 连接,此值将在 initial_frame_id 字段中发送。
- upstream_frame_id - 整数,发送到上游服务器的帧的 ID。
如果重新建立 KMP 连接,此值将在 initial_upstream_frame_id 字段中发送。
- offset - 整数,帧内要确认的偏移量。
如果重新建立 KMP 连接,此值将在 initial_offset 字段中发送。
- padding - 整数,保留供将来使用,必须设置为零。
确认数据包的数据不被使用。
Kaltura 分段媒体协议 (KSMP)
Kaltura 分段媒体协议是一种基于 HTTP 的协议,用于分段传输媒体,类似于 HLS/DASH。
KSMP 请求是一个 HTTP GET 请求,定义了以下查询参数 -
- channel_id - 必需的字符串,频道的 ID
- timeline_id - 必需的字符串,时间线的 ID
- flags - 必需的十六进制整数,标志:
- 选择所需数据的子集(如 SQL SELECT 语句中的列列表)
- 控制服务请求时的各种行为。
例如,“最近关键帧”标志,仅返回与请求时间戳最接近的关键帧,而不是返回整个段。
- variant_ids - 可选字符串,选择应返回的变体的子集,默认情况下返回所有变体。
如果指定多个变体,应该使用连字符(-)分隔。
- media_type_mask - 可选十六进制整数,设置应返回的媒体类型,默认情况下返回所有媒体类型。
- time - 可选整数,请求的时间戳。时间戳用于,例如,在特定时间捕获视频帧。
- segment_index - 可选整数,段的索引
- max_segment_index - 可选整数,用于限制响应中返回的段的范围。此参数可用于调试持久化流。
- part_index - 可选整数,段内部分段的零基索引。使用 part_index 的请求必须同时发送 segment_index。
- skip_boundary_percent - 可选整数,将 skip boundary 值设置为 target duration 的百分比
(有关更多详细信息,请参见 HLS 规范中 CAN-SKIP-UNTIL 属性的定义)
- padding - 可选整数,在响应末尾添加额外的零字节。用于满足 ffmpeg 的填充要求,而不产生额外的复制操作。
KSMP 响应使用 KLPF 格式(见下文),类型为 Serve (serv)。
KSMP 特定的定义可以在 ngx_ksmp.h 中找到。
Kaltura 实时持久文件 (KLPF)
Kaltura 实时持久文件是一种序列化方案,用于 KSMP 响应和 nginx-live-module 创建的 S3 对象。
KLPF 由块组成,类似于 MP4 原子/盒子。每个块都有以下头部 -
- id - 一个四个字符的代码,用于标识块
- size - uint32,块的完整大小(头部和数据)
- flags - 4 位,定义了以下标志:
- container (0x1) - 块包含其他块
- index (0x2) - 块是指向另一个块的索引,头部大小不应使用
- compressed (0x4) - 块的数据经过 zlib 压缩
- header_size - 28 位,块头部的大小。解析器必须使用头部大小来访问块的数据,
以便可以在不破坏兼容性的情况下向头部添加字段
KLPF 文件是一个 ID 设置为 klpf 的块。
在通用块头部字段(如上所列)之后,KLPF 文件的头部包含以下字段 -
- uncomp_size - uint32,保存未压缩数据的大小,当 KLPF 数据被压缩时
- version - uint32,文件格式的版本。用于新文件的版本在每次格式发生重大更改时更新,代码将更新以支持
- 同时读取新格式和旧格式,或
- 忽略使用旧格式的文件
- type - 一个四个字符的代码,用于标识存储在 KLPF 中的数据类型。类型决定支持哪些块 ID 及其内部结构。
类型 serv (Serve) 用于 KSMP 响应,在打包器与分段器之间的通信中。其他类型在分段器内部使用。
- created - uint64,KLPF 创建时的 unix 时间戳
有关 KLPF 块内部结构的更多详细信息,请参见 KLFP-SPEC.md。
要检查 KLPF 对象/KSMP 响应的内容,请使用 klpf_parse.py。
该脚本可以显示块结构而无需任何额外信息,但要解析块内部的字段:
- 运行 generate_persist_spec.py,并将输出保存到文件
- 使用 -s / --spec-file 选项将文件名提供给 klpf_parse.py。
API 概述
所有媒体处理组件都公开一个基于 JSON 的 REST API。 本节解释了媒体框架 API 的一般属性。 有关可用 API 端点的详细参考,请参见特定模块的文档。
请求类型
API 中使用以下 HTTP 动词:
- GET - 获取模块的完整状态或其子集。可以在请求中添加参数 ?pretty=1,以返回“美观”/缩进格式的响应。
- GET 和 ?list=1 - 返回 API 中某个路径下“文件夹”的名称。可用于遍历可能的 API 路由树。
- POST - 创建对象。
- PUT - 更新对象,更新的对象 ID 通过 URI 传递。
- DELETE - 删除对象,删除的对象 ID 通过 URI 传递。
POST / PUT 请求中的请求体必须是 JSON(通常是对象),请求必须使用头部 Content-Type: application/json。
当请求体的大小超过某个阈值时,nginx 会将其写入临时文件。
然而,媒体框架 API 的实现要求 POST / PUT 请求的请求体必须在内存中可用。
如有需要,可以使用 nginx 的 client_body_buffer_size 指令来增加为请求体分配的缓冲区大小。
多请求
在 nginx-live-module 中设置频道可能需要多个 API 调用 - 创建频道、创建时间线、创建变体等。 为了避免多次往返的惩罚,API 层支持“多”请求。 多请求将多个 API 请求打包在一个 HTTP 请求中。
多请求必须使用 POST 动词,其 URI 必须设置为 /multi。
请求体必须是一个 JSON 对象数组,每个对象代表一个单独的 API 请求。
对象包含以下字段:
- uri - 字符串,必需,相对 API 路径。
- method - 字符串,必需,请求的 HTTP 动词 - GET / POST / PUT / DELETE。
- body - 任意(通常是对象),可选,POST / PUT 请求的主体。
多请求的响应也是一个 JSON 对象数组。 响应数组中的元素数量始终与请求数组中的元素数量匹配, 响应数组中对象的顺序与请求数组中的顺序匹配。 换句话说,响应数组的第 N 个项目是请求数组中第 N 个请求的响应。
每个响应对象包含以下字段:
- code - 整数,必需,HTTP 状态码
- body - 任意(通常是对象/数组),可选,响应体
GitHub
您可以在 GitHub repository for nginx-module-live-common 中找到有关此模块的其他配置提示和文档。