perf: reduce NV12 per-frame copies from 5 to 2 via single from_buffer_copy + byref offset
This commit is contained in:
@@ -147,8 +147,11 @@ class _Frame:
|
||||
pitch: int
|
||||
pixels: bytes
|
||||
pixel_format: str = "BGRA" # "BGRA" or "NV12"
|
||||
# For NV12: pitch is the Y-plane stride; uv_pixels is the interleaved UV plane.
|
||||
uv_pixels: bytes | None = None
|
||||
# For NV12: pixels holds the FULL raw buffer (Y + interleaved UV); y_size is
|
||||
# the byte offset where the UV plane starts. uv_pitch is the UV plane stride.
|
||||
# Storing the whole buffer in one object avoids two separate bytes slices (each
|
||||
# a copy) and lets render() do a single from_buffer_copy instead of two.
|
||||
y_size: int = 0
|
||||
uv_pitch: int = 0
|
||||
|
||||
|
||||
@@ -243,25 +246,30 @@ class GStreamerBackend(PlayerBackend):
|
||||
self._texture_format = frame.pixel_format
|
||||
|
||||
if self._frame_dirty:
|
||||
if frame.pixel_format == "NV12" and frame.uv_pixels is not None:
|
||||
# Zero-copy NV12 path: upload Y and UV planes separately.
|
||||
# SDL_UpdateNVTexture avoids a full BGRA conversion on CPU.
|
||||
# Use named c_ubyte arrays so the buffers stay alive for the
|
||||
# duration of the C call (ctypes.cast of a temporary would produce
|
||||
# a dangling pointer after CPython reference-counts the temp away).
|
||||
if frame.pixel_format == "NV12" and frame.y_size > 0:
|
||||
# NV12 upload via SDL_UpdateNVTexture.
|
||||
# ONE from_buffer_copy of the full Y+UV buffer, then use
|
||||
# ctypes.byref(arr, offset) to address Y at 0 and UV at y_size.
|
||||
# This avoids the two extra bytes slices that were previously
|
||||
# created in _on_new_sample, cutting per-frame copies from 5 to 2.
|
||||
try:
|
||||
y_arr = (ctypes.c_ubyte * len(frame.pixels)).from_buffer_copy(frame.pixels)
|
||||
uv_arr = (ctypes.c_ubyte * len(frame.uv_pixels)).from_buffer_copy(frame.uv_pixels)
|
||||
raw_buf = frame.pixels
|
||||
arr = (ctypes.c_ubyte * len(raw_buf)).from_buffer_copy(raw_buf)
|
||||
y_ptr = ctypes.cast(arr, ctypes.POINTER(ctypes.c_ubyte))
|
||||
uv_ptr = ctypes.cast(
|
||||
ctypes.byref(arr, frame.y_size),
|
||||
ctypes.POINTER(ctypes.c_ubyte),
|
||||
)
|
||||
log.debug(
|
||||
"SDL_UpdateNVTexture: %dx%d y_len=%d uv_len=%d pitch=%d uv_pitch=%d",
|
||||
"SDL_UpdateNVTexture: %dx%d buf=%d y_size=%d pitch=%d uv_pitch=%d",
|
||||
frame.width, frame.height,
|
||||
len(frame.pixels), len(frame.uv_pixels),
|
||||
len(raw_buf), frame.y_size,
|
||||
frame.pitch, frame.uv_pitch,
|
||||
)
|
||||
result = sdl2.SDL_UpdateNVTexture(
|
||||
self._texture, None,
|
||||
y_arr, frame.pitch,
|
||||
uv_arr, frame.uv_pitch,
|
||||
y_ptr, frame.pitch,
|
||||
uv_ptr, frame.uv_pitch,
|
||||
)
|
||||
except Exception:
|
||||
log.error(
|
||||
@@ -458,25 +466,25 @@ class GStreamerBackend(PlayerBackend):
|
||||
|
||||
if fmt_str == "NV12":
|
||||
# NV12: Y plane (stride[0]) followed immediately by interleaved UV plane (stride[1]).
|
||||
y_size = int(info.stride[0]) * height
|
||||
uv_size = int(info.stride[1]) * (height // 2)
|
||||
raw = buffer.extract_dup(0, buffer.get_size())
|
||||
pixels = raw[:y_size]
|
||||
uv_pixels = raw[y_size:y_size + uv_size]
|
||||
pitch = int(info.stride[0])
|
||||
# Store the WHOLE raw buffer in pixels without slicing — slicing bytes
|
||||
# creates two extra copies (2 MB + 1 MB) that we can avoid. render()
|
||||
# uses a single from_buffer_copy of the full buffer and ctypes.byref to
|
||||
# address the UV plane at the y_size byte offset.
|
||||
pitch = int(info.stride[0])
|
||||
uv_pitch = int(info.stride[1])
|
||||
y_size = pitch * height
|
||||
raw = buffer.extract_dup(0, buffer.get_size())
|
||||
if self._frame_count == 0:
|
||||
buf_total = buffer.get_size()
|
||||
log.info(
|
||||
"First NV12 frame: %dx%d y_pitch=%d uv_pitch=%d "
|
||||
"y_size=%d uv_size=%d buf_total=%d",
|
||||
width, height, pitch, uv_pitch, y_size, uv_size, buf_total,
|
||||
"y_size=%d buf_total=%d",
|
||||
width, height, pitch, uv_pitch, y_size, len(raw),
|
||||
)
|
||||
frame = _Frame(
|
||||
width=width, height=height,
|
||||
pitch=pitch, pixels=pixels,
|
||||
pitch=pitch, pixels=raw,
|
||||
pixel_format="NV12",
|
||||
uv_pixels=uv_pixels, uv_pitch=uv_pitch,
|
||||
y_size=y_size, uv_pitch=uv_pitch,
|
||||
)
|
||||
else:
|
||||
pitch = int(info.stride[0]) if info.stride else width * 4
|
||||
|
||||
Reference in New Issue
Block a user