Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2aedbaf04 | |||
| 1654ac8c82 | |||
| 38a18cab62 | |||
| a006394e5f | |||
| 1b3024b055 | |||
| 9101cd4458 | |||
| 62c0fcd1ed | |||
| ff810d3394 | |||
| 14dae12ce2 |
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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, "#") {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
@SET GOOS=linux
|
||||
@SET GOARCH=arm
|
||||
@cd ..
|
||||
del go2rtc
|
||||
go build -ldflags "-s -w" -trimpath
|
||||
Reference in New Issue
Block a user