infinicode 2.8.87 → 2.8.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -70,6 +70,14 @@ for actual PCM frames before reporting `microphone_published`, reports a blocked
|
|
|
70
70
|
stage when capture does not start within five seconds, and applies bounded mic
|
|
71
71
|
gain before publication.
|
|
72
72
|
|
|
73
|
+
### Camera Relay With Multiple Viewers
|
|
74
|
+
|
|
75
|
+
RoboVision must have exactly one OpenCV camera reader. Preview, dashboard, and
|
|
76
|
+
motion detection consume a shared latest-frame stream. Starting one
|
|
77
|
+
`generate_frames()` camera loop per MJPEG client causes concurrent
|
|
78
|
+
`VideoCapture.read()` calls, which can block the second client and make the hub
|
|
79
|
+
relay time out with zero bytes.
|
|
80
|
+
|
|
73
81
|
### Duplicate Speaker Tests
|
|
74
82
|
|
|
75
83
|
Only one robot process may claim a scheduler `speaker_test`. The scheduler
|
package/package.json
CHANGED
|
@@ -18,6 +18,11 @@ CORS(app)
|
|
|
18
18
|
# Camera management
|
|
19
19
|
current_camera_index = 0
|
|
20
20
|
camera = None
|
|
21
|
+
camera_lock = threading.Lock()
|
|
22
|
+
frame_condition = threading.Condition()
|
|
23
|
+
latest_frame_bytes = None
|
|
24
|
+
latest_frame_sequence = 0
|
|
25
|
+
camera_worker_started = False
|
|
21
26
|
audio_input_device = "default"
|
|
22
27
|
audio_output_device = "default"
|
|
23
28
|
ROBOVISION_AUDIO_URL = os.getenv("ROBOVISION_AUDIO_URL", "http://127.0.0.1:8000")
|
|
@@ -224,22 +229,25 @@ def send_webhook(frame_data):
|
|
|
224
229
|
except Exception as e:
|
|
225
230
|
print(f"Error preparing webhook: {e}")
|
|
226
231
|
|
|
227
|
-
def
|
|
228
|
-
global latest_detections, motion_detection_active, motion_detected_state
|
|
229
|
-
global last_motion_time, motion_frame_buffer
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
232
|
+
def camera_worker():
|
|
233
|
+
global latest_detections, motion_detection_active, motion_detected_state
|
|
234
|
+
global last_motion_time, motion_frame_buffer
|
|
235
|
+
global latest_frame_bytes, latest_frame_sequence
|
|
236
|
+
|
|
237
|
+
prev_gray = None
|
|
238
|
+
|
|
239
|
+
while True:
|
|
240
|
+
with camera_lock:
|
|
241
|
+
cam = get_camera()
|
|
242
|
+
if cam is None or not cam.isOpened():
|
|
243
|
+
time.sleep(1)
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
with camera_lock:
|
|
247
|
+
success, frame = cam.read()
|
|
248
|
+
if not success:
|
|
249
|
+
time.sleep(0.1)
|
|
250
|
+
continue
|
|
243
251
|
|
|
244
252
|
# Run object detection
|
|
245
253
|
processed_frame, detections = detect_objects(frame, conf_threshold=0.5)
|
|
@@ -288,11 +296,40 @@ def generate_frames():
|
|
|
288
296
|
|
|
289
297
|
prev_gray = gray
|
|
290
298
|
|
|
291
|
-
ret, buffer = cv2.imencode('.jpg', processed_frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
299
|
+
ret, buffer = cv2.imencode('.jpg', processed_frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
|
|
300
|
+
if not ret:
|
|
301
|
+
continue
|
|
302
|
+
with frame_condition:
|
|
303
|
+
latest_frame_bytes = buffer.tobytes()
|
|
304
|
+
latest_frame_sequence += 1
|
|
305
|
+
frame_condition.notify_all()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _ensure_camera_worker():
|
|
309
|
+
global camera_worker_started
|
|
310
|
+
with frame_condition:
|
|
311
|
+
if camera_worker_started:
|
|
312
|
+
return
|
|
313
|
+
camera_worker_started = True
|
|
314
|
+
threading.Thread(target=camera_worker, name='robovision-camera', daemon=True).start()
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def generate_frames():
|
|
318
|
+
_ensure_camera_worker()
|
|
319
|
+
sequence = -1
|
|
320
|
+
while True:
|
|
321
|
+
with frame_condition:
|
|
322
|
+
frame_condition.wait_for(
|
|
323
|
+
lambda: latest_frame_bytes is not None and latest_frame_sequence != sequence,
|
|
324
|
+
timeout=5.0,
|
|
325
|
+
)
|
|
326
|
+
if latest_frame_bytes is None or latest_frame_sequence == sequence:
|
|
327
|
+
continue
|
|
328
|
+
frame_bytes = latest_frame_bytes
|
|
329
|
+
sequence = latest_frame_sequence
|
|
330
|
+
|
|
331
|
+
yield (b'--frame\r\n'
|
|
332
|
+
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
|
|
296
333
|
|
|
297
334
|
@app.route('/')
|
|
298
335
|
def index():
|
|
@@ -379,11 +416,11 @@ def switch_camera():
|
|
|
379
416
|
if isinstance(new_index, str) and new_index.isdigit():
|
|
380
417
|
new_index = int(new_index)
|
|
381
418
|
|
|
382
|
-
|
|
383
|
-
camera
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
419
|
+
with camera_lock:
|
|
420
|
+
if camera:
|
|
421
|
+
camera.release()
|
|
422
|
+
current_camera_index = new_index
|
|
423
|
+
camera = None
|
|
387
424
|
|
|
388
425
|
return jsonify({"status": "ok", "camera_index": current_camera_index})
|
|
389
426
|
|