feat: add detailed logging for NV12 frames, GStreamer warnings, and render errors
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user