mirror of https://github.com/stashapp/stash.git
208 lines
5.3 KiB
Go
208 lines
5.3 KiB
Go
|
package ffmpeg
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/stashapp/stash/pkg/logger"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// Hardware codec's
|
||
|
VideoCodecN264 VideoCodec = "h264_nvenc"
|
||
|
VideoCodecI264 VideoCodec = "h264_qsv"
|
||
|
VideoCodecA264 VideoCodec = "h264_amf"
|
||
|
VideoCodecM264 VideoCodec = "h264_videotoolbox"
|
||
|
VideoCodecV264 VideoCodec = "h264_vaapi"
|
||
|
VideoCodecR264 VideoCodec = "h264_v4l2m2m"
|
||
|
VideoCodecO264 VideoCodec = "h264_omx"
|
||
|
VideoCodecIVP9 VideoCodec = "vp9_qsv"
|
||
|
VideoCodecVVP9 VideoCodec = "vp9_vaapi"
|
||
|
VideoCodecVVPX VideoCodec = "vp8_vaapi"
|
||
|
)
|
||
|
|
||
|
// Tests all (given) hardware codec's
|
||
|
func (f *FFMpeg) InitHWSupport(ctx context.Context) {
|
||
|
var hwCodecSupport []VideoCodec
|
||
|
|
||
|
for _, codec := range []VideoCodec{
|
||
|
VideoCodecN264,
|
||
|
VideoCodecI264,
|
||
|
VideoCodecV264,
|
||
|
VideoCodecR264,
|
||
|
VideoCodecIVP9,
|
||
|
VideoCodecVVP9,
|
||
|
} {
|
||
|
var args Args
|
||
|
args = append(args, "-hide_banner")
|
||
|
args = args.LogLevel(LogLevelWarning)
|
||
|
args = f.hwDeviceInit(args, codec)
|
||
|
args = args.Format("lavfi")
|
||
|
args = args.Input("color=c=red")
|
||
|
args = args.Duration(0.1)
|
||
|
|
||
|
videoFilter := f.hwFilterInit(codec)
|
||
|
// Test scaling
|
||
|
videoFilter = videoFilter.ScaleDimensions(-2, 160)
|
||
|
videoFilter = f.hwCodecFilter(videoFilter, codec)
|
||
|
args = append(args, CodecInit(codec)...)
|
||
|
args = args.VideoFilter(videoFilter)
|
||
|
|
||
|
args = args.Format("null")
|
||
|
args = args.Output("-")
|
||
|
|
||
|
cmd := f.Command(ctx, args)
|
||
|
|
||
|
var stderr bytes.Buffer
|
||
|
cmd.Stderr = &stderr
|
||
|
|
||
|
if err := cmd.Start(); err != nil {
|
||
|
logger.Debugf("[InitHWSupport] error starting command: %w", err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if err := cmd.Wait(); err != nil {
|
||
|
errOutput := stderr.String()
|
||
|
|
||
|
if len(errOutput) == 0 {
|
||
|
errOutput = err.Error()
|
||
|
}
|
||
|
|
||
|
logger.Debugf("[InitHWSupport] Codec %s not supported. Error output:\n%s", codec, errOutput)
|
||
|
} else {
|
||
|
hwCodecSupport = append(hwCodecSupport, codec)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
outstr := "[InitHWSupport] Supported HW codecs:\n"
|
||
|
for _, codec := range hwCodecSupport {
|
||
|
outstr += fmt.Sprintf("\t%s\n", codec)
|
||
|
}
|
||
|
logger.Info(outstr)
|
||
|
|
||
|
f.hwCodecSupport = hwCodecSupport
|
||
|
}
|
||
|
|
||
|
// Prepend input for hardware encoding only
|
||
|
func (f *FFMpeg) hwDeviceInit(args Args, codec VideoCodec) Args {
|
||
|
switch codec {
|
||
|
case VideoCodecN264:
|
||
|
args = append(args, "-hwaccel_device")
|
||
|
args = append(args, "0")
|
||
|
case VideoCodecV264,
|
||
|
VideoCodecVVP9:
|
||
|
args = append(args, "-vaapi_device")
|
||
|
args = append(args, "/dev/dri/renderD128")
|
||
|
case VideoCodecI264,
|
||
|
VideoCodecIVP9:
|
||
|
args = append(args, "-init_hw_device")
|
||
|
args = append(args, "qsv=hw")
|
||
|
args = append(args, "-filter_hw_device")
|
||
|
args = append(args, "hw")
|
||
|
}
|
||
|
|
||
|
return args
|
||
|
}
|
||
|
|
||
|
// Initialise a video filter for HW encoding
|
||
|
func (f *FFMpeg) hwFilterInit(codec VideoCodec) VideoFilter {
|
||
|
var videoFilter VideoFilter
|
||
|
switch codec {
|
||
|
case VideoCodecV264,
|
||
|
VideoCodecVVP9:
|
||
|
videoFilter = videoFilter.Append("format=nv12")
|
||
|
videoFilter = videoFilter.Append("hwupload")
|
||
|
case VideoCodecN264:
|
||
|
videoFilter = videoFilter.Append("format=nv12")
|
||
|
videoFilter = videoFilter.Append("hwupload_cuda")
|
||
|
case VideoCodecI264,
|
||
|
VideoCodecIVP9:
|
||
|
videoFilter = videoFilter.Append("hwupload=extra_hw_frames=64")
|
||
|
videoFilter = videoFilter.Append("format=qsv")
|
||
|
}
|
||
|
|
||
|
return videoFilter
|
||
|
}
|
||
|
|
||
|
// Replace video filter scaling with hardware scaling for full hardware transcoding
|
||
|
func (f *FFMpeg) hwCodecFilter(args VideoFilter, codec VideoCodec) VideoFilter {
|
||
|
sargs := string(args)
|
||
|
|
||
|
if strings.Contains(sargs, "scale=") {
|
||
|
switch codec {
|
||
|
case VideoCodecN264:
|
||
|
args = VideoFilter(strings.Replace(sargs, "scale=", "scale_cuda=", 1))
|
||
|
case VideoCodecV264,
|
||
|
VideoCodecVVP9:
|
||
|
args = VideoFilter(strings.Replace(sargs, "scale=", "scale_vaapi=", 1))
|
||
|
case VideoCodecI264,
|
||
|
VideoCodecIVP9:
|
||
|
// BUG: [scale_qsv]: Size values less than -1 are not acceptable.
|
||
|
// Fix: Replace all instances of -2 with -1 in a scale operation
|
||
|
re := regexp.MustCompile(`(scale=)([\d:]*)(-2)(.*)`)
|
||
|
sargs = re.ReplaceAllString(sargs, "scale=$2-1$4")
|
||
|
args = VideoFilter(strings.Replace(sargs, "scale=", "scale_qsv=", 1))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return args
|
||
|
}
|
||
|
|
||
|
// Returns the max resolution for a given codec, or a default
|
||
|
func (f *FFMpeg) hwCodecMaxRes(codec VideoCodec, dW int, dH int) (int, int) {
|
||
|
if codec == VideoCodecN264 {
|
||
|
return 4096, 4096
|
||
|
}
|
||
|
|
||
|
return dW, dH
|
||
|
}
|
||
|
|
||
|
// Return a maxres filter
|
||
|
func (f *FFMpeg) hwMaxResFilter(codec VideoCodec, width int, height int, max int) VideoFilter {
|
||
|
videoFilter := f.hwFilterInit(codec)
|
||
|
maxWidth, maxHeight := f.hwCodecMaxRes(codec, width, height)
|
||
|
videoFilter = videoFilter.ScaleMaxLM(width, height, max, maxWidth, maxHeight)
|
||
|
return f.hwCodecFilter(videoFilter, codec)
|
||
|
}
|
||
|
|
||
|
// Return if a hardware accelerated for HLS is available
|
||
|
func (f *FFMpeg) hwCodecHLSCompatible() *VideoCodec {
|
||
|
for _, element := range f.hwCodecSupport {
|
||
|
switch element {
|
||
|
case VideoCodecN264,
|
||
|
VideoCodecI264,
|
||
|
VideoCodecV264,
|
||
|
VideoCodecR264:
|
||
|
return &element
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Return if a hardware accelerated codec for MP4 is available
|
||
|
func (f *FFMpeg) hwCodecMP4Compatible() *VideoCodec {
|
||
|
for _, element := range f.hwCodecSupport {
|
||
|
switch element {
|
||
|
case VideoCodecN264,
|
||
|
VideoCodecI264:
|
||
|
return &element
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Return if a hardware accelerated codec for WebM is available
|
||
|
func (f *FFMpeg) hwCodecWEBMCompatible() *VideoCodec {
|
||
|
for _, element := range f.hwCodecSupport {
|
||
|
switch element {
|
||
|
case VideoCodecIVP9,
|
||
|
VideoCodecVVP9:
|
||
|
return &element
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|