为采集帧绑定主机时间与来源信息
采集时间随图像穿过处理队列,并修正未读帧的丢帧计数。通过采集与流水线针对性测试。
This commit is contained in:
@@ -199,6 +199,89 @@ def test_capture_source_read_and_status():
|
||||
assert cap.release_count == 1
|
||||
|
||||
|
||||
def test_file_read_binds_frame_sequence_and_host_clocks():
|
||||
cap = FakeCap([_frame(7), _frame(8)])
|
||||
src = CaptureSource(
|
||||
CaptureConfig(source="sample.mp4", reconnect=ReconnectPolicy(enabled=False)),
|
||||
factory=lambda cfg: (cap, "FAKE"),
|
||||
)
|
||||
src.open()
|
||||
before = time.perf_counter_ns()
|
||||
first = src.read_raw_stamped()
|
||||
second = src.read_raw_stamped()
|
||||
after = time.perf_counter_ns()
|
||||
assert first is not None and second is not None
|
||||
assert (first.sequence, second.sequence) == (1, 2)
|
||||
assert (int(first.image[0, 0, 0]), int(second.image[0, 0, 0])) == (7, 8)
|
||||
assert before <= first.host_mono_ns <= second.host_mono_ns <= after
|
||||
assert first.host_utc_ns > 0
|
||||
assert first.media_time_ms == 0.0
|
||||
src.close()
|
||||
|
||||
|
||||
def test_background_read_keeps_its_own_stamp_when_newer_frame_arrives():
|
||||
cap = FakeCap([_frame(i) for i in range(10)])
|
||||
src = CaptureSource(
|
||||
CaptureConfig(source=0, reconnect=ReconnectPolicy(enabled=False)),
|
||||
factory=lambda cfg: (cap, "FAKE"),
|
||||
)
|
||||
src.open()
|
||||
src.start_background()
|
||||
try:
|
||||
first = src.read_latest_stamped(wait_s=1.0)
|
||||
assert first is not None
|
||||
deadline = time.perf_counter() + 1.0
|
||||
second = first
|
||||
while second.sequence == first.sequence and time.perf_counter() < deadline:
|
||||
second = src.read_latest_stamped(wait_s=0.01)
|
||||
assert second is not None and second.sequence > first.sequence
|
||||
assert int(first.image[0, 0, 0]) == min(first.sequence - 1, 9)
|
||||
assert int(second.image[0, 0, 0]) == min(second.sequence - 1, 9)
|
||||
assert first.host_mono_ns <= second.host_mono_ns
|
||||
assert src.status.last_seq >= 2
|
||||
finally:
|
||||
src.close()
|
||||
|
||||
|
||||
def test_background_drop_count_only_increases_for_unread_replacements():
|
||||
class StepCap(FakeCap):
|
||||
def __init__(self):
|
||||
super().__init__([_frame(i) for i in range(4)])
|
||||
self.allow = threading.Semaphore(0)
|
||||
|
||||
def read(self):
|
||||
if not self.allow.acquire(timeout=0.1):
|
||||
return False, None
|
||||
return super().read()
|
||||
|
||||
cap = StepCap()
|
||||
src = CaptureSource(
|
||||
CaptureConfig(source=0, reconnect=ReconnectPolicy(enabled=False)),
|
||||
factory=lambda cfg: (cap, "FAKE"),
|
||||
)
|
||||
src.open()
|
||||
src.start_background()
|
||||
|
||||
def wait_seq(seq):
|
||||
deadline = time.perf_counter() + 2.0
|
||||
while src.status.last_seq < seq and time.perf_counter() < deadline:
|
||||
time.sleep(0.001)
|
||||
assert src.status.last_seq >= seq
|
||||
|
||||
try:
|
||||
cap.allow.release()
|
||||
wait_seq(1)
|
||||
assert src.read_latest_stamped().sequence == 1
|
||||
cap.allow.release()
|
||||
wait_seq(2)
|
||||
assert src.status.dropped_frames == 0
|
||||
cap.allow.release()
|
||||
wait_seq(3)
|
||||
assert src.status.dropped_frames == 1
|
||||
finally:
|
||||
src.close()
|
||||
|
||||
|
||||
def test_auto_reconnect_after_failed_reads():
|
||||
first = FakeCap([_frame(1)], fail_after=1)
|
||||
second = FakeCap([_frame(9)])
|
||||
|
||||
+25
-1
@@ -8,7 +8,7 @@ import time
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from orisys import FrameSnapshot, StreamPipeline
|
||||
from orisys import FrameSnapshot, ProducedFrame, StreamPipeline
|
||||
|
||||
|
||||
class FakeSensor:
|
||||
@@ -149,6 +149,30 @@ def test_on_frame_receives_every_frame_in_order():
|
||||
assert pipe.stats.published == 12
|
||||
|
||||
|
||||
def test_producer_metadata_stays_with_its_image_through_the_queues():
|
||||
sensor = FakeSensor(flow_delay=0.002)
|
||||
frames = []
|
||||
produced = 0
|
||||
|
||||
def produce():
|
||||
nonlocal produced
|
||||
if produced == 12:
|
||||
return None
|
||||
value = produced
|
||||
produced += 1
|
||||
return ProducedFrame(sensor.make_frame(value), {"source_seq": value})
|
||||
|
||||
pipe = StreamPipeline(sensor, produce, on_frame=frames.append)
|
||||
pipe.start()
|
||||
deadline = time.perf_counter() + 10.0
|
||||
while len(frames) < 12 and time.perf_counter() < deadline:
|
||||
time.sleep(0.001)
|
||||
pipe.stop()
|
||||
assert len(frames) == 12
|
||||
for snap in frames:
|
||||
assert snap.source_metadata["source_seq"] == int(snap.image[0, 0])
|
||||
|
||||
|
||||
def test_forces_come_from_the_matching_frame():
|
||||
"""Each snapshot must carry the forces computed from its own frame's flow."""
|
||||
sensor = FakeSensor()
|
||||
|
||||
Reference in New Issue
Block a user