157 lines
5.7 KiB
Go
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)
|
|
}
|