在视频分析系统里,"录制"看起来很简单:把摄像头流保存成文件就行。但在真实项目中,持续录制往往意味着巨大的存储成本、检索成本和网络压力。
很多场景真正需要的不是全天录像,而是"事件发生前后的一小段视频":比如车辆闯入、人员聚集、异常行为、告警触发前后的 10 秒。
NVIDIA DeepStream 的 Smart Video Record,也就是 Smart Recording,就是为这种场景设计的。根据官方文档,它支持基于本地事件或云端事件录制关键片段,而不是持续保存完整视频流。录制过程与推理 pipeline 并行运行,并且内部维护一段音视频缓存,因此最终文件可以包含事件发生前和发生后的画面。参考:NVIDIA Smart Video Record 文档
Smart Recording 解决什么问题
传统录像方式通常有两种。
一种是全天候录制。优点是简单,缺点是成本高,后期检索困难。
另一种是事件发生后才开始录制。优点是节省存储,缺点是经常错过事件发生前的关键上下文。
Smart Recording 采用的是第三种方式:应用一直缓存最近一段已经编码好的音视频数据,但只有在事件触发时才把缓存片段封装成文件。
假设事件在 t1 时刻发生,应用调用录制 API 时传入:
startTime = 5
duration = 10
那么录制结果大致覆盖:
t1 - 5s 到 t1 + 10s
也就是事件前 5 秒和事件后 10 秒。
这对于告警系统非常重要。因为很多事件的判断依据并不只在告警瞬间,而是在告警前几秒已经开始出现。
它为什么缓存 encoded frame
Smart Recording 缓存的是编码后的帧,而不是解码后的 raw frame。
这点很关键。RTSP 摄像头通常输出 H.264/H.265 encoded stream,如果缓存 raw frame,内存压力会非常大;而缓存 encoded bitstream 则轻得多,也更适合直接封装成 MP4 或 MKV。
但这也带来一个限制:录制文件必须从 I-frame 开始。如果缓存最前面的帧不是 I-frame,Smart Recording 会丢弃前面一部分帧,直到遇到关键帧。因此实际生成的视频时长可能比配置的 startTime + duration 略短。
这不是 bug,而是视频编码格式本身决定的。
Smart Recording 的核心 API
底层 API 定义在 gst-nvdssr.h 中,核心函数只有四个:
NvDsSRCreate(...)
NvDsSRStart(...)
NvDsSRStop(...)
NvDsSRDestroy(...)
NvDsSRCreate() 用来创建 Smart Record 上下文。它会生成一个 recordbin,应用必须把这个 bin 加入 GStreamer pipeline。这个 recordbin 接收 encoded frame,并负责缓存、封装和写文件。
NvDsSRStart() 用来启动录制。调用时传入 startTime 和 duration,它会返回一个 session id。当前实现中,同一路 source 不支持重叠录制。
NvDsSRStop() 用来停止录制。如果应用不主动 stop,Smart Recording 也可以根据 defaultDuration 自动停止。
NvDsSRDestroy() 用来释放资源,通常在 pipeline 退出时调用。
录制完成后,callback 会收到 NvDsSRRecordingInfo,里面包含文件名、目录、时长、分辨率、容器类型,以及是否包含音频和视频等信息。
deepstream-testsr:最直接的学习入口
deepstream-testsr 是理解 Smart Recording 最好的 sample。
它支持单路 H.264 RTSP 输入,演示如何通过 NvDsSRStart() 和 NvDsSRStop() 自定义触发录制。示例里有几个默认参数:
#define SMART_REC_CONTAINER 0
#define CACHE_SIZE_SEC 15
#define SMART_REC_DEFAULT_DURATION 10
#define START_TIME 2
#define SMART_REC_DURATION 7
#define SMART_REC_INTERVAL 7
含义大致是:
缓存 15 秒
触发录制时保存事件前 2 秒
事件后继续录制 7 秒
每 7 秒触发一次 start/stop 演示事件
容器默认 MP4
deepstream-testsr 还有一个很实用的能力:可以选择录制带 bbox 的视频,或者录制原始输入流。
如果 bbox-enable=0,recordbin 接在解码前的 encoded stream 上,录制的是更接近原始 RTSP 输入的内容。
如果 bbox-enable=1,sample 会在 OSD 后面增加 encoder 和 parser,再接入 recordbin。这样录制出来的文件就包含检测框。但 README 也提醒:带 bbox 的录制目前只支持视频。
它还支持 sr-mode:
0: Audio + Video
1: Video only
2: Audio only
音频接入方式是把 encoded audio bitstream 链到 recordbin 的 asink pad;视频则链到 sink pad。
deepstream-app:生产场景里的 Smart Recording
相比 deepstream-testsr,deepstream-app 更接近真实业务。它支持多路 source、推理、跟踪、消息消费和 Smart Recording。
Smart Recording 主要通过 [sourceX] 配置启用:
source0
enable=1
type=4
uri=rtsp://...
smart-record=2
smart-rec-cache=30
smart-rec-start-time=5
smart-rec-duration=10
smart-rec-default-duration=10
smart-rec-container=0
smart-rec-interval=10
smart-rec-file-prefix=Camera_0
smart-rec-dir-path=./records
几个关键配置项如下:
smart-record=1 表示只响应云端消息触发录制。
smart-record=2 表示既响应云端消息,也启用本地事件触发。官方文档里提到,默认本地事件会每 10 秒触发一次 start/stop,主要用于演示。
smart-rec-cache 是缓存时长。它必须大于 smart-rec-start-time,否则你想录事件前 5 秒,但缓存里没有足够历史数据。
smart-rec-container=0 表示 MP4,1 表示 MKV。
smart-rec-dir-path 是保存目录,目录必须存在且有写权限。
在 deepstream_source_bin.c 中,RTSP source 创建时会根据配置创建 NvDsSRContext,把 recordbin 加进 source bin,并从 RTSP pre-decode tee 分一路 encoded stream 给 Smart Recording。这样录制过程不会干扰主推理 pipeline。
云端触发:通过 Kafka 控制录制
Smart Recording 也可以由云端消息触发。官方文档和 deepstream-app 都展示了这条路径。
应用从 Kafka 等消息系统接收 JSON,解析成 start/stop 命令,然后找到对应 sensor/source 的 NvDsSRContext,调用 NvDsSRStart() 或 NvDsSRStop()。
消息大致长这样:
{
"command": "start-recording",
"start": "2020-05-18T20:02:00.051Z",
"end": "2020-05-18T20:02:02.851Z",
"sensor": {
"id": "0"
}
}
这非常适合边缘 AI + 云端控制架构:边缘设备持续分析视频,云端告警系统或业务系统决定什么时候保存关键片段。
gst-nvdssr 关键源码逻辑分析
Smart Recording 的核心实现位于:
sources/libs/gst-nvdssr/gst-nvdssr.c
从源码看,它的设计可以概括为:
常驻缓存 + 事件放行 + 关键帧门控 + EOS 收尾
1. recordbin 的内部结构
NvDsSRCreate() 会创建一个 recordbin。这个 bin 内部有视频缓存路径和音频缓存路径。
视频入口 ghost pad 叫 sink,音频入口 ghost pad 叫 asink。
内部结构大致是:
encoded video -> cache queue -> tee -> encodebin sink
encoded audio -> cache queue -> tee -> encodebin asink
encodebin -> muxer -> filesink
这里的 encodebin 名字有点容易误解。它并不是负责重新编码,而是包含 queue、muxer 和 filesink,用来把 encoded bitstream 封装成文件。
源码里根据容器类型选择 muxer:
case NVDSSR_CONTAINER_MP4:
muxer = gst_element_factory_make ("qtmux", elem_name);
break;
case NVDSSR_CONTAINER_MKV:
muxer = gst_element_factory_make ("matroskamux", elem_name);
break;
因此 Smart Recording 支持 MP4 和 MKV。
2. cache queue 只按时间缓存
create_cache_queue() 是缓存机制的关键。
它关闭了按 buffer 数量和字节大小限制 queue 的行为:
g_object_set (G_OBJECT (queue), "max-size-buffers", 0, NULL);
g_object_set (G_OBJECT (queue), "max-size-bytes", 0, NULL);
然后用时间控制缓存窗口:
g_object_set (G_OBJECT (queue), "min-threshold-time",
ctx->initParams.cacheSize * 1000 * 1000 * 1000ULL, NULL);
g_object_set (G_OBJECT (queue), "max-size-time",
1000000000ULL * (ctx->initParams.cacheSize + 5), NULL);
并设置 leaky:
g_object_set (G_OBJECT (queue), "leaky", 2, "silent", TRUE, NULL);
这意味着 queue 会始终保留最近一段时间的数据,超过上限就丢弃旧数据。Smart Recording 要的正是这种"滑动时间窗口"。
3. Start 的本质:调整 queue 阈值并打开放行状态
NvDsSRStart() 并不会临时搭建一条录制 pipeline。它做的是修改已有 recordbin 的状态。
首先设置新文件名:
SetNewFileName (ctx);
文件名由目录、前缀、递增 counter、UTC 时间戳和 pid 组成,类似:
Camera_0_00001_20260521-143000_12345.mp4
然后设置状态:
ctx->recordOn = TRUE;
ctx->resetDone = FALSE;
ctx->uData = userData;
接着把 queue 的 max-size-time 调整为 startTime:
g_object_set (G_OBJECT (ctx->recordQue), "max-size-time",
startTime * 1000 * 1000 * 1000ULL, NULL);
g_object_set (G_OBJECT (ctx->recordQue), "min-threshold-time", 0, NULL);
可以把它理解为:事件来了,现在从缓存中释放事件前 startTime 秒的数据,并继续把后续数据写入文件。
duration 则通过定时器控制:
privData->timeoutSrcId = g_timeout_add (timeout, DefaultStopCallback, ctx);
如果应用没有主动调用 NvDsSRStop(),定时器到期后会自动 stop。
4. 关键帧门控:录制为什么必须等 I-frame
视频 queue 的 src pad 上挂了 queue_src_pad_probe()。这段逻辑决定 buffer 是否可以进入文件写入路径:
if (ctx->recordOn && !ctx->gotKeyFrame &&
(!GST_BUFFER_FLAG_IS_SET (GST_BUFFER_CAST(info->data), GST_BUFFER_FLAG_DELTA_UNIT))) {
ctx->gotKeyFrame = TRUE;
}
GST_BUFFER_FLAG_DELTA_UNIT 通常表示非关键帧。也就是说,源码会等待第一个非 delta frame,也就是 I-frame。
只有拿到关键帧后,数据才允许通过:
if (ctx->recordOn && ctx->gotKeyFrame) {
return GST_PAD_PROBE_OK;
}
return GST_PAD_PROBE_DROP;
这解释了一个常见现象:即使配置了事件前 N 秒,实际输出文件也可能短一些。因为文件必须从关键帧开始,关键帧之前的 delta frame 不能单独构成可解码视频。
5. 音频路径更简单
音频的 probe 是 audio_queue_src_pad_probe()。它没有关键帧判断,只根据 recordOn 决定是否放行:
return ctx->recordOn ? GST_PAD_PROBE_OK : GST_PAD_PROBE_DROP;
这也符合音频编码流的特点:它不需要像视频那样等待 I-frame。
6. Stop 的本质:恢复缓存窗口并发送 EOS
NvDsSRStop() 会把 queue 的缓存阈值恢复成正常 cache size:
g_object_set (G_OBJECT (ctx->recordQue), "min-threshold-time",
ctx->initParams.cacheSize * 1000 * 1000 * 1000ULL, NULL);
g_object_set (G_OBJECT (ctx->recordQue), "max-size-time",
1000000000ULL * (ctx->initParams.cacheSize + 5), NULL);
然后关闭录制状态:
ctx->gotKeyFrame = FALSE;
ctx->recordOn = FALSE;
最后向 encodebin 发送 EOS:
if (privData->haveVideo)
gst_pad_send_event (gst_element_get_static_pad (ctx->encodebin, "sink"),
gst_event_new_eos ());
if (privData->haveAudio)
gst_pad_send_event (gst_element_get_static_pad (ctx->encodebin, "asink"),
gst_event_new_eos ());
EOS 非常关键。MP4/MKV muxer 需要 EOS 才能正确写完容器尾部信息,否则文件可能无法正常播放。
随后 bus handler 捕获 encodebin 转发出来的 EOS,触发 callback,把录制文件信息返回给应用层。
使用 Smart Recording 时要注意什么
第一,Smart Recording 需要 encoded stream。recordbin 应该接在 parser 后面,典型是 H.264/H.265 parser 后。
第二,smart-rec-cache 要大于 smart-rec-start-time。否则你要求保存事件前 N 秒,但缓存里没有足够历史数据。
第三,录制依赖 I-frame。RTSP 源 GOP 太长、关键帧间隔太大、网络质量差,都可能导致实际录制时长不足。
第四,当前官方文档明确说明不支持 overlapping smart record。同一路 source 不要同时启动多段重叠录制。
第五,保存目录必须存在且可写。deepstream-app 的配置解析代码会检查 smart-rec-dir-path 的写权限。
第六,如果要录制带 bbox 的视频,需要在 OSD 后重新编码再送入 recordbin;如果只录原始流,可以从解码前的 encoded stream 分支进入 recordbin。
总结
DeepStream Smart Recording 的价值在于,它把"持续分析"和"按事件留证"分离开来。
主 pipeline 继续做解码、推理、跟踪和显示;Smart Recording 在旁路缓存 encoded stream。事件发生时,它动态调整缓存窗口,等待关键帧,把事件前后的片段封装成 MP4 或 MKV 文件。
如果只是学习 API,建议先看 deepstream-testsr。它足够小,能清楚展示 NvDsSRCreate()、NvDsSRStart()、NvDsSRStop() 和 NvDsSRDestroy() 的用法。
如果要接入多路 RTSP、Kafka 云端控制和配置化管理,则应该看 deepstream-app。它展示了 Smart Recording 在真实 DeepStream 应用中的集成方式。
在安防、交通、工业巡检和边缘告警场景中,Smart Recording 是一个非常实用的能力:平时不浪费存储,关键时刻保留事件前后的完整证据。