Skip to content

Commit

Permalink
Add ffmpeg auto codec selection logic
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexxIT committed May 22, 2024
1 parent 82fa803 commit 8a7712a
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 4 deletions.
12 changes: 8 additions & 4 deletions internal/ffmpeg/ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ffmpeg

import (
"net/url"
"slices"
"strings"

"github.com/AlexxIT/go2rtc/internal/app"
Expand All @@ -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"])
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
112 changes: 112 additions & 0 deletions internal/ffmpeg/producer.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 8a7712a

Please sign in to comment.