
出图进入板子gst-launch-1.0 v4l2src device/dev/video0 num-buffers1 ! videoconvert ! jpegencquality90 ! filesink locationtest.jpg使用该命令截图gst-launch-1.0作用GStreamer 的命令行工具用于构建和运行多媒体处理管道说明这是 GStreamer 框架的入口程序版本1.0v4l2src device/dev/video0 num-buffers1参数含义说明v4l2srcVideo4Linux2 Source从V4L2设备读取视频数据的插件device/dev/video0设备节点路径指定使用哪个摄像头/dev/video0是第一个视频设备num-buffers1缓冲区数量关键参数只捕获1帧数据后就停止。如果不指定会无限循环捕获videoconvert颜色空间转换器参数含义说明videoconvertVideo Converter自动转换不同的视频格式和颜色空间。例如将摄像头的 YUYV 格式转换为 JPEG 编码器需要的 RGB 或 YUV 格式使用原因摄像头输出可能是 YUYV、NV12、RG12 等格式JPEG 编码器需要特定格式的输入通常是 YUV 4:2:0videoconvert 会自动完成这个转换无需手动指定jpegenc quality90编码元素负责压缩图像参数含义说明jpegencJPEG Encoder将原始视频帧编码为 JPEG 格式quality90图像质量取值范围 1-100数值越高质量越好但文件越大。90 是高质量设置适合保存截图质量对照表100无损级文件最大90-95高质量适合存档推荐75-80良好质量适合网页50-60中等质量文件较小filesink locationtest.jpg输出元素负责写入文件参数含义说明filesinkFile Sink将数据写入文件的插件locationtest.jpg文件路径指定输出文件的名称和位置。可以是绝对路径如/home/nvidia/image.jpg或相对路径其他用法video/x-raw,width1920,height1080 增加分辨率参数location$(date %Y%m%d_%H%M%S).jpg 保存为当前时间gst-launch-1.0 -v v4l2src device/dev/video0 num-buffers1 ! \ videoconvert ! \ jpegenc quality90 ! \ filesink locationtest.jpg执行完成后保存在当前路径下常见问题问题可能原因解决方法文件为0字节num-buffers1丢失确保参数拼写正确颜色异常颜色空间转换错误在jpegenc前加video/x-raw,formatI420权限错误无法访问/dev/video0检查用户组sudo usermod -a -G video $USER只生成部分图像捕获过早结束增加num-buffers2跳过第一帧延时与帧率帧率gst-launch-1.0 -v v4l2src device/dev/video0 ! videoconvert ! fpsdisplaysink text-overlay0 video-sinkfakesink sync0参数说明-v输出详细信息方便你看到 GStreamer 在后台具体做了哪些事。text-overlay0明确禁止在不存在的画面上叠加文字避免潜在的问题。video-sinkfakesink确保数据流最终被送入一个“空槽”这样我们的终端就只专注于处理并打印帧率信息。正常输出如下/GstPipeline:pipeline0/GstFPSDisplaySink:fpsdisplaysink0: last-message rendered: 1316, dropped: 0, current: 30.00, average: 30.02内容解释参数含义你的数值说明rendered自启动以来成功渲染处理的总帧数1316, 1332, 1347这是一个累加器。可以看到数值在持续增长说明数据流非常稳定。dropped因处理不过来而被丢弃的帧数0这是一个最关键的指标。0意味着你的 Orin 处理性能完全足够没有丢帧。任何dropped不为 0 的情况都意味着系统存在性能瓶颈。current当前的瞬时帧率30.00, 29.99, 30.00表示最近一秒内测得的帧率。数值非常稳定紧贴目标帧率。average从程序启动到现在的平均帧率30.02这是整个测试期间的整体表现。30.02 是一个极其完美的数值说明从启动那一刻起流水线就以满帧率在稳定运行。延时-方案1使用identity元素延迟/帧间隔的计算公式帧间隔当前帧PTS−前一帧PTS!/usr/bin/env python3 import subprocess import re import sys def parse_and_calculate_latency(): # 构建 Pipeline使用 identity 元素并开启 silentfalse pipeline [ gst-launch-1.0, -v, v4l2src, device/dev/video0, !, videoconvert, !, identity, silentfalse, nameidentity0, !, fakesink, syncfalse ] # 正则表达式匹配 PTS 值格式示例pts: 0:00:00.033333333 pts_pattern re.compile(rpts:\s(\d):(\d):([\d\.])) last_pts_ns None frame_count 0 print(开始监控帧间隔延迟...) print(- * 60) process subprocess.Popen(pipeline, stdoutsubprocess.PIPE, stderrsubprocess.STDOUT, textTrue, bufsize1) try: for line in process.stdout: match pts_pattern.search(line) if match: # 解析时间戳时:分:秒.纳秒 hours int(match.group(1)) minutes int(match.group(2)) seconds_str match.group(3) # 转换为纳秒 seconds float(seconds_str) current_pts_ns int((hours * 3600 minutes * 60 seconds) * 1e9) frame_count 1 if last_pts_ns is not None: delta_ns current_pts_ns - last_pts_ns delta_ms delta_ns / 1_000_000.0 # 打印结果 print(f帧 {frame_count}: PTS{current_pts_ns/1e9:.6f}s, f间隔{delta_ms:.2f}ms) # 简单的异常检测对于30fps流理论间隔约33.33ms if delta_ms 50: print(f ⚠️ 警告帧间隔过大 ({delta_ms:.2f}ms 50ms)可能存在丢帧或卡顿) else: print(f帧 {frame_count}: PTS{current_pts_ns/1e9:.6f}s (第一帧)) last_pts_ns current_pts_ns except KeyboardInterrupt: print(\n监控停止) finally: process.terminate() if name main: parse_and_calculate_latency()延时-方案 2使用 Probe!/usr/bin/env python3 import gi gi.require_version(Gst, 1.0) from gi.repository import Gst, GLib, GObject import sys class LatencyMeasurer: def init(self): Gst.init(None) self.last_pts None self.frame_count 0 self.pipeline None self.probe_id None def pad_probe_callback(self, pad, info): Probe 回调函数 - 更稳定的实现 # 获取 buffer buffer info.get_buffer() if buffer is None: return Gst.PadProbeReturn.OK pts buffer.pts self.frame_count 1 if pts ! Gst.CLOCK_TIME_NONE: pts_ms pts / 1_000_000.0 if self.last_pts is not None: delta_ns pts - self.last_pts delta_ms delta_ns / 1_000_000.0 # 计算瞬时帧率 fps 1000.0 / delta_ms if delta_ms 0 else 0 print(f帧 {self.frame_count:4d}: PTS{pts_ms:8.3f}ms, f间隔{delta_ms:7.3f}ms, fFPS{fps:6.2f}) # 检测异常超过理论间隔 33.33ms 的 1.5 倍 if delta_ms 50: print(f ⚠️ 警告帧间隔过大可能存在丢帧或卡顿) else: print(f帧 {self.frame_count:4d}: PTS{pts_ms:8.3f}ms (第一帧)) self.last_pts pts else: print(f帧 {self.frame_count:4d}: 无效 PTS) return Gst.PadProbeReturn.OK def bus_call(self, bus, message, loop): t message.type if t Gst.MessageType.EOS: print(\nEnd-of-stream) loop.quit() elif t Gst.MessageType.ERROR: err, debug message.parse_error() print(fError: {err}, {debug}) loop.quit() elif t Gst.MessageType.STATE_CHANGED: # 可选打印状态变化信息 if message.src self.pipeline: old, new, pending message.parse_state_changed() print(fPipeline 状态: {old.value_nick} - {new.value_nick}) return True def run(self, device/dev/video0, width1920, height1080): # 构建 Pipeline添加了 caps filter 确保格式 pipeline_str ( fv4l2src device{device} ! fvideo/x-raw,width{width},height{height} ! videoconvert ! video/x-raw,formatI420 ! identity namelatency_identity ! fakesink syncfalse ) print(fPipeline: {pipeline_str}) try: self.pipeline Gst.parse_launch(pipeline_str) except GLib.GError as e: print(fPipeline 解析错误: {e}) return # 获取 identity 元素 identity self.pipeline.get_by_name(latency_identity) if not identity: print(Error: Failed to get identity element) return # 获取 identity 元素的 src pad输出端 src_pad identity.get_static_pad(src) if not src_pad: print(Error: Failed to get src pad from identity) return # 添加 probePROBE_TYPE_BUFFER 表示在每个 buffer 通过时触发 self.probe_id src_pad.add_probe( Gst.PadProbeType.BUFFER, self.pad_probe_callback ) # 设置总线监控 bus self.pipeline.get_bus() bus.add_signal_watch() loop GLib.MainLoop() bus.connect(message, self.bus_call, loop) # 启动 Pipeline self.pipeline.set_state(Gst.State.PLAYING) print(f\n开始监控设备 {device} 的帧间隔...) print(f分辨率: {width}x{height}) print(- * 80) print(f{帧号:6} {PTS(ms):12} {帧间隔(ms):12} {瞬时FPS:10}) print(- * 80) try: loop.run() except KeyboardInterrupt: print(\n\n监控停止) finally: # 清理 if self.probe_id: src_pad.remove_probe(self.probe_id) self.pipeline.set_state(Gst.State.NULL) # 输出统计信息 if self.frame_count 1: print(f\n 统计: 共捕获 {self.frame_count} 帧) if name main: measurer LatencyMeasurer() # 可以根据需要修改设备路径和分辨率 measurer.run(device/dev/video0, width1920, height1080)Probe 版本的优点不会出现参数不匹配的错误更稳定适用于所有 GStreamer 1.x 版本性能更好因为是底层探测机制更容易扩展可以同时获取 buffer 的更多信息如 DTS、duration 等预期输出示例Pipeline 状态: NULL - READY Pipeline 状态: READY - PAUSED Pipeline 状态: PAUSED - PLAYING开始监控设备 /dev/video0 的帧间隔... 分辨率: 1920x1080帧号 PTS(ms) 帧间隔(ms) 瞬时FPS帧 1: PTS 33.333ms (第一帧) 帧 2: PTS 66.667ms, 间隔 33.334ms, FPS 30.00 帧 3: PTS 100.000ms, 间隔 33.333ms, FPS 30.00问题会出现FPS为15最可能的原因Jetson Orin 主动降频这是 NVIDIA Jetson 设备上一个非常典型的特征与我们之前的测试方法或代码无关。当 Orin 的 SoC 温度升高到约 60-65°C 时系统为了自我保护会强制将摄像头接口ISP/VI的帧率降低到原来的一半甚至更低例如从 60fps 降至 2fps。由于你的相机是 30fps降频后就表现为稳定的 15fps。可能的原因PipelinePipeline 中存在 CPU 瓶颈 如果你的 pipeline 中使用了软件进行格式转换例如 videoconvert这在 Jetson 上是非常消耗 CPU 资源的。当 CPU 负载过高来不及处理所有帧时后面环节的帧率就会被迫下降导致 probe 测得的数值减半。排查方法可以查看 CPU 占用率。如果接近 100%就说明瓶颈在这里。可以尝试将 pipeline 改为硬件加速版本例如使用 nvvidconv 替代 videoconvert。Pipeline 被强制丢帧 这往往是由于 pipeline 中某个元素的处理速度跟不上相机的输出速度GStreamer 框架为了保持实时性会自动丢弃部分帧。排查方法在你之前的 fpsdisplaysink 输出中有一个非常关键的指标——dropped: 0。请再次确认这个值是否为 0。如果 dropped 计数在增加说明确实有丢帧现象。摄像头 V4L2 驱动问题 在某些场景下GStreamer 的 v4l2src 元素可能无法正确读取或协商到摄像头传感器报告的帧率从而使用一个错误的、甚至是降级的帧率来运行。虽然这种情况相对少见但也是一个潜在的可能性。解决方案用硬件加速的nvvidconv替换videoconvert。