diff --git a/README.md b/README.md
index c803b7a3..8c97ad38 100644
--- a/README.md
+++ b/README.md
@@ -2,15 +2,15 @@

- [](https://github.com/AlexxIT/go2rtc/stargazers)
- [](https://hub.docker.com/r/alexxit/go2rtc)
+ [](https://github.com/AlexxIT/go2rtc/stargazers)
+ [](https://hub.docker.com/r/alexxit/go2rtc)
[](https://github.com/AlexxIT/go2rtc/releases)
[](https://goreportcard.com/report/github.com/AlexxIT/go2rtc)
Ultimate camera streaming application with support for RTSP, WebRTC, HomeKit, FFmpeg, RTMP, etc.
-
+
- zero-dependency and zero-config [small app](#go2rtc-binary) for all OS (Windows, macOS, Linux, ARM)
- zero-delay for many supported protocols (lowest possible streaming latency)
@@ -22,9 +22,9 @@ Ultimate camera streaming application with support for RTSP, WebRTC, HomeKit, FF
- on-the-fly transcoding for unsupported codecs via [FFmpeg](#source-ffmpeg)
- play audio files and live streams on some cameras with [speaker](#stream-to-camera)
- multi-source two-way [codecs negotiation](#codecs-negotiation)
- - mixing tracks from different sources to single stream
- - auto-match client-supported codecs
- - [two-way audio](#two-way-audio) for some cameras
+ - mixing tracks from different sources to single stream
+ - auto-match client-supported codecs
+ - [two-way audio](#two-way-audio) for some cameras
- can be [integrated to](#module-api) any smart home platform or be used as [standalone app](#go2rtc-binary)
**Supported Formats** - describes the communication API: authorization, encryption, command set, structure of media packets
@@ -49,77 +49,77 @@ Ultimate camera streaming application with support for RTSP, WebRTC, HomeKit, FF
- [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)
-- creator of the project's logo [@v_novoseltsev](https://www.instagram.com/v_novoseltsev)
+- creator of the project's logo [@v_novoseltsev](https://www.instagram.com/v_novoseltsev)
> [!CAUTION]
> The official website of the project is this GitHub repository and go2rtc.org (hosted on GitHub Pages). The website go2rtc[.]com is in no way associated with the authors of this project.
---
-* [Fast start](#fast-start)
- * [go2rtc: Binary](#go2rtc-binary)
- * [go2rtc: Docker](#go2rtc-docker)
- * [go2rtc: Home Assistant Add-on](#go2rtc-home-assistant-add-on)
- * [go2rtc: Home Assistant Integration](#go2rtc-home-assistant-integration)
- * [go2rtc: Dev version](#go2rtc-dev-version)
-* [Configuration](#configuration)
- * [Module: Streams](#module-streams)
- * [Two way audio](#two-way-audio)
- * [Source: RTSP](#source-rtsp)
- * [Source: RTMP](#source-rtmp)
- * [Source: HTTP](#source-http)
- * [Source: ONVIF](#source-onvif)
- * [Source: FFmpeg](#source-ffmpeg)
- * [Source: FFmpeg Device](#source-ffmpeg-device)
- * [Source: Exec](#source-exec)
- * [Source: Echo](#source-echo)
- * [Source: Expr](#source-expr)
- * [Source: HomeKit](#source-homekit)
- * [Source: Bubble](#source-bubble)
- * [Source: DVRIP](#source-dvrip)
- * [Source: Tapo](#source-tapo)
- * [Source: Kasa](#source-kasa)
- * [Source: Multitrans](#source-multitrans)
- * [Source: Tuya](#source-tuya)
- * [Source: Xiaomi](#source-xiaomi)
- * [Source: Wyze](#source-wyze)
- * [Source: GoPro](#source-gopro)
- * [Source: Ivideon](#source-ivideon)
- * [Source: Hass](#source-hass)
- * [Source: ISAPI](#source-isapi)
- * [Source: Nest](#source-nest)
- * [Source: Ring](#source-ring)
- * [Source: Roborock](#source-roborock)
- * [Source: Doorbird](#source-doorbird)
- * [Source: WebRTC](#source-webrtc)
- * [Source: WebTorrent](#source-webtorrent)
- * [Incoming sources](#incoming-sources)
- * [Stream to camera](#stream-to-camera)
- * [Publish stream](#publish-stream)
- * [Preload stream](#preload-stream)
- * [Module: API](#module-api)
- * [Module: RTSP](#module-rtsp)
- * [Module: RTMP](#module-rtmp)
- * [Module: WebRTC](#module-webrtc)
- * [Module: HomeKit](#module-homekit)
- * [Module: WebTorrent](#module-webtorrent)
- * [Module: ngrok](#module-ngrok)
- * [Module: Hass](#module-hass)
- * [Module: MP4](#module-mp4)
- * [Module: HLS](#module-hls)
- * [Module: MJPEG](#module-mjpeg)
- * [Module: Log](#module-log)
-* [Security](#security)
-* [Codecs filters](#codecs-filters)
-* [Codecs madness](#codecs-madness)
-* [Codecs negotiation](#codecs-negotiation)
-* [Projects using go2rtc](#projects-using-go2rtc)
-* [Camera experience](#cameras-experience)
-* [TIPS](#tips)
+- [Fast start](#fast-start)
+ - [go2rtc: Binary](#go2rtc-binary)
+ - [go2rtc: Docker](#go2rtc-docker)
+ - [go2rtc: Home Assistant add-on](#go2rtc-home-assistant-add-on)
+ - [go2rtc: Home Assistant integration](#go2rtc-home-assistant-integration)
+ - [go2rtc: Dev version](#go2rtc-dev-version)
+- [Configuration](#configuration)
+ - [Module: Streams](#module-streams)
+ - [Two-way audio](#two-way-audio)
+ - [Source: RTSP](#source-rtsp)
+ - [Source: RTMP](#source-rtmp)
+ - [Source: HTTP](#source-http)
+ - [Source: ONVIF](#source-onvif)
+ - [Source: FFmpeg](#source-ffmpeg)
+ - [Source: FFmpeg Device](#source-ffmpeg-device)
+ - [Source: Exec](#source-exec)
+ - [Source: Echo](#source-echo)
+ - [Source: Expr](#source-expr)
+ - [Source: HomeKit](#source-homekit)
+ - [Source: Bubble](#source-bubble)
+ - [Source: DVRIP](#source-dvrip)
+ - [Source: Tapo](#source-tapo)
+ - [Source: Kasa](#source-kasa)
+ - [Source: Multitrans](#source-multitrans)
+ - [Source: Tuya](#source-tuya)
+ - [Source: Xiaomi](#source-xiaomi)
+ - [Source: Wyze](#source-wyze)
+ - [Source: GoPro](#source-gopro)
+ - [Source: Ivideon](#source-ivideon)
+ - [Source: Hass](#source-hass)
+ - [Source: ISAPI](#source-isapi)
+ - [Source: Nest](#source-nest)
+ - [Source: Ring](#source-ring)
+ - [Source: Roborock](#source-roborock)
+ - [Source: Doorbird](#source-doorbird)
+ - [Source: WebRTC](#source-webrtc)
+ - [Source: WebTorrent](#source-webtorrent)
+ - [Incoming sources](#incoming-sources)
+ - [Stream to camera](#stream-to-camera)
+ - [Publish stream](#publish-stream)
+ - [Preload stream](#preload-stream)
+ - [Module: API](#module-api)
+ - [Module: RTSP](#module-rtsp)
+ - [Module: RTMP](#module-rtmp)
+ - [Module: WebRTC](#module-webrtc)
+ - [Module: HomeKit](#module-homekit)
+ - [Module: WebTorrent](#module-webtorrent)
+ - [Module: ngrok](#module-ngrok)
+ - [Module: Hass](#module-hass)
+ - [Module: MP4](#module-mp4)
+ - [Module: HLS](#module-hls)
+ - [Module: MJPEG](#module-mjpeg)
+ - [Module: Log](#module-log)
+- [Security](#security)
+- [Codecs filters](#codecs-filters)
+- [Codecs madness](#codecs-madness)
+- [Codecs negotiation](#codecs-negotiation)
+- [Projects using go2rtc](#projects-using-go2rtc)
+- [Camera experience](#camera-experience)
+- [Tips](#tips)
# Fast start
-1. Download [binary](#go2rtc-binary) or use [Docker](#go2rtc-docker) or Home Assistant [Add-on](#go2rtc-home-assistant-add-on) or [Integration](#go2rtc-home-assistant-integration)
+1. Download [binary](#go2rtc-binary) or use [Docker](#go2rtc-docker) or Home Assistant [add-on](#go2rtc-home-assistant-add-on) or [Integration](#go2rtc-home-assistant-integration)
2. Open web interface: `http://localhost:1984/`
**Optionally:**
@@ -156,11 +156,11 @@ PS. The application is compiled with the latest versions of the Go language for
## go2rtc: Docker
-The Docker container [`alexxit/go2rtc`](https://hub.docker.com/r/alexxit/go2rtc) supports multiple architectures including `amd64`, `386`, `arm64`, and `arm`. This container offers the same functionality as the [Home Assistant Add-on](#go2rtc-home-assistant-add-on) but is designed to operate independently of Home Assistant. It comes preinstalled with [FFmpeg](#source-ffmpeg) and [Python](#source-echo).
+The Docker container [`alexxit/go2rtc`](https://hub.docker.com/r/alexxit/go2rtc) supports multiple architectures including `amd64`, `386`, `arm64`, and `arm`. This container offers the same functionality as the [Home Assistant add-on](#go2rtc-home-assistant-add-on) but is designed to operate independently of Home Assistant. It comes preinstalled with [FFmpeg](#source-ffmpeg) and [Python](#source-echo).
-## go2rtc: Home Assistant Add-on
+## 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)
+[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?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`
@@ -177,7 +177,7 @@ Latest, but maybe unstable version:
- Binary: [latest nightly release](https://nightly.link/AlexxIT/go2rtc/workflows/build/master)
- Docker: `alexxit/go2rtc:master` or `alexxit/go2rtc:master-hardware` versions
-- Hass Add-on: `go2rtc master` or `go2rtc master hardware` versions
+- Home Assistant add-on: `go2rtc master` or `go2rtc master hardware` versions
# Configuration
@@ -222,20 +222,20 @@ Available source types:
- [bubble](#source-bubble) - streaming from ESeeCloud/dvr163 NVR
- [dvrip](#source-dvrip) - streaming from DVR-IP NVR
- [eseecloud](#source-eseecloud) - streaming from ESeeCloud/dvr163 NVR
-- [tapo](#source-tapo) - TP-Link Tapo cameras with [two way audio](#two-way-audio) support
-- [ring](#source-ring) - Ring cameras with [two way audio](#two-way-audio) support
-- [tuya](#source-tuya) - Tuya cameras with [two way audio](#two-way-audio) support
-- [xiaomi](#source-xiaomi) - Xiaomi cameras with [two way audio](#two-way-audio) support
+- [tapo](#source-tapo) - TP-Link Tapo cameras with [two-way audio](#two-way-audio) support
+- [ring](#source-ring) - Ring cameras with [two-way audio](#two-way-audio) support
+- [tuya](#source-tuya) - Tuya cameras with [two-way audio](#two-way-audio) support
+- [xiaomi](#source-xiaomi) - Xiaomi cameras with [two-way audio](#two-way-audio) support
- [kasa](#source-tapo) - TP-Link Kasa cameras
- [gopro](#source-gopro) - GoPro cameras
- [ivideon](#source-ivideon) - public cameras from [Ivideon](https://tv.ivideon.com/) service
- [hass](#source-hass) - Home Assistant integration
- [isapi](#source-isapi) - two-way audio for Hikvision (ISAPI) cameras
- [roborock](#source-roborock) - Roborock vacuums with cameras
-- [doorbird](#source-doorbird) - Doorbird cameras with [two way audio](#two-way-audio) support
+- [doorbird](#source-doorbird) - Doorbird cameras with [two-way audio](#two-way-audio) support
- [webrtc](#source-webrtc) - WebRTC/WHEP sources
- [webtorrent](#source-webtorrent) - WebTorrent source from another go2rtc
-- [wyze](#source-wyze) - Wyze cameras with [two way audio](#two-way-audio) support
+- [wyze](#source-wyze) - Wyze cameras with [two-way audio](#two-way-audio) support
Read more about [incoming sources](#incoming-sources)
@@ -277,7 +277,7 @@ streams:
**Recommendations**
- **Amcrest Doorbell** users may want to disable two-way audio, because with an active stream, you won't have a working call button. You need to add `#backchannel=0` to the end of your RTSP link in YAML config file
-- **Dahua Doorbell** users may want to change [audio codec](https://github.com/AlexxIT/go2rtc/issues/49#issuecomment-2127107379) for proper 2-way audio. Make sure not to request backchannel multiple times by adding `#backchannel=0` to other stream sources of the same doorbell. The `unicast=true&proto=Onvif` is preferred for 2-way audio as this makes the doorbell accept multiple codecs for the incoming audio
+- **Dahua Doorbell** users may want to change [audio codec](https://github.com/AlexxIT/go2rtc/issues/49#issuecomment-2127107379) for proper two-way audio. Make sure not to request backchannel multiple times by adding `#backchannel=0` to other stream sources of the same doorbell. The `unicast=true&proto=Onvif` is preferred for two-way audio as this makes the doorbell accept multiple codecs for the incoming audio
- **Reolink** users may want NOT to use RTSP protocol at all, some camera models have a very awful, unusable stream implementation
- **Ubiquiti UniFi** users may want to disable HTTPS verification. Use `rtspx://` prefix instead of `rtsps://`. And don't use `?enableSrtp` [suffix](https://github.com/AlexxIT/go2rtc/issues/81)
- **TP-Link Tapo** users may skip login and password, because go2rtc support login [without them](https://drmnsamoliu.github.io/video.html)
@@ -290,7 +290,7 @@ streams:
Format: `rtsp...#{param1}#{param2}#{param3}`
- Add custom timeout `#timeout=30` (in seconds)
-- Ignore audio - `#media=video` or ignore video - `#media=audio`
+- Ignore audio - `#media=video` or ignore video - `#media=audio`
- Ignore two-way audio API `#backchannel=0` - important for some glitchy cameras
- Use WebSocket transport `#transport=ws...`
@@ -363,8 +363,8 @@ streams:
You can get any stream, file or device via FFmpeg and push it to go2rtc. The app will automatically start FFmpeg with the proper arguments when someone starts watching the stream.
-- FFmpeg preistalled for **Docker** and **Hass Add-on** users
-- **Hass Add-on** users can target files from [/media](https://www.home-assistant.io/more-info/local-media/setup-media/) folder
+- FFmpeg preinstalled for **Docker** and **Home Assistant add-on** users
+- **Home Assistant add-on** users can target files from [/media](https://www.home-assistant.io/more-info/local-media/setup-media/) folder
Format: `ffmpeg:{input}#{param1}#{param2}#{param3}`. Examples:
@@ -479,7 +479,7 @@ streams:
Some sources may have a dynamic link. And you will need to get it using a Bash or Python script. Your script should echo a link to the source. RTSP, FFmpeg or any of the [supported sources](#module-streams).
-**Docker** and **Hass Add-on** users has preinstalled `python3`, `curl`, `jq`.
+**Docker** and **Home Assistant add-on** users has preinstalled `python3`, `curl`, `jq`.
Check examples in [wiki](https://github.com/AlexxIT/go2rtc/wiki/Source-Echo-examples).
@@ -504,7 +504,7 @@ Like `echo` source, but uses the built-in [expr](https://github.com/antonmedv/ex
- 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 an iPhone
- HomeKit device should be on the same network with working [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) between the device and go2rtc
-go2rtc supports importing paired HomeKit devices from [Home Assistant](#source-hass). So you can use HomeKit camera with Hass and go2rtc simultaneously. If you are using Hass, I recommend pairing devices with it; it will give you more options.
+go2rtc supports importing paired HomeKit devices from [Home Assistant](#source-hass). So you can use HomeKit camera with Home Assistant and go2rtc simultaneously. If you are using Home Assistant, 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 rebooting your HomeKit device (power off). If you still can't see it, you have a problem with mDNS.
@@ -518,7 +518,7 @@ If you see a device but it does not have a pairing button, it is paired to some
Recommended settings for using HomeKit Camera with WebRTC, MSE, MP4, RTSP:
-```
+```yaml
streams:
aqara_g3:
- hass:Camera-Hub-G3-AB12
@@ -576,7 +576,7 @@ streams:
*[New in v1.2.0](https://github.com/AlexxIT/go2rtc/releases/tag/v1.2.0)*
-[TP-Link Tapo](https://www.tapo.com/) proprietary camera protocol with **two way audio** support.
+[TP-Link Tapo](https://www.tapo.com/) proprietary camera protocol with **two-way audio** support.
- stream quality is the same as [RTSP protocol](https://www.tapo.com/en/faq/34/)
- use the **cloud password**, this is not the RTSP password! you do not need to add a login!
@@ -628,7 +628,7 @@ Two-way audio support for Chinese version of [TP-Link cameras](https://www.tp-li
*[New in v1.9.13](https://github.com/AlexxIT/go2rtc/releases/tag/v1.9.13)*
-[Tuya](https://www.tuya.com/) proprietary camera protocol with **two way audio** support. Go2rtc supports `Tuya Smart API` and `Tuya Cloud API`.
+[Tuya](https://www.tuya.com/) proprietary camera protocol with **two-way audio** support. Go2rtc supports `Tuya Smart API` and `Tuya Cloud API`.
*[read more](internal/tuya/README.md)*
@@ -674,7 +674,7 @@ Support import camera links from [Home Assistant](https://www.home-assistant.io/
```yaml
hass:
- config: "/config" # skip this setting if you Hass add-on user
+ config: "/config" # skip this setting if you are a Home Assistant add-on user
streams:
generic_camera: hass:Camera1 # Settings > Integrations > Integration Name
@@ -691,13 +691,13 @@ Any cameras in WebRTC format are supported. But at the moment Home Assistant onl
streams:
# link to Home Assistant Supervised
hass-webrtc1: hass://supervisor?entity_id=camera.nest_doorbell
- # link to external Hass with Long-Lived Access Tokens
+ # link to external Home Assistant with Long-Lived Access Tokens
hass-webrtc2: hass://192.168.1.123:8123?entity_id=camera.nest_doorbell&token=eyXYZ...
```
**RTSP Cameras**
-By default, the Home Assistant API does not allow you to get a dynamic RTSP link to a camera stream. So more cameras, like [Tuya](https://www.home-assistant.io/integrations/tuya/), and possibly others, can also be imported using [this method](https://github.com/felipecrs/hass-expose-camera-stream-source#importing-home-assistant-cameras-to-go2rtc-andor-frigate).
+By default, the Home Assistant API does not allow you to get a dynamic RTSP link to a camera stream. [This method](https://github.com/felipecrs/hass-expose-camera-stream-source#importing-cameras-from-home-assistant-to-go2rtc-or-frigate) can work around it.
## Source: ISAPI
@@ -718,7 +718,7 @@ streams:
Currently, only WebRTC cameras are supported.
-For simplicity, it is recommended to connect the Nest/WebRTC camera to the [Home Assistant](#source-hass). But if you can somehow get the below parameters, Nest/WebRTC source will work without Hass.
+For simplicity, it is recommended to connect the Nest/WebRTC camera to the [Home Assistant](#source-hass). But if you can somehow get the below parameters, Nest/WebRTC source will work without Home Assistant.
```yaml
streams:
@@ -727,7 +727,7 @@ streams:
## Source: Ring
-This source type support Ring cameras with [two way audio](#two-way-audio) support. If you have a `refresh_token` and `device_id` - you can use it in `go2rtc.yaml` config file. Otherwise, you can use the go2rtc interface and add your ring account (WebUI > Add > Ring). Once added, it will list all your Ring cameras.
+This source type support Ring cameras with [two-way audio](#two-way-audio) support. If you have a `refresh_token` and `device_id` - you can use it in `go2rtc.yaml` config file. Otherwise, you can use the go2rtc interface and add your ring account (WebUI > Add > Ring). Once added, it will list all your Ring cameras.
```yaml
streams:
@@ -821,18 +821,25 @@ By default, go2rtc establishes a connection to the source when any client reques
**Examples**
- RTSP with any codec
+
```yaml
ffmpeg -re -i BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp rtsp://localhost:8554/camera1
```
+
- HTTP-MJPEG with MJPEG codec
+
```yaml
ffmpeg -re -i BigBuckBunny.mp4 -c mjpeg -f mpjpeg http://localhost:1984/api/stream.mjpeg?dst=camera1
```
+
- HTTP-FLV with H264, AAC codecs
+
```yaml
ffmpeg -re -i BigBuckBunny.mp4 -c copy -f flv http://localhost:1984/api/stream.flv?dst=camera1
```
+
- MPEG-TS with H264 codec
+
```yaml
ffmpeg -re -i BigBuckBunny.mp4 -c copy -f mpegts http://localhost:1984/api/stream.ts?dst=camera1
```
@@ -855,7 +862,7 @@ You can turn the browser of any PC or mobile into an IP camera with support for
You can use **OBS Studio** or any other broadcast software with [WHIP](https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html) protocol support. This standard has not yet been approved. But you can download OBS Studio [dev version](https://github.com/obsproject/obs-studio/actions/runs/3969201209):
-- Settings > Stream > Service: WHIP > http://192.168.1.123:1984/api/webrtc?dst=camera1
+- Settings > Stream > Service: WHIP > `http://192.168.1.123:1984/api/webrtc?dst=camera1`
## Stream to camera
@@ -865,11 +872,11 @@ go2rtc supports playing audio files (ex. music or [TTS](https://www.home-assista
API example:
-```
+```text
POST http://localhost:1984/api/streams?dst=camera1&src=ffmpeg:http://example.com/song.mp3#audio=pcma#input=file
```
-- you can stream: local files, web files, live streams or any format, supported by FFmpeg
+- you can stream: local files, web files, live streams or any format, supported by FFmpeg
- you should use [ffmpeg source](#source-ffmpeg) for transcoding audio to codec, that your camera supports
- you can check camera codecs on the go2rtc WebUI info page when the stream is active
- some cameras support only low quality `PCMA/8000` codec (ex. [Tapo](#source-tapo))
@@ -891,7 +898,7 @@ You can publish any stream to streaming services (YouTube, Telegram, etc.) via R
You can use the API:
-```
+```text
POST http://localhost:1984/api/streams?src=camera1&dst=rtmps://...
```
@@ -983,7 +990,7 @@ api:
You can get any stream as RTSP-stream: `rtsp://192.168.1.123:8554/{stream_name}`
-You can enable external password protection for your RTSP streams. Password protection is always disabled for localhost calls (ex. FFmpeg or Hass on the same server).
+You can enable external password protection for your RTSP streams. Password protection is always disabled for localhost calls (ex. FFmpeg or Home Assistant on the same server).
```yaml
rtsp:
@@ -1008,7 +1015,7 @@ Read more about [codecs filters](#codecs-filters).
You can get any stream as RTMP-stream: `rtmp://192.168.1.123/{stream_name}`. Only H264/AAC codecs supported right now.
-[Incoming stream](#incoming-sources) in RTMP format tested only with [OBS Studio](https://obsproject.com/) and a Dahua camera. Different FFmpeg versions have different problems with this format.
+[Incoming stream](#incoming-sources) in RTMP format tested only with [OBS Studio](https://obsproject.com/) and a Dahua camera. Different FFmpeg versions have different problems with this format.
```yaml
rtmp:
@@ -1151,19 +1158,17 @@ webtorrent:
src: rtsp-dahua1 # stream name from streams section
```
-Link example: https://go2rtc.org/webtorrent/#share=02SNtgjKXY&pwd=wznEQqznxW&media=video+audio
+Link example: `https://go2rtc.org/webtorrent/#share=02SNtgjKXY&pwd=wznEQqznxW&media=video+audio`
## Module: ngrok
-With [ngrok](https://ngrok.com/) integration, you can get external access to your streams in situations when you have Internet with a private IP address.
+With [ngrok](https://ngrok.com/) integration, you can get external access to your streams in situations when you have internet with a private IP address.
*[read more](internal/ngrok/README.md)*
## Module: Hass
-The best and easiest way to use go2rtc inside Home Assistant is to install the custom integration [WebRTC Camera](#go2rtc-home-assistant-integration) and custom Lovelace card.
-
-But go2rtc is also compatible and can be used with the [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) built-in integration.
+While [go2rtc is used by default in Home Assistant](https://www.home-assistant.io/integrations/go2rtc/), the best and easiest way to have full control over it is to install the [WebRTC Camera](#go2rtc-home-assistant-integration) custom integration and card.
You have several options on how to add a camera to Home Assistant:
@@ -1171,20 +1176,15 @@ You have several options on how to add a camera to Home Assistant:
2. Camera [any source](#module-streams) => [go2rtc config](#configuration) => [Generic Camera](https://www.home-assistant.io/integrations/generic/)
- Install any [go2rtc](#fast-start)
- Add your stream to [go2rtc config](#configuration)
- - Hass > Settings > Integrations > Add Integration > [ONVIF](https://my.home-assistant.io/redirect/config_flow_start/?domain=onvif) > Host: `127.0.0.1`, Port: `1984`
- - Hass > Settings > Integrations > Add Integration > [Generic Camera](https://my.home-assistant.io/redirect/config_flow_start/?domain=generic) > Stream Source URL: `rtsp://127.0.0.1:8554/camera1` (change to your stream name, leave everything else as is)
+ - Home Assistant > Settings > Integrations > Add Integration > [ONVIF](https://my.home-assistant.io/redirect/config_flow_start/?domain=onvif) > Host: `127.0.0.1`, Port: `1984`
+ - Home Assistant > Settings > Integrations > Add Integration > [Generic Camera](https://my.home-assistant.io/redirect/config_flow_start/?domain=generic) > Stream Source URL: `rtsp://127.0.0.1:8554/camera1` (change to your stream name, leave everything else as is)
You have several options on how to watch the stream from the cameras in Home Assistant:
-1. `Camera Entity` => `Picture Entity Card` => Technology `HLS`, codecs: `H264/H265/AAC`, poor latency.
-2. `Camera Entity` => [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) => `Picture Entity Card` => Technology `WebRTC`, codecs: `H264/PCMU/PCMA/OPUS`, best latency.
- - Install any [go2rtc](#fast-start)
- - 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/`
- - RTSPtoWebRTC > Configure > STUN server: `stun.l.google.com:19302`
- - Use Picture Entity or Picture Glance Lovelace card
-3. `Camera Entity` or `Camera URL` => [WebRTC Camera](https://github.com/AlexxIT/WebRTC) => Technology: `WebRTC/MSE/MP4/MJPEG`, codecs: `H264/H265/AAC/PCMU/PCMA/OPUS`, best latency, best compatibility.
+1. `Camera Entity` => `Picture Entity Card` => Technology `WebRTC` (through [built-in go2rtc](https://www.home-assistant.io/integrations/go2rtc/)), codecs `H264/H265/AAC/PCMU/PCMA/OPUS`, best latency. Fallbacks to: Technology `HLS`, codecs: `H264/H265/AAC`, poor latency.
+2. `Camera Entity` or `Camera URL` => [WebRTC Camera](https://github.com/AlexxIT/WebRTC) => Technology: `WebRTC/MSE/MP4/MJPEG`, codecs: `H264/H265/AAC/PCMU/PCMA/OPUS`, best latency, best compatibility.
- Install and add [WebRTC Camera](https://github.com/AlexxIT/WebRTC) custom integration
- - Use WebRTC Camera custom Lovelace card
+ - Use WebRTC Camera custom card in your dashboard
You can add camera `entity_id` to [go2rtc config](#configuration) if you need transcoding:
@@ -1193,9 +1193,9 @@ streams:
"camera.hall": ffmpeg:{input}#video=copy#audio=opus
```
-**PS.** Default Home Assistant lovelace cards don't support two-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 to use HTTPS to access the microphone. This is a browser restriction and cannot be avoided.
+**PS.** Default Home Assistant cards don't support two-way audio. You can use two-way audio from the [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 to use HTTPS to access the microphone. This is a browser restriction and cannot be avoided.
-**PS.** There is also another nice card with go2rtc support - [Frigate Lovelace Card](https://github.com/dermotduffy/frigate-hass-card).
+**PS.** There is also another nice card with two-way audio support through go2rtc - [Advanced Camera Card](https://github.com/dermotduffy/advanced-camera-card).
## Module: MP4
@@ -1218,7 +1218,7 @@ API examples:
Read more about [codecs filters](#codecs-filters).
-**PS.** Rotate and scale params don't use transcoding and change video using metadata.
+**PS.** Rotate and scale params don't use transcoding and change video using metadata.
## Module: HLS
@@ -1280,7 +1280,7 @@ exec:
allow_paths: [ffmpeg]
```
-By default, `go2rtc` starts the Web interface on port `1984` and RTSP on port `8554`, as well as uses port `8555` for WebRTC connections. The three ports are accessible from your local network. So anyone on your local network can watch video from your cameras without authorization. The same rule applies to the Home Assistant Add-on.
+By default, `go2rtc` starts the Web interface on port `1984` and RTSP on port `8554`, as well as uses port `8555` for WebRTC connections. The three ports are accessible from your local network. So anyone on your local network can watch video from your cameras without authorization. The same rule applies to the Home Assistant add-on.
This is not a problem if you trust your local network as much as I do. But you can change this behaviour with a `go2rtc.yaml` config:
@@ -1296,7 +1296,7 @@ webrtc:
```
- local access to RTSP is not a problem for [FFmpeg](#source-ffmpeg) integration, because it runs locally on your server
-- local access to API is not a problem for the [Home Assistant add-on](#go2rtc-home-assistant-add-on), because Hass runs locally on the same server, and the add-on web UI is protected with Hass authorization ([Ingress feature](https://www.home-assistant.io/blog/2019/04/15/hassio-ingress/))
+- local access to API is not a problem for the [Home Assistant add-on](#go2rtc-home-assistant-add-on), because Home Assistant runs locally on the same server, and the add-on web UI is protected with Home Assistant authorization ([Ingress feature](https://www.home-assistant.io/blog/2019/04/15/hassio-ingress/))
- external access to WebRTC TCP port is not a problem, because it is used only for transmitting encrypted media data
- anyway you need to open this port to your local network and to the Internet for WebRTC to work
@@ -1318,7 +1318,7 @@ Without filters:
Some examples:
-- `rtsp://192.168.1.123:8554/camera1?mp4` - useful for recording as MP4 files (e.g. Hass or Frigate)
+- `rtsp://192.168.1.123:8554/camera1?mp4` - useful for recording as MP4 files (e.g. Home Assistant or Frigate)
- `rtsp://192.168.1.123:8554/camera1?video=h264,h265&audio=aac` - full version of the filter above
- `rtsp://192.168.1.123:8554/camera1?video=h264&audio=aac&audio=opus` - H264 video codec and two separate audio tracks
- `rtsp://192.168.1.123:8554/camera1?video&audio=all` - any video codec and all audio codecs as separate tracks
@@ -1342,7 +1342,7 @@ Some examples:
[1]: https://apps.apple.com/app/home-assistant/id1099568401
-- `HTTP*` - HTTP Progressive Streaming, not related to [progressive download](https://en.wikipedia.org/wiki/Progressive_download), because the file has no size and no end
+- `HTTP*` - HTTP Progressive Streaming, not related to [progressive download](https://en.wikipedia.org/wiki/Progressive_download), because the file has no size and no end
- `WebRTC H265` - supported in [Chrome 136+](https://developer.chrome.com/release-notes/136), supported in [Safari 18+](https://developer.apple.com/documentation/safari-release-notes/safari-18-release-notes)
- `MSE iPhone` - supported in [iOS 17.1+](https://webkit.org/blog/14735/webkit-features-in-safari-17-1/)
@@ -1378,7 +1378,7 @@ But go2rtc has some simple algorithms. They are turned on automatically; you do
Go2rtc can pack `PCMA`, `PCMU` and `PCM` codecs into an MP4 container so that they work in all browsers and all built-in players on modern devices. Including Apple QuickTime:
-```
+```text
PCMA/PCMU => PCM => FLAC => MSE/MP4/HLS
```
@@ -1386,7 +1386,7 @@ PCMA/PCMU => PCM => FLAC => MSE/MP4/HLS
By default WebRTC supports only `PCMA/8000` and `PCMU/8000`. But go2rtc can automatically resample PCMA and PCMU codecs with a different sample rate. Also, go2rtc can transcode `PCM` codec to `PCMA/8000`, so WebRTC can play it:
-```
+```text
PCM/xxx => PCMA/8000 => WebRTC
PCMA/xxx => PCMA/8000 => WebRTC
PCMU/xxx => PCMU/8000 => WebRTC
@@ -1394,7 +1394,7 @@ PCMU/xxx => PCMU/8000 => WebRTC
**Important**
-- FLAC codec not supported in an RTSP stream. If you are using Frigate or Hass for recording MP4 files with PCMA/PCMU/PCM audio, you should set up transcoding to the AAC codec.
+- FLAC codec not supported in an RTSP stream. If you are using Frigate or Home Assistant for recording MP4 files with PCMA/PCMU/PCM audio, you should set up transcoding to the AAC codec.
- PCMA and PCMU are VERY low-quality codecs. They support only 256! different sounds. Use them only when you have no other options.
# Codecs negotiation
@@ -1422,7 +1422,7 @@ streams:
**go2rtc** automatically matches codecs for your browser and all your stream sources. This is called **multi-source two-way codec negotiation**. And this is one of the main features of this app.
-
+
**PS.** You can select `PCMU` or `PCMA` codec in camera settings and not 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.
@@ -1430,7 +1430,7 @@ streams:
- [Home Assistant](https://www.home-assistant.io/) [2024.11+](https://www.home-assistant.io/integrations/go2rtc/) - top open-source smart home project
- [Frigate](https://frigate.video/) [0.12+](https://docs.frigate.video/guides/configuring_go2rtc/) - open-source NVR built around real-time AI object detection
-- [Frigate Lovelace Card](https://github.com/dermotduffy/frigate-hass-card) - custom card for Home Assistant
+- [Advanced Camera Card](https://github.com/dermotduffy/advanced-camera-card) - custom card for Home Assistant
- [OpenIPC](https://github.com/OpenIPC/firmware/tree/master/general/package/go2rtc) - alternative IP camera firmware from an open community
- [wz_mini_hacks](https://github.com/gtxaspec/wz_mini_hacks) - custom firmware for Wyze cameras
- [EufyP2PStream](https://github.com/oischinger/eufyp2pstream) - a small project that provides a video/audio stream from Eufy cameras that don't directly support RTSP
@@ -1460,7 +1460,7 @@ streams:
- [TP-Link](https://www.tp-link.com/) - few streaming clients, packet loss?
- Chinese cheap noname cameras, Wyze Cams, Xiaomi cameras with hacks (usually have `/live/ch00_1` in RTSP URL) - awful but usable RTSP protocol implementation, low stream quality, few settings, packet loss?
-# TIPS
+# Tips
**Using apps for low RTSP delay**
diff --git a/go.mod b/go.mod
index 1e649cae..485509e6 100644
--- a/go.mod
+++ b/go.mod
@@ -5,26 +5,27 @@ go 1.24.0
require (
github.com/asticode/go-astits v1.14.0
github.com/eclipse/paho.mqtt.golang v1.5.1
- github.com/expr-lang/expr v1.17.6
+ github.com/expr-lang/expr v1.17.7
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/mattn/go-isatty v0.0.20
- github.com/miekg/dns v1.1.69
- github.com/pion/ice/v4 v4.1.0
- github.com/pion/interceptor v0.1.42
+ github.com/miekg/dns v1.1.70
+ github.com/pion/dtls/v3 v3.0.10
+ github.com/pion/ice/v4 v4.2.0
+ github.com/pion/interceptor v0.1.43
github.com/pion/rtcp v1.2.16
- github.com/pion/rtp v1.8.26
- github.com/pion/sdp/v3 v3.0.16
- github.com/pion/srtp/v3 v3.0.9
- github.com/pion/stun/v3 v3.0.2
- github.com/pion/webrtc/v4 v4.1.8
+ github.com/pion/rtp v1.10.0
+ github.com/pion/sdp/v3 v3.0.17
+ github.com/pion/srtp/v3 v3.0.10
+ github.com/pion/stun/v3 v3.1.1
+ github.com/pion/webrtc/v4 v4.2.3
github.com/rs/zerolog v1.34.0
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f
github.com/stretchr/testify v1.11.1
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9
- golang.org/x/crypto v0.46.0
- golang.org/x/net v0.48.0
+ golang.org/x/crypto v0.47.0
+ golang.org/x/net v0.49.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -33,18 +34,19 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
- github.com/pion/datachannel v1.5.10 // indirect
- github.com/pion/dtls/v3 v3.0.9 // indirect
+ github.com/pion/datachannel v1.6.0 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/mdns/v2 v2.1.0 // indirect
github.com/pion/randutil v0.1.0 // indirect
- github.com/pion/sctp v1.8.41 // indirect
+ github.com/pion/sctp v1.9.2 // indirect
github.com/pion/transport/v3 v3.1.1 // indirect
- github.com/pion/turn/v4 v4.1.3 // indirect
+ github.com/pion/transport/v4 v4.0.1 // indirect
+ github.com/pion/turn/v4 v4.1.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
- golang.org/x/mod v0.31.0 // indirect
+ golang.org/x/mod v0.32.0 // indirect
golang.org/x/sync v0.19.0 // indirect
- golang.org/x/sys v0.39.0 // indirect
- golang.org/x/tools v0.40.0 // indirect
+ golang.org/x/sys v0.40.0 // indirect
+ golang.org/x/time v0.14.0 // indirect
+ golang.org/x/tools v0.41.0 // indirect
)
diff --git a/go.sum b/go.sum
index d251618d..897bb8a2 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,8 @@ github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2I
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec=
github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
+github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
+github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -32,14 +34,24 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
+github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
+github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
+github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=
+github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk=
github.com/pion/dtls/v3 v3.0.9 h1:4AijfFRm8mAjd1gfdlB1wzJF3fjjR/VPIpJgkEtvYmM=
github.com/pion/dtls/v3 v3.0.9/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os=
+github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
+github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
github.com/pion/ice/v4 v4.1.0 h1:YlxIii2bTPWyC08/4hdmtYq4srbrY0T9xcTsTjldGqU=
github.com/pion/ice/v4 v4.1.0/go.mod h1:5gPbzYxqenvn05k7zKPIZFuSAufolygiy6P1U9HzvZ4=
+github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw=
+github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4=
github.com/pion/interceptor v0.1.42 h1:0/4tvNtruXflBxLfApMVoMubUMik57VZ+94U0J7cmkQ=
github.com/pion/interceptor v0.1.42/go.mod h1:g6XYTChs9XyolIQFhRHOOUS+bGVGLRfgTCUzH29EfVU=
+github.com/pion/interceptor v0.1.43 h1:6hmRfnmjogSs300xfkR0JxYFZ9k5blTEvCD7wxEDuNQ=
+github.com/pion/interceptor v0.1.43/go.mod h1:BSiC1qKIJt1XVr3l3xQ2GEmCFStk9tx8fwtCZxxgR7M=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY=
@@ -50,20 +62,36 @@ github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc=
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
+github.com/pion/rtp v1.10.0 h1:XN/xca4ho6ZEcijpdF2VGFbwuHUfiIMf3ew8eAAE43w=
+github.com/pion/rtp v1.10.0/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/sctp v1.8.41 h1:20R4OHAno4Vky3/iE4xccInAScAa83X6nWUfyc65MIs=
github.com/pion/sctp v1.8.41/go.mod h1:2wO6HBycUH7iCssuGyc2e9+0giXVW0pyCv3ZuL8LiyY=
+github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo=
+github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8=
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
+github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo=
+github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY=
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8=
+github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
+github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
github.com/pion/stun/v3 v3.0.2 h1:BJuGEN2oLrJisiNEJtUTJC4BGbzbfp37LizfqswblFU=
github.com/pion/stun/v3 v3.0.2/go.mod h1:JFJKfIWvt178MCF5H/YIgZ4VX3LYE77vca4b9HP60SA=
+github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=
+github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
+github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
+github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pion/turn/v4 v4.1.3 h1:jVNW0iR05AS94ysEtvzsrk3gKs9Zqxf6HmnsLfRvlzA=
github.com/pion/turn/v4 v4.1.3/go.mod h1:TD/eiBUf5f5LwXbCJa35T7dPtTpCHRJ9oJWmyPLVT3A=
+github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=
+github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=
github.com/pion/webrtc/v4 v4.1.8 h1:ynkjfiURDQ1+8EcJsoa60yumHAmyeYjz08AaOuor+sk=
github.com/pion/webrtc/v4 v4.1.8/go.mod h1:KVaARG2RN0lZx0jc7AWTe38JpPv+1/KicOZ9jN52J/s=
+github.com/pion/webrtc/v4 v4.2.3 h1:RtdWDnkenNQGxUrZqWa5gSkTm5ncsLg5d+zu0M4cXt4=
+github.com/pion/webrtc/v4 v4.2.3/go.mod h1:7vsyFzRzaKP5IELUnj8zLcglPyIT6wWwqTppBZ1k6Kc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
@@ -88,10 +116,16 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
+golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
+golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
+golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
+golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
+golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
+golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -99,10 +133,17 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
+golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
+golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
+golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/api/ws/ws.go b/internal/api/ws/ws.go
index 981d1b41..02c2f90c 100644
--- a/internal/api/ws/ws.go
+++ b/internal/api/ws/ws.go
@@ -11,7 +11,7 @@ import (
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/app"
- "github.com/AlexxIT/go2rtc/pkg/core"
+ "github.com/AlexxIT/go2rtc/pkg/creds"
"github.com/gorilla/websocket"
"github.com/rs/zerolog"
)
@@ -133,7 +133,7 @@ func apiWS(w http.ResponseWriter, r *http.Request) {
if handler := wsHandlers[msg.Type]; handler != nil {
go func() {
if err = handler(tr, msg); err != nil {
- errMsg := core.StripUserinfo(err.Error())
+ errMsg := creds.SecretString(err.Error())
tr.Write(&Message{Type: "error", Value: msg.Type + ": " + errMsg})
}
}()
diff --git a/internal/app/README.md b/internal/app/README.md
index 9ec3d9fc..135bb7da 100644
--- a/internal/app/README.md
+++ b/internal/app/README.md
@@ -35,7 +35,7 @@ rtsp:
Editors like [GoLand](https://www.jetbrains.com/go/) and [VS Code](https://code.visualstudio.com/) supports autocomplete and syntax validation.
```yaml
-# yaml-language-server: $schema=https://raw.githubusercontent.com/AlexxIT/go2rtc/master/website/schema.json
+# yaml-language-server: $schema=https://raw.githubusercontent.com/AlexxIT/go2rtc/master/www/schema.json
```
## Defaults
diff --git a/internal/expr/README.md b/internal/expr/README.md
index db5a27d4..2ad005ce 100644
--- a/internal/expr/README.md
+++ b/internal/expr/README.md
@@ -136,7 +136,7 @@ streams:
"ffmpeg:" + url3 + "#video=copy"
```
-## Comparsion
+## Comparison
| expr | python | js |
|------------------------------|----------------------------|--------------------------------|
diff --git a/internal/ffmpeg/hardware/README.md b/internal/ffmpeg/hardware/README.md
index 2d7f21cf..df8d1d8f 100644
--- a/internal/ffmpeg/hardware/README.md
+++ b/internal/ffmpeg/hardware/README.md
@@ -22,7 +22,7 @@ You **NEED** hardware acceleration if you using `#video=h264`, `#video=h265`, `#
streams:
# auto select hardware encoder
camera1_hw: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=h264#hardware
-
+
# manual select hardware encoder (vaapi, cuda, v4l2m2m, dxva2, videotoolbox)
camera1_vaapi: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=h264#hardware=vaapi
```
@@ -47,7 +47,7 @@ Read more [here](https://en.wikipedia.org/wiki/Intel_Quick_Sync_Video#Hardware_d
Linux and Docker:
- It may be important to have the latest version of the OS with the latest version of the Linux kernel. For example, on my **Debian 10 (kernel 4.19)** it did not work, but after update to **Debian 11 (kernel 5.10)** all was fine.
-- In case of troube check you have `/dev/dri/` folder on your host.
+- In case of trouble check you have `/dev/dri/` folder on your host.
Docker users should add `--privileged` option to container for access to Hardware.
@@ -79,7 +79,7 @@ Read more [here](https://docs.frigate.video/configuration/hardware_acceleration)
**Supported on:** Linux binary, Docker, Hass Addon.
-I don't recommend using transcoding on the Raspberry Pi 3. It's extreamly slow, even with hardware acceleration. Also it may fail when transcoding 2K+ stream.
+I don't recommend using transcoding on the Raspberry Pi 3. It's extremely slow, even with hardware acceleration. Also it may fail when transcoding 2K+ stream.
## Raspberry Pi 4
diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go
index 65f8599a..c305b706 100644
--- a/internal/onvif/onvif.go
+++ b/internal/onvif/onvif.go
@@ -74,8 +74,10 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b)
switch operation {
- case onvif.DeviceGetNetworkInterfaces, // important for Hass
+ case onvif.ServiceGetServiceCapabilities, // important for Hass
+ onvif.DeviceGetNetworkInterfaces, // important for Hass
onvif.DeviceGetSystemDateAndTime, // important for Hass
+ onvif.DeviceSetSystemDateAndTime, // return just OK
onvif.DeviceGetDiscoveryMode,
onvif.DeviceGetDNS,
onvif.DeviceGetHostname,
@@ -83,8 +85,10 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
onvif.DeviceGetNetworkProtocols,
onvif.DeviceGetNTP,
onvif.DeviceGetScopes,
+ onvif.MediaGetVideoEncoderConfiguration,
onvif.MediaGetVideoEncoderConfigurations,
onvif.MediaGetAudioEncoderConfigurations,
+ onvif.MediaGetVideoEncoderConfigurationOptions,
onvif.MediaGetAudioSources,
onvif.MediaGetAudioSourceConfigurations:
b = onvif.StaticResponse(operation)
@@ -100,11 +104,6 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
// important for Hass: SerialNumber (unique server ID)
b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
- case onvif.ServiceGetServiceCapabilities:
- // important for Hass
- // TODO: check path links to media
- b = onvif.GetMediaServiceCapabilitiesResponse()
-
case onvif.DeviceSystemReboot:
b = onvif.StaticResponse(operation)
@@ -134,8 +133,7 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
case onvif.MediaGetStreamUri:
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
+ host = r.Host // in case of Host without port
}
uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken")
diff --git a/internal/webrtc/README.md b/internal/webrtc/README.md
index d50ed06c..22113023 100644
--- a/internal/webrtc/README.md
+++ b/internal/webrtc/README.md
@@ -10,8 +10,8 @@ What you should to know about WebRTC:
If an external connection via STUN is used:
- Uses [UDP hole punching](https://en.wikipedia.org/wiki/UDP_hole_punching) technology to bypass NAT even if you not open your server to the World
-- For about 20% of users, the techology will not work because of the [Symmetric NAT](https://tomchen.github.io/symmetric-nat-test/)
-- UDP is not suitable for transmitting 2K and 4K high bitrate video over open networks because of the high loss rate:
+- For about 20% of users, the technology will not work because of the [Symmetric NAT](https://tomchen.github.io/symmetric-nat-test/)
+- UDP is not suitable for transmitting 2K and 4K high bit rate video over open networks because of the high loss rate:
- https://habr.com/ru/companies/flashphoner/articles/480006/
- https://www.youtube.com/watch?v=FXVg2ckuKfs
@@ -26,7 +26,7 @@ webrtc:
## Config
-**Important!** This example is not for copypasting!
+**Important!** This example is not for copy pasting!
```yaml
webrtc:
@@ -49,13 +49,13 @@ webrtc:
credential: your_pass
# optional filter list for auto discovery logic
- # some settings only make sense if you don't specify a fixed UDP port
+ # some settings only make sense if you don't specify a fixed UDP port
filters:
# list of host candidates from auto discovery to be sent
# including candidates from the `listen` option
# use `candidates: []` to remove all auto discovery candidates
candidates: [ 192.168.1.123 ]
-
+
# enable localhost candidates
loopback: true
@@ -84,7 +84,7 @@ Don't know why, but you can disable TCP port and leave only random UDP ports - `
## Config filters
-**Importan!** By default go2rtc exclude all Docker-like candidates (`172.16.0.0/12`). This can not be disabled.
+**Important!** By default go2rtc exclude all Docker-like candidates (`172.16.0.0/12`). This can not be disabled.
Filters allow you to exclude unnecessary candidates. Extra candidates don't make your connection worse or better. But the wrong filter settings can break everything. Skip this setting if you don't understand it.
@@ -106,7 +106,7 @@ webrtc:
candidates: [ 192.168.1.2:8555 ] # add manual host candidate (use docker port forwarding)
```
-## Userful links
+## Useful links
- https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html
- https://www.ietf.org/id/draft-murillo-whep-01.html
diff --git a/internal/xiaomi/xiaomi.go b/internal/xiaomi/xiaomi.go
index 1801fa86..a5b23420 100644
--- a/internal/xiaomi/xiaomi.go
+++ b/internal/xiaomi/xiaomi.go
@@ -16,6 +16,7 @@ import (
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/xiaomi"
"github.com/AlexxIT/go2rtc/pkg/xiaomi/crypto"
+ "github.com/rs/zerolog"
)
func Init() {
@@ -26,7 +27,7 @@ func Init() {
tokens = v.Cfg
- log := app.GetLogger("xiaomi")
+ log = app.GetLogger("xiaomi")
streams.HandleFunc("xiaomi", func(rawURL string) (core.Producer, error) {
u, err := url.Parse(rawURL)
@@ -49,6 +50,8 @@ func Init() {
api.HandleFunc("api/xiaomi", apiXiaomi)
}
+var log zerolog.Logger
+
var tokens map[string]string
var clouds map[string]*xiaomi.Cloud
var cloudsMu sync.Mutex
@@ -250,6 +253,8 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) {
List []*Device `json:"list"`
}
+ log.Trace().Str("user", user).Msgf("[xiaomi] devices list: %s", res)
+
if err = json.Unmarshal(res, &v); err != nil {
return err
}
diff --git a/main.go b/main.go
index def7ee35..ca70e84d 100644
--- a/main.go
+++ b/main.go
@@ -52,7 +52,7 @@ import (
func main() {
// version will be set later from -buildvcs info, this used only as fallback
- app.Version = "1.9.13"
+ app.Version = "1.9.14"
type module struct {
name string
diff --git a/pkg/core/helpers.go b/pkg/core/helpers.go
index 52b969a7..45bbd0d5 100644
--- a/pkg/core/helpers.go
+++ b/pkg/core/helpers.go
@@ -2,7 +2,6 @@ package core
import (
"crypto/rand"
- "regexp"
"runtime"
"strconv"
"strings"
@@ -93,14 +92,3 @@ func Caller() string {
_, file, line, _ := runtime.Caller(1)
return file + ":" + strconv.Itoa(line)
}
-
-const (
- unreserved = `A-Za-z0-9-._~`
- subdelims = `!$&'()*+,;=`
- userinfo = unreserved + subdelims + `%:`
-)
-
-func StripUserinfo(s string) string {
- sanitizer := regexp.MustCompile(`://[` + userinfo + `]+@`)
- return sanitizer.ReplaceAllString(s, `://***@`)
-}
diff --git a/pkg/creds/secrets.go b/pkg/creds/secrets.go
index a9a0094e..95ab4828 100644
--- a/pkg/creds/secrets.go
+++ b/pkg/creds/secrets.go
@@ -3,6 +3,7 @@ package creds
import (
"io"
"net/http"
+ "regexp"
"slices"
"strings"
"sync"
@@ -27,6 +28,7 @@ func AddSecret(value string) {
var secrets []string
var secretsMu sync.Mutex
var secretsReplacer *strings.Replacer
+var userinfoRegexp *regexp.Regexp
func getReplacer() *strings.Replacer {
secretsMu.Lock()
@@ -40,14 +42,33 @@ func getReplacer() *strings.Replacer {
secretsReplacer = strings.NewReplacer(oldnew...)
}
+ if userinfoRegexp == nil {
+ userinfoRegexp = regexp.MustCompile(`://[` + userinfo + `]+@`)
+ }
+
return secretsReplacer
}
+// Uniform Resource Identifier (URI)
+// https://datatracker.ietf.org/doc/html/rfc3986
+const (
+ unreserved = `A-Za-z0-9-._~`
+ subdelims = `!$&'()*+,;=`
+ userinfo = unreserved + subdelims + `%:`
+)
+
func SecretString(s string) string {
re := getReplacer()
+ s = userinfoRegexp.ReplaceAllString(s, `://***@`)
return re.Replace(s)
}
+func SecretWrite(w io.Writer, s string) (n int, err error) {
+ re := getReplacer()
+ s = userinfoRegexp.ReplaceAllString(s, `://***@`)
+ return re.WriteString(w, s)
+}
+
func SecretWriter(w io.Writer) io.Writer {
return &secretWriter{w}
}
@@ -57,27 +78,17 @@ type secretWriter struct {
}
func (s *secretWriter) Write(b []byte) (int, error) {
- re := getReplacer()
- return re.WriteString(s.w, string(b))
-}
-
-type secretResponse struct {
- w http.ResponseWriter
-}
-
-func (s *secretResponse) Header() http.Header {
- return s.w.Header()
-}
-
-func (s *secretResponse) Write(b []byte) (int, error) {
- re := getReplacer()
- return re.WriteString(s.w, string(b))
-}
-
-func (s *secretResponse) WriteHeader(statusCode int) {
- s.w.WriteHeader(statusCode)
+ return SecretWrite(s.w, string(b))
}
func SecretResponse(w http.ResponseWriter) http.ResponseWriter {
return &secretResponse{w}
}
+
+type secretResponse struct {
+ http.ResponseWriter
+}
+
+func (s *secretResponse) Write(b []byte) (int, error) {
+ return SecretWrite(s.ResponseWriter, string(b))
+}
diff --git a/pkg/onvif/envelope.go b/pkg/onvif/envelope.go
index f0e1b29c..76a41260 100644
--- a/pkg/onvif/envelope.go
+++ b/pkg/onvif/envelope.go
@@ -15,14 +15,9 @@ type Envelope struct {
}
const (
- prefix1 = `
-
-`
- prefix2 = `
-`
- suffix = `
-
-`
+ prefix1 = ``
+ prefix2 = ``
+ suffix = ``
)
func NewEnvelope() *Envelope {
@@ -54,8 +49,7 @@ func NewEnvelopeWithUser(user *url.Userinfo) *Envelope {
%s
-
-`,
+`,
user.Username(),
base64.StdEncoding.EncodeToString(h.Sum(nil)),
base64.StdEncoding.EncodeToString([]byte(nonce)),
diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go
index 54272798..fe3ba8b3 100644
--- a/pkg/onvif/server.go
+++ b/pkg/onvif/server.go
@@ -21,21 +21,24 @@ const (
DeviceGetScopes = "GetScopes"
DeviceGetServices = "GetServices"
DeviceGetSystemDateAndTime = "GetSystemDateAndTime"
+ DeviceSetSystemDateAndTime = "SetSystemDateAndTime"
DeviceSystemReboot = "SystemReboot"
)
const (
- MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
- MediaGetAudioSources = "GetAudioSources"
- MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
- MediaGetProfile = "GetProfile"
- MediaGetProfiles = "GetProfiles"
- MediaGetSnapshotUri = "GetSnapshotUri"
- MediaGetStreamUri = "GetStreamUri"
- MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
- MediaGetVideoSources = "GetVideoSources"
- MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration"
- MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
+ MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
+ MediaGetAudioSources = "GetAudioSources"
+ MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
+ MediaGetProfile = "GetProfile"
+ MediaGetProfiles = "GetProfiles"
+ MediaGetSnapshotUri = "GetSnapshotUri"
+ MediaGetStreamUri = "GetStreamUri"
+ MediaGetVideoEncoderConfiguration = "GetVideoEncoderConfiguration"
+ MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
+ MediaGetVideoEncoderConfigurationOptions = "GetVideoEncoderConfigurationOptions"
+ MediaGetVideoSources = "GetVideoSources"
+ MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration"
+ MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
)
func GetRequestAction(b []byte) string {
@@ -54,13 +57,13 @@ func GetRequestAction(b []byte) string {
func GetCapabilitiesResponse(host string) []byte {
e := NewEnvelope()
- e.Append(`
+ e.Appendf(`
- http://`, host, `/onvif/device_service
+ http://%s/onvif/device_service
- http://`, host, `/onvif/media_service
+ http://%s/onvif/media_service
false
false
@@ -68,24 +71,24 @@ func GetCapabilitiesResponse(host string) []byte {
-`)
+`, host, host)
return e.Bytes()
}
func GetServicesResponse(host string) []byte {
e := NewEnvelope()
- e.Append(`
+ e.Appendf(`
http://www.onvif.org/ver10/device/wsdl
- http://`, host, `/onvif/device_service
+ http://%s/onvif/device_service
25
http://www.onvif.org/ver10/media/wsdl
- http://`, host, `/onvif/media_service
+ http://%s/onvif/media_service
25
-`)
+`, host, host)
return e.Bytes()
}
@@ -120,30 +123,19 @@ func GetSystemDateAndTimeResponse() []byte {
func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte {
e := NewEnvelope()
- e.Append(`
- `, manuf, `
- `, model, `
- `, firmware, `
- `, serial, `
+ e.Appendf(`
+ %s
+ %s
+ %s
+ %s
1.00
-`)
- return e.Bytes()
-}
-
-func GetMediaServiceCapabilitiesResponse() []byte {
- e := NewEnvelope()
- e.Append(`
-
-
-
-`)
+`, manuf, model, firmware, serial)
return e.Bytes()
}
func GetProfilesResponse(names []string) []byte {
e := NewEnvelope()
- e.Append(`
-`)
+ e.Append(``)
for _, name := range names {
appendProfile(e, "Profiles", name)
}
@@ -153,38 +145,40 @@ func GetProfilesResponse(names []string) []byte {
func GetProfileResponse(name string) []byte {
e := NewEnvelope()
- e.Append(`
-`)
+ e.Append(``)
appendProfile(e, "Profile", name)
e.Append(``)
return e.Bytes()
}
func appendProfile(e *Envelope, tag, name string) {
- // empty `RateControl` important for UniFi Protect
- e.Append(`
- `, name, `
-
- VSC
- `, name, `
-
-
-
- VEC
- H264
- 19201080
-
-
-
-`)
+ // go2rtc name = ONVIF Profile Name = ONVIF Profile token
+ e.Appendf(``, tag, name)
+ e.Appendf(`%s`, name)
+ appendVideoSourceConfiguration(e, "VideoSourceConfiguration", name)
+ appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
+ e.Appendf(``, tag)
+}
+
+func GetVideoSourcesResponse(names []string) []byte {
+ // go2rtc name = ONVIF VideoSource token
+ e := NewEnvelope()
+ e.Append(``)
+ for _, name := range names {
+ e.Appendf(`
+ 30.000000
+ 19201080
+`, name)
+ }
+ e.Append(``)
+ return e.Bytes()
}
func GetVideoSourceConfigurationsResponse(names []string) []byte {
e := NewEnvelope()
- e.Append(`
-`)
+ e.Append(``)
for _, name := range names {
- appendProfile(e, "Configurations", name)
+ appendVideoSourceConfiguration(e, "Configurations", name)
}
e.Append(``)
return e.Bytes()
@@ -192,46 +186,60 @@ func GetVideoSourceConfigurationsResponse(names []string) []byte {
func GetVideoSourceConfigurationResponse(name string) []byte {
e := NewEnvelope()
- e.Append(`
-`)
+ e.Append(``)
appendVideoSourceConfiguration(e, "Configuration", name)
e.Append(``)
return e.Bytes()
}
func appendVideoSourceConfiguration(e *Envelope, tag, name string) {
- e.Append(`
+ // go2rtc name = ONVIF VideoSourceConfiguration token
+ e.Appendf(`
VSC
- `, name, `
+ %s
-
-`)
+`, tag, name, name, tag)
}
-func GetVideoSourcesResponse(names []string) []byte {
+func GetVideoEncoderConfigurationsResponse() []byte {
e := NewEnvelope()
- e.Append(`
-`)
- for _, name := range names {
- e.Append(`
- 30.000000
- 19201080
-
-`)
- }
- e.Append(``)
+ e.Append(``)
+ appendVideoEncoderConfiguration(e, "VideoEncoderConfigurations")
+ e.Append(``)
return e.Bytes()
}
+func GetVideoEncoderConfigurationResponse() []byte {
+ e := NewEnvelope()
+ e.Append(``)
+ appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
+ e.Append(``)
+ return e.Bytes()
+}
+
+func appendVideoEncoderConfiguration(e *Envelope, tag string) {
+ // empty `RateControl` important for UniFi Protect
+ e.Appendf(`
+ VEC
+ 1
+ H264
+ 19201080
+ 0
+ 3018192
+ 10Main
+ PT10S
+ `, tag, tag)
+}
+
func GetStreamUriResponse(uri string) []byte {
e := NewEnvelope()
- e.Append(``, uri, ``)
+ e.Appendf(`%s`, uri)
return e.Bytes()
}
func GetSnapshotUriResponse(uri string) []byte {
e := NewEnvelope()
- e.Append(``, uri, ``)
+ e.Appendf(`%s`, uri)
return e.Bytes()
}
@@ -239,6 +247,10 @@ func StaticResponse(operation string) []byte {
switch operation {
case DeviceGetSystemDateAndTime:
return GetSystemDateAndTimeResponse()
+ case MediaGetVideoEncoderConfiguration:
+ return GetVideoEncoderConfigurationResponse()
+ case MediaGetVideoEncoderConfigurations:
+ return GetVideoEncoderConfigurationsResponse()
}
e := NewEnvelope()
@@ -247,11 +259,18 @@ func StaticResponse(operation string) []byte {
}
var responses = map[string]string{
+ ServiceGetServiceCapabilities: `
+
+
+
+`,
+
DeviceGetDiscoveryMode: `Discoverable`,
DeviceGetDNS: ``,
DeviceGetHostname: ``,
DeviceGetNetworkDefaultGateway: ``,
DeviceGetNTP: ``,
+ DeviceSetSystemDateAndTime: ``,
DeviceSystemReboot: `OK`,
DeviceGetNetworkInterfaces: ``,
@@ -263,16 +282,20 @@ var responses = map[string]string{
Fixedonvif://www.onvif.org/type/Network_Video_Transmitter
`,
- MediaGetVideoEncoderConfigurations: `
-
- VEC
- H264
- 19201080
-
-
-`,
-
MediaGetAudioEncoderConfigurations: ``,
MediaGetAudioSources: ``,
MediaGetAudioSourceConfigurations: ``,
+
+ MediaGetVideoEncoderConfigurationOptions: `
+
+ 16
+
+ 19201080
+ 0100
+ 130
+ 1100
+ Main
+
+
+`,
}
diff --git a/pkg/tutk/session16.go b/pkg/tutk/session16.go
index 47110dd3..5344bdbc 100644
--- a/pkg/tutk/session16.go
+++ b/pkg/tutk/session16.go
@@ -50,7 +50,8 @@ type Session16 struct {
seqSendCmd1 uint16
seqSendAud uint16
- waitSeq uint16
+ waitFSeq uint16
+ waitCSeq uint16
waitSize int
waitData []byte
}
@@ -183,7 +184,7 @@ func (s *Session16) SessionRead(chID byte, cmd []byte) int {
}
// 0 01030800 command + version
- // 4 00000000 frame num
+ // 4 00000000 frame seq
// 8 ac880100 total size
// 12 6200 chunk seq
// 14 2000 tail (pkt header) size
@@ -197,25 +198,27 @@ func (s *Session16) SessionRead(chID byte, cmd []byte) int {
switch cmd[1] {
case 0x03:
- seq := binary.LittleEndian.Uint16(cmd[12:])
- if seq != s.waitSeq {
- s.waitSeq = 0
- return msgMediaLost
- }
- if seq == 0 {
+ frameSeq := binary.LittleEndian.Uint16(cmd[4:])
+ chunkSeq := binary.LittleEndian.Uint16(cmd[12:])
+ if chunkSeq == 0 {
+ s.waitFSeq = frameSeq
+ s.waitCSeq = 0
s.waitData = s.waitData[:0]
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
hdrSize := binary.LittleEndian.Uint16(cmd[14:])
s.waitSize = int(hdrSize) + int(payloadSize)
+ } else if frameSeq != s.waitFSeq || chunkSeq != s.waitCSeq {
+ s.waitCSeq = 0
+ return msgMediaLost
}
s.waitData = append(s.waitData, cmd[24:]...)
if n := len(s.waitData); n < s.waitSize {
- s.waitSeq++
+ s.waitCSeq++
return msgMediaChunk
}
- s.waitSeq = 0
+ s.waitCSeq = 0
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
packetData[0] = bytes.Clone(s.waitData[payloadSize:])
diff --git a/pkg/tutk/session25.go b/pkg/tutk/session25.go
index fd1f16b4..dc79d3a7 100644
--- a/pkg/tutk/session25.go
+++ b/pkg/tutk/session25.go
@@ -171,15 +171,15 @@ func (s *Session25) handleChunk(cmd []byte, checkSeq bool) int {
// "0x20 chunk seq for first chunk if only one chunk".
if binary.LittleEndian.Uint16(cmd2[6:]) == 0 || binary.LittleEndian.Uint16(cmd2[4:]) == 1 {
s.waitData = s.waitData[:0]
- s.waitSeq = seq
- } else if seq != s.waitSeq {
+ s.waitCSeq = seq
+ } else if seq != s.waitCSeq {
return msgMediaLost
}
s.waitData = append(s.waitData, cmd2[20:]...)
if flags&0b0001 == 0 {
- s.waitSeq++
+ s.waitCSeq++
return msgMediaChunk
}
diff --git a/pkg/xiaomi/legacy/client.go b/pkg/xiaomi/legacy/client.go
index 242fda3d..57bd0a08 100644
--- a/pkg/xiaomi/legacy/client.go
+++ b/pkg/xiaomi/legacy/client.go
@@ -33,10 +33,10 @@ func NewClient(rawURL string) (*Client, error) {
`{"public_key":"%s","sign":"%s","account":"admin"}`,
query.Get("client_public"), query.Get("sign"),
)
- } else if model == ModelXiaobai {
+ } else if model == ModelMijia || model == ModelXiaobai {
username = "admin"
password = query.Get("password")
- } else if model == ModelXiaofang {
+ } else if model == ModelDafang || model == ModelXiaofang {
username = "admin"
} else {
return nil, fmt.Errorf("xiaomi: unsupported model: %s", model)
@@ -47,7 +47,7 @@ func NewClient(rawURL string) (*Client, error) {
return nil, err
}
- if model == ModelXiaofang {
+ if model == ModelDafang || model == ModelXiaofang {
err = xiaofangLogin(conn, query.Get("password"))
if err != nil {
_ = conn.Close()
@@ -104,27 +104,78 @@ func (c *Client) ReadPacket() (hdr, payload []byte, err error) {
return
}
if c.key != nil {
- switch hdr[0] {
- case tutk.CodecH264, tutk.CodecH265:
+ if c.model == ModelAqaraG2 && hdr[0] == tutk.CodecH265 {
payload, err = DecodeVideo(payload, c.key)
- case tutk.CodecAACLATM:
+ } else {
+ // ModelAqaraG2: audio AAC
+ // ModelIMILABA1: video HEVC, audio PCMA
payload, err = crypto.Decode(payload, c.key)
}
}
return
}
+const (
+ cmdVideoStart = 0x01ff
+ cmdVideoStop = 0x02ff
+ cmdAudioStart = 0x0300
+ cmdAudioStop = 0x0301
+ cmdStreamCtrlReq = 0x0320
+)
+
+func (c *Client) WriteCommandJSON(ctrlType uint32, format string, a ...any) error {
+ if len(a) > 0 {
+ format = fmt.Sprintf(format, a...)
+ }
+ return c.WriteCommand(ctrlType, []byte(format))
+}
+
func (c *Client) StartMedia(video, audio string) error {
switch c.model {
case ModelAqaraG2:
- return c.WriteCommand(0x01ff, []byte(`{}`))
+ // 0 - 1920x1080, 1 - 1280x720, 2 - ?
+ switch video {
+ case "", "fhd":
+ video = "0"
+ case "hd":
+ video = "1"
+ case "sd":
+ video = "2"
+ }
+
+ return errors.Join(
+ c.WriteCommandJSON(cmdVideoStart, `{}`),
+ c.WriteCommandJSON(0x0605, `{"channel":%s}`, video),
+ c.WriteCommandJSON(0x0704, `{}`), // don't know why
+ )
+
+ case ModelIMILABA1, ModelMijia:
+ // 0 - auto, 1 - low, 3 - hd
+ switch video {
+ case "", "hd":
+ video = "3"
+ case "sd":
+ video = "1" // 2 is also low quality
+ case "auto":
+ video = "0"
+ }
+
+ // quality after start
+ return errors.Join(
+ c.WriteCommandJSON(cmdAudioStart, `{}`),
+ c.WriteCommandJSON(cmdVideoStart, `{}`),
+ c.WriteCommandJSON(cmdStreamCtrlReq, `{"videoquality":%s}`, video),
+ )
case ModelXiaobai:
// 00030000 7b7d audio on
// 01030000 7b7d audio off
- if err := c.WriteCommand(0x0300, []byte(`{}`)); err != nil {
- return err
- }
+ // 20030000 0000000001000000 fhd (1920x1080)
+ // 20030000 0000000002000000 hd (1280x720)
+ // 20030000 0000000004000000 low (640x360)
+ // 20030000 00000000ff000000 auto (1920x1080)
+ // ff010000 7b7d video tart
+ // ff020000 7b7d video stop
var b byte
switch video {
@@ -137,19 +188,15 @@ func (c *Client) StartMedia(video, audio string) error {
case "auto":
b = 0xff
}
- // 20030000 0000000001000000 fhd (1920x1080)
- // 20030000 0000000002000000 hd (1280x720)
- // 20030000 0000000004000000 low (640x360)
- // 20030000 00000000ff000000 auto (1920x1080)
- if err := c.WriteCommand(0x0320, []byte{0, 0, 0, 0, b, 0, 0, 0}); err != nil {
- return err
- }
- // ff010000 7b7d video tart
- // ff020000 7b7d video stop
- return c.WriteCommand(0x01ff, []byte(`{}`))
+ // quality before start
+ return errors.Join(
+ c.WriteCommandJSON(cmdAudioStart, `{}`),
+ c.WriteCommand(cmdStreamCtrlReq, []byte{0, 0, 0, 0, b, 0, 0, 0}),
+ c.WriteCommandJSON(cmdVideoStart, `{}`),
+ )
- case ModelXiaofang:
+ case ModelDafang, ModelXiaofang:
// 00010000 4943414d 95010400000000000000000600000000000000d20400005a07 - 90k bitrate
// 00010000 4943414d 95010400000000000000000600000000000000d20400001e07 - 30k bitrate
//var b byte
@@ -163,15 +210,16 @@ func (c *Client) StartMedia(video, audio string) error {
//if err := c.WriteCommand(0x100, data); err != nil {
// return err
//}
+ return nil
}
- return nil
+ return fmt.Errorf("xiaomi: unsupported model: %s", c.model)
}
func (c *Client) StopMedia() error {
return errors.Join(
- c.WriteCommand(0x02ff, []byte(`{}`)),
- c.WriteCommand(0x02ff, make([]byte, 8)),
+ c.WriteCommandJSON(cmdVideoStop, `{}`),
+ c.WriteCommand(cmdVideoStop, make([]byte, 8)),
)
}
@@ -186,8 +234,8 @@ func DecodeVideo(data, key []byte) ([]byte, error) {
}
nonce8 := data[:8]
- i1 := binary.LittleEndian.Uint16(data[9:])
- i2 := binary.LittleEndian.Uint16(data[13:])
+ i1 := binary.LittleEndian.Uint32(data[9:])
+ i2 := binary.LittleEndian.Uint32(data[13:])
data = data[17:]
src := data[i1 : i1+i2]
@@ -204,14 +252,19 @@ func DecodeVideo(data, key []byte) ([]byte, error) {
const (
ModelAqaraG2 = "lumi.camera.gwagl01"
+ ModelIMILABA1 = "chuangmi.camera.ipc019e"
ModelLoockV1 = "loock.cateye.v01"
ModelXiaobai = "chuangmi.camera.xiaobai"
ModelXiaofang = "isa.camera.isc5"
+ // ModelMijia support miss format for new fw and legacy format for old fw
+ ModelMijia = "chuangmi.camera.v2"
+ // ModelDafang support miss format for new fw and legacy format for old fw
+ ModelDafang = "isa.camera.df3"
)
func Supported(model string) bool {
switch model {
- case ModelAqaraG2, ModelLoockV1, ModelXiaobai, ModelXiaofang:
+ case ModelAqaraG2, ModelIMILABA1, ModelLoockV1, ModelXiaobai, ModelXiaofang:
return true
}
return false
diff --git a/pkg/xiaomi/miss/client.go b/pkg/xiaomi/miss/client.go
index 6eaa06cf..95c3f9f3 100644
--- a/pkg/xiaomi/miss/client.go
+++ b/pkg/xiaomi/miss/client.go
@@ -139,11 +139,14 @@ const (
ModelLoockV2 = "loock.cateye.v02"
ModelC200 = "chuangmi.camera.046c04"
ModelC300 = "chuangmi.camera.72ac1"
+ // ModelXiaofang looks like it has the same firmware as the ModelDafang.
+ // There is also an older model "isa.camera.isc5" that only works with the legacy protocol.
+ ModelXiaofang = "isa.camera.isc5c1"
)
func (c *Client) StartMedia(channel, quality, audio string) error {
switch c.model {
- case ModelDafang:
+ case ModelDafang, ModelXiaofang:
var q, a byte
if quality == "sd" {
q = 1 // 0 - hd, 1 - sd, default - hd
@@ -181,9 +184,10 @@ func (c *Client) StartMedia(channel, quality, audio string) error {
}
data := binary.BigEndian.AppendUint32(nil, cmdVideoStart)
- if channel == "" {
+ switch channel {
+ case "", "0":
data = fmt.Appendf(data, `{"videoquality":%s,"enableaudio":%s}`, quality, audio)
- } else {
+ default:
data = fmt.Appendf(data, `{"videoquality":-1,"videoquality2":%s,"enableaudio":%s}`, quality, audio)
}
return c.WriteCommand(data)
@@ -207,7 +211,7 @@ func (c *Client) StartSpeaker() error {
// SpeakerCodec if the camera model has a non-standard two-way codec.
func (c *Client) SpeakerCodec() uint32 {
switch c.model {
- case ModelDafang, "isa.camera.hlc6":
+ case ModelDafang, ModelXiaofang, "isa.camera.hlc6":
return codecPCM
case "chuangmi.camera.72ac1":
return codecOPUS
@@ -240,7 +244,7 @@ func (c *Client) ReadPacket() (*Packet, error) {
}
switch c.model {
- case ModelDafang, ModelLoockV2:
+ case ModelDafang, ModelXiaofang, ModelLoockV2:
// Dafang has ts in sec
// LoockV2 has ts in msec for video, but zero ts for audio
pkt.Timestamp = uint64(time.Now().UnixMilli())
diff --git a/pkg/xiaomi/miss/cs2/conn.go b/pkg/xiaomi/miss/cs2/conn.go
index 1bea07c6..2c1b395e 100644
--- a/pkg/xiaomi/miss/cs2/conn.go
+++ b/pkg/xiaomi/miss/cs2/conn.go
@@ -57,7 +57,8 @@ const (
msgDrwAck = 0xD1
msgPing = 0xE0
msgPong = 0xE1
- msgClose = 0xF1
+ msgClose = 0xF0
+ msgCloseAck = 0xF1
)
func handshake(host, transport string) (net.Conn, error) {
@@ -162,7 +163,7 @@ func (c *Conn) worker() {
case msgPing:
_, _ = c.Conn.Write([]byte{magic, msgPong, 0, 0})
- case msgPong, msgP2PRdyUDP, msgP2PRdyTCP, msgClose: // skip it
+ case msgPong, msgP2PRdyUDP, msgP2PRdyTCP, msgClose, msgCloseAck: // skip it
case msgDrwAck: // only for UDP
if c.cmdAck != nil {
c.cmdAck()