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.
77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
"""16-align I420→NV12 for Intel VA (NV12 only). Pad up, never crop."""
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
|
|
def coded_dim(n: int) -> int:
|
|
n = int(n)
|
|
if n <= 0:
|
|
return 0
|
|
return (n + 15) // 16 * 16
|
|
|
|
|
|
def nv12_bytes(width: int, height: int) -> int:
|
|
return int(width) * int(height) * 3 // 2
|
|
|
|
|
|
def va_visible_height(width: int, height: int) -> int:
|
|
"""Drop the 8-row VA pad when the remainder is 16:9 (360/1080)."""
|
|
w, h = int(width), int(height)
|
|
vis = h - 8
|
|
if w > 0 and vis > 0 and vis * 16 == w * 9:
|
|
return vis
|
|
return h
|
|
|
|
|
|
def drop_va_pad_i420(src, width: int, height: int):
|
|
"""Crop coded pad off I420. Returns (payload, w, visible_h)."""
|
|
w, h = int(width), int(height)
|
|
vis = va_visible_height(w, h)
|
|
if vis == h:
|
|
return src, w, h
|
|
ysz = w * h
|
|
uvw, uvh = w // 2, h // 2
|
|
uvsz = uvw * uvh
|
|
raw = np.frombuffer(src, dtype=np.uint8, count=ysz + 2 * uvsz)
|
|
y = raw[:ysz].reshape(h, w)[:vis, :]
|
|
u = raw[ysz:ysz + uvsz].reshape(uvh, uvw)[: vis // 2, :]
|
|
v = raw[ysz + uvsz:ysz + 2 * uvsz].reshape(uvh, uvw)[: vis // 2, :]
|
|
out = np.empty(w * vis * 3 // 2, dtype=np.uint8)
|
|
out[: w * vis] = y.reshape(-1)
|
|
out[w * vis:w * vis + (w // 2) * (vis // 2)] = u.reshape(-1)
|
|
out[w * vis + (w // 2) * (vis // 2):] = v.reshape(-1)
|
|
return out.tobytes(), w, vis
|
|
|
|
|
|
def i420_to_nv12_into(src, width: int, height: int, out: bytearray) -> int:
|
|
"""Pad to 16-aligned WxH and write NV12 into out. Returns nbytes."""
|
|
w, h = int(width), int(height)
|
|
cw, ch = coded_dim(w), coded_dim(h)
|
|
need = nv12_bytes(cw, ch)
|
|
if len(out) < need:
|
|
raise ValueError("NV12 dest too small")
|
|
ysz = w * h
|
|
uvw, uvh = w // 2, h // 2
|
|
uvsz = uvw * uvh
|
|
raw = np.frombuffer(src, dtype=np.uint8, count=ysz + 2 * uvsz)
|
|
y = raw[:ysz].reshape(h, w)
|
|
u = raw[ysz:ysz + uvsz].reshape(uvh, uvw)
|
|
v = raw[ysz + uvsz:ysz + 2 * uvsz].reshape(uvh, uvw)
|
|
nv = np.frombuffer(out, dtype=np.uint8, count=need).reshape(ch + ch // 2, cw)
|
|
nv[:h, :w] = y
|
|
if ch > h:
|
|
nv[h:ch, :w] = y[-1]
|
|
if cw > w:
|
|
nv[:ch, w:cw] = nv[:ch, w - 1:w]
|
|
uv = nv[ch:, :].reshape(ch // 2, cw // 2, 2)
|
|
uh, uw = h // 2, w // 2
|
|
uv[:uh, :uw, 0] = u
|
|
uv[:uh, :uw, 1] = v
|
|
if ch // 2 > uh:
|
|
uv[uh:, :uw, 0] = u[-1]
|
|
uv[uh:, :uw, 1] = v[-1]
|
|
if cw // 2 > uw:
|
|
uv[:, uw:, :] = uv[:, uw - 1:uw, :]
|
|
return need
|