feat: add detailed logging for NV12 frames, GStreamer warnings, and render errors

This commit is contained in:
Matteo Benedetto
2026-03-24 00:26:17 +01:00
parent 0c8e2c2a11
commit bb0ac90c96
@@ -7,6 +7,7 @@ import logging
import os import os
import threading import threading
import time import time
import traceback
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Callable from typing import Any, Callable
@@ -192,6 +193,7 @@ class GStreamerBackend(PlayerBackend):
self._texture_format: str = "BGRA" self._texture_format: str = "BGRA"
self._resolution = "" self._resolution = ""
self._hw_decoders: list | None = None # None = not yet probed self._hw_decoders: list | None = None # None = not yet probed
self._frame_count = 0 # total frames decoded
def attach_window(self, window: object) -> None: def attach_window(self, window: object) -> None:
self._window = window self._window = window
@@ -246,19 +248,38 @@ class GStreamerBackend(PlayerBackend):
# SDL_UpdateNVTexture avoids a full BGRA conversion on CPU. # SDL_UpdateNVTexture avoids a full BGRA conversion on CPU.
# ctypes.create_string_buffer returns c_char_Array_N; cast to # ctypes.create_string_buffer returns c_char_Array_N; cast to
# LP_c_ubyte which SDL_UpdateNVTexture requires. # LP_c_ubyte which SDL_UpdateNVTexture requires.
_ubyte_p = ctypes.POINTER(ctypes.c_ubyte) try:
y_buf = ctypes.cast(ctypes.create_string_buffer(frame.pixels), _ubyte_p) _ubyte_p = ctypes.POINTER(ctypes.c_ubyte)
uv_buf = ctypes.cast(ctypes.create_string_buffer(frame.uv_pixels), _ubyte_p) y_data = bytes(frame.pixels)
result = sdl2.SDL_UpdateNVTexture( uv_data = bytes(frame.uv_pixels)
self._texture, None, y_buf = ctypes.cast(ctypes.create_string_buffer(y_data), _ubyte_p)
y_buf, frame.pitch, uv_buf = ctypes.cast(ctypes.create_string_buffer(uv_data), _ubyte_p)
uv_buf, frame.uv_pitch, log.debug(
) "SDL_UpdateNVTexture: %dx%d y_len=%d uv_len=%d pitch=%d uv_pitch=%d",
frame.width, frame.height,
len(y_data), len(uv_data),
frame.pitch, frame.uv_pitch,
)
result = sdl2.SDL_UpdateNVTexture(
self._texture, None,
y_buf, frame.pitch,
uv_buf, frame.uv_pitch,
)
except Exception:
log.error(
"NV12 texture upload exception (frame %d): %s",
self._frame_count,
traceback.format_exc(),
)
return False
else: else:
pixel_buffer = ctypes.create_string_buffer(frame.pixels) pixel_buffer = ctypes.create_string_buffer(frame.pixels)
result = sdl2.SDL_UpdateTexture(self._texture, None, pixel_buffer, frame.pitch) result = sdl2.SDL_UpdateTexture(self._texture, None, pixel_buffer, frame.pitch)
if result != 0: if result != 0:
log.error("Could not upload SDL video texture: %s", sdl2.SDL_GetError()) log.error(
"Could not upload SDL video texture (frame %d fmt=%s): %s",
self._frame_count, frame.pixel_format, sdl2.SDL_GetError(),
)
return False return False
self._frame_dirty = False self._frame_dirty = False
@@ -446,6 +467,13 @@ class GStreamerBackend(PlayerBackend):
uv_pixels = raw[y_size:y_size + uv_size] uv_pixels = raw[y_size:y_size + uv_size]
pitch = int(info.stride[0]) pitch = int(info.stride[0])
uv_pitch = int(info.stride[1]) uv_pitch = int(info.stride[1])
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,
)
frame = _Frame( frame = _Frame(
width=width, height=height, width=width, height=height,
pitch=pitch, pixels=pixels, pitch=pitch, pixels=pixels,
@@ -455,13 +483,24 @@ class GStreamerBackend(PlayerBackend):
else: else:
pitch = int(info.stride[0]) if info.stride else width * 4 pitch = int(info.stride[0]) if info.stride else width * 4
pixels = buffer.extract_dup(0, buffer.get_size()) pixels = buffer.extract_dup(0, buffer.get_size())
if self._frame_count == 0:
log.info(
"First %s frame: %dx%d pitch=%d buf_total=%d",
fmt_str, width, height, pitch, buffer.get_size(),
)
frame = _Frame(width=width, height=height, pitch=pitch, pixels=pixels) frame = _Frame(width=width, height=height, pitch=pitch, pixels=pixels)
with self._frame_lock: with self._frame_lock:
self._frame_count += 1
if self._frame_count <= 3 or self._frame_count % 300 == 0:
log.debug("Frame #%d fmt=%s %dx%d", self._frame_count, fmt_str, width, height)
prev_res = self._resolution
self._latest_frame = frame self._latest_frame = frame
self._frame_dirty = True self._frame_dirty = True
if resolution != self._resolution: if resolution != prev_res:
self._resolution = resolution self._resolution = resolution
if prev_res:
log.info("Resolution changed: %s -> %s", prev_res, resolution)
self._event_callback("resolution", resolution) self._event_callback("resolution", resolution)
return self._flow_ok() return self._flow_ok()
@@ -495,6 +534,7 @@ class GStreamerBackend(PlayerBackend):
message = bus.timed_pop_filtered( message = bus.timed_pop_filtered(
100 * self._gst.MSECOND, 100 * self._gst.MSECOND,
self._gst.MessageType.ERROR self._gst.MessageType.ERROR
| self._gst.MessageType.WARNING
| self._gst.MessageType.EOS | self._gst.MessageType.EOS
| self._gst.MessageType.BUFFERING | self._gst.MessageType.BUFFERING
| self._gst.MessageType.STATE_CHANGED, | self._gst.MessageType.STATE_CHANGED,
@@ -517,6 +557,14 @@ class GStreamerBackend(PlayerBackend):
self._set_playing(False, notify=True) self._set_playing(False, notify=True)
return return
if msg_type == self._gst.MessageType.WARNING:
err, debug = message.parse_warning()
text = err.message if hasattr(err, "message") else str(err)
if debug:
text = f"{text} ({debug})"
log.warning("GStreamer playback warning: %s", text)
return
if msg_type == self._gst.MessageType.ERROR: if msg_type == self._gst.MessageType.ERROR:
err, debug = message.parse_error() err, debug = message.parse_error()
text = err.message if hasattr(err, "message") else str(err) text = err.message if hasattr(err, "message") else str(err)
@@ -592,6 +640,7 @@ class GStreamerBackend(PlayerBackend):
self._latest_frame = None self._latest_frame = None
self._frame_dirty = False self._frame_dirty = False
self._resolution = "" self._resolution = ""
self._frame_count = 0
self._destroy_texture() self._destroy_texture()
def _set_playing(self, value: bool, notify: bool) -> None: def _set_playing(self, value: bool, notify: bool) -> None: