major remote desktop improvements
This commit is contained in:
@@ -46,7 +46,8 @@ func (b *amfD3D11TextureBackend) Encode(req h264D3D11TextureRequest) ([]byte, er
|
||||
b.device, b.inputWidth, b.inputHeight = req.Device, req.InputWidth, req.InputHeight
|
||||
b.encodeWidth, b.encodeHeight, b.fps, b.dxgiFormat = req.EncodeWidth, req.EncodeHeight, req.FPS, req.DXGIFormat
|
||||
}
|
||||
forceIDR := req.ForceIDR || b.forceIDR.Swap(false)
|
||||
pendingIDR := b.forceIDR.Swap(false)
|
||||
forceIDR := req.ForceIDR || pendingIDR
|
||||
for attempts := 0; attempts < 2; attempts++ {
|
||||
result, outputSize, err := b.encoder.Encode(req.Texture, forceIDR, b.output)
|
||||
if err != nil {
|
||||
|
||||
@@ -108,19 +108,41 @@ func CaptureAndSend(ctx context.Context, env *rt.Env) error {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
codec := desktopCodec()
|
||||
directCanvasVideo := (codec == "h264" || codec == "hevc") &&
|
||||
(codec != "h264" || !useDesktopSoftwareH264()) &&
|
||||
useDesktopDuplication() &&
|
||||
!(codec == "h264" && webrtcpub.IsActive(webrtcpub.KindDesktop))
|
||||
directSlotAcquired := false
|
||||
if directCanvasVideo {
|
||||
if !AcquireFrameSlot() {
|
||||
return nil
|
||||
}
|
||||
directSlotAcquired = true
|
||||
}
|
||||
|
||||
if frame, captureDur, encodeDur, used, err := tryBuildDirectH264Frame(display); used {
|
||||
if err != nil {
|
||||
if directSlotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
directSlotAcquired = false
|
||||
}
|
||||
suppressDesktopHardwareH264(err)
|
||||
log.Printf("capture: direct video encode failed: %v; falling back to the software H.264 path", err)
|
||||
} else {
|
||||
emitDesktopCursor(ctx, env, display, frame.Header.Width, frame.Header.Height)
|
||||
if len(frame.Data) == 0 {
|
||||
if directSlotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
consecutiveCaptureFails.Store(0)
|
||||
return nil
|
||||
}
|
||||
consecutiveCaptureFails.Store(0)
|
||||
return sendCompletedFrame(ctx, env, frame, display, t0, captureDur, encodeDur)
|
||||
return sendCompletedFrame(ctx, env, frame, display, t0, captureDur, encodeDur, directSlotAcquired)
|
||||
}
|
||||
} else if directSlotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
|
||||
img, err := safeCaptureDisplay(display)
|
||||
@@ -247,7 +269,7 @@ func CaptureAndSend(ctx context.Context, env *rt.Env) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func sendCompletedFrame(ctx context.Context, env *rt.Env, frame wire.Frame, display int, t0 time.Time, captureDur, encodeDur time.Duration) error {
|
||||
func sendCompletedFrame(ctx context.Context, env *rt.Env, frame wire.Frame, display int, t0 time.Time, captureDur, encodeDur time.Duration, slotAcquired bool) error {
|
||||
now := time.Now()
|
||||
fps := frameFPS(now)
|
||||
if fps <= 0 {
|
||||
@@ -255,6 +277,9 @@ func sendCompletedFrame(ctx context.Context, env *rt.Env, frame wire.Frame, disp
|
||||
}
|
||||
frame.Header.FPS = fps
|
||||
if ctx.Err() != nil {
|
||||
if slotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindDesktop) {
|
||||
@@ -274,10 +299,16 @@ func sendCompletedFrame(ctx context.Context, env *rt.Env, frame wire.Frame, disp
|
||||
statTotalNs.Add(totalDur.Nanoseconds())
|
||||
statBytes.Add(int64(len(frame.Data)))
|
||||
emitDesktopStreamStats(ctx, env, frame, fps, captureDur, encodeDur, sendDur, totalDur, "webrtc")
|
||||
if slotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !AcquireFrameSlot() {
|
||||
return nil
|
||||
if !slotAcquired {
|
||||
if !AcquireFrameSlot() {
|
||||
return nil
|
||||
}
|
||||
slotAcquired = true
|
||||
}
|
||||
sendStart := time.Now()
|
||||
err := wire.WriteMsg(ctx, env.Conn, frame)
|
||||
@@ -675,6 +706,8 @@ func SetDesktopSoftwareH264(enabled bool) {
|
||||
}
|
||||
desktopHardwareFallbackUntil.Store(0)
|
||||
resetH264Encoder()
|
||||
lastKeyframe.Store(0)
|
||||
webrtcpub.RequestKeyframe(webrtcpub.KindDesktop)
|
||||
requestFullFrames(2)
|
||||
log.Printf("capture: desktop software h264 %v", enabled)
|
||||
}
|
||||
@@ -796,6 +829,25 @@ const (
|
||||
backstageChangeThresh = 1
|
||||
)
|
||||
|
||||
var videoKeyframeEvery = configuredVideoKeyframeInterval()
|
||||
|
||||
func configuredVideoKeyframeInterval() time.Duration {
|
||||
raw := strings.TrimSpace(os.Getenv("OVERLORD_VIDEO_KEYFRAME_INTERVAL"))
|
||||
if raw == "" || strings.EqualFold(raw, "off") || raw == "0" {
|
||||
return 0
|
||||
}
|
||||
if interval, err := time.ParseDuration(raw); err == nil {
|
||||
if interval > 0 {
|
||||
return interval
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if seconds, err := strconv.Atoi(raw); err == nil && seconds > 0 {
|
||||
return time.Duration(seconds) * time.Second
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func buildFrame(img *image.RGBA, display int, quality int) (wire.Frame, time.Duration, error) {
|
||||
encStart := time.Now()
|
||||
width := img.Bounds().Dx()
|
||||
@@ -808,15 +860,22 @@ func buildFrame(img *image.RGBA, display int, quality int) (wire.Frame, time.Dur
|
||||
log.Printf("capture: hevc skipped for odd dimensions (%dx%d), falling back to h264/jpeg", width, height)
|
||||
codec = "h264"
|
||||
} else {
|
||||
if webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop) {
|
||||
requestedKeyframe := webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop)
|
||||
if requestedKeyframe || lastKeyframe.Load() == 0 ||
|
||||
(videoKeyframeEvery > 0 && now.Sub(time.Unix(0, lastKeyframe.Load())) >= videoKeyframeEvery) {
|
||||
resetHEVCEncoder()
|
||||
}
|
||||
hevcBytes, err := encodeHEVCFrame(img)
|
||||
if err == nil && len(hevcBytes) > 0 {
|
||||
lastKeyframe.Store(now.UnixNano())
|
||||
if retainKeyframeRequestUntilOutput(webrtcpub.KindDesktop, requestedKeyframe, "hevc", hevcBytes) {
|
||||
lastKeyframe.Store(now.UnixNano())
|
||||
}
|
||||
statFullFrames.Add(1)
|
||||
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "hevc"}, Data: hevcBytes}, time.Since(encStart), nil
|
||||
}
|
||||
if requestedKeyframe {
|
||||
webrtcpub.RequestKeyframe(webrtcpub.KindDesktop)
|
||||
}
|
||||
hevcWarnOnce.Do(func() {
|
||||
log.Printf("capture: hevc encode unavailable, falling back to h264/jpeg: %v (%s)", err, hevcAvailabilityDetail())
|
||||
})
|
||||
@@ -836,15 +895,22 @@ func buildFrame(img *image.RGBA, display int, quality int) (wire.Frame, time.Dur
|
||||
log.Printf("capture: h264 skipped for odd dimensions (%dx%d), falling back to jpeg", width, height)
|
||||
codec = "jpeg"
|
||||
} else {
|
||||
if webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop) {
|
||||
requestedKeyframe := webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop)
|
||||
if requestedKeyframe || lastKeyframe.Load() == 0 ||
|
||||
(videoKeyframeEvery > 0 && now.Sub(time.Unix(0, lastKeyframe.Load())) >= videoKeyframeEvery) {
|
||||
resetH264Encoder()
|
||||
}
|
||||
h264Bytes, err := encodeH264Frame(img)
|
||||
if err == nil && len(h264Bytes) > 0 {
|
||||
lastKeyframe.Store(now.UnixNano())
|
||||
if retainKeyframeRequestUntilOutput(webrtcpub.KindDesktop, requestedKeyframe, "h264", h264Bytes) {
|
||||
lastKeyframe.Store(now.UnixNano())
|
||||
}
|
||||
statFullFrames.Add(1)
|
||||
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "h264"}, Data: h264Bytes}, time.Since(encStart), nil
|
||||
}
|
||||
if requestedKeyframe {
|
||||
webrtcpub.RequestKeyframe(webrtcpub.KindDesktop)
|
||||
}
|
||||
h264WarnOnce.Do(func() {
|
||||
detail := h264AvailabilityDetail()
|
||||
if detail != "" {
|
||||
@@ -941,16 +1007,23 @@ func buildFramebackstage(img *image.RGBA, display int, quality int) (wire.Frame,
|
||||
log.Printf("backstage capture: h264 skipped for odd dimensions (%dx%d), falling back to jpeg", width, height)
|
||||
codec = "jpeg"
|
||||
} else {
|
||||
if webrtcpub.ConsumeKeyframeRequest(webrtcpub.Kindbackstage) {
|
||||
requestedKeyframe := webrtcpub.ConsumeKeyframeRequest(webrtcpub.Kindbackstage)
|
||||
if requestedKeyframe || backstageLastKeyframe.Load() == 0 ||
|
||||
(videoKeyframeEvery > 0 && now.Sub(time.Unix(0, backstageLastKeyframe.Load())) >= videoKeyframeEvery) {
|
||||
resetH264Encoderbackstage()
|
||||
}
|
||||
h264Bytes, err := encodeH264Framebackstage(img)
|
||||
if err == nil && len(h264Bytes) > 0 {
|
||||
backstageLastKeyframe.Store(now.UnixNano())
|
||||
if retainKeyframeRequestUntilOutput(webrtcpub.Kindbackstage, requestedKeyframe, "h264", h264Bytes) {
|
||||
backstageLastKeyframe.Store(now.UnixNano())
|
||||
}
|
||||
statFullFrames.Add(1)
|
||||
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "h264", Backstage: true}, Data: h264Bytes}
|
||||
return frame, time.Since(encStart), nil
|
||||
}
|
||||
if requestedKeyframe {
|
||||
webrtcpub.RequestKeyframe(webrtcpub.Kindbackstage)
|
||||
}
|
||||
backstageH264WarnOnce.Do(func() {
|
||||
detail := h264AvailabilityDetail()
|
||||
if detail != "" {
|
||||
@@ -1671,6 +1744,7 @@ func backstageCodec() string {
|
||||
}
|
||||
|
||||
func SetDesktopQualityAndCodec(quality int, codec string) {
|
||||
previousCodec := desktopCodec()
|
||||
if quality > 0 {
|
||||
if quality > 100 {
|
||||
quality = 100
|
||||
@@ -1706,6 +1780,10 @@ func SetDesktopQualityAndCodec(quality int, codec string) {
|
||||
if s != "hevc" {
|
||||
resetHEVCEncoder()
|
||||
}
|
||||
if previousCodec != s && (s == "h264" || s == "hevc") {
|
||||
lastKeyframe.Store(0)
|
||||
webrtcpub.RequestKeyframe(webrtcpub.KindDesktop)
|
||||
}
|
||||
case "":
|
||||
default:
|
||||
desktopOverrideCodec.Store("jpeg")
|
||||
@@ -1715,6 +1793,7 @@ func SetDesktopQualityAndCodec(quality int, codec string) {
|
||||
}
|
||||
|
||||
func SetBackstageQualityAndCodec(quality int, codec string) {
|
||||
previousCodec := backstageCodec()
|
||||
if quality > 0 {
|
||||
if quality > 100 {
|
||||
quality = 100
|
||||
@@ -1734,6 +1813,10 @@ func SetBackstageQualityAndCodec(quality int, codec string) {
|
||||
if s != "h264" {
|
||||
resetH264Encoderbackstage()
|
||||
}
|
||||
if previousCodec != s && s == "h264" {
|
||||
backstageLastKeyframe.Store(0)
|
||||
webrtcpub.RequestKeyframe(webrtcpub.Kindbackstage)
|
||||
}
|
||||
case "":
|
||||
default:
|
||||
backstageOverrideCodec.Store("jpeg")
|
||||
|
||||
@@ -456,16 +456,16 @@ func TestSetFrameFlowTargetFPSScalesHighFPS(t *testing.T) {
|
||||
t.Setenv("OVERLORD_DESKTOP_IN_FLIGHT_FRAMES", "")
|
||||
ResetFrameSlots()
|
||||
SetFrameFlowTargetFPS(60)
|
||||
if got := activeFrameSlotLimit(); got != 2 {
|
||||
t.Fatalf("expected 60 fps slot limit 2, got %d", got)
|
||||
if got := activeFrameSlotLimit(); got != 4 {
|
||||
t.Fatalf("expected 60 fps slot limit 4, got %d", got)
|
||||
}
|
||||
SetFrameFlowTargetFPS(120)
|
||||
if got := activeFrameSlotLimit(); got != 4 {
|
||||
t.Fatalf("expected 120 fps slot limit 4, got %d", got)
|
||||
if got := activeFrameSlotLimit(); got != 8 {
|
||||
t.Fatalf("expected 120 fps slot limit 8, got %d", got)
|
||||
}
|
||||
SetFrameFlowTargetFPS(240)
|
||||
if got := activeFrameSlotLimit(); got != 8 {
|
||||
t.Fatalf("expected 240 fps slot limit 8, got %d", got)
|
||||
if got := activeFrameSlotLimit(); got != 12 {
|
||||
t.Fatalf("expected 240 fps slot limit 12, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,9 @@ func probeDesktopEncoderCapabilities(display int, bounds image.Rectangle) Deskto
|
||||
for _, probe := range probes {
|
||||
maxFPS := 0
|
||||
for _, fps := range fpsOptions {
|
||||
if fps > desktopProfileFPSCeiling(size.height) {
|
||||
continue
|
||||
}
|
||||
req := h264D3D11TextureRequest{
|
||||
Device: unsafe.Pointer(device), Texture: unsafe.Pointer(texture),
|
||||
InputWidth: nativeWidth, InputHeight: nativeHeight,
|
||||
@@ -140,7 +143,7 @@ func probeDesktopEncoderCapabilities(display int, bounds image.Rectangle) Deskto
|
||||
continue
|
||||
}
|
||||
for _, fps := range fpsOptions {
|
||||
if fps <= maxFPS {
|
||||
if fps <= maxFPS && fps <= desktopProfileFPSCeiling(size.height) {
|
||||
key := desktopProfileKey(size.width, size.height, fps)
|
||||
providersByProfile[key] = appendUniqueString(providersByProfile[key], probe.name)
|
||||
}
|
||||
@@ -254,6 +257,20 @@ func safeDesktopProfiles(nativeWidth, nativeHeight int) []DesktopEncoderProfile
|
||||
return profiles
|
||||
}
|
||||
|
||||
// The probe repeatedly encodes an unchanged synthetic texture and excludes
|
||||
// capture, conversion, transport, and browser decode. Bound its advertised
|
||||
// result to practical end-to-end desktop profiles.
|
||||
func desktopProfileFPSCeiling(height int) int {
|
||||
switch {
|
||||
case height <= 1080:
|
||||
return 240
|
||||
case height <= 1440:
|
||||
return 120
|
||||
default:
|
||||
return 60
|
||||
}
|
||||
}
|
||||
|
||||
func desktopProfileKey(width, height, fps int) string {
|
||||
return fmt.Sprintf("%dx%d@%d", width, height, fps)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package capture
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDesktopProfileFPSCeiling(t *testing.T) {
|
||||
tests := []struct {
|
||||
height int
|
||||
want int
|
||||
}{
|
||||
{height: 720, want: 240},
|
||||
{height: 1080, want: 240},
|
||||
{height: 1440, want: 120},
|
||||
{height: 2160, want: 60},
|
||||
{height: 4320, want: 60},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if got := desktopProfileFPSCeiling(test.height); got != test.want {
|
||||
t.Fatalf("height %d: expected ceiling %d, got %d", test.height, test.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,10 @@ func SetFrameFlowTargetFPS(fps int) {
|
||||
limit := defaultMaxInFlightFrames
|
||||
switch {
|
||||
case fps >= 180:
|
||||
limit = 8
|
||||
limit = 12
|
||||
case fps >= 120:
|
||||
limit = 8
|
||||
case fps >= 60:
|
||||
limit = 4
|
||||
}
|
||||
if env := strings.TrimSpace(os.Getenv("OVERLORD_DESKTOP_IN_FLIGHT_FRAMES")); env != "" {
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"overlord-client/cmd/agent/webrtcpub"
|
||||
)
|
||||
|
||||
func resetCodecSelectionForTest() {
|
||||
@@ -18,6 +21,8 @@ func resetCodecSelectionForTest() {
|
||||
backstageOverrideCodec.Store("")
|
||||
desktopOverrideQuality.Store(0)
|
||||
backstageOverrideQuality.Store(0)
|
||||
_ = webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop)
|
||||
_ = webrtcpub.ConsumeKeyframeRequest(webrtcpub.Kindbackstage)
|
||||
}
|
||||
|
||||
func TestSetQualityAndCodec_H264FallbackDependsOnAvailability(t *testing.T) {
|
||||
@@ -72,6 +77,27 @@ func TestDesktopAndBackstageCodecSelectionsAreIndependent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchingToH264RequestsRecoveryPoint(t *testing.T) {
|
||||
if !h264Available() {
|
||||
t.Skip("h264 is unavailable in this build")
|
||||
}
|
||||
t.Cleanup(resetCodecSelectionForTest)
|
||||
resetCodecSelectionForTest()
|
||||
|
||||
SetDesktopQualityAndCodec(80, "jpeg")
|
||||
_ = webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop)
|
||||
lastKeyframe.Store(time.Now().UnixNano())
|
||||
|
||||
SetDesktopQualityAndCodec(80, "h264")
|
||||
|
||||
if lastKeyframe.Load() != 0 {
|
||||
t.Fatal("codec transition did not invalidate the previous keyframe timestamp")
|
||||
}
|
||||
if !webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop) {
|
||||
t.Fatal("codec transition did not request an H.264 recovery point")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesktopHardwareFailureDoesNotChangeBackstageCodec(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
desktopHardwareFallbackUntil.Store(0)
|
||||
|
||||
@@ -13,7 +13,7 @@ func directVideoKeyframeDue(requested bool, nowNs, lastKeyframeNs int64) bool {
|
||||
if requested || lastKeyframeNs == 0 {
|
||||
return true
|
||||
}
|
||||
return keyframeEvery > 0 && time.Duration(nowNs-lastKeyframeNs) >= keyframeEvery
|
||||
return videoKeyframeEvery > 0 && time.Duration(nowNs-lastKeyframeNs) >= videoKeyframeEvery
|
||||
}
|
||||
|
||||
func tryBuildDirectH264Frame(display int) (wire.Frame, time.Duration, time.Duration, bool, error) {
|
||||
@@ -22,8 +22,9 @@ func tryBuildDirectH264Frame(display int) (wire.Frame, time.Duration, time.Durat
|
||||
return wire.Frame{}, 0, 0, false, nil
|
||||
}
|
||||
now := time.Now()
|
||||
requestedKeyframe := webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop)
|
||||
forceKeyframe := directVideoKeyframeDue(
|
||||
webrtcpub.ConsumeKeyframeRequest(webrtcpub.KindDesktop),
|
||||
requestedKeyframe,
|
||||
now.UnixNano(),
|
||||
lastKeyframe.Load(),
|
||||
)
|
||||
@@ -38,12 +39,18 @@ func tryBuildDirectH264Frame(display int) (wire.Frame, time.Duration, time.Durat
|
||||
data, width, height, captureDur, encodeDur, used, err = captureDisplayDXGIH264(display, forceKeyframe)
|
||||
}
|
||||
if !used || err != nil {
|
||||
if requestedKeyframe {
|
||||
webrtcpub.RequestKeyframe(webrtcpub.KindDesktop)
|
||||
}
|
||||
return wire.Frame{}, captureDur, encodeDur, used, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
if requestedKeyframe {
|
||||
webrtcpub.RequestKeyframe(webrtcpub.KindDesktop)
|
||||
}
|
||||
return wire.Frame{}, captureDur, encodeDur, true, nil
|
||||
}
|
||||
if forceKeyframe {
|
||||
if retainKeyframeRequestUntilOutput(webrtcpub.KindDesktop, requestedKeyframe, codec, data) {
|
||||
lastKeyframe.Store(now.UnixNano())
|
||||
}
|
||||
statFullFrames.Add(1)
|
||||
|
||||
@@ -122,9 +122,14 @@ static void nvenc_configure_h264_stream(NV_ENC_CONFIG *cfg, int fps) {
|
||||
(void)fps;
|
||||
cfg->gopLength = NVENC_INFINITE_GOPLENGTH;
|
||||
cfg->frameIntervalP = 1;
|
||||
// The low-latency presets otherwise constrain an IDR to roughly the same
|
||||
// bit budget as a P-frame. That makes recovery frames visibly pulse or
|
||||
// smear under motion. Allow IDRs a larger transient budget while keeping
|
||||
// the normal one-frame VBV/CBR latency behavior.
|
||||
cfg->rcParams.lowDelayKeyFrameScale = 4;
|
||||
cfg->encodeCodecConfig.h264Config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
|
||||
cfg->encodeCodecConfig.h264Config.repeatSPSPPS = 1;
|
||||
cfg->encodeCodecConfig.h264Config.level = NV_ENC_LEVEL_H264_52;
|
||||
cfg->encodeCodecConfig.h264Config.level = NV_ENC_LEVEL_AUTOSELECT;
|
||||
cfg->encodeCodecConfig.h264Config.h264VUIParameters.videoSignalTypePresentFlag = 1;
|
||||
cfg->encodeCodecConfig.h264Config.h264VUIParameters.videoFormat = NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED;
|
||||
cfg->encodeCodecConfig.h264Config.h264VUIParameters.videoFullRangeFlag = 0;
|
||||
@@ -393,7 +398,7 @@ static nvenc_d3d11_texture_create_result nvenc_create_d3d11_texture_encoder(ID3D
|
||||
nvenc_release_texture_encoder(enc);
|
||||
return out;
|
||||
}
|
||||
preset.presetCfg.profileGUID = codec == 1 ? NV_ENC_HEVC_PROFILE_MAIN_GUID : NV_ENC_H264_PROFILE_HIGH_GUID;
|
||||
preset.presetCfg.profileGUID = codec == 1 ? NV_ENC_HEVC_PROFILE_MAIN_GUID : NV_ENC_H264_PROFILE_MAIN_GUID;
|
||||
preset.presetCfg.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR;
|
||||
preset.presetCfg.rcParams.averageBitRate = (uint32_t)bitrate;
|
||||
preset.presetCfg.rcParams.maxBitRate = (uint32_t)bitrate;
|
||||
@@ -703,7 +708,7 @@ static nvenc_d3d11_create_result nvenc_create_d3d11_encoder(int width, int heigh
|
||||
nvenc_release_encoder(enc);
|
||||
return out;
|
||||
}
|
||||
preset.presetCfg.profileGUID = codec == 1 ? NV_ENC_HEVC_PROFILE_MAIN_GUID : NV_ENC_H264_PROFILE_HIGH_GUID;
|
||||
preset.presetCfg.profileGUID = codec == 1 ? NV_ENC_HEVC_PROFILE_MAIN_GUID : NV_ENC_H264_PROFILE_MAIN_GUID;
|
||||
preset.presetCfg.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR;
|
||||
preset.presetCfg.rcParams.averageBitRate = (uint32_t)bitrate;
|
||||
preset.presetCfg.rcParams.maxBitRate = (uint32_t)bitrate;
|
||||
@@ -1160,7 +1165,7 @@ static nvenc_d3d11_smoke_result run_nvenc_d3d11_smoke_c(int width, int height, i
|
||||
return out;
|
||||
}
|
||||
|
||||
preset.presetCfg.profileGUID = NV_ENC_H264_PROFILE_HIGH_GUID;
|
||||
preset.presetCfg.profileGUID = NV_ENC_H264_PROFILE_MAIN_GUID;
|
||||
preset.presetCfg.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR;
|
||||
preset.presetCfg.rcParams.averageBitRate = (uint32_t)bitrate;
|
||||
preset.presetCfg.rcParams.maxBitRate = (uint32_t)bitrate;
|
||||
@@ -1961,7 +1966,8 @@ func encodeNativeH264D3D11Texture(stream string, device, texture unsafe.Pointer,
|
||||
}
|
||||
state.encoder = enc
|
||||
}
|
||||
return state.encoder.EncodeTexture(texture, forceIDR || state.forceIDR.Swap(false))
|
||||
pendingIDR := state.forceIDR.Swap(false)
|
||||
return state.encoder.EncodeTexture(texture, forceIDR || pendingIDR)
|
||||
}
|
||||
|
||||
func requestNativeH264D3D11TextureKeyframe(stream string) {
|
||||
@@ -1997,7 +2003,8 @@ func encodeNativeHEVCD3D11Texture(device, texture unsafe.Pointer, inputWidth, in
|
||||
}
|
||||
nativeTextureHEVCEnc = enc
|
||||
}
|
||||
return nativeTextureHEVCEnc.EncodeTexture(texture, forceIDR || nativeTextureHEVCForceIDR.Swap(false))
|
||||
pendingIDR := nativeTextureHEVCForceIDR.Swap(false)
|
||||
return nativeTextureHEVCEnc.EncodeTexture(texture, forceIDR || pendingIDR)
|
||||
}
|
||||
|
||||
func requestNativeHEVCD3D11TextureKeyframe() {
|
||||
|
||||
@@ -42,7 +42,8 @@ func (b *qsvD3D11TextureBackend) Encode(req h264D3D11TextureRequest) ([]byte, er
|
||||
b.device, b.inputWidth, b.inputHeight = req.Device, req.InputWidth, req.InputHeight
|
||||
b.encodeWidth, b.encodeHeight, b.fps, b.dxgiFormat = req.EncodeWidth, req.EncodeHeight, req.FPS, req.DXGIFormat
|
||||
}
|
||||
forceIDR := req.ForceIDR || b.forceIDR.Swap(false)
|
||||
pendingIDR := b.forceIDR.Swap(false)
|
||||
forceIDR := req.ForceIDR || pendingIDR
|
||||
for attempts := 0; attempts < 2; attempts++ {
|
||||
result, size, err := b.encoder.Encode(req.Texture, forceIDR, b.output)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"overlord-client/cmd/agent/h264util"
|
||||
"overlord-client/cmd/agent/webrtcpub"
|
||||
)
|
||||
|
||||
func videoAccessUnitIsKey(codec string, data []byte) bool {
|
||||
if codec == "hevc" {
|
||||
return inspectHEVCAccessUnit(data).hasIRAP
|
||||
}
|
||||
return h264util.IsIDR(data)
|
||||
}
|
||||
|
||||
type hevcAccessUnit struct {
|
||||
hasVPS bool
|
||||
hasSPS bool
|
||||
hasPPS bool
|
||||
hasIRAP bool
|
||||
}
|
||||
|
||||
func inspectHEVCAccessUnit(data []byte) hevcAccessUnit {
|
||||
var out hevcAccessUnit
|
||||
for i := 0; i+4 < len(data); {
|
||||
startCodeLen := 0
|
||||
switch {
|
||||
case data[i] == 0 && data[i+1] == 0 && data[i+2] == 1:
|
||||
startCodeLen = 3
|
||||
case i+4 < len(data) && data[i] == 0 && data[i+1] == 0 && data[i+2] == 0 && data[i+3] == 1:
|
||||
startCodeLen = 4
|
||||
}
|
||||
if startCodeLen == 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
nal := i + startCodeLen
|
||||
if nal >= len(data) {
|
||||
break
|
||||
}
|
||||
nalType := (data[nal] >> 1) & 0x3f
|
||||
switch nalType {
|
||||
case 32:
|
||||
out.hasVPS = true
|
||||
case 33:
|
||||
out.hasSPS = true
|
||||
case 34:
|
||||
out.hasPPS = true
|
||||
default:
|
||||
if nalType >= 16 && nalType <= 21 {
|
||||
out.hasIRAP = true
|
||||
}
|
||||
}
|
||||
i = nal + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func videoAccessUnitIsRecoveryPoint(codec string, data []byte) bool {
|
||||
if codec == "hevc" {
|
||||
accessUnit := inspectHEVCAccessUnit(data)
|
||||
return accessUnit.hasVPS && accessUnit.hasSPS && accessUnit.hasPPS && accessUnit.hasIRAP
|
||||
}
|
||||
accessUnit := h264util.InspectAnnexB(data)
|
||||
return accessUnit.HasSPS && accessUnit.HasPPS && accessUnit.HasIDR
|
||||
}
|
||||
|
||||
func retainKeyframeRequestUntilOutput(kind webrtcpub.Kind, requested bool, codec string, data []byte) bool {
|
||||
isKey := videoAccessUnitIsKey(codec, data)
|
||||
if requested && !videoAccessUnitIsRecoveryPoint(codec, data) {
|
||||
webrtcpub.RequestKeyframe(kind)
|
||||
}
|
||||
return isKey
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"overlord-client/cmd/agent/webrtcpub"
|
||||
)
|
||||
|
||||
func TestRetainKeyframeRequestUntilIDROutput(t *testing.T) {
|
||||
kind := webrtcpub.KindDesktop
|
||||
_ = webrtcpub.ConsumeKeyframeRequest(kind)
|
||||
|
||||
if retainKeyframeRequestUntilOutput(kind, true, "h264", []byte{0, 0, 1, 0x41, 1}) {
|
||||
t.Fatal("delta access unit was classified as a keyframe")
|
||||
}
|
||||
if !webrtcpub.ConsumeKeyframeRequest(kind) {
|
||||
t.Fatal("keyframe request was not retained after delta output")
|
||||
}
|
||||
|
||||
if !retainKeyframeRequestUntilOutput(kind, true, "h264", []byte{0, 0, 1, 0x65, 1}) {
|
||||
t.Fatal("IDR access unit was not classified as a keyframe")
|
||||
}
|
||||
if !webrtcpub.ConsumeKeyframeRequest(kind) {
|
||||
t.Fatal("keyframe request was not retained when IDR omitted SPS/PPS")
|
||||
}
|
||||
|
||||
recoveryPoint := []byte{
|
||||
0, 0, 1, 0x67, 1,
|
||||
0, 0, 1, 0x68, 1,
|
||||
0, 0, 1, 0x65, 1,
|
||||
}
|
||||
if !retainKeyframeRequestUntilOutput(kind, true, "h264", recoveryPoint) {
|
||||
t.Fatal("complete recovery access unit was not classified as a keyframe")
|
||||
}
|
||||
if webrtcpub.ConsumeKeyframeRequest(kind) {
|
||||
t.Fatal("completed keyframe request was unexpectedly requeued")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConfiguredVideoKeyframeInterval(t *testing.T) {
|
||||
tests := []struct {
|
||||
value string
|
||||
want time.Duration
|
||||
}{
|
||||
{value: "", want: 0},
|
||||
{value: "off", want: 0},
|
||||
{value: "0", want: 0},
|
||||
{value: "30", want: 30 * time.Second},
|
||||
{value: "2m", want: 2 * time.Minute},
|
||||
{value: "invalid", want: 0},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.value, func(t *testing.T) {
|
||||
t.Setenv("OVERLORD_VIDEO_KEYFRAME_INTERVAL", test.value)
|
||||
if got := configuredVideoKeyframeInterval(); got != test.want {
|
||||
t.Fatalf("configuredVideoKeyframeInterval() = %s, want %s", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -281,19 +281,31 @@ func (s *virtualDuplicationState) captureDirectH264() (wire.Frame, time.Duration
|
||||
|
||||
func (s *virtualDuplicationState) encodeCachedDirectH264(capStart time.Time, width, height int) (wire.Frame, time.Duration, time.Duration, bool, error) {
|
||||
fps := activeH264FPSForStream("backstage")
|
||||
now := time.Now()
|
||||
requestedKeyframe := webrtcpub.ConsumeKeyframeRequest(webrtcpub.Kindbackstage)
|
||||
forceKeyframe := directVideoKeyframeDue(requestedKeyframe, now.UnixNano(), backstageLastKeyframe.Load())
|
||||
encStart := time.Now()
|
||||
out, _, err := encodeH264D3D11Texture("backstage", h264D3D11TextureRequest{
|
||||
Device: unsafe.Pointer(s.device), Texture: unsafe.Pointer(s.h264LastTex),
|
||||
InputWidth: width, InputHeight: height, EncodeWidth: width, EncodeHeight: height,
|
||||
FPS: fps, DXGIFormat: s.h264LastDesc.Format, ForceIDR: webrtcpub.ConsumeKeyframeRequest(webrtcpub.Kindbackstage),
|
||||
FPS: fps, DXGIFormat: s.h264LastDesc.Format, ForceIDR: forceKeyframe,
|
||||
})
|
||||
encodeDur := time.Since(encStart)
|
||||
if err != nil {
|
||||
if requestedKeyframe {
|
||||
webrtcpub.RequestKeyframe(webrtcpub.Kindbackstage)
|
||||
}
|
||||
return wire.Frame{}, time.Since(capStart), encodeDur, true, err
|
||||
}
|
||||
if len(out) == 0 {
|
||||
if requestedKeyframe {
|
||||
webrtcpub.RequestKeyframe(webrtcpub.Kindbackstage)
|
||||
}
|
||||
return wire.Frame{}, time.Since(capStart), encodeDur, true, nil
|
||||
}
|
||||
if retainKeyframeRequestUntilOutput(webrtcpub.Kindbackstage, requestedKeyframe, "h264", out) {
|
||||
backstageLastKeyframe.Store(now.UnixNano())
|
||||
}
|
||||
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: 0, FPS: 0, Format: "h264", Backstage: true}, Data: out}, time.Since(capStart), encodeDur, true, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,9 @@ func TestCaptureDisplayDXGI(t *testing.T) {
|
||||
|
||||
func TestDirectVideoKeyframeDue(t *testing.T) {
|
||||
now := (10 * time.Second).Nanoseconds()
|
||||
previousInterval := videoKeyframeEvery
|
||||
videoKeyframeEvery = 5 * time.Second
|
||||
defer func() { videoKeyframeEvery = previousInterval }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -102,7 +105,8 @@ func TestDirectVideoKeyframeDue(t *testing.T) {
|
||||
}{
|
||||
{name: "explicit request", requested: true, last: now, want: true},
|
||||
{name: "first frame", last: 0, want: true},
|
||||
{name: "periodic keyframes disabled", last: now - (30 * time.Second).Nanoseconds(), want: false},
|
||||
{name: "periodic keyframe due", last: now - (30 * time.Second).Nanoseconds(), want: true},
|
||||
{name: "periodic keyframe not due", last: now - time.Second.Nanoseconds(), want: false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -112,4 +116,9 @@ func TestDirectVideoKeyframeDue(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
videoKeyframeEvery = 0
|
||||
if directVideoKeyframeDue(false, now, now-(30*time.Second).Nanoseconds()) {
|
||||
t.Fatal("periodic keyframe fired while the interval was disabled")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user