From b23c3cd61872e29590a617b72695efe3bf63fd4c Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:57:00 +1100 Subject: [PATCH] Perform hardware codec checks on separate go routine (#6414) Warn if tests are taking a long time Add WaitDelay to try to kill process if hanging --- internal/manager/init.go | 3 ++- pkg/ffmpeg/codec_hardware.go | 35 ++++++++++++++++++++++++++++++++--- pkg/ffmpeg/ffmpeg.go | 14 +++++++++++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/internal/manager/init.go b/internal/manager/init.go index dd1640ed3..b388bd15c 100644 --- a/internal/manager/init.go +++ b/internal/manager/init.go @@ -313,6 +313,7 @@ func (s *Manager) RefreshFFMpeg(ctx context.Context) { s.FFMpeg = ffmpeg.NewEncoder(ffmpegPath) s.FFProbe = ffmpeg.NewFFProbe(ffprobePath) - s.FFMpeg.InitHWSupport(ctx) + // initialise hardware support with background context + s.FFMpeg.InitHWSupport(context.Background()) } } diff --git a/pkg/ffmpeg/codec_hardware.go b/pkg/ffmpeg/codec_hardware.go index 530f4dc59..86fe56bde 100644 --- a/pkg/ffmpeg/codec_hardware.go +++ b/pkg/ffmpeg/codec_hardware.go @@ -36,6 +36,32 @@ const minHeight int = 480 // Tests all (given) hardware codec's func (f *FFMpeg) InitHWSupport(ctx context.Context) { + // do the hardware codec tests in a separate goroutine to avoid blocking + done := make(chan struct{}) + go func() { + f.initHWSupport(ctx) + close(done) + }() + + // log if the initialization takes too long + const hwInitLogTimeoutSecondsDefault = 5 + hwInitLogTimeoutSeconds := hwInitLogTimeoutSecondsDefault * time.Second + timer := time.NewTimer(hwInitLogTimeoutSeconds) + + go func() { + select { + case <-timer.C: + logger.Warnf("[InitHWSupport] Hardware codec initialization is taking longer than %s...", hwInitLogTimeoutSeconds) + logger.Info("[InitHWSupport] Hardware encoding will not be available until initialization is complete.") + case <-done: + if !timer.Stop() { + <-timer.C + } + } + }() +} + +func (f *FFMpeg) initHWSupport(ctx context.Context) { var hwCodecSupport []VideoCodec // Note that the first compatible codec is returned, so order is important @@ -83,6 +109,7 @@ func (f *FFMpeg) InitHWSupport(ctx context.Context) { defer cancel() cmd := f.Command(testCtx, args) + cmd.WaitDelay = time.Second logger.Tracef("[InitHWSupport] Testing codec %s: %v", codec, cmd.Args) var stderr bytes.Buffer @@ -112,6 +139,8 @@ func (f *FFMpeg) InitHWSupport(ctx context.Context) { } logger.Info(outstr) + f.hwCodecSupportMutex.Lock() + defer f.hwCodecSupportMutex.Unlock() f.hwCodecSupport = hwCodecSupport } @@ -411,7 +440,7 @@ func (f *FFMpeg) hwMaxResFilter(toCodec VideoCodec, vf *models.VideoFile, reqHei // Return if a hardware accelerated for HLS is available func (f *FFMpeg) hwCodecHLSCompatible() *VideoCodec { - for _, element := range f.hwCodecSupport { + for _, element := range f.getHWCodecSupport() { switch element { case VideoCodecN264, VideoCodecN264H, @@ -429,7 +458,7 @@ func (f *FFMpeg) hwCodecHLSCompatible() *VideoCodec { // Return if a hardware accelerated codec for MP4 is available func (f *FFMpeg) hwCodecMP4Compatible() *VideoCodec { - for _, element := range f.hwCodecSupport { + for _, element := range f.getHWCodecSupport() { switch element { case VideoCodecN264, VideoCodecN264H, @@ -445,7 +474,7 @@ func (f *FFMpeg) hwCodecMP4Compatible() *VideoCodec { // Return if a hardware accelerated codec for WebM is available func (f *FFMpeg) hwCodecWEBMCompatible() *VideoCodec { - for _, element := range f.hwCodecSupport { + for _, element := range f.getHWCodecSupport() { switch element { case VideoCodecIVP9, VideoCodecVVP9: diff --git a/pkg/ffmpeg/ffmpeg.go b/pkg/ffmpeg/ffmpeg.go index ce1232e5d..04c58f04b 100644 --- a/pkg/ffmpeg/ffmpeg.go +++ b/pkg/ffmpeg/ffmpeg.go @@ -10,6 +10,7 @@ import ( "regexp" "strconv" "strings" + "sync" stashExec "github.com/stashapp/stash/pkg/exec" "github.com/stashapp/stash/pkg/fsutil" @@ -216,9 +217,10 @@ func (v Version) String() string { // FFMpeg provides an interface to ffmpeg. type FFMpeg struct { - ffmpeg string - version Version - hwCodecSupport []VideoCodec + ffmpeg string + version Version + hwCodecSupport []VideoCodec + hwCodecSupportMutex sync.RWMutex } // Creates a new FFMpeg encoder @@ -241,3 +243,9 @@ func (f *FFMpeg) Command(ctx context.Context, args []string) *exec.Cmd { func (f *FFMpeg) Path() string { return f.ffmpeg } + +func (f *FFMpeg) getHWCodecSupport() []VideoCodec { + f.hwCodecSupportMutex.RLock() + defer f.hwCodecSupportMutex.RUnlock() + return f.hwCodecSupport +}