Publish and subscribe over wss://livekit.uni-wh.de:7800 and refuse cleartext ws://. Conference room is uwh-telhai. Includes the uncommitted encoded H.264 publish path, Rally hairpin, and KMS wall overlay.
200 lines
7.7 KiB
Python
200 lines
7.7 KiB
Python
"""Green speaking chrome on the KMS grid (LiveKit active-speaker)."""
|
|
import numpy as np
|
|
|
|
from display_grid import FRAME_RAISE, FRAME_SPEAK, CameraGrid, bgra_to_i420, bgra_to_yuv_pixel
|
|
|
|
|
|
def test_set_speakers_paints_green_i420_border():
|
|
g = CameraGrid(width=1920, height=1080, cols=5, rows=4)
|
|
src = np.zeros((360, 640, 4), dtype=np.uint8)
|
|
src[:] = (40, 40, 40, 255)
|
|
g.set_frame_i420("cam-07", bgra_to_i420(src), 640, 360)
|
|
before = np.frombuffer(g.render_i420(), dtype=np.uint8).copy()
|
|
g.set_speakers(["cam-07"])
|
|
assert g.speaking("cam-07") is True
|
|
after = np.frombuffer(g.render_i420(), dtype=np.uint8)
|
|
assert not np.array_equal(before, after)
|
|
idx = g.identities.index("cam-07")
|
|
ox, oy = g.tile_origin(idx)
|
|
yplane = after[: g.width * g.height].reshape(g.height, g.width)
|
|
yv, _, _ = bgra_to_yuv_pixel(FRAME_SPEAK)
|
|
assert int(yplane[oy, ox + g.tile_w // 2]) == yv
|
|
# Extra thickness lives in the gutter, not on the feed.
|
|
assert int(yplane[oy - 2, ox + g.tile_w // 2]) == yv
|
|
ix, iy, iw, _ih = g.inner_rect(idx)
|
|
assert int(yplane[iy, ix + iw // 2]) != yv
|
|
|
|
|
|
def test_speaking_overrides_yellow_then_yellow_returns():
|
|
g = CameraGrid(width=1920, height=1080, cols=5, rows=4)
|
|
src = np.zeros((360, 640, 4), dtype=np.uint8)
|
|
g.set_frame_i420("cam-07", bgra_to_i420(src), 640, 360)
|
|
g.set_highlight("cam-07", True)
|
|
g.set_speakers(["cam-07"])
|
|
idx = g.identities.index("cam-07")
|
|
ox, oy = g.tile_origin(idx)
|
|
yplane = np.frombuffer(g.render_i420(), dtype=np.uint8)[: g.width * g.height].reshape(
|
|
g.height, g.width)
|
|
y_speak, _, _ = bgra_to_yuv_pixel(FRAME_SPEAK)
|
|
y_raise, _, _ = bgra_to_yuv_pixel(FRAME_RAISE)
|
|
assert int(yplane[oy, ox + g.tile_w // 2]) == y_speak
|
|
g.set_speakers([])
|
|
yplane = np.frombuffer(g.render_i420(), dtype=np.uint8)[: g.width * g.height].reshape(
|
|
g.height, g.width)
|
|
assert g.speaking("cam-07") is False
|
|
assert g.highlighted("cam-07") is True
|
|
assert int(yplane[oy, ox + g.tile_w // 2]) == y_raise
|
|
|
|
|
|
def test_clear_drops_speaking():
|
|
g = CameraGrid(width=1920, height=1080, cols=5, rows=4)
|
|
src = np.zeros((360, 640, 4), dtype=np.uint8)
|
|
g.set_frame_i420("cam-01", bgra_to_i420(src), 640, 360)
|
|
g.set_speakers(["cam-01"])
|
|
g.clear("cam-01")
|
|
assert g.speaking("cam-01") is False
|
|
|
|
|
|
class _P:
|
|
def __init__(self, identity, keep=True):
|
|
self.identity = identity
|
|
self.keep = keep
|
|
|
|
|
|
def test_active_speaker_ids_keeps_order_and_filter():
|
|
from display_grid import active_speaker_ids, primary_grid_speaker
|
|
speakers = [_P("cam-07"), _P("rally", False), _P("cam-03"), _P("")]
|
|
assert active_speaker_ids(speakers, want=lambda p: p.keep) == ["cam-07", "cam-03"]
|
|
ids = ["rally", "cam-03", "cam-07"]
|
|
assert primary_grid_speaker(ids, ["cam-01", "cam-03", "cam-07"]) == ["cam-03"]
|
|
assert primary_grid_speaker(["rally"], ["cam-01"]) == []
|
|
|
|
|
|
def test_select_grid_speakers_drops_floor_noise():
|
|
from display_grid import select_grid_speakers
|
|
assert select_grid_speakers(
|
|
[("cam-01", -34.0), ("cam-02", -36.0)], min_db=-24.0, margin_db=6.0,
|
|
) == []
|
|
|
|
|
|
def test_select_grid_speakers_bleed_keeps_louder_only():
|
|
from display_grid import select_grid_speakers
|
|
assert select_grid_speakers(
|
|
[("cam-07", -12.0), ("cam-08", -22.0)], min_db=-24.0, margin_db=6.0,
|
|
) == ["cam-07"]
|
|
|
|
|
|
def test_select_grid_speakers_two_talkers_both_kept():
|
|
from display_grid import select_grid_speakers
|
|
assert select_grid_speakers(
|
|
[("cam-07", -12.0), ("cam-08", -14.0)], min_db=-24.0, margin_db=6.0,
|
|
) == ["cam-07", "cam-08"]
|
|
|
|
|
|
def test_select_grid_speakers_normal_speech_above_mic_floor():
|
|
"""Conversational C920 speech is often ~-24 dBFS; absolute -20 missed it."""
|
|
from display_grid import select_grid_speakers
|
|
floors = {"cam-07": -34.0, "cam-08": -34.0}
|
|
assert select_grid_speakers(
|
|
[("cam-07", -24.0), ("cam-08", -33.0)],
|
|
min_db=-40.0, margin_db=6.0, floors=floors, rise_db=6.0,
|
|
) == ["cam-07"]
|
|
|
|
|
|
def test_select_grid_speakers_two_talkers_each_above_own_floor():
|
|
from display_grid import select_grid_speakers
|
|
floors = {"cam-07": -34.0, "cam-08": -36.0}
|
|
assert select_grid_speakers(
|
|
[("cam-07", -24.0), ("cam-08", -26.0)],
|
|
min_db=-40.0, margin_db=6.0, floors=floors, rise_db=6.0,
|
|
) == ["cam-07", "cam-08"]
|
|
|
|
|
|
def test_select_quiet_mic_speech_not_cut_by_abs_min():
|
|
"""Quiet C920 idle is ~-45; +3 dB speech is still below -40 dBFS."""
|
|
from display_grid import select_grid_speakers
|
|
floors = {"cam-10": -45.0, "cam-01": -37.0}
|
|
assert select_grid_speakers(
|
|
[("cam-10", -41.5), ("cam-01", -36.5)],
|
|
min_db=-40.0, margin_db=6.0, floors=floors, rise_db=3.0,
|
|
) == ["cam-10"]
|
|
|
|
|
|
def test_update_noise_floor_tracks_idle_not_speech():
|
|
from display_grid import update_noise_floor
|
|
fl = update_noise_floor(-34.0, -33.5, speaking=False, rise_db=4.0)
|
|
assert -34.0 < fl < -33.5
|
|
stuck = update_noise_floor(-34.0, -20.0, speaking=True, rise_db=4.0)
|
|
assert stuck == -34.0
|
|
|
|
|
|
def test_confirm_speakers_needs_two_hops_new_talker():
|
|
from display_grid import confirm_speakers
|
|
first, counts = confirm_speakers({}, ["cam-07"], need=2)
|
|
assert first == []
|
|
second, counts = confirm_speakers(counts, ["cam-07"], need=2)
|
|
assert second == ["cam-07"]
|
|
# already on: one hop of continued speech keeps chrome
|
|
stay, _ = confirm_speakers(counts, ["cam-07"], need=2, already=["cam-07"])
|
|
assert stay == ["cam-07"]
|
|
# a one-hop idle spike on another seat does not light
|
|
spike, _ = confirm_speakers({}, ["cam-01"], need=2, already=["cam-07"])
|
|
assert spike == []
|
|
|
|
|
|
def test_waveform_similarity_high_for_delayed_copy():
|
|
from display_grid import waveform_similarity
|
|
t = np.linspace(0, 1, 1600, endpoint=False)
|
|
a = np.sin(2 * np.pi * 220 * t).astype(np.float32)
|
|
b = np.roll(a * 0.4, 80)
|
|
assert waveform_similarity(a, b) > 0.85
|
|
|
|
|
|
def test_waveform_similarity_low_for_independent_signals():
|
|
from display_grid import waveform_similarity
|
|
rng = np.random.default_rng(0)
|
|
a = rng.normal(size=1600).astype(np.float32)
|
|
b = rng.normal(size=1600).astype(np.float32)
|
|
assert waveform_similarity(a, b) < 0.35
|
|
|
|
|
|
def test_collapse_bleed_drops_similar_quieter_mic():
|
|
from display_grid import collapse_bleed
|
|
t = np.linspace(0, 1, 1600, endpoint=False)
|
|
src = np.sin(2 * np.pi * 180 * t).astype(np.float32)
|
|
waves = {"cam-07": src, "cam-08": np.roll(src * 0.3, 40)}
|
|
levels = {"cam-07": -12.0, "cam-08": -18.0}
|
|
assert collapse_bleed(["cam-07", "cam-08"], waves, levels, corr_min=0.4) == ["cam-07"]
|
|
|
|
|
|
def test_collapse_bleed_keeps_two_unlike_talkers():
|
|
from display_grid import collapse_bleed
|
|
t = np.linspace(0, 1, 1600, endpoint=False)
|
|
a = np.sin(2 * np.pi * 180 * t).astype(np.float32)
|
|
b = np.sin(2 * np.pi * 440 * t + 1.3).astype(np.float32)
|
|
waves = {"cam-07": a, "cam-08": b}
|
|
levels = {"cam-07": -12.0, "cam-08": -13.0}
|
|
assert collapse_bleed(["cam-07", "cam-08"], waves, levels, corr_min=0.6) == [
|
|
"cam-07", "cam-08",
|
|
]
|
|
|
|
|
|
def test_hold_speakers_adds_second_talker_immediately():
|
|
from display_grid import hold_speakers
|
|
cur, until = hold_speakers(
|
|
["cam-07"], ["cam-07", "cam-08"], prev_peak=-12.0, new_peak=-12.0,
|
|
now=10.0, hold_until=10.5, hold_s=0.6, switch_db=3.0)
|
|
assert cur == ["cam-07", "cam-08"]
|
|
|
|
|
|
def test_hold_speakers_ignores_similar_bleed_flicker():
|
|
from display_grid import hold_speakers
|
|
cur, until = hold_speakers(
|
|
["cam-07"], ["cam-08"], prev_peak=-12.0, new_peak=-12.5,
|
|
now=10.2, hold_until=10.8, hold_s=0.6, switch_db=3.0)
|
|
assert cur == ["cam-07"]
|
|
cur, _ = hold_speakers(
|
|
["cam-07"], ["cam-08"], prev_peak=-12.0, new_peak=-8.0,
|
|
now=10.2, hold_until=10.8, hold_s=0.6, switch_db=3.0)
|
|
assert cur == ["cam-08"]
|