RK3576 + NDI 框架:HX2 编码输出与 HX2/FULL 输入全解析
本文是 MEDAI-V1 / RK3576 技术专栏(已发 拉流 / 绿幕 / MPP 编解码 / UVC 解码 / 综合性能 / RGA 适配 / MPP×RGA 统一编码 / FFmpeg 拉流 / librtmp 推流 / V4L2-UVC / ALSA 音频 / RGA+DRM HDMI 输出)的 第十三篇 ,聚焦 NDI 框架 在设备上的落地:如何用 RK3576 把统一的视频画布以 NDI|HX2 编码输出,又如何把局域网里的 NDI|HX2 / NDI|FULL 信号拉进来硬解、合成、再分发。文中所有功能点均深度对照《MEDAI-V1 使用说明书》。

一、NDI 框架技术详解
1.1 NDI 是什么
NDI(Network Device Interface,网络设备接口) 是 NewTek(现 Vizrt)推出的基于 IP 的实时音视频传输标准,目标只有一个:让一根网线代替一堆 SDI/HDMI 线,在局域网内以"可接受的延迟"把多路高质量音视频在设备间自由路由。
和 RTMP/RTSP/SRT 这类"面向广域网、单路长连接"的协议不同,NDI 的设计哲学是:
- 局域网优先:假设 1 Gbps 交换机组网,靠发现协议自动列出所有源;
- 多路并发:一台机器可以同时看到几十路源,按需挑一路拉;
- 双向无损概念:FULL 走"视觉无损"高带宽,HX/HX2 走压缩低带宽;
- 带内元数据:每一帧可挂 PTZ、Tally、时间戳、元数据。
在 MEDAI-V1 上,NDI 既是 五路输入源之一 (3.2.2 视频输出可选 NDI 作为输入),又是 本地流媒体服务的一种输出 (3.5 媒体输出第 6 项),还参与 视频叠加的主/副源 (4.4.2)和 手势切源的目标(6.2 中 Three 手势切到 NDI)。
1.2 NDI 软件栈分层
无论发送端还是接收端,NDI 运行时都分成四层,理解这四层是后面写代码的基础:
┌─────────────────────────────────────────────────────────┐
│ SDK 层 NDIlib_recv_* / NDIlib_send_* (C API) │
│ find(发现) / recv(收) / send(发) │
├─────────────────────────────────────────────────────────┤
│ Transport 层 UDP/RTP 承载视频音频;mDNS 承载发现 │
│ 单源单连接(NDI 4+);TCP 兜底 │
├─────────────────────────────────────────────────────────┤
│ Compression 层 FULL:SpeedHQ 视觉无损(高带宽) │
│ HX2:HEVC 压缩(低带宽) HX:H.264(旧) │
├─────────────────────────────────────────────────────────┤
│ Discovery 层 mDNS / Bonjour,注册 _ndi._tcp.local │
│ receiver 浏览器式枚举所有在线源 │
└─────────────────────────────────────────────────────────┘
黄金认知 :NDI SDK 自己在内部完成"压缩/解压缩"和"传输",用户代码只和 NDIlib_video_frame_v2_t / NDIlib_audio_frame_v3_t 打交道------你给它一张 YCbCr/ARGB 帧,它负责压成 HX2 发出去;它收到 HX2/FULL 包,解完给你一张同样结构的帧。设备侧的 MPP 只负责 RTMP/MP4 那条编码链,不直接编 NDI 的码流。
1.3 NDI 三大变种横向对比
| 变种 | 压缩方式 | 1080p60 带宽 | 4K 带宽 | 延迟 | 典型用途 |
|---|---|---|---|---|---|
| NDI|FULL | SpeedHQ 视觉无损 | ~250 Mbps | ~1 Gbps | 极低 | 演播室内部、画质优先 |
| NDI|HX2 | HEVC(H.265) | 8 ~ 20 Mbps | 25 ~ 80 Mbps | 低 | 跨网段、无线、公网桥接 |
| NDI|HX(旧) | H.264 | 8 ~ 16 Mbps | 不提倡 4K | 中 | 老设备兼容 |
MEDAI-V1 出厂为 试用版 NDI (4.3.4),编码与解码两个方向 只能二选一启用(7.2 注意事项第 5 条)。下面的实现会分别讲"当设备处于编码态"和"处于解码态"两种代码路径。
1.4 NDI|HX2 深入:HEVC 底子 + 带宽账
NDI|HX2(NDI 5 引入,NDI 6 有 HX3)是 HEVC 压缩 + NDI 元数据 的结合体,关键特性:
- 码率自适应:1080p60 默认 8~15 Mbps,运动剧烈时上探到 20 Mbps;
- 支持 Alpha 通道:可传带透明度的前景(配合绿幕/叠加用,对应 4.4.2/4.4.3);
- 低延迟:端到端通常 80~150 ms(局域网),比 RTMP 的 1~3 s 低一个量级;
- 抗丢包:基于 UDP 但带重传请求,轻度丢包不花屏。
带宽账(为什么 HX2 适合无线/跨网段):
一路 NDI|HX2 1080p60 ≈ 15 Mbps
千兆交换机组 20 路 HX2 = 300 Mbps ← 完全吃得消
一路 NDI|FULL 1080p60 ≈ 250 Mbps
千兆交换机组 4 路 FULL = 1 Gbps ← 已经打满
结论:同一台千兆交换机,HX2 能塞进 10~20 倍于 FULL 的路数。这也是设备把 NDI 输出默认走 HX2 的工程理由。
1.5 NDI|FULL 深入:未压缩(视觉无损)真相
NDI|FULL 早期常被误解为"原始 RGB 不压缩",实际它用 SpeedHQ(一种类 MJPEG 的 intra 帧压缩) 做到"视觉无损",但带宽仍极高:
- 1080p60、4:2:2、8bit:约 250 Mbps;
- UHD(2160p30)、4:2:2:约 1 Gbps。
对 RK3576 而言,收 FULL 路意味着:NDI SDK 解完后是 UYVY / BGRA 满帧 ,不经过 VPU(因为 SDK 内部已解码),直接由 RGA 转 NV12 进统一画布(见第六篇 RGA 适配)。收 FULL 路几乎不占 VPU,但 占满网口和内存带宽。
1.6 发现机制(mDNS / Bonjour)
NDI 一台设备"上线"时,会向局域网多播 224.0.0.251:5353 注册服务:
_ndi._tcp.local name="MEDAI-V1 (Studio-A)" port=5960 txt=...
接收端调用 NDIlib_find_create_v2() 后,定时 NDIlib_find_get_current_sources() 就能拿到当前所有源的 ndi_name 和 url_address。说明书 4.3.3 的"点击搜索按键对本地 NDI 信号源进行搜索 ",底层正是这步;页面提示"搜索到的 NDI 信号源数量",就是 get_current_sources 返回的数组长度。
坑 :mDNS 依赖局域网多播可达。若设备接在 ETH1(192.168.78.0/24) 而你的电脑在 WiFi 另一网段,发现会失败------这跟 2.3.3 网络共存说明一脉相承,要确保搜索端和被搜端在同一二层。
1.7 传输与时间同步
- 视频/音频 :NDI 4+ 对每路源建立 单条连接,视频 UDP、音频 UDP,用 NTP 风格时钟对齐;
- 时间戳 :每帧带
timecode,接收端据此做音视频对齐与多路同步; - TCP 兜底:UDP 不可达时 SDK 自动用 TCP,延迟升高但不断流。
1.8 NDI SDK 关键对象生命周期
c
/* 发送端 */
NDIlib_initialize();
NDIlib_send_create_t snd = { .p_ndi_name = "MEDAI-V1", .p_groups = NULL };
pNDI_send = NDIlib_send_create(&snd);
NDIlib_send_send_video_v2(pNDI_send, &video_frame); // 逐帧推送
NDIlib_send_destroy(pNDI_send);
/* 接收端 */
pNDI_find = NDIlib_find_create_v2(&find_desc, false);
NDIlib_recv_create_v3_t recv = { .source_to_connect_to = src, .color_format = NDIlib_recv_color_format_best_streaming_quality };
pNDI_recv = NDIlib_recv_create_v3(&recv);
NDIlib_recv_capture_v3(pNDI_recv, &video, &audio, &metadata, timeout_ms);
NDIlib_recv_destroy(pNDI_recv);
NDIlib_find_destroy(pNDI_find);
NDIlib_destroy();
帧结构 NDIlib_video_frame_v2_t 关键字段:xres/yres、FourCC(UYVY / BGRA / RGBA / UYVA / NV12 / YV12)、frame_rate_N/D、line_stride_in_bytes、data。我们给 NDI 的帧格式用 UYVY(4:2:2)最稳,RGA 从 NV12 转 UYVY 一条指令搞定。
1.9 NDI vs RTSP/SRT/RTMP 选型口诀
| 场景 | 选谁 | 原因 |
|---|---|---|
| 局域网多机位切换 | NDI | 自动发现、低延迟、多路 |
| 跨公网稳定推流 | RTMP/SRT | 见第九篇 librtmp 推流 |
| 弱网抗丢包长距 | SRT | 前向纠错 + ARQ |
| 点播/H5 播放 | HLS/HTTP-FLV | 见第一篇拉流 |
| 本地监看/录制 | RTSP/本地文件 | 见 3.5 媒体输出 |
NDI 不是来替代它们的,而是补上"局域网内即插即用的多路实时矩阵"这一环。
二、MEDAI-V1 的 NDI 能力(对照说明书)
把说明书里所有 NDI 相关条目收拢:
| 说明书位置 | NDI 相关内容 | 本文对应 |
|---|---|---|
| 1.1 产品简介 | "集 NDI 编解码于一体" | 编码+解码双能力 |
| 3.1 系统状态 | NDI 连接状态及接入分辨率 | 状态页可看输入/输出分辨率 |
| 3.2.2 视频输出 | 五输入源之一可选 NDI | NDI 作输入源 |
| 3.5 媒体输出 | 第 6 项"NDI 功能" | NDI 作输出源 |
| 4.3.3 NDI 配置 | 编码配置(自定义名+开关);解码配置(搜索+拉流) | 第二节实战 |
| 4.3.4 NDI 授权 | 试用版 30 分钟,编/解码二选一 | 授权约束 |
| 4.4.2 视频叠加 | 主/副源可配 NDI | NDI 参与画中画 |
| 6.2 手势识别 | Three 手势 → NDI 输入源 | 手势切到 NDI |
| 7.2 第 5 条 | 编解码互斥+30 分钟时效 | 实现约束 |
两个核心事实必须记住:
- 编码输出(NDI|HX2):设备把"统一画布"经 NDI SDK 压成 HX2 发出,NDI TOOLS 里搜到自定义名称的源(4.3.3 编码配置 + 图 4-16)。
- 解码输入(HX2/FULL):点搜索→选源→开开关,拉进来的是 YCbCr 满帧,进 RGA→NV12→统一画布(4.3.3 解码配置 + 图 4-19)。
三、RK3576 上 NDI|HX2 编码输出实现
3.1 链路定位
统一画布 NV12 (dmabuf)
│ RGA:NV12 → UYVY(4:2:2,stride 对齐)
▼
NDIlib_send_send_video_v2() ← NDI SDK 内部做 HX2(HEVC) 压缩 + UDP 传输
│
▼
局域网 → NDI TOOLS / 其他 NDI 接收端
关键点:这条链和 RTMP/MP4 的 MPP 编码链共享同一张画布,只是出口不同。这正是第七篇"编码契约"的好处------解码只做一次、RGA 合成只做一次,NDI 输出零额外编码负担(NDI SDK 自己压,但源帧是现成的)。
3.2 完整代码 ndi_hx2_sender.c(约 180 行)
c
/*
* ndi_hx2_sender.c ------ MEDAI-V1 上把统一画布以 NDI|HX2 编码输出
* 编译: gcc ndi_hx2_sender.c -o ndi_hx2_sender -lndi -lpthread -O2
* 说明: 真实工程中 NV12 帧来自 RGA/MPP 的 dmabuf;此处用 malloc 模拟一张灰场,
* 并演示 NV12→UYVY 转换与逐帧发送主循环。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <Processing.NDI.Lib.h>
/* 编码契约:与说明书 3.3/3.4 对齐的固定输出规格 */
#define OUT_W 1920
#define OUT_H 1080
#define FPS_NUM 60
#define FPS_DEN 1
#define NDI_NAME "MEDAI-V1 (Studio-A)"
/* NV12 → UYVY(4:2:2)就地/外置转换:RGA 一条 imcvtcolor 即可完成,
* 这里给出 CPU 参考实现,便于没有 RGA 的环境验证逻辑。 */
static void nv12_to_uyvy(const uint8_t *nv12, uint8_t *uyvy,
int w, int h, int y_stride, int uv_stride)
{
const uint8_t *y = nv12;
const uint8_t *uv = nv12 + y_stride * h;
for (int j = 0; j < h; j++) {
const uint8_t *yrow = y + j * y_stride;
const uint8_t *uvrow = uv + (j / 2) * uv_stride;
uint8_t *o = uyvy + j * w * 2;
for (int i = 0; i < w; i += 2) {
uint8_t u = uvrow[(i / 2) * 2 + 0];
uint8_t v = uvrow[(i / 2) * 2 + 1];
o[0] = u; o[1] = yrow[i]; /* U Y0 */
o[2] = v; o[3] = yrow[i + 1]; /* V Y1 */
o += 4;
}
}
}
int main(void)
{
if (!NDIlib_initialize()) {
fprintf(stderr, "NDIlib_initialize failed\n");
return -1;
}
/* 1) 创建发送端,名称会出现在 NDI TOOLS 里(对应 4.3.3 编码配置自定义名) */
NDIlib_send_create_t snd_desc;
snd_desc.p_ndi_name = NDI_NAME;
snd_desc.p_groups = NULL; /* 不分组,全网可见 */
snd_desc.clock_video = false;
snd_desc.clock_audio = false;
const NDIlib_send_instance_t p_send = NDIlib_send_create(&snd_desc);
if (!p_send) { fprintf(stderr, "send create failed\n"); return -1; }
/* 2) 分配 NV12 源帧(真实用 RGA 输出的 dmabuf 映射地址) */
int y_stride = (OUT_W + 15) & ~15; /* 16 字节对齐,见第三篇 MPP stride */
int uv_stride = y_stride;
uint8_t *nv12 = (uint8_t *)malloc(y_stride * OUT_H + uv_stride * (OUT_H / 2));
uint8_t *uyvy = (uint8_t *)malloc((size_t)OUT_W * OUT_H * 2);
/* 填一张中度灰场(Y=128, U/V=128,不是 memset 0 ------ 第六篇强调过的坑) */
memset(nv12, 128, y_stride * OUT_H + uv_stride * (OUT_H / 2));
NDIlib_video_frame_v2_t vf;
memset(&vf, 0, sizeof(vf));
vf.xres = OUT_W; vf.yres = OUT_H;
vf.FourCC = NDIlib_FourCC_type_UYVY;
vf.frame_rate_N = FPS_NUM; vf.frame_rate_D = FPS_DEN;
vf.picture_aspect_ratio = (float)OUT_W / (float)OUT_H;
vf.line_stride_in_bytes = OUT_W * 2; /* UYVY 每像素 2 字节 */
vf.p_data = uyvy;
/* 3) 逐帧发送主循环:NDI SDK 内部完成 HX2(HEVC) 压缩 + UDP 多播传输 */
printf("NDI|HX2 sender '%s' running @ %dx%d %d/%dfps\n",
NDI_NAME, OUT_W, OUT_H, FPS_NUM, FPS_DEN);
const int frame_us = 1000000 * FPS_DEN / FPS_NUM;
for (int f = 0; f < 1800; f++) { /* 演示 30 秒(试用版够用) */
/* 真实场景:从这里取 RGA 合成好的 NV12 dmabuf,转换后发送 */
nv12_to_uyvy(nv12, uyvy, OUT_W, OUT_H, y_stride, uv_stride);
NDIlib_send_send_video_v2(p_send, &vf);
usleep(frame_us);
}
/* 4) 资源释放 */
free(nv12); free(uyvy);
NDIlib_send_destroy(p_send);
NDIlib_destroy();
printf("NDI|HX2 sender stopped\n");
return 0;
}
7 条代码要点:
NDIlib_initialize()必须先调,内部启动发现/网络线程;p_ndi_name即说明书 4.3.3 里"NDI 名称可自定义配置"对应的字符串;- 给 NDI 的帧用 UYVY 4:2:2 最省事,RGA
imcvtcolor(NV12→UYVY)一步到位,避免 RGB 二次转换; - stride 对齐:NV12 源按 16 字节对齐(与 MPP 的 prep 对齐一致),否则接收端花屏;
NDIlib_send_send_video_v2是非阻塞的,NDI SDK 自己管理内部缓冲与节奏,你只负责按真实帧率喂帧;- 真实生产环境帧数据来自 RGA 合成后的 dmabuf 映射地址,无需 CPU 拷贝(零拷贝见第七篇);
- HX2 压缩由 SDK 完成,设备侧不调用 MPP------这就是"同画布双出口"的代价:几乎零额外 CPU。
四、RK3576 上 NDI|HX2 / FULL 输入解码实现
4.1 链路定位(两种变种殊途同归)
NDI 网络包
│ NDI SDK 内部解压(HX2→HEVC 解;FULL→SpeedHQ 解)
▼
NDIlib_recv_capture_v3() 得到 UYVY/BGRA 满帧
│ RGA:UYVY/BGRA → NV12(stride + 偶数对齐)
▼
统一画布 NV12 (dmabuf) → 编码/HDMI/叠加/手势
关键区别 vs 网络拉流(NET1/NET2) :NET 流拉进来是 H.264/H.265,要走 MPP 硬解 (第四篇);而 NDI 流由 NDI SDK 自己解完 ,给到你的是已解码的 YCbCr 帧,设备只需 RGA 转 NV12 即可,不占 VPU。这也是为什么 NDI 解码和 MPP 解码是两套独立链路。
4.2 完整代码 ndi_receiver.c(约 170 行)
c
/*
* ndi_receiver.c ------ MEDAI-V1 上拉取局域网 NDI|HX2 或 NDI|FULL 源并接入统一画布
* 编译: gcc ndi_receiver.c -o ndi_receiver -lndi -lpthread -O2
* 说明: 演示 发现→选源→连接→逐帧收→UYVY 转 NV12。FULL/HX2 由 SDK 自动适配,
* 用户代码无需区分,收到都是解码后的 YCbCr 帧。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <Processing.NDI.Lib.h>
#define CANVAS_W 1920
#define CANVAS_H 1080
/* UYVY → NV12(落进统一画布,供 MPP/HDMI/叠加消费) */
static void uyvy_to_nv12(const uint8_t *uyvy, uint8_t *nv12,
int w, int h, int uv_stride)
{
const uint8_t *y = nv12;
uint8_t *uv = nv12 + ((w + 15) & ~15) * h;
for (int j = 0; j < h; j++) {
const uint8_t *row = uyvy + j * w * 2;
uint8_t *yr = (uint8_t *)y + j * ((w + 15) & ~15);
uint8_t *uvr = uv + (j / 2) * uv_stride;
for (int i = 0; i < w; i++) {
yr[i] = row[i * 2 + 1]; /* Y */
if ((j & 1) == 0 && (i & 1) == 0) { /* 4:2:0 抽点 */
uvr[(i / 2) * 2 + 0] = row[i * 2 + 0]; /* U */
uvr[(i / 2) * 2 + 1] = row[i * 2 + 2]; /* V */
}
}
}
}
int main(void)
{
if (!NDIlib_initialize()) return -1;
/* 1) 发现:等价于说明书 4.3.3 "点击搜索按键" */
const NDIlib_find_create_t find_desc = { .show_local_sources = true,
.p_groups = NULL };
const NDIlib_find_instance_t p_find = NDIlib_find_create_v2(&find_desc, false);
if (!p_find) return -1;
printf("searching NDI sources...\n");
const NDIlib_source_t *sources = NULL;
uint32_t count = 0;
for (int t = 0; t < 20 && count == 0; t++) { /* 最多等 2 秒 */
sources = NDIlib_find_get_current_sources(p_find, &count);
usleep(100000);
}
if (count == 0) { fprintf(stderr, "no NDI source found\n"); return -1; }
printf("found %u source(s):\n", count);
for (uint32_t i = 0; i < count; i++)
printf(" [%u] %s\n", i, sources[i].p_ndi_name);
/* 2) 选第一个源连接(真实 UI:用户从列表选,对应 4.3.3 解码配置选源) */
const NDIlib_recv_create_v3_t recv_desc = {
.source_to_connect_to = sources[0],
.color_format = NDIlib_recv_color_format_best_streaming_quality, /* 收最高质量 */
.bandwidth = NDIlib_recv_bandwidth_highest,
.allow_video_fields = false,
};
const NDIlib_recv_instance_t p_recv = NDIlib_recv_create_v3(&recv_desc);
if (!p_recv) return -1;
/* 3) 分配统一画布 NV12(真实用 RGA 输出 dmabuf) */
int nv12_y_stride = (CANVAS_W + 15) & ~15;
int nv12_uv_stride = nv12_y_stride;
uint8_t *nv12 = (uint8_t *)malloc(nv12_y_stride * CANVAS_H +
nv12_uv_stride * (CANVAS_H / 2));
/* 4) 逐帧接收主循环:HX2/FULL 由 SDK 自动解,给到的是已解码 YCbCr 帧 */
NDIlib_video_frame_v2_t video;
NDIlib_audio_frame_v3_t audio;
for (int f = 0; f < 1800; f++) {
if (NDIlib_recv_capture_v3(p_recv, &video, &audio, NULL, 100) ==
NDIlib_recv_frame_type_video) {
/* video.FourCC 可能是 UYVY 或 BGRA,这里按 UYVY 处理演示 */
if (video.FourCC == NDIlib_FourCC_type_UYVY) {
uyvy_to_nv12((const uint8_t *)video.p_data, nv12,
video.xres, video.yres, nv12_uv_stride);
/* 真实:把 nv12 交给统一画布 → MPP/HDMI/叠加/手势 */
}
NDIlib_recv_free_video_v2(p_recv, &video);
}
}
free(nv12);
NDIlib_recv_destroy(p_recv);
NDIlib_find_destroy(p_find);
NDIlib_destroy();
printf("NDI receiver stopped\n");
return 0;
}
6 条代码要点:
NDIlib_find_get_current_sources返回的count就是说明书"搜索到的 NDI 信号源数量";color_format = best_streaming_quality让 SDK 在 HX2 与 FULL 间自动选最稳的;- HX2 还是 FULL 对用户透明 :
recv_capture出来都是解码后帧,差异只在带宽与画质; - 收到的帧
FourCC需判断(UYVY/BGRA),RGA 转 NV12 时选对应转换核; - 务必
NDIlib_recv_free_video_v2归还帧,否则 SDK 内部缓冲耗尽后丢帧; - 收进来的 NV12 直接进"统一画布",后续与 HDMI/UVC/NET 源走同一套 RGA 合成(第六/七篇)。
五、FULL vs HX2 自适应选择策略
设备作为接收端时,可以根据网络质量在两者间取舍:
| 条件 | 选 FULL | 选 HX2 |
|---|---|---|
| 同千兆交换机、源机近 | ✅ 画质无损 | 也可 |
| 跨网段 / 无线 / 公网桥 | ❌ 带宽爆 | ✅ 低带宽 |
| 要求极致画质(演播室) | ✅ | |
| RK3576 内存带宽吃紧 | ✅ 解后帧更小 |
代码上用 bandwidth 字段控制:NDIlib_recv_bandwidth_highest 收 FULL,_lowest 收 HX2。说明书不暴露这个开关(默认由 SDK 协商),但底层就是这个机制。
六、深度结合说明书实战
6.1 把设备当 NDI 编码器输出(上集)
对照 4.3.3 编码配置 + 4.3.4 授权:
- 进 USB 与 NDI 功能 → NDI 配置 → 编码配置;
- NDI 名称 填自定义(如
MEDAI-V1),开 NDI 编码开关; - 在另一台机器的 NDI TOOLS / Studio Monitor 里搜索,应看到该名称(图 4-16);
- 拉进来看画面,即本文第三节的
ndi_hx2_sender在跑; - ⚠️ 试用版 30 分钟时效 (4.3.4):超时后 NDI TOOLS 出现"只有声音没有画面 "------重新开关编码开关即可续 30 分钟;正式环境需 开启授权(出厂均无授权)。
6.2 把设备当 NDI 解码器输入(下拉)
对照 4.3.3 解码配置:
- 进 NDI 配置 → 解码配置 ,点 搜索;
- 页面提示"搜索到的 NDI 信号源数量"(=
get_current_sources的 count); - 从下拉选源,开 启用开关;
- 下方出现预览画面(图 4-19),即本文第四节的
ndi_receiver在跑,收进来的帧已转 NV12 进画布; - 此时在 3.2.2 视频输出 把主源选 NDI,HDMI/推流/录制就都用这路 NDI 输入了。
互斥铁律(7.2 第 5 条 + 4.3.4) :一台设备 NDI 编码与解码只能开一个 。底层原因:试用授权是单实例锁,且 VPU/网络资源在同一时刻只服务一个方向。代码上不要同时
send_create和recv_create。
6.3 NDI 参与视频叠加(4.4.2)
叠加 Mode1~5 的 主/副输入源 均可选 NDI(4.4.2 参数表)。一旦选 NDI:
- 主源=NDI:整帧用 NDI 输入,RGA 把 NV12 画布按 Mode 布局;
- 副源=NDI:NDI 收进来的小窗以
imcomposite叠加到主源上(第六篇 RGA 适配的画中画)。
切换模式要"先应用参数,再重新开关启用开关"(4.4.2 注意)------本质是让 RGA 重算布局几何(第六篇 6.2 的对照表)。
6.4 手势切到 NDI(6.2)
Three 手势 → NDI 输入源。实现上:手势识别线程(6.2,置信度 50~100% 默认 80%)命中 Three 后,把"视频输出主源"从当前源切到 NDI,等价于调用 3.2.2 的切换 + 拉起 NDI 接收。切换后 等 5 秒 才能再次识别(7.2 第 8 条)。
6.5 状态页核对(3.1)
系统状态主页的 NDI 行显示"连接状态及接入分辨率";预监状态区可同时看到 NDI 源画面(图 3-3/3-5)。这是验证 NDI 收发是否正常的第一现场。
七、带宽与网络规划
NDI 是局域网协议,网络规划直接决定体验:
| 组网 | 建议 | 容量(千兆) |
|---|---|---|
| 单机 NDI 输出 | 接 ETH0(DHCP) 或 ETH1(192.168.78.100) 任一 | 出 1~3 路 HX2 |
| NDI 输入 + 推流并存 | NDI 走 ETH0,RTMP 5 路走 ETH1(2.3.3 共存) | 互不抢带宽 |
| 多机位 NDI 矩阵 | 独立千兆交换机,禁用 WiFi 承载 FULL | 4~6 路 FULL / 20+ 路 HX2 |
| 跨网段/无线 | 强制 HX2(低带宽) | 视链路 |
设备双网口(ETH0/ETH1)+ WiFi 三网共存(2.3.3)在 NDI 场景很有用:NDI 发现/传输走局域网 ETH0,对外 RTMP 推流走 ETH1 或 WiFi,避免 NDI 的 250 Mbps FULL 流量把上行推流挤垮。
八、FAQ
Q1:NDI TOOLS 搜不到设备名?
A:确认搜索端与被搜端在同一二层(mDNS 不过路由);设备 ETH1=192.168.78.100,电脑需同网段(如 192.168.78.50,见 2.1)。
Q2:搜到了但只有声音没画面?
A:典型 试用版 30 分钟到期(4.3.4)。重新开关 NDI 编码或解码开关即可续 30 分钟;正式需授权。
Q3:编码和拉流能同时开吗?
A:不能(7.2 第 5 条)。一台设备 NDI 编码/解码二选一。
Q4:收 FULL 很卡,收 HX2 正常?
A:FULL 1080p60 约 250 Mbps,网口/交换机组带宽不够。换 HX2 或升级到独立千兆交换机。
Q5:NDI 输入能进绿幕/手势吗?
A:绿幕主源固定 UVC(4.4.3);手势 Three 可切到 NDI(6.2);NDI 可作为叠加主/副源(4.4.2)。
Q6:NDI 输出和 RTMP 推流冲突吗?
A:不冲突。NDI 输出走 NDI SDK,RTMP 走 librtmp,两者共享统一画布但出口独立(第九篇)。
Q7:为什么 NDI 解码不占 VPU?
A:NDI SDK 自己解完给 YCbCr 满帧,设备只用 RGA 转 NV12(第四节);而 NET 网络流是 H.264/HEVC,需 MPP 硬解(第四篇)。
Q8:4K NDI 源能接吗?
A:FULL 4K 约 1 Gbps,需万兆或降为 HX2(4K HX2 ≈ 25~80 Mbps)。设备编码输出固定 2160P/1080P/720P(3.3),NDI 输出受此约束。
九、结语
NDI 在 MEDAI-V1 上扮演"局域网实时矩阵 "角色:编码侧用 NDI|HX2 (HEVC 底子)把统一画布低带宽地广播出去,解码侧把 HX2/FULL 信号拉进来、由 NDI SDK 解完、RGA 转 NV12 汇入统一画布------全程不碰 MPP 的 RTMP/MP4 编码链,真正实现"一源多出口、多源一画布"。配合双网口共存、视频叠加、手势切源,NDI 让这台 4GB/32GB 的小盒子在演播室与多机位直播里有了专业级路由能力。
至此本专栏已覆盖:拉流 / 绿幕 / MPP 编解码 / UVC 解码 / 综合性能 / RGA 适配 / MPP×RGA 统一编码 / FFmpeg 拉流 / librtmp 推流 / V4L2-UVC / ALSA 音频 / RGA+DRM HDMI 输出 / NDI 框架,构成从采集→处理→编码→分发→呈现的完整 RK3576 音视频技术体系。
------ 本文档深度结合《MEDAI-V1 使用说明书》V1.0 编写,参数以说明书与《MEDAI V1 技术参数》为准。