mirror of
https://github.com/owncast/owncast.git
synced 2025-11-02 03:54:54 +08:00
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>
This commit is contained in:
373
core/transcoder/codecs.go
Normal file
373
core/transcoder/codecs.go
Normal file
@ -0,0 +1,373 @@
|
||||
//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{}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user