Files
rdplib/plugin/rdpgfx/h264_decoder.go
T

157 lines
5.7 KiB
Go

package rdpgfx
import "time"
// H264BrokenReason describes why a decoder became unrecoverable.
type H264BrokenReason int
const (
H264BrokenReasonNone H264BrokenReason = iota
H264BrokenReasonInitFailure
H264BrokenReasonHWStall
H264BrokenReasonNoIDR
)
func (r H264BrokenReason) String() string {
switch r {
case H264BrokenReasonInitFailure:
return "init-failure"
case H264BrokenReasonHWStall:
return "hw-stall"
case H264BrokenReasonNoIDR:
return "no-idr"
default:
return "none"
}
}
// H264Frame holds a decoded H.264 frame in BGRA pixel format.
type H264Frame struct {
Data []byte // BGRA pixel data, 4 bytes per pixel (nil when Dropped)
Width, Height int
// Dropped is true when the decoder intentionally discarded this frame
// (e.g. zero-filled VideoToolbox IOSurface) rather than experiencing a
// genuine codec stall. Callers must skip bitmap updates but must NOT
// request a keyframe or flag the decoder as broken.
Dropped bool
}
// H264FrameI420 holds a decoded H.264 frame in planar I420 (YUV420P) format.
// SDL2 can render I420 natively via hardware-accelerated YUV→RGB shaders using
// a PIXELFORMAT_IYUV texture, eliminating CPU-side colour conversion.
// Plane slices borrow ring-buffer memory; the caller must copy all slices
// before the next Decode call.
type H264FrameI420 struct {
Y, U, V []byte
YStride, UStride, VStride int
Width, Height int
FullRange bool // true when the source used full-range (JPEG/PC) YUV
}
// H264FrameNV12 holds a decoded H.264 frame in NV12 format (Y plane plus
// interleaved UV plane). VideoToolbox commonly transfers hardware-decoded
// H.264 frames as NV12; SDL2 can upload NV12 directly, avoiding the CPU-side
// NV12->I420 deinterleave needed by the I420 path.
type H264FrameNV12 struct {
Y, UV []byte
YStride int
UVStride int
Width int
Height int
FullRange bool
}
// I420Decoder is an optional interface that an H264Decoder may implement to
// produce I420 output alongside the normal BGRA frame. Callers detect support
// via a type assertion.
type I420Decoder interface {
// DecodeWithI420 decodes H.264 NAL data and returns both a BGRA frame
// and an optional I420 frame for GPU-accelerated rendering. The I420
// frame is nil when the pixel format is not directly convertible;
// callers must fall back to the BGRA frame in that case.
DecodeWithI420(h264Data []byte) (*H264Frame, *H264FrameI420, error)
}
// NV12Decoder is an optional interface for decoders that can expose native
// NV12 output. Callers should fall back to I420 or BGRA when the returned
// NV12 frame is nil.
type NV12Decoder interface {
DecodeWithNV12(h264Data []byte) (*H264Frame, *H264FrameNV12, error)
}
// RegionHinter is an optional interface implemented by decoders that support
// region-aware YUV→BGRA conversion. When SetRegionHint is called immediately
// before Decode, the decoder only converts pixels within the specified dirty
// rectangles, skipping unchanged areas of the frame.
// Each element of rects is [left, top, right, bottom].
type RegionHinter interface {
SetRegionHint(rects [][4]uint16)
}
// H264Decoder decodes H.264 Annex B bitstream data into BGRA frames.
type H264Decoder interface {
// Decode decodes H.264 NAL units and returns a decoded frame.
// Returns nil frame (no error) when the decoder needs more input data.
Decode(h264Data []byte) (*H264Frame, error)
// NeedsKeyframe reports whether the decoder is waiting for a keyframe.
NeedsKeyframe() bool
// NeedsIDR reports whether the decoder is explicitly waiting for an IDR frame.
NeedsIDR() bool
// IsBroken reports whether the decoder is permanently unrecoverable.
IsBroken() bool
// BrokenReason reports why the decoder became unrecoverable.
BrokenReason() H264BrokenReason
// ForceBroken marks the decoder unrecoverable for the given reason.
ForceBroken(reason H264BrokenReason)
// HardResetCount returns the number of hard resets performed so far.
HardResetCount() int
// LastReceiveTime returns the wall-clock time of the most recent Decode() call.
LastReceiveTime() time.Time
// Close releases all resources held by the decoder.
Close()
}
// H264DecoderBackend holds factory functions for creating H264Decoder instances.
// Register a backend via SetH264Backend before starting any RDP session.
// Typically called from an init() function in the application binary.
type H264DecoderBackend struct {
// NewHW creates a hardware-preferred decoder with an optional watchdog channel.
// watchdogCh may be nil for the initial decoder (no watchdog).
NewHW func(watchdogCh chan<- struct{}) H264Decoder
// NewSW creates a software-only decoder without a watchdog (for aux decoders).
NewSW func() H264Decoder
// NewSWFallback creates a software-only decoder with a watchdog
// (used as the post-VideoToolbox-stall fallback decoder).
NewSWFallback func(watchdogCh chan<- struct{}) H264Decoder
}
var h264Backend *H264DecoderBackend
// SetH264Backend registers the H.264 decoder backend.
// Must be called before any RDP session is started.
// When the h264 build tag is set, example/h264_ffmpeg.go calls this in its init().
func SetH264Backend(b *H264DecoderBackend) { h264Backend = b }
func newH264Decoder() H264Decoder { return newH264DecoderWithWatchdog(nil) }
func newH264DecoderWithWatchdog(ch chan<- struct{}) H264Decoder {
if h264Backend == nil || h264Backend.NewHW == nil {
return nil
}
return h264Backend.NewHW(ch)
}
func newH264DecoderSW() H264Decoder {
if h264Backend == nil || h264Backend.NewSW == nil {
return nil
}
return h264Backend.NewSW()
}
func newH264DecoderSWWithWatchdog(ch chan<- struct{}) H264Decoder {
if h264Backend == nil || h264Backend.NewSWFallback == nil {
return nil
}
return h264Backend.NewSWFallback(ch)
}