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
|
||||
|
||||
@@ -11,7 +11,11 @@ mppvideodec auto-selected) and reports:
|
||||
• A/V sync drift (video PTS vs pipeline clock position)
|
||||
• from_buffer_copy() time per frame (CPU copy cost)
|
||||
|
||||
Run on device:
|
||||
Run on device (must use same env as the app):
|
||||
export LD_LIBRARY_PATH=/home/ark/miniconda3/envs/r36s-dlna-browser/lib
|
||||
export GST_PLUGIN_PATH=/usr/lib/aarch64-linux-gnu/gstreamer-1.0
|
||||
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libgomp.so.1
|
||||
export PYTHONPATH=/home/ark/R36SHack/src
|
||||
/home/ark/miniconda3/envs/r36s-dlna-browser/bin/python \\
|
||||
/home/ark/R36SHack/tests/benchmark_nv12_decode.py [URL]
|
||||
|
||||
@@ -186,12 +190,13 @@ def _on_sample(sink) -> Gst.FlowReturn:
|
||||
|
||||
if fmt_str == "NV12":
|
||||
y_size = int(info.stride[0]) * int(info.height)
|
||||
uv_size = int(info.stride[1]) * (int(info.height) // 2)
|
||||
# Simulate named c_ubyte array creation (the actual upload path)
|
||||
# Simulate the single from_buffer_copy the app now does:
|
||||
# ONE copy of the full Y+UV buffer, then ctypes.byref for the UV offset.
|
||||
t1 = time.monotonic()
|
||||
y_arr = (ctypes.c_ubyte * len(raw[:y_size])).from_buffer_copy(raw[:y_size])
|
||||
uv_arr = (ctypes.c_ubyte * len(raw[y_size:y_size+uv_size])).from_buffer_copy(raw[y_size:y_size+uv_size])
|
||||
copy_us = (time.monotonic() - t1) * 1e6 # override with NV12-specific copy
|
||||
arr = (ctypes.c_ubyte * len(raw)).from_buffer_copy(raw)
|
||||
y_ptr = ctypes.cast(arr, ctypes.POINTER(ctypes.c_ubyte))
|
||||
uv_ptr = ctypes.cast(ctypes.byref(arr, y_size), ctypes.POINTER(ctypes.c_ubyte))
|
||||
copy_us = (time.monotonic() - t1) * 1e6
|
||||
|
||||
with stats.lock:
|
||||
stats.total_frames += 1
|
||||
|
||||
Reference in New Issue
Block a user