small optimizations + allow for users to force software H264 since it's not slow as balls
This commit is contained in:
@@ -494,10 +494,11 @@ var (
|
||||
statBlockRegions atomic.Int64
|
||||
statBlockFallbacks atomic.Int64
|
||||
|
||||
overrideQuality atomic.Int64
|
||||
overrideCodec atomic.Value
|
||||
h264WarnOnce sync.Once
|
||||
codecLogOnce sync.Once
|
||||
overrideQuality atomic.Int64
|
||||
overrideCodec atomic.Value
|
||||
desktopSoftwareH264 atomic.Bool
|
||||
h264WarnOnce sync.Once
|
||||
codecLogOnce sync.Once
|
||||
|
||||
prevMu sync.Mutex
|
||||
prevFrame *prevImage
|
||||
@@ -627,6 +628,19 @@ func RequestDesktopFullFrame() {
|
||||
RequestDesktopH264Keyframe()
|
||||
}
|
||||
|
||||
func SetDesktopSoftwareH264(enabled bool) {
|
||||
if desktopSoftwareH264.Swap(enabled) == enabled {
|
||||
return
|
||||
}
|
||||
resetH264Encoder()
|
||||
requestFullFrames(2)
|
||||
log.Printf("capture: desktop software h264 %v", enabled)
|
||||
}
|
||||
|
||||
func useDesktopSoftwareH264() bool {
|
||||
return desktopSoftwareH264.Load()
|
||||
}
|
||||
|
||||
func RequestHVNCFullFrame() {
|
||||
hvncPrevMu.Lock()
|
||||
hvncPrevFrame = nil
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func tryBuildDirectH264Frame(display int) (wire.Frame, time.Duration, time.Duration, bool, error) {
|
||||
if blockCodec() != "h264" || !useDesktopDuplication() {
|
||||
if blockCodec() != "h264" || useDesktopSoftwareH264() || !useDesktopDuplication() {
|
||||
return wire.Frame{}, 0, 0, false, nil
|
||||
}
|
||||
forceKeyframe := webrtcpub.ConsumeKeyframeRequest()
|
||||
|
||||
@@ -547,6 +547,10 @@ func closeH264Encoder(slot *h264FrameEncoder) {
|
||||
}
|
||||
|
||||
func newWindowsH264Encoder(stream string, width, height, fps int) (h264FrameEncoder, error) {
|
||||
if stream == "desktop" && useDesktopSoftwareH264() {
|
||||
log.Printf("capture: desktop software h264 requested; skipping native NVENC and Media Foundation hardware encoders")
|
||||
return newMFSoftwareH264Encoder(width, height, fps)
|
||||
}
|
||||
if enc, err := newNativeH264Encoder(stream, width, height, fps); err == nil {
|
||||
return enc, nil
|
||||
} else {
|
||||
@@ -592,6 +596,21 @@ func newMFH264Encoder(width, height, fps int) (*mfH264Encoder, error) {
|
||||
if fallbackErr != nil {
|
||||
return nil, fallbackErr
|
||||
}
|
||||
return newMFH264EncoderFromSoftwareTransform(transform, fallbackHardware, fallbackProvider, width, height, fps)
|
||||
}
|
||||
|
||||
func newMFSoftwareH264Encoder(width, height, fps int) (*mfH264Encoder, error) {
|
||||
if err := ensureMFStartup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transform, fallbackHardware, fallbackProvider, fallbackErr := createSoftwareH264Transform()
|
||||
if fallbackErr != nil {
|
||||
return nil, fallbackErr
|
||||
}
|
||||
return newMFH264EncoderFromSoftwareTransform(transform, fallbackHardware, fallbackProvider, width, height, fps)
|
||||
}
|
||||
|
||||
func newMFH264EncoderFromSoftwareTransform(transform *mfTransform, fallbackHardware bool, fallbackProvider string, width, height, fps int) (*mfH264Encoder, error) {
|
||||
enc := newMFH264EncoderFromTransform(transform, width, height, fps, fps, fallbackHardware, fallbackProvider, MFVideoFormat_NV12, "NV12")
|
||||
if err := enc.configure(); err != nil {
|
||||
enc.Close()
|
||||
|
||||
@@ -2,23 +2,38 @@ package capture
|
||||
|
||||
import (
|
||||
"image"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var rgbaPool sync.Pool
|
||||
const (
|
||||
maxPooledRGBABuffers = 8
|
||||
maxPooledRGBABytes = 64 << 20
|
||||
maxTotalRGBABytes = 192 << 20
|
||||
)
|
||||
|
||||
var (
|
||||
rgbaPool = make(chan []byte, maxPooledRGBABuffers)
|
||||
pooledRGBABytes atomic.Int64
|
||||
)
|
||||
|
||||
func GetRGBA(w, h int) *image.RGBA {
|
||||
need := w * h * 4
|
||||
if need <= 0 {
|
||||
return image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
}
|
||||
if v := rgbaPool.Get(); v != nil {
|
||||
if buf, ok := v.([]byte); ok && cap(buf) >= need {
|
||||
return &image.RGBA{
|
||||
Pix: buf[:need],
|
||||
Stride: w * 4,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
for attempts := len(rgbaPool); attempts > 0; attempts-- {
|
||||
select {
|
||||
case buf := <-rgbaPool:
|
||||
pooledRGBABytes.Add(-int64(cap(buf)))
|
||||
if cap(buf) >= need && cap(buf) <= maxReusableCapacity(need) {
|
||||
return &image.RGBA{
|
||||
Pix: buf[:need],
|
||||
Stride: w * 4,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
}
|
||||
}
|
||||
default:
|
||||
attempts = 0
|
||||
}
|
||||
}
|
||||
return image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
@@ -28,5 +43,34 @@ func PutRGBA(img *image.RGBA) {
|
||||
if img == nil || len(img.Pix) == 0 {
|
||||
return
|
||||
}
|
||||
rgbaPool.Put(img.Pix)
|
||||
buf := img.Pix
|
||||
capacity := cap(buf)
|
||||
if capacity <= 0 || capacity > maxPooledRGBABytes {
|
||||
return
|
||||
}
|
||||
for {
|
||||
cur := pooledRGBABytes.Load()
|
||||
if cur+int64(capacity) > maxTotalRGBABytes {
|
||||
return
|
||||
}
|
||||
if pooledRGBABytes.CompareAndSwap(cur, cur+int64(capacity)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
select {
|
||||
case rgbaPool <- buf:
|
||||
default:
|
||||
pooledRGBABytes.Add(-int64(capacity))
|
||||
}
|
||||
}
|
||||
|
||||
func maxReusableCapacity(need int) int {
|
||||
limit := need * 2
|
||||
if limit < need+(1<<20) {
|
||||
limit = need + (1 << 20)
|
||||
}
|
||||
if limit > maxPooledRGBABytes {
|
||||
limit = maxPooledRGBABytes
|
||||
}
|
||||
return limit
|
||||
}
|
||||
|
||||
@@ -687,7 +687,9 @@ func (s *duplicationState) closeLocked() {
|
||||
s.bounds = image.Rectangle{}
|
||||
s.cursorBounds = image.Rectangle{}
|
||||
s.display = -1
|
||||
PutRGBA(s.lastBase)
|
||||
s.lastBase = nil
|
||||
PutRGBA(s.lastFrame)
|
||||
s.lastFrame = nil
|
||||
s.lastFrameAt = time.Time{}
|
||||
s.cursorScratch = nil
|
||||
@@ -807,11 +809,15 @@ func (s *duplicationState) capture(display int) (*image.RGBA, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Cache an independent clone so the timeout path (which serves the cached
|
||||
// Cache an independent copy so the timeout path (which serves the cached
|
||||
// frame back to callers) doesn't alias a buffer the caller will return to
|
||||
// the RGBA pool via PutRGBA.
|
||||
s.lastBase = cloneRGBA(img)
|
||||
img = s.composeFrame(img, width, height)
|
||||
captured := img
|
||||
s.copyLastBaseLocked(captured)
|
||||
img = s.composeFrame(captured, width, height)
|
||||
if img != captured {
|
||||
PutRGBA(captured)
|
||||
}
|
||||
if img == s.cursorScratch {
|
||||
// cursorScratch is reused across captures; clone before returning so
|
||||
// the next compose doesn't write into a buffer the caller has pooled.
|
||||
@@ -823,6 +829,26 @@ func (s *duplicationState) capture(display int) (*image.RGBA, error) {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (s *duplicationState) copyLastBaseLocked(src *image.RGBA) {
|
||||
if src == nil {
|
||||
PutRGBA(s.lastBase)
|
||||
s.lastBase = nil
|
||||
return
|
||||
}
|
||||
width := src.Rect.Dx()
|
||||
height := src.Rect.Dy()
|
||||
need := len(src.Pix)
|
||||
if s.lastBase != nil && cap(s.lastBase.Pix) >= need && cap(s.lastBase.Pix) <= need*2 {
|
||||
s.lastBase.Pix = s.lastBase.Pix[:need]
|
||||
s.lastBase.Stride = width * 4
|
||||
s.lastBase.Rect = image.Rect(0, 0, width, height)
|
||||
copy(s.lastBase.Pix, src.Pix)
|
||||
return
|
||||
}
|
||||
PutRGBA(s.lastBase)
|
||||
s.lastBase = cloneRGBA(src)
|
||||
}
|
||||
|
||||
func (s *duplicationState) captureH264(display int, forceKeyframe bool) ([]byte, int, int, time.Duration, time.Duration, bool, error) {
|
||||
capStart := time.Now()
|
||||
s.mu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user