Add ffmpeg auto codec selection logic

This commit is contained in:
Alex X
2024-05-22 18:49:43 +03:00
parent 82fa803a37
commit 8a7712a4c8
2 changed files with 120 additions and 4 deletions
+8 -4
View File
@@ -2,6 +2,7 @@ package ffmpeg
import (
"net/url"
"slices"
"strings"
"github.com/AlexxIT/go2rtc/internal/app"
@@ -28,9 +29,14 @@ func Init() {
streams.RedirectFunc("ffmpeg", func(url string) (string, error) {
args := parseArgs(url[7:])
if slices.Contains(args.Codecs, "auto") {
return "", nil // force call streams.HandleFunc("ffmpeg")
}
return "exec:" + args.String(), nil
})
streams.HandleFunc("ffmpeg", NewProducer)
device.Init(defaults["bin"])
hardware.Init(defaults["bin"])
}
@@ -85,6 +91,8 @@ var defaults = map[string]string{
"pcml/8000": "-c:a pcm_s16le -ar:a 8000 -ac:a 1",
"pcml/44100": "-c:a pcm_s16le -ar:a 44100 -ac:a 1",
"opus/48000/2": "-c:a libopus -application:a lowdelay -min_comp 0 -ar:a 48000 -ac:a 2",
// hardware Intel and AMD on Linux
// better not to set `-async_depth:v 1` like for QSV, because framedrops
// `-bf 0` - disable B-frames is very important
@@ -202,10 +210,6 @@ func parseArgs(s string) *ffmpeg.Args {
args.Input = inputTemplate("file", s, query)
}
if args.Input == "" {
return nil
}
if query["async"] != nil {
args.Input = "-use_wallclock_as_timestamps 1 -async 1 " + args.Input
}
+112
View File
@@ -0,0 +1,112 @@
package ffmpeg
import (
"encoding/json"
"errors"
"net/url"
"strconv"
"strings"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/aac"
"github.com/AlexxIT/go2rtc/pkg/core"
)
type Producer struct {
core.SuperProducer
url string
query url.Values
ffmpeg core.Producer
}
// NewProducer - FFmpeg producer with auto selection video/audio codec based on client capabilities
func NewProducer(url string) (core.Producer, error) {
p := &Producer{}
i := strings.IndexByte(url, '#')
p.url, p.query = url[:i], streams.ParseQuery(url[i+1:])
// ffmpeg.NewProducer support only one audio
if len(p.query["video"]) != 0 || len(p.query["audio"]) != 1 {
return nil, errors.New("ffmpeg: unsupported params: " + url[i:])
}
p.Type = "FFmpeg producer"
p.Medias = []*core.Media{
{
Kind: core.KindAudio,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{Name: core.CodecOpus, ClockRate: 48000, Channels: 2},
{Name: core.CodecAAC, ClockRate: 16000, FmtpLine: aac.FMTP + "1408"},
{Name: core.CodecPCM, ClockRate: 16000},
{Name: core.CodecPCM, ClockRate: 8000},
{Name: core.CodecPCMA, ClockRate: 16000},
{Name: core.CodecPCMA, ClockRate: 8000},
{Name: core.CodecPCMU, ClockRate: 16000},
{Name: core.CodecPCMU, ClockRate: 8000},
},
},
}
return p, nil
}
func (p *Producer) Start() error {
var err error
if p.ffmpeg, err = streams.GetProducer(p.newURL()); err != nil {
return err
}
for i, media := range p.ffmpeg.GetMedias() {
track, err := p.ffmpeg.GetTrack(media, media.Codecs[0])
if err != nil {
return err
}
p.Receivers[i].Replace(track)
}
return p.ffmpeg.Start()
}
func (p *Producer) Stop() error {
if p.ffmpeg == nil {
return nil
}
return p.ffmpeg.Stop()
}
func (p *Producer) MarshalJSON() ([]byte, error) {
if p.ffmpeg == nil {
return json.Marshal(p.SuperProducer)
}
return json.Marshal(p.ffmpeg)
}
func (p *Producer) newURL() string {
s := p.url
// rewrite codecs in url from auto to known presets from defaults
for _, receiver := range p.Receivers {
codec := receiver.Codec
switch codec.Name {
case core.CodecPCMU, core.CodecPCMA:
s += "#audio=" + strings.ToLower(codec.Name)
if codec.ClockRate != 0 {
s += "/" + strconv.Itoa(int(codec.ClockRate))
}
case core.CodecAAC:
s += "#audio=aac/16000"
case core.CodecOpus:
s += "#audio=opus/48000/2"
}
}
// add other params
for key, values := range p.query {
if key != "audio" {
for _, value := range values {
s += "#" + key + "=" + value
}
}
}
return s
}