owncast/core/transcoder/codecs.go
Gabe Kangas 5214d81264
Codec selection (#892)
* 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>
2021-04-15 13:55:51 -07:00

373 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{}
}
}