Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26b5745f0a | |||
| 46f6a5d8e1 | |||
| 48f58d0669 | |||
| fd0b8f3c39 | |||
| 863bf503e2 | |||
| 7a3a1a5336 | |||
| b851041caa | |||
| a4acde6d95 | |||
| 1139d4fcad | |||
| 159ad52277 | |||
| 87bc07e404 |
@@ -5,9 +5,9 @@
|
||||
- zero-dependency and zero-config small [app for all OS](#installation) (Windows, macOS, Linux, ARM)
|
||||
- zero-delay for all supported protocols (lowest possible streaming latency)
|
||||
- zero-load on CPU for supported codecs
|
||||
- on the fly transcoding for unsupported codecs [via FFmpeg](#source-ffmpeg)
|
||||
- on the fly transcoding for unsupported codecs via [FFmpeg](#source-ffmpeg)
|
||||
- multi-source 2-way [codecs negotiation](#codecs-negotiation)
|
||||
- streaming from private networks via [Ngrok or SSH-tunnels](#module-webrtc)
|
||||
- streaming from private networks via [Ngrok](#module-webrtc)
|
||||
|
||||
**Inspired by:**
|
||||
|
||||
@@ -45,7 +45,24 @@ streams:
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
## Fast start
|
||||
|
||||
1. Download [binary](#go2rtc-binary) or use [Docker](#go2rtc-docker) or [Home Assistant Add-on](#go2rtc-home-assistant-add-on)
|
||||
2. Open web interface [http://localhost:1984/](http://localhost:1984/)
|
||||
|
||||
**Optionally:**
|
||||
|
||||
- add your [streams](#module-streams) to [config](#configuration) file
|
||||
- setup [external access](#module-webrtc) to webrtc
|
||||
- setup [external access](#module-ngrok) to web interface
|
||||
- install [ffmpeg](#source-ffmpeg) for transcoding
|
||||
|
||||
**Developers:**
|
||||
|
||||
- write your own [web interface](#module-api)
|
||||
- integrate [web api](#module-api) into your smart home platform
|
||||
|
||||
### go2rtc: Binary
|
||||
|
||||
Download binary for your OS from [latest release](https://github.com/AlexxIT/go2rtc/releases/):
|
||||
|
||||
@@ -61,6 +78,23 @@ Download binary for your OS from [latest release](https://github.com/AlexxIT/go2
|
||||
|
||||
Don't forget to fix the rights `chmod +x go2rtc_linux_xxx` on Linux and Mac.
|
||||
|
||||
### go2rtc: Home Assistant Add-on
|
||||
|
||||
[](https://my.home-assistant.io/redirect/supervisor_addon/?addon=a889bffc_go2rtc&repository_url=https%3A%2F%2Fgithub.com%2FAlexxIT%2Fhassio-addons)
|
||||
|
||||
1. Install Add-On:
|
||||
- Settings > Add-ons > Plus > Repositories > Add `https://github.com/AlexxIT/hassio-addons`
|
||||
- go2rtc > Install > Start
|
||||
2. Setup [Integration](#module-hass)
|
||||
|
||||
**Optionally:**
|
||||
|
||||
- create `go2rtc.yaml` in your Home Assistant [config](https://www.home-assistant.io/docs/configuration) folder
|
||||
|
||||
### go2rtc: Docker
|
||||
|
||||
Container [alexxit/go2rtc](https://hub.docker.com/r/alexxit/go2rtc) with support `amd64`, `386`, `arm64`, `arm`. This container same as [Home Assistant Add-on](#go2rtc-home-assistant-add-on), but can be used separately from the Home Assistant. Container has preinstalled [FFmpeg](#source-ffmpeg) and [Ngrok](#module-ngrok) applications.
|
||||
|
||||
## Configuration
|
||||
|
||||
Create file `go2rtc.yaml` next to the app.
|
||||
@@ -76,7 +110,7 @@ Available modules:
|
||||
- [streams](#module-streams)
|
||||
- [api](#module-api) - HTTP API (important for WebRTC support)
|
||||
- [rtsp](#module-rtsp) - RTSP Server (important for FFmpeg support)
|
||||
- [webrtc](#module-webrtc) - WebRTC Server (important for external access)
|
||||
- [webrtc](#module-webrtc) - WebRTC Server
|
||||
- [ngrok](#module-ngrok) - Ngrok integration (external access for private network)
|
||||
- [ffmpeg](#source-ffmpeg) - FFmpeg integration
|
||||
- [hass](#module-hass) - Home Assistant integration
|
||||
@@ -202,7 +236,7 @@ streams:
|
||||
|
||||
### Module: API
|
||||
|
||||
The HTTP API is the main part for interacting with the application.
|
||||
The HTTP API is the main part for interacting with the application. Default address: `http://127.0.0.1:1984/`.
|
||||
|
||||
- you can use WebRTC only when HTTP API enabled
|
||||
- you can disable HTTP API with `listen: ""` and use, for example, only RTSP client/server protocol
|
||||
@@ -212,11 +246,15 @@ The HTTP API is the main part for interacting with the application.
|
||||
|
||||
```yaml
|
||||
api:
|
||||
listen: ":1984" # HTTP API port ("" - disabled)
|
||||
base_path: "" # API prefix for serve on suburl
|
||||
static_dir: "www" # folder for static files ("" - disabled)
|
||||
listen: ":1984" # HTTP API port ("" - disabled)
|
||||
base_path: "" # API prefix for serve on suburl
|
||||
static_dir: "" # folder for static files (custom web interface)
|
||||
```
|
||||
|
||||
**PS. go2rtc** don't provide HTTPS or password protection. Use [Nginx](https://nginx.org/) or [Ngrok](#module-ngrok) or [Home Assistant Add-on](#go2rtc-home-assistant-add-on) for this tasks.
|
||||
|
||||
**PS2.** You can access microphone (for 2-way audio) only with HTTPS
|
||||
|
||||
### Module: RTSP
|
||||
|
||||
You can get any stream as RTSP-stream with codecs filter:
|
||||
@@ -353,14 +391,25 @@ tunnels:
|
||||
|
||||
### Module: Hass
|
||||
|
||||
go2rtc compatible with Home Assistant [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) integration API.
|
||||
**go2rtc** compatible with Home Assistant [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) integration.
|
||||
|
||||
- add integration with link to go2rtc HTTP API:
|
||||
- Hass > Settings > Integrations > Add Integration > RTSPtoWebRTC > `http://192.168.1.123:1984/`
|
||||
- add generic camera with RTSP link:
|
||||
- Hass > Settings > Integrations > Add Integration > Generic Camera > `rtsp://...`
|
||||
- use Picture Entity or Picture Glance lovelace card
|
||||
- open full screen card - this is should be WebRTC stream
|
||||
If you install **go2rtc** as [Hass Add-on](#go2rtc-home-assistant-add-on) - you need to use localhost IP-address, example:
|
||||
|
||||
- `http://127.0.0.1:1984/` to web interface
|
||||
- `rtsp://127.0.0.1:8554/camera1` to RTSP streams
|
||||
|
||||
In other cases you need to use IP-address of server with **go2rtc** application.
|
||||
|
||||
1. Add integration with link to go2rtc HTTP API:
|
||||
- Hass > Settings > Integrations > Add Integration > [RTSPtoWebRTC](https://my.home-assistant.io/redirect/config_flow_start/?domain=rtsp_to_webrtc) > `http://127.0.0.1:1984/`
|
||||
2. Add generic camera with RTSP link:
|
||||
- Hass > Settings > Integrations > Add Integration > [Generic Camera](https://my.home-assistant.io/redirect/config_flow_start/?domain=generic) > `rtsp://...` or `rtmp://...`
|
||||
3. Use Picture Entity or Picture Glance lovelace card
|
||||
4. Open full screen card - this is should be WebRTC stream
|
||||
|
||||
- you can use either direct RTSP links to cameras or take RTSP streams from **go2rtc**
|
||||
|
||||
PS. Default Home Assistant lovelace cards don't support 2-way audio. You can use 2-way audio from [Add-on Web UI](https://my.home-assistant.io/redirect/supervisor_addon/?addon=a889bffc_go2rtc&repository_url=https%3A%2F%2Fgithub.com%2FAlexxIT%2Fhassio-addons). But you need use HTTPS to access the microphone. This is a browser restriction and cannot be avoided.
|
||||
|
||||
### Module: Log
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ func HandleFunc(scheme string, handler Handler) {
|
||||
|
||||
func HasProducer(url string) bool {
|
||||
i := strings.IndexByte(url, ':')
|
||||
if i <= 0 { // TODO: i < 4 ?
|
||||
return false
|
||||
}
|
||||
return handlers[url[:i]] != nil
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ func (s *Stream) AddConsumer(cons streamer.Consumer) (err error) {
|
||||
|
||||
// Step 4. Get producer track
|
||||
prodTrack := prod.GetTrack(prodMedia, prodCodec)
|
||||
if prodTrack == nil {
|
||||
log.Warn().Msg("[stream] can't get track")
|
||||
continue
|
||||
}
|
||||
|
||||
// Step 5. Add track to consumer and get new track
|
||||
consTrack := consumer.element.AddTrack(consMedia, prodTrack)
|
||||
@@ -121,7 +125,7 @@ func (s *Stream) RemoveProducer(prod streamer.Producer) {
|
||||
}
|
||||
|
||||
func (s *Stream) Active() bool {
|
||||
if len(s.consumers) > 0{
|
||||
if len(s.consumers) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+32
-2
@@ -3,9 +3,12 @@ package rtmp
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
||||
"github.com/deepch/vdk/av"
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
"github.com/deepch/vdk/format/rtmp"
|
||||
"github.com/pion/rtp"
|
||||
@@ -70,9 +73,36 @@ func (c *Client) Dial() (err error) {
|
||||
c.tracks = append(c.tracks, track)
|
||||
|
||||
case av.AAC:
|
||||
panic("not implemented")
|
||||
// TODO: fix support
|
||||
cd := stream.(aacparser.CodecData)
|
||||
|
||||
// a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1588
|
||||
fmtp := fmt.Sprintf(
|
||||
"config=%s",
|
||||
hex.EncodeToString(cd.ConfigBytes),
|
||||
)
|
||||
|
||||
codec := &streamer.Codec{
|
||||
Name: streamer.CodecAAC,
|
||||
ClockRate: uint32(cd.Config.SampleRate),
|
||||
Channels: uint16(cd.Config.ChannelConfig),
|
||||
FmtpLine: fmtp,
|
||||
}
|
||||
|
||||
media := &streamer.Media{
|
||||
Kind: streamer.KindAudio,
|
||||
Direction: streamer.DirectionSendonly,
|
||||
Codecs: []*streamer.Codec{codec},
|
||||
}
|
||||
c.medias = append(c.medias, media)
|
||||
|
||||
track := &streamer.Track{
|
||||
Codec: codec, Direction: media.Direction,
|
||||
}
|
||||
c.tracks = append(c.tracks, track)
|
||||
|
||||
default:
|
||||
panic("unsupported codec")
|
||||
fmt.Printf("[rtmp] unsupported codec %+v\n", stream)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+48
-8
@@ -43,6 +43,8 @@ const (
|
||||
ModeServerConsumer
|
||||
)
|
||||
|
||||
const KeepAlive = time.Second * 25
|
||||
|
||||
type Conn struct {
|
||||
streamer.Element
|
||||
|
||||
@@ -189,7 +191,7 @@ func (c *Conn) Do(req *tcp.Request) (*tcp.Response, error) {
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("wrong response on %s", req.Method)
|
||||
return res, fmt.Errorf("wrong response on %s", req.Method)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
@@ -261,7 +263,21 @@ func (c *Conn) Describe() error {
|
||||
|
||||
res, err := c.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
if res != nil {
|
||||
// if we have answer - give second chanse without onvif header
|
||||
req.Header.Del("Require")
|
||||
res, err = c.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val := res.Header.Get("Content-Base"); val != "" {
|
||||
c.URL, err = url.Parse(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// fix bug in Sonoff camera SDP "o=- 1 1 IN IP4 rom t_rtsplin"
|
||||
@@ -355,10 +371,22 @@ func (c *Conn) SetupMedia(
|
||||
// we send our `interleaved`, but camera can answer with another
|
||||
|
||||
// Transport: RTP/AVP/TCP;unicast;interleaved=10-11;ssrc=10117CB7
|
||||
// Transport: RTP/AVP/TCP;unicast;destination=192.168.1.123;source=192.168.10.12;interleaved=0
|
||||
s := res.Header.Get("Transport")
|
||||
s, ok1, ok2 := between(s, "RTP/AVP/TCP;unicast;interleaved=", "-")
|
||||
if !ok1 || !ok2 {
|
||||
panic("wrong response")
|
||||
// TODO: rewrite
|
||||
if !strings.HasPrefix(s, "RTP/AVP/TCP;unicast") {
|
||||
return nil, fmt.Errorf("wrong transport: %s", s)
|
||||
}
|
||||
|
||||
i := strings.Index(s, "interleaved=")
|
||||
if i < 0 {
|
||||
return nil, fmt.Errorf("wrong transport: %s", s)
|
||||
}
|
||||
|
||||
s = s[i+len("interleaved="):]
|
||||
i = strings.IndexAny(s, "-;")
|
||||
if i > 0 {
|
||||
s = s[:i]
|
||||
}
|
||||
|
||||
ch, err = strconv.Atoi(s)
|
||||
@@ -549,6 +577,7 @@ func (c *Conn) Handle() (err error) {
|
||||
}()
|
||||
|
||||
//c.Fire(streamer.StatePlaying)
|
||||
ts := time.Now().Add(KeepAlive)
|
||||
|
||||
for {
|
||||
// we can read:
|
||||
@@ -603,7 +632,7 @@ func (c *Conn) Handle() (err error) {
|
||||
if channelID&1 == 0 {
|
||||
packet := &rtp.Packet{}
|
||||
if err = packet.Unmarshal(buf); err != nil {
|
||||
return errors.New("wrong RTP data")
|
||||
return
|
||||
}
|
||||
|
||||
track := c.channels[channelID]
|
||||
@@ -617,16 +646,27 @@ func (c *Conn) Handle() (err error) {
|
||||
msg := &RTCP{Channel: channelID}
|
||||
|
||||
if err = msg.Header.Unmarshal(buf); err != nil {
|
||||
return errors.New("wrong RTCP data")
|
||||
return
|
||||
}
|
||||
|
||||
msg.Packets, err = rtcp.Unmarshal(buf)
|
||||
if err != nil {
|
||||
return errors.New("wrong RTCP data")
|
||||
return
|
||||
}
|
||||
|
||||
c.Fire(msg)
|
||||
}
|
||||
|
||||
// keep-alive
|
||||
now := time.Now()
|
||||
if now.After(ts) {
|
||||
req := &tcp.Request{Method: MethodOptions, URL: c.URL}
|
||||
// don't need to wait respose on this request
|
||||
if err = c.Request(req); err != nil {
|
||||
return err
|
||||
}
|
||||
ts = now.Add(KeepAlive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package streamer
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pion/rtp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type WriterFunc func(packet *rtp.Packet) error
|
||||
@@ -12,6 +13,7 @@ type Track struct {
|
||||
Codec *Codec
|
||||
Direction string
|
||||
Sink map[*Track]WriterFunc
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
func (t *Track) String() string {
|
||||
@@ -21,9 +23,11 @@ func (t *Track) String() string {
|
||||
}
|
||||
|
||||
func (t *Track) WriteRTP(p *rtp.Packet) error {
|
||||
t.mx.Lock()
|
||||
for _, f := range t.Sink {
|
||||
_ = f(p)
|
||||
}
|
||||
t.mx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,10 +39,14 @@ func (t *Track) Bind(w WriterFunc) *Track {
|
||||
clone := &Track{
|
||||
Codec: t.Codec, Direction: t.Direction, Sink: t.Sink,
|
||||
}
|
||||
t.mx.Lock()
|
||||
t.Sink[clone] = w
|
||||
t.mx.Unlock()
|
||||
return clone
|
||||
}
|
||||
|
||||
func (t *Track) Unbind() {
|
||||
t.mx.Lock()
|
||||
delete(t.Sink, t)
|
||||
t.mx.Unlock()
|
||||
}
|
||||
|
||||
+3
-1
@@ -130,7 +130,9 @@ 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()
|
||||
return pair.Remote.String()
|
||||
if pair.Remote != nil {
|
||||
return pair.Remote.String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user