Compare commits

...

9 Commits

Author SHA1 Message Date
Alexey Khit f2aedbaf04 Adds build script for linux amd 2022-08-28 06:29:37 +03:00
Alexey Khit 1654ac8c82 Move ffmpeg device to separate module 2022-08-28 06:29:16 +03:00
Alexey Khit 38a18cab62 Clear webrtc resources on failed connection 2022-08-27 15:57:47 +03:00
Alexey Khit a006394e5f Fix empty webrtc remote 2022-08-27 15:57:16 +03:00
Alexey Khit 1b3024b055 Adds license 2022-08-27 06:35:30 +03:00
Alexey Khit 9101cd4458 Adds warn about reading config 2022-08-26 20:14:26 +03:00
Alexey Khit 62c0fcd1ed Adds about video rotation to readme 2022-08-26 17:14:54 +03:00
Alexey Khit ff810d3394 Return support skip RTSPS verification 2022-08-26 17:14:29 +03:00
Alexey Khit 14dae12ce2 Adds webcam support to readme 2022-08-26 11:54:26 +03:00
12 changed files with 173 additions and 46 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Alexey Khit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+30 -5
View File
@@ -4,9 +4,14 @@ Ultimate camera streaming application with support RTSP, WebRTC, FFmpeg, RTMP, e
- zero-dependency and zero-config [small app](#go2rtc-binary) for all OS (Windows, macOS, Linux, ARM)
- zero-delay for all supported protocols (lowest possible streaming latency)
- streaming from `RTSP`, `RTMP`, `MJPEG`, `HLS`, `USB Cameras`, `files` and [other sources](#module-streams)
- streaming to `RTSP` or `WebRTC` (any modern browser)
- low CPU load for supported codecs
- on the fly transcoding for unsupported codecs via [FFmpeg](#source-ffmpeg)
- multi-source 2-way [codecs negotiation](#codecs-negotiation)
- mixing tracks from different sources to single stream
- auto match client supported codecs
- 2-way audio for `ONVIF Profile T` Cameras
- streaming from private networks via [Ngrok](#module-ngrok)
- can be [integrated to](#module-api) any smart home platform or be used as [standalone app](#go2rtc-binary)
@@ -134,9 +139,10 @@ Available modules:
Available source types:
- [rtsp](#source-rtsp) - most cameras on market
- [rtmp](#source-rtmp)
- [ffmpeg](#source-ffmpeg) - FFmpeg integration
- [rtsp](#source-rtsp) - `RTSP` and `RTSPS` cameras
- [rtmp](#source-rtmp) - `RTMP` streams
- [ffmpeg](#source-ffmpeg) - FFmpeg integration (`MJPEG`, `HLS`, `files` and source types)
- [ffmpeg:device](#source-ffmpeg-device) - local USB Camera or Webcam
- [exec](#source-exec) - advanced FFmpeg and GStreamer integration
- [hass](#source-hass) - Home Assistant integration
@@ -200,8 +206,8 @@ streams:
# [MJPEG] video will be transcoded to H264
mjpeg: ffmpeg:http://185.97.122.128/cgi-bin/faststream.jpg?stream=half&fps=15#video=h264
# [RTSP] video and audio will be copied
rtsp: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=copy#audio=copy
# [RTSP] video with rotation, should be transcoded, so select H264
rotate: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#raw=-vf transpose=1#video=h264
```
All trascoding formats has [built-in templates](https://github.com/AlexxIT/go2rtc/blob/master/cmd/ffmpeg/ffmpeg.go): `h264`, `h264/ultra`, `h264/high`, `h265`, `opus`, `pcmu`, `pcmu/16000`, `pcmu/48000`, `pcma`, `pcma/16000`, `pcma/48000`, `aac/16000`.
@@ -215,6 +221,25 @@ ffmpeg:
mycodec: "-any args that support ffmpeg..."
```
Also you can use `raw` param for any additional FFmpeg arguments. As example for video rotation (`#raw=-vf transpose=1`). Remember that rotation is not possible without transcoding, so add supported codec as second param (`#video=h264`).
#### Source: FFmpeg Device
You can get video from any USB-camera or Webcam as RTSP or WebRTC stream. This is part of FFmpeg integration.
- check available devices in Web interface
- `resolution` and `framerate` must be supported by your camera!
- for Linux supported only video for now
- for macOS you can stream Facetime camera or whole Desktop!
- for macOS important to set right framerate
```yaml
streams:
linux_usbcam: ffmpeg:device?video=0&resolution=1280x720#video=h264
windows_webcam: ffmpeg:device?video=0#video=h264
macos_facetime: ffmpeg:device?video=0&audio=1&resolution=1280x720&framerate=30#video=h264#audio=pcma
```
#### Source: Exec
FFmpeg source just a shortcut to exec source. You can get any stream or file or device via FFmpeg or GStreamer and push it to go2rtc via RTSP protocol:
+3 -1
View File
@@ -59,7 +59,9 @@ func Init() {
func LoadConfig(v interface{}) {
if data != nil {
_ = yaml.Unmarshal(data, v)
if err := yaml.Unmarshal(data, v); err != nil {
log.Warn().Err(err).Msg("[app] read config")
}
}
}
@@ -1,4 +1,4 @@
package ffmpeg
package device
import (
"bytes"
@@ -26,7 +26,7 @@ func deviceInputSuffix(videoIdx, audioIdx int) string {
func loadMedias() {
cmd := exec.Command(
tpl["bin"], "-hide_banner", "-list_devices", "true", "-f", "avfoundation", "-i", "dummy",
Bin, "-hide_banner", "-list_devices", "true", "-f", "avfoundation", "-i", "dummy",
)
var buf bytes.Buffer
@@ -1,9 +1,10 @@
package ffmpeg
package device
import (
"bytes"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog/log"
"io/ioutil"
"os/exec"
"strings"
)
@@ -24,12 +25,25 @@ func loadMedias() {
log.Trace().Msg("[ffmpeg] " + file.Name())
if strings.HasPrefix(file.Name(), streamer.KindVideo) {
media := loadMedia(streamer.KindVideo, "/dev/"+file.Name())
medias = append(medias, media)
if media != nil {
medias = append(medias, media)
}
}
}
}
func loadMedia(kind, name string) *streamer.Media {
cmd := exec.Command(
Bin, "-hide_banner", "-f", "v4l2", "-list_formats", "all", "-i", name,
)
var buf bytes.Buffer
cmd.Stderr = &buf
_ = cmd.Run()
if !bytes.Contains(buf.Bytes(), []byte("Raw")) {
return nil
}
return &streamer.Media{
Kind: kind, Title: name,
}
@@ -1,4 +1,4 @@
package ffmpeg
package device
import (
"bytes"
@@ -26,7 +26,7 @@ func deviceInputSuffix(videoIdx, audioIdx int) string {
func loadMedias() {
cmd := exec.Command(
tpl["bin"], "-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "",
Bin, "-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "",
)
var buf bytes.Buffer
@@ -1,16 +1,24 @@
package ffmpeg
package device
import (
"encoding/json"
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog"
"net/http"
"net/url"
"strconv"
"strings"
)
func getDevice(src string) (string, error) {
func Init() {
log = app.GetLogger("exec")
api.HandleFunc("/api/devices", handle)
}
func GetInput(src string) (string, error) {
if medias == nil {
loadMedias()
}
@@ -42,6 +50,8 @@ func getDevice(src string) (string, error) {
return input, nil
}
var Bin string
var log zerolog.Logger
var medias []*streamer.Media
func findMedia(kind string, index int) *streamer.Media {
@@ -57,7 +67,7 @@ func findMedia(kind string, index int) *streamer.Media {
return nil
}
func handleDevices(w http.ResponseWriter, r *http.Request) {
func handle(w http.ResponseWriter, r *http.Request) {
if medias == nil {
loadMedias()
}
+27 -19
View File
@@ -1,9 +1,9 @@
package ffmpeg
import (
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/exec"
"github.com/AlexxIT/go2rtc/cmd/ffmpeg/device"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"net/url"
@@ -21,9 +21,9 @@ func Init() {
"bin": "ffmpeg",
// inputs
"link": "-i {input}",
"rtsp": "-fflags nobuffer -flags low_delay -rtsp_transport tcp -i {input}",
"file": "-re -stream_loop -1 -i {input}",
"http": "-i {input}",
"rtsp": "-fflags nobuffer -flags low_delay -rtsp_transport tcp -i {input}",
// output
"out": "-rtsp_transport tcp -f rtsp {output}",
@@ -49,7 +49,7 @@ func Init() {
app.LoadConfig(&cfg)
tpl = cfg.Mod
tpl := cfg.Mod
streams.HandleFunc("ffmpeg", func(s string) (streamer.Producer, error) {
s = s[7:] // remove `ffmpeg:`
@@ -60,20 +60,29 @@ func Init() {
s = s[:i]
}
var template string
switch {
case strings.HasPrefix(s, "rtsp"):
template = tpl["rtsp"]
case strings.HasPrefix(s, "device"):
template, _ = getDevice(s)
case strings.Contains(s, "://"):
template = tpl["link"]
default:
template = tpl["file"]
var input string
if i := strings.IndexByte(s, ':'); i > 0 {
switch s[:i] {
case "http", "https":
input = strings.Replace(tpl["http"], "{input}", s, 1)
case "rtsp", "rtsps":
input = strings.Replace(tpl["rtsp"], "{input}", s, 1)
}
}
s = "exec:" + tpl["bin"] + " -hide_banner " +
strings.Replace(template, "{input}", s, 1)
if input == "" {
if strings.HasPrefix(s, "device?") {
var err error
input, err = device.GetInput(s)
if err != nil {
return nil, err
}
} else {
input = strings.Replace(tpl["file"], "{input}", s, 1)
}
}
s = "exec:" + tpl["bin"] + " -hide_banner " + input
if query != nil {
for _, raw := range query["raw"] {
@@ -114,11 +123,10 @@ func Init() {
return exec.Handle(s)
})
api.HandleFunc("/api/devices", handleDevices)
device.Bin = cfg.Mod["bin"]
device.Init()
}
var tpl map[string]string
func parseQuery(s string) map[string][]string {
query := map[string][]string{}
for _, key := range strings.Split(s, "#") {
+1
View File
@@ -27,6 +27,7 @@ func Init() {
// RTSP client support
streams.HandleFunc("rtsp", rtspHandler)
streams.HandleFunc("rtsps", rtspHandler)
streams.HandleFunc("rtspx", rtspHandler)
// RTSP server support
address := conf.Mod.Listen
+2 -2
View File
@@ -151,8 +151,8 @@ func ExchangeSDP(
conn.UserAgent = userAgent
conn.Listen(func(msg interface{}) {
switch msg := msg.(type) {
case streamer.EventType:
if msg == streamer.StateNull {
case pion.PeerConnectionState:
if msg == pion.PeerConnectionStateClosed{
stream.RemoveConsumer(conn)
}
}
+49 -8
View File
@@ -62,17 +62,32 @@ func (c *Conn) Init() {
fmt.Printf("TODO: webrtc ontrack %#v\n", remote)
})
// OK connection:
// 15:01:46 ICE connection state changed: checking
// 15:01:46 peer connection state changed: connected
// 15:01:54 peer connection state changed: disconnected
// 15:02:20 peer connection state changed: failed
//
// Fail connection:
// 14:53:08 ICE connection state changed: checking
// 14:53:39 peer connection state changed: failed
c.Conn.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
c.Fire(state)
// TODO: remove
switch state {
case webrtc.PeerConnectionStateConnected:
c.Fire(streamer.StatePlaying)
c.Fire(streamer.StatePlaying) // TODO: remove
case webrtc.PeerConnectionStateDisconnected:
c.Fire(streamer.StateNull)
case webrtc.PeerConnectionStateFailed:
c.Fire(streamer.StateNull) // TODO: remove
// disconnect event comes earlier, than failed
// but it comes only for success connections
_ = c.Conn.Close()
c.Conn = nil
case webrtc.PeerConnectionStateFailed:
if c.Conn != nil {
_ = c.Conn.Close()
}
}
})
}
@@ -128,11 +143,37 @@ func (c *Conn) GetCompleteAnswer() (answer string, err error) {
}
func (c *Conn) remote() string {
for _, trans := range c.Conn.GetTransceivers() {
pair, _ := trans.Receiver().Transport().ICETransport().GetSelectedCandidatePair()
if pair.Remote != nil {
return pair.Remote.String()
}
if c.Conn == nil {
return ""
}
for _, trans := range c.Conn.GetTransceivers() {
if trans == nil {
continue
}
receiver := trans.Receiver()
if receiver == nil {
continue
}
transport := receiver.Transport()
if transport == nil {
continue
}
iceTransport := transport.ICETransport()
if iceTransport == nil {
continue
}
pair, _ := iceTransport.GetSelectedCandidatePair()
if pair == nil || pair.Remote == nil {
continue
}
return pair.Remote.String()
}
return ""
}
+5
View File
@@ -0,0 +1,5 @@
@SET GOOS=linux
@SET GOARCH=arm
@cd ..
del go2rtc
go build -ldflags "-s -w" -trimpath