Compare commits

...

15 Commits

Author SHA1 Message Date
Cedric Verstraeten
e8a355d992 upgrade joy4: add setreaddeadline for RTSP connection 2023-11-19 21:40:08 +01:00
Cedric Verstraeten
ca84664071 hotfix: add locks to make sure candidates are not send to a closed candidate channel 2023-11-18 20:38:29 +01:00
Cedric Verstraeten
dd7fcb31b1 Add ONVIF backchannel functionality with G711 encoding 2023-11-17 16:28:03 +01:00
Cédric Verstraeten
324fffde6b Merge pull request #125 from Izzotop/feat/add-russian-language-support
Add Russian language
2023-11-14 21:22:33 +01:00
Izzotop
cd8347d20f Add Russian language 2023-11-09 16:03:40 +03:00
Cedric Verstraeten
efcbf52b06 Merge branch 'master' of https://github.com/kerberos-io/agent 2023-11-06 17:07:50 +01:00
Cedric Verstraeten
c33469a7b3 add --fix-missing to fix random broken builds (armv6 image) 2023-11-06 17:07:35 +01:00
Cédric Verstraeten
3717535f0b Merge pull request #121 from Chaitanya110703/patch-1
doc(README): remove typo
2023-11-06 16:54:28 +01:00
Cedric Verstraeten
8eb2de5e28 When Kerberos Vault is configured without Kerberos Hub, cameras do not show up in Kerberos Vault #123 2023-11-06 14:37:54 +01:00
Cedric Verstraeten
96f6bcb1dd test file in wrong directory 2023-11-03 13:18:00 +01:00
Cedric Verstraeten
860077a3eb turn off linting for: jsx-a11y/control-has-associated-label 2023-11-02 08:28:26 +01:00
Cedric Verstraeten
8be9343314 upgrade joy issues with audio codec (wrong FFMPEG version) 2023-11-02 08:15:32 +01:00
Cedric Verstraeten
dac04fbb57 upgrade joy for https://github.com/kerberos-io/agent/issues/105 2023-11-01 22:03:48 +01:00
Cedric Verstraeten
b9acf4c150 hot fix: factory needs to override encryption settings 2023-10-24 22:50:55 +02:00
Chaitanya110703
6608018f86 doc(README): remove typo 2023-10-24 21:25:45 +05:30
18 changed files with 539 additions and 118 deletions

View File

@@ -10,7 +10,7 @@ ENV GOSUMDB=off
##########################################
# Installing some additional dependencies.
RUN apt-get upgrade -y && apt-get update && apt-get install -y --no-install-recommends \
RUN apt-get upgrade -y && apt-get update && apt-get install -y --fix-missing --no-install-recommends \
git build-essential cmake pkg-config unzip libgtk2.0-dev \
curl ca-certificates libcurl4-openssl-dev libssl-dev libjpeg62-turbo-dev && \
rm -rf /var/lib/apt/lists/*

View File

@@ -29,7 +29,7 @@ Kerberos Agent is an isolated and scalable video (surveillance) management agent
## :thinking: Prerequisites
- An IP camera which supports a RTSP H264 encoded stream,
- (or) a USB camera, Raspberry Pi camera or other camera, that [you can tranform to a valid RTSP H264 stream](https://github.com/kerberos-io/camera-to-rtsp).
- (or) a USB camera, Raspberry Pi camera or other camera, that [you can transform to a valid RTSP H264 stream](https://github.com/kerberos-io/camera-to-rtsp).
- Any hardware (ARMv6, ARMv7, ARM64, AMD) that can run a binary or container, for example: a Raspberry Pi, NVidia Jetson, Intel NUC, a VM, Bare metal machine or a full blown Kubernetes cluster.
## :video_camera: Is my camera working?

View File

@@ -2,7 +2,8 @@ module github.com/kerberos-io/agent/machinery
go 1.19
// replace github.com/kerberos-io/joy4 v1.0.57 => ../../../../github.com/kerberos-io/joy4
//replace github.com/kerberos-io/joy4 v1.0.60 => ../../../../github.com/kerberos-io/joy4
// replace github.com/kerberos-io/onvif v0.0.6 => ../../../../github.com/kerberos-io/onvif
require (
@@ -20,11 +21,12 @@ require (
github.com/gin-contrib/pprof v1.4.0
github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2
github.com/gin-gonic/gin v1.8.2
github.com/gofrs/uuid v3.2.0+incompatible
github.com/golang-jwt/jwt/v4 v4.4.3
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.58
github.com/kerberos-io/joy4 v1.0.63
github.com/kerberos-io/onvif v0.0.7
github.com/minio/minio-go/v6 v6.0.57
github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e
@@ -36,6 +38,7 @@ require (
github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.9
github.com/tevino/abool v1.2.0
github.com/zaf/g711 v0.0.0-20220109202201-cf0017bf0359
go.mongodb.org/mongo-driver v1.7.5
gopkg.in/DataDog/dd-trace-go.v1 v1.46.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
@@ -72,7 +75,6 @@ require (
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/go-stack/stack v1.8.0 // indirect
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.3 // indirect
github.com/golang/snappy v0.0.4 // indirect

View File

@@ -264,8 +264,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.58 h1:R8EECSF+bG7o2yHC6cX/lF77Z+bDVGl6OioLZ3+5MN4=
github.com/kerberos-io/joy4 v1.0.58/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
github.com/kerberos-io/joy4 v1.0.63 h1:1VucVzE+WojpWWVKxpVpjmnArnZ2XIQSqn9Sldm6PrE=
github.com/kerberos-io/joy4 v1.0.63/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
github.com/kerberos-io/onvif v0.0.7 h1:LIrXjTH7G2W9DN69xZeJSB0uS3W1+C3huFO8kTqx7/A=
github.com/kerberos-io/onvif v0.0.7/go.mod h1:Hr2dJOH2LM5SpYKk17gYZ1CMjhGhUl+QlT5kwYogrW0=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -471,6 +471,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zaf/g711 v0.0.0-20220109202201-cf0017bf0359 h1:P9yeMx2iNJxJqXEwLtMjSwWcD2a0AlFmFByeosMZhLM=
github.com/zaf/g711 v0.0.0-20220109202201-cf0017bf0359/go.mod h1:ySLGJD8AQluMQuu5JDvfJrwsBra+8iX1jFsKS8KfB2I=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI=

View File

@@ -231,7 +231,7 @@ loop:
log.Log.Debug("HandleHeartBeat: stopping as Offline is enabled.")
} else {
url := config.HeartbeatURI
hubURI := config.HeartbeatURI
key := ""
username := ""
vaultURI := ""
@@ -247,98 +247,115 @@ loop:
// This is the new way ;)
if config.HubURI != "" {
url = config.HubURI + "/devices/heartbeat"
hubURI = config.HubURI + "/devices/heartbeat"
}
if config.HubKey != "" {
key = config.HubKey
}
if key != "" {
// Check if we have a friendly name or not.
name := config.Name
if config.FriendlyName != "" {
name = config.FriendlyName
}
// Check if we have a friendly name or not.
name := config.Name
if config.FriendlyName != "" {
name = config.FriendlyName
}
// Get some system information
// like the uptime, hostname, memory usage, etc.
system, _ := GetSystemInfo()
// Get some system information
// like the uptime, hostname, memory usage, etc.
system, _ := GetSystemInfo()
// We will formated the uptime to a human readable format
// this will be used on Kerberos Hub: Uptime -> 1 day and 2 hours.
uptimeFormatted := uptimeStart.Format("2006-01-02 15:04:05")
uptimeString := carbon.Parse(uptimeFormatted).DiffForHumans()
uptimeString = strings.ReplaceAll(uptimeString, "ago", "")
// Check if the agent is running inside a cluster (Kerberos Factory) or as
// an open source agent
isEnterprise := false
if os.Getenv("DEPLOYMENT") == "factory" || os.Getenv("MACHINERY_ENVIRONMENT") == "kubernetes" {
isEnterprise = true
}
// Do the same for boottime
bootTimeFormatted := time.Unix(int64(system.BootTime), 0).Format("2006-01-02 15:04:05")
boottimeString := carbon.Parse(bootTimeFormatted).DiffForHumans()
boottimeString = strings.ReplaceAll(boottimeString, "ago", "")
// Congert to string
macs, _ := json.Marshal(system.MACs)
ips, _ := json.Marshal(system.IPs)
cameraConnected := "true"
if !communication.CameraConnected {
cameraConnected = "false"
}
// We'll check which mode is enabled for the camera.
onvifEnabled := "false"
onvifZoom := "false"
onvifPanTilt := "false"
onvifPresets := "false"
var onvifPresetsList []byte
if config.Capture.IPCamera.ONVIFXAddr != "" {
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
hasBackChannel := "false"
if communication.HasBackChannel {
hasBackChannel = "true"
}
// We will formated the uptime to a human readable format
// this will be used on Kerberos Hub: Uptime -> 1 day and 2 hours.
uptimeFormatted := uptimeStart.Format("2006-01-02 15:04:05")
uptimeString := carbon.Parse(uptimeFormatted).DiffForHumans()
uptimeString = strings.ReplaceAll(uptimeString, "ago", "")
// Do the same for boottime
bootTimeFormatted := time.Unix(int64(system.BootTime), 0).Format("2006-01-02 15:04:05")
boottimeString := carbon.Parse(bootTimeFormatted).DiffForHumans()
boottimeString = strings.ReplaceAll(boottimeString, "ago", "")
// We'll check which mode is enabled for the camera.
onvifEnabled := "false"
onvifZoom := "false"
onvifPanTilt := "false"
onvifPresets := "false"
var onvifPresetsList []byte
if config.Capture.IPCamera.ONVIFXAddr != "" {
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
if err == nil {
configurations, err := onvif.GetPTZConfigurationsFromDevice(device)
if err == nil {
configurations, err := onvif.GetPTZConfigurationsFromDevice(device)
if err == nil {
onvifEnabled = "true"
_, canZoom, canPanTilt := onvif.GetPTZFunctionsFromDevice(configurations)
if canZoom {
onvifZoom = "true"
}
if canPanTilt {
onvifPanTilt = "true"
}
// Try to read out presets
presets, err := onvif.GetPresetsFromDevice(device)
if err == nil && len(presets) > 0 {
onvifPresets = "true"
onvifPresetsList, err = json.Marshal(presets)
if err != nil {
log.Log.Error("HandleHeartBeat: error while marshalling presets: " + err.Error())
onvifPresetsList = []byte("[]")
}
} else {
if err != nil {
log.Log.Error("HandleHeartBeat: error while getting presets: " + err.Error())
} else {
log.Log.Debug("HandleHeartBeat: no presets found.")
}
onvifEnabled = "true"
_, canZoom, canPanTilt := onvif.GetPTZFunctionsFromDevice(configurations)
if canZoom {
onvifZoom = "true"
}
if canPanTilt {
onvifPanTilt = "true"
}
// Try to read out presets
presets, err := onvif.GetPresetsFromDevice(device)
if err == nil && len(presets) > 0 {
onvifPresets = "true"
onvifPresetsList, err = json.Marshal(presets)
if err != nil {
log.Log.Error("HandleHeartBeat: error while marshalling presets: " + err.Error())
onvifPresetsList = []byte("[]")
}
} else {
log.Log.Error("HandleHeartBeat: error while getting PTZ configurations: " + err.Error())
if err != nil {
log.Log.Error("HandleHeartBeat: error while getting presets: " + err.Error())
} else {
log.Log.Debug("HandleHeartBeat: no presets found.")
}
onvifPresetsList = []byte("[]")
}
} else {
log.Log.Error("HandleHeartBeat: error while connecting to ONVIF device: " + err.Error())
log.Log.Error("HandleHeartBeat: error while getting PTZ configurations: " + err.Error())
onvifPresetsList = []byte("[]")
}
} else {
log.Log.Debug("HandleHeartBeat: ONVIF is not enabled.")
log.Log.Error("HandleHeartBeat: error while connecting to ONVIF device: " + err.Error())
onvifPresetsList = []byte("[]")
}
} else {
log.Log.Debug("HandleHeartBeat: ONVIF is not enabled.")
onvifPresetsList = []byte("[]")
}
// Check if the agent is running inside a cluster (Kerberos Factory) or as
// an open source agent
isEnterprise := false
if os.Getenv("DEPLOYMENT") == "factory" || os.Getenv("MACHINERY_ENVIRONMENT") == "kubernetes" {
isEnterprise = true
var client *http.Client
if os.Getenv("AGENT_TLS_INSECURE") == "true" {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: tr}
} else {
client = &http.Client{}
}
// Congert to string
macs, _ := json.Marshal(system.MACs)
ips, _ := json.Marshal(system.IPs)
cameraConnected := "true"
if communication.CameraConnected == false {
cameraConnected = "false"
}
// We need a hub URI and hub public key before we will send a heartbeat
if hubURI != "" && key != "" {
var object = fmt.Sprintf(`{
"key" : "%s",
@@ -370,29 +387,19 @@ loop:
"onvif_presets": "%s",
"onvif_presets_list": %s,
"cameraConnected": "%s",
"hasBackChannel": "%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, onvifZoom, onvifPanTilt, onvifPresets, onvifPresetsList, cameraConnected)
}`, 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, onvifZoom, onvifPanTilt, onvifPresets, onvifPresetsList, cameraConnected, hasBackChannel)
var jsonStr = []byte(object)
buffy := bytes.NewBuffer(jsonStr)
req, _ := http.NewRequest("POST", url, buffy)
req, _ := http.NewRequest("POST", hubURI, buffy)
req.Header.Set("Content-Type", "application/json")
var client *http.Client
if os.Getenv("AGENT_TLS_INSECURE") == "true" {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: tr}
} else {
client = &http.Client{}
}
resp, err := client.Do(req)
if resp != nil {
resp.Body.Close()
@@ -406,27 +413,68 @@ loop:
}
log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Hub.")
}
// If we have a Kerberos Vault connected, we will also send some analytics
// to that service.
vaultURI = config.KStorage.URI
if vaultURI != "" {
buffy = bytes.NewBuffer(jsonStr)
req, _ = http.NewRequest("POST", vaultURI+"/devices/heartbeat", buffy)
req.Header.Set("Content-Type", "application/json")
resp, err = client.Do(req)
if resp != nil {
resp.Body.Close()
}
if err == nil && resp.StatusCode == 200 {
log.Log.Info("HandleHeartBeat: (200) Heartbeat received by Kerberos Vault.")
} else {
log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Vault.")
}
}
} else {
log.Log.Error("HandleHeartBeat: Disabled as we do not have a public key defined.")
}
// If we have a Kerberos Vault connected, we will also send some analytics
// to that service.
vaultURI = config.KStorage.URI
if vaultURI != "" {
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",
"onvif_zoom" : "%s",
"onvif_pantilt" : "%s",
"onvif_presets": "%s",
"onvif_presets_list": %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, onvifZoom, onvifPanTilt, onvifPresets, onvifPresetsList, cameraConnected)
var jsonStr = []byte(object)
buffy := bytes.NewBuffer(jsonStr)
req, _ := http.NewRequest("POST", vaultURI+"/devices/heartbeat", buffy)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if resp != nil {
resp.Body.Close()
}
if err == nil && resp.StatusCode == 200 {
log.Log.Info("HandleHeartBeat: (200) Heartbeat received by Kerberos Vault.")
} else {
log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Vault.")
}
}
}
// This will check if we need to stop the thread,
@@ -780,7 +828,7 @@ func VerifyPersistence(c *gin.Context, configDirectory string) {
"_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4"
// Open test-480p.mp4
file, err := os.Open(configDirectory + "/test-480p.mp4")
file, err := os.Open(configDirectory + "/data/test-480p.mp4")
if err != nil {
msg := "VerifyPersistence: error reading test-480p.mp4: " + err.Error()
log.Log.Error(msg)

View File

@@ -0,0 +1,80 @@
package components
import (
"bufio"
"fmt"
"os"
"time"
"github.com/kerberos-io/agent/machinery/src/log"
"github.com/kerberos-io/agent/machinery/src/models"
"github.com/kerberos-io/joy4/av"
"github.com/zaf/g711"
)
func GetBackChannelAudioCodec(streams []av.CodecData, communication *models.Communication) av.AudioCodecData {
for _, stream := range streams {
if stream.Type().IsAudio() {
if stream.Type().String() == "PCM_MULAW" {
pcmuCodec := stream.(av.AudioCodecData)
if pcmuCodec.IsBackChannel() {
communication.HasBackChannel = true
return pcmuCodec
}
}
}
}
return nil
}
func WriteAudioToBackchannel(infile av.DemuxCloser, streams []av.CodecData, communication *models.Communication) {
log.Log.Info("WriteAudioToBackchannel: looking for backchannel audio codec")
pcmuCodec := GetBackChannelAudioCodec(streams, communication)
if pcmuCodec != nil {
log.Log.Info("WriteAudioToBackchannel: found backchannel audio codec")
length := 0
channel := pcmuCodec.GetIndex() * 2 // This is the same calculation as Interleaved property in the SDP file.
for audio := range communication.HandleAudio {
// Encode PCM to MULAW
var bufferUlaw []byte
for _, v := range audio.Data {
b := g711.EncodeUlawFrame(v)
bufferUlaw = append(bufferUlaw, b)
}
infile.Write(bufferUlaw, channel, uint32(length))
length = (length + len(bufferUlaw)) % 65536
time.Sleep(128 * time.Millisecond)
}
}
log.Log.Info("WriteAudioToBackchannel: finished")
}
func WriteFileToBackChannel(infile av.DemuxCloser) {
// Do the warmup!
file, err := os.Open("./audiofile.bye")
if err != nil {
fmt.Println("WriteFileToBackChannel: error opening audiofile.bye file")
}
defer file.Close()
// Read file into buffer
reader := bufio.NewReader(file)
buffer := make([]byte, 1024)
count := 0
for {
_, err := reader.Read(buffer)
if err != nil {
break
}
// Send to backchannel
fmt.Println(buffer)
infile.Write(buffer, 2, uint32(count))
count = count + 1024
time.Sleep(128 * time.Millisecond)
}
}

View File

@@ -11,6 +11,8 @@ import (
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/kerberos-io/joy4/cgo/ffmpeg"
//"github.com/youpy/go-wav"
"github.com/kerberos-io/agent/machinery/src/capture"
"github.com/kerberos-io/agent/machinery/src/cloud"
"github.com/kerberos-io/agent/machinery/src/computervision"
@@ -244,6 +246,9 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu
go capture.HandleSubStream(subInfile, subQueue, communication)
}
// Handle processing of audio
communication.HandleAudio = make(chan models.AudioDataPartial)
// Handle processing of motion
communication.HandleMotion = make(chan models.MotionDataPartial, 1)
if subStreamEnabled {
@@ -285,6 +290,10 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu
// If we reach this point, we have a working RTSP connection.
communication.CameraConnected = true
// We might have a camera with audio backchannel enabled.
// Check if we have a stream with a backchannel and is PCMU encoded.
go WriteAudioToBackchannel(infile, streams, communication)
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This will go into a blocking state, once this channel is triggered
// the agent will cleanup and restart.
@@ -328,6 +337,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu
}
close(communication.HandleMotion)
communication.HandleMotion = nil
close(communication.HandleAudio)
communication.HandleAudio = nil
// 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.")

View File

@@ -162,6 +162,12 @@ func OpenConfig(configDirectory string, configuration *models.Configuration) {
conjungo.Merge(&s3, configuration.CustomConfig.S3, opts)
configuration.Config.S3 = &s3
// Merge Encryption settings
var encryption models.Encryption
conjungo.Merge(&encryption, configuration.GlobalConfig.Encryption, opts)
conjungo.Merge(&encryption, configuration.CustomConfig.Encryption, opts)
configuration.Config.Encryption = &encryption
// Merge timetable manually because it's an array
configuration.Config.Timetable = configuration.CustomConfig.Timetable

View File

@@ -0,0 +1,6 @@
package models
type AudioDataPartial struct {
Timestamp int64 `json:"timestamp" bson:"timestamp"`
Data []int16 `json:"data" bson:"data"`
}

View File

@@ -22,6 +22,7 @@ type Communication struct {
HandleStream chan string
HandleSubStream chan string
HandleMotion chan MotionDataPartial
HandleAudio chan AudioDataPartial
HandleUpload chan string
HandleHeartBeat chan string
HandleLiveSD chan int64
@@ -38,4 +39,5 @@ type Communication struct {
SubDecoder *ffmpeg.VideoDecoder
Image string
CameraConnected bool
HasBackChannel bool
}

View File

@@ -107,6 +107,12 @@ type Payload struct {
Value map[string]interface{} `json:"value"`
}
// We received a audio input
type AudioPayload struct {
Timestamp int64 `json:"timestamp"` // timestamp of the recording request.
Data []int16 `json:"data"`
}
// We received a recording request, we'll send it to the motion handler.
type RecordPayload struct {
Timestamp int64 `json:"timestamp"` // timestamp of the recording request.

View File

@@ -229,6 +229,8 @@ func MQTTListenerHandler(mqttClient mqtt.Client, hubKey string, configDirectory
switch payload.Action {
case "record":
go HandleRecording(mqttClient, hubKey, payload, configuration, communication)
case "get-audio-backchannel":
go HandleAudio(mqttClient, hubKey, payload, configuration, communication)
case "get-ptz-position":
go HandleGetPTZPosition(mqttClient, hubKey, payload, configuration, communication)
case "update-ptz-position":
@@ -268,6 +270,23 @@ func HandleRecording(mqttClient mqtt.Client, hubKey string, payload models.Paylo
}
}
func HandleAudio(mqttClient mqtt.Client, hubKey string, payload models.Payload, configuration *models.Configuration, communication *models.Communication) {
value := payload.Value
// Convert map[string]interface{} to AudioPayload
jsonData, _ := json.Marshal(value)
var audioPayload models.AudioPayload
json.Unmarshal(jsonData, &audioPayload)
if audioPayload.Timestamp != 0 {
audioDataPartial := models.AudioDataPartial{
Timestamp: audioPayload.Timestamp,
Data: audioPayload.Data,
}
communication.HandleAudio <- audioDataPartial
}
}
func HandleGetPTZPosition(mqttClient mqtt.Client, hubKey string, payload models.Payload, configuration *models.Configuration, communication *models.Communication) {
value := payload.Value
@@ -457,14 +476,9 @@ func HandleReceiveHDCandidates(mqttClient mqtt.Client, hubKey string, payload mo
if receiveHDCandidatesPayload.Timestamp != 0 {
if communication.CameraConnected {
// Register candidate channel
key := configuration.Config.Key + "/" + receiveHDCandidatesPayload.SessionID
channel := webrtc.CandidateArrays[key]
if channel == nil {
channel = make(chan string)
webrtc.CandidateArrays[key] = channel
}
log.Log.Info("HandleReceiveHDCandidates: " + receiveHDCandidatesPayload.Candidate)
channel <- receiveHDCandidatesPayload.Candidate
webrtc.RegisterCandidates(key, receiveHDCandidatesPayload)
} else {
log.Log.Info("HandleReceiveHDCandidates: received candidate, but camera is not connected.")
}

View File

@@ -87,6 +87,21 @@ func (w WebRTC) CreateOffer(sd []byte) pionWebRTC.SessionDescription {
return offer
}
func RegisterCandidates(key string, candidate models.ReceiveHDCandidatesPayload) {
// Set lock
CandidatesMutex.Lock()
defer CandidatesMutex.Unlock()
channel := CandidateArrays[key]
if channel == nil {
channel = make(chan string)
CandidateArrays[key] = channel
}
log.Log.Info("HandleReceiveHDCandidates: " + candidate.Candidate)
channel <- candidate.Candidate
}
func InitializeWebRTCConnection(configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, videoTrack *pionWebRTC.TrackLocalStaticSample, audioTrack *pionWebRTC.TrackLocalStaticSample, handshake models.RequestHDStreamPayload, candidates chan string) {
config := configuration.Config
@@ -145,6 +160,9 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati
peerConnection.OnICEConnectionStateChange(func(connectionState pionWebRTC.ICEConnectionState) {
if connectionState == pionWebRTC.ICEConnectionStateDisconnected {
CandidatesMutex.Lock()
defer CandidatesMutex.Unlock()
atomic.AddInt64(&peerConnectionCount, -1)
peerConnections[handshake.SessionID] = nil
close(candidates)

View File

@@ -25,6 +25,7 @@
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/control-has-associated-label": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/label-has-associated-control": [

View File

@@ -23,7 +23,7 @@
},
"dashboard": {
"title": "Dashboard",
"heading": "Overview of your video surveilance",
"heading": "Overview of your video surveillance",
"number_of_days": "Number of days",
"total_recordings": "Total recordings",
"connected": "Connected",
@@ -151,7 +151,7 @@
"stun_turn_description_webrtc": "Forward h264 stream through MQTT",
"stun_turn_transcode": "Transcode stream",
"stun_turn_description_transcode": "Convert stream to a lower resolution",
"stun_turn_downscale": "Downscale resolution (in % or original resolution)",
"stun_turn_downscale": "Downscale resolution (in % of original resolution)",
"mqtt": "MQTT",
"description_mqtt": "A MQTT broker is used to communicate from",
"description2_mqtt": "to the Kerberos Agent, to achieve for example livestreaming or ONVIF (PTZ) capabilities.",

View File

@@ -0,0 +1,224 @@
{
"breadcrumb": {
"watch_recordings": "Смотреть записи",
"configure": "Настроить"
},
"buttons": {
"save": "Сохранить",
"verify_connection": "Проверить подключение"
},
"navigation": {
"profile": "Профиль",
"admin": "admin",
"management": "Управление",
"dashboard": "Панель",
"recordings": "Записи",
"settings": "Настройки",
"help_support": "Помощь & Поддержка",
"swagger": "Swagger API",
"documentation": "Документация",
"ui_library": "UI Библиотека",
"layout": "Язык & Макет ",
"choose_language": "Выбрать язык"
},
"dashboard": {
"title": "Панель",
"heading": "Обзор системы видеонаблюдения",
"number_of_days": "Количество дней",
"total_recordings": "Всего записей",
"connected": "Подключён",
"not_connected": "Не подключён",
"offline_mode": "Оффлайн режим",
"latest_events": "Последние события",
"configure_connection": "Настроить подключение",
"no_events": "Нет событий",
"no_events_description": "Записи не найдены, убедитесь, что ваш Kerberos Agent правильно настроен.",
"motion_detected": "Обнаружено движение",
"live_view": "Прямая трансляция",
"loading_live_view": "Загрузка трансляции",
"loading_live_view_description": "Подождите, мы загружаем сюда изображение в реальном времени. Если вы не настроили подключение камеры, обновите его на страницах настроек.",
"time": "Время",
"description": "Описание",
"name": "Название"
},
"recordings": {
"title": "Записи",
"heading": "Все ваши записи в одном месте",
"search_media": "Поиск записи"
},
"settings": {
"title": "Настройки",
"heading": "Обзор настроек камеры и агента",
"submenu": {
"all": "Все",
"overview": "Обзор",
"camera": "Камера",
"recording": "Запись",
"streaming": "Потоковое вещание",
"conditions": "Условия",
"persistence": "Хранилище"
},
"info": {
"kerberos_hub_demo": "Посмотрите на демо, чтобы увидеть Kerberos Hub в действии!",
"configuration_updated_success": "Настройки успешно обновлены.",
"configuration_updated_error": "При сохранении что-то пошло не так.",
"verify_hub": "Проверка настроек Kerberos Hub.",
"verify_hub_success": "Настройки Kerberos Hub успешно проверены.",
"verify_hub_error": "Что-то пошло не так при проверке концентратора Kerberos Hub",
"verify_persistence": "Проверка настроек хранилища.",
"verify_persistence_success": "Настройки хранилища успешно проверены.",
"verify_persistence_error": "Что-то пошло не так при проверке хранилища",
"verify_camera": "Проверка настроек камеры.",
"verify_camera_success": "Настройки камеры успешно проверены.",
"verify_camera_error": "Что-то пошло не так при проверке настроек камеры",
"verify_onvif": "Проверка настроек ONVIF.",
"verify_onvif_success": "Настройки ONVIF успешно проверены.",
"verify_onvif_error": "Что-то пошло не так при проверке настроек ONVIF"
},
"overview": {
"general": "Главная",
"description_general": "Общие настройки Kerberos Agent",
"key": "Ключ",
"camera_name": "Название камеры",
"timezone": "Часовой пояс",
"select_timezone": "Выберите часовой пояс",
"advanced_configuration": "Расширенные настройки",
"description_advanced_configuration": "Расширенные настройки для включения или отключения определенных частей Kerberos Agent",
"offline_mode": "Автономный режим",
"description_offline_mode": "Отключить весь исходящий трафик",
"encryption": "Шифрование",
"description_encryption": "Включите шифрование для всего исходящего трафика. MQTT-сообщения и/или записи будут зашифрованы с использованием AES-256. Для подписи используется закрытый ключ.",
"encryption_enabled": "Включить шифрование MQTT",
"description_encryption_enabled": "Включает шифрование для всех сообщений MQTT.",
"encryption_recordings_enabled": "Включить шифрование записей",
"description_encryption_recordings_enabled": "Включает шифрование для всех записей.",
"encryption_fingerprint": "Отпечаток",
"encryption_privatekey": "Закрытый ключ",
"encryption_symmetrickey": "Симметричный ключ"
},
"camera": {
"camera": "Камера",
"description_camera": "Настройки камеры необходимы для установки соединения с выбранной камерой.",
"only_h264": "В настоящее время поддерживаются только потоки H264 RTSP.",
"rtsp_url": "Адрес основного потока RTSP",
"rtsp_h264": "Подключение к камере по протоколу H264 RTSP.",
"sub_rtsp_url": "Адрес дополнительного потока RTSP (используется для прямой трансляции)",
"sub_rtsp_h264": "Дополнительное RTSP-соединение с низким разрешением камеры.",
"onvif": "ONVIF",
"description_onvif": "Учетные данные для связи по протоколу ONVIF. Они используются для PTZ или других возможностей, предоставляемых камерой.",
"onvif_xaddr": "ONVIF xaddr",
"onvif_username": "ONVIF пользователь",
"onvif_password": "ONVIF пароль",
"verify_connection": "Проверка основного соединения",
"verify_sub_connection": "Проверка дополнительного подключения"
},
"recording": {
"recording": "Запись",
"description_recording": "Укажите, как вы хотите вести запись. Непрерывная круглосуточная запись или запись по движению.",
"continuous_recording": "Непрерывная запись",
"description_continuous_recording": "Осуществлять 24/7 запись или запись по движению.",
"max_duration": "максимальная продолжительность видео (секунд)",
"description_max_duration": "Максимальная продолжительность записи.",
"pre_recording": "Предзапись (секунд)",
"description_pre_recording": "Секунд до наступления события.",
"post_recording": "Записывать после (секунд)",
"description_post_recording": "Секунд после наступления события.",
"threshold": "Уровень срабатывания записи (пикселей)",
"description_threshold": "Количество пикселей, измененных для записи",
"autoclean": "Автоочистка",
"description_autoclean": "Укажите, может ли Kerberos Agent очищать записи при достижении определенного объема памяти (МБ). При этом по достижении указанной емкости будут удаляться самые старые записи.",
"autoclean_enable": "Включить автоматическую очистку",
"autoclean_description_enable": "При достижении емкости удаляется самая старая запись.",
"autoclean_max_directory_size": "Максимальный размер каталога (МБ)",
"autoclean_description_max_directory_size": "Максимальное количество хранимых мегабайт записей.",
"fragmentedrecordings": "Фрагментированные записи",
"description_fragmentedrecordings": "Когда записи фрагментированы, они подходят для HLS-потока. При включении контейнер MP4 будет выглядеть несколько иначе.",
"fragmentedrecordings_enable": "Включить фрагментацию",
"fragmentedrecordings_description_enable": "Фрагментированные записи необходимы для HLS.",
"fragmentedrecordings_duration": "продолжительность фрагмента",
"fragmentedrecordings_description_duration": "Продолжительность одного фрагмента."
},
"streaming": {
"stun_turn": "STUN/TURN для WebRTC",
"description_stun_turn": "Для организации трансляций в полном разрешении мы используем технологию WebRTC. Одной из ключевых возможностей является функция ICE-candidate, которая позволяет обходить NAT, используя концепции STUN/TURN.",
"stun_server": "STUN сервер",
"turn_server": "TURN сервер",
"turn_username": "Имя пользователя",
"turn_password": "Пароль",
"stun_turn_forward": "Переадресация и транскодирование",
"stun_turn_description_forward": "Оптимизация и усовершенствование связи TURN/STUN.",
"stun_turn_webrtc": "Переадресация на WebRTC-брокера",
"stun_turn_description_webrtc": "Передача потока h264 через MQTT",
"stun_turn_transcode": "Транскодирование потока",
"stun_turn_description_transcode": "Преобразование потока в меньшее разрешение",
"stun_turn_downscale": "Уменьшение разрешения (в % от исходного разрешения)",
"mqtt": "MQTT",
"description_mqtt": "Брокер MQTT используется для обмена данными с",
"description2_mqtt": "к Kerberos Agent, чтобы, например, получить возможность трансляции видео или ONVIF (PTZ).",
"mqtt_brokeruri": "Адрес брокера",
"mqtt_username": "Имя пользователя",
"mqtt_password": "Пароль"
},
"conditions": {
"timeofinterest": "Время интереса",
"description_timeofinterest": "Производить запись только в определенные временные интервалы (в зависимости от часового пояса).",
"timeofinterest_enabled": "Включено",
"timeofinterest_description_enabled": "Если эта функция включена, то можно указать временные окна",
"sunday": "Воскресенье",
"monday": "Понедельник",
"tuesday": "Вторник",
"wednesday": "Среда",
"thursday": "Четверг",
"friday": "Пятница",
"saturday": "Суббота",
"externalcondition": "Внешнее условия",
"description_externalcondition": "В зависимости от внешнего веб-сервиса запись может быть включена или отключена.",
"regionofinterest": "Область интереса",
"description_regionofinterest": "Если задать одну или несколько областей, то движение будет отслеживаться только в заданных областях."
},
"persistence": {
"kerberoshub": "Kerberos Hub",
"description_kerberoshub": "Kerberos Agent'ы могут отправлять heartbeat сообщения в центральный",
"description2_kerberoshub": "узел. Heartbeat и другая необходимая информация синхронизируются с Kerberos Hub для отображения информации о видеоландшафте в реальном времени.",
"persistence": "Хранилище",
"saasoffering": "Kerberos Hub (SAAS предложение)",
"description_persistence": "Возможность хранения записей - это начало всего. Вы можете выбрать один из наших вариантов",
"description2_persistence": ", или стороннего провайдера",
"select_persistence": "Выберите хранилище",
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
"kerberoshub_description_proxyurl": "Конечная точка Proxy для загрузки записей.",
"kerberoshub_apiurl": "Kerberos Hub API URL",
"kerberoshub_description_apiurl": "Конечная точка API для загрузки записей.",
"kerberoshub_publickey": "Открытый ключ",
"kerberoshub_description_publickey": "Открытый ключ, присвоенный вашей учетной записи Kerberos Hub.",
"kerberoshub_privatekey": "Закрытый ключ",
"kerberoshub_description_privatekey": "Закрытый ключ, присвоенный вашей учетной записи Kerberos Hub.",
"kerberoshub_site": "Сайт",
"kerberoshub_description_site": "Идентификатор сайта, к которому принадлежат агенты Kerberos (Agent) в Kerberos Hub.",
"kerberoshub_region": "Регион",
"kerberoshub_description_region": "Регион, в котором хранятся наши записи.",
"kerberoshub_bucket": "Bucket",
"kerberoshub_description_bucket": "Bucket, в котором мы храним наши записи.",
"kerberoshub_username": "Имя пользователя/каталог (должно соответствовать имени пользователя в Kerberos Hub)",
"kerberoshub_description_username": "Имя пользователя вашей учетной записи Kerberos Hub.",
"kerberosvault_apiurl": "Kerberos Vault API URL",
"kerberosvault_description_apiurl": "The Kerberos Vault API",
"kerberosvault_provider": "Провайдер",
"kerberosvault_description_provider": "Провайдер, которому будут отправляться ваши записи.",
"kerberosvault_directory": "Каталог (должен совпадать с именем пользователя в Kerberos Hub)",
"kerberosvault_description_directory": "Подкаталог, в котором будут храниться записи у вашего провайдера.",
"kerberosvault_accesskey": "Ключ доступа",
"kerberosvault_description_accesskey": "Ключ доступа вашей учетной записи Kerberos Vault.",
"kerberosvault_secretkey": "Секретный ключ",
"kerberosvault_description_secretkey": "Секретный ключ учетной записи Kerberos Vault.",
"dropbox_directory": "Каталог",
"dropbox_description_directory": "Подкаталог, в котором будут храниться записи в вашем аккаунте Dropbox.",
"dropbox_accesstoken": "Токен доступа",
"dropbox_description_accesstoken": "Токен доступа вашего аккаунта/приложения Dropbox.",
"verify_connection": "Проверка соединения",
"remove_after_upload": "Как только записи будут загружены на какой-либо сервер, вы, возможно, захотите удалить их из локального агента Kerberos.",
"remove_after_upload_description": "Удаление записей после их успешной загрузки.",
"remove_after_upload_enabled": "Включено удаление при выгрузке"
}
}
}

View File

@@ -25,6 +25,7 @@ const LanguageSelect = () => {
es: { label: 'Español', dir: 'ltr', active: false },
ja: { label: '日本', dir: 'rlt', active: false },
hi: { label: 'हिंदी', dir: 'ltr', active: false },
ru: { label: 'Русский', dir: 'ltr', active: false },
};
if (!languageMap[selected]) {

View File

@@ -14,7 +14,7 @@ i18n
escapeValue: false,
},
load: 'languageOnly',
whitelist: ['de', 'en', 'nl', 'fr', 'pl', 'es', 'pt', 'ja'],
whitelist: ['de', 'en', 'nl', 'fr', 'pl', 'es', 'pt', 'ja', 'ru'],
});
export default i18n;