mirror of
https://github.com/kerberos-io/agent.git
synced 2026-03-07 12:51:19 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afefd32a1f | ||
|
|
89e01e065c | ||
|
|
02f3e6a1e2 | ||
|
|
ec5a00f3df | ||
|
|
2860775954 | ||
|
|
d2e8e04833 | ||
|
|
fad90390a6 | ||
|
|
d6ba875473 | ||
|
|
e9ea34c20f | ||
|
|
99cc7d419f | ||
|
|
1a6dc27535 | ||
|
|
93f40a8d34 | ||
|
|
d7f7de97b4 | ||
|
|
50babedcbf | ||
|
|
d4e37e0bae |
@@ -14,7 +14,7 @@
|
||||
// Uncomment the next line to run commands after the container is created - for example installing curl.
|
||||
"postCreateCommand": "cd ui && yarn install && yarn build && cd ../machinery && go mod download",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/ansible:1": {},
|
||||
"ghcr.io/devcontainers-contrib/features/ansible:1": {}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,4 +10,5 @@ machinery/data/recordings
|
||||
machinery/data/snapshots
|
||||
machinery/test*
|
||||
machinery/init-dev.sh
|
||||
machinery/.env
|
||||
deployments/docker/private-docker-compose.yaml
|
||||
@@ -32,7 +32,7 @@ RUN cat /go/src/github.com/kerberos-io/agent/machinery/version
|
||||
|
||||
RUN cd /go/src/github.com/kerberos-io/agent/machinery && \
|
||||
go mod download && \
|
||||
go build -tags timetzdata --ldflags '-s -w -extldflags "-static -latomic"' main.go && \
|
||||
go build -tags timetzdata,netgo --ldflags '-s -w -extldflags "-static -latomic"' main.go && \
|
||||
mkdir -p /agent && \
|
||||
mv main /agent && \
|
||||
mv version /agent && \
|
||||
|
||||
109
README.md
109
README.md
@@ -104,7 +104,7 @@ This repository contains everything you'll need to know about our core product,
|
||||
- Post- and pre-recording on motion detection.
|
||||
- Ability to create fragmented recordings, and streaming though HLS fMP4.
|
||||
- [Deploy where you want](#how-to-run-and-deploy-a-kerberos-agent) with the tools you use: `docker`, `docker compose`, `ansible`, `terraform`, `kubernetes`, etc.
|
||||
- Cloud storage (Kerberos Hub, Kerberos Vault). WIP: Minio, Storj, Dropbox, Google Drive etc.
|
||||
- Cloud storage/persistance: Kerberos Hub, Kerberos Vault and Dropbox. [(WIP: Minio, Storj, Google Drive, FTP etc.)](https://github.com/kerberos-io/agent/issues/95)
|
||||
- WIP: Integrations (Webhooks, MQTT, Script, etc).
|
||||
- REST API access and documentation through Swagger (trigger recording, update configuration, etc).
|
||||
- MIT License
|
||||
@@ -147,7 +147,10 @@ You attach a volume to your container by leveraging the `-v` option. To mount yo
|
||||
-v $(pwd)/agent/recordings:/home/agent/data/recordings \
|
||||
-d --restart=always kerberos/agent:latest
|
||||
|
||||
More example [can be found in the deployment section](https://github.com/kerberos-io/agent/tree/master/deployments) for each deployment and automation tool.
|
||||
More example [can be found in the deployment section](https://github.com/kerberos-io/agent/tree/master/deployments) for each deployment and automation tool. Please note to verify the permissions of the directory/volume you are attaching. More information in [this issue](https://github.com/kerberos-io/agent/issues/80).
|
||||
|
||||
chmod -R 755 kerberos-agent/
|
||||
chown 100:101 kerberos-agent/ -R
|
||||
|
||||
## Configure with environment variables
|
||||
|
||||
@@ -160,52 +163,54 @@ Next to attaching the configuration file, it is also possible to override the co
|
||||
-e AGENT_CAPTURE_CONTINUOUS=true \
|
||||
-d --restart=always kerberos/agent:latest
|
||||
|
||||
| Name | Description | Default Value |
|
||||
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------- |
|
||||
| `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" |
|
||||
| `AGENT_PASSWORD` | The password used to authenticate against the Kerberos Agent login page. | "root" |
|
||||
| `AGENT_KEY` | A unique identifier for your Kerberos Agent, this is auto-generated but can be overriden. | "" |
|
||||
| `AGENT_NAME` | The agent friendly-name. | "agent" |
|
||||
| `AGENT_TIMEZONE` | Timezone which is used for converting time. | "Africa/Ceuta" |
|
||||
| `AGENT_REMOVE_AFTER_UPLOAD` | When enabled, recordings uploaded successfully to a storage will be removed from disk. | "true" |
|
||||
| `AGENT_OFFLINE` | Makes sure no external connection is made. | "false" |
|
||||
| `AGENT_AUTO_CLEAN` | Cleans up the recordings directory. | "true" |
|
||||
| `AGENT_AUTO_CLEAN_MAX_SIZE` | If `AUTO_CLEAN` enabled, set the max size of the recordings directory in (MB). | "100" |
|
||||
| `AGENT_TIME` | Enable the timetable for Kerberos Agent | "false" |
|
||||
| `AGENT_TIMETABLE` | A (weekly) time table to specify when to make recordings "start1,end1,start2,end2;start1.. | "" |
|
||||
| `AGENT_REGION_POLYGON` | A single polygon set for motion detection: "x1,y1;x2,y2;x3,y3;... | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_RTSP` | Full-HD RTSP endpoint to the camera you're targetting. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_SUB_RTSP` | Sub-stream RTSP endpoint used for livestreaming (WebRTC). | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF` | Mark as a compliant ONVIF device. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_XADDR` | ONVIF endpoint/address running on the camera. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_USERNAME` | ONVIF username to authenticate against. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_PASSWORD` | ONVIF password to authenticate against. | "" |
|
||||
| `AGENT_CAPTURE_RECORDING` | Toggle for enabling making recordings. | "true" |
|
||||
| `AGENT_CAPTURE_CONTINUOUS` | Toggle for enabling continuous or motion based recording. | "false" |
|
||||
| `AGENT_CAPTURE_PRERECORDING` | If `CONTINUOUS` set to `false`, specify the recording time (seconds) before after motion event. | "10" |
|
||||
| `AGENT_CAPTURE_POSTRECORDING` | If `CONTINUOUS` set to `false`, specify the recording time (seconds) after motion event. | "20" |
|
||||
| `AGENT_CAPTURE_MAXLENGTH` | The maximum length of a single recording (seconds). | "30" |
|
||||
| `AGENT_CAPTURE_PIXEL_CHANGE` | If `CONTINUOUS` set to `false`, the number of pixel require to change before motion triggers. | "150" |
|
||||
| `AGENT_CAPTURE_FRAGMENTED` | Set the format of the recorded MP4 to fragmented (suitable for HLS). | "false" |
|
||||
| `AGENT_CAPTURE_FRAGMENTED_DURATION` | If `AGENT_CAPTURE_FRAGMENTED` set to `true`, define the duration (seconds) of a fragment. | "8" |
|
||||
| `AGENT_MQTT_URI` | A MQTT broker endpoint that is used for bi-directional communication (live view, onvif, etc) | "tcp://mqtt.kerberos.io:1883" |
|
||||
| `AGENT_MQTT_USERNAME` | Username of the MQTT broker. | "" |
|
||||
| `AGENT_MQTT_PASSWORD` | Password of the MQTT broker. | "" |
|
||||
| `AGENT_STUN_URI` | When using WebRTC, you'll need to provide a STUN server. | "stun:turn.kerberos.io:8443" |
|
||||
| `AGENT_TURN_URI` | When using WebRTC, you'll need to provide a TURN server. | "turn:turn.kerberos.io:8443" |
|
||||
| `AGENT_TURN_USERNAME` | TURN username used for WebRTC. | "username1" |0
|
||||
| `AGENT_TURN_PASSWORD` | TURN password used for WebRTC. | "password1" |
|
||||
| `AGENT_CLOUD` | Store recordings in a Kerberos Hub (s3) or your Kerberos Vault (kstorage). | "s3" |
|
||||
| `AGENT_HUB_URI` | The Kerberos Hub API, defaults to our Kerberos Hub SAAS. | "https://api.cloud.kerberos.io" |
|
||||
| `AGENT_HUB_KEY` | The access key linked to your account in Kerberos Hub. | "" |
|
||||
| `AGENT_HUB_PRIVATE_KEY` | The secret access key linked to your account in Kerberos Hub. | "" |
|
||||
| `AGENT_HUB_USERNAME` | Your Kerberos Hub username, which owns the above access and secret keys. | "" |
|
||||
| `AGENT_HUB_SITE` | The site ID of a site you've created in your Kerberos Hub account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_URI` | The Kerberos Vault API url. | "" |
|
||||
| `AGENT_KERBEROSVAULT_ACCESS_KEY` | The access key of a Kerberos Vault account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_SECRET_KEY` | The secret key of a Kerberos Vault account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_PROVIDER` | A Kerberos Vault provider you have created (optional). | "" |
|
||||
| `AGENT_KERBEROSVAULT_DIRECTORY` | The directory, in the provider, where the recordings will be stored in. | "" |
|
||||
| Name | Description | Default Value |
|
||||
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" |
|
||||
| `AGENT_PASSWORD` | The password used to authenticate against the Kerberos Agent login page. | "root" |
|
||||
| `AGENT_KEY` | A unique identifier for your Kerberos Agent, this is auto-generated but can be overriden. | "" |
|
||||
| `AGENT_NAME` | The agent friendly-name. | "agent" |
|
||||
| `AGENT_TIMEZONE` | Timezone which is used for converting time. | "Africa/Ceuta" |
|
||||
| `AGENT_REMOVE_AFTER_UPLOAD` | When enabled, recordings uploaded successfully to a storage will be removed from disk. | "true" |
|
||||
| `AGENT_OFFLINE` | Makes sure no external connection is made. | "false" |
|
||||
| `AGENT_AUTO_CLEAN` | Cleans up the recordings directory. | "true" |
|
||||
| `AGENT_AUTO_CLEAN_MAX_SIZE` | If `AUTO_CLEAN` enabled, set the max size of the recordings directory in (MB). | "100" |
|
||||
| `AGENT_TIME` | Enable the timetable for Kerberos Agent | "false" |
|
||||
| `AGENT_TIMETABLE` | A (weekly) time table to specify when to make recordings "start1,end1,start2,end2;start1.. | "" |
|
||||
| `AGENT_REGION_POLYGON` | A single polygon set for motion detection: "x1,y1;x2,y2;x3,y3;... | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_RTSP` | Full-HD RTSP endpoint to the camera you're targetting. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_SUB_RTSP` | Sub-stream RTSP endpoint used for livestreaming (WebRTC). | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF` | Mark as a compliant ONVIF device. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_XADDR` | ONVIF endpoint/address running on the camera. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_USERNAME` | ONVIF username to authenticate against. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_PASSWORD` | ONVIF password to authenticate against. | "" |
|
||||
| `AGENT_CAPTURE_RECORDING` | Toggle for enabling making recordings. | "true" |
|
||||
| `AGENT_CAPTURE_CONTINUOUS` | Toggle for enabling continuous or motion based recording. | "false" |
|
||||
| `AGENT_CAPTURE_PRERECORDING` | If `CONTINUOUS` set to `false`, specify the recording time (seconds) before after motion event. | "10" |
|
||||
| `AGENT_CAPTURE_POSTRECORDING` | If `CONTINUOUS` set to `false`, specify the recording time (seconds) after motion event. | "20" |
|
||||
| `AGENT_CAPTURE_MAXLENGTH` | The maximum length of a single recording (seconds). | "30" |
|
||||
| `AGENT_CAPTURE_PIXEL_CHANGE` | If `CONTINUOUS` set to `false`, the number of pixel require to change before motion triggers. | "150" |
|
||||
| `AGENT_CAPTURE_FRAGMENTED` | Set the format of the recorded MP4 to fragmented (suitable for HLS). | "false" |
|
||||
| `AGENT_CAPTURE_FRAGMENTED_DURATION` | If `AGENT_CAPTURE_FRAGMENTED` set to `true`, define the duration (seconds) of a fragment. | "8" |
|
||||
| `AGENT_MQTT_URI` | A MQTT broker endpoint that is used for bi-directional communication (live view, onvif, etc) | "tcp://mqtt.kerberos.io:1883" |
|
||||
| `AGENT_MQTT_USERNAME` | Username of the MQTT broker. | "" |
|
||||
| `AGENT_MQTT_PASSWORD` | Password of the MQTT broker. | "" |
|
||||
| `AGENT_STUN_URI` | When using WebRTC, you'll need to provide a STUN server. | "stun:turn.kerberos.io:8443" |
|
||||
| `AGENT_TURN_URI` | When using WebRTC, you'll need to provide a TURN server. | "turn:turn.kerberos.io:8443" |
|
||||
| `AGENT_TURN_USERNAME` | TURN username used for WebRTC. | "username1" |
|
||||
| `AGENT_TURN_PASSWORD` | TURN password used for WebRTC. | "password1" |
|
||||
| `AGENT_CLOUD` | Store recordings in Kerberos Hub (s3), Kerberos Vault (kstorage) or Dropbox (dropbox). | "s3" |
|
||||
| `AGENT_HUB_URI` | The Kerberos Hub API, defaults to our Kerberos Hub SAAS. | "https://api.hub.domain.com" |
|
||||
| `AGENT_HUB_KEY` | The access key linked to your account in Kerberos Hub. | "" |
|
||||
| `AGENT_HUB_PRIVATE_KEY` | The secret access key linked to your account in Kerberos Hub. | "" |
|
||||
| `AGENT_HUB_USERNAME` | Your Kerberos Hub username, which owns the above access and secret keys. | "" |
|
||||
| `AGENT_HUB_SITE` | The site ID of a site you've created in your Kerberos Hub account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_URI` | The Kerberos Vault API url. | "https://vault.domain.com/api" |
|
||||
| `AGENT_KERBEROSVAULT_ACCESS_KEY` | The access key of a Kerberos Vault account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_SECRET_KEY` | The secret key of a Kerberos Vault account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_PROVIDER` | A Kerberos Vault provider you have created (optional). | "" |
|
||||
| `AGENT_KERBEROSVAULT_DIRECTORY` | The directory, in the provider, where the recordings will be stored in. | "" |
|
||||
| `AGENT_DROPBOX_ACCESS_TOKEN` | The Access Token from your Dropbox app, that is used to leverage the Dropbox SDK. | "" |
|
||||
| `AGENT_DROPBOX_DIRECTORY` | The directory, in the provider, where the recordings will be stored in. | "" |
|
||||
|
||||
## Contribute with Codespaces
|
||||
|
||||
@@ -319,14 +324,6 @@ By running the `docker build` command, you will create the Kerberos Agent Docker
|
||||
|
||||
docker build -t kerberos/agent .
|
||||
|
||||
## Support our project
|
||||
|
||||
If you like our product please feel free to execute an Ethereum donation. All donations will flow back and split to our Open Source contributors, as they are the heart of this community.
|
||||
|
||||
<img width="272" alt="Ethereum donation linke" src="https://user-images.githubusercontent.com/1546779/173443671-3d773068-ae10-4862-a990-dc7c89f3d9c2.png">
|
||||
|
||||
Ethereum Address: `0xf4a759C9436E2280Ea9cdd23d3144D95538fF4bE`
|
||||
|
||||
## What is new?
|
||||
|
||||
This repository contains the next generation of Kerberos.io, **Kerberos Agent (v3)**, and is the successor of the machinery and web repositories. A switch in technologies and architecture has been made. This version is still under active development and can be followed on the [develop branch](https://github.com/kerberos-io/agent/tree/develop) and [project overview](https://github.com/kerberos-io/agent/projects/1).
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}"
|
||||
}
|
||||
"program": "main.go",
|
||||
"args": ["run", "cameraname", "8080"],
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"buildFlags": "--tags dynamic",
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,9 @@ go 1.19
|
||||
require (
|
||||
github.com/InVisionApp/conjungo v1.1.0
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1
|
||||
github.com/asticode/go-astits v1.11.0
|
||||
github.com/bluenviron/gortsplib/v3 v3.6.1
|
||||
github.com/bluenviron/mediacommon v0.5.0
|
||||
github.com/cedricve/go-onvif v0.0.0-20200222191200-567e8ce298f6
|
||||
github.com/deepch/vdk v0.0.19
|
||||
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5
|
||||
@@ -22,11 +25,12 @@ require (
|
||||
github.com/golang-module/carbon/v2 v2.2.3
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/kellydunn/golang-geo v0.7.0
|
||||
github.com/kerberos-io/joy4 v1.0.55
|
||||
github.com/kerberos-io/joy4 v1.0.57
|
||||
github.com/kerberos-io/onvif v0.0.5
|
||||
github.com/minio/minio-go/v6 v6.0.57
|
||||
github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pion/rtp v1.7.13
|
||||
github.com/pion/webrtc/v3 v3.1.50
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/swaggo/files v1.0.0
|
||||
@@ -50,6 +54,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/asticode/go-astikit v0.30.0 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
@@ -69,7 +74,8 @@ require (
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210423192551-a2663126120b // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||
@@ -86,6 +92,7 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/gomega v1.27.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/philhofer/fwd v1.1.1 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
@@ -96,7 +103,6 @@ require (
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.10 // indirect
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pion/sctp v1.8.5 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.10 // indirect
|
||||
@@ -115,13 +121,13 @@ require (
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/net v0.4.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.6 // indirect
|
||||
google.golang.org/grpc v1.32.0 // indirect
|
||||
|
||||
@@ -64,8 +64,16 @@ github.com/appleboy/gin-jwt/v2 v2.9.1 h1:l29et8iLW6omcHltsOP6LLk4s3v4g2FbFs0koxG
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1/go.mod h1:jwcPZJ92uoC9nOUTOKWoN/f6JZOgMSKlFSHw5/FrRUk=
|
||||
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
|
||||
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||
github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflxkRsZA=
|
||||
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||
github.com/asticode/go-astits v1.11.0 h1:GTHUXht0ZXAJXsVbsLIcyfHr1Bchi4QQwMARw2ZWAng=
|
||||
github.com/asticode/go-astits v1.11.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/bluenviron/gortsplib/v3 v3.6.1 h1:+/kPiwmdRwUasU5thOBATJQ4/yD+vrIEutJyRTB/f+0=
|
||||
github.com/bluenviron/gortsplib/v3 v3.6.1/go.mod h1:gc6Z8pBUMC9QBqYxcOY9eVxjDPOrmFcwVH61Xs3Gu2A=
|
||||
github.com/bluenviron/mediacommon v0.5.0 h1:YsVFlEknaXWhZGfz+Y1QbuzXLMVSmHODc7OnRqZoITY=
|
||||
github.com/bluenviron/mediacommon v0.5.0/go.mod h1:t0dqPsWUTchyvib0MhixIwXEgvDX4V9G+I0GzWLQRb8=
|
||||
github.com/cedricve/go-onvif v0.0.0-20200222191200-567e8ce298f6 h1:bzFZYgZD5vf4PWaa2GjOh90HG88uKi2a+B6VnQcDlCA=
|
||||
github.com/cedricve/go-onvif v0.0.0-20200222191200-567e8ce298f6/go.mod h1:nBrjN2nMHendp0Cvb/6GaJ1v92Qv/kzqxWtNBnKJEK0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -190,8 +198,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@@ -203,7 +212,8 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
@@ -249,8 +259,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kellydunn/golang-geo v0.7.0 h1:A5j0/BvNgGwY6Yb6inXQxzYwlPHc6WVZR+MrarZYNNg=
|
||||
github.com/kellydunn/golang-geo v0.7.0/go.mod h1:YYlQPJ+DPEzrHx8kT3oPHC/NjyvCCXE+IuKGKdrjrcU=
|
||||
github.com/kerberos-io/joy4 v1.0.55 h1:P5RISBp8kUowgb/bvqLPVKPJL9n9jI/wXBCLs+XFMWg=
|
||||
github.com/kerberos-io/joy4 v1.0.55/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
|
||||
github.com/kerberos-io/joy4 v1.0.57 h1:/8epNAJv4cOzBG8pFiM9hVNXfwsgA+8/2nHQ2yOeyII=
|
||||
github.com/kerberos-io/joy4 v1.0.57/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
|
||||
github.com/kerberos-io/onvif v0.0.5 h1:kq9mnHZkih9Jl4DyIJ4Rzt++Y3DDKy3nI8S2ESEfZ5w=
|
||||
github.com/kerberos-io/onvif v0.0.5/go.mod h1:Hr2dJOH2LM5SpYKk17gYZ1CMjhGhUl+QlT5kwYogrW0=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
@@ -311,8 +321,9 @@ github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
|
||||
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
@@ -368,6 +379,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -408,8 +420,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/files v1.0.0 h1:1gGXVIeUFCS/dta17rnP0iOpr6CXFwKD7EO5ID233e4=
|
||||
github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO13kc=
|
||||
@@ -498,8 +510,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -547,8 +559,9 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -566,8 +579,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -622,8 +636,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -637,8 +652,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -689,8 +705,9 @@ golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -128,8 +128,10 @@ func HandleUpload(configuration *models.Configuration, communication *models.Com
|
||||
log.Log.Error("HandleUpload: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
delay = 5 * time.Second // slow down
|
||||
log.Log.Error("HandleUpload: " + err.Error())
|
||||
delay = 20 * time.Second // slow down
|
||||
if err != nil {
|
||||
log.Log.Error("HandleUpload: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(delay)
|
||||
@@ -215,36 +217,36 @@ func GetSystemInfo() (models.System, error) {
|
||||
func HandleHeartBeat(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time) {
|
||||
log.Log.Debug("HandleHeartBeat: started")
|
||||
|
||||
config := configuration.Config
|
||||
loop:
|
||||
for {
|
||||
|
||||
if config.Offline == "true" {
|
||||
log.Log.Debug("HandleHeartBeat: stopping as Offline is enabled.")
|
||||
} else {
|
||||
config := configuration.Config
|
||||
|
||||
url := config.HeartbeatURI
|
||||
key := ""
|
||||
username := ""
|
||||
vaultURI := ""
|
||||
if config.Offline == "true" {
|
||||
log.Log.Debug("HandleHeartBeat: stopping as Offline is enabled.")
|
||||
} else {
|
||||
|
||||
url := config.HeartbeatURI
|
||||
key := ""
|
||||
username := ""
|
||||
vaultURI := ""
|
||||
|
||||
username = config.S3.Username
|
||||
if config.Cloud == "s3" && config.S3 != nil && config.S3.Publickey != "" {
|
||||
username = config.S3.Username
|
||||
key = config.S3.Publickey
|
||||
} else if config.Cloud == "kstorage" && config.KStorage != nil && config.KStorage.CloudKey != "" {
|
||||
key = config.KStorage.CloudKey
|
||||
username = config.KStorage.Directory
|
||||
}
|
||||
if config.Cloud == "s3" && config.S3 != nil && config.S3.Publickey != "" {
|
||||
username = config.S3.Username
|
||||
key = config.S3.Publickey
|
||||
} else if config.Cloud == "kstorage" && config.KStorage != nil && config.KStorage.CloudKey != "" {
|
||||
key = config.KStorage.CloudKey
|
||||
username = config.KStorage.Directory
|
||||
}
|
||||
|
||||
// This is the new way ;)
|
||||
if config.HubURI != "" {
|
||||
url = config.HubURI + "/devices/heartbeat"
|
||||
}
|
||||
if config.HubKey != "" {
|
||||
key = config.HubKey
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
// This is the new way ;)
|
||||
if config.HubURI != "" {
|
||||
url = config.HubURI + "/devices/heartbeat"
|
||||
}
|
||||
if config.HubKey != "" {
|
||||
key = config.HubKey
|
||||
}
|
||||
|
||||
if key != "" {
|
||||
// Check if we have a friendly name or not.
|
||||
@@ -292,39 +294,44 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
// Congert to string
|
||||
macs, _ := json.Marshal(system.MACs)
|
||||
ips, _ := json.Marshal(system.IPs)
|
||||
cameraConnected := "true"
|
||||
if communication.CameraConnected == false {
|
||||
cameraConnected = "false"
|
||||
}
|
||||
|
||||
var object = fmt.Sprintf(`{
|
||||
"key" : "%s",
|
||||
"version" : "3.0.0",
|
||||
"release" : "%s",
|
||||
"cpuid" : "%s",
|
||||
"clouduser" : "%s",
|
||||
"cloudpublickey" : "%s",
|
||||
"cameraname" : "%s",
|
||||
"enterprise" : %t,
|
||||
"hostname" : "%s",
|
||||
"architecture" : "%s",
|
||||
"totalMemory" : "%d",
|
||||
"usedMemory" : "%d",
|
||||
"freeMemory" : "%d",
|
||||
"processMemory" : "%d",
|
||||
"mac_list" : %s,
|
||||
"ip_list" : %s,
|
||||
"board" : "",
|
||||
"disk1size" : "%s",
|
||||
"disk3size" : "%s",
|
||||
"diskvdasize" : "%s",
|
||||
"uptime" : "%s",
|
||||
"boot_time" : "%s",
|
||||
"siteID" : "%s",
|
||||
"onvif" : "%s",
|
||||
"numberoffiles" : "33",
|
||||
"timestamp" : 1564747908,
|
||||
"cameratype" : "IPCamera",
|
||||
"docker" : true,
|
||||
"kios" : false,
|
||||
"raspberrypi" : false
|
||||
}`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled)
|
||||
"key" : "%s",
|
||||
"version" : "3.0.0",
|
||||
"release" : "%s",
|
||||
"cpuid" : "%s",
|
||||
"clouduser" : "%s",
|
||||
"cloudpublickey" : "%s",
|
||||
"cameraname" : "%s",
|
||||
"enterprise" : %t,
|
||||
"hostname" : "%s",
|
||||
"architecture" : "%s",
|
||||
"totalMemory" : "%d",
|
||||
"usedMemory" : "%d",
|
||||
"freeMemory" : "%d",
|
||||
"processMemory" : "%d",
|
||||
"mac_list" : %s,
|
||||
"ip_list" : %s,
|
||||
"board" : "",
|
||||
"disk1size" : "%s",
|
||||
"disk3size" : "%s",
|
||||
"diskvdasize" : "%s",
|
||||
"uptime" : "%s",
|
||||
"boot_time" : "%s",
|
||||
"siteID" : "%s",
|
||||
"onvif" : "%s",
|
||||
"cameraConnected": "%s",
|
||||
"numberoffiles" : "33",
|
||||
"timestamp" : 1564747908,
|
||||
"cameratype" : "IPCamera",
|
||||
"docker" : true,
|
||||
"kios" : false,
|
||||
"raspberrypi" : false
|
||||
}`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled, cameraConnected)
|
||||
|
||||
var jsonStr = []byte(object)
|
||||
buffy := bytes.NewBuffer(jsonStr)
|
||||
@@ -365,14 +372,14 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
} else {
|
||||
log.Log.Error("HandleHeartBeat: Disabled as we do not have a public key defined.")
|
||||
}
|
||||
}
|
||||
|
||||
// This will check if we need to stop the thread,
|
||||
// because of a reconfiguration.
|
||||
select {
|
||||
case <-communication.HandleHeartBeat:
|
||||
break loop
|
||||
case <-time.After(15 * time.Second):
|
||||
}
|
||||
// This will check if we need to stop the thread,
|
||||
// because of a reconfiguration.
|
||||
select {
|
||||
case <-communication.HandleHeartBeat:
|
||||
break loop
|
||||
case <-time.After(15 * time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,8 +471,9 @@ func HandleLiveStreamHD(livestreamCursor *pubsub.QueueCursor, configuration *mod
|
||||
if config.Capture.Liveview != "false" {
|
||||
|
||||
// Should create a track here.
|
||||
track := webrtc.NewVideoTrack()
|
||||
go webrtc.WriteToTrack(livestreamCursor, configuration, communication, mqttClient, track, codecs, decoder, decoderMutex)
|
||||
videoTrack := webrtc.NewVideoTrack(codecs)
|
||||
audioTrack := webrtc.NewAudioTrack(codecs)
|
||||
go webrtc.WriteToTrack(livestreamCursor, configuration, communication, mqttClient, videoTrack, audioTrack, codecs, decoder, decoderMutex)
|
||||
|
||||
if config.Capture.ForwardWebRTC == "true" {
|
||||
// We get a request with an offer, but we'll forward it.
|
||||
@@ -488,7 +496,7 @@ func HandleLiveStreamHD(livestreamCursor *pubsub.QueueCursor, configuration *mod
|
||||
webrtc.CandidateArrays[key] = make(chan string, 30)
|
||||
}
|
||||
webrtc.CandidatesMutex.Unlock()
|
||||
webrtc.InitializeWebRTCConnection(configuration, communication, mqttClient, track, handshake, webrtc.CandidateArrays[key])
|
||||
webrtc.InitializeWebRTCConnection(configuration, communication, mqttClient, videoTrack, audioTrack, handshake, webrtc.CandidateArrays[key])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"image"
|
||||
_ "image/png"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
@@ -64,6 +64,9 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
|
||||
subDecoder := &ffmpeg.VideoDecoder{}
|
||||
cameraSettings := &models.Camera{}
|
||||
|
||||
// Handle heartbeats
|
||||
go cloud.HandleHeartBeat(configuration, communication, uptimeStart)
|
||||
|
||||
// Run the agent and fire up all the other
|
||||
// goroutines which do image capture, motion detection, onvif, etc.
|
||||
|
||||
@@ -75,13 +78,16 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
|
||||
}
|
||||
// We will re open the configuration, might have changed :O!
|
||||
OpenConfig(configuration)
|
||||
|
||||
// We will override the configuration with the environment variables
|
||||
OverrideWithEnvironmentVariables(configuration)
|
||||
}
|
||||
log.Log.Debug("Bootstrap: finished")
|
||||
}
|
||||
|
||||
func RunAgent(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time, cameraSettings *models.Camera, decoder *ffmpeg.VideoDecoder, subDecoder *ffmpeg.VideoDecoder) string {
|
||||
log.Log.Debug("RunAgent: bootstrapping agent")
|
||||
|
||||
log.Log.Debug("RunAgent: bootstrapping agent")
|
||||
config := configuration.Config
|
||||
|
||||
// Currently only support H264 encoded cameras, this will change.
|
||||
@@ -185,9 +191,6 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
communication.HandleONVIF = make(chan models.OnvifAction, 1)
|
||||
mqttClient := routers.ConfigureMQTT(configuration, communication)
|
||||
|
||||
// Handle heartbeats
|
||||
go cloud.HandleHeartBeat(configuration, communication, uptimeStart)
|
||||
|
||||
// Handle the camera stream
|
||||
go capture.HandleStream(infile, queue, communication)
|
||||
|
||||
@@ -241,14 +244,14 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
|
||||
// Here we are cleaning up everything!
|
||||
if configuration.Config.Offline != "true" {
|
||||
communication.HandleHeartBeat <- "stop"
|
||||
communication.HandleUpload <- "stop"
|
||||
}
|
||||
communication.HandleStream <- "stop"
|
||||
if subStreamEnabled {
|
||||
communication.HandleSubStream <- "stop"
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
infile.Close()
|
||||
infile = nil
|
||||
@@ -278,6 +281,7 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
// Waiting for some seconds to make sure everything is properly closed.
|
||||
log.Log.Info("RunAgent: waiting 3 seconds to make sure everything is properly closed.")
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
} else {
|
||||
log.Log.Error("Something went wrong while opening RTSP: " + err.Error())
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
@@ -34,4 +34,5 @@ type Communication struct {
|
||||
Decoder *ffmpeg.VideoDecoder
|
||||
SubDecoder *ffmpeg.VideoDecoder
|
||||
Image string
|
||||
CameraConnected bool
|
||||
}
|
||||
|
||||
247
machinery/src/rtsp/client.go
Normal file
247
machinery/src/rtsp/client.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v3"
|
||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph265"
|
||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
func CreateClient() {
|
||||
c := &gortsplib.Client{
|
||||
OnRequest: func(req *base.Request) {
|
||||
//log.Log.Info(logger.Debug, "c->s %v", req)
|
||||
},
|
||||
OnResponse: func(res *base.Response) {
|
||||
//s.Log(logger.Debug, "s->c %v", res)
|
||||
},
|
||||
OnTransportSwitch: func(err error) {
|
||||
//s.Log(logger.Warn, err.Error())
|
||||
},
|
||||
OnPacketLost: func(err error) {
|
||||
//s.Log(logger.Warn, err.Error())
|
||||
},
|
||||
OnDecodeError: func(err error) {
|
||||
//s.Log(logger.Warn, err.Error())
|
||||
},
|
||||
}
|
||||
|
||||
u, err := url.Parse("rtsp://admin:admin@192.168.1.111") //"rtsp://seing:bud-edPTQc@109.159.199.103:554/rtsp/defaultPrimary?mtu=1440&streamType=m") //
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = c.Start(u.Scheme, u.Host)
|
||||
if err != nil {
|
||||
//return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
if err != nil {
|
||||
//return err
|
||||
}
|
||||
fmt.Println(medias)
|
||||
|
||||
// find the H264 media and format
|
||||
var forma *formats.H265
|
||||
medi := medias.FindFormat(&forma)
|
||||
if medi == nil {
|
||||
panic("media not found")
|
||||
}
|
||||
|
||||
// setup RTP/H264 -> H264 decoder
|
||||
rtpDec := forma.CreateDecoder()
|
||||
// setup H264 -> MPEG-TS muxer
|
||||
//pegtsMuxer, err := newMPEGTSMuxer(forma.SPS, forma.PPS)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// setup H264 -> raw frames decoder
|
||||
/*h264RawDec, err := newH264Decoder()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer h264RawDec.close()
|
||||
|
||||
// if SPS and PPS are present into the SDP, send them to the decoder
|
||||
if forma.SPS != nil {
|
||||
h264RawDec.decode(forma.SPS)
|
||||
}
|
||||
if forma.PPS != nil {
|
||||
h264RawDec.decode(forma.PPS)
|
||||
}*/
|
||||
|
||||
readErr := make(chan error)
|
||||
go func() {
|
||||
readErr <- func() error {
|
||||
// Get codecs
|
||||
for _, medi := range medias {
|
||||
for _, forma := range medi.Formats {
|
||||
fmt.Println(forma)
|
||||
}
|
||||
}
|
||||
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, medi := range medias {
|
||||
for _, forma := range medi.Formats {
|
||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||
|
||||
au, pts, err := rtpDec.Decode(pkt)
|
||||
if err != nil {
|
||||
if err != rtph265.ErrNonStartingPacketAndNoPrevious && err != rtph265.ErrMorePacketsNeeded {
|
||||
log.Printf("ERR: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, nalu := range au {
|
||||
log.Printf("received NALU with PTS %v and size %d\n", pts, len(nalu))
|
||||
}
|
||||
|
||||
/*// extract access unit from RTP packets
|
||||
// DecodeUntilMarker is necessary for the DTS extractor to work
|
||||
if pkt.PayloadType == 96 {
|
||||
au, pts, err := rtpDec.DecodeUntilMarker(pkt)
|
||||
|
||||
if err != nil {
|
||||
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
|
||||
log.Printf("ERR: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// encode the access unit into MPEG-TS
|
||||
mpegtsMuxer.encode(au, pts)
|
||||
|
||||
for _, nalu := range au {
|
||||
// convert NALUs into RGBA frames
|
||||
img, err := h264RawDec.decode(nalu)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// wait for a frame
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// convert frame to JPEG and save to file
|
||||
err = saveToFile(img)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_, err = c.Play(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Wait()
|
||||
}()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-readErr:
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveToFile(img image.Image) error {
|
||||
// create file
|
||||
fname := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10) + ".jpg"
|
||||
f, err := os.Create(fname)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
log.Println("saving", fname)
|
||||
|
||||
// convert to jpeg
|
||||
return jpeg.Encode(f, img, &jpeg.Options{
|
||||
Quality: 60,
|
||||
})
|
||||
}
|
||||
|
||||
// extract SPS and PPS without decoding RTP packets
|
||||
func rtpH264ExtractSPSPPS(pkt *rtp.Packet) ([]byte, []byte) {
|
||||
if len(pkt.Payload) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
typ := h264.NALUType(pkt.Payload[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case h264.NALUTypeSPS:
|
||||
return pkt.Payload, nil
|
||||
|
||||
case h264.NALUTypePPS:
|
||||
return nil, pkt.Payload
|
||||
|
||||
case h264.NALUTypeSTAPA:
|
||||
payload := pkt.Payload[1:]
|
||||
var sps []byte
|
||||
var pps []byte
|
||||
|
||||
for len(payload) > 0 {
|
||||
if len(payload) < 2 {
|
||||
break
|
||||
}
|
||||
|
||||
size := uint16(payload[0])<<8 | uint16(payload[1])
|
||||
payload = payload[2:]
|
||||
|
||||
if size == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if int(size) > len(payload) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nalu := payload[:size]
|
||||
payload = payload[size:]
|
||||
|
||||
typ = h264.NALUType(nalu[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case h264.NALUTypeSPS:
|
||||
sps = nalu
|
||||
|
||||
case h264.NALUTypePPS:
|
||||
pps = nalu
|
||||
}
|
||||
}
|
||||
|
||||
return sps, pps
|
||||
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
140
machinery/src/rtsp/h264decoder.go
Normal file
140
machinery/src/rtsp/h264decoder.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #cgo pkg-config: libavcodec libavutil libswscale
|
||||
// #include <libavcodec/avcodec.h>
|
||||
// #include <libavutil/imgutils.h>
|
||||
// #include <libswscale/swscale.h>
|
||||
import "C"
|
||||
|
||||
func frameData(frame *C.AVFrame) **C.uint8_t {
|
||||
return (**C.uint8_t)(unsafe.Pointer(&frame.data[0]))
|
||||
}
|
||||
|
||||
func frameLineSize(frame *C.AVFrame) *C.int {
|
||||
return (*C.int)(unsafe.Pointer(&frame.linesize[0]))
|
||||
}
|
||||
|
||||
// h264Decoder is a wrapper around ffmpeg's H264 decoder.
|
||||
type h264Decoder struct {
|
||||
codecCtx *C.AVCodecContext
|
||||
srcFrame *C.AVFrame
|
||||
swsCtx *C.struct_SwsContext
|
||||
dstFrame *C.AVFrame
|
||||
dstFramePtr []uint8
|
||||
}
|
||||
|
||||
// newH264Decoder allocates a new h264Decoder.
|
||||
func newH264Decoder() (*h264Decoder, error) {
|
||||
codec := C.avcodec_find_decoder(C.AV_CODEC_ID_H264)
|
||||
if codec == nil {
|
||||
return nil, fmt.Errorf("avcodec_find_decoder() failed")
|
||||
}
|
||||
|
||||
codecCtx := C.avcodec_alloc_context3(codec)
|
||||
if codecCtx == nil {
|
||||
return nil, fmt.Errorf("avcodec_alloc_context3() failed")
|
||||
}
|
||||
|
||||
res := C.avcodec_open2(codecCtx, codec, nil)
|
||||
if res < 0 {
|
||||
C.avcodec_close(codecCtx)
|
||||
return nil, fmt.Errorf("avcodec_open2() failed")
|
||||
}
|
||||
|
||||
srcFrame := C.av_frame_alloc()
|
||||
if srcFrame == nil {
|
||||
C.avcodec_close(codecCtx)
|
||||
return nil, fmt.Errorf("av_frame_alloc() failed")
|
||||
}
|
||||
|
||||
return &h264Decoder{
|
||||
codecCtx: codecCtx,
|
||||
srcFrame: srcFrame,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// close closes the decoder.
|
||||
func (d *h264Decoder) close() {
|
||||
if d.dstFrame != nil {
|
||||
C.av_frame_free(&d.dstFrame)
|
||||
}
|
||||
|
||||
if d.swsCtx != nil {
|
||||
C.sws_freeContext(d.swsCtx)
|
||||
}
|
||||
|
||||
C.av_frame_free(&d.srcFrame)
|
||||
C.avcodec_close(d.codecCtx)
|
||||
}
|
||||
|
||||
func (d *h264Decoder) decode(nalu []byte) (image.Image, error) {
|
||||
nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...)
|
||||
|
||||
// send frame to decoder
|
||||
var avPacket C.AVPacket
|
||||
avPacket.data = (*C.uint8_t)(C.CBytes(nalu))
|
||||
defer C.free(unsafe.Pointer(avPacket.data))
|
||||
avPacket.size = C.int(len(nalu))
|
||||
res := C.avcodec_send_packet(d.codecCtx, &avPacket)
|
||||
if res < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// receive frame if available
|
||||
res = C.avcodec_receive_frame(d.codecCtx, d.srcFrame)
|
||||
if res < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if frame size has changed, allocate needed objects
|
||||
if d.dstFrame == nil || d.dstFrame.width != d.srcFrame.width || d.dstFrame.height != d.srcFrame.height {
|
||||
if d.dstFrame != nil {
|
||||
C.av_frame_free(&d.dstFrame)
|
||||
}
|
||||
|
||||
if d.swsCtx != nil {
|
||||
C.sws_freeContext(d.swsCtx)
|
||||
}
|
||||
|
||||
d.dstFrame = C.av_frame_alloc()
|
||||
d.dstFrame.format = C.AV_PIX_FMT_RGBA
|
||||
d.dstFrame.width = d.srcFrame.width
|
||||
d.dstFrame.height = d.srcFrame.height
|
||||
d.dstFrame.color_range = C.AVCOL_RANGE_JPEG
|
||||
res = C.av_frame_get_buffer(d.dstFrame, 1)
|
||||
if res < 0 {
|
||||
return nil, fmt.Errorf("av_frame_get_buffer() err")
|
||||
}
|
||||
|
||||
d.swsCtx = C.sws_getContext(d.srcFrame.width, d.srcFrame.height, C.AV_PIX_FMT_YUV420P,
|
||||
d.dstFrame.width, d.dstFrame.height, (int32)(d.dstFrame.format), C.SWS_BILINEAR, nil, nil, nil)
|
||||
if d.swsCtx == nil {
|
||||
return nil, fmt.Errorf("sws_getContext() err")
|
||||
}
|
||||
|
||||
dstFrameSize := C.av_image_get_buffer_size((int32)(d.dstFrame.format), d.dstFrame.width, d.dstFrame.height, 1)
|
||||
d.dstFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.dstFrame.data[0]))[:dstFrameSize:dstFrameSize]
|
||||
}
|
||||
|
||||
// convert frame from YUV420 to RGB
|
||||
res = C.sws_scale(d.swsCtx, frameData(d.srcFrame), frameLineSize(d.srcFrame),
|
||||
0, d.srcFrame.height, frameData(d.dstFrame), frameLineSize(d.dstFrame))
|
||||
if res < 0 {
|
||||
return nil, fmt.Errorf("sws_scale() err")
|
||||
}
|
||||
|
||||
// embed frame into an image.Image
|
||||
return &image.RGBA{
|
||||
Pix: d.dstFramePtr,
|
||||
Stride: 4 * (int)(d.dstFrame.width),
|
||||
Rect: image.Rectangle{
|
||||
Max: image.Point{(int)(d.dstFrame.width), (int)(d.dstFrame.height)},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
15
machinery/src/rtsp/mp4muxer.go
Normal file
15
machinery/src/rtsp/mp4muxer.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package rtsp
|
||||
|
||||
// mp4Muxer allows to save a H264 stream into a Mp4 file.
|
||||
type mp4Muxer struct {
|
||||
sps []byte
|
||||
pps []byte
|
||||
}
|
||||
|
||||
// newMp4Muxer allocates a mp4Muxer.
|
||||
func newMp4Muxer(sps []byte, pps []byte) (*mp4Muxer, error) {
|
||||
return &mp4Muxer{
|
||||
sps: sps,
|
||||
pps: pps,
|
||||
}, nil
|
||||
}
|
||||
173
machinery/src/rtsp/mpegtsmuxer.go
Normal file
173
machinery/src/rtsp/mpegtsmuxer.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||
)
|
||||
|
||||
// mpegtsMuxer allows to save a H264 stream into a MPEG-TS file.
|
||||
type mpegtsMuxer struct {
|
||||
sps []byte
|
||||
pps []byte
|
||||
|
||||
f *os.File
|
||||
b *bufio.Writer
|
||||
mux *astits.Muxer
|
||||
dtsExtractor *h264.DTSExtractor
|
||||
firstIDRReceived bool
|
||||
startDTS time.Duration
|
||||
}
|
||||
|
||||
// newMPEGTSMuxer allocates a mpegtsMuxer.
|
||||
func newMPEGTSMuxer(sps []byte, pps []byte) (*mpegtsMuxer, error) {
|
||||
f, err := os.Create("mystream.ts")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := bufio.NewWriter(f)
|
||||
|
||||
mux := astits.NewMuxer(context.Background(), b)
|
||||
mux.AddElementaryStream(astits.PMTElementaryStream{
|
||||
ElementaryPID: 256,
|
||||
StreamType: astits.StreamTypeH264Video,
|
||||
})
|
||||
mux.SetPCRPID(256)
|
||||
|
||||
return &mpegtsMuxer{
|
||||
sps: sps,
|
||||
pps: pps,
|
||||
f: f,
|
||||
b: b,
|
||||
mux: mux,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// close closes all the mpegtsMuxer resources.
|
||||
func (e *mpegtsMuxer) close() {
|
||||
e.b.Flush()
|
||||
e.f.Close()
|
||||
}
|
||||
|
||||
// encode encodes a H264 access unit into MPEG-TS.
|
||||
func (e *mpegtsMuxer) encode(au [][]byte, pts time.Duration) error {
|
||||
// prepend an AUD. This is required by some players
|
||||
filteredNALUs := [][]byte{
|
||||
{byte(h264.NALUTypeAccessUnitDelimiter), 240},
|
||||
}
|
||||
|
||||
nonIDRPresent := false
|
||||
idrPresent := false
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := h264.NALUType(nalu[0] & 0x1F)
|
||||
switch typ {
|
||||
case h264.NALUTypeSPS:
|
||||
e.sps = append([]byte(nil), nalu...)
|
||||
continue
|
||||
|
||||
case h264.NALUTypePPS:
|
||||
e.pps = append([]byte(nil), nalu...)
|
||||
continue
|
||||
|
||||
case h264.NALUTypeAccessUnitDelimiter:
|
||||
continue
|
||||
|
||||
case h264.NALUTypeIDR:
|
||||
idrPresent = true
|
||||
|
||||
case h264.NALUTypeNonIDR:
|
||||
nonIDRPresent = true
|
||||
}
|
||||
|
||||
filteredNALUs = append(filteredNALUs, nalu)
|
||||
}
|
||||
|
||||
au = filteredNALUs
|
||||
|
||||
if !nonIDRPresent && !idrPresent {
|
||||
return nil
|
||||
}
|
||||
|
||||
// add SPS and PPS before every group that contains an IDR
|
||||
if idrPresent {
|
||||
au = append([][]byte{e.sps, e.pps}, au...)
|
||||
}
|
||||
|
||||
var dts time.Duration
|
||||
|
||||
if !e.firstIDRReceived {
|
||||
// skip samples silently until we find one with a IDR
|
||||
if !idrPresent {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.firstIDRReceived = true
|
||||
e.dtsExtractor = h264.NewDTSExtractor()
|
||||
|
||||
var err error
|
||||
dts, err = e.dtsExtractor.Extract(au, pts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.startDTS = dts
|
||||
dts = 0
|
||||
pts -= e.startDTS
|
||||
|
||||
} else {
|
||||
var err error
|
||||
dts, err = e.dtsExtractor.Extract(au, pts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dts -= e.startDTS
|
||||
pts -= e.startDTS
|
||||
}
|
||||
|
||||
oh := &astits.PESOptionalHeader{
|
||||
MarkerBits: 2,
|
||||
}
|
||||
|
||||
if dts == pts {
|
||||
oh.PTSDTSIndicator = astits.PTSDTSIndicatorOnlyPTS
|
||||
oh.PTS = &astits.ClockReference{Base: int64(pts.Seconds() * 90000)}
|
||||
} else {
|
||||
oh.PTSDTSIndicator = astits.PTSDTSIndicatorBothPresent
|
||||
oh.DTS = &astits.ClockReference{Base: int64(dts.Seconds() * 90000)}
|
||||
oh.PTS = &astits.ClockReference{Base: int64(pts.Seconds() * 90000)}
|
||||
}
|
||||
|
||||
// encode into Annex-B
|
||||
annexb, err := h264.AnnexBMarshal(au)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write TS packet
|
||||
_, err = e.mux.WriteData(&astits.MuxerData{
|
||||
PID: 256,
|
||||
AdaptationField: &astits.PacketAdaptationField{
|
||||
RandomAccessIndicator: idrPresent,
|
||||
},
|
||||
PES: &astits.PESData{
|
||||
Header: &astits.PESHeader{
|
||||
OptionalHeader: oh,
|
||||
StreamID: 224, // video
|
||||
},
|
||||
Data: annexb,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("wrote TS packet")
|
||||
return nil
|
||||
}
|
||||
@@ -27,7 +27,7 @@ var (
|
||||
CandidateArrays map[string](chan string)
|
||||
peerConnectionCount int64
|
||||
peerConnections map[string]*pionWebRTC.PeerConnection
|
||||
encoder *ffmpeg.VideoEncoder
|
||||
//encoder *ffmpeg.VideoEncoder
|
||||
)
|
||||
|
||||
type WebRTC struct {
|
||||
@@ -40,7 +40,8 @@ type WebRTC struct {
|
||||
PacketsCount chan int
|
||||
}
|
||||
|
||||
func init() {
|
||||
// No longer used, is for transcoding, might comeback on this!
|
||||
/*func init() {
|
||||
// Encoder is created for once and for all.
|
||||
var err error
|
||||
encoder, err = ffmpeg.NewVideoEncoderByCodecType(av.H264)
|
||||
@@ -55,7 +56,7 @@ func init() {
|
||||
encoder.SetPixelFormat(av.I420)
|
||||
encoder.SetBitrate(1000000) // 1MB
|
||||
encoder.SetGopSize(30 / 1) // 1s
|
||||
}
|
||||
}*/
|
||||
|
||||
func CreateWebRTC(name string, stunServers []string, turnServers []string, turnServersUsername string, turnServersCredential string) *WebRTC {
|
||||
return &WebRTC{
|
||||
@@ -86,7 +87,7 @@ func (w WebRTC) CreateOffer(sd []byte) pionWebRTC.SessionDescription {
|
||||
return offer
|
||||
}
|
||||
|
||||
func InitializeWebRTCConnection(configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, track *pionWebRTC.TrackLocalStaticSample, handshake models.SDPPayload, candidates chan string) {
|
||||
func InitializeWebRTCConnection(configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, videoTrack *pionWebRTC.TrackLocalStaticSample, audioTrack *pionWebRTC.TrackLocalStaticSample, handshake models.SDPPayload, candidates chan string) {
|
||||
|
||||
config := configuration.Config
|
||||
|
||||
@@ -127,15 +128,13 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati
|
||||
|
||||
if err == nil && peerConnection != nil {
|
||||
|
||||
if _, err = peerConnection.AddTrack(track); err != nil {
|
||||
if _, err = peerConnection.AddTrack(videoTrack); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = peerConnection.AddTransceiverFromTrack(track,
|
||||
pionWebRTC.RtpTransceiverInit{
|
||||
Direction: pionWebRTC.RTPTransceiverDirectionSendonly,
|
||||
},
|
||||
)
|
||||
if _, err = peerConnection.AddTrack(audioTrack); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -214,12 +213,29 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati
|
||||
}
|
||||
}
|
||||
|
||||
func NewVideoTrack() *pionWebRTC.TrackLocalStaticSample {
|
||||
outboundVideoTrack, _ := pionWebRTC.NewTrackLocalStaticSample(pionWebRTC.RTPCodecCapability{MimeType: "video/h264"}, "video", "pion124")
|
||||
func NewVideoTrack(codecs []av.CodecData) *pionWebRTC.TrackLocalStaticSample {
|
||||
var mimeType string
|
||||
mimeType = pionWebRTC.MimeTypeH264
|
||||
outboundVideoTrack, _ := pionWebRTC.NewTrackLocalStaticSample(pionWebRTC.RTPCodecCapability{MimeType: mimeType}, "video", "pion124")
|
||||
return outboundVideoTrack
|
||||
}
|
||||
|
||||
func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, track *pionWebRTC.TrackLocalStaticSample, codecs []av.CodecData, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) {
|
||||
func NewAudioTrack(codecs []av.CodecData) *pionWebRTC.TrackLocalStaticSample {
|
||||
var mimeType string
|
||||
for _, codec := range codecs {
|
||||
if codec.Type().String() == "OPUS" {
|
||||
mimeType = pionWebRTC.MimeTypeOpus
|
||||
} else if codec.Type().String() == "PCM_MULAW" {
|
||||
mimeType = pionWebRTC.MimeTypePCMU
|
||||
} else if codec.Type().String() == "PCM_ALAW" {
|
||||
mimeType = pionWebRTC.MimeTypePCMA
|
||||
}
|
||||
}
|
||||
outboundAudioTrack, _ := pionWebRTC.NewTrackLocalStaticSample(pionWebRTC.RTPCodecCapability{MimeType: mimeType}, "audio", "pion124")
|
||||
return outboundAudioTrack
|
||||
}
|
||||
|
||||
func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, videoTrack *pionWebRTC.TrackLocalStaticSample, audioTrack *pionWebRTC.TrackLocalStaticSample, codecs []av.CodecData, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) {
|
||||
|
||||
config := configuration.Config
|
||||
|
||||
@@ -233,7 +249,7 @@ func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Co
|
||||
for i, codec := range codecs {
|
||||
if codec.Type().String() == "H264" && videoIdx < 0 {
|
||||
videoIdx = i
|
||||
} else if codec.Type().String() == "PCM_MULAW" && audioIdx < 0 {
|
||||
} else if (codec.Type().String() == "OPUS" || codec.Type().String() == "PCM_MULAW" || codec.Type().String() == "PCM_ALAW") && audioIdx < 0 {
|
||||
audioIdx = i
|
||||
}
|
||||
}
|
||||
@@ -363,13 +379,17 @@ func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Co
|
||||
log.Log.Info("WriteToTrack: Error marshalling frame, " + err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := track.WriteSample(sample); err != nil && err != io.ErrClosedPipe {
|
||||
if err := videoTrack.WriteSample(sample); err != nil && err != io.ErrClosedPipe {
|
||||
log.Log.Error("WriteToTrack: something went wrong while writing sample: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
case audioIdx:
|
||||
//log.Log.Info("WriteToTrack: not writing audio for the moment.")
|
||||
// We will send the audio
|
||||
sample := pionMedia.Sample{Data: pkt.Data, Duration: pkt.Time}
|
||||
if err := audioTrack.WriteSample(sample); err != nil && err != io.ErrClosedPipe {
|
||||
log.Log.Error("WriteToTrack: something went wrong while writing sample: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,6 +398,7 @@ func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Co
|
||||
p.Close()
|
||||
}
|
||||
}
|
||||
|
||||
peerConnectionCount = 0
|
||||
log.Log.Info("WriteToTrack: stop writing to track.")
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
(function (window) {
|
||||
window['env'] = window['env'] || {};
|
||||
// Environment variables
|
||||
window['env']['apiUrl'] = 'http://api.factory.kerberos.io';
|
||||
})(this);
|
||||
@@ -1,5 +0,0 @@
|
||||
(function (window) {
|
||||
window['env'] = window['env'] || {};
|
||||
// Environment variables
|
||||
window['env']['apiUrl'] = '${FACTORY_API_URL}';
|
||||
})(this);
|
||||
Reference in New Issue
Block a user