From bb0ac90c96b11e55d223162e4d654c19e00b5cfb Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Tue, 24 Mar 2026 00:26:17 +0100 Subject: [PATCH] feat: add detailed logging for NV12 frames, GStreamer warnings, and render errors --- .../player/gstreamer_backend.py | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/src/r36s_dlna_browser/player/gstreamer_backend.py b/src/r36s_dlna_browser/player/gstreamer_backend.py index d6ffe8b..0bf58bc 100644 --- a/src/r36s_dlna_browser/player/gstreamer_backend.py +++ b/src/r36s_dlna_browser/player/gstreamer_backend.py @@ -7,6 +7,7 @@ import logging import os import threading import time +import traceback from dataclasses import dataclass from typing import Any, Callable @@ -192,6 +193,7 @@ class GStreamerBackend(PlayerBackend): self._texture_format: str = "BGRA" self._resolution = "" self._hw_decoders: list | None = None # None = not yet probed + self._frame_count = 0 # total frames decoded def attach_window(self, window: object) -> None: self._window = window @@ -246,19 +248,38 @@ class GStreamerBackend(PlayerBackend): # SDL_UpdateNVTexture avoids a full BGRA conversion on CPU. # ctypes.create_string_buffer returns c_char_Array_N; cast to # LP_c_ubyte which SDL_UpdateNVTexture requires. - _ubyte_p = ctypes.POINTER(ctypes.c_ubyte) - y_buf = ctypes.cast(ctypes.create_string_buffer(frame.pixels), _ubyte_p) - uv_buf = ctypes.cast(ctypes.create_string_buffer(frame.uv_pixels), _ubyte_p) - result = sdl2.SDL_UpdateNVTexture( - self._texture, None, - y_buf, frame.pitch, - uv_buf, frame.uv_pitch, - ) + try: + _ubyte_p = ctypes.POINTER(ctypes.c_ubyte) + y_data = bytes(frame.pixels) + uv_data = bytes(frame.uv_pixels) + y_buf = ctypes.cast(ctypes.create_string_buffer(y_data), _ubyte_p) + uv_buf = ctypes.cast(ctypes.create_string_buffer(uv_data), _ubyte_p) + 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: pixel_buffer = ctypes.create_string_buffer(frame.pixels) result = sdl2.SDL_UpdateTexture(self._texture, None, pixel_buffer, frame.pitch) 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 self._frame_dirty = False @@ -446,6 +467,13 @@ class GStreamerBackend(PlayerBackend): uv_pixels = raw[y_size:y_size + uv_size] pitch = int(info.stride[0]) 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( width=width, height=height, pitch=pitch, pixels=pixels, @@ -455,13 +483,24 @@ class GStreamerBackend(PlayerBackend): else: pitch = int(info.stride[0]) if info.stride else width * 4 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) 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._frame_dirty = True - if resolution != self._resolution: + if resolution != prev_res: self._resolution = resolution + if prev_res: + log.info("Resolution changed: %s -> %s", prev_res, resolution) self._event_callback("resolution", resolution) return self._flow_ok() @@ -495,6 +534,7 @@ class GStreamerBackend(PlayerBackend): message = bus.timed_pop_filtered( 100 * self._gst.MSECOND, self._gst.MessageType.ERROR + | self._gst.MessageType.WARNING | self._gst.MessageType.EOS | self._gst.MessageType.BUFFERING | self._gst.MessageType.STATE_CHANGED, @@ -517,6 +557,14 @@ class GStreamerBackend(PlayerBackend): self._set_playing(False, notify=True) 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: err, debug = message.parse_error() text = err.message if hasattr(err, "message") else str(err) @@ -592,6 +640,7 @@ class GStreamerBackend(PlayerBackend): self._latest_frame = None self._frame_dirty = False self._resolution = "" + self._frame_count = 0 self._destroy_texture() def _set_playing(self, value: bool, notify: bool) -> None: