Merge branch 'master' of https://github.com/horttorrell32/go2rtc
This commit is contained in:
@@ -3,24 +3,41 @@ package device
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/internal/api"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// https://trac.ffmpeg.org/wiki/Capture/Webcam
|
||||
const deviceInputPrefix = "-f avfoundation"
|
||||
func queryToInput(query url.Values) string {
|
||||
video := query.Get("video")
|
||||
audio := query.Get("audio")
|
||||
|
||||
func deviceInputSuffix(video, audio string) string {
|
||||
switch {
|
||||
case video != "" && audio != "":
|
||||
return `"` + video + `:` + audio + `"`
|
||||
case video != "":
|
||||
return `"` + video + `"`
|
||||
case audio != "":
|
||||
return `":` + audio + `"`
|
||||
if video == "" && audio == "" {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
|
||||
// https://ffmpeg.org/ffmpeg-devices.html#avfoundation
|
||||
input := "-f avfoundation"
|
||||
|
||||
if video != "" {
|
||||
video = indexToItem(videos, video)
|
||||
|
||||
for key, value := range query {
|
||||
switch key {
|
||||
case "resolution":
|
||||
input += " -video_size " + value[0]
|
||||
case "pixel_format", "framerate", "video_size", "capture_cursor", "capture_mouse_clicks", "capture_raw_data":
|
||||
input += " -" + key + " " + value[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if audio != "" {
|
||||
audio = indexToItem(audios, audio)
|
||||
}
|
||||
|
||||
return input + ` -i "` + video + `:` + audio + `"`
|
||||
}
|
||||
|
||||
func initDevices() {
|
||||
|
||||
@@ -3,19 +3,36 @@ package device
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/internal/api"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// https://trac.ffmpeg.org/wiki/Capture/Webcam
|
||||
const deviceInputPrefix = "-f v4l2"
|
||||
func queryToInput(query url.Values) string {
|
||||
if video := query.Get("video"); video != "" {
|
||||
// https://ffmpeg.org/ffmpeg-devices.html#video4linux2_002c-v4l2
|
||||
input := "-f v4l2"
|
||||
|
||||
func deviceInputSuffix(video, audio string) string {
|
||||
if video != "" {
|
||||
return video
|
||||
for key, value := range query {
|
||||
switch key {
|
||||
case "resolution":
|
||||
input += " -video_size " + value[0]
|
||||
case "video_size", "pixel_format", "input_format", "framerate", "use_libv4l2":
|
||||
input += " -" + key + " " + value[0]
|
||||
}
|
||||
}
|
||||
|
||||
return input + " -i " + indexToItem(videos, video)
|
||||
}
|
||||
|
||||
if audio := query.Get("audio"); audio != "" {
|
||||
input := "-f alsa"
|
||||
|
||||
return input + " -i " + indexToItem(audios, audio)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -57,4 +74,15 @@ func initDevices() {
|
||||
streams = append(streams, stream)
|
||||
}
|
||||
}
|
||||
|
||||
err = exec.Command(Bin, "-f", "alsa", "-i", "default", "-t", "1", "-f", "null", "-").Run()
|
||||
if err == nil {
|
||||
stream := api.Stream{
|
||||
Name: "ALSA default",
|
||||
URL: "ffmpeg:device?audio=default#audio=opus",
|
||||
}
|
||||
|
||||
audios = append(audios, "default")
|
||||
streams = append(streams, stream)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,58 @@ package device
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/internal/api"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// https://trac.ffmpeg.org/wiki/DirectShow
|
||||
const deviceInputPrefix = "-f dshow"
|
||||
func queryToInput(query url.Values) string {
|
||||
video := query.Get("video")
|
||||
audio := query.Get("audio")
|
||||
|
||||
if video == "" && audio == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// https://ffmpeg.org/ffmpeg-devices.html#dshow
|
||||
input := "-f dshow"
|
||||
|
||||
if video != "" {
|
||||
video = indexToItem(videos, video)
|
||||
|
||||
for key, value := range query {
|
||||
switch key {
|
||||
case "resolution":
|
||||
input += " -video_size " + value[0]
|
||||
case "video_size", "framerate", "pixel_format":
|
||||
input += " -" + key + " " + value[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if audio != "" {
|
||||
audio = indexToItem(audios, audio)
|
||||
|
||||
for key, value := range query {
|
||||
switch key {
|
||||
case "sample_rate", "sample_size", "channels", "audio_buffer_size":
|
||||
input += " -" + key + " " + value[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if video != "" {
|
||||
input += ` -i video="` + video + `"`
|
||||
|
||||
if audio != "" {
|
||||
input += `:audio="` + audio + `"`
|
||||
}
|
||||
} else {
|
||||
input += ` -i audio="` + audio + `"`
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
func deviceInputSuffix(video, audio string) string {
|
||||
switch {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/AlexxIT/go2rtc/internal/api"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -16,45 +17,23 @@ func Init(bin string) {
|
||||
}
|
||||
|
||||
func GetInput(src string) (string, error) {
|
||||
i := strings.IndexByte(src, '?')
|
||||
if i < 0 {
|
||||
return "", errors.New("empty query: " + src)
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(src[i+1:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
runonce.Do(initDevices)
|
||||
|
||||
input := deviceInputPrefix
|
||||
|
||||
var video, audio string
|
||||
|
||||
if i := strings.IndexByte(src, '?'); i > 0 {
|
||||
query, err := url.ParseQuery(src[i+1:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for key, value := range query {
|
||||
switch key {
|
||||
case "video":
|
||||
video = value[0]
|
||||
case "audio":
|
||||
audio = value[0]
|
||||
case "resolution":
|
||||
input += " -video_size " + value[0]
|
||||
default: // "input_format", "framerate", "video_size"
|
||||
input += " -" + key + " " + value[0]
|
||||
}
|
||||
}
|
||||
if input := queryToInput(query); input != "" {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
if video != "" {
|
||||
if i, err := strconv.Atoi(video); err == nil && i < len(videos) {
|
||||
video = videos[i]
|
||||
}
|
||||
}
|
||||
if audio != "" {
|
||||
if i, err := strconv.Atoi(audio); err == nil && i < len(audios) {
|
||||
audio = audios[i]
|
||||
}
|
||||
}
|
||||
|
||||
input += " -i " + deviceInputSuffix(video, audio)
|
||||
|
||||
return input, nil
|
||||
return "", errors.New("wrong query: " + src)
|
||||
}
|
||||
|
||||
var Bin string
|
||||
@@ -68,3 +47,10 @@ func apiDevices(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
api.ResponseStreams(w, streams)
|
||||
}
|
||||
|
||||
func indexToItem(items []string, index string) string {
|
||||
if i, err := strconv.Atoi(index); err == nil && i < len(items) {
|
||||
return items[i]
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
@@ -63,7 +63,9 @@ var defaults = map[string]string{
|
||||
//"mjpeg": "-c:v mjpeg -force_duplicated_matrix:v 1 -huffman:v 0 -pix_fmt:v yuvj420p",
|
||||
|
||||
// https://ffmpeg.org/ffmpeg-codecs.html#libopus-1
|
||||
"opus": "-c:a libopus -ar:a 48000 -ac:a 2 -application:a voip -compression_level:a 0",
|
||||
// https://github.com/pion/webrtc/issues/1514
|
||||
// `-af adelay=0|0` - force frame_size=960, important for WebRTC audio quality
|
||||
"opus": "-c:a libopus -ar:a 48000 -ac:a 2 -application:a voip -af adelay=0|0",
|
||||
"pcmu": "-c:a pcm_mulaw -ar:a 8000 -ac:a 1",
|
||||
"pcmu/16000": "-c:a pcm_mulaw -ar:a 16000 -ac:a 1",
|
||||
"pcmu/48000": "-c:a pcm_mulaw -ar:a 48000 -ac:a 1",
|
||||
@@ -90,8 +92,8 @@ var defaults = map[string]string{
|
||||
|
||||
// hardware NVidia on Linux and Windows
|
||||
// preset=p2 - faster, tune=ll - low latency
|
||||
"h264/cuda": "-c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll",
|
||||
"h265/cuda": "-c:v hevc_nvenc -g 50 -profile:v high -level:v auto",
|
||||
"h264/cuda": "-c:v h264_nvenc -g 50 -bf 0 -profile:v high -level:v auto -preset:v p2 -tune:v ll",
|
||||
"h265/cuda": "-c:v hevc_nvenc -g 50 -bf 0 -profile:v high -level:v auto",
|
||||
|
||||
// hardware Intel on Windows
|
||||
"h264/dxva2": "-c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1",
|
||||
@@ -103,6 +105,14 @@ var defaults = map[string]string{
|
||||
"h265/videotoolbox": "-c:v hevc_videotoolbox -g 50 -bf 0 -profile:v high -level:v 5.1",
|
||||
}
|
||||
|
||||
// configTemplate - return template from config (defaults) if exist or return raw template
|
||||
func configTemplate(template string) string {
|
||||
if s := defaults[template]; s != "" {
|
||||
return s
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
||||
// inputTemplate - select input template from YAML config by template name
|
||||
// if query has input param - select another template by this name
|
||||
// if there is no another template - use input param as template
|
||||
@@ -110,9 +120,7 @@ var defaults = map[string]string{
|
||||
func inputTemplate(name, s string, query url.Values) string {
|
||||
var template string
|
||||
if input := query.Get("input"); input != "" {
|
||||
if template = defaults[input]; template == "" {
|
||||
template = input
|
||||
}
|
||||
template = configTemplate(input)
|
||||
} else {
|
||||
template = defaults[name]
|
||||
}
|
||||
@@ -199,6 +207,8 @@ func parseArgs(s string) *ffmpeg.Args {
|
||||
if len(query) != 0 {
|
||||
// 1. Process raw params for FFmpeg
|
||||
for _, raw := range query["raw"] {
|
||||
// support templates https://github.com/AlexxIT/go2rtc/issues/487
|
||||
raw = configTemplate(raw)
|
||||
args.AddCodec(raw)
|
||||
}
|
||||
|
||||
@@ -234,6 +244,18 @@ func parseArgs(s string) *ffmpeg.Args {
|
||||
}
|
||||
}
|
||||
|
||||
for _, drawtext := range query["drawtext"] {
|
||||
// support templates https://github.com/AlexxIT/go2rtc/issues/487
|
||||
drawtext = configTemplate(drawtext)
|
||||
|
||||
// support default timestamp format
|
||||
if !strings.Contains(drawtext, "text=") {
|
||||
drawtext += `:text='%{localtime\:%Y-%m-%d %X}'`
|
||||
}
|
||||
|
||||
args.AddFilter("drawtext=" + drawtext)
|
||||
}
|
||||
|
||||
// 3. Process video codecs
|
||||
if args.Video > 0 {
|
||||
for _, video := range query["video"] {
|
||||
|
||||
@@ -55,33 +55,51 @@ func MakeHardware(args *ffmpeg.Args, engine string, defaults map[string]string)
|
||||
|
||||
switch engine {
|
||||
case EngineVAAPI:
|
||||
args.Input = "-hwaccel vaapi -hwaccel_output_format vaapi " + args.Input
|
||||
args.Codecs[i] = defaults[name+"/"+engine]
|
||||
|
||||
for i, filter := range args.Filters {
|
||||
if strings.HasPrefix(filter, "scale=") {
|
||||
args.Filters[i] = "scale_vaapi=" + filter[6:]
|
||||
}
|
||||
if strings.HasPrefix(filter, "transpose=") {
|
||||
if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
|
||||
args.Filters[i] = "transpose_vaapi=4" // reversal
|
||||
} else {
|
||||
args.Filters[i] = "transpose_vaapi=" + filter[10:]
|
||||
if !args.HasFilters("drawtext=") {
|
||||
args.Input = "-hwaccel vaapi -hwaccel_output_format vaapi " + args.Input
|
||||
|
||||
for i, filter := range args.Filters {
|
||||
if strings.HasPrefix(filter, "scale=") {
|
||||
args.Filters[i] = "scale_vaapi=" + filter[6:]
|
||||
}
|
||||
if strings.HasPrefix(filter, "transpose=") {
|
||||
if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
|
||||
args.Filters[i] = "transpose_vaapi=4" // reversal
|
||||
} else {
|
||||
args.Filters[i] = "transpose_vaapi=" + filter[10:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fix if input doesn't support hwaccel, do nothing when support
|
||||
// insert as first filter before hardware scale and transpose
|
||||
args.InsertFilter("format=vaapi|nv12,hwupload")
|
||||
} else {
|
||||
// enable software pixel for drawtext, scale and transpose
|
||||
args.Input = "-hwaccel vaapi -hwaccel_output_format nv12 " + args.Input
|
||||
|
||||
args.AddFilter("hwupload")
|
||||
}
|
||||
|
||||
// fix if input doesn't support hwaccel, do nothing when support
|
||||
args.InsertFilter("format=vaapi|nv12,hwupload")
|
||||
|
||||
case EngineCUDA:
|
||||
args.Input = "-hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 2 " + args.Input
|
||||
args.Codecs[i] = defaults[name+"/"+engine]
|
||||
|
||||
for i, filter := range args.Filters {
|
||||
if strings.HasPrefix(filter, "scale=") {
|
||||
args.Filters[i] = "scale_cuda=" + filter[6:]
|
||||
// CUDA doesn't support hardware transpose
|
||||
// https://github.com/AlexxIT/go2rtc/issues/389
|
||||
if !args.HasFilters("drawtext=", "transpose=") {
|
||||
args.Input = "-hwaccel cuda -hwaccel_output_format cuda " + args.Input
|
||||
|
||||
for i, filter := range args.Filters {
|
||||
if strings.HasPrefix(filter, "scale=") {
|
||||
args.Filters[i] = "scale_cuda=" + filter[6:]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
args.Input = "-hwaccel cuda -hwaccel_output_format nv12 " + args.Input
|
||||
|
||||
args.AddFilter("hwupload")
|
||||
}
|
||||
|
||||
case EngineDXVA2:
|
||||
|
||||
Reference in New Issue
Block a user