Compare commits

..

10 Commits

Author SHA1 Message Date
Alexey Khit
5460e194e8 Update loggers 2022-09-06 07:08:35 +03:00
Alexey Khit
e4f565f343 Fix H264 in RTSP processing 2022-09-06 07:00:22 +03:00
Alexey Khit
6b274f2a37 Adds Hass stream info to log 2022-09-06 06:36:13 +03:00
Alexey Khit
f442aab176 Fix RTSP from RTMP stream 2022-09-05 20:04:30 +03:00
Alexey Khit
0e71bd4dcb Adds low delay for any ffmpeg source 2022-09-04 23:02:54 +03:00
Alexey Khit
e3618d70c3 Adds support MPA codec 2022-09-04 22:34:39 +03:00
Alexey Khit
99c4a3e34a Update RTSP Setup link logic 2022-09-04 21:43:32 +03:00
Alexey Khit
b78de349ab Hide streams from Hass by default 2022-09-02 20:52:39 +03:00
Alexey Khit
b4990b1e90 Fix support RTSPtoWebRTC API 2022-09-02 20:52:22 +03:00
Alexey Khit
687bdadba6 Update docs about HomeKit 2022-09-01 16:11:47 +03:00
10 changed files with 139 additions and 69 deletions

View File

@@ -1,11 +1,14 @@
# go2rtc
Ultimate camera streaming application with support RTSP, WebRTC, FFmpeg, RTMP, etc.
Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg, RTMP, etc.
![](assets/go2rtc.png)
- 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)
- streaming from [RTSP](#source-rtsp), [RTMP](#source-rtmp), [MJPEG](#source-ffmpeg), [HLS](#source-ffmpeg), [USB Cameras](#source-ffmpeg-device), [files](#source-ffmpeg) and [other sources](#module-streams)
- streaming to [RTSP](#module-rtsp), [WebRTC](#module-webrtc) or [MSE](#module-api)
- first project in the World with support streaming from [HomeKit Cameras](#source-homekit)
- 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)
@@ -22,6 +25,7 @@ Ultimate camera streaming application with support RTSP, WebRTC, FFmpeg, RTMP, e
- [rtsp-simple-server](https://github.com/aler9/rtsp-simple-server) idea from [@aler9](https://github.com/aler9)
- [GStreamer](https://gstreamer.freedesktop.org/) framework pipeline idea
- [MediaSoup](https://mediasoup.org/) framework routing idea
- HomeKit Accessory Protocol from [@brutella](https://github.com/brutella/hap)
## Codecs negotiation
@@ -48,7 +52,7 @@ streams:
**go2rtc** automatically match codecs for you browser and all your stream sources. This called **multi-source 2-way codecs negotiation**. And this is one of the main features of this app.
![](codecs.svg)
![](assets/codecs.svg)
**PS.** You can select `PCMU` or `PCMA` codec in camera setting and don't use transcoding at all. Or you can select `AAC` codec for main stream and `PCMU` codec for second stream and add both RTSP to YAML config, this also will work fine.
@@ -144,6 +148,7 @@ Available source types:
- [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
- [homekit](#source-homekit) - streaming from HomeKit Camera
- [hass](#source-hass) - Home Assistant integration
**PS.** You can use sources like `MJPEG`, `HLS` and others via FFmpeg integration.
@@ -249,11 +254,28 @@ streams:
stream1: exec:ffmpeg -hide_banner -re -stream_loop -1 -i /media/BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp {output}
```
#### Source: HomeKit
**Important:**
- You can use HomeKit Cameras **without Apple devices** (iPhone, iPad, etc.), it's just a yet another protocol
- HomeKit device can be paired with only one ecosystem. So, if you have paired it to an iPhone (Apple Home) - you can't pair it with Home Assistant or go2rtc. Or if you have paired it to go2rtc - you can't pair it with iPhone
- HomeKit device should be in same network with working [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) between device and go2rtc
go2rtc support import paired HomeKit devices from [Home Assistant](#source-hass). So you can use HomeKit camera with Hass and go2rtc simultaneously. If you using Hass, I recommend pairing devices with it, it will give you more options.
You can pair device with go2rtc on the HomeKit page. If you can't see your devices - reload the page. Also try reboot your HomeKit device (power off). If you still can't see it - you have a problems with mDNS.
If you see a device but it does not have a pair button - it is paired to some ecosystem (Apple Home, Home Assistant, HomeBridge etc). You need to delete device from that ecosystem, and it will be available for pairing. If you cannot unpair device, you will have to reset it.
**This source is in active development!** Tested only with [Aqara Camera Hub G3](https://www.aqara.com/eu/product/camera-hub-g3) (both EU and CN versions).
#### Source: Hass
Support import camera links from [Home Assistant](https://www.home-assistant.io/) config files:
- support ONLY [Generic Camera](https://www.home-assistant.io/integrations/generic/), setup via GUI
- support [Generic Camera](https://www.home-assistant.io/integrations/generic/), setup via GUI
- support [HomeKit Camera](https://www.home-assistant.io/integrations/homekit_controller/)
```yaml
hass:
@@ -261,6 +283,7 @@ hass:
streams:
generic_camera: hass:Camera1 # Settings > Integrations > Integration Name
aqara_g3: hass:Camera-Hub-G3-AB12
```
### Module: API

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/go2rtc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

View File

@@ -22,7 +22,7 @@ func Init() {
// inputs
"file": "-re -stream_loop -1 -i {input}",
"http": "-i {input}",
"http": "-fflags nobuffer -flags low_delay -i {input}",
"rtsp": "-fflags nobuffer -flags low_delay -rtsp_transport tcp -i {input}",
// output

View File

@@ -6,12 +6,12 @@ import (
"fmt"
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/rtsp"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/cmd/webrtc"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog"
"net/http"
"net/url"
"os"
"path"
"strings"
@@ -26,7 +26,7 @@ func Init() {
app.LoadConfig(&conf)
log = app.GetLogger("api")
log = app.GetLogger("hass")
// support https://www.home-assistant.io/integrations/rtsp_to_webrtc/
api.HandleFunc("/static", func(w http.ResponseWriter, r *http.Request) {
@@ -78,7 +78,8 @@ func Init() {
continue
}
streams.Get("hass:" + entrie.Title)
log.Info().Str("url", "hass:" + entrie.Title).Msg("[hass] load stream")
//streams.Get("hass:" + entrie.Title)
}
}
@@ -90,7 +91,13 @@ func handler(w http.ResponseWriter, r *http.Request) {
return
}
url := r.FormValue("url")
src := r.FormValue("url")
src, err := url.QueryUnescape(src)
if err != nil {
log.Error().Err(err).Msg("[api.hass] query unescape")
return
}
str := r.FormValue("sdp64")
offer, err := base64.StdEncoding.DecodeString(str)
@@ -99,16 +106,20 @@ func handler(w http.ResponseWriter, r *http.Request) {
return
}
// TODO: fixme
if strings.HasPrefix(url, "rtsp://") {
port := ":" + rtsp.Port + "/"
i := strings.Index(url, port)
if i > 0 {
url = url[i+len(port):]
// check if stream links to our rtsp server
if strings.HasPrefix(src, "rtsp://") {
i := strings.IndexByte(src[7:], '/')
if i > 0 && streams.Has(src[8+i:]) {
src = src[8+i:]
}
}
stream := streams.Get(url)
stream := streams.Get(src)
if stream == nil {
log.Error().Str("url", src).Msg("[api.hass] unsupported source")
return
}
str, err = webrtc.ExchangeSDP(stream, string(offer), r.UserAgent())
if err != nil {
log.Error().Err(err).Msg("[api.hass] exchange SDP")

View File

@@ -24,19 +24,24 @@ func Init() {
}
}
func Get(name string) *Stream {
if stream, ok := streams[name]; ok {
func Get(src string) *Stream {
if stream, ok := streams[src]; ok {
return stream
}
if HasProducer(name) {
log.Info().Str("url", name).Msg("[streams] create new stream")
stream := NewStream(name)
streams[name] = stream
return stream
if !HasProducer(src) {
return nil
}
return nil
log.Info().Str("url", src).Msg("[streams] create new stream")
stream := NewStream(src)
streams[src] = stream
return stream
}
func Has(src string) bool {
return streams[src] != nil
}
func New(name string, source interface{}) {

View File

@@ -69,7 +69,7 @@ func offerHandler(ctx *api.Context, msg *streamer.Message) {
return
}
log.Debug().Str("src", src).Msg("[webrtc] new consumer")
log.Debug().Str("url", src).Msg("[webrtc] new consumer")
var err error

View File

@@ -1,6 +1,7 @@
package h264
import (
"encoding/binary"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
@@ -19,62 +20,81 @@ func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
return func(push streamer.WriterFunc) streamer.WriterFunc {
return func(packet *rtp.Packet) error {
//println(packet.SequenceNumber, packet.Payload[0]&0x1F, packet.Payload[0], packet.Payload[1], packet.Marker, packet.Timestamp)
//nalUnitType := packet.Payload[0] & 0x1F
//fmt.Printf(
// "[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d\n",
// track.Codec.Name, nalUnitType, len(packet.Payload), packet.Timestamp,
// packet.PayloadType, packet.SSRC,
//)
data, err := depack.Unmarshal(packet.Payload)
if len(data) == 0 || err != nil {
// NALu packets can be split in different ways:
// - single type 7 and type 8 packets
// - join type 7 and type 8 packet (type 24)
// - split type 5 on multiple 28 packets
// - split type 5 on multiple separate 28 packets
units, err := depack.Unmarshal(packet.Payload)
if len(units) == 0 || err != nil {
return nil
}
naluType := NALUType(data)
//println(naluType, len(data))
for len(units) > 0 {
i := int(binary.BigEndian.Uint32(units)) + 4
unit := units[:i] // NAL Unit with AVC header
units = units[i:]
switch naluType {
case NALUTypeSPS:
//println("new SPS")
sps = data
return nil
case NALUTypePPS:
//println("new PPS")
pps = data
return nil
}
unitType := NALUType(unit)
//fmt.Printf("[H264] type: %2d, size: %6d\n", unitType, i)
switch unitType {
case NALUTypeSPS:
//println("new SPS")
sps = unit
continue
case NALUTypePPS:
//println("new PPS")
pps = unit
continue
}
// ffmpeg with `-tune zerolatency` enable option `-x264opts sliced-threads=1`
// and every NALU will be sliced to multiple NALUs
if !packet.Marker {
buffer = append(buffer, data...)
return nil
}
// ffmpeg with `-tune zerolatency` enable option `-x264opts sliced-threads=1`
// and every NALU will be sliced to multiple NALUs
if !packet.Marker {
buffer = append(buffer, unit...)
continue
}
if buffer != nil {
buffer = append(buffer, data...)
data = buffer
buffer = nil
}
if buffer != nil {
buffer = append(buffer, unit...)
unit = buffer
buffer = nil
}
var clone rtp.Packet
var clone rtp.Packet
if naluType == NALUTypeIFrame {
clone = *packet
clone.Version = RTPPacketVersionAVC
clone.Payload = sps
if err = push(&clone); err != nil {
return err
if unitType == NALUTypeIFrame {
clone = *packet
clone.Version = RTPPacketVersionAVC
clone.Payload = sps
if err = push(&clone); err != nil {
return err
}
clone = *packet
clone.Version = RTPPacketVersionAVC
clone.Payload = pps
if err = push(&clone); err != nil {
return err
}
}
clone = *packet
clone.Version = RTPPacketVersionAVC
clone.Payload = pps
clone.Payload = unit
if err = push(&clone); err != nil {
return err
}
}
clone = *packet
clone.Version = RTPPacketVersionAVC
clone.Payload = data
return push(&clone)
return nil
}
}
}

View File

@@ -331,11 +331,18 @@ func (c *Conn) SetupMedia(
return nil, fmt.Errorf("wrong media: %v", media)
}
trackURL, err := url.Parse(media.Control)
rawURL := media.Control
if !strings.Contains(rawURL, "://") {
rawURL = c.URL.String()
if !strings.HasSuffix(rawURL, "/") {
rawURL += "/"
}
rawURL += media.Control
}
trackURL, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
trackURL = c.URL.ResolveReference(trackURL)
req := &tcp.Request{
Method: MethodSetup,

View File

@@ -30,13 +30,14 @@ const (
CodecAAC = "MPEG4-GENERIC"
CodecOpus = "OPUS" // payloadType: 111
CodecG722 = "G722"
CodecMPA = "MPA" // payload: 14
)
func GetKind(name string) string {
switch name {
case CodecH264, CodecH265, CodecVP8, CodecVP9, CodecAV1:
return KindVideo
case CodecPCMU, CodecPCMA, CodecAAC, CodecOpus, CodecG722:
case CodecPCMU, CodecPCMA, CodecAAC, CodecOpus, CodecG722, CodecMPA:
return KindAudio
}
return ""
@@ -256,11 +257,14 @@ func UnmarshalCodec(md *sdp.MediaDescription, payloadType string) *Codec {
if c.Name == "" {
switch payloadType {
case "0":
c.Name = "PCMU"
c.Name = CodecPCMU
c.ClockRate = 8000
case "8":
c.Name = "PCMA"
c.Name = CodecPCMA
c.ClockRate = 8000
case "14":
c.Name = CodecMPA
c.ClockRate = 44100
default:
c.Name = payloadType
}