diff --git a/agv_app/app.py b/agv_app/app.py index 42a21da..d567c60 100644 --- a/agv_app/app.py +++ b/agv_app/app.py @@ -1130,26 +1130,16 @@ def api_camera_capture(): @app.route("/api/camera/arm_refresh") def api_arm_camera_refresh(): - """从机械臂拉一张 JPEG(流式读第一个完整帧)""" + """从机械臂拉一张 JPEG(请求 snapshot 端点,简单 HTTP GET)""" import requests try: - r = requests.get(ARM_CAMERA_CONFIG["url"], stream=True, timeout=8) - if r.status_code != 200: - return "", 404 - data = b"" - for chunk in r.iter_content(chunk_size=8192): - data += chunk - # 在累积数据中找 JPEG 完整帧 - s = data.find(b"\xff\xd8") - e = data.find(b"\xff\xd9", s + 2) if s >= 0 else -1 - if s >= 0 and e > s: - r.close() - return Response(data[s:e+2], mimetype="image/jpeg") - # 累积超过 5MB 还没找到完整帧,说明流异常,放弃 - if len(data) > 5 * 1024 * 1024: - r.close() - return "", 404 - r.close() + r = requests.get(ARM_CAMERA_CONFIG.get("snapshot_url", ARM_CAMERA_CONFIG["url"]), timeout=8) + if r.status_code == 200 and r.content: + resp = Response(r.content, mimetype="image/jpeg") + resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0" + resp.headers["Pragma"] = "no-cache" + resp.headers["Expires"] = "0" + return resp return "", 404 except Exception as ex: logger.error(f"arm_refresh 失败: {ex}") diff --git a/agv_app/config.py b/agv_app/config.py index 1d1349d..21abed5 100644 --- a/agv_app/config.py +++ b/agv_app/config.py @@ -40,6 +40,7 @@ CAMERA_CONFIG = { # ========== 机械臂摄像头流 ========== ARM_CAMERA_CONFIG = { "url": "http://192.168.110.164:5003/api/camera/preview", + "snapshot_url": "http://192.168.110.164:5003/api/camera/snapshot", } # ========== HTTP 上传 ========== diff --git a/arm_server/arm_server.py b/arm_server/arm_server.py index 1723b46..b389e08 100644 --- a/arm_server/arm_server.py +++ b/arm_server/arm_server.py @@ -45,6 +45,7 @@ def _ensure_ffmpeg(): "ffmpeg", "-f", "v4l2", "-input_format", "mjpeg", + "-re", "-i", f"/dev/video{ARM_CAMERA_INDEX}", "-vf", "rotate=PI", "-q:v", "8", @@ -103,6 +104,40 @@ def arm_camera_restart(): _ensure_ffmpeg() return jsonify({"ok": True}) +@arm_video_app.route("/api/camera/snapshot") +def arm_camera_snapshot(): + """机械臂摄像头单帧 JPEG — pkill -9 强杀旧 ffmpeg,再临时抓一帧""" + import subprocess + global _ffmpeg_proc + # 用 pkill -9 强杀所有 ffmpeg 进程,释放 /dev/video0 + subprocess.run(["pkill", "-9", "-f", "ffmpeg"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=3) + time.sleep(0.3) + _ffmpeg_proc = None + proc = subprocess.run( + [ + "ffmpeg", + "-f", "v4l2", + "-input_format", "mjpeg", + "-i", f"/dev/video{ARM_CAMERA_INDEX}", + "-vf", "rotate=PI", + "-vframes", "1", + "-q:v", "8", + "-f", "mjpeg", + "pipe:1" + ], + stdout=subprocess.PIPE, + timeout=5, + stderr=subprocess.DEVNULL + ) + if proc.returncode == 0 and proc.stdout: + r = Response(proc.stdout, mimetype="image/jpeg") + r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0" + r.headers["Pragma"] = "no-cache" + r.headers["Expires"] = "0" + return r + logger.warning(f"ffmpeg snapshot failed: rc={proc.returncode}") + return "", 500 + # ========== RoboFlow 630 Socket API 客户端 ========== class RoboFlowClient: