mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-21 23:39:31 +08:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3acea1ed5a | ||
![]() |
3fb8d9af66 | ||
![]() |
9bbaf41d54 | ||
![]() |
c43530fbd3 | ||
![]() |
15777a3d94 | ||
![]() |
6e61ac6d2f | ||
![]() |
6d7d5f53d8 | ||
![]() |
d2bca8d461 | ||
![]() |
94b089d1e3 | ||
![]() |
b3d16c9fcc | ||
![]() |
f0def68482 |
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -2,9 +2,9 @@ name: release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
# push:
|
||||||
tags:
|
# tags:
|
||||||
- 'v*'
|
# - 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-release:
|
build-and-release:
|
||||||
|
55
README.md
55
README.md
@@ -27,6 +27,40 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
|
|||||||
- [MediaSoup](https://mediasoup.org/) framework routing idea
|
- [MediaSoup](https://mediasoup.org/) framework routing idea
|
||||||
- HomeKit Accessory Protocol from [@brutella](https://github.com/brutella/hap)
|
- HomeKit Accessory Protocol from [@brutella](https://github.com/brutella/hap)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* [Fast start](#fast-start)
|
||||||
|
* [go2rtc: Binary](#go2rtc-binary)
|
||||||
|
* [go2rtc: Home Assistant Add-on](#go2rtc-home-assistant-add-on)
|
||||||
|
* [go2rtc: Docker](#go2rtc-docker)
|
||||||
|
* [Configuration](#configuration)
|
||||||
|
* [Module: Streams](#module-streams)
|
||||||
|
* [Source: RTSP](#source-rtsp)
|
||||||
|
* [Source: RTMP](#source-rtmp)
|
||||||
|
* [Source: HTTP](#source-http)
|
||||||
|
* [Source: FFmpeg](#source-ffmpeg)
|
||||||
|
* [Source: FFmpeg Device](#source-ffmpeg-device)
|
||||||
|
* [Source: Exec](#source-exec)
|
||||||
|
* [Source: Echo](#source-echo)
|
||||||
|
* [Source: HomeKit](#source-homekit)
|
||||||
|
* [Source: Ivideon](#source-ivideon)
|
||||||
|
* [Source: Hass](#source-hass)
|
||||||
|
* [Module: API](#module-api)
|
||||||
|
* [Module: RTSP](#module-rtsp)
|
||||||
|
* [Module: WebRTC](#module-webrtc)
|
||||||
|
* [Module: Ngrok](#module-ngrok)
|
||||||
|
* [Module: Hass](#module-hass)
|
||||||
|
* [From go2rtc to Hass](#from-go2rtc-to-hass)
|
||||||
|
* [From Hass to go2rtc](#from-hass-to-go2rtc)
|
||||||
|
* [Module: MP4](#module-mp4)
|
||||||
|
* [Module: MJPEG](#module-mjpeg)
|
||||||
|
* [Module: Log](#module-log)
|
||||||
|
* [Security](#security)
|
||||||
|
* [Codecs madness](#codecs-madness)
|
||||||
|
* [Codecs negotiation](#codecs-negotiation)
|
||||||
|
* [TIPS](#tips)
|
||||||
|
* [FAQ](#faq)
|
||||||
|
|
||||||
## Fast start
|
## Fast start
|
||||||
|
|
||||||
1. Download [binary](#go2rtc-binary) or use [Docker](#go2rtc-docker) or [Home Assistant Add-on](#go2rtc-home-assistant-add-on)
|
1. Download [binary](#go2rtc-binary) or use [Docker](#go2rtc-docker) or [Home Assistant Add-on](#go2rtc-home-assistant-add-on)
|
||||||
@@ -36,7 +70,6 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
|
|||||||
|
|
||||||
- add your [streams](#module-streams) to [config](#configuration) file
|
- add your [streams](#module-streams) to [config](#configuration) file
|
||||||
- setup [external access](#module-webrtc) to webrtc
|
- setup [external access](#module-webrtc) to webrtc
|
||||||
- setup [external access](#module-ngrok) to web interface
|
|
||||||
|
|
||||||
**Developers:**
|
**Developers:**
|
||||||
|
|
||||||
@@ -74,14 +107,14 @@ Container [alexxit/go2rtc](https://hub.docker.com/r/alexxit/go2rtc) with support
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Create file `go2rtc.yaml`. go2rtc will search this file in current work dirrectory by default.
|
- by default go2rtc will search `go2rtc.yaml` in the current work dirrectory
|
||||||
|
|
||||||
- by default, you need to config only your `streams` links
|
|
||||||
- `api` server will start on default **1984 port** (TCP)
|
- `api` server will start on default **1984 port** (TCP)
|
||||||
- `rtsp` server will start on default **8554 port** (TCP)
|
- `rtsp` server will start on default **8554 port** (TCP)
|
||||||
- `webrtc` will use port **8555** (TCP/UDP) for connections
|
- `webrtc` will use port **8555** (TCP/UDP) for connections
|
||||||
- `ffmpeg` will use default transcoding options
|
- `ffmpeg` will use default transcoding options
|
||||||
|
|
||||||
|
Configuration options and a complete list of settings can be found in [the wiki](https://github.com/AlexxIT/go2rtc/wiki/Configuration).
|
||||||
|
|
||||||
Available modules:
|
Available modules:
|
||||||
|
|
||||||
- [streams](#module-streams)
|
- [streams](#module-streams)
|
||||||
@@ -95,8 +128,6 @@ Available modules:
|
|||||||
- [hass](#module-hass) - Home Assistant integration
|
- [hass](#module-hass) - Home Assistant integration
|
||||||
- [log](#module-log) - logs config
|
- [log](#module-log) - logs config
|
||||||
|
|
||||||
Full default config [example](https://github.com/AlexxIT/go2rtc/wiki/Configuration).
|
|
||||||
|
|
||||||
### Module: Streams
|
### Module: Streams
|
||||||
|
|
||||||
**go2rtc** support different stream source types. You can config one or multiple links of any type as stream source.
|
**go2rtc** support different stream source types. You can config one or multiple links of any type as stream source.
|
||||||
@@ -359,6 +390,8 @@ go2rtc has simple HTML page (`stream.html`) with support params in URL:
|
|||||||
```yaml
|
```yaml
|
||||||
api:
|
api:
|
||||||
listen: ":1984" # default ":1984", HTTP API port ("" - disabled)
|
listen: ":1984" # default ":1984", HTTP API port ("" - disabled)
|
||||||
|
username: "admin" # default "", Basic auth for WebUI
|
||||||
|
password: "pass" # default "", Basic auth for WebUI
|
||||||
base_path: "/rtc" # default "", API prefix for serve on suburl (/api => /rtc/api)
|
base_path: "/rtc" # default "", API prefix for serve on suburl (/api => /rtc/api)
|
||||||
static_dir: "www" # default "", folder for static files (custom web interface)
|
static_dir: "www" # default "", folder for static files (custom web interface)
|
||||||
origin: "*" # default "", allow CORS requests (only * supported)
|
origin: "*" # default "", allow CORS requests (only * supported)
|
||||||
@@ -366,7 +399,7 @@ api:
|
|||||||
|
|
||||||
**PS:**
|
**PS:**
|
||||||
|
|
||||||
- go2rtc doesn'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
|
- go2rtc doesn't provide HTTPS. Use [Nginx](https://nginx.org/) or [Ngrok](#module-ngrok) or [Home Assistant Add-on](#go2rtc-home-assistant-add-on) for this tasks
|
||||||
- you can access microphone (for 2-way audio) only with HTTPS ([read more](https://stackoverflow.com/questions/52759992/how-to-access-camera-and-microphone-in-chrome-without-https))
|
- you can access microphone (for 2-way audio) only with HTTPS ([read more](https://stackoverflow.com/questions/52759992/how-to-access-camera-and-microphone-in-chrome-without-https))
|
||||||
- MJPEG over WebSocket plays better than native MJPEG because Chrome [bug](https://bugs.chromium.org/p/chromium/issues/detail?id=527446)
|
- MJPEG over WebSocket plays better than native MJPEG because Chrome [bug](https://bugs.chromium.org/p/chromium/issues/detail?id=527446)
|
||||||
- MP4 over WebSocket was created only for Apple iOS because it doesn't support MSE and native MP4
|
- MP4 over WebSocket was created only for Apple iOS because it doesn't support MSE and native MP4
|
||||||
@@ -384,9 +417,9 @@ Password protection always disabled for localhost calls (ex. FFmpeg or Hass on s
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
rtsp:
|
rtsp:
|
||||||
listen: ":8554" # RTSP Server TCP port, default - 8554
|
listen: ":8554" # RTSP Server TCP port, default - 8554
|
||||||
username: admin # optional, default - disabled
|
username: "admin" # optional, default - disabled
|
||||||
password: pass # optional, default - disabled
|
password: "pass" # optional, default - disabled
|
||||||
```
|
```
|
||||||
|
|
||||||
### Module: WebRTC
|
### Module: WebRTC
|
||||||
@@ -399,7 +432,7 @@ WebRTC usually works without problems in the local network. But external access
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
webrtc:
|
webrtc:
|
||||||
listen: ":8555" # address of your local server and port (TCP/UDP)
|
listen: ":8555" # address of your local server and port (TCP/UDP)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Static public IP**
|
**Static public IP**
|
||||||
|
@@ -81,7 +81,9 @@ func apiWS(w http.ResponseWriter, r *http.Request) {
|
|||||||
for {
|
for {
|
||||||
msg := new(Message)
|
msg := new(Message)
|
||||||
if err = ws.ReadJSON(msg); err != nil {
|
if err = ws.ReadJSON(msg); err != nil {
|
||||||
log.Trace().Err(err).Caller().Send()
|
if !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) {
|
||||||
|
log.Trace().Err(err).Caller().Send()
|
||||||
|
}
|
||||||
_ = ws.Close()
|
_ = ws.Close()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "1.0.0"
|
var Version = "1.0.1"
|
||||||
var UserAgent = "go2rtc/" + Version
|
var UserAgent = "go2rtc/" + Version
|
||||||
|
|
||||||
var ConfigPath string
|
var ConfigPath string
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package h264
|
package h264
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
@@ -27,7 +28,8 @@ func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix TP-Link Tapo TC70: sends SPS and PPS with packet.Marker = true
|
// Fix TP-Link Tapo TC70: sends SPS and PPS with packet.Marker = true
|
||||||
if packet.Marker {
|
// Reolink Duo 2: sends SPS with Marker and PPS without
|
||||||
|
if packet.Marker && len(payload) < 128 {
|
||||||
switch NALUType(payload) {
|
switch NALUType(payload) {
|
||||||
case NALUTypeSPS, NALUTypePPS:
|
case NALUTypeSPS, NALUTypePPS:
|
||||||
buf = append(buf, payload...)
|
buf = append(buf, payload...)
|
||||||
@@ -68,6 +70,27 @@ func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
|
|||||||
if len(buf) > 0 {
|
if len(buf) > 0 {
|
||||||
payload = append(buf, payload...)
|
payload = append(buf, payload...)
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
|
} else {
|
||||||
|
// some Chinese buggy cameras has single packet with SPS+PPS+IFrame separated by 00 00 00 01
|
||||||
|
// https://github.com/AlexxIT/WebRTC/issues/391
|
||||||
|
// https://github.com/AlexxIT/WebRTC/issues/392
|
||||||
|
for i := 0; i < len(payload); {
|
||||||
|
if i+4 >= len(payload) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
size := bytes.Index(payload[i+4:], []byte{0, 0, 0, 1})
|
||||||
|
if size < 0 {
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
size = len(payload) - (i + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(payload[i:], uint32(size))
|
||||||
|
|
||||||
|
i += size + 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("[AVC] %v, len: %d, ts: %10d, seq: %d", Types(payload), len(payload), packet.Timestamp, packet.SequenceNumber)
|
//log.Printf("[AVC] %v, len: %d, ts: %10d, seq: %d", Types(payload), len(payload), packet.Timestamp, packet.SequenceNumber)
|
||||||
|
97
pkg/httpflv/flvio.go
Normal file
97
pkg/httpflv/flvio.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package httpflv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/deepch/vdk/format/flv/flvio"
|
||||||
|
"github.com/deepch/vdk/utils/bits/pio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: rewrite all of this someday
|
||||||
|
|
||||||
|
func ReadTag(r io.Reader, b []byte) (tag flvio.Tag, ts int32, err error) {
|
||||||
|
if _, err = io.ReadFull(r, b[:flvio.TagHeaderLength]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var datalen int
|
||||||
|
if tag, ts, datalen, err = flvio.ParseTagHeader(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, datalen)
|
||||||
|
if _, err = io.ReadFull(r, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := ParseHeader(&tag, data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tag.Data = data[n:]
|
||||||
|
|
||||||
|
if _, err = io.ReadFull(r, b[:4]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseHeader(self *flvio.Tag, b []byte) (n int, err error) {
|
||||||
|
switch self.Type {
|
||||||
|
case flvio.TAG_AUDIO:
|
||||||
|
return audioParseHeader(self, b)
|
||||||
|
|
||||||
|
case flvio.TAG_VIDEO:
|
||||||
|
return videoParseHeader(self, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioParseHeader(tag *flvio.Tag, b []byte) (n int, err error) {
|
||||||
|
if len(b) < n+1 {
|
||||||
|
err = fmt.Errorf("audiodata: parse invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := b[n]
|
||||||
|
n++
|
||||||
|
tag.SoundFormat = flags >> 4
|
||||||
|
tag.SoundRate = (flags >> 2) & 0x3
|
||||||
|
tag.SoundSize = (flags >> 1) & 0x1
|
||||||
|
tag.SoundType = flags & 0x1
|
||||||
|
|
||||||
|
switch tag.SoundFormat {
|
||||||
|
case flvio.SOUND_AAC:
|
||||||
|
if len(b) < n+1 {
|
||||||
|
err = fmt.Errorf("audiodata: parse invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tag.AACPacketType = b[n]
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func videoParseHeader(tag *flvio.Tag, b []byte) (n int, err error) {
|
||||||
|
if len(b) < n+1 {
|
||||||
|
err = fmt.Errorf("videodata: parse invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
flags := b[n]
|
||||||
|
tag.FrameType = flags >> 4
|
||||||
|
tag.CodecID = flags & 0xf
|
||||||
|
n++
|
||||||
|
|
||||||
|
if len(b) < n+4 {
|
||||||
|
err = fmt.Errorf("videodata: parse invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tag.AVCPacketType = b[n]
|
||||||
|
n++
|
||||||
|
|
||||||
|
tag.CompositionTime = pio.I24BE(b[n:])
|
||||||
|
n += 3
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@@ -128,8 +128,10 @@ func (c *Conn) Streams() ([]av.CodecData, error) {
|
|||||||
return []av.CodecData{video, audio}, nil
|
return []av.CodecData{video, audio}, nil
|
||||||
} else if video != nil {
|
} else if video != nil {
|
||||||
c.videoIdx = 0
|
c.videoIdx = 0
|
||||||
|
c.audioIdx = -1
|
||||||
return []av.CodecData{video}, nil
|
return []av.CodecData{video}, nil
|
||||||
} else if audio != nil {
|
} else if audio != nil {
|
||||||
|
c.videoIdx = -1
|
||||||
c.audioIdx = 0
|
c.audioIdx = 0
|
||||||
return []av.CodecData{audio}, nil
|
return []av.CodecData{audio}, nil
|
||||||
}
|
}
|
||||||
@@ -139,17 +141,19 @@ func (c *Conn) Streams() ([]av.CodecData, error) {
|
|||||||
|
|
||||||
func (c *Conn) ReadPacket() (av.Packet, error) {
|
func (c *Conn) ReadPacket() (av.Packet, error) {
|
||||||
for {
|
for {
|
||||||
tag, ts, err := flvio.ReadTag(c.reader, c.buf)
|
tag, ts, err := ReadTag(c.reader, c.buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return av.Packet{}, err
|
return av.Packet{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tag.Type {
|
switch tag.Type {
|
||||||
case flvio.TAG_VIDEO:
|
case flvio.TAG_VIDEO:
|
||||||
if tag.AVCPacketType != flvio.AVC_NALU {
|
if c.videoIdx < 0 || tag.AVCPacketType != flvio.AVC_NALU {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//log.Printf("[FLV] %v, len: %d, ts: %10d", h264.Types(tag.Data), len(tag.Data), flvio.TsToTime(ts))
|
||||||
|
|
||||||
return av.Packet{
|
return av.Packet{
|
||||||
Idx: c.videoIdx,
|
Idx: c.videoIdx,
|
||||||
Data: tag.Data,
|
Data: tag.Data,
|
||||||
@@ -159,7 +163,7 @@ func (c *Conn) ReadPacket() (av.Packet, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
case flvio.TAG_AUDIO:
|
case flvio.TAG_AUDIO:
|
||||||
if tag.SoundFormat != flvio.SOUND_AAC || tag.AACPacketType != flvio.AAC_RAW {
|
if c.audioIdx < 0 || tag.SoundFormat != flvio.SOUND_AAC || tag.AACPacketType != flvio.AAC_RAW {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ import (
|
|||||||
type Consumer struct {
|
type Consumer struct {
|
||||||
streamer.Element
|
streamer.Element
|
||||||
|
|
||||||
|
Medias []*streamer.Media
|
||||||
UserAgent string
|
UserAgent string
|
||||||
RemoteAddr string
|
RemoteAddr string
|
||||||
|
|
||||||
@@ -26,6 +27,10 @@ type Consumer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Consumer) GetMedias() []*streamer.Media {
|
func (c *Consumer) GetMedias() []*streamer.Media {
|
||||||
|
if c.Medias != nil {
|
||||||
|
return c.Medias
|
||||||
|
}
|
||||||
|
|
||||||
return []*streamer.Media{
|
return []*streamer.Media{
|
||||||
{
|
{
|
||||||
Kind: streamer.KindVideo,
|
Kind: streamer.KindVideo,
|
||||||
|
@@ -2,7 +2,6 @@ package rtsp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -281,7 +280,7 @@ func (c *Conn) Options() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if val := res.Header.Get("Content-Base"); val != "" {
|
if val := res.Header.Get("Content-Base"); val != "" {
|
||||||
c.URL, err = url.Parse(val)
|
c.URL, err = urlParse(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -311,7 +310,7 @@ func (c *Conn) Describe() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if val := res.Header.Get("Content-Base"); val != "" {
|
if val := res.Header.Get("Content-Base"); val != "" {
|
||||||
c.URL, err = url.Parse(val)
|
c.URL, err = urlParse(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -380,7 +379,7 @@ func (c *Conn) SetupMedia(
|
|||||||
}
|
}
|
||||||
rawURL += media.Control
|
rawURL += media.Control
|
||||||
}
|
}
|
||||||
trackURL, err := url.Parse(rawURL)
|
trackURL, err := urlParse(rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -882,42 +881,3 @@ func (c *Conn) bindTrack(
|
|||||||
|
|
||||||
return track.Bind(push)
|
return track.Bind(push)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RTCP struct {
|
|
||||||
Channel byte
|
|
||||||
Header rtcp.Header
|
|
||||||
Packets []rtcp.Packet
|
|
||||||
}
|
|
||||||
|
|
||||||
const sdpHeader = `v=0
|
|
||||||
o=- 0 0 IN IP4 0.0.0.0
|
|
||||||
s=-
|
|
||||||
t=0 0`
|
|
||||||
|
|
||||||
func UnmarshalSDP(rawSDP []byte) ([]*streamer.Media, error) {
|
|
||||||
medias, err := streamer.UnmarshalSDP(rawSDP)
|
|
||||||
if err != nil {
|
|
||||||
// fix SDP header for some cameras
|
|
||||||
i := bytes.Index(rawSDP, []byte("\nm="))
|
|
||||||
if i > 0 {
|
|
||||||
rawSDP = append([]byte(sdpHeader), rawSDP[i:]...)
|
|
||||||
medias, err = streamer.UnmarshalSDP(rawSDP)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix bug in ONVIF spec
|
|
||||||
// https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec-v241.pdf
|
|
||||||
for _, media := range medias {
|
|
||||||
switch media.Direction {
|
|
||||||
case streamer.DirectionRecvonly, "":
|
|
||||||
media.Direction = streamer.DirectionSendonly
|
|
||||||
case streamer.DirectionSendonly:
|
|
||||||
media.Direction = streamer.DirectionRecvonly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return medias, nil
|
|
||||||
}
|
|
||||||
|
63
pkg/rtsp/helpers.go
Normal file
63
pkg/rtsp/helpers.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package rtsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
||||||
|
"github.com/pion/rtcp"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RTCP struct {
|
||||||
|
Channel byte
|
||||||
|
Header rtcp.Header
|
||||||
|
Packets []rtcp.Packet
|
||||||
|
}
|
||||||
|
|
||||||
|
const sdpHeader = `v=0
|
||||||
|
o=- 0 0 IN IP4 0.0.0.0
|
||||||
|
s=-
|
||||||
|
t=0 0`
|
||||||
|
|
||||||
|
func UnmarshalSDP(rawSDP []byte) ([]*streamer.Media, error) {
|
||||||
|
medias, err := streamer.UnmarshalSDP(rawSDP)
|
||||||
|
if err != nil {
|
||||||
|
// fix SDP header for some cameras
|
||||||
|
i := bytes.Index(rawSDP, []byte("\nm="))
|
||||||
|
if i > 0 {
|
||||||
|
rawSDP = append([]byte(sdpHeader), rawSDP[i:]...)
|
||||||
|
medias, err = streamer.UnmarshalSDP(rawSDP)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix bug in ONVIF spec
|
||||||
|
// https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec-v241.pdf
|
||||||
|
for _, media := range medias {
|
||||||
|
switch media.Direction {
|
||||||
|
case streamer.DirectionRecvonly, "":
|
||||||
|
media.Direction = streamer.DirectionSendonly
|
||||||
|
case streamer.DirectionSendonly:
|
||||||
|
media.Direction = streamer.DirectionRecvonly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return medias, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// urlParse fix bug in URL from D-Link camera:
|
||||||
|
// Content-Base: rtsp://::ffff:192.168.1.123/onvif/profile.1/
|
||||||
|
func urlParse(rawURL string) (*url.URL, error) {
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil && strings.HasSuffix(err.Error(), "after host") {
|
||||||
|
if i1 := strings.Index(rawURL, "://"); i1 > 0 {
|
||||||
|
if i2 := strings.IndexByte(rawURL[i1+3:], '/'); i2 > 0 {
|
||||||
|
return urlParse(rawURL[:i1+3+i2] + ":" + rawURL[i1+3+i2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, err
|
||||||
|
}
|
12
pkg/rtsp/rtsp_test.go
Normal file
12
pkg/rtsp/rtsp_test.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package rtsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLParse(t *testing.T) {
|
||||||
|
base := "rtsp://::ffff:192.168.1.123/onvif/profile.1/"
|
||||||
|
_, err := urlParse(base)
|
||||||
|
assert.Empty(t, err)
|
||||||
|
}
|
@@ -395,11 +395,11 @@ export class VideoRTC extends HTMLElement {
|
|||||||
bufLen = 0;
|
bufLen = 0;
|
||||||
sb.appendBuffer(data);
|
sb.appendBuffer(data);
|
||||||
} else if (sb.buffered && sb.buffered.length) {
|
} else if (sb.buffered && sb.buffered.length) {
|
||||||
const end = sb.buffered.end(sb.buffered.length - 1) - 5;
|
const end = sb.buffered.end(sb.buffered.length - 1) - 15;
|
||||||
const start = sb.buffered.start(0);
|
const start = sb.buffered.start(0);
|
||||||
if (end > start) {
|
if (end > start) {
|
||||||
sb.remove(start, end);
|
sb.remove(start, end);
|
||||||
ms.setLiveSeekableRange(end, end + 5);
|
ms.setLiveSeekableRange(end, end + 15);
|
||||||
}
|
}
|
||||||
// console.debug("VideoRTC.buffered", start, end);
|
// console.debug("VideoRTC.buffered", start, end);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user