> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 移植完整的多媒体应用，而不仅是模型（第 6 部分，共 7 部分）

> 把完整的 Jetson 摄像头应用——不仅是它的模型——迁移到 Qualcomm Dragonwing 多媒体和 GStreamer 流水线。

<hr style={{ border: "none", borderTop: "1px solid #eee", margin: "0 0 2rem" }} />

<div style={{ display: "flex", justifyContent: "space-between", gap: "1rem", marginBottom: "2rem", flexWrap: "wrap" }}>
  <a href="/zh/tutorials/porting-llm-vlm-audio-and-vla-workloads-to-qualcomm" style={{ color: "#31017D", fontWeight: 600, textDecoration: "none" }}>← 上一篇：第 5 部分</a>
  <a href="/zh/tutorials/from-ubuntu-evk-demo-to-customer-maintained-yocto-with-qli" style={{ color: "#31017D", fontWeight: 600, textDecoration: "none" }}>下一篇：第 7 部分 →</a>
</div>

假设你的模型工件现在在 Qualcomm Dragonwing 上运行。这是必要的，但它很少就是整个产品。

大多数 Jetson 边缘 AI 产品是围绕模型包装的应用：

```text theme={null}
摄像头捕获
预处理
推理
后处理
跟踪
叠加
显示
编码
流传输
控制消息
健康监控
```

在 Jetson 上，这个应用可能是 DeepStream、CUDA 内核、TensorRT、OpenCV，以及一个锁定到 JetPack 的 Docker 镜像。在 Qualcomm 上，等价的产品路径通常围绕 Qualcomm Intelligent Multimedia SDK、GStreamer、QNN/QAIRT、LiteRT/QNN 委托、摄像头 ISP、Adreno GPU、视频编解码和 HTP/NPU 构建。

关键的迁移思想：

> 移植流水线，而不仅仅是神经网络。

在贯穿的案例研究中，这里就是 YOLO 上下文二进制文件不再是独立模型工件、而成为实时摄像头、元数据、叠加、编码和健康监控应用一部分的地方。

在开始之前：

```text theme={null}
[ ] 模型工件在目标运行时上运行
[ ] 用于文件输入验证的样本视频
[ ] 已识别 BSP 上的目标摄像头/传感器路径
[ ] 已安装并检查 GStreamer/IM SDK 插件
[ ] 已知预期的摄像头格式、分辨率、FPS 和时间戳
[ ] 已定义端到端延迟/FPS/功耗关口
```

***

## 为什么仅模型移植不够

一个模型可以通过每个张量级验证关口，仍然在产品内部失败。

常见原因：

```text theme={null}
摄像头格式变了
缩放行为变了
RGB/BGR 交换
NCHW/NHWC 不匹配
归一化变了
letterbox 填充变了
NMS 移动或改变了
跟踪 ID 行为不同
叠加绘制陈旧元数据
编码器增加了太多延迟
GPU 忙于显示工作
在 30 分钟后功耗或热特性漂移
```

这就是为什么我们的最佳实践是在优化单个部件之前冻结应用契约。

对于视觉应用，写下：

```text theme={null}
输入源：摄像头 / 文件 / RTSP
输入分辨率和帧率
像素格式
预处理数学
模型输入张量形状和布局
模型输出张量名称和形状
后处理步骤
跟踪行为
叠加字段
输出流/显示要求
延迟/FPS/功耗目标
```

然后一次移植一个阶段。

***

## DeepStream 到 IM SDK 的映射

一份实用映射看起来是这样的：

| Jetson / DeepStream   | Qualcomm / IM SDK 方向                |
| --------------------- | ----------------------------------- |
| `nvv4l2camerasrc`     | `qticamsrc`                         |
| `nvinfer`             | `qtimlqnn` 或带 QNN 委托的 `qtimltflite` |
| `nvtracker`           | `qtiobjtracker`                     |
| `nvosd`               | `qtivoverlay`                       |
| `nveglglessink`       | `waylandsink`                       |
| CUDA resize/normalize | `qtimlvconverter` 或 CPU OpenCV      |
| DeepStream 元数据        | IM SDK ML 元数据 / `qtimetamux`        |
| NVENC 编码路径            | V4L2 硬件编码路径                         |
| DeepStream 配置文件       | IM SDK/GStreamer 流水线加模型 JSON/设置     |

这不是每个属性的一对一替换。这是一份起始地图，让团队能够识别哪些代码消失、哪些配置移动、哪些行为需要新的验证检查。当前 IM SDK 示例使用 `qticamsrc`；旧示例可能显示 `qtiqmmfsrc`。在复制流水线之前，请在你的镜像上运行 `gst-inspect-1.0`。

***

## 先从文件开始，然后再到摄像头

最便宜的路径是首先去除实时摄像头的可变性。

在 Jetson 和 Qualcomm 上使用同一个输入视频：

```text theme={null}
sample.mp4
  -> 解码
  -> 预处理
  -> 推理
  -> 后处理
  -> 叠加或元数据转储
```

一旦文件路径匹配，就转到真实摄像头：

```text theme={null}
摄像头
  -> 相同预处理
  -> 相同推理
  -> 相同后处理
  -> 相同叠加 / 流
```

这让失败更容易分类。如果文件路径匹配而摄像头路径不匹配，bug 可能是捕获格式、时间戳、帧率、曝光、缓冲内存或颜色转换。如果两者都失败，请更早地查看模型导出、预处理或后处理。

在指责模型之前，检查摄像头路径：

```bash theme={null}
gst-inspect-1.0 | grep -E 'qti|v4l2|wayland'
```

```text theme={null}
[ ] 传感器被目标 BSP/镜像支持
[ ] ISP/摄像头服务已为该模块配置
[ ] 请求的格式/分辨率/FPS 实际上来自摄像头
[ ] 时间戳能在捕获、推理、叠加和编码中存活
[ ] 在 SDK 支持处使用硬件/DMABUF 路径
[ ] 在添加多摄像头同步之前一个摄像头是稳定的
```

***

## 一条最小 Qualcomm 摄像头 AI 流水线

一条用于实时摄像头检测器的简化 IM SDK 风格流水线如下。它遵循当前 IM SDK 目标检测示例，但确切属性是模型和版本特定的；请在你的镜像上用 `gst-inspect-1.0 qticamsrc qtiqmmfsrc qtimlqnn qtimlpostprocess` 验证它们。在本系列使用的 IQ-9075 实验室镜像上，`qtiqmmfsrc` 存在而 `qticamsrc` 不存在，因此请把源元素视为镜像相关的：

```bash theme={null}
gst-launch-1.0 -e --gst-debug=2 \
  qticamsrc ! \
  video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1 ! \
  queue ! tee name=t \
  t. ! queue ! qtimetamux name=obj_mux ! qtivoverlay ! waylandsink fullscreen=true sync=false \
  t. ! queue ! qtimlvconverter ! queue ! \
  qtimlqnn model=$HOME/models/best_ctx.bin backend=/usr/lib/libQnnHtp.so tensors="<boxes,scores,class_idx>" ! queue ! \
  qtimlpostprocess module=yolov8 labels=$HOME/labels/yolov8.json settings="{\"confidence\": 51.0}" ! \
  text/x-raw ! queue ! obj_mux.
```

对于 LiteRT 模型，推理阶段可以改用 QNN 委托路径：

```bash theme={null}
qtimltflite \
  model=$HOME/models/model_w8a8.tflite \
  delegate=external \
  external-delegate-path=libQnnTFLiteDelegate.so \
  external-delegate-options="QNNExternalDelegate,backend_type=htp;"
```

确切的流水线取决于你的模型、传感器、显示和输出要求。模式才是重点：

```text theme={null}
source -> preprocess -> inference -> postprocess -> metadata -> overlay/stream
```

***

## 保持预处理无聊

大多数迁移 bug 隐藏在预处理中。

对于每个模型，在模型侧配置文件中记录这些字段：

```json theme={null}
{
  "input_width": 640,
  "input_height": 640,
  "input_layout": "NCHW",
  "input_color": "RGB",
  "scale": 0.00392156862745098,
  "mean": [0.0, 0.0, 0.0],
  "std": [1.0, 1.0, 1.0],
  "letterbox": true,
  "pad_value": 114,
  "nms_in_graph": false
}
```

然后让 Jetson 和 Qualcomm 读取相同的契约。

如果旧应用有 CUDA 预处理，在替换之前隔离其行为：

```text theme={null}
从 Jetson 保存预处理后的张量
从 Qualcomm 保存预处理后的张量
比较形状、dtype、min/max、mean/std 和几个像素
```

一行颜色交换代码可能比运行时移植花费更多准确率。

***

## 把后处理视为应用代码

对于 YOLO 类模型，把 NMS 保持在图外通常是最简单的迁移路径。

后处理通常包括：

```text theme={null}
输出张量解码
置信度阈值
类别过滤
框转换
NMS
把框缩放回源图像
元数据格式化
```

保持这段代码显式并在可能时共享。如果 Jetson 有 Python 后处理而 Qualcomm 有 C++/GStreamer 后处理，从保存的张量构建一个小的黄金输出测试。

最小有用测试：

```text theme={null}
相同的原始输出张量
相同的标签
相同的阈值
相同的 NMS 设置
相同的预期框
```

这能在指责加速器之前捕捉到布局和阈值漂移。

***

## 元数据取代胶水代码

DeepStream 应用常常依赖附加到缓冲区的元数据。IM SDK 有相同的宽泛想法：推理产生张量，后处理把张量转换为元数据或掩码，下游元素使用该元数据进行跟踪、叠加、流传输或下一阶段推理。

一种常见模式：

```text theme={null}
视频分支
  -> 显示/编码路径

推理分支
  -> qtimlvconverter
  -> qtimlqnn 或 qtimltflite
  -> qtimlpostprocess
  -> 元数据

元数据 + 视频
  -> qtimetamux
  -> qtiobjtracker
  -> qtivoverlay
  -> 显示或流
```

这让视频缓冲和推理元数据保持对齐，而不会把应用变成一堆自定义胶水。

***

## 多流是 Qualcomm 可以大放异彩的地方

Jetson 应用常常依赖一个 GPU 完成推理、CUDA 预处理、显示和编码。Qualcomm Dragonwing 设备有更异构的流水线：摄像头 ISP、HTP/NPU、Adreno GPU、视频编解码、CPU 和 DSP 资源可以各承担不同的部分。

这不会让性能自动发生。它给你更多的放置选择。

对于多流产品，测量：

```text theme={null}
每流 FPS
端到端延迟
帧丢失
队列深度
编码器延迟
HTP 利用率
GPU/显示争用
CPU 负载
内存带宽压力
功耗和热行为
```

最佳流水线通常是避免不必要拷贝的那一个，而不是拥有最快单模型调用的那一个。

使用症状找出瓶颈：

| 症状              | 可能的瓶颈                |
| --------------- | -------------------- |
| 模型剖析很快，应用 FPS 低 | 摄像头、队列、后处理、显示或编码     |
| HTP 利用率低        | 流水线在饥饿推理             |
| CPU 负载高         | 拷贝、颜色转换、回退算子或应用侧 NMS |
| GPU 高           | 显示、叠加或缩放争用           |
| FPS 随时间下降       | 热节流、功耗模式或内存增长        |
| 多摄像头漂移          | 时间戳/同步策略，而不是检测器      |

***

## 部署包

只包含模型的包对真实多媒体应用来说太小了。把契约打包在模型周围：

```text theme={null}
models/
  detector.bin 或 detector.tflite
  classifier.bin 或 classifier.tflite
labels/
  detector_labels.json
  classifier_labels.json
config/
  preprocess.json
  postprocess.json
  tracker.json
  pipeline.env
app/
  start.sh
  healthcheck.sh
manifest.json
```

清单应记录：

```text theme={null}
模型版本
源检查点或 ONNX 哈希
AI Hub 作业或构建 ID
QAIRT/QNN SDK 版本
BSP/OS 镜像版本
目标 SoC
精度
预期的输入/输出张量名称
已知验证数据集
```

这把"在我的 EVK 上工作"变成另一位工程师可以复现的东西。

***

## 验证清单

在称应用已迁移之前，让这些检查变得无聊：

```text theme={null}
[ ] 文件输入流水线用已知测试视频运行
[ ] 摄像头输入流水线以目标分辨率/FPS 运行
[ ] 预处理后的张量与 Jetson/参考行为匹配
[ ] 模型输出在容差内与 FP32 ONNX 基线匹配
[ ] 后处理产生预期的框/类别/掩码
[ ] 跟踪 ID 对产品足够稳定
[ ] 叠加匹配源坐标
[ ] 编码/流路径达到延迟目标
[ ] 在持续负载下测量端到端 FPS
[ ] 为真实运行长度测量功耗和热特性
[ ] 应用干净地启动、停止和恢复
```

第一个演示可以是一条 `gst-launch-1.0` 命令。生产版本应仍保持相同的流水线形态，只是有更好的配置、日志、服务管理和更新行为。

***

## 结论

一次成功的 Jetson 到 Qualcomm 迁移不是"TensorRT 模型变成 QNN 模型"。它是：

```text theme={null}
DeepStream/CUDA 产品流水线
  -> IM SDK/GStreamer/QNN 产品流水线
```

保持模型契约显式，分别验证预处理和后处理，从文件输入开始，然后转到摄像头。你带过来的自定义胶水越少，产品在全天运行时越容易调试。
