Merge branch 'AlexxIT:master' into documentation-site
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
Ultimate camera streaming application with support for RTSP, WebRTC, HomeKit, FFmpeg, RTMP, etc.
|
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-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)
|
- 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)
|
- 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)
|
- play audio files and live streams on some cameras with [speaker](#stream-to-camera)
|
||||||
- multi-source two-way [codecs negotiation](#codecs-negotiation)
|
- multi-source two-way [codecs negotiation](#codecs-negotiation)
|
||||||
- mixing tracks from different sources to single stream
|
- mixing tracks from different sources to single stream
|
||||||
- auto-match client-supported codecs
|
- auto-match client-supported codecs
|
||||||
- [two-way audio](#two-way-audio) for some cameras
|
- [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)
|
- 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
|
**Supported Formats** - describes the communication API: authorization, encryption, command set, structure of media packets
|
||||||
@@ -56,70 +56,70 @@ Ultimate camera streaming application with support for RTSP, WebRTC, HomeKit, FF
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
* [Fast start](#fast-start)
|
- [Fast start](#fast-start)
|
||||||
* [go2rtc: Binary](#go2rtc-binary)
|
- [go2rtc: Binary](#go2rtc-binary)
|
||||||
* [go2rtc: Docker](#go2rtc-docker)
|
- [go2rtc: Docker](#go2rtc-docker)
|
||||||
* [go2rtc: Home Assistant Add-on](#go2rtc-home-assistant-add-on)
|
- [go2rtc: Home Assistant add-on](#go2rtc-home-assistant-add-on)
|
||||||
* [go2rtc: Home Assistant Integration](#go2rtc-home-assistant-integration)
|
- [go2rtc: Home Assistant integration](#go2rtc-home-assistant-integration)
|
||||||
* [go2rtc: Dev version](#go2rtc-dev-version)
|
- [go2rtc: Dev version](#go2rtc-dev-version)
|
||||||
* [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
* [Module: Streams](#module-streams)
|
- [Module: Streams](#module-streams)
|
||||||
* [Two way audio](#two-way-audio)
|
- [Two-way audio](#two-way-audio)
|
||||||
* [Source: RTSP](#source-rtsp)
|
- [Source: RTSP](#source-rtsp)
|
||||||
* [Source: RTMP](#source-rtmp)
|
- [Source: RTMP](#source-rtmp)
|
||||||
* [Source: HTTP](#source-http)
|
- [Source: HTTP](#source-http)
|
||||||
* [Source: ONVIF](#source-onvif)
|
- [Source: ONVIF](#source-onvif)
|
||||||
* [Source: FFmpeg](#source-ffmpeg)
|
- [Source: FFmpeg](#source-ffmpeg)
|
||||||
* [Source: FFmpeg Device](#source-ffmpeg-device)
|
- [Source: FFmpeg Device](#source-ffmpeg-device)
|
||||||
* [Source: Exec](#source-exec)
|
- [Source: Exec](#source-exec)
|
||||||
* [Source: Echo](#source-echo)
|
- [Source: Echo](#source-echo)
|
||||||
* [Source: Expr](#source-expr)
|
- [Source: Expr](#source-expr)
|
||||||
* [Source: HomeKit](#source-homekit)
|
- [Source: HomeKit](#source-homekit)
|
||||||
* [Source: Bubble](#source-bubble)
|
- [Source: Bubble](#source-bubble)
|
||||||
* [Source: DVRIP](#source-dvrip)
|
- [Source: DVRIP](#source-dvrip)
|
||||||
* [Source: Tapo](#source-tapo)
|
- [Source: Tapo](#source-tapo)
|
||||||
* [Source: Kasa](#source-kasa)
|
- [Source: Kasa](#source-kasa)
|
||||||
* [Source: Multitrans](#source-multitrans)
|
- [Source: Multitrans](#source-multitrans)
|
||||||
* [Source: Tuya](#source-tuya)
|
- [Source: Tuya](#source-tuya)
|
||||||
* [Source: Xiaomi](#source-xiaomi)
|
- [Source: Xiaomi](#source-xiaomi)
|
||||||
* [Source: Wyze](#source-wyze)
|
- [Source: Wyze](#source-wyze)
|
||||||
* [Source: GoPro](#source-gopro)
|
- [Source: GoPro](#source-gopro)
|
||||||
* [Source: Ivideon](#source-ivideon)
|
- [Source: Ivideon](#source-ivideon)
|
||||||
* [Source: Hass](#source-hass)
|
- [Source: Hass](#source-hass)
|
||||||
* [Source: ISAPI](#source-isapi)
|
- [Source: ISAPI](#source-isapi)
|
||||||
* [Source: Nest](#source-nest)
|
- [Source: Nest](#source-nest)
|
||||||
* [Source: Ring](#source-ring)
|
- [Source: Ring](#source-ring)
|
||||||
* [Source: Roborock](#source-roborock)
|
- [Source: Roborock](#source-roborock)
|
||||||
* [Source: Doorbird](#source-doorbird)
|
- [Source: Doorbird](#source-doorbird)
|
||||||
* [Source: WebRTC](#source-webrtc)
|
- [Source: WebRTC](#source-webrtc)
|
||||||
* [Source: WebTorrent](#source-webtorrent)
|
- [Source: WebTorrent](#source-webtorrent)
|
||||||
* [Incoming sources](#incoming-sources)
|
- [Incoming sources](#incoming-sources)
|
||||||
* [Stream to camera](#stream-to-camera)
|
- [Stream to camera](#stream-to-camera)
|
||||||
* [Publish stream](#publish-stream)
|
- [Publish stream](#publish-stream)
|
||||||
* [Preload stream](#preload-stream)
|
- [Preload stream](#preload-stream)
|
||||||
* [Module: API](#module-api)
|
- [Module: API](#module-api)
|
||||||
* [Module: RTSP](#module-rtsp)
|
- [Module: RTSP](#module-rtsp)
|
||||||
* [Module: RTMP](#module-rtmp)
|
- [Module: RTMP](#module-rtmp)
|
||||||
* [Module: WebRTC](#module-webrtc)
|
- [Module: WebRTC](#module-webrtc)
|
||||||
* [Module: HomeKit](#module-homekit)
|
- [Module: HomeKit](#module-homekit)
|
||||||
* [Module: WebTorrent](#module-webtorrent)
|
- [Module: WebTorrent](#module-webtorrent)
|
||||||
* [Module: ngrok](#module-ngrok)
|
- [Module: ngrok](#module-ngrok)
|
||||||
* [Module: Hass](#module-hass)
|
- [Module: Hass](#module-hass)
|
||||||
* [Module: MP4](#module-mp4)
|
- [Module: MP4](#module-mp4)
|
||||||
* [Module: HLS](#module-hls)
|
- [Module: HLS](#module-hls)
|
||||||
* [Module: MJPEG](#module-mjpeg)
|
- [Module: MJPEG](#module-mjpeg)
|
||||||
* [Module: Log](#module-log)
|
- [Module: Log](#module-log)
|
||||||
* [Security](#security)
|
- [Security](#security)
|
||||||
* [Codecs filters](#codecs-filters)
|
- [Codecs filters](#codecs-filters)
|
||||||
* [Codecs madness](#codecs-madness)
|
- [Codecs madness](#codecs-madness)
|
||||||
* [Codecs negotiation](#codecs-negotiation)
|
- [Codecs negotiation](#codecs-negotiation)
|
||||||
* [Projects using go2rtc](#projects-using-go2rtc)
|
- [Projects using go2rtc](#projects-using-go2rtc)
|
||||||
* [Camera experience](#cameras-experience)
|
- [Camera experience](#camera-experience)
|
||||||
* [TIPS](#tips)
|
- [Tips](#tips)
|
||||||
|
|
||||||
# Fast start
|
# 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/`
|
2. Open web interface: `http://localhost:1984/`
|
||||||
|
|
||||||
**Optionally:**
|
**Optionally:**
|
||||||
@@ -156,11 +156,11 @@ PS. The application is compiled with the latest versions of the Go language for
|
|||||||
|
|
||||||
## go2rtc: Docker
|
## 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:
|
1. Install Add-On:
|
||||||
- Settings > Add-ons > Plus > Repositories > Add `https://github.com/AlexxIT/hassio-addons`
|
- 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)
|
- Binary: [latest nightly release](https://nightly.link/AlexxIT/go2rtc/workflows/build/master)
|
||||||
- Docker: `alexxit/go2rtc:master` or `alexxit/go2rtc:master-hardware` versions
|
- 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
|
# Configuration
|
||||||
|
|
||||||
@@ -222,20 +222,20 @@ Available source types:
|
|||||||
- [bubble](#source-bubble) - streaming from ESeeCloud/dvr163 NVR
|
- [bubble](#source-bubble) - streaming from ESeeCloud/dvr163 NVR
|
||||||
- [dvrip](#source-dvrip) - streaming from DVR-IP NVR
|
- [dvrip](#source-dvrip) - streaming from DVR-IP NVR
|
||||||
- [eseecloud](#source-eseecloud) - streaming from ESeeCloud/dvr163 NVR
|
- [eseecloud](#source-eseecloud) - streaming from ESeeCloud/dvr163 NVR
|
||||||
- [tapo](#source-tapo) - TP-Link Tapo 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
|
- [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
|
- [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
|
- [xiaomi](#source-xiaomi) - Xiaomi cameras with [two-way audio](#two-way-audio) support
|
||||||
- [kasa](#source-tapo) - TP-Link Kasa cameras
|
- [kasa](#source-tapo) - TP-Link Kasa cameras
|
||||||
- [gopro](#source-gopro) - GoPro cameras
|
- [gopro](#source-gopro) - GoPro cameras
|
||||||
- [ivideon](#source-ivideon) - public cameras from [Ivideon](https://tv.ivideon.com/) service
|
- [ivideon](#source-ivideon) - public cameras from [Ivideon](https://tv.ivideon.com/) service
|
||||||
- [hass](#source-hass) - Home Assistant integration
|
- [hass](#source-hass) - Home Assistant integration
|
||||||
- [isapi](#source-isapi) - two-way audio for Hikvision (ISAPI) cameras
|
- [isapi](#source-isapi) - two-way audio for Hikvision (ISAPI) cameras
|
||||||
- [roborock](#source-roborock) - Roborock vacuums with 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
|
- [webrtc](#source-webrtc) - WebRTC/WHEP sources
|
||||||
- [webtorrent](#source-webtorrent) - WebTorrent source from another go2rtc
|
- [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)
|
Read more about [incoming sources](#incoming-sources)
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ streams:
|
|||||||
**Recommendations**
|
**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
|
- **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
|
- **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)
|
- **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)
|
- **TP-Link Tapo** users may skip login and password, because go2rtc support login [without them](https://drmnsamoliu.github.io/video.html)
|
||||||
@@ -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.
|
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
|
- FFmpeg preinstalled for **Docker** and **Home Assistant add-on** users
|
||||||
- **Hass Add-on** users can target files from [/media](https://www.home-assistant.io/more-info/local-media/setup-media/) folder
|
- **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:
|
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).
|
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).
|
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 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
|
- 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.
|
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:
|
Recommended settings for using HomeKit Camera with WebRTC, MSE, MP4, RTSP:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
streams:
|
streams:
|
||||||
aqara_g3:
|
aqara_g3:
|
||||||
- hass:Camera-Hub-G3-AB12
|
- 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)*
|
*[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/)
|
- 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!
|
- 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)*
|
*[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)*
|
*[read more](internal/tuya/README.md)*
|
||||||
|
|
||||||
@@ -674,7 +674,7 @@ Support import camera links from [Home Assistant](https://www.home-assistant.io/
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
hass:
|
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:
|
streams:
|
||||||
generic_camera: hass:Camera1 # Settings > Integrations > Integration Name
|
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:
|
streams:
|
||||||
# link to Home Assistant Supervised
|
# link to Home Assistant Supervised
|
||||||
hass-webrtc1: hass://supervisor?entity_id=camera.nest_doorbell
|
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...
|
hass-webrtc2: hass://192.168.1.123:8123?entity_id=camera.nest_doorbell&token=eyXYZ...
|
||||||
```
|
```
|
||||||
|
|
||||||
**RTSP Cameras**
|
**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
|
## Source: ISAPI
|
||||||
|
|
||||||
@@ -718,7 +718,7 @@ streams:
|
|||||||
|
|
||||||
Currently, only WebRTC cameras are supported.
|
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
|
```yaml
|
||||||
streams:
|
streams:
|
||||||
@@ -727,7 +727,7 @@ streams:
|
|||||||
|
|
||||||
## Source: Ring
|
## 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
|
```yaml
|
||||||
streams:
|
streams:
|
||||||
@@ -821,18 +821,25 @@ By default, go2rtc establishes a connection to the source when any client reques
|
|||||||
**Examples**
|
**Examples**
|
||||||
|
|
||||||
- RTSP with any codec
|
- RTSP with any codec
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ffmpeg -re -i BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp rtsp://localhost:8554/camera1
|
ffmpeg -re -i BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp rtsp://localhost:8554/camera1
|
||||||
```
|
```
|
||||||
|
|
||||||
- HTTP-MJPEG with MJPEG codec
|
- HTTP-MJPEG with MJPEG codec
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ffmpeg -re -i BigBuckBunny.mp4 -c mjpeg -f mpjpeg http://localhost:1984/api/stream.mjpeg?dst=camera1
|
ffmpeg -re -i BigBuckBunny.mp4 -c mjpeg -f mpjpeg http://localhost:1984/api/stream.mjpeg?dst=camera1
|
||||||
```
|
```
|
||||||
|
|
||||||
- HTTP-FLV with H264, AAC codecs
|
- HTTP-FLV with H264, AAC codecs
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ffmpeg -re -i BigBuckBunny.mp4 -c copy -f flv http://localhost:1984/api/stream.flv?dst=camera1
|
ffmpeg -re -i BigBuckBunny.mp4 -c copy -f flv http://localhost:1984/api/stream.flv?dst=camera1
|
||||||
```
|
```
|
||||||
|
|
||||||
- MPEG-TS with H264 codec
|
- MPEG-TS with H264 codec
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ffmpeg -re -i BigBuckBunny.mp4 -c copy -f mpegts http://localhost:1984/api/stream.ts?dst=camera1
|
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):
|
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
|
## Stream to camera
|
||||||
|
|
||||||
@@ -865,7 +872,7 @@ go2rtc supports playing audio files (ex. music or [TTS](https://www.home-assista
|
|||||||
|
|
||||||
API example:
|
API example:
|
||||||
|
|
||||||
```
|
```text
|
||||||
POST http://localhost:1984/api/streams?dst=camera1&src=ffmpeg:http://example.com/song.mp3#audio=pcma#input=file
|
POST http://localhost:1984/api/streams?dst=camera1&src=ffmpeg:http://example.com/song.mp3#audio=pcma#input=file
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -891,7 +898,7 @@ You can publish any stream to streaming services (YouTube, Telegram, etc.) via R
|
|||||||
|
|
||||||
You can use the API:
|
You can use the API:
|
||||||
|
|
||||||
```
|
```text
|
||||||
POST http://localhost:1984/api/streams?src=camera1&dst=rtmps://...
|
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 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
|
```yaml
|
||||||
rtsp:
|
rtsp:
|
||||||
@@ -1151,19 +1158,17 @@ webtorrent:
|
|||||||
src: rtsp-dahua1 # stream name from streams section
|
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
|
## 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)*
|
*[read more](internal/ngrok/README.md)*
|
||||||
|
|
||||||
## Module: Hass
|
## 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.
|
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.
|
||||||
|
|
||||||
But go2rtc is also compatible and can be used with the [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) built-in integration.
|
|
||||||
|
|
||||||
You have several options on how to add a camera to Home Assistant:
|
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/)
|
2. Camera [any source](#module-streams) => [go2rtc config](#configuration) => [Generic Camera](https://www.home-assistant.io/integrations/generic/)
|
||||||
- Install any [go2rtc](#fast-start)
|
- Install any [go2rtc](#fast-start)
|
||||||
- Add your stream to [go2rtc config](#configuration)
|
- 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`
|
- 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`
|
||||||
- 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 > [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:
|
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.
|
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` => [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) => `Picture Entity Card` => Technology `WebRTC`, codecs: `H264/PCMU/PCMA/OPUS`, best 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 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.
|
|
||||||
- Install and add [WebRTC Camera](https://github.com/AlexxIT/WebRTC) custom integration
|
- 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:
|
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
|
"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
|
## Module: MP4
|
||||||
|
|
||||||
@@ -1280,7 +1280,7 @@ exec:
|
|||||||
allow_paths: [ffmpeg]
|
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:
|
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 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
|
- 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
|
- 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:
|
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,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=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
|
- `rtsp://192.168.1.123:8554/camera1?video&audio=all` - any video codec and all audio codecs as separate tracks
|
||||||
@@ -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:
|
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
|
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:
|
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
|
PCM/xxx => PCMA/8000 => WebRTC
|
||||||
PCMA/xxx => PCMA/8000 => WebRTC
|
PCMA/xxx => PCMA/8000 => WebRTC
|
||||||
PCMU/xxx => PCMU/8000 => WebRTC
|
PCMU/xxx => PCMU/8000 => WebRTC
|
||||||
@@ -1394,7 +1394,7 @@ PCMU/xxx => PCMU/8000 => WebRTC
|
|||||||
|
|
||||||
**Important**
|
**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.
|
- 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
|
# 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.
|
**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.
|
**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
|
- [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](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
|
- [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
|
- [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
|
- [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?
|
- [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?
|
- 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**
|
**Using apps for low RTSP delay**
|
||||||
|
|
||||||
|
|||||||
@@ -5,26 +5,27 @@ go 1.24.0
|
|||||||
require (
|
require (
|
||||||
github.com/asticode/go-astits v1.14.0
|
github.com/asticode/go-astits v1.14.0
|
||||||
github.com/eclipse/paho.mqtt.golang v1.5.1
|
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/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/miekg/dns v1.1.69
|
github.com/miekg/dns v1.1.70
|
||||||
github.com/pion/ice/v4 v4.1.0
|
github.com/pion/dtls/v3 v3.0.10
|
||||||
github.com/pion/interceptor v0.1.42
|
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/rtcp v1.2.16
|
||||||
github.com/pion/rtp v1.8.26
|
github.com/pion/rtp v1.10.0
|
||||||
github.com/pion/sdp/v3 v3.0.16
|
github.com/pion/sdp/v3 v3.0.17
|
||||||
github.com/pion/srtp/v3 v3.0.9
|
github.com/pion/srtp/v3 v3.0.10
|
||||||
github.com/pion/stun/v3 v3.0.2
|
github.com/pion/stun/v3 v3.1.1
|
||||||
github.com/pion/webrtc/v4 v4.1.8
|
github.com/pion/webrtc/v4 v4.2.3
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
|
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
|
||||||
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f
|
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9
|
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.47.0
|
||||||
golang.org/x/net v0.48.0
|
golang.org/x/net v0.49.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,18 +34,19 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/pion/datachannel v1.5.10 // indirect
|
github.com/pion/datachannel v1.6.0 // indirect
|
||||||
github.com/pion/dtls/v3 v3.0.9 // indirect
|
|
||||||
github.com/pion/logging v0.2.4 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
github.com/pion/mdns/v2 v2.1.0 // indirect
|
github.com/pion/mdns/v2 v2.1.0 // indirect
|
||||||
github.com/pion/randutil v0.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/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/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // 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/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
golang.org/x/tools v0.40.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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/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 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.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/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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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/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 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
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 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||||
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
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 h1:4AijfFRm8mAjd1gfdlB1wzJF3fjjR/VPIpJgkEtvYmM=
|
||||||
github.com/pion/dtls/v3 v3.0.9/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os=
|
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 h1:YlxIii2bTPWyC08/4hdmtYq4srbrY0T9xcTsTjldGqU=
|
||||||
github.com/pion/ice/v4 v4.1.0/go.mod h1:5gPbzYxqenvn05k7zKPIZFuSAufolygiy6P1U9HzvZ4=
|
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 h1:0/4tvNtruXflBxLfApMVoMubUMik57VZ+94U0J7cmkQ=
|
||||||
github.com/pion/interceptor v0.1.42/go.mod h1:g6XYTChs9XyolIQFhRHOOUS+bGVGLRfgTCUzH29EfVU=
|
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 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
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=
|
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/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 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc=
|
||||||
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
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 h1:20R4OHAno4Vky3/iE4xccInAScAa83X6nWUfyc65MIs=
|
||||||
github.com/pion/sctp v1.8.41/go.mod h1:2wO6HBycUH7iCssuGyc2e9+0giXVW0pyCv3ZuL8LiyY=
|
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 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
|
||||||
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
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 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY=
|
||||||
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8=
|
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 h1:BJuGEN2oLrJisiNEJtUTJC4BGbzbfp37LizfqswblFU=
|
||||||
github.com/pion/stun/v3 v3.0.2/go.mod h1:JFJKfIWvt178MCF5H/YIgZ4VX3LYE77vca4b9HP60SA=
|
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 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
|
||||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
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 h1:jVNW0iR05AS94ysEtvzsrk3gKs9Zqxf6HmnsLfRvlzA=
|
||||||
github.com/pion/turn/v4 v4.1.3/go.mod h1:TD/eiBUf5f5LwXbCJa35T7dPtTpCHRJ9oJWmyPLVT3A=
|
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 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.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/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/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
|
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=
|
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 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
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 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
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 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
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 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
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=
|
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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
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.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 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
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 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
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 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -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)
|
log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b)
|
||||||
|
|
||||||
switch operation {
|
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.DeviceGetSystemDateAndTime, // important for Hass
|
||||||
|
onvif.DeviceSetSystemDateAndTime, // return just OK
|
||||||
onvif.DeviceGetDiscoveryMode,
|
onvif.DeviceGetDiscoveryMode,
|
||||||
onvif.DeviceGetDNS,
|
onvif.DeviceGetDNS,
|
||||||
onvif.DeviceGetHostname,
|
onvif.DeviceGetHostname,
|
||||||
@@ -83,8 +85,10 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
|
|||||||
onvif.DeviceGetNetworkProtocols,
|
onvif.DeviceGetNetworkProtocols,
|
||||||
onvif.DeviceGetNTP,
|
onvif.DeviceGetNTP,
|
||||||
onvif.DeviceGetScopes,
|
onvif.DeviceGetScopes,
|
||||||
|
onvif.MediaGetVideoEncoderConfiguration,
|
||||||
onvif.MediaGetVideoEncoderConfigurations,
|
onvif.MediaGetVideoEncoderConfigurations,
|
||||||
onvif.MediaGetAudioEncoderConfigurations,
|
onvif.MediaGetAudioEncoderConfigurations,
|
||||||
|
onvif.MediaGetVideoEncoderConfigurationOptions,
|
||||||
onvif.MediaGetAudioSources,
|
onvif.MediaGetAudioSources,
|
||||||
onvif.MediaGetAudioSourceConfigurations:
|
onvif.MediaGetAudioSourceConfigurations:
|
||||||
b = onvif.StaticResponse(operation)
|
b = onvif.StaticResponse(operation)
|
||||||
@@ -100,11 +104,6 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
|
|||||||
// important for Hass: SerialNumber (unique server ID)
|
// important for Hass: SerialNumber (unique server ID)
|
||||||
b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
|
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:
|
case onvif.DeviceSystemReboot:
|
||||||
b = onvif.StaticResponse(operation)
|
b = onvif.StaticResponse(operation)
|
||||||
|
|
||||||
@@ -134,8 +133,7 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
|
|||||||
case onvif.MediaGetStreamUri:
|
case onvif.MediaGetStreamUri:
|
||||||
host, _, err := net.SplitHostPort(r.Host)
|
host, _, err := net.SplitHostPort(r.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
host = r.Host // in case of Host without port
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken")
|
uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken")
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/xiaomi"
|
"github.com/AlexxIT/go2rtc/pkg/xiaomi"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/xiaomi/crypto"
|
"github.com/AlexxIT/go2rtc/pkg/xiaomi/crypto"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
@@ -26,7 +27,7 @@ func Init() {
|
|||||||
|
|
||||||
tokens = v.Cfg
|
tokens = v.Cfg
|
||||||
|
|
||||||
log := app.GetLogger("xiaomi")
|
log = app.GetLogger("xiaomi")
|
||||||
|
|
||||||
streams.HandleFunc("xiaomi", func(rawURL string) (core.Producer, error) {
|
streams.HandleFunc("xiaomi", func(rawURL string) (core.Producer, error) {
|
||||||
u, err := url.Parse(rawURL)
|
u, err := url.Parse(rawURL)
|
||||||
@@ -49,6 +50,8 @@ func Init() {
|
|||||||
api.HandleFunc("api/xiaomi", apiXiaomi)
|
api.HandleFunc("api/xiaomi", apiXiaomi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var log zerolog.Logger
|
||||||
|
|
||||||
var tokens map[string]string
|
var tokens map[string]string
|
||||||
var clouds map[string]*xiaomi.Cloud
|
var clouds map[string]*xiaomi.Cloud
|
||||||
var cloudsMu sync.Mutex
|
var cloudsMu sync.Mutex
|
||||||
@@ -250,6 +253,8 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) {
|
|||||||
List []*Device `json:"list"`
|
List []*Device `json:"list"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Trace().Str("user", user).Msgf("[xiaomi] devices list: %s", res)
|
||||||
|
|
||||||
if err = json.Unmarshal(res, &v); err != nil {
|
if err = json.Unmarshal(res, &v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// version will be set later from -buildvcs info, this used only as fallback
|
// 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 {
|
type module struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
+4
-10
@@ -15,14 +15,9 @@ type Envelope struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
prefix1 = `<?xml version="1.0" encoding="utf-8"?>
|
prefix1 = `<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl">`
|
||||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl">
|
prefix2 = `<s:Body>`
|
||||||
`
|
suffix = `</s:Body></s:Envelope>`
|
||||||
prefix2 = `<s:Body>
|
|
||||||
`
|
|
||||||
suffix = `
|
|
||||||
</s:Body>
|
|
||||||
</s:Envelope>`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEnvelope() *Envelope {
|
func NewEnvelope() *Envelope {
|
||||||
@@ -54,8 +49,7 @@ func NewEnvelopeWithUser(user *url.Userinfo) *Envelope {
|
|||||||
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">%s</wsu:Created>
|
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">%s</wsu:Created>
|
||||||
</wsse:UsernameToken>
|
</wsse:UsernameToken>
|
||||||
</wsse:Security>
|
</wsse:Security>
|
||||||
</s:Header>
|
</s:Header>`,
|
||||||
`,
|
|
||||||
user.Username(),
|
user.Username(),
|
||||||
base64.StdEncoding.EncodeToString(h.Sum(nil)),
|
base64.StdEncoding.EncodeToString(h.Sum(nil)),
|
||||||
base64.StdEncoding.EncodeToString([]byte(nonce)),
|
base64.StdEncoding.EncodeToString([]byte(nonce)),
|
||||||
|
|||||||
+109
-86
@@ -21,21 +21,24 @@ const (
|
|||||||
DeviceGetScopes = "GetScopes"
|
DeviceGetScopes = "GetScopes"
|
||||||
DeviceGetServices = "GetServices"
|
DeviceGetServices = "GetServices"
|
||||||
DeviceGetSystemDateAndTime = "GetSystemDateAndTime"
|
DeviceGetSystemDateAndTime = "GetSystemDateAndTime"
|
||||||
|
DeviceSetSystemDateAndTime = "SetSystemDateAndTime"
|
||||||
DeviceSystemReboot = "SystemReboot"
|
DeviceSystemReboot = "SystemReboot"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
|
MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
|
||||||
MediaGetAudioSources = "GetAudioSources"
|
MediaGetAudioSources = "GetAudioSources"
|
||||||
MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
|
MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
|
||||||
MediaGetProfile = "GetProfile"
|
MediaGetProfile = "GetProfile"
|
||||||
MediaGetProfiles = "GetProfiles"
|
MediaGetProfiles = "GetProfiles"
|
||||||
MediaGetSnapshotUri = "GetSnapshotUri"
|
MediaGetSnapshotUri = "GetSnapshotUri"
|
||||||
MediaGetStreamUri = "GetStreamUri"
|
MediaGetStreamUri = "GetStreamUri"
|
||||||
MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
|
MediaGetVideoEncoderConfiguration = "GetVideoEncoderConfiguration"
|
||||||
MediaGetVideoSources = "GetVideoSources"
|
MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
|
||||||
MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration"
|
MediaGetVideoEncoderConfigurationOptions = "GetVideoEncoderConfigurationOptions"
|
||||||
MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
|
MediaGetVideoSources = "GetVideoSources"
|
||||||
|
MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration"
|
||||||
|
MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRequestAction(b []byte) string {
|
func GetRequestAction(b []byte) string {
|
||||||
@@ -54,13 +57,13 @@ func GetRequestAction(b []byte) string {
|
|||||||
|
|
||||||
func GetCapabilitiesResponse(host string) []byte {
|
func GetCapabilitiesResponse(host string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<tds:GetCapabilitiesResponse>
|
e.Appendf(`<tds:GetCapabilitiesResponse>
|
||||||
<tds:Capabilities>
|
<tds:Capabilities>
|
||||||
<tt:Device>
|
<tt:Device>
|
||||||
<tt:XAddr>http://`, host, `/onvif/device_service</tt:XAddr>
|
<tt:XAddr>http://%s/onvif/device_service</tt:XAddr>
|
||||||
</tt:Device>
|
</tt:Device>
|
||||||
<tt:Media>
|
<tt:Media>
|
||||||
<tt:XAddr>http://`, host, `/onvif/media_service</tt:XAddr>
|
<tt:XAddr>http://%s/onvif/media_service</tt:XAddr>
|
||||||
<tt:StreamingCapabilities>
|
<tt:StreamingCapabilities>
|
||||||
<tt:RTPMulticast>false</tt:RTPMulticast>
|
<tt:RTPMulticast>false</tt:RTPMulticast>
|
||||||
<tt:RTP_TCP>false</tt:RTP_TCP>
|
<tt:RTP_TCP>false</tt:RTP_TCP>
|
||||||
@@ -68,24 +71,24 @@ func GetCapabilitiesResponse(host string) []byte {
|
|||||||
</tt:StreamingCapabilities>
|
</tt:StreamingCapabilities>
|
||||||
</tt:Media>
|
</tt:Media>
|
||||||
</tds:Capabilities>
|
</tds:Capabilities>
|
||||||
</tds:GetCapabilitiesResponse>`)
|
</tds:GetCapabilitiesResponse>`, host, host)
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetServicesResponse(host string) []byte {
|
func GetServicesResponse(host string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<tds:GetServicesResponse>
|
e.Appendf(`<tds:GetServicesResponse>
|
||||||
<tds:Service>
|
<tds:Service>
|
||||||
<tds:Namespace>http://www.onvif.org/ver10/device/wsdl</tds:Namespace>
|
<tds:Namespace>http://www.onvif.org/ver10/device/wsdl</tds:Namespace>
|
||||||
<tds:XAddr>http://`, host, `/onvif/device_service</tds:XAddr>
|
<tds:XAddr>http://%s/onvif/device_service</tds:XAddr>
|
||||||
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
|
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
|
||||||
</tds:Service>
|
</tds:Service>
|
||||||
<tds:Service>
|
<tds:Service>
|
||||||
<tds:Namespace>http://www.onvif.org/ver10/media/wsdl</tds:Namespace>
|
<tds:Namespace>http://www.onvif.org/ver10/media/wsdl</tds:Namespace>
|
||||||
<tds:XAddr>http://`, host, `/onvif/media_service</tds:XAddr>
|
<tds:XAddr>http://%s/onvif/media_service</tds:XAddr>
|
||||||
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
|
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
|
||||||
</tds:Service>
|
</tds:Service>
|
||||||
</tds:GetServicesResponse>`)
|
</tds:GetServicesResponse>`, host, host)
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,30 +123,19 @@ func GetSystemDateAndTimeResponse() []byte {
|
|||||||
|
|
||||||
func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte {
|
func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<tds:GetDeviceInformationResponse>
|
e.Appendf(`<tds:GetDeviceInformationResponse>
|
||||||
<tds:Manufacturer>`, manuf, `</tds:Manufacturer>
|
<tds:Manufacturer>%s</tds:Manufacturer>
|
||||||
<tds:Model>`, model, `</tds:Model>
|
<tds:Model>%s</tds:Model>
|
||||||
<tds:FirmwareVersion>`, firmware, `</tds:FirmwareVersion>
|
<tds:FirmwareVersion>%s</tds:FirmwareVersion>
|
||||||
<tds:SerialNumber>`, serial, `</tds:SerialNumber>
|
<tds:SerialNumber>%s</tds:SerialNumber>
|
||||||
<tds:HardwareId>1.00</tds:HardwareId>
|
<tds:HardwareId>1.00</tds:HardwareId>
|
||||||
</tds:GetDeviceInformationResponse>`)
|
</tds:GetDeviceInformationResponse>`, manuf, model, firmware, serial)
|
||||||
return e.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMediaServiceCapabilitiesResponse() []byte {
|
|
||||||
e := NewEnvelope()
|
|
||||||
e.Append(`<trt:GetServiceCapabilitiesResponse>
|
|
||||||
<trt:Capabilities SnapshotUri="true" Rotation="false" VideoSourceMode="false" OSD="false" TemporaryOSDText="false" EXICompression="false">
|
|
||||||
<trt:StreamingCapabilities RTPMulticast="false" RTP_TCP="false" RTP_RTSP_TCP="true" NonAggregateControl="false" NoRTSPStreaming="false" />
|
|
||||||
</trt:Capabilities>
|
|
||||||
</trt:GetServiceCapabilitiesResponse>`)
|
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetProfilesResponse(names []string) []byte {
|
func GetProfilesResponse(names []string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<trt:GetProfilesResponse>
|
e.Append(`<trt:GetProfilesResponse>`)
|
||||||
`)
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
appendProfile(e, "Profiles", name)
|
appendProfile(e, "Profiles", name)
|
||||||
}
|
}
|
||||||
@@ -153,38 +145,40 @@ func GetProfilesResponse(names []string) []byte {
|
|||||||
|
|
||||||
func GetProfileResponse(name string) []byte {
|
func GetProfileResponse(name string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<trt:GetProfileResponse>
|
e.Append(`<trt:GetProfileResponse>`)
|
||||||
`)
|
|
||||||
appendProfile(e, "Profile", name)
|
appendProfile(e, "Profile", name)
|
||||||
e.Append(`</trt:GetProfileResponse>`)
|
e.Append(`</trt:GetProfileResponse>`)
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendProfile(e *Envelope, tag, name string) {
|
func appendProfile(e *Envelope, tag, name string) {
|
||||||
// empty `RateControl` important for UniFi Protect
|
// go2rtc name = ONVIF Profile Name = ONVIF Profile token
|
||||||
e.Append(`<trt:`, tag, ` token="`, name, `" fixed="true">
|
e.Appendf(`<trt:%s token="%s" fixed="true">`, tag, name)
|
||||||
<tt:Name>`, name, `</tt:Name>
|
e.Appendf(`<tt:Name>%s</tt:Name>`, name)
|
||||||
<tt:VideoSourceConfiguration token="`, name, `">
|
appendVideoSourceConfiguration(e, "VideoSourceConfiguration", name)
|
||||||
<tt:Name>VSC</tt:Name>
|
appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
|
||||||
<tt:SourceToken>`, name, `</tt:SourceToken>
|
e.Appendf(`</trt:%s>`, tag)
|
||||||
<tt:Bounds x="0" y="0" width="1920" height="1080"></tt:Bounds>
|
}
|
||||||
</tt:VideoSourceConfiguration>
|
|
||||||
<tt:VideoEncoderConfiguration token="vec">
|
func GetVideoSourcesResponse(names []string) []byte {
|
||||||
<tt:Name>VEC</tt:Name>
|
// go2rtc name = ONVIF VideoSource token
|
||||||
<tt:Encoding>H264</tt:Encoding>
|
e := NewEnvelope()
|
||||||
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
|
e.Append(`<trt:GetVideoSourcesResponse>`)
|
||||||
<tt:RateControl />
|
for _, name := range names {
|
||||||
</tt:VideoEncoderConfiguration>
|
e.Appendf(`<trt:VideoSources token="%s">
|
||||||
</trt:`, tag, `>
|
<tt:Framerate>30.000000</tt:Framerate>
|
||||||
`)
|
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
|
||||||
|
</trt:VideoSources>`, name)
|
||||||
|
}
|
||||||
|
e.Append(`</trt:GetVideoSourcesResponse>`)
|
||||||
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetVideoSourceConfigurationsResponse(names []string) []byte {
|
func GetVideoSourceConfigurationsResponse(names []string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<trt:GetVideoSourceConfigurationsResponse>
|
e.Append(`<trt:GetVideoSourceConfigurationsResponse>`)
|
||||||
`)
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
appendProfile(e, "Configurations", name)
|
appendVideoSourceConfiguration(e, "Configurations", name)
|
||||||
}
|
}
|
||||||
e.Append(`</trt:GetVideoSourceConfigurationsResponse>`)
|
e.Append(`</trt:GetVideoSourceConfigurationsResponse>`)
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
@@ -192,46 +186,60 @@ func GetVideoSourceConfigurationsResponse(names []string) []byte {
|
|||||||
|
|
||||||
func GetVideoSourceConfigurationResponse(name string) []byte {
|
func GetVideoSourceConfigurationResponse(name string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<trt:GetVideoSourceConfigurationResponse>
|
e.Append(`<trt:GetVideoSourceConfigurationResponse>`)
|
||||||
`)
|
|
||||||
appendVideoSourceConfiguration(e, "Configuration", name)
|
appendVideoSourceConfiguration(e, "Configuration", name)
|
||||||
e.Append(`</trt:GetVideoSourceConfigurationResponse>`)
|
e.Append(`</trt:GetVideoSourceConfigurationResponse>`)
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendVideoSourceConfiguration(e *Envelope, tag, name string) {
|
func appendVideoSourceConfiguration(e *Envelope, tag, name string) {
|
||||||
e.Append(`<trt:`, tag, ` token="`, name, `" fixed="true">
|
// go2rtc name = ONVIF VideoSourceConfiguration token
|
||||||
|
e.Appendf(`<tt:%s token="%s" fixed="true">
|
||||||
<tt:Name>VSC</tt:Name>
|
<tt:Name>VSC</tt:Name>
|
||||||
<tt:SourceToken>`, name, `</tt:SourceToken>
|
<tt:SourceToken>%s</tt:SourceToken>
|
||||||
<tt:Bounds x="0" y="0" width="1920" height="1080"></tt:Bounds>
|
<tt:Bounds x="0" y="0" width="1920" height="1080"></tt:Bounds>
|
||||||
</trt:`, tag, `>
|
</tt:%s>`, tag, name, name, tag)
|
||||||
`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetVideoSourcesResponse(names []string) []byte {
|
func GetVideoEncoderConfigurationsResponse() []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<trt:GetVideoSourcesResponse>
|
e.Append(`<trt:GetVideoEncoderConfigurationsResponse>`)
|
||||||
`)
|
appendVideoEncoderConfiguration(e, "VideoEncoderConfigurations")
|
||||||
for _, name := range names {
|
e.Append(`</trt:GetVideoEncoderConfigurationsResponse>`)
|
||||||
e.Append(`<trt:VideoSources token="`, name, `">
|
|
||||||
<tt:Framerate>30.000000</tt:Framerate>
|
|
||||||
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
|
|
||||||
</trt:VideoSources>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
e.Append(`</trt:GetVideoSourcesResponse>`)
|
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetVideoEncoderConfigurationResponse() []byte {
|
||||||
|
e := NewEnvelope()
|
||||||
|
e.Append(`<trt:GetVideoEncoderConfigurationResponse>`)
|
||||||
|
appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
|
||||||
|
e.Append(`</trt:GetVideoEncoderConfigurationResponse>`)
|
||||||
|
return e.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendVideoEncoderConfiguration(e *Envelope, tag string) {
|
||||||
|
// empty `RateControl` important for UniFi Protect
|
||||||
|
e.Appendf(`<tt:%s token="vec">
|
||||||
|
<tt:Name>VEC</tt:Name>
|
||||||
|
<tt:UseCount>1</tt:UseCount>
|
||||||
|
<tt:Encoding>H264</tt:Encoding>
|
||||||
|
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
|
||||||
|
<tt:Quality>0</tt:Quality>
|
||||||
|
<tt:RateControl><tt:FrameRateLimit>30</tt:FrameRateLimit><tt:EncodingInterval>1</tt:EncodingInterval><tt:BitrateLimit>8192</tt:BitrateLimit></tt:RateControl>
|
||||||
|
<tt:H264><tt:GovLength>10</tt:GovLength><tt:H264Profile>Main</tt:H264Profile></tt:H264>
|
||||||
|
<tt:SessionTimeout>PT10S</tt:SessionTimeout>
|
||||||
|
</tt:%s>`, tag, tag)
|
||||||
|
}
|
||||||
|
|
||||||
func GetStreamUriResponse(uri string) []byte {
|
func GetStreamUriResponse(uri string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<trt:GetStreamUriResponse><trt:MediaUri><tt:Uri>`, uri, `</tt:Uri></trt:MediaUri></trt:GetStreamUriResponse>`)
|
e.Appendf(`<trt:GetStreamUriResponse><trt:MediaUri><tt:Uri>%s</tt:Uri></trt:MediaUri></trt:GetStreamUriResponse>`, uri)
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSnapshotUriResponse(uri string) []byte {
|
func GetSnapshotUriResponse(uri string) []byte {
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
e.Append(`<trt:GetSnapshotUriResponse><trt:MediaUri><tt:Uri>`, uri, `</tt:Uri></trt:MediaUri></trt:GetSnapshotUriResponse>`)
|
e.Appendf(`<trt:GetSnapshotUriResponse><trt:MediaUri><tt:Uri>%s</tt:Uri></trt:MediaUri></trt:GetSnapshotUriResponse>`, uri)
|
||||||
return e.Bytes()
|
return e.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +247,10 @@ func StaticResponse(operation string) []byte {
|
|||||||
switch operation {
|
switch operation {
|
||||||
case DeviceGetSystemDateAndTime:
|
case DeviceGetSystemDateAndTime:
|
||||||
return GetSystemDateAndTimeResponse()
|
return GetSystemDateAndTimeResponse()
|
||||||
|
case MediaGetVideoEncoderConfiguration:
|
||||||
|
return GetVideoEncoderConfigurationResponse()
|
||||||
|
case MediaGetVideoEncoderConfigurations:
|
||||||
|
return GetVideoEncoderConfigurationsResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
e := NewEnvelope()
|
e := NewEnvelope()
|
||||||
@@ -247,11 +259,18 @@ func StaticResponse(operation string) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var responses = map[string]string{
|
var responses = map[string]string{
|
||||||
|
ServiceGetServiceCapabilities: `<trt:GetServiceCapabilitiesResponse>
|
||||||
|
<trt:Capabilities SnapshotUri="true" Rotation="false" VideoSourceMode="false" OSD="false" TemporaryOSDText="false" EXICompression="false">
|
||||||
|
<trt:StreamingCapabilities RTPMulticast="false" RTP_TCP="false" RTP_RTSP_TCP="true" NonAggregateControl="false" NoRTSPStreaming="false" />
|
||||||
|
</trt:Capabilities>
|
||||||
|
</trt:GetServiceCapabilitiesResponse>`,
|
||||||
|
|
||||||
DeviceGetDiscoveryMode: `<tds:GetDiscoveryModeResponse><tds:DiscoveryMode>Discoverable</tds:DiscoveryMode></tds:GetDiscoveryModeResponse>`,
|
DeviceGetDiscoveryMode: `<tds:GetDiscoveryModeResponse><tds:DiscoveryMode>Discoverable</tds:DiscoveryMode></tds:GetDiscoveryModeResponse>`,
|
||||||
DeviceGetDNS: `<tds:GetDNSResponse><tds:DNSInformation /></tds:GetDNSResponse>`,
|
DeviceGetDNS: `<tds:GetDNSResponse><tds:DNSInformation /></tds:GetDNSResponse>`,
|
||||||
DeviceGetHostname: `<tds:GetHostnameResponse><tds:HostnameInformation /></tds:GetHostnameResponse>`,
|
DeviceGetHostname: `<tds:GetHostnameResponse><tds:HostnameInformation /></tds:GetHostnameResponse>`,
|
||||||
DeviceGetNetworkDefaultGateway: `<tds:GetNetworkDefaultGatewayResponse><tds:NetworkGateway /></tds:GetNetworkDefaultGatewayResponse>`,
|
DeviceGetNetworkDefaultGateway: `<tds:GetNetworkDefaultGatewayResponse><tds:NetworkGateway /></tds:GetNetworkDefaultGatewayResponse>`,
|
||||||
DeviceGetNTP: `<tds:GetNTPResponse><tds:NTPInformation /></tds:GetNTPResponse>`,
|
DeviceGetNTP: `<tds:GetNTPResponse><tds:NTPInformation /></tds:GetNTPResponse>`,
|
||||||
|
DeviceSetSystemDateAndTime: `<tds:SetSystemDateAndTimeResponse />`,
|
||||||
DeviceSystemReboot: `<tds:SystemRebootResponse><tds:Message>OK</tds:Message></tds:SystemRebootResponse>`,
|
DeviceSystemReboot: `<tds:SystemRebootResponse><tds:Message>OK</tds:Message></tds:SystemRebootResponse>`,
|
||||||
|
|
||||||
DeviceGetNetworkInterfaces: `<tds:GetNetworkInterfacesResponse />`,
|
DeviceGetNetworkInterfaces: `<tds:GetNetworkInterfacesResponse />`,
|
||||||
@@ -263,16 +282,20 @@ var responses = map[string]string{
|
|||||||
<tds:Scopes><tt:ScopeDef>Fixed</tt:ScopeDef><tt:ScopeItem>onvif://www.onvif.org/type/Network_Video_Transmitter</tt:ScopeItem></tds:Scopes>
|
<tds:Scopes><tt:ScopeDef>Fixed</tt:ScopeDef><tt:ScopeItem>onvif://www.onvif.org/type/Network_Video_Transmitter</tt:ScopeItem></tds:Scopes>
|
||||||
</tds:GetScopesResponse>`,
|
</tds:GetScopesResponse>`,
|
||||||
|
|
||||||
MediaGetVideoEncoderConfigurations: `<trt:GetVideoEncoderConfigurationsResponse>
|
|
||||||
<tt:VideoEncoderConfiguration token="vec">
|
|
||||||
<tt:Name>VEC</tt:Name>
|
|
||||||
<tt:Encoding>H264</tt:Encoding>
|
|
||||||
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
|
|
||||||
<tt:RateControl />
|
|
||||||
</tt:VideoEncoderConfiguration>
|
|
||||||
</trt:GetVideoEncoderConfigurationsResponse>`,
|
|
||||||
|
|
||||||
MediaGetAudioEncoderConfigurations: `<trt:GetAudioEncoderConfigurationsResponse />`,
|
MediaGetAudioEncoderConfigurations: `<trt:GetAudioEncoderConfigurationsResponse />`,
|
||||||
MediaGetAudioSources: `<trt:GetAudioSourcesResponse />`,
|
MediaGetAudioSources: `<trt:GetAudioSourcesResponse />`,
|
||||||
MediaGetAudioSourceConfigurations: `<trt:GetAudioSourceConfigurationsResponse />`,
|
MediaGetAudioSourceConfigurations: `<trt:GetAudioSourceConfigurationsResponse />`,
|
||||||
|
|
||||||
|
MediaGetVideoEncoderConfigurationOptions: `<trt:GetVideoEncoderConfigurationOptionsResponse>
|
||||||
|
<trt:Options>
|
||||||
|
<tt:QualityRange><tt:Min>1</tt:Min><tt:Max>6</tt:Max></tt:QualityRange>
|
||||||
|
<tt:H264>
|
||||||
|
<tt:ResolutionsAvailable><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:ResolutionsAvailable>
|
||||||
|
<tt:GovLengthRange><tt:Min>0</tt:Min><tt:Max>100</tt:Max></tt:GovLengthRange>
|
||||||
|
<tt:FrameRateRange><tt:Min>1</tt:Min><tt:Max>30</tt:Max></tt:FrameRateRange>
|
||||||
|
<tt:EncodingIntervalRange><tt:Min>1</tt:Min><tt:Max>100</tt:Max></tt:EncodingIntervalRange>
|
||||||
|
<tt:H264ProfilesSupported>Main</tt:H264ProfilesSupported>
|
||||||
|
</tt:H264>
|
||||||
|
</trt:Options>
|
||||||
|
</trt:GetVideoEncoderConfigurationOptionsResponse>`,
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-10
@@ -50,7 +50,8 @@ type Session16 struct {
|
|||||||
seqSendCmd1 uint16
|
seqSendCmd1 uint16
|
||||||
seqSendAud uint16
|
seqSendAud uint16
|
||||||
|
|
||||||
waitSeq uint16
|
waitFSeq uint16
|
||||||
|
waitCSeq uint16
|
||||||
waitSize int
|
waitSize int
|
||||||
waitData []byte
|
waitData []byte
|
||||||
}
|
}
|
||||||
@@ -183,7 +184,7 @@ func (s *Session16) SessionRead(chID byte, cmd []byte) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 0 01030800 command + version
|
// 0 01030800 command + version
|
||||||
// 4 00000000 frame num
|
// 4 00000000 frame seq
|
||||||
// 8 ac880100 total size
|
// 8 ac880100 total size
|
||||||
// 12 6200 chunk seq
|
// 12 6200 chunk seq
|
||||||
// 14 2000 tail (pkt header) size
|
// 14 2000 tail (pkt header) size
|
||||||
@@ -197,25 +198,27 @@ func (s *Session16) SessionRead(chID byte, cmd []byte) int {
|
|||||||
|
|
||||||
switch cmd[1] {
|
switch cmd[1] {
|
||||||
case 0x03:
|
case 0x03:
|
||||||
seq := binary.LittleEndian.Uint16(cmd[12:])
|
frameSeq := binary.LittleEndian.Uint16(cmd[4:])
|
||||||
if seq != s.waitSeq {
|
chunkSeq := binary.LittleEndian.Uint16(cmd[12:])
|
||||||
s.waitSeq = 0
|
if chunkSeq == 0 {
|
||||||
return msgMediaLost
|
s.waitFSeq = frameSeq
|
||||||
}
|
s.waitCSeq = 0
|
||||||
if seq == 0 {
|
|
||||||
s.waitData = s.waitData[:0]
|
s.waitData = s.waitData[:0]
|
||||||
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
|
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
|
||||||
hdrSize := binary.LittleEndian.Uint16(cmd[14:])
|
hdrSize := binary.LittleEndian.Uint16(cmd[14:])
|
||||||
s.waitSize = int(hdrSize) + int(payloadSize)
|
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:]...)
|
s.waitData = append(s.waitData, cmd[24:]...)
|
||||||
if n := len(s.waitData); n < s.waitSize {
|
if n := len(s.waitData); n < s.waitSize {
|
||||||
s.waitSeq++
|
s.waitCSeq++
|
||||||
return msgMediaChunk
|
return msgMediaChunk
|
||||||
}
|
}
|
||||||
|
|
||||||
s.waitSeq = 0
|
s.waitCSeq = 0
|
||||||
|
|
||||||
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
|
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
|
||||||
packetData[0] = bytes.Clone(s.waitData[payloadSize:])
|
packetData[0] = bytes.Clone(s.waitData[payloadSize:])
|
||||||
|
|||||||
@@ -171,15 +171,15 @@ func (s *Session25) handleChunk(cmd []byte, checkSeq bool) int {
|
|||||||
// "0x20 chunk seq for first chunk if only one chunk".
|
// "0x20 chunk seq for first chunk if only one chunk".
|
||||||
if binary.LittleEndian.Uint16(cmd2[6:]) == 0 || binary.LittleEndian.Uint16(cmd2[4:]) == 1 {
|
if binary.LittleEndian.Uint16(cmd2[6:]) == 0 || binary.LittleEndian.Uint16(cmd2[4:]) == 1 {
|
||||||
s.waitData = s.waitData[:0]
|
s.waitData = s.waitData[:0]
|
||||||
s.waitSeq = seq
|
s.waitCSeq = seq
|
||||||
} else if seq != s.waitSeq {
|
} else if seq != s.waitCSeq {
|
||||||
return msgMediaLost
|
return msgMediaLost
|
||||||
}
|
}
|
||||||
|
|
||||||
s.waitData = append(s.waitData, cmd2[20:]...)
|
s.waitData = append(s.waitData, cmd2[20:]...)
|
||||||
|
|
||||||
if flags&0b0001 == 0 {
|
if flags&0b0001 == 0 {
|
||||||
s.waitSeq++
|
s.waitCSeq++
|
||||||
return msgMediaChunk
|
return msgMediaChunk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+75
-24
@@ -33,7 +33,7 @@ func NewClient(rawURL string) (*Client, error) {
|
|||||||
`{"public_key":"%s","sign":"%s","account":"admin"}`,
|
`{"public_key":"%s","sign":"%s","account":"admin"}`,
|
||||||
query.Get("client_public"), query.Get("sign"),
|
query.Get("client_public"), query.Get("sign"),
|
||||||
)
|
)
|
||||||
} else if model == ModelXiaobai {
|
} else if model == ModelMijia || model == ModelXiaobai {
|
||||||
username = "admin"
|
username = "admin"
|
||||||
password = query.Get("password")
|
password = query.Get("password")
|
||||||
} else if model == ModelXiaofang {
|
} else if model == ModelXiaofang {
|
||||||
@@ -104,27 +104,78 @@ func (c *Client) ReadPacket() (hdr, payload []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.key != nil {
|
if c.key != nil {
|
||||||
switch hdr[0] {
|
if c.model == ModelAqaraG2 && hdr[0] == tutk.CodecH265 {
|
||||||
case tutk.CodecH264, tutk.CodecH265:
|
|
||||||
payload, err = DecodeVideo(payload, c.key)
|
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)
|
payload, err = crypto.Decode(payload, c.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
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 {
|
func (c *Client) StartMedia(video, audio string) error {
|
||||||
switch c.model {
|
switch c.model {
|
||||||
case ModelAqaraG2:
|
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:
|
case ModelXiaobai:
|
||||||
// 00030000 7b7d audio on
|
// 00030000 7b7d audio on
|
||||||
// 01030000 7b7d audio off
|
// 01030000 7b7d audio off
|
||||||
if err := c.WriteCommand(0x0300, []byte(`{}`)); err != nil {
|
// 20030000 0000000001000000 fhd (1920x1080)
|
||||||
return err
|
// 20030000 0000000002000000 hd (1280x720)
|
||||||
}
|
// 20030000 0000000004000000 low (640x360)
|
||||||
|
// 20030000 00000000ff000000 auto (1920x1080)
|
||||||
|
// ff010000 7b7d video tart
|
||||||
|
// ff020000 7b7d video stop
|
||||||
|
|
||||||
var b byte
|
var b byte
|
||||||
switch video {
|
switch video {
|
||||||
@@ -137,17 +188,13 @@ func (c *Client) StartMedia(video, audio string) error {
|
|||||||
case "auto":
|
case "auto":
|
||||||
b = 0xff
|
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
|
// quality before start
|
||||||
// ff020000 7b7d video stop
|
return errors.Join(
|
||||||
return c.WriteCommand(0x01ff, []byte(`{}`))
|
c.WriteCommandJSON(cmdAudioStart, `{}`),
|
||||||
|
c.WriteCommand(cmdStreamCtrlReq, []byte{0, 0, 0, 0, b, 0, 0, 0}),
|
||||||
|
c.WriteCommandJSON(cmdVideoStart, `{}`),
|
||||||
|
)
|
||||||
|
|
||||||
case ModelXiaofang:
|
case ModelXiaofang:
|
||||||
// 00010000 4943414d 95010400000000000000000600000000000000d20400005a07 - 90k bitrate
|
// 00010000 4943414d 95010400000000000000000600000000000000d20400005a07 - 90k bitrate
|
||||||
@@ -163,15 +210,16 @@ func (c *Client) StartMedia(video, audio string) error {
|
|||||||
//if err := c.WriteCommand(0x100, data); err != nil {
|
//if err := c.WriteCommand(0x100, data); err != nil {
|
||||||
// return err
|
// return err
|
||||||
//}
|
//}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return fmt.Errorf("xiaomi: unsupported model: %s", c.model)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) StopMedia() error {
|
func (c *Client) StopMedia() error {
|
||||||
return errors.Join(
|
return errors.Join(
|
||||||
c.WriteCommand(0x02ff, []byte(`{}`)),
|
c.WriteCommandJSON(cmdVideoStop, `{}`),
|
||||||
c.WriteCommand(0x02ff, make([]byte, 8)),
|
c.WriteCommand(cmdVideoStop, make([]byte, 8)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +234,8 @@ func DecodeVideo(data, key []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nonce8 := data[:8]
|
nonce8 := data[:8]
|
||||||
i1 := binary.LittleEndian.Uint16(data[9:])
|
i1 := binary.LittleEndian.Uint32(data[9:])
|
||||||
i2 := binary.LittleEndian.Uint16(data[13:])
|
i2 := binary.LittleEndian.Uint32(data[13:])
|
||||||
data = data[17:]
|
data = data[17:]
|
||||||
src := data[i1 : i1+i2]
|
src := data[i1 : i1+i2]
|
||||||
|
|
||||||
@@ -204,14 +252,17 @@ func DecodeVideo(data, key []byte) ([]byte, error) {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
ModelAqaraG2 = "lumi.camera.gwagl01"
|
ModelAqaraG2 = "lumi.camera.gwagl01"
|
||||||
|
ModelIMILABA1 = "chuangmi.camera.ipc019e"
|
||||||
ModelLoockV1 = "loock.cateye.v01"
|
ModelLoockV1 = "loock.cateye.v01"
|
||||||
ModelXiaobai = "chuangmi.camera.xiaobai"
|
ModelXiaobai = "chuangmi.camera.xiaobai"
|
||||||
ModelXiaofang = "isa.camera.isc5"
|
ModelXiaofang = "isa.camera.isc5"
|
||||||
|
// ModelMijia support miss format for new fw and legacy format for old fw
|
||||||
|
ModelMijia = "chuangmi.camera.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Supported(model string) bool {
|
func Supported(model string) bool {
|
||||||
switch model {
|
switch model {
|
||||||
case ModelAqaraG2, ModelLoockV1, ModelXiaobai, ModelXiaofang:
|
case ModelAqaraG2, ModelIMILABA1, ModelLoockV1, ModelXiaobai, ModelXiaofang:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -139,11 +139,14 @@ const (
|
|||||||
ModelLoockV2 = "loock.cateye.v02"
|
ModelLoockV2 = "loock.cateye.v02"
|
||||||
ModelC200 = "chuangmi.camera.046c04"
|
ModelC200 = "chuangmi.camera.046c04"
|
||||||
ModelC300 = "chuangmi.camera.72ac1"
|
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 {
|
func (c *Client) StartMedia(channel, quality, audio string) error {
|
||||||
switch c.model {
|
switch c.model {
|
||||||
case ModelDafang:
|
case ModelDafang, ModelXiaofang:
|
||||||
var q, a byte
|
var q, a byte
|
||||||
if quality == "sd" {
|
if quality == "sd" {
|
||||||
q = 1 // 0 - hd, 1 - sd, default - hd
|
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)
|
data := binary.BigEndian.AppendUint32(nil, cmdVideoStart)
|
||||||
if channel == "" {
|
switch channel {
|
||||||
|
case "", "0":
|
||||||
data = fmt.Appendf(data, `{"videoquality":%s,"enableaudio":%s}`, quality, audio)
|
data = fmt.Appendf(data, `{"videoquality":%s,"enableaudio":%s}`, quality, audio)
|
||||||
} else {
|
default:
|
||||||
data = fmt.Appendf(data, `{"videoquality":-1,"videoquality2":%s,"enableaudio":%s}`, quality, audio)
|
data = fmt.Appendf(data, `{"videoquality":-1,"videoquality2":%s,"enableaudio":%s}`, quality, audio)
|
||||||
}
|
}
|
||||||
return c.WriteCommand(data)
|
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.
|
// SpeakerCodec if the camera model has a non-standard two-way codec.
|
||||||
func (c *Client) SpeakerCodec() uint32 {
|
func (c *Client) SpeakerCodec() uint32 {
|
||||||
switch c.model {
|
switch c.model {
|
||||||
case ModelDafang, "isa.camera.hlc6":
|
case ModelDafang, ModelXiaofang, "isa.camera.hlc6":
|
||||||
return codecPCM
|
return codecPCM
|
||||||
case "chuangmi.camera.72ac1":
|
case "chuangmi.camera.72ac1":
|
||||||
return codecOPUS
|
return codecOPUS
|
||||||
@@ -240,7 +244,7 @@ func (c *Client) ReadPacket() (*Packet, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch c.model {
|
switch c.model {
|
||||||
case ModelDafang, ModelLoockV2:
|
case ModelDafang, ModelXiaofang, ModelLoockV2:
|
||||||
// Dafang has ts in sec
|
// Dafang has ts in sec
|
||||||
// LoockV2 has ts in msec for video, but zero ts for audio
|
// LoockV2 has ts in msec for video, but zero ts for audio
|
||||||
pkt.Timestamp = uint64(time.Now().UnixMilli())
|
pkt.Timestamp = uint64(time.Now().UnixMilli())
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ const (
|
|||||||
msgDrwAck = 0xD1
|
msgDrwAck = 0xD1
|
||||||
msgPing = 0xE0
|
msgPing = 0xE0
|
||||||
msgPong = 0xE1
|
msgPong = 0xE1
|
||||||
msgClose = 0xF1
|
msgClose = 0xF0
|
||||||
|
msgCloseAck = 0xF1
|
||||||
)
|
)
|
||||||
|
|
||||||
func handshake(host, transport string) (net.Conn, error) {
|
func handshake(host, transport string) (net.Conn, error) {
|
||||||
@@ -162,7 +163,7 @@ func (c *Conn) worker() {
|
|||||||
|
|
||||||
case msgPing:
|
case msgPing:
|
||||||
_, _ = c.Conn.Write([]byte{magic, msgPong, 0, 0})
|
_, _ = 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
|
case msgDrwAck: // only for UDP
|
||||||
if c.cmdAck != nil {
|
if c.cmdAck != nil {
|
||||||
c.cmdAck()
|
c.cmdAck()
|
||||||
|
|||||||
Reference in New Issue
Block a user