5214d81264
* Query for installed codecs * Start modeling out codecs * Can now specify a codec and get the correct settings returned from the model * Return codecs in admin/serverconfig * Start handling transcoding errors and return messages to user * filter available codecs against a whitelist * Fix merge * Codecs are working * Switching between codecs work * Add apis for setting a custom video codec * Cleanup the logging of transcoder errors * Add v4l codec * Add fetching v4l * Add support for per-codec presets * Use updated nvenc encoding parameters * Update log message * Some more codec WIP * Turn off v4l. It is a mess. * Try to make the lowest latency level a bit more playable * Use a human redable display name in console messages * Turn on transcoder persistent connections * Add more codec-related user-facing error messages * Give the initial offline state transcoder an id * Force a minimum segment count of 3 * Disable qsv for now. set x264 specific params in VariantFlags * Close body in case * Ignore vbv underflow message, it is not actionable * Determine a dynamic gop value based on the length of segments * Add codec-specific tests * Cleanup * Ignore goconst lint warnings in codec file * Troubleshoot omx * Add more codec tests * Remove no longer accurate comment * Bundle admin from codec branch * Revert back to old setting * Cleanup list of codecs a bit * Remove old references to the encoder preset * Commit updated API documentation * Update admin bundle * Commit updated API documentation * Add codec setting to api spec * Commit updated API documentation Co-authored-by: Owncast <owncast@owncast.online>
374 lines
6.7 KiB
Go
374 lines
6.7 KiB
Go
//nolint:goconst
|
|
|
|
package transcoder
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Codec represents a supported codec on the system.
|
|
type Codec interface {
|
|
Name() string
|
|
DisplayName() string
|
|
GlobalFlags() string
|
|
PixelFormat() string
|
|
ExtraArguments() string
|
|
ExtraFilters() string
|
|
VariantFlags(v *HLSVariant) string
|
|
GetPresetForLevel(l int) string
|
|
}
|
|
|
|
var supportedCodecs = map[string]string{
|
|
(&Libx264Codec{}).Name(): "libx264",
|
|
(&OmxCodec{}).Name(): "omx",
|
|
(&VaapiCodec{}).Name(): "vaapi",
|
|
(&NvencCodec{}).Name(): "NVIDIA nvenc",
|
|
}
|
|
|
|
type Libx264Codec struct {
|
|
}
|
|
|
|
func (c *Libx264Codec) Name() string {
|
|
return "libx264"
|
|
}
|
|
|
|
func (c *Libx264Codec) DisplayName() string {
|
|
return "x264"
|
|
}
|
|
|
|
func (c *Libx264Codec) GlobalFlags() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *Libx264Codec) PixelFormat() string {
|
|
return "yuv420p"
|
|
}
|
|
|
|
func (c *Libx264Codec) ExtraArguments() string {
|
|
return strings.Join([]string{
|
|
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
|
}, " ")
|
|
}
|
|
|
|
func (c *Libx264Codec) ExtraFilters() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *Libx264Codec) VariantFlags(v *HLSVariant) string {
|
|
bufferSize := int(float64(v.videoBitrate) * 1.2) // How often it checks the bitrate of encoded segments to see if it's too high/low.
|
|
|
|
return strings.Join([]string{
|
|
fmt.Sprintf("-x264-params:v:%d \"scenecut=0:open_gop=0\"", v.index), // How often the encoder checks the bitrate in order to meet average/max values
|
|
fmt.Sprintf("-bufsize:v:%d %dk", v.index, bufferSize),
|
|
fmt.Sprintf("-profile:v:%d %s", v.index, "high"), // Encoding profile
|
|
}, " ")
|
|
}
|
|
|
|
func (c *Libx264Codec) GetPresetForLevel(l int) string {
|
|
presetMapping := []string{
|
|
"ultrafast",
|
|
"superfast",
|
|
"veryfast",
|
|
"faster",
|
|
"fast",
|
|
}
|
|
|
|
if l >= len(presetMapping) {
|
|
return "superfast"
|
|
}
|
|
|
|
return presetMapping[l]
|
|
}
|
|
|
|
type OmxCodec struct {
|
|
}
|
|
|
|
func (c *OmxCodec) Name() string {
|
|
return "h264_omx"
|
|
}
|
|
|
|
func (c *OmxCodec) DisplayName() string {
|
|
return "OpenMAX (omx)"
|
|
}
|
|
|
|
func (c *OmxCodec) GlobalFlags() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *OmxCodec) PixelFormat() string {
|
|
return "yuv420p"
|
|
}
|
|
|
|
func (c *OmxCodec) ExtraArguments() string {
|
|
return strings.Join([]string{
|
|
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
|
}, " ")
|
|
}
|
|
|
|
func (c *OmxCodec) ExtraFilters() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *OmxCodec) VariantFlags(v *HLSVariant) string {
|
|
return ""
|
|
}
|
|
|
|
func (c *OmxCodec) GetPresetForLevel(l int) string {
|
|
presetMapping := []string{
|
|
"ultrafast",
|
|
"superfast",
|
|
"veryfast",
|
|
"faster",
|
|
"fast",
|
|
}
|
|
|
|
if l >= len(presetMapping) {
|
|
return "superfast"
|
|
}
|
|
|
|
return presetMapping[l]
|
|
}
|
|
|
|
type VaapiCodec struct {
|
|
}
|
|
|
|
func (c *VaapiCodec) Name() string {
|
|
return "h264_vaapi"
|
|
}
|
|
|
|
func (c *VaapiCodec) DisplayName() string {
|
|
return "VA-API"
|
|
}
|
|
|
|
func (c *VaapiCodec) GlobalFlags() string {
|
|
flags := []string{
|
|
"-vaapi_device", "/dev/dri/renderD128",
|
|
}
|
|
|
|
return strings.Join(flags, " ")
|
|
}
|
|
|
|
func (c *VaapiCodec) PixelFormat() string {
|
|
return "vaapi_vld"
|
|
}
|
|
|
|
func (c *VaapiCodec) ExtraFilters() string {
|
|
return "format=nv12,hwupload"
|
|
}
|
|
|
|
func (c *VaapiCodec) ExtraArguments() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *VaapiCodec) VariantFlags(v *HLSVariant) string {
|
|
return ""
|
|
}
|
|
|
|
func (c *VaapiCodec) GetPresetForLevel(l int) string {
|
|
presetMapping := []string{
|
|
"ultrafast",
|
|
"superfast",
|
|
"veryfast",
|
|
"faster",
|
|
"fast",
|
|
}
|
|
|
|
if l >= len(presetMapping) {
|
|
return "superfast"
|
|
}
|
|
|
|
return presetMapping[l]
|
|
}
|
|
|
|
type NvencCodec struct {
|
|
}
|
|
|
|
func (c *NvencCodec) Name() string {
|
|
return "h264_nvenc"
|
|
}
|
|
|
|
func (c *NvencCodec) DisplayName() string {
|
|
return "nvidia nvenc"
|
|
}
|
|
|
|
func (c *NvencCodec) GlobalFlags() string {
|
|
flags := []string{
|
|
"-hwaccel cuda",
|
|
}
|
|
|
|
return strings.Join(flags, " ")
|
|
}
|
|
|
|
func (c *NvencCodec) PixelFormat() string {
|
|
return "yuv420p"
|
|
}
|
|
|
|
func (c *NvencCodec) ExtraArguments() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *NvencCodec) ExtraFilters() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *NvencCodec) VariantFlags(v *HLSVariant) string {
|
|
tuning := "ll" // low latency
|
|
return fmt.Sprintf("-tune:v:%d %s", v.index, tuning)
|
|
}
|
|
|
|
func (c *NvencCodec) GetPresetForLevel(l int) string {
|
|
presetMapping := []string{
|
|
"p1",
|
|
"p2",
|
|
"p3",
|
|
"p4",
|
|
"p5",
|
|
}
|
|
|
|
if l >= len(presetMapping) {
|
|
return "p3"
|
|
}
|
|
|
|
return presetMapping[l]
|
|
}
|
|
|
|
type QuicksyncCodec struct {
|
|
}
|
|
|
|
func (c *QuicksyncCodec) Name() string {
|
|
return "h264_qsv"
|
|
}
|
|
|
|
func (c *QuicksyncCodec) DisplayName() string {
|
|
return "Intel QuickSync"
|
|
}
|
|
|
|
func (c *QuicksyncCodec) GlobalFlags() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *QuicksyncCodec) PixelFormat() string {
|
|
return "nv12"
|
|
}
|
|
|
|
func (c *QuicksyncCodec) ExtraArguments() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *QuicksyncCodec) ExtraFilters() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *QuicksyncCodec) VariantFlags(v *HLSVariant) string {
|
|
return ""
|
|
}
|
|
|
|
func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
|
|
presetMapping := []string{
|
|
"ultrafast",
|
|
"superfast",
|
|
"veryfast",
|
|
"faster",
|
|
"fast",
|
|
}
|
|
|
|
if l >= len(presetMapping) {
|
|
return "superfast"
|
|
}
|
|
|
|
return presetMapping[l]
|
|
}
|
|
|
|
type Video4Linux struct{}
|
|
|
|
func (c *Video4Linux) Name() string {
|
|
return "h264_v4l2m2m"
|
|
}
|
|
|
|
func (c *Video4Linux) DisplayName() string {
|
|
return "Video4Linux"
|
|
}
|
|
|
|
func (c *Video4Linux) GlobalFlags() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *Video4Linux) PixelFormat() string {
|
|
return "nv21"
|
|
}
|
|
|
|
func (c *Video4Linux) ExtraArguments() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *Video4Linux) ExtraFilters() string {
|
|
return ""
|
|
}
|
|
|
|
func (c *Video4Linux) VariantFlags(v *HLSVariant) string {
|
|
return ""
|
|
}
|
|
|
|
func (c *Video4Linux) GetPresetForLevel(l int) string {
|
|
presetMapping := []string{
|
|
"ultrafast",
|
|
"superfast",
|
|
"veryfast",
|
|
"faster",
|
|
"fast",
|
|
}
|
|
|
|
if l >= len(presetMapping) {
|
|
return "superfast"
|
|
}
|
|
|
|
return presetMapping[l]
|
|
}
|
|
|
|
// GetCodecs will return the supported codecs available on the system.
|
|
func GetCodecs(ffmpegPath string) []string {
|
|
codecs := make([]string, 0)
|
|
|
|
cmd := exec.Command(ffmpegPath, "-encoders")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return codecs
|
|
}
|
|
|
|
response := string(out)
|
|
lines := strings.Split(response, "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "H.264") {
|
|
fields := strings.Fields(line)
|
|
codec := fields[1]
|
|
if _, supported := supportedCodecs[codec]; supported {
|
|
codecs = append(codecs, codec)
|
|
}
|
|
}
|
|
}
|
|
|
|
return codecs
|
|
}
|
|
|
|
func getCodec(name string) Codec {
|
|
switch name {
|
|
case (&NvencCodec{}).Name():
|
|
return &NvencCodec{}
|
|
case (&VaapiCodec{}).Name():
|
|
return &VaapiCodec{}
|
|
case (&QuicksyncCodec{}).Name():
|
|
return &QuicksyncCodec{}
|
|
case (&OmxCodec{}).Name():
|
|
return &OmxCodec{}
|
|
case (&Video4Linux{}).Name():
|
|
return &Video4Linux{}
|
|
default:
|
|
return &Libx264Codec{}
|
|
}
|
|
}
|