mirror of
https://github.com/stashapp/stash.git
synced 2026-05-09 05:05:29 +02:00
pkg/ffmpeg: refactor codec args and hardware API
Move the per-codec CodecInit flag groups to VideoCodec.ExtraArgs and add ExtraArgsHQ(preset, crf) for quality-oriented encoding. Neither emits -c:v; callers set that explicitly. Rename hw* methods to HW*, change HWCanFullHWTranscode / HWMaxResFilter to take path/width/height primitives, and flip the HWCodec*Compatible helpers to take a default codec. Merge ScaleMaxLM into a single 5-arg ScaleMax.
This commit is contained in:
parent
2c8a0ad192
commit
900305685c
5 changed files with 240 additions and 172 deletions
|
|
@ -1,5 +1,7 @@
|
|||
package ffmpeg
|
||||
|
||||
import "strconv"
|
||||
|
||||
type VideoCodec struct {
|
||||
Name string // The full name of the codec including profile/quality
|
||||
CodeName string // The core codec name without profile/quality suffix
|
||||
|
|
@ -44,3 +46,148 @@ var (
|
|||
AudioCodecLibOpus AudioCodec = "libopus"
|
||||
AudioCodecCopy AudioCodec = "copy"
|
||||
)
|
||||
|
||||
func (c VideoCodec) ExtraArgs() (args Args) {
|
||||
switch c {
|
||||
// CPU Codecs
|
||||
case VideoCodecLibX264:
|
||||
args = append(args,
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-preset", "veryfast",
|
||||
"-crf", "25",
|
||||
"-sc_threshold", "0",
|
||||
)
|
||||
case VideoCodecVP9:
|
||||
args = append(args,
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-deadline", "realtime",
|
||||
"-cpu-used", "5",
|
||||
"-row-mt", "1",
|
||||
"-crf", "30",
|
||||
"-b:v", "0",
|
||||
)
|
||||
// HW Codecs
|
||||
case VideoCodecN264:
|
||||
args = append(args,
|
||||
"-rc", "vbr",
|
||||
"-cq", "15",
|
||||
)
|
||||
case VideoCodecN264H:
|
||||
args = append(args,
|
||||
"-tune", "hq",
|
||||
"-profile", "high",
|
||||
"-rc", "vbr",
|
||||
"-rc-lookahead", "60",
|
||||
"-surfaces", "64",
|
||||
"-spatial-aq", "1",
|
||||
"-aq-strength", "15",
|
||||
"-cq", "15",
|
||||
"-coder", "cabac",
|
||||
"-b_ref_mode", "middle",
|
||||
)
|
||||
case VideoCodecI264, VideoCodecIVP9:
|
||||
args = append(args,
|
||||
"-global_quality", "20",
|
||||
"-preset", "faster",
|
||||
)
|
||||
case VideoCodecI264C:
|
||||
args = append(args,
|
||||
"-q", "20",
|
||||
"-preset", "faster",
|
||||
)
|
||||
case VideoCodecV264, VideoCodecVVP9:
|
||||
args = append(args,
|
||||
"-qp", "20",
|
||||
)
|
||||
case VideoCodecA264:
|
||||
args = append(args,
|
||||
"-quality", "speed",
|
||||
)
|
||||
case VideoCodecM264:
|
||||
args = append(args,
|
||||
"-realtime", "1",
|
||||
)
|
||||
case VideoCodecO264:
|
||||
args = append(args,
|
||||
"-preset", "superfast",
|
||||
"-crf", "25",
|
||||
)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func (c VideoCodec) ExtraArgsHQ(preset string, crf int) (args Args) {
|
||||
switch c {
|
||||
// CPU Codecs
|
||||
case VideoCodecLibX264:
|
||||
args = append(args,
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-profile:v", "high",
|
||||
"-level", "4.2",
|
||||
"-preset", preset,
|
||||
"-crf", strconv.Itoa(crf),
|
||||
"-sc_threshold", "0",
|
||||
"-threads", "4",
|
||||
"-sws_flags", "lanczos",
|
||||
)
|
||||
case VideoCodecVP9:
|
||||
args = append(args,
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-deadline", "good",
|
||||
"-cpu-used", "0",
|
||||
"-row-mt", "1",
|
||||
"-crf", strconv.Itoa(crf),
|
||||
"-b:v", "0",
|
||||
"-threads", "4",
|
||||
)
|
||||
// HW Codecs - fixed HQ quality.
|
||||
case VideoCodecN264:
|
||||
args = append(args,
|
||||
"-rc", "vbr",
|
||||
"-cq", "10",
|
||||
)
|
||||
case VideoCodecN264H:
|
||||
args = append(args,
|
||||
"-tune", "hq",
|
||||
"-profile", "high",
|
||||
"-rc", "vbr",
|
||||
"-rc-lookahead", "60",
|
||||
"-surfaces", "64",
|
||||
"-spatial-aq", "1",
|
||||
"-aq-strength", "15",
|
||||
"-cq", "10",
|
||||
"-coder", "cabac",
|
||||
"-b_ref_mode", "middle",
|
||||
)
|
||||
case VideoCodecI264, VideoCodecIVP9:
|
||||
args = append(args,
|
||||
"-global_quality", "15",
|
||||
"-preset", "slow",
|
||||
)
|
||||
case VideoCodecI264C:
|
||||
args = append(args,
|
||||
"-q", "15",
|
||||
"-preset", "slow",
|
||||
)
|
||||
case VideoCodecV264, VideoCodecVVP9:
|
||||
args = append(args,
|
||||
"-qp", "15",
|
||||
)
|
||||
case VideoCodecA264:
|
||||
args = append(args,
|
||||
"-quality", "quality",
|
||||
)
|
||||
case VideoCodecM264:
|
||||
args = append(args,
|
||||
"-realtime", "0",
|
||||
)
|
||||
case VideoCodecO264:
|
||||
args = append(args,
|
||||
"-preset", "slow",
|
||||
"-crf", strconv.Itoa(crf),
|
||||
)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -80,15 +79,16 @@ func (f *FFMpeg) initHWSupport(ctx context.Context) {
|
|||
var args Args
|
||||
args = append(args, "-hide_banner")
|
||||
args = args.LogLevel(LogLevelWarning)
|
||||
args = f.hwDeviceInit(args, codec, false)
|
||||
args = f.HWDeviceInit(args, codec, false)
|
||||
args = args.Format("lavfi")
|
||||
vFile := &models.VideoFile{Width: 1280, Height: 720}
|
||||
args = args.Input(fmt.Sprintf("color=c=red:s=%dx%d", vFile.Width, vFile.Height))
|
||||
width, height := 1280, 720
|
||||
args = args.Input(fmt.Sprintf("color=c=red:s=%dx%d", width, height))
|
||||
args = args.Duration(0.1)
|
||||
|
||||
// Test scaling
|
||||
videoFilter := f.hwMaxResFilter(codec, vFile, minHeight, false)
|
||||
args = append(args, CodecInit(codec)...)
|
||||
videoFilter := f.HWMaxResFilter(codec, width, height, minHeight, false)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
args = args.VideoFilter(videoFilter)
|
||||
|
||||
args = args.Format("null")
|
||||
|
|
@ -144,7 +144,7 @@ func (f *FFMpeg) initHWSupport(ctx context.Context) {
|
|||
f.hwCodecSupport = hwCodecSupport
|
||||
}
|
||||
|
||||
func (f *FFMpeg) hwCanFullHWTranscode(ctx context.Context, codec VideoCodec, vf *models.VideoFile, reqHeight int) bool {
|
||||
func (f *FFMpeg) HWCanFullHWTranscode(ctx context.Context, codec VideoCodec, path string, width int, height int, reqHeight int) bool {
|
||||
if codec == VideoCodecCopy {
|
||||
return false
|
||||
}
|
||||
|
|
@ -153,12 +153,13 @@ func (f *FFMpeg) hwCanFullHWTranscode(ctx context.Context, codec VideoCodec, vf
|
|||
args = append(args, "-hide_banner")
|
||||
args = args.LogLevel(LogLevelWarning)
|
||||
args = args.XError()
|
||||
args = f.hwDeviceInit(args, codec, true)
|
||||
args = args.Input(vf.Path)
|
||||
args = f.HWDeviceInit(args, codec, true)
|
||||
args = args.Input(path)
|
||||
args = args.Duration(1)
|
||||
|
||||
videoFilter := f.hwMaxResFilter(codec, vf, reqHeight, true)
|
||||
args = append(args, CodecInit(codec)...)
|
||||
videoFilter := f.HWMaxResFilter(codec, width, height, reqHeight, true)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
args = args.VideoFilter(videoFilter)
|
||||
|
||||
args = args.Format("null")
|
||||
|
|
@ -176,7 +177,7 @@ func (f *FFMpeg) hwCanFullHWTranscode(ctx context.Context, codec VideoCodec, vf
|
|||
errOutput = err.Error()
|
||||
}
|
||||
|
||||
logger.Debugf("[InitHWSupport] Full hardware transcode for file %s not supported. Error output:\n%s", vf.Basename, errOutput)
|
||||
logger.Debugf("[InitHWSupport] Full hardware transcode for file %s not supported. Error output:\n%s", path, errOutput)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +185,7 @@ func (f *FFMpeg) hwCanFullHWTranscode(ctx context.Context, codec VideoCodec, vf
|
|||
}
|
||||
|
||||
// Prepend input for hardware encoding only
|
||||
func (f *FFMpeg) hwDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args {
|
||||
func (f *FFMpeg) HWDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args {
|
||||
// check for custom /dev/dri device #6435
|
||||
driDevice := os.Getenv("STASH_HW_DRI_DEVICE")
|
||||
if driDevice == "" {
|
||||
|
|
@ -257,7 +258,7 @@ func (f *FFMpeg) hwDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args {
|
|||
}
|
||||
|
||||
// Initialise a video filter for HW encoding
|
||||
func (f *FFMpeg) hwFilterInit(toCodec VideoCodec, fullhw bool) VideoFilter {
|
||||
func (f *FFMpeg) HWFilterInit(toCodec VideoCodec, fullhw bool) VideoFilter {
|
||||
var videoFilter VideoFilter
|
||||
switch toCodec {
|
||||
case VideoCodecV264,
|
||||
|
|
@ -298,7 +299,7 @@ func (f *FFMpeg) hwFilterInit(toCodec VideoCodec, fullhw bool) VideoFilter {
|
|||
|
||||
var scaler_re = regexp.MustCompile(`scale=(?P<value>([-\d]+):([-\d]+))`)
|
||||
|
||||
func templateReplaceScale(input string, template string, match []int, vf *models.VideoFile, minusonehack bool) string {
|
||||
func templateReplaceScale(input string, template string, match []int, width, height int, minusonehack bool) string {
|
||||
result := []byte{}
|
||||
|
||||
if minusonehack {
|
||||
|
|
@ -315,7 +316,7 @@ func templateReplaceScale(input string, template string, match []int, vf *models
|
|||
}
|
||||
|
||||
// Calculate ratio
|
||||
ratio := float64(vf.Width) / float64(vf.Height)
|
||||
ratio := float64(width) / float64(height)
|
||||
if w < 0 {
|
||||
w = int(math.Round(float64(h) * ratio))
|
||||
} else if h < 0 {
|
||||
|
|
@ -342,19 +343,19 @@ func templateReplaceScale(input string, template string, match []int, vf *models
|
|||
}
|
||||
|
||||
// Replace video filter scaling with hardware scaling for full hardware transcoding (also fixes the format)
|
||||
func (f *FFMpeg) hwCodecFilter(args VideoFilter, codec VideoCodec, vf *models.VideoFile, fullhw bool) VideoFilter {
|
||||
func (f *FFMpeg) HWCodecFilter(args VideoFilter, codec VideoCodec, width, height int, fullhw bool) VideoFilter {
|
||||
sargs := string(args)
|
||||
|
||||
match := scaler_re.FindStringSubmatchIndex(sargs)
|
||||
if match == nil {
|
||||
return f.hwApplyFullHWFilter(args, codec, fullhw)
|
||||
return f.HWApplyFullHWFilter(args, codec, fullhw)
|
||||
}
|
||||
|
||||
return f.hwApplyScaleTemplate(sargs, codec, match, vf, fullhw)
|
||||
return f.HWApplyScaleTemplate(sargs, codec, match, width, height, fullhw)
|
||||
}
|
||||
|
||||
// Apply format switching if applicable
|
||||
func (f *FFMpeg) hwApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw bool) VideoFilter {
|
||||
func (f *FFMpeg) HWApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw bool) VideoFilter {
|
||||
switch codec {
|
||||
case VideoCodecN264, VideoCodecN264H:
|
||||
if fullhw && f.version.Gteq(Version{major: 5}) { // Added in FFMpeg 5
|
||||
|
|
@ -380,7 +381,7 @@ func (f *FFMpeg) hwApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw
|
|||
}
|
||||
|
||||
// Switch scaler
|
||||
func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []int, vf *models.VideoFile, fullhw bool) VideoFilter {
|
||||
func (f *FFMpeg) HWApplyScaleTemplate(sargs string, codec VideoCodec, match []int, width, height int, fullhw bool) VideoFilter {
|
||||
var template string
|
||||
|
||||
switch codec {
|
||||
|
|
@ -418,11 +419,11 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in
|
|||
// BUG: scale_vt doesn't call ff_scale_adjust_dimensions, thus cant accept negative size values
|
||||
isApple := codec == VideoCodecM264
|
||||
// Rockchip's scale_rkrga supports -1/-2; don't apply minus-one hack here.
|
||||
return VideoFilter(templateReplaceScale(sargs, template, match, vf, isIntel || isApple))
|
||||
return VideoFilter(templateReplaceScale(sargs, template, match, width, height, isIntel || isApple))
|
||||
}
|
||||
|
||||
// Returns the max resolution for a given codec, or a default
|
||||
func (f *FFMpeg) hwCodecMaxRes(codec VideoCodec) (int, int) {
|
||||
func (f *FFMpeg) HWCodecMaxRes(codec VideoCodec) (int, int) {
|
||||
switch codec {
|
||||
case VideoCodecRK264:
|
||||
return 8192, 8192
|
||||
|
|
@ -437,18 +438,18 @@ func (f *FFMpeg) hwCodecMaxRes(codec VideoCodec) (int, int) {
|
|||
}
|
||||
|
||||
// Return a maxres filter
|
||||
func (f *FFMpeg) hwMaxResFilter(toCodec VideoCodec, vf *models.VideoFile, reqHeight int, fullhw bool) VideoFilter {
|
||||
if vf.Width == 0 || vf.Height == 0 {
|
||||
func (f *FFMpeg) HWMaxResFilter(toCodec VideoCodec, width int, height int, reqHeight int, fullhw bool) VideoFilter {
|
||||
if width == 0 || height == 0 {
|
||||
return ""
|
||||
}
|
||||
videoFilter := f.hwFilterInit(toCodec, fullhw)
|
||||
maxWidth, maxHeight := f.hwCodecMaxRes(toCodec)
|
||||
videoFilter = videoFilter.ScaleMaxLM(vf.Width, vf.Height, reqHeight, maxWidth, maxHeight)
|
||||
return f.hwCodecFilter(videoFilter, toCodec, vf, fullhw)
|
||||
videoFilter := f.HWFilterInit(toCodec, fullhw)
|
||||
maxWidth, maxHeight := f.HWCodecMaxRes(toCodec)
|
||||
videoFilter = videoFilter.ScaleMax(width, height, reqHeight, maxWidth, maxHeight)
|
||||
return f.HWCodecFilter(videoFilter, toCodec, width, height, fullhw)
|
||||
}
|
||||
|
||||
// Return if a hardware accelerated for HLS is available
|
||||
func (f *FFMpeg) hwCodecHLSCompatible() *VideoCodec {
|
||||
// Return a hardware accelerated codec for HLS if available, otherwise the default
|
||||
func (f *FFMpeg) HWCodecHLSCompatible(defaultCodec VideoCodec) VideoCodec {
|
||||
for _, element := range f.getHWCodecSupport() {
|
||||
switch element {
|
||||
case VideoCodecN264,
|
||||
|
|
@ -459,14 +460,14 @@ func (f *FFMpeg) hwCodecHLSCompatible() *VideoCodec {
|
|||
VideoCodecR264,
|
||||
VideoCodecM264, // Note that the Apple encoder sucks at startup, thus HLS quality is crap
|
||||
VideoCodecRK264:
|
||||
return &element
|
||||
return element
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return defaultCodec
|
||||
}
|
||||
|
||||
// Return if a hardware accelerated codec for MP4 is available
|
||||
func (f *FFMpeg) hwCodecMP4Compatible() *VideoCodec {
|
||||
// Return a hardware accelerated codec for MP4 if available, otherwise the default
|
||||
func (f *FFMpeg) HWCodecMP4Compatible(defaultCodec VideoCodec) VideoCodec {
|
||||
for _, element := range f.getHWCodecSupport() {
|
||||
switch element {
|
||||
case VideoCodecN264,
|
||||
|
|
@ -475,20 +476,20 @@ func (f *FFMpeg) hwCodecMP4Compatible() *VideoCodec {
|
|||
VideoCodecI264C,
|
||||
VideoCodecM264,
|
||||
VideoCodecRK264:
|
||||
return &element
|
||||
return element
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return defaultCodec
|
||||
}
|
||||
|
||||
// Return if a hardware accelerated codec for WebM is available
|
||||
func (f *FFMpeg) hwCodecWEBMCompatible() *VideoCodec {
|
||||
// Return a hardware accelerated codec for WebM if available, otherwise the default
|
||||
func (f *FFMpeg) HWCodecWEBMCompatible(defaultCodec VideoCodec) VideoCodec {
|
||||
for _, element := range f.getHWCodecSupport() {
|
||||
switch element {
|
||||
case VideoCodecIVP9,
|
||||
VideoCodecVVP9:
|
||||
return &element
|
||||
return element
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return defaultCodec
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,51 +36,38 @@ func (f VideoFilter) ScaleMaxSize(maxDimensions int) VideoFilter {
|
|||
return f.Append(fmt.Sprintf("scale=%v:%v:force_original_aspect_ratio=decrease", maxDimensions, maxDimensions))
|
||||
}
|
||||
|
||||
// ScaleMax returns a VideoFilter scaling to maxSize. It will scale width if it is larger than height, otherwise it will scale height.
|
||||
func (f VideoFilter) ScaleMax(inputWidth, inputHeight, maxSize int) VideoFilter {
|
||||
// get the smaller dimension of the input
|
||||
videoSize := inputHeight
|
||||
if inputWidth < videoSize {
|
||||
videoSize = inputWidth
|
||||
// ScaleMax scales to reqHeight (0 = source height), optionally clamped to a
|
||||
// maxWidth x maxHeight rect. Aspect ratio is preserved.
|
||||
func (f VideoFilter) ScaleMax(width, height, reqHeight, maxWidth, maxHeight int) VideoFilter {
|
||||
// if a rect is given, clamp to whichever edge overshoots it by more
|
||||
if maxWidth > 0 && maxHeight > 0 {
|
||||
// projected dimensions at reqHeight (or source height if 0)
|
||||
target := reqHeight
|
||||
if target == 0 {
|
||||
target = height
|
||||
}
|
||||
projectedWidth := target * width / height
|
||||
|
||||
if target > maxHeight || projectedWidth > maxWidth {
|
||||
// cap the edge that exceeds its limit by more
|
||||
if target-maxHeight > projectedWidth-maxWidth {
|
||||
return f.ScaleDimensions(-2, maxHeight)
|
||||
}
|
||||
return f.ScaleDimensions(maxWidth, -2)
|
||||
}
|
||||
}
|
||||
|
||||
// if maxSize is larger than the video dimension, then no-op
|
||||
if maxSize >= videoSize || maxSize == 0 {
|
||||
// no-op if reqHeight is larger than the smaller dimension
|
||||
if reqHeight == 0 || reqHeight >= min(width, height) {
|
||||
return f
|
||||
}
|
||||
|
||||
// we're setting either the width or height
|
||||
// we'll set the smaller dimesion
|
||||
if inputWidth > inputHeight {
|
||||
// scale the smaller dimension to reqHeight
|
||||
if width > height {
|
||||
// set the height
|
||||
return f.ScaleDimensions(-2, maxSize)
|
||||
}
|
||||
|
||||
return f.ScaleDimensions(maxSize, -2)
|
||||
}
|
||||
|
||||
// ScaleMaxLM scales an image to fit within specified maximum dimensions while maintaining its aspect ratio.
|
||||
func (f VideoFilter) ScaleMaxLM(width int, height int, reqHeight int, maxWidth int, maxHeight int) VideoFilter {
|
||||
if maxWidth == 0 || maxHeight == 0 {
|
||||
return f.ScaleMax(width, height, reqHeight)
|
||||
}
|
||||
|
||||
aspectRatio := float64(width) / float64(height)
|
||||
desiredHeight := reqHeight
|
||||
if desiredHeight == 0 {
|
||||
desiredHeight = height
|
||||
}
|
||||
desiredWidth := int(float64(desiredHeight) * aspectRatio)
|
||||
|
||||
if desiredHeight <= maxHeight && desiredWidth <= maxWidth {
|
||||
return f.ScaleMax(width, height, reqHeight)
|
||||
}
|
||||
|
||||
if float64(desiredHeight-maxHeight) > float64(desiredWidth-maxWidth) {
|
||||
return f.ScaleDimensions(-2, maxHeight)
|
||||
} else {
|
||||
return f.ScaleDimensions(maxWidth, -2)
|
||||
return f.ScaleDimensions(-2, reqHeight)
|
||||
}
|
||||
return f.ScaleDimensions(reqHeight, -2)
|
||||
}
|
||||
|
||||
// Fps returns a VideoFilter setting the frames per second.
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ var (
|
|||
SegmentType: SegmentTypeTS,
|
||||
ServeManifest: serveHLSManifest,
|
||||
Args: func(codec VideoCodec, segment int, videoFilter VideoFilter, videoOnly bool, outputDir string) (args Args) {
|
||||
args = CodecInit(codec)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
args = append(args,
|
||||
"-flags", "+cgop",
|
||||
"-force_key_frames", fmt.Sprintf("expr:gte(t,n_forced*%d)", segmentLength),
|
||||
|
|
@ -100,7 +101,8 @@ var (
|
|||
SegmentType: SegmentTypeTS,
|
||||
ServeManifest: serveHLSManifest,
|
||||
Args: func(codec VideoCodec, segment int, videoFilter VideoFilter, videoOnly bool, outputDir string) (args Args) {
|
||||
args = CodecInit(codec)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
if videoOnly {
|
||||
args = append(args, "-an")
|
||||
} else {
|
||||
|
|
@ -137,7 +139,8 @@ var (
|
|||
init = "init"
|
||||
}
|
||||
|
||||
args = CodecInit(codec)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
args = append(args,
|
||||
"-force_key_frames", fmt.Sprintf("expr:gte(t,n_forced*%d)", segmentLength),
|
||||
)
|
||||
|
|
@ -311,13 +314,13 @@ func HLSGetCodec(sm *StreamManager, name string) (codec VideoCodec) {
|
|||
switch name {
|
||||
case "hls":
|
||||
codec = VideoCodecLibX264
|
||||
if hwcodec := sm.encoder.hwCodecHLSCompatible(); hwcodec != nil && sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = *hwcodec
|
||||
if sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = sm.encoder.HWCodecHLSCompatible(VideoCodecLibX264)
|
||||
}
|
||||
case "dash-v":
|
||||
codec = VideoCodecVP9
|
||||
if hwcodec := sm.encoder.hwCodecWEBMCompatible(); hwcodec != nil && sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = *hwcodec
|
||||
if sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = sm.encoder.HWCodecWEBMCompatible(VideoCodecVP9)
|
||||
}
|
||||
case "hls-copy":
|
||||
codec = VideoCodecCopy
|
||||
|
|
@ -335,8 +338,8 @@ func (s *runningStream) makeStreamArgs(sm *StreamManager, segment int) Args {
|
|||
|
||||
codec := HLSGetCodec(sm, s.streamType.Name)
|
||||
|
||||
fullhw := sm.config.GetTranscodeHardwareAcceleration() && sm.encoder.hwCanFullHWTranscode(sm.context, codec, s.vf, s.maxTranscodeSize)
|
||||
args = sm.encoder.hwDeviceInit(args, codec, fullhw)
|
||||
fullhw := sm.config.GetTranscodeHardwareAcceleration() && sm.encoder.HWCanFullHWTranscode(sm.context, codec, s.vf.Path, s.vf.Width, s.vf.Height, s.maxTranscodeSize)
|
||||
args = sm.encoder.HWDeviceInit(args, codec, fullhw)
|
||||
args = append(args, extraInputArgs...)
|
||||
|
||||
if segment > 0 {
|
||||
|
|
@ -347,7 +350,7 @@ func (s *runningStream) makeStreamArgs(sm *StreamManager, segment int) Args {
|
|||
|
||||
videoOnly := ProbeAudioCodec(s.vf.AudioCodec) == MissingUnsupported
|
||||
|
||||
videoFilter := sm.encoder.hwMaxResFilter(codec, s.vf, s.maxTranscodeSize, fullhw)
|
||||
videoFilter := sm.encoder.HWMaxResFilter(codec, s.vf.Width, s.vf.Height, s.maxTranscodeSize, fullhw)
|
||||
|
||||
args = append(args, s.streamType.Args(codec, segment, videoFilter, videoOnly, s.outputDir)...)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,84 +19,12 @@ type StreamFormat struct {
|
|||
Args func(codec VideoCodec, videoFilter VideoFilter, videoOnly bool) Args
|
||||
}
|
||||
|
||||
func CodecInit(codec VideoCodec) (args Args) {
|
||||
args = args.VideoCodec(codec)
|
||||
|
||||
switch codec {
|
||||
// CPU Codecs
|
||||
case VideoCodecLibX264:
|
||||
args = append(args,
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-preset", "veryfast",
|
||||
"-crf", "25",
|
||||
"-sc_threshold", "0",
|
||||
)
|
||||
case VideoCodecVP9:
|
||||
args = append(args,
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-deadline", "realtime",
|
||||
"-cpu-used", "5",
|
||||
"-row-mt", "1",
|
||||
"-crf", "30",
|
||||
"-b:v", "0",
|
||||
)
|
||||
// HW Codecs
|
||||
case VideoCodecN264:
|
||||
args = append(args,
|
||||
"-rc", "vbr",
|
||||
"-cq", "15",
|
||||
)
|
||||
case VideoCodecN264H:
|
||||
args = append(args,
|
||||
"-profile", "p7",
|
||||
"-tune", "hq",
|
||||
"-profile", "high",
|
||||
"-rc", "vbr",
|
||||
"-rc-lookahead", "60",
|
||||
"-surfaces", "64",
|
||||
"-spatial-aq", "1",
|
||||
"-aq-strength", "15",
|
||||
"-cq", "15",
|
||||
"-coder", "cabac",
|
||||
"-b_ref_mode", "middle",
|
||||
)
|
||||
case VideoCodecI264, VideoCodecIVP9:
|
||||
args = append(args,
|
||||
"-global_quality", "20",
|
||||
"-preset", "faster",
|
||||
)
|
||||
case VideoCodecI264C:
|
||||
args = append(args,
|
||||
"-q", "20",
|
||||
"-preset", "faster",
|
||||
)
|
||||
case VideoCodecV264, VideoCodecVVP9:
|
||||
args = append(args,
|
||||
"-qp", "20",
|
||||
)
|
||||
case VideoCodecA264:
|
||||
args = append(args,
|
||||
"-quality", "speed",
|
||||
)
|
||||
case VideoCodecM264:
|
||||
args = append(args,
|
||||
"-realtime", "1",
|
||||
)
|
||||
case VideoCodecO264:
|
||||
args = append(args,
|
||||
"-preset", "superfast",
|
||||
"-crf", "25",
|
||||
)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
var (
|
||||
StreamTypeMP4 = StreamFormat{
|
||||
MimeType: MimeMp4Video,
|
||||
Args: func(codec VideoCodec, videoFilter VideoFilter, videoOnly bool) (args Args) {
|
||||
args = CodecInit(codec)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
args = append(args, "-movflags", "frag_keyframe+empty_moov")
|
||||
args = args.VideoFilter(videoFilter)
|
||||
if videoOnly {
|
||||
|
|
@ -111,7 +39,8 @@ var (
|
|||
StreamTypeWEBM = StreamFormat{
|
||||
MimeType: MimeWebmVideo,
|
||||
Args: func(codec VideoCodec, videoFilter VideoFilter, videoOnly bool) (args Args) {
|
||||
args = CodecInit(codec)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
args = args.VideoFilter(videoFilter)
|
||||
if videoOnly {
|
||||
args = args.SkipAudio()
|
||||
|
|
@ -125,7 +54,8 @@ var (
|
|||
StreamTypeMKV = StreamFormat{
|
||||
MimeType: MimeMkvVideo,
|
||||
Args: func(codec VideoCodec, videoFilter VideoFilter, videoOnly bool) (args Args) {
|
||||
args = CodecInit(codec)
|
||||
args = args.VideoCodec(codec)
|
||||
args = append(args, codec.ExtraArgs()...)
|
||||
if videoOnly {
|
||||
args = args.SkipAudio()
|
||||
} else {
|
||||
|
|
@ -166,16 +96,16 @@ func (o TranscodeOptions) FileGetCodec(sm *StreamManager, maxTranscodeSize int)
|
|||
return VideoCodecCopy
|
||||
}
|
||||
codec = VideoCodecLibX264
|
||||
if hwcodec := sm.encoder.hwCodecMP4Compatible(); hwcodec != nil && sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = *hwcodec
|
||||
if sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = sm.encoder.HWCodecMP4Compatible(VideoCodecLibX264)
|
||||
}
|
||||
case MimeWebmVideo:
|
||||
if !needsResize && (o.VideoFile.VideoCodec == Vp8 || o.VideoFile.VideoCodec == Vp9) {
|
||||
return VideoCodecCopy
|
||||
}
|
||||
codec = VideoCodecVP9
|
||||
if hwcodec := sm.encoder.hwCodecWEBMCompatible(); hwcodec != nil && sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = *hwcodec
|
||||
if sm.config.GetTranscodeHardwareAcceleration() {
|
||||
codec = sm.encoder.HWCodecWEBMCompatible(VideoCodecVP9)
|
||||
}
|
||||
case MimeMkvVideo:
|
||||
codec = VideoCodecCopy
|
||||
|
|
@ -197,8 +127,8 @@ func (o TranscodeOptions) makeStreamArgs(sm *StreamManager) Args {
|
|||
|
||||
codec := o.FileGetCodec(sm, maxTranscodeSize)
|
||||
|
||||
fullhw := sm.config.GetTranscodeHardwareAcceleration() && sm.encoder.hwCanFullHWTranscode(sm.context, codec, o.VideoFile, maxTranscodeSize)
|
||||
args = sm.encoder.hwDeviceInit(args, codec, fullhw)
|
||||
fullhw := sm.config.GetTranscodeHardwareAcceleration() && sm.encoder.HWCanFullHWTranscode(sm.context, codec, o.VideoFile.Path, o.VideoFile.Width, o.VideoFile.Height, maxTranscodeSize)
|
||||
args = sm.encoder.HWDeviceInit(args, codec, fullhw)
|
||||
args = append(args, extraInputArgs...)
|
||||
|
||||
if o.StartTime != 0 {
|
||||
|
|
@ -209,7 +139,7 @@ func (o TranscodeOptions) makeStreamArgs(sm *StreamManager) Args {
|
|||
|
||||
videoOnly := ProbeAudioCodec(o.VideoFile.AudioCodec) == MissingUnsupported
|
||||
|
||||
videoFilter := sm.encoder.hwMaxResFilter(codec, o.VideoFile, maxTranscodeSize, fullhw)
|
||||
videoFilter := sm.encoder.HWMaxResFilter(codec, o.VideoFile.Width, o.VideoFile.Height, maxTranscodeSize, fullhw)
|
||||
|
||||
args = append(args, o.StreamType.Args(codec, videoFilter, videoOnly)...)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue