From 341a6a7fae39fe6cfc1880457d360151680c60fe Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 26 Nov 2023 00:07:53 +0100 Subject: [PATCH 01/81] refactoring the rtspclient to be able to swap out easily --- machinery/go.mod | 20 +- machinery/go.sum | 40 +-- machinery/src/capture/Golibrtsp.go | 239 +++++++++++++ machinery/src/capture/Joy4.go | 241 +++++++++++++ machinery/src/capture/RTSPClient.go | 33 ++ machinery/src/cloud/Cloud.go | 41 +-- machinery/src/components/Kerberos.go | 498 +++++++++++++++++---------- machinery/src/packets/buf.go | 69 ++++ machinery/src/packets/packet.go | 38 ++ machinery/src/packets/queue.go | 225 ++++++++++++ machinery/src/packets/stream.go | 33 ++ machinery/src/packets/timeline.go | 60 ++++ machinery/src/rtsp/client.go | 247 ------------- machinery/src/rtsp/h264decoder.go | 140 -------- machinery/src/rtsp/mp4muxer.go | 15 - machinery/src/rtsp/mpegtsmuxer.go | 173 ---------- machinery/src/webrtc/main.go | 35 +- 17 files changed, 1308 insertions(+), 839 deletions(-) create mode 100644 machinery/src/capture/Golibrtsp.go create mode 100644 machinery/src/capture/Joy4.go create mode 100644 machinery/src/capture/RTSPClient.go create mode 100644 machinery/src/packets/buf.go create mode 100644 machinery/src/packets/packet.go create mode 100644 machinery/src/packets/queue.go create mode 100644 machinery/src/packets/stream.go create mode 100644 machinery/src/packets/timeline.go delete mode 100644 machinery/src/rtsp/client.go delete mode 100644 machinery/src/rtsp/h264decoder.go delete mode 100644 machinery/src/rtsp/mp4muxer.go delete mode 100644 machinery/src/rtsp/mpegtsmuxer.go diff --git a/machinery/go.mod b/machinery/go.mod index 5bd543ed..4756d343 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -9,9 +9,7 @@ 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/bluenviron/gortsplib/v4 v4.6.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 @@ -31,7 +29,6 @@ require ( 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 @@ -56,8 +53,8 @@ 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/bluenviron/mediacommon v1.5.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/clbanning/mxj v1.8.4 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect @@ -80,7 +77,7 @@ require ( github.com/golang/snappy v0.0.4 // 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/google/uuid v1.4.0 // indirect github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -106,7 +103,8 @@ require ( github.com/pion/logging v0.2.2 // indirect 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/rtcp v1.2.12 // indirect + github.com/pion/rtp v1.8.3 // 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 @@ -128,12 +126,12 @@ require ( github.com/ziutek/mymysql v1.5.4 // indirect 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.9.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/net v0.18.0 // indirect golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 // 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/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect diff --git a/machinery/go.sum b/machinery/go.sum index d40a558d..2496b752 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -64,16 +64,12 @@ 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/bluenviron/gortsplib/v4 v4.6.0 h1:6z4ZEU9sl8H4rQMfxhd8z6FIGi/jmutU0R9HM6mZkMg= +github.com/bluenviron/gortsplib/v4 v4.6.0/go.mod h1:hb4lwJ+LMLfk0YbImTIrWLA8u3yWj77z4nzv2kxYLdk= +github.com/bluenviron/mediacommon v1.5.0 h1:lS0YKNo22ZOyCsYcLh3jn3TgUALqYw0f7RVwalC09vI= +github.com/bluenviron/mediacommon v1.5.0/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w= 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= @@ -236,8 +232,9 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210423192551-a2663126120b h1:l2YRhr+YLzmSp7KJMswRVk/lO5SwoFIcCLzJsVj+YPc= github.com/google/pprof v0.0.0-20210423192551-a2663126120b/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -361,10 +358,12 @@ github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01 github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= -github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc= github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= -github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= +github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM= +github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8= +github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= github.com/pion/sctp v1.8.5 h1:JCc25nghnXWOlSn3OVtEnA9PjQ2JsxQbG+CXZ1UkJKQ= github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= @@ -388,7 +387,6 @@ 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= @@ -430,7 +428,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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= @@ -500,8 +499,9 @@ golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -583,8 +583,8 @@ 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/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/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= 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= @@ -660,8 +660,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc 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/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/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= @@ -677,8 +677,8 @@ 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/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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= diff --git a/machinery/src/capture/Golibrtsp.go b/machinery/src/capture/Golibrtsp.go new file mode 100644 index 00000000..da6c7ca0 --- /dev/null +++ b/machinery/src/capture/Golibrtsp.go @@ -0,0 +1,239 @@ +package capture + +import ( + "context" + "image" + "strconv" + "sync" + "time" + + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" + "github.com/bluenviron/mediacommon/pkg/codecs/h264" + "github.com/kerberos-io/agent/machinery/src/log" + "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/packets" + "github.com/pion/rtp" +) + +// Implements the RTSPClient interface. +type Golibrtsp struct { + RTSPClient + Url string + WithBackChannel bool + + Client gortsplib.Client + Media *description.Media + Forma *format.H264 + Streams []packets.Stream + + DecoderMutex *sync.Mutex + Decoder *rtph264.Decoder + //FrameDecoder *h264Decoder +} + +// Connect to the RTSP server. +func (g *Golibrtsp) Connect(ctx context.Context) (err error) { + + g.Client = gortsplib.Client{} + + // parse URL + u, err := base.ParseURL(g.Url) + if err != nil { + panic(err) + } + + // connect to the server + err = g.Client.Start(u.Scheme, u.Host) + if err != nil { + panic(err) + } + + // find published medias + desc, _, err := g.Client.Describe(u) + if err != nil { + panic(err) + } + + // find the H264 media and format + var forma *format.H264 + medi := desc.FindFormat(&forma) + if medi == nil { + panic("media not found") + } + g.Media = medi + g.Forma = forma + + g.Streams = append(g.Streams, packets.Stream{ + Name: forma.Codec(), + IsVideo: true, + IsAudio: false, + SPS: forma.SPS, + PPS: forma.PPS, + }) + + // setup RTP/H264 -> H264 decoder + rtpDec, err := forma.CreateDecoder() + if err != nil { + panic(err) + } + g.Decoder = rtpDec + + // setup H264 -> raw frames decoder + /*frameDec, err := newH264Decoder() + if err != nil { + panic(err) + } + g.FrameDecoder = frameDec + + // if SPS and PPS are present into the SDP, send them to the decoder + if forma.SPS != nil { + frameDec.decode(forma.SPS) + } + if forma.PPS != nil { + frameDec.decode(forma.PPS) + }*/ + + // setup a single media + _, err = g.Client.Setup(desc.BaseURL, medi, 0, 0) + if err != nil { + panic(err) + } + + return +} + +// Start the RTSP client, and start reading packets. +func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { + log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") + + // called when a RTP packet arrives + g.Client.OnPacketRTP(g.Media, g.Forma, func(rtppkt *rtp.Packet) { + + // This will check if we need to stop the thread, + // because of a reconfiguration. + select { + case <-communication.HandleStream: + return + default: + } + + //og.Log.Info("RTSPClient(Golibrtsp).Start(): " + "read packet from stream: " + strconv.Itoa(len(pkt.Payload)) + " bytes") + if len(rtppkt.Payload) > 0 { + + // extract access units from RTP packets + au, err := g.Decoder.Decode(rtppkt) + if err != nil { + if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + } + return + } + + isKeyFrame := h264.IDRPresent(au) + + // Conver to packet. + pkt := packets.Packet{ + IsKeyFrame: isKeyFrame, + Idx: 0, + CompositionTime: time.Duration(rtppkt.Timestamp), + Time: time.Duration(rtppkt.Timestamp), + Data: rtppkt.Payload, + Header: packets.Header{ + Version: rtppkt.Version, + Padding: rtppkt.Padding, + Extension: rtppkt.Extension, + Marker: rtppkt.Marker, + PayloadType: rtppkt.PayloadType, + SequenceNumber: rtppkt.SequenceNumber, + Timestamp: rtppkt.Timestamp, + SSRC: rtppkt.SSRC, + CSRC: rtppkt.CSRC, + }, + PaddingSize: rtppkt.PaddingSize, + } + + queue.WritePacket(pkt) + + /*for _, nalu := range au { + // convert NALUs into RGBA frames + img, err := g.FrameDecoder.decode(nalu) + if err != nil { + panic(err) + } + fmt.Println(img) + }*/ + + // This will check if we need to stop the thread, + // because of a reconfiguration. + select { + case <-communication.HandleStream: + return + default: + } + + if isKeyFrame { + // Increment packets, so we know the device + // is not blocking. + r := communication.PackageCounter.Load().(int64) + log.Log.Info("RTSPClient(Golibrtsp).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + communication.PackageCounter.Store((r + 1) % 1000) + communication.LastPacketTimer.Store(time.Now().Unix()) + } + } + }) + + // Play the stream. + _, err = g.Client.Play(nil) + if err != nil { + panic(err) + } + + return +} + +// Decode a packet to an image. +func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { + return image.YCbCr{}, nil +} + +// Get a list of streams from the RTSP server. +func (j *Golibrtsp) GetStreams() ([]packets.Stream, error) { + var streams []packets.Stream + for _, stream := range j.Streams { + streams = append(streams, stream) + } + return streams, nil +} + +// Get a list of video streams from the RTSP server. +func (j *Golibrtsp) GetVideoStreams() ([]packets.Stream, error) { + var videoStreams []packets.Stream + for _, stream := range j.Streams { + if stream.IsVideo { + videoStreams = append(videoStreams, stream) + } + } + return videoStreams, nil +} + +// Get a list of audio streams from the RTSP server. +func (j *Golibrtsp) GetAudioStreams() ([]packets.Stream, error) { + var audioStreams []packets.Stream + for _, stream := range j.Streams { + if stream.IsAudio { + audioStreams = append(audioStreams, stream) + } + } + return audioStreams, nil +} + +// Close the connection to the RTSP server. +func (g *Golibrtsp) Close() error { + // Close the demuxer. + g.Client.Close() + return nil +} diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go new file mode 100644 index 00000000..45573e28 --- /dev/null +++ b/machinery/src/capture/Joy4.go @@ -0,0 +1,241 @@ +package capture + +import ( + "context" + "image" + "strconv" + "sync" + "time" + + "github.com/kerberos-io/agent/machinery/src/log" + "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/packets" + "github.com/kerberos-io/joy4/av" + "github.com/kerberos-io/joy4/av/avutil" + "github.com/kerberos-io/joy4/cgo/ffmpeg" + "github.com/kerberos-io/joy4/codec/h264parser" + "github.com/kerberos-io/joy4/format" +) + +// Implements the RTSPClient interface. +type Joy4 struct { + RTSPClient + Url string + WithBackChannel bool + + Demuxer av.DemuxCloser + Streams []packets.Stream + + DecoderMutex *sync.Mutex + Decoder *ffmpeg.VideoDecoder + Frame *ffmpeg.VideoFrame +} + +// Connect to the RTSP server. +func (j *Joy4) Connect(ctx context.Context) (err error) { + + // Register all formats and codecs. + format.RegisterAll() + + // Try with backchannel first (if variable is set to true) + // If set to true, it will try to open the stream with a backchannel + // If fails we will try again (see below). + infile, err := avutil.Open(ctx, j.Url, j.WithBackChannel) + if err == nil { + s, err := infile.Streams() + if err == nil && len(s) > 0 { + j.Decoder = &ffmpeg.VideoDecoder{} + var streams []packets.Stream + for _, str := range s { + stream := packets.Stream{ + Name: str.Type().String(), + IsVideo: str.Type().IsVideo(), + IsAudio: str.Type().IsAudio(), + } + if stream.IsVideo { + num, denum := str.(av.VideoCodecData).Framerate() + stream.Num = num + stream.Denum = denum + width := str.(av.VideoCodecData).Width() + stream.Width = width + height := str.(av.VideoCodecData).Height() + stream.Height = height + + if stream.Name == "H264" { + stream.PPS = str.(h264parser.CodecData).PPS() + stream.SPS = str.(h264parser.CodecData).SPS() + } + + // Specific to Joy4, we need to create a decoder. + codec := str.(av.VideoCodecData) + ffmpeg.NewVideoDecoder(j.Decoder, codec) + err := ffmpeg.NewVideoDecoder(j.Decoder, codec) + if err != nil { + log.Log.Error("RTSPClient(JOY4).Connect(): " + err.Error()) + } + } + streams = append(streams, stream) + } + j.Demuxer = infile + j.Streams = streams + } else { + // Try again without backchannel + log.Log.Info("OpenRTSP: trying without backchannel") + j.WithBackChannel = false + infile, err := avutil.Open(ctx, j.Url, j.WithBackChannel) + if err == nil { + var streams []packets.Stream + for _, str := range s { + stream := packets.Stream{ + Name: str.Type().String(), + IsVideo: str.Type().IsVideo(), + IsAudio: str.Type().IsAudio(), + } + if stream.IsVideo { + num, denum := str.(av.VideoCodecData).Framerate() + stream.Num = num + stream.Denum = denum + width := str.(av.VideoCodecData).Width() + stream.Width = width + height := str.(av.VideoCodecData).Height() + stream.Height = height + + if stream.Name == "H264" { + stream.PPS = str.(h264parser.CodecData).PPS() + stream.SPS = str.(h264parser.CodecData).SPS() + } + + // Specific to Joy4, we need to create a decoder. + codec := str.(av.VideoCodecData) + ffmpeg.NewVideoDecoder(j.Decoder, codec) + err := ffmpeg.NewVideoDecoder(j.Decoder, codec) + if err != nil { + log.Log.Error("RTSPClient(JOY4).Connect(): " + err.Error()) + } + } + streams = append(streams, stream) + } + j.Demuxer = infile + j.Streams = streams + } + } + } + + // Create a single frame used for decoding. + j.Frame = ffmpeg.AllocVideoFrame() + + // Iniatlise the mutex. + j.DecoderMutex = &sync.Mutex{} + + return +} + +// Start the RTSP client, and start reading packets. +func (j *Joy4) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { + log.Log.Debug("RTSPClient(JOY4).Start(): started") +loop: + for { + // This will check if we need to stop the thread, + // because of a reconfiguration. + select { + case <-communication.HandleStream: + break loop + default: + } + + var avpkt av.Packet + if avpkt, err = j.Demuxer.ReadPacket(); err != nil { // sometimes this throws an end of file.. + log.Log.Error("RTSPClient(JOY4).Start(): " + err.Error()) + time.Sleep(1 * time.Second) + } + + log.Log.Info("RTSPClient(JOY4).Start(): " + "read packet from stream: " + strconv.Itoa(int(avpkt.Idx)) + " " + strconv.Itoa(len(avpkt.Data)) + " bytes") + + // Could be that a decode is throwing errors. + if len(avpkt.Data) > 0 { + + // Conver to packet. + pkt := packets.Packet{ + IsKeyFrame: avpkt.IsKeyFrame, + Idx: int8(avpkt.Idx), + CompositionTime: avpkt.CompositionTime, + Time: avpkt.Time, + Data: avpkt.Data, + } + + queue.WritePacket(pkt) + + // This will check if we need to stop the thread, + // because of a reconfiguration. + select { + case <-communication.HandleStream: + break loop + default: + } + + if pkt.IsKeyFrame { + + // Increment packets, so we know the device + // is not blocking. + r := communication.PackageCounter.Load().(int64) + log.Log.Info("RTSPClient(JOY4).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + communication.PackageCounter.Store((r + 1) % 1000) + communication.LastPacketTimer.Store(time.Now().Unix()) + } + } + } + + queue.Close() + log.Log.Debug("RTSPClient(JOY4).Start(): done") + + return +} + +// Decode a packet to an image. +func (j *Joy4) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { + j.DecoderMutex.Lock() + _, err := j.Decoder.Decode(j.Frame, pkt.Data) + j.DecoderMutex.Unlock() + return j.Frame.Image, err +} + +// Get a list of streams from the RTSP server. +func (j *Joy4) GetStreams() ([]packets.Stream, error) { + var streams []packets.Stream + for _, stream := range j.Streams { + streams = append(streams, stream) + } + return streams, nil +} + +// Get a list of video streams from the RTSP server. +func (j *Joy4) GetVideoStreams() ([]packets.Stream, error) { + var videoStreams []packets.Stream + for _, stream := range j.Streams { + if stream.IsVideo { + videoStreams = append(videoStreams, stream) + } + } + return videoStreams, nil +} + +// Get a list of audio streams from the RTSP server. +func (j *Joy4) GetAudioStreams() ([]packets.Stream, error) { + var audioStreams []packets.Stream + for _, stream := range j.Streams { + if stream.IsAudio { + audioStreams = append(audioStreams, stream) + } + } + return audioStreams, nil +} + +// Close the connection to the RTSP server. +func (j *Joy4) Close() error { + // Cleanup the frame. + j.Frame.Free() + // Close the decoder. + j.Decoder.Close() + // Close the demuxer. + return j.Demuxer.Close() +} diff --git a/machinery/src/capture/RTSPClient.go b/machinery/src/capture/RTSPClient.go new file mode 100644 index 00000000..590a30e5 --- /dev/null +++ b/machinery/src/capture/RTSPClient.go @@ -0,0 +1,33 @@ +package capture + +import ( + "context" + "image" + + "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/packets" +) + +// RTSPClient is a interface that abstracts the RTSP client implementation. +type RTSPClient interface { + // Connect to the RTSP server. + Connect(ctx context.Context) error + + // Start the RTSP client, and start reading packets. + Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) error + + // Decode a packet into a image. + DecodePacket(pkt packets.Packet) (image.YCbCr, error) + + // Close the connection to the RTSP server. + Close() error + + // Get a list of streams from the RTSP server. + GetStreams() ([]packets.Stream, error) + + // Get a list of video streams from the RTSP server. + GetVideoStreams() ([]packets.Stream, error) + + // Get a list of audio streams from the RTSP server. + GetAudioStreams() ([]packets.Stream, error) +} diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index ca4372b5..46b93bd9 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -9,25 +9,23 @@ import ( "io/ioutil" "os" "strings" - "sync" "github.com/elastic/go-sysinfo" "github.com/gin-gonic/gin" "github.com/golang-module/carbon/v2" - "github.com/kerberos-io/joy4/av/pubsub" mqtt "github.com/eclipse/paho.mqtt.golang" - av "github.com/kerberos-io/joy4/av" - "github.com/kerberos-io/joy4/cgo/ffmpeg" "net/http" "strconv" "time" + "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/computervision" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/onvif" + "github.com/kerberos-io/agent/machinery/src/packets" "github.com/kerberos-io/agent/machinery/src/utils" "github.com/kerberos-io/agent/machinery/src/webrtc" ) @@ -489,23 +487,20 @@ loop: log.Log.Debug("HandleHeartBeat: finished") } -func HandleLiveStreamSD(livestreamCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { +func HandleLiveStreamSD(livestreamCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, rtspClient capture.RTSPClient) { - log.Log.Debug("HandleLiveStreamSD: started") + log.Log.Debug("cloud.HandleLiveStreamSD(): started") config := configuration.Config // If offline made is enabled, we will stop the thread. if config.Offline == "true" { - log.Log.Debug("HandleLiveStreamSD: stopping as Offline is enabled.") + log.Log.Debug("cloud.HandleLiveStreamSD(): stopping as Offline is enabled.") } else { // Check if we need to enable the live stream if config.Capture.Liveview != "false" { - // Allocate frame - frame := ffmpeg.AllocVideoFrame() - hubKey := "" if config.Cloud == "s3" && config.S3 != nil && config.S3.Publickey != "" { hubKey = config.S3.Publickey @@ -520,7 +515,7 @@ func HandleLiveStreamSD(livestreamCursor *pubsub.QueueCursor, configuration *mod lastLivestreamRequest := int64(0) var cursorError error - var pkt av.Packet + var pkt packets.Packet for cursorError == nil { pkt, cursorError = livestreamCursor.ReadPacket() @@ -536,10 +531,10 @@ func HandleLiveStreamSD(livestreamCursor *pubsub.QueueCursor, configuration *mod if now-lastLivestreamRequest > 3 { continue } - log.Log.Info("HandleLiveStreamSD: Sending base64 encoded images to MQTT.") - _, err := computervision.GetRawImage(frame, pkt, decoder, decoderMutex) + log.Log.Info("cloud.HandleLiveStreamSD(): Sending base64 encoded images to MQTT.") + img, err := rtspClient.DecodePacket(pkt) if err == nil { - bytes, _ := computervision.ImageToBytes(&frame.Image) + bytes, _ := computervision.ImageToBytes(&img) encoded := base64.StdEncoding.EncodeToString(bytes) valueMap := make(map[string]interface{}) @@ -555,23 +550,20 @@ func HandleLiveStreamSD(livestreamCursor *pubsub.QueueCursor, configuration *mod if err == nil { mqttClient.Publish("kerberos/hub/"+hubKey, 0, false, payload) } else { - log.Log.Info("HandleRequestConfig: something went wrong while sending acknowledge config to hub: " + string(payload)) + log.Log.Info("cloud.HandleLiveStreamSD(): something went wrong while sending acknowledge config to hub: " + string(payload)) } } } - // Cleanup the frame. - frame.Free() - } else { - log.Log.Debug("HandleLiveStreamSD: stopping as Liveview is disabled.") + log.Log.Debug("cloud.HandleLiveStreamSD(): stopping as Liveview is disabled.") } } - log.Log.Debug("HandleLiveStreamSD: finished") + log.Log.Debug("cloud.HandleLiveStreamSD(): finished") } -func HandleLiveStreamHD(livestreamCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, codecs []av.CodecData, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { +func HandleLiveStreamHD(livestreamCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, rtspClient capture.RTSPClient) { config := configuration.Config @@ -583,9 +575,10 @@ func HandleLiveStreamHD(livestreamCursor *pubsub.QueueCursor, configuration *mod if config.Capture.Liveview != "false" { // Should create a track here. - videoTrack := webrtc.NewVideoTrack(codecs) - audioTrack := webrtc.NewAudioTrack(codecs) - go webrtc.WriteToTrack(livestreamCursor, configuration, communication, mqttClient, videoTrack, audioTrack, codecs, decoder, decoderMutex) + streams, _ := rtspClient.GetStreams() + videoTrack := webrtc.NewVideoTrack(streams) + audioTrack := webrtc.NewAudioTrack(streams) + go webrtc.WriteToTrack(livestreamCursor, configuration, communication, mqttClient, videoTrack, audioTrack, rtspClient) if config.Capture.ForwardWebRTC == "true" { // We get a request with an offer, but we'll forward it. diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 2b2c7949..265d9752 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -2,27 +2,20 @@ package components import ( "context" - "runtime" "strconv" - "sync" "sync/atomic" "time" 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" configService "github.com/kerberos-io/agent/machinery/src/config" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/onvif" + "github.com/kerberos-io/agent/machinery/src/packets" routers "github.com/kerberos-io/agent/machinery/src/routers/mqtt" - "github.com/kerberos-io/joy4/av" - "github.com/kerberos-io/joy4/av/pubsub" "github.com/tevino/abool" ) @@ -66,8 +59,8 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm go ControlAgent(communication) // Create some global variables - decoder := &ffmpeg.VideoDecoder{} - subDecoder := &ffmpeg.VideoDecoder{} + //decoder := &ffmpeg.VideoDecoder{} + //subDecoder := &ffmpeg.VideoDecoder{} cameraSettings := &models.Camera{} // Handle heartbeats @@ -82,7 +75,8 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm for { // This will blocking until receiving a signal to be restarted, reconfigured, stopped, etc. - status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings, decoder, subDecoder) + //status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings, decoder, subDecoder) + status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings) if status == "stop" { break @@ -110,7 +104,7 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm log.Log.Debug("Bootstrap: finished") } -func RunAgent(configDirectory string, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, uptimeStart time.Time, cameraSettings *models.Camera, decoder *ffmpeg.VideoDecoder, subDecoder *ffmpeg.VideoDecoder) string { +func RunAgent(configDirectory string, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, uptimeStart time.Time, cameraSettings *models.Camera) string { log.Log.Debug("RunAgent: bootstrapping agent") config := configuration.Config @@ -121,241 +115,363 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Establishing the camera connection without backchannel if no substream rtspUrl := config.Capture.IPCamera.RTSP withBackChannel := true - infile, streams, err := capture.OpenRTSP(context.Background(), rtspUrl, withBackChannel) + rtspClient := &capture.Golibrtsp{ + Url: rtspUrl, + WithBackChannel: withBackChannel, + } - // We will initialise the camera settings object - // so we can check if the camera settings have changed, and we need - // to reload the decoders. + err := rtspClient.Connect(context.Background()) + if err != nil { + log.Log.Error("RunAgent: error connecting to RTSP stream: " + err.Error()) + time.Sleep(time.Second * 3) + return status + } - videoStream, _ := capture.GetVideoStream(streams) - if videoStream == nil { + // Get the video streams from the RTSP server. + videoStreams, err := rtspClient.GetVideoStreams() + if err != nil && len(videoStreams) == 0 { log.Log.Error("RunAgent: no video stream found, might be the wrong codec (we only support H264 for the moment)") time.Sleep(time.Second * 3) return status } - num, denum := videoStream.(av.VideoCodecData).Framerate() - width := videoStream.(av.VideoCodecData).Width() - height := videoStream.(av.VideoCodecData).Height() + // Get the video stream from the RTSP server. + videoStream := videoStreams[0] + + // Get some information from the video stream. + // num := videoStream.Num + //denum := videoStream.Denum + width := videoStream.Width + height := videoStream.Height // Set config values as well configuration.Config.Capture.IPCamera.Width = width configuration.Config.Capture.IPCamera.Height = height - var queue *pubsub.Queue - var subQueue *pubsub.Queue + var queue *packets.Queue + //var subQueue *packets.Queue - var decoderMutex sync.Mutex - var subDecoderMutex sync.Mutex + //var decoderMutex sync.Mutex + //var subDecoderMutex sync.Mutex + subStreamEnabled := false - if err == nil { + log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl) - log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl) + // Create a packet queue, which is filled by the HandleStream routing + // and consumed by all other routines: motion, livestream, etc. + if config.Capture.PreRecording <= 0 { + config.Capture.PreRecording = 1 + log.Log.Warning("RunAgent: Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10)) + } - // We might have a secondary rtsp url, so we might need to use that. - var subInfile av.DemuxCloser - var subStreams []av.CodecData - subStreamEnabled := false - subRtspUrl := config.Capture.IPCamera.SubRTSP - if subRtspUrl != "" && subRtspUrl != rtspUrl { - withBackChannel := false - subInfile, subStreams, err = capture.OpenRTSP(context.Background(), subRtspUrl, withBackChannel) // We'll try to enable backchannel for the substream. - if err == nil { - log.Log.Info("RunAgent: opened RTSP sub stream " + subRtspUrl) - subStreamEnabled = true - } + // TODO add the substream + another check if the resolution changed. - videoStream, _ := capture.GetVideoStream(subStreams) - if videoStream == nil { - log.Log.Error("RunAgent: no video substream found, might be the wrong codec (we only support H264 for the moment)") - time.Sleep(time.Second * 3) - return status - } + // We are creating a queue to store the RTSP frames in, these frames will be + // processed by the different consumers: motion detection, recording, etc. + queue = packets.NewQueue() + //communication.Queue = queue - width := videoStream.(av.VideoCodecData).Width() - height := videoStream.(av.VideoCodecData).Height() + queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). + log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) + queue.WriteHeader(videoStreams) - // Set config values as well - configuration.Config.Capture.IPCamera.Width = width - configuration.Config.Capture.IPCamera.Height = height - } + // Handle the camera stream + //go capture.HandleStream(infile, queue, communication) + go rtspClient.Start(context.Background(), queue, communication) - if cameraSettings.RTSP != rtspUrl || cameraSettings.SubRTSP != subRtspUrl || cameraSettings.Width != width || cameraSettings.Height != height || cameraSettings.Num != num || cameraSettings.Denum != denum || cameraSettings.Codec != videoStream.(av.VideoCodecData).Type() { + // Handle livestream SD (low resolution over MQTT) + if subStreamEnabled { + //livestreamCursor := subQueue.Latest() + //go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, rtspSubClient) + } else { + livestreamCursor := queue.Latest() + go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, rtspClient) + } - if cameraSettings.RTSP != "" && cameraSettings.SubRTSP != "" && cameraSettings.Initialized { - decoder.Close() - if subStreamEnabled { - subDecoder.Close() + // Handle livestream HD (high resolution over WEBRTC) + communication.HandleLiveHDHandshake = make(chan models.RequestHDStreamPayload, 1) + if subStreamEnabled { + //livestreamHDCursor := subQueue.Latest() + //go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, subStreams, subDecoder, &decoderMutex) + } else { + livestreamHDCursor := queue.Latest() + go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, rtspClient) + } + + // Handle Upload to cloud provider (Kerberos Hub, Kerberos Vault and others) + go cloud.HandleUpload(configDirectory, configuration, communication) + + // Handle ONVIF actions + go onvif.HandleONVIFActions(configuration, communication) + + // If we reach this point, we have a working RTSP connection. + communication.CameraConnected = true + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // This will go into a blocking state, once this channel is triggered + // the agent will cleanup and restart. + + status = <-communication.HandleBootstrap + + // If we reach this point, we are stopping the stream. + communication.CameraConnected = false + + // Cancel the main context, this will stop all the other goroutines. + (*communication.CancelContext)() + + // We will re open the configuration, might have changed :O! + configService.OpenConfig(configDirectory, configuration) + + // We will override the configuration with the environment variables + configService.OverrideWithEnvironmentVariables(configuration) + + // Here we are cleaning up everything! + if configuration.Config.Offline != "true" { + communication.HandleUpload <- "stop" + } + communication.HandleStream <- "stop" + if subStreamEnabled { + communication.HandleSubStream <- "stop" + } + + time.Sleep(time.Second * 3) + + queue.Close() + queue = nil + communication.Queue = nil + if subStreamEnabled { + //subInfile.Close() + //subInfile = nil + //subQueue.Close() + //subQueue = nil + communication.SubQueue = nil + } + //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.") + time.Sleep(time.Second * 3) + /* + + if err == nil { + + + + // We might have a secondary rtsp url, so we might need to use that. + var subInfile av.DemuxCloser + var subStreams []av.CodecData + subStreamEnabled := false + subRtspUrl := config.Capture.IPCamera.SubRTSP + if subRtspUrl != "" && subRtspUrl != rtspUrl { + withBackChannel := false + subInfile, subStreams, err = capture.OpenRTSP(context.Background(), subRtspUrl, withBackChannel) // We'll try to enable backchannel for the substream. + if err == nil { + log.Log.Info("RunAgent: opened RTSP sub stream " + subRtspUrl) + subStreamEnabled = true } + + videoStream, _ := capture.GetVideoStream(subStreams) + if videoStream == nil { + log.Log.Error("RunAgent: no video substream found, might be the wrong codec (we only support H264 for the moment)") + time.Sleep(time.Second * 3) + return status + } + + width := videoStream.(av.VideoCodecData).Width() + height := videoStream.(av.VideoCodecData).Height() + + // Set config values as well + configuration.Config.Capture.IPCamera.Width = width + configuration.Config.Capture.IPCamera.Height = height } - // At some routines we will need to decode the image. - // Make sure its properly locked as we only have a single decoder. - log.Log.Info("RunAgent: camera settings changed, reloading decoder") - capture.GetVideoDecoder(decoder, streams) - if subStreamEnabled { - capture.GetVideoDecoder(subDecoder, subStreams) + if cameraSettings.RTSP != rtspUrl || cameraSettings.SubRTSP != subRtspUrl || cameraSettings.Width != width || cameraSettings.Height != height || cameraSettings.Num != num || cameraSettings.Denum != denum || cameraSettings.Codec != videoStream.(av.VideoCodecData).Type() { + + if cameraSettings.RTSP != "" && cameraSettings.SubRTSP != "" && cameraSettings.Initialized { + decoder.Close() + if subStreamEnabled { + subDecoder.Close() + } + } + + // At some routines we will need to decode the image. + // Make sure its properly locked as we only have a single decoder. + log.Log.Info("RunAgent: camera settings changed, reloading decoder") + capture.GetVideoDecoder(decoder, streams) + if subStreamEnabled { + capture.GetVideoDecoder(subDecoder, subStreams) + } + + cameraSettings.RTSP = rtspUrl + cameraSettings.SubRTSP = subRtspUrl + cameraSettings.Width = width + cameraSettings.Height = height + cameraSettings.Framerate = float64(num) / float64(denum) + cameraSettings.Num = num + cameraSettings.Denum = denum + cameraSettings.Codec = videoStream.(av.VideoCodecData).Type() + cameraSettings.Initialized = true + + } else { + log.Log.Info("RunAgent: camera settings did not change, keeping decoder") } - cameraSettings.RTSP = rtspUrl - cameraSettings.SubRTSP = subRtspUrl - cameraSettings.Width = width - cameraSettings.Height = height - cameraSettings.Framerate = float64(num) / float64(denum) - cameraSettings.Num = num - cameraSettings.Denum = denum - cameraSettings.Codec = videoStream.(av.VideoCodecData).Type() - cameraSettings.Initialized = true + communication.Decoder = decoder + communication.SubDecoder = subDecoder + communication.DecoderMutex = &decoderMutex + communication.SubDecoderMutex = &subDecoderMutex - } else { - log.Log.Info("RunAgent: camera settings did not change, keeping decoder") - } + // Create a packet queue, which is filled by the HandleStream routing + // and consumed by all other routines: motion, livestream, etc. + if config.Capture.PreRecording <= 0 { + config.Capture.PreRecording = 1 + log.Log.Warning("RunAgent: Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10)) + } - communication.Decoder = decoder - communication.SubDecoder = subDecoder - communication.DecoderMutex = &decoderMutex - communication.SubDecoderMutex = &subDecoderMutex + // We are creating a queue to store the RTSP frames in, these frames will be + // processed by the different consumers: motion detection, recording, etc. + queue = pubsub.NewQueue() + communication.Queue = queue + queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). + log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) + queue.WriteHeader(streams) - // Create a packet queue, which is filled by the HandleStream routing - // and consumed by all other routines: motion, livestream, etc. - if config.Capture.PreRecording <= 0 { - config.Capture.PreRecording = 1 - log.Log.Warning("RunAgent: Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10)) - } + // We might have a substream, if so we'll create a seperate queue. + if subStreamEnabled { + log.Log.Info("RunAgent: Creating sub stream queue with SetMaxGopCount set to " + strconv.Itoa(int(1))) + subQueue = pubsub.NewQueue() + communication.SubQueue = subQueue + subQueue.SetMaxGopCount(1) + subQueue.WriteHeader(subStreams) + } - // We are creating a queue to store the RTSP frames in, these frames will be - // processed by the different consumers: motion detection, recording, etc. - queue = pubsub.NewQueue() - communication.Queue = queue - queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). - log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) - queue.WriteHeader(streams) - - // We might have a substream, if so we'll create a seperate queue. - if subStreamEnabled { - log.Log.Info("RunAgent: Creating sub stream queue with SetMaxGopCount set to " + strconv.Itoa(int(1))) - subQueue = pubsub.NewQueue() - communication.SubQueue = subQueue - subQueue.SetMaxGopCount(1) - subQueue.WriteHeader(subStreams) - } + // Handle the camera stream + go capture.HandleStream(infile, queue, communication) - // Handle the camera stream - go capture.HandleStream(infile, queue, communication) + // Handle the substream if enabled + if subStreamEnabled { + go capture.HandleSubStream(subInfile, subQueue, communication) + } - // Handle the substream if enabled - if subStreamEnabled { - go capture.HandleSubStream(subInfile, subQueue, communication) - } + // Handle processing of audio + communication.HandleAudio = make(chan models.AudioDataPartial) - // Handle processing of audio - communication.HandleAudio = make(chan models.AudioDataPartial) + // Handle processing of motion + communication.HandleMotion = make(chan models.MotionDataPartial, 1) + if subStreamEnabled { + motionCursor := subQueue.Latest() + go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex) + } else { + motionCursor := queue.Latest() + go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, decoder, &decoderMutex) + } - // Handle processing of motion - communication.HandleMotion = make(chan models.MotionDataPartial, 1) - if subStreamEnabled { - motionCursor := subQueue.Latest() - go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex) - } else { - motionCursor := queue.Latest() - go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, decoder, &decoderMutex) - } + // Handle livestream SD (low resolution over MQTT) + if subStreamEnabled { + livestreamCursor := subQueue.Latest() + go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex) + } else { + livestreamCursor := queue.Latest() + go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, decoder, &decoderMutex) + } - // Handle livestream SD (low resolution over MQTT) - if subStreamEnabled { - livestreamCursor := subQueue.Latest() - go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex) - } else { - livestreamCursor := queue.Latest() - go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, decoder, &decoderMutex) - } + // Handle livestream HD (high resolution over WEBRTC) + communication.HandleLiveHDHandshake = make(chan models.RequestHDStreamPayload, 1) + if subStreamEnabled { + livestreamHDCursor := subQueue.Latest() + go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, subStreams, subDecoder, &decoderMutex) + } else { + livestreamHDCursor := queue.Latest() + go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, streams, decoder, &decoderMutex) + } - // Handle livestream HD (high resolution over WEBRTC) - communication.HandleLiveHDHandshake = make(chan models.RequestHDStreamPayload, 1) - if subStreamEnabled { - livestreamHDCursor := subQueue.Latest() - go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, subStreams, subDecoder, &decoderMutex) - } else { - livestreamHDCursor := queue.Latest() - go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, streams, decoder, &decoderMutex) - } + // Handle recording, will write an mp4 to disk. + go capture.HandleRecordStream(queue, configDirectory, configuration, communication, streams) - // Handle recording, will write an mp4 to disk. - go capture.HandleRecordStream(queue, configDirectory, configuration, communication, streams) + // Handle Upload to cloud provider (Kerberos Hub, Kerberos Vault and others) + go cloud.HandleUpload(configDirectory, configuration, communication) - // Handle Upload to cloud provider (Kerberos Hub, Kerberos Vault and others) - go cloud.HandleUpload(configDirectory, configuration, communication) + // Handle ONVIF actions + go onvif.HandleONVIFActions(configuration, communication) - // Handle ONVIF actions - go onvif.HandleONVIFActions(configuration, communication) + // If we reach this point, we have a working RTSP connection. + communication.CameraConnected = true - // 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) - // 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. - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // This will go into a blocking state, once this channel is triggered - // the agent will cleanup and restart. + status = <-communication.HandleBootstrap - status = <-communication.HandleBootstrap + // If we reach this point, we are stopping the stream. + communication.CameraConnected = false - // If we reach this point, we are stopping the stream. - communication.CameraConnected = false + // Cancel the main context, this will stop all the other goroutines. + (*communication.CancelContext)() - // Cancel the main context, this will stop all the other goroutines. - (*communication.CancelContext)() + // We will re open the configuration, might have changed :O! + configService.OpenConfig(configDirectory, configuration) - // We will re open the configuration, might have changed :O! - configService.OpenConfig(configDirectory, configuration) + // We will override the configuration with the environment variables + configService.OverrideWithEnvironmentVariables(configuration) - // We will override the configuration with the environment variables - configService.OverrideWithEnvironmentVariables(configuration) + // Here we are cleaning up everything! + if configuration.Config.Offline != "true" { + communication.HandleUpload <- "stop" + } + communication.HandleStream <- "stop" + if subStreamEnabled { + communication.HandleSubStream <- "stop" + } - // Here we are cleaning up everything! - if configuration.Config.Offline != "true" { - communication.HandleUpload <- "stop" - } - communication.HandleStream <- "stop" - if subStreamEnabled { - communication.HandleSubStream <- "stop" - } + time.Sleep(time.Second * 3) - time.Sleep(time.Second * 3) + infile.Close() + infile = nil + queue.Close() + queue = nil + communication.Queue = nil + if subStreamEnabled { + subInfile.Close() + subInfile = nil + subQueue.Close() + subQueue = nil + communication.SubQueue = nil + } + 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.") + time.Sleep(time.Second * 3) - infile.Close() - infile = nil - queue.Close() - queue = nil - communication.Queue = nil - if subStreamEnabled { - subInfile.Close() - subInfile = nil - subQueue.Close() - subQueue = nil - communication.SubQueue = nil + } else { + log.Log.Error("Something went wrong while opening RTSP: " + err.Error()) + time.Sleep(time.Second * 3) } - 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.") - time.Sleep(time.Second * 3) + log.Log.Debug("RunAgent: finished") - } else { - log.Log.Error("Something went wrong while opening RTSP: " + err.Error()) + // Clean up, force garbage collection + runtime.GC()*/ + + // Close the connection to the RTSP server. + err = rtspClient.Close() + if err != nil { + log.Log.Error("RunAgent: error closing RTSP stream: " + err.Error()) time.Sleep(time.Second * 3) + return status } - log.Log.Debug("RunAgent: finished") - - // Clean up, force garbage collection - runtime.GC() - return status } diff --git a/machinery/src/packets/buf.go b/machinery/src/packets/buf.go new file mode 100644 index 00000000..fdb07485 --- /dev/null +++ b/machinery/src/packets/buf.go @@ -0,0 +1,69 @@ +package packets + +type Buf struct { + Head, Tail BufPos + pkts []Packet + Size int + Count int +} + +func NewBuf() *Buf { + return &Buf{ + pkts: make([]Packet, 64), + } +} + +func (self *Buf) Pop() Packet { + if self.Count == 0 { + panic("pktque.Buf: Pop() when count == 0") + } + + i := int(self.Head) & (len(self.pkts) - 1) + pkt := self.pkts[i] + self.pkts[i] = Packet{} + self.Size -= len(pkt.Data) + self.Head++ + self.Count-- + + return pkt +} + +func (self *Buf) grow() { + newpkts := make([]Packet, len(self.pkts)*2) + for i := self.Head; i.LT(self.Tail); i++ { + newpkts[int(i)&(len(newpkts)-1)] = self.pkts[int(i)&(len(self.pkts)-1)] + } + self.pkts = newpkts +} + +func (self *Buf) Push(pkt Packet) { + if self.Count == len(self.pkts) { + self.grow() + } + self.pkts[int(self.Tail)&(len(self.pkts)-1)] = pkt + self.Tail++ + self.Count++ + self.Size += len(pkt.Data) +} + +func (self *Buf) Get(pos BufPos) Packet { + return self.pkts[int(pos)&(len(self.pkts)-1)] +} + +func (self *Buf) IsValidPos(pos BufPos) bool { + return pos.GE(self.Head) && pos.LT(self.Tail) +} + +type BufPos int + +func (self BufPos) LT(pos BufPos) bool { + return self-pos < 0 +} + +func (self BufPos) GE(pos BufPos) bool { + return self-pos >= 0 +} + +func (self BufPos) GT(pos BufPos) bool { + return self-pos > 0 +} diff --git a/machinery/src/packets/packet.go b/machinery/src/packets/packet.go new file mode 100644 index 00000000..94128979 --- /dev/null +++ b/machinery/src/packets/packet.go @@ -0,0 +1,38 @@ +package packets + +import "time" + +// Packet stores compressed audio/video data. +type Packet struct { + Header Header // RTP header + PaddingSize byte + IsKeyFrame bool // video packet is key frame + Idx int8 // stream index in container format + CompositionTime time.Duration // packet presentation time minus decode time for H264 B-Frame + Time time.Duration // packet decode time + Data []byte // packet data + +} + +type Header struct { + Version uint8 + Padding bool + Extension bool + Marker bool + PayloadType uint8 + SequenceNumber uint16 + Timestamp uint32 + SSRC uint32 + CSRC []uint32 + ExtensionProfile uint16 + Extensions []Extension + + // Deprecated: will be removed in a future version. + PayloadOffset int +} + +// Extension RTP Header extension +type Extension struct { + id uint8 + payload []byte +} diff --git a/machinery/src/packets/queue.go b/machinery/src/packets/queue.go new file mode 100644 index 00000000..a0de7236 --- /dev/null +++ b/machinery/src/packets/queue.go @@ -0,0 +1,225 @@ +// Packege pubsub implements publisher-subscribers model used in multi-channel streaming. +package packets + +import ( + "io" + "sync" + "time" +) + +// time +// -----------------> +// +// V-A-V-V-A-V-V-A-V-V +// | | +// 0 5 10 +// head tail +// oldest latest +// + +// One publisher and multiple subscribers thread-safe packet buffer queue. +type Queue struct { + buf *Buf + head, tail int + lock *sync.RWMutex + cond *sync.Cond + curgopcount, maxgopcount int + streams []Stream + videoidx int + closed bool +} + +func NewQueue() *Queue { + q := &Queue{} + q.buf = NewBuf() + q.maxgopcount = 2 + q.lock = &sync.RWMutex{} + q.cond = sync.NewCond(q.lock.RLocker()) + q.videoidx = -1 + return q +} + +func (self *Queue) SetMaxGopCount(n int) { + self.lock.Lock() + self.maxgopcount = n + self.lock.Unlock() + return +} + +func (self *Queue) WriteHeader(streams []Stream) error { + self.lock.Lock() + + self.streams = streams + for i, stream := range streams { + if stream.IsVideo { + self.videoidx = i + } + } + self.cond.Broadcast() + + self.lock.Unlock() + + return nil +} + +func (self *Queue) WriteTrailer() error { + return nil +} + +// After Close() called, all QueueCursor's ReadPacket will return io.EOF. +func (self *Queue) Close() (err error) { + self.lock.Lock() + + self.closed = true + self.cond.Broadcast() + + // Close all QueueCursor's ReadPacket + for i := 0; i < self.buf.Size; i++ { + pkt := self.buf.Pop() + pkt.Data = nil + } + + self.lock.Unlock() + return +} + +func (self *Queue) GetSize() int { + return self.buf.Count +} + +// Put packet into buffer, old packets will be discared. +func (self *Queue) WritePacket(pkt Packet) (err error) { + self.lock.Lock() + + self.buf.Push(pkt) + if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame { + self.curgopcount++ + } + + for self.curgopcount >= self.maxgopcount && self.buf.Count > 1 { + pkt := self.buf.Pop() + if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame { + self.curgopcount-- + } + if self.curgopcount < self.maxgopcount { + break + } + } + //println("shrink", self.curgopcount, self.maxgopcount, self.buf.Head, self.buf.Tail, "count", self.buf.Count, "size", self.buf.Size) + + self.cond.Broadcast() + + self.lock.Unlock() + return +} + +type QueueCursor struct { + que *Queue + pos BufPos + gotpos bool + init func(buf *Buf, videoidx int) BufPos +} + +func (self *Queue) newCursor() *QueueCursor { + return &QueueCursor{ + que: self, + } +} + +// Create cursor position at latest packet. +func (self *Queue) Latest() *QueueCursor { + cursor := self.newCursor() + cursor.init = func(buf *Buf, videoidx int) BufPos { + return buf.Tail + } + return cursor +} + +// Create cursor position at oldest buffered packet. +func (self *Queue) Oldest() *QueueCursor { + cursor := self.newCursor() + cursor.init = func(buf *Buf, videoidx int) BufPos { + return buf.Head + } + return cursor +} + +// Create cursor position at specific time in buffered packets. +func (self *Queue) DelayedTime(dur time.Duration) *QueueCursor { + cursor := self.newCursor() + cursor.init = func(buf *Buf, videoidx int) BufPos { + i := buf.Tail - 1 + if buf.IsValidPos(i) { + end := buf.Get(i) + for buf.IsValidPos(i) { + if end.Time-buf.Get(i).Time > dur { + break + } + i-- + } + } + return i + } + return cursor +} + +// Create cursor position at specific delayed GOP count in buffered packets. +func (self *Queue) DelayedGopCount(n int) *QueueCursor { + cursor := self.newCursor() + cursor.init = func(buf *Buf, videoidx int) BufPos { + i := buf.Tail - 1 + if videoidx != -1 { + for gop := 0; buf.IsValidPos(i) && gop < n; i-- { + pkt := buf.Get(i) + if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame { + gop++ + } + } + } + return i + } + return cursor +} + +func (self *QueueCursor) Streams() (streams []Stream, err error) { + self.que.cond.L.Lock() + for self.que.streams == nil && !self.que.closed { + self.que.cond.Wait() + } + if self.que.streams != nil { + streams = self.que.streams + } else { + err = io.EOF + } + self.que.cond.L.Unlock() + return +} + +// ReadPacket will not consume packets in Queue, it's just a cursor. +func (self *QueueCursor) ReadPacket() (pkt Packet, err error) { + self.que.cond.L.Lock() + buf := self.que.buf + if !self.gotpos { + self.pos = self.init(buf, self.que.videoidx) + self.gotpos = true + } + for { + if self.pos.LT(buf.Head) { + self.pos = buf.Head + } else if self.pos.GT(buf.Tail) { + self.pos = buf.Tail + } + if buf.IsValidPos(self.pos) { + pkt = buf.Get(self.pos) + self.pos++ + break + } + if self.que.closed { + err = io.EOF + break + } + self.que.cond.Wait() + } + self.que.cond.L.Unlock() + return +} diff --git a/machinery/src/packets/stream.go b/machinery/src/packets/stream.go new file mode 100644 index 00000000..75b06f35 --- /dev/null +++ b/machinery/src/packets/stream.go @@ -0,0 +1,33 @@ +package packets + +type Stream struct { + // The name of the stream. + Name string + + // The URL of the stream. + URL string + + // Is the stream a video stream. + IsVideo bool + + // Is the stream a audio stream. + IsAudio bool + + // The width of the stream. + Width int + + // The height of the stream. + Height int + + // Num is the numerator of the framerate. + Num int + + // Denum is the denominator of the framerate. + Denum int + + // For H264, this is the sps. + SPS []byte + + // For H264, this is the pps. + PPS []byte +} diff --git a/machinery/src/packets/timeline.go b/machinery/src/packets/timeline.go new file mode 100644 index 00000000..eb13b469 --- /dev/null +++ b/machinery/src/packets/timeline.go @@ -0,0 +1,60 @@ +package packets + +import ( + "time" +) + +/* +pop push + + seg seg seg + |--------| |---------| |---| + 20ms 40ms 5ms +----------------- time --------------------> +headtm tailtm +*/ + +type tlSeg struct { + tm, dur time.Duration +} + +type Timeline struct { + segs []tlSeg + headtm time.Duration +} + +func (self *Timeline) Push(tm time.Duration, dur time.Duration) { + if len(self.segs) > 0 { + tail := self.segs[len(self.segs)-1] + diff := tm - (tail.tm + tail.dur) + if diff < 0 { + tm -= diff + } + } + self.segs = append(self.segs, tlSeg{tm, dur}) +} + +func (self *Timeline) Pop(dur time.Duration) (tm time.Duration) { + if len(self.segs) == 0 { + return self.headtm + } + + tm = self.segs[0].tm + for dur > 0 && len(self.segs) > 0 { + seg := &self.segs[0] + sub := dur + if seg.dur < sub { + sub = seg.dur + } + seg.dur -= sub + dur -= sub + seg.tm += sub + self.headtm += sub + if seg.dur == 0 { + copy(self.segs[0:], self.segs[1:]) + self.segs = self.segs[:len(self.segs)-1] + } + } + + return +} diff --git a/machinery/src/rtsp/client.go b/machinery/src/rtsp/client.go deleted file mode 100644 index 130d0a9c..00000000 --- a/machinery/src/rtsp/client.go +++ /dev/null @@ -1,247 +0,0 @@ -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 - } -} diff --git a/machinery/src/rtsp/h264decoder.go b/machinery/src/rtsp/h264decoder.go deleted file mode 100644 index 1093487e..00000000 --- a/machinery/src/rtsp/h264decoder.go +++ /dev/null @@ -1,140 +0,0 @@ -package rtsp - -import ( - "fmt" - "image" - "unsafe" -) - -// #cgo pkg-config: libavcodec libavutil libswscale -// #include -// #include -// #include -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 -} diff --git a/machinery/src/rtsp/mp4muxer.go b/machinery/src/rtsp/mp4muxer.go deleted file mode 100644 index 21c8e203..00000000 --- a/machinery/src/rtsp/mp4muxer.go +++ /dev/null @@ -1,15 +0,0 @@ -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 -} diff --git a/machinery/src/rtsp/mpegtsmuxer.go b/machinery/src/rtsp/mpegtsmuxer.go deleted file mode 100644 index 4d1bc030..00000000 --- a/machinery/src/rtsp/mpegtsmuxer.go +++ /dev/null @@ -1,173 +0,0 @@ -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 -} diff --git a/machinery/src/webrtc/main.go b/machinery/src/webrtc/main.go index 67ee4dcd..3f6830b5 100644 --- a/machinery/src/webrtc/main.go +++ b/machinery/src/webrtc/main.go @@ -10,14 +10,12 @@ import ( "sync/atomic" "time" + "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/joy4/av/pubsub" + "github.com/kerberos-io/agent/machinery/src/packets" mqtt "github.com/eclipse/paho.mqtt.golang" - av "github.com/kerberos-io/joy4/av" - "github.com/kerberos-io/joy4/cgo/ffmpeg" - h264parser "github.com/kerberos-io/joy4/codec/h264parser" pionWebRTC "github.com/pion/webrtc/v3" pionMedia "github.com/pion/webrtc/v3/pkg/media" ) @@ -282,21 +280,21 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati } } -func NewVideoTrack(codecs []av.CodecData) *pionWebRTC.TrackLocalStaticSample { +func NewVideoTrack(streams []packets.Stream) *pionWebRTC.TrackLocalStaticSample { var mimeType string mimeType = pionWebRTC.MimeTypeH264 outboundVideoTrack, _ := pionWebRTC.NewTrackLocalStaticSample(pionWebRTC.RTPCodecCapability{MimeType: mimeType}, "video", "pion124") return outboundVideoTrack } -func NewAudioTrack(codecs []av.CodecData) *pionWebRTC.TrackLocalStaticSample { +func NewAudioTrack(streams []packets.Stream) *pionWebRTC.TrackLocalStaticSample { var mimeType string - for _, codec := range codecs { - if codec.Type().String() == "OPUS" { + for _, stream := range streams { + if stream.Name == "OPUS" { mimeType = pionWebRTC.MimeTypeOpus - } else if codec.Type().String() == "PCM_MULAW" { + } else if stream.Name == "PCM_MULAW" { mimeType = pionWebRTC.MimeTypePCMU - } else if codec.Type().String() == "PCM_ALAW" { + } else if stream.Name == "PCM_ALAW" { mimeType = pionWebRTC.MimeTypePCMA } } @@ -304,7 +302,7 @@ func NewAudioTrack(codecs []av.CodecData) *pionWebRTC.TrackLocalStaticSample { 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) { +func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, videoTrack *pionWebRTC.TrackLocalStaticSample, audioTrack *pionWebRTC.TrackLocalStaticSample, rtspClient capture.RTSPClient) { config := configuration.Config @@ -315,10 +313,11 @@ func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Co // Later when we read a packet we need to figure out which track to send it to. videoIdx := -1 audioIdx := -1 - for i, codec := range codecs { - if codec.Type().String() == "H264" && videoIdx < 0 { + streams, _ := rtspClient.GetStreams() + for i, stream := range streams { + if stream.Name == "H264" && videoIdx < 0 { videoIdx = i - } else if (codec.Type().String() == "OPUS" || codec.Type().String() == "PCM_MULAW" || codec.Type().String() == "PCM_ALAW") && audioIdx < 0 { + } else if (stream.Name == "OPUS" || stream.Name == "PCM_MULAW" || stream.Name == "PCM_ALAW") && audioIdx < 0 { audioIdx = i } } @@ -338,12 +337,12 @@ func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Co } var cursorError error - var pkt av.Packet + var pkt packets.Packet var previousTime time.Duration start := false receivedKeyFrame := false - codecData := codecs[videoIdx] + stream := streams[videoIdx] lastKeepAlive := "0" peerCount := "0" @@ -422,9 +421,9 @@ func WriteToTrack(livestreamCursor *pubsub.QueueCursor, configuration *models.Co if pkt.IsKeyFrame { start = true pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - pkt.Data = append(codecData.(h264parser.CodecData).PPS(), pkt.Data...) + pkt.Data = append(stream.PPS, pkt.Data...) pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - pkt.Data = append(codecData.(h264parser.CodecData).SPS(), pkt.Data...) + pkt.Data = append(stream.SPS, pkt.Data...) pkt.Data = append(annexbNALUStartCode(), pkt.Data...) log.Log.Info("WriteToTrack: Sending keyframe") } From ac2b99a3dda96796cf70d0cc56c1e96d08321eae Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 26 Nov 2023 16:58:55 +0100 Subject: [PATCH 02/81] inherit from golibrtsp rtp.packet + fix the decoding for livestream + motion --- machinery/src/capture/Golibrtsp.go | 172 ++++++++++++++++++++++++----- machinery/src/packets/packet.go | 37 ++----- 2 files changed, 153 insertions(+), 56 deletions(-) diff --git a/machinery/src/capture/Golibrtsp.go b/machinery/src/capture/Golibrtsp.go index da6c7ca0..712d7f5d 100644 --- a/machinery/src/capture/Golibrtsp.go +++ b/machinery/src/capture/Golibrtsp.go @@ -1,11 +1,20 @@ package capture +// #cgo pkg-config: libavcodec libavutil libswscale +// #include +// #include +// #include +import "C" + import ( "context" + "fmt" "image" + "reflect" "strconv" "sync" "time" + "unsafe" "github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4/pkg/base" @@ -32,7 +41,7 @@ type Golibrtsp struct { DecoderMutex *sync.Mutex Decoder *rtph264.Decoder - //FrameDecoder *h264Decoder + FrameDecoder *h264Decoder } // Connect to the RTSP server. @@ -83,7 +92,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.Decoder = rtpDec // setup H264 -> raw frames decoder - /*frameDec, err := newH264Decoder() + frameDec, err := newH264Decoder() if err != nil { panic(err) } @@ -95,7 +104,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } if forma.PPS != nil { frameDec.decode(forma.PPS) - }*/ + } // setup a single media _, err = g.Client.Setup(desc.BaseURL, medi, 0, 0) @@ -137,36 +146,15 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // Conver to packet. pkt := packets.Packet{ - IsKeyFrame: isKeyFrame, - Idx: 0, - CompositionTime: time.Duration(rtppkt.Timestamp), - Time: time.Duration(rtppkt.Timestamp), - Data: rtppkt.Payload, - Header: packets.Header{ - Version: rtppkt.Version, - Padding: rtppkt.Padding, - Extension: rtppkt.Extension, - Marker: rtppkt.Marker, - PayloadType: rtppkt.PayloadType, - SequenceNumber: rtppkt.SequenceNumber, - Timestamp: rtppkt.Timestamp, - SSRC: rtppkt.SSRC, - CSRC: rtppkt.CSRC, - }, - PaddingSize: rtppkt.PaddingSize, + IsKeyFrame: isKeyFrame, + Packet: rtppkt, + AccessUnits: au, + Data: rtppkt.Payload, + Time: time.Duration(rtppkt.Timestamp), } queue.WritePacket(pkt) - /*for _, nalu := range au { - // convert NALUs into RGBA frames - img, err := g.FrameDecoder.decode(nalu) - if err != nil { - panic(err) - } - fmt.Println(img) - }*/ - // This will check if we need to stop the thread, // because of a reconfiguration. select { @@ -197,6 +185,23 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // Decode a packet to an image. func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { + accessUnits := pkt.AccessUnits + // decode access units + for _, nalu := range accessUnits { + img, err := g.FrameDecoder.decode(nalu) + + if err != nil { + return image.YCbCr{}, err + } + + // wait for a frame + if img.Bounds().Empty() { + log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") + continue + } + + return img, nil + } return image.YCbCr{}, nil } @@ -237,3 +242,112 @@ func (g *Golibrtsp) Close() error { g.Client.Close() return nil } + +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.YCbCr, error) { + nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...) + + // send NALU 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 image.YCbCr{}, nil + } + + // receive frame if available + res = C.avcodec_receive_frame(d.codecCtx, d.srcFrame) + if res < 0 { + return image.YCbCr{}, nil + } + + if res == 0 { + fr := d.srcFrame + w := int(fr.width) + h := int(fr.height) + ys := int(fr.linesize[0]) + cs := int(fr.linesize[1]) + + return image.YCbCr{ + Y: fromCPtr(unsafe.Pointer(fr.data[0]), ys*h), + Cb: fromCPtr(unsafe.Pointer(fr.data[1]), cs*h/2), + Cr: fromCPtr(unsafe.Pointer(fr.data[2]), cs*h/2), + YStride: ys, + CStride: cs, + SubsampleRatio: image.YCbCrSubsampleRatio420, + Rect: image.Rect(0, 0, w, h), + }, nil + } + + return image.YCbCr{}, nil +} + +func fromCPtr(buf unsafe.Pointer, size int) (ret []uint8) { + hdr := (*reflect.SliceHeader)((unsafe.Pointer(&ret))) + hdr.Cap = size + hdr.Len = size + hdr.Data = uintptr(buf) + return +} diff --git a/machinery/src/packets/packet.go b/machinery/src/packets/packet.go index 94128979..e056ccbd 100644 --- a/machinery/src/packets/packet.go +++ b/machinery/src/packets/packet.go @@ -1,11 +1,17 @@ package packets -import "time" +import ( + "time" -// Packet stores compressed audio/video data. + "github.com/pion/rtp" +) + +// Packet represents an RTP Packet type Packet struct { - Header Header // RTP header - PaddingSize byte + Packet *rtp.Packet + AccessUnits [][]byte + + // for JOY4 library IsKeyFrame bool // video packet is key frame Idx int8 // stream index in container format CompositionTime time.Duration // packet presentation time minus decode time for H264 B-Frame @@ -13,26 +19,3 @@ type Packet struct { Data []byte // packet data } - -type Header struct { - Version uint8 - Padding bool - Extension bool - Marker bool - PayloadType uint8 - SequenceNumber uint16 - Timestamp uint32 - SSRC uint32 - CSRC []uint32 - ExtensionProfile uint16 - Extensions []Extension - - // Deprecated: will be removed in a future version. - PayloadOffset int -} - -// Extension RTP Header extension -type Extension struct { - id uint8 - payload []byte -} From e241a03fc42ce28553bd9ed544a78f422610b525 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 26 Nov 2023 17:30:05 +0100 Subject: [PATCH 03/81] comment out unused code! --- machinery/src/webrtc/main.go | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/machinery/src/webrtc/main.go b/machinery/src/webrtc/main.go index 3f6830b5..d36e8df3 100644 --- a/machinery/src/webrtc/main.go +++ b/machinery/src/webrtc/main.go @@ -3,7 +3,6 @@ package webrtc import ( "encoding/base64" "encoding/json" - "fmt" "io" "strconv" "sync" @@ -392,27 +391,10 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C } } - if config.Capture.TranscodingWebRTC == "true" { - - /*decoderMutex.Lock() - decoder.SetFramerate(30, 1) - frame, err := decoder.Decode(pkt.Data) - decoderMutex.Unlock() - if err == nil && frame != nil && frame.Width() > 0 && frame.Height() > 0 { - var _outpkts []av.Packet - transcodingResolution := config.Capture.TranscodingResolution - newWidth := frame.Width() * int(transcodingResolution) / 100 - newHeight := frame.Height() * int(transcodingResolution) / 100 - encoder.SetResolution(newWidth, newHeight) - if _outpkts, err = encoder.Encode(frame); err != nil { - } - if len(_outpkts) > 0 { - pkt = _outpkts[0] - codecData, _ = encoder.CodecData() - } - }*/ - - } + //if config.Capture.TranscodingWebRTC == "true" { + // We will transcode the video + // TODO.. + //} switch int(pkt.Idx) { case videoIdx: @@ -431,14 +413,8 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C if start { sample := pionMedia.Sample{Data: pkt.Data, Duration: bufferDuration} if config.Capture.ForwardWebRTC == "true" { - samplePacket, err := json.Marshal(sample) - if err == nil { - // Write packets - topic := fmt.Sprintf("kerberos/webrtc/packets/%s", config.Key) - mqttClient.Publish(topic, 0, false, samplePacket) - } else { - log.Log.Info("WriteToTrack: Error marshalling frame, " + err.Error()) - } + // We will send the video to a remote peer + // TODO.. } else { if err := videoTrack.WriteSample(sample); err != nil && err != io.ErrClosedPipe { log.Log.Error("WriteToTrack: something went wrong while writing sample: " + err.Error()) From c6428d8c5a0510482a0c8b77495f1369679073bb Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 27 Nov 2023 17:05:55 +0100 Subject: [PATCH 04/81] Fix for WebRTC using new library had to encode nalu --- machinery/src/capture/Golibrtsp.go | 89 +++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/machinery/src/capture/Golibrtsp.go b/machinery/src/capture/Golibrtsp.go index 712d7f5d..13f75174 100644 --- a/machinery/src/capture/Golibrtsp.go +++ b/machinery/src/capture/Golibrtsp.go @@ -119,6 +119,8 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") + //dtsExtractor := h264.NewDTSExtractor() + // called when a RTP packet arrives g.Client.OnPacketRTP(g.Media, g.Forma, func(rtppkt *rtp.Packet) { @@ -133,24 +135,69 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati //og.Log.Info("RTSPClient(Golibrtsp).Start(): " + "read packet from stream: " + strconv.Itoa(len(pkt.Payload)) + " bytes") if len(rtppkt.Payload) > 0 { + // decode timestamp + pts, ok := g.Client.PacketPTS(g.Media, rtppkt) + if !ok { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + return + } + // extract access units from RTP packets - au, err := g.Decoder.Decode(rtppkt) - if err != nil { - if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + au, errDecode := g.Decoder.Decode(rtppkt) + if errDecode != nil { + if errDecode != rtph264.ErrNonStartingPacketAndNoPrevious && errDecode != rtph264.ErrMorePacketsNeeded { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + errDecode.Error()) + } + return + } + + // We'll need to read out a few things. + // prepend an AUD. This is required by some players + filteredAU := [][]byte{ + {byte(h264.NALUTypeAccessUnitDelimiter), 240}, + } + + nonIDRPresent := false + idrPresent := false + for _, nalu := range au { + typ := h264.NALUType(nalu[0] & 0x1F) + switch typ { + case h264.NALUTypeAccessUnitDelimiter: + continue + case h264.NALUTypeIDR: + idrPresent = true + + case h264.NALUTypeNonIDR: + nonIDRPresent = true } + filteredAU = append(filteredAU, nalu) + } + + au = filteredAU + + if len(au) <= 1 || (!nonIDRPresent && !idrPresent) { return } - isKeyFrame := h264.IDRPresent(au) + /*dts, errDts := dtsExtractor.Extract(au, pts) + if errDts != nil { + return + } + fmt.Println("DTS: ", dts)*/ // Conver to packet. + enc, err := h264.AnnexBMarshal(au) + if err != nil { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + return + } + pkt := packets.Packet{ - IsKeyFrame: isKeyFrame, + IsKeyFrame: idrPresent, Packet: rtppkt, AccessUnits: au, - Data: rtppkt.Payload, - Time: time.Duration(rtppkt.Timestamp), + Data: enc, + Time: pts, } queue.WritePacket(pkt) @@ -163,7 +210,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati default: } - if isKeyFrame { + if idrPresent { // Increment packets, so we know the device // is not blocking. r := communication.PackageCounter.Load().(int64) @@ -185,24 +232,14 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // Decode a packet to an image. func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { - accessUnits := pkt.AccessUnits - // decode access units - for _, nalu := range accessUnits { - img, err := g.FrameDecoder.decode(nalu) - - if err != nil { - return image.YCbCr{}, err - } - - // wait for a frame - if img.Bounds().Empty() { - log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") - continue - } - - return img, nil + img, err := g.FrameDecoder.decode(pkt.Data) + if err != nil { + return image.YCbCr{}, err } - return image.YCbCr{}, nil + if img.Bounds().Empty() { + log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") + } + return img, nil } // Get a list of streams from the RTSP server. From 55b1abe2439179fe532fb88149e0a48f23629762 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 29 Nov 2023 10:21:58 +0100 Subject: [PATCH 05/81] Add mp4 muxer, still some work to do --- machinery/go.mod | 31 +- machinery/go.sum | 58 +- .../capture/{Golibrtsp.go => Gortsplib.go} | 312 +- machinery/src/capture/main.go | 26 +- machinery/src/components/Kerberos.go | 7 +- machinery/src/mp4/mp4io/atoms.go | 3561 +++++++++++++++++ machinery/src/mp4/mp4io/gen/gen.go | 1057 +++++ machinery/src/mp4/mp4io/gen/pattern.go | 438 ++ machinery/src/mp4/mp4io/mp4io.go | 535 +++ machinery/src/mp4/muxer.go | 381 ++ machinery/src/mp4/stream.go | 58 + machinery/src/packets/packet.go | 7 +- machinery/src/utils/bits/bits.go | 118 + machinery/src/utils/bits/bits_test.go | 51 + machinery/src/utils/bits/bufio/bufio.go | 23 + machinery/src/utils/bits/golomb_reader.go | 89 + machinery/src/utils/bits/pio/pio.go | 3 + machinery/src/utils/bits/pio/reader.go | 91 + machinery/src/utils/bits/pio/vec.go | 69 + machinery/src/utils/bits/pio/vec_test.go | 22 + machinery/src/utils/bits/pio/writer.go | 89 + 21 files changed, 6861 insertions(+), 165 deletions(-) rename machinery/src/capture/{Golibrtsp.go => Gortsplib.go} (52%) create mode 100644 machinery/src/mp4/mp4io/atoms.go create mode 100644 machinery/src/mp4/mp4io/gen/gen.go create mode 100644 machinery/src/mp4/mp4io/gen/pattern.go create mode 100644 machinery/src/mp4/mp4io/mp4io.go create mode 100644 machinery/src/mp4/muxer.go create mode 100644 machinery/src/mp4/stream.go create mode 100644 machinery/src/utils/bits/bits.go create mode 100644 machinery/src/utils/bits/bits_test.go create mode 100644 machinery/src/utils/bits/bufio/bufio.go create mode 100644 machinery/src/utils/bits/golomb_reader.go create mode 100644 machinery/src/utils/bits/pio/pio.go create mode 100644 machinery/src/utils/bits/pio/reader.go create mode 100644 machinery/src/utils/bits/pio/vec.go create mode 100644 machinery/src/utils/bits/pio/vec_test.go create mode 100644 machinery/src/utils/bits/pio/writer.go diff --git a/machinery/go.mod b/machinery/go.mod index 4756d343..1fd45f45 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -10,6 +10,7 @@ require ( github.com/InVisionApp/conjungo v1.1.0 github.com/appleboy/gin-jwt/v2 v2.9.1 github.com/bluenviron/gortsplib/v4 v4.6.0 + github.com/bluenviron/mediacommon v1.5.1 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 @@ -18,7 +19,7 @@ require ( github.com/gin-contrib/cors v1.4.0 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/gin-gonic/gin v1.9.1 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 @@ -29,6 +30,7 @@ require ( 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.8.3 github.com/pion/webrtc/v3 v3.1.50 github.com/sirupsen/logrus v1.9.0 github.com/swaggo/files v1.0.0 @@ -54,24 +56,26 @@ require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/beevik/etree v1.1.0 // indirect - github.com/bluenviron/mediacommon v1.5.0 // indirect + github.com/bytedance/sonic v1.9.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/clbanning/mxj v1.8.4 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/elastic/go-windows v1.0.0 // indirect github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae // indirect github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/swag v0.19.15 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-stack/stack v1.8.0 // indirect - github.com/goccy/go-json v0.10.0 // indirect + github.com/goccy/go-json v0.10.2 // 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 @@ -83,18 +87,19 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.0 // indirect github.com/klauspost/cpuid v1.2.3 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kylelemons/go-gypsy v1.0.0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/lib/pq v1.10.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/minio/md5-simd v1.1.0 // indirect github.com/minio/sha256-simd v0.1.1 // indirect 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/pelletier/go-toml/v2 v2.0.8 // indirect github.com/philhofer/fwd v1.1.1 // indirect github.com/pion/datachannel v1.5.5 // indirect github.com/pion/dtls/v2 v2.1.5 // indirect @@ -104,7 +109,6 @@ require ( github.com/pion/mdns v0.0.5 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.12 // indirect - github.com/pion/rtp v1.8.3 // 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 @@ -118,7 +122,8 @@ require ( github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/tinylib/msgp v1.1.6 // indirect - github.com/ugorji/go/codec v1.2.7 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect @@ -126,6 +131,7 @@ require ( github.com/ziutek/mymysql v1.5.4 // indirect 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/arch v0.3.0 // indirect golang.org/x/crypto v0.15.0 // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 // indirect @@ -137,9 +143,10 @@ require ( 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 - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.42.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect inet.af/netaddr v0.0.0-20220617031823-097006376321 // indirect ) diff --git a/machinery/go.sum b/machinery/go.sum index 2496b752..81f19b47 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -68,14 +68,20 @@ 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/v4 v4.6.0 h1:6z4ZEU9sl8H4rQMfxhd8z6FIGi/jmutU0R9HM6mZkMg= github.com/bluenviron/gortsplib/v4 v4.6.0/go.mod h1:hb4lwJ+LMLfk0YbImTIrWLA8u3yWj77z4nzv2kxYLdk= -github.com/bluenviron/mediacommon v1.5.0 h1:lS0YKNo22ZOyCsYcLh3jn3TgUALqYw0f7RVwalC09vI= -github.com/bluenviron/mediacommon v1.5.0/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w= +github.com/bluenviron/mediacommon v1.5.1 h1:yYVF+ebqZOJh8yH+EeuPcAtTmWR66BqbJGmStxkScoI= +github.com/bluenviron/mediacommon v1.5.1/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 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= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -118,6 +124,8 @@ github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e/go.mod h1:HyVoz1Mz github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= @@ -131,8 +139,8 @@ github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2 h1:dyuNlYlG1faym github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= -github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= -github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -146,24 +154,28 @@ github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= @@ -271,6 +283,9 @@ github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwc github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -283,8 +298,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/go-gypsy v1.0.0 h1:7/wQ7A3UL1bnqRMnZ6T8cwCOArfZCxFmb1iTxaOOo1s= github.com/kylelemons/go-gypsy v1.0.0/go.mod h1:chkXM0zjdpXOiqkCW1XcCHDfjfk14PH2KKkQWxfJUcU= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -294,8 +310,9 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/minio-go/v6 v6.0.57 h1:ixPkbKkyD7IhnluRgQpGSpHdpvNVaW6OD5R9IAO/9Tw= @@ -339,8 +356,9 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= @@ -428,6 +446,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= @@ -450,11 +470,14 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -486,6 +509,9 @@ go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7C go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -654,12 +680,14 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -819,8 +847,9 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/DataDog/dd-trace-go.v1 v1.46.0 h1:h/SbNfGfDMhBkB+/zzCWKPOlLcdd0Fc+QBAnZm009XM= gopkg.in/DataDog/dd-trace-go.v1 v1.46.0/go.mod h1:kaa8caaECrtY0V/MUtPQAh1lx/euFzPJwrY1taTx3O4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -863,5 +892,6 @@ howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCU inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQG4WsMej0WXaHxunmU= inet.af/netaddr v0.0.0-20220617031823-097006376321/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/machinery/src/capture/Golibrtsp.go b/machinery/src/capture/Gortsplib.go similarity index 52% rename from machinery/src/capture/Golibrtsp.go rename to machinery/src/capture/Gortsplib.go index 13f75174..a127b633 100644 --- a/machinery/src/capture/Golibrtsp.go +++ b/machinery/src/capture/Gortsplib.go @@ -21,6 +21,8 @@ import ( "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" @@ -34,14 +36,26 @@ type Golibrtsp struct { Url string WithBackChannel bool - Client gortsplib.Client - Media *description.Media - Forma *format.H264 - Streams []packets.Stream + Client gortsplib.Client + + VideoH264Index int8 + VideoH264Media *description.Media + VideoH264Forma *format.H264 + VideoH264Decoder *rtph264.Decoder + VideoH264FrameDecoder *h264Decoder + VideoH264DecoderMutex *sync.Mutex + + AudioLPCMIndex int8 + AudioLPCMMedia *description.Media + AudioLPCMForma *format.LPCM + AudioLPCMDecoder *rtplpcm.Decoder - DecoderMutex *sync.Mutex - Decoder *rtph264.Decoder - FrameDecoder *h264Decoder + AudioG711Index int8 + AudioG711Media *description.Media + AudioG711Forma *format.G711 + AudioG711Decoder *rtpsimpleaudio.Decoder + + Streams []packets.Stream } // Connect to the RTSP server. @@ -70,46 +84,81 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { // find the H264 media and format var forma *format.H264 medi := desc.FindFormat(&forma) + g.VideoH264Media = medi + g.VideoH264Forma = forma if medi == nil { - panic("media not found") - } - g.Media = medi - g.Forma = forma - - g.Streams = append(g.Streams, packets.Stream{ - Name: forma.Codec(), - IsVideo: true, - IsAudio: false, - SPS: forma.SPS, - PPS: forma.PPS, - }) - - // setup RTP/H264 -> H264 decoder - rtpDec, err := forma.CreateDecoder() - if err != nil { - panic(err) - } - g.Decoder = rtpDec + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "video media not found") + } else { + g.Streams = append(g.Streams, packets.Stream{ + Name: forma.Codec(), + IsVideo: true, + IsAudio: false, + SPS: forma.SPS, + PPS: forma.PPS, + }) + + // Set the index for the video + g.VideoH264Index = int8(len(g.Streams)) - 1 + + // setup RTP/H264 -> H264 decoder + rtpDec, err := forma.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + } + g.VideoH264Decoder = rtpDec - // setup H264 -> raw frames decoder - frameDec, err := newH264Decoder() - if err != nil { - panic(err) - } - g.FrameDecoder = frameDec + // setup H264 -> raw frames decoder + frameDec, err := newH264Decoder() + if err != nil { + // Something went wrong .. Do something + } + g.VideoH264FrameDecoder = frameDec - // if SPS and PPS are present into the SDP, send them to the decoder - if forma.SPS != nil { - frameDec.decode(forma.SPS) - } - if forma.PPS != nil { - frameDec.decode(forma.PPS) + // if SPS and PPS are present into the SDP, send them to the decoder + if forma.SPS != nil { + frameDec.decode(forma.SPS) + } + if forma.PPS != nil { + frameDec.decode(forma.PPS) + } + + // setup a video media + _, err = g.Client.Setup(desc.BaseURL, medi, 0, 0) + if err != nil { + // Something went wrong .. Do something + } } - // setup a single media - _, err = g.Client.Setup(desc.BaseURL, medi, 0, 0) - if err != nil { - panic(err) + // Look for audio stream. + // find the G711 media and format + var audioForma *format.G711 + audioMedi := desc.FindFormat(&audioForma) + g.AudioG711Media = audioMedi + g.AudioG711Forma = audioForma + if audioMedi == nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio media not found") + } else { + g.Streams = append(g.Streams, packets.Stream{ + Name: "PCM_MULAW", + IsVideo: false, + IsAudio: true, + }) + + // Set the index for the audio + g.AudioG711Index = int8(len(g.Streams)) - 1 + + // create decoder + audiortpDec, err := audioForma.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + } + g.AudioG711Decoder = audiortpDec + + // setup a audio media + _, err = g.Client.Setup(desc.BaseURL, audioMedi, 0, 0) + if err != nil { + // Something went wrong .. Do something + } } return @@ -119,88 +168,37 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") - //dtsExtractor := h264.NewDTSExtractor() - - // called when a RTP packet arrives - g.Client.OnPacketRTP(g.Media, g.Forma, func(rtppkt *rtp.Packet) { - - // This will check if we need to stop the thread, - // because of a reconfiguration. - select { - case <-communication.HandleStream: - return - default: - } - - //og.Log.Info("RTSPClient(Golibrtsp).Start(): " + "read packet from stream: " + strconv.Itoa(len(pkt.Payload)) + " bytes") - if len(rtppkt.Payload) > 0 { - + // called when a audio RTP packet arrives + if g.AudioG711Media != nil { + g.Client.OnPacketRTP(g.AudioG711Media, g.AudioG711Forma, func(rtppkt *rtp.Packet) { // decode timestamp - pts, ok := g.Client.PacketPTS(g.Media, rtppkt) + pts, ok := g.Client.PacketPTS(g.AudioG711Media, rtppkt) if !ok { log.Log.Error("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") return } - // extract access units from RTP packets - au, errDecode := g.Decoder.Decode(rtppkt) - if errDecode != nil { - if errDecode != rtph264.ErrNonStartingPacketAndNoPrevious && errDecode != rtph264.ErrMorePacketsNeeded { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + errDecode.Error()) - } - return - } - - // We'll need to read out a few things. - // prepend an AUD. This is required by some players - filteredAU := [][]byte{ - {byte(h264.NALUTypeAccessUnitDelimiter), 240}, - } - - nonIDRPresent := false - idrPresent := false - for _, nalu := range au { - typ := h264.NALUType(nalu[0] & 0x1F) - switch typ { - case h264.NALUTypeAccessUnitDelimiter: - continue - case h264.NALUTypeIDR: - idrPresent = true - - case h264.NALUTypeNonIDR: - nonIDRPresent = true - } - filteredAU = append(filteredAU, nalu) - } - - au = filteredAU - - if len(au) <= 1 || (!nonIDRPresent && !idrPresent) { - return - } - - /*dts, errDts := dtsExtractor.Extract(au, pts) - if errDts != nil { - return - } - fmt.Println("DTS: ", dts)*/ - - // Conver to packet. - enc, err := h264.AnnexBMarshal(au) + // extract LPCM samples from RTP packets + op, err := g.AudioG711Decoder.Decode(rtppkt) if err != nil { log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) return } pkt := packets.Packet{ - IsKeyFrame: idrPresent, - Packet: rtppkt, - AccessUnits: au, - Data: enc, - Time: pts, + IsKeyFrame: false, + Packet: rtppkt, + Data: op, + Time: pts, + Idx: g.AudioG711Index, } - queue.WritePacket(pkt) + }) + } + + // called when a video RTP packet arrives + if g.VideoH264Media != nil { + g.Client.OnPacketRTP(g.VideoH264Media, g.VideoH264Forma, func(rtppkt *rtp.Packet) { // This will check if we need to stop the thread, // because of a reconfiguration. @@ -210,16 +208,90 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati default: } - if idrPresent { - // Increment packets, so we know the device - // is not blocking. - r := communication.PackageCounter.Load().(int64) - log.Log.Info("RTSPClient(Golibrtsp).Start(): packet size " + strconv.Itoa(len(pkt.Data))) - communication.PackageCounter.Store((r + 1) % 1000) - communication.LastPacketTimer.Store(time.Now().Unix()) + if len(rtppkt.Payload) > 0 { + + // decode timestamp + pts, ok := g.Client.PacketPTS(g.VideoH264Media, rtppkt) + if !ok { + log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + return + } + + // Extract access units from RTP packets + // We need to do this, because the decoder expects a full + // access unit. Once we have a full access unit, we can + // decode it, and know if it's a keyframe or not. + au, errDecode := g.VideoH264Decoder.Decode(rtppkt) + if errDecode != nil { + if errDecode != rtph264.ErrNonStartingPacketAndNoPrevious && errDecode != rtph264.ErrMorePacketsNeeded { + log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + errDecode.Error()) + } + return + } + + // We'll need to read out a few things. + // prepend an AUD. This is required by some players + filteredAU := [][]byte{ + {byte(h264.NALUTypeAccessUnitDelimiter), 240}, + } + + // Check if we have a keyframe. + nonIDRPresent := false + idrPresent := false + for _, nalu := range au { + typ := h264.NALUType(nalu[0] & 0x1F) + switch typ { + case h264.NALUTypeAccessUnitDelimiter: + continue + case h264.NALUTypeIDR: + idrPresent = true + + case h264.NALUTypeNonIDR: + nonIDRPresent = true + } + filteredAU = append(filteredAU, nalu) + } + + if len(filteredAU) <= 1 || (!nonIDRPresent && !idrPresent) { + return + } + + // Conver to packet. + enc, err := h264.AnnexBMarshal(filteredAU) + if err != nil { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + return + } + + pkt := packets.Packet{ + IsKeyFrame: idrPresent, + Packet: rtppkt, + Data: enc, + Time: pts, + Idx: g.VideoH264Index, + } + queue.WritePacket(pkt) + + // This will check if we need to stop the thread, + // because of a reconfiguration. + select { + case <-communication.HandleStream: + return + default: + } + + if idrPresent { + // Increment packets, so we know the device + // is not blocking. + r := communication.PackageCounter.Load().(int64) + log.Log.Info("RTSPClient(Golibrtsp).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + communication.PackageCounter.Store((r + 1) % 1000) + communication.LastPacketTimer.Store(time.Now().Unix()) + } } - } - }) + + }) + } // Play the stream. _, err = g.Client.Play(nil) @@ -232,7 +304,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // Decode a packet to an image. func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { - img, err := g.FrameDecoder.decode(pkt.Data) + img, err := g.VideoH264FrameDecoder.decode(pkt.Data) if err != nil { return image.YCbCr{}, err } diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 31de56d7..b28c4282 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -11,11 +11,9 @@ import ( "github.com/kerberos-io/agent/machinery/src/encryption" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/mp4" + "github.com/kerberos-io/agent/machinery/src/packets" "github.com/kerberos-io/agent/machinery/src/utils" - "github.com/kerberos-io/joy4/av/pubsub" - "github.com/kerberos-io/joy4/format/mp4" - - "github.com/kerberos-io/joy4/av" ) func CleanupRecordingDirectory(configDirectory string, configuration *models.Configuration) { @@ -52,10 +50,13 @@ func CleanupRecordingDirectory(configDirectory string, configuration *models.Con } } -func HandleRecordStream(queue *pubsub.Queue, configDirectory string, configuration *models.Configuration, communication *models.Communication, streams []av.CodecData) { +func HandleRecordStream(queue *packets.Queue, configDirectory string, configuration *models.Configuration, communication *models.Communication, rtspClient RTSPClient) { config := configuration.Config + // Get the streams from the rtsp client. + streams, _ := rtspClient.GetStreams() + if config.Capture.Recording == "false" { log.Log.Info("HandleRecordStream: disabled, we will not record anything.") } else { @@ -92,8 +93,8 @@ func HandleRecordStream(queue *pubsub.Queue, configDirectory string, configurati // Get as much packets we need. //for pkt := range packets { var cursorError error - var pkt av.Packet - var nextPkt av.Packet + var pkt packets.Packet + var nextPkt packets.Packet recordingStatus := "idle" recordingCursor := queue.Oldest() @@ -330,14 +331,15 @@ func HandleRecordStream(queue *pubsub.Queue, configDirectory string, configurati log.Log.Info("HandleRecordStream: composing recording") log.Log.Info("HandleRecordStream: write header") // Creating the file, might block sometimes. - if err := myMuxer.WriteHeader(streams); err != nil { - log.Log.Error(err.Error()) - } + // TODO CHANGE!!! + //if err := myMuxer.WriteHeader(streams); err != nil { + // log.Log.Error(err.Error()) + //} // Get as much packets we need. var cursorError error - var pkt av.Packet - var nextPkt av.Packet + var pkt packets.Packet + var nextPkt packets.Packet recordingCursor := queue.DelayedGopCount(int(config.Capture.PreRecording)) if cursorError == nil { diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 265d9752..7070aa2b 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -198,6 +198,9 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, rtspClient) } + // Handle recording, will write an mp4 to disk. + go capture.HandleRecordStream(queue, configDirectory, configuration, communication, rtspClient) + // Handle Upload to cloud provider (Kerberos Hub, Kerberos Vault and others) go cloud.HandleUpload(configDirectory, configuration, communication) @@ -256,9 +259,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu time.Sleep(time.Second * 3) /* - if err == nil { - - + if err == nil // We might have a secondary rtsp url, so we might need to use that. var subInfile av.DemuxCloser diff --git a/machinery/src/mp4/mp4io/atoms.go b/machinery/src/mp4/mp4io/atoms.go new file mode 100644 index 00000000..e192f17b --- /dev/null +++ b/machinery/src/mp4/mp4io/atoms.go @@ -0,0 +1,3561 @@ +package mp4io + +import ( + "time" + + "github.com/kerberos-io/joy4/utils/bits/pio" +) + +const MOOF = Tag(0x6d6f6f66) + +func (self MovieFrag) Tag() Tag { + return MOOF +} + +const HDLR = Tag(0x68646c72) + +func (self HandlerRefer) Tag() Tag { + return HDLR +} + +const AVC1 = Tag(0x61766331) + +func (self AVC1Desc) Tag() Tag { + return AVC1 +} + +const URL = Tag(0x75726c20) + +func (self DataReferUrl) Tag() Tag { + return URL +} + +const TREX = Tag(0x74726578) + +func (self TrackExtend) Tag() Tag { + return TREX +} + +const ESDS = Tag(0x65736473) + +func (self ElemStreamDesc) Tag() Tag { + return ESDS +} + +const MDHD = Tag(0x6d646864) + +func (self MediaHeader) Tag() Tag { + return MDHD +} + +const STTS = Tag(0x73747473) + +func (self TimeToSample) Tag() Tag { + return STTS +} + +const STSS = Tag(0x73747373) + +func (self SyncSample) Tag() Tag { + return STSS +} + +const MFHD = Tag(0x6d666864) + +func (self MovieFragHeader) Tag() Tag { + return MFHD +} + +const MVHD = Tag(0x6d766864) + +func (self MovieHeader) Tag() Tag { + return MVHD +} + +const MINF = Tag(0x6d696e66) + +func (self MediaInfo) Tag() Tag { + return MINF +} + +const MOOV = Tag(0x6d6f6f76) + +func (self Movie) Tag() Tag { + return MOOV +} + +const MVEX = Tag(0x6d766578) + +func (self MovieExtend) Tag() Tag { + return MVEX +} + +const STSD = Tag(0x73747364) + +func (self SampleDesc) Tag() Tag { + return STSD +} + +const MP4A = Tag(0x6d703461) + +func (self MP4ADesc) Tag() Tag { + return MP4A +} + +const CTTS = Tag(0x63747473) + +func (self CompositionOffset) Tag() Tag { + return CTTS +} + +const STCO = Tag(0x7374636f) + +func (self ChunkOffset) Tag() Tag { + return STCO +} + +const TRUN = Tag(0x7472756e) + +func (self TrackFragRun) Tag() Tag { + return TRUN +} + +const TRAK = Tag(0x7472616b) + +func (self Track) Tag() Tag { + return TRAK +} + +const MDIA = Tag(0x6d646961) + +func (self Media) Tag() Tag { + return MDIA +} + +const STSC = Tag(0x73747363) + +func (self SampleToChunk) Tag() Tag { + return STSC +} + +const VMHD = Tag(0x766d6864) + +func (self VideoMediaInfo) Tag() Tag { + return VMHD +} + +const STBL = Tag(0x7374626c) + +func (self SampleTable) Tag() Tag { + return STBL +} + +const AVCC = Tag(0x61766343) + +func (self AVC1Conf) Tag() Tag { + return AVCC +} + +const TFDT = Tag(0x74666474) + +func (self TrackFragDecodeTime) Tag() Tag { + return TFDT +} + +const DINF = Tag(0x64696e66) + +func (self DataInfo) Tag() Tag { + return DINF +} + +const DREF = Tag(0x64726566) + +func (self DataRefer) Tag() Tag { + return DREF +} + +const TRAF = Tag(0x74726166) + +func (self TrackFrag) Tag() Tag { + return TRAF +} + +const STSZ = Tag(0x7374737a) + +func (self SampleSize) Tag() Tag { + return STSZ +} + +const TFHD = Tag(0x74666864) + +func (self TrackFragHeader) Tag() Tag { + return TFHD +} + +const TKHD = Tag(0x746b6864) + +func (self TrackHeader) Tag() Tag { + return TKHD +} + +const SMHD = Tag(0x736d6864) + +func (self SoundMediaInfo) Tag() Tag { + return SMHD +} + +const MDAT = Tag(0x6d646174) + +type Movie struct { + Header *MovieHeader + MovieExtend *MovieExtend + Tracks []*Track + Unknowns []Atom + AtomPos +} + +func (self Movie) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MOOV)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self Movie) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.MovieExtend != nil { + n += self.MovieExtend.Marshal(b[n:]) + } + for _, atom := range self.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self Movie) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.MovieExtend != nil { + n += self.MovieExtend.Len() + } + for _, atom := range self.Tracks { + n += atom.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *Movie) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MVHD: + { + atom := &MovieHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mvhd", n+offset, err) + return + } + self.Header = atom + } + case MVEX: + { + atom := &MovieExtend{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mvex", n+offset, err) + return + } + self.MovieExtend = atom + } + case TRAK: + { + atom := &Track{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trak", n+offset, err) + return + } + self.Tracks = append(self.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self Movie) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.MovieExtend != nil { + r = append(r, self.MovieExtend) + } + for _, atom := range self.Tracks { + r = append(r, atom) + } + r = append(r, self.Unknowns...) + return +} + +type MovieHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TimeScale int32 + Duration int32 + PreferredRate float64 + PreferredVolume float64 + Matrix [9]int32 + PreviewTime time.Time + PreviewDuration time.Time + PosterTime time.Time + SelectionTime time.Time + SelectionDuration time.Time + CurrentTime time.Time + NextTrackId int32 + AtomPos +} + +func (self MovieHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MVHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + PutTime32(b[n:], self.CreateTime) + n += 4 + PutTime32(b[n:], self.ModifyTime) + n += 4 + pio.PutI32BE(b[n:], self.TimeScale) + n += 4 + pio.PutI32BE(b[n:], self.Duration) + n += 4 + PutFixed32(b[n:], self.PreferredRate) + n += 4 + PutFixed16(b[n:], self.PreferredVolume) + n += 2 + n += 10 + for _, entry := range self.Matrix { + pio.PutI32BE(b[n:], entry) + n += 4 + } + PutTime32(b[n:], self.PreviewTime) + n += 4 + PutTime32(b[n:], self.PreviewDuration) + n += 4 + PutTime32(b[n:], self.PosterTime) + n += 4 + PutTime32(b[n:], self.SelectionTime) + n += 4 + PutTime32(b[n:], self.SelectionDuration) + n += 4 + PutTime32(b[n:], self.CurrentTime) + n += 4 + pio.PutI32BE(b[n:], self.NextTrackId) + n += 4 + return +} +func (self MovieHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 2 + n += 10 + n += 4 * len(self.Matrix[:]) + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + return +} +func (self *MovieHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + self.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + self.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TimeScale", n+offset, err) + return + } + self.TimeScale = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + self.Duration = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("PreferredRate", n+offset, err) + return + } + self.PreferredRate = GetFixed32(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("PreferredVolume", n+offset, err) + return + } + self.PreferredVolume = GetFixed16(b[n:]) + n += 2 + n += 10 + if len(b) < n+4*len(self.Matrix) { + err = parseErr("Matrix", n+offset, err) + return + } + for i := range self.Matrix { + self.Matrix[i] = pio.I32BE(b[n:]) + n += 4 + } + if len(b) < n+4 { + err = parseErr("PreviewTime", n+offset, err) + return + } + self.PreviewTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("PreviewDuration", n+offset, err) + return + } + self.PreviewDuration = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("PosterTime", n+offset, err) + return + } + self.PosterTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SelectionTime", n+offset, err) + return + } + self.SelectionTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SelectionDuration", n+offset, err) + return + } + self.SelectionDuration = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("CurrentTime", n+offset, err) + return + } + self.CurrentTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("NextTrackId", n+offset, err) + return + } + self.NextTrackId = pio.I32BE(b[n:]) + n += 4 + return +} +func (self MovieHeader) Children() (r []Atom) { + return +} + +type Track struct { + Header *TrackHeader + Media *Media + Unknowns []Atom + AtomPos +} + +func (self Track) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRAK)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self Track) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.Media != nil { + n += self.Media.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self Track) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.Media != nil { + n += self.Media.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *Track) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TKHD: + { + atom := &TrackHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tkhd", n+offset, err) + return + } + self.Header = atom + } + case MDIA: + { + atom := &Media{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mdia", n+offset, err) + return + } + self.Media = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self Track) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.Media != nil { + r = append(r, self.Media) + } + r = append(r, self.Unknowns...) + return +} + +type TrackHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TrackId int32 + Duration int32 + Layer int16 + AlternateGroup int16 + Volume float64 + Matrix [9]int32 + TrackWidth float64 + TrackHeight float64 + AtomPos +} + +func (self TrackHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TKHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + PutTime32(b[n:], self.CreateTime) + n += 4 + PutTime32(b[n:], self.ModifyTime) + n += 4 + pio.PutI32BE(b[n:], self.TrackId) + n += 4 + n += 4 + pio.PutI32BE(b[n:], self.Duration) + n += 4 + n += 8 + pio.PutI16BE(b[n:], self.Layer) + n += 2 + pio.PutI16BE(b[n:], self.AlternateGroup) + n += 2 + PutFixed16(b[n:], self.Volume) + n += 2 + n += 2 + for _, entry := range self.Matrix { + pio.PutI32BE(b[n:], entry) + n += 4 + } + PutFixed32(b[n:], self.TrackWidth) + n += 4 + PutFixed32(b[n:], self.TrackHeight) + n += 4 + return +} +func (self TrackHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 8 + n += 2 + n += 2 + n += 2 + n += 2 + n += 4 * len(self.Matrix[:]) + n += 4 + n += 4 + return +} +func (self *TrackHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + self.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + self.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TrackId", n+offset, err) + return + } + self.TrackId = pio.I32BE(b[n:]) + n += 4 + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + self.Duration = pio.I32BE(b[n:]) + n += 4 + n += 8 + if len(b) < n+2 { + err = parseErr("Layer", n+offset, err) + return + } + self.Layer = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("AlternateGroup", n+offset, err) + return + } + self.AlternateGroup = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Volume", n+offset, err) + return + } + self.Volume = GetFixed16(b[n:]) + n += 2 + n += 2 + if len(b) < n+4*len(self.Matrix) { + err = parseErr("Matrix", n+offset, err) + return + } + for i := range self.Matrix { + self.Matrix[i] = pio.I32BE(b[n:]) + n += 4 + } + if len(b) < n+4 { + err = parseErr("TrackWidth", n+offset, err) + return + } + self.TrackWidth = GetFixed32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TrackHeight", n+offset, err) + return + } + self.TrackHeight = GetFixed32(b[n:]) + n += 4 + return +} +func (self TrackHeader) Children() (r []Atom) { + return +} + +type HandlerRefer struct { + Version uint8 + Flags uint32 + Type [4]byte + SubType [4]byte + Name []byte + AtomPos +} + +func (self HandlerRefer) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(HDLR)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self HandlerRefer) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + copy(b[n:], self.Type[:]) + n += len(self.Type[:]) + copy(b[n:], self.SubType[:]) + n += len(self.SubType[:]) + // TODO: document component + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + copy(b[n:], self.Name[:]) + n += len(self.Name[:]) + return +} +func (self HandlerRefer) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += len(self.Type[:]) + n += len(self.SubType[:]) + // TODO: document component + n += 4 + n += 4 + n += 4 + n += len(self.Name[:]) + return +} +func (self *HandlerRefer) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+len(self.Type) { + err = parseErr("Type", n+offset, err) + return + } + copy(self.Type[:], b[n:]) + n += len(self.Type) + if len(b) < n+len(self.SubType) { + err = parseErr("SubType", n+offset, err) + return + } + copy(self.SubType[:], b[n:]) + n += len(self.SubType) + // TODO: document component + n += 4 + n += 4 + n += 4 + self.Name = b[n:] + n += len(b[n:]) + return +} +func (self HandlerRefer) Children() (r []Atom) { + return +} + +type Media struct { + Header *MediaHeader + Handler *HandlerRefer + Info *MediaInfo + Unknowns []Atom + AtomPos +} + +func (self Media) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MDIA)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self Media) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.Handler != nil { + n += self.Handler.Marshal(b[n:]) + } + if self.Info != nil { + n += self.Info.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self Media) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.Handler != nil { + n += self.Handler.Len() + } + if self.Info != nil { + n += self.Info.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *Media) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MDHD: + { + atom := &MediaHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mdhd", n+offset, err) + return + } + self.Header = atom + } + case HDLR: + { + atom := &HandlerRefer{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("hdlr", n+offset, err) + return + } + self.Handler = atom + } + case MINF: + { + atom := &MediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("minf", n+offset, err) + return + } + self.Info = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self Media) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.Handler != nil { + r = append(r, self.Handler) + } + if self.Info != nil { + r = append(r, self.Info) + } + r = append(r, self.Unknowns...) + return +} + +type MediaHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TimeScale int32 + Duration int32 + Language int16 + Quality int16 + AtomPos +} + +func (self MediaHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MDHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MediaHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + PutTime32(b[n:], self.CreateTime) + n += 4 + PutTime32(b[n:], self.ModifyTime) + n += 4 + pio.PutI32BE(b[n:], self.TimeScale) + n += 4 + pio.PutI32BE(b[n:], self.Duration) + n += 4 + pio.PutI16BE(b[n:], self.Language) + n += 2 + pio.PutI16BE(b[n:], self.Quality) + n += 2 + return +} +func (self MediaHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 2 + n += 2 + return +} +func (self *MediaHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + self.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + self.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TimeScale", n+offset, err) + return + } + self.TimeScale = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + self.Duration = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("Language", n+offset, err) + return + } + self.Language = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Quality", n+offset, err) + return + } + self.Quality = pio.I16BE(b[n:]) + n += 2 + return +} +func (self MediaHeader) Children() (r []Atom) { + return +} + +type MediaInfo struct { + Sound *SoundMediaInfo + Video *VideoMediaInfo + Data *DataInfo + Sample *SampleTable + Unknowns []Atom + AtomPos +} + +func (self MediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MINF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MediaInfo) marshal(b []byte) (n int) { + if self.Sound != nil { + n += self.Sound.Marshal(b[n:]) + } + if self.Video != nil { + n += self.Video.Marshal(b[n:]) + } + if self.Data != nil { + n += self.Data.Marshal(b[n:]) + } + if self.Sample != nil { + n += self.Sample.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MediaInfo) Len() (n int) { + n += 8 + if self.Sound != nil { + n += self.Sound.Len() + } + if self.Video != nil { + n += self.Video.Len() + } + if self.Data != nil { + n += self.Data.Len() + } + if self.Sample != nil { + n += self.Sample.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case SMHD: + { + atom := &SoundMediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("smhd", n+offset, err) + return + } + self.Sound = atom + } + case VMHD: + { + atom := &VideoMediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("vmhd", n+offset, err) + return + } + self.Video = atom + } + case DINF: + { + atom := &DataInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("dinf", n+offset, err) + return + } + self.Data = atom + } + case STBL: + { + atom := &SampleTable{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stbl", n+offset, err) + return + } + self.Sample = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MediaInfo) Children() (r []Atom) { + if self.Sound != nil { + r = append(r, self.Sound) + } + if self.Video != nil { + r = append(r, self.Video) + } + if self.Data != nil { + r = append(r, self.Data) + } + if self.Sample != nil { + r = append(r, self.Sample) + } + r = append(r, self.Unknowns...) + return +} + +type DataInfo struct { + Refer *DataRefer + Unknowns []Atom + AtomPos +} + +func (self DataInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(DINF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self DataInfo) marshal(b []byte) (n int) { + if self.Refer != nil { + n += self.Refer.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self DataInfo) Len() (n int) { + n += 8 + if self.Refer != nil { + n += self.Refer.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *DataInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case DREF: + { + atom := &DataRefer{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("dref", n+offset, err) + return + } + self.Refer = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self DataInfo) Children() (r []Atom) { + if self.Refer != nil { + r = append(r, self.Refer) + } + r = append(r, self.Unknowns...) + return +} + +type DataRefer struct { + Version uint8 + Flags uint32 + Url *DataReferUrl + AtomPos +} + +func (self DataRefer) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(DREF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self DataRefer) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + _childrenNR := 0 + if self.Url != nil { + _childrenNR++ + } + pio.PutI32BE(b[n:], int32(_childrenNR)) + n += 4 + if self.Url != nil { + n += self.Url.Marshal(b[n:]) + } + return +} +func (self DataRefer) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.Url != nil { + n += self.Url.Len() + } + return +} +func (self *DataRefer) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case URL: + { + atom := &DataReferUrl{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("url ", n+offset, err) + return + } + self.Url = atom + } + } + n += size + } + return +} +func (self DataRefer) Children() (r []Atom) { + if self.Url != nil { + r = append(r, self.Url) + } + return +} + +type DataReferUrl struct { + Version uint8 + Flags uint32 + AtomPos +} + +func (self DataReferUrl) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(URL)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self DataReferUrl) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + return +} +func (self DataReferUrl) Len() (n int) { + n += 8 + n += 1 + n += 3 + return +} +func (self *DataReferUrl) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + return +} +func (self DataReferUrl) Children() (r []Atom) { + return +} + +type SoundMediaInfo struct { + Version uint8 + Flags uint32 + Balance int16 + AtomPos +} + +func (self SoundMediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(SMHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SoundMediaInfo) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutI16BE(b[n:], self.Balance) + n += 2 + n += 2 + return +} +func (self SoundMediaInfo) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 2 + n += 2 + return +} +func (self *SoundMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+2 { + err = parseErr("Balance", n+offset, err) + return + } + self.Balance = pio.I16BE(b[n:]) + n += 2 + n += 2 + return +} +func (self SoundMediaInfo) Children() (r []Atom) { + return +} + +type VideoMediaInfo struct { + Version uint8 + Flags uint32 + GraphicsMode int16 + Opcolor [3]int16 + AtomPos +} + +func (self VideoMediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(VMHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self VideoMediaInfo) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutI16BE(b[n:], self.GraphicsMode) + n += 2 + for _, entry := range self.Opcolor { + pio.PutI16BE(b[n:], entry) + n += 2 + } + return +} +func (self VideoMediaInfo) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 2 + n += 2 * len(self.Opcolor[:]) + return +} +func (self *VideoMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+2 { + err = parseErr("GraphicsMode", n+offset, err) + return + } + self.GraphicsMode = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2*len(self.Opcolor) { + err = parseErr("Opcolor", n+offset, err) + return + } + for i := range self.Opcolor { + self.Opcolor[i] = pio.I16BE(b[n:]) + n += 2 + } + return +} +func (self VideoMediaInfo) Children() (r []Atom) { + return +} + +type SampleTable struct { + SampleDesc *SampleDesc + TimeToSample *TimeToSample + CompositionOffset *CompositionOffset + SampleToChunk *SampleToChunk + SyncSample *SyncSample + ChunkOffset *ChunkOffset + SampleSize *SampleSize + AtomPos +} + +func (self SampleTable) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STBL)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleTable) marshal(b []byte) (n int) { + if self.SampleDesc != nil { + n += self.SampleDesc.Marshal(b[n:]) + } + if self.TimeToSample != nil { + n += self.TimeToSample.Marshal(b[n:]) + } + if self.CompositionOffset != nil { + n += self.CompositionOffset.Marshal(b[n:]) + } + if self.SampleToChunk != nil { + n += self.SampleToChunk.Marshal(b[n:]) + } + if self.SyncSample != nil { + n += self.SyncSample.Marshal(b[n:]) + } + if self.ChunkOffset != nil { + n += self.ChunkOffset.Marshal(b[n:]) + } + if self.SampleSize != nil { + n += self.SampleSize.Marshal(b[n:]) + } + return +} +func (self SampleTable) Len() (n int) { + n += 8 + if self.SampleDesc != nil { + n += self.SampleDesc.Len() + } + if self.TimeToSample != nil { + n += self.TimeToSample.Len() + } + if self.CompositionOffset != nil { + n += self.CompositionOffset.Len() + } + if self.SampleToChunk != nil { + n += self.SampleToChunk.Len() + } + if self.SyncSample != nil { + n += self.SyncSample.Len() + } + if self.ChunkOffset != nil { + n += self.ChunkOffset.Len() + } + if self.SampleSize != nil { + n += self.SampleSize.Len() + } + return +} +func (self *SampleTable) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case STSD: + { + atom := &SampleDesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsd", n+offset, err) + return + } + self.SampleDesc = atom + } + case STTS: + { + atom := &TimeToSample{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stts", n+offset, err) + return + } + self.TimeToSample = atom + } + case CTTS: + { + atom := &CompositionOffset{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("ctts", n+offset, err) + return + } + self.CompositionOffset = atom + } + case STSC: + { + atom := &SampleToChunk{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsc", n+offset, err) + return + } + self.SampleToChunk = atom + } + case STSS: + { + atom := &SyncSample{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stss", n+offset, err) + return + } + self.SyncSample = atom + } + case STCO: + { + atom := &ChunkOffset{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stco", n+offset, err) + return + } + self.ChunkOffset = atom + } + case STSZ: + { + atom := &SampleSize{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsz", n+offset, err) + return + } + self.SampleSize = atom + } + } + n += size + } + return +} +func (self SampleTable) Children() (r []Atom) { + if self.SampleDesc != nil { + r = append(r, self.SampleDesc) + } + if self.TimeToSample != nil { + r = append(r, self.TimeToSample) + } + if self.CompositionOffset != nil { + r = append(r, self.CompositionOffset) + } + if self.SampleToChunk != nil { + r = append(r, self.SampleToChunk) + } + if self.SyncSample != nil { + r = append(r, self.SyncSample) + } + if self.ChunkOffset != nil { + r = append(r, self.ChunkOffset) + } + if self.SampleSize != nil { + r = append(r, self.SampleSize) + } + return +} + +type SampleDesc struct { + Version uint8 + AVC1Desc *AVC1Desc + MP4ADesc *MP4ADesc + Unknowns []Atom + AtomPos +} + +func (self SampleDesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleDesc) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + n += 3 + _childrenNR := 0 + if self.AVC1Desc != nil { + _childrenNR++ + } + if self.MP4ADesc != nil { + _childrenNR++ + } + _childrenNR += len(self.Unknowns) + pio.PutI32BE(b[n:], int32(_childrenNR)) + n += 4 + if self.AVC1Desc != nil { + n += self.AVC1Desc.Marshal(b[n:]) + } + if self.MP4ADesc != nil { + n += self.MP4ADesc.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self SampleDesc) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.AVC1Desc != nil { + n += self.AVC1Desc.Len() + } + if self.MP4ADesc != nil { + n += self.MP4ADesc.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *SampleDesc) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + n += 3 + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case AVC1: + { + atom := &AVC1Desc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("avc1", n+offset, err) + return + } + self.AVC1Desc = atom + } + case MP4A: + { + atom := &MP4ADesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mp4a", n+offset, err) + return + } + self.MP4ADesc = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self SampleDesc) Children() (r []Atom) { + if self.AVC1Desc != nil { + r = append(r, self.AVC1Desc) + } + if self.MP4ADesc != nil { + r = append(r, self.MP4ADesc) + } + r = append(r, self.Unknowns...) + return +} + +type MP4ADesc struct { + DataRefIdx int16 + Version int16 + RevisionLevel int16 + Vendor int32 + NumberOfChannels int16 + SampleSize int16 + CompressionId int16 + SampleRate float64 + Conf *ElemStreamDesc + Unknowns []Atom + AtomPos +} + +func (self MP4ADesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MP4A)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MP4ADesc) marshal(b []byte) (n int) { + n += 6 + pio.PutI16BE(b[n:], self.DataRefIdx) + n += 2 + pio.PutI16BE(b[n:], self.Version) + n += 2 + pio.PutI16BE(b[n:], self.RevisionLevel) + n += 2 + pio.PutI32BE(b[n:], self.Vendor) + n += 4 + pio.PutI16BE(b[n:], self.NumberOfChannels) + n += 2 + pio.PutI16BE(b[n:], self.SampleSize) + n += 2 + pio.PutI16BE(b[n:], self.CompressionId) + n += 2 + n += 2 + PutFixed32(b[n:], self.SampleRate) + n += 4 + if self.Conf != nil { + n += self.Conf.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MP4ADesc) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 2 + n += 2 + n += 4 + n += 2 + n += 2 + n += 2 + n += 2 + n += 4 + if self.Conf != nil { + n += self.Conf.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MP4ADesc) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + self.DataRefIdx = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("RevisionLevel", n+offset, err) + return + } + self.RevisionLevel = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("Vendor", n+offset, err) + return + } + self.Vendor = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("NumberOfChannels", n+offset, err) + return + } + self.NumberOfChannels = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("SampleSize", n+offset, err) + return + } + self.SampleSize = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("CompressionId", n+offset, err) + return + } + self.CompressionId = pio.I16BE(b[n:]) + n += 2 + n += 2 + if len(b) < n+4 { + err = parseErr("SampleRate", n+offset, err) + return + } + self.SampleRate = GetFixed32(b[n:]) + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case ESDS: + { + atom := &ElemStreamDesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("esds", n+offset, err) + return + } + self.Conf = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MP4ADesc) Children() (r []Atom) { + if self.Conf != nil { + r = append(r, self.Conf) + } + r = append(r, self.Unknowns...) + return +} + +type AVC1Desc struct { + DataRefIdx int16 + Version int16 + Revision int16 + Vendor int32 + TemporalQuality int32 + SpatialQuality int32 + Width int16 + Height int16 + HorizontalResolution float64 + VorizontalResolution float64 + FrameCount int16 + CompressorName [32]byte + Depth int16 + ColorTableId int16 + Conf *AVC1Conf + Unknowns []Atom + AtomPos +} + +func (self AVC1Desc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(AVC1)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self AVC1Desc) marshal(b []byte) (n int) { + n += 6 + pio.PutI16BE(b[n:], self.DataRefIdx) + n += 2 + pio.PutI16BE(b[n:], self.Version) + n += 2 + pio.PutI16BE(b[n:], self.Revision) + n += 2 + pio.PutI32BE(b[n:], self.Vendor) + n += 4 + pio.PutI32BE(b[n:], self.TemporalQuality) + n += 4 + pio.PutI32BE(b[n:], self.SpatialQuality) + n += 4 + pio.PutI16BE(b[n:], self.Width) + n += 2 + pio.PutI16BE(b[n:], self.Height) + n += 2 + PutFixed32(b[n:], self.HorizontalResolution) + n += 4 + PutFixed32(b[n:], self.VorizontalResolution) + n += 4 + n += 4 + pio.PutI16BE(b[n:], self.FrameCount) + n += 2 + copy(b[n:], self.CompressorName[:]) + n += len(self.CompressorName[:]) + pio.PutI16BE(b[n:], self.Depth) + n += 2 + pio.PutI16BE(b[n:], self.ColorTableId) + n += 2 + if self.Conf != nil { + n += self.Conf.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self AVC1Desc) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += len(self.CompressorName[:]) + n += 2 + n += 2 + if self.Conf != nil { + n += self.Conf.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + self.DataRefIdx = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Revision", n+offset, err) + return + } + self.Revision = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("Vendor", n+offset, err) + return + } + self.Vendor = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TemporalQuality", n+offset, err) + return + } + self.TemporalQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SpatialQuality", n+offset, err) + return + } + self.SpatialQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("Width", n+offset, err) + return + } + self.Width = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Height", n+offset, err) + return + } + self.Height = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("HorizontalResolution", n+offset, err) + return + } + self.HorizontalResolution = GetFixed32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("VorizontalResolution", n+offset, err) + return + } + self.VorizontalResolution = GetFixed32(b[n:]) + n += 4 + n += 4 + if len(b) < n+2 { + err = parseErr("FrameCount", n+offset, err) + return + } + self.FrameCount = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+len(self.CompressorName) { + err = parseErr("CompressorName", n+offset, err) + return + } + copy(self.CompressorName[:], b[n:]) + n += len(self.CompressorName) + if len(b) < n+2 { + err = parseErr("Depth", n+offset, err) + return + } + self.Depth = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("ColorTableId", n+offset, err) + return + } + self.ColorTableId = pio.I16BE(b[n:]) + n += 2 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case AVCC: + { + atom := &AVC1Conf{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("avcC", n+offset, err) + return + } + self.Conf = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self AVC1Desc) Children() (r []Atom) { + if self.Conf != nil { + r = append(r, self.Conf) + } + r = append(r, self.Unknowns...) + return +} + +type AVC1Conf struct { + Data []byte + AtomPos +} + +func (self AVC1Conf) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(AVCC)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self AVC1Conf) marshal(b []byte) (n int) { + copy(b[n:], self.Data[:]) + n += len(self.Data[:]) + return +} +func (self AVC1Conf) Len() (n int) { + n += 8 + n += len(self.Data[:]) + return +} +func (self *AVC1Conf) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + self.Data = b[n:] + n += len(b[n:]) + return +} +func (self AVC1Conf) Children() (r []Atom) { + return +} + +type TimeToSample struct { + Version uint8 + Flags uint32 + Entries []TimeToSampleEntry + AtomPos +} + +func (self TimeToSample) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STTS)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TimeToSample) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + PutTimeToSampleEntry(b[n:], entry) + n += LenTimeToSampleEntry + } + return +} +func (self TimeToSample) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += LenTimeToSampleEntry * len(self.Entries) + return +} +func (self *TimeToSample) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]TimeToSampleEntry, _len_Entries) + if len(b) < n+LenTimeToSampleEntry*len(self.Entries) { + err = parseErr("TimeToSampleEntry", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = GetTimeToSampleEntry(b[n:]) + n += LenTimeToSampleEntry + } + return +} +func (self TimeToSample) Children() (r []Atom) { + return +} + +type TimeToSampleEntry struct { + Count uint32 + Duration uint32 +} + +func GetTimeToSampleEntry(b []byte) (self TimeToSampleEntry) { + self.Count = pio.U32BE(b[0:]) + self.Duration = pio.U32BE(b[4:]) + return +} +func PutTimeToSampleEntry(b []byte, self TimeToSampleEntry) { + pio.PutU32BE(b[0:], self.Count) + pio.PutU32BE(b[4:], self.Duration) +} + +const LenTimeToSampleEntry = 8 + +type SampleToChunk struct { + Version uint8 + Flags uint32 + Entries []SampleToChunkEntry + AtomPos +} + +func (self SampleToChunk) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSC)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleToChunk) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + PutSampleToChunkEntry(b[n:], entry) + n += LenSampleToChunkEntry + } + return +} +func (self SampleToChunk) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += LenSampleToChunkEntry * len(self.Entries) + return +} +func (self *SampleToChunk) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]SampleToChunkEntry, _len_Entries) + if len(b) < n+LenSampleToChunkEntry*len(self.Entries) { + err = parseErr("SampleToChunkEntry", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = GetSampleToChunkEntry(b[n:]) + n += LenSampleToChunkEntry + } + return +} +func (self SampleToChunk) Children() (r []Atom) { + return +} + +type SampleToChunkEntry struct { + FirstChunk uint32 + SamplesPerChunk uint32 + SampleDescId uint32 +} + +func GetSampleToChunkEntry(b []byte) (self SampleToChunkEntry) { + self.FirstChunk = pio.U32BE(b[0:]) + self.SamplesPerChunk = pio.U32BE(b[4:]) + self.SampleDescId = pio.U32BE(b[8:]) + return +} +func PutSampleToChunkEntry(b []byte, self SampleToChunkEntry) { + pio.PutU32BE(b[0:], self.FirstChunk) + pio.PutU32BE(b[4:], self.SamplesPerChunk) + pio.PutU32BE(b[8:], self.SampleDescId) +} + +const LenSampleToChunkEntry = 12 + +type CompositionOffset struct { + Version uint8 + Flags uint32 + Entries []CompositionOffsetEntry + AtomPos +} + +func (self CompositionOffset) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(CTTS)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self CompositionOffset) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + PutCompositionOffsetEntry(b[n:], entry) + n += LenCompositionOffsetEntry + } + return +} +func (self CompositionOffset) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += LenCompositionOffsetEntry * len(self.Entries) + return +} +func (self *CompositionOffset) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]CompositionOffsetEntry, _len_Entries) + if len(b) < n+LenCompositionOffsetEntry*len(self.Entries) { + err = parseErr("CompositionOffsetEntry", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = GetCompositionOffsetEntry(b[n:]) + n += LenCompositionOffsetEntry + } + return +} +func (self CompositionOffset) Children() (r []Atom) { + return +} + +type CompositionOffsetEntry struct { + Count uint32 + Offset uint32 +} + +func GetCompositionOffsetEntry(b []byte) (self CompositionOffsetEntry) { + self.Count = pio.U32BE(b[0:]) + self.Offset = pio.U32BE(b[4:]) + return +} +func PutCompositionOffsetEntry(b []byte, self CompositionOffsetEntry) { + pio.PutU32BE(b[0:], self.Count) + pio.PutU32BE(b[4:], self.Offset) +} + +const LenCompositionOffsetEntry = 8 + +type SyncSample struct { + Version uint8 + Flags uint32 + Entries []uint32 + AtomPos +} + +func (self SyncSample) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSS)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SyncSample) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} +func (self SyncSample) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 * len(self.Entries) + return +} +func (self *SyncSample) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(self.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} +func (self SyncSample) Children() (r []Atom) { + return +} + +type ChunkOffset struct { + Version uint8 + Flags uint32 + Entries []uint32 + AtomPos +} + +func (self ChunkOffset) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STCO)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self ChunkOffset) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} +func (self ChunkOffset) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 * len(self.Entries) + return +} +func (self *ChunkOffset) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(self.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} +func (self ChunkOffset) Children() (r []Atom) { + return +} + +type MovieFrag struct { + Header *MovieFragHeader + Tracks []*TrackFrag + Unknowns []Atom + AtomPos +} + +func (self MovieFrag) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MOOF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieFrag) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + for _, atom := range self.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MovieFrag) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + for _, atom := range self.Tracks { + n += atom.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MFHD: + { + atom := &MovieFragHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mfhd", n+offset, err) + return + } + self.Header = atom + } + case TRAF: + { + atom := &TrackFrag{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("traf", n+offset, err) + return + } + self.Tracks = append(self.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MovieFrag) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + for _, atom := range self.Tracks { + r = append(r, atom) + } + r = append(r, self.Unknowns...) + return +} + +type MovieFragHeader struct { + Version uint8 + Flags uint32 + Seqnum uint32 + AtomPos +} + +func (self MovieFragHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MFHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieFragHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.Seqnum) + n += 4 + return +} +func (self MovieFragHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + return +} +func (self *MovieFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("Seqnum", n+offset, err) + return + } + self.Seqnum = pio.U32BE(b[n:]) + n += 4 + return +} +func (self MovieFragHeader) Children() (r []Atom) { + return +} + +type TrackFrag struct { + Header *TrackFragHeader + DecodeTime *TrackFragDecodeTime + Run *TrackFragRun + Unknowns []Atom + AtomPos +} + +func (self TrackFrag) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRAF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFrag) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.DecodeTime != nil { + n += self.DecodeTime.Marshal(b[n:]) + } + if self.Run != nil { + n += self.Run.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self TrackFrag) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.DecodeTime != nil { + n += self.DecodeTime.Len() + } + if self.Run != nil { + n += self.Run.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TFHD: + { + atom := &TrackFragHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tfhd", n+offset, err) + return + } + self.Header = atom + } + case TFDT: + { + atom := &TrackFragDecodeTime{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tfdt", n+offset, err) + return + } + self.DecodeTime = atom + } + case TRUN: + { + atom := &TrackFragRun{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trun", n+offset, err) + return + } + self.Run = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self TrackFrag) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.DecodeTime != nil { + r = append(r, self.DecodeTime) + } + if self.Run != nil { + r = append(r, self.Run) + } + r = append(r, self.Unknowns...) + return +} + +type MovieExtend struct { + Tracks []*TrackExtend + Unknowns []Atom + AtomPos +} + +func (self MovieExtend) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MVEX)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieExtend) marshal(b []byte) (n int) { + for _, atom := range self.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MovieExtend) Len() (n int) { + n += 8 + for _, atom := range self.Tracks { + n += atom.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MovieExtend) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TREX: + { + atom := &TrackExtend{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trex", n+offset, err) + return + } + self.Tracks = append(self.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MovieExtend) Children() (r []Atom) { + for _, atom := range self.Tracks { + r = append(r, atom) + } + r = append(r, self.Unknowns...) + return +} + +type TrackExtend struct { + Version uint8 + Flags uint32 + TrackId uint32 + DefaultSampleDescIdx uint32 + DefaultSampleDuration uint32 + DefaultSampleSize uint32 + DefaultSampleFlags uint32 + AtomPos +} + +func (self TrackExtend) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TREX)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackExtend) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.TrackId) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleDescIdx) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleDuration) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleSize) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleFlags) + n += 4 + return +} +func (self TrackExtend) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + return +} +func (self *TrackExtend) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("TrackId", n+offset, err) + return + } + self.TrackId = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleDescIdx", n+offset, err) + return + } + self.DefaultSampleDescIdx = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleDuration", n+offset, err) + return + } + self.DefaultSampleDuration = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleSize", n+offset, err) + return + } + self.DefaultSampleSize = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleFlags", n+offset, err) + return + } + self.DefaultSampleFlags = pio.U32BE(b[n:]) + n += 4 + return +} +func (self TrackExtend) Children() (r []Atom) { + return +} + +type SampleSize struct { + Version uint8 + Flags uint32 + SampleSize uint32 + Entries []uint32 + AtomPos +} + +func (self SampleSize) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSZ)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleSize) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.SampleSize) + n += 4 + if self.SampleSize != 0 { + return + } + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} +func (self SampleSize) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.SampleSize != 0 { + return + } + n += 4 + n += 4 * len(self.Entries) + return +} +func (self *SampleSize) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("SampleSize", n+offset, err) + return + } + self.SampleSize = pio.U32BE(b[n:]) + n += 4 + if self.SampleSize != 0 { + return + } + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(self.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} +func (self SampleSize) Children() (r []Atom) { + return +} + +type TrackFragRun struct { + Version uint8 + Flags uint32 + DataOffset uint32 + FirstSampleFlags uint32 + Entries []TrackFragRunEntry + AtomPos +} + +func (self TrackFragRun) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRUN)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFragRun) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + if self.Flags&TRUN_DATA_OFFSET != 0 { + { + pio.PutU32BE(b[n:], self.DataOffset) + n += 4 + } + } + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + { + pio.PutU32BE(b[n:], self.FirstSampleFlags) + n += 4 + } + } + + for i, entry := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + //log.Printf("TRUN entry[%v]: flags = %v", i, flags) + if flags&TRUN_SAMPLE_DURATION != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_DURATION = %v, flags = %v", i, entry.Duration, flags) + pio.PutU32BE(b[n:], entry.Duration) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_SIZE = %v, flags = %v", i, entry.Size, flags) + pio.PutU32BE(b[n:], entry.Size) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_FLAGS = %v, flags = %v", i, entry.Flags, flags) + pio.PutU32BE(b[n:], entry.Flags) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_CTS = %v, flags = %v", i, entry.Cts, flags) + pio.PutU32BE(b[n:], entry.Cts) + n += 4 + } + } + return +} +func (self TrackFragRun) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.Flags&TRUN_DATA_OFFSET != 0 { + n += 4 + } + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + n += 4 + } + + for i := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + if flags&TRUN_SAMPLE_DURATION != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + n += 4 + } + } + return +} +func (self *TrackFragRun) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]TrackFragRunEntry, _len_Entries) + if self.Flags&TRUN_DATA_OFFSET != 0 { + { + if len(b) < n+4 { + err = parseErr("DataOffset", n+offset, err) + return + } + self.DataOffset = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + { + if len(b) < n+4 { + err = parseErr("FirstSampleFlags", n+offset, err) + return + } + self.FirstSampleFlags = pio.U32BE(b[n:]) + n += 4 + } + } + + for i := 0; i < int(_len_Entries); i++ { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + entry := &self.Entries[i] + if flags&TRUN_SAMPLE_DURATION != 0 { + entry.Duration = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + entry.Size = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + entry.Flags = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + entry.Cts = pio.U32BE(b[n:]) + n += 4 + } + } + return +} +func (self TrackFragRun) Children() (r []Atom) { + return +} + +type TrackFragRunEntry struct { + Duration uint32 + Size uint32 + Flags uint32 + Cts uint32 +} + +func GetTrackFragRunEntry(b []byte) (self TrackFragRunEntry) { + self.Duration = pio.U32BE(b[0:]) + self.Size = pio.U32BE(b[4:]) + self.Flags = pio.U32BE(b[8:]) + self.Cts = pio.U32BE(b[12:]) + return +} +func PutTrackFragRunEntry(b []byte, self TrackFragRunEntry) { + pio.PutU32BE(b[0:], self.Duration) + pio.PutU32BE(b[4:], self.Size) + pio.PutU32BE(b[8:], self.Flags) + pio.PutU32BE(b[12:], self.Cts) +} + +const LenTrackFragRunEntry = 16 + +type TrackFragHeader struct { + Version uint8 + Flags uint32 + TrackId uint32 + BaseDataOffset uint64 + StsdId uint32 + DefaultDuration uint32 + DefaultSize uint32 + DefaultFlags uint32 + AtomPos +} + +func (self TrackFragHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TFHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFragHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.TrackId) + n += 4 + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + { + pio.PutU64BE(b[n:], self.BaseDataOffset) + n += 8 + } + } + + if self.Flags&TFHD_STSD_ID != 0 { + { + pio.PutU32BE(b[n:], self.StsdId) + n += 4 + } + } + + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + { + pio.PutU32BE(b[n:], self.DefaultDuration) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + { + pio.PutU32BE(b[n:], self.DefaultSize) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + { + pio.PutU32BE(b[n:], self.DefaultFlags) + n += 4 + } + } + return +} +func (self TrackFragHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + { + n += 8 + } + } + if self.Flags&TFHD_STSD_ID != 0 { + { + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + { + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + { + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + { + n += 4 + } + } + return +} +func (self *TrackFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + + if len(b) < n+4 { + err = parseErr("TrackId", n+offset, err) + return + } + self.TrackId = pio.U32BE(b[n:]) + n += 4 + + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + { + if len(b) < n+8 { + err = parseErr("BaseDataOffset", n+offset, err) + return + } + self.BaseDataOffset = pio.U64BE(b[n:]) + n += 8 + } + } + if self.Flags&TFHD_STSD_ID != 0 { + { + if len(b) < n+4 { + err = parseErr("StsdId", n+offset, err) + return + } + self.StsdId = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultDuration", n+offset, err) + return + } + self.DefaultDuration = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultSize", n+offset, err) + return + } + self.DefaultSize = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultFlags", n+offset, err) + return + } + self.DefaultFlags = pio.U32BE(b[n:]) + n += 4 + } + } + return +} +func (self TrackFragHeader) Children() (r []Atom) { + return +} + +type TrackFragDecodeTime struct { + Version uint8 + Flags uint32 + DecodeTime uint64 + AtomPos +} + +func (self TrackFragDecodeTime) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TFDT)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFragDecodeTime) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + if self.Version != 0 { + pio.PutU64BE(b[n:], self.DecodeTime) + n += 8 + } else { + + pio.PutU32BE(b[n:], uint32(self.DecodeTime)) + n += 4 + } + return +} +func (self TrackFragDecodeTime) Len() (n int) { + n += 8 + n += 1 + n += 3 + if self.Version != 0 { + n += 8 + } else { + + n += 4 + } + return +} +func (self *TrackFragDecodeTime) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if self.Version != 0 { + self.DecodeTime = pio.U64BE(b[n:]) + n += 8 + } else { + + self.DecodeTime = uint64(pio.U32BE(b[n:])) + n += 4 + } + return +} +func (self TrackFragDecodeTime) Children() (r []Atom) { + return +} diff --git a/machinery/src/mp4/mp4io/gen/gen.go b/machinery/src/mp4/mp4io/gen/gen.go new file mode 100644 index 00000000..1a6efb37 --- /dev/null +++ b/machinery/src/mp4/mp4io/gen/gen.go @@ -0,0 +1,1057 @@ + +package main + +import ( + "strings" + "fmt" + "os" + "go/ast" + "go/parser" + "go/token" + "go/printer" +) + +func getexprs(e ast.Expr) string { + if lit, ok := e.(*ast.BasicLit); ok { + return lit.Value + } + if ident, ok := e.(*ast.Ident); ok { + return ident.Name + } + return "" +} + +func genatomdecl(origfn *ast.FuncDecl, origname, origtag string) (decls []ast.Decl) { + fieldslist := &ast.FieldList{} + typespec := &ast.TypeSpec{ + Name: ast.NewIdent(origname), + Type: &ast.StructType{Fields: fieldslist}, + } + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + + if strings.HasPrefix(typ, "_") { + if typ == "_unknowns" { + fieldslist.List = append(fieldslist.List, &ast.Field{ + Names: []*ast.Ident{ast.NewIdent("Unknowns")}, + Type: ast.NewIdent("[]Atom"), + }) + } + continue + } + + name := getexprs(callexpr.Args[0]) + + name2 := "" + if len(callexpr.Args) > 1 { + name2 = getexprs(callexpr.Args[1]) + } + + len3 := "" + if len(callexpr.Args) > 2 { + len3 = getexprs(callexpr.Args[2]) + } + + if strings.HasPrefix(name, "_") { + continue + } + + switch typ { + case "fixed16": + typ = "float64" + case "fixed32": + typ = "float64" + case "bytesleft": + typ = "[]byte" + case "bytes": + typ = "["+name2+"]byte" + case "uint24": + typ = "uint32" + case "time64", "time32": + typ = "time.Time" + case "atom": + typ = "*"+name2 + case "atoms": + typ = "[]*"+name2 + case "slice": + typ = "[]"+name2 + case "array": + typ = "["+len3+"]"+name2 + } + + fieldslist.List = append(fieldslist.List, &ast.Field{ + Names: []*ast.Ident{ast.NewIdent(name)}, + Type: ast.NewIdent(typ), + }) + } + + if origtag != "" { + fieldslist.List = append(fieldslist.List, &ast.Field{ + Type: ast.NewIdent("AtomPos"), + }) + } + + gendecl := &ast.GenDecl{ + Tok: token.TYPE, + Specs: []ast.Spec{ + typespec, + }, + } + decls = append(decls, gendecl) + return +} + +func typegetlen(typ string) (n int) { + switch typ { + case "uint8": + n = 1 + case "uint16": + n = 2 + case "uint24": + n = 3 + case "uint32": + n = 4 + case "int16": + n = 2 + case "int32": + n = 4 + case "uint64": + n = 8 + case "time32": + n = 4 + case "time64": + n = 8 + case "fixed32": + n = 4 + case "fixed16": + n = 2 + } + return +} + +func typegetlens(typ string) string { + n := typegetlen(typ) + if n == 0 { + return "Len"+typ + } else { + return fmt.Sprint(n) + } +} + +func typegetvartype(typ string) string { + switch typ { + case "uint8": + return "uint8" + case "uint16": + return "uint16" + case "uint24": + return "uint32" + case "uint32": + return "uint32" + case "uint64": + return "uint64" + case "int16": + return "int16" + case "int32": + return "int32" + } + return "" +} + +func typegetputfn(typ string) (fn string) { + fn = typ + switch typ { + case "uint8": + fn = "pio.PutU8" + case "uint16": + fn = "pio.PutU16BE" + case "uint24": + fn = "pio.PutU24BE" + case "uint32": + fn = "pio.PutU32BE" + case "int16": + fn = "pio.PutI16BE" + case "int32": + fn = "pio.PutI32BE" + case "uint64": + fn = "pio.PutU64BE" + case "time32": + fn = "PutTime32" + case "time64": + fn = "PutTime64" + case "fixed32": + fn = "PutFixed32" + case "fixed16": + fn = "PutFixed16" + default: + fn = "Put"+typ + } + return +} + +func typegetgetfn(typ string) (fn string) { + fn = typ + switch typ { + case "uint8": + fn = "pio.U8" + case "uint16": + fn = "pio.U16BE" + case "uint24": + fn = "pio.U24BE" + case "uint32": + fn = "pio.U32BE" + case "int16": + fn = "pio.I16BE" + case "int32": + fn = "pio.I32BE" + case "uint64": + fn = "pio.U64BE" + case "time32": + fn = "GetTime32" + case "time64": + fn = "GetTime64" + case "fixed32": + fn = "GetFixed32" + case "fixed16": + fn = "GetFixed16" + default: + fn = "Get"+typ + } + return +} + +func addns(n string) (stmts []ast.Stmt) { + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: n}}, + } + stmts = append(stmts, assign) + return +} + +func addn(n int) (stmts []ast.Stmt) { + return addns(fmt.Sprint(n)) +} + +func simplecall(fun string, args... string) *ast.ExprStmt { + _args := []ast.Expr{} + for _, s := range args { + _args = append(_args, ast.NewIdent(s)) + } + return &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: ast.NewIdent(fun), + Args: _args, + }, + } +} + +func getxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { + fn := typegetgetfn(typ) + assign := &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ast.NewIdent(name)}, + Rhs: []ast.Expr{simplecall(fn, "b["+pos+":]").X}, + } + stmts = append(stmts, assign) + return +} + +func putxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { + if conv { + name = fmt.Sprintf("%s(%s)", typ, name) + } + fn := typegetputfn(typ) + stmts = append(stmts, simplecall(fn, "b["+pos+":]", name)) + return +} + +func putxxadd(fn string, name string, conv bool) (stmts []ast.Stmt) { + n := typegetlen(fn) + stmts = append(stmts, putxx(fn, "n", name, conv)...) + stmts = append(stmts, addn(n)...) + return +} + +func newdecl(origname, name string, params, res []*ast.Field, stmts []ast.Stmt) *ast.FuncDecl { + return &ast.FuncDecl{ + Recv: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{ + Names: []*ast.Ident{ast.NewIdent("self")}, + Type: ast.NewIdent(origname), + }, + }, + }, + Name: ast.NewIdent(name), + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: params, + }, + Results: &ast.FieldList{ + List: res, + }, + }, + Body: &ast.BlockStmt{List: stmts}, + } +} + +func getstructputgetlenfn(origfn *ast.FuncDecl, origname string) (decls []ast.Decl) { + getstmts := []ast.Stmt{} + putstmts := []ast.Stmt{} + totlen := 0 + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + name := getexprs(callexpr.Args[0]) + + getstmts = append(getstmts, getxx(typ, fmt.Sprint(totlen), "self."+name, false)...) + putstmts = append(putstmts, putxx(typ, fmt.Sprint(totlen), "self."+name, false)...) + totlen += typegetlen(typ) + } + + getstmts = append(getstmts, &ast.ReturnStmt{}) + + decls = append(decls, &ast.FuncDecl{ + Name: ast.NewIdent("Get"+origname), + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + }, + }, + Results: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, + }, + }, + }, + Body: &ast.BlockStmt{List: getstmts}, + }) + + decls = append(decls, &ast.FuncDecl{ + Name: ast.NewIdent("Put"+origname), + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, + }, + }, + }, + Body: &ast.BlockStmt{List: putstmts}, + }) + + decls = append(decls, &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ast.NewIdent("Len"+origname)}, + Values: []ast.Expr{ast.NewIdent(fmt.Sprint(totlen))}, + }, + }, + }) + + return +} + +func cc4decls(name string) (decls []ast.Decl) { + constdecl := &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ + ast.NewIdent(strings.ToUpper(name)), + }, + Values: []ast.Expr{ + &ast.CallExpr{ + Fun: ast.NewIdent("Tag"), + Args: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("0x%x", []byte(name))}}, + }, + }, + }, + }, + } + decls = append(decls, constdecl) + return +} + +func codeclonereplace(stmts []ast.Stmt, doit []ast.Stmt) (out []ast.Stmt) { + out = append([]ast.Stmt(nil), stmts...) + for i := range out { + if ifstmt, ok := out[i].(*ast.IfStmt); ok { + newifstmt := &ast.IfStmt{ + Cond: ifstmt.Cond, + Body: &ast.BlockStmt{ + List: codeclonereplace(ifstmt.Body.List, doit), + }, + } + if ifstmt.Else != nil { + newifstmt.Else = &ast.BlockStmt{ + List: codeclonereplace(ifstmt.Else.(*ast.BlockStmt).List, doit), + } + } + out[i] = newifstmt + } else if exprstmt, ok := out[i].(*ast.ExprStmt); ok { + if callexpr, ok := exprstmt.X.(*ast.CallExpr); ok { + if getexprs(callexpr.Fun) == "doit" { + out[i] = &ast.BlockStmt{List: doit} + } + } + } + } + return +} + +func getatommarshalfn(origfn *ast.FuncDecl, + origname, origtag string, + tagnamemap map[string]string, +) (decls []ast.Decl) { + marstmts := []ast.Stmt{} + unmarstmts := []ast.Stmt{} + lenstmts := []ast.Stmt{} + childrenstmts := []ast.Stmt{} + + parseerrreturn := func(debug string) (stmts []ast.Stmt) { + return []ast.Stmt{ + &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("err")}, + Rhs: []ast.Expr{ast.NewIdent(fmt.Sprintf(`parseErr("%s", n+offset, err)`, debug))}, + }, + &ast.ReturnStmt{}, + } + } + + callmarshal := func(name string) (stmts []ast.Stmt) { + callexpr := &ast.CallExpr{ + Fun: ast.NewIdent(name+".Marshal"), + Args: []ast.Expr{ast.NewIdent("b[n:]")}, + } + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{callexpr}, + } + stmts = append(stmts, assign) + return + } + + callputstruct := func(typ, name string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: ast.NewIdent(typegetputfn(typ)), + Args: []ast.Expr{ast.NewIdent("b[n:]"), ast.NewIdent(name)}, + }, + }) + stmts = append(stmts, &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{ast.NewIdent(typegetlens(typ))}, + }) + return + } + + calllenstruct := func(typ, name string) (stmts []ast.Stmt) { + inc := typegetlens(typ)+"*len("+name+")" + stmts = append(stmts, &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{ast.NewIdent(inc)}, + }) + return + } + + calllen := func(name string) (stmts []ast.Stmt) { + callexpr := &ast.CallExpr{ + Fun: ast.NewIdent(name+".Len"), + Args: []ast.Expr{}, + } + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{callexpr}, + } + stmts = append(stmts, assign) + return + } + + foreach := func(name, field string, block []ast.Stmt) (stmts []ast.Stmt) { + rangestmt := &ast.RangeStmt{ + Key: ast.NewIdent("_"), + Value: ast.NewIdent(name), + Body: &ast.BlockStmt{ + List: block, + }, + Tok: token.DEFINE, + X: ast.NewIdent(field), + } + stmts = append(stmts, rangestmt) + return + } + + foreachatom := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { + return foreach("atom", field, block) + } + + foreachentry := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { + return foreach("entry", field, block) + } + + foreachi := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { + rangestmt := &ast.RangeStmt{ + Key: ast.NewIdent("i"), + Body: &ast.BlockStmt{ + List: block, + }, + Tok: token.DEFINE, + X: ast.NewIdent(field), + } + stmts = append(stmts, rangestmt) + return + } + + foreachunknowns := func(block []ast.Stmt) (stmts []ast.Stmt) { + return foreachatom("self.Unknowns", block) + } + + declvar := func(name, typ string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.DeclStmt{ + Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ + ast.NewIdent(typ), + }, + Type: ast.NewIdent(name), + }, + }, + }, + }) + return + } + + makeslice := func(name, typ, size string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.ExprStmt{ + X: ast.NewIdent(fmt.Sprintf("%s = make([]%s, %s)", name, typ, size)), + }) + return + } + + simpleassign := func(tok token.Token, l, r string) *ast.AssignStmt { + return &ast.AssignStmt{ + Tok: tok, + Lhs: []ast.Expr{ast.NewIdent(l)}, + Rhs: []ast.Expr{ast.NewIdent(r)}, + } + } + + struct2tag := func(s string) string { + name := tagnamemap[s] + return name + } + + foreachatomsappendchildren := func(field string) (stmts []ast.Stmt) { + return foreachatom(field, []ast.Stmt{ + simpleassign(token.ASSIGN, "r", "append(r, atom)"), + }) + } + + var hasunknowns bool + var atomnames []string + var atomtypes []string + var atomarrnames []string + var atomarrtypes []string + slicenamemap := map[string]string{} + + unmarshalatom := func(typ, init string) (stmts []ast.Stmt) { + return []ast.Stmt{ + &ast.AssignStmt{Tok: token.DEFINE, + Lhs: []ast.Expr{ast.NewIdent("atom")}, Rhs: []ast.Expr{ast.NewIdent("&"+typ+"{"+init+"}")}, + }, + &ast.IfStmt{ + Init: &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("_"), ast.NewIdent("err")}, + Rhs: []ast.Expr{ast.NewIdent("atom.Unmarshal(b[n:n+size], offset+n)")}, + }, + Cond: ast.NewIdent("err != nil"), + Body: &ast.BlockStmt{List: parseerrreturn(struct2tag(typ))}, + }, + } + } + + unmrashalatoms := func() (stmts []ast.Stmt) { + blocks := []ast.Stmt{} + + blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("tag")}, + Rhs: []ast.Expr{ast.NewIdent("Tag(pio.U32BE(b[n+4:]))")}, + }) + blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("size")}, + Rhs: []ast.Expr{ast.NewIdent("int(pio.U32BE(b[n:]))")}, + }) + blocks = append(blocks, &ast.IfStmt{ + Cond: ast.NewIdent("len(b) < n+size"), + Body: &ast.BlockStmt{List: parseerrreturn("TagSizeInvalid")}, + }) + + cases := []ast.Stmt{} + + for i, atom := range atomnames { + cases = append(cases, &ast.CaseClause{ + List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomtypes[i])))}, + Body: []ast.Stmt{&ast.BlockStmt{ + List: append(unmarshalatom(atomtypes[i], ""), simpleassign(token.ASSIGN, "self."+atom, "atom")), + }}, + }) + } + + for i, atom := range atomarrnames { + selfatom := "self."+atom + cases = append(cases, &ast.CaseClause{ + List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomarrtypes[i])))}, + Body: []ast.Stmt{&ast.BlockStmt{ + List: append(unmarshalatom(atomarrtypes[i], ""), + simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), + }}, + }) + } + + if hasunknowns { + init := "Tag_: tag, Data: b[n:n+size]" + selfatom := "self.Unknowns" + cases = append(cases, &ast.CaseClause{ + Body: []ast.Stmt{&ast.BlockStmt{ + List: append(unmarshalatom("Dummy", init), simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), + }}, + }) + } + + blocks = append(blocks, &ast.SwitchStmt{ + Tag: ast.NewIdent("tag"), + Body: &ast.BlockStmt{List: cases}, + }) + + blocks = append(blocks, addns("size")...) + + stmts = append(stmts, &ast.ForStmt{ + Cond: ast.NewIdent("n+8 < len(b)"), + Body: &ast.BlockStmt{List: blocks}, + }) + return + } + + marshalwrapstmts := func() (stmts []ast.Stmt) { + stmts = append(stmts, putxx("uint32", "4", strings.ToUpper(origtag), true)...) + stmts = append(stmts, addns("self.marshal(b[8:])+8")...) + stmts = append(stmts, putxx("uint32", "0", "n", true)...) + stmts = append(stmts, &ast.ReturnStmt{}) + return + } + + ifnotnil := func(name string, block []ast.Stmt) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.IfStmt{ + Cond: &ast.BinaryExpr{ + X: ast.NewIdent(name), + Op: token.NEQ, + Y: ast.NewIdent("nil"), + }, + Body: &ast.BlockStmt{List: block}, + }) + return + } + + getchildrennr := func(name string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.AssignStmt{ + Tok: token.DEFINE, + Lhs: []ast.Expr{ast.NewIdent(name)}, + Rhs: []ast.Expr{ast.NewIdent("0")}, + }) + for _, atom := range atomnames { + stmts = append(stmts, ifnotnil("self."+atom, []ast.Stmt{ + &ast.IncDecStmt{X: ast.NewIdent(name), Tok: token.INC}, + })...) + } + if hasunknowns { + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("_childrenNR")}, + Rhs: []ast.Expr{ast.NewIdent("len(self.Unknowns)")}, + } + stmts = append(stmts, assign) + } + return + } + + checkcurlen := func(inc, debug string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.IfStmt{ + Cond: &ast.BinaryExpr{ + X: ast.NewIdent("len(b)"), + Op: token.LSS, + Y: ast.NewIdent("n+"+inc), + }, + Body: &ast.BlockStmt{List: parseerrreturn(debug)}, + }) + return + } + + checklendo := func(typ, name, debug string) (stmts []ast.Stmt) { + stmts = append(stmts, checkcurlen(typegetlens(typ), debug)...) + stmts = append(stmts, getxx(typ, "n", name, false)...) + stmts = append(stmts, addns(typegetlens(typ))...) + return + } + + checkstructlendo := func(typ, name, debug string, + foreach func(string,[]ast.Stmt)[]ast.Stmt, + ) (stmts []ast.Stmt) { + inc := typegetlens(typ)+"*len("+name+")" + stmts = append(stmts, checkcurlen(inc, debug)...) + stmts = append(stmts, foreach(name, append( + []ast.Stmt{ + &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ + ast.NewIdent(name+"[i]"), + }, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: ast.NewIdent(typegetgetfn(typ)), + Args: []ast.Expr{ast.NewIdent("b[n:]")}, + }, + }, + }, + }, + addns(typegetlens(typ))..., + ))...) + return + } + + checklencopy := func(name string) (stmts []ast.Stmt) { + lens := fmt.Sprintf("len(self.%s)", name) + stmts = append(stmts, checkcurlen(lens, name)...) + stmts = append(stmts, simplecall("copy", fmt.Sprintf("self.%s[:]", name), "b[n:]")) + stmts = append(stmts, addns(lens)...) + return + } + + appendcode := func(args []ast.Expr, + marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, unmarstmts *[]ast.Stmt, + defmarstmts []ast.Stmt, deflenstmts []ast.Stmt, defunmarstmts []ast.Stmt, + ) { + bodylist := func(i int, doit []ast.Stmt) []ast.Stmt { + return codeclonereplace(args[i].(*ast.FuncLit).Body.List, doit) + } + if len(args) == 1 { + *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) + *lenstmts = append(*lenstmts, bodylist(0, deflenstmts)...) + *unmarstmts = append(*unmarstmts, bodylist(0, defunmarstmts)...) + } else { + *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) + *lenstmts = append(*lenstmts, bodylist(1, deflenstmts)...) + *unmarstmts = append(*unmarstmts, bodylist(2, defunmarstmts)...) + } + } + + getdefaultstmts := func( + typ, name, name2 string, + marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, + unmarstmts *[]ast.Stmt, childrenstmts *[]ast.Stmt, + ) { + switch typ { + case "bytes", "bytesleft": + *marstmts = append(*marstmts, simplecall("copy", "b[n:]", "self."+name+"[:]")) + *marstmts = append(*marstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) + *lenstmts = append(*lenstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) + if typ == "bytes" { + *unmarstmts = append(*unmarstmts, checklencopy(name)...) + } else { + *unmarstmts = append(*unmarstmts, simpleassign(token.ASSIGN, "self."+name, "b[n:]")) + *unmarstmts = append(*unmarstmts, addns("len(b[n:])")...) + } + + case "array": + *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) + *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name+"[:]")...) + *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name, foreachi)...) + + case "atoms": + *marstmts = append(*marstmts, foreachatom("self."+name, callmarshal("atom"))...) + *lenstmts = append(*lenstmts, foreachatom("self."+name, calllen("atom"))...) + *childrenstmts = append(*childrenstmts, foreachatomsappendchildren("self."+name)...) + + case "slice": + *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) + *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name)...) + *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name2, foreachi)...) + + case "atom": + *marstmts = append(*marstmts, ifnotnil("self."+name, callmarshal("self."+name))...) + *lenstmts = append(*lenstmts, ifnotnil("self."+name, calllen("self."+name))...) + *childrenstmts = append(*childrenstmts, ifnotnil("self."+name, []ast.Stmt{ + simpleassign(token.ASSIGN, "r", fmt.Sprintf("append(r, %s)", "self."+name)), + })...) + + default: + *marstmts = append(*marstmts, putxxadd(typ, "self."+name, false)...) + *lenstmts = append(*lenstmts, addn(typegetlen(typ))...) + *unmarstmts = append(*unmarstmts, checklendo(typ, "self."+name, name)...) + } + } + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + if typ == "_unknowns" { + hasunknowns = true + } else if typ == "atom" { + name := getexprs(callexpr.Args[0]) + name2 := getexprs(callexpr.Args[1]) + atomnames = append(atomnames, name) + atomtypes = append(atomtypes, name2) + } else if typ == "atoms" { + name := getexprs(callexpr.Args[0]) + name2 := getexprs(callexpr.Args[1]) + atomarrnames = append(atomarrnames, name) + atomarrtypes = append(atomarrtypes, name2) + } else if typ == "slice" { + name := getexprs(callexpr.Args[0]) + name2 := getexprs(callexpr.Args[1]) + slicenamemap[name] = name2 + } + } + + lenstmts = append(lenstmts, addn(8)...) + unmarstmts = append(unmarstmts, simplecall("(&self.AtomPos).setPos", "offset", "len(b)")) + unmarstmts = append(unmarstmts, addn(8)...) + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + + name := "" + if len(callexpr.Args) > 0 { + name = getexprs(callexpr.Args[0]) + } + + name2 := "" + if len(callexpr.Args) > 1 { + name2 = getexprs(callexpr.Args[1]) + } + + var defmarstmts, deflenstmts, defunmarstmts, defchildrenstmts []ast.Stmt + getdefaultstmts(typ, name, name2, + &defmarstmts, &deflenstmts, &defunmarstmts, &defchildrenstmts) + + var code []ast.Expr + for _, arg := range callexpr.Args { + if fn, ok := arg.(*ast.CallExpr); ok { + if getexprs(fn.Fun) == "_code" { + code = fn.Args + } + } + } + if code != nil { + appendcode(code, + &marstmts, &lenstmts, &unmarstmts, + defmarstmts, deflenstmts, defunmarstmts, + ) + continue + } + + if strings.HasPrefix(typ, "_") { + if typ == "_unknowns" { + marstmts = append(marstmts, foreachunknowns(callmarshal("atom"))...) + lenstmts = append(lenstmts, foreachunknowns(calllen("atom"))...) + childrenstmts = append(childrenstmts, simpleassign(token.ASSIGN, "r", "append(r, self.Unknowns...)")) + } + if typ == "_skip" { + marstmts = append(marstmts, addns(name)...) + lenstmts = append(lenstmts, addns(name)...) + unmarstmts = append(unmarstmts, addns(name)...) + } + if typ == "_code" { + appendcode(callexpr.Args, + &marstmts, &lenstmts, &unmarstmts, + defmarstmts, deflenstmts, defunmarstmts, + ) + } + continue + } + + if name == "_childrenNR" { + marstmts = append(marstmts, getchildrennr(name)...) + marstmts = append(marstmts, putxxadd(typ, name, true)...) + lenstmts = append(lenstmts, addn(typegetlen(typ))...) + unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) + continue + } + + if strings.HasPrefix(name, "_len_") { + field := name[len("_len_"):] + marstmts = append(marstmts, putxxadd(typ, "len(self."+field+")", true)...) + lenstmts = append(lenstmts, addn(typegetlen(typ))...) + unmarstmts = append(unmarstmts, declvar(typegetvartype(typ), name)...) + unmarstmts = append(unmarstmts, getxx(typ, "n", name, false)...) + unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) + unmarstmts = append(unmarstmts, makeslice("self."+field, slicenamemap[field], name)...) + continue + } + + marstmts = append(marstmts, defmarstmts...) + lenstmts = append(lenstmts, deflenstmts...) + unmarstmts = append(unmarstmts, defunmarstmts...) + childrenstmts = append(childrenstmts, defchildrenstmts...) + } + + if len(atomnames) > 0 || len(atomarrnames) > 0 || hasunknowns { + unmarstmts = append(unmarstmts, unmrashalatoms()...) + } + + marstmts = append(marstmts, &ast.ReturnStmt{}) + lenstmts = append(lenstmts, &ast.ReturnStmt{}) + unmarstmts = append(unmarstmts, &ast.ReturnStmt{}) + childrenstmts = append(childrenstmts, &ast.ReturnStmt{}) + + decls = append(decls, newdecl(origname, "Marshal", []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + }, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + }, marshalwrapstmts())) + + decls = append(decls, newdecl(origname, "marshal", []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + }, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + }, marstmts)) + + decls = append(decls, newdecl(origname, "Len", []*ast.Field{}, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + }, lenstmts)) + + decls = append(decls, newdecl("*"+origname, "Unmarshal", []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + &ast.Field{Names: []*ast.Ident{ast.NewIdent("offset")}, Type: ast.NewIdent("int")}, + }, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + &ast.Field{Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}, + }, unmarstmts)) + + decls = append(decls, newdecl(origname, "Children", []*ast.Field{}, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("r")}, Type: ast.NewIdent("[]Atom")}, + }, childrenstmts)) + + return +} + +func genatoms(filename, outfilename string) { + // Create the AST by parsing src. + fset := token.NewFileSet() // positions are relative to fset + file, err := parser.ParseFile(fset, filename, nil, 0) + if err != nil { + panic(err) + } + + gen := &ast.File{} + gen.Name = ast.NewIdent("mp4io") + gen.Decls = []ast.Decl{ + &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/kerberos-io/joy4/utils/bits/pio"`}}, + }, + }, + &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"time"`}}, + }, + }, + } + + tagnamemap := map[string]string{} + tagnamemap["ElemStreamDesc"] = "esds" + + splittagname := func(fnname string) (ok bool, tag, name string) { + if len(fnname) > 5 && fnname[4] == '_' { + tag = fnname[0:4] + tag = strings.Replace(tag, "_", " ", 1) + name = fnname[5:] + ok = true + } else { + name = fnname + } + return + } + + for _, decl := range file.Decls { + if fndecl, ok := decl.(*ast.FuncDecl); ok { + ok, tag, name := splittagname(fndecl.Name.Name) + if ok { + tagnamemap[name] = tag + } + } + } + + tagfuncdecl := func(name, tag string) (decls ast.Decl) { + return newdecl(name, "Tag", []*ast.Field{}, []*ast.Field{ + &ast.Field{Type: ast.NewIdent("Tag")}, + }, []ast.Stmt{ + &ast.ReturnStmt{ + Results: []ast.Expr{ast.NewIdent(strings.ToUpper(tag))}}}) + } + + for k, v := range tagnamemap { + gen.Decls = append(gen.Decls, cc4decls(v)...) + gen.Decls = append(gen.Decls, tagfuncdecl(k, v)) + } + gen.Decls = append(gen.Decls, cc4decls("mdat")...) + + for _, decl := range file.Decls { + if fndecl, ok := decl.(*ast.FuncDecl); ok { + ok, tag, name := splittagname(fndecl.Name.Name) + if ok { + gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) + gen.Decls = append(gen.Decls, getatommarshalfn(fndecl, name, tag, tagnamemap)...) + } else { + gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) + gen.Decls = append(gen.Decls, getstructputgetlenfn(fndecl, name)...) + } + } + } + + outfile, _ := os.Create(outfilename) + printer.Fprint(outfile, fset, gen) + outfile.Close() +} + +func parse(filename, outfilename string) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, filename, nil, 0) + if err != nil { + panic(err) + } + outfile, _ := os.Create(outfilename) + ast.Fprint(outfile, fset, file, nil) + outfile.Close() +} + +func main() { + switch os.Args[1] { + case "parse": + parse(os.Args[2], os.Args[3]) + + case "gen": + genatoms(os.Args[2], os.Args[3]) + } +} + diff --git a/machinery/src/mp4/mp4io/gen/pattern.go b/machinery/src/mp4/mp4io/gen/pattern.go new file mode 100644 index 00000000..e039d8ee --- /dev/null +++ b/machinery/src/mp4/mp4io/gen/pattern.go @@ -0,0 +1,438 @@ +package main + +import "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" + +func moov_Movie() { + atom(Header, MovieHeader) + atom(MovieExtend, MovieExtend) + atoms(Tracks, Track) + _unknowns() +} + +func mvhd_MovieHeader() { + uint8(Version) + uint24(Flags) + time32(CreateTime) + time32(ModifyTime) + int32(TimeScale) + int32(Duration) + fixed32(PreferredRate) + fixed16(PreferredVolume) + _skip(10) + array(Matrix, int32, 9) + time32(PreviewTime) + time32(PreviewDuration) + time32(PosterTime) + time32(SelectionTime) + time32(SelectionDuration) + time32(CurrentTime) + int32(NextTrackId) +} + +func trak_Track() { + atom(Header, TrackHeader) + atom(Media, Media) + _unknowns() +} + +func tkhd_TrackHeader() { + uint8(Version) + uint24(Flags) + time32(CreateTime) + time32(ModifyTime) + int32(TrackId) + _skip(4) + int32(Duration) + _skip(8) + int16(Layer) + int16(AlternateGroup) + fixed16(Volume) + _skip(2) + array(Matrix, int32, 9) + fixed32(TrackWidth) + fixed32(TrackHeight) +} + +func hdlr_HandlerRefer() { + uint8(Version) + uint24(Flags) + bytes(Type, 4) + bytes(SubType, 4) + bytesleft(Name) +} + +func mdia_Media() { + atom(Header, MediaHeader) + atom(Handler, HandlerRefer) + atom(Info, MediaInfo) + _unknowns() +} + +func mdhd_MediaHeader() { + uint8(Version) + uint24(Flags) + time32(CreateTime) + time32(ModifyTime) + int32(TimeScale) + int32(Duration) + int16(Language) + int16(Quality) +} + +func minf_MediaInfo() { + atom(Sound, SoundMediaInfo) + atom(Video, VideoMediaInfo) + atom(Data, DataInfo) + atom(Sample, SampleTable) + _unknowns() +} + +func dinf_DataInfo() { + atom(Refer, DataRefer) + _unknowns() +} + +func dref_DataRefer() { + uint8(Version) + uint24(Flags) + int32(_childrenNR) + atom(Url, DataReferUrl) +} + +func url__DataReferUrl() { + uint8(Version) + uint24(Flags) +} + +func smhd_SoundMediaInfo() { + uint8(Version) + uint24(Flags) + int16(Balance) + _skip(2) +} + +func vmhd_VideoMediaInfo() { + uint8(Version) + uint24(Flags) + int16(GraphicsMode) + array(Opcolor, int16, 3) +} + +func stbl_SampleTable() { + atom(SampleDesc, SampleDesc) + atom(TimeToSample, TimeToSample) + atom(CompositionOffset, CompositionOffset) + atom(SampleToChunk, SampleToChunk) + atom(SyncSample, SyncSample) + atom(ChunkOffset, ChunkOffset) + atom(SampleSize, SampleSize) +} + +func stsd_SampleDesc() { + uint8(Version) + _skip(3) + int32(_childrenNR) + atom(AVC1Desc, AVC1Desc) + atom(MP4ADesc, MP4ADesc) + _unknowns() +} + +func mp4a_MP4ADesc() { + _skip(6) + int16(DataRefIdx) + int16(Version) + int16(RevisionLevel) + int32(Vendor) + int16(NumberOfChannels) + int16(SampleSize) + int16(CompressionId) + _skip(2) + fixed32(SampleRate) + atom(Conf, ElemStreamDesc) + _unknowns() +} + +func avc1_AVC1Desc() { + _skip(6) + int16(DataRefIdx) + int16(Version) + int16(Revision) + int32(Vendor) + int32(TemporalQuality) + int32(SpatialQuality) + int16(Width) + int16(Height) + fixed32(HorizontalResolution) + fixed32(VorizontalResolution) + _skip(4) + int16(FrameCount) + bytes(CompressorName, 32) + int16(Depth) + int16(ColorTableId) + atom(Conf, AVC1Conf) + _unknowns() +} + +func avcC_AVC1Conf() { + bytesleft(Data) +} + +func stts_TimeToSample() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, TimeToSampleEntry) +} + +func TimeToSampleEntry() { + uint32(Count) + uint32(Duration) +} + +func stsc_SampleToChunk() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, SampleToChunkEntry) +} + +func SampleToChunkEntry() { + uint32(FirstChunk) + uint32(SamplesPerChunk) + uint32(SampleDescId) +} + +func ctts_CompositionOffset() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, CompositionOffsetEntry) +} + +func CompositionOffsetEntry() { + uint32(Count) + uint32(Offset) +} + +func stss_SyncSample() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, uint32) +} + +func stco_ChunkOffset() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, uint32) +} + +func moof_MovieFrag() { + atom(Header, MovieFragHeader) + atoms(Tracks, TrackFrag) + _unknowns() +} + +func mfhd_MovieFragHeader() { + uint8(Version) + uint24(Flags) + uint32(Seqnum) +} + +func traf_TrackFrag() { + atom(Header, TrackFragHeader) + atom(DecodeTime, TrackFragDecodeTime) + atom(Run, TrackFragRun) + _unknowns() +} + +func mvex_MovieExtend() { + atoms(Tracks, TrackExtend) + _unknowns() +} + +func trex_TrackExtend() { + uint8(Version) + uint24(Flags) + uint32(TrackId) + uint32(DefaultSampleDescIdx) + uint32(DefaultSampleDuration) + uint32(DefaultSampleSize) + uint32(DefaultSampleFlags) +} + +func stsz_SampleSize() { + uint8(Version) + uint24(Flags) + uint32(SampleSize) + _code(func() { + if self.SampleSize != 0 { + return + } + }) + uint32(_len_Entries) + slice(Entries, uint32) +} + +func trun_TrackFragRun() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + + uint32(DataOffset, _code(func() { + if self.Flags&TRUN_DATA_OFFSET != 0 { + doit() + } + })) + + uint32(FirstSampleFlags, _code(func() { + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + doit() + } + })) + + slice(Entries, TrackFragRunEntry, _code(func() { + for i, entry := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + if flags&TRUN_SAMPLE_DURATION != 0 { + pio.PutU32BE(b[n:], entry.Duration) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + pio.PutU32BE(b[n:], entry.Size) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + pio.PutU32BE(b[n:], entry.Flags) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + pio.PutU32BE(b[n:], entry.Cts) + n += 4 + } + } + }, func() { + for i := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + if flags&TRUN_SAMPLE_DURATION != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + n += 4 + } + } + }, func() { + for i := 0; i < int(_len_Entries); i++ { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + entry := &self.Entries[i] + if flags&TRUN_SAMPLE_DURATION != 0 { + entry.Duration = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + entry.Size = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + entry.Flags = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + entry.Cts = pio.U32BE(b[n:]) + n += 4 + } + } + })) +} + +func TrackFragRunEntry() { + uint32(Duration) + uint32(Size) + uint32(Flags) + uint32(Cts) +} + +func tfhd_TrackFragHeader() { + uint8(Version) + uint24(Flags) + + uint64(BaseDataOffset, _code(func() { + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + doit() + } + })) + + uint32(StsdId, _code(func() { + if self.Flags&TFHD_STSD_ID != 0 { + doit() + } + })) + + uint32(DefaultDuration, _code(func() { + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + doit() + } + })) + + uint32(DefaultSize, _code(func() { + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + doit() + } + })) + + uint32(DefaultFlags, _code(func() { + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + doit() + } + })) +} + +func tfdt_TrackFragDecodeTime() { + uint8(Version) + uint24(Flags) + time64(Time, _code(func() { + if self.Version != 0 { + PutTime64(b[n:], self.Time) + n += 8 + } else { + PutTime32(b[n:], self.Time) + n += 4 + } + }, func() { + if self.Version != 0 { + n += 8 + } else { + n += 4 + } + }, func() { + if self.Version != 0 { + self.Time = GetTime64(b[n:]) + n += 8 + } else { + self.Time = GetTime32(b[n:]) + n += 4 + } + })) +} diff --git a/machinery/src/mp4/mp4io/mp4io.go b/machinery/src/mp4/mp4io/mp4io.go new file mode 100644 index 00000000..432ff1dd --- /dev/null +++ b/machinery/src/mp4/mp4io/mp4io.go @@ -0,0 +1,535 @@ +package mp4io + +import ( + "fmt" + "io" + "math" + "os" + "strings" + "time" + + "github.com/kerberos-io/joy4/utils/bits/pio" +) + +type ParseError struct { + Debug string + Offset int + prev *ParseError +} + +func (self *ParseError) Error() string { + s := []string{} + for p := self; p != nil; p = p.prev { + s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset)) + } + return "mp4io: parse error: " + strings.Join(s, ",") +} + +func parseErr(debug string, offset int, prev error) (err error) { + _prev, _ := prev.(*ParseError) + return &ParseError{Debug: debug, Offset: offset, prev: _prev} +} + +func GetTime32(b []byte) (t time.Time) { + sec := pio.U32BE(b) + t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) + t = t.Add(time.Second * time.Duration(sec)) + return +} + +func PutTime32(b []byte, t time.Time) { + dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) + sec := uint32(dur / time.Second) + pio.PutU32BE(b, sec) +} + +func GetTime64(b []byte) (t time.Time) { + sec := pio.U64BE(b) + t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) + t = t.Add(time.Second * time.Duration(sec)) + return +} + +func PutTime64(b []byte, t time.Time) { + dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) + sec := uint64(dur / time.Millisecond) + pio.PutU64BE(b, sec) +} + +func PutFixed16(b []byte, f float64) { + intpart, fracpart := math.Modf(f) + b[0] = uint8(intpart) + b[1] = uint8(fracpart * 256.0) +} + +func GetFixed16(b []byte) float64 { + return float64(b[0]) + float64(b[1])/256.0 +} + +func PutFixed32(b []byte, f float64) { + intpart, fracpart := math.Modf(f) + pio.PutU16BE(b[0:2], uint16(intpart)) + pio.PutU16BE(b[2:4], uint16(fracpart*65536.0)) +} + +func GetFixed32(b []byte) float64 { + return float64(pio.U16BE(b[0:2])) + float64(pio.U16BE(b[2:4]))/65536.0 +} + +type Tag uint32 + +func (self Tag) String() string { + var b [4]byte + pio.PutU32BE(b[:], uint32(self)) + for i := 0; i < 4; i++ { + if b[i] == 0 { + b[i] = ' ' + } + } + return string(b[:]) +} + +type Atom interface { + Pos() (int, int) + Tag() Tag + Marshal([]byte) int + Unmarshal([]byte, int) (int, error) + Len() int + Children() []Atom +} + +type AtomPos struct { + Offset int + Size int +} + +func (self AtomPos) Pos() (int, int) { + return self.Offset, self.Size +} + +func (self *AtomPos) setPos(offset int, size int) { + self.Offset, self.Size = offset, size +} + +type Dummy struct { + Data []byte + Tag_ Tag + AtomPos +} + +func (self Dummy) Children() []Atom { + return nil +} + +func (self Dummy) Tag() Tag { + return self.Tag_ +} + +func (self Dummy) Len() int { + return len(self.Data) +} + +func (self Dummy) Marshal(b []byte) int { + copy(b, self.Data) + return len(self.Data) +} + +func (self *Dummy) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + self.Data = b + n = len(b) + return +} + +func StringToTag(tag string) Tag { + var b [4]byte + copy(b[:], []byte(tag)) + return Tag(pio.U32BE(b[:])) +} + +func FindChildrenByName(root Atom, tag string) Atom { + return FindChildren(root, StringToTag(tag)) +} + +func FindChildren(root Atom, tag Tag) Atom { + if root.Tag() == tag { + return root + } + for _, child := range root.Children() { + if r := FindChildren(child, tag); r != nil { + return r + } + } + return nil +} + +const ( + TFHD_BASE_DATA_OFFSET = 0x01 + TFHD_STSD_ID = 0x02 + TFHD_DEFAULT_DURATION = 0x08 + TFHD_DEFAULT_SIZE = 0x10 + TFHD_DEFAULT_FLAGS = 0x20 + TFHD_DURATION_IS_EMPTY = 0x010000 + TFHD_DEFAULT_BASE_IS_MOOF = 0x020000 +) + +const ( + TRUN_DATA_OFFSET = 0x01 + TRUN_FIRST_SAMPLE_FLAGS = 0x04 + TRUN_SAMPLE_DURATION = 0x100 + TRUN_SAMPLE_SIZE = 0x200 + TRUN_SAMPLE_FLAGS = 0x400 + TRUN_SAMPLE_CTS = 0x800 +) + +const ( + MP4ESDescrTag = 3 + MP4DecConfigDescrTag = 4 + MP4DecSpecificDescrTag = 5 + MP4DescrTag = 6 +) + +const ( + MP4DecConfigDataSize = 2 + 3 + 4 + 4 +) + +type ElemStreamDesc struct { + DecConfig []byte + TrackId uint16 + AtomPos +} + +func (self ElemStreamDesc) Children() []Atom { + return nil +} + +func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) { + for i := 3; i > 0; i-- { + b[n] = uint8(length>>uint(7*i))&0x7f | 0x80 + n++ + } + b[n] = uint8(length & 0x7f) + n++ + return +} + +func (self ElemStreamDesc) lenDescHdr() (n int) { + return 5 +} + +func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) { + b[n] = tag + n++ + n += self.fillLength(b[n:], datalen) + return +} + +func (self ElemStreamDesc) lenESDescHdr() (n int) { + return self.lenDescHdr() +} + +func (self ElemStreamDesc) lenESDescData() (n int) { + return 3 +} + +func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) { + n += self.fillDescHdr(b[n:], MP4ESDescrTag, datalen) + pio.PutU16BE(b[n:], self.TrackId) + n += 2 + b[n] = 0 // flags + n++ + return +} + +func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) { + return self.lenDescHdr() + MP4DecConfigDataSize + self.lenDescHdr() +} + +func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) { + n += self.fillDescHdr(b[n:], MP4DecConfigDescrTag, datalen) + b[n] = 0x40 // objectid + n++ + b[n] = 0x15 // streamtype + n++ + // buffer size db + pio.PutU24BE(b[n:], 0) + n += 3 + // max bitrage + pio.PutU32BE(b[n:], uint32(200000)) + n += 4 + // avg bitrage + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + n += self.fillDescHdr(b[n:], MP4DecSpecificDescrTag, datalen-n) + return +} + +func (self ElemStreamDesc) Len() (n int) { + n += 8 + n += 4 + // 0x03 MP4ESDescHeader + // 5 + n += self.lenESDescHdr() + + // + ESID + ESFlags + // + 2 + 1 + n += self.lenESDescData() + + // 0x04 MP4DecConfigDescrTag + MP4DecConfigDataSize + 0x05 MP4DecSpecificDescrHeader + // 5 + 13 + 5 + n += self.lenDecConfigDescHdr() + + // Variable size configuration + n += len(self.DecConfig) + + // 0x06 MP4DescrHeader + 1 + // 5 + 1 + n += self.lenDescHdr() + 1 + + return // 8 + 4 + self.lenESDescHdr() + self.lenDecConfigDescHdr() + len(self.DecConfig) + self.lenDescHdr() + 1 +} + +// Version(4) +// ESDesc( +// MP4ESDescrTag +// ESID(2) +// ESFlags(1) +// DecConfigDesc( +// MP4DecConfigDescrTag +// objectId streamType bufSize avgBitrate +// DecSpecificDesc( +// MP4DecSpecificDescrTag +// decConfig +// ) +// ) +// ?Desc(lenDescHdr+1) +// ) + +func (self ElemStreamDesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(ESDS)) + n += 8 + pio.PutU32BE(b[n:], 0) // Version + n += 4 + datalen := self.Len() + n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr()) + + n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-self.lenDescHdr()-1) + copy(b[n:], self.DecConfig) + n += len(self.DecConfig) + + n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr()) + b[n] = 0x02 + n++ + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (self *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) { + if len(b) < n+12 { + err = parseErr("hdr", offset+n, err) + return + } + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + n += 4 + return self.parseDesc(b[n:], offset+n) +} + +func (self *ElemStreamDesc) parseDesc(b []byte, offset int) (n int, err error) { + var hdrlen int + var datalen int + var tag uint8 + if hdrlen, tag, datalen, err = self.parseDescHdr(b, offset); err != nil { + return + } + + // Skip over the header lenth (tag size 1 byte + lenlen) + n += hdrlen + + if len(b) < n+datalen { + err = parseErr("datalen", offset+n, err) + return + } + + switch tag { + case MP4ESDescrTag: + if len(b) < n+3 { + err = parseErr("MP4ESDescrTag", offset+n, err) + return + } + if _, err = self.parseDesc(b[n+3:], offset+n+3); err != nil { + return + } + + case MP4DecConfigDescrTag: + if len(b) < n+MP4DecConfigDataSize { + err = parseErr("MP4DecSpecificDescrTag", offset+n, err) + return + } + if _, err = self.parseDesc(b[n+MP4DecConfigDataSize:], offset+n+MP4DecConfigDataSize); err != nil { + return + } + + case MP4DecSpecificDescrTag: + self.DecConfig = b[n : n+datalen] + } + + n += datalen + return +} + +func (self *ElemStreamDesc) parseLength(b []byte, offset int) (n int, length int, err error) { + for n < 4 { + if len(b) < n+1 { + err = parseErr("len", offset+n, err) + return + } + c := b[n] + n++ + length = (length << 7) | (int(c) & 0x7f) + if c&0x80 == 0 { + break + } + } + return +} + +func (self *ElemStreamDesc) parseDescHdr(b []byte, offset int) (n int, tag uint8, datalen int, err error) { + var lenlen int + if len(b) < n+1 { + err = parseErr("tag", offset+n, err) + return + } + tag = b[n] + n++ + if lenlen, datalen, err = self.parseLength(b[n:], offset+n); err != nil { + return + } + n += lenlen + return +} + +func ReadFileAtoms(r io.ReadSeeker) (atoms []Atom, err error) { + for { + offset, _ := r.Seek(0, 1) + taghdr := make([]byte, 8) + if _, err = io.ReadFull(r, taghdr); err != nil { + if err == io.EOF { + err = nil + } + return + } + size := pio.U32BE(taghdr[0:]) + tag := Tag(pio.U32BE(taghdr[4:])) + + var atom Atom + switch tag { + case MOOV: + atom = &Movie{} + case MOOF: + atom = &MovieFrag{} + } + + if atom != nil { + b := make([]byte, int(size)) + if _, err = io.ReadFull(r, b[8:]); err != nil { + return + } + copy(b, taghdr) + if _, err = atom.Unmarshal(b, int(offset)); err != nil { + return + } + atoms = append(atoms, atom) + } else { + dummy := &Dummy{Tag_: tag} + dummy.setPos(int(offset), int(size)) + if _, err = r.Seek(int64(size)-8, 1); err != nil { + return + } + atoms = append(atoms, dummy) + } + } + return +} + +func printatom(out io.Writer, root Atom, depth int) { + offset, size := root.Pos() + + type stringintf interface { + String() string + } + + fmt.Fprintf(out, + "%s%s offset=%d size=%d", + strings.Repeat(" ", depth*2), root.Tag(), offset, size, + ) + if str, ok := root.(stringintf); ok { + fmt.Fprint(out, " ", str.String()) + } + fmt.Fprintln(out) + + children := root.Children() + for _, child := range children { + printatom(out, child, depth+1) + } +} + +func FprintAtom(out io.Writer, root Atom) { + printatom(out, root, 0) +} + +func PrintAtom(root Atom) { + FprintAtom(os.Stdout, root) +} + +func (self MovieHeader) String() string { + return fmt.Sprintf("dur=%d", self.Duration) +} + +func (self TimeToSample) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self SampleToChunk) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self SampleSize) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self SyncSample) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self CompositionOffset) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self ChunkOffset) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self TrackFragRun) String() string { + return fmt.Sprintf("dataoffset=%d", self.DataOffset) +} + +func (self TrackFragHeader) String() string { + return fmt.Sprintf("basedataoffset=%d", self.BaseDataOffset) +} + +func (self ElemStreamDesc) String() string { + return fmt.Sprintf("configlen=%d", len(self.DecConfig)) +} + +func (self *Track) GetAVC1Conf() (conf *AVC1Conf) { + atom := FindChildren(self, AVCC) + conf, _ = atom.(*AVC1Conf) + return +} + +func (self *Track) GetElemStreamDesc() (esds *ElemStreamDesc) { + atom := FindChildren(self, ESDS) + esds, _ = atom.(*ElemStreamDesc) + return +} diff --git a/machinery/src/mp4/muxer.go b/machinery/src/mp4/muxer.go new file mode 100644 index 00000000..8b9a2177 --- /dev/null +++ b/machinery/src/mp4/muxer.go @@ -0,0 +1,381 @@ +package mp4 + +import ( + "fmt" + "io" + "time" + + "github.com/kerberos-io/agent/machinery/src/mp4/mp4io" + "github.com/kerberos-io/agent/machinery/src/packets" + "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" +) + +type Muxer struct { + w io.WriteSeeker + wpos int64 + streams []*Stream + videoCodecIndex int + AudioCodecIndex int +} + +func NewMuxer(w io.WriteSeeker) *Muxer { + return &Muxer{ + w: w, + } +} + +func (self *Muxer) newStream(codec packets.Stream, index int, withoutAudio bool) (err error) { + switch codec.Name { + case "H264": + self.videoCodecIndex = index + case "AAC": + default: + self.AudioCodecIndex = index + if withoutAudio { + return + } + } + + stream := &Stream{CodecData: codec} + + stream.sample = &mp4io.SampleTable{ + SampleDesc: &mp4io.SampleDesc{}, + TimeToSample: &mp4io.TimeToSample{}, + SampleToChunk: &mp4io.SampleToChunk{ + Entries: []mp4io.SampleToChunkEntry{ + { + FirstChunk: 1, + SampleDescId: 1, + SamplesPerChunk: 1, + }, + }, + }, + SampleSize: &mp4io.SampleSize{}, + ChunkOffset: &mp4io.ChunkOffset{}, + } + + stream.trackAtom = &mp4io.Track{ + Header: &mp4io.TrackHeader{ + TrackId: int32(len(self.streams) + 1), + Flags: 0x0003, // Track enabled | Track in movie + Duration: 0, // fill later + Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + }, + Media: &mp4io.Media{ + Header: &mp4io.MediaHeader{ + TimeScale: 0, // fill later + Duration: 0, // fill later + Language: 21956, + }, + Info: &mp4io.MediaInfo{ + Sample: stream.sample, + Data: &mp4io.DataInfo{ + Refer: &mp4io.DataRefer{ + Url: &mp4io.DataReferUrl{ + Flags: 0x000001, // Self reference + }, + }, + }, + }, + }, + } + + switch codec.Name { + case "H264": + stream.sample.SyncSample = &mp4io.SyncSample{} + } + + stream.timeScale = 10000000 + stream.muxer = self + self.streams = append(self.streams, stream) + + return +} + +func (self *Stream) fillTrackAtom() (err error) { + self.trackAtom.Media.Header.TimeScale = int32(self.timeScale) + self.trackAtom.Media.Header.Duration = int32(self.duration) + + if self.CodecData.Name == "H264" { + + codec := self.CodecData + width, height := codec.Width, codec.Height + self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: int16(width), + Height: int16(height), + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + //Conf: &mp4io.AVC1Conf{Data: codec.AVCDecoderConfRecordBytes()}, + } + self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ + SubType: [4]byte{'v', 'i', 'd', 'e'}, + Name: []byte("Video Media Handler"), + } + self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{ + Flags: 0x000001, + } + self.trackAtom.Header.TrackWidth = float64(width) + self.trackAtom.Header.TrackHeight = float64(height) + + } else if self.CodecData.Name == "AAC" { + /*codec := self.CodecData.(aacparser.CodecData) + self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{ + DataRefIdx: 1, + NumberOfChannels: int16(codec.ChannelLayout().Count()), + SampleSize: int16(codec.SampleFormat().BytesPerSample()), + SampleRate: float64(codec.SampleRate()), + Conf: &mp4io.ElemStreamDesc{ + DecConfig: codec.MPEG4AudioConfigBytes(), + }, + } + self.trackAtom.Header.Volume = 1 + self.trackAtom.Header.AlternateGroup = 1 + self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ + SubType: [4]byte{'s', 'o', 'u', 'n'}, + Name: []byte("Sound Handler"), + } + self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}*/ + + } else { + err = fmt.Errorf("mp4: codec type=%d invalid", self.CodecData.Name) + } + + return +} + +func (self *Muxer) WriteHeader(streams []packets.Stream) (err error) { + self.streams = []*Stream{} + for index, stream := range streams { + if err = self.newStream(stream, index, false); err != nil { + } + } + + taghdr := make([]byte, 8) + pio.PutU32BE(taghdr[4:], uint32(mp4io.MDAT)) + if _, err = self.w.Write(taghdr); err != nil { + return + } + taghdr = nil + self.wpos += 8 + + for _, stream := range self.streams { + if stream.CodecData.Name == "H264" { + stream.sample.CompositionOffset = &mp4io.CompositionOffset{} + } + } + return +} + +func (self *Muxer) WritePacket(pkt packets.Packet) (err error) { + stream := self.streams[pkt.Idx] + switch stream.CodecData.Name { + case "H264", "av.AAC": + if stream.lastpkt != nil { + if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil { + return + } + } + stream.lastpkt = &pkt + return + default: + return + } +} + +func (self *Muxer) Write(buffer []byte, channel int, time uint32) (err error) { + return nil +} + +func (self *Stream) writePacket(pkt packets.Packet, rawdur time.Duration) (err error) { + if rawdur < 0 { + err = fmt.Errorf("mp4: stream#%d time=%v < lasttime=%v", pkt.Idx, pkt.Time, self.lastpkt.Time) + return + } + + if _, err = self.muxer.w.Write(pkt.Data); err != nil { + return + } + + if pkt.IsKeyFrame && self.sample.SyncSample != nil { + self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, uint32(self.sampleIndex+1)) + } + + duration := uint32(self.timeToTs(rawdur)) + if self.sttsEntry == nil || duration != self.sttsEntry.Duration { + self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, mp4io.TimeToSampleEntry{Duration: duration}) + self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] + } + self.sttsEntry.Count++ + + if self.sample.CompositionOffset != nil { + offset := uint32(self.timeToTs(pkt.CompositionTime)) + if self.cttsEntry == nil || offset != self.cttsEntry.Offset { + table := self.sample.CompositionOffset + table.Entries = append(table.Entries, mp4io.CompositionOffsetEntry{Offset: offset}) + self.cttsEntry = &table.Entries[len(table.Entries)-1] + } + self.cttsEntry.Count++ + } + + self.duration += int64(duration) + self.sampleIndex++ + self.sample.ChunkOffset.Entries = append(self.sample.ChunkOffset.Entries, uint32(self.muxer.wpos)) + self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, uint32(len(pkt.Data))) + + self.muxer.wpos += int64(len(pkt.Data)) + return +} + +func (self *Muxer) WriteTrailer() (err error) { + + for _, stream := range self.streams { + switch stream.CodecData.Name { + case "H264", "av.AAC": + if stream.lastpkt != nil { + if err = stream.writePacket(*stream.lastpkt, 0); err != nil { + //return + } + stream.lastpkt = nil + } + default: + } + } + + moov := &mp4io.Movie{} + moov.Header = &mp4io.MovieHeader{ + PreferredRate: 1, + PreferredVolume: 1, + Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + NextTrackId: 2, + } + + maxDur := time.Duration(0) + //timeScale := int64(10000) + timeScale := int64(1000) + for _, stream := range self.streams { + switch stream.CodecData.Name { + case "H264", "av.AAC": + if err = stream.fillTrackAtom(); err != nil { + return + } + dur := stream.tsToTime(stream.duration) + stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale)) + if dur > maxDur { + maxDur = dur + } + moov.Tracks = append(moov.Tracks, stream.trackAtom) + } + } + moov.Header.TimeScale = int32(timeScale) + moov.Header.Duration = int32(timeToTs(maxDur, timeScale)) + + var mdatsize int64 + if mdatsize, err = self.w.Seek(0, 1); err != nil { + return + } + if _, err = self.w.Seek(0, 0); err != nil { + return + } + taghdr := make([]byte, 4) + pio.PutU32BE(taghdr, uint32(mdatsize)) + if _, err = self.w.Write(taghdr); err != nil { + return + } + taghdr = nil + + if _, err = self.w.Seek(0, 2); err != nil { + return + } + b := make([]byte, moov.Len()) + moov.Marshal(b) + if _, err = self.w.Write(b); err != nil { + return + } + b = nil + return +} + +func (self *Muxer) WriteTrailerWithPacket(pkt packets.Packet) (err error) { + + for _, stream := range self.streams { + switch stream.CodecData.Name { + case "H264", "av.AAC": + if stream.lastpkt != nil { + if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil { + //return + } + stream.lastpkt = nil + } + default: + } + } + + moov := &mp4io.Movie{} + moov.Header = &mp4io.MovieHeader{ + PreferredRate: 1, + PreferredVolume: 1, + Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + NextTrackId: 2, + } + + maxDur := time.Duration(0) + //timeScale := int64(10000) + timeScale := int64(1000) + for _, stream := range self.streams { + switch stream.CodecData.Name { + case "H264", "av.AAC": + if err = stream.fillTrackAtom(); err != nil { + return + } + dur := stream.tsToTime(stream.duration) + stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale)) + if dur > maxDur { + maxDur = dur + } + moov.Tracks = append(moov.Tracks, stream.trackAtom) + } + } + moov.Header.TimeScale = int32(timeScale) + moov.Header.Duration = int32(timeToTs(maxDur, timeScale)) + + var mdatsize int64 + if mdatsize, err = self.w.Seek(0, 1); err != nil { + return + } + if _, err = self.w.Seek(0, 0); err != nil { + return + } + taghdr := make([]byte, 4) + pio.PutU32BE(taghdr, uint32(mdatsize)) + if _, err = self.w.Write(taghdr); err != nil { + return + } + taghdr = nil + + if _, err = self.w.Seek(0, 2); err != nil { + return + } + b := make([]byte, moov.Len()) + moov.Marshal(b) + if _, err = self.w.Write(b); err != nil { + return + } + b = nil + return +} + +func (self *Muxer) Close() (err error) { + for _, stream := range self.streams { + stream.muxer = nil + stream.trackAtom = nil + stream.sample = nil + stream.lastpkt = nil + stream = nil + } + self.streams = nil + return +} diff --git a/machinery/src/mp4/stream.go b/machinery/src/mp4/stream.go new file mode 100644 index 00000000..e9eb5133 --- /dev/null +++ b/machinery/src/mp4/stream.go @@ -0,0 +1,58 @@ +package mp4 + +import ( + "time" + + "github.com/kerberos-io/agent/machinery/src/mp4/mp4io" + "github.com/kerberos-io/agent/machinery/src/packets" +) + +type Stream struct { + CodecData packets.Stream + + trackAtom *mp4io.Track + idx int + + lastpkt *packets.Packet + + timeScale int64 + duration int64 + + muxer *Muxer + + sample *mp4io.SampleTable + sampleIndex int + + sampleOffsetInChunk int64 + syncSampleIndex int + + dts int64 + sttsEntryIndex int + sampleIndexInSttsEntry int + + cttsEntryIndex int + sampleIndexInCttsEntry int + + chunkGroupIndex int + chunkIndex int + sampleIndexInChunk int + + sttsEntry *mp4io.TimeToSampleEntry + cttsEntry *mp4io.CompositionOffsetEntry +} + +func timeToTs(tm time.Duration, timeScale int64) int64 { + return int64(tm * time.Duration(timeScale) / time.Second) +} + +func tsToTime(ts int64, timeScale int64) time.Duration { + return time.Duration(ts) * time.Second / time.Duration(timeScale) +} + +func (self *Stream) timeToTs(tm time.Duration) int64 { + return int64(tm * time.Duration(self.timeScale) / time.Second) +} + +func (self *Stream) tsToTime(ts int64) time.Duration { + return time.Duration(ts) * time.Second / time.Duration(self.timeScale) +} diff --git a/machinery/src/packets/packet.go b/machinery/src/packets/packet.go index e056ccbd..c2e5f4e7 100644 --- a/machinery/src/packets/packet.go +++ b/machinery/src/packets/packet.go @@ -8,14 +8,13 @@ import ( // Packet represents an RTP Packet type Packet struct { - Packet *rtp.Packet - AccessUnits [][]byte + // for Gortsplib library + Packet *rtp.Packet - // for JOY4 library + // for JOY4 and Gortsplib library IsKeyFrame bool // video packet is key frame Idx int8 // stream index in container format CompositionTime time.Duration // packet presentation time minus decode time for H264 B-Frame Time time.Duration // packet decode time Data []byte // packet data - } diff --git a/machinery/src/utils/bits/bits.go b/machinery/src/utils/bits/bits.go new file mode 100644 index 00000000..4a09f0a2 --- /dev/null +++ b/machinery/src/utils/bits/bits.go @@ -0,0 +1,118 @@ +package bits + +import ( + "io" +) + +type Reader struct { + R io.Reader + n int + bits uint64 +} + +func (self *Reader) ReadBits64(n int) (bits uint64, err error) { + if self.n < n { + var b [8]byte + var got int + want := (n - self.n + 7) / 8 + if got, err = self.R.Read(b[:want]); err != nil { + return + } + if got < want { + err = io.EOF + return + } + for i := 0; i < got; i++ { + self.bits <<= 8 + self.bits |= uint64(b[i]) + } + self.n += got * 8 + } + bits = self.bits >> uint(self.n-n) + self.bits ^= bits << uint(self.n-n) + self.n -= n + return +} + +func (self *Reader) ReadBits(n int) (bits uint, err error) { + var bits64 uint64 + if bits64, err = self.ReadBits64(n); err != nil { + return + } + bits = uint(bits64) + return +} + +func (self *Reader) Read(p []byte) (n int, err error) { + for n < len(p) { + want := 8 + if len(p)-n < want { + want = len(p) - n + } + var bits uint64 + if bits, err = self.ReadBits64(want * 8); err != nil { + break + } + for i := 0; i < want; i++ { + p[n+i] = byte(bits >> uint((want-i-1)*8)) + } + n += want + } + return +} + +type Writer struct { + W io.Writer + n int + bits uint64 +} + +func (self *Writer) WriteBits64(bits uint64, n int) (err error) { + if self.n+n > 64 { + move := uint(64 - self.n) + mask := bits >> move + self.bits = (self.bits << move) | mask + self.n = 64 + if err = self.FlushBits(); err != nil { + return + } + n -= int(move) + bits ^= (mask << move) + } + self.bits = (self.bits << uint(n)) | bits + self.n += n + return +} + +func (self *Writer) WriteBits(bits uint, n int) (err error) { + return self.WriteBits64(uint64(bits), n) +} + +func (self *Writer) Write(p []byte) (n int, err error) { + for n < len(p) { + if err = self.WriteBits64(uint64(p[n]), 8); err != nil { + return + } + n++ + } + return +} + +func (self *Writer) FlushBits() (err error) { + if self.n > 0 { + var b [8]byte + bits := self.bits + if self.n%8 != 0 { + bits <<= uint(8 - (self.n % 8)) + } + want := (self.n + 7) / 8 + for i := 0; i < want; i++ { + b[i] = byte(bits >> uint((want-i-1)*8)) + } + if _, err = self.W.Write(b[:want]); err != nil { + return + } + self.n = 0 + } + return +} diff --git a/machinery/src/utils/bits/bits_test.go b/machinery/src/utils/bits/bits_test.go new file mode 100644 index 00000000..c4957c94 --- /dev/null +++ b/machinery/src/utils/bits/bits_test.go @@ -0,0 +1,51 @@ +package bits + +import ( + "bytes" + "testing" +) + +func TestBits(t *testing.T) { + rdata := []byte{0xf3, 0xb3, 0x45, 0x60} + rbuf := bytes.NewReader(rdata[:]) + r := &Reader{R: rbuf} + var u32 uint + if u32, _ = r.ReadBits(4); u32 != 0xf { + t.FailNow() + } + if u32, _ = r.ReadBits(4); u32 != 0x3 { + t.FailNow() + } + if u32, _ = r.ReadBits(2); u32 != 0x2 { + t.FailNow() + } + if u32, _ = r.ReadBits(2); u32 != 0x3 { + t.FailNow() + } + b := make([]byte, 2) + if r.Read(b); b[0] != 0x34 || b[1] != 0x56 { + t.FailNow() + } + + wbuf := &bytes.Buffer{} + w := &Writer{W: wbuf} + w.WriteBits(0xf, 4) + w.WriteBits(0x3, 4) + w.WriteBits(0x2, 2) + w.WriteBits(0x3, 2) + n, _ := w.Write([]byte{0x34, 0x56}) + if n != 2 { + t.FailNow() + } + w.FlushBits() + wdata := wbuf.Bytes() + if wdata[0] != 0xf3 || wdata[1] != 0xb3 || wdata[2] != 0x45 || wdata[3] != 0x60 { + t.FailNow() + } + + b = make([]byte, 8) + PutUInt64BE(b, 0x11223344, 32) + if b[0] != 0x11 || b[1] != 0x22 || b[2] != 0x33 || b[3] != 0x44 { + t.FailNow() + } +} diff --git a/machinery/src/utils/bits/bufio/bufio.go b/machinery/src/utils/bits/bufio/bufio.go new file mode 100644 index 00000000..ec7eedfe --- /dev/null +++ b/machinery/src/utils/bits/bufio/bufio.go @@ -0,0 +1,23 @@ +package bufio + +import ( + "io" +) + +type Reader struct { + buf [][]byte + R io.ReadSeeker +} + +func NewReaderSize(r io.ReadSeeker, size int) *Reader { + buf := make([]byte, size*2) + return &Reader{ + R: r, + buf: [][]byte{buf[0:size], buf[size:]}, + } +} + +func (self *Reader) ReadAt(b []byte, off int64) (n int, err error) { + return +} + diff --git a/machinery/src/utils/bits/golomb_reader.go b/machinery/src/utils/bits/golomb_reader.go new file mode 100644 index 00000000..b40978be --- /dev/null +++ b/machinery/src/utils/bits/golomb_reader.go @@ -0,0 +1,89 @@ +package bits + +import ( + "io" +) + +type GolombBitReader struct { + R io.Reader + buf [1]byte + left byte +} + +func (self *GolombBitReader) ReadBit() (res uint, err error) { + if self.left == 0 { + if _, err = self.R.Read(self.buf[:]); err != nil { + return + } + self.left = 8 + } + self.left-- + res = uint(self.buf[0]>>self.left) & 1 + return +} + +func (self *GolombBitReader) ReadBits(n int) (res uint, err error) { + for i := 0; i < n; i++ { + var bit uint + if bit, err = self.ReadBit(); err != nil { + return + } + res |= bit << uint(n-i-1) + } + return +} + +func (self *GolombBitReader) ReadBits32(n uint) (r uint32, err error) { + var t uint + for i := uint(0); i < n; i++ { + t, err = self.ReadBit() + if err != nil { + return + } + r = (r << 1) | uint32(t) + } + return +} + +func (self *GolombBitReader) ReadBits64(n uint) (r uint64, err error) { + var t uint + for i := uint(0); i < n; i++ { + t, err = self.ReadBit() + if err != nil { + return + } + r = (r << 1) | uint64(t) + } + return +} + +func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) { + i := 0 + for { + var bit uint + if bit, err = self.ReadBit(); err != nil { + return + } + if !(bit == 0 && i < 32) { + break + } + i++ + } + if res, err = self.ReadBits(i); err != nil { + return + } + res += (1 << uint(i)) - 1 + return +} + +func (self *GolombBitReader) ReadSE() (res uint, err error) { + if res, err = self.ReadExponentialGolombCode(); err != nil { + return + } + if res&0x01 != 0 { + res = (res + 1) / 2 + } else { + res = -res / 2 + } + return +} diff --git a/machinery/src/utils/bits/pio/pio.go b/machinery/src/utils/bits/pio/pio.go new file mode 100644 index 00000000..3d7660da --- /dev/null +++ b/machinery/src/utils/bits/pio/pio.go @@ -0,0 +1,3 @@ +package pio + +var RecommendBufioSize = 1024 * 1024 * 1 // 1mb in buffer, then write :) diff --git a/machinery/src/utils/bits/pio/reader.go b/machinery/src/utils/bits/pio/reader.go new file mode 100644 index 00000000..87f024bb --- /dev/null +++ b/machinery/src/utils/bits/pio/reader.go @@ -0,0 +1,91 @@ + +package pio + +func U8(b []byte) (i uint8) { + return b[0] +} + +func U16BE(b []byte) (i uint16) { + i = uint16(b[0]) + i <<= 8; i |= uint16(b[1]) + return +} + +func I16BE(b []byte) (i int16) { + i = int16(b[0]) + i <<= 8; i |= int16(b[1]) + return +} + +func I24BE(b []byte) (i int32) { + i = int32(int8(b[0])) + i <<= 8; i |= int32(b[1]) + i <<= 8; i |= int32(b[2]) + return +} + +func U24BE(b []byte) (i uint32) { + i = uint32(b[0]) + i <<= 8; i |= uint32(b[1]) + i <<= 8; i |= uint32(b[2]) + return +} + +func I32BE(b []byte) (i int32) { + i = int32(int8(b[0])) + i <<= 8; i |= int32(b[1]) + i <<= 8; i |= int32(b[2]) + i <<= 8; i |= int32(b[3]) + return +} + +func U32LE(b []byte) (i uint32) { + i = uint32(b[3]) + i <<= 8; i |= uint32(b[2]) + i <<= 8; i |= uint32(b[1]) + i <<= 8; i |= uint32(b[0]) + return +} + +func U32BE(b []byte) (i uint32) { + i = uint32(b[0]) + i <<= 8; i |= uint32(b[1]) + i <<= 8; i |= uint32(b[2]) + i <<= 8; i |= uint32(b[3]) + return +} + +func U40BE(b []byte) (i uint64) { + i = uint64(b[0]) + i <<= 8; i |= uint64(b[1]) + i <<= 8; i |= uint64(b[2]) + i <<= 8; i |= uint64(b[3]) + i <<= 8; i |= uint64(b[4]) + return +} + +func U64BE(b []byte) (i uint64) { + i = uint64(b[0]) + i <<= 8; i |= uint64(b[1]) + i <<= 8; i |= uint64(b[2]) + i <<= 8; i |= uint64(b[3]) + i <<= 8; i |= uint64(b[4]) + i <<= 8; i |= uint64(b[5]) + i <<= 8; i |= uint64(b[6]) + i <<= 8; i |= uint64(b[7]) + return +} + +func I64BE(b []byte) (i int64) { + i = int64(int8(b[0])) + i <<= 8; i |= int64(b[1]) + i <<= 8; i |= int64(b[2]) + i <<= 8; i |= int64(b[3]) + i <<= 8; i |= int64(b[4]) + i <<= 8; i |= int64(b[5]) + i <<= 8; i |= int64(b[6]) + i <<= 8; i |= int64(b[7]) + return +} + + diff --git a/machinery/src/utils/bits/pio/vec.go b/machinery/src/utils/bits/pio/vec.go new file mode 100644 index 00000000..30d5e921 --- /dev/null +++ b/machinery/src/utils/bits/pio/vec.go @@ -0,0 +1,69 @@ +package pio + +func VecLen(vec [][]byte) (n int) { + for _, b := range vec { + n += len(b) + } + return +} + +func VecSliceTo(in [][]byte, out [][]byte, s int, e int) (n int) { + if s < 0 { + s = 0 + } + + if e >= 0 && e < s { + panic("pio: VecSlice start > end") + } + + i := 0 + off := 0 + for s > 0 && i < len(in) { + left := len(in[i]) + read := s + if left < read { + read = left + } + left -= read + off += read + s -= read + e -= read + if left == 0 { + i++ + off = 0 + } + } + if s > 0 { + panic("pio: VecSlice start out of range") + } + + for e != 0 && i < len(in) { + left := len(in[i])-off + read := left + if e > 0 && e < read { + read = e + } + out[n] = in[i][off:off+read] + n++ + left -= read + e -= read + off += read + if left == 0 { + i++ + off = 0 + } + } + if e > 0 { + panic("pio: VecSlice end out of range") + } + + return +} + +func VecSlice(in [][]byte, s int, e int) (out [][]byte) { + out = make([][]byte, len(in)) + n := VecSliceTo(in, out, s, e) + out = out[:n] + return +} + diff --git a/machinery/src/utils/bits/pio/vec_test.go b/machinery/src/utils/bits/pio/vec_test.go new file mode 100644 index 00000000..99ebb55e --- /dev/null +++ b/machinery/src/utils/bits/pio/vec_test.go @@ -0,0 +1,22 @@ + +package pio + +import ( + "fmt" +) + +func ExampleVec() { + vec := [][]byte{[]byte{1,2,3}, []byte{4,5,6,7,8,9}, []byte{10,11,12,13}} + println(VecLen(vec)) + + vec = VecSlice(vec, 1, -1) + fmt.Println(vec) + + vec = VecSlice(vec, 2, -1) + fmt.Println(vec) + + vec = VecSlice(vec, 8, 8) + fmt.Println(vec) + + // Output: +} diff --git a/machinery/src/utils/bits/pio/writer.go b/machinery/src/utils/bits/pio/writer.go new file mode 100644 index 00000000..2e709f9d --- /dev/null +++ b/machinery/src/utils/bits/pio/writer.go @@ -0,0 +1,89 @@ + +package pio + +func PutU8(b []byte, v uint8) { + b[0] = v +} + +func PutI16BE(b []byte, v int16) { + b[0] = byte(v>>8) + b[1] = byte(v) +} + +func PutU16BE(b []byte, v uint16) { + b[0] = byte(v>>8) + b[1] = byte(v) +} + +func PutI24BE(b []byte, v int32) { + b[0] = byte(v>>16) + b[1] = byte(v>>8) + b[2] = byte(v) +} + +func PutU24BE(b []byte, v uint32) { + b[0] = byte(v>>16) + b[1] = byte(v>>8) + b[2] = byte(v) +} + +func PutI32BE(b []byte, v int32) { + b[0] = byte(v>>24) + b[1] = byte(v>>16) + b[2] = byte(v>>8) + b[3] = byte(v) +} + +func PutU32BE(b []byte, v uint32) { + b[0] = byte(v>>24) + b[1] = byte(v>>16) + b[2] = byte(v>>8) + b[3] = byte(v) +} + +func PutU32LE(b []byte, v uint32) { + b[3] = byte(v>>24) + b[2] = byte(v>>16) + b[1] = byte(v>>8) + b[0] = byte(v) +} + +func PutU40BE(b []byte, v uint64) { + b[0] = byte(v>>32) + b[1] = byte(v>>24) + b[2] = byte(v>>16) + b[3] = byte(v>>8) + b[4] = byte(v) +} + +func PutU48BE(b []byte, v uint64) { + b[0] = byte(v>>40) + b[1] = byte(v>>32) + b[2] = byte(v>>24) + b[3] = byte(v>>16) + b[4] = byte(v>>8) + b[5] = byte(v) +} + +func PutU64BE(b []byte, v uint64) { + b[0] = byte(v>>56) + b[1] = byte(v>>48) + b[2] = byte(v>>40) + b[3] = byte(v>>32) + b[4] = byte(v>>24) + b[5] = byte(v>>16) + b[6] = byte(v>>8) + b[7] = byte(v) +} + +func PutI64BE(b []byte, v int64) { + b[0] = byte(v>>56) + b[1] = byte(v>>48) + b[2] = byte(v>>40) + b[3] = byte(v>>32) + b[4] = byte(v>>24) + b[5] = byte(v>>16) + b[6] = byte(v>>8) + b[7] = byte(v) +} + From 1359858e42c3938eb3f8cf4efc3b2e8740ce1813 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 29 Nov 2023 15:01:36 +0100 Subject: [PATCH 06/81] updates and cleanup --- machinery/src/capture/Gortsplib.go | 11 ++++-- machinery/src/capture/IPCamera.go | 56 ---------------------------- machinery/src/capture/main.go | 22 ++++++++--- machinery/src/components/Kerberos.go | 2 +- machinery/src/mp4/muxer.go | 12 +++--- 5 files changed, 31 insertions(+), 72 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index a127b633..f2d5f597 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -66,19 +66,21 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { // parse URL u, err := base.ParseURL(g.Url) if err != nil { - panic(err) + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + return } // connect to the server err = g.Client.Start(u.Scheme, u.Host) if err != nil { - panic(err) + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) } // find published medias desc, _, err := g.Client.Describe(u) if err != nil { - panic(err) + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + return } // find the H264 media and format @@ -95,6 +97,8 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { IsAudio: false, SPS: forma.SPS, PPS: forma.PPS, + Width: 640, + Height: 480, }) // Set the index for the video @@ -245,7 +249,6 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati continue case h264.NALUTypeIDR: idrPresent = true - case h264.NALUTypeNonIDR: nonIDRPresent = true } diff --git a/machinery/src/capture/IPCamera.go b/machinery/src/capture/IPCamera.go index 8108d183..f08d09aa 100644 --- a/machinery/src/capture/IPCamera.go +++ b/machinery/src/capture/IPCamera.go @@ -1,7 +1,6 @@ package capture import ( - "context" "strconv" "sync" "time" @@ -11,64 +10,9 @@ import ( "github.com/kerberos-io/joy4/av/pubsub" "github.com/kerberos-io/joy4/av" - "github.com/kerberos-io/joy4/av/avutil" "github.com/kerberos-io/joy4/cgo/ffmpeg" - "github.com/kerberos-io/joy4/format" ) -func OpenRTSP(ctx context.Context, url string, withBackChannel bool) (av.DemuxCloser, []av.CodecData, error) { - format.RegisterAll() - - // Try with backchannel first (if variable is set to true) - // If set to true, it will try to open the stream with a backchannel - // If fails we will try again (see below). - infile, err := avutil.Open(ctx, url, withBackChannel) - if err == nil { - streams, errstreams := infile.Streams() - if len(streams) > 0 { - return infile, streams, errstreams - } else { - // Try again without backchannel - log.Log.Info("OpenRTSP: trying without backchannel") - withBackChannel = false - infile, err := avutil.Open(ctx, url, withBackChannel) - if err == nil { - streams, errstreams := infile.Streams() - return infile, streams, errstreams - } - } - } - return nil, []av.CodecData{}, err -} - -func GetVideoStream(streams []av.CodecData) (av.CodecData, error) { - var videoStream av.CodecData - for _, stream := range streams { - if stream.Type().IsAudio() { - //astream := stream.(av.AudioCodecData) - } else if stream.Type().IsVideo() { - videoStream = stream - } - } - return videoStream, nil -} - -func GetVideoDecoder(decoder *ffmpeg.VideoDecoder, streams []av.CodecData) { - // Load video codec - var vstream av.VideoCodecData - for _, stream := range streams { - if stream.Type().IsAudio() { - //astream := stream.(av.AudioCodecData) - } else if stream.Type().IsVideo() { - vstream = stream.(av.VideoCodecData) - } - } - err := ffmpeg.NewVideoDecoder(decoder, vstream) - if err != nil { - log.Log.Error("GetVideoDecoder: " + err.Error()) - } -} - func DecodeImage(frame *ffmpeg.VideoFrame, pkt av.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) { decoderMutex.Lock() img, err := decoder.Decode(frame, pkt.Data) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index b28c4282..0b0e0cdb 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -471,15 +471,27 @@ func VerifyCamera(c *gin.Context) { if streamType == "secondary" { rtspUrl = cameraStreams.SubRTSP } - _, codecs, err := OpenRTSP(ctx, rtspUrl, true) + + // Currently only support H264 encoded cameras, this will change. + // Establishing the camera connection without backchannel if no substream + withBackChannel := true + rtspClient := &Golibrtsp{ + Url: rtspUrl, + WithBackChannel: withBackChannel, + } + + err := rtspClient.Connect(ctx) if err == nil { + // Get the streams from the rtsp client. + streams, _ := rtspClient.GetStreams() + videoIdx := -1 audioIdx := -1 - for i, codec := range codecs { - if codec.Type().String() == "H264" && videoIdx < 0 { + for i, stream := range streams { + if stream.Name == "H264" && videoIdx < 0 { videoIdx = i - } else if codec.Type().String() == "PCM_MULAW" && audioIdx < 0 { + } else if stream.Name == "PCM_MULAW" && audioIdx < 0 { audioIdx = i } } @@ -487,7 +499,7 @@ func VerifyCamera(c *gin.Context) { if videoIdx > -1 { c.JSON(200, models.APIResponse{ Message: "All good, detected a H264 codec.", - Data: codecs, + Data: streams, }) } else { c.JSON(400, models.APIResponse{ diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 7070aa2b..707babf0 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -129,7 +129,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Get the video streams from the RTSP server. videoStreams, err := rtspClient.GetVideoStreams() - if err != nil && len(videoStreams) == 0 { + if err != nil || len(videoStreams) == 0 { log.Log.Error("RunAgent: no video stream found, might be the wrong codec (we only support H264 for the moment)") time.Sleep(time.Second * 3) return status diff --git a/machinery/src/mp4/muxer.go b/machinery/src/mp4/muxer.go index 8b9a2177..0474141a 100644 --- a/machinery/src/mp4/muxer.go +++ b/machinery/src/mp4/muxer.go @@ -81,7 +81,7 @@ func (self *Muxer) newStream(codec packets.Stream, index int, withoutAudio bool) } switch codec.Name { - case "H264": + case "H264", "H265": stream.sample.SyncSample = &mp4io.SyncSample{} } @@ -173,7 +173,7 @@ func (self *Muxer) WriteHeader(streams []packets.Stream) (err error) { func (self *Muxer) WritePacket(pkt packets.Packet) (err error) { stream := self.streams[pkt.Idx] switch stream.CodecData.Name { - case "H264", "av.AAC": + case "H264", "AAC": if stream.lastpkt != nil { if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil { return @@ -234,7 +234,7 @@ func (self *Muxer) WriteTrailer() (err error) { for _, stream := range self.streams { switch stream.CodecData.Name { - case "H264", "av.AAC": + case "H264", "AAC": if stream.lastpkt != nil { if err = stream.writePacket(*stream.lastpkt, 0); err != nil { //return @@ -258,7 +258,7 @@ func (self *Muxer) WriteTrailer() (err error) { timeScale := int64(1000) for _, stream := range self.streams { switch stream.CodecData.Name { - case "H264", "av.AAC": + case "H264", "AAC": if err = stream.fillTrackAtom(); err != nil { return } @@ -303,7 +303,7 @@ func (self *Muxer) WriteTrailerWithPacket(pkt packets.Packet) (err error) { for _, stream := range self.streams { switch stream.CodecData.Name { - case "H264", "av.AAC": + case "H264", "AAC": if stream.lastpkt != nil { if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil { //return @@ -327,7 +327,7 @@ func (self *Muxer) WriteTrailerWithPacket(pkt packets.Packet) (err error) { timeScale := int64(1000) for _, stream := range self.streams { switch stream.CodecData.Name { - case "H264", "av.AAC": + case "H264", "AAC": if err = stream.fillTrackAtom(); err != nil { return } From 19bf456bdaf3eafffe30da395f9a67b68482d57a Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 29 Nov 2023 16:28:09 +0100 Subject: [PATCH 07/81] adding fragmented mp4 (not working) trying to fix black screen on quicktime player mp4 --- machinery/src/capture/Gortsplib.go | 1 + machinery/src/capture/main.go | 1 + machinery/src/fmp4/mp4io/atoms.go | 3561 +++++++++++++++++++++++ machinery/src/fmp4/mp4io/gen/gen.go | 1057 +++++++ machinery/src/fmp4/mp4io/gen/pattern.go | 438 +++ machinery/src/fmp4/mp4io/mp4io.go | 535 ++++ machinery/src/fmp4/muxer.go | 568 ++++ machinery/src/fmp4/stream.go | 45 + machinery/src/mp4/muxer.go | 70 +- 9 files changed, 6274 insertions(+), 2 deletions(-) create mode 100644 machinery/src/fmp4/mp4io/atoms.go create mode 100644 machinery/src/fmp4/mp4io/gen/gen.go create mode 100644 machinery/src/fmp4/mp4io/gen/pattern.go create mode 100644 machinery/src/fmp4/mp4io/mp4io.go create mode 100644 machinery/src/fmp4/muxer.go create mode 100644 machinery/src/fmp4/stream.go diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index f2d5f597..3255f06a 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -202,6 +202,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // called when a video RTP packet arrives if g.VideoH264Media != nil { + g.Client.OnPacketRTP(g.VideoH264Media, g.VideoH264Forma, func(rtppkt *rtp.Packet) { // This will check if we need to stop the thread, diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 0b0e0cdb..7c18802c 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -372,6 +372,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat start = true } if start { + if err := myMuxer.WritePacket(pkt); err != nil { log.Log.Error(err.Error()) } diff --git a/machinery/src/fmp4/mp4io/atoms.go b/machinery/src/fmp4/mp4io/atoms.go new file mode 100644 index 00000000..e192f17b --- /dev/null +++ b/machinery/src/fmp4/mp4io/atoms.go @@ -0,0 +1,3561 @@ +package mp4io + +import ( + "time" + + "github.com/kerberos-io/joy4/utils/bits/pio" +) + +const MOOF = Tag(0x6d6f6f66) + +func (self MovieFrag) Tag() Tag { + return MOOF +} + +const HDLR = Tag(0x68646c72) + +func (self HandlerRefer) Tag() Tag { + return HDLR +} + +const AVC1 = Tag(0x61766331) + +func (self AVC1Desc) Tag() Tag { + return AVC1 +} + +const URL = Tag(0x75726c20) + +func (self DataReferUrl) Tag() Tag { + return URL +} + +const TREX = Tag(0x74726578) + +func (self TrackExtend) Tag() Tag { + return TREX +} + +const ESDS = Tag(0x65736473) + +func (self ElemStreamDesc) Tag() Tag { + return ESDS +} + +const MDHD = Tag(0x6d646864) + +func (self MediaHeader) Tag() Tag { + return MDHD +} + +const STTS = Tag(0x73747473) + +func (self TimeToSample) Tag() Tag { + return STTS +} + +const STSS = Tag(0x73747373) + +func (self SyncSample) Tag() Tag { + return STSS +} + +const MFHD = Tag(0x6d666864) + +func (self MovieFragHeader) Tag() Tag { + return MFHD +} + +const MVHD = Tag(0x6d766864) + +func (self MovieHeader) Tag() Tag { + return MVHD +} + +const MINF = Tag(0x6d696e66) + +func (self MediaInfo) Tag() Tag { + return MINF +} + +const MOOV = Tag(0x6d6f6f76) + +func (self Movie) Tag() Tag { + return MOOV +} + +const MVEX = Tag(0x6d766578) + +func (self MovieExtend) Tag() Tag { + return MVEX +} + +const STSD = Tag(0x73747364) + +func (self SampleDesc) Tag() Tag { + return STSD +} + +const MP4A = Tag(0x6d703461) + +func (self MP4ADesc) Tag() Tag { + return MP4A +} + +const CTTS = Tag(0x63747473) + +func (self CompositionOffset) Tag() Tag { + return CTTS +} + +const STCO = Tag(0x7374636f) + +func (self ChunkOffset) Tag() Tag { + return STCO +} + +const TRUN = Tag(0x7472756e) + +func (self TrackFragRun) Tag() Tag { + return TRUN +} + +const TRAK = Tag(0x7472616b) + +func (self Track) Tag() Tag { + return TRAK +} + +const MDIA = Tag(0x6d646961) + +func (self Media) Tag() Tag { + return MDIA +} + +const STSC = Tag(0x73747363) + +func (self SampleToChunk) Tag() Tag { + return STSC +} + +const VMHD = Tag(0x766d6864) + +func (self VideoMediaInfo) Tag() Tag { + return VMHD +} + +const STBL = Tag(0x7374626c) + +func (self SampleTable) Tag() Tag { + return STBL +} + +const AVCC = Tag(0x61766343) + +func (self AVC1Conf) Tag() Tag { + return AVCC +} + +const TFDT = Tag(0x74666474) + +func (self TrackFragDecodeTime) Tag() Tag { + return TFDT +} + +const DINF = Tag(0x64696e66) + +func (self DataInfo) Tag() Tag { + return DINF +} + +const DREF = Tag(0x64726566) + +func (self DataRefer) Tag() Tag { + return DREF +} + +const TRAF = Tag(0x74726166) + +func (self TrackFrag) Tag() Tag { + return TRAF +} + +const STSZ = Tag(0x7374737a) + +func (self SampleSize) Tag() Tag { + return STSZ +} + +const TFHD = Tag(0x74666864) + +func (self TrackFragHeader) Tag() Tag { + return TFHD +} + +const TKHD = Tag(0x746b6864) + +func (self TrackHeader) Tag() Tag { + return TKHD +} + +const SMHD = Tag(0x736d6864) + +func (self SoundMediaInfo) Tag() Tag { + return SMHD +} + +const MDAT = Tag(0x6d646174) + +type Movie struct { + Header *MovieHeader + MovieExtend *MovieExtend + Tracks []*Track + Unknowns []Atom + AtomPos +} + +func (self Movie) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MOOV)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self Movie) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.MovieExtend != nil { + n += self.MovieExtend.Marshal(b[n:]) + } + for _, atom := range self.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self Movie) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.MovieExtend != nil { + n += self.MovieExtend.Len() + } + for _, atom := range self.Tracks { + n += atom.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *Movie) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MVHD: + { + atom := &MovieHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mvhd", n+offset, err) + return + } + self.Header = atom + } + case MVEX: + { + atom := &MovieExtend{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mvex", n+offset, err) + return + } + self.MovieExtend = atom + } + case TRAK: + { + atom := &Track{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trak", n+offset, err) + return + } + self.Tracks = append(self.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self Movie) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.MovieExtend != nil { + r = append(r, self.MovieExtend) + } + for _, atom := range self.Tracks { + r = append(r, atom) + } + r = append(r, self.Unknowns...) + return +} + +type MovieHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TimeScale int32 + Duration int32 + PreferredRate float64 + PreferredVolume float64 + Matrix [9]int32 + PreviewTime time.Time + PreviewDuration time.Time + PosterTime time.Time + SelectionTime time.Time + SelectionDuration time.Time + CurrentTime time.Time + NextTrackId int32 + AtomPos +} + +func (self MovieHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MVHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + PutTime32(b[n:], self.CreateTime) + n += 4 + PutTime32(b[n:], self.ModifyTime) + n += 4 + pio.PutI32BE(b[n:], self.TimeScale) + n += 4 + pio.PutI32BE(b[n:], self.Duration) + n += 4 + PutFixed32(b[n:], self.PreferredRate) + n += 4 + PutFixed16(b[n:], self.PreferredVolume) + n += 2 + n += 10 + for _, entry := range self.Matrix { + pio.PutI32BE(b[n:], entry) + n += 4 + } + PutTime32(b[n:], self.PreviewTime) + n += 4 + PutTime32(b[n:], self.PreviewDuration) + n += 4 + PutTime32(b[n:], self.PosterTime) + n += 4 + PutTime32(b[n:], self.SelectionTime) + n += 4 + PutTime32(b[n:], self.SelectionDuration) + n += 4 + PutTime32(b[n:], self.CurrentTime) + n += 4 + pio.PutI32BE(b[n:], self.NextTrackId) + n += 4 + return +} +func (self MovieHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 2 + n += 10 + n += 4 * len(self.Matrix[:]) + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + return +} +func (self *MovieHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + self.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + self.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TimeScale", n+offset, err) + return + } + self.TimeScale = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + self.Duration = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("PreferredRate", n+offset, err) + return + } + self.PreferredRate = GetFixed32(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("PreferredVolume", n+offset, err) + return + } + self.PreferredVolume = GetFixed16(b[n:]) + n += 2 + n += 10 + if len(b) < n+4*len(self.Matrix) { + err = parseErr("Matrix", n+offset, err) + return + } + for i := range self.Matrix { + self.Matrix[i] = pio.I32BE(b[n:]) + n += 4 + } + if len(b) < n+4 { + err = parseErr("PreviewTime", n+offset, err) + return + } + self.PreviewTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("PreviewDuration", n+offset, err) + return + } + self.PreviewDuration = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("PosterTime", n+offset, err) + return + } + self.PosterTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SelectionTime", n+offset, err) + return + } + self.SelectionTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SelectionDuration", n+offset, err) + return + } + self.SelectionDuration = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("CurrentTime", n+offset, err) + return + } + self.CurrentTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("NextTrackId", n+offset, err) + return + } + self.NextTrackId = pio.I32BE(b[n:]) + n += 4 + return +} +func (self MovieHeader) Children() (r []Atom) { + return +} + +type Track struct { + Header *TrackHeader + Media *Media + Unknowns []Atom + AtomPos +} + +func (self Track) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRAK)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self Track) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.Media != nil { + n += self.Media.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self Track) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.Media != nil { + n += self.Media.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *Track) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TKHD: + { + atom := &TrackHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tkhd", n+offset, err) + return + } + self.Header = atom + } + case MDIA: + { + atom := &Media{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mdia", n+offset, err) + return + } + self.Media = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self Track) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.Media != nil { + r = append(r, self.Media) + } + r = append(r, self.Unknowns...) + return +} + +type TrackHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TrackId int32 + Duration int32 + Layer int16 + AlternateGroup int16 + Volume float64 + Matrix [9]int32 + TrackWidth float64 + TrackHeight float64 + AtomPos +} + +func (self TrackHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TKHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + PutTime32(b[n:], self.CreateTime) + n += 4 + PutTime32(b[n:], self.ModifyTime) + n += 4 + pio.PutI32BE(b[n:], self.TrackId) + n += 4 + n += 4 + pio.PutI32BE(b[n:], self.Duration) + n += 4 + n += 8 + pio.PutI16BE(b[n:], self.Layer) + n += 2 + pio.PutI16BE(b[n:], self.AlternateGroup) + n += 2 + PutFixed16(b[n:], self.Volume) + n += 2 + n += 2 + for _, entry := range self.Matrix { + pio.PutI32BE(b[n:], entry) + n += 4 + } + PutFixed32(b[n:], self.TrackWidth) + n += 4 + PutFixed32(b[n:], self.TrackHeight) + n += 4 + return +} +func (self TrackHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 8 + n += 2 + n += 2 + n += 2 + n += 2 + n += 4 * len(self.Matrix[:]) + n += 4 + n += 4 + return +} +func (self *TrackHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + self.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + self.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TrackId", n+offset, err) + return + } + self.TrackId = pio.I32BE(b[n:]) + n += 4 + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + self.Duration = pio.I32BE(b[n:]) + n += 4 + n += 8 + if len(b) < n+2 { + err = parseErr("Layer", n+offset, err) + return + } + self.Layer = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("AlternateGroup", n+offset, err) + return + } + self.AlternateGroup = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Volume", n+offset, err) + return + } + self.Volume = GetFixed16(b[n:]) + n += 2 + n += 2 + if len(b) < n+4*len(self.Matrix) { + err = parseErr("Matrix", n+offset, err) + return + } + for i := range self.Matrix { + self.Matrix[i] = pio.I32BE(b[n:]) + n += 4 + } + if len(b) < n+4 { + err = parseErr("TrackWidth", n+offset, err) + return + } + self.TrackWidth = GetFixed32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TrackHeight", n+offset, err) + return + } + self.TrackHeight = GetFixed32(b[n:]) + n += 4 + return +} +func (self TrackHeader) Children() (r []Atom) { + return +} + +type HandlerRefer struct { + Version uint8 + Flags uint32 + Type [4]byte + SubType [4]byte + Name []byte + AtomPos +} + +func (self HandlerRefer) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(HDLR)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self HandlerRefer) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + copy(b[n:], self.Type[:]) + n += len(self.Type[:]) + copy(b[n:], self.SubType[:]) + n += len(self.SubType[:]) + // TODO: document component + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + copy(b[n:], self.Name[:]) + n += len(self.Name[:]) + return +} +func (self HandlerRefer) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += len(self.Type[:]) + n += len(self.SubType[:]) + // TODO: document component + n += 4 + n += 4 + n += 4 + n += len(self.Name[:]) + return +} +func (self *HandlerRefer) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+len(self.Type) { + err = parseErr("Type", n+offset, err) + return + } + copy(self.Type[:], b[n:]) + n += len(self.Type) + if len(b) < n+len(self.SubType) { + err = parseErr("SubType", n+offset, err) + return + } + copy(self.SubType[:], b[n:]) + n += len(self.SubType) + // TODO: document component + n += 4 + n += 4 + n += 4 + self.Name = b[n:] + n += len(b[n:]) + return +} +func (self HandlerRefer) Children() (r []Atom) { + return +} + +type Media struct { + Header *MediaHeader + Handler *HandlerRefer + Info *MediaInfo + Unknowns []Atom + AtomPos +} + +func (self Media) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MDIA)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self Media) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.Handler != nil { + n += self.Handler.Marshal(b[n:]) + } + if self.Info != nil { + n += self.Info.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self Media) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.Handler != nil { + n += self.Handler.Len() + } + if self.Info != nil { + n += self.Info.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *Media) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MDHD: + { + atom := &MediaHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mdhd", n+offset, err) + return + } + self.Header = atom + } + case HDLR: + { + atom := &HandlerRefer{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("hdlr", n+offset, err) + return + } + self.Handler = atom + } + case MINF: + { + atom := &MediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("minf", n+offset, err) + return + } + self.Info = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self Media) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.Handler != nil { + r = append(r, self.Handler) + } + if self.Info != nil { + r = append(r, self.Info) + } + r = append(r, self.Unknowns...) + return +} + +type MediaHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TimeScale int32 + Duration int32 + Language int16 + Quality int16 + AtomPos +} + +func (self MediaHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MDHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MediaHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + PutTime32(b[n:], self.CreateTime) + n += 4 + PutTime32(b[n:], self.ModifyTime) + n += 4 + pio.PutI32BE(b[n:], self.TimeScale) + n += 4 + pio.PutI32BE(b[n:], self.Duration) + n += 4 + pio.PutI16BE(b[n:], self.Language) + n += 2 + pio.PutI16BE(b[n:], self.Quality) + n += 2 + return +} +func (self MediaHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 2 + n += 2 + return +} +func (self *MediaHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + self.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + self.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TimeScale", n+offset, err) + return + } + self.TimeScale = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + self.Duration = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("Language", n+offset, err) + return + } + self.Language = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Quality", n+offset, err) + return + } + self.Quality = pio.I16BE(b[n:]) + n += 2 + return +} +func (self MediaHeader) Children() (r []Atom) { + return +} + +type MediaInfo struct { + Sound *SoundMediaInfo + Video *VideoMediaInfo + Data *DataInfo + Sample *SampleTable + Unknowns []Atom + AtomPos +} + +func (self MediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MINF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MediaInfo) marshal(b []byte) (n int) { + if self.Sound != nil { + n += self.Sound.Marshal(b[n:]) + } + if self.Video != nil { + n += self.Video.Marshal(b[n:]) + } + if self.Data != nil { + n += self.Data.Marshal(b[n:]) + } + if self.Sample != nil { + n += self.Sample.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MediaInfo) Len() (n int) { + n += 8 + if self.Sound != nil { + n += self.Sound.Len() + } + if self.Video != nil { + n += self.Video.Len() + } + if self.Data != nil { + n += self.Data.Len() + } + if self.Sample != nil { + n += self.Sample.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case SMHD: + { + atom := &SoundMediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("smhd", n+offset, err) + return + } + self.Sound = atom + } + case VMHD: + { + atom := &VideoMediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("vmhd", n+offset, err) + return + } + self.Video = atom + } + case DINF: + { + atom := &DataInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("dinf", n+offset, err) + return + } + self.Data = atom + } + case STBL: + { + atom := &SampleTable{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stbl", n+offset, err) + return + } + self.Sample = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MediaInfo) Children() (r []Atom) { + if self.Sound != nil { + r = append(r, self.Sound) + } + if self.Video != nil { + r = append(r, self.Video) + } + if self.Data != nil { + r = append(r, self.Data) + } + if self.Sample != nil { + r = append(r, self.Sample) + } + r = append(r, self.Unknowns...) + return +} + +type DataInfo struct { + Refer *DataRefer + Unknowns []Atom + AtomPos +} + +func (self DataInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(DINF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self DataInfo) marshal(b []byte) (n int) { + if self.Refer != nil { + n += self.Refer.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self DataInfo) Len() (n int) { + n += 8 + if self.Refer != nil { + n += self.Refer.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *DataInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case DREF: + { + atom := &DataRefer{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("dref", n+offset, err) + return + } + self.Refer = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self DataInfo) Children() (r []Atom) { + if self.Refer != nil { + r = append(r, self.Refer) + } + r = append(r, self.Unknowns...) + return +} + +type DataRefer struct { + Version uint8 + Flags uint32 + Url *DataReferUrl + AtomPos +} + +func (self DataRefer) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(DREF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self DataRefer) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + _childrenNR := 0 + if self.Url != nil { + _childrenNR++ + } + pio.PutI32BE(b[n:], int32(_childrenNR)) + n += 4 + if self.Url != nil { + n += self.Url.Marshal(b[n:]) + } + return +} +func (self DataRefer) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.Url != nil { + n += self.Url.Len() + } + return +} +func (self *DataRefer) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case URL: + { + atom := &DataReferUrl{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("url ", n+offset, err) + return + } + self.Url = atom + } + } + n += size + } + return +} +func (self DataRefer) Children() (r []Atom) { + if self.Url != nil { + r = append(r, self.Url) + } + return +} + +type DataReferUrl struct { + Version uint8 + Flags uint32 + AtomPos +} + +func (self DataReferUrl) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(URL)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self DataReferUrl) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + return +} +func (self DataReferUrl) Len() (n int) { + n += 8 + n += 1 + n += 3 + return +} +func (self *DataReferUrl) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + return +} +func (self DataReferUrl) Children() (r []Atom) { + return +} + +type SoundMediaInfo struct { + Version uint8 + Flags uint32 + Balance int16 + AtomPos +} + +func (self SoundMediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(SMHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SoundMediaInfo) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutI16BE(b[n:], self.Balance) + n += 2 + n += 2 + return +} +func (self SoundMediaInfo) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 2 + n += 2 + return +} +func (self *SoundMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+2 { + err = parseErr("Balance", n+offset, err) + return + } + self.Balance = pio.I16BE(b[n:]) + n += 2 + n += 2 + return +} +func (self SoundMediaInfo) Children() (r []Atom) { + return +} + +type VideoMediaInfo struct { + Version uint8 + Flags uint32 + GraphicsMode int16 + Opcolor [3]int16 + AtomPos +} + +func (self VideoMediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(VMHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self VideoMediaInfo) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutI16BE(b[n:], self.GraphicsMode) + n += 2 + for _, entry := range self.Opcolor { + pio.PutI16BE(b[n:], entry) + n += 2 + } + return +} +func (self VideoMediaInfo) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 2 + n += 2 * len(self.Opcolor[:]) + return +} +func (self *VideoMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+2 { + err = parseErr("GraphicsMode", n+offset, err) + return + } + self.GraphicsMode = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2*len(self.Opcolor) { + err = parseErr("Opcolor", n+offset, err) + return + } + for i := range self.Opcolor { + self.Opcolor[i] = pio.I16BE(b[n:]) + n += 2 + } + return +} +func (self VideoMediaInfo) Children() (r []Atom) { + return +} + +type SampleTable struct { + SampleDesc *SampleDesc + TimeToSample *TimeToSample + CompositionOffset *CompositionOffset + SampleToChunk *SampleToChunk + SyncSample *SyncSample + ChunkOffset *ChunkOffset + SampleSize *SampleSize + AtomPos +} + +func (self SampleTable) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STBL)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleTable) marshal(b []byte) (n int) { + if self.SampleDesc != nil { + n += self.SampleDesc.Marshal(b[n:]) + } + if self.TimeToSample != nil { + n += self.TimeToSample.Marshal(b[n:]) + } + if self.CompositionOffset != nil { + n += self.CompositionOffset.Marshal(b[n:]) + } + if self.SampleToChunk != nil { + n += self.SampleToChunk.Marshal(b[n:]) + } + if self.SyncSample != nil { + n += self.SyncSample.Marshal(b[n:]) + } + if self.ChunkOffset != nil { + n += self.ChunkOffset.Marshal(b[n:]) + } + if self.SampleSize != nil { + n += self.SampleSize.Marshal(b[n:]) + } + return +} +func (self SampleTable) Len() (n int) { + n += 8 + if self.SampleDesc != nil { + n += self.SampleDesc.Len() + } + if self.TimeToSample != nil { + n += self.TimeToSample.Len() + } + if self.CompositionOffset != nil { + n += self.CompositionOffset.Len() + } + if self.SampleToChunk != nil { + n += self.SampleToChunk.Len() + } + if self.SyncSample != nil { + n += self.SyncSample.Len() + } + if self.ChunkOffset != nil { + n += self.ChunkOffset.Len() + } + if self.SampleSize != nil { + n += self.SampleSize.Len() + } + return +} +func (self *SampleTable) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case STSD: + { + atom := &SampleDesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsd", n+offset, err) + return + } + self.SampleDesc = atom + } + case STTS: + { + atom := &TimeToSample{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stts", n+offset, err) + return + } + self.TimeToSample = atom + } + case CTTS: + { + atom := &CompositionOffset{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("ctts", n+offset, err) + return + } + self.CompositionOffset = atom + } + case STSC: + { + atom := &SampleToChunk{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsc", n+offset, err) + return + } + self.SampleToChunk = atom + } + case STSS: + { + atom := &SyncSample{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stss", n+offset, err) + return + } + self.SyncSample = atom + } + case STCO: + { + atom := &ChunkOffset{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stco", n+offset, err) + return + } + self.ChunkOffset = atom + } + case STSZ: + { + atom := &SampleSize{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsz", n+offset, err) + return + } + self.SampleSize = atom + } + } + n += size + } + return +} +func (self SampleTable) Children() (r []Atom) { + if self.SampleDesc != nil { + r = append(r, self.SampleDesc) + } + if self.TimeToSample != nil { + r = append(r, self.TimeToSample) + } + if self.CompositionOffset != nil { + r = append(r, self.CompositionOffset) + } + if self.SampleToChunk != nil { + r = append(r, self.SampleToChunk) + } + if self.SyncSample != nil { + r = append(r, self.SyncSample) + } + if self.ChunkOffset != nil { + r = append(r, self.ChunkOffset) + } + if self.SampleSize != nil { + r = append(r, self.SampleSize) + } + return +} + +type SampleDesc struct { + Version uint8 + AVC1Desc *AVC1Desc + MP4ADesc *MP4ADesc + Unknowns []Atom + AtomPos +} + +func (self SampleDesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleDesc) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + n += 3 + _childrenNR := 0 + if self.AVC1Desc != nil { + _childrenNR++ + } + if self.MP4ADesc != nil { + _childrenNR++ + } + _childrenNR += len(self.Unknowns) + pio.PutI32BE(b[n:], int32(_childrenNR)) + n += 4 + if self.AVC1Desc != nil { + n += self.AVC1Desc.Marshal(b[n:]) + } + if self.MP4ADesc != nil { + n += self.MP4ADesc.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self SampleDesc) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.AVC1Desc != nil { + n += self.AVC1Desc.Len() + } + if self.MP4ADesc != nil { + n += self.MP4ADesc.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *SampleDesc) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + n += 3 + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case AVC1: + { + atom := &AVC1Desc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("avc1", n+offset, err) + return + } + self.AVC1Desc = atom + } + case MP4A: + { + atom := &MP4ADesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mp4a", n+offset, err) + return + } + self.MP4ADesc = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self SampleDesc) Children() (r []Atom) { + if self.AVC1Desc != nil { + r = append(r, self.AVC1Desc) + } + if self.MP4ADesc != nil { + r = append(r, self.MP4ADesc) + } + r = append(r, self.Unknowns...) + return +} + +type MP4ADesc struct { + DataRefIdx int16 + Version int16 + RevisionLevel int16 + Vendor int32 + NumberOfChannels int16 + SampleSize int16 + CompressionId int16 + SampleRate float64 + Conf *ElemStreamDesc + Unknowns []Atom + AtomPos +} + +func (self MP4ADesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MP4A)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MP4ADesc) marshal(b []byte) (n int) { + n += 6 + pio.PutI16BE(b[n:], self.DataRefIdx) + n += 2 + pio.PutI16BE(b[n:], self.Version) + n += 2 + pio.PutI16BE(b[n:], self.RevisionLevel) + n += 2 + pio.PutI32BE(b[n:], self.Vendor) + n += 4 + pio.PutI16BE(b[n:], self.NumberOfChannels) + n += 2 + pio.PutI16BE(b[n:], self.SampleSize) + n += 2 + pio.PutI16BE(b[n:], self.CompressionId) + n += 2 + n += 2 + PutFixed32(b[n:], self.SampleRate) + n += 4 + if self.Conf != nil { + n += self.Conf.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MP4ADesc) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 2 + n += 2 + n += 4 + n += 2 + n += 2 + n += 2 + n += 2 + n += 4 + if self.Conf != nil { + n += self.Conf.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MP4ADesc) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + self.DataRefIdx = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("RevisionLevel", n+offset, err) + return + } + self.RevisionLevel = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("Vendor", n+offset, err) + return + } + self.Vendor = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("NumberOfChannels", n+offset, err) + return + } + self.NumberOfChannels = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("SampleSize", n+offset, err) + return + } + self.SampleSize = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("CompressionId", n+offset, err) + return + } + self.CompressionId = pio.I16BE(b[n:]) + n += 2 + n += 2 + if len(b) < n+4 { + err = parseErr("SampleRate", n+offset, err) + return + } + self.SampleRate = GetFixed32(b[n:]) + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case ESDS: + { + atom := &ElemStreamDesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("esds", n+offset, err) + return + } + self.Conf = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MP4ADesc) Children() (r []Atom) { + if self.Conf != nil { + r = append(r, self.Conf) + } + r = append(r, self.Unknowns...) + return +} + +type AVC1Desc struct { + DataRefIdx int16 + Version int16 + Revision int16 + Vendor int32 + TemporalQuality int32 + SpatialQuality int32 + Width int16 + Height int16 + HorizontalResolution float64 + VorizontalResolution float64 + FrameCount int16 + CompressorName [32]byte + Depth int16 + ColorTableId int16 + Conf *AVC1Conf + Unknowns []Atom + AtomPos +} + +func (self AVC1Desc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(AVC1)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self AVC1Desc) marshal(b []byte) (n int) { + n += 6 + pio.PutI16BE(b[n:], self.DataRefIdx) + n += 2 + pio.PutI16BE(b[n:], self.Version) + n += 2 + pio.PutI16BE(b[n:], self.Revision) + n += 2 + pio.PutI32BE(b[n:], self.Vendor) + n += 4 + pio.PutI32BE(b[n:], self.TemporalQuality) + n += 4 + pio.PutI32BE(b[n:], self.SpatialQuality) + n += 4 + pio.PutI16BE(b[n:], self.Width) + n += 2 + pio.PutI16BE(b[n:], self.Height) + n += 2 + PutFixed32(b[n:], self.HorizontalResolution) + n += 4 + PutFixed32(b[n:], self.VorizontalResolution) + n += 4 + n += 4 + pio.PutI16BE(b[n:], self.FrameCount) + n += 2 + copy(b[n:], self.CompressorName[:]) + n += len(self.CompressorName[:]) + pio.PutI16BE(b[n:], self.Depth) + n += 2 + pio.PutI16BE(b[n:], self.ColorTableId) + n += 2 + if self.Conf != nil { + n += self.Conf.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self AVC1Desc) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += len(self.CompressorName[:]) + n += 2 + n += 2 + if self.Conf != nil { + n += self.Conf.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + self.DataRefIdx = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Revision", n+offset, err) + return + } + self.Revision = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("Vendor", n+offset, err) + return + } + self.Vendor = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TemporalQuality", n+offset, err) + return + } + self.TemporalQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SpatialQuality", n+offset, err) + return + } + self.SpatialQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("Width", n+offset, err) + return + } + self.Width = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Height", n+offset, err) + return + } + self.Height = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("HorizontalResolution", n+offset, err) + return + } + self.HorizontalResolution = GetFixed32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("VorizontalResolution", n+offset, err) + return + } + self.VorizontalResolution = GetFixed32(b[n:]) + n += 4 + n += 4 + if len(b) < n+2 { + err = parseErr("FrameCount", n+offset, err) + return + } + self.FrameCount = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+len(self.CompressorName) { + err = parseErr("CompressorName", n+offset, err) + return + } + copy(self.CompressorName[:], b[n:]) + n += len(self.CompressorName) + if len(b) < n+2 { + err = parseErr("Depth", n+offset, err) + return + } + self.Depth = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("ColorTableId", n+offset, err) + return + } + self.ColorTableId = pio.I16BE(b[n:]) + n += 2 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case AVCC: + { + atom := &AVC1Conf{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("avcC", n+offset, err) + return + } + self.Conf = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self AVC1Desc) Children() (r []Atom) { + if self.Conf != nil { + r = append(r, self.Conf) + } + r = append(r, self.Unknowns...) + return +} + +type AVC1Conf struct { + Data []byte + AtomPos +} + +func (self AVC1Conf) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(AVCC)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self AVC1Conf) marshal(b []byte) (n int) { + copy(b[n:], self.Data[:]) + n += len(self.Data[:]) + return +} +func (self AVC1Conf) Len() (n int) { + n += 8 + n += len(self.Data[:]) + return +} +func (self *AVC1Conf) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + self.Data = b[n:] + n += len(b[n:]) + return +} +func (self AVC1Conf) Children() (r []Atom) { + return +} + +type TimeToSample struct { + Version uint8 + Flags uint32 + Entries []TimeToSampleEntry + AtomPos +} + +func (self TimeToSample) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STTS)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TimeToSample) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + PutTimeToSampleEntry(b[n:], entry) + n += LenTimeToSampleEntry + } + return +} +func (self TimeToSample) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += LenTimeToSampleEntry * len(self.Entries) + return +} +func (self *TimeToSample) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]TimeToSampleEntry, _len_Entries) + if len(b) < n+LenTimeToSampleEntry*len(self.Entries) { + err = parseErr("TimeToSampleEntry", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = GetTimeToSampleEntry(b[n:]) + n += LenTimeToSampleEntry + } + return +} +func (self TimeToSample) Children() (r []Atom) { + return +} + +type TimeToSampleEntry struct { + Count uint32 + Duration uint32 +} + +func GetTimeToSampleEntry(b []byte) (self TimeToSampleEntry) { + self.Count = pio.U32BE(b[0:]) + self.Duration = pio.U32BE(b[4:]) + return +} +func PutTimeToSampleEntry(b []byte, self TimeToSampleEntry) { + pio.PutU32BE(b[0:], self.Count) + pio.PutU32BE(b[4:], self.Duration) +} + +const LenTimeToSampleEntry = 8 + +type SampleToChunk struct { + Version uint8 + Flags uint32 + Entries []SampleToChunkEntry + AtomPos +} + +func (self SampleToChunk) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSC)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleToChunk) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + PutSampleToChunkEntry(b[n:], entry) + n += LenSampleToChunkEntry + } + return +} +func (self SampleToChunk) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += LenSampleToChunkEntry * len(self.Entries) + return +} +func (self *SampleToChunk) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]SampleToChunkEntry, _len_Entries) + if len(b) < n+LenSampleToChunkEntry*len(self.Entries) { + err = parseErr("SampleToChunkEntry", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = GetSampleToChunkEntry(b[n:]) + n += LenSampleToChunkEntry + } + return +} +func (self SampleToChunk) Children() (r []Atom) { + return +} + +type SampleToChunkEntry struct { + FirstChunk uint32 + SamplesPerChunk uint32 + SampleDescId uint32 +} + +func GetSampleToChunkEntry(b []byte) (self SampleToChunkEntry) { + self.FirstChunk = pio.U32BE(b[0:]) + self.SamplesPerChunk = pio.U32BE(b[4:]) + self.SampleDescId = pio.U32BE(b[8:]) + return +} +func PutSampleToChunkEntry(b []byte, self SampleToChunkEntry) { + pio.PutU32BE(b[0:], self.FirstChunk) + pio.PutU32BE(b[4:], self.SamplesPerChunk) + pio.PutU32BE(b[8:], self.SampleDescId) +} + +const LenSampleToChunkEntry = 12 + +type CompositionOffset struct { + Version uint8 + Flags uint32 + Entries []CompositionOffsetEntry + AtomPos +} + +func (self CompositionOffset) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(CTTS)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self CompositionOffset) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + PutCompositionOffsetEntry(b[n:], entry) + n += LenCompositionOffsetEntry + } + return +} +func (self CompositionOffset) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += LenCompositionOffsetEntry * len(self.Entries) + return +} +func (self *CompositionOffset) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]CompositionOffsetEntry, _len_Entries) + if len(b) < n+LenCompositionOffsetEntry*len(self.Entries) { + err = parseErr("CompositionOffsetEntry", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = GetCompositionOffsetEntry(b[n:]) + n += LenCompositionOffsetEntry + } + return +} +func (self CompositionOffset) Children() (r []Atom) { + return +} + +type CompositionOffsetEntry struct { + Count uint32 + Offset uint32 +} + +func GetCompositionOffsetEntry(b []byte) (self CompositionOffsetEntry) { + self.Count = pio.U32BE(b[0:]) + self.Offset = pio.U32BE(b[4:]) + return +} +func PutCompositionOffsetEntry(b []byte, self CompositionOffsetEntry) { + pio.PutU32BE(b[0:], self.Count) + pio.PutU32BE(b[4:], self.Offset) +} + +const LenCompositionOffsetEntry = 8 + +type SyncSample struct { + Version uint8 + Flags uint32 + Entries []uint32 + AtomPos +} + +func (self SyncSample) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSS)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SyncSample) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} +func (self SyncSample) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 * len(self.Entries) + return +} +func (self *SyncSample) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(self.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} +func (self SyncSample) Children() (r []Atom) { + return +} + +type ChunkOffset struct { + Version uint8 + Flags uint32 + Entries []uint32 + AtomPos +} + +func (self ChunkOffset) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STCO)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self ChunkOffset) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} +func (self ChunkOffset) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 * len(self.Entries) + return +} +func (self *ChunkOffset) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(self.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} +func (self ChunkOffset) Children() (r []Atom) { + return +} + +type MovieFrag struct { + Header *MovieFragHeader + Tracks []*TrackFrag + Unknowns []Atom + AtomPos +} + +func (self MovieFrag) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MOOF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieFrag) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + for _, atom := range self.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MovieFrag) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + for _, atom := range self.Tracks { + n += atom.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MFHD: + { + atom := &MovieFragHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mfhd", n+offset, err) + return + } + self.Header = atom + } + case TRAF: + { + atom := &TrackFrag{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("traf", n+offset, err) + return + } + self.Tracks = append(self.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MovieFrag) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + for _, atom := range self.Tracks { + r = append(r, atom) + } + r = append(r, self.Unknowns...) + return +} + +type MovieFragHeader struct { + Version uint8 + Flags uint32 + Seqnum uint32 + AtomPos +} + +func (self MovieFragHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MFHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieFragHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.Seqnum) + n += 4 + return +} +func (self MovieFragHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + return +} +func (self *MovieFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("Seqnum", n+offset, err) + return + } + self.Seqnum = pio.U32BE(b[n:]) + n += 4 + return +} +func (self MovieFragHeader) Children() (r []Atom) { + return +} + +type TrackFrag struct { + Header *TrackFragHeader + DecodeTime *TrackFragDecodeTime + Run *TrackFragRun + Unknowns []Atom + AtomPos +} + +func (self TrackFrag) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRAF)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFrag) marshal(b []byte) (n int) { + if self.Header != nil { + n += self.Header.Marshal(b[n:]) + } + if self.DecodeTime != nil { + n += self.DecodeTime.Marshal(b[n:]) + } + if self.Run != nil { + n += self.Run.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self TrackFrag) Len() (n int) { + n += 8 + if self.Header != nil { + n += self.Header.Len() + } + if self.DecodeTime != nil { + n += self.DecodeTime.Len() + } + if self.Run != nil { + n += self.Run.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TFHD: + { + atom := &TrackFragHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tfhd", n+offset, err) + return + } + self.Header = atom + } + case TFDT: + { + atom := &TrackFragDecodeTime{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tfdt", n+offset, err) + return + } + self.DecodeTime = atom + } + case TRUN: + { + atom := &TrackFragRun{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trun", n+offset, err) + return + } + self.Run = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self TrackFrag) Children() (r []Atom) { + if self.Header != nil { + r = append(r, self.Header) + } + if self.DecodeTime != nil { + r = append(r, self.DecodeTime) + } + if self.Run != nil { + r = append(r, self.Run) + } + r = append(r, self.Unknowns...) + return +} + +type MovieExtend struct { + Tracks []*TrackExtend + Unknowns []Atom + AtomPos +} + +func (self MovieExtend) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MVEX)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self MovieExtend) marshal(b []byte) (n int) { + for _, atom := range self.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} +func (self MovieExtend) Len() (n int) { + n += 8 + for _, atom := range self.Tracks { + n += atom.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} +func (self *MovieExtend) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TREX: + { + atom := &TrackExtend{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trex", n+offset, err) + return + } + self.Tracks = append(self.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} +func (self MovieExtend) Children() (r []Atom) { + for _, atom := range self.Tracks { + r = append(r, atom) + } + r = append(r, self.Unknowns...) + return +} + +type TrackExtend struct { + Version uint8 + Flags uint32 + TrackId uint32 + DefaultSampleDescIdx uint32 + DefaultSampleDuration uint32 + DefaultSampleSize uint32 + DefaultSampleFlags uint32 + AtomPos +} + +func (self TrackExtend) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TREX)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackExtend) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.TrackId) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleDescIdx) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleDuration) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleSize) + n += 4 + pio.PutU32BE(b[n:], self.DefaultSampleFlags) + n += 4 + return +} +func (self TrackExtend) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + return +} +func (self *TrackExtend) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("TrackId", n+offset, err) + return + } + self.TrackId = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleDescIdx", n+offset, err) + return + } + self.DefaultSampleDescIdx = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleDuration", n+offset, err) + return + } + self.DefaultSampleDuration = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleSize", n+offset, err) + return + } + self.DefaultSampleSize = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleFlags", n+offset, err) + return + } + self.DefaultSampleFlags = pio.U32BE(b[n:]) + n += 4 + return +} +func (self TrackExtend) Children() (r []Atom) { + return +} + +type SampleSize struct { + Version uint8 + Flags uint32 + SampleSize uint32 + Entries []uint32 + AtomPos +} + +func (self SampleSize) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSZ)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self SampleSize) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.SampleSize) + n += 4 + if self.SampleSize != 0 { + return + } + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + for _, entry := range self.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} +func (self SampleSize) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.SampleSize != 0 { + return + } + n += 4 + n += 4 * len(self.Entries) + return +} +func (self *SampleSize) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("SampleSize", n+offset, err) + return + } + self.SampleSize = pio.U32BE(b[n:]) + n += 4 + if self.SampleSize != 0 { + return + } + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(self.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range self.Entries { + self.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} +func (self SampleSize) Children() (r []Atom) { + return +} + +type TrackFragRun struct { + Version uint8 + Flags uint32 + DataOffset uint32 + FirstSampleFlags uint32 + Entries []TrackFragRunEntry + AtomPos +} + +func (self TrackFragRun) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRUN)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFragRun) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(self.Entries))) + n += 4 + if self.Flags&TRUN_DATA_OFFSET != 0 { + { + pio.PutU32BE(b[n:], self.DataOffset) + n += 4 + } + } + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + { + pio.PutU32BE(b[n:], self.FirstSampleFlags) + n += 4 + } + } + + for i, entry := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + //log.Printf("TRUN entry[%v]: flags = %v", i, flags) + if flags&TRUN_SAMPLE_DURATION != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_DURATION = %v, flags = %v", i, entry.Duration, flags) + pio.PutU32BE(b[n:], entry.Duration) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_SIZE = %v, flags = %v", i, entry.Size, flags) + pio.PutU32BE(b[n:], entry.Size) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_FLAGS = %v, flags = %v", i, entry.Flags, flags) + pio.PutU32BE(b[n:], entry.Flags) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_CTS = %v, flags = %v", i, entry.Cts, flags) + pio.PutU32BE(b[n:], entry.Cts) + n += 4 + } + } + return +} +func (self TrackFragRun) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.Flags&TRUN_DATA_OFFSET != 0 { + n += 4 + } + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + n += 4 + } + + for i := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + if flags&TRUN_SAMPLE_DURATION != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + n += 4 + } + } + return +} +func (self *TrackFragRun) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + self.Entries = make([]TrackFragRunEntry, _len_Entries) + if self.Flags&TRUN_DATA_OFFSET != 0 { + { + if len(b) < n+4 { + err = parseErr("DataOffset", n+offset, err) + return + } + self.DataOffset = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + { + if len(b) < n+4 { + err = parseErr("FirstSampleFlags", n+offset, err) + return + } + self.FirstSampleFlags = pio.U32BE(b[n:]) + n += 4 + } + } + + for i := 0; i < int(_len_Entries); i++ { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + entry := &self.Entries[i] + if flags&TRUN_SAMPLE_DURATION != 0 { + entry.Duration = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + entry.Size = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + entry.Flags = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + entry.Cts = pio.U32BE(b[n:]) + n += 4 + } + } + return +} +func (self TrackFragRun) Children() (r []Atom) { + return +} + +type TrackFragRunEntry struct { + Duration uint32 + Size uint32 + Flags uint32 + Cts uint32 +} + +func GetTrackFragRunEntry(b []byte) (self TrackFragRunEntry) { + self.Duration = pio.U32BE(b[0:]) + self.Size = pio.U32BE(b[4:]) + self.Flags = pio.U32BE(b[8:]) + self.Cts = pio.U32BE(b[12:]) + return +} +func PutTrackFragRunEntry(b []byte, self TrackFragRunEntry) { + pio.PutU32BE(b[0:], self.Duration) + pio.PutU32BE(b[4:], self.Size) + pio.PutU32BE(b[8:], self.Flags) + pio.PutU32BE(b[12:], self.Cts) +} + +const LenTrackFragRunEntry = 16 + +type TrackFragHeader struct { + Version uint8 + Flags uint32 + TrackId uint32 + BaseDataOffset uint64 + StsdId uint32 + DefaultDuration uint32 + DefaultSize uint32 + DefaultFlags uint32 + AtomPos +} + +func (self TrackFragHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TFHD)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFragHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + pio.PutU32BE(b[n:], self.TrackId) + n += 4 + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + { + pio.PutU64BE(b[n:], self.BaseDataOffset) + n += 8 + } + } + + if self.Flags&TFHD_STSD_ID != 0 { + { + pio.PutU32BE(b[n:], self.StsdId) + n += 4 + } + } + + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + { + pio.PutU32BE(b[n:], self.DefaultDuration) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + { + pio.PutU32BE(b[n:], self.DefaultSize) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + { + pio.PutU32BE(b[n:], self.DefaultFlags) + n += 4 + } + } + return +} +func (self TrackFragHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + { + n += 8 + } + } + if self.Flags&TFHD_STSD_ID != 0 { + { + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + { + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + { + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + { + n += 4 + } + } + return +} +func (self *TrackFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + + if len(b) < n+4 { + err = parseErr("TrackId", n+offset, err) + return + } + self.TrackId = pio.U32BE(b[n:]) + n += 4 + + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + { + if len(b) < n+8 { + err = parseErr("BaseDataOffset", n+offset, err) + return + } + self.BaseDataOffset = pio.U64BE(b[n:]) + n += 8 + } + } + if self.Flags&TFHD_STSD_ID != 0 { + { + if len(b) < n+4 { + err = parseErr("StsdId", n+offset, err) + return + } + self.StsdId = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultDuration", n+offset, err) + return + } + self.DefaultDuration = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultSize", n+offset, err) + return + } + self.DefaultSize = pio.U32BE(b[n:]) + n += 4 + } + } + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultFlags", n+offset, err) + return + } + self.DefaultFlags = pio.U32BE(b[n:]) + n += 4 + } + } + return +} +func (self TrackFragHeader) Children() (r []Atom) { + return +} + +type TrackFragDecodeTime struct { + Version uint8 + Flags uint32 + DecodeTime uint64 + AtomPos +} + +func (self TrackFragDecodeTime) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TFDT)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self TrackFragDecodeTime) marshal(b []byte) (n int) { + pio.PutU8(b[n:], self.Version) + n += 1 + pio.PutU24BE(b[n:], self.Flags) + n += 3 + if self.Version != 0 { + pio.PutU64BE(b[n:], self.DecodeTime) + n += 8 + } else { + + pio.PutU32BE(b[n:], uint32(self.DecodeTime)) + n += 4 + } + return +} +func (self TrackFragDecodeTime) Len() (n int) { + n += 8 + n += 1 + n += 3 + if self.Version != 0 { + n += 8 + } else { + + n += 4 + } + return +} +func (self *TrackFragDecodeTime) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + self.Flags = pio.U24BE(b[n:]) + n += 3 + if self.Version != 0 { + self.DecodeTime = pio.U64BE(b[n:]) + n += 8 + } else { + + self.DecodeTime = uint64(pio.U32BE(b[n:])) + n += 4 + } + return +} +func (self TrackFragDecodeTime) Children() (r []Atom) { + return +} diff --git a/machinery/src/fmp4/mp4io/gen/gen.go b/machinery/src/fmp4/mp4io/gen/gen.go new file mode 100644 index 00000000..1a6efb37 --- /dev/null +++ b/machinery/src/fmp4/mp4io/gen/gen.go @@ -0,0 +1,1057 @@ + +package main + +import ( + "strings" + "fmt" + "os" + "go/ast" + "go/parser" + "go/token" + "go/printer" +) + +func getexprs(e ast.Expr) string { + if lit, ok := e.(*ast.BasicLit); ok { + return lit.Value + } + if ident, ok := e.(*ast.Ident); ok { + return ident.Name + } + return "" +} + +func genatomdecl(origfn *ast.FuncDecl, origname, origtag string) (decls []ast.Decl) { + fieldslist := &ast.FieldList{} + typespec := &ast.TypeSpec{ + Name: ast.NewIdent(origname), + Type: &ast.StructType{Fields: fieldslist}, + } + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + + if strings.HasPrefix(typ, "_") { + if typ == "_unknowns" { + fieldslist.List = append(fieldslist.List, &ast.Field{ + Names: []*ast.Ident{ast.NewIdent("Unknowns")}, + Type: ast.NewIdent("[]Atom"), + }) + } + continue + } + + name := getexprs(callexpr.Args[0]) + + name2 := "" + if len(callexpr.Args) > 1 { + name2 = getexprs(callexpr.Args[1]) + } + + len3 := "" + if len(callexpr.Args) > 2 { + len3 = getexprs(callexpr.Args[2]) + } + + if strings.HasPrefix(name, "_") { + continue + } + + switch typ { + case "fixed16": + typ = "float64" + case "fixed32": + typ = "float64" + case "bytesleft": + typ = "[]byte" + case "bytes": + typ = "["+name2+"]byte" + case "uint24": + typ = "uint32" + case "time64", "time32": + typ = "time.Time" + case "atom": + typ = "*"+name2 + case "atoms": + typ = "[]*"+name2 + case "slice": + typ = "[]"+name2 + case "array": + typ = "["+len3+"]"+name2 + } + + fieldslist.List = append(fieldslist.List, &ast.Field{ + Names: []*ast.Ident{ast.NewIdent(name)}, + Type: ast.NewIdent(typ), + }) + } + + if origtag != "" { + fieldslist.List = append(fieldslist.List, &ast.Field{ + Type: ast.NewIdent("AtomPos"), + }) + } + + gendecl := &ast.GenDecl{ + Tok: token.TYPE, + Specs: []ast.Spec{ + typespec, + }, + } + decls = append(decls, gendecl) + return +} + +func typegetlen(typ string) (n int) { + switch typ { + case "uint8": + n = 1 + case "uint16": + n = 2 + case "uint24": + n = 3 + case "uint32": + n = 4 + case "int16": + n = 2 + case "int32": + n = 4 + case "uint64": + n = 8 + case "time32": + n = 4 + case "time64": + n = 8 + case "fixed32": + n = 4 + case "fixed16": + n = 2 + } + return +} + +func typegetlens(typ string) string { + n := typegetlen(typ) + if n == 0 { + return "Len"+typ + } else { + return fmt.Sprint(n) + } +} + +func typegetvartype(typ string) string { + switch typ { + case "uint8": + return "uint8" + case "uint16": + return "uint16" + case "uint24": + return "uint32" + case "uint32": + return "uint32" + case "uint64": + return "uint64" + case "int16": + return "int16" + case "int32": + return "int32" + } + return "" +} + +func typegetputfn(typ string) (fn string) { + fn = typ + switch typ { + case "uint8": + fn = "pio.PutU8" + case "uint16": + fn = "pio.PutU16BE" + case "uint24": + fn = "pio.PutU24BE" + case "uint32": + fn = "pio.PutU32BE" + case "int16": + fn = "pio.PutI16BE" + case "int32": + fn = "pio.PutI32BE" + case "uint64": + fn = "pio.PutU64BE" + case "time32": + fn = "PutTime32" + case "time64": + fn = "PutTime64" + case "fixed32": + fn = "PutFixed32" + case "fixed16": + fn = "PutFixed16" + default: + fn = "Put"+typ + } + return +} + +func typegetgetfn(typ string) (fn string) { + fn = typ + switch typ { + case "uint8": + fn = "pio.U8" + case "uint16": + fn = "pio.U16BE" + case "uint24": + fn = "pio.U24BE" + case "uint32": + fn = "pio.U32BE" + case "int16": + fn = "pio.I16BE" + case "int32": + fn = "pio.I32BE" + case "uint64": + fn = "pio.U64BE" + case "time32": + fn = "GetTime32" + case "time64": + fn = "GetTime64" + case "fixed32": + fn = "GetFixed32" + case "fixed16": + fn = "GetFixed16" + default: + fn = "Get"+typ + } + return +} + +func addns(n string) (stmts []ast.Stmt) { + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: n}}, + } + stmts = append(stmts, assign) + return +} + +func addn(n int) (stmts []ast.Stmt) { + return addns(fmt.Sprint(n)) +} + +func simplecall(fun string, args... string) *ast.ExprStmt { + _args := []ast.Expr{} + for _, s := range args { + _args = append(_args, ast.NewIdent(s)) + } + return &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: ast.NewIdent(fun), + Args: _args, + }, + } +} + +func getxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { + fn := typegetgetfn(typ) + assign := &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ast.NewIdent(name)}, + Rhs: []ast.Expr{simplecall(fn, "b["+pos+":]").X}, + } + stmts = append(stmts, assign) + return +} + +func putxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { + if conv { + name = fmt.Sprintf("%s(%s)", typ, name) + } + fn := typegetputfn(typ) + stmts = append(stmts, simplecall(fn, "b["+pos+":]", name)) + return +} + +func putxxadd(fn string, name string, conv bool) (stmts []ast.Stmt) { + n := typegetlen(fn) + stmts = append(stmts, putxx(fn, "n", name, conv)...) + stmts = append(stmts, addn(n)...) + return +} + +func newdecl(origname, name string, params, res []*ast.Field, stmts []ast.Stmt) *ast.FuncDecl { + return &ast.FuncDecl{ + Recv: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{ + Names: []*ast.Ident{ast.NewIdent("self")}, + Type: ast.NewIdent(origname), + }, + }, + }, + Name: ast.NewIdent(name), + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: params, + }, + Results: &ast.FieldList{ + List: res, + }, + }, + Body: &ast.BlockStmt{List: stmts}, + } +} + +func getstructputgetlenfn(origfn *ast.FuncDecl, origname string) (decls []ast.Decl) { + getstmts := []ast.Stmt{} + putstmts := []ast.Stmt{} + totlen := 0 + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + name := getexprs(callexpr.Args[0]) + + getstmts = append(getstmts, getxx(typ, fmt.Sprint(totlen), "self."+name, false)...) + putstmts = append(putstmts, putxx(typ, fmt.Sprint(totlen), "self."+name, false)...) + totlen += typegetlen(typ) + } + + getstmts = append(getstmts, &ast.ReturnStmt{}) + + decls = append(decls, &ast.FuncDecl{ + Name: ast.NewIdent("Get"+origname), + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + }, + }, + Results: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, + }, + }, + }, + Body: &ast.BlockStmt{List: getstmts}, + }) + + decls = append(decls, &ast.FuncDecl{ + Name: ast.NewIdent("Put"+origname), + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, + }, + }, + }, + Body: &ast.BlockStmt{List: putstmts}, + }) + + decls = append(decls, &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ast.NewIdent("Len"+origname)}, + Values: []ast.Expr{ast.NewIdent(fmt.Sprint(totlen))}, + }, + }, + }) + + return +} + +func cc4decls(name string) (decls []ast.Decl) { + constdecl := &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ + ast.NewIdent(strings.ToUpper(name)), + }, + Values: []ast.Expr{ + &ast.CallExpr{ + Fun: ast.NewIdent("Tag"), + Args: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("0x%x", []byte(name))}}, + }, + }, + }, + }, + } + decls = append(decls, constdecl) + return +} + +func codeclonereplace(stmts []ast.Stmt, doit []ast.Stmt) (out []ast.Stmt) { + out = append([]ast.Stmt(nil), stmts...) + for i := range out { + if ifstmt, ok := out[i].(*ast.IfStmt); ok { + newifstmt := &ast.IfStmt{ + Cond: ifstmt.Cond, + Body: &ast.BlockStmt{ + List: codeclonereplace(ifstmt.Body.List, doit), + }, + } + if ifstmt.Else != nil { + newifstmt.Else = &ast.BlockStmt{ + List: codeclonereplace(ifstmt.Else.(*ast.BlockStmt).List, doit), + } + } + out[i] = newifstmt + } else if exprstmt, ok := out[i].(*ast.ExprStmt); ok { + if callexpr, ok := exprstmt.X.(*ast.CallExpr); ok { + if getexprs(callexpr.Fun) == "doit" { + out[i] = &ast.BlockStmt{List: doit} + } + } + } + } + return +} + +func getatommarshalfn(origfn *ast.FuncDecl, + origname, origtag string, + tagnamemap map[string]string, +) (decls []ast.Decl) { + marstmts := []ast.Stmt{} + unmarstmts := []ast.Stmt{} + lenstmts := []ast.Stmt{} + childrenstmts := []ast.Stmt{} + + parseerrreturn := func(debug string) (stmts []ast.Stmt) { + return []ast.Stmt{ + &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("err")}, + Rhs: []ast.Expr{ast.NewIdent(fmt.Sprintf(`parseErr("%s", n+offset, err)`, debug))}, + }, + &ast.ReturnStmt{}, + } + } + + callmarshal := func(name string) (stmts []ast.Stmt) { + callexpr := &ast.CallExpr{ + Fun: ast.NewIdent(name+".Marshal"), + Args: []ast.Expr{ast.NewIdent("b[n:]")}, + } + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{callexpr}, + } + stmts = append(stmts, assign) + return + } + + callputstruct := func(typ, name string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: ast.NewIdent(typegetputfn(typ)), + Args: []ast.Expr{ast.NewIdent("b[n:]"), ast.NewIdent(name)}, + }, + }) + stmts = append(stmts, &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{ast.NewIdent(typegetlens(typ))}, + }) + return + } + + calllenstruct := func(typ, name string) (stmts []ast.Stmt) { + inc := typegetlens(typ)+"*len("+name+")" + stmts = append(stmts, &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{ast.NewIdent(inc)}, + }) + return + } + + calllen := func(name string) (stmts []ast.Stmt) { + callexpr := &ast.CallExpr{ + Fun: ast.NewIdent(name+".Len"), + Args: []ast.Expr{}, + } + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("n")}, + Rhs: []ast.Expr{callexpr}, + } + stmts = append(stmts, assign) + return + } + + foreach := func(name, field string, block []ast.Stmt) (stmts []ast.Stmt) { + rangestmt := &ast.RangeStmt{ + Key: ast.NewIdent("_"), + Value: ast.NewIdent(name), + Body: &ast.BlockStmt{ + List: block, + }, + Tok: token.DEFINE, + X: ast.NewIdent(field), + } + stmts = append(stmts, rangestmt) + return + } + + foreachatom := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { + return foreach("atom", field, block) + } + + foreachentry := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { + return foreach("entry", field, block) + } + + foreachi := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { + rangestmt := &ast.RangeStmt{ + Key: ast.NewIdent("i"), + Body: &ast.BlockStmt{ + List: block, + }, + Tok: token.DEFINE, + X: ast.NewIdent(field), + } + stmts = append(stmts, rangestmt) + return + } + + foreachunknowns := func(block []ast.Stmt) (stmts []ast.Stmt) { + return foreachatom("self.Unknowns", block) + } + + declvar := func(name, typ string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.DeclStmt{ + Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ + ast.NewIdent(typ), + }, + Type: ast.NewIdent(name), + }, + }, + }, + }) + return + } + + makeslice := func(name, typ, size string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.ExprStmt{ + X: ast.NewIdent(fmt.Sprintf("%s = make([]%s, %s)", name, typ, size)), + }) + return + } + + simpleassign := func(tok token.Token, l, r string) *ast.AssignStmt { + return &ast.AssignStmt{ + Tok: tok, + Lhs: []ast.Expr{ast.NewIdent(l)}, + Rhs: []ast.Expr{ast.NewIdent(r)}, + } + } + + struct2tag := func(s string) string { + name := tagnamemap[s] + return name + } + + foreachatomsappendchildren := func(field string) (stmts []ast.Stmt) { + return foreachatom(field, []ast.Stmt{ + simpleassign(token.ASSIGN, "r", "append(r, atom)"), + }) + } + + var hasunknowns bool + var atomnames []string + var atomtypes []string + var atomarrnames []string + var atomarrtypes []string + slicenamemap := map[string]string{} + + unmarshalatom := func(typ, init string) (stmts []ast.Stmt) { + return []ast.Stmt{ + &ast.AssignStmt{Tok: token.DEFINE, + Lhs: []ast.Expr{ast.NewIdent("atom")}, Rhs: []ast.Expr{ast.NewIdent("&"+typ+"{"+init+"}")}, + }, + &ast.IfStmt{ + Init: &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("_"), ast.NewIdent("err")}, + Rhs: []ast.Expr{ast.NewIdent("atom.Unmarshal(b[n:n+size], offset+n)")}, + }, + Cond: ast.NewIdent("err != nil"), + Body: &ast.BlockStmt{List: parseerrreturn(struct2tag(typ))}, + }, + } + } + + unmrashalatoms := func() (stmts []ast.Stmt) { + blocks := []ast.Stmt{} + + blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("tag")}, + Rhs: []ast.Expr{ast.NewIdent("Tag(pio.U32BE(b[n+4:]))")}, + }) + blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("size")}, + Rhs: []ast.Expr{ast.NewIdent("int(pio.U32BE(b[n:]))")}, + }) + blocks = append(blocks, &ast.IfStmt{ + Cond: ast.NewIdent("len(b) < n+size"), + Body: &ast.BlockStmt{List: parseerrreturn("TagSizeInvalid")}, + }) + + cases := []ast.Stmt{} + + for i, atom := range atomnames { + cases = append(cases, &ast.CaseClause{ + List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomtypes[i])))}, + Body: []ast.Stmt{&ast.BlockStmt{ + List: append(unmarshalatom(atomtypes[i], ""), simpleassign(token.ASSIGN, "self."+atom, "atom")), + }}, + }) + } + + for i, atom := range atomarrnames { + selfatom := "self."+atom + cases = append(cases, &ast.CaseClause{ + List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomarrtypes[i])))}, + Body: []ast.Stmt{&ast.BlockStmt{ + List: append(unmarshalatom(atomarrtypes[i], ""), + simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), + }}, + }) + } + + if hasunknowns { + init := "Tag_: tag, Data: b[n:n+size]" + selfatom := "self.Unknowns" + cases = append(cases, &ast.CaseClause{ + Body: []ast.Stmt{&ast.BlockStmt{ + List: append(unmarshalatom("Dummy", init), simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), + }}, + }) + } + + blocks = append(blocks, &ast.SwitchStmt{ + Tag: ast.NewIdent("tag"), + Body: &ast.BlockStmt{List: cases}, + }) + + blocks = append(blocks, addns("size")...) + + stmts = append(stmts, &ast.ForStmt{ + Cond: ast.NewIdent("n+8 < len(b)"), + Body: &ast.BlockStmt{List: blocks}, + }) + return + } + + marshalwrapstmts := func() (stmts []ast.Stmt) { + stmts = append(stmts, putxx("uint32", "4", strings.ToUpper(origtag), true)...) + stmts = append(stmts, addns("self.marshal(b[8:])+8")...) + stmts = append(stmts, putxx("uint32", "0", "n", true)...) + stmts = append(stmts, &ast.ReturnStmt{}) + return + } + + ifnotnil := func(name string, block []ast.Stmt) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.IfStmt{ + Cond: &ast.BinaryExpr{ + X: ast.NewIdent(name), + Op: token.NEQ, + Y: ast.NewIdent("nil"), + }, + Body: &ast.BlockStmt{List: block}, + }) + return + } + + getchildrennr := func(name string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.AssignStmt{ + Tok: token.DEFINE, + Lhs: []ast.Expr{ast.NewIdent(name)}, + Rhs: []ast.Expr{ast.NewIdent("0")}, + }) + for _, atom := range atomnames { + stmts = append(stmts, ifnotnil("self."+atom, []ast.Stmt{ + &ast.IncDecStmt{X: ast.NewIdent(name), Tok: token.INC}, + })...) + } + if hasunknowns { + assign := &ast.AssignStmt{ + Tok: token.ADD_ASSIGN, + Lhs: []ast.Expr{ast.NewIdent("_childrenNR")}, + Rhs: []ast.Expr{ast.NewIdent("len(self.Unknowns)")}, + } + stmts = append(stmts, assign) + } + return + } + + checkcurlen := func(inc, debug string) (stmts []ast.Stmt) { + stmts = append(stmts, &ast.IfStmt{ + Cond: &ast.BinaryExpr{ + X: ast.NewIdent("len(b)"), + Op: token.LSS, + Y: ast.NewIdent("n+"+inc), + }, + Body: &ast.BlockStmt{List: parseerrreturn(debug)}, + }) + return + } + + checklendo := func(typ, name, debug string) (stmts []ast.Stmt) { + stmts = append(stmts, checkcurlen(typegetlens(typ), debug)...) + stmts = append(stmts, getxx(typ, "n", name, false)...) + stmts = append(stmts, addns(typegetlens(typ))...) + return + } + + checkstructlendo := func(typ, name, debug string, + foreach func(string,[]ast.Stmt)[]ast.Stmt, + ) (stmts []ast.Stmt) { + inc := typegetlens(typ)+"*len("+name+")" + stmts = append(stmts, checkcurlen(inc, debug)...) + stmts = append(stmts, foreach(name, append( + []ast.Stmt{ + &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ + ast.NewIdent(name+"[i]"), + }, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: ast.NewIdent(typegetgetfn(typ)), + Args: []ast.Expr{ast.NewIdent("b[n:]")}, + }, + }, + }, + }, + addns(typegetlens(typ))..., + ))...) + return + } + + checklencopy := func(name string) (stmts []ast.Stmt) { + lens := fmt.Sprintf("len(self.%s)", name) + stmts = append(stmts, checkcurlen(lens, name)...) + stmts = append(stmts, simplecall("copy", fmt.Sprintf("self.%s[:]", name), "b[n:]")) + stmts = append(stmts, addns(lens)...) + return + } + + appendcode := func(args []ast.Expr, + marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, unmarstmts *[]ast.Stmt, + defmarstmts []ast.Stmt, deflenstmts []ast.Stmt, defunmarstmts []ast.Stmt, + ) { + bodylist := func(i int, doit []ast.Stmt) []ast.Stmt { + return codeclonereplace(args[i].(*ast.FuncLit).Body.List, doit) + } + if len(args) == 1 { + *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) + *lenstmts = append(*lenstmts, bodylist(0, deflenstmts)...) + *unmarstmts = append(*unmarstmts, bodylist(0, defunmarstmts)...) + } else { + *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) + *lenstmts = append(*lenstmts, bodylist(1, deflenstmts)...) + *unmarstmts = append(*unmarstmts, bodylist(2, defunmarstmts)...) + } + } + + getdefaultstmts := func( + typ, name, name2 string, + marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, + unmarstmts *[]ast.Stmt, childrenstmts *[]ast.Stmt, + ) { + switch typ { + case "bytes", "bytesleft": + *marstmts = append(*marstmts, simplecall("copy", "b[n:]", "self."+name+"[:]")) + *marstmts = append(*marstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) + *lenstmts = append(*lenstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) + if typ == "bytes" { + *unmarstmts = append(*unmarstmts, checklencopy(name)...) + } else { + *unmarstmts = append(*unmarstmts, simpleassign(token.ASSIGN, "self."+name, "b[n:]")) + *unmarstmts = append(*unmarstmts, addns("len(b[n:])")...) + } + + case "array": + *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) + *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name+"[:]")...) + *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name, foreachi)...) + + case "atoms": + *marstmts = append(*marstmts, foreachatom("self."+name, callmarshal("atom"))...) + *lenstmts = append(*lenstmts, foreachatom("self."+name, calllen("atom"))...) + *childrenstmts = append(*childrenstmts, foreachatomsappendchildren("self."+name)...) + + case "slice": + *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) + *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name)...) + *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name2, foreachi)...) + + case "atom": + *marstmts = append(*marstmts, ifnotnil("self."+name, callmarshal("self."+name))...) + *lenstmts = append(*lenstmts, ifnotnil("self."+name, calllen("self."+name))...) + *childrenstmts = append(*childrenstmts, ifnotnil("self."+name, []ast.Stmt{ + simpleassign(token.ASSIGN, "r", fmt.Sprintf("append(r, %s)", "self."+name)), + })...) + + default: + *marstmts = append(*marstmts, putxxadd(typ, "self."+name, false)...) + *lenstmts = append(*lenstmts, addn(typegetlen(typ))...) + *unmarstmts = append(*unmarstmts, checklendo(typ, "self."+name, name)...) + } + } + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + if typ == "_unknowns" { + hasunknowns = true + } else if typ == "atom" { + name := getexprs(callexpr.Args[0]) + name2 := getexprs(callexpr.Args[1]) + atomnames = append(atomnames, name) + atomtypes = append(atomtypes, name2) + } else if typ == "atoms" { + name := getexprs(callexpr.Args[0]) + name2 := getexprs(callexpr.Args[1]) + atomarrnames = append(atomarrnames, name) + atomarrtypes = append(atomarrtypes, name2) + } else if typ == "slice" { + name := getexprs(callexpr.Args[0]) + name2 := getexprs(callexpr.Args[1]) + slicenamemap[name] = name2 + } + } + + lenstmts = append(lenstmts, addn(8)...) + unmarstmts = append(unmarstmts, simplecall("(&self.AtomPos).setPos", "offset", "len(b)")) + unmarstmts = append(unmarstmts, addn(8)...) + + for _, _stmt := range origfn.Body.List { + stmt := _stmt.(*ast.ExprStmt) + callexpr := stmt.X.(*ast.CallExpr) + typ := callexpr.Fun.(*ast.Ident).Name + + name := "" + if len(callexpr.Args) > 0 { + name = getexprs(callexpr.Args[0]) + } + + name2 := "" + if len(callexpr.Args) > 1 { + name2 = getexprs(callexpr.Args[1]) + } + + var defmarstmts, deflenstmts, defunmarstmts, defchildrenstmts []ast.Stmt + getdefaultstmts(typ, name, name2, + &defmarstmts, &deflenstmts, &defunmarstmts, &defchildrenstmts) + + var code []ast.Expr + for _, arg := range callexpr.Args { + if fn, ok := arg.(*ast.CallExpr); ok { + if getexprs(fn.Fun) == "_code" { + code = fn.Args + } + } + } + if code != nil { + appendcode(code, + &marstmts, &lenstmts, &unmarstmts, + defmarstmts, deflenstmts, defunmarstmts, + ) + continue + } + + if strings.HasPrefix(typ, "_") { + if typ == "_unknowns" { + marstmts = append(marstmts, foreachunknowns(callmarshal("atom"))...) + lenstmts = append(lenstmts, foreachunknowns(calllen("atom"))...) + childrenstmts = append(childrenstmts, simpleassign(token.ASSIGN, "r", "append(r, self.Unknowns...)")) + } + if typ == "_skip" { + marstmts = append(marstmts, addns(name)...) + lenstmts = append(lenstmts, addns(name)...) + unmarstmts = append(unmarstmts, addns(name)...) + } + if typ == "_code" { + appendcode(callexpr.Args, + &marstmts, &lenstmts, &unmarstmts, + defmarstmts, deflenstmts, defunmarstmts, + ) + } + continue + } + + if name == "_childrenNR" { + marstmts = append(marstmts, getchildrennr(name)...) + marstmts = append(marstmts, putxxadd(typ, name, true)...) + lenstmts = append(lenstmts, addn(typegetlen(typ))...) + unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) + continue + } + + if strings.HasPrefix(name, "_len_") { + field := name[len("_len_"):] + marstmts = append(marstmts, putxxadd(typ, "len(self."+field+")", true)...) + lenstmts = append(lenstmts, addn(typegetlen(typ))...) + unmarstmts = append(unmarstmts, declvar(typegetvartype(typ), name)...) + unmarstmts = append(unmarstmts, getxx(typ, "n", name, false)...) + unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) + unmarstmts = append(unmarstmts, makeslice("self."+field, slicenamemap[field], name)...) + continue + } + + marstmts = append(marstmts, defmarstmts...) + lenstmts = append(lenstmts, deflenstmts...) + unmarstmts = append(unmarstmts, defunmarstmts...) + childrenstmts = append(childrenstmts, defchildrenstmts...) + } + + if len(atomnames) > 0 || len(atomarrnames) > 0 || hasunknowns { + unmarstmts = append(unmarstmts, unmrashalatoms()...) + } + + marstmts = append(marstmts, &ast.ReturnStmt{}) + lenstmts = append(lenstmts, &ast.ReturnStmt{}) + unmarstmts = append(unmarstmts, &ast.ReturnStmt{}) + childrenstmts = append(childrenstmts, &ast.ReturnStmt{}) + + decls = append(decls, newdecl(origname, "Marshal", []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + }, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + }, marshalwrapstmts())) + + decls = append(decls, newdecl(origname, "marshal", []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + }, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + }, marstmts)) + + decls = append(decls, newdecl(origname, "Len", []*ast.Field{}, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + }, lenstmts)) + + decls = append(decls, newdecl("*"+origname, "Unmarshal", []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, + &ast.Field{Names: []*ast.Ident{ast.NewIdent("offset")}, Type: ast.NewIdent("int")}, + }, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, + &ast.Field{Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}, + }, unmarstmts)) + + decls = append(decls, newdecl(origname, "Children", []*ast.Field{}, []*ast.Field{ + &ast.Field{Names: []*ast.Ident{ast.NewIdent("r")}, Type: ast.NewIdent("[]Atom")}, + }, childrenstmts)) + + return +} + +func genatoms(filename, outfilename string) { + // Create the AST by parsing src. + fset := token.NewFileSet() // positions are relative to fset + file, err := parser.ParseFile(fset, filename, nil, 0) + if err != nil { + panic(err) + } + + gen := &ast.File{} + gen.Name = ast.NewIdent("mp4io") + gen.Decls = []ast.Decl{ + &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/kerberos-io/joy4/utils/bits/pio"`}}, + }, + }, + &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"time"`}}, + }, + }, + } + + tagnamemap := map[string]string{} + tagnamemap["ElemStreamDesc"] = "esds" + + splittagname := func(fnname string) (ok bool, tag, name string) { + if len(fnname) > 5 && fnname[4] == '_' { + tag = fnname[0:4] + tag = strings.Replace(tag, "_", " ", 1) + name = fnname[5:] + ok = true + } else { + name = fnname + } + return + } + + for _, decl := range file.Decls { + if fndecl, ok := decl.(*ast.FuncDecl); ok { + ok, tag, name := splittagname(fndecl.Name.Name) + if ok { + tagnamemap[name] = tag + } + } + } + + tagfuncdecl := func(name, tag string) (decls ast.Decl) { + return newdecl(name, "Tag", []*ast.Field{}, []*ast.Field{ + &ast.Field{Type: ast.NewIdent("Tag")}, + }, []ast.Stmt{ + &ast.ReturnStmt{ + Results: []ast.Expr{ast.NewIdent(strings.ToUpper(tag))}}}) + } + + for k, v := range tagnamemap { + gen.Decls = append(gen.Decls, cc4decls(v)...) + gen.Decls = append(gen.Decls, tagfuncdecl(k, v)) + } + gen.Decls = append(gen.Decls, cc4decls("mdat")...) + + for _, decl := range file.Decls { + if fndecl, ok := decl.(*ast.FuncDecl); ok { + ok, tag, name := splittagname(fndecl.Name.Name) + if ok { + gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) + gen.Decls = append(gen.Decls, getatommarshalfn(fndecl, name, tag, tagnamemap)...) + } else { + gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) + gen.Decls = append(gen.Decls, getstructputgetlenfn(fndecl, name)...) + } + } + } + + outfile, _ := os.Create(outfilename) + printer.Fprint(outfile, fset, gen) + outfile.Close() +} + +func parse(filename, outfilename string) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, filename, nil, 0) + if err != nil { + panic(err) + } + outfile, _ := os.Create(outfilename) + ast.Fprint(outfile, fset, file, nil) + outfile.Close() +} + +func main() { + switch os.Args[1] { + case "parse": + parse(os.Args[2], os.Args[3]) + + case "gen": + genatoms(os.Args[2], os.Args[3]) + } +} + diff --git a/machinery/src/fmp4/mp4io/gen/pattern.go b/machinery/src/fmp4/mp4io/gen/pattern.go new file mode 100644 index 00000000..e039d8ee --- /dev/null +++ b/machinery/src/fmp4/mp4io/gen/pattern.go @@ -0,0 +1,438 @@ +package main + +import "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" + +func moov_Movie() { + atom(Header, MovieHeader) + atom(MovieExtend, MovieExtend) + atoms(Tracks, Track) + _unknowns() +} + +func mvhd_MovieHeader() { + uint8(Version) + uint24(Flags) + time32(CreateTime) + time32(ModifyTime) + int32(TimeScale) + int32(Duration) + fixed32(PreferredRate) + fixed16(PreferredVolume) + _skip(10) + array(Matrix, int32, 9) + time32(PreviewTime) + time32(PreviewDuration) + time32(PosterTime) + time32(SelectionTime) + time32(SelectionDuration) + time32(CurrentTime) + int32(NextTrackId) +} + +func trak_Track() { + atom(Header, TrackHeader) + atom(Media, Media) + _unknowns() +} + +func tkhd_TrackHeader() { + uint8(Version) + uint24(Flags) + time32(CreateTime) + time32(ModifyTime) + int32(TrackId) + _skip(4) + int32(Duration) + _skip(8) + int16(Layer) + int16(AlternateGroup) + fixed16(Volume) + _skip(2) + array(Matrix, int32, 9) + fixed32(TrackWidth) + fixed32(TrackHeight) +} + +func hdlr_HandlerRefer() { + uint8(Version) + uint24(Flags) + bytes(Type, 4) + bytes(SubType, 4) + bytesleft(Name) +} + +func mdia_Media() { + atom(Header, MediaHeader) + atom(Handler, HandlerRefer) + atom(Info, MediaInfo) + _unknowns() +} + +func mdhd_MediaHeader() { + uint8(Version) + uint24(Flags) + time32(CreateTime) + time32(ModifyTime) + int32(TimeScale) + int32(Duration) + int16(Language) + int16(Quality) +} + +func minf_MediaInfo() { + atom(Sound, SoundMediaInfo) + atom(Video, VideoMediaInfo) + atom(Data, DataInfo) + atom(Sample, SampleTable) + _unknowns() +} + +func dinf_DataInfo() { + atom(Refer, DataRefer) + _unknowns() +} + +func dref_DataRefer() { + uint8(Version) + uint24(Flags) + int32(_childrenNR) + atom(Url, DataReferUrl) +} + +func url__DataReferUrl() { + uint8(Version) + uint24(Flags) +} + +func smhd_SoundMediaInfo() { + uint8(Version) + uint24(Flags) + int16(Balance) + _skip(2) +} + +func vmhd_VideoMediaInfo() { + uint8(Version) + uint24(Flags) + int16(GraphicsMode) + array(Opcolor, int16, 3) +} + +func stbl_SampleTable() { + atom(SampleDesc, SampleDesc) + atom(TimeToSample, TimeToSample) + atom(CompositionOffset, CompositionOffset) + atom(SampleToChunk, SampleToChunk) + atom(SyncSample, SyncSample) + atom(ChunkOffset, ChunkOffset) + atom(SampleSize, SampleSize) +} + +func stsd_SampleDesc() { + uint8(Version) + _skip(3) + int32(_childrenNR) + atom(AVC1Desc, AVC1Desc) + atom(MP4ADesc, MP4ADesc) + _unknowns() +} + +func mp4a_MP4ADesc() { + _skip(6) + int16(DataRefIdx) + int16(Version) + int16(RevisionLevel) + int32(Vendor) + int16(NumberOfChannels) + int16(SampleSize) + int16(CompressionId) + _skip(2) + fixed32(SampleRate) + atom(Conf, ElemStreamDesc) + _unknowns() +} + +func avc1_AVC1Desc() { + _skip(6) + int16(DataRefIdx) + int16(Version) + int16(Revision) + int32(Vendor) + int32(TemporalQuality) + int32(SpatialQuality) + int16(Width) + int16(Height) + fixed32(HorizontalResolution) + fixed32(VorizontalResolution) + _skip(4) + int16(FrameCount) + bytes(CompressorName, 32) + int16(Depth) + int16(ColorTableId) + atom(Conf, AVC1Conf) + _unknowns() +} + +func avcC_AVC1Conf() { + bytesleft(Data) +} + +func stts_TimeToSample() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, TimeToSampleEntry) +} + +func TimeToSampleEntry() { + uint32(Count) + uint32(Duration) +} + +func stsc_SampleToChunk() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, SampleToChunkEntry) +} + +func SampleToChunkEntry() { + uint32(FirstChunk) + uint32(SamplesPerChunk) + uint32(SampleDescId) +} + +func ctts_CompositionOffset() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, CompositionOffsetEntry) +} + +func CompositionOffsetEntry() { + uint32(Count) + uint32(Offset) +} + +func stss_SyncSample() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, uint32) +} + +func stco_ChunkOffset() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + slice(Entries, uint32) +} + +func moof_MovieFrag() { + atom(Header, MovieFragHeader) + atoms(Tracks, TrackFrag) + _unknowns() +} + +func mfhd_MovieFragHeader() { + uint8(Version) + uint24(Flags) + uint32(Seqnum) +} + +func traf_TrackFrag() { + atom(Header, TrackFragHeader) + atom(DecodeTime, TrackFragDecodeTime) + atom(Run, TrackFragRun) + _unknowns() +} + +func mvex_MovieExtend() { + atoms(Tracks, TrackExtend) + _unknowns() +} + +func trex_TrackExtend() { + uint8(Version) + uint24(Flags) + uint32(TrackId) + uint32(DefaultSampleDescIdx) + uint32(DefaultSampleDuration) + uint32(DefaultSampleSize) + uint32(DefaultSampleFlags) +} + +func stsz_SampleSize() { + uint8(Version) + uint24(Flags) + uint32(SampleSize) + _code(func() { + if self.SampleSize != 0 { + return + } + }) + uint32(_len_Entries) + slice(Entries, uint32) +} + +func trun_TrackFragRun() { + uint8(Version) + uint24(Flags) + uint32(_len_Entries) + + uint32(DataOffset, _code(func() { + if self.Flags&TRUN_DATA_OFFSET != 0 { + doit() + } + })) + + uint32(FirstSampleFlags, _code(func() { + if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { + doit() + } + })) + + slice(Entries, TrackFragRunEntry, _code(func() { + for i, entry := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + if flags&TRUN_SAMPLE_DURATION != 0 { + pio.PutU32BE(b[n:], entry.Duration) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + pio.PutU32BE(b[n:], entry.Size) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + pio.PutU32BE(b[n:], entry.Flags) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + pio.PutU32BE(b[n:], entry.Cts) + n += 4 + } + } + }, func() { + for i := range self.Entries { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + if flags&TRUN_SAMPLE_DURATION != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + n += 4 + } + } + }, func() { + for i := 0; i < int(_len_Entries); i++ { + var flags uint32 + if i > 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + entry := &self.Entries[i] + if flags&TRUN_SAMPLE_DURATION != 0 { + entry.Duration = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_SIZE != 0 { + entry.Size = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + entry.Flags = pio.U32BE(b[n:]) + n += 4 + } + if flags&TRUN_SAMPLE_CTS != 0 { + entry.Cts = pio.U32BE(b[n:]) + n += 4 + } + } + })) +} + +func TrackFragRunEntry() { + uint32(Duration) + uint32(Size) + uint32(Flags) + uint32(Cts) +} + +func tfhd_TrackFragHeader() { + uint8(Version) + uint24(Flags) + + uint64(BaseDataOffset, _code(func() { + if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { + doit() + } + })) + + uint32(StsdId, _code(func() { + if self.Flags&TFHD_STSD_ID != 0 { + doit() + } + })) + + uint32(DefaultDuration, _code(func() { + if self.Flags&TFHD_DEFAULT_DURATION != 0 { + doit() + } + })) + + uint32(DefaultSize, _code(func() { + if self.Flags&TFHD_DEFAULT_SIZE != 0 { + doit() + } + })) + + uint32(DefaultFlags, _code(func() { + if self.Flags&TFHD_DEFAULT_FLAGS != 0 { + doit() + } + })) +} + +func tfdt_TrackFragDecodeTime() { + uint8(Version) + uint24(Flags) + time64(Time, _code(func() { + if self.Version != 0 { + PutTime64(b[n:], self.Time) + n += 8 + } else { + PutTime32(b[n:], self.Time) + n += 4 + } + }, func() { + if self.Version != 0 { + n += 8 + } else { + n += 4 + } + }, func() { + if self.Version != 0 { + self.Time = GetTime64(b[n:]) + n += 8 + } else { + self.Time = GetTime32(b[n:]) + n += 4 + } + })) +} diff --git a/machinery/src/fmp4/mp4io/mp4io.go b/machinery/src/fmp4/mp4io/mp4io.go new file mode 100644 index 00000000..432ff1dd --- /dev/null +++ b/machinery/src/fmp4/mp4io/mp4io.go @@ -0,0 +1,535 @@ +package mp4io + +import ( + "fmt" + "io" + "math" + "os" + "strings" + "time" + + "github.com/kerberos-io/joy4/utils/bits/pio" +) + +type ParseError struct { + Debug string + Offset int + prev *ParseError +} + +func (self *ParseError) Error() string { + s := []string{} + for p := self; p != nil; p = p.prev { + s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset)) + } + return "mp4io: parse error: " + strings.Join(s, ",") +} + +func parseErr(debug string, offset int, prev error) (err error) { + _prev, _ := prev.(*ParseError) + return &ParseError{Debug: debug, Offset: offset, prev: _prev} +} + +func GetTime32(b []byte) (t time.Time) { + sec := pio.U32BE(b) + t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) + t = t.Add(time.Second * time.Duration(sec)) + return +} + +func PutTime32(b []byte, t time.Time) { + dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) + sec := uint32(dur / time.Second) + pio.PutU32BE(b, sec) +} + +func GetTime64(b []byte) (t time.Time) { + sec := pio.U64BE(b) + t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) + t = t.Add(time.Second * time.Duration(sec)) + return +} + +func PutTime64(b []byte, t time.Time) { + dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) + sec := uint64(dur / time.Millisecond) + pio.PutU64BE(b, sec) +} + +func PutFixed16(b []byte, f float64) { + intpart, fracpart := math.Modf(f) + b[0] = uint8(intpart) + b[1] = uint8(fracpart * 256.0) +} + +func GetFixed16(b []byte) float64 { + return float64(b[0]) + float64(b[1])/256.0 +} + +func PutFixed32(b []byte, f float64) { + intpart, fracpart := math.Modf(f) + pio.PutU16BE(b[0:2], uint16(intpart)) + pio.PutU16BE(b[2:4], uint16(fracpart*65536.0)) +} + +func GetFixed32(b []byte) float64 { + return float64(pio.U16BE(b[0:2])) + float64(pio.U16BE(b[2:4]))/65536.0 +} + +type Tag uint32 + +func (self Tag) String() string { + var b [4]byte + pio.PutU32BE(b[:], uint32(self)) + for i := 0; i < 4; i++ { + if b[i] == 0 { + b[i] = ' ' + } + } + return string(b[:]) +} + +type Atom interface { + Pos() (int, int) + Tag() Tag + Marshal([]byte) int + Unmarshal([]byte, int) (int, error) + Len() int + Children() []Atom +} + +type AtomPos struct { + Offset int + Size int +} + +func (self AtomPos) Pos() (int, int) { + return self.Offset, self.Size +} + +func (self *AtomPos) setPos(offset int, size int) { + self.Offset, self.Size = offset, size +} + +type Dummy struct { + Data []byte + Tag_ Tag + AtomPos +} + +func (self Dummy) Children() []Atom { + return nil +} + +func (self Dummy) Tag() Tag { + return self.Tag_ +} + +func (self Dummy) Len() int { + return len(self.Data) +} + +func (self Dummy) Marshal(b []byte) int { + copy(b, self.Data) + return len(self.Data) +} + +func (self *Dummy) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + self.Data = b + n = len(b) + return +} + +func StringToTag(tag string) Tag { + var b [4]byte + copy(b[:], []byte(tag)) + return Tag(pio.U32BE(b[:])) +} + +func FindChildrenByName(root Atom, tag string) Atom { + return FindChildren(root, StringToTag(tag)) +} + +func FindChildren(root Atom, tag Tag) Atom { + if root.Tag() == tag { + return root + } + for _, child := range root.Children() { + if r := FindChildren(child, tag); r != nil { + return r + } + } + return nil +} + +const ( + TFHD_BASE_DATA_OFFSET = 0x01 + TFHD_STSD_ID = 0x02 + TFHD_DEFAULT_DURATION = 0x08 + TFHD_DEFAULT_SIZE = 0x10 + TFHD_DEFAULT_FLAGS = 0x20 + TFHD_DURATION_IS_EMPTY = 0x010000 + TFHD_DEFAULT_BASE_IS_MOOF = 0x020000 +) + +const ( + TRUN_DATA_OFFSET = 0x01 + TRUN_FIRST_SAMPLE_FLAGS = 0x04 + TRUN_SAMPLE_DURATION = 0x100 + TRUN_SAMPLE_SIZE = 0x200 + TRUN_SAMPLE_FLAGS = 0x400 + TRUN_SAMPLE_CTS = 0x800 +) + +const ( + MP4ESDescrTag = 3 + MP4DecConfigDescrTag = 4 + MP4DecSpecificDescrTag = 5 + MP4DescrTag = 6 +) + +const ( + MP4DecConfigDataSize = 2 + 3 + 4 + 4 +) + +type ElemStreamDesc struct { + DecConfig []byte + TrackId uint16 + AtomPos +} + +func (self ElemStreamDesc) Children() []Atom { + return nil +} + +func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) { + for i := 3; i > 0; i-- { + b[n] = uint8(length>>uint(7*i))&0x7f | 0x80 + n++ + } + b[n] = uint8(length & 0x7f) + n++ + return +} + +func (self ElemStreamDesc) lenDescHdr() (n int) { + return 5 +} + +func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) { + b[n] = tag + n++ + n += self.fillLength(b[n:], datalen) + return +} + +func (self ElemStreamDesc) lenESDescHdr() (n int) { + return self.lenDescHdr() +} + +func (self ElemStreamDesc) lenESDescData() (n int) { + return 3 +} + +func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) { + n += self.fillDescHdr(b[n:], MP4ESDescrTag, datalen) + pio.PutU16BE(b[n:], self.TrackId) + n += 2 + b[n] = 0 // flags + n++ + return +} + +func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) { + return self.lenDescHdr() + MP4DecConfigDataSize + self.lenDescHdr() +} + +func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) { + n += self.fillDescHdr(b[n:], MP4DecConfigDescrTag, datalen) + b[n] = 0x40 // objectid + n++ + b[n] = 0x15 // streamtype + n++ + // buffer size db + pio.PutU24BE(b[n:], 0) + n += 3 + // max bitrage + pio.PutU32BE(b[n:], uint32(200000)) + n += 4 + // avg bitrage + pio.PutU32BE(b[n:], uint32(0)) + n += 4 + n += self.fillDescHdr(b[n:], MP4DecSpecificDescrTag, datalen-n) + return +} + +func (self ElemStreamDesc) Len() (n int) { + n += 8 + n += 4 + // 0x03 MP4ESDescHeader + // 5 + n += self.lenESDescHdr() + + // + ESID + ESFlags + // + 2 + 1 + n += self.lenESDescData() + + // 0x04 MP4DecConfigDescrTag + MP4DecConfigDataSize + 0x05 MP4DecSpecificDescrHeader + // 5 + 13 + 5 + n += self.lenDecConfigDescHdr() + + // Variable size configuration + n += len(self.DecConfig) + + // 0x06 MP4DescrHeader + 1 + // 5 + 1 + n += self.lenDescHdr() + 1 + + return // 8 + 4 + self.lenESDescHdr() + self.lenDecConfigDescHdr() + len(self.DecConfig) + self.lenDescHdr() + 1 +} + +// Version(4) +// ESDesc( +// MP4ESDescrTag +// ESID(2) +// ESFlags(1) +// DecConfigDesc( +// MP4DecConfigDescrTag +// objectId streamType bufSize avgBitrate +// DecSpecificDesc( +// MP4DecSpecificDescrTag +// decConfig +// ) +// ) +// ?Desc(lenDescHdr+1) +// ) + +func (self ElemStreamDesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(ESDS)) + n += 8 + pio.PutU32BE(b[n:], 0) // Version + n += 4 + datalen := self.Len() + n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr()) + + n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-self.lenDescHdr()-1) + copy(b[n:], self.DecConfig) + n += len(self.DecConfig) + + n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr()) + b[n] = 0x02 + n++ + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (self *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) { + if len(b) < n+12 { + err = parseErr("hdr", offset+n, err) + return + } + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + n += 4 + return self.parseDesc(b[n:], offset+n) +} + +func (self *ElemStreamDesc) parseDesc(b []byte, offset int) (n int, err error) { + var hdrlen int + var datalen int + var tag uint8 + if hdrlen, tag, datalen, err = self.parseDescHdr(b, offset); err != nil { + return + } + + // Skip over the header lenth (tag size 1 byte + lenlen) + n += hdrlen + + if len(b) < n+datalen { + err = parseErr("datalen", offset+n, err) + return + } + + switch tag { + case MP4ESDescrTag: + if len(b) < n+3 { + err = parseErr("MP4ESDescrTag", offset+n, err) + return + } + if _, err = self.parseDesc(b[n+3:], offset+n+3); err != nil { + return + } + + case MP4DecConfigDescrTag: + if len(b) < n+MP4DecConfigDataSize { + err = parseErr("MP4DecSpecificDescrTag", offset+n, err) + return + } + if _, err = self.parseDesc(b[n+MP4DecConfigDataSize:], offset+n+MP4DecConfigDataSize); err != nil { + return + } + + case MP4DecSpecificDescrTag: + self.DecConfig = b[n : n+datalen] + } + + n += datalen + return +} + +func (self *ElemStreamDesc) parseLength(b []byte, offset int) (n int, length int, err error) { + for n < 4 { + if len(b) < n+1 { + err = parseErr("len", offset+n, err) + return + } + c := b[n] + n++ + length = (length << 7) | (int(c) & 0x7f) + if c&0x80 == 0 { + break + } + } + return +} + +func (self *ElemStreamDesc) parseDescHdr(b []byte, offset int) (n int, tag uint8, datalen int, err error) { + var lenlen int + if len(b) < n+1 { + err = parseErr("tag", offset+n, err) + return + } + tag = b[n] + n++ + if lenlen, datalen, err = self.parseLength(b[n:], offset+n); err != nil { + return + } + n += lenlen + return +} + +func ReadFileAtoms(r io.ReadSeeker) (atoms []Atom, err error) { + for { + offset, _ := r.Seek(0, 1) + taghdr := make([]byte, 8) + if _, err = io.ReadFull(r, taghdr); err != nil { + if err == io.EOF { + err = nil + } + return + } + size := pio.U32BE(taghdr[0:]) + tag := Tag(pio.U32BE(taghdr[4:])) + + var atom Atom + switch tag { + case MOOV: + atom = &Movie{} + case MOOF: + atom = &MovieFrag{} + } + + if atom != nil { + b := make([]byte, int(size)) + if _, err = io.ReadFull(r, b[8:]); err != nil { + return + } + copy(b, taghdr) + if _, err = atom.Unmarshal(b, int(offset)); err != nil { + return + } + atoms = append(atoms, atom) + } else { + dummy := &Dummy{Tag_: tag} + dummy.setPos(int(offset), int(size)) + if _, err = r.Seek(int64(size)-8, 1); err != nil { + return + } + atoms = append(atoms, dummy) + } + } + return +} + +func printatom(out io.Writer, root Atom, depth int) { + offset, size := root.Pos() + + type stringintf interface { + String() string + } + + fmt.Fprintf(out, + "%s%s offset=%d size=%d", + strings.Repeat(" ", depth*2), root.Tag(), offset, size, + ) + if str, ok := root.(stringintf); ok { + fmt.Fprint(out, " ", str.String()) + } + fmt.Fprintln(out) + + children := root.Children() + for _, child := range children { + printatom(out, child, depth+1) + } +} + +func FprintAtom(out io.Writer, root Atom) { + printatom(out, root, 0) +} + +func PrintAtom(root Atom) { + FprintAtom(os.Stdout, root) +} + +func (self MovieHeader) String() string { + return fmt.Sprintf("dur=%d", self.Duration) +} + +func (self TimeToSample) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self SampleToChunk) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self SampleSize) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self SyncSample) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self CompositionOffset) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self ChunkOffset) String() string { + return fmt.Sprintf("entries=%d", len(self.Entries)) +} + +func (self TrackFragRun) String() string { + return fmt.Sprintf("dataoffset=%d", self.DataOffset) +} + +func (self TrackFragHeader) String() string { + return fmt.Sprintf("basedataoffset=%d", self.BaseDataOffset) +} + +func (self ElemStreamDesc) String() string { + return fmt.Sprintf("configlen=%d", len(self.DecConfig)) +} + +func (self *Track) GetAVC1Conf() (conf *AVC1Conf) { + atom := FindChildren(self, AVCC) + conf, _ = atom.(*AVC1Conf) + return +} + +func (self *Track) GetElemStreamDesc() (esds *ElemStreamDesc) { + atom := FindChildren(self, ESDS) + esds, _ = atom.(*ElemStreamDesc) + return +} diff --git a/machinery/src/fmp4/muxer.go b/machinery/src/fmp4/muxer.go new file mode 100644 index 00000000..ed53b9e9 --- /dev/null +++ b/machinery/src/fmp4/muxer.go @@ -0,0 +1,568 @@ +package fmp4 + +import ( + "bufio" + "fmt" + "io" + "time" + + "github.com/kerberos-io/agent/machinery/src/fmp4/mp4io" + "github.com/kerberos-io/agent/machinery/src/packets" + "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" +) + +type Muxer struct { + w io.WriteSeeker + bufw *bufio.Writer + wpos int64 + streams []*Stream + videoCodecIndex int + AudioCodecIndex int + + moof_seqnum uint32 + // Streams must start with Keyframes / IDRs + // A keyframe is a complete sample that contains all information to produce a single image. + // All other samples are deltas w.r.t to the last keyframe that's why MP4's must + // always start with a keyframe because any other type of frame will have not point of reference. + // It does mean we lose some data but it was useless anyways. + // This is on the muxer & not on an individual stream to prevent audio (pre first keyframe) of making + // it into our MP4 essentially delaying the audio by a few seconds perhaps (depends on keyframe interval). + gotFirstKeyframe bool +} + +func NewMuxer(w io.WriteSeeker) *Muxer { + return &Muxer{ + w: w, + bufw: bufio.NewWriterSize(w, pio.RecommendBufioSize), + } +} + +func (self *Muxer) newStream(codec packets.Stream, index int, withoutAudio bool) (err error) { + + switch codec.Name { + case "H264": + self.videoCodecIndex = index + case "AAC": + default: + self.AudioCodecIndex = index + if withoutAudio { + return + } + } + + stream := &Stream{ + CodecData: codec, + Idx: index, + } + + stream.sample = &mp4io.SampleTable{ + SampleDesc: &mp4io.SampleDesc{}, + TimeToSample: &mp4io.TimeToSample{}, + SampleToChunk: &mp4io.SampleToChunk{}, + SampleSize: &mp4io.SampleSize{}, + ChunkOffset: &mp4io.ChunkOffset{}, + } + + stream.trackAtom = &mp4io.Track{ + Header: &mp4io.TrackHeader{ + TrackId: int32(len(self.streams) + 1), + Flags: 0x0003, // Track enabled | Track in movie + Duration: 0, // fill later + Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + }, + Media: &mp4io.Media{ + Header: &mp4io.MediaHeader{ + TimeScale: 0, // fill later + Duration: 0, // fill later + Language: 21956, + }, + Info: &mp4io.MediaInfo{ + Sample: stream.sample, + Data: &mp4io.DataInfo{ + Refer: &mp4io.DataRefer{ + Url: &mp4io.DataReferUrl{ + Flags: 0x000001, // Self reference + }, + }, + }, + }, + }, + } + + switch codec.Name { + case "H264": + stream.sample.SyncSample = &mp4io.SyncSample{} + } + + stream.timeScale = 12288 // 90000 // + stream.muxer = self + self.streams = append(self.streams, stream) + + return +} + +func (self *Stream) fillTrackAtom() (err error) { + self.trackAtom.Media.Header.TimeScale = int32(self.timeScale) + self.trackAtom.Media.Header.Duration = int32(self.duration) + + if self.CodecData.Name == "H264" { + + codec := self.CodecData + width, height := codec.Width, codec.Height + decoderData := []byte{} + + recordinfo := AVCDecoderConfRecord{} + if len(self.CodecData.SPS) > 0 { + recordinfo.AVCProfileIndication = self.CodecData.SPS[1] + recordinfo.ProfileCompatibility = self.CodecData.SPS[2] + recordinfo.AVCLevelIndication = self.CodecData.SPS[3] + recordinfo.SPS = [][]byte{self.CodecData.SPS} + + } + if len(self.CodecData.PPS) > 0 { + recordinfo.PPS = [][]byte{self.CodecData.PPS} + } + recordinfo.LengthSizeMinusOne = 3 // check... + + buf := make([]byte, recordinfo.Len()) + recordinfo.Marshal(buf) + + self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: int16(width), + Height: int16(height), + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &mp4io.AVC1Conf{Data: decoderData}, + } + self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ + SubType: [4]byte{'v', 'i', 'd', 'e'}, + Name: []byte("Video Media Handler"), + } + self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{ + Flags: 0x000001, + } + self.trackAtom.Header.TrackWidth = float64(width) + self.trackAtom.Header.TrackHeight = float64(height) + + } else if self.CodecData.Name == "AAC" { + /*codec := self.CodecData.(aacparser.CodecData) + audioConfig := codec.MPEG4AudioConfigBytes() + self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{ + DataRefIdx: 2, + NumberOfChannels: int16(codec.ChannelLayout().Count()), + SampleSize: int16(codec.SampleFormat().BytesPerSample()), + SampleRate: float64(codec.SampleRate()), + Conf: &mp4io.ElemStreamDesc{ + DecConfig: audioConfig, + }, + } + self.trackAtom.Header.Volume = 1 + self.trackAtom.Header.AlternateGroup = 1 + self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ + SubType: [4]byte{'s', 'o', 'u', 'n'}, + Name: []byte{'S', 'o', 'u', 'n', 'd', 'H', 'a', 'n', 'd', 'l', 'e', 'r', 0}, + } + self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}*/ + + } else { + err = fmt.Errorf("mp4: codec type=%d invalid", self.CodecData.Name) + } + + return +} + +func (self *Muxer) WriteHeader(streams []packets.Stream) (err error) { + self.streams = []*Stream{} + for i, stream := range streams { + if err = self.newStream(stream, i, false); err != nil { + // no need to stop the recording if a codec doesnt match, still try to... + } + } + /* + https://www.w3.org/2013/12/byte-stream-format-registry/isobmff-byte-stream-format.html#h2_iso-init-segments + The user agent must run the end of stream algorithm with the error parameter set to "decode" if any of the following conditions are met: + - A File Type Box contains a major_brand or compatible_brand that the user agent does not support. + - A box or field in the Movie Header Box is encountered that violates the requirements mandated by the major_brand or one of the compatible_brands in the File Type Box. + - The tracks in the Movie Header Box contain samples (i.e. the entry_count in the stts, stsc or stco boxes are not set to zero). + - A Movie Extends (mvex) box is not contained in the Movie (moov) box to indicate that Movie Fragments are to be expected. + */ + + moov := &mp4io.Movie{} + moov.Header = &mp4io.MovieHeader{ + PreferredRate: 1, + PreferredVolume: 1, + Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + NextTrackId: int32(len(self.streams)), // ffmpeg uses the last track id as the next track id, makes no sense + PreviewTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), + PreviewDuration: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), + PosterTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), + SelectionTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), + SelectionDuration: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), + CurrentTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), + } + + // Movie Extend MVEX is required for fragmented MP4s + trackExtends := make([]*mp4io.TrackExtend, 0) + for _, stream := range self.streams { + // Add an extension for every available track along with their track ids. + ext := &mp4io.TrackExtend{ + TrackId: uint32(stream.Idx), + DefaultSampleDescIdx: uint32(1), + } + trackExtends = append(trackExtends, ext) + } + moov.MovieExtend = &mp4io.MovieExtend{ + Tracks: trackExtends, + } + + // TODO(atom): write a parser of the User Data Box (udta) + + maxDur := time.Duration(0) + timeScale := int64(10000) + for _, stream := range self.streams { + if err = stream.fillTrackAtom(); err != nil { + return + } + dur := stream.tsToTime(stream.duration) + stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale)) + if dur > maxDur { + maxDur = dur + } + moov.Tracks = append(moov.Tracks, stream.trackAtom) + } + moov.Header.TimeScale = int32(timeScale) + moov.Header.Duration = int32(timeToTs(maxDur, timeScale)) + + b := make([]byte, moov.Len()) + moov.Marshal(b) + if _, err = self.w.Write(b); err != nil { + return + } + + return +} + +func (self *Stream) BuildTrackFragmentWithoutOffset() (trackFragment *mp4io.TrackFrag, err error) { + // new duration + newDts := self.dts + + // Create TrackFragRunEntries + trackfragentries := make([]mp4io.TrackFragRunEntry, 0) // https://ffmpeg.org/pipermail/ffmpeg-devel/2014-November/164898.html + // Loop over all the samples and build the Track Fragment Entries. + // Each sample gets its own entry which essentially captures the duration of the sample + // and the location within the MDAT relative by the size of the sample. + for _, pkt := range self.pkts { + + // Calculate the duration of the frame, if no previous frames were recorded then + // invent a timestamp (1ms) for it to make sure it's not 0. + var duration time.Duration + if self.lastpkt != nil { + duration = pkt.Time - self.lastpkt.Time + } + + // Increment the decode timestamp for the next Track Fragment Decode Time (TFDT) + // Essentially it is the combination of the durations of the samples. + newDts += self.timeToTs(duration) + + /* + if duration == 0 { + duration = 40 * time.Millisecond + }*/ + + // Audio tends to build very predictable packets due to its sampling rate + // A possible optimization would be to rely on the default flags instead. + // This requires looping over all packets and verifying the default size & duration.const + // Saves a few bytes for each trun entry and could be looked into.const + // Current behavior is to explicitly write all of the entries their size & duration. + entry := mp4io.TrackFragRunEntry{ + Duration: uint32(self.timeToTs(duration)), // The timescaled duration e.g 2999 + Size: uint32(len(pkt.Data)), // The length of the sample in bytes e.g 51677 + Flags: uint32(33554432), + Cts: uint32(self.timeToTs(pkt.CompositionTime)), // Composition timestamp is typically for B-frames, which are not used in RTSP + } + trackfragentries = append(trackfragentries, entry) + self.lastpkt = pkt + } + + // Build the Track Fragment + DefaultSampleFlags := uint32(0) + if self.CodecData.Name == "H264" { + DefaultSampleFlags = 16842752 + } else { + // audio + DefaultSampleFlags = 33554432 + } + + // If no fragment entries are available, then just set the durations to 512 + // TODO: demuxer bug for B-frames has the same dts + DefaultDuration := uint32(512) + + // Set the track frag flags such that they include the flag for CTS + trackFragRunFlags := uint32(mp4io.TRUN_DATA_OFFSET | mp4io.TRUN_FIRST_SAMPLE_FLAGS | mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_DURATION) + if self.hasBFrames { // TODO: add check if video track + //trackFragRunFlags = trackFragRunFlags | mp4io.TRUN_SAMPLE_CTS + trackFragRunFlags = uint32(mp4io.TRUN_DATA_OFFSET | mp4io.TRUN_FIRST_SAMPLE_FLAGS | mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_CTS) + // TODO: in ffmpeg this is 33554432 for video track & none if audio + } + + FirstSampleFlags := uint32(mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_DURATION) // mp4io.TRUN_DATA_OFFSET | mp4io.TRUN_FIRST_SAMPLE_FLAGS | + // The first packet is a b-frame so set the first sample flags to have a CTS. + if len(self.pkts) > 0 && self.pkts[0].CompositionTime > 0 { + FirstSampleFlags = uint32(mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_CTS) + } + + trackFragment = &mp4io.TrackFrag{ + Header: &mp4io.TrackFragHeader{ + Version: uint8(0), + Flags: uint32(mp4io.TFHD_DEFAULT_FLAGS | mp4io.TFHD_DEFAULT_DURATION | mp4io.TFHD_DEFAULT_BASE_IS_MOOF), // uint32(131128), + TrackId: uint32(self.Idx), + DefaultDuration: DefaultDuration, + DefaultFlags: DefaultSampleFlags, // TODO: fix to real flags + }, + DecodeTime: &mp4io.TrackFragDecodeTime{ + Version: uint8(1), // Decides whether 1 = 64bit, 0 = 32bit timestamp + Flags: uint32(0), + DecodeTime: uint64(self.dts), // Decode timestamp timescaled + }, + Run: &mp4io.TrackFragRun{ + Version: uint8(0), + Flags: trackFragRunFlags, // The flags if 0 then no DataOffset & no FirstSampleFlags + DataOffset: uint32(368), // NOTE: this is rewritten later + FirstSampleFlags: FirstSampleFlags, + Entries: trackfragentries, + }, + } + + // Set the next dts + newDts += self.timeToTs(1 * time.Millisecond) + self.dts = newDts + + // Reset hasBFrames + self.hasBFrames = false + + return +} + +func (self *Muxer) flushMoof() (err error) { + + // Build the Track Frags + trackFragments := make([]*mp4io.TrackFrag, 0) + for _, stream := range self.streams { + // Build the Track Frag for this stream + var trackFragment *mp4io.TrackFrag + trackFragment, err = stream.BuildTrackFragmentWithoutOffset() + if err != nil { + return + } + trackFragments = append(trackFragments, trackFragment) + } + + // Defer the clearing of the packets, we'll need them later in this function to + // write the MDAT contents & calculate its size. + defer func() { + for _, stream := range self.streams { + stream.pkts = make([]*packets.Packet, 0) + } + }() + + moof := &mp4io.MovieFrag{ + Header: &mp4io.MovieFragHeader{ + Version: uint8(0), + Flags: uint32(0), + Seqnum: self.moof_seqnum, + }, + Tracks: trackFragments, + } + + // Fix the dataoffsets of the track run + nextDataOffset := uint32(moof.Len() + 8) + for _, track := range moof.Tracks { + track.Run.DataOffset = nextDataOffset + for _, entry := range track.Run.Entries { + nextDataOffset += entry.Size + } + } + + // Write the MOOF + b := make([]byte, moof.Len()) + moof.Marshal(b) + if _, err = self.w.Write(b); err != nil { + return + } + b = nil + + // Write the MDAT size + mdatsize := uint32(8) // skip itself + for _, fragment := range trackFragments { + for _, entry := range fragment.Run.Entries { + mdatsize += entry.Size + } + } + + taghdr := make([]byte, 4) + pio.PutU32BE(taghdr, mdatsize) + if _, err = self.w.Write(taghdr); err != nil { + return + } + taghdr = nil + + // Write the MDAT header + taghdr = make([]byte, 4) + pio.PutU32BE(taghdr, uint32(mp4io.MDAT)) + if _, err = self.w.Write(taghdr); err != nil { + return + } + taghdr = nil + + // Write the MDAT contents + for _, stream := range self.streams { + for _, pkt := range stream.pkts { + if _, err = self.w.Write(pkt.Data); err != nil { + return + } + } + } + + // Increment the SeqNum + self.moof_seqnum++ + + return +} + +func (self *Muxer) Write(buffer []byte, channel int, time uint32) (err error) { + return nil +} + +func (self *Muxer) WritePacket(pkt packets.Packet) (err error) { + // Check if pkt.Idx is a valid stream + if len(self.streams) < int(pkt.Idx+1) { + return + } + stream := self.streams[pkt.Idx] + + // Wait until we have a video packet & it's a keyframe + if pkt.IsKeyFrame && !self.gotFirstKeyframe && stream.CodecData.IsVideo { + // First keyframe found, we can start processing + self.gotFirstKeyframe = true + } else if !self.gotFirstKeyframe { + // Skip all packets until keyframe first + return + } else if pkt.IsKeyFrame { + // At this point, we have a keyframe and had one before. + self.flushMoof() + } + + if err = stream.writePacket(pkt); err != nil { + return + } + + return +} + +func (self *Stream) writePacket(pkt packets.Packet /*, rawdur time.Duration*/) (err error) { + self.pkts = append(self.pkts, &pkt) + // Optimization: set the has B Frames boolean to indicate that there are B-Frames + // that require the TrackFragRun will require the CTS flags. + self.hasBFrames = self.hasBFrames || pkt.CompositionTime > 0 + return +} + +func (self *Muxer) WriteTrailer() (err error) { + self.bufw = nil + self.streams = nil + return +} + +func (self *Muxer) WriteTrailerWithPacket(pkt packets.Packet) (err error) { + // Check if pkt.Idx is a valid stream + if len(self.streams) < int(pkt.Idx+1) { + return + } + stream := self.streams[pkt.Idx] + + // Wait until we have a video packet & it's a keyframe + if pkt.IsKeyFrame && !self.gotFirstKeyframe && stream.CodecData.IsVideo { + // First keyframe found, we can start processing + self.gotFirstKeyframe = true + } else if !self.gotFirstKeyframe { + // Skip all packets until keyframe first + return + } else if pkt.IsKeyFrame { + // At this point, we have a keyframe and had one before. + self.flushMoof() + } + + if err = stream.writePacket(pkt); err != nil { + return + } + + self.bufw = nil + self.streams = nil + + return +} + +func (self *Muxer) Close() (err error) { + for _, stream := range self.streams { + stream.muxer = nil + stream.trackAtom = nil + stream.sample = nil + stream.lastpkt = nil + stream = nil + } + self.streams = nil + return +} + +type AVCDecoderConfRecord struct { + AVCProfileIndication uint8 + ProfileCompatibility uint8 + AVCLevelIndication uint8 + LengthSizeMinusOne uint8 + SPS [][]byte + PPS [][]byte +} + +func (self AVCDecoderConfRecord) Len() (n int) { + n = 7 + for _, sps := range self.SPS { + n += 2 + len(sps) + } + for _, pps := range self.PPS { + n += 2 + len(pps) + } + return +} + +func (self AVCDecoderConfRecord) Marshal(b []byte) (n int) { + b[0] = 1 + b[1] = self.AVCProfileIndication + b[2] = self.ProfileCompatibility + b[3] = self.AVCLevelIndication + b[4] = self.LengthSizeMinusOne | 0xfc + b[5] = uint8(len(self.SPS)) | 0xe0 + n += 6 + + for _, sps := range self.SPS { + pio.PutU16BE(b[n:], uint16(len(sps))) + n += 2 + copy(b[n:], sps) + n += len(sps) + } + + b[n] = uint8(len(self.PPS)) + n++ + + for _, pps := range self.PPS { + pio.PutU16BE(b[n:], uint16(len(pps))) + n += 2 + copy(b[n:], pps) + n += len(pps) + } + + return +} diff --git a/machinery/src/fmp4/stream.go b/machinery/src/fmp4/stream.go new file mode 100644 index 00000000..52f57b87 --- /dev/null +++ b/machinery/src/fmp4/stream.go @@ -0,0 +1,45 @@ +package fmp4 + +import ( + "time" + + "github.com/kerberos-io/agent/machinery/src/fmp4/mp4io" + "github.com/kerberos-io/agent/machinery/src/packets" +) + +type Stream struct { + CodecData packets.Stream + + trackAtom *mp4io.Track + Idx int + + // pkts to be used in MDAT and MOOF > TRAF > TRUN + lastpkt *packets.Packet + pkts []*packets.Packet + hasBFrames bool + + timeScale int64 + duration int64 + + muxer *Muxer + //demuxer *Demuxer + + sample *mp4io.SampleTable + dts int64 +} + +func timeToTs(tm time.Duration, timeScale int64) int64 { + return int64(tm * time.Duration(timeScale) / time.Second) +} + +func tsToTime(ts int64, timeScale int64) time.Duration { + return time.Duration(ts) * time.Second / time.Duration(timeScale) +} + +func (self *Stream) timeToTs(tm time.Duration) int64 { + return int64(tm * time.Duration(self.timeScale) / time.Second) +} + +func (self *Stream) tsToTime(ts int64) time.Duration { + return time.Duration(ts) * time.Second / time.Duration(self.timeScale) +} diff --git a/machinery/src/mp4/muxer.go b/machinery/src/mp4/muxer.go index 0474141a..ce007b2a 100644 --- a/machinery/src/mp4/muxer.go +++ b/machinery/src/mp4/muxer.go @@ -91,7 +91,6 @@ func (self *Muxer) newStream(codec packets.Stream, index int, withoutAudio bool) return } - func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Media.Header.TimeScale = int32(self.timeScale) self.trackAtom.Media.Header.Duration = int32(self.duration) @@ -100,6 +99,24 @@ func (self *Stream) fillTrackAtom() (err error) { codec := self.CodecData width, height := codec.Width, codec.Height + decoderData := []byte{} + + recordinfo := AVCDecoderConfRecord{} + if len(self.CodecData.SPS) > 0 { + recordinfo.AVCProfileIndication = self.CodecData.SPS[1] + recordinfo.ProfileCompatibility = self.CodecData.SPS[2] + recordinfo.AVCLevelIndication = self.CodecData.SPS[3] + recordinfo.SPS = [][]byte{self.CodecData.SPS} + + } + if len(self.CodecData.PPS) > 0 { + recordinfo.PPS = [][]byte{self.CodecData.PPS} + } + recordinfo.LengthSizeMinusOne = 3 // check... + + buf := make([]byte, recordinfo.Len()) + recordinfo.Marshal(buf) + self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{ DataRefIdx: 1, HorizontalResolution: 72, @@ -109,7 +126,7 @@ func (self *Stream) fillTrackAtom() (err error) { FrameCount: 1, Depth: 24, ColorTableId: -1, - //Conf: &mp4io.AVC1Conf{Data: codec.AVCDecoderConfRecordBytes()}, + Conf: &mp4io.AVC1Conf{Data: decoderData}, } self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ SubType: [4]byte{'v', 'i', 'd', 'e'}, @@ -379,3 +396,52 @@ func (self *Muxer) Close() (err error) { self.streams = nil return } + +type AVCDecoderConfRecord struct { + AVCProfileIndication uint8 + ProfileCompatibility uint8 + AVCLevelIndication uint8 + LengthSizeMinusOne uint8 + SPS [][]byte + PPS [][]byte +} + +func (self AVCDecoderConfRecord) Len() (n int) { + n = 7 + for _, sps := range self.SPS { + n += 2 + len(sps) + } + for _, pps := range self.PPS { + n += 2 + len(pps) + } + return +} + +func (self AVCDecoderConfRecord) Marshal(b []byte) (n int) { + b[0] = 1 + b[1] = self.AVCProfileIndication + b[2] = self.ProfileCompatibility + b[3] = self.AVCLevelIndication + b[4] = self.LengthSizeMinusOne | 0xfc + b[5] = uint8(len(self.SPS)) | 0xe0 + n += 6 + + for _, sps := range self.SPS { + pio.PutU16BE(b[n:], uint16(len(sps))) + n += 2 + copy(b[n:], sps) + n += len(sps) + } + + b[n] = uint8(len(self.PPS)) + n++ + + for _, pps := range self.PPS { + pio.PutU16BE(b[n:], uint16(len(pps))) + n += 2 + copy(b[n:], pps) + n += len(pps) + } + + return +} From bce5d443d55966458cc10df7663b39f19136ffe0 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 29 Nov 2023 17:18:51 +0100 Subject: [PATCH 08/81] try new muxer --- machinery/go.mod | 1 + machinery/go.sum | 2 + machinery/src/capture/Gortsplib.go | 37 ++++++--- machinery/src/capture/main.go | 126 +++++++++++++++++++++++------ 4 files changed, 130 insertions(+), 36 deletions(-) diff --git a/machinery/go.mod b/machinery/go.mod index 1fd45f45..3c6fc3e0 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -37,6 +37,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/yapingcat/gomedia v0.0.0-20231111085550-145f641a02d1 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 diff --git a/machinery/go.sum b/machinery/go.sum index 81f19b47..3b38c2c0 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -485,6 +485,8 @@ github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/yapingcat/gomedia v0.0.0-20231111085550-145f641a02d1 h1:o+Dra6HPMfqbIAFinocMGt3fr5R0OGcOIWy7omeujiI= +github.com/yapingcat/gomedia v0.0.0-20231111085550-145f641a02d1/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 3255f06a..49686672 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -190,11 +190,12 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati } pkt := packets.Packet{ - IsKeyFrame: false, - Packet: rtppkt, - Data: op, - Time: pts, - Idx: g.AudioG711Index, + IsKeyFrame: false, + Packet: rtppkt, + Data: op, + Time: pts, + CompositionTime: pts, + Idx: g.AudioG711Index, } queue.WritePacket(pkt) }) @@ -202,7 +203,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // called when a video RTP packet arrives if g.VideoH264Media != nil { - + var dtsExtractor *h264.DTSExtractor g.Client.OnPacketRTP(g.VideoH264Media, g.VideoH264Forma, func(rtppkt *rtp.Packet) { // This will check if we need to stop the thread, @@ -260,6 +261,14 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati return } + if dtsExtractor == nil { + // skip samples silently until we find one with a IDR + if !idrPresent { + return + } + dtsExtractor = h264.NewDTSExtractor() + } + // Conver to packet. enc, err := h264.AnnexBMarshal(filteredAU) if err != nil { @@ -267,12 +276,18 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati return } + dts, err := dtsExtractor.Extract(au, pts) + if err != nil { + return + } + pkt := packets.Packet{ - IsKeyFrame: idrPresent, - Packet: rtppkt, - Data: enc, - Time: pts, - Idx: g.VideoH264Index, + IsKeyFrame: idrPresent, + Packet: rtppkt, + Data: enc, + Time: pts, + CompositionTime: dts, + Idx: g.VideoH264Index, } queue.WritePacket(pkt) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 7c18802c..6654f383 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -3,6 +3,9 @@ package capture import ( "context" + "errors" + "fmt" + "io" "os" "strconv" "time" @@ -11,9 +14,9 @@ import ( "github.com/kerberos-io/agent/machinery/src/encryption" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/agent/machinery/src/mp4" "github.com/kerberos-io/agent/machinery/src/packets" "github.com/kerberos-io/agent/machinery/src/utils" + "github.com/yapingcat/gomedia/go-mp4" ) func CleanupRecordingDirectory(configDirectory string, configuration *models.Configuration) { @@ -55,7 +58,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat config := configuration.Config // Get the streams from the rtsp client. - streams, _ := rtspClient.GetStreams() + //streams, _ := rtspClient.GetStreams() if config.Capture.Recording == "false" { log.Log.Info("HandleRecordStream: disabled, we will not record anything.") @@ -73,6 +76,10 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Check if continuous recording. if config.Capture.Continuous == "true" { + var cws *cacheWriterSeeker + var myMuxer *mp4.Movmuxer + var videoTrack uint32 + // Do not do anything! log.Log.Info("HandleRecordStream: Start continuous recording ") @@ -81,7 +88,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat timestamp = now start := false var name string - var myMuxer *mp4.Muxer var file *os.File var err error @@ -112,12 +118,12 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat nextPkt.IsKeyFrame && (timestamp+recordingPeriod-now <= 0 || now-startRecording >= maxRecordingPeriod) { // Write the last packet - if err := myMuxer.WritePacket(pkt); err != nil { + if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { log.Log.Error(err.Error()) } // This will write the trailer a well. - if err := myMuxer.WriteTrailerWithPacket(nextPkt); err != nil { + if err := myMuxer.WriteTrailer(); err != nil { log.Log.Error(err.Error()) } @@ -125,8 +131,15 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Cleanup muxer start = false - myMuxer.Close() - myMuxer = nil + //myMuxer.Close() + //myMuxer = nil + _, err = file.Write(cws.buf) + if err != nil { + panic(err) + } + + fmt.Println(cws.offset, len(cws.buf)) + file.Close() file = nil @@ -201,25 +214,28 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat file, err = os.Create(fullName) if err == nil { - myMuxer = mp4.NewMuxer(file) + //myMuxer = mp4.NewMuxer(file) + cws = newCacheWriterSeeker(4096) + myMuxer, _ = mp4.CreateMp4Muxer(cws) + videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H264) } log.Log.Info("HandleRecordStream: composing recording") log.Log.Info("HandleRecordStream: write header") // Creating the file, might block sometimes. - if err := myMuxer.WriteHeader(streams); err != nil { + /*if err := myMuxer.WriteHeader(streams); err != nil { log.Log.Error(err.Error()) - } + }*/ - if err := myMuxer.WritePacket(pkt); err != nil { + if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { log.Log.Error(err.Error()) } recordingStatus = "started" } else if start { - if err := myMuxer.WritePacket(pkt); err != nil { + if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { log.Log.Error(err.Error()) } @@ -250,8 +266,8 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Info("HandleRecordStream: Recording finished: file save: " + name) // Cleanup muxer start = false - myMuxer.Close() - myMuxer = nil + //myMuxer.Close() + //myMuxer = nil file.Close() file = nil @@ -271,13 +287,14 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Info("HandleRecordStream: Start motion based recording ") - var myMuxer *mp4.Muxer var file *os.File - var err error - var lastDuration time.Duration var lastRecordingTime int64 + var cws *cacheWriterSeeker + var myMuxer *mp4.Movmuxer + var videoTrack uint32 + for motion := range communication.HandleMotion { timestamp = time.Now().Unix() @@ -321,10 +338,11 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Running... log.Log.Info("HandleRecordStream: Recording started") - file, err = os.Create(fullName) - if err == nil { - myMuxer = mp4.NewMuxer(file) - } + file, _ = os.Create(fullName) + + cws = newCacheWriterSeeker(4096) + myMuxer, _ = mp4.CreateMp4Muxer(cws) + videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H264) start := false @@ -373,7 +391,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } if start { - if err := myMuxer.WritePacket(pkt); err != nil { + if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { log.Log.Error(err.Error()) } @@ -392,15 +410,23 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } // This will write the trailer as well. - myMuxer.WriteTrailerWithPacket(nextPkt) + if err := myMuxer.Write(videoTrack, nextPkt.Data, uint64(nextPkt.Time), uint64(nextPkt.CompositionTime)); err != nil { + log.Log.Error(err.Error()) + } + myMuxer.WriteTrailer() + log.Log.Info("HandleRecordStream: file save: " + name) lastDuration = pkt.Time lastRecordingTime = time.Now().Unix() // Cleanup muxer - myMuxer.Close() - myMuxer = nil + //myMuxer.Close() + //myMuxer = nil + _, err := file.Write(cws.buf) + if err != nil { + panic(err) + } file.Close() file = nil @@ -519,3 +545,53 @@ func VerifyCamera(c *gin.Context) { }) } } + +type cacheWriterSeeker struct { + buf []byte + offset int +} + +func newCacheWriterSeeker(capacity int) *cacheWriterSeeker { + return &cacheWriterSeeker{ + buf: make([]byte, 0, capacity), + offset: 0, + } +} + +func (ws *cacheWriterSeeker) Write(p []byte) (n int, err error) { + if cap(ws.buf)-ws.offset >= len(p) { + if len(ws.buf) < ws.offset+len(p) { + ws.buf = ws.buf[:ws.offset+len(p)] + } + copy(ws.buf[ws.offset:], p) + ws.offset += len(p) + return len(p), nil + } + tmp := make([]byte, len(ws.buf), cap(ws.buf)+len(p)*2) + copy(tmp, ws.buf) + if len(ws.buf) < ws.offset+len(p) { + tmp = tmp[:ws.offset+len(p)] + } + copy(tmp[ws.offset:], p) + ws.buf = tmp + ws.offset += len(p) + return len(p), nil +} + +func (ws *cacheWriterSeeker) Seek(offset int64, whence int) (int64, error) { + if whence == io.SeekCurrent { + if ws.offset+int(offset) > len(ws.buf) { + return -1, errors.New(fmt.Sprint("SeekCurrent out of range", len(ws.buf), offset, ws.offset)) + } + ws.offset += int(offset) + return int64(ws.offset), nil + } else if whence == io.SeekStart { + if offset > int64(len(ws.buf)) { + return -1, errors.New(fmt.Sprint("SeekStart out of range", len(ws.buf), offset, ws.offset)) + } + ws.offset = int(offset) + return offset, nil + } else { + return 0, errors.New("unsupport SeekEnd") + } +} From 0a8f097c767febea1bb8be798dd7e9f61ab5731b Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 29 Nov 2023 19:33:03 +0100 Subject: [PATCH 09/81] cleanup and fix for recording (wrong DTS value) + fix for recording using "old" joy library --- machinery/src/capture/Gortsplib.go | 8 +- machinery/src/capture/Joy4.go | 53 +- machinery/src/capture/main.go | 16 +- machinery/src/components/Kerberos.go | 2 +- machinery/src/fmp4/mp4io/atoms.go | 3561 --------------------- machinery/src/fmp4/mp4io/gen/gen.go | 1057 ------ machinery/src/fmp4/mp4io/gen/pattern.go | 438 --- machinery/src/fmp4/mp4io/mp4io.go | 535 ---- machinery/src/fmp4/muxer.go | 568 ---- machinery/src/fmp4/stream.go | 45 - machinery/src/mp4/mp4io/atoms.go | 3561 --------------------- machinery/src/mp4/mp4io/gen/gen.go | 1057 ------ machinery/src/mp4/mp4io/gen/pattern.go | 438 --- machinery/src/mp4/mp4io/mp4io.go | 535 ---- machinery/src/mp4/muxer.go | 447 --- machinery/src/mp4/stream.go | 58 - machinery/src/utils/bits/bits.go | 118 - machinery/src/utils/bits/bits_test.go | 51 - machinery/src/utils/bits/bufio/bufio.go | 23 - machinery/src/utils/bits/golomb_reader.go | 89 - machinery/src/utils/bits/pio/pio.go | 3 - machinery/src/utils/bits/pio/reader.go | 91 - machinery/src/utils/bits/pio/vec.go | 69 - machinery/src/utils/bits/pio/vec_test.go | 22 - machinery/src/utils/bits/pio/writer.go | 89 - 25 files changed, 49 insertions(+), 12885 deletions(-) delete mode 100644 machinery/src/fmp4/mp4io/atoms.go delete mode 100644 machinery/src/fmp4/mp4io/gen/gen.go delete mode 100644 machinery/src/fmp4/mp4io/gen/pattern.go delete mode 100644 machinery/src/fmp4/mp4io/mp4io.go delete mode 100644 machinery/src/fmp4/muxer.go delete mode 100644 machinery/src/fmp4/stream.go delete mode 100644 machinery/src/mp4/mp4io/atoms.go delete mode 100644 machinery/src/mp4/mp4io/gen/gen.go delete mode 100644 machinery/src/mp4/mp4io/gen/pattern.go delete mode 100644 machinery/src/mp4/mp4io/mp4io.go delete mode 100644 machinery/src/mp4/muxer.go delete mode 100644 machinery/src/mp4/stream.go delete mode 100644 machinery/src/utils/bits/bits.go delete mode 100644 machinery/src/utils/bits/bits_test.go delete mode 100644 machinery/src/utils/bits/bufio/bufio.go delete mode 100644 machinery/src/utils/bits/golomb_reader.go delete mode 100644 machinery/src/utils/bits/pio/pio.go delete mode 100644 machinery/src/utils/bits/pio/reader.go delete mode 100644 machinery/src/utils/bits/pio/vec.go delete mode 100644 machinery/src/utils/bits/pio/vec_test.go delete mode 100644 machinery/src/utils/bits/pio/writer.go diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 49686672..5c6c5a7e 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -8,6 +8,7 @@ import "C" import ( "context" + "errors" "fmt" "image" "reflect" @@ -329,17 +330,14 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { } if img.Bounds().Empty() { log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") + return image.YCbCr{}, errors.New("Empty frame") } return img, nil } // Get a list of streams from the RTSP server. func (j *Golibrtsp) GetStreams() ([]packets.Stream, error) { - var streams []packets.Stream - for _, stream := range j.Streams { - streams = append(streams, stream) - } - return streams, nil + return j.Streams, nil } // Get a list of video streams from the RTSP server. diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go index 45573e28..9420d128 100644 --- a/machinery/src/capture/Joy4.go +++ b/machinery/src/capture/Joy4.go @@ -133,6 +133,7 @@ func (j *Joy4) Connect(ctx context.Context) (err error) { // Start the RTSP client, and start reading packets. func (j *Joy4) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(JOY4).Start(): started") + start := false loop: for { // This will check if we need to stop the thread, @@ -149,21 +150,43 @@ loop: time.Sleep(1 * time.Second) } - log.Log.Info("RTSPClient(JOY4).Start(): " + "read packet from stream: " + strconv.Itoa(int(avpkt.Idx)) + " " + strconv.Itoa(len(avpkt.Data)) + " bytes") - // Could be that a decode is throwing errors. if len(avpkt.Data) > 0 { - // Conver to packet. - pkt := packets.Packet{ - IsKeyFrame: avpkt.IsKeyFrame, - Idx: int8(avpkt.Idx), - CompositionTime: avpkt.CompositionTime, - Time: avpkt.Time, - Data: avpkt.Data, + avpkt.Data = avpkt.Data[4:] + if avpkt.IsKeyFrame { + start = true + // Add SPS and PPS to the packet. + stream := j.Streams[avpkt.Idx] + annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } + avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) + avpkt.Data = append(stream.PPS, avpkt.Data...) + avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) + avpkt.Data = append(stream.SPS, avpkt.Data...) + avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) } - queue.WritePacket(pkt) + if start { + // Conver to packet. + pkt := packets.Packet{ + IsKeyFrame: avpkt.IsKeyFrame, + Idx: int8(avpkt.Idx), + CompositionTime: avpkt.CompositionTime, + Time: avpkt.Time, + Data: avpkt.Data, + } + + queue.WritePacket(pkt) + + if pkt.IsKeyFrame { + // Increment packets, so we know the device + // is not blocking. + r := communication.PackageCounter.Load().(int64) + log.Log.Info("RTSPClient(JOY4).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + communication.PackageCounter.Store((r + 1) % 1000) + communication.LastPacketTimer.Store(time.Now().Unix()) + } + } // This will check if we need to stop the thread, // because of a reconfiguration. @@ -172,16 +195,6 @@ loop: break loop default: } - - if pkt.IsKeyFrame { - - // Increment packets, so we know the device - // is not blocking. - r := communication.PackageCounter.Load().(int64) - log.Log.Info("RTSPClient(JOY4).Start(): packet size " + strconv.Itoa(len(pkt.Data))) - communication.PackageCounter.Store((r + 1) % 1000) - communication.LastPacketTimer.Store(time.Now().Unix()) - } } } diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 6654f383..77561ee7 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -118,7 +118,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat nextPkt.IsKeyFrame && (timestamp+recordingPeriod-now <= 0 || now-startRecording >= maxRecordingPeriod) { // Write the last packet - if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { + if err := myMuxer.Write(videoTrack, pkt.Data, durationGoToMPEGTS(pkt.Time), durationGoToMPEGTS(pkt.CompositionTime)); err != nil { log.Log.Error(err.Error()) } @@ -228,15 +228,19 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Error(err.Error()) }*/ - if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { + time := durationGoToMPEGTS(pkt.Time) + if err := myMuxer.Write(videoTrack, pkt.Data, time, time); err != nil { log.Log.Error(err.Error()) } recordingStatus = "started" } else if start { - if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { - log.Log.Error(err.Error()) + if pkt.Idx == 0 { + time := durationGoToMPEGTS(pkt.Time) + if err := myMuxer.Write(videoTrack, pkt.Data, time, time); err != nil { + log.Log.Error(err.Error()) + } } // We will sync to file every keyframe. @@ -595,3 +599,7 @@ func (ws *cacheWriterSeeker) Seek(offset int64, whence int) (int64, error) { return 0, errors.New("unsupport SeekEnd") } } + +func durationGoToMPEGTS(v time.Duration) uint64 { + return uint64(v.Milliseconds()) +} diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 707babf0..6f85aca2 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -115,7 +115,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Establishing the camera connection without backchannel if no substream rtspUrl := config.Capture.IPCamera.RTSP withBackChannel := true - rtspClient := &capture.Golibrtsp{ + rtspClient := &capture.Joy4{ Url: rtspUrl, WithBackChannel: withBackChannel, } diff --git a/machinery/src/fmp4/mp4io/atoms.go b/machinery/src/fmp4/mp4io/atoms.go deleted file mode 100644 index e192f17b..00000000 --- a/machinery/src/fmp4/mp4io/atoms.go +++ /dev/null @@ -1,3561 +0,0 @@ -package mp4io - -import ( - "time" - - "github.com/kerberos-io/joy4/utils/bits/pio" -) - -const MOOF = Tag(0x6d6f6f66) - -func (self MovieFrag) Tag() Tag { - return MOOF -} - -const HDLR = Tag(0x68646c72) - -func (self HandlerRefer) Tag() Tag { - return HDLR -} - -const AVC1 = Tag(0x61766331) - -func (self AVC1Desc) Tag() Tag { - return AVC1 -} - -const URL = Tag(0x75726c20) - -func (self DataReferUrl) Tag() Tag { - return URL -} - -const TREX = Tag(0x74726578) - -func (self TrackExtend) Tag() Tag { - return TREX -} - -const ESDS = Tag(0x65736473) - -func (self ElemStreamDesc) Tag() Tag { - return ESDS -} - -const MDHD = Tag(0x6d646864) - -func (self MediaHeader) Tag() Tag { - return MDHD -} - -const STTS = Tag(0x73747473) - -func (self TimeToSample) Tag() Tag { - return STTS -} - -const STSS = Tag(0x73747373) - -func (self SyncSample) Tag() Tag { - return STSS -} - -const MFHD = Tag(0x6d666864) - -func (self MovieFragHeader) Tag() Tag { - return MFHD -} - -const MVHD = Tag(0x6d766864) - -func (self MovieHeader) Tag() Tag { - return MVHD -} - -const MINF = Tag(0x6d696e66) - -func (self MediaInfo) Tag() Tag { - return MINF -} - -const MOOV = Tag(0x6d6f6f76) - -func (self Movie) Tag() Tag { - return MOOV -} - -const MVEX = Tag(0x6d766578) - -func (self MovieExtend) Tag() Tag { - return MVEX -} - -const STSD = Tag(0x73747364) - -func (self SampleDesc) Tag() Tag { - return STSD -} - -const MP4A = Tag(0x6d703461) - -func (self MP4ADesc) Tag() Tag { - return MP4A -} - -const CTTS = Tag(0x63747473) - -func (self CompositionOffset) Tag() Tag { - return CTTS -} - -const STCO = Tag(0x7374636f) - -func (self ChunkOffset) Tag() Tag { - return STCO -} - -const TRUN = Tag(0x7472756e) - -func (self TrackFragRun) Tag() Tag { - return TRUN -} - -const TRAK = Tag(0x7472616b) - -func (self Track) Tag() Tag { - return TRAK -} - -const MDIA = Tag(0x6d646961) - -func (self Media) Tag() Tag { - return MDIA -} - -const STSC = Tag(0x73747363) - -func (self SampleToChunk) Tag() Tag { - return STSC -} - -const VMHD = Tag(0x766d6864) - -func (self VideoMediaInfo) Tag() Tag { - return VMHD -} - -const STBL = Tag(0x7374626c) - -func (self SampleTable) Tag() Tag { - return STBL -} - -const AVCC = Tag(0x61766343) - -func (self AVC1Conf) Tag() Tag { - return AVCC -} - -const TFDT = Tag(0x74666474) - -func (self TrackFragDecodeTime) Tag() Tag { - return TFDT -} - -const DINF = Tag(0x64696e66) - -func (self DataInfo) Tag() Tag { - return DINF -} - -const DREF = Tag(0x64726566) - -func (self DataRefer) Tag() Tag { - return DREF -} - -const TRAF = Tag(0x74726166) - -func (self TrackFrag) Tag() Tag { - return TRAF -} - -const STSZ = Tag(0x7374737a) - -func (self SampleSize) Tag() Tag { - return STSZ -} - -const TFHD = Tag(0x74666864) - -func (self TrackFragHeader) Tag() Tag { - return TFHD -} - -const TKHD = Tag(0x746b6864) - -func (self TrackHeader) Tag() Tag { - return TKHD -} - -const SMHD = Tag(0x736d6864) - -func (self SoundMediaInfo) Tag() Tag { - return SMHD -} - -const MDAT = Tag(0x6d646174) - -type Movie struct { - Header *MovieHeader - MovieExtend *MovieExtend - Tracks []*Track - Unknowns []Atom - AtomPos -} - -func (self Movie) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MOOV)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self Movie) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.MovieExtend != nil { - n += self.MovieExtend.Marshal(b[n:]) - } - for _, atom := range self.Tracks { - n += atom.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self Movie) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.MovieExtend != nil { - n += self.MovieExtend.Len() - } - for _, atom := range self.Tracks { - n += atom.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *Movie) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case MVHD: - { - atom := &MovieHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mvhd", n+offset, err) - return - } - self.Header = atom - } - case MVEX: - { - atom := &MovieExtend{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mvex", n+offset, err) - return - } - self.MovieExtend = atom - } - case TRAK: - { - atom := &Track{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("trak", n+offset, err) - return - } - self.Tracks = append(self.Tracks, atom) - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self Movie) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.MovieExtend != nil { - r = append(r, self.MovieExtend) - } - for _, atom := range self.Tracks { - r = append(r, atom) - } - r = append(r, self.Unknowns...) - return -} - -type MovieHeader struct { - Version uint8 - Flags uint32 - CreateTime time.Time - ModifyTime time.Time - TimeScale int32 - Duration int32 - PreferredRate float64 - PreferredVolume float64 - Matrix [9]int32 - PreviewTime time.Time - PreviewDuration time.Time - PosterTime time.Time - SelectionTime time.Time - SelectionDuration time.Time - CurrentTime time.Time - NextTrackId int32 - AtomPos -} - -func (self MovieHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MVHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - PutTime32(b[n:], self.CreateTime) - n += 4 - PutTime32(b[n:], self.ModifyTime) - n += 4 - pio.PutI32BE(b[n:], self.TimeScale) - n += 4 - pio.PutI32BE(b[n:], self.Duration) - n += 4 - PutFixed32(b[n:], self.PreferredRate) - n += 4 - PutFixed16(b[n:], self.PreferredVolume) - n += 2 - n += 10 - for _, entry := range self.Matrix { - pio.PutI32BE(b[n:], entry) - n += 4 - } - PutTime32(b[n:], self.PreviewTime) - n += 4 - PutTime32(b[n:], self.PreviewDuration) - n += 4 - PutTime32(b[n:], self.PosterTime) - n += 4 - PutTime32(b[n:], self.SelectionTime) - n += 4 - PutTime32(b[n:], self.SelectionDuration) - n += 4 - PutTime32(b[n:], self.CurrentTime) - n += 4 - pio.PutI32BE(b[n:], self.NextTrackId) - n += 4 - return -} -func (self MovieHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 2 - n += 10 - n += 4 * len(self.Matrix[:]) - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - return -} -func (self *MovieHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("CreateTime", n+offset, err) - return - } - self.CreateTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("ModifyTime", n+offset, err) - return - } - self.ModifyTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TimeScale", n+offset, err) - return - } - self.TimeScale = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("Duration", n+offset, err) - return - } - self.Duration = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("PreferredRate", n+offset, err) - return - } - self.PreferredRate = GetFixed32(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("PreferredVolume", n+offset, err) - return - } - self.PreferredVolume = GetFixed16(b[n:]) - n += 2 - n += 10 - if len(b) < n+4*len(self.Matrix) { - err = parseErr("Matrix", n+offset, err) - return - } - for i := range self.Matrix { - self.Matrix[i] = pio.I32BE(b[n:]) - n += 4 - } - if len(b) < n+4 { - err = parseErr("PreviewTime", n+offset, err) - return - } - self.PreviewTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("PreviewDuration", n+offset, err) - return - } - self.PreviewDuration = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("PosterTime", n+offset, err) - return - } - self.PosterTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("SelectionTime", n+offset, err) - return - } - self.SelectionTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("SelectionDuration", n+offset, err) - return - } - self.SelectionDuration = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("CurrentTime", n+offset, err) - return - } - self.CurrentTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("NextTrackId", n+offset, err) - return - } - self.NextTrackId = pio.I32BE(b[n:]) - n += 4 - return -} -func (self MovieHeader) Children() (r []Atom) { - return -} - -type Track struct { - Header *TrackHeader - Media *Media - Unknowns []Atom - AtomPos -} - -func (self Track) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TRAK)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self Track) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.Media != nil { - n += self.Media.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self Track) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.Media != nil { - n += self.Media.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *Track) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case TKHD: - { - atom := &TrackHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("tkhd", n+offset, err) - return - } - self.Header = atom - } - case MDIA: - { - atom := &Media{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mdia", n+offset, err) - return - } - self.Media = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self Track) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.Media != nil { - r = append(r, self.Media) - } - r = append(r, self.Unknowns...) - return -} - -type TrackHeader struct { - Version uint8 - Flags uint32 - CreateTime time.Time - ModifyTime time.Time - TrackId int32 - Duration int32 - Layer int16 - AlternateGroup int16 - Volume float64 - Matrix [9]int32 - TrackWidth float64 - TrackHeight float64 - AtomPos -} - -func (self TrackHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TKHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - PutTime32(b[n:], self.CreateTime) - n += 4 - PutTime32(b[n:], self.ModifyTime) - n += 4 - pio.PutI32BE(b[n:], self.TrackId) - n += 4 - n += 4 - pio.PutI32BE(b[n:], self.Duration) - n += 4 - n += 8 - pio.PutI16BE(b[n:], self.Layer) - n += 2 - pio.PutI16BE(b[n:], self.AlternateGroup) - n += 2 - PutFixed16(b[n:], self.Volume) - n += 2 - n += 2 - for _, entry := range self.Matrix { - pio.PutI32BE(b[n:], entry) - n += 4 - } - PutFixed32(b[n:], self.TrackWidth) - n += 4 - PutFixed32(b[n:], self.TrackHeight) - n += 4 - return -} -func (self TrackHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 8 - n += 2 - n += 2 - n += 2 - n += 2 - n += 4 * len(self.Matrix[:]) - n += 4 - n += 4 - return -} -func (self *TrackHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("CreateTime", n+offset, err) - return - } - self.CreateTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("ModifyTime", n+offset, err) - return - } - self.ModifyTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TrackId", n+offset, err) - return - } - self.TrackId = pio.I32BE(b[n:]) - n += 4 - n += 4 - if len(b) < n+4 { - err = parseErr("Duration", n+offset, err) - return - } - self.Duration = pio.I32BE(b[n:]) - n += 4 - n += 8 - if len(b) < n+2 { - err = parseErr("Layer", n+offset, err) - return - } - self.Layer = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("AlternateGroup", n+offset, err) - return - } - self.AlternateGroup = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Volume", n+offset, err) - return - } - self.Volume = GetFixed16(b[n:]) - n += 2 - n += 2 - if len(b) < n+4*len(self.Matrix) { - err = parseErr("Matrix", n+offset, err) - return - } - for i := range self.Matrix { - self.Matrix[i] = pio.I32BE(b[n:]) - n += 4 - } - if len(b) < n+4 { - err = parseErr("TrackWidth", n+offset, err) - return - } - self.TrackWidth = GetFixed32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TrackHeight", n+offset, err) - return - } - self.TrackHeight = GetFixed32(b[n:]) - n += 4 - return -} -func (self TrackHeader) Children() (r []Atom) { - return -} - -type HandlerRefer struct { - Version uint8 - Flags uint32 - Type [4]byte - SubType [4]byte - Name []byte - AtomPos -} - -func (self HandlerRefer) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(HDLR)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self HandlerRefer) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - copy(b[n:], self.Type[:]) - n += len(self.Type[:]) - copy(b[n:], self.SubType[:]) - n += len(self.SubType[:]) - // TODO: document component - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - copy(b[n:], self.Name[:]) - n += len(self.Name[:]) - return -} -func (self HandlerRefer) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += len(self.Type[:]) - n += len(self.SubType[:]) - // TODO: document component - n += 4 - n += 4 - n += 4 - n += len(self.Name[:]) - return -} -func (self *HandlerRefer) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+len(self.Type) { - err = parseErr("Type", n+offset, err) - return - } - copy(self.Type[:], b[n:]) - n += len(self.Type) - if len(b) < n+len(self.SubType) { - err = parseErr("SubType", n+offset, err) - return - } - copy(self.SubType[:], b[n:]) - n += len(self.SubType) - // TODO: document component - n += 4 - n += 4 - n += 4 - self.Name = b[n:] - n += len(b[n:]) - return -} -func (self HandlerRefer) Children() (r []Atom) { - return -} - -type Media struct { - Header *MediaHeader - Handler *HandlerRefer - Info *MediaInfo - Unknowns []Atom - AtomPos -} - -func (self Media) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MDIA)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self Media) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.Handler != nil { - n += self.Handler.Marshal(b[n:]) - } - if self.Info != nil { - n += self.Info.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self Media) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.Handler != nil { - n += self.Handler.Len() - } - if self.Info != nil { - n += self.Info.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *Media) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case MDHD: - { - atom := &MediaHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mdhd", n+offset, err) - return - } - self.Header = atom - } - case HDLR: - { - atom := &HandlerRefer{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("hdlr", n+offset, err) - return - } - self.Handler = atom - } - case MINF: - { - atom := &MediaInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("minf", n+offset, err) - return - } - self.Info = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self Media) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.Handler != nil { - r = append(r, self.Handler) - } - if self.Info != nil { - r = append(r, self.Info) - } - r = append(r, self.Unknowns...) - return -} - -type MediaHeader struct { - Version uint8 - Flags uint32 - CreateTime time.Time - ModifyTime time.Time - TimeScale int32 - Duration int32 - Language int16 - Quality int16 - AtomPos -} - -func (self MediaHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MDHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MediaHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - PutTime32(b[n:], self.CreateTime) - n += 4 - PutTime32(b[n:], self.ModifyTime) - n += 4 - pio.PutI32BE(b[n:], self.TimeScale) - n += 4 - pio.PutI32BE(b[n:], self.Duration) - n += 4 - pio.PutI16BE(b[n:], self.Language) - n += 2 - pio.PutI16BE(b[n:], self.Quality) - n += 2 - return -} -func (self MediaHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 2 - n += 2 - return -} -func (self *MediaHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("CreateTime", n+offset, err) - return - } - self.CreateTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("ModifyTime", n+offset, err) - return - } - self.ModifyTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TimeScale", n+offset, err) - return - } - self.TimeScale = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("Duration", n+offset, err) - return - } - self.Duration = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("Language", n+offset, err) - return - } - self.Language = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Quality", n+offset, err) - return - } - self.Quality = pio.I16BE(b[n:]) - n += 2 - return -} -func (self MediaHeader) Children() (r []Atom) { - return -} - -type MediaInfo struct { - Sound *SoundMediaInfo - Video *VideoMediaInfo - Data *DataInfo - Sample *SampleTable - Unknowns []Atom - AtomPos -} - -func (self MediaInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MINF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MediaInfo) marshal(b []byte) (n int) { - if self.Sound != nil { - n += self.Sound.Marshal(b[n:]) - } - if self.Video != nil { - n += self.Video.Marshal(b[n:]) - } - if self.Data != nil { - n += self.Data.Marshal(b[n:]) - } - if self.Sample != nil { - n += self.Sample.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MediaInfo) Len() (n int) { - n += 8 - if self.Sound != nil { - n += self.Sound.Len() - } - if self.Video != nil { - n += self.Video.Len() - } - if self.Data != nil { - n += self.Data.Len() - } - if self.Sample != nil { - n += self.Sample.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case SMHD: - { - atom := &SoundMediaInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("smhd", n+offset, err) - return - } - self.Sound = atom - } - case VMHD: - { - atom := &VideoMediaInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("vmhd", n+offset, err) - return - } - self.Video = atom - } - case DINF: - { - atom := &DataInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("dinf", n+offset, err) - return - } - self.Data = atom - } - case STBL: - { - atom := &SampleTable{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stbl", n+offset, err) - return - } - self.Sample = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MediaInfo) Children() (r []Atom) { - if self.Sound != nil { - r = append(r, self.Sound) - } - if self.Video != nil { - r = append(r, self.Video) - } - if self.Data != nil { - r = append(r, self.Data) - } - if self.Sample != nil { - r = append(r, self.Sample) - } - r = append(r, self.Unknowns...) - return -} - -type DataInfo struct { - Refer *DataRefer - Unknowns []Atom - AtomPos -} - -func (self DataInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(DINF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self DataInfo) marshal(b []byte) (n int) { - if self.Refer != nil { - n += self.Refer.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self DataInfo) Len() (n int) { - n += 8 - if self.Refer != nil { - n += self.Refer.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *DataInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case DREF: - { - atom := &DataRefer{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("dref", n+offset, err) - return - } - self.Refer = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self DataInfo) Children() (r []Atom) { - if self.Refer != nil { - r = append(r, self.Refer) - } - r = append(r, self.Unknowns...) - return -} - -type DataRefer struct { - Version uint8 - Flags uint32 - Url *DataReferUrl - AtomPos -} - -func (self DataRefer) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(DREF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self DataRefer) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - _childrenNR := 0 - if self.Url != nil { - _childrenNR++ - } - pio.PutI32BE(b[n:], int32(_childrenNR)) - n += 4 - if self.Url != nil { - n += self.Url.Marshal(b[n:]) - } - return -} -func (self DataRefer) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.Url != nil { - n += self.Url.Len() - } - return -} -func (self *DataRefer) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - n += 4 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case URL: - { - atom := &DataReferUrl{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("url ", n+offset, err) - return - } - self.Url = atom - } - } - n += size - } - return -} -func (self DataRefer) Children() (r []Atom) { - if self.Url != nil { - r = append(r, self.Url) - } - return -} - -type DataReferUrl struct { - Version uint8 - Flags uint32 - AtomPos -} - -func (self DataReferUrl) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(URL)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self DataReferUrl) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - return -} -func (self DataReferUrl) Len() (n int) { - n += 8 - n += 1 - n += 3 - return -} -func (self *DataReferUrl) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - return -} -func (self DataReferUrl) Children() (r []Atom) { - return -} - -type SoundMediaInfo struct { - Version uint8 - Flags uint32 - Balance int16 - AtomPos -} - -func (self SoundMediaInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(SMHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SoundMediaInfo) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutI16BE(b[n:], self.Balance) - n += 2 - n += 2 - return -} -func (self SoundMediaInfo) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 2 - n += 2 - return -} -func (self *SoundMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+2 { - err = parseErr("Balance", n+offset, err) - return - } - self.Balance = pio.I16BE(b[n:]) - n += 2 - n += 2 - return -} -func (self SoundMediaInfo) Children() (r []Atom) { - return -} - -type VideoMediaInfo struct { - Version uint8 - Flags uint32 - GraphicsMode int16 - Opcolor [3]int16 - AtomPos -} - -func (self VideoMediaInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(VMHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self VideoMediaInfo) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutI16BE(b[n:], self.GraphicsMode) - n += 2 - for _, entry := range self.Opcolor { - pio.PutI16BE(b[n:], entry) - n += 2 - } - return -} -func (self VideoMediaInfo) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 2 - n += 2 * len(self.Opcolor[:]) - return -} -func (self *VideoMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+2 { - err = parseErr("GraphicsMode", n+offset, err) - return - } - self.GraphicsMode = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2*len(self.Opcolor) { - err = parseErr("Opcolor", n+offset, err) - return - } - for i := range self.Opcolor { - self.Opcolor[i] = pio.I16BE(b[n:]) - n += 2 - } - return -} -func (self VideoMediaInfo) Children() (r []Atom) { - return -} - -type SampleTable struct { - SampleDesc *SampleDesc - TimeToSample *TimeToSample - CompositionOffset *CompositionOffset - SampleToChunk *SampleToChunk - SyncSample *SyncSample - ChunkOffset *ChunkOffset - SampleSize *SampleSize - AtomPos -} - -func (self SampleTable) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STBL)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleTable) marshal(b []byte) (n int) { - if self.SampleDesc != nil { - n += self.SampleDesc.Marshal(b[n:]) - } - if self.TimeToSample != nil { - n += self.TimeToSample.Marshal(b[n:]) - } - if self.CompositionOffset != nil { - n += self.CompositionOffset.Marshal(b[n:]) - } - if self.SampleToChunk != nil { - n += self.SampleToChunk.Marshal(b[n:]) - } - if self.SyncSample != nil { - n += self.SyncSample.Marshal(b[n:]) - } - if self.ChunkOffset != nil { - n += self.ChunkOffset.Marshal(b[n:]) - } - if self.SampleSize != nil { - n += self.SampleSize.Marshal(b[n:]) - } - return -} -func (self SampleTable) Len() (n int) { - n += 8 - if self.SampleDesc != nil { - n += self.SampleDesc.Len() - } - if self.TimeToSample != nil { - n += self.TimeToSample.Len() - } - if self.CompositionOffset != nil { - n += self.CompositionOffset.Len() - } - if self.SampleToChunk != nil { - n += self.SampleToChunk.Len() - } - if self.SyncSample != nil { - n += self.SyncSample.Len() - } - if self.ChunkOffset != nil { - n += self.ChunkOffset.Len() - } - if self.SampleSize != nil { - n += self.SampleSize.Len() - } - return -} -func (self *SampleTable) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case STSD: - { - atom := &SampleDesc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stsd", n+offset, err) - return - } - self.SampleDesc = atom - } - case STTS: - { - atom := &TimeToSample{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stts", n+offset, err) - return - } - self.TimeToSample = atom - } - case CTTS: - { - atom := &CompositionOffset{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("ctts", n+offset, err) - return - } - self.CompositionOffset = atom - } - case STSC: - { - atom := &SampleToChunk{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stsc", n+offset, err) - return - } - self.SampleToChunk = atom - } - case STSS: - { - atom := &SyncSample{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stss", n+offset, err) - return - } - self.SyncSample = atom - } - case STCO: - { - atom := &ChunkOffset{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stco", n+offset, err) - return - } - self.ChunkOffset = atom - } - case STSZ: - { - atom := &SampleSize{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stsz", n+offset, err) - return - } - self.SampleSize = atom - } - } - n += size - } - return -} -func (self SampleTable) Children() (r []Atom) { - if self.SampleDesc != nil { - r = append(r, self.SampleDesc) - } - if self.TimeToSample != nil { - r = append(r, self.TimeToSample) - } - if self.CompositionOffset != nil { - r = append(r, self.CompositionOffset) - } - if self.SampleToChunk != nil { - r = append(r, self.SampleToChunk) - } - if self.SyncSample != nil { - r = append(r, self.SyncSample) - } - if self.ChunkOffset != nil { - r = append(r, self.ChunkOffset) - } - if self.SampleSize != nil { - r = append(r, self.SampleSize) - } - return -} - -type SampleDesc struct { - Version uint8 - AVC1Desc *AVC1Desc - MP4ADesc *MP4ADesc - Unknowns []Atom - AtomPos -} - -func (self SampleDesc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleDesc) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - n += 3 - _childrenNR := 0 - if self.AVC1Desc != nil { - _childrenNR++ - } - if self.MP4ADesc != nil { - _childrenNR++ - } - _childrenNR += len(self.Unknowns) - pio.PutI32BE(b[n:], int32(_childrenNR)) - n += 4 - if self.AVC1Desc != nil { - n += self.AVC1Desc.Marshal(b[n:]) - } - if self.MP4ADesc != nil { - n += self.MP4ADesc.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self SampleDesc) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.AVC1Desc != nil { - n += self.AVC1Desc.Len() - } - if self.MP4ADesc != nil { - n += self.MP4ADesc.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *SampleDesc) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - n += 3 - n += 4 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case AVC1: - { - atom := &AVC1Desc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("avc1", n+offset, err) - return - } - self.AVC1Desc = atom - } - case MP4A: - { - atom := &MP4ADesc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mp4a", n+offset, err) - return - } - self.MP4ADesc = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self SampleDesc) Children() (r []Atom) { - if self.AVC1Desc != nil { - r = append(r, self.AVC1Desc) - } - if self.MP4ADesc != nil { - r = append(r, self.MP4ADesc) - } - r = append(r, self.Unknowns...) - return -} - -type MP4ADesc struct { - DataRefIdx int16 - Version int16 - RevisionLevel int16 - Vendor int32 - NumberOfChannels int16 - SampleSize int16 - CompressionId int16 - SampleRate float64 - Conf *ElemStreamDesc - Unknowns []Atom - AtomPos -} - -func (self MP4ADesc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MP4A)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MP4ADesc) marshal(b []byte) (n int) { - n += 6 - pio.PutI16BE(b[n:], self.DataRefIdx) - n += 2 - pio.PutI16BE(b[n:], self.Version) - n += 2 - pio.PutI16BE(b[n:], self.RevisionLevel) - n += 2 - pio.PutI32BE(b[n:], self.Vendor) - n += 4 - pio.PutI16BE(b[n:], self.NumberOfChannels) - n += 2 - pio.PutI16BE(b[n:], self.SampleSize) - n += 2 - pio.PutI16BE(b[n:], self.CompressionId) - n += 2 - n += 2 - PutFixed32(b[n:], self.SampleRate) - n += 4 - if self.Conf != nil { - n += self.Conf.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MP4ADesc) Len() (n int) { - n += 8 - n += 6 - n += 2 - n += 2 - n += 2 - n += 4 - n += 2 - n += 2 - n += 2 - n += 2 - n += 4 - if self.Conf != nil { - n += self.Conf.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MP4ADesc) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - n += 6 - if len(b) < n+2 { - err = parseErr("DataRefIdx", n+offset, err) - return - } - self.DataRefIdx = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("RevisionLevel", n+offset, err) - return - } - self.RevisionLevel = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+4 { - err = parseErr("Vendor", n+offset, err) - return - } - self.Vendor = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("NumberOfChannels", n+offset, err) - return - } - self.NumberOfChannels = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("SampleSize", n+offset, err) - return - } - self.SampleSize = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("CompressionId", n+offset, err) - return - } - self.CompressionId = pio.I16BE(b[n:]) - n += 2 - n += 2 - if len(b) < n+4 { - err = parseErr("SampleRate", n+offset, err) - return - } - self.SampleRate = GetFixed32(b[n:]) - n += 4 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case ESDS: - { - atom := &ElemStreamDesc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("esds", n+offset, err) - return - } - self.Conf = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MP4ADesc) Children() (r []Atom) { - if self.Conf != nil { - r = append(r, self.Conf) - } - r = append(r, self.Unknowns...) - return -} - -type AVC1Desc struct { - DataRefIdx int16 - Version int16 - Revision int16 - Vendor int32 - TemporalQuality int32 - SpatialQuality int32 - Width int16 - Height int16 - HorizontalResolution float64 - VorizontalResolution float64 - FrameCount int16 - CompressorName [32]byte - Depth int16 - ColorTableId int16 - Conf *AVC1Conf - Unknowns []Atom - AtomPos -} - -func (self AVC1Desc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(AVC1)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self AVC1Desc) marshal(b []byte) (n int) { - n += 6 - pio.PutI16BE(b[n:], self.DataRefIdx) - n += 2 - pio.PutI16BE(b[n:], self.Version) - n += 2 - pio.PutI16BE(b[n:], self.Revision) - n += 2 - pio.PutI32BE(b[n:], self.Vendor) - n += 4 - pio.PutI32BE(b[n:], self.TemporalQuality) - n += 4 - pio.PutI32BE(b[n:], self.SpatialQuality) - n += 4 - pio.PutI16BE(b[n:], self.Width) - n += 2 - pio.PutI16BE(b[n:], self.Height) - n += 2 - PutFixed32(b[n:], self.HorizontalResolution) - n += 4 - PutFixed32(b[n:], self.VorizontalResolution) - n += 4 - n += 4 - pio.PutI16BE(b[n:], self.FrameCount) - n += 2 - copy(b[n:], self.CompressorName[:]) - n += len(self.CompressorName[:]) - pio.PutI16BE(b[n:], self.Depth) - n += 2 - pio.PutI16BE(b[n:], self.ColorTableId) - n += 2 - if self.Conf != nil { - n += self.Conf.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self AVC1Desc) Len() (n int) { - n += 8 - n += 6 - n += 2 - n += 2 - n += 2 - n += 4 - n += 4 - n += 4 - n += 2 - n += 2 - n += 4 - n += 4 - n += 4 - n += 2 - n += len(self.CompressorName[:]) - n += 2 - n += 2 - if self.Conf != nil { - n += self.Conf.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - n += 6 - if len(b) < n+2 { - err = parseErr("DataRefIdx", n+offset, err) - return - } - self.DataRefIdx = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Revision", n+offset, err) - return - } - self.Revision = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+4 { - err = parseErr("Vendor", n+offset, err) - return - } - self.Vendor = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TemporalQuality", n+offset, err) - return - } - self.TemporalQuality = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("SpatialQuality", n+offset, err) - return - } - self.SpatialQuality = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("Width", n+offset, err) - return - } - self.Width = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Height", n+offset, err) - return - } - self.Height = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+4 { - err = parseErr("HorizontalResolution", n+offset, err) - return - } - self.HorizontalResolution = GetFixed32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("VorizontalResolution", n+offset, err) - return - } - self.VorizontalResolution = GetFixed32(b[n:]) - n += 4 - n += 4 - if len(b) < n+2 { - err = parseErr("FrameCount", n+offset, err) - return - } - self.FrameCount = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+len(self.CompressorName) { - err = parseErr("CompressorName", n+offset, err) - return - } - copy(self.CompressorName[:], b[n:]) - n += len(self.CompressorName) - if len(b) < n+2 { - err = parseErr("Depth", n+offset, err) - return - } - self.Depth = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("ColorTableId", n+offset, err) - return - } - self.ColorTableId = pio.I16BE(b[n:]) - n += 2 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case AVCC: - { - atom := &AVC1Conf{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("avcC", n+offset, err) - return - } - self.Conf = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self AVC1Desc) Children() (r []Atom) { - if self.Conf != nil { - r = append(r, self.Conf) - } - r = append(r, self.Unknowns...) - return -} - -type AVC1Conf struct { - Data []byte - AtomPos -} - -func (self AVC1Conf) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(AVCC)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self AVC1Conf) marshal(b []byte) (n int) { - copy(b[n:], self.Data[:]) - n += len(self.Data[:]) - return -} -func (self AVC1Conf) Len() (n int) { - n += 8 - n += len(self.Data[:]) - return -} -func (self *AVC1Conf) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - self.Data = b[n:] - n += len(b[n:]) - return -} -func (self AVC1Conf) Children() (r []Atom) { - return -} - -type TimeToSample struct { - Version uint8 - Flags uint32 - Entries []TimeToSampleEntry - AtomPos -} - -func (self TimeToSample) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STTS)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TimeToSample) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - PutTimeToSampleEntry(b[n:], entry) - n += LenTimeToSampleEntry - } - return -} -func (self TimeToSample) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += LenTimeToSampleEntry * len(self.Entries) - return -} -func (self *TimeToSample) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]TimeToSampleEntry, _len_Entries) - if len(b) < n+LenTimeToSampleEntry*len(self.Entries) { - err = parseErr("TimeToSampleEntry", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = GetTimeToSampleEntry(b[n:]) - n += LenTimeToSampleEntry - } - return -} -func (self TimeToSample) Children() (r []Atom) { - return -} - -type TimeToSampleEntry struct { - Count uint32 - Duration uint32 -} - -func GetTimeToSampleEntry(b []byte) (self TimeToSampleEntry) { - self.Count = pio.U32BE(b[0:]) - self.Duration = pio.U32BE(b[4:]) - return -} -func PutTimeToSampleEntry(b []byte, self TimeToSampleEntry) { - pio.PutU32BE(b[0:], self.Count) - pio.PutU32BE(b[4:], self.Duration) -} - -const LenTimeToSampleEntry = 8 - -type SampleToChunk struct { - Version uint8 - Flags uint32 - Entries []SampleToChunkEntry - AtomPos -} - -func (self SampleToChunk) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSC)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleToChunk) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - PutSampleToChunkEntry(b[n:], entry) - n += LenSampleToChunkEntry - } - return -} -func (self SampleToChunk) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += LenSampleToChunkEntry * len(self.Entries) - return -} -func (self *SampleToChunk) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]SampleToChunkEntry, _len_Entries) - if len(b) < n+LenSampleToChunkEntry*len(self.Entries) { - err = parseErr("SampleToChunkEntry", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = GetSampleToChunkEntry(b[n:]) - n += LenSampleToChunkEntry - } - return -} -func (self SampleToChunk) Children() (r []Atom) { - return -} - -type SampleToChunkEntry struct { - FirstChunk uint32 - SamplesPerChunk uint32 - SampleDescId uint32 -} - -func GetSampleToChunkEntry(b []byte) (self SampleToChunkEntry) { - self.FirstChunk = pio.U32BE(b[0:]) - self.SamplesPerChunk = pio.U32BE(b[4:]) - self.SampleDescId = pio.U32BE(b[8:]) - return -} -func PutSampleToChunkEntry(b []byte, self SampleToChunkEntry) { - pio.PutU32BE(b[0:], self.FirstChunk) - pio.PutU32BE(b[4:], self.SamplesPerChunk) - pio.PutU32BE(b[8:], self.SampleDescId) -} - -const LenSampleToChunkEntry = 12 - -type CompositionOffset struct { - Version uint8 - Flags uint32 - Entries []CompositionOffsetEntry - AtomPos -} - -func (self CompositionOffset) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(CTTS)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self CompositionOffset) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - PutCompositionOffsetEntry(b[n:], entry) - n += LenCompositionOffsetEntry - } - return -} -func (self CompositionOffset) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += LenCompositionOffsetEntry * len(self.Entries) - return -} -func (self *CompositionOffset) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]CompositionOffsetEntry, _len_Entries) - if len(b) < n+LenCompositionOffsetEntry*len(self.Entries) { - err = parseErr("CompositionOffsetEntry", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = GetCompositionOffsetEntry(b[n:]) - n += LenCompositionOffsetEntry - } - return -} -func (self CompositionOffset) Children() (r []Atom) { - return -} - -type CompositionOffsetEntry struct { - Count uint32 - Offset uint32 -} - -func GetCompositionOffsetEntry(b []byte) (self CompositionOffsetEntry) { - self.Count = pio.U32BE(b[0:]) - self.Offset = pio.U32BE(b[4:]) - return -} -func PutCompositionOffsetEntry(b []byte, self CompositionOffsetEntry) { - pio.PutU32BE(b[0:], self.Count) - pio.PutU32BE(b[4:], self.Offset) -} - -const LenCompositionOffsetEntry = 8 - -type SyncSample struct { - Version uint8 - Flags uint32 - Entries []uint32 - AtomPos -} - -func (self SyncSample) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSS)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SyncSample) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - pio.PutU32BE(b[n:], entry) - n += 4 - } - return -} -func (self SyncSample) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 * len(self.Entries) - return -} -func (self *SyncSample) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]uint32, _len_Entries) - if len(b) < n+4*len(self.Entries) { - err = parseErr("uint32", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = pio.U32BE(b[n:]) - n += 4 - } - return -} -func (self SyncSample) Children() (r []Atom) { - return -} - -type ChunkOffset struct { - Version uint8 - Flags uint32 - Entries []uint32 - AtomPos -} - -func (self ChunkOffset) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STCO)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self ChunkOffset) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - pio.PutU32BE(b[n:], entry) - n += 4 - } - return -} -func (self ChunkOffset) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 * len(self.Entries) - return -} -func (self *ChunkOffset) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]uint32, _len_Entries) - if len(b) < n+4*len(self.Entries) { - err = parseErr("uint32", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = pio.U32BE(b[n:]) - n += 4 - } - return -} -func (self ChunkOffset) Children() (r []Atom) { - return -} - -type MovieFrag struct { - Header *MovieFragHeader - Tracks []*TrackFrag - Unknowns []Atom - AtomPos -} - -func (self MovieFrag) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MOOF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieFrag) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - for _, atom := range self.Tracks { - n += atom.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MovieFrag) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - for _, atom := range self.Tracks { - n += atom.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case MFHD: - { - atom := &MovieFragHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mfhd", n+offset, err) - return - } - self.Header = atom - } - case TRAF: - { - atom := &TrackFrag{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("traf", n+offset, err) - return - } - self.Tracks = append(self.Tracks, atom) - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MovieFrag) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - for _, atom := range self.Tracks { - r = append(r, atom) - } - r = append(r, self.Unknowns...) - return -} - -type MovieFragHeader struct { - Version uint8 - Flags uint32 - Seqnum uint32 - AtomPos -} - -func (self MovieFragHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MFHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieFragHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.Seqnum) - n += 4 - return -} -func (self MovieFragHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - return -} -func (self *MovieFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("Seqnum", n+offset, err) - return - } - self.Seqnum = pio.U32BE(b[n:]) - n += 4 - return -} -func (self MovieFragHeader) Children() (r []Atom) { - return -} - -type TrackFrag struct { - Header *TrackFragHeader - DecodeTime *TrackFragDecodeTime - Run *TrackFragRun - Unknowns []Atom - AtomPos -} - -func (self TrackFrag) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TRAF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFrag) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.DecodeTime != nil { - n += self.DecodeTime.Marshal(b[n:]) - } - if self.Run != nil { - n += self.Run.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self TrackFrag) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.DecodeTime != nil { - n += self.DecodeTime.Len() - } - if self.Run != nil { - n += self.Run.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case TFHD: - { - atom := &TrackFragHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("tfhd", n+offset, err) - return - } - self.Header = atom - } - case TFDT: - { - atom := &TrackFragDecodeTime{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("tfdt", n+offset, err) - return - } - self.DecodeTime = atom - } - case TRUN: - { - atom := &TrackFragRun{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("trun", n+offset, err) - return - } - self.Run = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self TrackFrag) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.DecodeTime != nil { - r = append(r, self.DecodeTime) - } - if self.Run != nil { - r = append(r, self.Run) - } - r = append(r, self.Unknowns...) - return -} - -type MovieExtend struct { - Tracks []*TrackExtend - Unknowns []Atom - AtomPos -} - -func (self MovieExtend) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MVEX)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieExtend) marshal(b []byte) (n int) { - for _, atom := range self.Tracks { - n += atom.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MovieExtend) Len() (n int) { - n += 8 - for _, atom := range self.Tracks { - n += atom.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MovieExtend) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case TREX: - { - atom := &TrackExtend{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("trex", n+offset, err) - return - } - self.Tracks = append(self.Tracks, atom) - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MovieExtend) Children() (r []Atom) { - for _, atom := range self.Tracks { - r = append(r, atom) - } - r = append(r, self.Unknowns...) - return -} - -type TrackExtend struct { - Version uint8 - Flags uint32 - TrackId uint32 - DefaultSampleDescIdx uint32 - DefaultSampleDuration uint32 - DefaultSampleSize uint32 - DefaultSampleFlags uint32 - AtomPos -} - -func (self TrackExtend) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TREX)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackExtend) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.TrackId) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleDescIdx) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleDuration) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleSize) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleFlags) - n += 4 - return -} -func (self TrackExtend) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - return -} -func (self *TrackExtend) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("TrackId", n+offset, err) - return - } - self.TrackId = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleDescIdx", n+offset, err) - return - } - self.DefaultSampleDescIdx = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleDuration", n+offset, err) - return - } - self.DefaultSampleDuration = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleSize", n+offset, err) - return - } - self.DefaultSampleSize = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleFlags", n+offset, err) - return - } - self.DefaultSampleFlags = pio.U32BE(b[n:]) - n += 4 - return -} -func (self TrackExtend) Children() (r []Atom) { - return -} - -type SampleSize struct { - Version uint8 - Flags uint32 - SampleSize uint32 - Entries []uint32 - AtomPos -} - -func (self SampleSize) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSZ)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleSize) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.SampleSize) - n += 4 - if self.SampleSize != 0 { - return - } - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - pio.PutU32BE(b[n:], entry) - n += 4 - } - return -} -func (self SampleSize) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.SampleSize != 0 { - return - } - n += 4 - n += 4 * len(self.Entries) - return -} -func (self *SampleSize) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("SampleSize", n+offset, err) - return - } - self.SampleSize = pio.U32BE(b[n:]) - n += 4 - if self.SampleSize != 0 { - return - } - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]uint32, _len_Entries) - if len(b) < n+4*len(self.Entries) { - err = parseErr("uint32", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = pio.U32BE(b[n:]) - n += 4 - } - return -} -func (self SampleSize) Children() (r []Atom) { - return -} - -type TrackFragRun struct { - Version uint8 - Flags uint32 - DataOffset uint32 - FirstSampleFlags uint32 - Entries []TrackFragRunEntry - AtomPos -} - -func (self TrackFragRun) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TRUN)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFragRun) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - if self.Flags&TRUN_DATA_OFFSET != 0 { - { - pio.PutU32BE(b[n:], self.DataOffset) - n += 4 - } - } - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - { - pio.PutU32BE(b[n:], self.FirstSampleFlags) - n += 4 - } - } - - for i, entry := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - //log.Printf("TRUN entry[%v]: flags = %v", i, flags) - if flags&TRUN_SAMPLE_DURATION != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_DURATION = %v, flags = %v", i, entry.Duration, flags) - pio.PutU32BE(b[n:], entry.Duration) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_SIZE = %v, flags = %v", i, entry.Size, flags) - pio.PutU32BE(b[n:], entry.Size) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_FLAGS = %v, flags = %v", i, entry.Flags, flags) - pio.PutU32BE(b[n:], entry.Flags) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_CTS = %v, flags = %v", i, entry.Cts, flags) - pio.PutU32BE(b[n:], entry.Cts) - n += 4 - } - } - return -} -func (self TrackFragRun) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.Flags&TRUN_DATA_OFFSET != 0 { - n += 4 - } - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - n += 4 - } - - for i := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - if flags&TRUN_SAMPLE_DURATION != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - n += 4 - } - } - return -} -func (self *TrackFragRun) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]TrackFragRunEntry, _len_Entries) - if self.Flags&TRUN_DATA_OFFSET != 0 { - { - if len(b) < n+4 { - err = parseErr("DataOffset", n+offset, err) - return - } - self.DataOffset = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - { - if len(b) < n+4 { - err = parseErr("FirstSampleFlags", n+offset, err) - return - } - self.FirstSampleFlags = pio.U32BE(b[n:]) - n += 4 - } - } - - for i := 0; i < int(_len_Entries); i++ { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - entry := &self.Entries[i] - if flags&TRUN_SAMPLE_DURATION != 0 { - entry.Duration = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - entry.Size = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - entry.Flags = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - entry.Cts = pio.U32BE(b[n:]) - n += 4 - } - } - return -} -func (self TrackFragRun) Children() (r []Atom) { - return -} - -type TrackFragRunEntry struct { - Duration uint32 - Size uint32 - Flags uint32 - Cts uint32 -} - -func GetTrackFragRunEntry(b []byte) (self TrackFragRunEntry) { - self.Duration = pio.U32BE(b[0:]) - self.Size = pio.U32BE(b[4:]) - self.Flags = pio.U32BE(b[8:]) - self.Cts = pio.U32BE(b[12:]) - return -} -func PutTrackFragRunEntry(b []byte, self TrackFragRunEntry) { - pio.PutU32BE(b[0:], self.Duration) - pio.PutU32BE(b[4:], self.Size) - pio.PutU32BE(b[8:], self.Flags) - pio.PutU32BE(b[12:], self.Cts) -} - -const LenTrackFragRunEntry = 16 - -type TrackFragHeader struct { - Version uint8 - Flags uint32 - TrackId uint32 - BaseDataOffset uint64 - StsdId uint32 - DefaultDuration uint32 - DefaultSize uint32 - DefaultFlags uint32 - AtomPos -} - -func (self TrackFragHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TFHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFragHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.TrackId) - n += 4 - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - { - pio.PutU64BE(b[n:], self.BaseDataOffset) - n += 8 - } - } - - if self.Flags&TFHD_STSD_ID != 0 { - { - pio.PutU32BE(b[n:], self.StsdId) - n += 4 - } - } - - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - { - pio.PutU32BE(b[n:], self.DefaultDuration) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - { - pio.PutU32BE(b[n:], self.DefaultSize) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - { - pio.PutU32BE(b[n:], self.DefaultFlags) - n += 4 - } - } - return -} -func (self TrackFragHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - { - n += 8 - } - } - if self.Flags&TFHD_STSD_ID != 0 { - { - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - { - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - { - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - { - n += 4 - } - } - return -} -func (self *TrackFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - - if len(b) < n+4 { - err = parseErr("TrackId", n+offset, err) - return - } - self.TrackId = pio.U32BE(b[n:]) - n += 4 - - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - { - if len(b) < n+8 { - err = parseErr("BaseDataOffset", n+offset, err) - return - } - self.BaseDataOffset = pio.U64BE(b[n:]) - n += 8 - } - } - if self.Flags&TFHD_STSD_ID != 0 { - { - if len(b) < n+4 { - err = parseErr("StsdId", n+offset, err) - return - } - self.StsdId = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - { - if len(b) < n+4 { - err = parseErr("DefaultDuration", n+offset, err) - return - } - self.DefaultDuration = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - { - if len(b) < n+4 { - err = parseErr("DefaultSize", n+offset, err) - return - } - self.DefaultSize = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - { - if len(b) < n+4 { - err = parseErr("DefaultFlags", n+offset, err) - return - } - self.DefaultFlags = pio.U32BE(b[n:]) - n += 4 - } - } - return -} -func (self TrackFragHeader) Children() (r []Atom) { - return -} - -type TrackFragDecodeTime struct { - Version uint8 - Flags uint32 - DecodeTime uint64 - AtomPos -} - -func (self TrackFragDecodeTime) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TFDT)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFragDecodeTime) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - if self.Version != 0 { - pio.PutU64BE(b[n:], self.DecodeTime) - n += 8 - } else { - - pio.PutU32BE(b[n:], uint32(self.DecodeTime)) - n += 4 - } - return -} -func (self TrackFragDecodeTime) Len() (n int) { - n += 8 - n += 1 - n += 3 - if self.Version != 0 { - n += 8 - } else { - - n += 4 - } - return -} -func (self *TrackFragDecodeTime) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if self.Version != 0 { - self.DecodeTime = pio.U64BE(b[n:]) - n += 8 - } else { - - self.DecodeTime = uint64(pio.U32BE(b[n:])) - n += 4 - } - return -} -func (self TrackFragDecodeTime) Children() (r []Atom) { - return -} diff --git a/machinery/src/fmp4/mp4io/gen/gen.go b/machinery/src/fmp4/mp4io/gen/gen.go deleted file mode 100644 index 1a6efb37..00000000 --- a/machinery/src/fmp4/mp4io/gen/gen.go +++ /dev/null @@ -1,1057 +0,0 @@ - -package main - -import ( - "strings" - "fmt" - "os" - "go/ast" - "go/parser" - "go/token" - "go/printer" -) - -func getexprs(e ast.Expr) string { - if lit, ok := e.(*ast.BasicLit); ok { - return lit.Value - } - if ident, ok := e.(*ast.Ident); ok { - return ident.Name - } - return "" -} - -func genatomdecl(origfn *ast.FuncDecl, origname, origtag string) (decls []ast.Decl) { - fieldslist := &ast.FieldList{} - typespec := &ast.TypeSpec{ - Name: ast.NewIdent(origname), - Type: &ast.StructType{Fields: fieldslist}, - } - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - - if strings.HasPrefix(typ, "_") { - if typ == "_unknowns" { - fieldslist.List = append(fieldslist.List, &ast.Field{ - Names: []*ast.Ident{ast.NewIdent("Unknowns")}, - Type: ast.NewIdent("[]Atom"), - }) - } - continue - } - - name := getexprs(callexpr.Args[0]) - - name2 := "" - if len(callexpr.Args) > 1 { - name2 = getexprs(callexpr.Args[1]) - } - - len3 := "" - if len(callexpr.Args) > 2 { - len3 = getexprs(callexpr.Args[2]) - } - - if strings.HasPrefix(name, "_") { - continue - } - - switch typ { - case "fixed16": - typ = "float64" - case "fixed32": - typ = "float64" - case "bytesleft": - typ = "[]byte" - case "bytes": - typ = "["+name2+"]byte" - case "uint24": - typ = "uint32" - case "time64", "time32": - typ = "time.Time" - case "atom": - typ = "*"+name2 - case "atoms": - typ = "[]*"+name2 - case "slice": - typ = "[]"+name2 - case "array": - typ = "["+len3+"]"+name2 - } - - fieldslist.List = append(fieldslist.List, &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: ast.NewIdent(typ), - }) - } - - if origtag != "" { - fieldslist.List = append(fieldslist.List, &ast.Field{ - Type: ast.NewIdent("AtomPos"), - }) - } - - gendecl := &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{ - typespec, - }, - } - decls = append(decls, gendecl) - return -} - -func typegetlen(typ string) (n int) { - switch typ { - case "uint8": - n = 1 - case "uint16": - n = 2 - case "uint24": - n = 3 - case "uint32": - n = 4 - case "int16": - n = 2 - case "int32": - n = 4 - case "uint64": - n = 8 - case "time32": - n = 4 - case "time64": - n = 8 - case "fixed32": - n = 4 - case "fixed16": - n = 2 - } - return -} - -func typegetlens(typ string) string { - n := typegetlen(typ) - if n == 0 { - return "Len"+typ - } else { - return fmt.Sprint(n) - } -} - -func typegetvartype(typ string) string { - switch typ { - case "uint8": - return "uint8" - case "uint16": - return "uint16" - case "uint24": - return "uint32" - case "uint32": - return "uint32" - case "uint64": - return "uint64" - case "int16": - return "int16" - case "int32": - return "int32" - } - return "" -} - -func typegetputfn(typ string) (fn string) { - fn = typ - switch typ { - case "uint8": - fn = "pio.PutU8" - case "uint16": - fn = "pio.PutU16BE" - case "uint24": - fn = "pio.PutU24BE" - case "uint32": - fn = "pio.PutU32BE" - case "int16": - fn = "pio.PutI16BE" - case "int32": - fn = "pio.PutI32BE" - case "uint64": - fn = "pio.PutU64BE" - case "time32": - fn = "PutTime32" - case "time64": - fn = "PutTime64" - case "fixed32": - fn = "PutFixed32" - case "fixed16": - fn = "PutFixed16" - default: - fn = "Put"+typ - } - return -} - -func typegetgetfn(typ string) (fn string) { - fn = typ - switch typ { - case "uint8": - fn = "pio.U8" - case "uint16": - fn = "pio.U16BE" - case "uint24": - fn = "pio.U24BE" - case "uint32": - fn = "pio.U32BE" - case "int16": - fn = "pio.I16BE" - case "int32": - fn = "pio.I32BE" - case "uint64": - fn = "pio.U64BE" - case "time32": - fn = "GetTime32" - case "time64": - fn = "GetTime64" - case "fixed32": - fn = "GetFixed32" - case "fixed16": - fn = "GetFixed16" - default: - fn = "Get"+typ - } - return -} - -func addns(n string) (stmts []ast.Stmt) { - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: n}}, - } - stmts = append(stmts, assign) - return -} - -func addn(n int) (stmts []ast.Stmt) { - return addns(fmt.Sprint(n)) -} - -func simplecall(fun string, args... string) *ast.ExprStmt { - _args := []ast.Expr{} - for _, s := range args { - _args = append(_args, ast.NewIdent(s)) - } - return &ast.ExprStmt{ - X: &ast.CallExpr{ - Fun: ast.NewIdent(fun), - Args: _args, - }, - } -} - -func getxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { - fn := typegetgetfn(typ) - assign := &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ast.NewIdent(name)}, - Rhs: []ast.Expr{simplecall(fn, "b["+pos+":]").X}, - } - stmts = append(stmts, assign) - return -} - -func putxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { - if conv { - name = fmt.Sprintf("%s(%s)", typ, name) - } - fn := typegetputfn(typ) - stmts = append(stmts, simplecall(fn, "b["+pos+":]", name)) - return -} - -func putxxadd(fn string, name string, conv bool) (stmts []ast.Stmt) { - n := typegetlen(fn) - stmts = append(stmts, putxx(fn, "n", name, conv)...) - stmts = append(stmts, addn(n)...) - return -} - -func newdecl(origname, name string, params, res []*ast.Field, stmts []ast.Stmt) *ast.FuncDecl { - return &ast.FuncDecl{ - Recv: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{ - Names: []*ast.Ident{ast.NewIdent("self")}, - Type: ast.NewIdent(origname), - }, - }, - }, - Name: ast.NewIdent(name), - Type: &ast.FuncType{ - Params: &ast.FieldList{ - List: params, - }, - Results: &ast.FieldList{ - List: res, - }, - }, - Body: &ast.BlockStmt{List: stmts}, - } -} - -func getstructputgetlenfn(origfn *ast.FuncDecl, origname string) (decls []ast.Decl) { - getstmts := []ast.Stmt{} - putstmts := []ast.Stmt{} - totlen := 0 - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - name := getexprs(callexpr.Args[0]) - - getstmts = append(getstmts, getxx(typ, fmt.Sprint(totlen), "self."+name, false)...) - putstmts = append(putstmts, putxx(typ, fmt.Sprint(totlen), "self."+name, false)...) - totlen += typegetlen(typ) - } - - getstmts = append(getstmts, &ast.ReturnStmt{}) - - decls = append(decls, &ast.FuncDecl{ - Name: ast.NewIdent("Get"+origname), - Type: &ast.FuncType{ - Params: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - }, - }, - Results: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, - }, - }, - }, - Body: &ast.BlockStmt{List: getstmts}, - }) - - decls = append(decls, &ast.FuncDecl{ - Name: ast.NewIdent("Put"+origname), - Type: &ast.FuncType{ - Params: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, - }, - }, - }, - Body: &ast.BlockStmt{List: putstmts}, - }) - - decls = append(decls, &ast.GenDecl{ - Tok: token.CONST, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ast.NewIdent("Len"+origname)}, - Values: []ast.Expr{ast.NewIdent(fmt.Sprint(totlen))}, - }, - }, - }) - - return -} - -func cc4decls(name string) (decls []ast.Decl) { - constdecl := &ast.GenDecl{ - Tok: token.CONST, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ - ast.NewIdent(strings.ToUpper(name)), - }, - Values: []ast.Expr{ - &ast.CallExpr{ - Fun: ast.NewIdent("Tag"), - Args: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("0x%x", []byte(name))}}, - }, - }, - }, - }, - } - decls = append(decls, constdecl) - return -} - -func codeclonereplace(stmts []ast.Stmt, doit []ast.Stmt) (out []ast.Stmt) { - out = append([]ast.Stmt(nil), stmts...) - for i := range out { - if ifstmt, ok := out[i].(*ast.IfStmt); ok { - newifstmt := &ast.IfStmt{ - Cond: ifstmt.Cond, - Body: &ast.BlockStmt{ - List: codeclonereplace(ifstmt.Body.List, doit), - }, - } - if ifstmt.Else != nil { - newifstmt.Else = &ast.BlockStmt{ - List: codeclonereplace(ifstmt.Else.(*ast.BlockStmt).List, doit), - } - } - out[i] = newifstmt - } else if exprstmt, ok := out[i].(*ast.ExprStmt); ok { - if callexpr, ok := exprstmt.X.(*ast.CallExpr); ok { - if getexprs(callexpr.Fun) == "doit" { - out[i] = &ast.BlockStmt{List: doit} - } - } - } - } - return -} - -func getatommarshalfn(origfn *ast.FuncDecl, - origname, origtag string, - tagnamemap map[string]string, -) (decls []ast.Decl) { - marstmts := []ast.Stmt{} - unmarstmts := []ast.Stmt{} - lenstmts := []ast.Stmt{} - childrenstmts := []ast.Stmt{} - - parseerrreturn := func(debug string) (stmts []ast.Stmt) { - return []ast.Stmt{ - &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("err")}, - Rhs: []ast.Expr{ast.NewIdent(fmt.Sprintf(`parseErr("%s", n+offset, err)`, debug))}, - }, - &ast.ReturnStmt{}, - } - } - - callmarshal := func(name string) (stmts []ast.Stmt) { - callexpr := &ast.CallExpr{ - Fun: ast.NewIdent(name+".Marshal"), - Args: []ast.Expr{ast.NewIdent("b[n:]")}, - } - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{callexpr}, - } - stmts = append(stmts, assign) - return - } - - callputstruct := func(typ, name string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.ExprStmt{ - X: &ast.CallExpr{ - Fun: ast.NewIdent(typegetputfn(typ)), - Args: []ast.Expr{ast.NewIdent("b[n:]"), ast.NewIdent(name)}, - }, - }) - stmts = append(stmts, &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{ast.NewIdent(typegetlens(typ))}, - }) - return - } - - calllenstruct := func(typ, name string) (stmts []ast.Stmt) { - inc := typegetlens(typ)+"*len("+name+")" - stmts = append(stmts, &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{ast.NewIdent(inc)}, - }) - return - } - - calllen := func(name string) (stmts []ast.Stmt) { - callexpr := &ast.CallExpr{ - Fun: ast.NewIdent(name+".Len"), - Args: []ast.Expr{}, - } - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{callexpr}, - } - stmts = append(stmts, assign) - return - } - - foreach := func(name, field string, block []ast.Stmt) (stmts []ast.Stmt) { - rangestmt := &ast.RangeStmt{ - Key: ast.NewIdent("_"), - Value: ast.NewIdent(name), - Body: &ast.BlockStmt{ - List: block, - }, - Tok: token.DEFINE, - X: ast.NewIdent(field), - } - stmts = append(stmts, rangestmt) - return - } - - foreachatom := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { - return foreach("atom", field, block) - } - - foreachentry := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { - return foreach("entry", field, block) - } - - foreachi := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { - rangestmt := &ast.RangeStmt{ - Key: ast.NewIdent("i"), - Body: &ast.BlockStmt{ - List: block, - }, - Tok: token.DEFINE, - X: ast.NewIdent(field), - } - stmts = append(stmts, rangestmt) - return - } - - foreachunknowns := func(block []ast.Stmt) (stmts []ast.Stmt) { - return foreachatom("self.Unknowns", block) - } - - declvar := func(name, typ string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.DeclStmt{ - Decl: &ast.GenDecl{ - Tok: token.VAR, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ - ast.NewIdent(typ), - }, - Type: ast.NewIdent(name), - }, - }, - }, - }) - return - } - - makeslice := func(name, typ, size string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.ExprStmt{ - X: ast.NewIdent(fmt.Sprintf("%s = make([]%s, %s)", name, typ, size)), - }) - return - } - - simpleassign := func(tok token.Token, l, r string) *ast.AssignStmt { - return &ast.AssignStmt{ - Tok: tok, - Lhs: []ast.Expr{ast.NewIdent(l)}, - Rhs: []ast.Expr{ast.NewIdent(r)}, - } - } - - struct2tag := func(s string) string { - name := tagnamemap[s] - return name - } - - foreachatomsappendchildren := func(field string) (stmts []ast.Stmt) { - return foreachatom(field, []ast.Stmt{ - simpleassign(token.ASSIGN, "r", "append(r, atom)"), - }) - } - - var hasunknowns bool - var atomnames []string - var atomtypes []string - var atomarrnames []string - var atomarrtypes []string - slicenamemap := map[string]string{} - - unmarshalatom := func(typ, init string) (stmts []ast.Stmt) { - return []ast.Stmt{ - &ast.AssignStmt{Tok: token.DEFINE, - Lhs: []ast.Expr{ast.NewIdent("atom")}, Rhs: []ast.Expr{ast.NewIdent("&"+typ+"{"+init+"}")}, - }, - &ast.IfStmt{ - Init: &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("_"), ast.NewIdent("err")}, - Rhs: []ast.Expr{ast.NewIdent("atom.Unmarshal(b[n:n+size], offset+n)")}, - }, - Cond: ast.NewIdent("err != nil"), - Body: &ast.BlockStmt{List: parseerrreturn(struct2tag(typ))}, - }, - } - } - - unmrashalatoms := func() (stmts []ast.Stmt) { - blocks := []ast.Stmt{} - - blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("tag")}, - Rhs: []ast.Expr{ast.NewIdent("Tag(pio.U32BE(b[n+4:]))")}, - }) - blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("size")}, - Rhs: []ast.Expr{ast.NewIdent("int(pio.U32BE(b[n:]))")}, - }) - blocks = append(blocks, &ast.IfStmt{ - Cond: ast.NewIdent("len(b) < n+size"), - Body: &ast.BlockStmt{List: parseerrreturn("TagSizeInvalid")}, - }) - - cases := []ast.Stmt{} - - for i, atom := range atomnames { - cases = append(cases, &ast.CaseClause{ - List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomtypes[i])))}, - Body: []ast.Stmt{&ast.BlockStmt{ - List: append(unmarshalatom(atomtypes[i], ""), simpleassign(token.ASSIGN, "self."+atom, "atom")), - }}, - }) - } - - for i, atom := range atomarrnames { - selfatom := "self."+atom - cases = append(cases, &ast.CaseClause{ - List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomarrtypes[i])))}, - Body: []ast.Stmt{&ast.BlockStmt{ - List: append(unmarshalatom(atomarrtypes[i], ""), - simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), - }}, - }) - } - - if hasunknowns { - init := "Tag_: tag, Data: b[n:n+size]" - selfatom := "self.Unknowns" - cases = append(cases, &ast.CaseClause{ - Body: []ast.Stmt{&ast.BlockStmt{ - List: append(unmarshalatom("Dummy", init), simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), - }}, - }) - } - - blocks = append(blocks, &ast.SwitchStmt{ - Tag: ast.NewIdent("tag"), - Body: &ast.BlockStmt{List: cases}, - }) - - blocks = append(blocks, addns("size")...) - - stmts = append(stmts, &ast.ForStmt{ - Cond: ast.NewIdent("n+8 < len(b)"), - Body: &ast.BlockStmt{List: blocks}, - }) - return - } - - marshalwrapstmts := func() (stmts []ast.Stmt) { - stmts = append(stmts, putxx("uint32", "4", strings.ToUpper(origtag), true)...) - stmts = append(stmts, addns("self.marshal(b[8:])+8")...) - stmts = append(stmts, putxx("uint32", "0", "n", true)...) - stmts = append(stmts, &ast.ReturnStmt{}) - return - } - - ifnotnil := func(name string, block []ast.Stmt) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.IfStmt{ - Cond: &ast.BinaryExpr{ - X: ast.NewIdent(name), - Op: token.NEQ, - Y: ast.NewIdent("nil"), - }, - Body: &ast.BlockStmt{List: block}, - }) - return - } - - getchildrennr := func(name string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.AssignStmt{ - Tok: token.DEFINE, - Lhs: []ast.Expr{ast.NewIdent(name)}, - Rhs: []ast.Expr{ast.NewIdent("0")}, - }) - for _, atom := range atomnames { - stmts = append(stmts, ifnotnil("self."+atom, []ast.Stmt{ - &ast.IncDecStmt{X: ast.NewIdent(name), Tok: token.INC}, - })...) - } - if hasunknowns { - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("_childrenNR")}, - Rhs: []ast.Expr{ast.NewIdent("len(self.Unknowns)")}, - } - stmts = append(stmts, assign) - } - return - } - - checkcurlen := func(inc, debug string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.IfStmt{ - Cond: &ast.BinaryExpr{ - X: ast.NewIdent("len(b)"), - Op: token.LSS, - Y: ast.NewIdent("n+"+inc), - }, - Body: &ast.BlockStmt{List: parseerrreturn(debug)}, - }) - return - } - - checklendo := func(typ, name, debug string) (stmts []ast.Stmt) { - stmts = append(stmts, checkcurlen(typegetlens(typ), debug)...) - stmts = append(stmts, getxx(typ, "n", name, false)...) - stmts = append(stmts, addns(typegetlens(typ))...) - return - } - - checkstructlendo := func(typ, name, debug string, - foreach func(string,[]ast.Stmt)[]ast.Stmt, - ) (stmts []ast.Stmt) { - inc := typegetlens(typ)+"*len("+name+")" - stmts = append(stmts, checkcurlen(inc, debug)...) - stmts = append(stmts, foreach(name, append( - []ast.Stmt{ - &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ - ast.NewIdent(name+"[i]"), - }, - Rhs: []ast.Expr{ - &ast.CallExpr{ - Fun: ast.NewIdent(typegetgetfn(typ)), - Args: []ast.Expr{ast.NewIdent("b[n:]")}, - }, - }, - }, - }, - addns(typegetlens(typ))..., - ))...) - return - } - - checklencopy := func(name string) (stmts []ast.Stmt) { - lens := fmt.Sprintf("len(self.%s)", name) - stmts = append(stmts, checkcurlen(lens, name)...) - stmts = append(stmts, simplecall("copy", fmt.Sprintf("self.%s[:]", name), "b[n:]")) - stmts = append(stmts, addns(lens)...) - return - } - - appendcode := func(args []ast.Expr, - marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, unmarstmts *[]ast.Stmt, - defmarstmts []ast.Stmt, deflenstmts []ast.Stmt, defunmarstmts []ast.Stmt, - ) { - bodylist := func(i int, doit []ast.Stmt) []ast.Stmt { - return codeclonereplace(args[i].(*ast.FuncLit).Body.List, doit) - } - if len(args) == 1 { - *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) - *lenstmts = append(*lenstmts, bodylist(0, deflenstmts)...) - *unmarstmts = append(*unmarstmts, bodylist(0, defunmarstmts)...) - } else { - *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) - *lenstmts = append(*lenstmts, bodylist(1, deflenstmts)...) - *unmarstmts = append(*unmarstmts, bodylist(2, defunmarstmts)...) - } - } - - getdefaultstmts := func( - typ, name, name2 string, - marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, - unmarstmts *[]ast.Stmt, childrenstmts *[]ast.Stmt, - ) { - switch typ { - case "bytes", "bytesleft": - *marstmts = append(*marstmts, simplecall("copy", "b[n:]", "self."+name+"[:]")) - *marstmts = append(*marstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) - *lenstmts = append(*lenstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) - if typ == "bytes" { - *unmarstmts = append(*unmarstmts, checklencopy(name)...) - } else { - *unmarstmts = append(*unmarstmts, simpleassign(token.ASSIGN, "self."+name, "b[n:]")) - *unmarstmts = append(*unmarstmts, addns("len(b[n:])")...) - } - - case "array": - *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) - *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name+"[:]")...) - *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name, foreachi)...) - - case "atoms": - *marstmts = append(*marstmts, foreachatom("self."+name, callmarshal("atom"))...) - *lenstmts = append(*lenstmts, foreachatom("self."+name, calllen("atom"))...) - *childrenstmts = append(*childrenstmts, foreachatomsappendchildren("self."+name)...) - - case "slice": - *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) - *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name)...) - *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name2, foreachi)...) - - case "atom": - *marstmts = append(*marstmts, ifnotnil("self."+name, callmarshal("self."+name))...) - *lenstmts = append(*lenstmts, ifnotnil("self."+name, calllen("self."+name))...) - *childrenstmts = append(*childrenstmts, ifnotnil("self."+name, []ast.Stmt{ - simpleassign(token.ASSIGN, "r", fmt.Sprintf("append(r, %s)", "self."+name)), - })...) - - default: - *marstmts = append(*marstmts, putxxadd(typ, "self."+name, false)...) - *lenstmts = append(*lenstmts, addn(typegetlen(typ))...) - *unmarstmts = append(*unmarstmts, checklendo(typ, "self."+name, name)...) - } - } - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - if typ == "_unknowns" { - hasunknowns = true - } else if typ == "atom" { - name := getexprs(callexpr.Args[0]) - name2 := getexprs(callexpr.Args[1]) - atomnames = append(atomnames, name) - atomtypes = append(atomtypes, name2) - } else if typ == "atoms" { - name := getexprs(callexpr.Args[0]) - name2 := getexprs(callexpr.Args[1]) - atomarrnames = append(atomarrnames, name) - atomarrtypes = append(atomarrtypes, name2) - } else if typ == "slice" { - name := getexprs(callexpr.Args[0]) - name2 := getexprs(callexpr.Args[1]) - slicenamemap[name] = name2 - } - } - - lenstmts = append(lenstmts, addn(8)...) - unmarstmts = append(unmarstmts, simplecall("(&self.AtomPos).setPos", "offset", "len(b)")) - unmarstmts = append(unmarstmts, addn(8)...) - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - - name := "" - if len(callexpr.Args) > 0 { - name = getexprs(callexpr.Args[0]) - } - - name2 := "" - if len(callexpr.Args) > 1 { - name2 = getexprs(callexpr.Args[1]) - } - - var defmarstmts, deflenstmts, defunmarstmts, defchildrenstmts []ast.Stmt - getdefaultstmts(typ, name, name2, - &defmarstmts, &deflenstmts, &defunmarstmts, &defchildrenstmts) - - var code []ast.Expr - for _, arg := range callexpr.Args { - if fn, ok := arg.(*ast.CallExpr); ok { - if getexprs(fn.Fun) == "_code" { - code = fn.Args - } - } - } - if code != nil { - appendcode(code, - &marstmts, &lenstmts, &unmarstmts, - defmarstmts, deflenstmts, defunmarstmts, - ) - continue - } - - if strings.HasPrefix(typ, "_") { - if typ == "_unknowns" { - marstmts = append(marstmts, foreachunknowns(callmarshal("atom"))...) - lenstmts = append(lenstmts, foreachunknowns(calllen("atom"))...) - childrenstmts = append(childrenstmts, simpleassign(token.ASSIGN, "r", "append(r, self.Unknowns...)")) - } - if typ == "_skip" { - marstmts = append(marstmts, addns(name)...) - lenstmts = append(lenstmts, addns(name)...) - unmarstmts = append(unmarstmts, addns(name)...) - } - if typ == "_code" { - appendcode(callexpr.Args, - &marstmts, &lenstmts, &unmarstmts, - defmarstmts, deflenstmts, defunmarstmts, - ) - } - continue - } - - if name == "_childrenNR" { - marstmts = append(marstmts, getchildrennr(name)...) - marstmts = append(marstmts, putxxadd(typ, name, true)...) - lenstmts = append(lenstmts, addn(typegetlen(typ))...) - unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) - continue - } - - if strings.HasPrefix(name, "_len_") { - field := name[len("_len_"):] - marstmts = append(marstmts, putxxadd(typ, "len(self."+field+")", true)...) - lenstmts = append(lenstmts, addn(typegetlen(typ))...) - unmarstmts = append(unmarstmts, declvar(typegetvartype(typ), name)...) - unmarstmts = append(unmarstmts, getxx(typ, "n", name, false)...) - unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) - unmarstmts = append(unmarstmts, makeslice("self."+field, slicenamemap[field], name)...) - continue - } - - marstmts = append(marstmts, defmarstmts...) - lenstmts = append(lenstmts, deflenstmts...) - unmarstmts = append(unmarstmts, defunmarstmts...) - childrenstmts = append(childrenstmts, defchildrenstmts...) - } - - if len(atomnames) > 0 || len(atomarrnames) > 0 || hasunknowns { - unmarstmts = append(unmarstmts, unmrashalatoms()...) - } - - marstmts = append(marstmts, &ast.ReturnStmt{}) - lenstmts = append(lenstmts, &ast.ReturnStmt{}) - unmarstmts = append(unmarstmts, &ast.ReturnStmt{}) - childrenstmts = append(childrenstmts, &ast.ReturnStmt{}) - - decls = append(decls, newdecl(origname, "Marshal", []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - }, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - }, marshalwrapstmts())) - - decls = append(decls, newdecl(origname, "marshal", []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - }, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - }, marstmts)) - - decls = append(decls, newdecl(origname, "Len", []*ast.Field{}, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - }, lenstmts)) - - decls = append(decls, newdecl("*"+origname, "Unmarshal", []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - &ast.Field{Names: []*ast.Ident{ast.NewIdent("offset")}, Type: ast.NewIdent("int")}, - }, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - &ast.Field{Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}, - }, unmarstmts)) - - decls = append(decls, newdecl(origname, "Children", []*ast.Field{}, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("r")}, Type: ast.NewIdent("[]Atom")}, - }, childrenstmts)) - - return -} - -func genatoms(filename, outfilename string) { - // Create the AST by parsing src. - fset := token.NewFileSet() // positions are relative to fset - file, err := parser.ParseFile(fset, filename, nil, 0) - if err != nil { - panic(err) - } - - gen := &ast.File{} - gen.Name = ast.NewIdent("mp4io") - gen.Decls = []ast.Decl{ - &ast.GenDecl{ - Tok: token.IMPORT, - Specs: []ast.Spec{ - &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/kerberos-io/joy4/utils/bits/pio"`}}, - }, - }, - &ast.GenDecl{ - Tok: token.IMPORT, - Specs: []ast.Spec{ - &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"time"`}}, - }, - }, - } - - tagnamemap := map[string]string{} - tagnamemap["ElemStreamDesc"] = "esds" - - splittagname := func(fnname string) (ok bool, tag, name string) { - if len(fnname) > 5 && fnname[4] == '_' { - tag = fnname[0:4] - tag = strings.Replace(tag, "_", " ", 1) - name = fnname[5:] - ok = true - } else { - name = fnname - } - return - } - - for _, decl := range file.Decls { - if fndecl, ok := decl.(*ast.FuncDecl); ok { - ok, tag, name := splittagname(fndecl.Name.Name) - if ok { - tagnamemap[name] = tag - } - } - } - - tagfuncdecl := func(name, tag string) (decls ast.Decl) { - return newdecl(name, "Tag", []*ast.Field{}, []*ast.Field{ - &ast.Field{Type: ast.NewIdent("Tag")}, - }, []ast.Stmt{ - &ast.ReturnStmt{ - Results: []ast.Expr{ast.NewIdent(strings.ToUpper(tag))}}}) - } - - for k, v := range tagnamemap { - gen.Decls = append(gen.Decls, cc4decls(v)...) - gen.Decls = append(gen.Decls, tagfuncdecl(k, v)) - } - gen.Decls = append(gen.Decls, cc4decls("mdat")...) - - for _, decl := range file.Decls { - if fndecl, ok := decl.(*ast.FuncDecl); ok { - ok, tag, name := splittagname(fndecl.Name.Name) - if ok { - gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) - gen.Decls = append(gen.Decls, getatommarshalfn(fndecl, name, tag, tagnamemap)...) - } else { - gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) - gen.Decls = append(gen.Decls, getstructputgetlenfn(fndecl, name)...) - } - } - } - - outfile, _ := os.Create(outfilename) - printer.Fprint(outfile, fset, gen) - outfile.Close() -} - -func parse(filename, outfilename string) { - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, filename, nil, 0) - if err != nil { - panic(err) - } - outfile, _ := os.Create(outfilename) - ast.Fprint(outfile, fset, file, nil) - outfile.Close() -} - -func main() { - switch os.Args[1] { - case "parse": - parse(os.Args[2], os.Args[3]) - - case "gen": - genatoms(os.Args[2], os.Args[3]) - } -} - diff --git a/machinery/src/fmp4/mp4io/gen/pattern.go b/machinery/src/fmp4/mp4io/gen/pattern.go deleted file mode 100644 index e039d8ee..00000000 --- a/machinery/src/fmp4/mp4io/gen/pattern.go +++ /dev/null @@ -1,438 +0,0 @@ -package main - -import "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" - -func moov_Movie() { - atom(Header, MovieHeader) - atom(MovieExtend, MovieExtend) - atoms(Tracks, Track) - _unknowns() -} - -func mvhd_MovieHeader() { - uint8(Version) - uint24(Flags) - time32(CreateTime) - time32(ModifyTime) - int32(TimeScale) - int32(Duration) - fixed32(PreferredRate) - fixed16(PreferredVolume) - _skip(10) - array(Matrix, int32, 9) - time32(PreviewTime) - time32(PreviewDuration) - time32(PosterTime) - time32(SelectionTime) - time32(SelectionDuration) - time32(CurrentTime) - int32(NextTrackId) -} - -func trak_Track() { - atom(Header, TrackHeader) - atom(Media, Media) - _unknowns() -} - -func tkhd_TrackHeader() { - uint8(Version) - uint24(Flags) - time32(CreateTime) - time32(ModifyTime) - int32(TrackId) - _skip(4) - int32(Duration) - _skip(8) - int16(Layer) - int16(AlternateGroup) - fixed16(Volume) - _skip(2) - array(Matrix, int32, 9) - fixed32(TrackWidth) - fixed32(TrackHeight) -} - -func hdlr_HandlerRefer() { - uint8(Version) - uint24(Flags) - bytes(Type, 4) - bytes(SubType, 4) - bytesleft(Name) -} - -func mdia_Media() { - atom(Header, MediaHeader) - atom(Handler, HandlerRefer) - atom(Info, MediaInfo) - _unknowns() -} - -func mdhd_MediaHeader() { - uint8(Version) - uint24(Flags) - time32(CreateTime) - time32(ModifyTime) - int32(TimeScale) - int32(Duration) - int16(Language) - int16(Quality) -} - -func minf_MediaInfo() { - atom(Sound, SoundMediaInfo) - atom(Video, VideoMediaInfo) - atom(Data, DataInfo) - atom(Sample, SampleTable) - _unknowns() -} - -func dinf_DataInfo() { - atom(Refer, DataRefer) - _unknowns() -} - -func dref_DataRefer() { - uint8(Version) - uint24(Flags) - int32(_childrenNR) - atom(Url, DataReferUrl) -} - -func url__DataReferUrl() { - uint8(Version) - uint24(Flags) -} - -func smhd_SoundMediaInfo() { - uint8(Version) - uint24(Flags) - int16(Balance) - _skip(2) -} - -func vmhd_VideoMediaInfo() { - uint8(Version) - uint24(Flags) - int16(GraphicsMode) - array(Opcolor, int16, 3) -} - -func stbl_SampleTable() { - atom(SampleDesc, SampleDesc) - atom(TimeToSample, TimeToSample) - atom(CompositionOffset, CompositionOffset) - atom(SampleToChunk, SampleToChunk) - atom(SyncSample, SyncSample) - atom(ChunkOffset, ChunkOffset) - atom(SampleSize, SampleSize) -} - -func stsd_SampleDesc() { - uint8(Version) - _skip(3) - int32(_childrenNR) - atom(AVC1Desc, AVC1Desc) - atom(MP4ADesc, MP4ADesc) - _unknowns() -} - -func mp4a_MP4ADesc() { - _skip(6) - int16(DataRefIdx) - int16(Version) - int16(RevisionLevel) - int32(Vendor) - int16(NumberOfChannels) - int16(SampleSize) - int16(CompressionId) - _skip(2) - fixed32(SampleRate) - atom(Conf, ElemStreamDesc) - _unknowns() -} - -func avc1_AVC1Desc() { - _skip(6) - int16(DataRefIdx) - int16(Version) - int16(Revision) - int32(Vendor) - int32(TemporalQuality) - int32(SpatialQuality) - int16(Width) - int16(Height) - fixed32(HorizontalResolution) - fixed32(VorizontalResolution) - _skip(4) - int16(FrameCount) - bytes(CompressorName, 32) - int16(Depth) - int16(ColorTableId) - atom(Conf, AVC1Conf) - _unknowns() -} - -func avcC_AVC1Conf() { - bytesleft(Data) -} - -func stts_TimeToSample() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, TimeToSampleEntry) -} - -func TimeToSampleEntry() { - uint32(Count) - uint32(Duration) -} - -func stsc_SampleToChunk() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, SampleToChunkEntry) -} - -func SampleToChunkEntry() { - uint32(FirstChunk) - uint32(SamplesPerChunk) - uint32(SampleDescId) -} - -func ctts_CompositionOffset() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, CompositionOffsetEntry) -} - -func CompositionOffsetEntry() { - uint32(Count) - uint32(Offset) -} - -func stss_SyncSample() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, uint32) -} - -func stco_ChunkOffset() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, uint32) -} - -func moof_MovieFrag() { - atom(Header, MovieFragHeader) - atoms(Tracks, TrackFrag) - _unknowns() -} - -func mfhd_MovieFragHeader() { - uint8(Version) - uint24(Flags) - uint32(Seqnum) -} - -func traf_TrackFrag() { - atom(Header, TrackFragHeader) - atom(DecodeTime, TrackFragDecodeTime) - atom(Run, TrackFragRun) - _unknowns() -} - -func mvex_MovieExtend() { - atoms(Tracks, TrackExtend) - _unknowns() -} - -func trex_TrackExtend() { - uint8(Version) - uint24(Flags) - uint32(TrackId) - uint32(DefaultSampleDescIdx) - uint32(DefaultSampleDuration) - uint32(DefaultSampleSize) - uint32(DefaultSampleFlags) -} - -func stsz_SampleSize() { - uint8(Version) - uint24(Flags) - uint32(SampleSize) - _code(func() { - if self.SampleSize != 0 { - return - } - }) - uint32(_len_Entries) - slice(Entries, uint32) -} - -func trun_TrackFragRun() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - - uint32(DataOffset, _code(func() { - if self.Flags&TRUN_DATA_OFFSET != 0 { - doit() - } - })) - - uint32(FirstSampleFlags, _code(func() { - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - doit() - } - })) - - slice(Entries, TrackFragRunEntry, _code(func() { - for i, entry := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - if flags&TRUN_SAMPLE_DURATION != 0 { - pio.PutU32BE(b[n:], entry.Duration) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - pio.PutU32BE(b[n:], entry.Size) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - pio.PutU32BE(b[n:], entry.Flags) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - pio.PutU32BE(b[n:], entry.Cts) - n += 4 - } - } - }, func() { - for i := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - if flags&TRUN_SAMPLE_DURATION != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - n += 4 - } - } - }, func() { - for i := 0; i < int(_len_Entries); i++ { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - entry := &self.Entries[i] - if flags&TRUN_SAMPLE_DURATION != 0 { - entry.Duration = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - entry.Size = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - entry.Flags = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - entry.Cts = pio.U32BE(b[n:]) - n += 4 - } - } - })) -} - -func TrackFragRunEntry() { - uint32(Duration) - uint32(Size) - uint32(Flags) - uint32(Cts) -} - -func tfhd_TrackFragHeader() { - uint8(Version) - uint24(Flags) - - uint64(BaseDataOffset, _code(func() { - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - doit() - } - })) - - uint32(StsdId, _code(func() { - if self.Flags&TFHD_STSD_ID != 0 { - doit() - } - })) - - uint32(DefaultDuration, _code(func() { - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - doit() - } - })) - - uint32(DefaultSize, _code(func() { - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - doit() - } - })) - - uint32(DefaultFlags, _code(func() { - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - doit() - } - })) -} - -func tfdt_TrackFragDecodeTime() { - uint8(Version) - uint24(Flags) - time64(Time, _code(func() { - if self.Version != 0 { - PutTime64(b[n:], self.Time) - n += 8 - } else { - PutTime32(b[n:], self.Time) - n += 4 - } - }, func() { - if self.Version != 0 { - n += 8 - } else { - n += 4 - } - }, func() { - if self.Version != 0 { - self.Time = GetTime64(b[n:]) - n += 8 - } else { - self.Time = GetTime32(b[n:]) - n += 4 - } - })) -} diff --git a/machinery/src/fmp4/mp4io/mp4io.go b/machinery/src/fmp4/mp4io/mp4io.go deleted file mode 100644 index 432ff1dd..00000000 --- a/machinery/src/fmp4/mp4io/mp4io.go +++ /dev/null @@ -1,535 +0,0 @@ -package mp4io - -import ( - "fmt" - "io" - "math" - "os" - "strings" - "time" - - "github.com/kerberos-io/joy4/utils/bits/pio" -) - -type ParseError struct { - Debug string - Offset int - prev *ParseError -} - -func (self *ParseError) Error() string { - s := []string{} - for p := self; p != nil; p = p.prev { - s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset)) - } - return "mp4io: parse error: " + strings.Join(s, ",") -} - -func parseErr(debug string, offset int, prev error) (err error) { - _prev, _ := prev.(*ParseError) - return &ParseError{Debug: debug, Offset: offset, prev: _prev} -} - -func GetTime32(b []byte) (t time.Time) { - sec := pio.U32BE(b) - t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) - t = t.Add(time.Second * time.Duration(sec)) - return -} - -func PutTime32(b []byte, t time.Time) { - dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) - sec := uint32(dur / time.Second) - pio.PutU32BE(b, sec) -} - -func GetTime64(b []byte) (t time.Time) { - sec := pio.U64BE(b) - t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) - t = t.Add(time.Second * time.Duration(sec)) - return -} - -func PutTime64(b []byte, t time.Time) { - dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) - sec := uint64(dur / time.Millisecond) - pio.PutU64BE(b, sec) -} - -func PutFixed16(b []byte, f float64) { - intpart, fracpart := math.Modf(f) - b[0] = uint8(intpart) - b[1] = uint8(fracpart * 256.0) -} - -func GetFixed16(b []byte) float64 { - return float64(b[0]) + float64(b[1])/256.0 -} - -func PutFixed32(b []byte, f float64) { - intpart, fracpart := math.Modf(f) - pio.PutU16BE(b[0:2], uint16(intpart)) - pio.PutU16BE(b[2:4], uint16(fracpart*65536.0)) -} - -func GetFixed32(b []byte) float64 { - return float64(pio.U16BE(b[0:2])) + float64(pio.U16BE(b[2:4]))/65536.0 -} - -type Tag uint32 - -func (self Tag) String() string { - var b [4]byte - pio.PutU32BE(b[:], uint32(self)) - for i := 0; i < 4; i++ { - if b[i] == 0 { - b[i] = ' ' - } - } - return string(b[:]) -} - -type Atom interface { - Pos() (int, int) - Tag() Tag - Marshal([]byte) int - Unmarshal([]byte, int) (int, error) - Len() int - Children() []Atom -} - -type AtomPos struct { - Offset int - Size int -} - -func (self AtomPos) Pos() (int, int) { - return self.Offset, self.Size -} - -func (self *AtomPos) setPos(offset int, size int) { - self.Offset, self.Size = offset, size -} - -type Dummy struct { - Data []byte - Tag_ Tag - AtomPos -} - -func (self Dummy) Children() []Atom { - return nil -} - -func (self Dummy) Tag() Tag { - return self.Tag_ -} - -func (self Dummy) Len() int { - return len(self.Data) -} - -func (self Dummy) Marshal(b []byte) int { - copy(b, self.Data) - return len(self.Data) -} - -func (self *Dummy) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - self.Data = b - n = len(b) - return -} - -func StringToTag(tag string) Tag { - var b [4]byte - copy(b[:], []byte(tag)) - return Tag(pio.U32BE(b[:])) -} - -func FindChildrenByName(root Atom, tag string) Atom { - return FindChildren(root, StringToTag(tag)) -} - -func FindChildren(root Atom, tag Tag) Atom { - if root.Tag() == tag { - return root - } - for _, child := range root.Children() { - if r := FindChildren(child, tag); r != nil { - return r - } - } - return nil -} - -const ( - TFHD_BASE_DATA_OFFSET = 0x01 - TFHD_STSD_ID = 0x02 - TFHD_DEFAULT_DURATION = 0x08 - TFHD_DEFAULT_SIZE = 0x10 - TFHD_DEFAULT_FLAGS = 0x20 - TFHD_DURATION_IS_EMPTY = 0x010000 - TFHD_DEFAULT_BASE_IS_MOOF = 0x020000 -) - -const ( - TRUN_DATA_OFFSET = 0x01 - TRUN_FIRST_SAMPLE_FLAGS = 0x04 - TRUN_SAMPLE_DURATION = 0x100 - TRUN_SAMPLE_SIZE = 0x200 - TRUN_SAMPLE_FLAGS = 0x400 - TRUN_SAMPLE_CTS = 0x800 -) - -const ( - MP4ESDescrTag = 3 - MP4DecConfigDescrTag = 4 - MP4DecSpecificDescrTag = 5 - MP4DescrTag = 6 -) - -const ( - MP4DecConfigDataSize = 2 + 3 + 4 + 4 -) - -type ElemStreamDesc struct { - DecConfig []byte - TrackId uint16 - AtomPos -} - -func (self ElemStreamDesc) Children() []Atom { - return nil -} - -func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) { - for i := 3; i > 0; i-- { - b[n] = uint8(length>>uint(7*i))&0x7f | 0x80 - n++ - } - b[n] = uint8(length & 0x7f) - n++ - return -} - -func (self ElemStreamDesc) lenDescHdr() (n int) { - return 5 -} - -func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) { - b[n] = tag - n++ - n += self.fillLength(b[n:], datalen) - return -} - -func (self ElemStreamDesc) lenESDescHdr() (n int) { - return self.lenDescHdr() -} - -func (self ElemStreamDesc) lenESDescData() (n int) { - return 3 -} - -func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) { - n += self.fillDescHdr(b[n:], MP4ESDescrTag, datalen) - pio.PutU16BE(b[n:], self.TrackId) - n += 2 - b[n] = 0 // flags - n++ - return -} - -func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) { - return self.lenDescHdr() + MP4DecConfigDataSize + self.lenDescHdr() -} - -func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) { - n += self.fillDescHdr(b[n:], MP4DecConfigDescrTag, datalen) - b[n] = 0x40 // objectid - n++ - b[n] = 0x15 // streamtype - n++ - // buffer size db - pio.PutU24BE(b[n:], 0) - n += 3 - // max bitrage - pio.PutU32BE(b[n:], uint32(200000)) - n += 4 - // avg bitrage - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - n += self.fillDescHdr(b[n:], MP4DecSpecificDescrTag, datalen-n) - return -} - -func (self ElemStreamDesc) Len() (n int) { - n += 8 - n += 4 - // 0x03 MP4ESDescHeader - // 5 - n += self.lenESDescHdr() - - // + ESID + ESFlags - // + 2 + 1 - n += self.lenESDescData() - - // 0x04 MP4DecConfigDescrTag + MP4DecConfigDataSize + 0x05 MP4DecSpecificDescrHeader - // 5 + 13 + 5 - n += self.lenDecConfigDescHdr() - - // Variable size configuration - n += len(self.DecConfig) - - // 0x06 MP4DescrHeader + 1 - // 5 + 1 - n += self.lenDescHdr() + 1 - - return // 8 + 4 + self.lenESDescHdr() + self.lenDecConfigDescHdr() + len(self.DecConfig) + self.lenDescHdr() + 1 -} - -// Version(4) -// ESDesc( -// MP4ESDescrTag -// ESID(2) -// ESFlags(1) -// DecConfigDesc( -// MP4DecConfigDescrTag -// objectId streamType bufSize avgBitrate -// DecSpecificDesc( -// MP4DecSpecificDescrTag -// decConfig -// ) -// ) -// ?Desc(lenDescHdr+1) -// ) - -func (self ElemStreamDesc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(ESDS)) - n += 8 - pio.PutU32BE(b[n:], 0) // Version - n += 4 - datalen := self.Len() - n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr()) - - n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-self.lenDescHdr()-1) - copy(b[n:], self.DecConfig) - n += len(self.DecConfig) - - n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr()) - b[n] = 0x02 - n++ - pio.PutU32BE(b[0:], uint32(n)) - return -} - -func (self *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) { - if len(b) < n+12 { - err = parseErr("hdr", offset+n, err) - return - } - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - n += 4 - return self.parseDesc(b[n:], offset+n) -} - -func (self *ElemStreamDesc) parseDesc(b []byte, offset int) (n int, err error) { - var hdrlen int - var datalen int - var tag uint8 - if hdrlen, tag, datalen, err = self.parseDescHdr(b, offset); err != nil { - return - } - - // Skip over the header lenth (tag size 1 byte + lenlen) - n += hdrlen - - if len(b) < n+datalen { - err = parseErr("datalen", offset+n, err) - return - } - - switch tag { - case MP4ESDescrTag: - if len(b) < n+3 { - err = parseErr("MP4ESDescrTag", offset+n, err) - return - } - if _, err = self.parseDesc(b[n+3:], offset+n+3); err != nil { - return - } - - case MP4DecConfigDescrTag: - if len(b) < n+MP4DecConfigDataSize { - err = parseErr("MP4DecSpecificDescrTag", offset+n, err) - return - } - if _, err = self.parseDesc(b[n+MP4DecConfigDataSize:], offset+n+MP4DecConfigDataSize); err != nil { - return - } - - case MP4DecSpecificDescrTag: - self.DecConfig = b[n : n+datalen] - } - - n += datalen - return -} - -func (self *ElemStreamDesc) parseLength(b []byte, offset int) (n int, length int, err error) { - for n < 4 { - if len(b) < n+1 { - err = parseErr("len", offset+n, err) - return - } - c := b[n] - n++ - length = (length << 7) | (int(c) & 0x7f) - if c&0x80 == 0 { - break - } - } - return -} - -func (self *ElemStreamDesc) parseDescHdr(b []byte, offset int) (n int, tag uint8, datalen int, err error) { - var lenlen int - if len(b) < n+1 { - err = parseErr("tag", offset+n, err) - return - } - tag = b[n] - n++ - if lenlen, datalen, err = self.parseLength(b[n:], offset+n); err != nil { - return - } - n += lenlen - return -} - -func ReadFileAtoms(r io.ReadSeeker) (atoms []Atom, err error) { - for { - offset, _ := r.Seek(0, 1) - taghdr := make([]byte, 8) - if _, err = io.ReadFull(r, taghdr); err != nil { - if err == io.EOF { - err = nil - } - return - } - size := pio.U32BE(taghdr[0:]) - tag := Tag(pio.U32BE(taghdr[4:])) - - var atom Atom - switch tag { - case MOOV: - atom = &Movie{} - case MOOF: - atom = &MovieFrag{} - } - - if atom != nil { - b := make([]byte, int(size)) - if _, err = io.ReadFull(r, b[8:]); err != nil { - return - } - copy(b, taghdr) - if _, err = atom.Unmarshal(b, int(offset)); err != nil { - return - } - atoms = append(atoms, atom) - } else { - dummy := &Dummy{Tag_: tag} - dummy.setPos(int(offset), int(size)) - if _, err = r.Seek(int64(size)-8, 1); err != nil { - return - } - atoms = append(atoms, dummy) - } - } - return -} - -func printatom(out io.Writer, root Atom, depth int) { - offset, size := root.Pos() - - type stringintf interface { - String() string - } - - fmt.Fprintf(out, - "%s%s offset=%d size=%d", - strings.Repeat(" ", depth*2), root.Tag(), offset, size, - ) - if str, ok := root.(stringintf); ok { - fmt.Fprint(out, " ", str.String()) - } - fmt.Fprintln(out) - - children := root.Children() - for _, child := range children { - printatom(out, child, depth+1) - } -} - -func FprintAtom(out io.Writer, root Atom) { - printatom(out, root, 0) -} - -func PrintAtom(root Atom) { - FprintAtom(os.Stdout, root) -} - -func (self MovieHeader) String() string { - return fmt.Sprintf("dur=%d", self.Duration) -} - -func (self TimeToSample) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self SampleToChunk) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self SampleSize) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self SyncSample) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self CompositionOffset) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self ChunkOffset) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self TrackFragRun) String() string { - return fmt.Sprintf("dataoffset=%d", self.DataOffset) -} - -func (self TrackFragHeader) String() string { - return fmt.Sprintf("basedataoffset=%d", self.BaseDataOffset) -} - -func (self ElemStreamDesc) String() string { - return fmt.Sprintf("configlen=%d", len(self.DecConfig)) -} - -func (self *Track) GetAVC1Conf() (conf *AVC1Conf) { - atom := FindChildren(self, AVCC) - conf, _ = atom.(*AVC1Conf) - return -} - -func (self *Track) GetElemStreamDesc() (esds *ElemStreamDesc) { - atom := FindChildren(self, ESDS) - esds, _ = atom.(*ElemStreamDesc) - return -} diff --git a/machinery/src/fmp4/muxer.go b/machinery/src/fmp4/muxer.go deleted file mode 100644 index ed53b9e9..00000000 --- a/machinery/src/fmp4/muxer.go +++ /dev/null @@ -1,568 +0,0 @@ -package fmp4 - -import ( - "bufio" - "fmt" - "io" - "time" - - "github.com/kerberos-io/agent/machinery/src/fmp4/mp4io" - "github.com/kerberos-io/agent/machinery/src/packets" - "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" -) - -type Muxer struct { - w io.WriteSeeker - bufw *bufio.Writer - wpos int64 - streams []*Stream - videoCodecIndex int - AudioCodecIndex int - - moof_seqnum uint32 - // Streams must start with Keyframes / IDRs - // A keyframe is a complete sample that contains all information to produce a single image. - // All other samples are deltas w.r.t to the last keyframe that's why MP4's must - // always start with a keyframe because any other type of frame will have not point of reference. - // It does mean we lose some data but it was useless anyways. - // This is on the muxer & not on an individual stream to prevent audio (pre first keyframe) of making - // it into our MP4 essentially delaying the audio by a few seconds perhaps (depends on keyframe interval). - gotFirstKeyframe bool -} - -func NewMuxer(w io.WriteSeeker) *Muxer { - return &Muxer{ - w: w, - bufw: bufio.NewWriterSize(w, pio.RecommendBufioSize), - } -} - -func (self *Muxer) newStream(codec packets.Stream, index int, withoutAudio bool) (err error) { - - switch codec.Name { - case "H264": - self.videoCodecIndex = index - case "AAC": - default: - self.AudioCodecIndex = index - if withoutAudio { - return - } - } - - stream := &Stream{ - CodecData: codec, - Idx: index, - } - - stream.sample = &mp4io.SampleTable{ - SampleDesc: &mp4io.SampleDesc{}, - TimeToSample: &mp4io.TimeToSample{}, - SampleToChunk: &mp4io.SampleToChunk{}, - SampleSize: &mp4io.SampleSize{}, - ChunkOffset: &mp4io.ChunkOffset{}, - } - - stream.trackAtom = &mp4io.Track{ - Header: &mp4io.TrackHeader{ - TrackId: int32(len(self.streams) + 1), - Flags: 0x0003, // Track enabled | Track in movie - Duration: 0, // fill later - Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - }, - Media: &mp4io.Media{ - Header: &mp4io.MediaHeader{ - TimeScale: 0, // fill later - Duration: 0, // fill later - Language: 21956, - }, - Info: &mp4io.MediaInfo{ - Sample: stream.sample, - Data: &mp4io.DataInfo{ - Refer: &mp4io.DataRefer{ - Url: &mp4io.DataReferUrl{ - Flags: 0x000001, // Self reference - }, - }, - }, - }, - }, - } - - switch codec.Name { - case "H264": - stream.sample.SyncSample = &mp4io.SyncSample{} - } - - stream.timeScale = 12288 // 90000 // - stream.muxer = self - self.streams = append(self.streams, stream) - - return -} - -func (self *Stream) fillTrackAtom() (err error) { - self.trackAtom.Media.Header.TimeScale = int32(self.timeScale) - self.trackAtom.Media.Header.Duration = int32(self.duration) - - if self.CodecData.Name == "H264" { - - codec := self.CodecData - width, height := codec.Width, codec.Height - decoderData := []byte{} - - recordinfo := AVCDecoderConfRecord{} - if len(self.CodecData.SPS) > 0 { - recordinfo.AVCProfileIndication = self.CodecData.SPS[1] - recordinfo.ProfileCompatibility = self.CodecData.SPS[2] - recordinfo.AVCLevelIndication = self.CodecData.SPS[3] - recordinfo.SPS = [][]byte{self.CodecData.SPS} - - } - if len(self.CodecData.PPS) > 0 { - recordinfo.PPS = [][]byte{self.CodecData.PPS} - } - recordinfo.LengthSizeMinusOne = 3 // check... - - buf := make([]byte, recordinfo.Len()) - recordinfo.Marshal(buf) - - self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{ - DataRefIdx: 1, - HorizontalResolution: 72, - VorizontalResolution: 72, - Width: int16(width), - Height: int16(height), - FrameCount: 1, - Depth: 24, - ColorTableId: -1, - Conf: &mp4io.AVC1Conf{Data: decoderData}, - } - self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ - SubType: [4]byte{'v', 'i', 'd', 'e'}, - Name: []byte("Video Media Handler"), - } - self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{ - Flags: 0x000001, - } - self.trackAtom.Header.TrackWidth = float64(width) - self.trackAtom.Header.TrackHeight = float64(height) - - } else if self.CodecData.Name == "AAC" { - /*codec := self.CodecData.(aacparser.CodecData) - audioConfig := codec.MPEG4AudioConfigBytes() - self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{ - DataRefIdx: 2, - NumberOfChannels: int16(codec.ChannelLayout().Count()), - SampleSize: int16(codec.SampleFormat().BytesPerSample()), - SampleRate: float64(codec.SampleRate()), - Conf: &mp4io.ElemStreamDesc{ - DecConfig: audioConfig, - }, - } - self.trackAtom.Header.Volume = 1 - self.trackAtom.Header.AlternateGroup = 1 - self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ - SubType: [4]byte{'s', 'o', 'u', 'n'}, - Name: []byte{'S', 'o', 'u', 'n', 'd', 'H', 'a', 'n', 'd', 'l', 'e', 'r', 0}, - } - self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}*/ - - } else { - err = fmt.Errorf("mp4: codec type=%d invalid", self.CodecData.Name) - } - - return -} - -func (self *Muxer) WriteHeader(streams []packets.Stream) (err error) { - self.streams = []*Stream{} - for i, stream := range streams { - if err = self.newStream(stream, i, false); err != nil { - // no need to stop the recording if a codec doesnt match, still try to... - } - } - /* - https://www.w3.org/2013/12/byte-stream-format-registry/isobmff-byte-stream-format.html#h2_iso-init-segments - The user agent must run the end of stream algorithm with the error parameter set to "decode" if any of the following conditions are met: - - A File Type Box contains a major_brand or compatible_brand that the user agent does not support. - - A box or field in the Movie Header Box is encountered that violates the requirements mandated by the major_brand or one of the compatible_brands in the File Type Box. - - The tracks in the Movie Header Box contain samples (i.e. the entry_count in the stts, stsc or stco boxes are not set to zero). - - A Movie Extends (mvex) box is not contained in the Movie (moov) box to indicate that Movie Fragments are to be expected. - */ - - moov := &mp4io.Movie{} - moov.Header = &mp4io.MovieHeader{ - PreferredRate: 1, - PreferredVolume: 1, - Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - NextTrackId: int32(len(self.streams)), // ffmpeg uses the last track id as the next track id, makes no sense - PreviewTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), - PreviewDuration: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), - PosterTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), - SelectionTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), - SelectionDuration: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), - CurrentTime: time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC), - } - - // Movie Extend MVEX is required for fragmented MP4s - trackExtends := make([]*mp4io.TrackExtend, 0) - for _, stream := range self.streams { - // Add an extension for every available track along with their track ids. - ext := &mp4io.TrackExtend{ - TrackId: uint32(stream.Idx), - DefaultSampleDescIdx: uint32(1), - } - trackExtends = append(trackExtends, ext) - } - moov.MovieExtend = &mp4io.MovieExtend{ - Tracks: trackExtends, - } - - // TODO(atom): write a parser of the User Data Box (udta) - - maxDur := time.Duration(0) - timeScale := int64(10000) - for _, stream := range self.streams { - if err = stream.fillTrackAtom(); err != nil { - return - } - dur := stream.tsToTime(stream.duration) - stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale)) - if dur > maxDur { - maxDur = dur - } - moov.Tracks = append(moov.Tracks, stream.trackAtom) - } - moov.Header.TimeScale = int32(timeScale) - moov.Header.Duration = int32(timeToTs(maxDur, timeScale)) - - b := make([]byte, moov.Len()) - moov.Marshal(b) - if _, err = self.w.Write(b); err != nil { - return - } - - return -} - -func (self *Stream) BuildTrackFragmentWithoutOffset() (trackFragment *mp4io.TrackFrag, err error) { - // new duration - newDts := self.dts - - // Create TrackFragRunEntries - trackfragentries := make([]mp4io.TrackFragRunEntry, 0) // https://ffmpeg.org/pipermail/ffmpeg-devel/2014-November/164898.html - // Loop over all the samples and build the Track Fragment Entries. - // Each sample gets its own entry which essentially captures the duration of the sample - // and the location within the MDAT relative by the size of the sample. - for _, pkt := range self.pkts { - - // Calculate the duration of the frame, if no previous frames were recorded then - // invent a timestamp (1ms) for it to make sure it's not 0. - var duration time.Duration - if self.lastpkt != nil { - duration = pkt.Time - self.lastpkt.Time - } - - // Increment the decode timestamp for the next Track Fragment Decode Time (TFDT) - // Essentially it is the combination of the durations of the samples. - newDts += self.timeToTs(duration) - - /* - if duration == 0 { - duration = 40 * time.Millisecond - }*/ - - // Audio tends to build very predictable packets due to its sampling rate - // A possible optimization would be to rely on the default flags instead. - // This requires looping over all packets and verifying the default size & duration.const - // Saves a few bytes for each trun entry and could be looked into.const - // Current behavior is to explicitly write all of the entries their size & duration. - entry := mp4io.TrackFragRunEntry{ - Duration: uint32(self.timeToTs(duration)), // The timescaled duration e.g 2999 - Size: uint32(len(pkt.Data)), // The length of the sample in bytes e.g 51677 - Flags: uint32(33554432), - Cts: uint32(self.timeToTs(pkt.CompositionTime)), // Composition timestamp is typically for B-frames, which are not used in RTSP - } - trackfragentries = append(trackfragentries, entry) - self.lastpkt = pkt - } - - // Build the Track Fragment - DefaultSampleFlags := uint32(0) - if self.CodecData.Name == "H264" { - DefaultSampleFlags = 16842752 - } else { - // audio - DefaultSampleFlags = 33554432 - } - - // If no fragment entries are available, then just set the durations to 512 - // TODO: demuxer bug for B-frames has the same dts - DefaultDuration := uint32(512) - - // Set the track frag flags such that they include the flag for CTS - trackFragRunFlags := uint32(mp4io.TRUN_DATA_OFFSET | mp4io.TRUN_FIRST_SAMPLE_FLAGS | mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_DURATION) - if self.hasBFrames { // TODO: add check if video track - //trackFragRunFlags = trackFragRunFlags | mp4io.TRUN_SAMPLE_CTS - trackFragRunFlags = uint32(mp4io.TRUN_DATA_OFFSET | mp4io.TRUN_FIRST_SAMPLE_FLAGS | mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_CTS) - // TODO: in ffmpeg this is 33554432 for video track & none if audio - } - - FirstSampleFlags := uint32(mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_DURATION) // mp4io.TRUN_DATA_OFFSET | mp4io.TRUN_FIRST_SAMPLE_FLAGS | - // The first packet is a b-frame so set the first sample flags to have a CTS. - if len(self.pkts) > 0 && self.pkts[0].CompositionTime > 0 { - FirstSampleFlags = uint32(mp4io.TRUN_SAMPLE_SIZE | mp4io.TRUN_SAMPLE_CTS) - } - - trackFragment = &mp4io.TrackFrag{ - Header: &mp4io.TrackFragHeader{ - Version: uint8(0), - Flags: uint32(mp4io.TFHD_DEFAULT_FLAGS | mp4io.TFHD_DEFAULT_DURATION | mp4io.TFHD_DEFAULT_BASE_IS_MOOF), // uint32(131128), - TrackId: uint32(self.Idx), - DefaultDuration: DefaultDuration, - DefaultFlags: DefaultSampleFlags, // TODO: fix to real flags - }, - DecodeTime: &mp4io.TrackFragDecodeTime{ - Version: uint8(1), // Decides whether 1 = 64bit, 0 = 32bit timestamp - Flags: uint32(0), - DecodeTime: uint64(self.dts), // Decode timestamp timescaled - }, - Run: &mp4io.TrackFragRun{ - Version: uint8(0), - Flags: trackFragRunFlags, // The flags if 0 then no DataOffset & no FirstSampleFlags - DataOffset: uint32(368), // NOTE: this is rewritten later - FirstSampleFlags: FirstSampleFlags, - Entries: trackfragentries, - }, - } - - // Set the next dts - newDts += self.timeToTs(1 * time.Millisecond) - self.dts = newDts - - // Reset hasBFrames - self.hasBFrames = false - - return -} - -func (self *Muxer) flushMoof() (err error) { - - // Build the Track Frags - trackFragments := make([]*mp4io.TrackFrag, 0) - for _, stream := range self.streams { - // Build the Track Frag for this stream - var trackFragment *mp4io.TrackFrag - trackFragment, err = stream.BuildTrackFragmentWithoutOffset() - if err != nil { - return - } - trackFragments = append(trackFragments, trackFragment) - } - - // Defer the clearing of the packets, we'll need them later in this function to - // write the MDAT contents & calculate its size. - defer func() { - for _, stream := range self.streams { - stream.pkts = make([]*packets.Packet, 0) - } - }() - - moof := &mp4io.MovieFrag{ - Header: &mp4io.MovieFragHeader{ - Version: uint8(0), - Flags: uint32(0), - Seqnum: self.moof_seqnum, - }, - Tracks: trackFragments, - } - - // Fix the dataoffsets of the track run - nextDataOffset := uint32(moof.Len() + 8) - for _, track := range moof.Tracks { - track.Run.DataOffset = nextDataOffset - for _, entry := range track.Run.Entries { - nextDataOffset += entry.Size - } - } - - // Write the MOOF - b := make([]byte, moof.Len()) - moof.Marshal(b) - if _, err = self.w.Write(b); err != nil { - return - } - b = nil - - // Write the MDAT size - mdatsize := uint32(8) // skip itself - for _, fragment := range trackFragments { - for _, entry := range fragment.Run.Entries { - mdatsize += entry.Size - } - } - - taghdr := make([]byte, 4) - pio.PutU32BE(taghdr, mdatsize) - if _, err = self.w.Write(taghdr); err != nil { - return - } - taghdr = nil - - // Write the MDAT header - taghdr = make([]byte, 4) - pio.PutU32BE(taghdr, uint32(mp4io.MDAT)) - if _, err = self.w.Write(taghdr); err != nil { - return - } - taghdr = nil - - // Write the MDAT contents - for _, stream := range self.streams { - for _, pkt := range stream.pkts { - if _, err = self.w.Write(pkt.Data); err != nil { - return - } - } - } - - // Increment the SeqNum - self.moof_seqnum++ - - return -} - -func (self *Muxer) Write(buffer []byte, channel int, time uint32) (err error) { - return nil -} - -func (self *Muxer) WritePacket(pkt packets.Packet) (err error) { - // Check if pkt.Idx is a valid stream - if len(self.streams) < int(pkt.Idx+1) { - return - } - stream := self.streams[pkt.Idx] - - // Wait until we have a video packet & it's a keyframe - if pkt.IsKeyFrame && !self.gotFirstKeyframe && stream.CodecData.IsVideo { - // First keyframe found, we can start processing - self.gotFirstKeyframe = true - } else if !self.gotFirstKeyframe { - // Skip all packets until keyframe first - return - } else if pkt.IsKeyFrame { - // At this point, we have a keyframe and had one before. - self.flushMoof() - } - - if err = stream.writePacket(pkt); err != nil { - return - } - - return -} - -func (self *Stream) writePacket(pkt packets.Packet /*, rawdur time.Duration*/) (err error) { - self.pkts = append(self.pkts, &pkt) - // Optimization: set the has B Frames boolean to indicate that there are B-Frames - // that require the TrackFragRun will require the CTS flags. - self.hasBFrames = self.hasBFrames || pkt.CompositionTime > 0 - return -} - -func (self *Muxer) WriteTrailer() (err error) { - self.bufw = nil - self.streams = nil - return -} - -func (self *Muxer) WriteTrailerWithPacket(pkt packets.Packet) (err error) { - // Check if pkt.Idx is a valid stream - if len(self.streams) < int(pkt.Idx+1) { - return - } - stream := self.streams[pkt.Idx] - - // Wait until we have a video packet & it's a keyframe - if pkt.IsKeyFrame && !self.gotFirstKeyframe && stream.CodecData.IsVideo { - // First keyframe found, we can start processing - self.gotFirstKeyframe = true - } else if !self.gotFirstKeyframe { - // Skip all packets until keyframe first - return - } else if pkt.IsKeyFrame { - // At this point, we have a keyframe and had one before. - self.flushMoof() - } - - if err = stream.writePacket(pkt); err != nil { - return - } - - self.bufw = nil - self.streams = nil - - return -} - -func (self *Muxer) Close() (err error) { - for _, stream := range self.streams { - stream.muxer = nil - stream.trackAtom = nil - stream.sample = nil - stream.lastpkt = nil - stream = nil - } - self.streams = nil - return -} - -type AVCDecoderConfRecord struct { - AVCProfileIndication uint8 - ProfileCompatibility uint8 - AVCLevelIndication uint8 - LengthSizeMinusOne uint8 - SPS [][]byte - PPS [][]byte -} - -func (self AVCDecoderConfRecord) Len() (n int) { - n = 7 - for _, sps := range self.SPS { - n += 2 + len(sps) - } - for _, pps := range self.PPS { - n += 2 + len(pps) - } - return -} - -func (self AVCDecoderConfRecord) Marshal(b []byte) (n int) { - b[0] = 1 - b[1] = self.AVCProfileIndication - b[2] = self.ProfileCompatibility - b[3] = self.AVCLevelIndication - b[4] = self.LengthSizeMinusOne | 0xfc - b[5] = uint8(len(self.SPS)) | 0xe0 - n += 6 - - for _, sps := range self.SPS { - pio.PutU16BE(b[n:], uint16(len(sps))) - n += 2 - copy(b[n:], sps) - n += len(sps) - } - - b[n] = uint8(len(self.PPS)) - n++ - - for _, pps := range self.PPS { - pio.PutU16BE(b[n:], uint16(len(pps))) - n += 2 - copy(b[n:], pps) - n += len(pps) - } - - return -} diff --git a/machinery/src/fmp4/stream.go b/machinery/src/fmp4/stream.go deleted file mode 100644 index 52f57b87..00000000 --- a/machinery/src/fmp4/stream.go +++ /dev/null @@ -1,45 +0,0 @@ -package fmp4 - -import ( - "time" - - "github.com/kerberos-io/agent/machinery/src/fmp4/mp4io" - "github.com/kerberos-io/agent/machinery/src/packets" -) - -type Stream struct { - CodecData packets.Stream - - trackAtom *mp4io.Track - Idx int - - // pkts to be used in MDAT and MOOF > TRAF > TRUN - lastpkt *packets.Packet - pkts []*packets.Packet - hasBFrames bool - - timeScale int64 - duration int64 - - muxer *Muxer - //demuxer *Demuxer - - sample *mp4io.SampleTable - dts int64 -} - -func timeToTs(tm time.Duration, timeScale int64) int64 { - return int64(tm * time.Duration(timeScale) / time.Second) -} - -func tsToTime(ts int64, timeScale int64) time.Duration { - return time.Duration(ts) * time.Second / time.Duration(timeScale) -} - -func (self *Stream) timeToTs(tm time.Duration) int64 { - return int64(tm * time.Duration(self.timeScale) / time.Second) -} - -func (self *Stream) tsToTime(ts int64) time.Duration { - return time.Duration(ts) * time.Second / time.Duration(self.timeScale) -} diff --git a/machinery/src/mp4/mp4io/atoms.go b/machinery/src/mp4/mp4io/atoms.go deleted file mode 100644 index e192f17b..00000000 --- a/machinery/src/mp4/mp4io/atoms.go +++ /dev/null @@ -1,3561 +0,0 @@ -package mp4io - -import ( - "time" - - "github.com/kerberos-io/joy4/utils/bits/pio" -) - -const MOOF = Tag(0x6d6f6f66) - -func (self MovieFrag) Tag() Tag { - return MOOF -} - -const HDLR = Tag(0x68646c72) - -func (self HandlerRefer) Tag() Tag { - return HDLR -} - -const AVC1 = Tag(0x61766331) - -func (self AVC1Desc) Tag() Tag { - return AVC1 -} - -const URL = Tag(0x75726c20) - -func (self DataReferUrl) Tag() Tag { - return URL -} - -const TREX = Tag(0x74726578) - -func (self TrackExtend) Tag() Tag { - return TREX -} - -const ESDS = Tag(0x65736473) - -func (self ElemStreamDesc) Tag() Tag { - return ESDS -} - -const MDHD = Tag(0x6d646864) - -func (self MediaHeader) Tag() Tag { - return MDHD -} - -const STTS = Tag(0x73747473) - -func (self TimeToSample) Tag() Tag { - return STTS -} - -const STSS = Tag(0x73747373) - -func (self SyncSample) Tag() Tag { - return STSS -} - -const MFHD = Tag(0x6d666864) - -func (self MovieFragHeader) Tag() Tag { - return MFHD -} - -const MVHD = Tag(0x6d766864) - -func (self MovieHeader) Tag() Tag { - return MVHD -} - -const MINF = Tag(0x6d696e66) - -func (self MediaInfo) Tag() Tag { - return MINF -} - -const MOOV = Tag(0x6d6f6f76) - -func (self Movie) Tag() Tag { - return MOOV -} - -const MVEX = Tag(0x6d766578) - -func (self MovieExtend) Tag() Tag { - return MVEX -} - -const STSD = Tag(0x73747364) - -func (self SampleDesc) Tag() Tag { - return STSD -} - -const MP4A = Tag(0x6d703461) - -func (self MP4ADesc) Tag() Tag { - return MP4A -} - -const CTTS = Tag(0x63747473) - -func (self CompositionOffset) Tag() Tag { - return CTTS -} - -const STCO = Tag(0x7374636f) - -func (self ChunkOffset) Tag() Tag { - return STCO -} - -const TRUN = Tag(0x7472756e) - -func (self TrackFragRun) Tag() Tag { - return TRUN -} - -const TRAK = Tag(0x7472616b) - -func (self Track) Tag() Tag { - return TRAK -} - -const MDIA = Tag(0x6d646961) - -func (self Media) Tag() Tag { - return MDIA -} - -const STSC = Tag(0x73747363) - -func (self SampleToChunk) Tag() Tag { - return STSC -} - -const VMHD = Tag(0x766d6864) - -func (self VideoMediaInfo) Tag() Tag { - return VMHD -} - -const STBL = Tag(0x7374626c) - -func (self SampleTable) Tag() Tag { - return STBL -} - -const AVCC = Tag(0x61766343) - -func (self AVC1Conf) Tag() Tag { - return AVCC -} - -const TFDT = Tag(0x74666474) - -func (self TrackFragDecodeTime) Tag() Tag { - return TFDT -} - -const DINF = Tag(0x64696e66) - -func (self DataInfo) Tag() Tag { - return DINF -} - -const DREF = Tag(0x64726566) - -func (self DataRefer) Tag() Tag { - return DREF -} - -const TRAF = Tag(0x74726166) - -func (self TrackFrag) Tag() Tag { - return TRAF -} - -const STSZ = Tag(0x7374737a) - -func (self SampleSize) Tag() Tag { - return STSZ -} - -const TFHD = Tag(0x74666864) - -func (self TrackFragHeader) Tag() Tag { - return TFHD -} - -const TKHD = Tag(0x746b6864) - -func (self TrackHeader) Tag() Tag { - return TKHD -} - -const SMHD = Tag(0x736d6864) - -func (self SoundMediaInfo) Tag() Tag { - return SMHD -} - -const MDAT = Tag(0x6d646174) - -type Movie struct { - Header *MovieHeader - MovieExtend *MovieExtend - Tracks []*Track - Unknowns []Atom - AtomPos -} - -func (self Movie) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MOOV)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self Movie) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.MovieExtend != nil { - n += self.MovieExtend.Marshal(b[n:]) - } - for _, atom := range self.Tracks { - n += atom.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self Movie) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.MovieExtend != nil { - n += self.MovieExtend.Len() - } - for _, atom := range self.Tracks { - n += atom.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *Movie) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case MVHD: - { - atom := &MovieHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mvhd", n+offset, err) - return - } - self.Header = atom - } - case MVEX: - { - atom := &MovieExtend{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mvex", n+offset, err) - return - } - self.MovieExtend = atom - } - case TRAK: - { - atom := &Track{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("trak", n+offset, err) - return - } - self.Tracks = append(self.Tracks, atom) - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self Movie) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.MovieExtend != nil { - r = append(r, self.MovieExtend) - } - for _, atom := range self.Tracks { - r = append(r, atom) - } - r = append(r, self.Unknowns...) - return -} - -type MovieHeader struct { - Version uint8 - Flags uint32 - CreateTime time.Time - ModifyTime time.Time - TimeScale int32 - Duration int32 - PreferredRate float64 - PreferredVolume float64 - Matrix [9]int32 - PreviewTime time.Time - PreviewDuration time.Time - PosterTime time.Time - SelectionTime time.Time - SelectionDuration time.Time - CurrentTime time.Time - NextTrackId int32 - AtomPos -} - -func (self MovieHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MVHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - PutTime32(b[n:], self.CreateTime) - n += 4 - PutTime32(b[n:], self.ModifyTime) - n += 4 - pio.PutI32BE(b[n:], self.TimeScale) - n += 4 - pio.PutI32BE(b[n:], self.Duration) - n += 4 - PutFixed32(b[n:], self.PreferredRate) - n += 4 - PutFixed16(b[n:], self.PreferredVolume) - n += 2 - n += 10 - for _, entry := range self.Matrix { - pio.PutI32BE(b[n:], entry) - n += 4 - } - PutTime32(b[n:], self.PreviewTime) - n += 4 - PutTime32(b[n:], self.PreviewDuration) - n += 4 - PutTime32(b[n:], self.PosterTime) - n += 4 - PutTime32(b[n:], self.SelectionTime) - n += 4 - PutTime32(b[n:], self.SelectionDuration) - n += 4 - PutTime32(b[n:], self.CurrentTime) - n += 4 - pio.PutI32BE(b[n:], self.NextTrackId) - n += 4 - return -} -func (self MovieHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 2 - n += 10 - n += 4 * len(self.Matrix[:]) - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - return -} -func (self *MovieHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("CreateTime", n+offset, err) - return - } - self.CreateTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("ModifyTime", n+offset, err) - return - } - self.ModifyTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TimeScale", n+offset, err) - return - } - self.TimeScale = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("Duration", n+offset, err) - return - } - self.Duration = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("PreferredRate", n+offset, err) - return - } - self.PreferredRate = GetFixed32(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("PreferredVolume", n+offset, err) - return - } - self.PreferredVolume = GetFixed16(b[n:]) - n += 2 - n += 10 - if len(b) < n+4*len(self.Matrix) { - err = parseErr("Matrix", n+offset, err) - return - } - for i := range self.Matrix { - self.Matrix[i] = pio.I32BE(b[n:]) - n += 4 - } - if len(b) < n+4 { - err = parseErr("PreviewTime", n+offset, err) - return - } - self.PreviewTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("PreviewDuration", n+offset, err) - return - } - self.PreviewDuration = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("PosterTime", n+offset, err) - return - } - self.PosterTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("SelectionTime", n+offset, err) - return - } - self.SelectionTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("SelectionDuration", n+offset, err) - return - } - self.SelectionDuration = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("CurrentTime", n+offset, err) - return - } - self.CurrentTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("NextTrackId", n+offset, err) - return - } - self.NextTrackId = pio.I32BE(b[n:]) - n += 4 - return -} -func (self MovieHeader) Children() (r []Atom) { - return -} - -type Track struct { - Header *TrackHeader - Media *Media - Unknowns []Atom - AtomPos -} - -func (self Track) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TRAK)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self Track) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.Media != nil { - n += self.Media.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self Track) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.Media != nil { - n += self.Media.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *Track) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case TKHD: - { - atom := &TrackHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("tkhd", n+offset, err) - return - } - self.Header = atom - } - case MDIA: - { - atom := &Media{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mdia", n+offset, err) - return - } - self.Media = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self Track) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.Media != nil { - r = append(r, self.Media) - } - r = append(r, self.Unknowns...) - return -} - -type TrackHeader struct { - Version uint8 - Flags uint32 - CreateTime time.Time - ModifyTime time.Time - TrackId int32 - Duration int32 - Layer int16 - AlternateGroup int16 - Volume float64 - Matrix [9]int32 - TrackWidth float64 - TrackHeight float64 - AtomPos -} - -func (self TrackHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TKHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - PutTime32(b[n:], self.CreateTime) - n += 4 - PutTime32(b[n:], self.ModifyTime) - n += 4 - pio.PutI32BE(b[n:], self.TrackId) - n += 4 - n += 4 - pio.PutI32BE(b[n:], self.Duration) - n += 4 - n += 8 - pio.PutI16BE(b[n:], self.Layer) - n += 2 - pio.PutI16BE(b[n:], self.AlternateGroup) - n += 2 - PutFixed16(b[n:], self.Volume) - n += 2 - n += 2 - for _, entry := range self.Matrix { - pio.PutI32BE(b[n:], entry) - n += 4 - } - PutFixed32(b[n:], self.TrackWidth) - n += 4 - PutFixed32(b[n:], self.TrackHeight) - n += 4 - return -} -func (self TrackHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - n += 8 - n += 2 - n += 2 - n += 2 - n += 2 - n += 4 * len(self.Matrix[:]) - n += 4 - n += 4 - return -} -func (self *TrackHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("CreateTime", n+offset, err) - return - } - self.CreateTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("ModifyTime", n+offset, err) - return - } - self.ModifyTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TrackId", n+offset, err) - return - } - self.TrackId = pio.I32BE(b[n:]) - n += 4 - n += 4 - if len(b) < n+4 { - err = parseErr("Duration", n+offset, err) - return - } - self.Duration = pio.I32BE(b[n:]) - n += 4 - n += 8 - if len(b) < n+2 { - err = parseErr("Layer", n+offset, err) - return - } - self.Layer = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("AlternateGroup", n+offset, err) - return - } - self.AlternateGroup = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Volume", n+offset, err) - return - } - self.Volume = GetFixed16(b[n:]) - n += 2 - n += 2 - if len(b) < n+4*len(self.Matrix) { - err = parseErr("Matrix", n+offset, err) - return - } - for i := range self.Matrix { - self.Matrix[i] = pio.I32BE(b[n:]) - n += 4 - } - if len(b) < n+4 { - err = parseErr("TrackWidth", n+offset, err) - return - } - self.TrackWidth = GetFixed32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TrackHeight", n+offset, err) - return - } - self.TrackHeight = GetFixed32(b[n:]) - n += 4 - return -} -func (self TrackHeader) Children() (r []Atom) { - return -} - -type HandlerRefer struct { - Version uint8 - Flags uint32 - Type [4]byte - SubType [4]byte - Name []byte - AtomPos -} - -func (self HandlerRefer) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(HDLR)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self HandlerRefer) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - copy(b[n:], self.Type[:]) - n += len(self.Type[:]) - copy(b[n:], self.SubType[:]) - n += len(self.SubType[:]) - // TODO: document component - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - copy(b[n:], self.Name[:]) - n += len(self.Name[:]) - return -} -func (self HandlerRefer) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += len(self.Type[:]) - n += len(self.SubType[:]) - // TODO: document component - n += 4 - n += 4 - n += 4 - n += len(self.Name[:]) - return -} -func (self *HandlerRefer) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+len(self.Type) { - err = parseErr("Type", n+offset, err) - return - } - copy(self.Type[:], b[n:]) - n += len(self.Type) - if len(b) < n+len(self.SubType) { - err = parseErr("SubType", n+offset, err) - return - } - copy(self.SubType[:], b[n:]) - n += len(self.SubType) - // TODO: document component - n += 4 - n += 4 - n += 4 - self.Name = b[n:] - n += len(b[n:]) - return -} -func (self HandlerRefer) Children() (r []Atom) { - return -} - -type Media struct { - Header *MediaHeader - Handler *HandlerRefer - Info *MediaInfo - Unknowns []Atom - AtomPos -} - -func (self Media) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MDIA)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self Media) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.Handler != nil { - n += self.Handler.Marshal(b[n:]) - } - if self.Info != nil { - n += self.Info.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self Media) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.Handler != nil { - n += self.Handler.Len() - } - if self.Info != nil { - n += self.Info.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *Media) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case MDHD: - { - atom := &MediaHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mdhd", n+offset, err) - return - } - self.Header = atom - } - case HDLR: - { - atom := &HandlerRefer{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("hdlr", n+offset, err) - return - } - self.Handler = atom - } - case MINF: - { - atom := &MediaInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("minf", n+offset, err) - return - } - self.Info = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self Media) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.Handler != nil { - r = append(r, self.Handler) - } - if self.Info != nil { - r = append(r, self.Info) - } - r = append(r, self.Unknowns...) - return -} - -type MediaHeader struct { - Version uint8 - Flags uint32 - CreateTime time.Time - ModifyTime time.Time - TimeScale int32 - Duration int32 - Language int16 - Quality int16 - AtomPos -} - -func (self MediaHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MDHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MediaHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - PutTime32(b[n:], self.CreateTime) - n += 4 - PutTime32(b[n:], self.ModifyTime) - n += 4 - pio.PutI32BE(b[n:], self.TimeScale) - n += 4 - pio.PutI32BE(b[n:], self.Duration) - n += 4 - pio.PutI16BE(b[n:], self.Language) - n += 2 - pio.PutI16BE(b[n:], self.Quality) - n += 2 - return -} -func (self MediaHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 2 - n += 2 - return -} -func (self *MediaHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("CreateTime", n+offset, err) - return - } - self.CreateTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("ModifyTime", n+offset, err) - return - } - self.ModifyTime = GetTime32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TimeScale", n+offset, err) - return - } - self.TimeScale = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("Duration", n+offset, err) - return - } - self.Duration = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("Language", n+offset, err) - return - } - self.Language = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Quality", n+offset, err) - return - } - self.Quality = pio.I16BE(b[n:]) - n += 2 - return -} -func (self MediaHeader) Children() (r []Atom) { - return -} - -type MediaInfo struct { - Sound *SoundMediaInfo - Video *VideoMediaInfo - Data *DataInfo - Sample *SampleTable - Unknowns []Atom - AtomPos -} - -func (self MediaInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MINF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MediaInfo) marshal(b []byte) (n int) { - if self.Sound != nil { - n += self.Sound.Marshal(b[n:]) - } - if self.Video != nil { - n += self.Video.Marshal(b[n:]) - } - if self.Data != nil { - n += self.Data.Marshal(b[n:]) - } - if self.Sample != nil { - n += self.Sample.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MediaInfo) Len() (n int) { - n += 8 - if self.Sound != nil { - n += self.Sound.Len() - } - if self.Video != nil { - n += self.Video.Len() - } - if self.Data != nil { - n += self.Data.Len() - } - if self.Sample != nil { - n += self.Sample.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case SMHD: - { - atom := &SoundMediaInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("smhd", n+offset, err) - return - } - self.Sound = atom - } - case VMHD: - { - atom := &VideoMediaInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("vmhd", n+offset, err) - return - } - self.Video = atom - } - case DINF: - { - atom := &DataInfo{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("dinf", n+offset, err) - return - } - self.Data = atom - } - case STBL: - { - atom := &SampleTable{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stbl", n+offset, err) - return - } - self.Sample = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MediaInfo) Children() (r []Atom) { - if self.Sound != nil { - r = append(r, self.Sound) - } - if self.Video != nil { - r = append(r, self.Video) - } - if self.Data != nil { - r = append(r, self.Data) - } - if self.Sample != nil { - r = append(r, self.Sample) - } - r = append(r, self.Unknowns...) - return -} - -type DataInfo struct { - Refer *DataRefer - Unknowns []Atom - AtomPos -} - -func (self DataInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(DINF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self DataInfo) marshal(b []byte) (n int) { - if self.Refer != nil { - n += self.Refer.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self DataInfo) Len() (n int) { - n += 8 - if self.Refer != nil { - n += self.Refer.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *DataInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case DREF: - { - atom := &DataRefer{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("dref", n+offset, err) - return - } - self.Refer = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self DataInfo) Children() (r []Atom) { - if self.Refer != nil { - r = append(r, self.Refer) - } - r = append(r, self.Unknowns...) - return -} - -type DataRefer struct { - Version uint8 - Flags uint32 - Url *DataReferUrl - AtomPos -} - -func (self DataRefer) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(DREF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self DataRefer) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - _childrenNR := 0 - if self.Url != nil { - _childrenNR++ - } - pio.PutI32BE(b[n:], int32(_childrenNR)) - n += 4 - if self.Url != nil { - n += self.Url.Marshal(b[n:]) - } - return -} -func (self DataRefer) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.Url != nil { - n += self.Url.Len() - } - return -} -func (self *DataRefer) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - n += 4 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case URL: - { - atom := &DataReferUrl{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("url ", n+offset, err) - return - } - self.Url = atom - } - } - n += size - } - return -} -func (self DataRefer) Children() (r []Atom) { - if self.Url != nil { - r = append(r, self.Url) - } - return -} - -type DataReferUrl struct { - Version uint8 - Flags uint32 - AtomPos -} - -func (self DataReferUrl) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(URL)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self DataReferUrl) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - return -} -func (self DataReferUrl) Len() (n int) { - n += 8 - n += 1 - n += 3 - return -} -func (self *DataReferUrl) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - return -} -func (self DataReferUrl) Children() (r []Atom) { - return -} - -type SoundMediaInfo struct { - Version uint8 - Flags uint32 - Balance int16 - AtomPos -} - -func (self SoundMediaInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(SMHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SoundMediaInfo) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutI16BE(b[n:], self.Balance) - n += 2 - n += 2 - return -} -func (self SoundMediaInfo) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 2 - n += 2 - return -} -func (self *SoundMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+2 { - err = parseErr("Balance", n+offset, err) - return - } - self.Balance = pio.I16BE(b[n:]) - n += 2 - n += 2 - return -} -func (self SoundMediaInfo) Children() (r []Atom) { - return -} - -type VideoMediaInfo struct { - Version uint8 - Flags uint32 - GraphicsMode int16 - Opcolor [3]int16 - AtomPos -} - -func (self VideoMediaInfo) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(VMHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self VideoMediaInfo) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutI16BE(b[n:], self.GraphicsMode) - n += 2 - for _, entry := range self.Opcolor { - pio.PutI16BE(b[n:], entry) - n += 2 - } - return -} -func (self VideoMediaInfo) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 2 - n += 2 * len(self.Opcolor[:]) - return -} -func (self *VideoMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+2 { - err = parseErr("GraphicsMode", n+offset, err) - return - } - self.GraphicsMode = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2*len(self.Opcolor) { - err = parseErr("Opcolor", n+offset, err) - return - } - for i := range self.Opcolor { - self.Opcolor[i] = pio.I16BE(b[n:]) - n += 2 - } - return -} -func (self VideoMediaInfo) Children() (r []Atom) { - return -} - -type SampleTable struct { - SampleDesc *SampleDesc - TimeToSample *TimeToSample - CompositionOffset *CompositionOffset - SampleToChunk *SampleToChunk - SyncSample *SyncSample - ChunkOffset *ChunkOffset - SampleSize *SampleSize - AtomPos -} - -func (self SampleTable) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STBL)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleTable) marshal(b []byte) (n int) { - if self.SampleDesc != nil { - n += self.SampleDesc.Marshal(b[n:]) - } - if self.TimeToSample != nil { - n += self.TimeToSample.Marshal(b[n:]) - } - if self.CompositionOffset != nil { - n += self.CompositionOffset.Marshal(b[n:]) - } - if self.SampleToChunk != nil { - n += self.SampleToChunk.Marshal(b[n:]) - } - if self.SyncSample != nil { - n += self.SyncSample.Marshal(b[n:]) - } - if self.ChunkOffset != nil { - n += self.ChunkOffset.Marshal(b[n:]) - } - if self.SampleSize != nil { - n += self.SampleSize.Marshal(b[n:]) - } - return -} -func (self SampleTable) Len() (n int) { - n += 8 - if self.SampleDesc != nil { - n += self.SampleDesc.Len() - } - if self.TimeToSample != nil { - n += self.TimeToSample.Len() - } - if self.CompositionOffset != nil { - n += self.CompositionOffset.Len() - } - if self.SampleToChunk != nil { - n += self.SampleToChunk.Len() - } - if self.SyncSample != nil { - n += self.SyncSample.Len() - } - if self.ChunkOffset != nil { - n += self.ChunkOffset.Len() - } - if self.SampleSize != nil { - n += self.SampleSize.Len() - } - return -} -func (self *SampleTable) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case STSD: - { - atom := &SampleDesc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stsd", n+offset, err) - return - } - self.SampleDesc = atom - } - case STTS: - { - atom := &TimeToSample{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stts", n+offset, err) - return - } - self.TimeToSample = atom - } - case CTTS: - { - atom := &CompositionOffset{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("ctts", n+offset, err) - return - } - self.CompositionOffset = atom - } - case STSC: - { - atom := &SampleToChunk{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stsc", n+offset, err) - return - } - self.SampleToChunk = atom - } - case STSS: - { - atom := &SyncSample{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stss", n+offset, err) - return - } - self.SyncSample = atom - } - case STCO: - { - atom := &ChunkOffset{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stco", n+offset, err) - return - } - self.ChunkOffset = atom - } - case STSZ: - { - atom := &SampleSize{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("stsz", n+offset, err) - return - } - self.SampleSize = atom - } - } - n += size - } - return -} -func (self SampleTable) Children() (r []Atom) { - if self.SampleDesc != nil { - r = append(r, self.SampleDesc) - } - if self.TimeToSample != nil { - r = append(r, self.TimeToSample) - } - if self.CompositionOffset != nil { - r = append(r, self.CompositionOffset) - } - if self.SampleToChunk != nil { - r = append(r, self.SampleToChunk) - } - if self.SyncSample != nil { - r = append(r, self.SyncSample) - } - if self.ChunkOffset != nil { - r = append(r, self.ChunkOffset) - } - if self.SampleSize != nil { - r = append(r, self.SampleSize) - } - return -} - -type SampleDesc struct { - Version uint8 - AVC1Desc *AVC1Desc - MP4ADesc *MP4ADesc - Unknowns []Atom - AtomPos -} - -func (self SampleDesc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleDesc) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - n += 3 - _childrenNR := 0 - if self.AVC1Desc != nil { - _childrenNR++ - } - if self.MP4ADesc != nil { - _childrenNR++ - } - _childrenNR += len(self.Unknowns) - pio.PutI32BE(b[n:], int32(_childrenNR)) - n += 4 - if self.AVC1Desc != nil { - n += self.AVC1Desc.Marshal(b[n:]) - } - if self.MP4ADesc != nil { - n += self.MP4ADesc.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self SampleDesc) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.AVC1Desc != nil { - n += self.AVC1Desc.Len() - } - if self.MP4ADesc != nil { - n += self.MP4ADesc.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *SampleDesc) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - n += 3 - n += 4 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case AVC1: - { - atom := &AVC1Desc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("avc1", n+offset, err) - return - } - self.AVC1Desc = atom - } - case MP4A: - { - atom := &MP4ADesc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mp4a", n+offset, err) - return - } - self.MP4ADesc = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self SampleDesc) Children() (r []Atom) { - if self.AVC1Desc != nil { - r = append(r, self.AVC1Desc) - } - if self.MP4ADesc != nil { - r = append(r, self.MP4ADesc) - } - r = append(r, self.Unknowns...) - return -} - -type MP4ADesc struct { - DataRefIdx int16 - Version int16 - RevisionLevel int16 - Vendor int32 - NumberOfChannels int16 - SampleSize int16 - CompressionId int16 - SampleRate float64 - Conf *ElemStreamDesc - Unknowns []Atom - AtomPos -} - -func (self MP4ADesc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MP4A)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MP4ADesc) marshal(b []byte) (n int) { - n += 6 - pio.PutI16BE(b[n:], self.DataRefIdx) - n += 2 - pio.PutI16BE(b[n:], self.Version) - n += 2 - pio.PutI16BE(b[n:], self.RevisionLevel) - n += 2 - pio.PutI32BE(b[n:], self.Vendor) - n += 4 - pio.PutI16BE(b[n:], self.NumberOfChannels) - n += 2 - pio.PutI16BE(b[n:], self.SampleSize) - n += 2 - pio.PutI16BE(b[n:], self.CompressionId) - n += 2 - n += 2 - PutFixed32(b[n:], self.SampleRate) - n += 4 - if self.Conf != nil { - n += self.Conf.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MP4ADesc) Len() (n int) { - n += 8 - n += 6 - n += 2 - n += 2 - n += 2 - n += 4 - n += 2 - n += 2 - n += 2 - n += 2 - n += 4 - if self.Conf != nil { - n += self.Conf.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MP4ADesc) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - n += 6 - if len(b) < n+2 { - err = parseErr("DataRefIdx", n+offset, err) - return - } - self.DataRefIdx = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("RevisionLevel", n+offset, err) - return - } - self.RevisionLevel = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+4 { - err = parseErr("Vendor", n+offset, err) - return - } - self.Vendor = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("NumberOfChannels", n+offset, err) - return - } - self.NumberOfChannels = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("SampleSize", n+offset, err) - return - } - self.SampleSize = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("CompressionId", n+offset, err) - return - } - self.CompressionId = pio.I16BE(b[n:]) - n += 2 - n += 2 - if len(b) < n+4 { - err = parseErr("SampleRate", n+offset, err) - return - } - self.SampleRate = GetFixed32(b[n:]) - n += 4 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case ESDS: - { - atom := &ElemStreamDesc{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("esds", n+offset, err) - return - } - self.Conf = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MP4ADesc) Children() (r []Atom) { - if self.Conf != nil { - r = append(r, self.Conf) - } - r = append(r, self.Unknowns...) - return -} - -type AVC1Desc struct { - DataRefIdx int16 - Version int16 - Revision int16 - Vendor int32 - TemporalQuality int32 - SpatialQuality int32 - Width int16 - Height int16 - HorizontalResolution float64 - VorizontalResolution float64 - FrameCount int16 - CompressorName [32]byte - Depth int16 - ColorTableId int16 - Conf *AVC1Conf - Unknowns []Atom - AtomPos -} - -func (self AVC1Desc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(AVC1)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self AVC1Desc) marshal(b []byte) (n int) { - n += 6 - pio.PutI16BE(b[n:], self.DataRefIdx) - n += 2 - pio.PutI16BE(b[n:], self.Version) - n += 2 - pio.PutI16BE(b[n:], self.Revision) - n += 2 - pio.PutI32BE(b[n:], self.Vendor) - n += 4 - pio.PutI32BE(b[n:], self.TemporalQuality) - n += 4 - pio.PutI32BE(b[n:], self.SpatialQuality) - n += 4 - pio.PutI16BE(b[n:], self.Width) - n += 2 - pio.PutI16BE(b[n:], self.Height) - n += 2 - PutFixed32(b[n:], self.HorizontalResolution) - n += 4 - PutFixed32(b[n:], self.VorizontalResolution) - n += 4 - n += 4 - pio.PutI16BE(b[n:], self.FrameCount) - n += 2 - copy(b[n:], self.CompressorName[:]) - n += len(self.CompressorName[:]) - pio.PutI16BE(b[n:], self.Depth) - n += 2 - pio.PutI16BE(b[n:], self.ColorTableId) - n += 2 - if self.Conf != nil { - n += self.Conf.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self AVC1Desc) Len() (n int) { - n += 8 - n += 6 - n += 2 - n += 2 - n += 2 - n += 4 - n += 4 - n += 4 - n += 2 - n += 2 - n += 4 - n += 4 - n += 4 - n += 2 - n += len(self.CompressorName[:]) - n += 2 - n += 2 - if self.Conf != nil { - n += self.Conf.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - n += 6 - if len(b) < n+2 { - err = parseErr("DataRefIdx", n+offset, err) - return - } - self.DataRefIdx = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Revision", n+offset, err) - return - } - self.Revision = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+4 { - err = parseErr("Vendor", n+offset, err) - return - } - self.Vendor = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("TemporalQuality", n+offset, err) - return - } - self.TemporalQuality = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("SpatialQuality", n+offset, err) - return - } - self.SpatialQuality = pio.I32BE(b[n:]) - n += 4 - if len(b) < n+2 { - err = parseErr("Width", n+offset, err) - return - } - self.Width = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("Height", n+offset, err) - return - } - self.Height = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+4 { - err = parseErr("HorizontalResolution", n+offset, err) - return - } - self.HorizontalResolution = GetFixed32(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("VorizontalResolution", n+offset, err) - return - } - self.VorizontalResolution = GetFixed32(b[n:]) - n += 4 - n += 4 - if len(b) < n+2 { - err = parseErr("FrameCount", n+offset, err) - return - } - self.FrameCount = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+len(self.CompressorName) { - err = parseErr("CompressorName", n+offset, err) - return - } - copy(self.CompressorName[:], b[n:]) - n += len(self.CompressorName) - if len(b) < n+2 { - err = parseErr("Depth", n+offset, err) - return - } - self.Depth = pio.I16BE(b[n:]) - n += 2 - if len(b) < n+2 { - err = parseErr("ColorTableId", n+offset, err) - return - } - self.ColorTableId = pio.I16BE(b[n:]) - n += 2 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case AVCC: - { - atom := &AVC1Conf{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("avcC", n+offset, err) - return - } - self.Conf = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self AVC1Desc) Children() (r []Atom) { - if self.Conf != nil { - r = append(r, self.Conf) - } - r = append(r, self.Unknowns...) - return -} - -type AVC1Conf struct { - Data []byte - AtomPos -} - -func (self AVC1Conf) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(AVCC)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self AVC1Conf) marshal(b []byte) (n int) { - copy(b[n:], self.Data[:]) - n += len(self.Data[:]) - return -} -func (self AVC1Conf) Len() (n int) { - n += 8 - n += len(self.Data[:]) - return -} -func (self *AVC1Conf) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - self.Data = b[n:] - n += len(b[n:]) - return -} -func (self AVC1Conf) Children() (r []Atom) { - return -} - -type TimeToSample struct { - Version uint8 - Flags uint32 - Entries []TimeToSampleEntry - AtomPos -} - -func (self TimeToSample) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STTS)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TimeToSample) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - PutTimeToSampleEntry(b[n:], entry) - n += LenTimeToSampleEntry - } - return -} -func (self TimeToSample) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += LenTimeToSampleEntry * len(self.Entries) - return -} -func (self *TimeToSample) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]TimeToSampleEntry, _len_Entries) - if len(b) < n+LenTimeToSampleEntry*len(self.Entries) { - err = parseErr("TimeToSampleEntry", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = GetTimeToSampleEntry(b[n:]) - n += LenTimeToSampleEntry - } - return -} -func (self TimeToSample) Children() (r []Atom) { - return -} - -type TimeToSampleEntry struct { - Count uint32 - Duration uint32 -} - -func GetTimeToSampleEntry(b []byte) (self TimeToSampleEntry) { - self.Count = pio.U32BE(b[0:]) - self.Duration = pio.U32BE(b[4:]) - return -} -func PutTimeToSampleEntry(b []byte, self TimeToSampleEntry) { - pio.PutU32BE(b[0:], self.Count) - pio.PutU32BE(b[4:], self.Duration) -} - -const LenTimeToSampleEntry = 8 - -type SampleToChunk struct { - Version uint8 - Flags uint32 - Entries []SampleToChunkEntry - AtomPos -} - -func (self SampleToChunk) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSC)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleToChunk) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - PutSampleToChunkEntry(b[n:], entry) - n += LenSampleToChunkEntry - } - return -} -func (self SampleToChunk) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += LenSampleToChunkEntry * len(self.Entries) - return -} -func (self *SampleToChunk) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]SampleToChunkEntry, _len_Entries) - if len(b) < n+LenSampleToChunkEntry*len(self.Entries) { - err = parseErr("SampleToChunkEntry", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = GetSampleToChunkEntry(b[n:]) - n += LenSampleToChunkEntry - } - return -} -func (self SampleToChunk) Children() (r []Atom) { - return -} - -type SampleToChunkEntry struct { - FirstChunk uint32 - SamplesPerChunk uint32 - SampleDescId uint32 -} - -func GetSampleToChunkEntry(b []byte) (self SampleToChunkEntry) { - self.FirstChunk = pio.U32BE(b[0:]) - self.SamplesPerChunk = pio.U32BE(b[4:]) - self.SampleDescId = pio.U32BE(b[8:]) - return -} -func PutSampleToChunkEntry(b []byte, self SampleToChunkEntry) { - pio.PutU32BE(b[0:], self.FirstChunk) - pio.PutU32BE(b[4:], self.SamplesPerChunk) - pio.PutU32BE(b[8:], self.SampleDescId) -} - -const LenSampleToChunkEntry = 12 - -type CompositionOffset struct { - Version uint8 - Flags uint32 - Entries []CompositionOffsetEntry - AtomPos -} - -func (self CompositionOffset) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(CTTS)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self CompositionOffset) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - PutCompositionOffsetEntry(b[n:], entry) - n += LenCompositionOffsetEntry - } - return -} -func (self CompositionOffset) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += LenCompositionOffsetEntry * len(self.Entries) - return -} -func (self *CompositionOffset) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]CompositionOffsetEntry, _len_Entries) - if len(b) < n+LenCompositionOffsetEntry*len(self.Entries) { - err = parseErr("CompositionOffsetEntry", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = GetCompositionOffsetEntry(b[n:]) - n += LenCompositionOffsetEntry - } - return -} -func (self CompositionOffset) Children() (r []Atom) { - return -} - -type CompositionOffsetEntry struct { - Count uint32 - Offset uint32 -} - -func GetCompositionOffsetEntry(b []byte) (self CompositionOffsetEntry) { - self.Count = pio.U32BE(b[0:]) - self.Offset = pio.U32BE(b[4:]) - return -} -func PutCompositionOffsetEntry(b []byte, self CompositionOffsetEntry) { - pio.PutU32BE(b[0:], self.Count) - pio.PutU32BE(b[4:], self.Offset) -} - -const LenCompositionOffsetEntry = 8 - -type SyncSample struct { - Version uint8 - Flags uint32 - Entries []uint32 - AtomPos -} - -func (self SyncSample) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSS)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SyncSample) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - pio.PutU32BE(b[n:], entry) - n += 4 - } - return -} -func (self SyncSample) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 * len(self.Entries) - return -} -func (self *SyncSample) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]uint32, _len_Entries) - if len(b) < n+4*len(self.Entries) { - err = parseErr("uint32", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = pio.U32BE(b[n:]) - n += 4 - } - return -} -func (self SyncSample) Children() (r []Atom) { - return -} - -type ChunkOffset struct { - Version uint8 - Flags uint32 - Entries []uint32 - AtomPos -} - -func (self ChunkOffset) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STCO)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self ChunkOffset) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - pio.PutU32BE(b[n:], entry) - n += 4 - } - return -} -func (self ChunkOffset) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 * len(self.Entries) - return -} -func (self *ChunkOffset) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]uint32, _len_Entries) - if len(b) < n+4*len(self.Entries) { - err = parseErr("uint32", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = pio.U32BE(b[n:]) - n += 4 - } - return -} -func (self ChunkOffset) Children() (r []Atom) { - return -} - -type MovieFrag struct { - Header *MovieFragHeader - Tracks []*TrackFrag - Unknowns []Atom - AtomPos -} - -func (self MovieFrag) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MOOF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieFrag) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - for _, atom := range self.Tracks { - n += atom.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MovieFrag) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - for _, atom := range self.Tracks { - n += atom.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case MFHD: - { - atom := &MovieFragHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("mfhd", n+offset, err) - return - } - self.Header = atom - } - case TRAF: - { - atom := &TrackFrag{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("traf", n+offset, err) - return - } - self.Tracks = append(self.Tracks, atom) - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MovieFrag) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - for _, atom := range self.Tracks { - r = append(r, atom) - } - r = append(r, self.Unknowns...) - return -} - -type MovieFragHeader struct { - Version uint8 - Flags uint32 - Seqnum uint32 - AtomPos -} - -func (self MovieFragHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MFHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieFragHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.Seqnum) - n += 4 - return -} -func (self MovieFragHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - return -} -func (self *MovieFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("Seqnum", n+offset, err) - return - } - self.Seqnum = pio.U32BE(b[n:]) - n += 4 - return -} -func (self MovieFragHeader) Children() (r []Atom) { - return -} - -type TrackFrag struct { - Header *TrackFragHeader - DecodeTime *TrackFragDecodeTime - Run *TrackFragRun - Unknowns []Atom - AtomPos -} - -func (self TrackFrag) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TRAF)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFrag) marshal(b []byte) (n int) { - if self.Header != nil { - n += self.Header.Marshal(b[n:]) - } - if self.DecodeTime != nil { - n += self.DecodeTime.Marshal(b[n:]) - } - if self.Run != nil { - n += self.Run.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self TrackFrag) Len() (n int) { - n += 8 - if self.Header != nil { - n += self.Header.Len() - } - if self.DecodeTime != nil { - n += self.DecodeTime.Len() - } - if self.Run != nil { - n += self.Run.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case TFHD: - { - atom := &TrackFragHeader{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("tfhd", n+offset, err) - return - } - self.Header = atom - } - case TFDT: - { - atom := &TrackFragDecodeTime{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("tfdt", n+offset, err) - return - } - self.DecodeTime = atom - } - case TRUN: - { - atom := &TrackFragRun{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("trun", n+offset, err) - return - } - self.Run = atom - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self TrackFrag) Children() (r []Atom) { - if self.Header != nil { - r = append(r, self.Header) - } - if self.DecodeTime != nil { - r = append(r, self.DecodeTime) - } - if self.Run != nil { - r = append(r, self.Run) - } - r = append(r, self.Unknowns...) - return -} - -type MovieExtend struct { - Tracks []*TrackExtend - Unknowns []Atom - AtomPos -} - -func (self MovieExtend) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(MVEX)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self MovieExtend) marshal(b []byte) (n int) { - for _, atom := range self.Tracks { - n += atom.Marshal(b[n:]) - } - for _, atom := range self.Unknowns { - n += atom.Marshal(b[n:]) - } - return -} -func (self MovieExtend) Len() (n int) { - n += 8 - for _, atom := range self.Tracks { - n += atom.Len() - } - for _, atom := range self.Unknowns { - n += atom.Len() - } - return -} -func (self *MovieExtend) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - for n+8 < len(b) { - tag := Tag(pio.U32BE(b[n+4:])) - size := int(pio.U32BE(b[n:])) - if len(b) < n+size { - err = parseErr("TagSizeInvalid", n+offset, err) - return - } - switch tag { - case TREX: - { - atom := &TrackExtend{} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("trex", n+offset, err) - return - } - self.Tracks = append(self.Tracks, atom) - } - default: - { - atom := &Dummy{Tag_: tag, Data: b[n : n+size]} - if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { - err = parseErr("", n+offset, err) - return - } - self.Unknowns = append(self.Unknowns, atom) - } - } - n += size - } - return -} -func (self MovieExtend) Children() (r []Atom) { - for _, atom := range self.Tracks { - r = append(r, atom) - } - r = append(r, self.Unknowns...) - return -} - -type TrackExtend struct { - Version uint8 - Flags uint32 - TrackId uint32 - DefaultSampleDescIdx uint32 - DefaultSampleDuration uint32 - DefaultSampleSize uint32 - DefaultSampleFlags uint32 - AtomPos -} - -func (self TrackExtend) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TREX)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackExtend) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.TrackId) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleDescIdx) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleDuration) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleSize) - n += 4 - pio.PutU32BE(b[n:], self.DefaultSampleFlags) - n += 4 - return -} -func (self TrackExtend) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - n += 4 - n += 4 - n += 4 - n += 4 - return -} -func (self *TrackExtend) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("TrackId", n+offset, err) - return - } - self.TrackId = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleDescIdx", n+offset, err) - return - } - self.DefaultSampleDescIdx = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleDuration", n+offset, err) - return - } - self.DefaultSampleDuration = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleSize", n+offset, err) - return - } - self.DefaultSampleSize = pio.U32BE(b[n:]) - n += 4 - if len(b) < n+4 { - err = parseErr("DefaultSampleFlags", n+offset, err) - return - } - self.DefaultSampleFlags = pio.U32BE(b[n:]) - n += 4 - return -} -func (self TrackExtend) Children() (r []Atom) { - return -} - -type SampleSize struct { - Version uint8 - Flags uint32 - SampleSize uint32 - Entries []uint32 - AtomPos -} - -func (self SampleSize) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(STSZ)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self SampleSize) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.SampleSize) - n += 4 - if self.SampleSize != 0 { - return - } - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - for _, entry := range self.Entries { - pio.PutU32BE(b[n:], entry) - n += 4 - } - return -} -func (self SampleSize) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.SampleSize != 0 { - return - } - n += 4 - n += 4 * len(self.Entries) - return -} -func (self *SampleSize) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if len(b) < n+4 { - err = parseErr("SampleSize", n+offset, err) - return - } - self.SampleSize = pio.U32BE(b[n:]) - n += 4 - if self.SampleSize != 0 { - return - } - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]uint32, _len_Entries) - if len(b) < n+4*len(self.Entries) { - err = parseErr("uint32", n+offset, err) - return - } - for i := range self.Entries { - self.Entries[i] = pio.U32BE(b[n:]) - n += 4 - } - return -} -func (self SampleSize) Children() (r []Atom) { - return -} - -type TrackFragRun struct { - Version uint8 - Flags uint32 - DataOffset uint32 - FirstSampleFlags uint32 - Entries []TrackFragRunEntry - AtomPos -} - -func (self TrackFragRun) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TRUN)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFragRun) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], uint32(len(self.Entries))) - n += 4 - if self.Flags&TRUN_DATA_OFFSET != 0 { - { - pio.PutU32BE(b[n:], self.DataOffset) - n += 4 - } - } - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - { - pio.PutU32BE(b[n:], self.FirstSampleFlags) - n += 4 - } - } - - for i, entry := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - //log.Printf("TRUN entry[%v]: flags = %v", i, flags) - if flags&TRUN_SAMPLE_DURATION != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_DURATION = %v, flags = %v", i, entry.Duration, flags) - pio.PutU32BE(b[n:], entry.Duration) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_SIZE = %v, flags = %v", i, entry.Size, flags) - pio.PutU32BE(b[n:], entry.Size) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_FLAGS = %v, flags = %v", i, entry.Flags, flags) - pio.PutU32BE(b[n:], entry.Flags) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - //log.Printf("TRUN entry[%v]: TRUN_SAMPLE_CTS = %v, flags = %v", i, entry.Cts, flags) - pio.PutU32BE(b[n:], entry.Cts) - n += 4 - } - } - return -} -func (self TrackFragRun) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.Flags&TRUN_DATA_OFFSET != 0 { - n += 4 - } - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - n += 4 - } - - for i := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - if flags&TRUN_SAMPLE_DURATION != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - n += 4 - } - } - return -} -func (self *TrackFragRun) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - var _len_Entries uint32 - _len_Entries = pio.U32BE(b[n:]) - n += 4 - self.Entries = make([]TrackFragRunEntry, _len_Entries) - if self.Flags&TRUN_DATA_OFFSET != 0 { - { - if len(b) < n+4 { - err = parseErr("DataOffset", n+offset, err) - return - } - self.DataOffset = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - { - if len(b) < n+4 { - err = parseErr("FirstSampleFlags", n+offset, err) - return - } - self.FirstSampleFlags = pio.U32BE(b[n:]) - n += 4 - } - } - - for i := 0; i < int(_len_Entries); i++ { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - entry := &self.Entries[i] - if flags&TRUN_SAMPLE_DURATION != 0 { - entry.Duration = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - entry.Size = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - entry.Flags = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - entry.Cts = pio.U32BE(b[n:]) - n += 4 - } - } - return -} -func (self TrackFragRun) Children() (r []Atom) { - return -} - -type TrackFragRunEntry struct { - Duration uint32 - Size uint32 - Flags uint32 - Cts uint32 -} - -func GetTrackFragRunEntry(b []byte) (self TrackFragRunEntry) { - self.Duration = pio.U32BE(b[0:]) - self.Size = pio.U32BE(b[4:]) - self.Flags = pio.U32BE(b[8:]) - self.Cts = pio.U32BE(b[12:]) - return -} -func PutTrackFragRunEntry(b []byte, self TrackFragRunEntry) { - pio.PutU32BE(b[0:], self.Duration) - pio.PutU32BE(b[4:], self.Size) - pio.PutU32BE(b[8:], self.Flags) - pio.PutU32BE(b[12:], self.Cts) -} - -const LenTrackFragRunEntry = 16 - -type TrackFragHeader struct { - Version uint8 - Flags uint32 - TrackId uint32 - BaseDataOffset uint64 - StsdId uint32 - DefaultDuration uint32 - DefaultSize uint32 - DefaultFlags uint32 - AtomPos -} - -func (self TrackFragHeader) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TFHD)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFragHeader) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - pio.PutU32BE(b[n:], self.TrackId) - n += 4 - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - { - pio.PutU64BE(b[n:], self.BaseDataOffset) - n += 8 - } - } - - if self.Flags&TFHD_STSD_ID != 0 { - { - pio.PutU32BE(b[n:], self.StsdId) - n += 4 - } - } - - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - { - pio.PutU32BE(b[n:], self.DefaultDuration) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - { - pio.PutU32BE(b[n:], self.DefaultSize) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - { - pio.PutU32BE(b[n:], self.DefaultFlags) - n += 4 - } - } - return -} -func (self TrackFragHeader) Len() (n int) { - n += 8 - n += 1 - n += 3 - n += 4 - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - { - n += 8 - } - } - if self.Flags&TFHD_STSD_ID != 0 { - { - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - { - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - { - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - { - n += 4 - } - } - return -} -func (self *TrackFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - - if len(b) < n+4 { - err = parseErr("TrackId", n+offset, err) - return - } - self.TrackId = pio.U32BE(b[n:]) - n += 4 - - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - { - if len(b) < n+8 { - err = parseErr("BaseDataOffset", n+offset, err) - return - } - self.BaseDataOffset = pio.U64BE(b[n:]) - n += 8 - } - } - if self.Flags&TFHD_STSD_ID != 0 { - { - if len(b) < n+4 { - err = parseErr("StsdId", n+offset, err) - return - } - self.StsdId = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - { - if len(b) < n+4 { - err = parseErr("DefaultDuration", n+offset, err) - return - } - self.DefaultDuration = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - { - if len(b) < n+4 { - err = parseErr("DefaultSize", n+offset, err) - return - } - self.DefaultSize = pio.U32BE(b[n:]) - n += 4 - } - } - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - { - if len(b) < n+4 { - err = parseErr("DefaultFlags", n+offset, err) - return - } - self.DefaultFlags = pio.U32BE(b[n:]) - n += 4 - } - } - return -} -func (self TrackFragHeader) Children() (r []Atom) { - return -} - -type TrackFragDecodeTime struct { - Version uint8 - Flags uint32 - DecodeTime uint64 - AtomPos -} - -func (self TrackFragDecodeTime) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(TFDT)) - n += self.marshal(b[8:]) + 8 - pio.PutU32BE(b[0:], uint32(n)) - return -} -func (self TrackFragDecodeTime) marshal(b []byte) (n int) { - pio.PutU8(b[n:], self.Version) - n += 1 - pio.PutU24BE(b[n:], self.Flags) - n += 3 - if self.Version != 0 { - pio.PutU64BE(b[n:], self.DecodeTime) - n += 8 - } else { - - pio.PutU32BE(b[n:], uint32(self.DecodeTime)) - n += 4 - } - return -} -func (self TrackFragDecodeTime) Len() (n int) { - n += 8 - n += 1 - n += 3 - if self.Version != 0 { - n += 8 - } else { - - n += 4 - } - return -} -func (self *TrackFragDecodeTime) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - if len(b) < n+1 { - err = parseErr("Version", n+offset, err) - return - } - self.Version = pio.U8(b[n:]) - n += 1 - if len(b) < n+3 { - err = parseErr("Flags", n+offset, err) - return - } - self.Flags = pio.U24BE(b[n:]) - n += 3 - if self.Version != 0 { - self.DecodeTime = pio.U64BE(b[n:]) - n += 8 - } else { - - self.DecodeTime = uint64(pio.U32BE(b[n:])) - n += 4 - } - return -} -func (self TrackFragDecodeTime) Children() (r []Atom) { - return -} diff --git a/machinery/src/mp4/mp4io/gen/gen.go b/machinery/src/mp4/mp4io/gen/gen.go deleted file mode 100644 index 1a6efb37..00000000 --- a/machinery/src/mp4/mp4io/gen/gen.go +++ /dev/null @@ -1,1057 +0,0 @@ - -package main - -import ( - "strings" - "fmt" - "os" - "go/ast" - "go/parser" - "go/token" - "go/printer" -) - -func getexprs(e ast.Expr) string { - if lit, ok := e.(*ast.BasicLit); ok { - return lit.Value - } - if ident, ok := e.(*ast.Ident); ok { - return ident.Name - } - return "" -} - -func genatomdecl(origfn *ast.FuncDecl, origname, origtag string) (decls []ast.Decl) { - fieldslist := &ast.FieldList{} - typespec := &ast.TypeSpec{ - Name: ast.NewIdent(origname), - Type: &ast.StructType{Fields: fieldslist}, - } - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - - if strings.HasPrefix(typ, "_") { - if typ == "_unknowns" { - fieldslist.List = append(fieldslist.List, &ast.Field{ - Names: []*ast.Ident{ast.NewIdent("Unknowns")}, - Type: ast.NewIdent("[]Atom"), - }) - } - continue - } - - name := getexprs(callexpr.Args[0]) - - name2 := "" - if len(callexpr.Args) > 1 { - name2 = getexprs(callexpr.Args[1]) - } - - len3 := "" - if len(callexpr.Args) > 2 { - len3 = getexprs(callexpr.Args[2]) - } - - if strings.HasPrefix(name, "_") { - continue - } - - switch typ { - case "fixed16": - typ = "float64" - case "fixed32": - typ = "float64" - case "bytesleft": - typ = "[]byte" - case "bytes": - typ = "["+name2+"]byte" - case "uint24": - typ = "uint32" - case "time64", "time32": - typ = "time.Time" - case "atom": - typ = "*"+name2 - case "atoms": - typ = "[]*"+name2 - case "slice": - typ = "[]"+name2 - case "array": - typ = "["+len3+"]"+name2 - } - - fieldslist.List = append(fieldslist.List, &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: ast.NewIdent(typ), - }) - } - - if origtag != "" { - fieldslist.List = append(fieldslist.List, &ast.Field{ - Type: ast.NewIdent("AtomPos"), - }) - } - - gendecl := &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{ - typespec, - }, - } - decls = append(decls, gendecl) - return -} - -func typegetlen(typ string) (n int) { - switch typ { - case "uint8": - n = 1 - case "uint16": - n = 2 - case "uint24": - n = 3 - case "uint32": - n = 4 - case "int16": - n = 2 - case "int32": - n = 4 - case "uint64": - n = 8 - case "time32": - n = 4 - case "time64": - n = 8 - case "fixed32": - n = 4 - case "fixed16": - n = 2 - } - return -} - -func typegetlens(typ string) string { - n := typegetlen(typ) - if n == 0 { - return "Len"+typ - } else { - return fmt.Sprint(n) - } -} - -func typegetvartype(typ string) string { - switch typ { - case "uint8": - return "uint8" - case "uint16": - return "uint16" - case "uint24": - return "uint32" - case "uint32": - return "uint32" - case "uint64": - return "uint64" - case "int16": - return "int16" - case "int32": - return "int32" - } - return "" -} - -func typegetputfn(typ string) (fn string) { - fn = typ - switch typ { - case "uint8": - fn = "pio.PutU8" - case "uint16": - fn = "pio.PutU16BE" - case "uint24": - fn = "pio.PutU24BE" - case "uint32": - fn = "pio.PutU32BE" - case "int16": - fn = "pio.PutI16BE" - case "int32": - fn = "pio.PutI32BE" - case "uint64": - fn = "pio.PutU64BE" - case "time32": - fn = "PutTime32" - case "time64": - fn = "PutTime64" - case "fixed32": - fn = "PutFixed32" - case "fixed16": - fn = "PutFixed16" - default: - fn = "Put"+typ - } - return -} - -func typegetgetfn(typ string) (fn string) { - fn = typ - switch typ { - case "uint8": - fn = "pio.U8" - case "uint16": - fn = "pio.U16BE" - case "uint24": - fn = "pio.U24BE" - case "uint32": - fn = "pio.U32BE" - case "int16": - fn = "pio.I16BE" - case "int32": - fn = "pio.I32BE" - case "uint64": - fn = "pio.U64BE" - case "time32": - fn = "GetTime32" - case "time64": - fn = "GetTime64" - case "fixed32": - fn = "GetFixed32" - case "fixed16": - fn = "GetFixed16" - default: - fn = "Get"+typ - } - return -} - -func addns(n string) (stmts []ast.Stmt) { - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: n}}, - } - stmts = append(stmts, assign) - return -} - -func addn(n int) (stmts []ast.Stmt) { - return addns(fmt.Sprint(n)) -} - -func simplecall(fun string, args... string) *ast.ExprStmt { - _args := []ast.Expr{} - for _, s := range args { - _args = append(_args, ast.NewIdent(s)) - } - return &ast.ExprStmt{ - X: &ast.CallExpr{ - Fun: ast.NewIdent(fun), - Args: _args, - }, - } -} - -func getxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { - fn := typegetgetfn(typ) - assign := &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ast.NewIdent(name)}, - Rhs: []ast.Expr{simplecall(fn, "b["+pos+":]").X}, - } - stmts = append(stmts, assign) - return -} - -func putxx(typ string, pos, name string, conv bool) (stmts []ast.Stmt) { - if conv { - name = fmt.Sprintf("%s(%s)", typ, name) - } - fn := typegetputfn(typ) - stmts = append(stmts, simplecall(fn, "b["+pos+":]", name)) - return -} - -func putxxadd(fn string, name string, conv bool) (stmts []ast.Stmt) { - n := typegetlen(fn) - stmts = append(stmts, putxx(fn, "n", name, conv)...) - stmts = append(stmts, addn(n)...) - return -} - -func newdecl(origname, name string, params, res []*ast.Field, stmts []ast.Stmt) *ast.FuncDecl { - return &ast.FuncDecl{ - Recv: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{ - Names: []*ast.Ident{ast.NewIdent("self")}, - Type: ast.NewIdent(origname), - }, - }, - }, - Name: ast.NewIdent(name), - Type: &ast.FuncType{ - Params: &ast.FieldList{ - List: params, - }, - Results: &ast.FieldList{ - List: res, - }, - }, - Body: &ast.BlockStmt{List: stmts}, - } -} - -func getstructputgetlenfn(origfn *ast.FuncDecl, origname string) (decls []ast.Decl) { - getstmts := []ast.Stmt{} - putstmts := []ast.Stmt{} - totlen := 0 - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - name := getexprs(callexpr.Args[0]) - - getstmts = append(getstmts, getxx(typ, fmt.Sprint(totlen), "self."+name, false)...) - putstmts = append(putstmts, putxx(typ, fmt.Sprint(totlen), "self."+name, false)...) - totlen += typegetlen(typ) - } - - getstmts = append(getstmts, &ast.ReturnStmt{}) - - decls = append(decls, &ast.FuncDecl{ - Name: ast.NewIdent("Get"+origname), - Type: &ast.FuncType{ - Params: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - }, - }, - Results: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, - }, - }, - }, - Body: &ast.BlockStmt{List: getstmts}, - }) - - decls = append(decls, &ast.FuncDecl{ - Name: ast.NewIdent("Put"+origname), - Type: &ast.FuncType{ - Params: &ast.FieldList{ - List: []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - &ast.Field{Names: []*ast.Ident{ast.NewIdent("self")}, Type: ast.NewIdent(origname)}, - }, - }, - }, - Body: &ast.BlockStmt{List: putstmts}, - }) - - decls = append(decls, &ast.GenDecl{ - Tok: token.CONST, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ast.NewIdent("Len"+origname)}, - Values: []ast.Expr{ast.NewIdent(fmt.Sprint(totlen))}, - }, - }, - }) - - return -} - -func cc4decls(name string) (decls []ast.Decl) { - constdecl := &ast.GenDecl{ - Tok: token.CONST, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ - ast.NewIdent(strings.ToUpper(name)), - }, - Values: []ast.Expr{ - &ast.CallExpr{ - Fun: ast.NewIdent("Tag"), - Args: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("0x%x", []byte(name))}}, - }, - }, - }, - }, - } - decls = append(decls, constdecl) - return -} - -func codeclonereplace(stmts []ast.Stmt, doit []ast.Stmt) (out []ast.Stmt) { - out = append([]ast.Stmt(nil), stmts...) - for i := range out { - if ifstmt, ok := out[i].(*ast.IfStmt); ok { - newifstmt := &ast.IfStmt{ - Cond: ifstmt.Cond, - Body: &ast.BlockStmt{ - List: codeclonereplace(ifstmt.Body.List, doit), - }, - } - if ifstmt.Else != nil { - newifstmt.Else = &ast.BlockStmt{ - List: codeclonereplace(ifstmt.Else.(*ast.BlockStmt).List, doit), - } - } - out[i] = newifstmt - } else if exprstmt, ok := out[i].(*ast.ExprStmt); ok { - if callexpr, ok := exprstmt.X.(*ast.CallExpr); ok { - if getexprs(callexpr.Fun) == "doit" { - out[i] = &ast.BlockStmt{List: doit} - } - } - } - } - return -} - -func getatommarshalfn(origfn *ast.FuncDecl, - origname, origtag string, - tagnamemap map[string]string, -) (decls []ast.Decl) { - marstmts := []ast.Stmt{} - unmarstmts := []ast.Stmt{} - lenstmts := []ast.Stmt{} - childrenstmts := []ast.Stmt{} - - parseerrreturn := func(debug string) (stmts []ast.Stmt) { - return []ast.Stmt{ - &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("err")}, - Rhs: []ast.Expr{ast.NewIdent(fmt.Sprintf(`parseErr("%s", n+offset, err)`, debug))}, - }, - &ast.ReturnStmt{}, - } - } - - callmarshal := func(name string) (stmts []ast.Stmt) { - callexpr := &ast.CallExpr{ - Fun: ast.NewIdent(name+".Marshal"), - Args: []ast.Expr{ast.NewIdent("b[n:]")}, - } - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{callexpr}, - } - stmts = append(stmts, assign) - return - } - - callputstruct := func(typ, name string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.ExprStmt{ - X: &ast.CallExpr{ - Fun: ast.NewIdent(typegetputfn(typ)), - Args: []ast.Expr{ast.NewIdent("b[n:]"), ast.NewIdent(name)}, - }, - }) - stmts = append(stmts, &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{ast.NewIdent(typegetlens(typ))}, - }) - return - } - - calllenstruct := func(typ, name string) (stmts []ast.Stmt) { - inc := typegetlens(typ)+"*len("+name+")" - stmts = append(stmts, &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{ast.NewIdent(inc)}, - }) - return - } - - calllen := func(name string) (stmts []ast.Stmt) { - callexpr := &ast.CallExpr{ - Fun: ast.NewIdent(name+".Len"), - Args: []ast.Expr{}, - } - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("n")}, - Rhs: []ast.Expr{callexpr}, - } - stmts = append(stmts, assign) - return - } - - foreach := func(name, field string, block []ast.Stmt) (stmts []ast.Stmt) { - rangestmt := &ast.RangeStmt{ - Key: ast.NewIdent("_"), - Value: ast.NewIdent(name), - Body: &ast.BlockStmt{ - List: block, - }, - Tok: token.DEFINE, - X: ast.NewIdent(field), - } - stmts = append(stmts, rangestmt) - return - } - - foreachatom := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { - return foreach("atom", field, block) - } - - foreachentry := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { - return foreach("entry", field, block) - } - - foreachi := func(field string, block []ast.Stmt) (stmts []ast.Stmt) { - rangestmt := &ast.RangeStmt{ - Key: ast.NewIdent("i"), - Body: &ast.BlockStmt{ - List: block, - }, - Tok: token.DEFINE, - X: ast.NewIdent(field), - } - stmts = append(stmts, rangestmt) - return - } - - foreachunknowns := func(block []ast.Stmt) (stmts []ast.Stmt) { - return foreachatom("self.Unknowns", block) - } - - declvar := func(name, typ string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.DeclStmt{ - Decl: &ast.GenDecl{ - Tok: token.VAR, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ - ast.NewIdent(typ), - }, - Type: ast.NewIdent(name), - }, - }, - }, - }) - return - } - - makeslice := func(name, typ, size string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.ExprStmt{ - X: ast.NewIdent(fmt.Sprintf("%s = make([]%s, %s)", name, typ, size)), - }) - return - } - - simpleassign := func(tok token.Token, l, r string) *ast.AssignStmt { - return &ast.AssignStmt{ - Tok: tok, - Lhs: []ast.Expr{ast.NewIdent(l)}, - Rhs: []ast.Expr{ast.NewIdent(r)}, - } - } - - struct2tag := func(s string) string { - name := tagnamemap[s] - return name - } - - foreachatomsappendchildren := func(field string) (stmts []ast.Stmt) { - return foreachatom(field, []ast.Stmt{ - simpleassign(token.ASSIGN, "r", "append(r, atom)"), - }) - } - - var hasunknowns bool - var atomnames []string - var atomtypes []string - var atomarrnames []string - var atomarrtypes []string - slicenamemap := map[string]string{} - - unmarshalatom := func(typ, init string) (stmts []ast.Stmt) { - return []ast.Stmt{ - &ast.AssignStmt{Tok: token.DEFINE, - Lhs: []ast.Expr{ast.NewIdent("atom")}, Rhs: []ast.Expr{ast.NewIdent("&"+typ+"{"+init+"}")}, - }, - &ast.IfStmt{ - Init: &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("_"), ast.NewIdent("err")}, - Rhs: []ast.Expr{ast.NewIdent("atom.Unmarshal(b[n:n+size], offset+n)")}, - }, - Cond: ast.NewIdent("err != nil"), - Body: &ast.BlockStmt{List: parseerrreturn(struct2tag(typ))}, - }, - } - } - - unmrashalatoms := func() (stmts []ast.Stmt) { - blocks := []ast.Stmt{} - - blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("tag")}, - Rhs: []ast.Expr{ast.NewIdent("Tag(pio.U32BE(b[n+4:]))")}, - }) - blocks = append(blocks, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ast.NewIdent("size")}, - Rhs: []ast.Expr{ast.NewIdent("int(pio.U32BE(b[n:]))")}, - }) - blocks = append(blocks, &ast.IfStmt{ - Cond: ast.NewIdent("len(b) < n+size"), - Body: &ast.BlockStmt{List: parseerrreturn("TagSizeInvalid")}, - }) - - cases := []ast.Stmt{} - - for i, atom := range atomnames { - cases = append(cases, &ast.CaseClause{ - List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomtypes[i])))}, - Body: []ast.Stmt{&ast.BlockStmt{ - List: append(unmarshalatom(atomtypes[i], ""), simpleassign(token.ASSIGN, "self."+atom, "atom")), - }}, - }) - } - - for i, atom := range atomarrnames { - selfatom := "self."+atom - cases = append(cases, &ast.CaseClause{ - List: []ast.Expr{ast.NewIdent(strings.ToUpper(struct2tag(atomarrtypes[i])))}, - Body: []ast.Stmt{&ast.BlockStmt{ - List: append(unmarshalatom(atomarrtypes[i], ""), - simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), - }}, - }) - } - - if hasunknowns { - init := "Tag_: tag, Data: b[n:n+size]" - selfatom := "self.Unknowns" - cases = append(cases, &ast.CaseClause{ - Body: []ast.Stmt{&ast.BlockStmt{ - List: append(unmarshalatom("Dummy", init), simpleassign(token.ASSIGN, selfatom, "append("+selfatom+", atom)")), - }}, - }) - } - - blocks = append(blocks, &ast.SwitchStmt{ - Tag: ast.NewIdent("tag"), - Body: &ast.BlockStmt{List: cases}, - }) - - blocks = append(blocks, addns("size")...) - - stmts = append(stmts, &ast.ForStmt{ - Cond: ast.NewIdent("n+8 < len(b)"), - Body: &ast.BlockStmt{List: blocks}, - }) - return - } - - marshalwrapstmts := func() (stmts []ast.Stmt) { - stmts = append(stmts, putxx("uint32", "4", strings.ToUpper(origtag), true)...) - stmts = append(stmts, addns("self.marshal(b[8:])+8")...) - stmts = append(stmts, putxx("uint32", "0", "n", true)...) - stmts = append(stmts, &ast.ReturnStmt{}) - return - } - - ifnotnil := func(name string, block []ast.Stmt) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.IfStmt{ - Cond: &ast.BinaryExpr{ - X: ast.NewIdent(name), - Op: token.NEQ, - Y: ast.NewIdent("nil"), - }, - Body: &ast.BlockStmt{List: block}, - }) - return - } - - getchildrennr := func(name string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.AssignStmt{ - Tok: token.DEFINE, - Lhs: []ast.Expr{ast.NewIdent(name)}, - Rhs: []ast.Expr{ast.NewIdent("0")}, - }) - for _, atom := range atomnames { - stmts = append(stmts, ifnotnil("self."+atom, []ast.Stmt{ - &ast.IncDecStmt{X: ast.NewIdent(name), Tok: token.INC}, - })...) - } - if hasunknowns { - assign := &ast.AssignStmt{ - Tok: token.ADD_ASSIGN, - Lhs: []ast.Expr{ast.NewIdent("_childrenNR")}, - Rhs: []ast.Expr{ast.NewIdent("len(self.Unknowns)")}, - } - stmts = append(stmts, assign) - } - return - } - - checkcurlen := func(inc, debug string) (stmts []ast.Stmt) { - stmts = append(stmts, &ast.IfStmt{ - Cond: &ast.BinaryExpr{ - X: ast.NewIdent("len(b)"), - Op: token.LSS, - Y: ast.NewIdent("n+"+inc), - }, - Body: &ast.BlockStmt{List: parseerrreturn(debug)}, - }) - return - } - - checklendo := func(typ, name, debug string) (stmts []ast.Stmt) { - stmts = append(stmts, checkcurlen(typegetlens(typ), debug)...) - stmts = append(stmts, getxx(typ, "n", name, false)...) - stmts = append(stmts, addns(typegetlens(typ))...) - return - } - - checkstructlendo := func(typ, name, debug string, - foreach func(string,[]ast.Stmt)[]ast.Stmt, - ) (stmts []ast.Stmt) { - inc := typegetlens(typ)+"*len("+name+")" - stmts = append(stmts, checkcurlen(inc, debug)...) - stmts = append(stmts, foreach(name, append( - []ast.Stmt{ - &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ - ast.NewIdent(name+"[i]"), - }, - Rhs: []ast.Expr{ - &ast.CallExpr{ - Fun: ast.NewIdent(typegetgetfn(typ)), - Args: []ast.Expr{ast.NewIdent("b[n:]")}, - }, - }, - }, - }, - addns(typegetlens(typ))..., - ))...) - return - } - - checklencopy := func(name string) (stmts []ast.Stmt) { - lens := fmt.Sprintf("len(self.%s)", name) - stmts = append(stmts, checkcurlen(lens, name)...) - stmts = append(stmts, simplecall("copy", fmt.Sprintf("self.%s[:]", name), "b[n:]")) - stmts = append(stmts, addns(lens)...) - return - } - - appendcode := func(args []ast.Expr, - marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, unmarstmts *[]ast.Stmt, - defmarstmts []ast.Stmt, deflenstmts []ast.Stmt, defunmarstmts []ast.Stmt, - ) { - bodylist := func(i int, doit []ast.Stmt) []ast.Stmt { - return codeclonereplace(args[i].(*ast.FuncLit).Body.List, doit) - } - if len(args) == 1 { - *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) - *lenstmts = append(*lenstmts, bodylist(0, deflenstmts)...) - *unmarstmts = append(*unmarstmts, bodylist(0, defunmarstmts)...) - } else { - *marstmts = append(*marstmts, bodylist(0, defmarstmts)...) - *lenstmts = append(*lenstmts, bodylist(1, deflenstmts)...) - *unmarstmts = append(*unmarstmts, bodylist(2, defunmarstmts)...) - } - } - - getdefaultstmts := func( - typ, name, name2 string, - marstmts *[]ast.Stmt, lenstmts *[]ast.Stmt, - unmarstmts *[]ast.Stmt, childrenstmts *[]ast.Stmt, - ) { - switch typ { - case "bytes", "bytesleft": - *marstmts = append(*marstmts, simplecall("copy", "b[n:]", "self."+name+"[:]")) - *marstmts = append(*marstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) - *lenstmts = append(*lenstmts, addns(fmt.Sprintf("len(self.%s[:])", name))...) - if typ == "bytes" { - *unmarstmts = append(*unmarstmts, checklencopy(name)...) - } else { - *unmarstmts = append(*unmarstmts, simpleassign(token.ASSIGN, "self."+name, "b[n:]")) - *unmarstmts = append(*unmarstmts, addns("len(b[n:])")...) - } - - case "array": - *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) - *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name+"[:]")...) - *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name, foreachi)...) - - case "atoms": - *marstmts = append(*marstmts, foreachatom("self."+name, callmarshal("atom"))...) - *lenstmts = append(*lenstmts, foreachatom("self."+name, calllen("atom"))...) - *childrenstmts = append(*childrenstmts, foreachatomsappendchildren("self."+name)...) - - case "slice": - *marstmts = append(*marstmts, foreachentry("self."+name, callputstruct(name2, "entry"))...) - *lenstmts = append(*lenstmts, calllenstruct(name2, "self."+name)...) - *unmarstmts = append(*unmarstmts, checkstructlendo(name2, "self."+name, name2, foreachi)...) - - case "atom": - *marstmts = append(*marstmts, ifnotnil("self."+name, callmarshal("self."+name))...) - *lenstmts = append(*lenstmts, ifnotnil("self."+name, calllen("self."+name))...) - *childrenstmts = append(*childrenstmts, ifnotnil("self."+name, []ast.Stmt{ - simpleassign(token.ASSIGN, "r", fmt.Sprintf("append(r, %s)", "self."+name)), - })...) - - default: - *marstmts = append(*marstmts, putxxadd(typ, "self."+name, false)...) - *lenstmts = append(*lenstmts, addn(typegetlen(typ))...) - *unmarstmts = append(*unmarstmts, checklendo(typ, "self."+name, name)...) - } - } - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - if typ == "_unknowns" { - hasunknowns = true - } else if typ == "atom" { - name := getexprs(callexpr.Args[0]) - name2 := getexprs(callexpr.Args[1]) - atomnames = append(atomnames, name) - atomtypes = append(atomtypes, name2) - } else if typ == "atoms" { - name := getexprs(callexpr.Args[0]) - name2 := getexprs(callexpr.Args[1]) - atomarrnames = append(atomarrnames, name) - atomarrtypes = append(atomarrtypes, name2) - } else if typ == "slice" { - name := getexprs(callexpr.Args[0]) - name2 := getexprs(callexpr.Args[1]) - slicenamemap[name] = name2 - } - } - - lenstmts = append(lenstmts, addn(8)...) - unmarstmts = append(unmarstmts, simplecall("(&self.AtomPos).setPos", "offset", "len(b)")) - unmarstmts = append(unmarstmts, addn(8)...) - - for _, _stmt := range origfn.Body.List { - stmt := _stmt.(*ast.ExprStmt) - callexpr := stmt.X.(*ast.CallExpr) - typ := callexpr.Fun.(*ast.Ident).Name - - name := "" - if len(callexpr.Args) > 0 { - name = getexprs(callexpr.Args[0]) - } - - name2 := "" - if len(callexpr.Args) > 1 { - name2 = getexprs(callexpr.Args[1]) - } - - var defmarstmts, deflenstmts, defunmarstmts, defchildrenstmts []ast.Stmt - getdefaultstmts(typ, name, name2, - &defmarstmts, &deflenstmts, &defunmarstmts, &defchildrenstmts) - - var code []ast.Expr - for _, arg := range callexpr.Args { - if fn, ok := arg.(*ast.CallExpr); ok { - if getexprs(fn.Fun) == "_code" { - code = fn.Args - } - } - } - if code != nil { - appendcode(code, - &marstmts, &lenstmts, &unmarstmts, - defmarstmts, deflenstmts, defunmarstmts, - ) - continue - } - - if strings.HasPrefix(typ, "_") { - if typ == "_unknowns" { - marstmts = append(marstmts, foreachunknowns(callmarshal("atom"))...) - lenstmts = append(lenstmts, foreachunknowns(calllen("atom"))...) - childrenstmts = append(childrenstmts, simpleassign(token.ASSIGN, "r", "append(r, self.Unknowns...)")) - } - if typ == "_skip" { - marstmts = append(marstmts, addns(name)...) - lenstmts = append(lenstmts, addns(name)...) - unmarstmts = append(unmarstmts, addns(name)...) - } - if typ == "_code" { - appendcode(callexpr.Args, - &marstmts, &lenstmts, &unmarstmts, - defmarstmts, deflenstmts, defunmarstmts, - ) - } - continue - } - - if name == "_childrenNR" { - marstmts = append(marstmts, getchildrennr(name)...) - marstmts = append(marstmts, putxxadd(typ, name, true)...) - lenstmts = append(lenstmts, addn(typegetlen(typ))...) - unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) - continue - } - - if strings.HasPrefix(name, "_len_") { - field := name[len("_len_"):] - marstmts = append(marstmts, putxxadd(typ, "len(self."+field+")", true)...) - lenstmts = append(lenstmts, addn(typegetlen(typ))...) - unmarstmts = append(unmarstmts, declvar(typegetvartype(typ), name)...) - unmarstmts = append(unmarstmts, getxx(typ, "n", name, false)...) - unmarstmts = append(unmarstmts, addn(typegetlen(typ))...) - unmarstmts = append(unmarstmts, makeslice("self."+field, slicenamemap[field], name)...) - continue - } - - marstmts = append(marstmts, defmarstmts...) - lenstmts = append(lenstmts, deflenstmts...) - unmarstmts = append(unmarstmts, defunmarstmts...) - childrenstmts = append(childrenstmts, defchildrenstmts...) - } - - if len(atomnames) > 0 || len(atomarrnames) > 0 || hasunknowns { - unmarstmts = append(unmarstmts, unmrashalatoms()...) - } - - marstmts = append(marstmts, &ast.ReturnStmt{}) - lenstmts = append(lenstmts, &ast.ReturnStmt{}) - unmarstmts = append(unmarstmts, &ast.ReturnStmt{}) - childrenstmts = append(childrenstmts, &ast.ReturnStmt{}) - - decls = append(decls, newdecl(origname, "Marshal", []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - }, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - }, marshalwrapstmts())) - - decls = append(decls, newdecl(origname, "marshal", []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - }, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - }, marstmts)) - - decls = append(decls, newdecl(origname, "Len", []*ast.Field{}, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - }, lenstmts)) - - decls = append(decls, newdecl("*"+origname, "Unmarshal", []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("b")}, Type: ast.NewIdent("[]byte")}, - &ast.Field{Names: []*ast.Ident{ast.NewIdent("offset")}, Type: ast.NewIdent("int")}, - }, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("n")}, Type: ast.NewIdent("int")}, - &ast.Field{Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}, - }, unmarstmts)) - - decls = append(decls, newdecl(origname, "Children", []*ast.Field{}, []*ast.Field{ - &ast.Field{Names: []*ast.Ident{ast.NewIdent("r")}, Type: ast.NewIdent("[]Atom")}, - }, childrenstmts)) - - return -} - -func genatoms(filename, outfilename string) { - // Create the AST by parsing src. - fset := token.NewFileSet() // positions are relative to fset - file, err := parser.ParseFile(fset, filename, nil, 0) - if err != nil { - panic(err) - } - - gen := &ast.File{} - gen.Name = ast.NewIdent("mp4io") - gen.Decls = []ast.Decl{ - &ast.GenDecl{ - Tok: token.IMPORT, - Specs: []ast.Spec{ - &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/kerberos-io/joy4/utils/bits/pio"`}}, - }, - }, - &ast.GenDecl{ - Tok: token.IMPORT, - Specs: []ast.Spec{ - &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"time"`}}, - }, - }, - } - - tagnamemap := map[string]string{} - tagnamemap["ElemStreamDesc"] = "esds" - - splittagname := func(fnname string) (ok bool, tag, name string) { - if len(fnname) > 5 && fnname[4] == '_' { - tag = fnname[0:4] - tag = strings.Replace(tag, "_", " ", 1) - name = fnname[5:] - ok = true - } else { - name = fnname - } - return - } - - for _, decl := range file.Decls { - if fndecl, ok := decl.(*ast.FuncDecl); ok { - ok, tag, name := splittagname(fndecl.Name.Name) - if ok { - tagnamemap[name] = tag - } - } - } - - tagfuncdecl := func(name, tag string) (decls ast.Decl) { - return newdecl(name, "Tag", []*ast.Field{}, []*ast.Field{ - &ast.Field{Type: ast.NewIdent("Tag")}, - }, []ast.Stmt{ - &ast.ReturnStmt{ - Results: []ast.Expr{ast.NewIdent(strings.ToUpper(tag))}}}) - } - - for k, v := range tagnamemap { - gen.Decls = append(gen.Decls, cc4decls(v)...) - gen.Decls = append(gen.Decls, tagfuncdecl(k, v)) - } - gen.Decls = append(gen.Decls, cc4decls("mdat")...) - - for _, decl := range file.Decls { - if fndecl, ok := decl.(*ast.FuncDecl); ok { - ok, tag, name := splittagname(fndecl.Name.Name) - if ok { - gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) - gen.Decls = append(gen.Decls, getatommarshalfn(fndecl, name, tag, tagnamemap)...) - } else { - gen.Decls = append(gen.Decls, genatomdecl(fndecl, name, tag)...) - gen.Decls = append(gen.Decls, getstructputgetlenfn(fndecl, name)...) - } - } - } - - outfile, _ := os.Create(outfilename) - printer.Fprint(outfile, fset, gen) - outfile.Close() -} - -func parse(filename, outfilename string) { - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, filename, nil, 0) - if err != nil { - panic(err) - } - outfile, _ := os.Create(outfilename) - ast.Fprint(outfile, fset, file, nil) - outfile.Close() -} - -func main() { - switch os.Args[1] { - case "parse": - parse(os.Args[2], os.Args[3]) - - case "gen": - genatoms(os.Args[2], os.Args[3]) - } -} - diff --git a/machinery/src/mp4/mp4io/gen/pattern.go b/machinery/src/mp4/mp4io/gen/pattern.go deleted file mode 100644 index e039d8ee..00000000 --- a/machinery/src/mp4/mp4io/gen/pattern.go +++ /dev/null @@ -1,438 +0,0 @@ -package main - -import "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" - -func moov_Movie() { - atom(Header, MovieHeader) - atom(MovieExtend, MovieExtend) - atoms(Tracks, Track) - _unknowns() -} - -func mvhd_MovieHeader() { - uint8(Version) - uint24(Flags) - time32(CreateTime) - time32(ModifyTime) - int32(TimeScale) - int32(Duration) - fixed32(PreferredRate) - fixed16(PreferredVolume) - _skip(10) - array(Matrix, int32, 9) - time32(PreviewTime) - time32(PreviewDuration) - time32(PosterTime) - time32(SelectionTime) - time32(SelectionDuration) - time32(CurrentTime) - int32(NextTrackId) -} - -func trak_Track() { - atom(Header, TrackHeader) - atom(Media, Media) - _unknowns() -} - -func tkhd_TrackHeader() { - uint8(Version) - uint24(Flags) - time32(CreateTime) - time32(ModifyTime) - int32(TrackId) - _skip(4) - int32(Duration) - _skip(8) - int16(Layer) - int16(AlternateGroup) - fixed16(Volume) - _skip(2) - array(Matrix, int32, 9) - fixed32(TrackWidth) - fixed32(TrackHeight) -} - -func hdlr_HandlerRefer() { - uint8(Version) - uint24(Flags) - bytes(Type, 4) - bytes(SubType, 4) - bytesleft(Name) -} - -func mdia_Media() { - atom(Header, MediaHeader) - atom(Handler, HandlerRefer) - atom(Info, MediaInfo) - _unknowns() -} - -func mdhd_MediaHeader() { - uint8(Version) - uint24(Flags) - time32(CreateTime) - time32(ModifyTime) - int32(TimeScale) - int32(Duration) - int16(Language) - int16(Quality) -} - -func minf_MediaInfo() { - atom(Sound, SoundMediaInfo) - atom(Video, VideoMediaInfo) - atom(Data, DataInfo) - atom(Sample, SampleTable) - _unknowns() -} - -func dinf_DataInfo() { - atom(Refer, DataRefer) - _unknowns() -} - -func dref_DataRefer() { - uint8(Version) - uint24(Flags) - int32(_childrenNR) - atom(Url, DataReferUrl) -} - -func url__DataReferUrl() { - uint8(Version) - uint24(Flags) -} - -func smhd_SoundMediaInfo() { - uint8(Version) - uint24(Flags) - int16(Balance) - _skip(2) -} - -func vmhd_VideoMediaInfo() { - uint8(Version) - uint24(Flags) - int16(GraphicsMode) - array(Opcolor, int16, 3) -} - -func stbl_SampleTable() { - atom(SampleDesc, SampleDesc) - atom(TimeToSample, TimeToSample) - atom(CompositionOffset, CompositionOffset) - atom(SampleToChunk, SampleToChunk) - atom(SyncSample, SyncSample) - atom(ChunkOffset, ChunkOffset) - atom(SampleSize, SampleSize) -} - -func stsd_SampleDesc() { - uint8(Version) - _skip(3) - int32(_childrenNR) - atom(AVC1Desc, AVC1Desc) - atom(MP4ADesc, MP4ADesc) - _unknowns() -} - -func mp4a_MP4ADesc() { - _skip(6) - int16(DataRefIdx) - int16(Version) - int16(RevisionLevel) - int32(Vendor) - int16(NumberOfChannels) - int16(SampleSize) - int16(CompressionId) - _skip(2) - fixed32(SampleRate) - atom(Conf, ElemStreamDesc) - _unknowns() -} - -func avc1_AVC1Desc() { - _skip(6) - int16(DataRefIdx) - int16(Version) - int16(Revision) - int32(Vendor) - int32(TemporalQuality) - int32(SpatialQuality) - int16(Width) - int16(Height) - fixed32(HorizontalResolution) - fixed32(VorizontalResolution) - _skip(4) - int16(FrameCount) - bytes(CompressorName, 32) - int16(Depth) - int16(ColorTableId) - atom(Conf, AVC1Conf) - _unknowns() -} - -func avcC_AVC1Conf() { - bytesleft(Data) -} - -func stts_TimeToSample() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, TimeToSampleEntry) -} - -func TimeToSampleEntry() { - uint32(Count) - uint32(Duration) -} - -func stsc_SampleToChunk() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, SampleToChunkEntry) -} - -func SampleToChunkEntry() { - uint32(FirstChunk) - uint32(SamplesPerChunk) - uint32(SampleDescId) -} - -func ctts_CompositionOffset() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, CompositionOffsetEntry) -} - -func CompositionOffsetEntry() { - uint32(Count) - uint32(Offset) -} - -func stss_SyncSample() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, uint32) -} - -func stco_ChunkOffset() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - slice(Entries, uint32) -} - -func moof_MovieFrag() { - atom(Header, MovieFragHeader) - atoms(Tracks, TrackFrag) - _unknowns() -} - -func mfhd_MovieFragHeader() { - uint8(Version) - uint24(Flags) - uint32(Seqnum) -} - -func traf_TrackFrag() { - atom(Header, TrackFragHeader) - atom(DecodeTime, TrackFragDecodeTime) - atom(Run, TrackFragRun) - _unknowns() -} - -func mvex_MovieExtend() { - atoms(Tracks, TrackExtend) - _unknowns() -} - -func trex_TrackExtend() { - uint8(Version) - uint24(Flags) - uint32(TrackId) - uint32(DefaultSampleDescIdx) - uint32(DefaultSampleDuration) - uint32(DefaultSampleSize) - uint32(DefaultSampleFlags) -} - -func stsz_SampleSize() { - uint8(Version) - uint24(Flags) - uint32(SampleSize) - _code(func() { - if self.SampleSize != 0 { - return - } - }) - uint32(_len_Entries) - slice(Entries, uint32) -} - -func trun_TrackFragRun() { - uint8(Version) - uint24(Flags) - uint32(_len_Entries) - - uint32(DataOffset, _code(func() { - if self.Flags&TRUN_DATA_OFFSET != 0 { - doit() - } - })) - - uint32(FirstSampleFlags, _code(func() { - if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 { - doit() - } - })) - - slice(Entries, TrackFragRunEntry, _code(func() { - for i, entry := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - if flags&TRUN_SAMPLE_DURATION != 0 { - pio.PutU32BE(b[n:], entry.Duration) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - pio.PutU32BE(b[n:], entry.Size) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - pio.PutU32BE(b[n:], entry.Flags) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - pio.PutU32BE(b[n:], entry.Cts) - n += 4 - } - } - }, func() { - for i := range self.Entries { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - if flags&TRUN_SAMPLE_DURATION != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - n += 4 - } - } - }, func() { - for i := 0; i < int(_len_Entries); i++ { - var flags uint32 - if i > 0 { - flags = self.Flags - } else { - flags = self.FirstSampleFlags - } - entry := &self.Entries[i] - if flags&TRUN_SAMPLE_DURATION != 0 { - entry.Duration = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_SIZE != 0 { - entry.Size = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_FLAGS != 0 { - entry.Flags = pio.U32BE(b[n:]) - n += 4 - } - if flags&TRUN_SAMPLE_CTS != 0 { - entry.Cts = pio.U32BE(b[n:]) - n += 4 - } - } - })) -} - -func TrackFragRunEntry() { - uint32(Duration) - uint32(Size) - uint32(Flags) - uint32(Cts) -} - -func tfhd_TrackFragHeader() { - uint8(Version) - uint24(Flags) - - uint64(BaseDataOffset, _code(func() { - if self.Flags&TFHD_BASE_DATA_OFFSET != 0 { - doit() - } - })) - - uint32(StsdId, _code(func() { - if self.Flags&TFHD_STSD_ID != 0 { - doit() - } - })) - - uint32(DefaultDuration, _code(func() { - if self.Flags&TFHD_DEFAULT_DURATION != 0 { - doit() - } - })) - - uint32(DefaultSize, _code(func() { - if self.Flags&TFHD_DEFAULT_SIZE != 0 { - doit() - } - })) - - uint32(DefaultFlags, _code(func() { - if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - doit() - } - })) -} - -func tfdt_TrackFragDecodeTime() { - uint8(Version) - uint24(Flags) - time64(Time, _code(func() { - if self.Version != 0 { - PutTime64(b[n:], self.Time) - n += 8 - } else { - PutTime32(b[n:], self.Time) - n += 4 - } - }, func() { - if self.Version != 0 { - n += 8 - } else { - n += 4 - } - }, func() { - if self.Version != 0 { - self.Time = GetTime64(b[n:]) - n += 8 - } else { - self.Time = GetTime32(b[n:]) - n += 4 - } - })) -} diff --git a/machinery/src/mp4/mp4io/mp4io.go b/machinery/src/mp4/mp4io/mp4io.go deleted file mode 100644 index 432ff1dd..00000000 --- a/machinery/src/mp4/mp4io/mp4io.go +++ /dev/null @@ -1,535 +0,0 @@ -package mp4io - -import ( - "fmt" - "io" - "math" - "os" - "strings" - "time" - - "github.com/kerberos-io/joy4/utils/bits/pio" -) - -type ParseError struct { - Debug string - Offset int - prev *ParseError -} - -func (self *ParseError) Error() string { - s := []string{} - for p := self; p != nil; p = p.prev { - s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset)) - } - return "mp4io: parse error: " + strings.Join(s, ",") -} - -func parseErr(debug string, offset int, prev error) (err error) { - _prev, _ := prev.(*ParseError) - return &ParseError{Debug: debug, Offset: offset, prev: _prev} -} - -func GetTime32(b []byte) (t time.Time) { - sec := pio.U32BE(b) - t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) - t = t.Add(time.Second * time.Duration(sec)) - return -} - -func PutTime32(b []byte, t time.Time) { - dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) - sec := uint32(dur / time.Second) - pio.PutU32BE(b, sec) -} - -func GetTime64(b []byte) (t time.Time) { - sec := pio.U64BE(b) - t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) - t = t.Add(time.Second * time.Duration(sec)) - return -} - -func PutTime64(b []byte, t time.Time) { - dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) - sec := uint64(dur / time.Millisecond) - pio.PutU64BE(b, sec) -} - -func PutFixed16(b []byte, f float64) { - intpart, fracpart := math.Modf(f) - b[0] = uint8(intpart) - b[1] = uint8(fracpart * 256.0) -} - -func GetFixed16(b []byte) float64 { - return float64(b[0]) + float64(b[1])/256.0 -} - -func PutFixed32(b []byte, f float64) { - intpart, fracpart := math.Modf(f) - pio.PutU16BE(b[0:2], uint16(intpart)) - pio.PutU16BE(b[2:4], uint16(fracpart*65536.0)) -} - -func GetFixed32(b []byte) float64 { - return float64(pio.U16BE(b[0:2])) + float64(pio.U16BE(b[2:4]))/65536.0 -} - -type Tag uint32 - -func (self Tag) String() string { - var b [4]byte - pio.PutU32BE(b[:], uint32(self)) - for i := 0; i < 4; i++ { - if b[i] == 0 { - b[i] = ' ' - } - } - return string(b[:]) -} - -type Atom interface { - Pos() (int, int) - Tag() Tag - Marshal([]byte) int - Unmarshal([]byte, int) (int, error) - Len() int - Children() []Atom -} - -type AtomPos struct { - Offset int - Size int -} - -func (self AtomPos) Pos() (int, int) { - return self.Offset, self.Size -} - -func (self *AtomPos) setPos(offset int, size int) { - self.Offset, self.Size = offset, size -} - -type Dummy struct { - Data []byte - Tag_ Tag - AtomPos -} - -func (self Dummy) Children() []Atom { - return nil -} - -func (self Dummy) Tag() Tag { - return self.Tag_ -} - -func (self Dummy) Len() int { - return len(self.Data) -} - -func (self Dummy) Marshal(b []byte) int { - copy(b, self.Data) - return len(self.Data) -} - -func (self *Dummy) Unmarshal(b []byte, offset int) (n int, err error) { - (&self.AtomPos).setPos(offset, len(b)) - self.Data = b - n = len(b) - return -} - -func StringToTag(tag string) Tag { - var b [4]byte - copy(b[:], []byte(tag)) - return Tag(pio.U32BE(b[:])) -} - -func FindChildrenByName(root Atom, tag string) Atom { - return FindChildren(root, StringToTag(tag)) -} - -func FindChildren(root Atom, tag Tag) Atom { - if root.Tag() == tag { - return root - } - for _, child := range root.Children() { - if r := FindChildren(child, tag); r != nil { - return r - } - } - return nil -} - -const ( - TFHD_BASE_DATA_OFFSET = 0x01 - TFHD_STSD_ID = 0x02 - TFHD_DEFAULT_DURATION = 0x08 - TFHD_DEFAULT_SIZE = 0x10 - TFHD_DEFAULT_FLAGS = 0x20 - TFHD_DURATION_IS_EMPTY = 0x010000 - TFHD_DEFAULT_BASE_IS_MOOF = 0x020000 -) - -const ( - TRUN_DATA_OFFSET = 0x01 - TRUN_FIRST_SAMPLE_FLAGS = 0x04 - TRUN_SAMPLE_DURATION = 0x100 - TRUN_SAMPLE_SIZE = 0x200 - TRUN_SAMPLE_FLAGS = 0x400 - TRUN_SAMPLE_CTS = 0x800 -) - -const ( - MP4ESDescrTag = 3 - MP4DecConfigDescrTag = 4 - MP4DecSpecificDescrTag = 5 - MP4DescrTag = 6 -) - -const ( - MP4DecConfigDataSize = 2 + 3 + 4 + 4 -) - -type ElemStreamDesc struct { - DecConfig []byte - TrackId uint16 - AtomPos -} - -func (self ElemStreamDesc) Children() []Atom { - return nil -} - -func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) { - for i := 3; i > 0; i-- { - b[n] = uint8(length>>uint(7*i))&0x7f | 0x80 - n++ - } - b[n] = uint8(length & 0x7f) - n++ - return -} - -func (self ElemStreamDesc) lenDescHdr() (n int) { - return 5 -} - -func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) { - b[n] = tag - n++ - n += self.fillLength(b[n:], datalen) - return -} - -func (self ElemStreamDesc) lenESDescHdr() (n int) { - return self.lenDescHdr() -} - -func (self ElemStreamDesc) lenESDescData() (n int) { - return 3 -} - -func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) { - n += self.fillDescHdr(b[n:], MP4ESDescrTag, datalen) - pio.PutU16BE(b[n:], self.TrackId) - n += 2 - b[n] = 0 // flags - n++ - return -} - -func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) { - return self.lenDescHdr() + MP4DecConfigDataSize + self.lenDescHdr() -} - -func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) { - n += self.fillDescHdr(b[n:], MP4DecConfigDescrTag, datalen) - b[n] = 0x40 // objectid - n++ - b[n] = 0x15 // streamtype - n++ - // buffer size db - pio.PutU24BE(b[n:], 0) - n += 3 - // max bitrage - pio.PutU32BE(b[n:], uint32(200000)) - n += 4 - // avg bitrage - pio.PutU32BE(b[n:], uint32(0)) - n += 4 - n += self.fillDescHdr(b[n:], MP4DecSpecificDescrTag, datalen-n) - return -} - -func (self ElemStreamDesc) Len() (n int) { - n += 8 - n += 4 - // 0x03 MP4ESDescHeader - // 5 - n += self.lenESDescHdr() - - // + ESID + ESFlags - // + 2 + 1 - n += self.lenESDescData() - - // 0x04 MP4DecConfigDescrTag + MP4DecConfigDataSize + 0x05 MP4DecSpecificDescrHeader - // 5 + 13 + 5 - n += self.lenDecConfigDescHdr() - - // Variable size configuration - n += len(self.DecConfig) - - // 0x06 MP4DescrHeader + 1 - // 5 + 1 - n += self.lenDescHdr() + 1 - - return // 8 + 4 + self.lenESDescHdr() + self.lenDecConfigDescHdr() + len(self.DecConfig) + self.lenDescHdr() + 1 -} - -// Version(4) -// ESDesc( -// MP4ESDescrTag -// ESID(2) -// ESFlags(1) -// DecConfigDesc( -// MP4DecConfigDescrTag -// objectId streamType bufSize avgBitrate -// DecSpecificDesc( -// MP4DecSpecificDescrTag -// decConfig -// ) -// ) -// ?Desc(lenDescHdr+1) -// ) - -func (self ElemStreamDesc) Marshal(b []byte) (n int) { - pio.PutU32BE(b[4:], uint32(ESDS)) - n += 8 - pio.PutU32BE(b[n:], 0) // Version - n += 4 - datalen := self.Len() - n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr()) - - n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-self.lenDescHdr()-1) - copy(b[n:], self.DecConfig) - n += len(self.DecConfig) - - n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr()) - b[n] = 0x02 - n++ - pio.PutU32BE(b[0:], uint32(n)) - return -} - -func (self *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) { - if len(b) < n+12 { - err = parseErr("hdr", offset+n, err) - return - } - (&self.AtomPos).setPos(offset, len(b)) - n += 8 - n += 4 - return self.parseDesc(b[n:], offset+n) -} - -func (self *ElemStreamDesc) parseDesc(b []byte, offset int) (n int, err error) { - var hdrlen int - var datalen int - var tag uint8 - if hdrlen, tag, datalen, err = self.parseDescHdr(b, offset); err != nil { - return - } - - // Skip over the header lenth (tag size 1 byte + lenlen) - n += hdrlen - - if len(b) < n+datalen { - err = parseErr("datalen", offset+n, err) - return - } - - switch tag { - case MP4ESDescrTag: - if len(b) < n+3 { - err = parseErr("MP4ESDescrTag", offset+n, err) - return - } - if _, err = self.parseDesc(b[n+3:], offset+n+3); err != nil { - return - } - - case MP4DecConfigDescrTag: - if len(b) < n+MP4DecConfigDataSize { - err = parseErr("MP4DecSpecificDescrTag", offset+n, err) - return - } - if _, err = self.parseDesc(b[n+MP4DecConfigDataSize:], offset+n+MP4DecConfigDataSize); err != nil { - return - } - - case MP4DecSpecificDescrTag: - self.DecConfig = b[n : n+datalen] - } - - n += datalen - return -} - -func (self *ElemStreamDesc) parseLength(b []byte, offset int) (n int, length int, err error) { - for n < 4 { - if len(b) < n+1 { - err = parseErr("len", offset+n, err) - return - } - c := b[n] - n++ - length = (length << 7) | (int(c) & 0x7f) - if c&0x80 == 0 { - break - } - } - return -} - -func (self *ElemStreamDesc) parseDescHdr(b []byte, offset int) (n int, tag uint8, datalen int, err error) { - var lenlen int - if len(b) < n+1 { - err = parseErr("tag", offset+n, err) - return - } - tag = b[n] - n++ - if lenlen, datalen, err = self.parseLength(b[n:], offset+n); err != nil { - return - } - n += lenlen - return -} - -func ReadFileAtoms(r io.ReadSeeker) (atoms []Atom, err error) { - for { - offset, _ := r.Seek(0, 1) - taghdr := make([]byte, 8) - if _, err = io.ReadFull(r, taghdr); err != nil { - if err == io.EOF { - err = nil - } - return - } - size := pio.U32BE(taghdr[0:]) - tag := Tag(pio.U32BE(taghdr[4:])) - - var atom Atom - switch tag { - case MOOV: - atom = &Movie{} - case MOOF: - atom = &MovieFrag{} - } - - if atom != nil { - b := make([]byte, int(size)) - if _, err = io.ReadFull(r, b[8:]); err != nil { - return - } - copy(b, taghdr) - if _, err = atom.Unmarshal(b, int(offset)); err != nil { - return - } - atoms = append(atoms, atom) - } else { - dummy := &Dummy{Tag_: tag} - dummy.setPos(int(offset), int(size)) - if _, err = r.Seek(int64(size)-8, 1); err != nil { - return - } - atoms = append(atoms, dummy) - } - } - return -} - -func printatom(out io.Writer, root Atom, depth int) { - offset, size := root.Pos() - - type stringintf interface { - String() string - } - - fmt.Fprintf(out, - "%s%s offset=%d size=%d", - strings.Repeat(" ", depth*2), root.Tag(), offset, size, - ) - if str, ok := root.(stringintf); ok { - fmt.Fprint(out, " ", str.String()) - } - fmt.Fprintln(out) - - children := root.Children() - for _, child := range children { - printatom(out, child, depth+1) - } -} - -func FprintAtom(out io.Writer, root Atom) { - printatom(out, root, 0) -} - -func PrintAtom(root Atom) { - FprintAtom(os.Stdout, root) -} - -func (self MovieHeader) String() string { - return fmt.Sprintf("dur=%d", self.Duration) -} - -func (self TimeToSample) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self SampleToChunk) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self SampleSize) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self SyncSample) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self CompositionOffset) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self ChunkOffset) String() string { - return fmt.Sprintf("entries=%d", len(self.Entries)) -} - -func (self TrackFragRun) String() string { - return fmt.Sprintf("dataoffset=%d", self.DataOffset) -} - -func (self TrackFragHeader) String() string { - return fmt.Sprintf("basedataoffset=%d", self.BaseDataOffset) -} - -func (self ElemStreamDesc) String() string { - return fmt.Sprintf("configlen=%d", len(self.DecConfig)) -} - -func (self *Track) GetAVC1Conf() (conf *AVC1Conf) { - atom := FindChildren(self, AVCC) - conf, _ = atom.(*AVC1Conf) - return -} - -func (self *Track) GetElemStreamDesc() (esds *ElemStreamDesc) { - atom := FindChildren(self, ESDS) - esds, _ = atom.(*ElemStreamDesc) - return -} diff --git a/machinery/src/mp4/muxer.go b/machinery/src/mp4/muxer.go deleted file mode 100644 index ce007b2a..00000000 --- a/machinery/src/mp4/muxer.go +++ /dev/null @@ -1,447 +0,0 @@ -package mp4 - -import ( - "fmt" - "io" - "time" - - "github.com/kerberos-io/agent/machinery/src/mp4/mp4io" - "github.com/kerberos-io/agent/machinery/src/packets" - "github.com/kerberos-io/agent/machinery/src/utils/bits/pio" -) - -type Muxer struct { - w io.WriteSeeker - wpos int64 - streams []*Stream - videoCodecIndex int - AudioCodecIndex int -} - -func NewMuxer(w io.WriteSeeker) *Muxer { - return &Muxer{ - w: w, - } -} - -func (self *Muxer) newStream(codec packets.Stream, index int, withoutAudio bool) (err error) { - switch codec.Name { - case "H264": - self.videoCodecIndex = index - case "AAC": - default: - self.AudioCodecIndex = index - if withoutAudio { - return - } - } - - stream := &Stream{CodecData: codec} - - stream.sample = &mp4io.SampleTable{ - SampleDesc: &mp4io.SampleDesc{}, - TimeToSample: &mp4io.TimeToSample{}, - SampleToChunk: &mp4io.SampleToChunk{ - Entries: []mp4io.SampleToChunkEntry{ - { - FirstChunk: 1, - SampleDescId: 1, - SamplesPerChunk: 1, - }, - }, - }, - SampleSize: &mp4io.SampleSize{}, - ChunkOffset: &mp4io.ChunkOffset{}, - } - - stream.trackAtom = &mp4io.Track{ - Header: &mp4io.TrackHeader{ - TrackId: int32(len(self.streams) + 1), - Flags: 0x0003, // Track enabled | Track in movie - Duration: 0, // fill later - Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - }, - Media: &mp4io.Media{ - Header: &mp4io.MediaHeader{ - TimeScale: 0, // fill later - Duration: 0, // fill later - Language: 21956, - }, - Info: &mp4io.MediaInfo{ - Sample: stream.sample, - Data: &mp4io.DataInfo{ - Refer: &mp4io.DataRefer{ - Url: &mp4io.DataReferUrl{ - Flags: 0x000001, // Self reference - }, - }, - }, - }, - }, - } - - switch codec.Name { - case "H264", "H265": - stream.sample.SyncSample = &mp4io.SyncSample{} - } - - stream.timeScale = 10000000 - stream.muxer = self - self.streams = append(self.streams, stream) - - return -} -func (self *Stream) fillTrackAtom() (err error) { - self.trackAtom.Media.Header.TimeScale = int32(self.timeScale) - self.trackAtom.Media.Header.Duration = int32(self.duration) - - if self.CodecData.Name == "H264" { - - codec := self.CodecData - width, height := codec.Width, codec.Height - decoderData := []byte{} - - recordinfo := AVCDecoderConfRecord{} - if len(self.CodecData.SPS) > 0 { - recordinfo.AVCProfileIndication = self.CodecData.SPS[1] - recordinfo.ProfileCompatibility = self.CodecData.SPS[2] - recordinfo.AVCLevelIndication = self.CodecData.SPS[3] - recordinfo.SPS = [][]byte{self.CodecData.SPS} - - } - if len(self.CodecData.PPS) > 0 { - recordinfo.PPS = [][]byte{self.CodecData.PPS} - } - recordinfo.LengthSizeMinusOne = 3 // check... - - buf := make([]byte, recordinfo.Len()) - recordinfo.Marshal(buf) - - self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{ - DataRefIdx: 1, - HorizontalResolution: 72, - VorizontalResolution: 72, - Width: int16(width), - Height: int16(height), - FrameCount: 1, - Depth: 24, - ColorTableId: -1, - Conf: &mp4io.AVC1Conf{Data: decoderData}, - } - self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ - SubType: [4]byte{'v', 'i', 'd', 'e'}, - Name: []byte("Video Media Handler"), - } - self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{ - Flags: 0x000001, - } - self.trackAtom.Header.TrackWidth = float64(width) - self.trackAtom.Header.TrackHeight = float64(height) - - } else if self.CodecData.Name == "AAC" { - /*codec := self.CodecData.(aacparser.CodecData) - self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{ - DataRefIdx: 1, - NumberOfChannels: int16(codec.ChannelLayout().Count()), - SampleSize: int16(codec.SampleFormat().BytesPerSample()), - SampleRate: float64(codec.SampleRate()), - Conf: &mp4io.ElemStreamDesc{ - DecConfig: codec.MPEG4AudioConfigBytes(), - }, - } - self.trackAtom.Header.Volume = 1 - self.trackAtom.Header.AlternateGroup = 1 - self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ - SubType: [4]byte{'s', 'o', 'u', 'n'}, - Name: []byte("Sound Handler"), - } - self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}*/ - - } else { - err = fmt.Errorf("mp4: codec type=%d invalid", self.CodecData.Name) - } - - return -} - -func (self *Muxer) WriteHeader(streams []packets.Stream) (err error) { - self.streams = []*Stream{} - for index, stream := range streams { - if err = self.newStream(stream, index, false); err != nil { - } - } - - taghdr := make([]byte, 8) - pio.PutU32BE(taghdr[4:], uint32(mp4io.MDAT)) - if _, err = self.w.Write(taghdr); err != nil { - return - } - taghdr = nil - self.wpos += 8 - - for _, stream := range self.streams { - if stream.CodecData.Name == "H264" { - stream.sample.CompositionOffset = &mp4io.CompositionOffset{} - } - } - return -} - -func (self *Muxer) WritePacket(pkt packets.Packet) (err error) { - stream := self.streams[pkt.Idx] - switch stream.CodecData.Name { - case "H264", "AAC": - if stream.lastpkt != nil { - if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil { - return - } - } - stream.lastpkt = &pkt - return - default: - return - } -} - -func (self *Muxer) Write(buffer []byte, channel int, time uint32) (err error) { - return nil -} - -func (self *Stream) writePacket(pkt packets.Packet, rawdur time.Duration) (err error) { - if rawdur < 0 { - err = fmt.Errorf("mp4: stream#%d time=%v < lasttime=%v", pkt.Idx, pkt.Time, self.lastpkt.Time) - return - } - - if _, err = self.muxer.w.Write(pkt.Data); err != nil { - return - } - - if pkt.IsKeyFrame && self.sample.SyncSample != nil { - self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, uint32(self.sampleIndex+1)) - } - - duration := uint32(self.timeToTs(rawdur)) - if self.sttsEntry == nil || duration != self.sttsEntry.Duration { - self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, mp4io.TimeToSampleEntry{Duration: duration}) - self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] - } - self.sttsEntry.Count++ - - if self.sample.CompositionOffset != nil { - offset := uint32(self.timeToTs(pkt.CompositionTime)) - if self.cttsEntry == nil || offset != self.cttsEntry.Offset { - table := self.sample.CompositionOffset - table.Entries = append(table.Entries, mp4io.CompositionOffsetEntry{Offset: offset}) - self.cttsEntry = &table.Entries[len(table.Entries)-1] - } - self.cttsEntry.Count++ - } - - self.duration += int64(duration) - self.sampleIndex++ - self.sample.ChunkOffset.Entries = append(self.sample.ChunkOffset.Entries, uint32(self.muxer.wpos)) - self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, uint32(len(pkt.Data))) - - self.muxer.wpos += int64(len(pkt.Data)) - return -} - -func (self *Muxer) WriteTrailer() (err error) { - - for _, stream := range self.streams { - switch stream.CodecData.Name { - case "H264", "AAC": - if stream.lastpkt != nil { - if err = stream.writePacket(*stream.lastpkt, 0); err != nil { - //return - } - stream.lastpkt = nil - } - default: - } - } - - moov := &mp4io.Movie{} - moov.Header = &mp4io.MovieHeader{ - PreferredRate: 1, - PreferredVolume: 1, - Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - NextTrackId: 2, - } - - maxDur := time.Duration(0) - //timeScale := int64(10000) - timeScale := int64(1000) - for _, stream := range self.streams { - switch stream.CodecData.Name { - case "H264", "AAC": - if err = stream.fillTrackAtom(); err != nil { - return - } - dur := stream.tsToTime(stream.duration) - stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale)) - if dur > maxDur { - maxDur = dur - } - moov.Tracks = append(moov.Tracks, stream.trackAtom) - } - } - moov.Header.TimeScale = int32(timeScale) - moov.Header.Duration = int32(timeToTs(maxDur, timeScale)) - - var mdatsize int64 - if mdatsize, err = self.w.Seek(0, 1); err != nil { - return - } - if _, err = self.w.Seek(0, 0); err != nil { - return - } - taghdr := make([]byte, 4) - pio.PutU32BE(taghdr, uint32(mdatsize)) - if _, err = self.w.Write(taghdr); err != nil { - return - } - taghdr = nil - - if _, err = self.w.Seek(0, 2); err != nil { - return - } - b := make([]byte, moov.Len()) - moov.Marshal(b) - if _, err = self.w.Write(b); err != nil { - return - } - b = nil - return -} - -func (self *Muxer) WriteTrailerWithPacket(pkt packets.Packet) (err error) { - - for _, stream := range self.streams { - switch stream.CodecData.Name { - case "H264", "AAC": - if stream.lastpkt != nil { - if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil { - //return - } - stream.lastpkt = nil - } - default: - } - } - - moov := &mp4io.Movie{} - moov.Header = &mp4io.MovieHeader{ - PreferredRate: 1, - PreferredVolume: 1, - Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - NextTrackId: 2, - } - - maxDur := time.Duration(0) - //timeScale := int64(10000) - timeScale := int64(1000) - for _, stream := range self.streams { - switch stream.CodecData.Name { - case "H264", "AAC": - if err = stream.fillTrackAtom(); err != nil { - return - } - dur := stream.tsToTime(stream.duration) - stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale)) - if dur > maxDur { - maxDur = dur - } - moov.Tracks = append(moov.Tracks, stream.trackAtom) - } - } - moov.Header.TimeScale = int32(timeScale) - moov.Header.Duration = int32(timeToTs(maxDur, timeScale)) - - var mdatsize int64 - if mdatsize, err = self.w.Seek(0, 1); err != nil { - return - } - if _, err = self.w.Seek(0, 0); err != nil { - return - } - taghdr := make([]byte, 4) - pio.PutU32BE(taghdr, uint32(mdatsize)) - if _, err = self.w.Write(taghdr); err != nil { - return - } - taghdr = nil - - if _, err = self.w.Seek(0, 2); err != nil { - return - } - b := make([]byte, moov.Len()) - moov.Marshal(b) - if _, err = self.w.Write(b); err != nil { - return - } - b = nil - return -} - -func (self *Muxer) Close() (err error) { - for _, stream := range self.streams { - stream.muxer = nil - stream.trackAtom = nil - stream.sample = nil - stream.lastpkt = nil - stream = nil - } - self.streams = nil - return -} - -type AVCDecoderConfRecord struct { - AVCProfileIndication uint8 - ProfileCompatibility uint8 - AVCLevelIndication uint8 - LengthSizeMinusOne uint8 - SPS [][]byte - PPS [][]byte -} - -func (self AVCDecoderConfRecord) Len() (n int) { - n = 7 - for _, sps := range self.SPS { - n += 2 + len(sps) - } - for _, pps := range self.PPS { - n += 2 + len(pps) - } - return -} - -func (self AVCDecoderConfRecord) Marshal(b []byte) (n int) { - b[0] = 1 - b[1] = self.AVCProfileIndication - b[2] = self.ProfileCompatibility - b[3] = self.AVCLevelIndication - b[4] = self.LengthSizeMinusOne | 0xfc - b[5] = uint8(len(self.SPS)) | 0xe0 - n += 6 - - for _, sps := range self.SPS { - pio.PutU16BE(b[n:], uint16(len(sps))) - n += 2 - copy(b[n:], sps) - n += len(sps) - } - - b[n] = uint8(len(self.PPS)) - n++ - - for _, pps := range self.PPS { - pio.PutU16BE(b[n:], uint16(len(pps))) - n += 2 - copy(b[n:], pps) - n += len(pps) - } - - return -} diff --git a/machinery/src/mp4/stream.go b/machinery/src/mp4/stream.go deleted file mode 100644 index e9eb5133..00000000 --- a/machinery/src/mp4/stream.go +++ /dev/null @@ -1,58 +0,0 @@ -package mp4 - -import ( - "time" - - "github.com/kerberos-io/agent/machinery/src/mp4/mp4io" - "github.com/kerberos-io/agent/machinery/src/packets" -) - -type Stream struct { - CodecData packets.Stream - - trackAtom *mp4io.Track - idx int - - lastpkt *packets.Packet - - timeScale int64 - duration int64 - - muxer *Muxer - - sample *mp4io.SampleTable - sampleIndex int - - sampleOffsetInChunk int64 - syncSampleIndex int - - dts int64 - sttsEntryIndex int - sampleIndexInSttsEntry int - - cttsEntryIndex int - sampleIndexInCttsEntry int - - chunkGroupIndex int - chunkIndex int - sampleIndexInChunk int - - sttsEntry *mp4io.TimeToSampleEntry - cttsEntry *mp4io.CompositionOffsetEntry -} - -func timeToTs(tm time.Duration, timeScale int64) int64 { - return int64(tm * time.Duration(timeScale) / time.Second) -} - -func tsToTime(ts int64, timeScale int64) time.Duration { - return time.Duration(ts) * time.Second / time.Duration(timeScale) -} - -func (self *Stream) timeToTs(tm time.Duration) int64 { - return int64(tm * time.Duration(self.timeScale) / time.Second) -} - -func (self *Stream) tsToTime(ts int64) time.Duration { - return time.Duration(ts) * time.Second / time.Duration(self.timeScale) -} diff --git a/machinery/src/utils/bits/bits.go b/machinery/src/utils/bits/bits.go deleted file mode 100644 index 4a09f0a2..00000000 --- a/machinery/src/utils/bits/bits.go +++ /dev/null @@ -1,118 +0,0 @@ -package bits - -import ( - "io" -) - -type Reader struct { - R io.Reader - n int - bits uint64 -} - -func (self *Reader) ReadBits64(n int) (bits uint64, err error) { - if self.n < n { - var b [8]byte - var got int - want := (n - self.n + 7) / 8 - if got, err = self.R.Read(b[:want]); err != nil { - return - } - if got < want { - err = io.EOF - return - } - for i := 0; i < got; i++ { - self.bits <<= 8 - self.bits |= uint64(b[i]) - } - self.n += got * 8 - } - bits = self.bits >> uint(self.n-n) - self.bits ^= bits << uint(self.n-n) - self.n -= n - return -} - -func (self *Reader) ReadBits(n int) (bits uint, err error) { - var bits64 uint64 - if bits64, err = self.ReadBits64(n); err != nil { - return - } - bits = uint(bits64) - return -} - -func (self *Reader) Read(p []byte) (n int, err error) { - for n < len(p) { - want := 8 - if len(p)-n < want { - want = len(p) - n - } - var bits uint64 - if bits, err = self.ReadBits64(want * 8); err != nil { - break - } - for i := 0; i < want; i++ { - p[n+i] = byte(bits >> uint((want-i-1)*8)) - } - n += want - } - return -} - -type Writer struct { - W io.Writer - n int - bits uint64 -} - -func (self *Writer) WriteBits64(bits uint64, n int) (err error) { - if self.n+n > 64 { - move := uint(64 - self.n) - mask := bits >> move - self.bits = (self.bits << move) | mask - self.n = 64 - if err = self.FlushBits(); err != nil { - return - } - n -= int(move) - bits ^= (mask << move) - } - self.bits = (self.bits << uint(n)) | bits - self.n += n - return -} - -func (self *Writer) WriteBits(bits uint, n int) (err error) { - return self.WriteBits64(uint64(bits), n) -} - -func (self *Writer) Write(p []byte) (n int, err error) { - for n < len(p) { - if err = self.WriteBits64(uint64(p[n]), 8); err != nil { - return - } - n++ - } - return -} - -func (self *Writer) FlushBits() (err error) { - if self.n > 0 { - var b [8]byte - bits := self.bits - if self.n%8 != 0 { - bits <<= uint(8 - (self.n % 8)) - } - want := (self.n + 7) / 8 - for i := 0; i < want; i++ { - b[i] = byte(bits >> uint((want-i-1)*8)) - } - if _, err = self.W.Write(b[:want]); err != nil { - return - } - self.n = 0 - } - return -} diff --git a/machinery/src/utils/bits/bits_test.go b/machinery/src/utils/bits/bits_test.go deleted file mode 100644 index c4957c94..00000000 --- a/machinery/src/utils/bits/bits_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package bits - -import ( - "bytes" - "testing" -) - -func TestBits(t *testing.T) { - rdata := []byte{0xf3, 0xb3, 0x45, 0x60} - rbuf := bytes.NewReader(rdata[:]) - r := &Reader{R: rbuf} - var u32 uint - if u32, _ = r.ReadBits(4); u32 != 0xf { - t.FailNow() - } - if u32, _ = r.ReadBits(4); u32 != 0x3 { - t.FailNow() - } - if u32, _ = r.ReadBits(2); u32 != 0x2 { - t.FailNow() - } - if u32, _ = r.ReadBits(2); u32 != 0x3 { - t.FailNow() - } - b := make([]byte, 2) - if r.Read(b); b[0] != 0x34 || b[1] != 0x56 { - t.FailNow() - } - - wbuf := &bytes.Buffer{} - w := &Writer{W: wbuf} - w.WriteBits(0xf, 4) - w.WriteBits(0x3, 4) - w.WriteBits(0x2, 2) - w.WriteBits(0x3, 2) - n, _ := w.Write([]byte{0x34, 0x56}) - if n != 2 { - t.FailNow() - } - w.FlushBits() - wdata := wbuf.Bytes() - if wdata[0] != 0xf3 || wdata[1] != 0xb3 || wdata[2] != 0x45 || wdata[3] != 0x60 { - t.FailNow() - } - - b = make([]byte, 8) - PutUInt64BE(b, 0x11223344, 32) - if b[0] != 0x11 || b[1] != 0x22 || b[2] != 0x33 || b[3] != 0x44 { - t.FailNow() - } -} diff --git a/machinery/src/utils/bits/bufio/bufio.go b/machinery/src/utils/bits/bufio/bufio.go deleted file mode 100644 index ec7eedfe..00000000 --- a/machinery/src/utils/bits/bufio/bufio.go +++ /dev/null @@ -1,23 +0,0 @@ -package bufio - -import ( - "io" -) - -type Reader struct { - buf [][]byte - R io.ReadSeeker -} - -func NewReaderSize(r io.ReadSeeker, size int) *Reader { - buf := make([]byte, size*2) - return &Reader{ - R: r, - buf: [][]byte{buf[0:size], buf[size:]}, - } -} - -func (self *Reader) ReadAt(b []byte, off int64) (n int, err error) { - return -} - diff --git a/machinery/src/utils/bits/golomb_reader.go b/machinery/src/utils/bits/golomb_reader.go deleted file mode 100644 index b40978be..00000000 --- a/machinery/src/utils/bits/golomb_reader.go +++ /dev/null @@ -1,89 +0,0 @@ -package bits - -import ( - "io" -) - -type GolombBitReader struct { - R io.Reader - buf [1]byte - left byte -} - -func (self *GolombBitReader) ReadBit() (res uint, err error) { - if self.left == 0 { - if _, err = self.R.Read(self.buf[:]); err != nil { - return - } - self.left = 8 - } - self.left-- - res = uint(self.buf[0]>>self.left) & 1 - return -} - -func (self *GolombBitReader) ReadBits(n int) (res uint, err error) { - for i := 0; i < n; i++ { - var bit uint - if bit, err = self.ReadBit(); err != nil { - return - } - res |= bit << uint(n-i-1) - } - return -} - -func (self *GolombBitReader) ReadBits32(n uint) (r uint32, err error) { - var t uint - for i := uint(0); i < n; i++ { - t, err = self.ReadBit() - if err != nil { - return - } - r = (r << 1) | uint32(t) - } - return -} - -func (self *GolombBitReader) ReadBits64(n uint) (r uint64, err error) { - var t uint - for i := uint(0); i < n; i++ { - t, err = self.ReadBit() - if err != nil { - return - } - r = (r << 1) | uint64(t) - } - return -} - -func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) { - i := 0 - for { - var bit uint - if bit, err = self.ReadBit(); err != nil { - return - } - if !(bit == 0 && i < 32) { - break - } - i++ - } - if res, err = self.ReadBits(i); err != nil { - return - } - res += (1 << uint(i)) - 1 - return -} - -func (self *GolombBitReader) ReadSE() (res uint, err error) { - if res, err = self.ReadExponentialGolombCode(); err != nil { - return - } - if res&0x01 != 0 { - res = (res + 1) / 2 - } else { - res = -res / 2 - } - return -} diff --git a/machinery/src/utils/bits/pio/pio.go b/machinery/src/utils/bits/pio/pio.go deleted file mode 100644 index 3d7660da..00000000 --- a/machinery/src/utils/bits/pio/pio.go +++ /dev/null @@ -1,3 +0,0 @@ -package pio - -var RecommendBufioSize = 1024 * 1024 * 1 // 1mb in buffer, then write :) diff --git a/machinery/src/utils/bits/pio/reader.go b/machinery/src/utils/bits/pio/reader.go deleted file mode 100644 index 87f024bb..00000000 --- a/machinery/src/utils/bits/pio/reader.go +++ /dev/null @@ -1,91 +0,0 @@ - -package pio - -func U8(b []byte) (i uint8) { - return b[0] -} - -func U16BE(b []byte) (i uint16) { - i = uint16(b[0]) - i <<= 8; i |= uint16(b[1]) - return -} - -func I16BE(b []byte) (i int16) { - i = int16(b[0]) - i <<= 8; i |= int16(b[1]) - return -} - -func I24BE(b []byte) (i int32) { - i = int32(int8(b[0])) - i <<= 8; i |= int32(b[1]) - i <<= 8; i |= int32(b[2]) - return -} - -func U24BE(b []byte) (i uint32) { - i = uint32(b[0]) - i <<= 8; i |= uint32(b[1]) - i <<= 8; i |= uint32(b[2]) - return -} - -func I32BE(b []byte) (i int32) { - i = int32(int8(b[0])) - i <<= 8; i |= int32(b[1]) - i <<= 8; i |= int32(b[2]) - i <<= 8; i |= int32(b[3]) - return -} - -func U32LE(b []byte) (i uint32) { - i = uint32(b[3]) - i <<= 8; i |= uint32(b[2]) - i <<= 8; i |= uint32(b[1]) - i <<= 8; i |= uint32(b[0]) - return -} - -func U32BE(b []byte) (i uint32) { - i = uint32(b[0]) - i <<= 8; i |= uint32(b[1]) - i <<= 8; i |= uint32(b[2]) - i <<= 8; i |= uint32(b[3]) - return -} - -func U40BE(b []byte) (i uint64) { - i = uint64(b[0]) - i <<= 8; i |= uint64(b[1]) - i <<= 8; i |= uint64(b[2]) - i <<= 8; i |= uint64(b[3]) - i <<= 8; i |= uint64(b[4]) - return -} - -func U64BE(b []byte) (i uint64) { - i = uint64(b[0]) - i <<= 8; i |= uint64(b[1]) - i <<= 8; i |= uint64(b[2]) - i <<= 8; i |= uint64(b[3]) - i <<= 8; i |= uint64(b[4]) - i <<= 8; i |= uint64(b[5]) - i <<= 8; i |= uint64(b[6]) - i <<= 8; i |= uint64(b[7]) - return -} - -func I64BE(b []byte) (i int64) { - i = int64(int8(b[0])) - i <<= 8; i |= int64(b[1]) - i <<= 8; i |= int64(b[2]) - i <<= 8; i |= int64(b[3]) - i <<= 8; i |= int64(b[4]) - i <<= 8; i |= int64(b[5]) - i <<= 8; i |= int64(b[6]) - i <<= 8; i |= int64(b[7]) - return -} - - diff --git a/machinery/src/utils/bits/pio/vec.go b/machinery/src/utils/bits/pio/vec.go deleted file mode 100644 index 30d5e921..00000000 --- a/machinery/src/utils/bits/pio/vec.go +++ /dev/null @@ -1,69 +0,0 @@ -package pio - -func VecLen(vec [][]byte) (n int) { - for _, b := range vec { - n += len(b) - } - return -} - -func VecSliceTo(in [][]byte, out [][]byte, s int, e int) (n int) { - if s < 0 { - s = 0 - } - - if e >= 0 && e < s { - panic("pio: VecSlice start > end") - } - - i := 0 - off := 0 - for s > 0 && i < len(in) { - left := len(in[i]) - read := s - if left < read { - read = left - } - left -= read - off += read - s -= read - e -= read - if left == 0 { - i++ - off = 0 - } - } - if s > 0 { - panic("pio: VecSlice start out of range") - } - - for e != 0 && i < len(in) { - left := len(in[i])-off - read := left - if e > 0 && e < read { - read = e - } - out[n] = in[i][off:off+read] - n++ - left -= read - e -= read - off += read - if left == 0 { - i++ - off = 0 - } - } - if e > 0 { - panic("pio: VecSlice end out of range") - } - - return -} - -func VecSlice(in [][]byte, s int, e int) (out [][]byte) { - out = make([][]byte, len(in)) - n := VecSliceTo(in, out, s, e) - out = out[:n] - return -} - diff --git a/machinery/src/utils/bits/pio/vec_test.go b/machinery/src/utils/bits/pio/vec_test.go deleted file mode 100644 index 99ebb55e..00000000 --- a/machinery/src/utils/bits/pio/vec_test.go +++ /dev/null @@ -1,22 +0,0 @@ - -package pio - -import ( - "fmt" -) - -func ExampleVec() { - vec := [][]byte{[]byte{1,2,3}, []byte{4,5,6,7,8,9}, []byte{10,11,12,13}} - println(VecLen(vec)) - - vec = VecSlice(vec, 1, -1) - fmt.Println(vec) - - vec = VecSlice(vec, 2, -1) - fmt.Println(vec) - - vec = VecSlice(vec, 8, 8) - fmt.Println(vec) - - // Output: -} diff --git a/machinery/src/utils/bits/pio/writer.go b/machinery/src/utils/bits/pio/writer.go deleted file mode 100644 index 2e709f9d..00000000 --- a/machinery/src/utils/bits/pio/writer.go +++ /dev/null @@ -1,89 +0,0 @@ - -package pio - -func PutU8(b []byte, v uint8) { - b[0] = v -} - -func PutI16BE(b []byte, v int16) { - b[0] = byte(v>>8) - b[1] = byte(v) -} - -func PutU16BE(b []byte, v uint16) { - b[0] = byte(v>>8) - b[1] = byte(v) -} - -func PutI24BE(b []byte, v int32) { - b[0] = byte(v>>16) - b[1] = byte(v>>8) - b[2] = byte(v) -} - -func PutU24BE(b []byte, v uint32) { - b[0] = byte(v>>16) - b[1] = byte(v>>8) - b[2] = byte(v) -} - -func PutI32BE(b []byte, v int32) { - b[0] = byte(v>>24) - b[1] = byte(v>>16) - b[2] = byte(v>>8) - b[3] = byte(v) -} - -func PutU32BE(b []byte, v uint32) { - b[0] = byte(v>>24) - b[1] = byte(v>>16) - b[2] = byte(v>>8) - b[3] = byte(v) -} - -func PutU32LE(b []byte, v uint32) { - b[3] = byte(v>>24) - b[2] = byte(v>>16) - b[1] = byte(v>>8) - b[0] = byte(v) -} - -func PutU40BE(b []byte, v uint64) { - b[0] = byte(v>>32) - b[1] = byte(v>>24) - b[2] = byte(v>>16) - b[3] = byte(v>>8) - b[4] = byte(v) -} - -func PutU48BE(b []byte, v uint64) { - b[0] = byte(v>>40) - b[1] = byte(v>>32) - b[2] = byte(v>>24) - b[3] = byte(v>>16) - b[4] = byte(v>>8) - b[5] = byte(v) -} - -func PutU64BE(b []byte, v uint64) { - b[0] = byte(v>>56) - b[1] = byte(v>>48) - b[2] = byte(v>>40) - b[3] = byte(v>>32) - b[4] = byte(v>>24) - b[5] = byte(v>>16) - b[6] = byte(v>>8) - b[7] = byte(v) -} - -func PutI64BE(b []byte, v int64) { - b[0] = byte(v>>56) - b[1] = byte(v>>48) - b[2] = byte(v>>40) - b[3] = byte(v>>32) - b[4] = byte(v>>24) - b[5] = byte(v>>16) - b[6] = byte(v>>8) - b[7] = byte(v) -} - From ed46cbe35ae044073e257114d59397f7dea50857 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 00:47:30 +0100 Subject: [PATCH 10/81] cleanup enable more features --- machinery/src/capture/IPCamera.go | 3 +- machinery/src/capture/main.go | 17 -- machinery/src/components/Kerberos.go | 334 ++++++++---------------- machinery/src/computervision/main.go | 13 +- machinery/src/models/Communication.go | 12 +- machinery/src/routers/websocket/main.go | 18 +- 6 files changed, 122 insertions(+), 275 deletions(-) diff --git a/machinery/src/capture/IPCamera.go b/machinery/src/capture/IPCamera.go index f08d09aa..cee77d05 100644 --- a/machinery/src/capture/IPCamera.go +++ b/machinery/src/capture/IPCamera.go @@ -7,13 +7,14 @@ import ( "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/packets" "github.com/kerberos-io/joy4/av/pubsub" "github.com/kerberos-io/joy4/av" "github.com/kerberos-io/joy4/cgo/ffmpeg" ) -func DecodeImage(frame *ffmpeg.VideoFrame, pkt av.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) { +func DecodeImage(frame *ffmpeg.VideoFrame, pkt packets.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) { decoderMutex.Lock() img, err := decoder.Decode(frame, pkt.Data) decoderMutex.Unlock() diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 77561ee7..e12056fe 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -223,11 +223,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Info("HandleRecordStream: composing recording") log.Log.Info("HandleRecordStream: write header") - // Creating the file, might block sometimes. - /*if err := myMuxer.WriteHeader(streams); err != nil { - log.Log.Error(err.Error()) - }*/ - time := durationGoToMPEGTS(pkt.Time) if err := myMuxer.Write(videoTrack, pkt.Data, time, time); err != nil { log.Log.Error(err.Error()) @@ -242,16 +237,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Error(err.Error()) } } - - // We will sync to file every keyframe. - if pkt.IsKeyFrame { - err := file.Sync() - if err != nil { - log.Log.Error(err.Error()) - } else { - log.Log.Info("HandleRecordStream: Synced file: " + name) - } - } } pkt = nextPkt @@ -270,8 +255,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Info("HandleRecordStream: Recording finished: file save: " + name) // Cleanup muxer start = false - //myMuxer.Close() - //myMuxer = nil file.Close() file = nil diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 6f85aca2..7d23eadd 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -115,7 +115,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Establishing the camera connection without backchannel if no substream rtspUrl := config.Capture.IPCamera.RTSP withBackChannel := true - rtspClient := &capture.Joy4{ + rtspClient := &capture.Golibrtsp{ Url: rtspUrl, WithBackChannel: withBackChannel, } @@ -149,11 +149,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu configuration.Config.Capture.IPCamera.Height = height var queue *packets.Queue - //var subQueue *packets.Queue - - //var decoderMutex sync.Mutex - //var subDecoderMutex sync.Mutex - subStreamEnabled := false + var subQueue *packets.Queue log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl) @@ -164,12 +160,91 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu log.Log.Warning("RunAgent: Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10)) } - // TODO add the substream + another check if the resolution changed. + // We might have a secondary rtsp url, so we might need to use that for livestreaming let us check first! + subStreamEnabled := false + subRtspUrl := config.Capture.IPCamera.SubRTSP + var rtspSubClient capture.RTSPClient + var videoSubStreams []packets.Stream + + if subRtspUrl != "" && subRtspUrl != rtspUrl { + // For the sub stream we will not enable backchannel. + withBackChannel := false + rtspSubClient := &capture.Golibrtsp{ + Url: subRtspUrl, + WithBackChannel: withBackChannel, + } + + err := rtspSubClient.Connect(context.Background()) + if err != nil { + log.Log.Error("RunAgent: error connecting to RTSP sub stream: " + err.Error()) + time.Sleep(time.Second * 3) + return status + } + + // Get the video streams from the RTSP server. + videoSubStreams, err = rtspClient.GetVideoStreams() + if err != nil || len(videoStreams) == 0 { + log.Log.Error("RunAgent: no video sub stream found, might be the wrong codec (we only support H264 for the moment)") + time.Sleep(time.Second * 3) + return status + } + + // Get the video stream from the RTSP server. + videoSubStream := videoSubStreams[0] + + width := videoSubStream.Width + height := videoSubStream.Height + + // Set config values as well + configuration.Config.Capture.IPCamera.Width = width + configuration.Config.Capture.IPCamera.Height = height + } + + if cameraSettings.RTSP != rtspUrl || + cameraSettings.SubRTSP != subRtspUrl || + cameraSettings.Width != width || + cameraSettings.Height != height { + //cameraSettings.Num != num || + //cameraSettings.Denum != denum || + //cameraSettings.Codec != videoStream.(av.VideoCodecData).Type() { + + // TODO: this condition is used to reset the decoder when the camera settings change. + // The main idea is that you only set the decoder once, and then reuse it on each restart (no new memory allocation). + // However the stream settings of the camera might have been changed, and so the decoder might need to be reloaded. + // .... + + if cameraSettings.RTSP != "" && cameraSettings.SubRTSP != "" && cameraSettings.Initialized { + //decoder.Close() + //if subStreamEnabled { + // subDecoder.Close() + //} + } + + // At some routines we will need to decode the image. + // Make sure its properly locked as we only have a single decoder. + log.Log.Info("RunAgent: camera settings changed, reloading decoder") + //capture.GetVideoDecoder(decoder, streams) + //if subStreamEnabled { + // capture.GetVideoDecoder(subDecoder, subStreams) + //} + + cameraSettings.RTSP = rtspUrl + cameraSettings.SubRTSP = subRtspUrl + cameraSettings.Width = width + cameraSettings.Height = height + //cameraSettings.Framerate = float64(num) / float64(denum) + //cameraSettings.Num = num + //cameraSettings.Denum = denum + //cameraSettings.Codec = videoStream.(av.VideoCodecData).Type() + cameraSettings.Initialized = true + } else { + log.Log.Info("RunAgent: camera settings did not change, keeping decoder") + } // We are creating a queue to store the RTSP frames in, these frames will be // processed by the different consumers: motion detection, recording, etc. queue = packets.NewQueue() - //communication.Queue = queue + communication.Queue = queue queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) @@ -181,8 +256,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Handle livestream SD (low resolution over MQTT) if subStreamEnabled { - //livestreamCursor := subQueue.Latest() - //go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, rtspSubClient) + livestreamCursor := subQueue.Latest() + go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, rtspSubClient) } else { livestreamCursor := queue.Latest() go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, rtspClient) @@ -191,8 +266,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Handle livestream HD (high resolution over WEBRTC) communication.HandleLiveHDHandshake = make(chan models.RequestHDStreamPayload, 1) if subStreamEnabled { - //livestreamHDCursor := subQueue.Latest() - //go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, subStreams, subDecoder, &decoderMutex) + livestreamHDCursor := subQueue.Latest() + go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, rtspSubClient) } else { livestreamHDCursor := queue.Latest() go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, rtspClient) @@ -239,16 +314,28 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu time.Sleep(time.Second * 3) + err = rtspClient.Close() + if err != nil { + log.Log.Error("RunAgent: error closing RTSP stream: " + err.Error()) + time.Sleep(time.Second * 3) + return status + } + queue.Close() queue = nil communication.Queue = nil if subStreamEnabled { - //subInfile.Close() - //subInfile = nil - //subQueue.Close() - //subQueue = nil + err = rtspSubClient.Close() + if err != nil { + log.Log.Error("RunAgent: error closing RTSP sub stream: " + err.Error()) + time.Sleep(time.Second * 3) + return status + } + subQueue.Close() + subQueue = nil communication.SubQueue = nil } + //close(communication.HandleMotion) //communication.HandleMotion = nil //close(communication.HandleAudio) @@ -257,221 +344,6 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // 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) - /* - - if err == nil - - // We might have a secondary rtsp url, so we might need to use that. - var subInfile av.DemuxCloser - var subStreams []av.CodecData - subStreamEnabled := false - subRtspUrl := config.Capture.IPCamera.SubRTSP - if subRtspUrl != "" && subRtspUrl != rtspUrl { - withBackChannel := false - subInfile, subStreams, err = capture.OpenRTSP(context.Background(), subRtspUrl, withBackChannel) // We'll try to enable backchannel for the substream. - if err == nil { - log.Log.Info("RunAgent: opened RTSP sub stream " + subRtspUrl) - subStreamEnabled = true - } - - videoStream, _ := capture.GetVideoStream(subStreams) - if videoStream == nil { - log.Log.Error("RunAgent: no video substream found, might be the wrong codec (we only support H264 for the moment)") - time.Sleep(time.Second * 3) - return status - } - - width := videoStream.(av.VideoCodecData).Width() - height := videoStream.(av.VideoCodecData).Height() - - // Set config values as well - configuration.Config.Capture.IPCamera.Width = width - configuration.Config.Capture.IPCamera.Height = height - } - - if cameraSettings.RTSP != rtspUrl || cameraSettings.SubRTSP != subRtspUrl || cameraSettings.Width != width || cameraSettings.Height != height || cameraSettings.Num != num || cameraSettings.Denum != denum || cameraSettings.Codec != videoStream.(av.VideoCodecData).Type() { - - if cameraSettings.RTSP != "" && cameraSettings.SubRTSP != "" && cameraSettings.Initialized { - decoder.Close() - if subStreamEnabled { - subDecoder.Close() - } - } - - // At some routines we will need to decode the image. - // Make sure its properly locked as we only have a single decoder. - log.Log.Info("RunAgent: camera settings changed, reloading decoder") - capture.GetVideoDecoder(decoder, streams) - if subStreamEnabled { - capture.GetVideoDecoder(subDecoder, subStreams) - } - - cameraSettings.RTSP = rtspUrl - cameraSettings.SubRTSP = subRtspUrl - cameraSettings.Width = width - cameraSettings.Height = height - cameraSettings.Framerate = float64(num) / float64(denum) - cameraSettings.Num = num - cameraSettings.Denum = denum - cameraSettings.Codec = videoStream.(av.VideoCodecData).Type() - cameraSettings.Initialized = true - - } else { - log.Log.Info("RunAgent: camera settings did not change, keeping decoder") - } - - communication.Decoder = decoder - communication.SubDecoder = subDecoder - communication.DecoderMutex = &decoderMutex - communication.SubDecoderMutex = &subDecoderMutex - - // Create a packet queue, which is filled by the HandleStream routing - // and consumed by all other routines: motion, livestream, etc. - if config.Capture.PreRecording <= 0 { - config.Capture.PreRecording = 1 - log.Log.Warning("RunAgent: Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10)) - } - - // We are creating a queue to store the RTSP frames in, these frames will be - // processed by the different consumers: motion detection, recording, etc. - queue = pubsub.NewQueue() - communication.Queue = queue - queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). - log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) - queue.WriteHeader(streams) - - // We might have a substream, if so we'll create a seperate queue. - if subStreamEnabled { - log.Log.Info("RunAgent: Creating sub stream queue with SetMaxGopCount set to " + strconv.Itoa(int(1))) - subQueue = pubsub.NewQueue() - communication.SubQueue = subQueue - subQueue.SetMaxGopCount(1) - subQueue.WriteHeader(subStreams) - } - - // Handle the camera stream - go capture.HandleStream(infile, queue, communication) - - // Handle the substream if enabled - if subStreamEnabled { - 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 { - motionCursor := subQueue.Latest() - go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex) - } else { - motionCursor := queue.Latest() - go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, decoder, &decoderMutex) - } - - // Handle livestream SD (low resolution over MQTT) - if subStreamEnabled { - livestreamCursor := subQueue.Latest() - go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex) - } else { - livestreamCursor := queue.Latest() - go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, decoder, &decoderMutex) - } - - // Handle livestream HD (high resolution over WEBRTC) - communication.HandleLiveHDHandshake = make(chan models.RequestHDStreamPayload, 1) - if subStreamEnabled { - livestreamHDCursor := subQueue.Latest() - go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, subStreams, subDecoder, &decoderMutex) - } else { - livestreamHDCursor := queue.Latest() - go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, streams, decoder, &decoderMutex) - } - - // Handle recording, will write an mp4 to disk. - go capture.HandleRecordStream(queue, configDirectory, configuration, communication, streams) - - // Handle Upload to cloud provider (Kerberos Hub, Kerberos Vault and others) - go cloud.HandleUpload(configDirectory, configuration, communication) - - // Handle ONVIF actions - go onvif.HandleONVIFActions(configuration, communication) - - // 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. - - status = <-communication.HandleBootstrap - - // If we reach this point, we are stopping the stream. - communication.CameraConnected = false - - // Cancel the main context, this will stop all the other goroutines. - (*communication.CancelContext)() - - // We will re open the configuration, might have changed :O! - configService.OpenConfig(configDirectory, configuration) - - // We will override the configuration with the environment variables - configService.OverrideWithEnvironmentVariables(configuration) - - // Here we are cleaning up everything! - if configuration.Config.Offline != "true" { - communication.HandleUpload <- "stop" - } - communication.HandleStream <- "stop" - if subStreamEnabled { - communication.HandleSubStream <- "stop" - } - - time.Sleep(time.Second * 3) - - infile.Close() - infile = nil - queue.Close() - queue = nil - communication.Queue = nil - if subStreamEnabled { - subInfile.Close() - subInfile = nil - subQueue.Close() - subQueue = nil - communication.SubQueue = nil - } - 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.") - time.Sleep(time.Second * 3) - - } else { - log.Log.Error("Something went wrong while opening RTSP: " + err.Error()) - time.Sleep(time.Second * 3) - } - - log.Log.Debug("RunAgent: finished") - - // Clean up, force garbage collection - runtime.GC()*/ - - // Close the connection to the RTSP server. - err = rtspClient.Close() - if err != nil { - log.Log.Error("RunAgent: error closing RTSP stream: " + err.Error()) - time.Sleep(time.Second * 3) - return status - } return status } diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index 664a8a15..8eff4bb6 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -14,12 +14,11 @@ import ( "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/joy4/av" - "github.com/kerberos-io/joy4/av/pubsub" + "github.com/kerberos-io/agent/machinery/src/packets" "github.com/kerberos-io/joy4/cgo/ffmpeg" ) -func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { //, wg *sync.WaitGroup) { +func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { //, wg *sync.WaitGroup) { log.Log.Debug("ProcessMotion: started") config := configuration.Config @@ -53,7 +52,7 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi j := 0 var cursorError error - var pkt av.Packet + var pkt packets.Packet for cursorError == nil { pkt, cursorError = motionCursor.ReadPacket() @@ -225,7 +224,7 @@ func FindMotion(imageArray [3]*image.Gray, coordinatesToCheck []int, pixelChange return changes > pixelChangeThreshold, changes } -func GetGrayImage(frame *ffmpeg.VideoFrame, pkt av.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*image.Gray, error) { +func GetGrayImage(frame *ffmpeg.VideoFrame, pkt packets.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*image.Gray, error) { _, err := capture.DecodeImage(frame, pkt, dec, decoderMutex) // Do a deep copy of the image @@ -236,7 +235,7 @@ func GetGrayImage(frame *ffmpeg.VideoFrame, pkt av.Packet, dec *ffmpeg.VideoDeco return imgDeepCopy, err } -func GetRawImage(frame *ffmpeg.VideoFrame, pkt av.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) { +func GetRawImage(frame *ffmpeg.VideoFrame, pkt packets.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) { _, err := capture.DecodeImage(frame, pkt, dec, decoderMutex) return frame, err } @@ -261,7 +260,7 @@ func AbsDiffBitwiseAndThreshold(img1 *image.Gray, img2 *image.Gray, img3 *image. return changes } -func StoreSnapshot(communication *models.Communication, frame *ffmpeg.VideoFrame, pkt av.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { +func StoreSnapshot(communication *models.Communication, frame *ffmpeg.VideoFrame, pkt packets.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { rgbImage, err := GetRawImage(frame, pkt, decoder, decoderMutex) if err == nil { buffer := new(bytes.Buffer) diff --git a/machinery/src/models/Communication.go b/machinery/src/models/Communication.go index c9723b86..e5e46db9 100644 --- a/machinery/src/models/Communication.go +++ b/machinery/src/models/Communication.go @@ -2,11 +2,9 @@ package models import ( "context" - "sync" "sync/atomic" - "github.com/kerberos-io/joy4/av/pubsub" - "github.com/kerberos-io/joy4/cgo/ffmpeg" + "github.com/kerberos-io/agent/machinery/src/packets" "github.com/tevino/abool" ) @@ -31,12 +29,8 @@ type Communication struct { HandleLiveHDPeers chan string HandleONVIF chan OnvifAction IsConfiguring *abool.AtomicBool - Queue *pubsub.Queue - SubQueue *pubsub.Queue - DecoderMutex *sync.Mutex - SubDecoderMutex *sync.Mutex - Decoder *ffmpeg.VideoDecoder - SubDecoder *ffmpeg.VideoDecoder + Queue *packets.Queue + SubQueue *packets.Queue Image string CameraConnected bool HasBackChannel bool diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index ca8d6d0d..9d847bad 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -2,16 +2,13 @@ package websocket import ( "context" - "encoding/base64" "net/http" "sync" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - "github.com/kerberos-io/agent/machinery/src/computervision" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/joy4/cgo/ffmpeg" ) type Message struct { @@ -128,26 +125,27 @@ func ForwardSDStream(ctx context.Context, clientID string, connection *Connectio queue := communication.Queue cursor := queue.Latest() - decoder := communication.Decoder - decoderMutex := communication.DecoderMutex + //decoder := communication.Decoder + //decoderMutex := communication.DecoderMutex // Allocate ffmpeg.VideoFrame - frame := ffmpeg.AllocVideoFrame() + //frame := ffmpeg.AllocVideoFrame() logreader: for { var encodedImage string - if queue != nil && cursor != nil && decoder != nil { + if queue != nil && cursor != nil { //&& decoder != nil { pkt, err := cursor.ReadPacket() if err == nil { if !pkt.IsKeyFrame { continue } - img, err := computervision.GetRawImage(frame, pkt, decoder, decoderMutex) + /*img, err := computervision.GetRawImage(frame, pkt, decoder, decoderMutex) if err == nil { bytes, _ := computervision.ImageToBytes(&img.Image) encodedImage = base64.StdEncoding.EncodeToString(bytes) - } + }*/ + } else { log.Log.Error("ForwardSDStream:" + err.Error()) break logreader @@ -173,7 +171,7 @@ logreader: } } - frame.Free() + //frame.Free() // Close socket for streaming _, exists := connection.Cancels["stream-sd"] From 7c285d36a12fc1b3ae8f3b3e9c73f4d2fc42ee13 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 13:45:34 +0100 Subject: [PATCH 11/81] isolate rtsp clients to be able to pass them through --- machinery/main.go | 12 ++++++++-- machinery/src/capture/Joy4.go | 13 ---------- machinery/src/capture/main.go | 21 ++++++++++++++++ machinery/src/components/Kerberos.go | 24 +++++++------------ machinery/src/routers/http/Routes.go | 4 ++-- machinery/src/routers/http/Server.go | 5 ++-- machinery/src/routers/main.go | 5 ++-- machinery/src/routers/websocket/main.go | 32 +++++++++++++++---------- 8 files changed, 67 insertions(+), 49 deletions(-) diff --git a/machinery/main.go b/machinery/main.go index eb88d396..3f98456b 100644 --- a/machinery/main.go +++ b/machinery/main.go @@ -6,6 +6,7 @@ import ( "os" "time" + "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/components" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" @@ -142,16 +143,23 @@ func main() { // This is used to restart the agent when the configuration is updated. ctx, cancel := context.WithCancel(context.Background()) + // We create a capture object. + capture := capture.Capture{ + RTSPClient: nil, + RTSPSubClient: nil, + } + // Bootstrapping the agent communication := models.Communication{ Context: &ctx, CancelContext: &cancel, HandleBootstrap: make(chan string, 1), } - go components.Bootstrap(configDirectory, &configuration, &communication) + + go components.Bootstrap(configDirectory, &configuration, &communication, &capture) // Start the REST API. - routers.StartWebserver(configDirectory, &configuration, &communication) + routers.StartWebserver(configDirectory, &configuration, &communication, &capture) } default: log.Log.Error("Main: Sorry I don't understand :(") diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go index 9420d128..8b8f6e27 100644 --- a/machinery/src/capture/Joy4.go +++ b/machinery/src/capture/Joy4.go @@ -153,19 +153,6 @@ loop: // Could be that a decode is throwing errors. if len(avpkt.Data) > 0 { - avpkt.Data = avpkt.Data[4:] - if avpkt.IsKeyFrame { - start = true - // Add SPS and PPS to the packet. - stream := j.Streams[avpkt.Idx] - annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } - avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) - avpkt.Data = append(stream.PPS, avpkt.Data...) - avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) - avpkt.Data = append(stream.SPS, avpkt.Data...) - avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) - } - if start { // Conver to packet. pkt := packets.Packet{ diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index e12056fe..74894379 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -19,6 +19,27 @@ import ( "github.com/yapingcat/gomedia/go-mp4" ) +type Capture struct { + RTSPClient *Golibrtsp + RTSPSubClient *Golibrtsp +} + +func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Golibrtsp { + c.RTSPClient = &Golibrtsp{ + Url: rtspUrl, + WithBackChannel: withBackChannel, + } + return c.RTSPClient +} + +func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Golibrtsp { + c.RTSPSubClient = &Golibrtsp{ + Url: rtspUrl, + WithBackChannel: withBackChannel, + } + return c.RTSPSubClient +} + func CleanupRecordingDirectory(configDirectory string, configuration *models.Configuration) { autoClean := configuration.Config.AutoClean if autoClean == "true" { diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 7d23eadd..bac3591a 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -19,7 +19,7 @@ import ( "github.com/tevino/abool" ) -func Bootstrap(configDirectory string, configuration *models.Configuration, communication *models.Communication) { +func Bootstrap(configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) { log.Log.Debug("Bootstrap: started") // We will keep track of the Kerberos Agent up time @@ -54,15 +54,12 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm communication.HandleONVIF = make(chan models.OnvifAction, 1) communication.IsConfiguring = abool.New() + cameraSettings := &models.Camera{} + // Before starting the agent, we have a control goroutine, that might // do several checks to see if the agent is still operational. go ControlAgent(communication) - // Create some global variables - //decoder := &ffmpeg.VideoDecoder{} - //subDecoder := &ffmpeg.VideoDecoder{} - cameraSettings := &models.Camera{} - // Handle heartbeats go cloud.HandleHeartBeat(configuration, communication, uptimeStart) @@ -76,7 +73,7 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm // This will blocking until receiving a signal to be restarted, reconfigured, stopped, etc. //status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings, decoder, subDecoder) - status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings) + status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings, captureDevice) if status == "stop" { break @@ -104,7 +101,7 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm log.Log.Debug("Bootstrap: finished") } -func RunAgent(configDirectory string, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, uptimeStart time.Time, cameraSettings *models.Camera) string { +func RunAgent(configDirectory string, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, uptimeStart time.Time, cameraSettings *models.Camera, captureDevice *capture.Capture) string { log.Log.Debug("RunAgent: bootstrapping agent") config := configuration.Config @@ -115,10 +112,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Establishing the camera connection without backchannel if no substream rtspUrl := config.Capture.IPCamera.RTSP withBackChannel := true - rtspClient := &capture.Golibrtsp{ - Url: rtspUrl, - WithBackChannel: withBackChannel, - } + rtspClient := captureDevice.SetMainClient(rtspUrl, withBackChannel) err := rtspClient.Connect(context.Background()) if err != nil { @@ -169,10 +163,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu if subRtspUrl != "" && subRtspUrl != rtspUrl { // For the sub stream we will not enable backchannel. withBackChannel := false - rtspSubClient := &capture.Golibrtsp{ - Url: subRtspUrl, - WithBackChannel: withBackChannel, - } + rtspSubClient := captureDevice.SetMainClient(subRtspUrl, withBackChannel) + captureDevice.RTSPSubClient = rtspSubClient err := rtspSubClient.Connect(context.Background()) if err != nil { diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/Routes.go index b8159467..01ffbd69 100644 --- a/machinery/src/routers/http/Routes.go +++ b/machinery/src/routers/http/Routes.go @@ -18,10 +18,10 @@ import ( "github.com/kerberos-io/agent/machinery/src/utils" ) -func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirectory string, configuration *models.Configuration, communication *models.Communication) *gin.RouterGroup { +func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) *gin.RouterGroup { r.GET("/ws", func(c *gin.Context) { - websocket.WebsocketHandler(c, communication) + websocket.WebsocketHandler(c, communication, captureDevice) }) // This is legacy should be removed in future! Now everything diff --git a/machinery/src/routers/http/Server.go b/machinery/src/routers/http/Server.go index 31b0772d..c01531da 100644 --- a/machinery/src/routers/http/Server.go +++ b/machinery/src/routers/http/Server.go @@ -14,6 +14,7 @@ import ( "log" _ "github.com/kerberos-io/agent/machinery/docs" + "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/encryption" "github.com/kerberos-io/agent/machinery/src/models" swaggerFiles "github.com/swaggo/files" @@ -38,7 +39,7 @@ import ( // @in header // @name Authorization -func StartServer(configDirectory string, configuration *models.Configuration, communication *models.Communication) { +func StartServer(configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) { // Initialize REST API r := gin.Default() @@ -60,7 +61,7 @@ func StartServer(configDirectory string, configuration *models.Configuration, co } // Add all routes - AddRoutes(r, authMiddleware, configDirectory, configuration, communication) + AddRoutes(r, authMiddleware, configDirectory, configuration, communication, captureDevice) // Update environment variables environmentVariables := configDirectory + "/www/env.js" diff --git a/machinery/src/routers/main.go b/machinery/src/routers/main.go index 509dc1b6..50524544 100644 --- a/machinery/src/routers/main.go +++ b/machinery/src/routers/main.go @@ -1,10 +1,11 @@ package routers import ( + "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/routers/http" ) -func StartWebserver(configDirectory string, configuration *models.Configuration, communication *models.Communication) { - http.StartServer(configDirectory, configuration, communication) +func StartWebserver(configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) { + http.StartServer(configDirectory, configuration, communication, captureDevice) } diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index 9d847bad..675842b8 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -2,11 +2,15 @@ package websocket import ( "context" + "encoding/base64" + "image" "net/http" "sync" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" + "github.com/kerberos-io/agent/machinery/src/capture" + "github.com/kerberos-io/agent/machinery/src/computervision" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" ) @@ -44,7 +48,7 @@ var upgrader = websocket.Upgrader{ }, } -func WebsocketHandler(c *gin.Context, communication *models.Communication) { +func WebsocketHandler(c *gin.Context, communication *models.Communication, captureDevice *capture.Capture) { w := c.Writer r := c.Request conn, err := upgrader.Upgrade(w, r, nil) @@ -102,7 +106,7 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication) { ctx, cancel := context.WithCancel(context.Background()) sockets[clientID].Cancels["stream-sd"] = cancel - go ForwardSDStream(ctx, clientID, sockets[clientID], communication) + go ForwardSDStream(ctx, clientID, sockets[clientID], communication, captureDevice) } } } @@ -121,30 +125,34 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication) { } } -func ForwardSDStream(ctx context.Context, clientID string, connection *Connection, communication *models.Communication) { +func ForwardSDStream(ctx context.Context, clientID string, connection *Connection, communication *models.Communication, captureDevice *capture.Capture) { queue := communication.Queue cursor := queue.Latest() - //decoder := communication.Decoder - //decoderMutex := communication.DecoderMutex - - // Allocate ffmpeg.VideoFrame - //frame := ffmpeg.AllocVideoFrame() logreader: for { var encodedImage string - if queue != nil && cursor != nil { //&& decoder != nil { + rtspClient := captureDevice.RTSPClient + if queue != nil && cursor != nil && rtspClient != nil { pkt, err := cursor.ReadPacket() if err == nil { if !pkt.IsKeyFrame { continue } - /*img, err := computervision.GetRawImage(frame, pkt, decoder, decoderMutex) + + var img image.YCbCr + rtspSubClient := captureDevice.RTSPSubClient + if rtspSubClient != nil { + img, err = (*rtspSubClient).DecodePacket(pkt) + } else { + img, err = (*rtspSubClient).DecodePacket(pkt) + } + if err == nil { - bytes, _ := computervision.ImageToBytes(&img.Image) + bytes, _ := computervision.ImageToBytes(&img) encodedImage = base64.StdEncoding.EncodeToString(bytes) - }*/ + } } else { log.Log.Error("ForwardSDStream:" + err.Error()) From fec2587b6d9050e0b7d2a76713c45318b0e4f667 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 13:49:46 +0100 Subject: [PATCH 12/81] Update Gortsplib.go --- machinery/src/capture/Gortsplib.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 5c6c5a7e..8648cf76 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -98,8 +98,6 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { IsAudio: false, SPS: forma.SPS, PPS: forma.PPS, - Width: 640, - Height: 480, }) // Set the index for the video From e7dc9aa64de85ede9f813677a8610159853e317d Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 14:10:07 +0100 Subject: [PATCH 13/81] swap to joy4 --- machinery/src/capture/Joy4.go | 13 +++++++++++++ machinery/src/capture/main.go | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go index 8b8f6e27..9420d128 100644 --- a/machinery/src/capture/Joy4.go +++ b/machinery/src/capture/Joy4.go @@ -153,6 +153,19 @@ loop: // Could be that a decode is throwing errors. if len(avpkt.Data) > 0 { + avpkt.Data = avpkt.Data[4:] + if avpkt.IsKeyFrame { + start = true + // Add SPS and PPS to the packet. + stream := j.Streams[avpkt.Idx] + annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } + avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) + avpkt.Data = append(stream.PPS, avpkt.Data...) + avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) + avpkt.Data = append(stream.SPS, avpkt.Data...) + avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) + } + if start { // Conver to packet. pkt := packets.Packet{ diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 74894379..7b566cdf 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -20,20 +20,20 @@ import ( ) type Capture struct { - RTSPClient *Golibrtsp - RTSPSubClient *Golibrtsp + RTSPClient *Joy4 + RTSPSubClient *Joy4 } -func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Golibrtsp { - c.RTSPClient = &Golibrtsp{ +func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Joy4 { + c.RTSPClient = &Joy4{ Url: rtspUrl, WithBackChannel: withBackChannel, } return c.RTSPClient } -func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Golibrtsp { - c.RTSPSubClient = &Golibrtsp{ +func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Joy4 { + c.RTSPSubClient = &Joy4{ Url: rtspUrl, WithBackChannel: withBackChannel, } From cd09ed332113f0c3c3790f1af82c18d9262b5d78 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 14:33:12 +0100 Subject: [PATCH 14/81] fix --- machinery/src/capture/Gortsplib.go | 2 +- machinery/src/capture/main.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 8648cf76..3cc4d499 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -268,7 +268,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati dtsExtractor = h264.NewDTSExtractor() } - // Conver to packet. + // Convert to packet. enc, err := h264.AnnexBMarshal(filteredAU) if err != nil { log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 7b566cdf..74894379 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -20,20 +20,20 @@ import ( ) type Capture struct { - RTSPClient *Joy4 - RTSPSubClient *Joy4 + RTSPClient *Golibrtsp + RTSPSubClient *Golibrtsp } -func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Joy4 { - c.RTSPClient = &Joy4{ +func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Golibrtsp { + c.RTSPClient = &Golibrtsp{ Url: rtspUrl, WithBackChannel: withBackChannel, } return c.RTSPClient } -func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Joy4 { - c.RTSPSubClient = &Joy4{ +func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Golibrtsp { + c.RTSPSubClient = &Golibrtsp{ Url: rtspUrl, WithBackChannel: withBackChannel, } From fd6449b377a12396d8d33f68b3b2de8ab39ee758 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 14:50:09 +0100 Subject: [PATCH 15/81] remove dtsextractor is blocks the stream --- machinery/src/capture/Gortsplib.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 3cc4d499..93b246df 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -202,7 +202,6 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // called when a video RTP packet arrives if g.VideoH264Media != nil { - var dtsExtractor *h264.DTSExtractor g.Client.OnPacketRTP(g.VideoH264Media, g.VideoH264Forma, func(rtppkt *rtp.Packet) { // This will check if we need to stop the thread, @@ -260,14 +259,6 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati return } - if dtsExtractor == nil { - // skip samples silently until we find one with a IDR - if !idrPresent { - return - } - dtsExtractor = h264.NewDTSExtractor() - } - // Convert to packet. enc, err := h264.AnnexBMarshal(filteredAU) if err != nil { @@ -275,17 +266,12 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati return } - dts, err := dtsExtractor.Extract(au, pts) - if err != nil { - return - } - pkt := packets.Packet{ IsKeyFrame: idrPresent, Packet: rtppkt, Data: enc, Time: pts, - CompositionTime: dts, + CompositionTime: pts, Idx: g.VideoH264Index, } queue.WritePacket(pkt) From dae2c1b5c454ce83958559d4be6fc79da927c272 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 17:17:10 +0100 Subject: [PATCH 16/81] fix keyframing --- machinery/src/capture/Gortsplib.go | 11 +++++++++++ machinery/src/webrtc/main.go | 13 +------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 93b246df..0b1ec2b0 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -274,6 +274,17 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati CompositionTime: pts, Idx: g.VideoH264Index, } + + pkt.Data = pkt.Data[4:] + if pkt.IsKeyFrame { + annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } + pkt.Data = append(annexbNALUStartCode(), pkt.Data...) + pkt.Data = append(g.VideoH264Forma.PPS, pkt.Data...) + pkt.Data = append(annexbNALUStartCode(), pkt.Data...) + pkt.Data = append(g.VideoH264Forma.SPS, pkt.Data...) + pkt.Data = append(annexbNALUStartCode(), pkt.Data...) + } + queue.WritePacket(pkt) // This will check if we need to stop the thread, diff --git a/machinery/src/webrtc/main.go b/machinery/src/webrtc/main.go index d36e8df3..df71b4e6 100644 --- a/machinery/src/webrtc/main.go +++ b/machinery/src/webrtc/main.go @@ -324,8 +324,6 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C if videoIdx == -1 { log.Log.Error("WriteToTrack: no video codec found.") } else { - annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } - if config.Capture.TranscodingWebRTC == "true" { if videoIdx > -1 { log.Log.Info("WriteToTrack: successfully using a transcoder.") @@ -341,7 +339,6 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C start := false receivedKeyFrame := false - stream := streams[videoIdx] lastKeepAlive := "0" peerCount := "0" @@ -398,18 +395,10 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C switch int(pkt.Idx) { case videoIdx: - // For every key-frame pre-pend the SPS and PPS - pkt.Data = pkt.Data[4:] + // Start at the first keyframe if pkt.IsKeyFrame { start = true - pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - pkt.Data = append(stream.PPS, pkt.Data...) - pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - pkt.Data = append(stream.SPS, pkt.Data...) - pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - log.Log.Info("WriteToTrack: Sending keyframe") } - if start { sample := pionMedia.Sample{Data: pkt.Data, Duration: bufferDuration} if config.Capture.ForwardWebRTC == "true" { From df38784a8dda46fac2ca01e1c5473ea4ecb51047 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 17:34:03 +0100 Subject: [PATCH 17/81] fixes --- machinery/src/capture/Joy4.go | 53 ++++++++++++++++------------------- machinery/src/capture/main.go | 12 ++++---- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go index 9420d128..ea64cf8e 100644 --- a/machinery/src/capture/Joy4.go +++ b/machinery/src/capture/Joy4.go @@ -133,7 +133,7 @@ func (j *Joy4) Connect(ctx context.Context) (err error) { // Start the RTSP client, and start reading packets. func (j *Joy4) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(JOY4).Start(): started") - start := false + loop: for { // This will check if we need to stop the thread, @@ -153,39 +153,34 @@ loop: // Could be that a decode is throwing errors. if len(avpkt.Data) > 0 { - avpkt.Data = avpkt.Data[4:] - if avpkt.IsKeyFrame { - start = true - // Add SPS and PPS to the packet. + pkt := packets.Packet{ + IsKeyFrame: avpkt.IsKeyFrame, + Idx: int8(avpkt.Idx), + CompositionTime: avpkt.CompositionTime, + Time: avpkt.Time, + Data: avpkt.Data, + } + + pkt.Data = pkt.Data[4:] + if pkt.IsKeyFrame { stream := j.Streams[avpkt.Idx] annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } - avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) - avpkt.Data = append(stream.PPS, avpkt.Data...) - avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) - avpkt.Data = append(stream.SPS, avpkt.Data...) - avpkt.Data = append(annexbNALUStartCode(), avpkt.Data...) + pkt.Data = append(annexbNALUStartCode(), pkt.Data...) + pkt.Data = append(stream.PPS, pkt.Data...) + pkt.Data = append(annexbNALUStartCode(), pkt.Data...) + pkt.Data = append(stream.SPS, pkt.Data...) + pkt.Data = append(annexbNALUStartCode(), pkt.Data...) } - if start { - // Conver to packet. - pkt := packets.Packet{ - IsKeyFrame: avpkt.IsKeyFrame, - Idx: int8(avpkt.Idx), - CompositionTime: avpkt.CompositionTime, - Time: avpkt.Time, - Data: avpkt.Data, - } - - queue.WritePacket(pkt) + queue.WritePacket(pkt) - if pkt.IsKeyFrame { - // Increment packets, so we know the device - // is not blocking. - r := communication.PackageCounter.Load().(int64) - log.Log.Info("RTSPClient(JOY4).Start(): packet size " + strconv.Itoa(len(pkt.Data))) - communication.PackageCounter.Store((r + 1) % 1000) - communication.LastPacketTimer.Store(time.Now().Unix()) - } + if pkt.IsKeyFrame { + // Increment packets, so we know the device + // is not blocking. + r := communication.PackageCounter.Load().(int64) + log.Log.Info("RTSPClient(JOY4).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + communication.PackageCounter.Store((r + 1) % 1000) + communication.LastPacketTimer.Store(time.Now().Unix()) } // This will check if we need to stop the thread, diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 74894379..7b566cdf 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -20,20 +20,20 @@ import ( ) type Capture struct { - RTSPClient *Golibrtsp - RTSPSubClient *Golibrtsp + RTSPClient *Joy4 + RTSPSubClient *Joy4 } -func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Golibrtsp { - c.RTSPClient = &Golibrtsp{ +func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Joy4 { + c.RTSPClient = &Joy4{ Url: rtspUrl, WithBackChannel: withBackChannel, } return c.RTSPClient } -func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Golibrtsp { - c.RTSPSubClient = &Golibrtsp{ +func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Joy4 { + c.RTSPSubClient = &Joy4{ Url: rtspUrl, WithBackChannel: withBackChannel, } From 677c9e334be625a8d70e60b62cad03e372592ae6 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 21:01:57 +0100 Subject: [PATCH 18/81] add decoder, fix livestream --- machinery/src/capture/Gortsplib.go | 48 +++++++++++++++++++++++++ machinery/src/capture/Joy4.go | 2 +- machinery/src/capture/RTSPClient.go | 24 +++++++++++++ machinery/src/capture/main.go | 21 ----------- machinery/src/routers/http/Server.go | 3 +- machinery/src/routers/websocket/main.go | 2 +- 6 files changed, 76 insertions(+), 24 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 0b1ec2b0..c45a61e7 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -330,6 +330,19 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { return img, nil } +// Decode a packet to a Gray image. +func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { + img, err := g.VideoH264FrameDecoder.decodeRaw(pkt.Data) + if err != nil { + return image.Gray{}, err + } + if img.Bounds().Empty() { + log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") + return image.Gray{}, errors.New("Empty frame") + } + return img, nil +} + // Get a list of streams from the RTSP server. func (j *Golibrtsp) GetStreams() ([]packets.Stream, error) { return j.Streams, nil @@ -465,6 +478,41 @@ func (d *h264Decoder) decode(nalu []byte) (image.YCbCr, error) { return image.YCbCr{}, nil } +func (d *h264Decoder) decodeRaw(nalu []byte) (image.Gray, error) { + nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...) + + // send NALU 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 image.Gray{}, nil + } + + // receive frame if available + res = C.avcodec_receive_frame(d.codecCtx, d.srcFrame) + if res < 0 { + return image.Gray{}, nil + } + + if res == 0 { + fr := d.srcFrame + w := int(fr.width) + h := int(fr.height) + ys := int(fr.linesize[0]) + + return image.Gray{ + Pix: fromCPtr(unsafe.Pointer(fr.data[0]), w*h), + Stride: ys, + Rect: image.Rect(0, 0, w, h), + }, nil + } + + return image.Gray{}, nil +} + func fromCPtr(buf unsafe.Pointer, size int) (ret []uint8) { hdr := (*reflect.SliceHeader)((unsafe.Pointer(&ret))) hdr.Cap = size diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go index ea64cf8e..59aef5b7 100644 --- a/machinery/src/capture/Joy4.go +++ b/machinery/src/capture/Joy4.go @@ -156,7 +156,7 @@ loop: pkt := packets.Packet{ IsKeyFrame: avpkt.IsKeyFrame, Idx: int8(avpkt.Idx), - CompositionTime: avpkt.CompositionTime, + CompositionTime: avpkt.Time, Time: avpkt.Time, Data: avpkt.Data, } diff --git a/machinery/src/capture/RTSPClient.go b/machinery/src/capture/RTSPClient.go index 590a30e5..d3d31a9d 100644 --- a/machinery/src/capture/RTSPClient.go +++ b/machinery/src/capture/RTSPClient.go @@ -8,6 +8,27 @@ import ( "github.com/kerberos-io/agent/machinery/src/packets" ) +type Capture struct { + RTSPClient *Golibrtsp + RTSPSubClient *Golibrtsp +} + +func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Golibrtsp { + c.RTSPClient = &Golibrtsp{ + Url: rtspUrl, + WithBackChannel: withBackChannel, + } + return c.RTSPClient +} + +func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Golibrtsp { + c.RTSPSubClient = &Golibrtsp{ + Url: rtspUrl, + WithBackChannel: withBackChannel, + } + return c.RTSPSubClient +} + // RTSPClient is a interface that abstracts the RTSP client implementation. type RTSPClient interface { // Connect to the RTSP server. @@ -19,6 +40,9 @@ type RTSPClient interface { // Decode a packet into a image. DecodePacket(pkt packets.Packet) (image.YCbCr, error) + // Decode a packet into a image. + DecodePacketRaw(pkt packets.Packet) (image.Gray, error) + // Close the connection to the RTSP server. Close() error diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 7b566cdf..e12056fe 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -19,27 +19,6 @@ import ( "github.com/yapingcat/gomedia/go-mp4" ) -type Capture struct { - RTSPClient *Joy4 - RTSPSubClient *Joy4 -} - -func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Joy4 { - c.RTSPClient = &Joy4{ - Url: rtspUrl, - WithBackChannel: withBackChannel, - } - return c.RTSPClient -} - -func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Joy4 { - c.RTSPSubClient = &Joy4{ - Url: rtspUrl, - WithBackChannel: withBackChannel, - } - return c.RTSPSubClient -} - func CleanupRecordingDirectory(configDirectory string, configuration *models.Configuration) { autoClean := configuration.Config.AutoClean if autoClean == "true" { diff --git a/machinery/src/routers/http/Server.go b/machinery/src/routers/http/Server.go index c01531da..f5bfb0d0 100644 --- a/machinery/src/routers/http/Server.go +++ b/machinery/src/routers/http/Server.go @@ -106,8 +106,9 @@ func Files(c *gin.Context, configDirectory string, configuration *models.Configu // Get symmetric key symmetricKey := configuration.Config.Encryption.SymmetricKey + encryptedRecordings := configuration.Config.Encryption.Recordings // Decrypt file - if symmetricKey != "" { + if encryptedRecordings == "true" && symmetricKey != "" { // Read file if err != nil { diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index 675842b8..9e65bee1 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -146,7 +146,7 @@ logreader: if rtspSubClient != nil { img, err = (*rtspSubClient).DecodePacket(pkt) } else { - img, err = (*rtspSubClient).DecodePacket(pkt) + img, err = (*rtspClient).DecodePacket(pkt) } if err == nil { From bffd377461ef0455ee2e2d6c711b0cfc780805dd Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 30 Nov 2023 21:33:14 +0100 Subject: [PATCH 19/81] add substream --- machinery/src/capture/Gortsplib.go | 19 ++++++------------- machinery/src/components/Kerberos.go | 20 +++++++++++++------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index c45a61e7..849eb5c6 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -374,6 +374,7 @@ func (j *Golibrtsp) GetAudioStreams() ([]packets.Stream, error) { func (g *Golibrtsp) Close() error { // Close the demuxer. g.Client.Close() + g.VideoH264FrameDecoder.Close() return nil } @@ -387,11 +388,8 @@ func frameLineSize(frame *C.AVFrame) *C.int { // 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 + codecCtx *C.AVCodecContext + srcFrame *C.AVFrame } // newH264Decoder allocates a new h264Decoder. @@ -425,15 +423,10 @@ func newH264Decoder() (*h264Decoder, error) { } // close closes the decoder. -func (d *h264Decoder) close() { - if d.dstFrame != nil { - C.av_frame_free(&d.dstFrame) +func (d *h264Decoder) Close() { + if d.srcFrame != nil { + C.av_frame_free(&d.srcFrame) } - - if d.swsCtx != nil { - C.sws_freeContext(d.swsCtx) - } - C.av_frame_free(&d.srcFrame) C.avcodec_close(d.codecCtx) } diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index bac3591a..1fa9cae5 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -72,7 +72,6 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm for { // This will blocking until receiving a signal to be restarted, reconfigured, stopped, etc. - //status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings, decoder, subDecoder) status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings, captureDevice) if status == "stop" { @@ -157,13 +156,13 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // We might have a secondary rtsp url, so we might need to use that for livestreaming let us check first! subStreamEnabled := false subRtspUrl := config.Capture.IPCamera.SubRTSP - var rtspSubClient capture.RTSPClient var videoSubStreams []packets.Stream if subRtspUrl != "" && subRtspUrl != rtspUrl { // For the sub stream we will not enable backchannel. + subStreamEnabled = true withBackChannel := false - rtspSubClient := captureDevice.SetMainClient(subRtspUrl, withBackChannel) + rtspSubClient := captureDevice.SetSubClient(subRtspUrl, withBackChannel) captureDevice.RTSPSubClient = rtspSubClient err := rtspSubClient.Connect(context.Background()) @@ -238,14 +237,21 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu queue = packets.NewQueue() communication.Queue = queue - queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). + // Set the maximum GOP count, this is used to determine the pre-recording time. log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) + queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). queue.WriteHeader(videoStreams) - - // Handle the camera stream - //go capture.HandleStream(infile, queue, communication) go rtspClient.Start(context.Background(), queue, communication) + rtspSubClient := captureDevice.RTSPSubClient + if subStreamEnabled && rtspSubClient != nil { + subQueue = packets.NewQueue() + communication.SubQueue = subQueue + subQueue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). + subQueue.WriteHeader(videoSubStreams) + go rtspSubClient.Start(context.Background(), subQueue, communication) + } + // Handle livestream SD (low resolution over MQTT) if subStreamEnabled { livestreamCursor := subQueue.Latest() From 044e167dd27ff8890ba7038c322552f72dacff5e Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 08:34:09 +0100 Subject: [PATCH 20/81] add lock + motion detection --- machinery/src/capture/Gortsplib.go | 15 +++- machinery/src/capture/IPCamera.go | 110 --------------------------- machinery/src/capture/main.go | 6 +- machinery/src/components/Kerberos.go | 23 +++++- machinery/src/computervision/main.go | 38 ++------- 5 files changed, 45 insertions(+), 147 deletions(-) delete mode 100644 machinery/src/capture/IPCamera.go diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 849eb5c6..c2f7c54c 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -84,6 +84,9 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { return } + // Iniatlise the mutex. + g.VideoH264DecoderMutex = &sync.Mutex{} + // find the H264 media and format var forma *format.H264 medi := desc.FindFormat(&forma) @@ -319,7 +322,9 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // Decode a packet to an image. func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { + g.VideoH264DecoderMutex.Lock() img, err := g.VideoH264FrameDecoder.decode(pkt.Data) + g.VideoH264DecoderMutex.Unlock() if err != nil { return image.YCbCr{}, err } @@ -332,7 +337,9 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { // Decode a packet to a Gray image. func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { + g.VideoH264DecoderMutex.Lock() img, err := g.VideoH264FrameDecoder.decodeRaw(pkt.Data) + g.VideoH264DecoderMutex.Unlock() if err != nil { return image.Gray{}, err } @@ -340,7 +347,13 @@ func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") return image.Gray{}, errors.New("Empty frame") } - return img, nil + + // Do a deep copy of the image + imgDeepCopy := image.NewGray(img.Bounds()) + imgDeepCopy.Stride = img.Stride + copy(imgDeepCopy.Pix, img.Pix) + + return *imgDeepCopy, err } // Get a list of streams from the RTSP server. diff --git a/machinery/src/capture/IPCamera.go b/machinery/src/capture/IPCamera.go deleted file mode 100644 index cee77d05..00000000 --- a/machinery/src/capture/IPCamera.go +++ /dev/null @@ -1,110 +0,0 @@ -package capture - -import ( - "strconv" - "sync" - "time" - - "github.com/kerberos-io/agent/machinery/src/log" - "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/agent/machinery/src/packets" - "github.com/kerberos-io/joy4/av/pubsub" - - "github.com/kerberos-io/joy4/av" - "github.com/kerberos-io/joy4/cgo/ffmpeg" -) - -func DecodeImage(frame *ffmpeg.VideoFrame, pkt packets.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) { - decoderMutex.Lock() - img, err := decoder.Decode(frame, pkt.Data) - decoderMutex.Unlock() - return img, err -} - -func HandleStream(infile av.DemuxCloser, queue *pubsub.Queue, communication *models.Communication) { //, wg *sync.WaitGroup) { - - log.Log.Debug("HandleStream: started") - var err error -loop: - for { - // This will check if we need to stop the thread, - // because of a reconfiguration. - select { - case <-communication.HandleStream: - break loop - default: - } - - var pkt av.Packet - if pkt, err = infile.ReadPacket(); err != nil { // sometimes this throws an end of file.. - log.Log.Error("HandleStream: " + err.Error()) - time.Sleep(1 * time.Second) - } - - // Could be that a decode is throwing errors. - if len(pkt.Data) > 0 { - - queue.WritePacket(pkt) - - // This will check if we need to stop the thread, - // because of a reconfiguration. - select { - case <-communication.HandleStream: - break loop - default: - } - - if pkt.IsKeyFrame { - - // Increment packets, so we know the device - // is not blocking. - r := communication.PackageCounter.Load().(int64) - log.Log.Info("HandleStream: packet size " + strconv.Itoa(len(pkt.Data))) - communication.PackageCounter.Store((r + 1) % 1000) - communication.LastPacketTimer.Store(time.Now().Unix()) - } - } - } - - queue.Close() - log.Log.Debug("HandleStream: finished") -} - -func HandleSubStream(infile av.DemuxCloser, queue *pubsub.Queue, communication *models.Communication) { //, wg *sync.WaitGroup) { - - log.Log.Debug("HandleSubStream: started") - var err error -loop: - for { - // This will check if we need to stop the thread, - // because of a reconfiguration. - select { - case <-communication.HandleSubStream: - break loop - default: - } - - var pkt av.Packet - if pkt, err = infile.ReadPacket(); err != nil { // sometimes this throws an end of file.. - log.Log.Error("HandleSubStream: " + err.Error()) - time.Sleep(1 * time.Second) - } - - // Could be that a decode is throwing errors. - if len(pkt.Data) > 0 { - - queue.WritePacket(pkt) - - // This will check if we need to stop the thread, - // because of a reconfiguration. - select { - case <-communication.HandleSubStream: - break loop - default: - } - } - } - - queue.Close() - log.Log.Debug("HandleSubStream: finished") -} diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index e12056fe..28ee6b83 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -378,7 +378,8 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } if start { - if err := myMuxer.Write(videoTrack, pkt.Data, uint64(pkt.Time), uint64(pkt.CompositionTime)); err != nil { + ttime := durationGoToMPEGTS(pkt.Time) + if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { log.Log.Error(err.Error()) } @@ -397,7 +398,8 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } // This will write the trailer as well. - if err := myMuxer.Write(videoTrack, nextPkt.Data, uint64(nextPkt.Time), uint64(nextPkt.CompositionTime)); err != nil { + ttime := durationGoToMPEGTS(nextPkt.Time) + if err := myMuxer.Write(videoTrack, nextPkt.Data, ttime, ttime); err != nil { log.Log.Error(err.Error()) } myMuxer.WriteTrailer() diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 1fa9cae5..c3a2ee2d 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -10,6 +10,7 @@ import ( "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/cloud" + "github.com/kerberos-io/agent/machinery/src/computervision" configService "github.com/kerberos-io/agent/machinery/src/config" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" @@ -274,12 +275,26 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Handle recording, will write an mp4 to disk. go capture.HandleRecordStream(queue, configDirectory, configuration, communication, rtspClient) + // Handle processing of motion + communication.HandleMotion = make(chan models.MotionDataPartial, 1) + if subStreamEnabled { + motionCursor := subQueue.Latest() + go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, rtspSubClient) + } else { + motionCursor := queue.Latest() + go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, rtspClient) + } + // Handle Upload to cloud provider (Kerberos Hub, Kerberos Vault and others) go cloud.HandleUpload(configDirectory, configuration, communication) // Handle ONVIF actions go onvif.HandleONVIFActions(configuration, communication) + // TODO: handle audio + communication.HandleAudio = make(chan models.AudioDataPartial, 1) + //go capture.HandleAudio(queue, configDirectory, configuration, communication, rtspClient) + // If we reach this point, we have a working RTSP connection. communication.CameraConnected = true @@ -334,10 +349,10 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu communication.SubQueue = nil } - //close(communication.HandleMotion) - //communication.HandleMotion = nil - //close(communication.HandleAudio) - //communication.HandleAudio = nil + 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.") diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index 8eff4bb6..5ab042d4 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -3,7 +3,6 @@ package computervision import ( "bufio" "bytes" - "encoding/base64" "image" "image/jpeg" "sync" @@ -18,7 +17,7 @@ import ( "github.com/kerberos-io/joy4/cgo/ffmpeg" ) -func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { //, wg *sync.WaitGroup) { +func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, rtspClient capture.RTSPClient) { log.Log.Debug("ProcessMotion: started") config := configuration.Config @@ -43,9 +42,6 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf hubKey := config.HubKey deviceKey := config.Key - // Allocate a VideoFrame - frame := ffmpeg.AllocVideoFrame() - // Initialise first 2 elements var imageArray [3]*image.Gray @@ -58,9 +54,9 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf pkt, cursorError = motionCursor.ReadPacket() // Check If valid package. if len(pkt.Data) > 0 && pkt.IsKeyFrame { - grayImage, err := GetGrayImage(frame, pkt, decoder, decoderMutex) + grayImage, err := rtspClient.DecodePacketRaw(pkt) if err == nil { - imageArray[j] = grayImage + imageArray[j] = &grayImage j++ } } @@ -120,14 +116,14 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf continue } - grayImage, err := GetGrayImage(frame, pkt, decoder, decoderMutex) + grayImage, err := rtspClient.DecodePacketRaw(pkt) if err == nil { - imageArray[2] = grayImage + imageArray[2] = &grayImage } // Store snapshots (jpg) for hull. if config.Capture.Snapshots != "false" { - StoreSnapshot(communication, frame, pkt, decoder, decoderMutex) + //StoreSnapshot(communication, frame, pkt, decoder, decoderMutex) } // Check if within time interval @@ -208,8 +204,6 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf img = nil } } - - frame.Free() } log.Log.Debug("ProcessMotion: finished") @@ -224,22 +218,6 @@ func FindMotion(imageArray [3]*image.Gray, coordinatesToCheck []int, pixelChange return changes > pixelChangeThreshold, changes } -func GetGrayImage(frame *ffmpeg.VideoFrame, pkt packets.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*image.Gray, error) { - _, err := capture.DecodeImage(frame, pkt, dec, decoderMutex) - - // Do a deep copy of the image - imgDeepCopy := image.NewGray(frame.ImageGray.Bounds()) - imgDeepCopy.Stride = frame.ImageGray.Stride - copy(imgDeepCopy.Pix, frame.ImageGray.Pix) - - return imgDeepCopy, err -} - -func GetRawImage(frame *ffmpeg.VideoFrame, pkt packets.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) { - _, err := capture.DecodeImage(frame, pkt, dec, decoderMutex) - return frame, err -} - func ImageToBytes(img image.Image) ([]byte, error) { buffer := new(bytes.Buffer) w := bufio.NewWriter(buffer) @@ -261,7 +239,7 @@ func AbsDiffBitwiseAndThreshold(img1 *image.Gray, img2 *image.Gray, img3 *image. } func StoreSnapshot(communication *models.Communication, frame *ffmpeg.VideoFrame, pkt packets.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { - rgbImage, err := GetRawImage(frame, pkt, decoder, decoderMutex) + /*rgbImage, err := GetRawImage(frame, pkt, decoder, decoderMutex) if err == nil { buffer := new(bytes.Buffer) w := bufio.NewWriter(buffer) @@ -270,5 +248,5 @@ func StoreSnapshot(communication *models.Communication, frame *ffmpeg.VideoFrame snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) communication.Image = snapshot } - } + }*/ } From 22f4a7e08a7c50a352c6a0bb3033bba4e726ecaa Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 11:05:58 +0100 Subject: [PATCH 21/81] fix closing of stream --- machinery/src/components/Kerberos.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index c3a2ee2d..81b70761 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -292,7 +292,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu go onvif.HandleONVIFActions(configuration, communication) // TODO: handle audio - communication.HandleAudio = make(chan models.AudioDataPartial, 1) + //communication.HandleAudio = make(chan models.AudioDataPartial, 1) //go capture.HandleAudio(queue, configDirectory, configuration, communication, rtspClient) // If we reach this point, we have a working RTSP connection. @@ -321,9 +321,10 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu communication.HandleUpload <- "stop" } communication.HandleStream <- "stop" - if subStreamEnabled { - communication.HandleSubStream <- "stop" - } + // We use the steam channel to stop both main and sub stream. + //if subStreamEnabled { + // communication.HandleSubStream <- "stop" + //} time.Sleep(time.Second * 3) @@ -351,8 +352,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu close(communication.HandleMotion) communication.HandleMotion = nil - close(communication.HandleAudio) - communication.HandleAudio = 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.") From 1e4affbf5c26a056b6cef207f2845f211214c83d Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 15:05:39 +0100 Subject: [PATCH 22/81] dont write trailer do +1 prerecording reader --- machinery/src/capture/main.go | 8 ++------ machinery/src/components/Kerberos.go | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 28ee6b83..ec2bb985 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -345,7 +345,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat var cursorError error var pkt packets.Packet var nextPkt packets.Packet - recordingCursor := queue.DelayedGopCount(int(config.Capture.PreRecording)) + recordingCursor := queue.DelayedGopCount(int(config.Capture.PreRecording + 1)) if cursorError == nil { pkt, cursorError = recordingCursor.ReadPacket() @@ -397,11 +397,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat pkt = nextPkt } - // This will write the trailer as well. - ttime := durationGoToMPEGTS(nextPkt.Time) - if err := myMuxer.Write(videoTrack, nextPkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) - } + // This will write the trailer a well. myMuxer.WriteTrailer() log.Log.Info("HandleRecordStream: file save: " + name) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 81b70761..b4d23f23 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -248,7 +248,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu if subStreamEnabled && rtspSubClient != nil { subQueue = packets.NewQueue() communication.SubQueue = subQueue - subQueue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). + subQueue.SetMaxGopCount(1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). subQueue.WriteHeader(videoSubStreams) go rtspSubClient.Start(context.Background(), subQueue, communication) } From 9757bc9b1817544ce393cccc01036b8bcba953b8 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 19:47:31 +0100 Subject: [PATCH 23/81] Calculate width and height + add FPS --- machinery/src/capture/Gortsplib.go | 28 ++++++++++++++++------------ machinery/src/packets/stream.go | 3 +++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index c2f7c54c..d46b4e77 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -95,12 +95,24 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { if medi == nil { log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "video media not found") } else { + // Get SPS from the SDP + // Calculate the width and height of the video + var sps h264.SPS + err = sps.Unmarshal(forma.SPS) + if err != nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + return + } + g.Streams = append(g.Streams, packets.Stream{ Name: forma.Codec(), IsVideo: true, IsAudio: false, SPS: forma.SPS, PPS: forma.PPS, + Width: sps.Width(), + Height: sps.Height(), + FPS: sps.FPS(), }) // Set the index for the video @@ -120,14 +132,6 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } g.VideoH264FrameDecoder = frameDec - // if SPS and PPS are present into the SDP, send them to the decoder - if forma.SPS != nil { - frameDec.decode(forma.SPS) - } - if forma.PPS != nil { - frameDec.decode(forma.PPS) - } - // setup a video media _, err = g.Client.Setup(desc.BaseURL, medi, 0, 0) if err != nil { @@ -362,9 +366,9 @@ func (j *Golibrtsp) GetStreams() ([]packets.Stream, error) { } // Get a list of video streams from the RTSP server. -func (j *Golibrtsp) GetVideoStreams() ([]packets.Stream, error) { +func (g *Golibrtsp) GetVideoStreams() ([]packets.Stream, error) { var videoStreams []packets.Stream - for _, stream := range j.Streams { + for _, stream := range g.Streams { if stream.IsVideo { videoStreams = append(videoStreams, stream) } @@ -373,9 +377,9 @@ func (j *Golibrtsp) GetVideoStreams() ([]packets.Stream, error) { } // Get a list of audio streams from the RTSP server. -func (j *Golibrtsp) GetAudioStreams() ([]packets.Stream, error) { +func (g *Golibrtsp) GetAudioStreams() ([]packets.Stream, error) { var audioStreams []packets.Stream - for _, stream := range j.Streams { + for _, stream := range g.Streams { if stream.IsAudio { audioStreams = append(audioStreams, stream) } diff --git a/machinery/src/packets/stream.go b/machinery/src/packets/stream.go index 75b06f35..277f49db 100644 --- a/machinery/src/packets/stream.go +++ b/machinery/src/packets/stream.go @@ -25,6 +25,9 @@ type Stream struct { // Denum is the denominator of the framerate. Denum int + // FPS is the framerate of the stream. + FPS float64 + // For H264, this is the sps. SPS []byte From 68b9c5f6791cf31f2c6cf285acabba2b6d9b61c0 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 20:24:35 +0100 Subject: [PATCH 24/81] fix videostream for subclient --- machinery/src/components/Kerberos.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index b4d23f23..08787e7c 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -174,8 +174,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu } // Get the video streams from the RTSP server. - videoSubStreams, err = rtspClient.GetVideoStreams() - if err != nil || len(videoStreams) == 0 { + videoSubStreams, err = rtspSubClient.GetVideoStreams() + if err != nil || len(videoSubStreams) == 0 { log.Log.Error("RunAgent: no video sub stream found, might be the wrong codec (we only support H264 for the moment)") time.Sleep(time.Second * 3) return status From 25c35ba91b2a45d85a5e1e86968ea7a45a8bfd0c Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 21:27:58 +0100 Subject: [PATCH 25/81] fix hull --- machinery/src/computervision/main.go | 29 +++++++++++++--------------- machinery/src/config/main.go | 21 -------------------- machinery/src/routers/http/Routes.go | 19 ------------------ 3 files changed, 13 insertions(+), 56 deletions(-) diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index 5ab042d4..87fb4f65 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -3,9 +3,9 @@ package computervision import ( "bufio" "bytes" + "encoding/base64" "image" "image/jpeg" - "sync" "time" mqtt "github.com/eclipse/paho.mqtt.golang" @@ -14,7 +14,6 @@ import ( "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/packets" - "github.com/kerberos-io/joy4/cgo/ffmpeg" ) func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, rtspClient capture.RTSPClient) { @@ -122,8 +121,19 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf } // Store snapshots (jpg) for hull. + // We'll store the last snapshot, so we can use it for hull on the frontend. + // But we'll also store the last 10 snapshots, so we can use it for the timelapse. if config.Capture.Snapshots != "false" { - //StoreSnapshot(communication, frame, pkt, decoder, decoderMutex) + image, err := rtspClient.DecodePacket(pkt) + if err == nil { + buffer := new(bytes.Buffer) + w := bufio.NewWriter(buffer) + err := jpeg.Encode(w, &image, &jpeg.Options{Quality: 15}) + if err == nil { + snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) + communication.Image = snapshot + } + } } // Check if within time interval @@ -237,16 +247,3 @@ func AbsDiffBitwiseAndThreshold(img1 *image.Gray, img2 *image.Gray, img3 *image. } return changes } - -func StoreSnapshot(communication *models.Communication, frame *ffmpeg.VideoFrame, pkt packets.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { - /*rgbImage, err := GetRawImage(frame, pkt, decoder, decoderMutex) - if err == nil { - buffer := new(bytes.Buffer) - w := bufio.NewWriter(buffer) - err := jpeg.Encode(w, &rgbImage.Image, &jpeg.Options{Quality: 15}) - if err == nil { - snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) - communication.Image = snapshot - } - }*/ -} diff --git a/machinery/src/config/main.go b/machinery/src/config/main.go index 107582b8..e8a004a3 100644 --- a/machinery/src/config/main.go +++ b/machinery/src/config/main.go @@ -4,11 +4,9 @@ import ( "context" "encoding/json" "errors" - "image" "io/ioutil" "os" "reflect" - "sort" "strconv" "strings" "time" @@ -20,25 +18,6 @@ import ( "go.mongodb.org/mongo-driver/bson" ) -func GetImageFromFilePath(configDirectory string) (image.Image, error) { - snapshotDirectory := configDirectory + "/data/snapshots" - files, err := ioutil.ReadDir(snapshotDirectory) - if err == nil && len(files) > 1 { - sort.Slice(files, func(i, j int) bool { - return files[i].ModTime().Before(files[j].ModTime()) - }) - filePath := configDirectory + "/data/snapshots/" + files[1].Name() - f, err := os.Open(filePath) - if err != nil { - return nil, err - } - defer f.Close() - image, _, err := image.Decode(f) - return image, err - } - return nil, errors.New("Could not find a snapshot in " + snapshotDirectory) -} - // ReadUserConfig Reads the user configuration of the Kerberos Open Source instance. // This will return a models.User struct including the username, password, // selected language, and if the installation was completed or not. diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/Routes.go index 01ffbd69..67b9d7e7 100644 --- a/machinery/src/routers/http/Routes.go +++ b/machinery/src/routers/http/Routes.go @@ -1,9 +1,6 @@ package http import ( - "image" - "time" - jwt "github.com/appleboy/gin-jwt/v2" "github.com/gin-gonic/gin" "github.com/kerberos-io/agent/machinery/src/capture" @@ -11,9 +8,7 @@ import ( "github.com/kerberos-io/agent/machinery/src/routers/websocket" "github.com/kerberos-io/agent/machinery/src/cloud" - "github.com/kerberos-io/agent/machinery/src/components" configService "github.com/kerberos-io/agent/machinery/src/config" - "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/utils" ) @@ -209,20 +204,6 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect cloud.VerifyPersistence(c, configDirectory) }) - // Streaming handler - api.GET("/stream", func(c *gin.Context) { - // TODO add a token validation! - imageFunction := func() (image.Image, error) { - // We will only send an image once per second. - time.Sleep(time.Second * 1) - log.Log.Info("AddRoutes (/stream): reading from MJPEG stream") - img, err := configService.GetImageFromFilePath(configDirectory) - return img, err - } - h := components.StartMotionJPEG(imageFunction, 80) - h.ServeHTTP(c.Writer, c.Request) - }) - // Camera specific methods. Doesn't require any authorization. // These are available for anyone, but require the agent, to reach // the camera. From df3183ec1c5f06c1303fe3d87fb1777eedae3010 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 22:18:06 +0100 Subject: [PATCH 26/81] add backchannel support --- machinery/src/capture/Gortsplib.go | 79 ++++++++++++++++++++++++------ machinery/src/packets/stream.go | 3 ++ 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index d46b4e77..b061c25f 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -56,13 +56,19 @@ type Golibrtsp struct { AudioG711Forma *format.G711 AudioG711Decoder *rtpsimpleaudio.Decoder + AudioG711IndexBackChannel int8 + AudioG711MediaBackChannel *description.Media + AudioG711FormaBackChannel *format.G711 + Streams []packets.Stream } // Connect to the RTSP server. func (g *Golibrtsp) Connect(ctx context.Context) (err error) { - g.Client = gortsplib.Client{} + g.Client = gortsplib.Client{ + RequestBackChannels: g.WithBackChannel, + } // parse URL u, err := base.ParseURL(g.Url) @@ -105,14 +111,15 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } g.Streams = append(g.Streams, packets.Stream{ - Name: forma.Codec(), - IsVideo: true, - IsAudio: false, - SPS: forma.SPS, - PPS: forma.PPS, - Width: sps.Width(), - Height: sps.Height(), - FPS: sps.FPS(), + Name: forma.Codec(), + IsVideo: true, + IsAudio: false, + SPS: forma.SPS, + PPS: forma.PPS, + Width: sps.Width(), + Height: sps.Height(), + FPS: sps.FPS(), + IsBackChannel: false, }) // Set the index for the video @@ -141,17 +148,18 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { // Look for audio stream. // find the G711 media and format - var audioForma *format.G711 - audioMedi := desc.FindFormat(&audioForma) + audioForma, audioMedi := FindPCMU(desc, false) g.AudioG711Media = audioMedi g.AudioG711Forma = audioForma if audioMedi == nil { log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio media not found") } else { + g.Streams = append(g.Streams, packets.Stream{ - Name: "PCM_MULAW", - IsVideo: false, - IsAudio: true, + Name: "PCM_MULAW", + IsVideo: false, + IsAudio: true, + IsBackChannel: false, }) // Set the index for the audio @@ -171,6 +179,34 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } } + // Look for audio back channel. + if g.WithBackChannel { + // find the LPCM media and format + audioFormaBackChannel, audioMediBackChannel := FindPCMU(desc, true) + g.AudioG711MediaBackChannel = audioMediBackChannel + g.AudioG711FormaBackChannel = audioFormaBackChannel + if audioMedi == nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio backchannel not found") + } else { + + g.Streams = append(g.Streams, packets.Stream{ + Name: "PCM_MULAW", + IsVideo: false, + IsAudio: true, + IsBackChannel: true, + }) + + // Set the index for the audio + g.AudioG711IndexBackChannel = int8(len(g.Streams)) - 1 + + // setup a audio media + _, err = g.Client.Setup(desc.BaseURL, audioMediBackChannel, 0, 0) + if err != nil { + // Something went wrong .. Do something + } + } + } + return } @@ -530,3 +566,18 @@ func fromCPtr(buf unsafe.Pointer, size int) (ret []uint8) { hdr.Data = uintptr(buf) return } + +func FindPCMU(desc *description.Session, isBackChannel bool) (*format.G711, *description.Media) { + for _, media := range desc.Medias { + if media.IsBackChannel == isBackChannel { + for _, forma := range media.Formats { + if g711, ok := forma.(*format.G711); ok { + if g711.MULaw { + return g711, media + } + } + } + } + } + return nil, nil +} diff --git a/machinery/src/packets/stream.go b/machinery/src/packets/stream.go index 277f49db..d3f463bc 100644 --- a/machinery/src/packets/stream.go +++ b/machinery/src/packets/stream.go @@ -33,4 +33,7 @@ type Stream struct { // For H264, this is the pps. PPS []byte + + // IsBackChannel is true if this stream is a back channel. + IsBackChannel bool } From 6798569b7f6407059c31539d2be446b1dbfcfb2f Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 22:57:33 +0100 Subject: [PATCH 27/81] first try for the backchannel using gortsplib getting error short buffer --- machinery/src/capture/Gortsplib.go | 15 ++++++++ machinery/src/capture/RTSPClient.go | 3 ++ machinery/src/components/Audio.go | 54 ++++++++++++++++++---------- machinery/src/components/Kerberos.go | 10 ++++-- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index b061c25f..fdbd34dc 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -56,6 +56,7 @@ type Golibrtsp struct { AudioG711Forma *format.G711 AudioG711Decoder *rtpsimpleaudio.Decoder + HasBackChannel bool AudioG711IndexBackChannel int8 AudioG711MediaBackChannel *description.Media AudioG711FormaBackChannel *format.G711 @@ -180,6 +181,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } // Look for audio back channel. + g.HasBackChannel = false if g.WithBackChannel { // find the LPCM media and format audioFormaBackChannel, audioMediBackChannel := FindPCMU(desc, true) @@ -203,6 +205,8 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { _, err = g.Client.Setup(desc.BaseURL, audioMediBackChannel, 0, 0) if err != nil { // Something went wrong .. Do something + } else { + g.HasBackChannel = true } } } @@ -360,6 +364,17 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati return } +func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { + if g.HasBackChannel { + err := g.Client.WritePacketRTP(g.AudioG711MediaBackChannel, pkt.Packet) + if err != nil { + log.Log.Debug("RTSPClient(Golibrtsp).WritePacketBackChannel(): " + err.Error()) + return err + } + } + return errors.New("Backchannel not enabled") +} + // Decode a packet to an image. func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { g.VideoH264DecoderMutex.Lock() diff --git a/machinery/src/capture/RTSPClient.go b/machinery/src/capture/RTSPClient.go index d3d31a9d..f135e975 100644 --- a/machinery/src/capture/RTSPClient.go +++ b/machinery/src/capture/RTSPClient.go @@ -43,6 +43,9 @@ type RTSPClient interface { // Decode a packet into a image. DecodePacketRaw(pkt packets.Packet) (image.Gray, error) + // Write a packet to the RTSP server. + WritePacket(pkt packets.Packet) error + // Close the connection to the RTSP server. Close() error diff --git a/machinery/src/components/Audio.go b/machinery/src/components/Audio.go index 4e24d2f3..5d693a96 100644 --- a/machinery/src/components/Audio.go +++ b/machinery/src/components/Audio.go @@ -6,9 +6,12 @@ import ( "os" "time" + "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/packets" "github.com/kerberos-io/joy4/av" + "github.com/pion/rtp" "github.com/zaf/g711" ) @@ -27,28 +30,41 @@ func GetBackChannelAudioCodec(streams []av.CodecData, communication *models.Comm 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") +func WriteAudioToBackchannel(communication *models.Communication, rtspClient capture.RTSPClient) { + log.Log.Info("Audio.WriteAudioToBackchannel(): writing to backchannel audio codec") + length := uint32(0) + sequenceNumber := uint16(0) + 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) + } - 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) + pkt := packets.Packet{ + Packet: &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + Marker: true, // should be true + PayloadType: 0, //packet.PayloadType, // will be owerwriten + SequenceNumber: sequenceNumber, + Timestamp: uint32(length), + SSRC: 1293847657, + }, + Payload: bufferUlaw, + }, } + err := rtspClient.WritePacket(pkt) + if err != nil { + log.Log.Error("Audio.WriteAudioToBackchannel(): error writing packet to backchannel") + } + + length = (length + uint32(len(bufferUlaw))) % 65536 + sequenceNumber = (sequenceNumber + 1) % 65535 + time.Sleep(128 * time.Millisecond) } - log.Log.Info("WriteAudioToBackchannel: finished") + log.Log.Info("Audio.WriteAudioToBackchannel(): finished") } diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 08787e7c..39a6dec9 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -121,6 +121,9 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu return status } + // Check if has backchannel, then we set it in the communication struct + communication.HasBackChannel = rtspClient.HasBackChannel + // Get the video streams from the RTSP server. videoStreams, err := rtspClient.GetVideoStreams() if err != nil || len(videoStreams) == 0 { @@ -291,9 +294,10 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Handle ONVIF actions go onvif.HandleONVIFActions(configuration, communication) - // TODO: handle audio - //communication.HandleAudio = make(chan models.AudioDataPartial, 1) - //go capture.HandleAudio(queue, configDirectory, configuration, communication, rtspClient) + communication.HandleAudio = make(chan models.AudioDataPartial, 1) + if rtspClient.HasBackChannel { + go WriteAudioToBackchannel(communication, rtspClient) + } // If we reach this point, we have a working RTSP connection. communication.CameraConnected = true From bd984ea1c7f223d410d8f0172cbcdbb2b6ba52f9 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 1 Dec 2023 23:17:32 +0100 Subject: [PATCH 28/81] works now, but needed to change size of paylod --- machinery/src/capture/Gortsplib.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index fdbd34dc..44a2759a 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -372,7 +372,7 @@ func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { return err } } - return errors.New("Backchannel not enabled") + return nil } // Decode a packet to an image. From e42f430bb8a119f5518c914639cd50b59e020cda Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 2 Dec 2023 00:43:31 +0100 Subject: [PATCH 29/81] add MPEG4 (AAC support), put ready for H265 --- machinery/src/capture/Gortsplib.go | 117 ++++++++++++++++++++++++++++- machinery/src/capture/main.go | 97 +++++++++++++++++++----- machinery/src/packets/packet.go | 3 + 3 files changed, 196 insertions(+), 21 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 44a2759a..b2b1627c 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -23,8 +23,10 @@ import ( "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" "github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" "github.com/bluenviron/mediacommon/pkg/codecs/h264" + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/packets" @@ -61,6 +63,11 @@ type Golibrtsp struct { AudioG711MediaBackChannel *description.Media AudioG711FormaBackChannel *format.G711 + AudioMPEG4Index int8 + AudioMPEG4Media *description.Media + AudioMPEG4Forma *format.MPEG4Audio + AudioMPEG4Decoder *rtpmpeg4audio.Decoder + Streams []packets.Stream } @@ -211,6 +218,38 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } } + // Look for audio stream. + // find the G711 media and format + audioFormaMPEG4, audioMediMPEG4 := FindMPEG4Audio(desc, false) + g.AudioMPEG4Media = audioMediMPEG4 + g.AudioMPEG4Forma = audioFormaMPEG4 + if audioMediMPEG4 == nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio media not found") + } else { + g.Streams = append(g.Streams, packets.Stream{ + Name: "AAC", + IsVideo: false, + IsAudio: true, + IsBackChannel: false, + }) + + // Set the index for the audio + g.AudioMPEG4Index = int8(len(g.Streams)) - 1 + + // create decoder + audiortpDec, err := audioFormaMPEG4.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + } + g.AudioMPEG4Decoder = audiortpDec + + // setup a audio media + _, err = g.Client.Setup(desc.BaseURL, audioMediMPEG4, 0, 0) + if err != nil { + // Something went wrong .. Do something + } + } + return } @@ -218,7 +257,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") - // called when a audio RTP packet arrives + // called when a MULAW audio RTP packet arrives if g.AudioG711Media != nil { g.Client.OnPacketRTP(g.AudioG711Media, g.AudioG711Forma, func(rtppkt *rtp.Packet) { // decode timestamp @@ -242,6 +281,48 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati Time: pts, CompositionTime: pts, Idx: g.AudioG711Index, + IsVideo: false, + IsAudio: true, + Codec: "PCM_MULAW", + } + queue.WritePacket(pkt) + }) + } + + // called when a AAC audio RTP packet arrives + if g.AudioMPEG4Media != nil { + g.Client.OnPacketRTP(g.AudioMPEG4Media, g.AudioMPEG4Forma, func(rtppkt *rtp.Packet) { + // decode timestamp + pts, ok := g.Client.PacketPTS(g.AudioMPEG4Media, rtppkt) + if !ok { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + return + } + + // Encode the AAC samples from RTP packets + // extract access units from RTP packets + aus, err := g.AudioMPEG4Decoder.Decode(rtppkt) + if err != nil { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + return + } + + enc, err := WriteMPEG4Audio(g.AudioMPEG4Forma, aus) + if err != nil { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + return + } + + pkt := packets.Packet{ + IsKeyFrame: false, + Packet: rtppkt, + Data: enc, + Time: pts, + CompositionTime: pts, + Idx: g.AudioG711Index, + IsVideo: false, + IsAudio: true, + Codec: "AAC", } queue.WritePacket(pkt) }) @@ -320,6 +401,9 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati Time: pts, CompositionTime: pts, Idx: g.VideoH264Index, + IsVideo: true, + IsAudio: false, + Codec: "H264", } pkt.Data = pkt.Data[4:] @@ -596,3 +680,34 @@ func FindPCMU(desc *description.Session, isBackChannel bool) (*format.G711, *des } return nil, nil } + +func FindMPEG4Audio(desc *description.Session, isBackChannel bool) (*format.MPEG4Audio, *description.Media) { + for _, media := range desc.Medias { + if media.IsBackChannel == isBackChannel { + for _, forma := range media.Formats { + if mpeg4, ok := forma.(*format.MPEG4Audio); ok { + return mpeg4, media + } + } + } + } + return nil, nil +} + +// WriteMPEG4Audio writes MPEG-4 Audio access units. +func WriteMPEG4Audio(forma *format.MPEG4Audio, aus [][]byte) ([]byte, error) { + pkts := make(mpeg4audio.ADTSPackets, len(aus)) + for i, au := range aus { + pkts[i] = &mpeg4audio.ADTSPacket{ + Type: forma.Config.Type, + SampleRate: forma.Config.SampleRate, + ChannelCount: forma.Config.ChannelCount, + AU: au, + } + } + enc, err := pkts.Marshal() + if err != nil { + return nil, err + } + return enc, nil +} diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index ec2bb985..53c63de1 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -79,6 +79,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat var cws *cacheWriterSeeker var myMuxer *mp4.Movmuxer var videoTrack uint32 + var audioTrack uint32 // Do not do anything! log.Log.Info("HandleRecordStream: Start continuous recording ") @@ -118,8 +119,20 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat nextPkt.IsKeyFrame && (timestamp+recordingPeriod-now <= 0 || now-startRecording >= maxRecordingPeriod) { // Write the last packet - if err := myMuxer.Write(videoTrack, pkt.Data, durationGoToMPEGTS(pkt.Time), durationGoToMPEGTS(pkt.CompositionTime)); err != nil { - log.Log.Error(err.Error()) + ttime := convertPTS(pkt.Time) + if pkt.IsVideo { + if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { + log.Log.Error(err.Error()) + } + } else if pkt.IsAudio { + if pkt.Codec == "AAC" { + if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { + log.Log.Error(err.Error()) + } + } else { + // TODO: transcode to AAC, some work to do.. + log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + } } // This will write the trailer a well. @@ -217,25 +230,52 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat //myMuxer = mp4.NewMuxer(file) cws = newCacheWriterSeeker(4096) myMuxer, _ = mp4.CreateMp4Muxer(cws) - videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H264) + // We choose between H264 and H265 + if pkt.Codec == "H264" { + videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H264) + } else if pkt.Codec == "H265" { + videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H265) + } + // For an MP4 container, AAC is the only audio codec supported. + audioTrack = myMuxer.AddAudioTrack(mp4.MP4_CODEC_AAC) } log.Log.Info("HandleRecordStream: composing recording") log.Log.Info("HandleRecordStream: write header") - time := durationGoToMPEGTS(pkt.Time) - if err := myMuxer.Write(videoTrack, pkt.Data, time, time); err != nil { - log.Log.Error(err.Error()) + ttime := convertPTS(pkt.Time) + if pkt.IsVideo { + if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { + log.Log.Error(err.Error()) + } + } else if pkt.IsAudio { + if pkt.Codec == "AAC" { + if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { + log.Log.Error(err.Error()) + } + } else { + // TODO: transcode to AAC, some work to do.. + log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + } } recordingStatus = "started" } else if start { - if pkt.Idx == 0 { - time := durationGoToMPEGTS(pkt.Time) - if err := myMuxer.Write(videoTrack, pkt.Data, time, time); err != nil { + ttime := convertPTS(pkt.Time) + if pkt.IsVideo { + if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { log.Log.Error(err.Error()) } + } else if pkt.IsAudio { + if pkt.Codec == "AAC" { + if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { + log.Log.Error(err.Error()) + } + } else { + // TODO: transcode to AAC, some work to do.. + log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + } } } @@ -281,6 +321,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat var cws *cacheWriterSeeker var myMuxer *mp4.Movmuxer var videoTrack uint32 + var audioTrack uint32 for motion := range communication.HandleMotion { @@ -329,17 +370,22 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat cws = newCacheWriterSeeker(4096) myMuxer, _ = mp4.CreateMp4Muxer(cws) - videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H264) - start := false + // Check which video codec we need to use. + videoSteams, _ := rtspClient.GetVideoStreams() + for _, stream := range videoSteams { + if stream.Name == "H264" { + videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H264) + } else if stream.Name == "H265" { + videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H265) + } + } + // For an MP4 container, AAC is the only audio codec supported. + audioTrack = myMuxer.AddAudioTrack(mp4.MP4_CODEC_AAC) + start := false log.Log.Info("HandleRecordStream: composing recording") log.Log.Info("HandleRecordStream: write header") - // Creating the file, might block sometimes. - // TODO CHANGE!!! - //if err := myMuxer.WriteHeader(streams); err != nil { - // log.Log.Error(err.Error()) - //} // Get as much packets we need. var cursorError error @@ -378,9 +424,20 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } if start { - ttime := durationGoToMPEGTS(pkt.Time) - if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + ttime := convertPTS(pkt.Time) + if pkt.IsVideo { + if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { + log.Log.Error(err.Error()) + } + } else if pkt.IsAudio { + if pkt.Codec == "AAC" { + if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { + log.Log.Error(err.Error()) + } + } else { + // TODO: transcode to AAC, some work to do.. + log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + } } // We will sync to file every keyframe. @@ -581,6 +638,6 @@ func (ws *cacheWriterSeeker) Seek(offset int64, whence int) (int64, error) { } } -func durationGoToMPEGTS(v time.Duration) uint64 { +func convertPTS(v time.Duration) uint64 { return uint64(v.Milliseconds()) } diff --git a/machinery/src/packets/packet.go b/machinery/src/packets/packet.go index c2e5f4e7..62390ad3 100644 --- a/machinery/src/packets/packet.go +++ b/machinery/src/packets/packet.go @@ -12,8 +12,11 @@ type Packet struct { Packet *rtp.Packet // for JOY4 and Gortsplib library + IsAudio bool // packet is audio + IsVideo bool // packet is video IsKeyFrame bool // video packet is key frame Idx int8 // stream index in container format + Codec string // codec name CompositionTime time.Duration // packet presentation time minus decode time for H264 B-Frame Time time.Duration // packet decode time Data []byte // packet data From b317a6a9dbb1905ce33d80f27ba0097fcf98c602 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 2 Dec 2023 12:34:28 +0100 Subject: [PATCH 30/81] fix closing of rtspclient + integrate h265 support now we can record in H265 and stream in H264 using webrtc or websocket --- machinery/src/capture/Gortsplib.go | 222 ++++++++++++++++++++++++--- machinery/src/components/Kerberos.go | 2 + machinery/src/packets/stream.go | 3 + machinery/src/webrtc/main.go | 28 ++-- 4 files changed, 217 insertions(+), 38 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index b2b1627c..2eb4e07a 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -22,10 +22,12 @@ import ( "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtph265" "github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" "github.com/bluenviron/mediacommon/pkg/codecs/h264" + "github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" @@ -45,9 +47,16 @@ type Golibrtsp struct { VideoH264Media *description.Media VideoH264Forma *format.H264 VideoH264Decoder *rtph264.Decoder - VideoH264FrameDecoder *h264Decoder + VideoH264FrameDecoder *Decoder VideoH264DecoderMutex *sync.Mutex + VideoH265Index int8 + VideoH265Media *description.Media + VideoH265Forma *format.H265 + VideoH265Decoder *rtph265.Decoder + VideoH265FrameDecoder *Decoder + VideoH265DecoderMutex *sync.Mutex + AudioLPCMIndex int8 AudioLPCMMedia *description.Media AudioLPCMForma *format.LPCM @@ -102,28 +111,28 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.VideoH264DecoderMutex = &sync.Mutex{} // find the H264 media and format - var forma *format.H264 - medi := desc.FindFormat(&forma) - g.VideoH264Media = medi - g.VideoH264Forma = forma - if medi == nil { + var formaH264 *format.H264 + mediH264 := desc.FindFormat(&formaH264) + g.VideoH264Media = mediH264 + g.VideoH264Forma = formaH264 + if mediH264 == nil { log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "video media not found") } else { // Get SPS from the SDP // Calculate the width and height of the video var sps h264.SPS - err = sps.Unmarshal(forma.SPS) + err = sps.Unmarshal(formaH264.SPS) if err != nil { log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) return } g.Streams = append(g.Streams, packets.Stream{ - Name: forma.Codec(), + Name: formaH264.Codec(), IsVideo: true, IsAudio: false, - SPS: forma.SPS, - PPS: forma.PPS, + SPS: formaH264.SPS, + PPS: formaH264.PPS, Width: sps.Width(), Height: sps.Height(), FPS: sps.FPS(), @@ -134,21 +143,78 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.VideoH264Index = int8(len(g.Streams)) - 1 // setup RTP/H264 -> H264 decoder - rtpDec, err := forma.CreateDecoder() + rtpDec, err := formaH264.CreateDecoder() if err != nil { // Something went wrong .. Do something } g.VideoH264Decoder = rtpDec // setup H264 -> raw frames decoder - frameDec, err := newH264Decoder() + frameDec, err := newDecoder("H264") if err != nil { // Something went wrong .. Do something } g.VideoH264FrameDecoder = frameDec // setup a video media - _, err = g.Client.Setup(desc.BaseURL, medi, 0, 0) + _, err = g.Client.Setup(desc.BaseURL, mediH264, 0, 0) + if err != nil { + // Something went wrong .. Do something + } + } + + // Iniatlise the mutex. + g.VideoH265DecoderMutex = &sync.Mutex{} + + // find the H265 media and format + var formaH265 *format.H265 + mediH265 := desc.FindFormat(&formaH265) + g.VideoH265Media = mediH265 + g.VideoH265Forma = formaH265 + if mediH265 == nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "video media not found") + } else { + // Get SPS from the SDP + // Calculate the width and height of the video + var sps h265.SPS + err = sps.Unmarshal(formaH265.SPS) + if err != nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + return + } + + g.Streams = append(g.Streams, packets.Stream{ + Name: formaH265.Codec(), + IsVideo: true, + IsAudio: false, + SPS: formaH265.SPS, + PPS: formaH265.PPS, + VPS: formaH265.VPS, + Width: sps.Width(), + Height: sps.Height(), + FPS: sps.FPS(), + IsBackChannel: false, + }) + + // Set the index for the video + g.VideoH265Index = int8(len(g.Streams)) - 1 + + // setup RTP/H265 -> H265 decoder + rtpDec, err := formaH265.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + } + g.VideoH265Decoder = rtpDec + + // setup H265 -> raw frames decoder + frameDec, err := newDecoder("H265") + if err != nil { + // Something went wrong .. Do something + } + g.VideoH265FrameDecoder = frameDec + + // setup a video media + _, err = g.Client.Setup(desc.BaseURL, mediH265, 0, 0) if err != nil { // Something went wrong .. Do something } @@ -194,7 +260,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { audioFormaBackChannel, audioMediBackChannel := FindPCMU(desc, true) g.AudioG711MediaBackChannel = audioMediBackChannel g.AudioG711FormaBackChannel = audioFormaBackChannel - if audioMedi == nil { + if audioMediBackChannel == nil { log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio backchannel not found") } else { @@ -328,7 +394,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati }) } - // called when a video RTP packet arrives + // called when a video RTP packet arrives for H264 if g.VideoH264Media != nil { g.Client.OnPacketRTP(g.VideoH264Media, g.VideoH264Forma, func(rtppkt *rtp.Packet) { @@ -439,6 +505,113 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati }) } + // called when a video RTP packet arrives for H265 + if g.VideoH265Media != nil { + g.Client.OnPacketRTP(g.VideoH265Media, g.VideoH265Forma, func(rtppkt *rtp.Packet) { + + // This will check if we need to stop the thread, + // because of a reconfiguration. + select { + case <-communication.HandleStream: + return + default: + } + + if len(rtppkt.Payload) > 0 { + + // decode timestamp + pts, ok := g.Client.PacketPTS(g.VideoH265Media, rtppkt) + if !ok { + log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + return + } + + // Extract access units from RTP packets + // We need to do this, because the decoder expects a full + // access unit. Once we have a full access unit, we can + // decode it, and know if it's a keyframe or not. + au, errDecode := g.VideoH265Decoder.Decode(rtppkt) + if errDecode != nil { + if errDecode != rtph265.ErrNonStartingPacketAndNoPrevious && errDecode != rtph265.ErrMorePacketsNeeded { + log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + errDecode.Error()) + } + return + } + + filteredAU := [][]byte{ + {byte(h265.NALUType_AUD_NUT) << 1, 1, 0x50}, + } + + isRandomAccess := false + for _, nalu := range au { + typ := h265.NALUType((nalu[0] >> 1) & 0b111111) + switch typ { + case h265.NALUType_VPS_NUT: + continue + case h265.NALUType_SPS_NUT: + continue + case h265.NALUType_PPS_NUT: + continue + case h265.NALUType_AUD_NUT: + continue + case h265.NALUType_IDR_W_RADL, h265.NALUType_IDR_N_LP, h265.NALUType_CRA_NUT: + isRandomAccess = true + } + filteredAU = append(filteredAU, nalu) + } + + au = filteredAU + + if len(au) <= 1 { + return + } + + // add VPS, SPS and PPS before random access access unit + if isRandomAccess { + au = append([][]byte{g.VideoH265Forma.VPS, g.VideoH265Forma.SPS, g.VideoH265Forma.PPS}, au...) + } + + enc, err := h264.AnnexBMarshal(au) + if err != nil { + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + return + } + + pkt := packets.Packet{ + IsKeyFrame: isRandomAccess, + Packet: rtppkt, + Data: enc, + Time: pts, + CompositionTime: pts, + Idx: g.VideoH265Index, + IsVideo: true, + IsAudio: false, + Codec: "H265", + } + + queue.WritePacket(pkt) + + // This will check if we need to stop the thread, + // because of a reconfiguration. + select { + case <-communication.HandleStream: + return + default: + } + + if isRandomAccess { + // Increment packets, so we know the device + // is not blocking. + r := communication.PackageCounter.Load().(int64) + log.Log.Info("RTSPClient(Golibrtsp).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + communication.PackageCounter.Store((r + 1) % 1000) + communication.LastPacketTimer.Store(time.Now().Unix()) + } + } + + }) + } + // Play the stream. _, err = g.Client.Play(nil) if err != nil { @@ -526,7 +699,9 @@ func (g *Golibrtsp) GetAudioStreams() ([]packets.Stream, error) { func (g *Golibrtsp) Close() error { // Close the demuxer. g.Client.Close() - g.VideoH264FrameDecoder.Close() + if g.VideoH264Decoder != nil { + g.VideoH264FrameDecoder.Close() + } return nil } @@ -539,14 +714,17 @@ func frameLineSize(frame *C.AVFrame) *C.int { } // h264Decoder is a wrapper around FFmpeg's H264 decoder. -type h264Decoder struct { +type Decoder struct { codecCtx *C.AVCodecContext srcFrame *C.AVFrame } // newH264Decoder allocates a new h264Decoder. -func newH264Decoder() (*h264Decoder, error) { +func newDecoder(codecName string) (*Decoder, error) { codec := C.avcodec_find_decoder(C.AV_CODEC_ID_H264) + if codecName == "H265" { + codec = C.avcodec_find_decoder(C.AV_CODEC_ID_H265) + } if codec == nil { return nil, fmt.Errorf("avcodec_find_decoder() failed") } @@ -568,14 +746,14 @@ func newH264Decoder() (*h264Decoder, error) { return nil, fmt.Errorf("av_frame_alloc() failed") } - return &h264Decoder{ + return &Decoder{ codecCtx: codecCtx, srcFrame: srcFrame, }, nil } // close closes the decoder. -func (d *h264Decoder) Close() { +func (d *Decoder) Close() { if d.srcFrame != nil { C.av_frame_free(&d.srcFrame) } @@ -583,7 +761,7 @@ func (d *h264Decoder) Close() { C.avcodec_close(d.codecCtx) } -func (d *h264Decoder) decode(nalu []byte) (image.YCbCr, error) { +func (d *Decoder) decode(nalu []byte) (image.YCbCr, error) { nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...) // send NALU to decoder @@ -623,7 +801,7 @@ func (d *h264Decoder) decode(nalu []byte) (image.YCbCr, error) { return image.YCbCr{}, nil } -func (d *h264Decoder) decodeRaw(nalu []byte) (image.Gray, error) { +func (d *Decoder) decodeRaw(nalu []byte) (image.Gray, error) { nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...) // send NALU to decoder diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 39a6dec9..8f4f620a 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -128,6 +128,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu videoStreams, err := rtspClient.GetVideoStreams() if err != nil || len(videoStreams) == 0 { log.Log.Error("RunAgent: no video stream found, might be the wrong codec (we only support H264 for the moment)") + rtspClient.Close() time.Sleep(time.Second * 3) return status } @@ -180,6 +181,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu videoSubStreams, err = rtspSubClient.GetVideoStreams() if err != nil || len(videoSubStreams) == 0 { log.Log.Error("RunAgent: no video sub stream found, might be the wrong codec (we only support H264 for the moment)") + rtspSubClient.Close() time.Sleep(time.Second * 3) return status } diff --git a/machinery/src/packets/stream.go b/machinery/src/packets/stream.go index d3f463bc..0b48acbe 100644 --- a/machinery/src/packets/stream.go +++ b/machinery/src/packets/stream.go @@ -34,6 +34,9 @@ type Stream struct { // For H264, this is the pps. PPS []byte + // For H265, this is the vps. + VPS []byte + // IsBackChannel is true if this stream is a back channel. IsBackChannel bool } diff --git a/machinery/src/webrtc/main.go b/machinery/src/webrtc/main.go index df71b4e6..1e339515 100644 --- a/machinery/src/webrtc/main.go +++ b/machinery/src/webrtc/main.go @@ -310,25 +310,22 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C // Set the indexes for the video & audio streams // Later when we read a packet we need to figure out which track to send it to. - videoIdx := -1 - audioIdx := -1 + hasH264 := false + hasPCM_MULAW := false streams, _ := rtspClient.GetStreams() - for i, stream := range streams { - if stream.Name == "H264" && videoIdx < 0 { - videoIdx = i - } else if (stream.Name == "OPUS" || stream.Name == "PCM_MULAW" || stream.Name == "PCM_ALAW") && audioIdx < 0 { - audioIdx = i + for _, stream := range streams { + if stream.Name == "H264" { + hasH264 = true + } else if stream.Name == "PCM_MULAW" { + hasPCM_MULAW = true } } - if videoIdx == -1 { - log.Log.Error("WriteToTrack: no video codec found.") + if !hasH264 && !hasPCM_MULAW { + log.Log.Error("WriteToTrack: no valid video codec and audio codec found.") } else { if config.Capture.TranscodingWebRTC == "true" { - if videoIdx > -1 { - log.Log.Info("WriteToTrack: successfully using a transcoder.") - } else { - } + // Todo.. } else { log.Log.Info("WriteToTrack: not using a transcoder.") } @@ -393,8 +390,7 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C // TODO.. //} - switch int(pkt.Idx) { - case videoIdx: + if pkt.IsVideo { // Start at the first keyframe if pkt.IsKeyFrame { start = true @@ -410,7 +406,7 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C } } } - case audioIdx: + } else if pkt.IsAudio { // We will send the audio sample := pionMedia.Sample{Data: pkt.Data, Duration: pkt.Time} if err := audioTrack.WriteSample(sample); err != nil && err != io.ErrClosedPipe { From cb3dce5ffd8ff9f77f17a5752ec2f9f428b00cc6 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 2 Dec 2023 13:07:52 +0100 Subject: [PATCH 31/81] closing --- machinery/src/components/Kerberos.go | 1 + 1 file changed, 1 insertion(+) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 8f4f620a..4e772bce 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -117,6 +117,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu err := rtspClient.Connect(context.Background()) if err != nil { log.Log.Error("RunAgent: error connecting to RTSP stream: " + err.Error()) + rtspClient.Close() time.Sleep(time.Second * 3) return status } From ba7f870d4b90c96e15adb816b6fc0a74e7fdc84e Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 2 Dec 2023 15:18:49 +0100 Subject: [PATCH 32/81] wait a bit to close the motion channel, also close audio channel --- machinery/src/components/Kerberos.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 4e772bce..e7af376b 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -357,10 +357,13 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu communication.SubQueue = nil } + time.Sleep(time.Second * 3) + close(communication.HandleMotion) communication.HandleMotion = nil - //close(communication.HandleAudio) - //communication.HandleAudio = 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.") From 976fbb65aa79b1bb0f397ecfaa604f3fb88d9d02 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 2 Dec 2023 15:41:36 +0100 Subject: [PATCH 33/81] Update Kerberos.go --- machinery/src/components/Kerberos.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index e7af376b..12db6311 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -341,10 +341,10 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu time.Sleep(time.Second * 3) return status } - queue.Close() queue = nil communication.Queue = nil + if subStreamEnabled { err = rtspSubClient.Close() if err != nil { From 6e7ade036e1ec2af017bab54549ec329d8ffc443 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 2 Dec 2023 21:30:07 +0100 Subject: [PATCH 34/81] add logging + fix private key pass through + fixed crash on websocket livestreaming --- machinery/src/components/Kerberos.go | 4 ++-- machinery/src/config/main.go | 3 ++- machinery/src/routers/websocket/main.go | 27 ++++++++++++++----------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 12db6311..2e30f978 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -121,6 +121,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu time.Sleep(time.Second * 3) return status } + log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl) // Check if has backchannel, then we set it in the communication struct communication.HasBackChannel = rtspClient.HasBackChannel @@ -150,8 +151,6 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu var queue *packets.Queue var subQueue *packets.Queue - log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl) - // Create a packet queue, which is filled by the HandleStream routing // and consumed by all other routines: motion, livestream, etc. if config.Capture.PreRecording <= 0 { @@ -177,6 +176,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu time.Sleep(time.Second * 3) return status } + log.Log.Info("RunAgent: opened RTSP sub stream: " + rtspUrl) // Get the video streams from the RTSP server. videoSubStreams, err = rtspSubClient.GetVideoStreams() diff --git a/machinery/src/config/main.go b/machinery/src/config/main.go index e8a004a3..f0776029 100644 --- a/machinery/src/config/main.go +++ b/machinery/src/config/main.go @@ -458,7 +458,8 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) { configuration.Config.Encryption.Fingerprint = value break case "AGENT_ENCRYPTION_PRIVATE_KEY": - configuration.Config.Encryption.PrivateKey = value + encryptionPrivateKey := strings.ReplaceAll(value, "\\n", "\n") + configuration.Config.Encryption.PrivateKey = encryptionPrivateKey break case "AGENT_ENCRYPTION_SYMMETRIC_KEY": configuration.Config.Encryption.SymmetricKey = value diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index 9e65bee1..f70d588a 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -13,6 +13,7 @@ import ( "github.com/kerberos-io/agent/machinery/src/computervision" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/packets" ) type Message struct { @@ -127,33 +128,35 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu func ForwardSDStream(ctx context.Context, clientID string, connection *Connection, communication *models.Communication, captureDevice *capture.Capture) { - queue := communication.Queue - cursor := queue.Latest() + var queue *packets.Queue + var cursor *packets.QueueCursor + + // We'll pick the right client and decoder. + rtspClient := captureDevice.RTSPSubClient + if rtspClient != nil { + queue = communication.SubQueue + cursor = queue.Latest() + } else { + rtspClient = captureDevice.RTSPClient + queue = communication.Queue + cursor = queue.Latest() + } logreader: for { var encodedImage string - rtspClient := captureDevice.RTSPClient if queue != nil && cursor != nil && rtspClient != nil { pkt, err := cursor.ReadPacket() if err == nil { if !pkt.IsKeyFrame { continue } - var img image.YCbCr - rtspSubClient := captureDevice.RTSPSubClient - if rtspSubClient != nil { - img, err = (*rtspSubClient).DecodePacket(pkt) - } else { - img, err = (*rtspClient).DecodePacket(pkt) - } - + img, err = (*rtspClient).DecodePacket(pkt) if err == nil { bytes, _ := computervision.ImageToBytes(&img) encodedImage = base64.StdEncoding.EncodeToString(bytes) } - } else { log.Log.Error("ForwardSDStream:" + err.Error()) break logreader From ced9355b78f03a45409044be6f79b1d5776e3278 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 2 Dec 2023 22:28:26 +0100 Subject: [PATCH 35/81] Run Backchannel on a seperate Gortsplib instance --- machinery/src/capture/Gortsplib.go | 104 ++++++++++++++++++--------- machinery/src/capture/RTSPClient.go | 28 +++++--- machinery/src/capture/main.go | 4 +- machinery/src/components/Kerberos.go | 29 +++++--- 4 files changed, 109 insertions(+), 56 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 2eb4e07a..b4c1269e 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -38,8 +38,7 @@ import ( // Implements the RTSPClient interface. type Golibrtsp struct { RTSPClient - Url string - WithBackChannel bool + Url string Client gortsplib.Client @@ -84,7 +83,7 @@ type Golibrtsp struct { func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.Client = gortsplib.Client{ - RequestBackChannels: g.WithBackChannel, + RequestBackChannels: false, } // parse URL @@ -253,37 +252,6 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } } - // Look for audio back channel. - g.HasBackChannel = false - if g.WithBackChannel { - // find the LPCM media and format - audioFormaBackChannel, audioMediBackChannel := FindPCMU(desc, true) - g.AudioG711MediaBackChannel = audioMediBackChannel - g.AudioG711FormaBackChannel = audioFormaBackChannel - if audioMediBackChannel == nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio backchannel not found") - } else { - - g.Streams = append(g.Streams, packets.Stream{ - Name: "PCM_MULAW", - IsVideo: false, - IsAudio: true, - IsBackChannel: true, - }) - - // Set the index for the audio - g.AudioG711IndexBackChannel = int8(len(g.Streams)) - 1 - - // setup a audio media - _, err = g.Client.Setup(desc.BaseURL, audioMediBackChannel, 0, 0) - if err != nil { - // Something went wrong .. Do something - } else { - g.HasBackChannel = true - } - } - } - // Look for audio stream. // find the G711 media and format audioFormaMPEG4, audioMediMPEG4 := FindMPEG4Audio(desc, false) @@ -319,6 +287,63 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { return } +func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { + + g.Client = gortsplib.Client{ + RequestBackChannels: true, + } + + // parse URL + u, err := base.ParseURL(g.Url) + if err != nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + return + } + + // connect to the server + err = g.Client.Start(u.Scheme, u.Host) + if err != nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + } + + // find published medias + desc, _, err := g.Client.Describe(u) + if err != nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + return + } + + // Look for audio back channel. + g.HasBackChannel = false + // find the LPCM media and format + audioFormaBackChannel, audioMediBackChannel := FindPCMU(desc, true) + g.AudioG711MediaBackChannel = audioMediBackChannel + g.AudioG711FormaBackChannel = audioFormaBackChannel + if audioMediBackChannel == nil { + log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio backchannel not found") + } else { + + g.Streams = append(g.Streams, packets.Stream{ + Name: "PCM_MULAW", + IsVideo: false, + IsAudio: true, + IsBackChannel: true, + }) + + // Set the index for the audio + g.AudioG711IndexBackChannel = int8(len(g.Streams)) - 1 + + // setup a audio media + _, err = g.Client.Setup(desc.BaseURL, audioMediBackChannel, 0, 0) + if err != nil { + // Something went wrong .. Do something + } else { + g.HasBackChannel = true + } + } + return +} + // Start the RTSP client, and start reading packets. func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") @@ -621,6 +646,17 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati return } +// Start the RTSP client, and start reading packets. +func (g *Golibrtsp) StartBackChannel(ctx context.Context) (err error) { + log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") + // Play the stream. + _, err = g.Client.Play(nil) + if err != nil { + panic(err) + } + return +} + func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { if g.HasBackChannel { err := g.Client.WritePacketRTP(g.AudioG711MediaBackChannel, pkt.Packet) diff --git a/machinery/src/capture/RTSPClient.go b/machinery/src/capture/RTSPClient.go index f135e975..eb8f444c 100644 --- a/machinery/src/capture/RTSPClient.go +++ b/machinery/src/capture/RTSPClient.go @@ -9,34 +9,46 @@ import ( ) type Capture struct { - RTSPClient *Golibrtsp - RTSPSubClient *Golibrtsp + RTSPClient *Golibrtsp + RTSPSubClient *Golibrtsp + RTSPBackChannelClient *Golibrtsp } -func (c *Capture) SetMainClient(rtspUrl string, withBackChannel bool) *Golibrtsp { +func (c *Capture) SetMainClient(rtspUrl string) *Golibrtsp { c.RTSPClient = &Golibrtsp{ - Url: rtspUrl, - WithBackChannel: withBackChannel, + Url: rtspUrl, } return c.RTSPClient } -func (c *Capture) SetSubClient(rtspUrl string, withBackChannel bool) *Golibrtsp { +func (c *Capture) SetSubClient(rtspUrl string) *Golibrtsp { c.RTSPSubClient = &Golibrtsp{ - Url: rtspUrl, - WithBackChannel: withBackChannel, + Url: rtspUrl, } return c.RTSPSubClient } +func (c *Capture) SetBackChannelClient(rtspUrl string) *Golibrtsp { + c.RTSPBackChannelClient = &Golibrtsp{ + Url: rtspUrl, + } + return c.RTSPBackChannelClient +} + // RTSPClient is a interface that abstracts the RTSP client implementation. type RTSPClient interface { // Connect to the RTSP server. Connect(ctx context.Context) error + // Connect to a backchannel RTSP server. + ConnectBackChannel(ctx context.Context) error + // Start the RTSP client, and start reading packets. Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) error + // Start the RTSP client, and start reading packets. + StartBackChannel(ctx context.Context) (err error) + // Decode a packet into a image. DecodePacket(pkt packets.Packet) (image.YCbCr, error) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 53c63de1..e7d8bec4 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -543,10 +543,8 @@ func VerifyCamera(c *gin.Context) { // Currently only support H264 encoded cameras, this will change. // Establishing the camera connection without backchannel if no substream - withBackChannel := true rtspClient := &Golibrtsp{ - Url: rtspUrl, - WithBackChannel: withBackChannel, + Url: rtspUrl, } err := rtspClient.Connect(ctx) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 2e30f978..d4f7b2b0 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -111,8 +111,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Currently only support H264 encoded cameras, this will change. // Establishing the camera connection without backchannel if no substream rtspUrl := config.Capture.IPCamera.RTSP - withBackChannel := true - rtspClient := captureDevice.SetMainClient(rtspUrl, withBackChannel) + rtspClient := captureDevice.SetMainClient(rtspUrl) err := rtspClient.Connect(context.Background()) if err != nil { @@ -123,9 +122,6 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu } log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl) - // Check if has backchannel, then we set it in the communication struct - communication.HasBackChannel = rtspClient.HasBackChannel - // Get the video streams from the RTSP server. videoStreams, err := rtspClient.GetVideoStreams() if err != nil || len(videoStreams) == 0 { @@ -139,8 +135,6 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu videoStream := videoStreams[0] // Get some information from the video stream. - // num := videoStream.Num - //denum := videoStream.Denum width := videoStream.Width height := videoStream.Height @@ -166,8 +160,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu if subRtspUrl != "" && subRtspUrl != rtspUrl { // For the sub stream we will not enable backchannel. subStreamEnabled = true - withBackChannel := false - rtspSubClient := captureDevice.SetSubClient(subRtspUrl, withBackChannel) + rtspSubClient := captureDevice.SetSubClient(subRtspUrl) captureDevice.RTSPSubClient = rtspSubClient err := rtspSubClient.Connect(context.Background()) @@ -250,6 +243,16 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu queue.WriteHeader(videoStreams) go rtspClient.Start(context.Background(), queue, communication) + // Try to create backchannel + rtspBackChannelClient := captureDevice.SetBackChannelClient(rtspUrl) + err = rtspBackChannelClient.ConnectBackChannel(context.Background()) + if err != nil { + log.Log.Error("RunAgent: error connecting to RTSP backchannel stream: " + err.Error()) + } else { + log.Log.Info("RunAgent: opened RTSP backchannel stream: " + rtspUrl) + go rtspBackChannelClient.StartBackChannel(context.Background()) + } + rtspSubClient := captureDevice.RTSPSubClient if subStreamEnabled && rtspSubClient != nil { subQueue = packets.NewQueue() @@ -298,8 +301,9 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu go onvif.HandleONVIFActions(configuration, communication) communication.HandleAudio = make(chan models.AudioDataPartial, 1) - if rtspClient.HasBackChannel { - go WriteAudioToBackchannel(communication, rtspClient) + if rtspBackChannelClient.HasBackChannel { + communication.HasBackChannel = true + go WriteAudioToBackchannel(communication, rtspBackChannelClient) } // If we reach this point, we have a working RTSP connection. @@ -341,6 +345,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu time.Sleep(time.Second * 3) return status } + queue.Close() queue = nil communication.Queue = nil @@ -357,6 +362,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu communication.SubQueue = nil } + err = rtspBackChannelClient.Close() + time.Sleep(time.Second * 3) close(communication.HandleMotion) From 3f1e01e66588112f9d71b444e3aa0ad4e243cd39 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 3 Dec 2023 08:14:56 +0100 Subject: [PATCH 36/81] dont panic on fail bachchannel --- machinery/src/capture/Gortsplib.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index b4c1269e..f5e8f63b 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -637,6 +637,8 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati }) } + // Wait for a second, so we can be sure the stream is playing. + time.Sleep(1 * time.Second) // Play the stream. _, err = g.Client.Play(nil) if err != nil { @@ -648,11 +650,13 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // Start the RTSP client, and start reading packets. func (g *Golibrtsp) StartBackChannel(ctx context.Context) (err error) { - log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") + log.Log.Info("RTSPClient(Golibrtsp).Start(): started") + // Wait for a second, so we can be sure the stream is playing. + time.Sleep(1 * time.Second) // Play the stream. _, err = g.Client.Play(nil) if err != nil { - panic(err) + log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) } return } From 1145008c6296f2f68c6213939f05a7c4361782d7 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 3 Dec 2023 09:53:20 +0100 Subject: [PATCH 37/81] reference implementation for transcoding from MULAW to AAC --- machinery/src/capture/main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index e7d8bec4..4b9e7ea6 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -129,7 +129,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { log.Log.Error(err.Error()) } - } else { + } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") } @@ -253,7 +253,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { log.Log.Error(err.Error()) } - } else { + } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") } @@ -272,7 +272,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { log.Log.Error(err.Error()) } - } else { + } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") } @@ -434,7 +434,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { log.Log.Error(err.Error()) } - } else { + } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") } From d1dd30577bb47bfbfdc869a0e6c2579afdbadd71 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 3 Dec 2023 19:18:01 +0100 Subject: [PATCH 38/81] get rid of VPS, fails to write in h265 (also upgrade dependencies) --- machinery/go.mod | 2 +- machinery/go.sum | 4 +- machinery/src/capture/Gortsplib.go | 9 +- machinery/src/capture/Joy4.go | 249 ----------------------------- 4 files changed, 9 insertions(+), 255 deletions(-) delete mode 100644 machinery/src/capture/Joy4.go diff --git a/machinery/go.mod b/machinery/go.mod index 3c6fc3e0..d9b32a4b 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -37,7 +37,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/yapingcat/gomedia v0.0.0-20231111085550-145f641a02d1 + github.com/yapingcat/gomedia v0.0.0-20231203152327-9078d4068ce7 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 diff --git a/machinery/go.sum b/machinery/go.sum index 3b38c2c0..ec40119f 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -485,8 +485,8 @@ github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/yapingcat/gomedia v0.0.0-20231111085550-145f641a02d1 h1:o+Dra6HPMfqbIAFinocMGt3fr5R0OGcOIWy7omeujiI= -github.com/yapingcat/gomedia v0.0.0-20231111085550-145f641a02d1/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= +github.com/yapingcat/gomedia v0.0.0-20231203152327-9078d4068ce7 h1:CDxRmG9/kGMMHbKuJezAM7Bp40P7EH2MqBn3qqf0bok= +github.com/yapingcat/gomedia v0.0.0-20231203152327-9078d4068ce7/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index f5e8f63b..e5417f17 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -571,8 +571,8 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati for _, nalu := range au { typ := h265.NALUType((nalu[0] >> 1) & 0b111111) switch typ { - case h265.NALUType_VPS_NUT: - continue + /*case h265.NALUType_VPS_NUT: + continue*/ case h265.NALUType_SPS_NUT: continue case h265.NALUType_PPS_NUT: @@ -593,7 +593,10 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // add VPS, SPS and PPS before random access access unit if isRandomAccess { - au = append([][]byte{g.VideoH265Forma.VPS, g.VideoH265Forma.SPS, g.VideoH265Forma.PPS}, au...) + au = append([][]byte{ + g.VideoH265Forma.VPS, + g.VideoH265Forma.SPS, + g.VideoH265Forma.PPS}, au...) } enc, err := h264.AnnexBMarshal(au) diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go deleted file mode 100644 index 59aef5b7..00000000 --- a/machinery/src/capture/Joy4.go +++ /dev/null @@ -1,249 +0,0 @@ -package capture - -import ( - "context" - "image" - "strconv" - "sync" - "time" - - "github.com/kerberos-io/agent/machinery/src/log" - "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/agent/machinery/src/packets" - "github.com/kerberos-io/joy4/av" - "github.com/kerberos-io/joy4/av/avutil" - "github.com/kerberos-io/joy4/cgo/ffmpeg" - "github.com/kerberos-io/joy4/codec/h264parser" - "github.com/kerberos-io/joy4/format" -) - -// Implements the RTSPClient interface. -type Joy4 struct { - RTSPClient - Url string - WithBackChannel bool - - Demuxer av.DemuxCloser - Streams []packets.Stream - - DecoderMutex *sync.Mutex - Decoder *ffmpeg.VideoDecoder - Frame *ffmpeg.VideoFrame -} - -// Connect to the RTSP server. -func (j *Joy4) Connect(ctx context.Context) (err error) { - - // Register all formats and codecs. - format.RegisterAll() - - // Try with backchannel first (if variable is set to true) - // If set to true, it will try to open the stream with a backchannel - // If fails we will try again (see below). - infile, err := avutil.Open(ctx, j.Url, j.WithBackChannel) - if err == nil { - s, err := infile.Streams() - if err == nil && len(s) > 0 { - j.Decoder = &ffmpeg.VideoDecoder{} - var streams []packets.Stream - for _, str := range s { - stream := packets.Stream{ - Name: str.Type().String(), - IsVideo: str.Type().IsVideo(), - IsAudio: str.Type().IsAudio(), - } - if stream.IsVideo { - num, denum := str.(av.VideoCodecData).Framerate() - stream.Num = num - stream.Denum = denum - width := str.(av.VideoCodecData).Width() - stream.Width = width - height := str.(av.VideoCodecData).Height() - stream.Height = height - - if stream.Name == "H264" { - stream.PPS = str.(h264parser.CodecData).PPS() - stream.SPS = str.(h264parser.CodecData).SPS() - } - - // Specific to Joy4, we need to create a decoder. - codec := str.(av.VideoCodecData) - ffmpeg.NewVideoDecoder(j.Decoder, codec) - err := ffmpeg.NewVideoDecoder(j.Decoder, codec) - if err != nil { - log.Log.Error("RTSPClient(JOY4).Connect(): " + err.Error()) - } - } - streams = append(streams, stream) - } - j.Demuxer = infile - j.Streams = streams - } else { - // Try again without backchannel - log.Log.Info("OpenRTSP: trying without backchannel") - j.WithBackChannel = false - infile, err := avutil.Open(ctx, j.Url, j.WithBackChannel) - if err == nil { - var streams []packets.Stream - for _, str := range s { - stream := packets.Stream{ - Name: str.Type().String(), - IsVideo: str.Type().IsVideo(), - IsAudio: str.Type().IsAudio(), - } - if stream.IsVideo { - num, denum := str.(av.VideoCodecData).Framerate() - stream.Num = num - stream.Denum = denum - width := str.(av.VideoCodecData).Width() - stream.Width = width - height := str.(av.VideoCodecData).Height() - stream.Height = height - - if stream.Name == "H264" { - stream.PPS = str.(h264parser.CodecData).PPS() - stream.SPS = str.(h264parser.CodecData).SPS() - } - - // Specific to Joy4, we need to create a decoder. - codec := str.(av.VideoCodecData) - ffmpeg.NewVideoDecoder(j.Decoder, codec) - err := ffmpeg.NewVideoDecoder(j.Decoder, codec) - if err != nil { - log.Log.Error("RTSPClient(JOY4).Connect(): " + err.Error()) - } - } - streams = append(streams, stream) - } - j.Demuxer = infile - j.Streams = streams - } - } - } - - // Create a single frame used for decoding. - j.Frame = ffmpeg.AllocVideoFrame() - - // Iniatlise the mutex. - j.DecoderMutex = &sync.Mutex{} - - return -} - -// Start the RTSP client, and start reading packets. -func (j *Joy4) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { - log.Log.Debug("RTSPClient(JOY4).Start(): started") - -loop: - for { - // This will check if we need to stop the thread, - // because of a reconfiguration. - select { - case <-communication.HandleStream: - break loop - default: - } - - var avpkt av.Packet - if avpkt, err = j.Demuxer.ReadPacket(); err != nil { // sometimes this throws an end of file.. - log.Log.Error("RTSPClient(JOY4).Start(): " + err.Error()) - time.Sleep(1 * time.Second) - } - - // Could be that a decode is throwing errors. - if len(avpkt.Data) > 0 { - - pkt := packets.Packet{ - IsKeyFrame: avpkt.IsKeyFrame, - Idx: int8(avpkt.Idx), - CompositionTime: avpkt.Time, - Time: avpkt.Time, - Data: avpkt.Data, - } - - pkt.Data = pkt.Data[4:] - if pkt.IsKeyFrame { - stream := j.Streams[avpkt.Idx] - annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } - pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - pkt.Data = append(stream.PPS, pkt.Data...) - pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - pkt.Data = append(stream.SPS, pkt.Data...) - pkt.Data = append(annexbNALUStartCode(), pkt.Data...) - } - - queue.WritePacket(pkt) - - if pkt.IsKeyFrame { - // Increment packets, so we know the device - // is not blocking. - r := communication.PackageCounter.Load().(int64) - log.Log.Info("RTSPClient(JOY4).Start(): packet size " + strconv.Itoa(len(pkt.Data))) - communication.PackageCounter.Store((r + 1) % 1000) - communication.LastPacketTimer.Store(time.Now().Unix()) - } - - // This will check if we need to stop the thread, - // because of a reconfiguration. - select { - case <-communication.HandleStream: - break loop - default: - } - } - } - - queue.Close() - log.Log.Debug("RTSPClient(JOY4).Start(): done") - - return -} - -// Decode a packet to an image. -func (j *Joy4) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { - j.DecoderMutex.Lock() - _, err := j.Decoder.Decode(j.Frame, pkt.Data) - j.DecoderMutex.Unlock() - return j.Frame.Image, err -} - -// Get a list of streams from the RTSP server. -func (j *Joy4) GetStreams() ([]packets.Stream, error) { - var streams []packets.Stream - for _, stream := range j.Streams { - streams = append(streams, stream) - } - return streams, nil -} - -// Get a list of video streams from the RTSP server. -func (j *Joy4) GetVideoStreams() ([]packets.Stream, error) { - var videoStreams []packets.Stream - for _, stream := range j.Streams { - if stream.IsVideo { - videoStreams = append(videoStreams, stream) - } - } - return videoStreams, nil -} - -// Get a list of audio streams from the RTSP server. -func (j *Joy4) GetAudioStreams() ([]packets.Stream, error) { - var audioStreams []packets.Stream - for _, stream := range j.Streams { - if stream.IsAudio { - audioStreams = append(audioStreams, stream) - } - } - return audioStreams, nil -} - -// Close the connection to the RTSP server. -func (j *Joy4) Close() error { - // Cleanup the frame. - j.Frame.Free() - // Close the decoder. - j.Decoder.Close() - // Close the demuxer. - return j.Demuxer.Close() -} From 5765f7c4f64978144823af479906abaae239b614 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 3 Dec 2023 20:10:05 +0100 Subject: [PATCH 39/81] additional checks for closed decoder + properly close recording when closed --- machinery/src/capture/Gortsplib.go | 30 ++++++++++--- machinery/src/capture/main.go | 69 +++++++++++++++--------------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index e5417f17..84bc980a 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -677,30 +677,48 @@ func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { // Decode a packet to an image. func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { + var img image.YCbCr + var err error g.VideoH264DecoderMutex.Lock() - img, err := g.VideoH264FrameDecoder.decode(pkt.Data) + if len(pkt.Data) == 0 { + err = errors.New("Empty frame") + } else if g.VideoH264Decoder != nil { + img, err = g.VideoH264FrameDecoder.decode(pkt.Data) + } else { + err = errors.New("No decoder found, might already be closed") + } g.VideoH264DecoderMutex.Unlock() if err != nil { + log.Log.Error("RTSPClient(Golibrtsp).DecodePacket(): " + err.Error()) return image.YCbCr{}, err } if img.Bounds().Empty() { - log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") - return image.YCbCr{}, errors.New("Empty frame") + log.Log.Debug("RTSPClient(Golibrtsp).DecodePacket(): " + "empty frame") + return image.YCbCr{}, errors.New("Empty image") } return img, nil } // Decode a packet to a Gray image. func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { + var img image.Gray + var err error g.VideoH264DecoderMutex.Lock() - img, err := g.VideoH264FrameDecoder.decodeRaw(pkt.Data) + if len(pkt.Data) == 0 { + err = errors.New("Empty frame") + } else if g.VideoH264Decoder != nil { + img, err = g.VideoH264FrameDecoder.decodeRaw(pkt.Data) + } else { + err = errors.New("No decoder found, might already be closed") + } g.VideoH264DecoderMutex.Unlock() if err != nil { + log.Log.Error("RTSPClient(Golibrtsp).DecodePacketRaw(): " + err.Error()) return image.Gray{}, err } if img.Bounds().Empty() { - log.Log.Debug("RTSPClient(Golibrtsp).Start(): " + "empty frame") - return image.Gray{}, errors.New("Empty frame") + log.Log.Debug("RTSPClient(Golibrtsp).DecodePacketRaw(): " + "empty image") + return image.Gray{}, errors.New("Empty image") } // Do a deep copy of the image diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 4b9e7ea6..ac604c5f 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -61,9 +61,9 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat //streams, _ := rtspClient.GetStreams() if config.Capture.Recording == "false" { - log.Log.Info("HandleRecordStream: disabled, we will not record anything.") + log.Log.Info("capture.HandleRecordStream(): disabled, we will not record anything.") } else { - log.Log.Debug("HandleRecordStream: started") + log.Log.Debug("capture.HandleRecordStream(): started") recordingPeriod := config.Capture.PostRecording // number of seconds to record. maxRecordingPeriod := config.Capture.MaxLengthRecording // maximum number of seconds to record. @@ -82,7 +82,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat var audioTrack uint32 // Do not do anything! - log.Log.Info("HandleRecordStream: Start continuous recording ") + log.Log.Info("capture.HandleRecordStream() - continuous: Start continuous recording ") loc, _ := time.LoadLocation(config.Timezone) now = time.Now().Unix() @@ -131,7 +131,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.HandleRecordStream() - continuous: no AAC audio codec detected, skipping audio track.") } } @@ -140,19 +140,14 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Error(err.Error()) } - log.Log.Info("HandleRecordStream: Recording finished: file save: " + name) + log.Log.Info("capture.HandleRecordStream() - continuous: Recording finished: file save: " + name) // Cleanup muxer start = false - //myMuxer.Close() - //myMuxer = nil _, err = file.Write(cws.buf) if err != nil { - panic(err) + log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) } - - fmt.Println(cws.offset, len(cws.buf)) - file.Close() file = nil @@ -193,7 +188,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat (currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) { } else { - log.Log.Debug("HandleRecordStream: Disabled: no continuous recording at this moment. Not within specified time interval.") + log.Log.Debug("capture.HandleRecordStream() - continuous: Disabled: no continuous recording at this moment. Not within specified time interval.") time.Sleep(5 * time.Second) continue } @@ -223,7 +218,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat fullName = configDirectory + "/data/recordings/" + name // Running... - log.Log.Info("Recording started") + log.Log.Info("capture.HandleRecordStream() - continuous: Recording started") file, err = os.Create(fullName) if err == nil { @@ -240,8 +235,8 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat audioTrack = myMuxer.AddAudioTrack(mp4.MP4_CODEC_AAC) } - log.Log.Info("HandleRecordStream: composing recording") - log.Log.Info("HandleRecordStream: write header") + log.Log.Info("capture.HandleRecordStream() - continuous: composing recording") + log.Log.Info("capture.HandleRecordStream() - continuous: write header") ttime := convertPTS(pkt.Time) if pkt.IsVideo { @@ -255,7 +250,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.HandleRecordStream() - continuous: no AAC audio codec detected, skipping audio track.") } } @@ -274,7 +269,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.HandleRecordStream() - continuous: no AAC audio codec detected, skipping audio track.") } } } @@ -286,15 +281,19 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // If this happens we need to check to properly close the recording. if cursorError != nil { if recordingStatus == "started" { - // This will write the trailer a well. if err := myMuxer.WriteTrailer(); err != nil { log.Log.Error(err.Error()) } - log.Log.Info("HandleRecordStream: Recording finished: file save: " + name) + log.Log.Info("capture.HandleRecordStream() - continuous: Recording finished: file save: " + name) + // Cleanup muxer start = false + _, err = file.Write(cws.buf) + if err != nil { + log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) + } file.Close() file = nil @@ -308,11 +307,14 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat fc.Close() recordingStatus = "idle" + + // Clean up the recording directory if necessary. + CleanupRecordingDirectory(configDirectory, configuration) } } } else { - log.Log.Info("HandleRecordStream: Start motion based recording ") + log.Log.Info("capture.HandleRecordStream() - motiondetection: Start motion based recording ") var file *os.File var lastDuration time.Duration @@ -384,8 +386,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat audioTrack = myMuxer.AddAudioTrack(mp4.MP4_CODEC_AAC) start := false - log.Log.Info("HandleRecordStream: composing recording") - log.Log.Info("HandleRecordStream: write header") + log.Log.Info("capture.HandleRecordStream() - motiondetection: composing recording") // Get as much packets we need. var cursorError error @@ -401,25 +402,25 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat nextPkt, cursorError = recordingCursor.ReadPacket() if cursorError != nil { - log.Log.Error("HandleRecordStream: " + cursorError.Error()) + log.Log.Error("capture.HandleRecordStream() - motiondetection: " + cursorError.Error()) } now := time.Now().Unix() select { case motion := <-communication.HandleMotion: timestamp = now - log.Log.Info("HandleRecordStream: motion detected while recording. Expanding recording.") + log.Log.Info("capture.HandleRecordStream() - motiondetection: motion detected while recording. Expanding recording.") numberOfChanges = motion.NumberOfChanges - log.Log.Info("Received message with recording data, detected changes to save: " + strconv.Itoa(numberOfChanges)) + log.Log.Info("capture.HandleRecordStream() - motiondetection: Received message with recording data, detected changes to save: " + strconv.Itoa(numberOfChanges)) default: } if (timestamp+recordingPeriod-now < 0 || now-startRecording > maxRecordingPeriod) && nextPkt.IsKeyFrame { - log.Log.Info("HandleRecordStream: closing recording (timestamp: " + strconv.FormatInt(timestamp, 10) + ", recordingPeriod: " + strconv.FormatInt(recordingPeriod, 10) + ", now: " + strconv.FormatInt(now, 10) + ", startRecording: " + strconv.FormatInt(startRecording, 10) + ", maxRecordingPeriod: " + strconv.FormatInt(maxRecordingPeriod, 10)) + log.Log.Info("capture.HandleRecordStream() - motiondetection: closing recording (timestamp: " + strconv.FormatInt(timestamp, 10) + ", recordingPeriod: " + strconv.FormatInt(recordingPeriod, 10) + ", now: " + strconv.FormatInt(now, 10) + ", startRecording: " + strconv.FormatInt(startRecording, 10) + ", maxRecordingPeriod: " + strconv.FormatInt(maxRecordingPeriod, 10)) break } if pkt.IsKeyFrame && !start && pkt.Time >= lastDuration { - log.Log.Info("HandleRecordStream: write frames") + log.Log.Info("capture.HandleRecordStream() - motiondetection: write frames") start = true } if start { @@ -436,7 +437,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("HandleRecordStream: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.HandleRecordStream() - motiondetection: no AAC audio codec detected, skipping audio track.") } } @@ -446,7 +447,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat if err != nil { log.Log.Error(err.Error()) } else { - log.Log.Info("HandleRecordStream: Synced file: " + name) + log.Log.Info("capture.HandleRecordStream() - motiondetection: Synced file: " + name) } } } @@ -457,7 +458,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // This will write the trailer a well. myMuxer.WriteTrailer() - log.Log.Info("HandleRecordStream: file save: " + name) + log.Log.Info("capture.HandleRecordStream() - motiondetection: file save: " + name) lastDuration = pkt.Time lastRecordingTime = time.Now().Unix() @@ -488,13 +489,13 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // write back to file err := os.WriteFile(fullName, []byte(encryptedContents), 0644) if err != nil { - log.Log.Error("HandleRecordStream: error writing file: " + err.Error()) + log.Log.Error("capture.HandleRecordStream() - motiondetection: error writing file: " + err.Error()) } } else { - log.Log.Error("HandleRecordStream: error encrypting file: " + err.Error()) + log.Log.Error("capture.HandleRecordStream() - motiondetection: error encrypting file: " + err.Error()) } } else { - log.Log.Error("HandleRecordStream: error reading file: " + err.Error()) + log.Log.Error("capture.HandleRecordStream() - motiondetection: error reading file: " + err.Error()) } } @@ -507,7 +508,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } } - log.Log.Debug("HandleRecordStream: finished") + log.Log.Debug("capture.HandleRecordStream(): finished") } } From 3ab4b5b54b562e031fab626ca544781c609fde93 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 3 Dec 2023 20:12:23 +0100 Subject: [PATCH 40/81] OOPS: missing encryption at some points --- machinery/src/capture/main.go | 44 +++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index ac604c5f..ae2f15f8 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -156,6 +156,27 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat utils.CreateFragmentedMP4(fullName, config.Capture.FragmentedDuration) } + // Check if we need to encrypt the recording. + if config.Encryption != nil && config.Encryption.Enabled == "true" && config.Encryption.Recordings == "true" && config.Encryption.SymmetricKey != "" { + // reopen file into memory 'fullName' + contents, err := os.ReadFile(fullName) + if err == nil { + // encrypt + encryptedContents, err := encryption.AesEncrypt(contents, config.Encryption.SymmetricKey) + if err == nil { + // write back to file + err := os.WriteFile(fullName, []byte(encryptedContents), 0644) + if err != nil { + log.Log.Error("capture.HandleRecordStream() - motiondetection: error writing file: " + err.Error()) + } + } else { + log.Log.Error("capture.HandleRecordStream() - motiondetection: error encrypting file: " + err.Error()) + } + } else { + log.Log.Error("capture.HandleRecordStream() - motiondetection: error reading file: " + err.Error()) + } + } + // Create a symbol link. fc, _ := os.Create(configDirectory + "/data/cloud/" + name) fc.Close() @@ -302,6 +323,27 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat utils.CreateFragmentedMP4(fullName, config.Capture.FragmentedDuration) } + // Check if we need to encrypt the recording. + if config.Encryption != nil && config.Encryption.Enabled == "true" && config.Encryption.Recordings == "true" && config.Encryption.SymmetricKey != "" { + // reopen file into memory 'fullName' + contents, err := os.ReadFile(fullName) + if err == nil { + // encrypt + encryptedContents, err := encryption.AesEncrypt(contents, config.Encryption.SymmetricKey) + if err == nil { + // write back to file + err := os.WriteFile(fullName, []byte(encryptedContents), 0644) + if err != nil { + log.Log.Error("capture.HandleRecordStream() - motiondetection: error writing file: " + err.Error()) + } + } else { + log.Log.Error("capture.HandleRecordStream() - motiondetection: error encrypting file: " + err.Error()) + } + } else { + log.Log.Error("capture.HandleRecordStream() - motiondetection: error reading file: " + err.Error()) + } + } + // Create a symbol link. fc, _ := os.Create(configDirectory + "/data/cloud/" + name) fc.Close() @@ -464,8 +506,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat lastRecordingTime = time.Now().Unix() // Cleanup muxer - //myMuxer.Close() - //myMuxer = nil _, err := file.Write(cws.buf) if err != nil { panic(err) From c82ead31f293993119202db67cb289477f86d09d Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 4 Dec 2023 14:02:41 +0100 Subject: [PATCH 41/81] decode using H265 --- machinery/src/capture/Gortsplib.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 84bc980a..6d0d5f68 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -40,21 +40,20 @@ type Golibrtsp struct { RTSPClient Url string - Client gortsplib.Client + Client gortsplib.Client + VideoDecoderMutex *sync.Mutex VideoH264Index int8 VideoH264Media *description.Media VideoH264Forma *format.H264 VideoH264Decoder *rtph264.Decoder VideoH264FrameDecoder *Decoder - VideoH264DecoderMutex *sync.Mutex VideoH265Index int8 VideoH265Media *description.Media VideoH265Forma *format.H265 VideoH265Decoder *rtph265.Decoder VideoH265FrameDecoder *Decoder - VideoH265DecoderMutex *sync.Mutex AudioLPCMIndex int8 AudioLPCMMedia *description.Media @@ -107,7 +106,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } // Iniatlise the mutex. - g.VideoH264DecoderMutex = &sync.Mutex{} + g.VideoDecoderMutex = &sync.Mutex{} // find the H264 media and format var formaH264 *format.H264 @@ -162,9 +161,6 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } } - // Iniatlise the mutex. - g.VideoH265DecoderMutex = &sync.Mutex{} - // find the H265 media and format var formaH265 *format.H265 mediH265 := desc.FindFormat(&formaH265) @@ -679,15 +675,17 @@ func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { var img image.YCbCr var err error - g.VideoH264DecoderMutex.Lock() + g.VideoDecoderMutex.Lock() if len(pkt.Data) == 0 { err = errors.New("Empty frame") } else if g.VideoH264Decoder != nil { img, err = g.VideoH264FrameDecoder.decode(pkt.Data) + } else if g.VideoH265Decoder != nil { + img, err = g.VideoH265FrameDecoder.decode(pkt.Data) } else { err = errors.New("No decoder found, might already be closed") } - g.VideoH264DecoderMutex.Unlock() + g.VideoDecoderMutex.Unlock() if err != nil { log.Log.Error("RTSPClient(Golibrtsp).DecodePacket(): " + err.Error()) return image.YCbCr{}, err @@ -703,15 +701,17 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { var img image.Gray var err error - g.VideoH264DecoderMutex.Lock() + g.VideoDecoderMutex.Lock() if len(pkt.Data) == 0 { err = errors.New("Empty frame") } else if g.VideoH264Decoder != nil { img, err = g.VideoH264FrameDecoder.decodeRaw(pkt.Data) + } else if g.VideoH265Decoder != nil { + img, err = g.VideoH265FrameDecoder.decodeRaw(pkt.Data) } else { err = errors.New("No decoder found, might already be closed") } - g.VideoH264DecoderMutex.Unlock() + g.VideoDecoderMutex.Unlock() if err != nil { log.Log.Error("RTSPClient(Golibrtsp).DecodePacketRaw(): " + err.Error()) return image.Gray{}, err @@ -763,6 +763,9 @@ func (g *Golibrtsp) Close() error { if g.VideoH264Decoder != nil { g.VideoH264FrameDecoder.Close() } + if g.VideoH265FrameDecoder != nil { + g.VideoH265FrameDecoder.Close() + } return nil } From 0a195a0dfb78924e8ddbe6ccd276d8ef89387fde Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 4 Dec 2023 14:47:53 +0100 Subject: [PATCH 42/81] Update Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 8bb517e9..ce64f810 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ RUN apt-get upgrade -y && apt-get update && apt-get install -y --fix-missing --n RUN mkdir -p /go/src/github.com/kerberos-io/agent COPY machinery /go/src/github.com/kerberos-io/agent/machinery +RUN rm -rf /go/src/github.com/kerberos-io/agent/machinery/.env ################################################################## # Get the latest commit hash, so we know which version we're running From 34a0d8f5c42ce9a11e86476b5a3af048590e7745 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Tue, 5 Dec 2023 08:30:00 +0100 Subject: [PATCH 43/81] force TCP + ignore motion detection if no region is set --- machinery/src/capture/Gortsplib.go | 52 ++++++++++++++++++++++--- machinery/src/capture/RTSPClient.go | 2 +- machinery/src/components/Kerberos.go | 4 +- machinery/src/computervision/main.go | 58 +++++++++++----------------- 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 6d0d5f68..31b34a7e 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -7,10 +7,14 @@ package capture import "C" import ( + "bufio" + "bytes" "context" + "encoding/base64" "errors" "fmt" "image" + "image/jpeg" "reflect" "strconv" "sync" @@ -81,8 +85,10 @@ type Golibrtsp struct { // Connect to the RTSP server. func (g *Golibrtsp) Connect(ctx context.Context) (err error) { + transport := gortsplib.TransportTCP g.Client = gortsplib.Client{ RequestBackChannels: false, + Transport: &transport, } // parse URL @@ -284,11 +290,12 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { } func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { - + // Transport TCP + transport := gortsplib.TransportTCP g.Client = gortsplib.Client{ RequestBackChannels: true, + Transport: &transport, } - // parse URL u, err := base.ParseURL(g.Url) if err != nil { @@ -341,9 +348,11 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { } // Start the RTSP client, and start reading packets. -func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) (err error) { +func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") + config := configuration.Config + // called when a MULAW audio RTP packet arrives if g.AudioG711Media != nil { g.Client.OnPacketRTP(g.AudioG711Media, g.AudioG711Forma, func(rtppkt *rtp.Packet) { @@ -416,6 +425,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati } // called when a video RTP packet arrives for H264 + var filteredAU [][]byte if g.VideoH264Media != nil { g.Client.OnPacketRTP(g.VideoH264Media, g.VideoH264Forma, func(rtppkt *rtp.Packet) { @@ -450,7 +460,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati // We'll need to read out a few things. // prepend an AUD. This is required by some players - filteredAU := [][]byte{ + filteredAU = [][]byte{ {byte(h264.NALUTypeAccessUnitDelimiter), 240}, } @@ -505,6 +515,22 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati queue.WritePacket(pkt) + // Store snapshots (jpg) for hull. + // We'll store the last snapshot, so we can use it for hull on the frontend. + // But we'll also store the last 10 snapshots, so we can use it for the timelapse. + if config.Capture.Snapshots != "false" { + image, err := g.DecodePacket(pkt) + if err == nil { + buffer := new(bytes.Buffer) + w := bufio.NewWriter(buffer) + err := jpeg.Encode(w, &image, &jpeg.Options{Quality: 15}) + if err == nil { + snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) + communication.Image = snapshot + } + } + } + // This will check if we need to stop the thread, // because of a reconfiguration. select { @@ -559,7 +585,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati return } - filteredAU := [][]byte{ + filteredAU = [][]byte{ {byte(h265.NALUType_AUD_NUT) << 1, 1, 0x50}, } @@ -615,6 +641,22 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, communicati queue.WritePacket(pkt) + // Store snapshots (jpg) for hull. + // We'll store the last snapshot, so we can use it for hull on the frontend. + // But we'll also store the last 10 snapshots, so we can use it for the timelapse. + if config.Capture.Snapshots != "false" { + image, err := g.DecodePacket(pkt) + if err == nil { + buffer := new(bytes.Buffer) + w := bufio.NewWriter(buffer) + err := jpeg.Encode(w, &image, &jpeg.Options{Quality: 15}) + if err == nil { + snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) + communication.Image = snapshot + } + } + } + // This will check if we need to stop the thread, // because of a reconfiguration. select { diff --git a/machinery/src/capture/RTSPClient.go b/machinery/src/capture/RTSPClient.go index eb8f444c..1c94f547 100644 --- a/machinery/src/capture/RTSPClient.go +++ b/machinery/src/capture/RTSPClient.go @@ -44,7 +44,7 @@ type RTSPClient interface { ConnectBackChannel(ctx context.Context) error // Start the RTSP client, and start reading packets. - Start(ctx context.Context, queue *packets.Queue, communication *models.Communication) error + Start(ctx context.Context, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) error // Start the RTSP client, and start reading packets. StartBackChannel(ctx context.Context) (err error) diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index d4f7b2b0..bf759ec7 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -241,7 +241,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). queue.WriteHeader(videoStreams) - go rtspClient.Start(context.Background(), queue, communication) + go rtspClient.Start(context.Background(), queue, configuration, communication) // Try to create backchannel rtspBackChannelClient := captureDevice.SetBackChannelClient(rtspUrl) @@ -259,7 +259,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu communication.SubQueue = subQueue subQueue.SetMaxGopCount(1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). subQueue.WriteHeader(videoSubStreams) - go rtspSubClient.Start(context.Background(), subQueue, communication) + go rtspSubClient.Start(context.Background(), subQueue, configuration, communication) } // Handle livestream SD (low resolution over MQTT) diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index 87fb4f65..5ff32d6b 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -3,7 +3,6 @@ package computervision import ( "bufio" "bytes" - "encoding/base64" "image" "image/jpeg" "time" @@ -64,34 +63,33 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf } } - img := imageArray[0] - if img != nil { - - // Calculate mask - var polyObjects []geo.Polygon - - if config.Region != nil { - for _, polygon := range config.Region.Polygon { - coords := polygon.Coordinates - poly := geo.Polygon{} - for _, c := range coords { - x := c.X - y := c.Y - p := geo.NewPoint(x, y) - if !poly.Contains(p) { - poly.Add(p) - } + // Calculate mask + var polyObjects []geo.Polygon + + if config.Region != nil { + for _, polygon := range config.Region.Polygon { + coords := polygon.Coordinates + poly := geo.Polygon{} + for _, c := range coords { + x := c.X + y := c.Y + p := geo.NewPoint(x, y) + if !poly.Contains(p) { + poly.Add(p) } - polyObjects = append(polyObjects, poly) } + polyObjects = append(polyObjects, poly) } + } + img := imageArray[0] + var coordinatesToCheck []int + if img != nil { bounds := img.Bounds() rows := bounds.Dy() cols := bounds.Dx() // Make fixed size array of uinty8 - var coordinatesToCheck []int for y := 0; y < rows; y++ { for x := 0; x < cols; x++ { for _, poly := range polyObjects { @@ -102,6 +100,10 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf } } } + } + + // If no region is set, we'll skip the motion detection + if len(coordinatesToCheck) > 0 { // Start the motion detection i := 0 @@ -120,22 +122,6 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf imageArray[2] = &grayImage } - // Store snapshots (jpg) for hull. - // We'll store the last snapshot, so we can use it for hull on the frontend. - // But we'll also store the last 10 snapshots, so we can use it for the timelapse. - if config.Capture.Snapshots != "false" { - image, err := rtspClient.DecodePacket(pkt) - if err == nil { - buffer := new(bytes.Buffer) - w := bufio.NewWriter(buffer) - err := jpeg.Encode(w, &image, &jpeg.Options{Quality: 15}) - if err == nil { - snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) - communication.Image = snapshot - } - } - } - // Check if within time interval detectMotion := true timeEnabled := config.Time From 8293d29ee8364f493c2ac26cb1b381054476478b Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Tue, 5 Dec 2023 22:07:29 +0100 Subject: [PATCH 44/81] make recording write directly to file + fix memory leaks with http on ONVIF API --- machinery/src/capture/Gortsplib.go | 2 +- machinery/src/capture/main.go | 42 ++++--- machinery/src/cloud/Cloud.go | 22 ++-- machinery/src/onvif/main.go | 170 ++++++++++++++++++----------- 4 files changed, 138 insertions(+), 98 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 31b34a7e..22950933 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -643,7 +643,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // Store snapshots (jpg) for hull. // We'll store the last snapshot, so we can use it for hull on the frontend. - // But we'll also store the last 10 snapshots, so we can use it for the timelapse. + // This will also be used to retrieve the last snapshot from the API. if config.Capture.Snapshots != "false" { image, err := g.DecodePacket(pkt) if err == nil { diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index ae2f15f8..6971c22b 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -73,10 +73,13 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat startRecording := now timestamp := now + // For continuous and motion based recording we will use a single file. + var file *os.File + // Check if continuous recording. if config.Capture.Continuous == "true" { - var cws *cacheWriterSeeker + //var cws *cacheWriterSeeker var myMuxer *mp4.Movmuxer var videoTrack uint32 var audioTrack uint32 @@ -89,7 +92,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat timestamp = now start := false var name string - var file *os.File var err error // If continuous record the full length @@ -144,10 +146,10 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Cleanup muxer start = false - _, err = file.Write(cws.buf) - if err != nil { - log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) - } + //_, err = file.Write(cws.buf) + //if err != nil { + // log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) + //} file.Close() file = nil @@ -243,9 +245,8 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat file, err = os.Create(fullName) if err == nil { - //myMuxer = mp4.NewMuxer(file) - cws = newCacheWriterSeeker(4096) - myMuxer, _ = mp4.CreateMp4Muxer(cws) + //cws = newCacheWriterSeeker(4096) + myMuxer, _ = mp4.CreateMp4Muxer(file) // We choose between H264 and H265 if pkt.Codec == "H264" { videoTrack = myMuxer.AddVideoTrack(mp4.MP4_CODEC_H264) @@ -311,10 +312,10 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Cleanup muxer start = false - _, err = file.Write(cws.buf) - if err != nil { - log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) - } + //_, err = file.Write(cws.buf) + //if err != nil { + // log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) + //} file.Close() file = nil @@ -358,11 +359,10 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Info("capture.HandleRecordStream() - motiondetection: Start motion based recording ") - var file *os.File var lastDuration time.Duration var lastRecordingTime int64 - var cws *cacheWriterSeeker + //var cws *cacheWriterSeeker var myMuxer *mp4.Movmuxer var videoTrack uint32 var audioTrack uint32 @@ -411,9 +411,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Running... log.Log.Info("HandleRecordStream: Recording started") file, _ = os.Create(fullName) - - cws = newCacheWriterSeeker(4096) - myMuxer, _ = mp4.CreateMp4Muxer(cws) + myMuxer, _ = mp4.CreateMp4Muxer(file) // Check which video codec we need to use. videoSteams, _ := rtspClient.GetVideoStreams() @@ -506,10 +504,10 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat lastRecordingTime = time.Now().Unix() // Cleanup muxer - _, err := file.Write(cws.buf) - if err != nil { - panic(err) - } + //_, err := file.Write(cws.buf) + //if err != nil { + // panic(err) + //} file.Close() file = nil diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 46b93bd9..c5fb04f1 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -220,6 +220,16 @@ func GetSystemInfo() (models.System, error) { func HandleHeartBeat(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time) { log.Log.Debug("HandleHeartBeat: started") + 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{} + } + loop: for { @@ -323,7 +333,7 @@ loop: } } else { if err != nil { - log.Log.Error("HandleHeartBeat: error while getting presets: " + err.Error()) + log.Log.Debug("HandleHeartBeat: error while getting presets: " + err.Error()) } else { log.Log.Debug("HandleHeartBeat: no presets found.") } @@ -342,16 +352,6 @@ loop: onvifPresetsList = []byte("[]") } - 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{} - } - // We need a hub URI and hub public key before we will send a heartbeat if hubURI != "" && key != "" { diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index fab04175..9e2f994b 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -6,7 +6,6 @@ import ( "encoding/xml" "errors" "io" - "io/ioutil" "strings" "time" @@ -219,8 +218,11 @@ func GetPTZConfigurationsFromDevice(device *onvif.Device) (ptz.GetConfigurations // Get the PTZ configurations from the device resp, err := device.CallMethod(ptz.GetConfigurations{}) + if resp != nil { + resp.Body.Close() + } + if err == nil { - defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err == nil { stringBody := string(b) @@ -277,20 +279,22 @@ func GetPosition(device *onvif.Device, token xsd.ReferenceToken) (xsd.PTZVector, ProfileToken: token, }) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } + if err == nil { - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - if err == nil { - stringBody := string(b) - decodedXML, et, err := getXMLNode(stringBody, "GetStatusResponse") - if err != nil { + stringBody := string(b) + decodedXML, et, err := getXMLNode(stringBody, "GetStatusResponse") + if err != nil { + log.Log.Error("GetPositionFromDevice: " + err.Error()) + return position, err + } else { + if err := decodedXML.DecodeElement(&status, et); err != nil { log.Log.Error("GetPositionFromDevice: " + err.Error()) return position, err - } else { - if err := decodedXML.DecodeElement(&status, et); err != nil { - log.Log.Error("GetPositionFromDevice: " + err.Error()) - return position, err - } } } } @@ -311,7 +315,7 @@ func AbsolutePanTiltMove(device *onvif.Device, configuration ptz.GetConfiguratio Space: configuration.PTZConfiguration.DefaultAbsoluteZoomPositionSpace, } - res, err := device.CallMethod(ptz.AbsoluteMove{ + resp, err := device.CallMethod(ptz.AbsoluteMove{ ProfileToken: token, Position: xsd.PTZVector{ PanTilt: absolutePantiltVector, @@ -319,12 +323,15 @@ func AbsolutePanTiltMove(device *onvif.Device, configuration ptz.GetConfiguratio }, }) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } if err != nil { log.Log.Error("AbsoluteMove: " + err.Error()) } - - bs, _ := ioutil.ReadAll(res.Body) - log.Log.Info("AbsoluteMove: " + string(bs)) + log.Log.Info("AbsoluteMove: " + string(b)) return err } @@ -373,6 +380,10 @@ func ZoomOutCompletely(device *onvif.Device, configuration ptz.GetConfigurations Zoom: zoomOut, }, }) + if err != nil { + log.Log.Error("ZoomOutCompletely: " + err.Error()) + } + for { position, _ := GetPosition(device, token) if position.Zoom.X == 0 { @@ -381,10 +392,13 @@ func ZoomOutCompletely(device *onvif.Device, configuration ptz.GetConfigurations time.Sleep(250 * time.Millisecond) } - device.CallMethod(ptz.Stop{ + _, err = device.CallMethod(ptz.Stop{ ProfileToken: token, Zoom: true, }) + if err != nil { + log.Log.Error("ZoomOutCompletely: " + err.Error()) + } return err } @@ -407,19 +421,22 @@ func PanUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsR Y: 0, Space: configuration.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace, } - res, err := device.CallMethod(ptz.ContinuousMove{ + resp, err := device.CallMethod(ptz.ContinuousMove{ ProfileToken: token, Velocity: xsd.PTZSpeedPanTilt{ PanTilt: panTiltVector, }, }) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } if err != nil { log.Log.Error("ContinuousPanTiltMove (Pan): " + err.Error()) } - - bs, _ := ioutil.ReadAll(res.Body) - log.Log.Debug("ContinuousPanTiltMove (Pan): " + string(bs)) + log.Log.Debug("ContinuousPanTiltMove (Pan): " + string(b)) // While moving we'll check if we reached the desired position. // or if we overshot the desired position. @@ -437,14 +454,14 @@ func PanUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsR time.Sleep(wait) } - _, errStop := device.CallMethod(ptz.Stop{ + _, err = device.CallMethod(ptz.Stop{ ProfileToken: token, PanTilt: true, Zoom: true, }) - if errStop != nil { - log.Log.Error("ContinuousPanTiltMove (Pan): " + errStop.Error()) + if err != nil { + log.Log.Error("ContinuousPanTiltMove (Pan): " + err.Error()) } } return err @@ -469,19 +486,23 @@ func TiltUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations Y: directionY, Space: configuration.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace, } - res, err := device.CallMethod(ptz.ContinuousMove{ + resp, err := device.CallMethod(ptz.ContinuousMove{ ProfileToken: token, Velocity: xsd.PTZSpeedPanTilt{ PanTilt: panTiltVector, }, }) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } + if err != nil { log.Log.Error("ContinuousPanTiltMove (Tilt): " + err.Error()) } - - bs, _ := ioutil.ReadAll(res.Body) - log.Log.Debug("ContinuousPanTiltMove (Tilt) " + string(bs)) + log.Log.Debug("ContinuousPanTiltMove (Tilt) " + string(b)) // While moving we'll check if we reached the desired position. // or if we overshot the desired position. @@ -499,14 +520,14 @@ func TiltUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations time.Sleep(wait) } - _, errStop := device.CallMethod(ptz.Stop{ + _, err = device.CallMethod(ptz.Stop{ ProfileToken: token, PanTilt: true, Zoom: true, }) - if errStop != nil { - log.Log.Error("ContinuousPanTiltMove (Tilt): " + errStop.Error()) + if err != nil { + log.Log.Error("ContinuousPanTiltMove (Tilt): " + err.Error()) } } return err @@ -530,19 +551,23 @@ func ZoomUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations X: directionZ, Space: configuration.PTZConfiguration.DefaultContinuousZoomVelocitySpace, } - res, err := device.CallMethod(ptz.ContinuousMove{ + resp, err := device.CallMethod(ptz.ContinuousMove{ ProfileToken: token, Velocity: xsd.PTZSpeedZoom{ Zoom: zoomVector, }, }) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } if err != nil { log.Log.Error("ContinuousPanTiltMove (Zoom): " + err.Error()) } - bs, _ := ioutil.ReadAll(res.Body) - log.Log.Debug("ContinuousPanTiltMove (Zoom) " + string(bs)) + log.Log.Debug("ContinuousPanTiltMove (Zoom) " + string(b)) // While moving we'll check if we reached the desired position. // or if we overshot the desired position. @@ -560,14 +585,14 @@ func ZoomUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations time.Sleep(wait) } - _, errStop := device.CallMethod(ptz.Stop{ + _, err = device.CallMethod(ptz.Stop{ ProfileToken: token, PanTilt: true, Zoom: true, }) - if errStop != nil { - log.Log.Error("ContinuousPanTiltMove (Zoom): " + errStop.Error()) + if err != nil { + log.Log.Error("ContinuousPanTiltMove (Zoom): " + err.Error()) } } return err @@ -581,36 +606,42 @@ func ContinuousPanTilt(device *onvif.Device, configuration ptz.GetConfigurations Space: configuration.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace, } - res, err := device.CallMethod(ptz.ContinuousMove{ + resp, err := device.CallMethod(ptz.ContinuousMove{ ProfileToken: token, Velocity: xsd.PTZSpeedPanTilt{ PanTilt: panTiltVector, }, }) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } if err != nil { log.Log.Error("ContinuousPanTiltMove: " + err.Error()) } - bs, _ := ioutil.ReadAll(res.Body) - log.Log.Debug("ContinuousPanTiltMove: " + string(bs)) + log.Log.Debug("ContinuousPanTiltMove: " + string(b)) time.Sleep(200 * time.Millisecond) - res, errStop := device.CallMethod(ptz.Stop{ + resp, err = device.CallMethod(ptz.Stop{ ProfileToken: token, PanTilt: true, }) - if errStop != nil { - log.Log.Error("ContinuousPanTiltMove: " + errStop.Error()) + b = []byte{} + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() } - if errStop == nil { - return err - } else { - return errStop + if err != nil { + log.Log.Error("ContinuousPanTiltMove: " + err.Error()) } + + return err } func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, zoom float64) error { @@ -620,36 +651,41 @@ func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsRes Space: configuration.PTZConfiguration.DefaultContinuousZoomVelocitySpace, } - res, err := device.CallMethod(ptz.ContinuousMove{ + resp, err := device.CallMethod(ptz.ContinuousMove{ ProfileToken: token, Velocity: xsd.PTZSpeedZoom{ Zoom: zoomVector, }, }) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } if err != nil { log.Log.Error("ContinuousPanTiltZoom: " + err.Error()) } - bs, _ := ioutil.ReadAll(res.Body) - log.Log.Debug("ContinuousPanTiltZoom: " + string(bs)) + log.Log.Debug("ContinuousPanTiltZoom: " + string(b)) time.Sleep(500 * time.Millisecond) - res, errStop := device.CallMethod(ptz.Stop{ + resp, err = device.CallMethod(ptz.Stop{ ProfileToken: token, Zoom: true, }) - if errStop != nil { - log.Log.Error("ContinuousPanTiltZoom: " + errStop.Error()) + b = []byte{} + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() } - - if errStop == nil { - return err - } else { - return errStop + if err != nil { + log.Log.Error("ContinuousPanTiltZoom: " + err.Error()) } + + return err } func GetCapabilitiesFromDevice(device *onvif.Device) []string { @@ -679,8 +715,12 @@ func GetPresetsFromDevice(device *onvif.Device) ([]models.OnvifActionPreset, err ProfileToken: token, }) - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } + if err == nil { stringBody := string(b) decodedXML, et, err := getXMLNode(stringBody, "GetPresetsResponse") @@ -725,9 +765,11 @@ func GoToPresetFromDevice(device *onvif.Device, presetName string) error { ProfileToken: token, PresetToken: xsd.ReferenceToken(presetName), }) - - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } if err == nil { stringBody := string(b) decodedXML, et, err := getXMLNode(stringBody, "GotoPresetResponses") From c8c9f6dff1ec0c1f4add063bb1c280e67833ddf8 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Tue, 5 Dec 2023 23:05:59 +0100 Subject: [PATCH 45/81] implement better logging, making logging levels configurable (WIP) --- machinery/src/onvif/main.go | 57 ++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 9e2f994b..bced5b5d 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -6,6 +6,7 @@ import ( "encoding/xml" "errors" "io" + "strconv" "strings" "time" @@ -20,7 +21,7 @@ import ( ) func HandleONVIFActions(configuration *models.Configuration, communication *models.Communication) { - log.Log.Debug("HandleONVIFActions: started") + log.Log.Debug("onvif.HandleONVIFActions(): started") for onvifAction := range communication.HandleONVIF { @@ -55,7 +56,7 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode functions, _, _ := GetPTZFunctionsFromDevice(configurations) // Log functions - log.Log.Info("HandleONVIFActions: functions: " + strings.Join(functions, ", ")) + log.Log.Debug("onvif.HandleONVIFActions(): functions: " + strings.Join(functions, ", ")) // Check if we need to use absolute or continuous move /*canAbsoluteMove := false @@ -76,9 +77,9 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode // on the ContinuousPanTiltMove function which is more compatible with more cameras. err = AbsolutePanTiltMoveFake(device, configurations, token, x, y, z) if err != nil { - log.Log.Error("HandleONVIFActions (AbsolutePanTitleMoveFake): " + err.Error()) + log.Log.Debug("onvif.HandleONVIFActions() - AbsolutePanTitleMoveFake: " + err.Error()) } else { - log.Log.Info("HandleONVIFActions (AbsolutePanTitleMoveFake): successfully moved camera") + log.Log.Info("onvif.HandleONVIFActions() - AbsolutePanTitleMoveFake: successfully moved camera.") } /*if canAbsoluteMove { @@ -99,9 +100,9 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode preset := ptzAction.Preset err := GoToPresetFromDevice(device, preset) if err != nil { - log.Log.Error("HandleONVIFActions (GotoPreset): " + err.Error()) + log.Log.Debug("onvif.HandleONVIFActions() - GotoPreset: " + err.Error()) } else { - log.Log.Info("HandleONVIFActions (GotoPreset): successfully moved camera") + log.Log.Info("onvif.HandleONVIFActions() - GotoPreset: successfully moved camera") } } else if onvifAction.Action == "ptz" { @@ -113,7 +114,9 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode // We will move the camera to zero position. err := AbsolutePanTiltMove(device, configurations, token, 0, 0, 0) if err != nil { - log.Log.Error("HandleONVIFActions (AbsolutePanTitleMove): " + err.Error()) + log.Log.Debug("onvif.HandleONVIFActions() - AbsolutePanTitleMove: " + err.Error()) + } else { + log.Log.Info("onvif.HandleONVIFActions() - AbsolutePanTitleMove: successfully centered camera") } } else { @@ -140,7 +143,9 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode err := ContinuousPanTilt(device, configurations, token, x, y) if err != nil { - log.Log.Error("HandleONVIFActions (ContinuousPanTilt): " + err.Error()) + log.Log.Debug("onvif.HandleONVIFActions() - ContinuousPanTilt: " + err.Error()) + } else { + log.Log.Info("onvif.HandleONVIFActions() - ContinuousPanTilt: successfully pan tilted camera") } } } @@ -150,7 +155,9 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode zoom := ptzAction.Zoom err := ContinuousZoom(device, configurations, token, zoom) if err != nil { - log.Log.Error("HandleONVIFActions (ContinuousZoom): " + err.Error()) + log.Log.Debug("onvif.HandleONVIFActions() - ContinuousZoom: " + err.Error()) + } else { + log.Log.Info("onvif.HandleONVIFActions() - ContinuousZoom: successfully zoomed camera") } } } @@ -158,23 +165,22 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode } } } - log.Log.Debug("HandleONVIFActions: finished") + log.Log.Debug("onvif.HandleONVIFActions(): finished") } func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, error) { - log.Log.Debug("ConnectToOnvifDevice: started") - + log.Log.Debug("onvif.ConnectToOnvifDevice(): started") device, err := onvif.NewDevice(onvif.DeviceParams{ Xaddr: cameraConfiguration.ONVIFXAddr, Username: cameraConfiguration.ONVIFUsername, Password: cameraConfiguration.ONVIFPassword, }) - if err != nil { - log.Log.Error("ConnectToOnvifDevice: " + err.Error()) + log.Log.Debug("onvif.ConnectToOnvifDevice(): " + err.Error()) + } else { + log.Log.Info("onvif.ConnectToOnvifDevice(): successfully connected to device") } - - log.Log.Debug("ConnectToOnvifDevice: finished") + log.Log.Debug("onvif.ConnectToOnvifDevice(): finished") return device, err } @@ -191,13 +197,13 @@ func GetTokenFromProfile(device *onvif.Device, profileId int) (xsd.ReferenceToke stringBody := string(b) decodedXML, et, err := getXMLNode(stringBody, "GetProfilesResponse") if err != nil { - log.Log.Error("GetTokenFromProfile: " + err.Error()) + log.Log.Debug("onvif.GetTokenFromProfile(): " + err.Error()) return profileToken, err } else { // Decode the profiles from the server var mProfilesResp media.GetProfilesResponse if err := decodedXML.DecodeElement(&mProfilesResp, et); err != nil { - log.Log.Error("GetTokenFromProfile: " + err.Error()) + log.Log.Debug("onvif.GetTokenFromProfile(): " + err.Error()) } // We'll try to get the token from a preferred profile @@ -228,11 +234,11 @@ func GetPTZConfigurationsFromDevice(device *onvif.Device) (ptz.GetConfigurations stringBody := string(b) decodedXML, et, err := getXMLNode(stringBody, "GetConfigurationsResponse") if err != nil { - log.Log.Error("GetPTZConfigurationsFromDevice: " + err.Error()) + log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error()) return configurations, err } else { if err := decodedXML.DecodeElement(&configurations, et); err != nil { - log.Log.Error("GetPTZConfigurationsFromDevice: " + err.Error()) + log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error()) return configurations, err } } @@ -254,17 +260,22 @@ func GetPositionFromDevice(configuration models.Configuration) (xsd.PTZVector, e // Get the PTZ configurations from the device position, err := GetPosition(device, token) if err == nil { + // float to string + x := strconv.FormatFloat(position.PanTilt.X, 'f', 6, 64) + y := strconv.FormatFloat(position.PanTilt.Y, 'f', 6, 64) + z := strconv.FormatFloat(position.Zoom.X, 'f', 6, 64) + log.Log.Info("onvif.GetPositionFromDevice(): successfully got position (" + x + ", " + y + ", " + z + ")") return position, err } else { - log.Log.Error("GetPositionFromDevice: " + err.Error()) + log.Log.Debug("onvif.GetPositionFromDevice(): " + err.Error()) return position, err } } else { - log.Log.Error("GetPositionFromDevice: " + err.Error()) + log.Log.Debug("onvif.GetPositionFromDevice(): " + err.Error()) return position, err } } else { - log.Log.Error("GetPositionFromDevice: " + err.Error()) + log.Log.Debug("onvif.GetPositionFromDevice(): " + err.Error()) return position, err } } From 9b796c049d64530980627dfad487469ee0ac3787 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 6 Dec 2023 18:53:55 +0100 Subject: [PATCH 46/81] mem leak for http close (still one) + not closing some channels properly --- machinery/src/capture/Gortsplib.go | 18 +++++++++--------- machinery/src/cloud/Cloud.go | 11 +---------- machinery/src/components/Kerberos.go | 11 ++++++++++- machinery/src/onvif/main.go | 3 ++- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 22950933..e9e21e95 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -691,13 +691,13 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // Start the RTSP client, and start reading packets. func (g *Golibrtsp) StartBackChannel(ctx context.Context) (err error) { - log.Log.Info("RTSPClient(Golibrtsp).Start(): started") + log.Log.Info("RTSPClient(Golibrtsp).StartBackChannel(): started") // Wait for a second, so we can be sure the stream is playing. time.Sleep(1 * time.Second) // Play the stream. _, err = g.Client.Play(nil) if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + log.Log.Error("RTSPClient(Golibrtsp).StartBackChannel(): " + err.Error()) } return } @@ -706,7 +706,7 @@ func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { if g.HasBackChannel { err := g.Client.WritePacketRTP(g.AudioG711MediaBackChannel, pkt.Packet) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).WritePacketBackChannel(): " + err.Error()) + log.Log.Debug("RTSPClient(Golibrtsp).WritePacket(): " + err.Error()) return err } } @@ -719,13 +719,13 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { var err error g.VideoDecoderMutex.Lock() if len(pkt.Data) == 0 { - err = errors.New("Empty frame") + err = errors.New("TSPClient(Golibrtsp).DecodePacket(): empty frame") } else if g.VideoH264Decoder != nil { img, err = g.VideoH264FrameDecoder.decode(pkt.Data) } else if g.VideoH265Decoder != nil { img, err = g.VideoH265FrameDecoder.decode(pkt.Data) } else { - err = errors.New("No decoder found, might already be closed") + err = errors.New("TSPClient(Golibrtsp).DecodePacket(): no decoder found, might already be closed") } g.VideoDecoderMutex.Unlock() if err != nil { @@ -733,7 +733,7 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { return image.YCbCr{}, err } if img.Bounds().Empty() { - log.Log.Debug("RTSPClient(Golibrtsp).DecodePacket(): " + "empty frame") + log.Log.Debug("RTSPClient(Golibrtsp).DecodePacket(): empty frame") return image.YCbCr{}, errors.New("Empty image") } return img, nil @@ -745,13 +745,13 @@ func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { var err error g.VideoDecoderMutex.Lock() if len(pkt.Data) == 0 { - err = errors.New("Empty frame") + err = errors.New("RTSPClient(Golibrtsp).DecodePacketRaw(): empty frame") } else if g.VideoH264Decoder != nil { img, err = g.VideoH264FrameDecoder.decodeRaw(pkt.Data) } else if g.VideoH265Decoder != nil { img, err = g.VideoH265FrameDecoder.decodeRaw(pkt.Data) } else { - err = errors.New("No decoder found, might already be closed") + err = errors.New("RTSPClient(Golibrtsp).DecodePacketRaw(): no decoder found, might already be closed") } g.VideoDecoderMutex.Unlock() if err != nil { @@ -759,7 +759,7 @@ func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { return image.Gray{}, err } if img.Bounds().Empty() { - log.Log.Debug("RTSPClient(Golibrtsp).DecodePacketRaw(): " + "empty image") + log.Log.Debug("RTSPClient(Golibrtsp).DecodePacketRaw(): empty image") return image.Gray{}, errors.New("Empty image") } diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index c5fb04f1..2a49bfec 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -581,21 +581,12 @@ func HandleLiveStreamHD(livestreamCursor *packets.QueueCursor, configuration *mo go webrtc.WriteToTrack(livestreamCursor, configuration, communication, mqttClient, videoTrack, audioTrack, rtspClient) if config.Capture.ForwardWebRTC == "true" { - // We get a request with an offer, but we'll forward it. - /*for m := range communication.HandleLiveHDHandshake { - // Forward SDP - m.CloudKey = config.Key - request, err := json.Marshal(m) - if err == nil { - mqttClient.Publish("kerberos/webrtc/request", 2, false, request) - } - }*/ + } else { log.Log.Info("HandleLiveStreamHD: Waiting for peer connections.") for handshake := range communication.HandleLiveHDHandshake { log.Log.Info("HandleLiveStreamHD: setting up a peer connection.") go webrtc.InitializeWebRTCConnection(configuration, communication, mqttClient, videoTrack, audioTrack, handshake) - } } diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index bf759ec7..927160e6 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -52,7 +52,6 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm communication.HandleLiveSD = make(chan int64, 1) communication.HandleLiveHDKeepalive = make(chan string, 1) communication.HandleLiveHDPeers = make(chan string, 1) - communication.HandleONVIF = make(chan models.OnvifAction, 1) communication.IsConfiguring = abool.New() cameraSettings := &models.Camera{} @@ -298,6 +297,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu go cloud.HandleUpload(configDirectory, configuration, communication) // Handle ONVIF actions + communication.HandleONVIF = make(chan models.OnvifAction, 1) go onvif.HandleONVIFActions(configuration, communication) communication.HandleAudio = make(chan models.AudioDataPartial, 1) @@ -363,15 +363,24 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu } err = rtspBackChannelClient.Close() + if err != nil { + log.Log.Error("RunAgent: error closing RTSP backchannel stream: " + err.Error()) + } time.Sleep(time.Second * 3) + close(communication.HandleLiveHDHandshake) + communication.HandleLiveHDHandshake = nil + close(communication.HandleMotion) communication.HandleMotion = nil close(communication.HandleAudio) communication.HandleAudio = nil + close(communication.HandleONVIF) + communication.HandleONVIF = 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.") time.Sleep(time.Second * 3) diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index bced5b5d..22af0f9e 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -224,12 +224,13 @@ func GetPTZConfigurationsFromDevice(device *onvif.Device) (ptz.GetConfigurations // Get the PTZ configurations from the device resp, err := device.CallMethod(ptz.GetConfigurations{}) + var b []byte if resp != nil { + b, err = io.ReadAll(resp.Body) resp.Body.Close() } if err == nil { - b, err := io.ReadAll(resp.Body) if err == nil { stringBody := string(b) decodedXML, et, err := getXMLNode(stringBody, "GetConfigurationsResponse") From 60d7b4b35647a2586489e7bce6e788b8f3c625c7 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 6 Dec 2023 19:03:36 +0100 Subject: [PATCH 47/81] if we have no backchannel we'll skip the setup --- machinery/src/capture/Gortsplib.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index e9e21e95..0d59cc37 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -299,20 +299,20 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { // parse URL u, err := base.ParseURL(g.Url) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + err.Error()) return } // connect to the server err = g.Client.Start(u.Scheme, u.Host) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + err.Error()) } // find published medias desc, _, err := g.Client.Describe(u) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + err.Error()) return } @@ -323,9 +323,9 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { g.AudioG711MediaBackChannel = audioMediBackChannel g.AudioG711FormaBackChannel = audioFormaBackChannel if audioMediBackChannel == nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio backchannel not found") + log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + "audio backchannel not found") + err = errors.New("no audio backchannel found") } else { - g.Streams = append(g.Streams, packets.Stream{ Name: "PCM_MULAW", IsVideo: false, From 8cfcfe464339aed98bcd5ed40cdd90426d03e5e7 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 7 Dec 2023 19:33:18 +0100 Subject: [PATCH 48/81] upgrade onvif --- machinery/go.mod | 3 ++- machinery/go.sum | 15 +++++++++++---- machinery/src/capture/Gortsplib.go | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/machinery/go.mod b/machinery/go.mod index d9b32a4b..229b83cc 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -26,7 +26,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/kellydunn/golang-geo v0.7.0 github.com/kerberos-io/joy4 v1.0.64 - github.com/kerberos-io/onvif v0.0.7 + github.com/kerberos-io/onvif v0.0.9 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 @@ -86,6 +86,7 @@ require ( github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/juju/errors v0.0.0-20220331221717-b38fca44723b // indirect github.com/klauspost/compress v1.15.0 // indirect github.com/klauspost/cpuid v1.2.3 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect diff --git a/machinery/go.sum b/machinery/go.sum index ec40119f..24e4cc68 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -90,6 +90,7 @@ github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5P github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -137,7 +138,7 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2 h1:dyuNlYlG1faymw39NdJddnzJICy6587tiGSVioWhYoE= github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= -github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= @@ -164,7 +165,7 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= @@ -176,6 +177,7 @@ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= @@ -271,12 +273,14 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY= +github.com/juju/errors v0.0.0-20220331221717-b38fca44723b/go.mod h1:jMGj9DWF/qbo91ODcfJq6z/RYc3FX3taCBZMCcpI4Ls= 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.64 h1:gTUSotHSOhp9mNqEecgq88tQHvpj7TjmrvPUsPm0idg= github.com/kerberos-io/joy4 v1.0.64/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/kerberos-io/onvif v0.0.9 h1:B2PGuxlQfv7mgDr47pRbdqREYD68w0oPymEb1i2RI4Y= +github.com/kerberos-io/onvif v0.0.9/go.mod h1:mxGnUYvDyaFN8VPIf6wWnU9Af8RmuxfseAQo2bh4vo0= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= @@ -416,6 +420,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/secure-systems-lab/go-securesystemslib v0.3.1/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U= @@ -525,6 +531,7 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 0d59cc37..c3af4218 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -703,7 +703,7 @@ func (g *Golibrtsp) StartBackChannel(ctx context.Context) (err error) { } func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { - if g.HasBackChannel { + if g.HasBackChannel && g.AudioG711MediaBackChannel != nil { err := g.Client.WritePacketRTP(g.AudioG711MediaBackChannel, pkt.Packet) if err != nil { log.Log.Debug("RTSPClient(Golibrtsp).WritePacket(): " + err.Error()) From fd01fc640ef433e9638ef5df8eedb895041f145f Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 7 Dec 2023 21:33:32 +0100 Subject: [PATCH 49/81] get rid of snapshots + was blocking stream and corrupted recordings --- machinery/src/capture/Gortsplib.go | 38 ----------------- ui/src/components/ImageCanvas/ImageCanvas.jsx | 20 ++++++++- ui/src/pages/Settings/Settings.jsx | 42 +++++++++++++++++-- 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index c3af4218..d1e25d0e 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -7,14 +7,10 @@ package capture import "C" import ( - "bufio" - "bytes" "context" - "encoding/base64" "errors" "fmt" "image" - "image/jpeg" "reflect" "strconv" "sync" @@ -351,8 +347,6 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) (err error) { log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") - config := configuration.Config - // called when a MULAW audio RTP packet arrives if g.AudioG711Media != nil { g.Client.OnPacketRTP(g.AudioG711Media, g.AudioG711Forma, func(rtppkt *rtp.Packet) { @@ -515,22 +509,6 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati queue.WritePacket(pkt) - // Store snapshots (jpg) for hull. - // We'll store the last snapshot, so we can use it for hull on the frontend. - // But we'll also store the last 10 snapshots, so we can use it for the timelapse. - if config.Capture.Snapshots != "false" { - image, err := g.DecodePacket(pkt) - if err == nil { - buffer := new(bytes.Buffer) - w := bufio.NewWriter(buffer) - err := jpeg.Encode(w, &image, &jpeg.Options{Quality: 15}) - if err == nil { - snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) - communication.Image = snapshot - } - } - } - // This will check if we need to stop the thread, // because of a reconfiguration. select { @@ -641,22 +619,6 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati queue.WritePacket(pkt) - // Store snapshots (jpg) for hull. - // We'll store the last snapshot, so we can use it for hull on the frontend. - // This will also be used to retrieve the last snapshot from the API. - if config.Capture.Snapshots != "false" { - image, err := g.DecodePacket(pkt) - if err == nil { - buffer := new(bytes.Buffer) - w := bufio.NewWriter(buffer) - err := jpeg.Encode(w, &image, &jpeg.Options{Quality: 15}) - if err == nil { - snapshot := base64.StdEncoding.EncodeToString(buffer.Bytes()) - communication.Image = snapshot - } - } - } - // This will check if we need to stop the thread, // because of a reconfiguration. select { diff --git a/ui/src/components/ImageCanvas/ImageCanvas.jsx b/ui/src/components/ImageCanvas/ImageCanvas.jsx index 62f86fb7..a8713887 100644 --- a/ui/src/components/ImageCanvas/ImageCanvas.jsx +++ b/ui/src/components/ImageCanvas/ImageCanvas.jsx @@ -7,6 +7,9 @@ import './ImageCanvas.css'; class ImageCanvas extends React.Component { componentDidMount() { + this.width = 0; + this.height = 0; + this.loadImage = this.loadImage.bind(this); this.generateRandomTagsDescriptor = this.generateRandomTagsDescriptor.bind(this); @@ -55,14 +58,27 @@ class ImageCanvas extends React.Component { const { image } = this.props; this.loadImage(image, (img) => { - this.loadData(img); + if (this.width !== img.width || this.height !== img.height) { + this.width = img.width; + this.height = img.height; + this.loadData(img); + } else { + this.editor.addContentSource(img); + } }); } componentDidUpdate() { const { image } = this.props; this.loadImage(image, (img) => { - this.loadData(img); + if (this.width !== img.width || this.height !== img.height) { + this.width = img.width; + this.height = img.height; + this.loadData(img); + } else { + // alert('ok'); + this.editor.addContentSource(img); + } }); } diff --git a/ui/src/pages/Settings/Settings.jsx b/ui/src/pages/Settings/Settings.jsx index af723ecd..16d57861 100644 --- a/ui/src/pages/Settings/Settings.jsx +++ b/ui/src/pages/Settings/Settings.jsx @@ -19,6 +19,8 @@ import { } from '@kerberos-io/ui'; import { Link, withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; +import { interval } from 'rxjs'; +import { send } from '@giantmachines/redux-websocket'; import ImageCanvas from '../../components/ImageCanvas/ImageCanvas'; import './Settings.scss'; import timezones from './timezones'; @@ -121,6 +123,7 @@ class Settings extends React.Component { this.onUpdateToggle = this.onUpdateToggle.bind(this); this.onUpdateNumberField = this.onUpdateNumberField.bind(this); this.onUpdateTimeline = this.onUpdateTimeline.bind(this); + this.initialiseLiveview = this.initialiseLiveview.bind(this); this.verifyPersistenceSettings = this.verifyPersistenceSettings.bind(this); this.verifyHubSettings = this.verifyHubSettings.bind(this); this.verifyCameraSettings = this.verifyCameraSettings.bind(this); @@ -144,11 +147,18 @@ class Settings extends React.Component { })); this.calculateTimetable(config.timetable); }); + this.initialiseLiveview(); } componentWillUnmount() { document.removeEventListener('keydown', this.escFunction, false); clearInterval(this.interval); + + const { dispatchSend } = this.props; + const message = { + message_type: 'stop-sd', + }; + dispatchSend(message); } onAddRegion(device, id, polygon) { @@ -227,6 +237,24 @@ class Settings extends React.Component { ]); } + initialiseLiveview() { + const message = { + message_type: 'stream-sd', + }; + const { connected, dispatchSend } = this.props; + if (connected) { + dispatchSend(message); + } + + const requestStreamInterval = interval(2000); + this.requestStreamSubscription = requestStreamInterval.subscribe(() => { + const { connected: isConnected } = this.props; + if (isConnected) { + dispatchSend(message); + } + }); + } + calculateTimetable(timetable) { this.timetable = timetable; if (this.timetable) { @@ -521,8 +549,8 @@ class Settings extends React.Component { loadingHub, } = this.state; - const { config: c, t } = this.props; - const { config, snapshot } = c; + const { config: c, t, images } = this.props; + const { config } = c; const snapshotBase64 = 'data:image/png;base64,'; // Determine which section(s) to be shown, depending on the searching criteria. @@ -1160,9 +1188,9 @@ class Settings extends React.Component {

{t('settings.conditions.description_regionofinterest')}

- {config.region && ( + {config.region && images && images.length > 0 && ( ({ config: state.agent.config, + connected: state.wss.connected, + images: state.wss.images, }); const mapDispatchToProps = (dispatch /* , ownProps */) => ({ @@ -2397,11 +2427,14 @@ const mapDispatchToProps = (dispatch /* , ownProps */) => ({ dispatchAddRegion: (id, polygon) => dispatch(addRegion(id, polygon)), dispatchRemoveRegion: (id, polygon) => dispatch(removeRegion(id, polygon)), dispatchUpdateRegion: (id, polygon) => dispatch(updateRegion(id, polygon)), + dispatchSend: (message) => dispatch(send(message)), }); Settings.propTypes = { t: PropTypes.func.isRequired, + connected: PropTypes.bool.isRequired, config: PropTypes.objectOf(PropTypes.object).isRequired, + images: PropTypes.array.isRequired, dispatchVerifyHub: PropTypes.func.isRequired, dispatchVerifyPersistence: PropTypes.func.isRequired, dispatchGetConfig: PropTypes.func.isRequired, @@ -2412,6 +2445,7 @@ Settings.propTypes = { dispatchRemoveRegion: PropTypes.func.isRequired, dispatchVerifyCamera: PropTypes.func.isRequired, dispatchVerifyOnvif: PropTypes.func.isRequired, + dispatchSend: PropTypes.func.isRequired, }; export default withTranslation()( From e95f545bf4afff32b451a3281b1fe2d98915ee65 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 9 Dec 2023 23:02:18 +0100 Subject: [PATCH 50/81] upgrade deps + fix nil error --- machinery/go.mod | 8 ++++---- machinery/go.sum | 16 ++++++++-------- machinery/src/capture/Gortsplib.go | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/machinery/go.mod b/machinery/go.mod index 229b83cc..0b86e301 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -9,7 +9,7 @@ go 1.19 require ( github.com/InVisionApp/conjungo v1.1.0 github.com/appleboy/gin-jwt/v2 v2.9.1 - github.com/bluenviron/gortsplib/v4 v4.6.0 + github.com/bluenviron/gortsplib/v4 v4.6.1 github.com/bluenviron/mediacommon v1.5.1 github.com/cedricve/go-onvif v0.0.0-20200222191200-567e8ce298f6 github.com/deepch/vdk v0.0.19 @@ -134,11 +134,11 @@ 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/arch v0.3.0 // indirect - golang.org/x/crypto v0.15.0 // indirect - golang.org/x/net v0.18.0 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect golang.org/x/tools v0.7.0 // indirect diff --git a/machinery/go.sum b/machinery/go.sum index 24e4cc68..8cd94df9 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -66,8 +66,8 @@ github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= 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/v4 v4.6.0 h1:6z4ZEU9sl8H4rQMfxhd8z6FIGi/jmutU0R9HM6mZkMg= -github.com/bluenviron/gortsplib/v4 v4.6.0/go.mod h1:hb4lwJ+LMLfk0YbImTIrWLA8u3yWj77z4nzv2kxYLdk= +github.com/bluenviron/gortsplib/v4 v4.6.1 h1:+xI/hrNM/KX3qenqzKIG0MG8z+IHg0xu8OEoMfDZ+wg= +github.com/bluenviron/gortsplib/v4 v4.6.1/go.mod h1:dN1YjyPNMfy/NwC17Ga6MiIMiUoQfg5GL7LGsVHa0Jo= github.com/bluenviron/mediacommon v1.5.1 h1:yYVF+ebqZOJh8yH+EeuPcAtTmWR66BqbJGmStxkScoI= github.com/bluenviron/mediacommon v1.5.1/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -535,8 +535,8 @@ golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -618,8 +618,8 @@ 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/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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= @@ -697,8 +697,8 @@ 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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index d1e25d0e..2a9ed8a1 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -348,7 +348,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") // called when a MULAW audio RTP packet arrives - if g.AudioG711Media != nil { + if g.AudioG711Media != nil && g.AudioG711Forma != nil { g.Client.OnPacketRTP(g.AudioG711Media, g.AudioG711Forma, func(rtppkt *rtp.Packet) { // decode timestamp pts, ok := g.Client.PacketPTS(g.AudioG711Media, rtppkt) @@ -380,7 +380,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati } // called when a AAC audio RTP packet arrives - if g.AudioMPEG4Media != nil { + if g.AudioMPEG4Media != nil && g.AudioMPEG4Forma != nil { g.Client.OnPacketRTP(g.AudioMPEG4Media, g.AudioMPEG4Forma, func(rtppkt *rtp.Packet) { // decode timestamp pts, ok := g.Client.PacketPTS(g.AudioMPEG4Media, rtppkt) @@ -420,7 +420,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // called when a video RTP packet arrives for H264 var filteredAU [][]byte - if g.VideoH264Media != nil { + if g.VideoH264Media != nil && g.VideoH264Forma != nil { g.Client.OnPacketRTP(g.VideoH264Media, g.VideoH264Forma, func(rtppkt *rtp.Packet) { // This will check if we need to stop the thread, @@ -531,7 +531,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati } // called when a video RTP packet arrives for H265 - if g.VideoH265Media != nil { + if g.VideoH265Media != nil && g.VideoH265Forma != nil { g.Client.OnPacketRTP(g.VideoH265Media, g.VideoH265Forma, func(rtppkt *rtp.Packet) { // This will check if we need to stop the thread, From be6eb6165c77b9a5e97e4255822095d52214743e Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 10 Dec 2023 23:13:42 +0100 Subject: [PATCH 51/81] get keyframe and decode on requesting config (required for factory) --- machinery/src/capture/main.go | 82 +++++++++++-------------- machinery/src/cloud/Cloud.go | 3 +- machinery/src/computervision/main.go | 10 --- machinery/src/routers/http/Routes.go | 14 +++++ machinery/src/routers/websocket/main.go | 4 +- machinery/src/utils/main.go | 10 +++ 6 files changed, 64 insertions(+), 59 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 6971c22b..0f1c8002 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -3,9 +3,8 @@ package capture import ( "context" - "errors" - "fmt" - "io" + "encoding/base64" + "image" "os" "strconv" "time" @@ -625,54 +624,47 @@ func VerifyCamera(c *gin.Context) { } } -type cacheWriterSeeker struct { - buf []byte - offset int -} +func Base64Image(captureDevice *Capture, communication *models.Communication) string { + // We'll try to get a snapshot from the camera. + var queue *packets.Queue + var cursor *packets.QueueCursor -func newCacheWriterSeeker(capacity int) *cacheWriterSeeker { - return &cacheWriterSeeker{ - buf: make([]byte, 0, capacity), - offset: 0, + // We'll pick the right client and decoder. + rtspClient := captureDevice.RTSPSubClient + if rtspClient != nil { + queue = communication.SubQueue + cursor = queue.Latest() + } else { + rtspClient = captureDevice.RTSPClient + queue = communication.Queue + cursor = queue.Latest() } -} -func (ws *cacheWriterSeeker) Write(p []byte) (n int, err error) { - if cap(ws.buf)-ws.offset >= len(p) { - if len(ws.buf) < ws.offset+len(p) { - ws.buf = ws.buf[:ws.offset+len(p)] + // We'll try to have a keyframe, if not we'll return an empty string. + var encodedImage string + for { + if queue != nil && cursor != nil && rtspClient != nil { + pkt, err := cursor.ReadPacket() + if err == nil { + if !pkt.IsKeyFrame { + continue + } + var img image.YCbCr + img, err = (*rtspClient).DecodePacket(pkt) + if err == nil { + bytes, _ := utils.ImageToBytes(&img) + encodedImage = base64.StdEncoding.EncodeToString(bytes) + break + } else { + break + } + } + } else { + break } - copy(ws.buf[ws.offset:], p) - ws.offset += len(p) - return len(p), nil - } - tmp := make([]byte, len(ws.buf), cap(ws.buf)+len(p)*2) - copy(tmp, ws.buf) - if len(ws.buf) < ws.offset+len(p) { - tmp = tmp[:ws.offset+len(p)] } - copy(tmp[ws.offset:], p) - ws.buf = tmp - ws.offset += len(p) - return len(p), nil -} -func (ws *cacheWriterSeeker) Seek(offset int64, whence int) (int64, error) { - if whence == io.SeekCurrent { - if ws.offset+int(offset) > len(ws.buf) { - return -1, errors.New(fmt.Sprint("SeekCurrent out of range", len(ws.buf), offset, ws.offset)) - } - ws.offset += int(offset) - return int64(ws.offset), nil - } else if whence == io.SeekStart { - if offset > int64(len(ws.buf)) { - return -1, errors.New(fmt.Sprint("SeekStart out of range", len(ws.buf), offset, ws.offset)) - } - ws.offset = int(offset) - return offset, nil - } else { - return 0, errors.New("unsupport SeekEnd") - } + return encodedImage } func convertPTS(v time.Duration) uint64 { diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 2a49bfec..cc101b46 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -21,7 +21,6 @@ import ( "time" "github.com/kerberos-io/agent/machinery/src/capture" - "github.com/kerberos-io/agent/machinery/src/computervision" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/onvif" @@ -534,7 +533,7 @@ func HandleLiveStreamSD(livestreamCursor *packets.QueueCursor, configuration *mo log.Log.Info("cloud.HandleLiveStreamSD(): Sending base64 encoded images to MQTT.") img, err := rtspClient.DecodePacket(pkt) if err == nil { - bytes, _ := computervision.ImageToBytes(&img) + bytes, _ := utils.ImageToBytes(&img) encoded := base64.StdEncoding.EncodeToString(bytes) valueMap := make(map[string]interface{}) diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index 5ff32d6b..d54742fa 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -1,10 +1,7 @@ package computervision import ( - "bufio" - "bytes" "image" - "image/jpeg" "time" mqtt "github.com/eclipse/paho.mqtt.golang" @@ -214,13 +211,6 @@ func FindMotion(imageArray [3]*image.Gray, coordinatesToCheck []int, pixelChange return changes > pixelChangeThreshold, changes } -func ImageToBytes(img image.Image) ([]byte, error) { - buffer := new(bytes.Buffer) - w := bufio.NewWriter(buffer) - err := jpeg.Encode(w, img, &jpeg.Options{Quality: 15}) - return buffer.Bytes(), err -} - func AbsDiffBitwiseAndThreshold(img1 *image.Gray, img2 *image.Gray, img3 *image.Gray, threshold int, coordinatesToCheck []int) int { changes := 0 for i := 0; i < len(coordinatesToCheck); i++ { diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/Routes.go index 67b9d7e7..8d3c7a02 100644 --- a/machinery/src/routers/http/Routes.go +++ b/machinery/src/routers/http/Routes.go @@ -22,6 +22,13 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect // This is legacy should be removed in future! Now everything // lives under the /api prefix. r.GET("/config", func(c *gin.Context) { + + // We'll try to get a snapshot from the camera. + base64Image := capture.Base64Image(captureDevice, communication) + if base64Image != "" { + communication.Image = base64Image + } + c.JSON(200, gin.H{ "config": configuration.Config, "custom": configuration.CustomConfig, @@ -149,6 +156,13 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect }) api.GET("/config", func(c *gin.Context) { + + // We'll try to get a snapshot from the camera. + base64Image := capture.Base64Image(captureDevice, communication) + if base64Image != "" { + communication.Image = base64Image + } + c.JSON(200, gin.H{ "config": configuration.Config, "custom": configuration.CustomConfig, diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index f70d588a..22aefdb9 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -10,10 +10,10 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/kerberos-io/agent/machinery/src/capture" - "github.com/kerberos-io/agent/machinery/src/computervision" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/packets" + "github.com/kerberos-io/agent/machinery/src/utils" ) type Message struct { @@ -154,7 +154,7 @@ logreader: var img image.YCbCr img, err = (*rtspClient).DecodePacket(pkt) if err == nil { - bytes, _ := computervision.ImageToBytes(&img) + bytes, _ := utils.ImageToBytes(&img) encodedImage = base64.StdEncoding.EncodeToString(bytes) } } else { diff --git a/machinery/src/utils/main.go b/machinery/src/utils/main.go index 045d0d0b..2708a42b 100644 --- a/machinery/src/utils/main.go +++ b/machinery/src/utils/main.go @@ -1,9 +1,12 @@ package utils import ( + "bufio" "bytes" "errors" "fmt" + "image" + "image/jpeg" "io/ioutil" "math/rand" "os" @@ -395,3 +398,10 @@ func Decrypt(directoryOrFile string, symmetricKey []byte) { } } } + +func ImageToBytes(img image.Image) ([]byte, error) { + buffer := new(bytes.Buffer) + w := bufio.NewWriter(buffer) + err := jpeg.Encode(w, img, &jpeg.Options{Quality: 15}) + return buffer.Bytes(), err +} From 898b3a52c209a1bc58fd389ee21bc3a1b37e4331 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 11 Dec 2023 20:32:03 +0100 Subject: [PATCH 52/81] update loggin + add new swagger endpoints --- machinery/docs/docs.go | 83 +++++++++++++++++++++ machinery/docs/swagger.json | 83 +++++++++++++++++++++ machinery/docs/swagger.yaml | 55 ++++++++++++++ machinery/src/components/Kerberos.go | 103 ++++++++++++++++++++------- machinery/src/components/Stream.go | 11 --- machinery/src/routers/http/Routes.go | 19 +++-- 6 files changed, 309 insertions(+), 45 deletions(-) diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index 18ec24c0..b5191f16 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -199,6 +199,60 @@ const docTemplate = `{ } } }, + "/api/camera/record": { + "post": { + "description": "Make a recording.", + "tags": [ + "camera" + ], + "summary": "Make a recording.", + "operationId": "camera-record", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, + "/api/camera/restart": { + "post": { + "description": "Restart the agent.", + "tags": [ + "camera" + ], + "summary": "Restart the agent.", + "operationId": "camera-restart", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, + "/api/camera/stop": { + "post": { + "description": "Stop the agent.", + "tags": [ + "camera" + ], + "summary": "Stop the agent.", + "operationId": "camera-stop", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, "/api/camera/verify/{streamType}": { "post": { "description": "This method will validate a specific profile connection from an RTSP camera, and try to get the codec.", @@ -505,6 +559,9 @@ const docTemplate = `{ "dropbox": { "$ref": "#/definitions/models.Dropbox" }, + "encryption": { + "$ref": "#/definitions/models.Encryption" + }, "friendly_name": { "type": "string" }, @@ -608,12 +665,35 @@ const docTemplate = `{ } } }, + "models.Encryption": { + "type": "object", + "properties": { + "enabled": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "private_key": { + "type": "string" + }, + "recordings": { + "type": "string" + }, + "symmetric_key": { + "type": "string" + } + } + }, "models.IPCamera": { "type": "object", "properties": { "fps": { "type": "string" }, + "height": { + "type": "integer" + }, "onvif": { "type": "string" }, @@ -631,6 +711,9 @@ const docTemplate = `{ }, "sub_rtsp": { "type": "string" + }, + "width": { + "type": "integer" } } }, diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index 796543bc..144eefd8 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -191,6 +191,60 @@ } } }, + "/api/camera/record": { + "post": { + "description": "Make a recording.", + "tags": [ + "camera" + ], + "summary": "Make a recording.", + "operationId": "camera-record", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, + "/api/camera/restart": { + "post": { + "description": "Restart the agent.", + "tags": [ + "camera" + ], + "summary": "Restart the agent.", + "operationId": "camera-restart", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, + "/api/camera/stop": { + "post": { + "description": "Stop the agent.", + "tags": [ + "camera" + ], + "summary": "Stop the agent.", + "operationId": "camera-stop", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, "/api/camera/verify/{streamType}": { "post": { "description": "This method will validate a specific profile connection from an RTSP camera, and try to get the codec.", @@ -497,6 +551,9 @@ "dropbox": { "$ref": "#/definitions/models.Dropbox" }, + "encryption": { + "$ref": "#/definitions/models.Encryption" + }, "friendly_name": { "type": "string" }, @@ -600,12 +657,35 @@ } } }, + "models.Encryption": { + "type": "object", + "properties": { + "enabled": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "private_key": { + "type": "string" + }, + "recordings": { + "type": "string" + }, + "symmetric_key": { + "type": "string" + } + } + }, "models.IPCamera": { "type": "object", "properties": { "fps": { "type": "string" }, + "height": { + "type": "integer" + }, "onvif": { "type": "string" }, @@ -623,6 +703,9 @@ }, "sub_rtsp": { "type": "string" + }, + "width": { + "type": "integer" } } }, diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index 2d50c8d2..d195b447 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -88,6 +88,8 @@ definitions: type: string dropbox: $ref: '#/definitions/models.Dropbox' + encryption: + $ref: '#/definitions/models.Encryption' friendly_name: type: string heartbeaturi: @@ -156,10 +158,25 @@ definitions: directory: type: string type: object + models.Encryption: + properties: + enabled: + type: string + fingerprint: + type: string + private_key: + type: string + recordings: + type: string + symmetric_key: + type: string + type: object models.IPCamera: properties: fps: type: string + height: + type: integer onvif: type: string onvif_password: @@ -172,6 +189,8 @@ definitions: type: string sub_rtsp: type: string + width: + type: integer type: object models.KStorage: properties: @@ -417,6 +436,42 @@ paths: summary: Zooming in or out the camera. tags: - camera + /api/camera/record: + post: + description: Make a recording. + operationId: camera-record + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.APIResponse' + summary: Make a recording. + tags: + - camera + /api/camera/restart: + post: + description: Restart the agent. + operationId: camera-restart + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.APIResponse' + summary: Restart the agent. + tags: + - camera + /api/camera/stop: + post: + description: Stop the agent. + operationId: camera-stop + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.APIResponse' + summary: Stop the agent. + tags: + - camera /api/camera/verify/{streamType}: post: description: This method will validate a specific profile connection from an diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 927160e6..2f81e26a 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -2,11 +2,13 @@ package components import ( "context" + "os" "strconv" "sync/atomic" "time" mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/gin-gonic/gin" "github.com/kerberos-io/agent/machinery/src/capture" "github.com/kerberos-io/agent/machinery/src/cloud" @@ -21,7 +23,7 @@ import ( ) func Bootstrap(configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) { - log.Log.Debug("Bootstrap: started") + log.Log.Debug("components.Kerberos.Bootstrap(): bootstrapping the kerberos agent.") // We will keep track of the Kerberos Agent up time // This is send to Kerberos Hub in a heartbeat. @@ -75,7 +77,9 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm status := RunAgent(configDirectory, configuration, communication, mqttClient, uptimeStart, cameraSettings, captureDevice) if status == "stop" { - break + log.Log.Info("components.Kerberos.Bootstrap(): shutting down the agent in 3 seconds.") + time.Sleep(time.Second * 3) + os.Exit(0) } if status == "not started" { @@ -97,12 +101,11 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm communication.Context = &ctx communication.CancelContext = &cancel } - log.Log.Debug("Bootstrap: finished") } func RunAgent(configDirectory string, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, uptimeStart time.Time, cameraSettings *models.Camera, captureDevice *capture.Capture) string { - log.Log.Debug("RunAgent: bootstrapping agent") + log.Log.Info("components.Kerberos.RunAgent(): Creating camera and processing threads.") config := configuration.Config status := "not started" @@ -114,17 +117,17 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu err := rtspClient.Connect(context.Background()) if err != nil { - log.Log.Error("RunAgent: error connecting to RTSP stream: " + err.Error()) + log.Log.Error("components.Kerberos.RunAgent(): error connecting to RTSP stream: " + err.Error()) rtspClient.Close() time.Sleep(time.Second * 3) return status } - log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl) + log.Log.Info("components.Kerberos.RunAgent(): opened RTSP stream: " + rtspUrl) // Get the video streams from the RTSP server. videoStreams, err := rtspClient.GetVideoStreams() if err != nil || len(videoStreams) == 0 { - log.Log.Error("RunAgent: no video stream found, might be the wrong codec (we only support H264 for the moment)") + log.Log.Error("components.Kerberos.RunAgent(): no video stream found, might be the wrong codec (we only support H264 for the moment)") rtspClient.Close() time.Sleep(time.Second * 3) return status @@ -148,7 +151,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // and consumed by all other routines: motion, livestream, etc. if config.Capture.PreRecording <= 0 { config.Capture.PreRecording = 1 - log.Log.Warning("RunAgent: Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10)) + log.Log.Warning("components.Kerberos.RunAgent(): Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10)) } // We might have a secondary rtsp url, so we might need to use that for livestreaming let us check first! @@ -164,16 +167,16 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu err := rtspSubClient.Connect(context.Background()) if err != nil { - log.Log.Error("RunAgent: error connecting to RTSP sub stream: " + err.Error()) + log.Log.Error("components.Kerberos.RunAgent(): error connecting to RTSP sub stream: " + err.Error()) time.Sleep(time.Second * 3) return status } - log.Log.Info("RunAgent: opened RTSP sub stream: " + rtspUrl) + log.Log.Info("components.Kerberos.RunAgent(): opened RTSP sub stream: " + rtspUrl) // Get the video streams from the RTSP server. videoSubStreams, err = rtspSubClient.GetVideoStreams() if err != nil || len(videoSubStreams) == 0 { - log.Log.Error("RunAgent: no video sub stream found, might be the wrong codec (we only support H264 for the moment)") + log.Log.Error("components.Kerberos.RunAgent(): no video sub stream found, might be the wrong codec (we only support H264 for the moment)") rtspSubClient.Close() time.Sleep(time.Second * 3) return status @@ -212,7 +215,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // At some routines we will need to decode the image. // Make sure its properly locked as we only have a single decoder. - log.Log.Info("RunAgent: camera settings changed, reloading decoder") + log.Log.Info("components.Kerberos.RunAgent(): camera settings changed, reloading decoder") //capture.GetVideoDecoder(decoder, streams) //if subStreamEnabled { // capture.GetVideoDecoder(subDecoder, subStreams) @@ -228,7 +231,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu //cameraSettings.Codec = videoStream.(av.VideoCodecData).Type() cameraSettings.Initialized = true } else { - log.Log.Info("RunAgent: camera settings did not change, keeping decoder") + log.Log.Info("components.Kerberos.RunAgent(): camera settings did not change, keeping decoder") } // We are creating a queue to store the RTSP frames in, these frames will be @@ -237,7 +240,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu communication.Queue = queue // Set the maximum GOP count, this is used to determine the pre-recording time. - log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) + log.Log.Info("components.Kerberos.RunAgent(): SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). queue.WriteHeader(videoStreams) go rtspClient.Start(context.Background(), queue, configuration, communication) @@ -246,9 +249,9 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu rtspBackChannelClient := captureDevice.SetBackChannelClient(rtspUrl) err = rtspBackChannelClient.ConnectBackChannel(context.Background()) if err != nil { - log.Log.Error("RunAgent: error connecting to RTSP backchannel stream: " + err.Error()) + log.Log.Error("components.Kerberos.RunAgent(): error connecting to RTSP backchannel stream: " + err.Error()) } else { - log.Log.Info("RunAgent: opened RTSP backchannel stream: " + rtspUrl) + log.Log.Info("components.Kerberos.RunAgent(): opened RTSP backchannel stream: " + rtspUrl) go rtspBackChannelClient.StartBackChannel(context.Background()) } @@ -341,7 +344,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu err = rtspClient.Close() if err != nil { - log.Log.Error("RunAgent: error closing RTSP stream: " + err.Error()) + log.Log.Error("components.Kerberos.RunAgent(): error closing RTSP stream: " + err.Error()) time.Sleep(time.Second * 3) return status } @@ -353,7 +356,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu if subStreamEnabled { err = rtspSubClient.Close() if err != nil { - log.Log.Error("RunAgent: error closing RTSP sub stream: " + err.Error()) + log.Log.Error("components.Kerberos.RunAgent(): error closing RTSP sub stream: " + err.Error()) time.Sleep(time.Second * 3) return status } @@ -364,7 +367,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu err = rtspBackChannelClient.Close() if err != nil { - log.Log.Error("RunAgent: error closing RTSP backchannel stream: " + err.Error()) + log.Log.Error("components.Kerberos.RunAgent(): error closing RTSP backchannel stream: " + err.Error()) } time.Sleep(time.Second * 3) @@ -382,14 +385,17 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu communication.HandleONVIF = 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.") + log.Log.Info("components.Kerberos.RunAgent(): waiting 3 seconds to make sure everything is properly closed.") time.Sleep(time.Second * 3) return status } +// ControlAgent will check if the camera is still connected, if not it will restart the agent. +// In the other thread we are keeping track of the number of packets received, and particular the keyframe packets. +// Once we are not receiving any packets anymore, we will restart the agent. func ControlAgent(communication *models.Communication) { - log.Log.Debug("ControlAgent: started") + log.Log.Debug("components.Kerberos.ControlAgent(): started") packageCounter := communication.PackageCounter go func() { // A channel to check the camera activity @@ -410,11 +416,11 @@ func ControlAgent(communication *models.Communication) { occurence = 0 } - log.Log.Info("ControlAgent: Number of packets read " + strconv.FormatInt(packetsR, 10)) + log.Log.Info("components.Kerberos.ControlAgent(): Number of packets read " + strconv.FormatInt(packetsR, 10)) // After 15 seconds without activity this is thrown.. if occurence == 3 { - log.Log.Info("Main: Restarting machinery.") + log.Log.Info("components.Kerberos.ControlAgent(): Restarting machinery.") communication.HandleBootstrap <- "restart" time.Sleep(2 * time.Second) occurence = 0 @@ -425,5 +431,54 @@ func ControlAgent(communication *models.Communication) { time.Sleep(5 * time.Second) } }() - log.Log.Debug("ControlAgent: finished") + log.Log.Debug("components.Kerberos.ControlAgent(): finished") +} + +// StopAgent godoc +// @Router /api/camera/stop [post] +// @ID camera-stop +// @Tags camera +// @Summary Stop the agent. +// @Description Stop the agent. +// @Success 200 {object} models.APIResponse +func StopAgent(c *gin.Context, communication *models.Communication) { + log.Log.Info("components.Kerberos.StopAgent(): sending signal to stop agent, this will os.Exit(0).") + communication.HandleBootstrap <- "stop" + c.JSON(200, gin.H{ + "stopped": true, + }) +} + +// RestartAgent godoc +// @Router /api/camera/restart [post] +// @ID camera-restart +// @Tags camera +// @Summary Restart the agent. +// @Description Restart the agent. +// @Success 200 {object} models.APIResponse +func RestartAgent(c *gin.Context, communication *models.Communication) { + log.Log.Info("components.Kerberos.RestartAgent(): sending signal to restart agent.") + communication.HandleBootstrap <- "restart" + c.JSON(200, gin.H{ + "restarted": true, + }) +} + +// MakeRecording godoc +// @Router /api/camera/record [post] +// @ID camera-record +// @Tags camera +// @Summary Make a recording. +// @Description Make a recording. +// @Success 200 {object} models.APIResponse +func MakeRecording(c *gin.Context, communication *models.Communication) { + log.Log.Info("components.Kerberos.MakeRecording(): sending signal to start recording.") + dataToPass := models.MotionDataPartial{ + Timestamp: time.Now().Unix(), + NumberOfChanges: 100000000, // hack set the number of changes to a high number to force recording + } + communication.HandleMotion <- dataToPass //Save data to the channel + c.JSON(200, gin.H{ + "recording": true, + }) } diff --git a/machinery/src/components/Stream.go b/machinery/src/components/Stream.go index 8ca54af8..435c684d 100644 --- a/machinery/src/components/Stream.go +++ b/machinery/src/components/Stream.go @@ -2,15 +2,12 @@ package components import ( "fmt" - "image" - "image/jpeg" "log" "time" "github.com/deepch/vdk/av" "github.com/deepch/vdk/codec/h264parser" "github.com/deepch/vdk/format/rtsp" - "github.com/nsmith5/mjpeg" ) type Stream struct { @@ -83,11 +80,3 @@ func GetSPSFromCodec(codecs []av.CodecData) ([]byte, []byte) { pps := codecs[0].(h264parser.CodecData).PPS() return sps, pps } - -func StartMotionJPEG(imageFunction func() (image.Image, error), quality int) mjpeg.Handler { - stream := mjpeg.Handler{ - Next: imageFunction, - Options: &jpeg.Options{Quality: quality}, - } - return stream -} diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/Routes.go index 8d3c7a02..0a94e769 100644 --- a/machinery/src/routers/http/Routes.go +++ b/machinery/src/routers/http/Routes.go @@ -4,6 +4,7 @@ import ( jwt "github.com/appleboy/gin-jwt/v2" "github.com/gin-gonic/gin" "github.com/kerberos-io/agent/machinery/src/capture" + "github.com/kerberos-io/agent/machinery/src/components" "github.com/kerberos-io/agent/machinery/src/onvif" "github.com/kerberos-io/agent/machinery/src/routers/websocket" @@ -192,18 +193,16 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect } }) - api.GET("/restart", func(c *gin.Context) { - communication.HandleBootstrap <- "restart" - c.JSON(200, gin.H{ - "restarted": true, - }) + api.POST("/camera/restart", func(c *gin.Context) { + components.RestartAgent(c, communication) }) - api.GET("/stop", func(c *gin.Context) { - communication.HandleBootstrap <- "stop" - c.JSON(200, gin.H{ - "stopped": true, - }) + api.POST("/camera/stop", func(c *gin.Context) { + components.StopAgent(c, communication) + }) + + api.POST("/camera/record", func(c *gin.Context) { + components.MakeRecording(c, communication) }) api.POST("/onvif/verify", func(c *gin.Context) { From 9151b38e7f84c893d4792859302f0637e8660052 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 11 Dec 2023 21:02:01 +0100 Subject: [PATCH 53/81] document more swagger endpoints + cleanup source --- machinery/docs/docs.go | 115 ++++++++++++++++- machinery/docs/swagger.json | 115 ++++++++++++++++- machinery/docs/swagger.yaml | 78 +++++++++++- machinery/src/cloud/Cloud.go | 4 +- machinery/src/components/Kerberos.go | 165 ++++++++++++++++++++++++ machinery/src/onvif/main.go | 2 +- machinery/src/routers/http/Routes.go | 181 ++++----------------------- 7 files changed, 490 insertions(+), 170 deletions(-) diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index b5191f16..0f85db49 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -293,6 +293,75 @@ const docTemplate = `{ } } }, + "/api/config": { + "get": { + "description": "Get the current configuration.", + "tags": [ + "general" + ], + "summary": "Get the current configuration.", + "operationId": "config", + "responses": { + "200": { + "description": "" + } + } + }, + "post": { + "description": "Update the current configuration.", + "tags": [ + "general" + ], + "summary": "Update the current configuration.", + "operationId": "config", + "parameters": [ + { + "description": "Configuration", + "name": "config", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Config" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/dashboard": { + "get": { + "description": "Get all information showed on the dashboard.", + "tags": [ + "general" + ], + "summary": "Get all information showed on the dashboard.", + "operationId": "dashboard", + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/days": { + "get": { + "description": "Get all days stored in the recordings directory.", + "tags": [ + "general" + ], + "summary": "Get all days stored in the recordings directory.", + "operationId": "days", + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/hub/verify": { "post": { "security": [ @@ -302,7 +371,7 @@ const docTemplate = `{ ], "description": "Will verify the hub connectivity.", "tags": [ - "config" + "general" ], "summary": "Will verify the hub connectivity.", "operationId": "verify-hub", @@ -327,6 +396,32 @@ const docTemplate = `{ } } }, + "/api/latest-events": { + "post": { + "description": "Get the latest recordings (events) from the recordings directory.", + "tags": [ + "general" + ], + "summary": "Get the latest recordings (events) from the recordings directory.", + "operationId": "latest-events", + "parameters": [ + { + "description": "Event filter", + "name": "eventFilter", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.EventFilter" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/login": { "post": { "description": "Get Authorization token.", @@ -365,7 +460,7 @@ const docTemplate = `{ ], "description": "Will verify the ONVIF connectivity.", "tags": [ - "config" + "general" ], "summary": "Will verify the ONVIF connectivity.", "operationId": "verify-onvif", @@ -399,7 +494,7 @@ const docTemplate = `{ ], "description": "Will verify the persistence.", "tags": [ - "config" + "general" ], "summary": "Will verify the persistence.", "operationId": "verify-persistence", @@ -685,6 +780,20 @@ const docTemplate = `{ } } }, + "models.EventFilter": { + "type": "object", + "properties": { + "number_of_elements": { + "type": "integer" + }, + "timestamp_offset_end": { + "type": "integer" + }, + "timestamp_offset_start": { + "type": "integer" + } + } + }, "models.IPCamera": { "type": "object", "properties": { diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index 144eefd8..5ca04444 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -285,6 +285,75 @@ } } }, + "/api/config": { + "get": { + "description": "Get the current configuration.", + "tags": [ + "general" + ], + "summary": "Get the current configuration.", + "operationId": "config", + "responses": { + "200": { + "description": "" + } + } + }, + "post": { + "description": "Update the current configuration.", + "tags": [ + "general" + ], + "summary": "Update the current configuration.", + "operationId": "config", + "parameters": [ + { + "description": "Configuration", + "name": "config", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Config" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/dashboard": { + "get": { + "description": "Get all information showed on the dashboard.", + "tags": [ + "general" + ], + "summary": "Get all information showed on the dashboard.", + "operationId": "dashboard", + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/days": { + "get": { + "description": "Get all days stored in the recordings directory.", + "tags": [ + "general" + ], + "summary": "Get all days stored in the recordings directory.", + "operationId": "days", + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/hub/verify": { "post": { "security": [ @@ -294,7 +363,7 @@ ], "description": "Will verify the hub connectivity.", "tags": [ - "config" + "general" ], "summary": "Will verify the hub connectivity.", "operationId": "verify-hub", @@ -319,6 +388,32 @@ } } }, + "/api/latest-events": { + "post": { + "description": "Get the latest recordings (events) from the recordings directory.", + "tags": [ + "general" + ], + "summary": "Get the latest recordings (events) from the recordings directory.", + "operationId": "latest-events", + "parameters": [ + { + "description": "Event filter", + "name": "eventFilter", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.EventFilter" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/login": { "post": { "description": "Get Authorization token.", @@ -357,7 +452,7 @@ ], "description": "Will verify the ONVIF connectivity.", "tags": [ - "config" + "general" ], "summary": "Will verify the ONVIF connectivity.", "operationId": "verify-onvif", @@ -391,7 +486,7 @@ ], "description": "Will verify the persistence.", "tags": [ - "config" + "general" ], "summary": "Will verify the persistence.", "operationId": "verify-persistence", @@ -677,6 +772,20 @@ } } }, + "models.EventFilter": { + "type": "object", + "properties": { + "number_of_elements": { + "type": "integer" + }, + "timestamp_offset_end": { + "type": "integer" + }, + "timestamp_offset_start": { + "type": "integer" + } + } + }, "models.IPCamera": { "type": "object", "properties": { diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index d195b447..f9ffdae1 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -171,6 +171,15 @@ definitions: symmetric_key: type: string type: object + models.EventFilter: + properties: + number_of_elements: + type: integer + timestamp_offset_end: + type: integer + timestamp_offset_start: + type: integer + type: object models.IPCamera: properties: fps: @@ -500,6 +509,52 @@ paths: summary: Validate a specific RTSP profile camera connection. tags: - camera + /api/config: + get: + description: Get the current configuration. + operationId: config + responses: + "200": + description: "" + summary: Get the current configuration. + tags: + - general + post: + description: Update the current configuration. + operationId: config + parameters: + - description: Configuration + in: body + name: config + required: true + schema: + $ref: '#/definitions/models.Config' + responses: + "200": + description: "" + summary: Update the current configuration. + tags: + - general + /api/dashboard: + get: + description: Get all information showed on the dashboard. + operationId: dashboard + responses: + "200": + description: "" + summary: Get all information showed on the dashboard. + tags: + - general + /api/days: + get: + description: Get all days stored in the recordings directory. + operationId: days + responses: + "200": + description: "" + summary: Get all days stored in the recordings directory. + tags: + - general /api/hub/verify: post: description: Will verify the hub connectivity. @@ -520,7 +575,24 @@ paths: - Bearer: [] summary: Will verify the hub connectivity. tags: - - config + - general + /api/latest-events: + post: + description: Get the latest recordings (events) from the recordings directory. + operationId: latest-events + parameters: + - description: Event filter + in: body + name: eventFilter + required: true + schema: + $ref: '#/definitions/models.EventFilter' + responses: + "200": + description: "" + summary: Get the latest recordings (events) from the recordings directory. + tags: + - general /api/login: post: description: Get Authorization token. @@ -560,7 +632,7 @@ paths: - Bearer: [] summary: Will verify the ONVIF connectivity. tags: - - config + - general /api/persistence/verify: post: description: Will verify the persistence. @@ -581,7 +653,7 @@ paths: - Bearer: [] summary: Will verify the persistence. tags: - - config + - general securityDefinitions: Bearer: in: header diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index cc101b46..0d40dac6 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -602,7 +602,7 @@ func HandleLiveStreamHD(livestreamCursor *packets.QueueCursor, configuration *mo // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags config +// @Tags general // @Param config body models.Config true "Config" // @Summary Will verify the hub connectivity. // @Description Will verify the hub connectivity. @@ -672,7 +672,7 @@ func VerifyHub(c *gin.Context) { // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags config +// @Tags general // @Param config body models.Config true "Config" // @Summary Will verify the persistence. // @Description Will verify the persistence. diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 2f81e26a..56723bff 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -19,6 +19,7 @@ import ( "github.com/kerberos-io/agent/machinery/src/onvif" "github.com/kerberos-io/agent/machinery/src/packets" routers "github.com/kerberos-io/agent/machinery/src/routers/mqtt" + "github.com/kerberos-io/agent/machinery/src/utils" "github.com/tevino/abool" ) @@ -434,6 +435,119 @@ func ControlAgent(communication *models.Communication) { log.Log.Debug("components.Kerberos.ControlAgent(): finished") } +// GetDashboard godoc +// @Router /api/dashboard [get] +// @ID dashboard +// @Tags general +// @Summary Get all information showed on the dashboard. +// @Description Get all information showed on the dashboard. +// @Success 200 +func GetDashboard(c *gin.Context, configDirectory string, configuration *models.Configuration, communication *models.Communication) { + + // Check if camera is online. + cameraIsOnline := communication.CameraConnected + + // If an agent is properly setup with Kerberos Hub, we will send + // a ping to Kerberos Hub every 15seconds. On receiving a positive response + // it will update the CloudTimestamp value. + cloudIsOnline := false + if communication.CloudTimestamp != nil && communication.CloudTimestamp.Load() != nil { + timestamp := communication.CloudTimestamp.Load().(int64) + if timestamp > 0 { + cloudIsOnline = true + } + } + + // The total number of recordings stored in the directory. + recordingDirectory := configDirectory + "/data/recordings" + numberOfRecordings := utils.NumberOfMP4sInDirectory(recordingDirectory) + + // All days stored in this agent. + days := []string{} + latestEvents := []models.Media{} + files, err := utils.ReadDirectory(recordingDirectory) + if err == nil { + events := utils.GetSortedDirectory(files) + + // Get All days + days = utils.GetDays(events, recordingDirectory, configuration) + + // Get all latest events + var eventFilter models.EventFilter + eventFilter.NumberOfElements = 5 + latestEvents = utils.GetMediaFormatted(events, recordingDirectory, configuration, eventFilter) // will get 5 latest recordings. + } + + c.JSON(200, gin.H{ + "offlineMode": configuration.Config.Offline, + "cameraOnline": cameraIsOnline, + "cloudOnline": cloudIsOnline, + "numberOfRecordings": numberOfRecordings, + "days": days, + "latestEvents": latestEvents, + }) +} + +// GetLatestEvents godoc +// @Router /api/latest-events [post] +// @ID latest-events +// @Tags general +// @Param eventFilter body models.EventFilter true "Event filter" +// @Summary Get the latest recordings (events) from the recordings directory. +// @Description Get the latest recordings (events) from the recordings directory. +// @Success 200 +func GetLatestEvents(c *gin.Context, configDirectory string, configuration *models.Configuration, communication *models.Communication) { + var eventFilter models.EventFilter + err := c.BindJSON(&eventFilter) + if err == nil { + // Default to 10 if no limit is set. + if eventFilter.NumberOfElements == 0 { + eventFilter.NumberOfElements = 10 + } + recordingDirectory := configDirectory + "/data/recordings" + files, err := utils.ReadDirectory(recordingDirectory) + if err == nil { + events := utils.GetSortedDirectory(files) + // We will get all recordings from the directory (as defined by the filter). + fileObjects := utils.GetMediaFormatted(events, recordingDirectory, configuration, eventFilter) + c.JSON(200, gin.H{ + "events": fileObjects, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } +} + +// GetDays godoc +// @Router /api/days [get] +// @ID days +// @Tags general +// @Summary Get all days stored in the recordings directory. +// @Description Get all days stored in the recordings directory. +// @Success 200 +func GetDays(c *gin.Context, configDirectory string, configuration *models.Configuration, communication *models.Communication) { + recordingDirectory := configDirectory + "/data/recordings" + files, err := utils.ReadDirectory(recordingDirectory) + if err == nil { + events := utils.GetSortedDirectory(files) + days := utils.GetDays(events, recordingDirectory, configuration) + c.JSON(200, gin.H{ + "events": days, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } +} + // StopAgent godoc // @Router /api/camera/stop [post] // @ID camera-stop @@ -482,3 +596,54 @@ func MakeRecording(c *gin.Context, communication *models.Communication) { "recording": true, }) } + +// GetConfig godoc +// @Router /api/config [get] +// @ID config +// @Tags general +// @Summary Get the current configuration. +// @Description Get the current configuration. +// @Success 200 +func GetConfig(c *gin.Context, captureDevice *capture.Capture, configuration *models.Configuration, communication *models.Communication) { + // We'll try to get a snapshot from the camera. + base64Image := capture.Base64Image(captureDevice, communication) + if base64Image != "" { + communication.Image = base64Image + } + + c.JSON(200, gin.H{ + "config": configuration.Config, + "custom": configuration.CustomConfig, + "global": configuration.GlobalConfig, + "snapshot": communication.Image, + }) +} + +// UpdateConfig godoc +// @Router /api/config [post] +// @ID config +// @Tags general +// @Param config body models.Config true "Configuration" +// @Summary Update the current configuration. +// @Description Update the current configuration. +// @Success 200 +func UpdateConfig(c *gin.Context, configDirectory string, configuration *models.Configuration, communication *models.Communication) { + var config models.Config + err := c.BindJSON(&config) + if err == nil { + err := configService.SaveConfig(configDirectory, config, configuration, communication) + if err == nil { + c.JSON(200, gin.H{ + "data": "☄ Reconfiguring", + }) + } else { + c.JSON(200, gin.H{ + "data": "☄ Reconfiguring", + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } +} diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 22af0f9e..2c381a11 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -851,7 +851,7 @@ func GetPTZFunctionsFromDevice(configurations ptz.GetConfigurationsResponse) ([] // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags config +// @Tags general // @Param cameraConfig body models.IPCamera true "Camera Config" // @Summary Will verify the ONVIF connectivity. // @Description Will verify the ONVIF connectivity. diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/Routes.go index 0a94e769..41f74c37 100644 --- a/machinery/src/routers/http/Routes.go +++ b/machinery/src/routers/http/Routes.go @@ -9,9 +9,7 @@ import ( "github.com/kerberos-io/agent/machinery/src/routers/websocket" "github.com/kerberos-io/agent/machinery/src/cloud" - configService "github.com/kerberos-io/agent/machinery/src/config" "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/agent/machinery/src/utils" ) func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) *gin.RouterGroup { @@ -23,42 +21,13 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect // This is legacy should be removed in future! Now everything // lives under the /api prefix. r.GET("/config", func(c *gin.Context) { - - // We'll try to get a snapshot from the camera. - base64Image := capture.Base64Image(captureDevice, communication) - if base64Image != "" { - communication.Image = base64Image - } - - c.JSON(200, gin.H{ - "config": configuration.Config, - "custom": configuration.CustomConfig, - "global": configuration.GlobalConfig, - "snapshot": communication.Image, - }) + components.GetConfig(c, captureDevice, configuration, communication) }) // This is legacy should be removed in future! Now everything // lives under the /api prefix. r.POST("/config", func(c *gin.Context) { - var config models.Config - err := c.BindJSON(&config) - if err == nil { - err := configService.SaveConfig(configDirectory, config, configuration, communication) - if err == nil { - c.JSON(200, gin.H{ - "data": "☄ Reconfiguring", - }) - } else { - c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), - }) - } - } else { - c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), - }) - } + components.UpdateConfig(c, configDirectory, configuration, communication) }) api := r.Group("/api") @@ -66,153 +35,36 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect api.POST("/login", authMiddleware.LoginHandler) api.GET("/dashboard", func(c *gin.Context) { - - // Check if camera is online. - cameraIsOnline := communication.CameraConnected - - // If an agent is properly setup with Kerberos Hub, we will send - // a ping to Kerberos Hub every 15seconds. On receiving a positive response - // it will update the CloudTimestamp value. - cloudIsOnline := false - if communication.CloudTimestamp != nil && communication.CloudTimestamp.Load() != nil { - timestamp := communication.CloudTimestamp.Load().(int64) - if timestamp > 0 { - cloudIsOnline = true - } - } - - // The total number of recordings stored in the directory. - recordingDirectory := configDirectory + "/data/recordings" - numberOfRecordings := utils.NumberOfMP4sInDirectory(recordingDirectory) - - // All days stored in this agent. - days := []string{} - latestEvents := []models.Media{} - files, err := utils.ReadDirectory(recordingDirectory) - if err == nil { - events := utils.GetSortedDirectory(files) - - // Get All days - days = utils.GetDays(events, recordingDirectory, configuration) - - // Get all latest events - var eventFilter models.EventFilter - eventFilter.NumberOfElements = 5 - latestEvents = utils.GetMediaFormatted(events, recordingDirectory, configuration, eventFilter) // will get 5 latest recordings. - } - - c.JSON(200, gin.H{ - "offlineMode": configuration.Config.Offline, - "cameraOnline": cameraIsOnline, - "cloudOnline": cloudIsOnline, - "numberOfRecordings": numberOfRecordings, - "days": days, - "latestEvents": latestEvents, - }) + components.GetDashboard(c, configDirectory, configuration, communication) }) api.POST("/latest-events", func(c *gin.Context) { - var eventFilter models.EventFilter - err := c.BindJSON(&eventFilter) - if err == nil { - // Default to 10 if no limit is set. - if eventFilter.NumberOfElements == 0 { - eventFilter.NumberOfElements = 10 - } - recordingDirectory := configDirectory + "/data/recordings" - files, err := utils.ReadDirectory(recordingDirectory) - if err == nil { - events := utils.GetSortedDirectory(files) - // We will get all recordings from the directory (as defined by the filter). - fileObjects := utils.GetMediaFormatted(events, recordingDirectory, configuration, eventFilter) - c.JSON(200, gin.H{ - "events": fileObjects, - }) - } else { - c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), - }) - } - } else { - c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), - }) - } + components.GetLatestEvents(c, configDirectory, configuration, communication) }) api.GET("/days", func(c *gin.Context) { - recordingDirectory := configDirectory + "/data/recordings" - files, err := utils.ReadDirectory(recordingDirectory) - if err == nil { - events := utils.GetSortedDirectory(files) - days := utils.GetDays(events, recordingDirectory, configuration) - c.JSON(200, gin.H{ - "events": days, - }) - } else { - c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), - }) - } + components.GetDays(c, configDirectory, configuration, communication) }) api.GET("/config", func(c *gin.Context) { - - // We'll try to get a snapshot from the camera. - base64Image := capture.Base64Image(captureDevice, communication) - if base64Image != "" { - communication.Image = base64Image - } - - c.JSON(200, gin.H{ - "config": configuration.Config, - "custom": configuration.CustomConfig, - "global": configuration.GlobalConfig, - "snapshot": communication.Image, - }) + components.GetConfig(c, captureDevice, configuration, communication) }) api.POST("/config", func(c *gin.Context) { - var config models.Config - err := c.BindJSON(&config) - if err == nil { - err := configService.SaveConfig(configDirectory, config, configuration, communication) - if err == nil { - c.JSON(200, gin.H{ - "data": "☄ Reconfiguring", - }) - } else { - c.JSON(200, gin.H{ - "data": "☄ Reconfiguring", - }) - } - } else { - c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), - }) - } - }) - - api.POST("/camera/restart", func(c *gin.Context) { - components.RestartAgent(c, communication) - }) - - api.POST("/camera/stop", func(c *gin.Context) { - components.StopAgent(c, communication) - }) - - api.POST("/camera/record", func(c *gin.Context) { - components.MakeRecording(c, communication) + components.UpdateConfig(c, configDirectory, configuration, communication) }) + // Will verify the current onvif settings. api.POST("/onvif/verify", func(c *gin.Context) { onvif.VerifyOnvifConnection(c) }) + // Will verify the current hub settings. api.POST("/hub/verify", func(c *gin.Context) { cloud.VerifyHub(c) }) + // Will verify the current persistence settings. api.POST("/persistence/verify", func(c *gin.Context) { cloud.VerifyPersistence(c, configDirectory) }) @@ -220,6 +72,19 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect // Camera specific methods. Doesn't require any authorization. // These are available for anyone, but require the agent, to reach // the camera. + + api.POST("/camera/restart", func(c *gin.Context) { + components.RestartAgent(c, communication) + }) + + api.POST("/camera/stop", func(c *gin.Context) { + components.StopAgent(c, communication) + }) + + api.POST("/camera/record", func(c *gin.Context) { + components.MakeRecording(c, communication) + }) + api.POST("/camera/onvif/login", LoginToOnvif) api.POST("/camera/onvif/capabilities", GetOnvifCapabilities) api.POST("/camera/onvif/presets", GetOnvifPresets) From b5f5567bcf4f604b24cc4dfeee3ca2d18e619cd7 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Tue, 12 Dec 2023 09:15:54 +0100 Subject: [PATCH 54/81] cleanup names of files (still need more cleanup)+ rework discover method + separated conditions in separate package --- machinery/main.go | 23 +++-- machinery/src/api/onvif.go | 1 - machinery/src/capture/main.go | 50 ++--------- .../cloud/{KerberosHub.go => kerberos_hub.go} | 0 .../{KerberosVault.go => kerberos_vault.go} | 0 machinery/src/components/Kerberos.go | 9 +- machinery/src/components/Onvif.go | 25 ------ machinery/src/components/Stream.go | 82 ----------------- .../components/{Audio.go => backchannel.go} | 0 machinery/src/computervision/main.go | 88 +++++++------------ machinery/src/conditions/timewindow.go | 38 ++++++++ machinery/src/conditions/uri.go | 15 ++++ .../{ApiResponse.go => api_response.go} | 0 .../models/{MotionData.go => motion_data.go} | 0 machinery/src/onvif/main.go | 18 ++++ .../{JWTMiddleware.go => jwt_middleware.go} | 0 16 files changed, 128 insertions(+), 221 deletions(-) delete mode 100644 machinery/src/api/onvif.go rename machinery/src/cloud/{KerberosHub.go => kerberos_hub.go} (100%) rename machinery/src/cloud/{KerberosVault.go => kerberos_vault.go} (100%) delete mode 100644 machinery/src/components/Onvif.go delete mode 100644 machinery/src/components/Stream.go rename machinery/src/components/{Audio.go => backchannel.go} (100%) create mode 100644 machinery/src/conditions/timewindow.go create mode 100644 machinery/src/conditions/uri.go rename machinery/src/models/{ApiResponse.go => api_response.go} (100%) rename machinery/src/models/{MotionData.go => motion_data.go} (100%) rename machinery/src/routers/http/{JWTMiddleware.go => jwt_middleware.go} (100%) diff --git a/machinery/main.go b/machinery/main.go index 3f98456b..d450533b 100644 --- a/machinery/main.go +++ b/machinery/main.go @@ -10,6 +10,7 @@ import ( "github.com/kerberos-io/agent/machinery/src/components" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" + "github.com/kerberos-io/agent/machinery/src/onvif" configService "github.com/kerberos-io/agent/machinery/src/config" "github.com/kerberos-io/agent/machinery/src/routers" @@ -74,21 +75,27 @@ func main() { switch action { case "version": - log.Log.Info("You are currrently running Kerberos Agent " + VERSION) + log.Log.Info("Main(): You are currrently running Kerberos Agent " + VERSION) case "discover": - log.Log.Info(timeout) + // Convert duration to int + timeout, err := time.ParseDuration(timeout + "ms") + if err != nil { + log.Log.Fatal("Main(): could not parse timeout: " + err.Error()) + return + } + onvif.Discover(timeout) case "decrypt": - log.Log.Info("Decrypting: " + flag.Arg(0) + " with key: " + flag.Arg(1)) + log.Log.Info("Main(): Decrypting: " + flag.Arg(0) + " with key: " + flag.Arg(1)) symmetricKey := []byte(flag.Arg(1)) if symmetricKey == nil || len(symmetricKey) == 0 { - log.Log.Fatal("Main: symmetric key should not be empty") + log.Log.Fatal("Main(): symmetric key should not be empty") return } if len(symmetricKey) != 32 { - log.Log.Fatal("Main: symmetric key should be 32 bytes") + log.Log.Fatal("Main(): symmetric key should be 32 bytes") return } @@ -133,9 +140,9 @@ func main() { configuration.Config.Key = key err := configService.StoreConfig(configDirectory, configuration.Config) if err == nil { - log.Log.Info("Main: updated unique key for agent to: " + key) + log.Log.Info("Main(): updated unique key for agent to: " + key) } else { - log.Log.Info("Main: something went wrong while trying to store key: " + key) + log.Log.Info("Main(): something went wrong while trying to store key: " + key) } } @@ -162,6 +169,6 @@ func main() { routers.StartWebserver(configDirectory, &configuration, &communication, &capture) } default: - log.Log.Error("Main: Sorry I don't understand :(") + log.Log.Error("Main(): Sorry I don't understand :(") } } diff --git a/machinery/src/api/onvif.go b/machinery/src/api/onvif.go deleted file mode 100644 index 778f64ec..00000000 --- a/machinery/src/api/onvif.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 0f1c8002..0c0bef74 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -10,6 +10,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/kerberos-io/agent/machinery/src/conditions" "github.com/kerberos-io/agent/machinery/src/encryption" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" @@ -55,9 +56,7 @@ func CleanupRecordingDirectory(configDirectory string, configuration *models.Con func HandleRecordStream(queue *packets.Queue, configDirectory string, configuration *models.Configuration, communication *models.Communication, rtspClient RTSPClient) { config := configuration.Config - - // Get the streams from the rtsp client. - //streams, _ := rtspClient.GetStreams() + loc, _ := time.LoadLocation(config.Timezone) if config.Capture.Recording == "false" { log.Log.Info("capture.HandleRecordStream(): disabled, we will not record anything.") @@ -86,7 +85,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Do not do anything! log.Log.Info("capture.HandleRecordStream() - continuous: Start continuous recording ") - loc, _ := time.LoadLocation(config.Timezone) now = time.Now().Unix() timestamp = now start := false @@ -99,7 +97,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat fullName := "" // Get as much packets we need. - //for pkt := range packets { var cursorError error var pkt packets.Packet var nextPkt packets.Packet @@ -145,10 +142,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Cleanup muxer start = false - //_, err = file.Write(cws.buf) - //if err != nil { - // log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) - //} file.Close() file = nil @@ -191,29 +184,11 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // If not yet started and a keyframe, let's make a recording if !start && pkt.IsKeyFrame { - // Check if within time interval - nowInTimezone := time.Now().In(loc) - weekday := nowInTimezone.Weekday() - hour := nowInTimezone.Hour() - minute := nowInTimezone.Minute() - second := nowInTimezone.Second() - timeEnabled := config.Time - timeInterval := config.Timetable[int(weekday)] - - if timeEnabled == "true" && timeInterval != nil { - start1 := timeInterval.Start1 - end1 := timeInterval.End1 - start2 := timeInterval.Start2 - end2 := timeInterval.End2 - currentTimeInSeconds := hour*60*60 + minute*60 + second - if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) || - (currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) { - - } else { - log.Log.Debug("capture.HandleRecordStream() - continuous: Disabled: no continuous recording at this moment. Not within specified time interval.") - time.Sleep(5 * time.Second) - continue - } + makeRecording := conditions.IsWithinTimeInterval(loc, configuration) + if !makeRecording { + log.Log.Debug("capture.HandleRecordStream() - continuous: Disabled: no continuous recording at this moment. Not within specified time interval.") + time.Sleep(5 * time.Second) + continue } start = true @@ -311,10 +286,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // Cleanup muxer start = false - //_, err = file.Write(cws.buf) - //if err != nil { - // log.Log.Info("capture.HandleRecordStream() - continuous: " + err.Error()) - //} file.Close() file = nil @@ -501,12 +472,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat lastDuration = pkt.Time lastRecordingTime = time.Now().Unix() - - // Cleanup muxer - //_, err := file.Write(cws.buf) - //if err != nil { - // panic(err) - //} file.Close() file = nil @@ -663,7 +628,6 @@ func Base64Image(captureDevice *Capture, communication *models.Communication) st break } } - return encodedImage } diff --git a/machinery/src/cloud/KerberosHub.go b/machinery/src/cloud/kerberos_hub.go similarity index 100% rename from machinery/src/cloud/KerberosHub.go rename to machinery/src/cloud/kerberos_hub.go diff --git a/machinery/src/cloud/KerberosVault.go b/machinery/src/cloud/kerberos_vault.go similarity index 100% rename from machinery/src/cloud/KerberosVault.go rename to machinery/src/cloud/kerberos_vault.go diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 56723bff..57375f70 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -198,14 +198,11 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu cameraSettings.SubRTSP != subRtspUrl || cameraSettings.Width != width || cameraSettings.Height != height { - //cameraSettings.Num != num || - //cameraSettings.Denum != denum || - //cameraSettings.Codec != videoStream.(av.VideoCodecData).Type() { // TODO: this condition is used to reset the decoder when the camera settings change. // The main idea is that you only set the decoder once, and then reuse it on each restart (no new memory allocation). // However the stream settings of the camera might have been changed, and so the decoder might need to be reloaded. - // .... + // .... Not used for the moment .... if cameraSettings.RTSP != "" && cameraSettings.SubRTSP != "" && cameraSettings.Initialized { //decoder.Close() @@ -226,10 +223,6 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu cameraSettings.SubRTSP = subRtspUrl cameraSettings.Width = width cameraSettings.Height = height - //cameraSettings.Framerate = float64(num) / float64(denum) - //cameraSettings.Num = num - //cameraSettings.Denum = denum - //cameraSettings.Codec = videoStream.(av.VideoCodecData).Type() cameraSettings.Initialized = true } else { log.Log.Info("components.Kerberos.RunAgent(): camera settings did not change, keeping decoder") diff --git a/machinery/src/components/Onvif.go b/machinery/src/components/Onvif.go deleted file mode 100644 index 9e8652f5..00000000 --- a/machinery/src/components/Onvif.go +++ /dev/null @@ -1,25 +0,0 @@ -package components - -import ( - "time" - - "github.com/cedricve/go-onvif" - "github.com/kerberos-io/agent/machinery/src/log" -) - -func Discover(timeout time.Duration) { - log.Log.Info("Discovering devices") - log.Log.Info("Waiting for " + (timeout * time.Second).String()) - devices, err := onvif.StartDiscovery(timeout * time.Second) - if err != nil { - log.Log.Error(err.Error()) - } else { - for _, device := range devices { - hostname, _ := device.GetHostname() - log.Log.Info(hostname.Name) - } - if len(devices) == 0 { - log.Log.Info("No devices descovered\n") - } - } -} diff --git a/machinery/src/components/Stream.go b/machinery/src/components/Stream.go deleted file mode 100644 index 435c684d..00000000 --- a/machinery/src/components/Stream.go +++ /dev/null @@ -1,82 +0,0 @@ -package components - -import ( - "fmt" - "log" - "time" - - "github.com/deepch/vdk/av" - "github.com/deepch/vdk/codec/h264parser" - "github.com/deepch/vdk/format/rtsp" -) - -type Stream struct { - Name string - Url string - Debug bool - Codecs string -} - -func CreateStream(name string, url string) *Stream { - return &Stream{ - Name: name, - Url: url, - } -} - -func (s Stream) Open() *rtsp.Client { - - // Enable debugging - if s.Debug { - rtsp.DebugRtsp = true - } - - fmt.Println("Dialing in to " + s.Url) - session, err := rtsp.Dial(s.Url) - if err != nil { - log.Println("Something went wrong dialing into stream: ", err) - time.Sleep(5 * time.Second) - } - session.RtpKeepAliveTimeout = 10 * time.Second - return session -} - -func (s Stream) Close(session *rtsp.Client) { - fmt.Println("Closing RTSP session.") - err := session.Close() - if err != nil { - log.Println("Something went wrong while closing your RTSP session: ", err) - } -} - -func (s Stream) GetCodecs() []av.CodecData { - session := s.Open() - codec, err := session.Streams() - log.Println("Reading codecs from stream: ", codec) - if err != nil { - log.Println("Something went wrong while reading codecs from stream: ", err) - time.Sleep(5 * time.Second) - } - s.Close(session) - return codec -} - -func (s Stream) ReadPackets(packetChannel chan av.Packet) { - session := s.Open() - for { - packet, err := session.ReadPacket() - if err != nil { - break - } - if len(packetChannel) < cap(packetChannel) { - packetChannel <- packet - } - } - s.Close(session) -} - -func GetSPSFromCodec(codecs []av.CodecData) ([]byte, []byte) { - sps := codecs[0].(h264parser.CodecData).SPS() - pps := codecs[0].(h264parser.CodecData).PPS() - return sps, pps -} diff --git a/machinery/src/components/Audio.go b/machinery/src/components/backchannel.go similarity index 100% rename from machinery/src/components/Audio.go rename to machinery/src/components/backchannel.go diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index d54742fa..e24dfa5e 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -7,6 +7,7 @@ import ( mqtt "github.com/eclipse/paho.mqtt.golang" geo "github.com/kellydunn/golang-geo" "github.com/kerberos-io/agent/machinery/src/capture" + "github.com/kerberos-io/agent/machinery/src/conditions" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/packets" @@ -16,6 +17,7 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf log.Log.Debug("ProcessMotion: started") config := configuration.Config + loc, _ := time.LoadLocation(config.Timezone) var isPixelChangeThresholdReached = false var changesToReturn = 0 @@ -104,7 +106,6 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf // Start the motion detection i := 0 - loc, _ := time.LoadLocation(config.Timezone) for cursorError == nil { pkt, cursorError = motionCursor.ReadPacket() @@ -121,69 +122,48 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf // Check if within time interval detectMotion := true - timeEnabled := config.Time - if timeEnabled != "false" { - now := time.Now().In(loc) - weekday := now.Weekday() - hour := now.Hour() - minute := now.Minute() - second := now.Second() - if config.Timetable != nil && len(config.Timetable) > 0 { - timeInterval := config.Timetable[int(weekday)] - if timeInterval != nil { - start1 := timeInterval.Start1 - end1 := timeInterval.End1 - start2 := timeInterval.Start2 - end2 := timeInterval.End2 - currentTimeInSeconds := hour*60*60 + minute*60 + second - if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) || - (currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) { - - } else { - detectMotion = false - log.Log.Info("ProcessMotion: Time interval not valid, disabling motion detection.") - } - } - } - } + detectMotion = conditions.IsWithinTimeInterval(loc, configuration) if config.Capture.Motion != "false" { - // Remember additional information about the result of findmotion - isPixelChangeThresholdReached, changesToReturn = FindMotion(imageArray, coordinatesToCheck, pixelThreshold) - if detectMotion && isPixelChangeThresholdReached { - - // If offline mode is disabled, send a message to the hub - if config.Offline != "true" { - if mqttClient != nil { - if hubKey != "" { - message := models.Message{ - Payload: models.Payload{ - Action: "motion", - DeviceId: configuration.Config.Key, - Value: map[string]interface{}{ - "timestamp": time.Now().Unix(), + if detectMotion { + + // Remember additional information about the result of findmotion + isPixelChangeThresholdReached, changesToReturn = FindMotion(imageArray, coordinatesToCheck, pixelThreshold) + if isPixelChangeThresholdReached { + + // If offline mode is disabled, send a message to the hub + if config.Offline != "true" { + if mqttClient != nil { + if hubKey != "" { + message := models.Message{ + Payload: models.Payload{ + Action: "motion", + DeviceId: configuration.Config.Key, + Value: map[string]interface{}{ + "timestamp": time.Now().Unix(), + }, }, - }, - } - payload, err := models.PackageMQTTMessage(configuration, message) - if err == nil { - mqttClient.Publish("kerberos/hub/"+hubKey, 0, false, payload) + } + payload, err := models.PackageMQTTMessage(configuration, message) + if err == nil { + mqttClient.Publish("kerberos/hub/"+hubKey, 0, false, payload) + } else { + log.Log.Info("ProcessMotion: failed to package MQTT message: " + err.Error()) + } } else { - log.Log.Info("ProcessMotion: failed to package MQTT message: " + err.Error()) + mqttClient.Publish("kerberos/agent/"+deviceKey, 2, false, "motion") } - } else { - mqttClient.Publish("kerberos/agent/"+deviceKey, 2, false, "motion") } } - } - if config.Capture.Recording != "false" { - dataToPass := models.MotionDataPartial{ - Timestamp: time.Now().Unix(), - NumberOfChanges: changesToReturn, + if config.Capture.Recording != "false" { + dataToPass := models.MotionDataPartial{ + Timestamp: time.Now().Unix(), + NumberOfChanges: changesToReturn, + } + communication.HandleMotion <- dataToPass //Save data to the channel } - communication.HandleMotion <- dataToPass //Save data to the channel } } diff --git a/machinery/src/conditions/timewindow.go b/machinery/src/conditions/timewindow.go new file mode 100644 index 00000000..e45a3dcb --- /dev/null +++ b/machinery/src/conditions/timewindow.go @@ -0,0 +1,38 @@ +package conditions + +import ( + "time" + + "github.com/kerberos-io/agent/machinery/src/log" + "github.com/kerberos-io/agent/machinery/src/models" +) + +func IsWithinTimeInterval(loc *time.Location, configuration *models.Configuration) bool { + config := configuration.Config + timeEnabled := config.Time + detectMotion := true + if timeEnabled != "false" { + now := time.Now().In(loc) + weekday := now.Weekday() + hour := now.Hour() + minute := now.Minute() + second := now.Second() + if config.Timetable != nil && len(config.Timetable) > 0 { + timeInterval := config.Timetable[int(weekday)] + if timeInterval != nil { + start1 := timeInterval.Start1 + end1 := timeInterval.End1 + start2 := timeInterval.Start2 + end2 := timeInterval.End2 + currentTimeInSeconds := hour*60*60 + minute*60 + second + if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) || + (currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) { + + } else { + log.Log.Info("ProcessMotion: Time interval not valid, disabling motion detection.") + } + } + } + } + return detectMotion +} diff --git a/machinery/src/conditions/uri.go b/machinery/src/conditions/uri.go new file mode 100644 index 00000000..a53baa6b --- /dev/null +++ b/machinery/src/conditions/uri.go @@ -0,0 +1,15 @@ +package conditions + +import ( + "github.com/kerberos-io/agent/machinery/src/models" +) + +func IsValidConditionResponse(configuration *models.Configuration) bool { + config := configuration.Config + conditionURI := config.ConditionURI + detectMotion := true + if conditionURI != "" { + + } + return detectMotion +} diff --git a/machinery/src/models/ApiResponse.go b/machinery/src/models/api_response.go similarity index 100% rename from machinery/src/models/ApiResponse.go rename to machinery/src/models/api_response.go diff --git a/machinery/src/models/MotionData.go b/machinery/src/models/motion_data.go similarity index 100% rename from machinery/src/models/MotionData.go rename to machinery/src/models/motion_data.go diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 2c381a11..cc19bb7b 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -10,6 +10,7 @@ import ( "strings" "time" + onvifc "github.com/cedricve/go-onvif" "github.com/gin-gonic/gin" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" @@ -20,6 +21,23 @@ import ( xsd "github.com/kerberos-io/onvif/xsd/onvif" ) +func Discover(timeout time.Duration) { + log.Log.Info("onvif.Discover(): Discovering devices") + log.Log.Info("Waiting for " + timeout.String()) + devices, err := onvifc.StartDiscovery(timeout) + if err != nil { + log.Log.Error("onvif.Discover(): " + err.Error()) + } else { + for _, device := range devices { + hostname, _ := device.GetHostname() + log.Log.Info("onvif.Discover(): " + hostname.Name + " (" + device.XAddr + ")") + } + if len(devices) == 0 { + log.Log.Info("onvif.Discover(): No devices descovered\n") + } + } +} + func HandleONVIFActions(configuration *models.Configuration, communication *models.Communication) { log.Log.Debug("onvif.HandleONVIFActions(): started") diff --git a/machinery/src/routers/http/JWTMiddleware.go b/machinery/src/routers/http/jwt_middleware.go similarity index 100% rename from machinery/src/routers/http/JWTMiddleware.go rename to machinery/src/routers/http/jwt_middleware.go From 15a51e7987b7ffe465e50113792b7e69948e498b Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Tue, 12 Dec 2023 09:52:35 +0100 Subject: [PATCH 55/81] align logging --- machinery/src/capture/Gortsplib.go | 72 ++++++++++++------------- machinery/src/capture/main.go | 87 +++++++++++++++--------------- 2 files changed, 78 insertions(+), 81 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 2a9ed8a1..d101c304 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -90,20 +90,20 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { // parse URL u, err := base.ParseURL(g.Url) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) return } // connect to the server err = g.Client.Start(u.Scheme, u.Host) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) } // find published medias desc, _, err := g.Client.Describe(u) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) return } @@ -116,14 +116,14 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.VideoH264Media = mediH264 g.VideoH264Forma = formaH264 if mediH264 == nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "video media not found") + log.Log.Debug("capture.golibrtsp.Connect(): " + "video media not found") } else { // Get SPS from the SDP // Calculate the width and height of the video var sps h264.SPS err = sps.Unmarshal(formaH264.SPS) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) return } @@ -169,14 +169,14 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.VideoH265Media = mediH265 g.VideoH265Forma = formaH265 if mediH265 == nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "video media not found") + log.Log.Info("capture.golibrtsp.Connect(): " + "video media not found") } else { // Get SPS from the SDP // Calculate the width and height of the video var sps h265.SPS err = sps.Unmarshal(formaH265.SPS) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + err.Error()) + log.Log.Info("capture.golibrtsp.Connect(): " + err.Error()) return } @@ -223,7 +223,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.AudioG711Media = audioMedi g.AudioG711Forma = audioForma if audioMedi == nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio media not found") + log.Log.Info("capture.golibrtsp.Connect(): " + "audio media not found") } else { g.Streams = append(g.Streams, packets.Stream{ @@ -256,7 +256,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.AudioMPEG4Media = audioMediMPEG4 g.AudioMPEG4Forma = audioFormaMPEG4 if audioMediMPEG4 == nil { - log.Log.Debug("RTSPClient(Golibrtsp).Connect(): " + "audio media not found") + log.Log.Info("capture.golibrtsp.Connect(): " + "audio media not found") } else { g.Streams = append(g.Streams, packets.Stream{ Name: "AAC", @@ -295,20 +295,20 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { // parse URL u, err := base.ParseURL(g.Url) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + err.Error()) + log.Log.Error("capture.golibrtsp.ConnectBackChannel(): " + err.Error()) return } // connect to the server err = g.Client.Start(u.Scheme, u.Host) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + err.Error()) + log.Log.Error("capture.golibrtsp.ConnectBackChannel(): " + err.Error()) } // find published medias desc, _, err := g.Client.Describe(u) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + err.Error()) + log.Log.Error("capture.golibrtsp.ConnectBackChannel(): " + err.Error()) return } @@ -319,7 +319,7 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { g.AudioG711MediaBackChannel = audioMediBackChannel g.AudioG711FormaBackChannel = audioFormaBackChannel if audioMediBackChannel == nil { - log.Log.Debug("RTSPClient(Golibrtsp).ConnectBackChannel(): " + "audio backchannel not found") + log.Log.Info("capture.golibrtsp.ConnectBackChannel(): " + "audio backchannel not found") err = errors.New("no audio backchannel found") } else { g.Streams = append(g.Streams, packets.Stream{ @@ -345,7 +345,7 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { // Start the RTSP client, and start reading packets. func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) (err error) { - log.Log.Debug("RTSPClient(Golibrtsp).Start(): started") + log.Log.Debug("capture.golibrtsp.Start(): started") // called when a MULAW audio RTP packet arrives if g.AudioG711Media != nil && g.AudioG711Forma != nil { @@ -353,14 +353,14 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // decode timestamp pts, ok := g.Client.PacketPTS(g.AudioG711Media, rtppkt) if !ok { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + log.Log.Warning("capture.golibrtsp.Start(): " + "unable to get PTS") return } // extract LPCM samples from RTP packets op, err := g.AudioG711Decoder.Decode(rtppkt) if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + log.Log.Error("capture.golibrtsp.Start(): " + err.Error()) return } @@ -385,7 +385,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // decode timestamp pts, ok := g.Client.PacketPTS(g.AudioMPEG4Media, rtppkt) if !ok { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + log.Log.Error("capture.golibrtsp.Start(): " + "unable to get PTS") return } @@ -393,13 +393,13 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // extract access units from RTP packets aus, err := g.AudioMPEG4Decoder.Decode(rtppkt) if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + log.Log.Error("capture.golibrtsp.Start(): " + err.Error()) return } enc, err := WriteMPEG4Audio(g.AudioMPEG4Forma, aus) if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + log.Log.Error("capture.golibrtsp.Start(): " + err.Error()) return } @@ -436,7 +436,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // decode timestamp pts, ok := g.Client.PacketPTS(g.VideoH264Media, rtppkt) if !ok { - log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + log.Log.Warning("capture.golibrtsp.Start(): " + "unable to get PTS") return } @@ -447,7 +447,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati au, errDecode := g.VideoH264Decoder.Decode(rtppkt) if errDecode != nil { if errDecode != rtph264.ErrNonStartingPacketAndNoPrevious && errDecode != rtph264.ErrMorePacketsNeeded { - log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + errDecode.Error()) + log.Log.Error("capture.golibrtsp.Start(): " + errDecode.Error()) } return } @@ -481,7 +481,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // Convert to packet. enc, err := h264.AnnexBMarshal(filteredAU) if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + log.Log.Error("capture.golibrtsp.Start(): " + err.Error()) return } @@ -521,7 +521,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // Increment packets, so we know the device // is not blocking. r := communication.PackageCounter.Load().(int64) - log.Log.Info("RTSPClient(Golibrtsp).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data))) communication.PackageCounter.Store((r + 1) % 1000) communication.LastPacketTimer.Store(time.Now().Unix()) } @@ -547,7 +547,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // decode timestamp pts, ok := g.Client.PacketPTS(g.VideoH265Media, rtppkt) if !ok { - log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + "unable to get PTS") + log.Log.Warning("capture.golibrtsp.Start(): " + "unable to get PTS") return } @@ -558,7 +558,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati au, errDecode := g.VideoH265Decoder.Decode(rtppkt) if errDecode != nil { if errDecode != rtph265.ErrNonStartingPacketAndNoPrevious && errDecode != rtph265.ErrMorePacketsNeeded { - log.Log.Warning("RTSPClient(Golibrtsp).Start(): " + errDecode.Error()) + log.Log.Error("capture.golibrtsp.Start(): " + errDecode.Error()) } return } @@ -601,7 +601,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati enc, err := h264.AnnexBMarshal(au) if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).Start(): " + err.Error()) + log.Log.Error("capture.golibrtsp.Start(): " + err.Error()) return } @@ -631,7 +631,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // Increment packets, so we know the device // is not blocking. r := communication.PackageCounter.Load().(int64) - log.Log.Info("RTSPClient(Golibrtsp).Start(): packet size " + strconv.Itoa(len(pkt.Data))) + log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data))) communication.PackageCounter.Store((r + 1) % 1000) communication.LastPacketTimer.Store(time.Now().Unix()) } @@ -653,13 +653,13 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // Start the RTSP client, and start reading packets. func (g *Golibrtsp) StartBackChannel(ctx context.Context) (err error) { - log.Log.Info("RTSPClient(Golibrtsp).StartBackChannel(): started") + log.Log.Info("capture.golibrtsp.StartBackChannel(): started") // Wait for a second, so we can be sure the stream is playing. time.Sleep(1 * time.Second) // Play the stream. _, err = g.Client.Play(nil) if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).StartBackChannel(): " + err.Error()) + log.Log.Error("capture.golibrtsp.StartBackChannel(): " + err.Error()) } return } @@ -668,7 +668,7 @@ func (g *Golibrtsp) WritePacket(pkt packets.Packet) error { if g.HasBackChannel && g.AudioG711MediaBackChannel != nil { err := g.Client.WritePacketRTP(g.AudioG711MediaBackChannel, pkt.Packet) if err != nil { - log.Log.Debug("RTSPClient(Golibrtsp).WritePacket(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.WritePacket(): " + err.Error()) return err } } @@ -691,11 +691,11 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) { } g.VideoDecoderMutex.Unlock() if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).DecodePacket(): " + err.Error()) + log.Log.Error("capture.golibrtsp.DecodePacket(): " + err.Error()) return image.YCbCr{}, err } if img.Bounds().Empty() { - log.Log.Debug("RTSPClient(Golibrtsp).DecodePacket(): empty frame") + log.Log.Debug("capture.golibrtsp.DecodePacket(): empty frame") return image.YCbCr{}, errors.New("Empty image") } return img, nil @@ -707,21 +707,21 @@ func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) { var err error g.VideoDecoderMutex.Lock() if len(pkt.Data) == 0 { - err = errors.New("RTSPClient(Golibrtsp).DecodePacketRaw(): empty frame") + err = errors.New("capture.golibrtsp.DecodePacketRaw(): empty frame") } else if g.VideoH264Decoder != nil { img, err = g.VideoH264FrameDecoder.decodeRaw(pkt.Data) } else if g.VideoH265Decoder != nil { img, err = g.VideoH265FrameDecoder.decodeRaw(pkt.Data) } else { - err = errors.New("RTSPClient(Golibrtsp).DecodePacketRaw(): no decoder found, might already be closed") + err = errors.New("capture.golibrtsp.DecodePacketRaw(): no decoder found, might already be closed") } g.VideoDecoderMutex.Unlock() if err != nil { - log.Log.Error("RTSPClient(Golibrtsp).DecodePacketRaw(): " + err.Error()) + log.Log.Error("capture.golibrtsp.DecodePacketRaw(): " + err.Error()) return image.Gray{}, err } if img.Bounds().Empty() { - log.Log.Debug("RTSPClient(Golibrtsp).DecodePacketRaw(): empty image") + log.Log.Debug("capture.golibrtsp.DecodePacketRaw(): empty image") return image.Gray{}, errors.New("Empty image") } diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 0c0bef74..970009dc 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -59,9 +59,9 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat loc, _ := time.LoadLocation(config.Timezone) if config.Capture.Recording == "false" { - log.Log.Info("capture.HandleRecordStream(): disabled, we will not record anything.") + log.Log.Info("capture.main.HandleRecordStream(): disabled, we will not record anything.") } else { - log.Log.Debug("capture.HandleRecordStream(): started") + log.Log.Debug("capture.main.HandleRecordStream(): started") recordingPeriod := config.Capture.PostRecording // number of seconds to record. maxRecordingPeriod := config.Capture.MaxLengthRecording // maximum number of seconds to record. @@ -83,7 +83,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat var audioTrack uint32 // Do not do anything! - log.Log.Info("capture.HandleRecordStream() - continuous: Start continuous recording ") + log.Log.Info("capture.main.HandleRecordStream(continuous): start recording") now = time.Now().Unix() timestamp = now @@ -120,25 +120,25 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat ttime := convertPTS(pkt.Time) if pkt.IsVideo { if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } } else if pkt.IsAudio { if pkt.Codec == "AAC" { if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("capture.HandleRecordStream() - continuous: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.main.HandleRecordStream(continuous): no AAC audio codec detected, skipping audio track.") } } // This will write the trailer a well. if err := myMuxer.WriteTrailer(); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } - log.Log.Info("capture.HandleRecordStream() - continuous: Recording finished: file save: " + name) + log.Log.Info("capture.main.HandleRecordStream(continuous): recording finished: file save: " + name) // Cleanup muxer start = false @@ -161,13 +161,13 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // write back to file err := os.WriteFile(fullName, []byte(encryptedContents), 0644) if err != nil { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error writing file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): error writing file: " + err.Error()) } } else { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error encrypting file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): error encrypting file: " + err.Error()) } } else { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error reading file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): error reading file: " + err.Error()) } } @@ -186,7 +186,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat makeRecording := conditions.IsWithinTimeInterval(loc, configuration) if !makeRecording { - log.Log.Debug("capture.HandleRecordStream() - continuous: Disabled: no continuous recording at this moment. Not within specified time interval.") + log.Log.Debug("capture.main.HandleRecordStream(continuous): no continuous recording at this moment, as not within specified time interval.") time.Sleep(5 * time.Second) continue } @@ -215,7 +215,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat fullName = configDirectory + "/data/recordings/" + name // Running... - log.Log.Info("capture.HandleRecordStream() - continuous: Recording started") + log.Log.Info("capture.main.HandleRecordStream(continuous): recording started") file, err = os.Create(fullName) if err == nil { @@ -229,24 +229,23 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } // For an MP4 container, AAC is the only audio codec supported. audioTrack = myMuxer.AddAudioTrack(mp4.MP4_CODEC_AAC) + } else { + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } - log.Log.Info("capture.HandleRecordStream() - continuous: composing recording") - log.Log.Info("capture.HandleRecordStream() - continuous: write header") - ttime := convertPTS(pkt.Time) if pkt.IsVideo { if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } } else if pkt.IsAudio { if pkt.Codec == "AAC" { if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("capture.HandleRecordStream() - continuous: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.main.HandleRecordStream(continuous): no AAC audio codec detected, skipping audio track.") } } @@ -256,16 +255,16 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat ttime := convertPTS(pkt.Time) if pkt.IsVideo { if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } } else if pkt.IsAudio { if pkt.Codec == "AAC" { if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(continuous): " + err.Error()) } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("capture.HandleRecordStream() - continuous: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.main.HandleRecordStream(continuous): no AAC audio codec detected, skipping audio track.") } } } @@ -282,7 +281,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat log.Log.Error(err.Error()) } - log.Log.Info("capture.HandleRecordStream() - continuous: Recording finished: file save: " + name) + log.Log.Info("capture.main.HandleRecordStream(continuous): Recording finished: file save: " + name) // Cleanup muxer start = false @@ -305,13 +304,13 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // write back to file err := os.WriteFile(fullName, []byte(encryptedContents), 0644) if err != nil { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error writing file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): error writing file: " + err.Error()) } } else { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error encrypting file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): error encrypting file: " + err.Error()) } } else { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error reading file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): error reading file: " + err.Error()) } } @@ -327,7 +326,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } } else { - log.Log.Info("capture.HandleRecordStream() - motiondetection: Start motion based recording ") + log.Log.Info("capture.main.HandleRecordStream(motiondetection): Start motion based recording ") var lastDuration time.Duration var lastRecordingTime int64 @@ -379,7 +378,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat fullName := configDirectory + "/data/recordings/" + name // Running... - log.Log.Info("HandleRecordStream: Recording started") + log.Log.Info("capture.main.HandleRecordStream(motiondetection): recording started") file, _ = os.Create(fullName) myMuxer, _ = mp4.CreateMp4Muxer(file) @@ -394,9 +393,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } // For an MP4 container, AAC is the only audio codec supported. audioTrack = myMuxer.AddAudioTrack(mp4.MP4_CODEC_AAC) - start := false - log.Log.Info("capture.HandleRecordStream() - motiondetection: composing recording") // Get as much packets we need. var cursorError error @@ -412,25 +409,25 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat nextPkt, cursorError = recordingCursor.ReadPacket() if cursorError != nil { - log.Log.Error("capture.HandleRecordStream() - motiondetection: " + cursorError.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): " + cursorError.Error()) } now := time.Now().Unix() select { case motion := <-communication.HandleMotion: timestamp = now - log.Log.Info("capture.HandleRecordStream() - motiondetection: motion detected while recording. Expanding recording.") + log.Log.Info("capture.main.HandleRecordStream(motiondetection): motion detected while recording. Expanding recording.") numberOfChanges = motion.NumberOfChanges - log.Log.Info("capture.HandleRecordStream() - motiondetection: Received message with recording data, detected changes to save: " + strconv.Itoa(numberOfChanges)) + log.Log.Info("capture.main.HandleRecordStream(motiondetection): Received message with recording data, detected changes to save: " + strconv.Itoa(numberOfChanges)) default: } if (timestamp+recordingPeriod-now < 0 || now-startRecording > maxRecordingPeriod) && nextPkt.IsKeyFrame { - log.Log.Info("capture.HandleRecordStream() - motiondetection: closing recording (timestamp: " + strconv.FormatInt(timestamp, 10) + ", recordingPeriod: " + strconv.FormatInt(recordingPeriod, 10) + ", now: " + strconv.FormatInt(now, 10) + ", startRecording: " + strconv.FormatInt(startRecording, 10) + ", maxRecordingPeriod: " + strconv.FormatInt(maxRecordingPeriod, 10)) + log.Log.Info("capture.main.HandleRecordStream(motiondetection): closing recording (timestamp: " + strconv.FormatInt(timestamp, 10) + ", recordingPeriod: " + strconv.FormatInt(recordingPeriod, 10) + ", now: " + strconv.FormatInt(now, 10) + ", startRecording: " + strconv.FormatInt(startRecording, 10) + ", maxRecordingPeriod: " + strconv.FormatInt(maxRecordingPeriod, 10)) break } if pkt.IsKeyFrame && !start && pkt.Time >= lastDuration { - log.Log.Info("capture.HandleRecordStream() - motiondetection: write frames") + log.Log.Debug("capture.main.HandleRecordStream(motiondetection): write frames") start = true } if start { @@ -438,16 +435,16 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat ttime := convertPTS(pkt.Time) if pkt.IsVideo { if err := myMuxer.Write(videoTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): " + err.Error()) } } else if pkt.IsAudio { if pkt.Codec == "AAC" { if err := myMuxer.Write(audioTrack, pkt.Data, ttime, ttime); err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): " + err.Error()) } } else if pkt.Codec == "PCM_MULAW" { // TODO: transcode to AAC, some work to do.. - log.Log.Debug("capture.HandleRecordStream() - motiondetection: no AAC audio codec detected, skipping audio track.") + log.Log.Debug("capture.main.HandleRecordStream(motiondetection): no AAC audio codec detected, skipping audio track.") } } @@ -455,9 +452,9 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat if pkt.IsKeyFrame { err := file.Sync() if err != nil { - log.Log.Error(err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): " + err.Error()) } else { - log.Log.Info("capture.HandleRecordStream() - motiondetection: Synced file: " + name) + log.Log.Debug("capture.main.HandleRecordStream(motiondetection): synced file " + name) } } } @@ -468,7 +465,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // This will write the trailer a well. myMuxer.WriteTrailer() - log.Log.Info("capture.HandleRecordStream() - motiondetection: file save: " + name) + log.Log.Info("capture.main.HandleRecordStream(motiondetection): file save: " + name) lastDuration = pkt.Time lastRecordingTime = time.Now().Unix() @@ -491,13 +488,13 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // write back to file err := os.WriteFile(fullName, []byte(encryptedContents), 0644) if err != nil { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error writing file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): error writing file: " + err.Error()) } } else { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error encrypting file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): error encrypting file: " + err.Error()) } } else { - log.Log.Error("capture.HandleRecordStream() - motiondetection: error reading file: " + err.Error()) + log.Log.Error("capture.main.HandleRecordStream(motiondetection): error reading file: " + err.Error()) } } @@ -510,7 +507,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat } } - log.Log.Debug("capture.HandleRecordStream(): finished") + log.Log.Debug("capture.main.HandleRecordStream(): finished") } } From b0bcf73b5273710e495661084e91b271fa1af54d Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Tue, 12 Dec 2023 17:30:41 +0100 Subject: [PATCH 56/81] add condition uri implementation, wrapped condition class so it's easier to extend --- machinery/src/capture/main.go | 11 +++--- machinery/src/computervision/main.go | 19 +++++----- machinery/src/conditions/main.go | 28 +++++++++++++++ machinery/src/conditions/timewindow.go | 11 +++--- machinery/src/conditions/uri.go | 50 ++++++++++++++++++++++++-- 5 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 machinery/src/conditions/main.go diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 970009dc..9d0a1aeb 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -81,6 +81,7 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat var myMuxer *mp4.Movmuxer var videoTrack uint32 var audioTrack uint32 + var name string // Do not do anything! log.Log.Info("capture.main.HandleRecordStream(continuous): start recording") @@ -88,8 +89,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat now = time.Now().Unix() timestamp = now start := false - var name string - var err error // If continuous record the full length recordingPeriod = maxRecordingPeriod @@ -184,9 +183,11 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat // If not yet started and a keyframe, let's make a recording if !start && pkt.IsKeyFrame { - makeRecording := conditions.IsWithinTimeInterval(loc, configuration) - if !makeRecording { - log.Log.Debug("capture.main.HandleRecordStream(continuous): no continuous recording at this moment, as not within specified time interval.") + // We might have different conditions enabled such as time window or uri response. + // We'll validate those conditions and if not valid we'll not do anything. + valid, err := conditions.Validate(loc, configuration) + if !valid && err != nil { + log.Log.Debug("capture.main.HandleRecordStream(continuous): " + err.Error() + ".") time.Sleep(5 * time.Second) continue } diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index e24dfa5e..8f14f468 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -15,7 +15,7 @@ import ( func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, rtspClient capture.RTSPClient) { - log.Log.Debug("ProcessMotion: started") + log.Log.Debug("computervision.main.ProcessMotion(): start motion detection") config := configuration.Config loc, _ := time.LoadLocation(config.Timezone) @@ -30,11 +30,11 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf if config.Capture.Continuous == "true" { - log.Log.Info("ProcessMotion: Continuous recording, so no motion detection.") + log.Log.Info("computervision.main.ProcessMotion(): you've enabled continuous recording, so no motion detection required.") } else { - log.Log.Info("ProcessMotion: Motion detection enabled.") + log.Log.Info("computervision.main.ProcessMotion(): motion detected is enabled, so starting the motion detection.") hubKey := config.HubKey deviceKey := config.Key @@ -120,9 +120,12 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf imageArray[2] = &grayImage } - // Check if within time interval - detectMotion := true - detectMotion = conditions.IsWithinTimeInterval(loc, configuration) + // We might have different conditions enabled such as time window or uri response. + // We'll validate those conditions and if not valid we'll not do anything. + detectMotion, err := conditions.Validate(loc, configuration) + if !detectMotion && err != nil { + log.Log.Debug("computervision.main.ProcessMotion(): " + err.Error() + ".") + } if config.Capture.Motion != "false" { @@ -149,7 +152,7 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf if err == nil { mqttClient.Publish("kerberos/hub/"+hubKey, 0, false, payload) } else { - log.Log.Info("ProcessMotion: failed to package MQTT message: " + err.Error()) + log.Log.Info("computervision.main.ProcessMotion(): failed to package MQTT message: " + err.Error()) } } else { mqttClient.Publish("kerberos/agent/"+deviceKey, 2, false, "motion") @@ -179,7 +182,7 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf } } - log.Log.Debug("ProcessMotion: finished") + log.Log.Debug("computervision.main.ProcessMotion(): stop the motion detection.") } func FindMotion(imageArray [3]*image.Gray, coordinatesToCheck []int, pixelChangeThreshold int) (thresholdReached bool, changesDetected int) { diff --git a/machinery/src/conditions/main.go b/machinery/src/conditions/main.go new file mode 100644 index 00000000..e2bf2f63 --- /dev/null +++ b/machinery/src/conditions/main.go @@ -0,0 +1,28 @@ +package conditions + +import ( + "errors" + "time" + + "github.com/kerberos-io/agent/machinery/src/models" +) + +func Validate(loc *time.Location, configuration *models.Configuration) (valid bool, err error) { + valid = true + err = nil + + withinTimeInterval := IsWithinTimeInterval(loc, configuration) + if !withinTimeInterval { + valid = false + err = errors.New("time interval not valid") + return + } + validUriResponse := IsValidUriResponse(configuration) + if !validUriResponse { + valid = false + err = errors.New("uri response not valid") + return + } + + return +} diff --git a/machinery/src/conditions/timewindow.go b/machinery/src/conditions/timewindow.go index e45a3dcb..1c90076f 100644 --- a/machinery/src/conditions/timewindow.go +++ b/machinery/src/conditions/timewindow.go @@ -7,10 +7,10 @@ import ( "github.com/kerberos-io/agent/machinery/src/models" ) -func IsWithinTimeInterval(loc *time.Location, configuration *models.Configuration) bool { +func IsWithinTimeInterval(loc *time.Location, configuration *models.Configuration) (enabled bool) { config := configuration.Config timeEnabled := config.Time - detectMotion := true + enabled = true if timeEnabled != "false" { now := time.Now().In(loc) weekday := now.Weekday() @@ -27,12 +27,13 @@ func IsWithinTimeInterval(loc *time.Location, configuration *models.Configuratio currentTimeInSeconds := hour*60*60 + minute*60 + second if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) || (currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) { - + log.Log.Info("conditions.timewindow.IsWithinTimeInterval(): time interval valid, enabling recording.") } else { - log.Log.Info("ProcessMotion: Time interval not valid, disabling motion detection.") + log.Log.Info("conditions.timewindow.IsWithinTimeInterval(): time interval not valid, disabling recording.") + enabled = false } } } } - return detectMotion + return } diff --git a/machinery/src/conditions/uri.go b/machinery/src/conditions/uri.go index a53baa6b..934b6e26 100644 --- a/machinery/src/conditions/uri.go +++ b/machinery/src/conditions/uri.go @@ -1,15 +1,59 @@ package conditions import ( + "bytes" + "crypto/tls" + "fmt" + "net/http" + "os" + "time" + + "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" ) -func IsValidConditionResponse(configuration *models.Configuration) bool { +func IsValidUriResponse(configuration *models.Configuration) (enabled bool) { config := configuration.Config conditionURI := config.ConditionURI - detectMotion := true + enabled = true if conditionURI != "" { + // We will send a POST request to the conditionURI, and expect a 200 response. + // In the payload we will send some information, so the other end can decide + // if it should enable or disable recording. + + 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{} + } + + var object = fmt.Sprintf(`{ + "camera_id" : "%s", + "camera_name" : "%s", + "site_id" : "%s", + "hub_key" : "%s", + "timestamp" : "%s", + }`, config.Key, config.FriendlyName, config.HubSite, config.HubKey, time.Now().Format("2006-01-02 15:04:05")) + + var jsonStr = []byte(object) + buffy := bytes.NewBuffer(jsonStr) + req, _ := http.NewRequest("POST", conditionURI, 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("conditions.uri.IsValidUriResponse(): response 200, enabling recording.") + } else { + log.Log.Info("conditions.uri.IsValidUriResponse(): response not 200, disabling recording.") + enabled = false + } } - return detectMotion + return } From 0e8a89c4c33c7d35592b7a8f3bd6e83e0b886c11 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Tue, 12 Dec 2023 23:34:04 +0100 Subject: [PATCH 57/81] add onvif inputs function --- machinery/docs/docs.go | 34 +++++++++++++++++ machinery/docs/swagger.json | 34 +++++++++++++++++ machinery/docs/swagger.yaml | 21 ++++++++++ machinery/go.mod | 2 - machinery/go.sum | 4 -- machinery/src/onvif/main.go | 30 +++++++++++++++ machinery/src/routers/http/Methods.go | 55 +++++++++++++++++++++++++++ machinery/src/routers/http/Routes.go | 1 + 8 files changed, 175 insertions(+), 6 deletions(-) diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index 0f85db49..4da1670f 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -83,6 +83,40 @@ const docTemplate = `{ } } }, + "/api/camera/onvif/inputs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Will get the digital inputs from the ONVIF device.", + "tags": [ + "camera" + ], + "summary": "Will get the digital inputs from the ONVIF device.", + "operationId": "get-digital-inputs", + "parameters": [ + { + "description": "Camera Config", + "name": "cameraConfig", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.IPCamera" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, "/api/camera/onvif/login": { "post": { "description": "Try to login into ONVIF supported camera.", diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index 5ca04444..cc96c143 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -75,6 +75,40 @@ } } }, + "/api/camera/onvif/inputs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Will get the digital inputs from the ONVIF device.", + "tags": [ + "camera" + ], + "summary": "Will get the digital inputs from the ONVIF device.", + "operationId": "get-digital-inputs", + "parameters": [ + { + "description": "Camera Config", + "name": "cameraConfig", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.IPCamera" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, "/api/camera/onvif/login": { "post": { "description": "Try to login into ONVIF supported camera.", diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index f9ffdae1..6033d7cb 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -369,6 +369,27 @@ paths: summary: Will activate the desired ONVIF preset. tags: - camera + /api/camera/onvif/inputs: + post: + description: Will get the digital inputs from the ONVIF device. + operationId: get-digital-inputs + parameters: + - description: Camera Config + in: body + name: cameraConfig + required: true + schema: + $ref: '#/definitions/models.IPCamera' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.APIResponse' + security: + - Bearer: [] + summary: Will get the digital inputs from the ONVIF device. + tags: + - camera /api/camera/onvif/login: post: description: Try to login into ONVIF supported camera. diff --git a/machinery/go.mod b/machinery/go.mod index 0b86e301..10585326 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -12,7 +12,6 @@ require ( github.com/bluenviron/gortsplib/v4 v4.6.1 github.com/bluenviron/mediacommon v1.5.1 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 github.com/eclipse/paho.mqtt.golang v1.4.2 github.com/elastic/go-sysinfo v1.9.0 @@ -28,7 +27,6 @@ require ( github.com/kerberos-io/joy4 v1.0.64 github.com/kerberos-io/onvif v0.0.9 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.8.3 github.com/pion/webrtc/v3 v3.1.50 diff --git a/machinery/go.sum b/machinery/go.sum index 8cd94df9..e7dd229c 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -96,8 +96,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deepch/vdk v0.0.19 h1:r6xYyBTtXEIEh+csO0XHT00sI7xLF+hQFkJE9/go5II= -github.com/deepch/vdk v0.0.19/go.mod h1:7ydHfSkflMZxBXfWR79dMjrT54xzvLxnPaByOa9Jpzg= github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= @@ -333,8 +331,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e h1:bQo/jQ9qvcw7zqnovm8IbLsaOq3F+ELUQcxtxvalQvA= -github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e/go.mod h1:PW9xCZScEClMBP22n37i0SnN/8B9YzNXTNvOaIkLjv0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index cc19bb7b..c790a386 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -909,6 +909,36 @@ func VerifyOnvifConnection(c *gin.Context) { } } +func GetDigitalInputs(device *onvif.Device) (ptz.GetConfigurationsResponse, error) { + // We'll try to receive the PTZ configurations from the server + var configurations ptz.GetConfigurationsResponse + + // Get the PTZ configurations from the device + resp, err := device.CallMethod(ptz.GetConfigurations{}) + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } + + if err == nil { + if err == nil { + stringBody := string(b) + decodedXML, et, err := getXMLNode(stringBody, "GetConfigurationsResponse") + if err != nil { + log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error()) + return configurations, err + } else { + if err := decodedXML.DecodeElement(&configurations, et); err != nil { + log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error()) + return configurations, err + } + } + } + } + return configurations, err +} + func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, error) { xmlBytes := bytes.NewBufferString(xmlBody) decodedXML := xml.NewDecoder(xmlBytes) diff --git a/machinery/src/routers/http/Methods.go b/machinery/src/routers/http/Methods.go index fa0aaa7a..f0faf815 100644 --- a/machinery/src/routers/http/Methods.go +++ b/machinery/src/routers/http/Methods.go @@ -352,3 +352,58 @@ func GoToOnvifPreset(c *gin.Context) { }) } } + +// DoGetDigitalInputs godoc +// @Router /api/camera/onvif/inputs [post] +// @ID get-digital-inputs +// @Security Bearer +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization +// @Tags camera +// @Param cameraConfig body models.IPCamera true "Camera Config" +// @Summary Will get the digital inputs from the ONVIF device. +// @Description Will get the digital inputs from the ONVIF device. +// @Success 200 {object} models.APIResponse +func DoGetDigitalInputs(c *gin.Context) { + var onvifPreset models.OnvifPreset + err := c.BindJSON(&onvifPreset) + + if err == nil || onvifPreset.OnvifCredentials.ONVIFXAddr != "" { + + configuration := &models.Configuration{ + Config: models.Config{ + Capture: models.Capture{ + IPCamera: models.IPCamera{ + ONVIFXAddr: onvifPreset.OnvifCredentials.ONVIFXAddr, + ONVIFUsername: onvifPreset.OnvifCredentials.ONVIFUsername, + ONVIFPassword: onvifPreset.OnvifCredentials.ONVIFPassword, + }, + }, + }, + } + + cameraConfiguration := configuration.Config.Capture.IPCamera + device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + if err == nil { + inputs, err := onvif.GetDigitalInputs(device) + if err == nil { + c.JSON(200, gin.H{ + "data": inputs, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } +} diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/Routes.go index 41f74c37..3d47bbf7 100644 --- a/machinery/src/routers/http/Routes.go +++ b/machinery/src/routers/http/Routes.go @@ -91,6 +91,7 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect api.POST("/camera/onvif/gotopreset", GoToOnvifPreset) api.POST("/camera/onvif/pantilt", DoOnvifPanTilt) api.POST("/camera/onvif/zoom", DoOnvifZoom) + api.POST("/camera/onvif/inputs", DoGetDigitalInputs) api.POST("/camera/verify/:streamType", capture.VerifyCamera) // Secured endpoints.. From 698b9c6b541f35444b4db7152a9ac4ab24f8c573 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 15 Dec 2023 15:07:25 +0100 Subject: [PATCH 58/81] cleanup comments + add ouputs --- README.md | 25 ++++---- machinery/docs/docs.go | 2 +- machinery/docs/swagger.json | 2 +- machinery/docs/swagger.yaml | 2 +- machinery/main.go | 29 +++++---- machinery/src/encryption/main.go | 22 ------- machinery/src/log/main.go | 20 +++++-- machinery/src/models/output.go | 15 +++++ machinery/src/onvif/main.go | 58 +++++++++++++----- machinery/src/outputs/main.go | 59 +++++++++++++++++++ machinery/src/outputs/onvif_relay.go | 12 ++++ machinery/src/outputs/script.go | 12 ++++ machinery/src/outputs/slack.go | 12 ++++ machinery/src/outputs/webhook.go | 12 ++++ machinery/src/packets/packet.go | 5 +- .../routers/http/{Methods.go => methods.go} | 6 +- .../src/routers/http/{Routes.go => routes.go} | 0 17 files changed, 218 insertions(+), 75 deletions(-) create mode 100644 machinery/src/models/output.go create mode 100644 machinery/src/outputs/main.go create mode 100644 machinery/src/outputs/onvif_relay.go create mode 100644 machinery/src/outputs/script.go create mode 100644 machinery/src/outputs/slack.go create mode 100644 machinery/src/outputs/webhook.go rename machinery/src/routers/http/{Methods.go => methods.go} (98%) rename machinery/src/routers/http/{Routes.go => routes.go} (100%) diff --git a/README.md b/README.md index b20ac8be..019adf9e 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ 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 transform to a valid RTSP H264 stream](https://github.com/kerberos-io/camera-to-rtsp). +- An IP camera which supports a RTSP H264 or H265 encoded stream, + - (or) a USB camera, Raspberry Pi camera or other camera, that [you can transform to a valid RTSP H264 or H265 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? @@ -104,19 +104,21 @@ This repository contains everything you'll need to know about our core product, - Low memory and CPU usage. - Simplified and modern user interface. -- Multi architecture (ARMv7, ARMv8, amd64, etc). -- Multi camera support: IP Cameras (H264), USB cameras and Raspberry Pi Cameras [through a RTSP proxy](https://github.com/kerberos-io/camera-to-rtsp). +- Multi architecture (ARMv7, ARMv8, amd64, etc).). +- Multi stream, for example recording in H265, live streaming and motion detection in H264. +- Multi camera support: IP Cameras (H264 and H265), USB cameras and Raspberry Pi Cameras [through a RTSP proxy](https://github.com/kerberos-io/camera-to-rtsp - Single camera per instance (e.g. one container per camera). -- Primary and secondary stream setup (record full-res, stream low-res). -- Low resolution streaming through MQTT and full resolution streaming through WebRTC. -- End-to-end encryption through MQTT using RSA and AES. -- Ability to specifiy conditions: offline mode, motion region, time table, continuous recording, etc. -- Post- and pre-recording on motion detection. +- Low resolution streaming through MQTT and high resolution streaming through WebRTC (only supports H264/PCM). +- Backchannel audio from Kerberos Hub to IP camera (requires PCM ULAW codec) +- Audio (AAC) and video (H264/H265) recording in MP4 container. +- End-to-end encryption through MQTT using RSA and AES (livestreaming, ONVIF, remote configuration, etc) +- Conditional recording: offline mode, motion region, time table, continuous recording, webhook condition etc. +- Post- and pre-recording for motion detection. - Encryption at rest using AES-256-CBC. -- Ability to create fragmented recordings, and streaming though HLS fMP4. +- Ability to create fragmented recordings, and streaming through 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/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). +- Outputs: trigger an integration (Webhooks, MQTT, Script, etc) when a specific event (motion detection or start recording ) occurs - REST API access and documentation through Swagger (trigger recording, update configuration, etc). - MIT License @@ -192,6 +194,7 @@ Next to attaching the configuration file, it is also possible to override the co | Name | Description | Default Value | | --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ | +| `LOG_LEVEL` | Level for logging, could be "info", "warning", "debug", "error" or "fatal". | "info" | | `AGENT_MODE` | You can choose to run this in 'release' for production, and or 'demo' for showcasing. | "release" | | `AGENT_TLS_INSECURE` | Specify if you want to use `InsecureSkipVerify` for the internal HTTP client. | "false" | | `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" | diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index 4da1670f..cbbb6e20 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -103,7 +103,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.IPCamera" + "$ref": "#/definitions/models.OnvifPreset" } } ], diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index cc96c143..c8327de0 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -95,7 +95,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.IPCamera" + "$ref": "#/definitions/models.OnvifPreset" } } ], diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index 6033d7cb..8dffd0cc 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -379,7 +379,7 @@ paths: name: cameraConfig required: true schema: - $ref: '#/definitions/models.IPCamera' + $ref: '#/definitions/models.OnvifPreset' responses: "200": description: OK diff --git a/machinery/main.go b/machinery/main.go index d450533b..4339c997 100644 --- a/machinery/main.go +++ b/machinery/main.go @@ -70,32 +70,38 @@ func main() { flag.Parse() timezone, _ := time.LoadLocation("CET") - log.Log.Init(configDirectory, timezone) + + // Specify the level of loggin: "info", "warning", "debug", "error" or "fatal." + logLevel := os.Getenv("LOG_LEVEL") + if logLevel == "" { + logLevel = "info" + } + log.Log.Init(logLevel, configDirectory, timezone) switch action { case "version": - log.Log.Info("Main(): You are currrently running Kerberos Agent " + VERSION) + log.Log.Info("main.Main(): You are currrently running Kerberos Agent " + VERSION) case "discover": // Convert duration to int timeout, err := time.ParseDuration(timeout + "ms") if err != nil { - log.Log.Fatal("Main(): could not parse timeout: " + err.Error()) + log.Log.Fatal("main.Main(): could not parse timeout: " + err.Error()) return } onvif.Discover(timeout) case "decrypt": - log.Log.Info("Main(): Decrypting: " + flag.Arg(0) + " with key: " + flag.Arg(1)) + log.Log.Info("main.Main(): Decrypting: " + flag.Arg(0) + " with key: " + flag.Arg(1)) symmetricKey := []byte(flag.Arg(1)) if symmetricKey == nil || len(symmetricKey) == 0 { - log.Log.Fatal("Main(): symmetric key should not be empty") + log.Log.Fatal("main.Main(): symmetric key should not be empty") return } if len(symmetricKey) != 32 { - log.Log.Fatal("Main(): symmetric key should be 32 bytes") + log.Log.Fatal("main.Main(): symmetric key should be 32 bytes") return } @@ -131,7 +137,7 @@ func main() { // Set timezone timezone, _ := time.LoadLocation(configuration.Config.Timezone) - log.Log.Init(configDirectory, timezone) + log.Log.Init(logLevel, configDirectory, timezone) // Check if we have a device Key or not, if not // we will generate one. @@ -140,9 +146,9 @@ func main() { configuration.Config.Key = key err := configService.StoreConfig(configDirectory, configuration.Config) if err == nil { - log.Log.Info("Main(): updated unique key for agent to: " + key) + log.Log.Info("main.Main(): updated unique key for agent to: " + key) } else { - log.Log.Info("Main(): something went wrong while trying to store key: " + key) + log.Log.Info("main.Main(): something went wrong while trying to store key: " + key) } } @@ -150,7 +156,8 @@ func main() { // This is used to restart the agent when the configuration is updated. ctx, cancel := context.WithCancel(context.Background()) - // We create a capture object. + // We create a capture object, this will contain all the streaming clients. + // And allow us to extract media from within difference places in the agent. capture := capture.Capture{ RTSPClient: nil, RTSPSubClient: nil, @@ -169,6 +176,6 @@ func main() { routers.StartWebserver(configDirectory, &configuration, &communication, &capture) } default: - log.Log.Error("Main(): Sorry I don't understand :(") + log.Log.Error("main.Main(): Sorry I don't understand :(") } } diff --git a/machinery/src/encryption/main.go b/machinery/src/encryption/main.go index 3d4e8a15..9c1f9952 100644 --- a/machinery/src/encryption/main.go +++ b/machinery/src/encryption/main.go @@ -14,27 +14,15 @@ import ( "hash" ) -// DecryptWithPrivateKey decrypts data with private key func DecryptWithPrivateKey(ciphertext string, privateKey *rsa.PrivateKey) ([]byte, error) { - - // decode our encrypted string into cipher bytes cipheredValue, _ := base64.StdEncoding.DecodeString(ciphertext) - - // decrypt the data out, err := rsa.DecryptPKCS1v15(nil, privateKey, cipheredValue) - return out, err } -// SignWithPrivateKey signs data with private key func SignWithPrivateKey(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) { - - // hash the data with sha256 hashed := sha256.Sum256(data) - - // sign the data signature, err := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, hashed[:]) - return signature, err } @@ -59,16 +47,10 @@ func AesEncrypt(content []byte, password string) ([]byte, error) { copy(cipherText[:8], []byte("Salted__")) copy(cipherText[8:16], salt) copy(cipherText[16:], cipherBytes) - - //cipherText := base64.StdEncoding.EncodeToString(data) return cipherText, nil } func AesDecrypt(cipherText []byte, password string) ([]byte, error) { - //data, err := base64.StdEncoding.DecodeString(cipherText) - //if err != nil { - // return nil, err - //} if string(cipherText[:8]) != "Salted__" { return nil, errors.New("invalid crypto js aes encryption") } @@ -92,8 +74,6 @@ func AesDecrypt(cipherText []byte, password string) ([]byte, error) { return result, nil } -// https://stackoverflow.com/questions/27677236/encryption-in-javascript-and-decryption-with-php/27678978#27678978 -// https://github.com/brix/crypto-js/blob/8e6d15bf2e26d6ff0af5277df2604ca12b60a718/src/evpkdf.js#L55 func EvpKDF(password []byte, salt []byte, keySize int, iterations int, hashAlgorithm string) ([]byte, error) { var block []byte var hasher hash.Hash @@ -124,7 +104,6 @@ func EvpKDF(password []byte, salt []byte, keySize int, iterations int, hashAlgor } func DefaultEvpKDF(password []byte, salt []byte) (key []byte, iv []byte, err error) { - // https://github.com/brix/crypto-js/blob/8e6d15bf2e26d6ff0af5277df2604ca12b60a718/src/cipher-core.js#L775 keySize := 256 / 32 ivSize := 128 / 32 derivedKeyBytes, err := EvpKDF(password, salt, keySize+ivSize, 1, "md5") @@ -134,7 +113,6 @@ func DefaultEvpKDF(password []byte, salt []byte) (key []byte, iv []byte, err err return derivedKeyBytes[:keySize*4], derivedKeyBytes[keySize*4:], nil } -// https://stackoverflow.com/questions/41579325/golang-how-do-i-decrypt-with-des-cbc-and-pkcs7 func PKCS5UnPadding(src []byte) []byte { length := len(src) unpadding := int(src[length-1]) diff --git a/machinery/src/log/main.go b/machinery/src/log/main.go index 11c562ef..fd3f274a 100644 --- a/machinery/src/log/main.go +++ b/machinery/src/log/main.go @@ -12,7 +12,6 @@ import ( // The logging library being used everywhere. var Log = Logging{ Logger: "logrus", - Level: "debug", } // ----------------- @@ -45,7 +44,7 @@ func ConfigureGoLogging(configDirectory string, timezone *time.Location) { // This a logrus // -> github.com/sirupsen/logrus -func ConfigureLogrus(timezone *time.Location) { +func ConfigureLogrus(level string, timezone *time.Location) { // Log as JSON instead of the default ASCII formatter. logrus.SetFormatter(LocalTimeZoneFormatter{ Timezone: timezone, @@ -57,7 +56,17 @@ func ConfigureLogrus(timezone *time.Location) { logrus.SetOutput(os.Stdout) // Only log the warning severity or above. - logrus.SetLevel(logrus.InfoLevel) + logLevel := logrus.InfoLevel + if level == "error" { + logLevel = logrus.ErrorLevel + } else if level == "debug" { + logLevel = logrus.DebugLevel + } else if level == "fatal" { + logLevel = logrus.FatalLevel + } else if level == "warning" { + logLevel = logrus.WarnLevel + } + logrus.SetLevel(logLevel) } type LocalTimeZoneFormatter struct { @@ -72,15 +81,14 @@ func (u LocalTimeZoneFormatter) Format(e *logrus.Entry) ([]byte, error) { type Logging struct { Logger string - Level string } -func (self *Logging) Init(configDirectory string, timezone *time.Location) { +func (self *Logging) Init(level string, configDirectory string, timezone *time.Location) { switch self.Logger { case "go-logging": ConfigureGoLogging(configDirectory, timezone) case "logrus": - ConfigureLogrus(timezone) + ConfigureLogrus(level, timezone) default: } } diff --git a/machinery/src/models/output.go b/machinery/src/models/output.go new file mode 100644 index 00000000..a8a24aeb --- /dev/null +++ b/machinery/src/models/output.go @@ -0,0 +1,15 @@ +package models + +import "time" + +// The OutputMessage contains the relevant information +// to specify the type of triggers we want to execute. +type OutputMessage struct { + Name string + Outputs []string + Trigger string + Timestamp time.Time + File string + CameraId string + SiteId string +} diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index c790a386..68eda3fd 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -14,9 +14,9 @@ import ( "github.com/gin-gonic/gin" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" - "github.com/kerberos-io/onvif/media" - "github.com/kerberos-io/onvif" + "github.com/kerberos-io/onvif/device" + "github.com/kerberos-io/onvif/media" "github.com/kerberos-io/onvif/ptz" xsd "github.com/kerberos-io/onvif/xsd/onvif" ) @@ -718,9 +718,9 @@ func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsRes return err } -func GetCapabilitiesFromDevice(device *onvif.Device) []string { +func GetCapabilitiesFromDevice(dev *onvif.Device) []string { var capabilities []string - services := device.GetServices() + services := dev.GetServices() for key, _ := range services { log.Log.Debug("GetCapabilitiesFromDevice: has key: " + key) if key != "" { @@ -731,6 +731,34 @@ func GetCapabilitiesFromDevice(device *onvif.Device) []string { } } } + + var getCapabilitiesResponse device.GetCapabilitiesResponse + resp, err := dev.CallMethod(device.GetCapabilities{ + Category: "All", + }) + + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } + if err == nil { + stringBody := string(b) + decodedXML, et, err := getXMLNode(stringBody, "GetCapabilitiesResponse") + if err != nil { + log.Log.Error("GetCapabilitiesResponse: " + err.Error()) + //return err + } else { + if err := decodedXML.DecodeElement(&getCapabilitiesResponse, et); err != nil { + log.Log.Error("GetCapabilitiesResponse: " + err.Error()) + // return err + } + //return err + } + } else { + log.Log.Error("GoToPresetFromDevice: " + err.Error()) + } + return capabilities } @@ -909,12 +937,12 @@ func VerifyOnvifConnection(c *gin.Context) { } } -func GetDigitalInputs(device *onvif.Device) (ptz.GetConfigurationsResponse, error) { - // We'll try to receive the PTZ configurations from the server - var configurations ptz.GetConfigurationsResponse +func GetRelayOutputs(dev *onvif.Device) (device.GetRelayOutputsResponse, error) { + // We'll try to receive the relay outputs from the server + var relayoutputs device.GetRelayOutputsResponse // Get the PTZ configurations from the device - resp, err := device.CallMethod(ptz.GetConfigurations{}) + resp, err := dev.CallMethod(device.GetRelayOutputs{}) var b []byte if resp != nil { b, err = io.ReadAll(resp.Body) @@ -924,19 +952,19 @@ func GetDigitalInputs(device *onvif.Device) (ptz.GetConfigurationsResponse, erro if err == nil { if err == nil { stringBody := string(b) - decodedXML, et, err := getXMLNode(stringBody, "GetConfigurationsResponse") + decodedXML, et, err := getXMLNode(stringBody, "GetRelayOutputsResponse") if err != nil { - log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error()) - return configurations, err + log.Log.Error("onvif.main.GetRelayOutputs(): " + err.Error()) + return relayoutputs, err } else { - if err := decodedXML.DecodeElement(&configurations, et); err != nil { - log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error()) - return configurations, err + if err := decodedXML.DecodeElement(&relayoutputs, et); err != nil { + log.Log.Debug("onvif.main.GetRelayOutputs(): " + err.Error()) + return relayoutputs, err } } } } - return configurations, err + return relayoutputs, err } func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, error) { diff --git a/machinery/src/outputs/main.go b/machinery/src/outputs/main.go new file mode 100644 index 00000000..a0254230 --- /dev/null +++ b/machinery/src/outputs/main.go @@ -0,0 +1,59 @@ +package outputs + +import ( + "github.com/kerberos-io/agent/machinery/src/log" + "github.com/kerberos-io/agent/machinery/src/models" +) + +type Output interface { + // Triggers the integration + Trigger(message models.OutputMessage) error +} + +func Execute(message *models.OutputMessage) (err error) { + err = nil + + outputs := message.Outputs + for _, output := range outputs { + switch output { + case "slack": + slack := &SlackOutput{} + err := slack.Trigger(message) + if err == nil { + log.Log.Debug("outputs.main.Execute(slack): message was processed by output.") + } else { + log.Log.Error("outputs.main.Execute(slack): " + err.Error()) + } + break + case "webhook": + webhook := &WebhookOutput{} + err := webhook.Trigger(message) + if err == nil { + log.Log.Debug("outputs.main.Execute(webhook): message was processed by output.") + } else { + log.Log.Error("outputs.main.Execute(webhook): " + err.Error()) + } + break + case "onvif_relay": + onvif := &OnvifRelayOutput{} + err := onvif.Trigger(message) + if err == nil { + log.Log.Debug("outputs.main.Execute(onvif): message was processed by output.") + } else { + log.Log.Error("outputs.main.Execute(onvif): " + err.Error()) + } + break + case "script": + script := &ScriptOutput{} + err := script.Trigger(message) + if err == nil { + log.Log.Debug("outputs.main.Execute(script): message was processed by output.") + } else { + log.Log.Error("outputs.main.Execute(script): " + err.Error()) + } + break + } + } + + return err +} diff --git a/machinery/src/outputs/onvif_relay.go b/machinery/src/outputs/onvif_relay.go new file mode 100644 index 00000000..63aceb36 --- /dev/null +++ b/machinery/src/outputs/onvif_relay.go @@ -0,0 +1,12 @@ +package outputs + +import "github.com/kerberos-io/agent/machinery/src/models" + +type OnvifRelayOutput struct { + Output +} + +func (o *OnvifRelayOutput) Trigger(message *models.OutputMessage) (err error) { + err = nil + return err +} diff --git a/machinery/src/outputs/script.go b/machinery/src/outputs/script.go new file mode 100644 index 00000000..e4f4c3d3 --- /dev/null +++ b/machinery/src/outputs/script.go @@ -0,0 +1,12 @@ +package outputs + +import "github.com/kerberos-io/agent/machinery/src/models" + +type ScriptOutput struct { + Output +} + +func (scr *ScriptOutput) Trigger(message *models.OutputMessage) (err error) { + err = nil + return err +} diff --git a/machinery/src/outputs/slack.go b/machinery/src/outputs/slack.go new file mode 100644 index 00000000..135dabaa --- /dev/null +++ b/machinery/src/outputs/slack.go @@ -0,0 +1,12 @@ +package outputs + +import "github.com/kerberos-io/agent/machinery/src/models" + +type SlackOutput struct { + Output +} + +func (s *SlackOutput) Trigger(message *models.OutputMessage) (err error) { + err = nil + return err +} diff --git a/machinery/src/outputs/webhook.go b/machinery/src/outputs/webhook.go new file mode 100644 index 00000000..04c8a0e6 --- /dev/null +++ b/machinery/src/outputs/webhook.go @@ -0,0 +1,12 @@ +package outputs + +import "github.com/kerberos-io/agent/machinery/src/models" + +type WebhookOutput struct { + Output +} + +func (w *WebhookOutput) Trigger(message *models.OutputMessage) (err error) { + err = nil + return err +} diff --git a/machinery/src/packets/packet.go b/machinery/src/packets/packet.go index 62390ad3..d385bbcc 100644 --- a/machinery/src/packets/packet.go +++ b/machinery/src/packets/packet.go @@ -8,10 +8,7 @@ import ( // Packet represents an RTP Packet type Packet struct { - // for Gortsplib library - Packet *rtp.Packet - - // for JOY4 and Gortsplib library + Packet *rtp.Packet IsAudio bool // packet is audio IsVideo bool // packet is video IsKeyFrame bool // video packet is key frame diff --git a/machinery/src/routers/http/Methods.go b/machinery/src/routers/http/methods.go similarity index 98% rename from machinery/src/routers/http/Methods.go rename to machinery/src/routers/http/methods.go index f0faf815..f4383909 100644 --- a/machinery/src/routers/http/Methods.go +++ b/machinery/src/routers/http/methods.go @@ -361,7 +361,7 @@ func GoToOnvifPreset(c *gin.Context) { // @in header // @name Authorization // @Tags camera -// @Param cameraConfig body models.IPCamera true "Camera Config" +// @Param cameraConfig body models.OnvifPreset true "Camera Config" // @Summary Will get the digital inputs from the ONVIF device. // @Description Will get the digital inputs from the ONVIF device. // @Success 200 {object} models.APIResponse @@ -386,10 +386,10 @@ func DoGetDigitalInputs(c *gin.Context) { cameraConfiguration := configuration.Config.Capture.IPCamera device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - inputs, err := onvif.GetDigitalInputs(device) + outputs, err := onvif.GetRelayOutputs(device) if err == nil { c.JSON(200, gin.H{ - "data": inputs, + "data": outputs, }) } else { c.JSON(400, gin.H{ diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/routes.go similarity index 100% rename from machinery/src/routers/http/Routes.go rename to machinery/src/routers/http/routes.go From 67064879e41bbd35ab8d303b85602b4739cf63b6 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 25 Dec 2023 20:55:51 +0100 Subject: [PATCH 59/81] input/output methods --- machinery/docs/docs.go | 81 ++++- machinery/docs/swagger.json | 81 ++++- machinery/docs/swagger.yaml | 53 +++- machinery/go.mod | 10 +- machinery/go.sum | 33 +- machinery/src/capture/Gortsplib.go | 269 +++++++++-------- machinery/src/cloud/Cloud.go | 41 ++- machinery/src/onvif/main.go | 417 ++++++++++++++++++++------ machinery/src/routers/http/methods.go | 145 ++++++++- machinery/src/routers/http/routes.go | 2 + 10 files changed, 861 insertions(+), 271 deletions(-) diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index cbbb6e20..a1cfe72c 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -98,12 +98,12 @@ const docTemplate = `{ "operationId": "get-digital-inputs", "parameters": [ { - "description": "Camera Config", - "name": "cameraConfig", + "description": "OnvifCredentials", + "name": "config", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.OnvifPreset" + "$ref": "#/definitions/models.OnvifCredentials" } } ], @@ -146,6 +146,81 @@ const docTemplate = `{ } } }, + "/api/camera/onvif/outputs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Will get the relay outputs from the ONVIF device.", + "tags": [ + "camera" + ], + "summary": "Will get the relay outputs from the ONVIF device.", + "operationId": "get-relay-outputs", + "parameters": [ + { + "description": "OnvifCredentials", + "name": "config", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.OnvifCredentials" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, + "/api/camera/onvif/outputs/{output}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Will trigger the relay output from the ONVIF device.", + "tags": [ + "camera" + ], + "summary": "Will trigger the relay output from the ONVIF device.", + "operationId": "trigger-relay-output", + "parameters": [ + { + "description": "OnvifCredentials", + "name": "config", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.OnvifCredentials" + } + }, + { + "type": "string", + "description": "Output", + "name": "output", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, "/api/camera/onvif/pantilt": { "post": { "description": "Panning or/and tilting the camera using a direction (x,y).", diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index c8327de0..a110b578 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -90,12 +90,12 @@ "operationId": "get-digital-inputs", "parameters": [ { - "description": "Camera Config", - "name": "cameraConfig", + "description": "OnvifCredentials", + "name": "config", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.OnvifPreset" + "$ref": "#/definitions/models.OnvifCredentials" } } ], @@ -138,6 +138,81 @@ } } }, + "/api/camera/onvif/outputs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Will get the relay outputs from the ONVIF device.", + "tags": [ + "camera" + ], + "summary": "Will get the relay outputs from the ONVIF device.", + "operationId": "get-relay-outputs", + "parameters": [ + { + "description": "OnvifCredentials", + "name": "config", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.OnvifCredentials" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, + "/api/camera/onvif/outputs/{output}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Will trigger the relay output from the ONVIF device.", + "tags": [ + "camera" + ], + "summary": "Will trigger the relay output from the ONVIF device.", + "operationId": "trigger-relay-output", + "parameters": [ + { + "description": "OnvifCredentials", + "name": "config", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.OnvifCredentials" + } + }, + { + "type": "string", + "description": "Output", + "name": "output", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.APIResponse" + } + } + } + } + }, "/api/camera/onvif/pantilt": { "post": { "description": "Panning or/and tilting the camera using a direction (x,y).", diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index 8dffd0cc..a8a4621b 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -374,12 +374,12 @@ paths: description: Will get the digital inputs from the ONVIF device. operationId: get-digital-inputs parameters: - - description: Camera Config + - description: OnvifCredentials in: body - name: cameraConfig + name: config required: true schema: - $ref: '#/definitions/models.OnvifPreset' + $ref: '#/definitions/models.OnvifCredentials' responses: "200": description: OK @@ -409,6 +409,53 @@ paths: summary: Try to login into ONVIF supported camera. tags: - camera + /api/camera/onvif/outputs: + post: + description: Will get the relay outputs from the ONVIF device. + operationId: get-relay-outputs + parameters: + - description: OnvifCredentials + in: body + name: config + required: true + schema: + $ref: '#/definitions/models.OnvifCredentials' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.APIResponse' + security: + - Bearer: [] + summary: Will get the relay outputs from the ONVIF device. + tags: + - camera + /api/camera/onvif/outputs/{output}: + post: + description: Will trigger the relay output from the ONVIF device. + operationId: trigger-relay-output + parameters: + - description: OnvifCredentials + in: body + name: config + required: true + schema: + $ref: '#/definitions/models.OnvifCredentials' + - description: Output + in: path + name: output + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.APIResponse' + security: + - Bearer: [] + summary: Will trigger the relay output from the ONVIF device. + tags: + - camera /api/camera/onvif/pantilt: post: description: Panning or/and tilting the camera using a direction (x,y). diff --git a/machinery/go.mod b/machinery/go.mod index 10585326..28538dfd 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -4,7 +4,7 @@ go 1.19 //replace github.com/kerberos-io/joy4 v1.0.63 => ../../../../github.com/kerberos-io/joy4 -// replace github.com/kerberos-io/onvif v0.0.6 => ../../../../github.com/kerberos-io/onvif +replace github.com/kerberos-io/onvif v0.0.9 => ../../../../github.com/kerberos-io/onvif require ( github.com/InVisionApp/conjungo v1.1.0 @@ -19,7 +19,7 @@ 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.9.1 - github.com/gofrs/uuid v3.2.0+incompatible + github.com/gofrs/uuid v4.4.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 @@ -54,15 +54,16 @@ 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/beevik/etree v1.1.0 // indirect + github.com/beevik/etree v1.2.0 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/clbanning/mxj v1.8.4 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/elastic/go-windows v1.0.0 // indirect - github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae // indirect + github.com/elgs/gostrgen v0.0.0-20220325073726-0c3e00d082f6 // indirect github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -84,7 +85,6 @@ require ( github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/juju/errors v0.0.0-20220331221717-b38fca44723b // indirect github.com/klauspost/compress v1.15.0 // indirect github.com/klauspost/cpuid v1.2.3 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect diff --git a/machinery/go.sum b/machinery/go.sum index e7dd229c..013a6eef 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -64,8 +64,8 @@ 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/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= -github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw= +github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/bluenviron/gortsplib/v4 v4.6.1 h1:+xI/hrNM/KX3qenqzKIG0MG8z+IHg0xu8OEoMfDZ+wg= github.com/bluenviron/gortsplib/v4 v4.6.1/go.mod h1:dN1YjyPNMfy/NwC17Ga6MiIMiUoQfg5GL7LGsVHa0Jo= github.com/bluenviron/mediacommon v1.5.1 h1:yYVF+ebqZOJh8yH+EeuPcAtTmWR66BqbJGmStxkScoI= @@ -87,10 +87,11 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -111,8 +112,8 @@ github.com/elastic/go-sysinfo v1.9.0 h1:usICqY/Nw4Mpn9f4LdtpFrKxXroJDe81GaxxUlCc github.com/elastic/go-sysinfo v1.9.0/go.mod h1:eBD1wEGVaRnRLGecc9iG1z8eOv5HnEdz9+nWd8UAxcE= github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= -github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae h1:3KvK2DmA7TxQ6PZ2f0rWbdqjgJhRcqgbY70bBeE4clI= -github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae/go.mod h1:wruC5r2gHdr/JIUs5Rr1V45YtsAzKXZxAnn/5rPC97g= +github.com/elgs/gostrgen v0.0.0-20220325073726-0c3e00d082f6 h1:x9TA+vnGEyqmWY+eA9HfgxNRkOQqwiEpFE9IPXSGuEA= +github.com/elgs/gostrgen v0.0.0-20220325073726-0c3e00d082f6/go.mod h1:wruC5r2gHdr/JIUs5Rr1V45YtsAzKXZxAnn/5rPC97g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -136,7 +137,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2 h1:dyuNlYlG1faymw39NdJddnzJICy6587tiGSVioWhYoE= github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= -github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= @@ -155,15 +155,12 @@ github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyr github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= @@ -175,9 +172,8 @@ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-module/carbon/v2 v2.2.3 h1:WvGIc5+qzq9drNzH+Gnjh1TZ0JgDY/IA+m2Dvk7Qm4Q= @@ -271,14 +267,10 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY= -github.com/juju/errors v0.0.0-20220331221717-b38fca44723b/go.mod h1:jMGj9DWF/qbo91ODcfJq6z/RYc3FX3taCBZMCcpI4Ls= 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.64 h1:gTUSotHSOhp9mNqEecgq88tQHvpj7TjmrvPUsPm0idg= github.com/kerberos-io/joy4 v1.0.64/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU= -github.com/kerberos-io/onvif v0.0.9 h1:B2PGuxlQfv7mgDr47pRbdqREYD68w0oPymEb1i2RI4Y= -github.com/kerberos-io/onvif v0.0.9/go.mod h1:mxGnUYvDyaFN8VPIf6wWnU9Af8RmuxfseAQo2bh4vo0= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= @@ -299,7 +291,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/go-gypsy v1.0.0 h1:7/wQ7A3UL1bnqRMnZ6T8cwCOArfZCxFmb1iTxaOOo1s= github.com/kylelemons/go-gypsy v1.0.0/go.mod h1:chkXM0zjdpXOiqkCW1XcCHDfjfk14PH2KKkQWxfJUcU= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= @@ -310,7 +301,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= @@ -416,8 +406,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/secure-systems-lab/go-securesystemslib v0.3.1/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U= @@ -474,9 +462,7 @@ github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= @@ -527,7 +513,6 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= @@ -655,7 +640,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -875,7 +859,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index d101c304..eb80af30 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -90,20 +90,20 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { // parse URL u, err := base.ParseURL(g.Url) if err != nil { - log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.Connect(ParseURL): " + err.Error()) return } // connect to the server err = g.Client.Start(u.Scheme, u.Host) if err != nil { - log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.Connect(Start): " + err.Error()) } // find published medias desc, _, err := g.Client.Describe(u) if err != nil { - log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) + log.Log.Debug("capture.golibrtsp.Connect(Describe): " + err.Error()) return } @@ -116,50 +116,51 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.VideoH264Media = mediH264 g.VideoH264Forma = formaH264 if mediH264 == nil { - log.Log.Debug("capture.golibrtsp.Connect(): " + "video media not found") + log.Log.Debug("capture.golibrtsp.Connect(H264): " + "video media not found") } else { - // Get SPS from the SDP - // Calculate the width and height of the video - var sps h264.SPS - err = sps.Unmarshal(formaH264.SPS) - if err != nil { - log.Log.Debug("capture.golibrtsp.Connect(): " + err.Error()) - return - } - - g.Streams = append(g.Streams, packets.Stream{ - Name: formaH264.Codec(), - IsVideo: true, - IsAudio: false, - SPS: formaH264.SPS, - PPS: formaH264.PPS, - Width: sps.Width(), - Height: sps.Height(), - FPS: sps.FPS(), - IsBackChannel: false, - }) - - // Set the index for the video - g.VideoH264Index = int8(len(g.Streams)) - 1 - - // setup RTP/H264 -> H264 decoder - rtpDec, err := formaH264.CreateDecoder() - if err != nil { - // Something went wrong .. Do something - } - g.VideoH264Decoder = rtpDec - - // setup H264 -> raw frames decoder - frameDec, err := newDecoder("H264") - if err != nil { - // Something went wrong .. Do something - } - g.VideoH264FrameDecoder = frameDec - // setup a video media _, err = g.Client.Setup(desc.BaseURL, mediH264, 0, 0) if err != nil { // Something went wrong .. Do something + log.Log.Error("capture.golibrtsp.Connect(H264): " + err.Error()) + } else { + // Get SPS from the SDP + // Calculate the width and height of the video + var sps h264.SPS + err = sps.Unmarshal(formaH264.SPS) + if err != nil { + log.Log.Debug("capture.golibrtsp.Connect(H264): " + err.Error()) + return + } + + g.Streams = append(g.Streams, packets.Stream{ + Name: formaH264.Codec(), + IsVideo: true, + IsAudio: false, + SPS: formaH264.SPS, + PPS: formaH264.PPS, + Width: sps.Width(), + Height: sps.Height(), + FPS: sps.FPS(), + IsBackChannel: false, + }) + + // Set the index for the video + g.VideoH264Index = int8(len(g.Streams)) - 1 + + // setup RTP/H264 -> H264 decoder + rtpDec, err := formaH264.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + } + g.VideoH264Decoder = rtpDec + + // setup H264 -> raw frames decoder + frameDec, err := newDecoder("H264") + if err != nil { + // Something went wrong .. Do something + } + g.VideoH264FrameDecoder = frameDec } } @@ -169,51 +170,52 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.VideoH265Media = mediH265 g.VideoH265Forma = formaH265 if mediH265 == nil { - log.Log.Info("capture.golibrtsp.Connect(): " + "video media not found") + log.Log.Info("capture.golibrtsp.Connect(H265): " + "video media not found") } else { - // Get SPS from the SDP - // Calculate the width and height of the video - var sps h265.SPS - err = sps.Unmarshal(formaH265.SPS) - if err != nil { - log.Log.Info("capture.golibrtsp.Connect(): " + err.Error()) - return - } - - g.Streams = append(g.Streams, packets.Stream{ - Name: formaH265.Codec(), - IsVideo: true, - IsAudio: false, - SPS: formaH265.SPS, - PPS: formaH265.PPS, - VPS: formaH265.VPS, - Width: sps.Width(), - Height: sps.Height(), - FPS: sps.FPS(), - IsBackChannel: false, - }) - - // Set the index for the video - g.VideoH265Index = int8(len(g.Streams)) - 1 - - // setup RTP/H265 -> H265 decoder - rtpDec, err := formaH265.CreateDecoder() - if err != nil { - // Something went wrong .. Do something - } - g.VideoH265Decoder = rtpDec - - // setup H265 -> raw frames decoder - frameDec, err := newDecoder("H265") - if err != nil { - // Something went wrong .. Do something - } - g.VideoH265FrameDecoder = frameDec - // setup a video media _, err = g.Client.Setup(desc.BaseURL, mediH265, 0, 0) if err != nil { // Something went wrong .. Do something + log.Log.Error("capture.golibrtsp.Connect(H265): " + err.Error()) + } else { + // Get SPS from the SDP + // Calculate the width and height of the video + var sps h265.SPS + err = sps.Unmarshal(formaH265.SPS) + if err != nil { + log.Log.Info("capture.golibrtsp.Connect(H265): " + err.Error()) + return + } + + g.Streams = append(g.Streams, packets.Stream{ + Name: formaH265.Codec(), + IsVideo: true, + IsAudio: false, + SPS: formaH265.SPS, + PPS: formaH265.PPS, + VPS: formaH265.VPS, + Width: sps.Width(), + Height: sps.Height(), + FPS: sps.FPS(), + IsBackChannel: false, + }) + + // Set the index for the video + g.VideoH265Index = int8(len(g.Streams)) - 1 + + // setup RTP/H265 -> H265 decoder + rtpDec, err := formaH265.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + } + g.VideoH265Decoder = rtpDec + + // setup H265 -> raw frames decoder + frameDec, err := newDecoder("H265") + if err != nil { + // Something went wrong .. Do something + } + g.VideoH265FrameDecoder = frameDec } } @@ -223,62 +225,67 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.AudioG711Media = audioMedi g.AudioG711Forma = audioForma if audioMedi == nil { - log.Log.Info("capture.golibrtsp.Connect(): " + "audio media not found") + log.Log.Info("capture.golibrtsp.Connect(G711): " + "audio media not found") } else { - - g.Streams = append(g.Streams, packets.Stream{ - Name: "PCM_MULAW", - IsVideo: false, - IsAudio: true, - IsBackChannel: false, - }) - - // Set the index for the audio - g.AudioG711Index = int8(len(g.Streams)) - 1 - - // create decoder - audiortpDec, err := audioForma.CreateDecoder() - if err != nil { - // Something went wrong .. Do something - } - g.AudioG711Decoder = audiortpDec - // setup a audio media _, err = g.Client.Setup(desc.BaseURL, audioMedi, 0, 0) if err != nil { // Something went wrong .. Do something + log.Log.Error("capture.golibrtsp.Connect(G711): " + err.Error()) + } else { + // create decoder + audiortpDec, err := audioForma.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + log.Log.Error("capture.golibrtsp.Connect(G711): " + err.Error()) + } else { + g.AudioG711Decoder = audiortpDec + + g.Streams = append(g.Streams, packets.Stream{ + Name: "PCM_MULAW", + IsVideo: false, + IsAudio: true, + IsBackChannel: false, + }) + + // Set the index for the audio + g.AudioG711Index = int8(len(g.Streams)) - 1 + } } } // Look for audio stream. - // find the G711 media and format + // find the AAC media and format audioFormaMPEG4, audioMediMPEG4 := FindMPEG4Audio(desc, false) g.AudioMPEG4Media = audioMediMPEG4 g.AudioMPEG4Forma = audioFormaMPEG4 if audioMediMPEG4 == nil { - log.Log.Info("capture.golibrtsp.Connect(): " + "audio media not found") + log.Log.Info("capture.golibrtsp.Connect(MPEG4): " + "audio media not found") } else { - g.Streams = append(g.Streams, packets.Stream{ - Name: "AAC", - IsVideo: false, - IsAudio: true, - IsBackChannel: false, - }) - - // Set the index for the audio - g.AudioMPEG4Index = int8(len(g.Streams)) - 1 - - // create decoder - audiortpDec, err := audioFormaMPEG4.CreateDecoder() - if err != nil { - // Something went wrong .. Do something - } - g.AudioMPEG4Decoder = audiortpDec - // setup a audio media _, err = g.Client.Setup(desc.BaseURL, audioMediMPEG4, 0, 0) if err != nil { // Something went wrong .. Do something + log.Log.Error("capture.golibrtsp.Connect(MPEG4): " + err.Error()) + } else { + g.Streams = append(g.Streams, packets.Stream{ + Name: "AAC", + IsVideo: false, + IsAudio: true, + IsBackChannel: false, + }) + + // Set the index for the audio + g.AudioMPEG4Index = int8(len(g.Streams)) - 1 + + // create decoder + audiortpDec, err := audioFormaMPEG4.CreateDecoder() + if err != nil { + // Something went wrong .. Do something + log.Log.Error("capture.golibrtsp.Connect(MPEG4): " + err.Error()) + } + g.AudioMPEG4Decoder = audiortpDec + } } @@ -322,22 +329,22 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { log.Log.Info("capture.golibrtsp.ConnectBackChannel(): " + "audio backchannel not found") err = errors.New("no audio backchannel found") } else { - g.Streams = append(g.Streams, packets.Stream{ - Name: "PCM_MULAW", - IsVideo: false, - IsAudio: true, - IsBackChannel: true, - }) - - // Set the index for the audio - g.AudioG711IndexBackChannel = int8(len(g.Streams)) - 1 - // setup a audio media _, err = g.Client.Setup(desc.BaseURL, audioMediBackChannel, 0, 0) if err != nil { // Something went wrong .. Do something + log.Log.Error("capture.golibrtsp.ConnectBackChannel(): " + err.Error()) + g.HasBackChannel = false } else { g.HasBackChannel = true + g.Streams = append(g.Streams, packets.Stream{ + Name: "PCM_MULAW", + IsVideo: false, + IsAudio: true, + IsBackChannel: true, + }) + // Set the index for the audio + g.AudioG711IndexBackChannel = int8(len(g.Streams)) - 1 } } return @@ -645,7 +652,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati // Play the stream. _, err = g.Client.Play(nil) if err != nil { - panic(err) + log.Log.Error("capture.golibrtsp.Start(): " + err.Error()) } return diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 0d40dac6..b00132c6 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -21,6 +21,7 @@ import ( "time" "github.com/kerberos-io/agent/machinery/src/capture" + "github.com/kerberos-io/agent/machinery/src/encryption" "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/onvif" @@ -307,6 +308,7 @@ loop: onvifPanTilt := "false" onvifPresets := "false" var onvifPresetsList []byte + var onvifEventsList []byte if config.Capture.IPCamera.ONVIFXAddr != "" { cameraConfiguration := configuration.Config.Capture.IPCamera device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) @@ -342,6 +344,21 @@ loop: log.Log.Error("HandleHeartBeat: error while getting PTZ configurations: " + err.Error()) onvifPresetsList = []byte("[]") } + + // We will also fetch some events, to know the status of the inputs and outputs. + // More event types might be added. + events, err := onvif.GetEventMessages(device) + if err == nil && len(events) > 0 { + onvifEventsList, err = json.Marshal(events) + if err != nil { + log.Log.Error("HandleHeartBeat: error while marshalling events: " + err.Error()) + onvifEventsList = []byte("[]") + } + } else { + log.Log.Debug("HandleHeartBeat: no events found.") + onvifEventsList = []byte("[]") + } + } else { log.Log.Error("HandleHeartBeat: error while connecting to ONVIF device: " + err.Error()) onvifPresetsList = []byte("[]") @@ -383,6 +400,7 @@ loop: "onvif_pantilt" : "%s", "onvif_presets": "%s", "onvif_presets_list": %s, + "onvif_events_list": %s, "cameraConnected": "%s", "hasBackChannel": "%s", "numberoffiles" : "33", @@ -391,7 +409,26 @@ loop: "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, hasBackChannel) + }`, 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, onvifEventsList, cameraConnected, hasBackChannel) + + // Get the private key to encrypt the data using symmetric encryption: AES. + privateKey := config.HubPrivateKey + if privateKey != "" { + // Encrypt the data using AES. + encrypted, err := encryption.AesEncrypt([]byte(object), privateKey) + if err != nil { + encrypted = []byte("") + log.Log.Error("HandleHeartBeat: error while encrypting data: " + err.Error()) + } + + // Base64 encode the encrypted data. + encryptedBase64 := base64.StdEncoding.EncodeToString(encrypted) + object = fmt.Sprintf(`{ + "cloudpublicKey": "%s", + "encrypted" : %t, + "encryptedData" : "%s" + }`, config.HubKey, true, encryptedBase64) + } var jsonStr = []byte(object) buffy := bytes.NewBuffer(jsonStr) @@ -479,7 +516,7 @@ loop: select { case <-communication.HandleHeartBeat: break loop - case <-time.After(15 * time.Second): + case <-time.After(10 * time.Second): } } diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 68eda3fd..43eeb318 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -5,7 +5,10 @@ import ( "encoding/json" "encoding/xml" "errors" + "fmt" "io" + "io/ioutil" + "net/http" "strconv" "strings" "time" @@ -16,9 +19,12 @@ import ( "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/onvif" "github.com/kerberos-io/onvif/device" + "github.com/kerberos-io/onvif/deviceio" + "github.com/kerberos-io/onvif/event" "github.com/kerberos-io/onvif/media" "github.com/kerberos-io/onvif/ptz" - xsd "github.com/kerberos-io/onvif/xsd/onvif" + xsd "github.com/kerberos-io/onvif/xsd" + xsdonvif "github.com/kerberos-io/onvif/xsd/onvif" ) func Discover(timeout time.Duration) { @@ -188,23 +194,41 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, error) { log.Log.Debug("onvif.ConnectToOnvifDevice(): started") - device, err := onvif.NewDevice(onvif.DeviceParams{ + dev, err := onvif.NewDevice(onvif.DeviceParams{ Xaddr: cameraConfiguration.ONVIFXAddr, Username: cameraConfiguration.ONVIFUsername, Password: cameraConfiguration.ONVIFPassword, + AuthMode: "both", }) if err != nil { log.Log.Debug("onvif.ConnectToOnvifDevice(): " + err.Error()) } else { + + getCapabilities := device.GetCapabilities{Category: []xsdonvif.CapabilityCategory{"All"}} + getCapabilitiesResponse, err := dev.CallMethod(getCapabilities) + if err != nil { + fmt.Println(err) + } else { + fmt.Println(readResponse(getCapabilitiesResponse)) + } + log.Log.Info("onvif.ConnectToOnvifDevice(): successfully connected to device") } log.Log.Debug("onvif.ConnectToOnvifDevice(): finished") - return device, err + return dev, err +} + +func readResponse(resp *http.Response) string { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + panic(err) + } + return string(b) } -func GetTokenFromProfile(device *onvif.Device, profileId int) (xsd.ReferenceToken, error) { +func GetTokenFromProfile(device *onvif.Device, profileId int) (xsdonvif.ReferenceToken, error) { // We aim to receive a profile token from the server - var profileToken xsd.ReferenceToken + var profileToken xsdonvif.ReferenceToken // Get Profiles resp, err := device.CallMethod(media.GetProfiles{}) @@ -266,8 +290,8 @@ func GetPTZConfigurationsFromDevice(device *onvif.Device) (ptz.GetConfigurations return configurations, err } -func GetPositionFromDevice(configuration models.Configuration) (xsd.PTZVector, error) { - var position xsd.PTZVector +func GetPositionFromDevice(configuration models.Configuration) (xsdonvif.PTZVector, error) { + var position xsdonvif.PTZVector // Connect to Onvif device cameraConfiguration := configuration.Config.Capture.IPCamera device, err := ConnectToOnvifDevice(&cameraConfiguration) @@ -299,10 +323,10 @@ func GetPositionFromDevice(configuration models.Configuration) (xsd.PTZVector, e } } -func GetPosition(device *onvif.Device, token xsd.ReferenceToken) (xsd.PTZVector, error) { +func GetPosition(device *onvif.Device, token xsdonvif.ReferenceToken) (xsdonvif.PTZVector, error) { // We'll try to receive the PTZ configurations from the server var status ptz.GetStatusResponse - var position xsd.PTZVector + var position xsdonvif.PTZVector // Get the PTZ configurations from the device resp, err := device.CallMethod(ptz.GetStatus{ @@ -332,24 +356,24 @@ func GetPosition(device *onvif.Device, token xsd.ReferenceToken) (xsd.PTZVector, return position, err } -func AbsolutePanTiltMove(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, pan float64, tilt float64, zoom float64) error { +func AbsolutePanTiltMove(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken, pan float64, tilt float64, zoom float64) error { - absolutePantiltVector := xsd.Vector2D{ + absolutePantiltVector := xsdonvif.Vector2D{ X: pan, Y: tilt, - Space: configuration.PTZConfiguration.DefaultAbsolutePantTiltPositionSpace, + Space: configuration.PTZConfiguration[0].DefaultAbsolutePantTiltPositionSpace, } - absoluteZoomVector := xsd.Vector1D{ + absoluteZoomVector := xsdonvif.Vector1D{ X: zoom, - Space: configuration.PTZConfiguration.DefaultAbsoluteZoomPositionSpace, + Space: configuration.PTZConfiguration[0].DefaultAbsoluteZoomPositionSpace, } resp, err := device.CallMethod(ptz.AbsoluteMove{ ProfileToken: token, - Position: xsd.PTZVector{ - PanTilt: absolutePantiltVector, - Zoom: absoluteZoomVector, + Position: xsdonvif.PTZVector{ + PanTilt: &absolutePantiltVector, + Zoom: &absoluteZoomVector, }, }) @@ -369,7 +393,7 @@ func AbsolutePanTiltMove(device *onvif.Device, configuration ptz.GetConfiguratio // This function will simulate the AbsolutePanTiltMove function. // However the AboslutePanTiltMove function is not working on all cameras. // So we'll use the ContinuousMove function to simulate the AbsolutePanTiltMove function using the position polling. -func AbsolutePanTiltMoveFake(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, pan float64, tilt float64, zoom float64) error { +func AbsolutePanTiltMoveFake(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken, pan float64, tilt float64, zoom float64) error { position, err := GetPosition(device, token) if position.PanTilt.X >= pan-0.01 && position.PanTilt.X <= pan+0.01 && position.PanTilt.Y >= tilt-0.01 && position.PanTilt.Y <= tilt+0.01 && position.Zoom.X >= zoom-0.01 && position.Zoom.X <= zoom+0.01 { log.Log.Debug("AbsolutePanTiltMoveFake: already at position") @@ -398,15 +422,15 @@ func AbsolutePanTiltMoveFake(device *onvif.Device, configuration ptz.GetConfigur return err } -func ZoomOutCompletely(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken) error { +func ZoomOutCompletely(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken) error { // Zoom out completely!!! - zoomOut := xsd.Vector1D{ + zoomOut := xsdonvif.Vector1D{ X: -1, - Space: configuration.PTZConfiguration.DefaultContinuousZoomVelocitySpace, + Space: configuration.PTZConfiguration[0].DefaultContinuousZoomVelocitySpace, } _, err := device.CallMethod(ptz.ContinuousMove{ - ProfileToken: token, - Velocity: xsd.PTZSpeedZoom{ + ProfileToken: &token, + Velocity: xsdonvif.PTZSpeedZoom{ Zoom: zoomOut, }, }) @@ -432,7 +456,7 @@ func ZoomOutCompletely(device *onvif.Device, configuration ptz.GetConfigurations return err } -func PanUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, pan float64, zoom float64, speed float64, wait time.Duration) error { +func PanUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken, pan float64, zoom float64, speed float64, wait time.Duration) error { position, err := GetPosition(device, token) if position.PanTilt.X >= pan-0.01 && position.PanTilt.X <= pan+0.01 { @@ -446,14 +470,14 @@ func PanUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsR directionX = speed * -1 } - panTiltVector := xsd.Vector2D{ + panTiltVector := xsdonvif.Vector2D{ X: directionX, Y: 0, - Space: configuration.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace, + Space: configuration.PTZConfiguration[0].DefaultContinuousPanTiltVelocitySpace, } resp, err := device.CallMethod(ptz.ContinuousMove{ - ProfileToken: token, - Velocity: xsd.PTZSpeedPanTilt{ + ProfileToken: &token, + Velocity: xsdonvif.PTZSpeedPanTilt{ PanTilt: panTiltVector, }, }) @@ -497,7 +521,7 @@ func PanUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsR return err } -func TiltUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, tilt float64, zoom float64, speed float64, wait time.Duration) error { +func TiltUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken, tilt float64, zoom float64, speed float64, wait time.Duration) error { position, err := GetPosition(device, token) if position.PanTilt.Y >= tilt-0.005 && position.PanTilt.Y <= tilt+0.005 { @@ -511,16 +535,19 @@ func TiltUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations directionY = speed * -1 } - panTiltVector := xsd.Vector2D{ + panTiltVector := xsdonvif.Vector2D{ X: 0, Y: directionY, - Space: configuration.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace, + Space: configuration.PTZConfiguration[0].DefaultContinuousPanTiltVelocitySpace, + } + + velocity := xsdonvif.PTZSpeedPanTilt{ + PanTilt: panTiltVector, } + resp, err := device.CallMethod(ptz.ContinuousMove{ - ProfileToken: token, - Velocity: xsd.PTZSpeedPanTilt{ - PanTilt: panTiltVector, - }, + ProfileToken: &token, + Velocity: velocity, }) var b []byte @@ -563,7 +590,7 @@ func TiltUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations return err } -func ZoomUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, zoom float64, speed float64, wait time.Duration) error { +func ZoomUntilPosition(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken, zoom float64, speed float64, wait time.Duration) error { position, err := GetPosition(device, token) if position.Zoom.X >= zoom-0.005 && position.Zoom.X <= zoom+0.005 { @@ -577,13 +604,13 @@ func ZoomUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations directionZ = speed * -1 } - zoomVector := xsd.Vector1D{ + zoomVector := xsdonvif.Vector1D{ X: directionZ, - Space: configuration.PTZConfiguration.DefaultContinuousZoomVelocitySpace, + Space: configuration.PTZConfiguration[0].DefaultContinuousZoomVelocitySpace, } resp, err := device.CallMethod(ptz.ContinuousMove{ - ProfileToken: token, - Velocity: xsd.PTZSpeedZoom{ + ProfileToken: &token, + Velocity: xsdonvif.PTZSpeedZoom{ Zoom: zoomVector, }, }) @@ -628,17 +655,17 @@ func ZoomUntilPosition(device *onvif.Device, configuration ptz.GetConfigurations return err } -func ContinuousPanTilt(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, pan float64, tilt float64) error { +func ContinuousPanTilt(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken, pan float64, tilt float64) error { - panTiltVector := xsd.Vector2D{ + panTiltVector := xsdonvif.Vector2D{ X: pan, Y: tilt, - Space: configuration.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace, + Space: configuration.PTZConfiguration[0].DefaultContinuousPanTiltVelocitySpace, } resp, err := device.CallMethod(ptz.ContinuousMove{ - ProfileToken: token, - Velocity: xsd.PTZSpeedPanTilt{ + ProfileToken: &token, + Velocity: xsdonvif.PTZSpeedPanTilt{ PanTilt: panTiltVector, }, }) @@ -674,18 +701,20 @@ func ContinuousPanTilt(device *onvif.Device, configuration ptz.GetConfigurations return err } -func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, zoom float64) error { +func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsdonvif.ReferenceToken, zoom float64) error { - zoomVector := xsd.Vector1D{ + zoomVector := xsdonvif.Vector1D{ X: zoom, - Space: configuration.PTZConfiguration.DefaultContinuousZoomVelocitySpace, + Space: configuration.PTZConfiguration[0].DefaultContinuousZoomVelocitySpace, + } + + velocity := xsdonvif.PTZSpeedZoom{ + Zoom: zoomVector, } resp, err := device.CallMethod(ptz.ContinuousMove{ - ProfileToken: token, - Velocity: xsd.PTZSpeedZoom{ - Zoom: zoomVector, - }, + ProfileToken: &token, + Velocity: &velocity, }) var b []byte @@ -731,34 +760,6 @@ func GetCapabilitiesFromDevice(dev *onvif.Device) []string { } } } - - var getCapabilitiesResponse device.GetCapabilitiesResponse - resp, err := dev.CallMethod(device.GetCapabilities{ - Category: "All", - }) - - var b []byte - if resp != nil { - b, err = io.ReadAll(resp.Body) - resp.Body.Close() - } - if err == nil { - stringBody := string(b) - decodedXML, et, err := getXMLNode(stringBody, "GetCapabilitiesResponse") - if err != nil { - log.Log.Error("GetCapabilitiesResponse: " + err.Error()) - //return err - } else { - if err := decodedXML.DecodeElement(&getCapabilitiesResponse, et); err != nil { - log.Log.Error("GetCapabilitiesResponse: " + err.Error()) - // return err - } - //return err - } - } else { - log.Log.Error("GoToPresetFromDevice: " + err.Error()) - } - return capabilities } @@ -818,10 +819,10 @@ func GoToPresetFromDevice(device *onvif.Device, presetName string) error { // Get token from the first profile token, err := GetTokenFromProfile(device, 0) if err == nil { - + preset := xsdonvif.ReferenceToken(presetName) resp, err := device.CallMethod(ptz.GotoPreset{ - ProfileToken: token, - PresetToken: xsd.ReferenceToken(presetName), + ProfileToken: &token, + PresetToken: &preset, }) var b []byte if resp != nil { @@ -856,34 +857,34 @@ func GetPTZFunctionsFromDevice(configurations ptz.GetConfigurationsResponse) ([] canZoom := false canPanTilt := false - if configurations.PTZConfiguration.DefaultAbsolutePantTiltPositionSpace != "" { + if configurations.PTZConfiguration[0].DefaultAbsolutePantTiltPositionSpace != nil { functions = append(functions, "AbsolutePanTiltMove") canPanTilt = true } - if configurations.PTZConfiguration.DefaultAbsoluteZoomPositionSpace != "" { + if configurations.PTZConfiguration[0].DefaultAbsoluteZoomPositionSpace != nil { functions = append(functions, "AbsoluteZoomMove") canZoom = true } - if configurations.PTZConfiguration.DefaultRelativePanTiltTranslationSpace != "" { + if configurations.PTZConfiguration[0].DefaultRelativePanTiltTranslationSpace != nil { functions = append(functions, "RelativePanTiltMove") canPanTilt = true } - if configurations.PTZConfiguration.DefaultRelativeZoomTranslationSpace != "" { + if configurations.PTZConfiguration[0].DefaultRelativeZoomTranslationSpace != nil { functions = append(functions, "RelativeZoomMove") canZoom = true } - if configurations.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace != "" { + if configurations.PTZConfiguration[0].DefaultContinuousPanTiltVelocitySpace != nil { functions = append(functions, "ContinuousPanTiltMove") canPanTilt = true } - if configurations.PTZConfiguration.DefaultContinuousZoomVelocitySpace != "" { + if configurations.PTZConfiguration[0].DefaultContinuousZoomVelocitySpace != nil { functions = append(functions, "ContinuousZoomMove") canZoom = true } - if configurations.PTZConfiguration.DefaultPTZSpeed != nil { + if configurations.PTZConfiguration[0].DefaultPTZSpeed != nil { functions = append(functions, "PTZSpeed") } - if configurations.PTZConfiguration.DefaultPTZTimeout != "" { + if configurations.PTZConfiguration[0].DefaultPTZTimeout != nil { functions = append(functions, "PTZTimeout") } @@ -937,6 +938,199 @@ func VerifyOnvifConnection(c *gin.Context) { } } +type ONVIFEvents struct { + Key string + Type string + Value string +} + +// ONVIF has a specific profile that requires a subscription to receive events. +// These events can show if an input or output is active or inactive, and also other events. +// For the time being we are only interested in the input and output events, but this can be extended in the future. +func GetEventMessages(dev *onvif.Device) ([]ONVIFEvents, error) { + + var eventsArray []ONVIFEvents + + // We'll create a subscription to the device + // This will allow us to receive events from the device + var createPullPointSubscriptionResponse event.CreatePullPointSubscriptionResponse + + // For the time being we are just interested in the digital inputs and outputs, therefore + // we have set the topic to the followin filter. + terminate := xsd.String("PT10S") + resp, err := dev.CallMethod(event.CreatePullPointSubscription{ + InitialTerminationTime: &terminate, + Filter: &event.FilterType{ + TopicExpression: &event.TopicExpressionType{ + Dialect: xsd.String("http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet"), + TopicKinds: "tns1:Device/Trigger//.", + }, + }, + }) + var b2 []byte + if resp != nil { + b2, err = io.ReadAll(resp.Body) + resp.Body.Close() + if err == nil { + stringBody := string(b2) + decodedXML, et, err := getXMLNode(stringBody, "CreatePullPointSubscriptionResponse") + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + } else { + if err := decodedXML.DecodeElement(&createPullPointSubscriptionResponse, et); err != nil { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + } else { + // We were able to create a subscription to the device. Now pull some messages from the subscription. + subscriptionURI := string(createPullPointSubscriptionResponse.SubscriptionReference.Address) + if subscriptionURI == "" { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): subscriptionURI is empty") + } else { + pullMessage := event.PullMessages{ + Timeout: xsd.Duration("PT5S"), + MessageLimit: 30, + } + requestBody, err := xml.Marshal(pullMessage) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + } + res, err := dev.SendSoap(string(subscriptionURI), string(requestBody)) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + } + + var pullMessagesResponse event.PullMessagesResponse + if res != nil { + bs, err := io.ReadAll(res.Body) + res.Body.Close() + + stringBody := string(bs) + decodedXML, et, err := getXMLNode(stringBody, "PullMessagesResponse") + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) + } else { + if err := decodedXML.DecodeElement(&pullMessagesResponse, et); err != nil { + log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) + } else { + log.Log.Info("onvif.main.GetEventMessages(pullMessages): " + stringBody) + } + } + } + + // Look for Source of input and output + for _, message := range pullMessagesResponse.NotificationMessage { + if message.Topic.TopicKinds == "tns1:Device/Trigger/Relay" { + if len(message.Message.Message.Data.SimpleItem) > 0 { + if message.Message.Message.Data.SimpleItem[0].Name == "LogicalState" { + key := string(message.Message.Message.Source.SimpleItem[0].Value) + value := string(message.Message.Message.Data.SimpleItem[0].Value) + log.Log.Debug("onvif.main.GetEventMessages(pullMessages) output: " + key + " " + value) + + // Depending on the onvif library they might use different values for active and inactive. + if value == "active" || value == "1" { + value = "true" + } else if value == "inactive" || value == "0" { + value = "false" + } + + eventsArray = append(eventsArray, ONVIFEvents{ + Key: key, + Type: "output", + Value: value, + }) + } + } + } else if message.Topic.TopicKinds == "tns1:Device/Trigger/DigitalInput" { + if len(message.Message.Message.Data.SimpleItem) > 0 { + if message.Message.Message.Data.SimpleItem[0].Name == "LogicalState" { + key := string(message.Message.Message.Source.SimpleItem[0].Value) + value := string(message.Message.Message.Data.SimpleItem[0].Value) + log.Log.Debug("onvif.main.GetEventMessages(pullMessages) input: " + key + " " + value) + + // Depending on the onvif library they might use different values for active and inactive. + if value == "active" || value == "1" { + value = "true" + } else if value == "inactive" || value == "0" { + value = "false" + } + + eventsArray = append(eventsArray, ONVIFEvents{ + Key: key, + Type: "input", + Value: value, + }) + } + } + } + } + + // Unscubscribe from the device + unsubscribe := event.Unsubscribe{} + requestBody, err = xml.Marshal(unsubscribe) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + } + res, err = dev.SendSoap(subscriptionURI, string(requestBody)) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + } + if res != nil { + bs, err := io.ReadAll(res.Body) + res.Body.Close() + if err == nil { + stringBody := string(bs) + log.Log.Info("onvif.main.GetEventMessages(unsubscribe): " + stringBody) + } + } + } + } + } + } + } + return eventsArray, err +} + +// This method will get the digital inputs from the device. +// But will not give any status information. +func GetDigitalInputs(dev *onvif.Device) (device.GetDigitalInputsResponse, error) { + + events, err := GetEventMessages(dev) + if err == nil { + for _, event := range events { + log.Log.Debug("onvif.main.GetDigitalInputs(): " + event.Key + " " + event.Value) + } + } + + // We'll try to receive the relay outputs from the server + var digitalinputs device.GetDigitalInputsResponse + + var b []byte + resp, err := dev.CallMethod(deviceio.GetDigitalInputs{}) + if resp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } + + if err == nil { + if err == nil { + stringBody := string(b) + decodedXML, et, err := getXMLNode(stringBody, "GetDigitalInputsResponse") + if err != nil { + log.Log.Error("onvif.main.GetDigitalInputs(): " + err.Error()) + return digitalinputs, err + } else { + if err := decodedXML.DecodeElement(&digitalinputs, et); err != nil { + log.Log.Debug("onvif.main.GetDigitalInputs(): " + err.Error()) + return digitalinputs, err + } + } + } + } + + return digitalinputs, err +} + +// This method will get the relay outputs from the device. +// But will not give any status information. func GetRelayOutputs(dev *onvif.Device) (device.GetRelayOutputsResponse, error) { // We'll try to receive the relay outputs from the server var relayoutputs device.GetRelayOutputsResponse @@ -964,9 +1158,50 @@ func GetRelayOutputs(dev *onvif.Device) (device.GetRelayOutputsResponse, error) } } } + return relayoutputs, err } +func TriggerRelayOutput(dev *onvif.Device, output string) (setRelayOutputState device.SetRelayOutputStateResponse, err error) { + err = nil + + // Get all outputs + relayoutputs, err := GetRelayOutputs(dev) + + // For the moment we expect a single output + // However in theory there might be multiple outputs. We might need to change + // this in the future "kerberos-io/onvif" library. + if err == nil { + token := relayoutputs.RelayOutputs.Token + + if output == string(token) { + outputState := device.SetRelayOutputState{ + RelayOutputToken: token, + LogicalState: "active", + } + + resp, errResp := dev.CallMethod(outputState) + var b []byte + if errResp != nil { + b, err = io.ReadAll(resp.Body) + resp.Body.Close() + } + stringBody := string(b) + decodedXML, et, errXML := getXMLNode(stringBody, "SetRelayOutputStateResponse") + if errXML != nil { + log.Log.Error("onvif.main.TriggerRelayOutput(): " + errXML.Error()) + return + } else { + if errDecode := decodedXML.DecodeElement(&setRelayOutputState, et); err != nil { + log.Log.Debug("onvif.main.TriggerRelayOutput(): " + errDecode.Error()) + return + } + } + } + } + return +} + func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, error) { xmlBytes := bytes.NewBufferString(xmlBody) decodedXML := xml.NewDecoder(xmlBytes) diff --git a/machinery/src/routers/http/methods.go b/machinery/src/routers/http/methods.go index f4383909..99dde8f5 100644 --- a/machinery/src/routers/http/methods.go +++ b/machinery/src/routers/http/methods.go @@ -361,23 +361,152 @@ func GoToOnvifPreset(c *gin.Context) { // @in header // @name Authorization // @Tags camera -// @Param cameraConfig body models.OnvifPreset true "Camera Config" +// @Param config body models.OnvifCredentials true "OnvifCredentials" // @Summary Will get the digital inputs from the ONVIF device. // @Description Will get the digital inputs from the ONVIF device. // @Success 200 {object} models.APIResponse func DoGetDigitalInputs(c *gin.Context) { - var onvifPreset models.OnvifPreset - err := c.BindJSON(&onvifPreset) + var onvifCredentials models.OnvifCredentials + err := c.BindJSON(&onvifCredentials) - if err == nil || onvifPreset.OnvifCredentials.ONVIFXAddr != "" { + if err == nil && onvifCredentials.ONVIFXAddr != "" { configuration := &models.Configuration{ Config: models.Config{ Capture: models.Capture{ IPCamera: models.IPCamera{ - ONVIFXAddr: onvifPreset.OnvifCredentials.ONVIFXAddr, - ONVIFUsername: onvifPreset.OnvifCredentials.ONVIFUsername, - ONVIFPassword: onvifPreset.OnvifCredentials.ONVIFPassword, + ONVIFXAddr: onvifCredentials.ONVIFXAddr, + ONVIFUsername: onvifCredentials.ONVIFUsername, + ONVIFPassword: onvifCredentials.ONVIFPassword, + }, + }, + }, + } + + cameraConfiguration := configuration.Config.Capture.IPCamera + device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + if err == nil { + events, err := onvif.GetEventMessages(device) + if err == nil { + // Get the digital inputs from the device + var inputs []onvif.ONVIFEvents + for _, event := range events { + if event.Type == "input" { + inputs = append(inputs, event) + } + } + + c.JSON(200, gin.H{ + "data": inputs, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } +} + +// DoGetRelayOutputs godoc +// @Router /api/camera/onvif/outputs [post] +// @ID get-relay-outputs +// @Security Bearer +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization +// @Tags camera +// @Param config body models.OnvifCredentials true "OnvifCredentials" +// @Summary Will get the relay outputs from the ONVIF device. +// @Description Will get the relay outputs from the ONVIF device. +// @Success 200 {object} models.APIResponse +func DoGetRelayOutputs(c *gin.Context) { + var onvifCredentials models.OnvifCredentials + err := c.BindJSON(&onvifCredentials) + + if err == nil && onvifCredentials.ONVIFXAddr != "" { + + configuration := &models.Configuration{ + Config: models.Config{ + Capture: models.Capture{ + IPCamera: models.IPCamera{ + ONVIFXAddr: onvifCredentials.ONVIFXAddr, + ONVIFUsername: onvifCredentials.ONVIFUsername, + ONVIFPassword: onvifCredentials.ONVIFPassword, + }, + }, + }, + } + + cameraConfiguration := configuration.Config.Capture.IPCamera + device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + if err == nil { + events, err := onvif.GetEventMessages(device) + if err == nil { + // Get the digital inputs from the device + var outputs []onvif.ONVIFEvents + for _, event := range events { + if event.Type == "output" { + outputs = append(outputs, event) + } + } + c.JSON(200, gin.H{ + "data": outputs, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } +} + +// DoTriggerRelayOutput godoc +// @Router /api/camera/onvif/outputs/{output} [post] +// @ID trigger-relay-output +// @Security Bearer +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization +// @Tags camera +// @Param config body models.OnvifCredentials true "OnvifCredentials" +// @Param output path string true "Output" +// @Summary Will trigger the relay output from the ONVIF device. +// @Description Will trigger the relay output from the ONVIF device. +// @Success 200 {object} models.APIResponse +func DoTriggerRelayOutput(c *gin.Context) { + var onvifCredentials models.OnvifCredentials + err := c.BindJSON(&onvifCredentials) + + // Get the output from the url + output := c.Param("output") + + if err == nil && onvifCredentials.ONVIFXAddr != "" && output != "" { + + configuration := &models.Configuration{ + Config: models.Config{ + Capture: models.Capture{ + IPCamera: models.IPCamera{ + ONVIFXAddr: onvifCredentials.ONVIFXAddr, + ONVIFUsername: onvifCredentials.ONVIFUsername, + ONVIFPassword: onvifCredentials.ONVIFPassword, }, }, }, @@ -386,7 +515,7 @@ func DoGetDigitalInputs(c *gin.Context) { cameraConfiguration := configuration.Config.Capture.IPCamera device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - outputs, err := onvif.GetRelayOutputs(device) + outputs, err := onvif.TriggerRelayOutput(device, output) if err == nil { c.JSON(200, gin.H{ "data": outputs, diff --git a/machinery/src/routers/http/routes.go b/machinery/src/routers/http/routes.go index 3d47bbf7..0ce02c9d 100644 --- a/machinery/src/routers/http/routes.go +++ b/machinery/src/routers/http/routes.go @@ -92,6 +92,8 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect api.POST("/camera/onvif/pantilt", DoOnvifPanTilt) api.POST("/camera/onvif/zoom", DoOnvifZoom) api.POST("/camera/onvif/inputs", DoGetDigitalInputs) + api.POST("/camera/onvif/outputs", DoGetRelayOutputs) + api.POST("/camera/onvif/outputs/:output", DoTriggerRelayOutput) api.POST("/camera/verify/:streamType", capture.VerifyCamera) // Secured endpoints.. From c48e3a56839caea59acd3cc536b6cbd3f13f8590 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 25 Dec 2023 21:01:52 +0100 Subject: [PATCH 60/81] Update go.mod --- machinery/go.mod | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/machinery/go.mod b/machinery/go.mod index 28538dfd..331c1d96 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -1,10 +1,10 @@ module github.com/kerberos-io/agent/machinery -go 1.19 +go 1.20 //replace github.com/kerberos-io/joy4 v1.0.63 => ../../../../github.com/kerberos-io/joy4 -replace github.com/kerberos-io/onvif v0.0.9 => ../../../../github.com/kerberos-io/onvif +//replace github.com/kerberos-io/onvif v0.0.9 => ../../../../github.com/kerberos-io/onvif require ( github.com/InVisionApp/conjungo v1.1.0 @@ -25,7 +25,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/kellydunn/golang-geo v0.7.0 github.com/kerberos-io/joy4 v1.0.64 - github.com/kerberos-io/onvif v0.0.9 + github.com/kerberos-io/onvif v0.0.10 github.com/minio/minio-go/v6 v6.0.57 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pion/rtp v1.8.3 From 400457af9fd9e657209a80144c8e17afa417a5db Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 25 Dec 2023 21:37:35 +0100 Subject: [PATCH 61/81] upgrade onvif to 14 --- machinery/go.mod | 4 ++-- machinery/go.sum | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/machinery/go.mod b/machinery/go.mod index 331c1d96..97a339b3 100644 --- a/machinery/go.mod +++ b/machinery/go.mod @@ -4,7 +4,7 @@ go 1.20 //replace github.com/kerberos-io/joy4 v1.0.63 => ../../../../github.com/kerberos-io/joy4 -//replace github.com/kerberos-io/onvif v0.0.9 => ../../../../github.com/kerberos-io/onvif +//replace github.com/kerberos-io/onvif v0.0.10 => ../../../../github.com/kerberos-io/onvif require ( github.com/InVisionApp/conjungo v1.1.0 @@ -25,7 +25,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/kellydunn/golang-geo v0.7.0 github.com/kerberos-io/joy4 v1.0.64 - github.com/kerberos-io/onvif v0.0.10 + github.com/kerberos-io/onvif v0.0.14 github.com/minio/minio-go/v6 v6.0.57 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pion/rtp v1.8.3 diff --git a/machinery/go.sum b/machinery/go.sum index 013a6eef..9afabb53 100644 --- a/machinery/go.sum +++ b/machinery/go.sum @@ -271,6 +271,8 @@ github.com/kellydunn/golang-geo v0.7.0 h1:A5j0/BvNgGwY6Yb6inXQxzYwlPHc6WVZR+Mrar github.com/kellydunn/golang-geo v0.7.0/go.mod h1:YYlQPJ+DPEzrHx8kT3oPHC/NjyvCCXE+IuKGKdrjrcU= github.com/kerberos-io/joy4 v1.0.64 h1:gTUSotHSOhp9mNqEecgq88tQHvpj7TjmrvPUsPm0idg= github.com/kerberos-io/joy4 v1.0.64/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU= +github.com/kerberos-io/onvif v0.0.14 h1:ZcpsIAFbuR/mEuTmMnyHM2sLX7OsnQ5sCjmhsgL33VI= +github.com/kerberos-io/onvif v0.0.14/go.mod h1:NAsn+VuMB/hvrm40xULWyiLJ/ArB5nAecX5hvDo5gcA= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= From 4f5597c441203a3c77ab84773546d1c12c196280 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 25 Dec 2023 23:10:04 +0100 Subject: [PATCH 62/81] remove unnecessary prints --- machinery/src/onvif/main.go | 40 ++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 43eeb318..cb19e14a 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -5,10 +5,7 @@ import ( "encoding/json" "encoding/xml" "errors" - "fmt" "io" - "io/ioutil" - "net/http" "strconv" "strings" "time" @@ -205,11 +202,30 @@ func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, } else { getCapabilities := device.GetCapabilities{Category: []xsdonvif.CapabilityCategory{"All"}} - getCapabilitiesResponse, err := dev.CallMethod(getCapabilities) + resp, err := dev.CallMethod(getCapabilities) if err != nil { - fmt.Println(err) + log.Log.Error("onvif.ConnectToOnvifDevice(): " + err.Error()) + } + + var b []byte + if resp != nil { + b, err = io.ReadAll(resp.Body) + if err != nil { + log.Log.Error("onvif.ConnectToOnvifDevice(): " + err.Error()) + } + resp.Body.Close() + } + stringBody := string(b) + decodedXML, et, err := getXMLNode(stringBody, "GetCapabilitiesResponse") + if err != nil { + log.Log.Error("onvif.ConnectToOnvifDevice(): " + err.Error()) } else { - fmt.Println(readResponse(getCapabilitiesResponse)) + var capabilities device.GetCapabilitiesResponse + if err := decodedXML.DecodeElement(&capabilities, et); err != nil { + log.Log.Error("onvif.ConnectToOnvifDevice(): " + err.Error()) + } else { + log.Log.Debug("onvif.ConnectToOnvifDevice(): capabilities: " + strings.Join(GetCapabilitiesFromDevice(dev), ", ")) + } } log.Log.Info("onvif.ConnectToOnvifDevice(): successfully connected to device") @@ -218,14 +234,6 @@ func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, return dev, err } -func readResponse(resp *http.Response) string { - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - panic(err) - } - return string(b) -} - func GetTokenFromProfile(device *onvif.Device, profileId int) (xsdonvif.ReferenceToken, error) { // We aim to receive a profile token from the server var profileToken xsdonvif.ReferenceToken @@ -1011,7 +1019,7 @@ func GetEventMessages(dev *onvif.Device) ([]ONVIFEvents, error) { if err := decodedXML.DecodeElement(&pullMessagesResponse, et); err != nil { log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) } else { - log.Log.Info("onvif.main.GetEventMessages(pullMessages): " + stringBody) + log.Log.Debug("onvif.main.GetEventMessages(pullMessages): " + stringBody) } } } @@ -1078,7 +1086,7 @@ func GetEventMessages(dev *onvif.Device) ([]ONVIFEvents, error) { res.Body.Close() if err == nil { stringBody := string(bs) - log.Log.Info("onvif.main.GetEventMessages(unsubscribe): " + stringBody) + log.Log.Debug("onvif.main.GetEventMessages(unsubscribe): " + stringBody) } } } From e7fd0bd8a3638ba0948aa9e67dfde9d63a737197 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 27 Dec 2023 10:06:55 +0100 Subject: [PATCH 63/81] add logging output variable (json or text) + improve logging --- machinery/main.go | 8 +- machinery/src/capture/Gortsplib.go | 8 +- machinery/src/cloud/Cloud.go | 382 +++++++++++++++----------- machinery/src/components/Kerberos.go | 4 +- machinery/src/log/main.go | 34 ++- machinery/src/models/MQTT.go | 10 +- machinery/src/onvif/main.go | 299 ++++++++++++-------- machinery/src/routers/http/methods.go | 55 ++-- machinery/src/routers/mqtt/main.go | 64 ++--- machinery/src/webrtc/main.go | 60 ++-- 10 files changed, 540 insertions(+), 384 deletions(-) diff --git a/machinery/main.go b/machinery/main.go index 4339c997..48bb9e29 100644 --- a/machinery/main.go +++ b/machinery/main.go @@ -76,7 +76,11 @@ func main() { if logLevel == "" { logLevel = "info" } - log.Log.Init(logLevel, configDirectory, timezone) + logOutput := os.Getenv("LOG_OUTPUT") + if logOutput == "" { + logOutput = "text" + } + log.Log.Init(logLevel, logOutput, configDirectory, timezone) switch action { @@ -137,7 +141,7 @@ func main() { // Set timezone timezone, _ := time.LoadLocation(configuration.Config.Timezone) - log.Log.Init(logLevel, configDirectory, timezone) + log.Log.Init(logLevel, logOutput, configDirectory, timezone) // Check if we have a device Key or not, if not // we will generate one. diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index eb80af30..d9db09f6 100644 --- a/machinery/src/capture/Gortsplib.go +++ b/machinery/src/capture/Gortsplib.go @@ -170,7 +170,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.VideoH265Media = mediH265 g.VideoH265Forma = formaH265 if mediH265 == nil { - log.Log.Info("capture.golibrtsp.Connect(H265): " + "video media not found") + log.Log.Debug("capture.golibrtsp.Connect(H265): " + "video media not found") } else { // setup a video media _, err = g.Client.Setup(desc.BaseURL, mediH265, 0, 0) @@ -225,7 +225,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.AudioG711Media = audioMedi g.AudioG711Forma = audioForma if audioMedi == nil { - log.Log.Info("capture.golibrtsp.Connect(G711): " + "audio media not found") + log.Log.Debug("capture.golibrtsp.Connect(G711): " + "audio media not found") } else { // setup a audio media _, err = g.Client.Setup(desc.BaseURL, audioMedi, 0, 0) @@ -260,7 +260,7 @@ func (g *Golibrtsp) Connect(ctx context.Context) (err error) { g.AudioMPEG4Media = audioMediMPEG4 g.AudioMPEG4Forma = audioFormaMPEG4 if audioMediMPEG4 == nil { - log.Log.Info("capture.golibrtsp.Connect(MPEG4): " + "audio media not found") + log.Log.Debug("capture.golibrtsp.Connect(MPEG4): " + "audio media not found") } else { // setup a audio media _, err = g.Client.Setup(desc.BaseURL, audioMediMPEG4, 0, 0) @@ -326,7 +326,7 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) { g.AudioG711MediaBackChannel = audioMediBackChannel g.AudioG711FormaBackChannel = audioFormaBackChannel if audioMediBackChannel == nil { - log.Log.Info("capture.golibrtsp.ConnectBackChannel(): " + "audio backchannel not found") + log.Log.Error("capture.golibrtsp.ConnectBackChannel(): audio backchannel not found, not a real error, however you might expect a backchannel. One of the reasons might be that the device already has an active client connected to the backchannel.") err = errors.New("no audio backchannel found") } else { // setup a audio media diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index b00132c6..298cf9bf 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -6,7 +6,7 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io/ioutil" + "io" "os" "strings" @@ -164,10 +164,10 @@ func GetSystemInfo() (models.System, error) { // Read agent version version, err := os.Open("./version") - defer version.Close() agentVersion = "unknown" if err == nil { - agentVersionBytes, err := ioutil.ReadAll(version) + defer version.Close() + agentVersionBytes, err := io.ReadAll(version) agentVersion = string(agentVersionBytes) if err != nil { log.Log.Error(err.Error()) @@ -218,7 +218,7 @@ func GetSystemInfo() (models.System, error) { } func HandleHeartBeat(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time) { - log.Log.Debug("HandleHeartBeat: started") + log.Log.Debug("cloud.HandleHeartBeat(): started") var client *http.Client if os.Getenv("AGENT_TLS_INSECURE") == "true" { @@ -229,14 +229,102 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models. } else { client = &http.Client{} } + config := configuration.Config + + // Get a pull point address + var pullPointAddress string + if config.Capture.IPCamera.ONVIFXAddr != "" { + cameraConfiguration := configuration.Config.Capture.IPCamera + device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + if err == nil { + pullPointAddress, err = onvif.CreatePullPointSubscription(device) + if err != nil { + log.Log.Error("cloud.HandleHeartBeat(): error while creating pull point subscription: " + err.Error()) + } + } + } loop: for { - + // Configuration migh have changed, so we will reload it. config := configuration.Config + // We'll check ONVIF capabilitites anyhow.. Verify if we have PTZ, presets and inputs/outputs. + // For the inputs we will keep track of a the inputs and outputs state. + + onvifEnabled := "false" + onvifZoom := "false" + onvifPanTilt := "false" + onvifPresets := "false" + var onvifPresetsList []byte + var onvifEventsList []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 { + 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("cloud.HandleHeartBeat(): error while marshalling presets: " + err.Error()) + onvifPresetsList = []byte("[]") + } + } else { + if err != nil { + log.Log.Debug("cloud.HandleHeartBeat(): error while getting presets: " + err.Error()) + } else { + log.Log.Debug("cloud.HandleHeartBeat(): no presets found.") + } + onvifPresetsList = []byte("[]") + } + } else { + log.Log.Error("cloud.HandleHeartBeat(): error while getting PTZ configurations: " + err.Error()) + onvifPresetsList = []byte("[]") + } + + // We will also fetch some events, to know the status of the inputs and outputs. + // More event types might be added. + if pullPointAddress != "" { + events, err := onvif.GetEventMessages(device, pullPointAddress) + if err == nil && len(events) > 0 { + onvifEventsList, err = json.Marshal(events) + if err != nil { + log.Log.Error("cloud.HandleHeartBeat(): error while marshalling events: " + err.Error()) + onvifEventsList = []byte("[]") + } + } else { + log.Log.Debug("cloud.HandleHeartBeat(): no events found.") + onvifEventsList = []byte("[]") + } + } else { + log.Log.Debug("cloud.HandleHeartBeat(): no pull point address found.") + onvifEventsList = []byte("[]") + } + + } else { + log.Log.Error("cloud.HandleHeartBeat(): error while connecting to ONVIF device: " + err.Error()) + onvifPresetsList = []byte("[]") + } + } else { + log.Log.Debug("cloud.HandleHeartBeat(): ONVIF is not enabled.") + onvifPresetsList = []byte("[]") + } + + // We'll capture some more metrics, and send it to Hub, if not in offline mode ofcourse ;) ;) if config.Offline == "true" { - log.Log.Debug("HandleHeartBeat: stopping as Offline is enabled.") + log.Log.Debug("cloud.HandleHeartBeat(): stopping as Offline is enabled.") } else { hubURI := config.HeartbeatURI @@ -302,72 +390,6 @@ loop: 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 - var onvifEventsList []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 { - 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.Debug("HandleHeartBeat: error while getting presets: " + err.Error()) - } else { - log.Log.Debug("HandleHeartBeat: no presets found.") - } - onvifPresetsList = []byte("[]") - } - } else { - log.Log.Error("HandleHeartBeat: error while getting PTZ configurations: " + err.Error()) - onvifPresetsList = []byte("[]") - } - - // We will also fetch some events, to know the status of the inputs and outputs. - // More event types might be added. - events, err := onvif.GetEventMessages(device) - if err == nil && len(events) > 0 { - onvifEventsList, err = json.Marshal(events) - if err != nil { - log.Log.Error("HandleHeartBeat: error while marshalling events: " + err.Error()) - onvifEventsList = []byte("[]") - } - } else { - log.Log.Debug("HandleHeartBeat: no events found.") - onvifEventsList = []byte("[]") - } - - } else { - 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("[]") - } - // We need a hub URI and hub public key before we will send a heartbeat if hubURI != "" && key != "" { @@ -418,7 +440,7 @@ loop: encrypted, err := encryption.AesEncrypt([]byte(object), privateKey) if err != nil { encrypted = []byte("") - log.Log.Error("HandleHeartBeat: error while encrypting data: " + err.Error()) + log.Log.Error("cloud.HandleHeartBeat(): error while encrypting data: " + err.Error()) } // Base64 encode the encrypted data. @@ -440,15 +462,15 @@ loop: } if err == nil && resp.StatusCode == 200 { communication.CloudTimestamp.Store(time.Now().Unix()) - log.Log.Info("HandleHeartBeat: (200) Heartbeat received by Kerberos Hub.") + log.Log.Info("cloud.HandleHeartBeat(): (200) Heartbeat received by Kerberos Hub.") } else { if communication.CloudTimestamp != nil && communication.CloudTimestamp.Load() != nil { communication.CloudTimestamp.Store(int64(0)) } - log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Hub.") + log.Log.Error("cloud.HandleHeartBeat(): (400) Something went wrong while sending to Kerberos Hub.") } } else { - log.Log.Error("HandleHeartBeat: Disabled as we do not have a public key defined.") + log.Log.Error("cloud.HandleHeartBeat(): Disabled as we do not have a public key defined.") } // If we have a Kerberos Vault connected, we will also send some analytics @@ -504,9 +526,9 @@ loop: resp.Body.Close() } if err == nil && resp.StatusCode == 200 { - log.Log.Info("HandleHeartBeat: (200) Heartbeat received by Kerberos Vault.") + log.Log.Info("cloud.HandleHeartBeat(): (200) Heartbeat received by Kerberos Vault.") } else { - log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Vault.") + log.Log.Error("cloud.HandleHeartBeat(): (400) Something went wrong while sending to Kerberos Vault.") } } } @@ -520,7 +542,15 @@ loop: } } - log.Log.Debug("HandleHeartBeat: finished") + if pullPointAddress != "" { + cameraConfiguration := configuration.Config.Capture.IPCamera + device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + if err == nil { + onvif.Unsubscribe(device, pullPointAddress) + } + } + + log.Log.Debug("cloud.HandleHeartBeat(): finished") } func HandleLiveStreamSD(livestreamCursor *packets.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, rtspClient capture.RTSPClient) { @@ -604,7 +634,7 @@ func HandleLiveStreamHD(livestreamCursor *packets.QueueCursor, configuration *mo config := configuration.Config if config.Offline == "true" { - log.Log.Debug("HandleLiveStreamHD: stopping as Offline is enabled.") + log.Log.Debug("cloud.HandleLiveStreamHD(): stopping as Offline is enabled.") } else { // Check if we need to enable the live stream @@ -619,15 +649,15 @@ func HandleLiveStreamHD(livestreamCursor *packets.QueueCursor, configuration *mo if config.Capture.ForwardWebRTC == "true" { } else { - log.Log.Info("HandleLiveStreamHD: Waiting for peer connections.") + log.Log.Info("cloud.HandleLiveStreamHD(): Waiting for peer connections.") for handshake := range communication.HandleLiveHDHandshake { - log.Log.Info("HandleLiveStreamHD: setting up a peer connection.") + log.Log.Info("cloud.HandleLiveStreamHD(): setting up a peer connection.") go webrtc.InitializeWebRTCConnection(configuration, communication, mqttClient, videoTrack, audioTrack, handshake) } } } else { - log.Log.Debug("HandleLiveStreamHD: stopping as Liveview is disabled.") + log.Log.Debug("cloud.HandleLiveStreamHD(): stopping as Liveview is disabled.") } } } @@ -670,34 +700,34 @@ func VerifyHub(c *gin.Context) { resp, err := client.Do(req) if err == nil { - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) defer resp.Body.Close() if err == nil { if resp.StatusCode == 200 { c.JSON(200, body) } else { c.JSON(400, models.APIResponse{ - Data: "Something went wrong while reaching the Kerberos Hub API: " + string(body), + Data: "cloud.VerifyHub(): something went wrong while reaching the Kerberos Hub API: " + string(body), }) } } else { c.JSON(400, models.APIResponse{ - Data: "Something went wrong while ready the response body: " + err.Error(), + Data: "cloud.VerifyHub(): something went wrong while ready the response body: " + err.Error(), }) } } else { c.JSON(400, models.APIResponse{ - Data: "Something went wrong while reaching to the Kerberos Hub API: " + hubURI, + Data: "cloud.VerifyHub(): something went wrong while reaching to the Kerberos Hub API: " + hubURI, }) } } else { c.JSON(400, models.APIResponse{ - Data: "Something went wrong while creating the HTTP request: " + err.Error(), + Data: "cloud.VerifyHub(): something went wrong while creating the HTTP request: " + err.Error(), }) } } else { c.JSON(400, models.APIResponse{ - Data: "Something went wrong while receiving the config " + err.Error(), + Data: "cloud.VerifyHub(): something went wrong while receiving the config " + err.Error(), }) } } @@ -728,8 +758,8 @@ func VerifyPersistence(c *gin.Context, configDirectory string) { config.HubKey == "" || config.HubPrivateKey == "" || config.S3.Region == "" { - msg := "VerifyPersistence: Kerberos Hub not properly configured." - log.Log.Info(msg) + msg := "cloud.VerifyPersistence(kerberoshub): Kerberos Hub not properly configured." + log.Log.Error(msg) c.JSON(400, models.APIResponse{ Data: msg, }) @@ -738,7 +768,7 @@ func VerifyPersistence(c *gin.Context, configDirectory string) { // Open test-480p.mp4 file, err := os.Open(configDirectory + "/data/test-480p.mp4") if err != nil { - msg := "VerifyPersistence: error reading test-480p.mp4: " + err.Error() + msg := "cloud.VerifyPersistence(kerberoshub): error reading test-480p.mp4: " + err.Error() log.Log.Error(msg) c.JSON(400, models.APIResponse{ Data: msg, @@ -748,7 +778,7 @@ func VerifyPersistence(c *gin.Context, configDirectory string) { req, err := http.NewRequest("POST", config.HubURI+"/storage/upload", file) if err != nil { - msg := "VerifyPersistence: error reading Kerberos Hub HEAD request, " + config.HubURI + "/storage: " + err.Error() + msg := "cloud.VerifyPersistence(kerberoshub): error reading Kerberos Hub HEAD request, " + config.HubURI + "/storage: " + err.Error() log.Log.Error(msg) c.JSON(400, models.APIResponse{ Data: msg, @@ -782,21 +812,21 @@ func VerifyPersistence(c *gin.Context, configDirectory string) { if err == nil && resp != nil { if resp.StatusCode == 200 { - msg := "VerifyPersistence: Upload allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")" + msg := "cloud.VerifyPersistence(kerberoshub): Upload allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")" log.Log.Info(msg) c.JSON(200, models.APIResponse{ Data: msg, }) } else { - msg := "VerifyPersistence: Upload NOT allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")" - log.Log.Info(msg) + msg := "cloud.VerifyPersistence(kerberoshub): Upload NOT allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")" + log.Log.Error(msg) c.JSON(400, models.APIResponse{ Data: msg, }) } } else { - msg := "VerifyPersistence: Error creating Kerberos Hub request" - log.Log.Info(msg) + msg := "cloud.VerifyPersistence(kerberoshub): Error creating Kerberos Hub request" + log.Log.Error(msg) c.JSON(400, models.APIResponse{ Data: msg, }) @@ -824,106 +854,134 @@ func VerifyPersistence(c *gin.Context, configDirectory string) { } req, err := http.NewRequest("POST", uri+"/ping", nil) - req.Header.Add("X-Kerberos-Storage-AccessKey", accessKey) - req.Header.Add("X-Kerberos-Storage-SecretAccessKey", secretAccessKey) - resp, err := client.Do(req) - if err == nil { - body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - if err == nil && resp.StatusCode == http.StatusOK { + req.Header.Add("X-Kerberos-Storage-AccessKey", accessKey) + req.Header.Add("X-Kerberos-Storage-SecretAccessKey", secretAccessKey) + resp, err := client.Do(req) - if provider != "" || directory != "" { + if err == nil { + body, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + if err == nil && resp.StatusCode == http.StatusOK { + + if provider != "" || directory != "" { + + // Generate a random name. + timestamp := time.Now().Unix() + fileName := strconv.FormatInt(timestamp, 10) + + "_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4" + + // Open test-480p.mp4 + file, err := os.Open(configDirectory + "/data/test-480p.mp4") + if err != nil { + msg := "cloud.VerifyPersistence(kerberosvault): error reading test-480p.mp4: " + err.Error() + log.Log.Error(msg) + c.JSON(400, models.APIResponse{ + Data: msg, + }) + } + defer file.Close() - // Generate a random name. - timestamp := time.Now().Unix() - fileName := strconv.FormatInt(timestamp, 10) + - "_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4" + req, err := http.NewRequest("POST", uri+"/storage", file) + if err == nil { - // Open 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) - c.JSON(400, models.APIResponse{ - Data: msg, - }) - } - defer file.Close() - - req, err := http.NewRequest("POST", uri+"/storage", file) - if err == nil { - - req.Header.Set("Content-Type", "video/mp4") - req.Header.Set("X-Kerberos-Storage-CloudKey", config.HubKey) - req.Header.Set("X-Kerberos-Storage-AccessKey", accessKey) - req.Header.Set("X-Kerberos-Storage-SecretAccessKey", secretAccessKey) - req.Header.Set("X-Kerberos-Storage-Provider", provider) - req.Header.Set("X-Kerberos-Storage-FileName", fileName) - req.Header.Set("X-Kerberos-Storage-Device", config.Key) - req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera") - req.Header.Set("X-Kerberos-Storage-Directory", directory) - - var client *http.Client - if os.Getenv("AGENT_TLS_INSECURE") == "true" { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + req.Header.Set("Content-Type", "video/mp4") + req.Header.Set("X-Kerberos-Storage-CloudKey", config.HubKey) + req.Header.Set("X-Kerberos-Storage-AccessKey", accessKey) + req.Header.Set("X-Kerberos-Storage-SecretAccessKey", secretAccessKey) + req.Header.Set("X-Kerberos-Storage-Provider", provider) + req.Header.Set("X-Kerberos-Storage-FileName", fileName) + req.Header.Set("X-Kerberos-Storage-Device", config.Key) + req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera") + req.Header.Set("X-Kerberos-Storage-Directory", directory) + + 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{} } - client = &http.Client{Transport: tr} - } else { - client = &http.Client{} - } - - resp, err := client.Do(req) - if err == nil { - if resp != nil { - body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - if err == nil { - if resp.StatusCode == 200 { - c.JSON(200, body) - } else { - c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Something went wrong while verifying your persistence settings. Make sure your provider is the same as the storage provider in your Kerberos Vault, and the relevant storage provider is configured properly.", - }) + resp, err := client.Do(req) + + if err == nil { + if resp != nil { + body, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + if err == nil { + if resp.StatusCode == 200 { + msg := "cloud.VerifyPersistence(kerberosvault): Upload allowed using the credentials provided (" + accessKey + ", " + secretAccessKey + ")" + log.Log.Info(msg) + c.JSON(200, models.APIResponse{ + Data: body, + }) + } else { + msg := "cloud.VerifyPersistence(kerberosvault): Something went wrong while verifying your persistence settings. Make sure your provider is the same as the storage provider in your Kerberos Vault, and the relevant storage provider is configured properly." + log.Log.Error(msg) + c.JSON(400, models.APIResponse{ + Data: msg, + }) + } } } + } else { + msg := "cloud.VerifyPersistence(kerberosvault): Upload of fake recording failed: " + err.Error() + log.Log.Error(msg) + c.JSON(400, models.APIResponse{ + Data: msg, + }) } } else { + msg := "cloud.VerifyPersistence(kerberosvault): Something went wrong while creating /storage POST request." + err.Error() + log.Log.Error(msg) c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Upload of fake recording failed: " + err.Error(), + Data: msg, }) } } else { + msg := "cloud.VerifyPersistence(kerberosvault): Provider and/or directory is missing from the request." + log.Log.Error(msg) c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Something went wrong while creating /storage POST request." + err.Error(), + Data: msg, }) } } else { + msg := "cloud.VerifyPersistence(kerberosvault): Something went wrong while verifying storage credentials: " + string(body) + log.Log.Error(msg) c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Provider and/or directory is missing from the request.", + Data: msg, }) } } else { + msg := "cloud.VerifyPersistence(kerberosvault): Something went wrong while verifying storage credentials:" + err.Error() + log.Log.Error(msg) c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Something went wrong while verifying storage credentials: " + string(body), + Data: msg, }) } } else { + msg := "cloud.VerifyPersistence(kerberosvault): Something went wrong while verifying storage credentials:" + err.Error() + log.Log.Error(msg) c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Something went wrong while verifying storage credentials:" + err.Error(), + Data: msg, }) } } else { + msg := "cloud.VerifyPersistence(kerberosvault): please fill-in the required Kerberos Vault credentials." + log.Log.Error(msg) c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: please fill-in the required Kerberos Vault credentials.", + Data: msg, }) } } } else { + msg := "cloud.VerifyPersistence(): No persistence was specified, so do not know what to verify:" + err.Error() + log.Log.Error(msg) c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: No persistence was specified, so do not know what to verify:" + err.Error(), + Data: msg, }) } } diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 57375f70..8156da0b 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -242,9 +242,7 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu // Try to create backchannel rtspBackChannelClient := captureDevice.SetBackChannelClient(rtspUrl) err = rtspBackChannelClient.ConnectBackChannel(context.Background()) - if err != nil { - log.Log.Error("components.Kerberos.RunAgent(): error connecting to RTSP backchannel stream: " + err.Error()) - } else { + if err == nil { log.Log.Info("components.Kerberos.RunAgent(): opened RTSP backchannel stream: " + rtspUrl) go rtspBackChannelClient.StartBackChannel(context.Background()) } diff --git a/machinery/src/log/main.go b/machinery/src/log/main.go index fd3f274a..cd909d1e 100644 --- a/machinery/src/log/main.go +++ b/machinery/src/log/main.go @@ -44,12 +44,27 @@ func ConfigureGoLogging(configDirectory string, timezone *time.Location) { // This a logrus // -> github.com/sirupsen/logrus -func ConfigureLogrus(level string, timezone *time.Location) { - // Log as JSON instead of the default ASCII formatter. - logrus.SetFormatter(LocalTimeZoneFormatter{ - Timezone: timezone, - Formatter: &logrus.JSONFormatter{}, - }) // Use local timezone for providing datetime in logs! +func ConfigureLogrus(level string, output string, timezone *time.Location) { + + if output == "json" { + // Log as JSON instead of the default ASCII formatter. + logrus.SetFormatter(LocalTimeZoneFormatter{ + Timezone: timezone, + Formatter: &logrus.JSONFormatter{}, + }) + } else if output == "text" { + // Log as text with colors. + formatter := logrus.TextFormatter{ + ForceColors: true, + FullTimestamp: true, + } + logrus.SetFormatter(LocalTimeZoneFormatter{ + Timezone: timezone, + Formatter: &formatter, + }) + } + + // Use local timezone for providing datetime in logs! // Output to stdout instead of the default stderr // Can be any io.Writer, see below for File example @@ -61,11 +76,12 @@ func ConfigureLogrus(level string, timezone *time.Location) { logLevel = logrus.ErrorLevel } else if level == "debug" { logLevel = logrus.DebugLevel + logrus.SetReportCaller(true) } else if level == "fatal" { logLevel = logrus.FatalLevel } else if level == "warning" { logLevel = logrus.WarnLevel - } + } // Add this line for logging filename and line number! logrus.SetLevel(logLevel) } @@ -83,12 +99,12 @@ type Logging struct { Logger string } -func (self *Logging) Init(level string, configDirectory string, timezone *time.Location) { +func (self *Logging) Init(level string, logoutput string, configDirectory string, timezone *time.Location) { switch self.Logger { case "go-logging": ConfigureGoLogging(configDirectory, timezone) case "logrus": - ConfigureLogrus(level, timezone) + ConfigureLogrus(level, logoutput, timezone) default: } } diff --git a/machinery/src/models/MQTT.go b/machinery/src/models/MQTT.go index a8aff43c..25043516 100644 --- a/machinery/src/models/MQTT.go +++ b/machinery/src/models/MQTT.go @@ -6,7 +6,7 @@ import ( "encoding/base64" "encoding/json" "encoding/pem" - "io/ioutil" + "io" "strings" "time" @@ -42,22 +42,22 @@ func PackageMQTTMessage(configuration *Configuration, msg Message) ([]byte, erro // Pload to base64 data, err := json.Marshal(pload) if err != nil { - log.Log.Error("failed to marshal payload: " + err.Error()) + log.Log.Error("models.mqtt.PackageMQTTMessage(): failed to marshal payload: " + err.Error()) } // Encrypt the value privateKey := configuration.Config.Encryption.PrivateKey r := strings.NewReader(privateKey) - pemBytes, _ := ioutil.ReadAll(r) + pemBytes, _ := io.ReadAll(r) block, _ := pem.Decode(pemBytes) if block == nil { - log.Log.Error("MQTTListenerHandler: error decoding PEM block containing private key") + log.Log.Error("models.mqtt.PackageMQTTMessage(): error decoding PEM block containing private key") } else { // Parse private key b := block.Bytes key, err := x509.ParsePKCS8PrivateKey(b) if err != nil { - log.Log.Error("MQTTListenerHandler: error parsing private key: " + err.Error()) + log.Log.Error("models.mqtt.PackageMQTTMessage(): error parsing private key: " + err.Error()) } // Conver key to *rsa.PrivateKey diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index cb19e14a..6de7001f 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -224,7 +224,7 @@ func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, if err := decodedXML.DecodeElement(&capabilities, et); err != nil { log.Log.Error("onvif.ConnectToOnvifDevice(): " + err.Error()) } else { - log.Log.Debug("onvif.ConnectToOnvifDevice(): capabilities: " + strings.Join(GetCapabilitiesFromDevice(dev), ", ")) + log.Log.Debug("onvif.ConnectToOnvifDevice(): capabilities.") } } @@ -947,27 +947,27 @@ func VerifyOnvifConnection(c *gin.Context) { } type ONVIFEvents struct { - Key string - Type string - Value string + Key string + Type string + Value string + Timestamp int64 } -// ONVIF has a specific profile that requires a subscription to receive events. -// These events can show if an input or output is active or inactive, and also other events. -// For the time being we are only interested in the input and output events, but this can be extended in the future. -func GetEventMessages(dev *onvif.Device) ([]ONVIFEvents, error) { - - var eventsArray []ONVIFEvents +// Create PullPointSubscription +func CreatePullPointSubscription(dev *onvif.Device) (string, error) { // We'll create a subscription to the device // This will allow us to receive events from the device var createPullPointSubscriptionResponse event.CreatePullPointSubscriptionResponse + var pullPointAdress string + var err error // For the time being we are just interested in the digital inputs and outputs, therefore // we have set the topic to the followin filter. - terminate := xsd.String("PT10S") + terminate := xsd.String("PT60S") resp, err := dev.CallMethod(event.CreatePullPointSubscription{ InitialTerminationTime: &terminate, + Filter: &event.FilterType{ TopicExpression: &event.TopicExpressionType{ Dialect: xsd.String("http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet"), @@ -988,105 +988,167 @@ func GetEventMessages(dev *onvif.Device) ([]ONVIFEvents, error) { if err := decodedXML.DecodeElement(&createPullPointSubscriptionResponse, et); err != nil { log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) } else { - // We were able to create a subscription to the device. Now pull some messages from the subscription. - subscriptionURI := string(createPullPointSubscriptionResponse.SubscriptionReference.Address) - if subscriptionURI == "" { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): subscriptionURI is empty") + pullPointAdress = string(createPullPointSubscriptionResponse.SubscriptionReference.Address) + } + } + } + } + return pullPointAdress, err +} + +func Unsubscribe(dev *onvif.Device, pullPointAddress string) error { + // Unsubscribe from the device + unsubscribe := event.Unsubscribe{} + requestBody, err := xml.Marshal(unsubscribe) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + } + res, err := dev.SendSoap(pullPointAddress, string(requestBody)) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + } + if res != nil { + _, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + } + } + return err +} + +// Look for Source of input and output +// Creat a map of the source and the value +// We'll use this map to determine if the value has changed. +// If the value has changed we'll send an event to the frontend. +var inputOutputDeviceMap = make(map[string]*ONVIFEvents) + +func GetInputOutputs() ([]ONVIFEvents, error) { + var eventsArray []ONVIFEvents + // We have some odd behaviour for inputs: the logical state is set to false even if circuit is closed. However we do see repeated events (looks like heartbeats). + // We are assuming that if we do not receive an event for 15 seconds the input is inactive, otherwise we set to active. + for key, value := range inputOutputDeviceMap { + if value.Type == "input" { + if time.Now().Unix()-value.Timestamp > 15 { + value.Value = "false" + } else { + value.Value = "true" + } + inputOutputDeviceMap[key] = value + } + eventsArray = append(eventsArray, *value) + } + for _, value := range eventsArray { + log.Log.Info("onvif.main.GetInputOutputs(): " + value.Key + " - " + value.Value + " (" + strconv.FormatInt(value.Timestamp, 10) + ")") + } + return eventsArray, nil +} + +// ONVIF has a specific profile that requires a subscription to receive events. +// These events can show if an input or output is active or inactive, and also other events. +// For the time being we are only interested in the input and output events, but this can be extended in the future. +func GetEventMessages(dev *onvif.Device, pullPointAddress string) ([]ONVIFEvents, error) { + + var eventsArray []ONVIFEvents + var err error + + if pullPointAddress != "" { + // We were able to create a subscription to the device. Now pull some messages from the subscription. + subscriptionURI := pullPointAddress + if subscriptionURI == "" { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): subscriptionURI is empty") + } else { + // Pull message + pullMessage := event.PullMessages{ + Timeout: xsd.Duration("PT5S"), + MessageLimit: 100, + } + requestBody, err := xml.Marshal(pullMessage) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + } + res, err := dev.SendSoap(string(subscriptionURI), string(requestBody)) + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + } + + var pullMessagesResponse event.PullMessagesResponse + if res != nil { + bs, err := io.ReadAll(res.Body) + res.Body.Close() + if err == nil { + stringBody := string(bs) + decodedXML, et, err := getXMLNode(stringBody, "PullMessagesResponse") + if err != nil { + log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) } else { - pullMessage := event.PullMessages{ - Timeout: xsd.Duration("PT5S"), - MessageLimit: 30, - } - requestBody, err := xml.Marshal(pullMessage) - if err != nil { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) - } - res, err := dev.SendSoap(string(subscriptionURI), string(requestBody)) - if err != nil { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + if err := decodedXML.DecodeElement(&pullMessagesResponse, et); err != nil { + log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) } + } + } + } - var pullMessagesResponse event.PullMessagesResponse - if res != nil { - bs, err := io.ReadAll(res.Body) - res.Body.Close() - - stringBody := string(bs) - decodedXML, et, err := getXMLNode(stringBody, "PullMessagesResponse") - if err != nil { - log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) - } else { - if err := decodedXML.DecodeElement(&pullMessagesResponse, et); err != nil { - log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) - } else { - log.Log.Debug("onvif.main.GetEventMessages(pullMessages): " + stringBody) - } + for _, message := range pullMessagesResponse.NotificationMessage { + log.Log.Debug("onvif.main.GetEventMessages(pullMessages): " + string(message.Topic.TopicKinds)) + log.Log.Debug("onvif.main.GetEventMessages(pullMessages): " + string(message.Message.Message.Data.SimpleItem[0].Name) + " " + string(message.Message.Message.Data.SimpleItem[0].Value)) + if message.Topic.TopicKinds == "tns1:Device/Trigger/Relay" { + if len(message.Message.Message.Data.SimpleItem) > 0 { + if message.Message.Message.Data.SimpleItem[0].Name == "LogicalState" { + key := string(message.Message.Message.Source.SimpleItem[0].Value) + value := string(message.Message.Message.Data.SimpleItem[0].Value) + log.Log.Debug("onvif.main.GetEventMessages(pullMessages) output: " + key + " " + value) + + // Depending on the onvif library they might use different values for active and inactive. + if value == "active" || value == "1" { + value = "true" + } else if value == "inactive" || value == "0" { + value = "false" } - } - // Look for Source of input and output - for _, message := range pullMessagesResponse.NotificationMessage { - if message.Topic.TopicKinds == "tns1:Device/Trigger/Relay" { - if len(message.Message.Message.Data.SimpleItem) > 0 { - if message.Message.Message.Data.SimpleItem[0].Name == "LogicalState" { - key := string(message.Message.Message.Source.SimpleItem[0].Value) - value := string(message.Message.Message.Data.SimpleItem[0].Value) - log.Log.Debug("onvif.main.GetEventMessages(pullMessages) output: " + key + " " + value) - - // Depending on the onvif library they might use different values for active and inactive. - if value == "active" || value == "1" { - value = "true" - } else if value == "inactive" || value == "0" { - value = "false" - } - - eventsArray = append(eventsArray, ONVIFEvents{ - Key: key, - Type: "output", - Value: value, - }) - } - } - } else if message.Topic.TopicKinds == "tns1:Device/Trigger/DigitalInput" { - if len(message.Message.Message.Data.SimpleItem) > 0 { - if message.Message.Message.Data.SimpleItem[0].Name == "LogicalState" { - key := string(message.Message.Message.Source.SimpleItem[0].Value) - value := string(message.Message.Message.Data.SimpleItem[0].Value) - log.Log.Debug("onvif.main.GetEventMessages(pullMessages) input: " + key + " " + value) - - // Depending on the onvif library they might use different values for active and inactive. - if value == "active" || value == "1" { - value = "true" - } else if value == "inactive" || value == "0" { - value = "false" - } - - eventsArray = append(eventsArray, ONVIFEvents{ - Key: key, - Type: "input", - Value: value, - }) - } + // Check if key exists in map + // If it does not exist we'll add it to the map otherwise we'll update the value. + if _, ok := inputOutputDeviceMap[key]; !ok { + inputOutputDeviceMap[key] = &ONVIFEvents{ + Key: key, + Type: "output", + Value: value, + Timestamp: 0, } + } else { + log.Log.Debug("onvif.main.GetEventMessages(pullMessages) output: " + key + " " + value) + inputOutputDeviceMap[key].Value = value + inputOutputDeviceMap[key].Timestamp = time.Now().Unix() } } + } + } else if message.Topic.TopicKinds == "tns1:Device/Trigger/DigitalInput" { + if len(message.Message.Message.Data.SimpleItem) > 0 { + if message.Message.Message.Data.SimpleItem[0].Name == "LogicalState" { + key := string(message.Message.Message.Source.SimpleItem[0].Value) + value := string(message.Message.Message.Data.SimpleItem[0].Value) + log.Log.Debug("onvif.main.GetEventMessages(pullMessages) input: " + key + " " + value) + + // Depending on the onvif library they might use different values for active and inactive. + if value == "active" || value == "1" { + value = "true" + } else if value == "inactive" || value == "0" { + value = "false" + } - // Unscubscribe from the device - unsubscribe := event.Unsubscribe{} - requestBody, err = xml.Marshal(unsubscribe) - if err != nil { - log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) - } - res, err = dev.SendSoap(subscriptionURI, string(requestBody)) - if err != nil { - log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) - } - if res != nil { - bs, err := io.ReadAll(res.Body) - res.Body.Close() - if err == nil { - stringBody := string(bs) - log.Log.Debug("onvif.main.GetEventMessages(unsubscribe): " + stringBody) + // Check if key exists in map + // If it does not exist we'll add it to the map otherwise we'll update the value. + if _, ok := inputOutputDeviceMap[key]; !ok { + inputOutputDeviceMap[key] = &ONVIFEvents{ + Key: key, + Type: "input", + Value: value, + Timestamp: 0, + } + } else { + log.Log.Debug("onvif.main.GetEventMessages(pullMessages) input: " + key + " " + value) + inputOutputDeviceMap[key].Value = value + inputOutputDeviceMap[key].Timestamp = time.Now().Unix() } } } @@ -1094,6 +1156,8 @@ func GetEventMessages(dev *onvif.Device) ([]ONVIFEvents, error) { } } } + + eventsArray, _ = GetInputOutputs() return eventsArray, err } @@ -1101,10 +1165,15 @@ func GetEventMessages(dev *onvif.Device) ([]ONVIFEvents, error) { // But will not give any status information. func GetDigitalInputs(dev *onvif.Device) (device.GetDigitalInputsResponse, error) { - events, err := GetEventMessages(dev) + // Create a pull point subscription + pullPointAddress, err := CreatePullPointSubscription(dev) + if err == nil { - for _, event := range events { - log.Log.Debug("onvif.main.GetDigitalInputs(): " + event.Key + " " + event.Value) + events, err := GetEventMessages(dev, pullPointAddress) + if err == nil { + for _, event := range events { + log.Log.Debug("onvif.main.GetDigitalInputs(): " + event.Key + " " + event.Value) + } } } @@ -1134,6 +1203,12 @@ func GetDigitalInputs(dev *onvif.Device) (device.GetDigitalInputsResponse, error } } + // Unsubscribe from the device + err = Unsubscribe(dev, pullPointAddress) + if err != nil { + log.Log.Error("onvif.main.GetDigitalInputs(): " + err.Error()) + } + return digitalinputs, err } @@ -1181,7 +1256,6 @@ func TriggerRelayOutput(dev *onvif.Device, output string) (setRelayOutputState d // this in the future "kerberos-io/onvif" library. if err == nil { token := relayoutputs.RelayOutputs.Token - if output == string(token) { outputState := device.SetRelayOutputState{ RelayOutputToken: token, @@ -1195,17 +1269,16 @@ func TriggerRelayOutput(dev *onvif.Device, output string) (setRelayOutputState d resp.Body.Close() } stringBody := string(b) - decodedXML, et, errXML := getXMLNode(stringBody, "SetRelayOutputStateResponse") - if errXML != nil { - log.Log.Error("onvif.main.TriggerRelayOutput(): " + errXML.Error()) - return + if err == nil && resp.StatusCode == 200 { + log.Log.Info("onvif.main.TriggerRelayOutput(): triggered relay output (" + string(token) + ")") } else { - if errDecode := decodedXML.DecodeElement(&setRelayOutputState, et); err != nil { - log.Log.Debug("onvif.main.TriggerRelayOutput(): " + errDecode.Error()) - return - } + log.Log.Error("onvif.main.TriggerRelayOutput(): " + stringBody) } + } else { + log.Log.Error("onvif.main.TriggerRelayOutput(): could not find relay output (" + output + ")") } + } else { + log.Log.Error("onvif.main.TriggerRelayOutput(): something went wrong while getting the relay outputs " + err.Error()) } return } diff --git a/machinery/src/routers/http/methods.go b/machinery/src/routers/http/methods.go index 99dde8f5..e76de2b0 100644 --- a/machinery/src/routers/http/methods.go +++ b/machinery/src/routers/http/methods.go @@ -384,21 +384,27 @@ func DoGetDigitalInputs(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - events, err := onvif.GetEventMessages(device) + // Get the digital inputs and outputs from the device + inputOutputs, err := onvif.GetInputOutputs() if err == nil { - // Get the digital inputs from the device - var inputs []onvif.ONVIFEvents - for _, event := range events { - if event.Type == "input" { - inputs = append(inputs, event) + if err == nil { + // Get the digital outputs from the device + var inputs []onvif.ONVIFEvents + for _, event := range inputOutputs { + if event.Type == "input" { + inputs = append(inputs, event) + } } + c.JSON(200, gin.H{ + "data": inputs, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) } - - c.JSON(200, gin.H{ - "data": inputs, - }) } else { c.JSON(400, gin.H{ "data": "Something went wrong: " + err.Error(), @@ -447,20 +453,27 @@ func DoGetRelayOutputs(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - events, err := onvif.GetEventMessages(device) + // Get the digital inputs and outputs from the device + inputOutputs, err := onvif.GetInputOutputs() if err == nil { - // Get the digital inputs from the device - var outputs []onvif.ONVIFEvents - for _, event := range events { - if event.Type == "output" { - outputs = append(outputs, event) + if err == nil { + // Get the digital outputs from the device + var outputs []onvif.ONVIFEvents + for _, event := range inputOutputs { + if event.Type == "output" { + outputs = append(outputs, event) + } } + c.JSON(200, gin.H{ + "data": outputs, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) } - c.JSON(200, gin.H{ - "data": outputs, - }) } else { c.JSON(400, gin.H{ "data": "Something went wrong: " + err.Error(), diff --git a/machinery/src/routers/mqtt/main.go b/machinery/src/routers/mqtt/main.go index 60e4e298..4e422d8b 100644 --- a/machinery/src/routers/mqtt/main.go +++ b/machinery/src/routers/mqtt/main.go @@ -66,7 +66,7 @@ func ConfigureMQTT(configDirectory string, configuration *models.Configuration, PREV_AgentKey = configuration.Config.Key if config.Offline == "true" { - log.Log.Info("ConfigureMQTT: not starting as running in Offline mode.") + log.Log.Info("routers.mqtt.main.ConfigureMQTT(): not starting as running in Offline mode.") } else { opts := mqtt.NewClientOptions() @@ -75,7 +75,7 @@ func ConfigureMQTT(configDirectory string, configuration *models.Configuration, // and share and receive messages to/from. mqttURL := config.MQTTURI opts.AddBroker(mqttURL) - log.Log.Info("ConfigureMQTT: Set broker uri " + mqttURL) + log.Log.Debug("routers.mqtt.main.ConfigureMQTT(): Set broker uri " + mqttURL) // Our MQTT broker can have username/password credentials // to protect it from the outside. @@ -84,8 +84,8 @@ func ConfigureMQTT(configDirectory string, configuration *models.Configuration, if mqtt_username != "" || mqtt_password != "" { opts.SetUsername(mqtt_username) opts.SetPassword(mqtt_password) - log.Log.Info("ConfigureMQTT: Set username " + mqtt_username) - log.Log.Info("ConfigureMQTT: Set password " + mqtt_password) + log.Log.Debug("routers.mqtt.main.ConfigureMQTT(): Set username " + mqtt_username) + log.Log.Debug("routers.mqtt.main.ConfigureMQTT(): Set password " + mqtt_password) } // Some extra options to make sure the connection behaves @@ -121,13 +121,13 @@ func ConfigureMQTT(configDirectory string, configuration *models.Configuration, } opts.SetClientID(mqttClientID) - log.Log.Info("ConfigureMQTT: Set ClientID " + mqttClientID) + log.Log.Info("routers.mqtt.main.ConfigureMQTT(): Set ClientID " + mqttClientID) rand.Seed(time.Now().UnixNano()) webrtc.CandidateArrays = make(map[string](chan string)) opts.OnConnect = func(c mqtt.Client) { // We managed to connect to the MQTT broker, hurray! - log.Log.Info("ConfigureMQTT: " + mqttClientID + " connected to " + mqttURL) + log.Log.Info("routers.mqtt.main.ConfigureMQTT(): " + mqttClientID + " connected to " + mqttURL) // Create a susbcription for listen and reply MQTTListenerHandler(c, hubKey, configDirectory, configuration, communication) @@ -136,7 +136,7 @@ func ConfigureMQTT(configDirectory string, configuration *models.Configuration, mqc := mqtt.NewClient(opts) if token := mqc.Connect(); token.WaitTimeout(3 * time.Second) { if token.Error() != nil { - log.Log.Error("ConfigureMQTT: unable to establish mqtt broker connection, error was: " + token.Error().Error()) + log.Log.Error("routers.mqtt.main.ConfigureMQTT(): unable to establish mqtt broker connection, error was: " + token.Error().Error()) } } return mqc @@ -147,7 +147,7 @@ func ConfigureMQTT(configDirectory string, configuration *models.Configuration, func MQTTListenerHandler(mqttClient mqtt.Client, hubKey string, configDirectory string, configuration *models.Configuration, communication *models.Communication) { if hubKey == "" { - log.Log.Info("MQTTListenerHandler: no hub key provided, not subscribing to kerberos/hub/{hubkey}") + log.Log.Info("routers.mqtt.main.MQTTListenerHandler(): no hub key provided, not subscribing to kerberos/hub/{hubkey}") } else { topicOnvif := fmt.Sprintf("kerberos/agent/%s", hubKey) mqttClient.Subscribe(topicOnvif, 1, func(c mqtt.Client, msg mqtt.Message) { @@ -178,14 +178,14 @@ func MQTTListenerHandler(mqttClient mqtt.Client, hubKey string, configDirectory pemBytes, _ := ioutil.ReadAll(r) block, _ := pem.Decode(pemBytes) if block == nil { - log.Log.Error("MQTTListenerHandler: error decoding PEM block containing private key") + log.Log.Error("routers.mqtt.main.MQTTListenerHandler(): error decoding PEM block containing private key") return } else { // Parse private key b := block.Bytes key, err := x509.ParsePKCS8PrivateKey(b) if err != nil { - log.Log.Error("MQTTListenerHandler: error parsing private key: " + err.Error()) + log.Log.Error("routers.mqtt.main.MQTTListenerHandler(): error parsing private key: " + err.Error()) return } else { // Conver key to *rsa.PrivateKey @@ -205,16 +205,16 @@ func MQTTListenerHandler(mqttClient mqtt.Client, hubKey string, configDirectory } decryptedValue, err := encryption.AesDecrypt(data, string(decryptedKey)) if err != nil { - log.Log.Error("MQTTListenerHandler: error decrypting message: " + err.Error()) + log.Log.Error("routers.mqtt.main.MQTTListenerHandler(): error decrypting message: " + err.Error()) return } json.Unmarshal(decryptedValue, &payload) } else { - log.Log.Error("MQTTListenerHandler: error decrypting message, assymetric keys do not match.") + log.Log.Error("routers.mqtt.main.MQTTListenerHandler(): error decrypting message, assymetric keys do not match.") return } } else if err != nil { - log.Log.Error("MQTTListenerHandler: error decrypting message: " + err.Error()) + log.Log.Error("routers.mqtt.main.MQTTListenerHandler(): error decrypting message: " + err.Error()) return } } @@ -225,7 +225,7 @@ func MQTTListenerHandler(mqttClient mqtt.Client, hubKey string, configDirectory } // We'll find out which message we received, and act accordingly. - log.Log.Info("MQTTListenerHandler: received message with action: " + payload.Action) + log.Log.Info("routers.mqtt.main.MQTTListenerHandler(): received message with action: " + payload.Action) switch payload.Action { case "record": go HandleRecording(mqttClient, hubKey, payload, configuration, communication) @@ -299,7 +299,7 @@ func HandleGetPTZPosition(mqttClient mqtt.Client, hubKey string, payload models. // Get Position from device pos, err := onvif.GetPositionFromDevice(*configuration) if err != nil { - log.Log.Error("HandlePTZPosition: error getting position from device: " + err.Error()) + log.Log.Error("routers.mqtt.main.HandlePTZPosition(): error getting position from device: " + err.Error()) } else { // Needs to wrapped! posString := fmt.Sprintf("%f,%f,%f", pos.PanTilt.X, pos.PanTilt.Y, pos.Zoom.X) @@ -317,7 +317,7 @@ func HandleGetPTZPosition(mqttClient mqtt.Client, hubKey string, payload models. if err == nil { mqttClient.Publish("kerberos/hub/"+hubKey, 0, false, payload) } else { - log.Log.Info("HandlePTZPosition: something went wrong while sending position to hub: " + string(payload)) + log.Log.Info("routers.mqtt.main.HandlePTZPosition(): something went wrong while sending position to hub: " + string(payload)) } } } @@ -334,9 +334,9 @@ func HandleUpdatePTZPosition(mqttClient mqtt.Client, hubKey string, payload mode if onvifAction.Action != "" { if communication.CameraConnected { communication.HandleONVIF <- onvifAction - log.Log.Info("MQTTListenerHandleONVIF: Received an action - " + onvifAction.Action) + log.Log.Info("routers.mqtt.main.MQTTListenerHandleONVIF(): Received an action - " + onvifAction.Action) } else { - log.Log.Info("MQTTListenerHandleONVIF: received action, but camera is not connected.") + log.Log.Info("routers.mqtt.main.MQTTListenerHandleONVIF(): received action, but camera is not connected.") } } } @@ -378,14 +378,14 @@ func HandleRequestConfig(mqttClient mqtt.Client, hubKey string, payload models.P if err == nil { mqttClient.Publish("kerberos/hub/"+hubKey, 0, false, payload) } else { - log.Log.Info("HandleRequestConfig: something went wrong while sending config to hub: " + string(payload)) + log.Log.Info("routers.mqtt.main.HandleRequestConfig(): something went wrong while sending config to hub: " + string(payload)) } } else { - log.Log.Info("HandleRequestConfig: no config available") + log.Log.Info("routers.mqtt.main.HandleRequestConfig(): no config available") } - log.Log.Info("HandleRequestConfig: Received a request for the config") + log.Log.Info("routers.mqtt.main.HandleRequestConfig(): Received a request for the config") } } @@ -406,7 +406,7 @@ func HandleUpdateConfig(mqttClient mqtt.Client, hubKey string, payload models.Pa err := configService.SaveConfig(configDirectory, config, configuration, communication) if err == nil { - log.Log.Info("HandleUpdateConfig: Config updated") + log.Log.Info("routers.mqtt.main.HandleUpdateConfig(): Config updated") message := models.Message{ Payload: models.Payload{ Action: "acknowledge-update-config", @@ -417,10 +417,10 @@ func HandleUpdateConfig(mqttClient mqtt.Client, hubKey string, payload models.Pa if err == nil { mqttClient.Publish("kerberos/hub/"+hubKey, 0, false, payload) } else { - log.Log.Info("HandleRequestConfig: something went wrong while sending acknowledge config to hub: " + string(payload)) + log.Log.Info("routers.mqtt.main.HandleUpdateConfig(): something went wrong while sending acknowledge config to hub: " + string(payload)) } } else { - log.Log.Info("HandleUpdateConfig: Config update failed") + log.Log.Info("routers.mqtt.main.HandleUpdateConfig(): Config update failed") } } } @@ -438,9 +438,9 @@ func HandleRequestSDStream(mqttClient mqtt.Client, hubKey string, payload models case communication.HandleLiveSD <- time.Now().Unix(): default: } - log.Log.Info("HandleRequestSDStream: received request to livestream.") + log.Log.Info("routers.mqtt.main.HandleRequestSDStream(): received request to livestream.") } else { - log.Log.Info("HandleRequestSDStream: received request to livestream, but camera is not connected.") + log.Log.Info("routers.mqtt.main.HandleRequestSDStream(): received request to livestream, but camera is not connected.") } } } @@ -460,9 +460,9 @@ func HandleRequestHDStream(mqttClient mqtt.Client, hubKey string, payload models case communication.HandleLiveHDHandshake <- requestHDStreamPayload: default: } - log.Log.Info("HandleRequestHDStream: received request to setup webrtc.") + log.Log.Info("routers.mqtt.main.HandleRequestHDStream(): received request to setup webrtc.") } else { - log.Log.Info("HandleRequestHDStream: received request to setup webrtc, but camera is not connected.") + log.Log.Info("routers.mqtt.main.HandleRequestHDStream(): received request to setup webrtc, but camera is not connected.") } } } @@ -480,7 +480,7 @@ func HandleReceiveHDCandidates(mqttClient mqtt.Client, hubKey string, payload mo key := configuration.Config.Key + "/" + receiveHDCandidatesPayload.SessionID go webrtc.RegisterCandidates(key, receiveHDCandidatesPayload) } else { - log.Log.Info("HandleReceiveHDCandidates: received candidate, but camera is not connected.") + log.Log.Info("routers.mqtt.main.HandleReceiveHDCandidates(): received candidate, but camera is not connected.") } } } @@ -497,10 +497,10 @@ func HandleNavigatePTZ(mqttClient mqtt.Client, hubKey string, payload models.Pay var onvifAction models.OnvifAction json.Unmarshal([]byte(action), &onvifAction) communication.HandleONVIF <- onvifAction - log.Log.Info("HandleNavigatePTZ: Received an action - " + onvifAction.Action) + log.Log.Info("routers.mqtt.main.HandleNavigatePTZ(): Received an action - " + onvifAction.Action) } else { - log.Log.Info("HandleNavigatePTZ: received action, but camera is not connected.") + log.Log.Info("routers.mqtt.main.HandleNavigatePTZ(): received action, but camera is not connected.") } } } @@ -512,6 +512,6 @@ func DisconnectMQTT(mqttClient mqtt.Client, config *models.Config) { mqttClient.Unsubscribe("kerberos/agent/" + PREV_HubKey) mqttClient.Disconnect(1000) mqttClient = nil - log.Log.Info("DisconnectMQTT: MQTT client disconnected.") + log.Log.Info("routers.mqtt.main.DisconnectMQTT(): MQTT client disconnected.") } } diff --git a/machinery/src/webrtc/main.go b/machinery/src/webrtc/main.go index 1e339515..db0f7fbb 100644 --- a/machinery/src/webrtc/main.go +++ b/machinery/src/webrtc/main.go @@ -70,7 +70,7 @@ func CreateWebRTC(name string, stunServers []string, turnServers []string, turnS func (w WebRTC) DecodeSessionDescription(data string) ([]byte, error) { sd, err := base64.StdEncoding.DecodeString(data) if err != nil { - log.Log.Error("DecodeString error: " + err.Error()) + log.Log.Error("webrtc.main.DecodeSessionDescription(): " + err.Error()) return []byte{}, err } return sd, nil @@ -91,11 +91,11 @@ func RegisterCandidates(key string, candidate models.ReceiveHDCandidatesPayload) if !ok { CandidateArrays[key] = make(chan string) } - log.Log.Info("HandleReceiveHDCandidates: " + candidate.Candidate) + log.Log.Info("webrtc.main.HandleReceiveHDCandidates(): " + candidate.Candidate) select { case CandidateArrays[key] <- candidate.Candidate: default: - log.Log.Info("HandleReceiveHDCandidates: channel is full.") + log.Log.Info("webrtc.main.HandleReceiveHDCandidates(): channel is full.") } CandidatesMutex.Unlock() } @@ -130,7 +130,7 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati mediaEngine := &pionWebRTC.MediaEngine{} if err := mediaEngine.RegisterDefaultCodecs(); err != nil { - log.Log.Error("InitializeWebRTCConnection: something went wrong registering codecs.") + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong registering codecs for media engine: " + err.Error()) } api := pionWebRTC.NewAPI(pionWebRTC.WithMediaEngine(mediaEngine)) @@ -147,22 +147,18 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati Credential: w.TurnServersCredential, }, }, - //ICETransportPolicy: pionWebRTC.ICETransportPolicyRelay, + //ICETransportPolicy: pionWebRTC.ICETransportPolicyRelay, // This will force a relay server, we might make this configurable. }, ) if err == nil && peerConnection != nil { if _, err = peerConnection.AddTrack(videoTrack); err != nil { - //panic(err) + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong while adding video track: " + err.Error()) } if _, err = peerConnection.AddTrack(audioTrack); err != nil { - //panic(err) - } - - if err != nil { - //panic(err) + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong while adding audio track: " + err.Error()) } peerConnection.OnICEConnectionStateChange(func(connectionState pionWebRTC.ICEConnectionState) { @@ -180,7 +176,7 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati close(w.PacketsCount) if err := peerConnection.Close(); err != nil { - //panic(err) + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong while closing peer connection: " + err.Error()) } } else if connectionState == pionWebRTC.ICEConnectionStateConnected { atomic.AddInt64(&peerConnectionCount, 1) @@ -188,28 +184,28 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati // Iterate over the candidates and send them to the remote client // Non blocking channel for candidate := range CandidateArrays[sessionKey] { - log.Log.Info("InitializeWebRTCConnection: Received candidate.") + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): Received candidate from channel: " + candidate) if candidateErr := peerConnection.AddICECandidate(pionWebRTC.ICECandidateInit{Candidate: string(candidate)}); candidateErr != nil { - log.Log.Error("InitializeWebRTCConnection: something went wrong while adding candidate: " + candidateErr.Error()) + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong while adding candidate: " + candidateErr.Error()) } } } else if connectionState == pionWebRTC.ICEConnectionStateFailed { - + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): ICEConnectionStateFailed") } - log.Log.Info("InitializeWebRTCConnection: connection state changed to: " + connectionState.String()) - log.Log.Info("InitializeWebRTCConnection: Number of peers connected (" + strconv.FormatInt(peerConnectionCount, 10) + ")") + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): connection state changed to: " + connectionState.String()) + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): Number of peers connected (" + strconv.FormatInt(peerConnectionCount, 10) + ")") }) offer := w.CreateOffer(sd) if err = peerConnection.SetRemoteDescription(offer); err != nil { - //panic(err) + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong while setting remote description: " + err.Error()) } answer, err := peerConnection.CreateAnswer(nil) if err != nil { - //panic(err) + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong while creating answer: " + err.Error()) } else if err = peerConnection.SetLocalDescription(answer); err != nil { - //panic(err) + log.Log.Error("webrtc.main.InitializeWebRTCConnection(): something went wrong while setting local description: " + err.Error()) } // When an ICE candidate is available send to the other peer using the signaling server (MQTT). @@ -227,7 +223,7 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati if err == nil { valueMap["candidate"] = string(candateBinary) } else { - log.Log.Info("HandleRequestConfig: something went wrong while marshalling candidate: " + err.Error()) + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): something went wrong while marshalling candidate: " + err.Error()) } // We'll send the candidate to the hub @@ -240,11 +236,10 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati } payload, err := models.PackageMQTTMessage(configuration, message) if err == nil { - log.Log.Info("InitializeWebRTCConnection:" + string(candateBinary)) token := mqttClient.Publish("kerberos/hub/"+hubKey, 2, false, payload) token.Wait() } else { - log.Log.Info("HandleRequestConfig: something went wrong while sending acknowledge config to hub: " + string(payload)) + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): while packaging mqtt message: " + err.Error()) } }) @@ -255,7 +250,7 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati // Create a config map valueMap := make(map[string]interface{}) valueMap["sdp"] = []byte(base64.StdEncoding.EncodeToString([]byte(answer.SDP))) - log.Log.Info("InitializeWebRTCConnection: Send SDP answer") + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): Send SDP answer") // We'll send the candidate to the hub message := models.Message{ @@ -270,18 +265,17 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati token := mqttClient.Publish("kerberos/hub/"+hubKey, 2, false, payload) token.Wait() } else { - log.Log.Info("HandleRequestConfig: something went wrong while sending acknowledge config to hub: " + string(payload)) + log.Log.Info("webrtc.main.InitializeWebRTCConnection(): while packaging mqtt message: " + err.Error()) } } } } else { - log.Log.Error("InitializeWebRTCConnection: NewPeerConnection failed: " + err.Error()) + log.Log.Error("Initializwebrtc.main.InitializeWebRTCConnection()eWebRTCConnection: NewPeerConnection failed: " + err.Error()) } } func NewVideoTrack(streams []packets.Stream) *pionWebRTC.TrackLocalStaticSample { - var mimeType string - mimeType = pionWebRTC.MimeTypeH264 + mimeType := pionWebRTC.MimeTypeH264 outboundVideoTrack, _ := pionWebRTC.NewTrackLocalStaticSample(pionWebRTC.RTPCodecCapability{MimeType: mimeType}, "video", "pion124") return outboundVideoTrack } @@ -322,12 +316,12 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C } if !hasH264 && !hasPCM_MULAW { - log.Log.Error("WriteToTrack: no valid video codec and audio codec found.") + log.Log.Error("webrtc.main.WriteToTrack(): no valid video codec and audio codec found.") } else { if config.Capture.TranscodingWebRTC == "true" { // Todo.. } else { - log.Log.Info("WriteToTrack: not using a transcoder.") + //log.Log.Info("webrtc.main.WriteToTrack(): not using a transcoder.") } var cursorError error @@ -402,7 +396,7 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C // TODO.. } else { if err := videoTrack.WriteSample(sample); err != nil && err != io.ErrClosedPipe { - log.Log.Error("WriteToTrack: something went wrong while writing sample: " + err.Error()) + log.Log.Error("webrtc.main.WriteToTrack(): something went wrong while writing sample: " + err.Error()) } } } @@ -410,7 +404,7 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C // 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()) + log.Log.Error("webrtc.main.WriteToTrack(): something went wrong while writing sample: " + err.Error()) } } } @@ -422,5 +416,5 @@ func WriteToTrack(livestreamCursor *packets.QueueCursor, configuration *models.C } peerConnectionCount = 0 - log.Log.Info("WriteToTrack: stop writing to track.") + log.Log.Info("webrtc.main.WriteToTrack(): stop writing to track.") } From b1ab6bf52251641ca6b08bb94be7fa95610fc38d Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 27 Dec 2023 10:25:03 +0100 Subject: [PATCH 64/81] improve logging + updated readme --- README.md | 1 + machinery/main.go | 5 +- machinery/src/onvif/main.go | 67 +++++++++---------------- machinery/src/routers/websocket/main.go | 20 ++++---- 4 files changed, 39 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 019adf9e..58c945d7 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ Next to attaching the configuration file, it is also possible to override the co | Name | Description | Default Value | | --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ | | `LOG_LEVEL` | Level for logging, could be "info", "warning", "debug", "error" or "fatal". | "info" | +| `LOG_OUTPUT` | Logging output format "json" or "text". | "text" | | `AGENT_MODE` | You can choose to run this in 'release' for production, and or 'demo' for showcasing. | "release" | | `AGENT_TLS_INSECURE` | Specify if you want to use `InsecureSkipVerify` for the internal HTTP client. | "false" | | `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" | diff --git a/machinery/main.go b/machinery/main.go index 48bb9e29..d49e4c84 100644 --- a/machinery/main.go +++ b/machinery/main.go @@ -69,17 +69,18 @@ func main() { flag.StringVar(&timeout, "timeout", "2000", "Number of milliseconds to wait for the ONVIF discovery to complete") flag.Parse() - timezone, _ := time.LoadLocation("CET") - // Specify the level of loggin: "info", "warning", "debug", "error" or "fatal." logLevel := os.Getenv("LOG_LEVEL") if logLevel == "" { logLevel = "info" } + // Specify the output formatter of the log: "text" or "json". logOutput := os.Getenv("LOG_OUTPUT") if logOutput == "" { logOutput = "text" } + // Specify the timezone of the log: "UTC" or "Local". + timezone, _ := time.LoadLocation("CET") log.Log.Init(logLevel, logOutput, configDirectory, timezone) switch action { diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 6de7001f..f76cd0b3 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -731,25 +731,19 @@ func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsRes resp.Body.Close() } if err != nil { - log.Log.Error("ContinuousPanTiltZoom: " + err.Error()) + log.Log.Error("onvif.main.ContinuousZoom(): " + err.Error()) } - log.Log.Debug("ContinuousPanTiltZoom: " + string(b)) - + log.Log.Debug("onvif.main.ContinuousZoom(): " + string(b)) time.Sleep(500 * time.Millisecond) - resp, err = device.CallMethod(ptz.Stop{ + _, err = device.CallMethod(ptz.Stop{ ProfileToken: token, Zoom: true, }) - b = []byte{} - if resp != nil { - b, err = io.ReadAll(resp.Body) - resp.Body.Close() - } if err != nil { - log.Log.Error("ContinuousPanTiltZoom: " + err.Error()) + log.Log.Error("onvif.main.ContinuousZoom(): " + err.Error()) } return err @@ -759,7 +753,7 @@ func GetCapabilitiesFromDevice(dev *onvif.Device) []string { var capabilities []string services := dev.GetServices() for key, _ := range services { - log.Log.Debug("GetCapabilitiesFromDevice: has key: " + key) + log.Log.Debug("onvif.main.GetCapabilitiesFromDevice(): has key: " + key) if key != "" { keyParts := strings.Split(key, "/") if len(keyParts) > 0 { @@ -781,41 +775,39 @@ func GetPresetsFromDevice(device *onvif.Device) ([]models.OnvifActionPreset, err resp, err := device.CallMethod(ptz.GetPresets{ ProfileToken: token, }) - var b []byte if resp != nil { b, err = io.ReadAll(resp.Body) resp.Body.Close() } - if err == nil { stringBody := string(b) decodedXML, et, err := getXMLNode(stringBody, "GetPresetsResponse") if err != nil { - log.Log.Error("GetPresetsFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GetPresetsFromDevice(): " + err.Error()) return presets, err } else { if err := decodedXML.DecodeElement(&presetsResponse, et); err != nil { - log.Log.Error("GetPresetsFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GetPresetsFromDevice(): " + err.Error()) return presets, err } for _, preset := range presetsResponse.Preset { + log.Log.Debug("onvif.main.GetPresetsFromDevice(): " + string(preset.Name) + " (" + string(preset.Token) + ")") p := models.OnvifActionPreset{ Name: string(preset.Name), Token: string(preset.Token), } - presets = append(presets, p) } return presets, err } } else { - log.Log.Error("GetPresetsFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GetPresetsFromDevice(): " + err.Error()) } } else { - log.Log.Error("GetPresetsFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GetPresetsFromDevice(): " + err.Error()) } return presets, err @@ -841,20 +833,20 @@ func GoToPresetFromDevice(device *onvif.Device, presetName string) error { stringBody := string(b) decodedXML, et, err := getXMLNode(stringBody, "GotoPresetResponses") if err != nil { - log.Log.Error("GoToPresetFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GoToPresetFromDevice(): " + err.Error()) return err } else { if err := decodedXML.DecodeElement(&goToPresetResponse, et); err != nil { - log.Log.Error("GoToPresetFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GoToPresetFromDevice(): " + err.Error()) return err } return err } } else { - log.Log.Error("GoToPresetFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GoToPresetFromDevice(): " + err.Error()) } } else { - log.Log.Error("GoToPresetFromDevice: " + err.Error()) + log.Log.Error("onvif.main.GoToPresetFromDevice(): " + err.Error()) } return err @@ -917,31 +909,18 @@ func VerifyOnvifConnection(c *gin.Context) { if err == nil { device, err := ConnectToOnvifDevice(&cameraConfig) if err == nil { - // Get the list of configurations - configurations, err := GetPTZConfigurationsFromDevice(device) - if err == nil { - - // Check if can zoom and/or pan/tilt is supported - ptzFunctions, canZoom, canPanTilt := GetPTZFunctionsFromDevice(configurations) - c.JSON(200, models.APIResponse{ - Data: device, - PTZFunctions: ptzFunctions, - CanZoom: canZoom, - CanPanTilt: canPanTilt, - }) - } else { - c.JSON(400, models.APIResponse{ - Message: "Something went wrong while getting the configurations " + err.Error(), - }) - } + log.Log.Info("onvif.main.VerifyOnvifConnection(): successfully verified the ONVIF connection") + c.JSON(200, models.APIResponse{ + Data: device, + }) } else { c.JSON(400, models.APIResponse{ - Message: "Something went wrong while verifying the ONVIF connection " + err.Error(), + Message: "onvif.main.VerifyOnvifConnection(): s went wrong while verifying the ONVIF connection " + err.Error(), }) } } else { c.JSON(400, models.APIResponse{ - Message: "Something went wrong while receiving the config " + err.Error(), + Message: "onvif.main.VerifyOnvifConnection(): s went wrong while receiving the config " + err.Error(), }) } } @@ -1286,8 +1265,10 @@ func TriggerRelayOutput(dev *onvif.Device, output string) (setRelayOutputState d func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, error) { xmlBytes := bytes.NewBufferString(xmlBody) decodedXML := xml.NewDecoder(xmlBytes) + var token xml.Token + var err error for { - token, err := decodedXML.Token() + token, err = decodedXML.Token() if err != nil { break } @@ -1298,5 +1279,5 @@ func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElemen } } } - return nil, nil, errors.New("error in NodeName - username and password might be wrong") + return nil, nil, errors.New("getXMLNode(): " + err.Error()) } diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index 22aefdb9..dcd4bf56 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -60,6 +60,10 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu var message Message err = conn.ReadJSON(&message) + if err != nil { + log.Log.Error("routers.websocket.main.WebsocketHandler(): " + err.Error()) + return + } clientID := message.ClientID if sockets[clientID] == nil { connection := new(Connection) @@ -87,14 +91,14 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu if exists { sockets[clientID].Cancels["stream-sd"]() } else { - log.Log.Error("Streaming sd does not exists for " + clientID) + log.Log.Error("routers.websocket.main.WebsocketHandler(): streaming sd does not exists for " + clientID) } case "stream-sd": if communication.CameraConnected { _, exists := sockets[clientID].Cancels["stream-sd"] if exists { - log.Log.Info("Already streaming sd for " + clientID) + log.Log.Info("routers.websocket.main.WebsocketHandler(): already streaming sd for " + clientID) } else { startStream := Message{ ClientID: clientID, @@ -121,7 +125,7 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu _, exists := sockets[clientID] if exists { delete(sockets, clientID) - log.Log.Info("WebsocketHandler: " + clientID + ": terminated and disconnected websocket connection.") + log.Log.Info("routers.websocket.main.WebsocketHandler(): " + clientID + ": terminated and disconnected websocket connection.") } } } @@ -158,7 +162,7 @@ logreader: encodedImage = base64.StdEncoding.EncodeToString(bytes) } } else { - log.Log.Error("ForwardSDStream:" + err.Error()) + log.Log.Error("routers.websocket.main.ForwardSDStream():" + err.Error()) break logreader } } @@ -172,7 +176,7 @@ logreader: } err := connection.WriteJson(startStrean) if err != nil { - log.Log.Error("ForwardSDStream:" + err.Error()) + log.Log.Error("routers.websocket.main.ForwardSDStream():" + err.Error()) break logreader } select { @@ -182,16 +186,14 @@ logreader: } } - //frame.Free() - // Close socket for streaming _, exists := connection.Cancels["stream-sd"] if exists { delete(connection.Cancels, "stream-sd") } else { - log.Log.Error("Streaming sd does not exists for " + clientID) + log.Log.Error("routers.websocket.main.ForwardSDStream(): streaming sd does not exists for " + clientID) } // Send stop streaming message - log.Log.Info("ForwardSDStream: stop sending streaming over websocket") + log.Log.Info("routers.websocket.main.ForwardSDStream(): stop sending streaming over websocket") } From 2df35a1999f3251204337a1f481c7c3323a01070 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 27 Dec 2023 10:39:12 +0100 Subject: [PATCH 65/81] add remote trigger relay output (mqtt endpoint) + rename a few methods --- machinery/src/cloud/Cloud.go | 2 +- machinery/src/models/MQTT.go | 6 +++++ machinery/src/onvif/main.go | 29 ++++------------------- machinery/src/routers/http/methods.go | 10 ++++---- machinery/src/routers/mqtt/main.go | 34 ++++++++++++++++++++++++++- 5 files changed, 50 insertions(+), 31 deletions(-) diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 298cf9bf..d2ff0ac9 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -546,7 +546,7 @@ loop: cameraConfiguration := configuration.Config.Capture.IPCamera device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - onvif.Unsubscribe(device, pullPointAddress) + onvif.UnsubscribePullPoint(device, pullPointAddress) } } diff --git a/machinery/src/models/MQTT.go b/machinery/src/models/MQTT.go index 25043516..a58215ad 100644 --- a/machinery/src/models/MQTT.go +++ b/machinery/src/models/MQTT.go @@ -159,3 +159,9 @@ type NavigatePTZPayload struct { DeviceId string `json:"device_id"` // device id Action string `json:"action"` // action } + +type TriggerRelay struct { + Timestamp int64 `json:"timestamp"` // timestamp + DeviceId string `json:"device_id"` // device id + Token string `json:"token"` // token +} diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index f76cd0b3..3c215ac8 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -975,22 +975,22 @@ func CreatePullPointSubscription(dev *onvif.Device) (string, error) { return pullPointAdress, err } -func Unsubscribe(dev *onvif.Device, pullPointAddress string) error { +func UnsubscribePullPoint(dev *onvif.Device, pullPointAddress string) error { // Unsubscribe from the device unsubscribe := event.Unsubscribe{} requestBody, err := xml.Marshal(unsubscribe) if err != nil { - log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + log.Log.Error("onvif.main.UnsubscribePullPoint(): " + err.Error()) } res, err := dev.SendSoap(pullPointAddress, string(requestBody)) if err != nil { - log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + log.Log.Error("onvif.main.UnsubscribePullPoint(): " + err.Error()) } if res != nil { _, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { - log.Log.Error("onvif.main.GetEventMessages(unsubscribe): " + err.Error()) + log.Log.Error("onvif.main.UnsubscribePullPoint(): " + err.Error()) } } return err @@ -1144,18 +1144,6 @@ func GetEventMessages(dev *onvif.Device, pullPointAddress string) ([]ONVIFEvents // But will not give any status information. func GetDigitalInputs(dev *onvif.Device) (device.GetDigitalInputsResponse, error) { - // Create a pull point subscription - pullPointAddress, err := CreatePullPointSubscription(dev) - - if err == nil { - events, err := GetEventMessages(dev, pullPointAddress) - if err == nil { - for _, event := range events { - log.Log.Debug("onvif.main.GetDigitalInputs(): " + event.Key + " " + event.Value) - } - } - } - // We'll try to receive the relay outputs from the server var digitalinputs device.GetDigitalInputsResponse @@ -1181,13 +1169,6 @@ func GetDigitalInputs(dev *onvif.Device) (device.GetDigitalInputsResponse, error } } } - - // Unsubscribe from the device - err = Unsubscribe(dev, pullPointAddress) - if err != nil { - log.Log.Error("onvif.main.GetDigitalInputs(): " + err.Error()) - } - return digitalinputs, err } @@ -1224,7 +1205,7 @@ func GetRelayOutputs(dev *onvif.Device) (device.GetRelayOutputsResponse, error) return relayoutputs, err } -func TriggerRelayOutput(dev *onvif.Device, output string) (setRelayOutputState device.SetRelayOutputStateResponse, err error) { +func TriggerRelayOutput(dev *onvif.Device, output string) (err error) { err = nil // Get all outputs diff --git a/machinery/src/routers/http/methods.go b/machinery/src/routers/http/methods.go index e76de2b0..5b129aaf 100644 --- a/machinery/src/routers/http/methods.go +++ b/machinery/src/routers/http/methods.go @@ -528,24 +528,24 @@ func DoTriggerRelayOutput(c *gin.Context) { cameraConfiguration := configuration.Config.Capture.IPCamera device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - outputs, err := onvif.TriggerRelayOutput(device, output) + err := onvif.TriggerRelayOutput(device, output) if err == nil { c.JSON(200, gin.H{ - "data": outputs, + "data": "routers.http.methods.DoTriggerRelayOutput(): relay output triggered: " + output, }) } else { c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), + "data": "routers.http.methods.DoTriggerRelayOutput(): something went wrong: " + err.Error(), }) } } else { c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), + "data": "routers.http.methods.DoTriggerRelayOutput(): something went wrong: " + err.Error(), }) } } else { c.JSON(400, gin.H{ - "data": "Something went wrong: " + err.Error(), + "data": "routers.http.methods.DoTriggerRelayOutput(): something went wrong: " + err.Error(), }) } } diff --git a/machinery/src/routers/mqtt/main.go b/machinery/src/routers/mqtt/main.go index 4e422d8b..f7311b0a 100644 --- a/machinery/src/routers/mqtt/main.go +++ b/machinery/src/routers/mqtt/main.go @@ -247,6 +247,8 @@ func MQTTListenerHandler(mqttClient mqtt.Client, hubKey string, configDirectory go HandleRequestHDStream(mqttClient, hubKey, payload, configuration, communication) case "receive-hd-candidates": go HandleReceiveHDCandidates(mqttClient, hubKey, payload, configuration, communication) + case "trigger-relay": + go HandleTriggerRelay(mqttClient, hubKey, payload, configuration, communication) } } @@ -498,13 +500,43 @@ func HandleNavigatePTZ(mqttClient mqtt.Client, hubKey string, payload models.Pay json.Unmarshal([]byte(action), &onvifAction) communication.HandleONVIF <- onvifAction log.Log.Info("routers.mqtt.main.HandleNavigatePTZ(): Received an action - " + onvifAction.Action) - } else { log.Log.Info("routers.mqtt.main.HandleNavigatePTZ(): received action, but camera is not connected.") } } } +func HandleTriggerRelay(mqttClient mqtt.Client, hubKey string, payload models.Payload, configuration *models.Configuration, communication *models.Communication) { + value := payload.Value + jsonData, _ := json.Marshal(value) + var triggerRelayPayload models.TriggerRelay + json.Unmarshal(jsonData, &triggerRelayPayload) + + if triggerRelayPayload.Timestamp != 0 { + if communication.CameraConnected { + // Get token (name of relay) + token := triggerRelayPayload.Token + // Connect to Onvif device + cameraConfiguration := configuration.Config.Capture.IPCamera + device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + if err == nil { + // Trigger relay output + err := onvif.TriggerRelayOutput(device, token) + if err != nil { + log.Log.Error("routers.mqtt.main.HandleTriggerRelay(): error triggering relay: " + err.Error()) + } else { + log.Log.Info("routers.mqtt.main.HandleTriggerRelay(): trigger (" + token + ") relay output.") + } + } else { + log.Log.Error("routers.mqtt.main.HandleTriggerRelay(): error connecting to device: " + err.Error()) + } + + } else { + log.Log.Info("routers.mqtt.main.HandleTriggerRelay(): received trigger, but camera is not connected.") + } + } +} + func DisconnectMQTT(mqttClient mqtt.Client, config *models.Config) { if mqttClient != nil { // Cleanup all subscriptions From 776571c7b334d86d87eddbc8496936cd320fcba7 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Wed, 27 Dec 2023 14:30:12 +0100 Subject: [PATCH 66/81] improve logging --- machinery/src/routers/http/methods.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/machinery/src/routers/http/methods.go b/machinery/src/routers/http/methods.go index 5b129aaf..49daf3a7 100644 --- a/machinery/src/routers/http/methods.go +++ b/machinery/src/routers/http/methods.go @@ -2,6 +2,7 @@ package http import ( "github.com/gin-gonic/gin" + "github.com/kerberos-io/agent/machinery/src/log" "github.com/kerberos-io/agent/machinery/src/models" "github.com/kerberos-io/agent/machinery/src/onvif" ) @@ -530,22 +531,30 @@ func DoTriggerRelayOutput(c *gin.Context) { if err == nil { err := onvif.TriggerRelayOutput(device, output) if err == nil { + msg := "relay output triggered: " + output + log.Log.Info("routers.http.methods.DoTriggerRelayOutput(): " + msg) c.JSON(200, gin.H{ - "data": "routers.http.methods.DoTriggerRelayOutput(): relay output triggered: " + output, + "data": msg, }) } else { + msg := "something went wrong: " + err.Error() + log.Log.Error("routers.http.methods.DoTriggerRelayOutput(): " + msg) c.JSON(400, gin.H{ - "data": "routers.http.methods.DoTriggerRelayOutput(): something went wrong: " + err.Error(), + "data": msg, }) } } else { + msg := "something went wrong: " + err.Error() + log.Log.Error("routers.http.methods.DoTriggerRelayOutput(): " + msg) c.JSON(400, gin.H{ - "data": "routers.http.methods.DoTriggerRelayOutput(): something went wrong: " + err.Error(), + "data": msg, }) } } else { + msg := "something went wrong: " + err.Error() + log.Log.Error("routers.http.methods.DoTriggerRelayOutput(): " + msg) c.JSON(400, gin.H{ - "data": "routers.http.methods.DoTriggerRelayOutput(): something went wrong: " + err.Error(), + "data": msg, }) } } From 6407f3da3d9203c25f1446cb8121b1923ee46722 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 28 Dec 2023 08:22:37 +0100 Subject: [PATCH 67/81] recover from failled pullpoint subscription --- machinery/src/cloud/Cloud.go | 11 ++++++++++- machinery/src/onvif/main.go | 14 +++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index d2ff0ac9..995538b4 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -304,9 +304,18 @@ loop: log.Log.Error("cloud.HandleHeartBeat(): error while marshalling events: " + err.Error()) onvifEventsList = []byte("[]") } - } else { + } else if len(events) == 0 { log.Log.Debug("cloud.HandleHeartBeat(): no events found.") onvifEventsList = []byte("[]") + } else if err != nil { + log.Log.Error("cloud.HandleHeartBeat(): error while getting events: " + err.Error()) + onvifEventsList = []byte("[]") + // Try to unsubscribe and subscribe again. + onvif.UnsubscribePullPoint(device, pullPointAddress) + pullPointAddress, err = onvif.CreatePullPointSubscription(device) + if err != nil { + log.Log.Error("cloud.HandleHeartBeat(): error while creating pull point subscription: " + err.Error()) + } } } else { log.Log.Debug("cloud.HandleHeartBeat(): no pull point address found.") diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 3c215ac8..91804aa6 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -962,10 +962,10 @@ func CreatePullPointSubscription(dev *onvif.Device) (string, error) { stringBody := string(b2) decodedXML, et, err := getXMLNode(stringBody, "CreatePullPointSubscriptionResponse") if err != nil { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + log.Log.Error("onvif.main.CreatePullPointSubscription(): " + err.Error()) } else { if err := decodedXML.DecodeElement(&createPullPointSubscriptionResponse, et); err != nil { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + log.Log.Error("onvif.main.CreatePullPointSubscription(): " + err.Error()) } else { pullPointAdress = string(createPullPointSubscriptionResponse.SubscriptionReference.Address) } @@ -1035,7 +1035,7 @@ func GetEventMessages(dev *onvif.Device, pullPointAddress string) ([]ONVIFEvents // We were able to create a subscription to the device. Now pull some messages from the subscription. subscriptionURI := pullPointAddress if subscriptionURI == "" { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): subscriptionURI is empty") + log.Log.Error("onvif.main.GetEventMessages(): subscriptionURI is empty") } else { // Pull message pullMessage := event.PullMessages{ @@ -1044,11 +1044,13 @@ func GetEventMessages(dev *onvif.Device, pullPointAddress string) ([]ONVIFEvents } requestBody, err := xml.Marshal(pullMessage) if err != nil { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) + return eventsArray, err } res, err := dev.SendSoap(string(subscriptionURI), string(requestBody)) if err != nil { - log.Log.Error("onvif.main.GetEventMessages(createPullPoint): " + err.Error()) + log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) + return eventsArray, err } var pullMessagesResponse event.PullMessagesResponse @@ -1060,9 +1062,11 @@ func GetEventMessages(dev *onvif.Device, pullPointAddress string) ([]ONVIFEvents decodedXML, et, err := getXMLNode(stringBody, "PullMessagesResponse") if err != nil { log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) + return eventsArray, err } else { if err := decodedXML.DecodeElement(&pullMessagesResponse, et); err != nil { log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) + return eventsArray, err } } } From b71dbddc1af63b818382c0e6c271ebbdc2518693 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 28 Dec 2023 10:24:15 +0100 Subject: [PATCH 68/81] add support for snapshots (raw + base64) #130 also tweaked the logging as bit more --- machinery/docs/docs.go | 58 +++++++++++++++++++------ machinery/docs/swagger.json | 58 +++++++++++++++++++------ machinery/docs/swagger.yaml | 48 ++++++++++++++------ machinery/src/capture/main.go | 38 +++++++++++++++- machinery/src/cloud/Cloud.go | 4 +- machinery/src/components/Kerberos.go | 41 ++++++++++++++++- machinery/src/onvif/main.go | 4 +- machinery/src/routers/http/methods.go | 18 ++++---- machinery/src/routers/http/routes.go | 10 +++++ machinery/src/routers/websocket/main.go | 3 +- 10 files changed, 222 insertions(+), 60 deletions(-) diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index a1cfe72c..da8980b6 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -29,7 +29,7 @@ const docTemplate = `{ "post": { "description": "Will return the ONVIF capabilities for the specific camera.", "tags": [ - "camera" + "onvif" ], "summary": "Will return the ONVIF capabilities for the specific camera.", "operationId": "camera-onvif-capabilities", @@ -58,7 +58,7 @@ const docTemplate = `{ "post": { "description": "Will activate the desired ONVIF preset.", "tags": [ - "camera" + "onvif" ], "summary": "Will activate the desired ONVIF preset.", "operationId": "camera-onvif-gotopreset", @@ -92,7 +92,7 @@ const docTemplate = `{ ], "description": "Will get the digital inputs from the ONVIF device.", "tags": [ - "camera" + "onvif" ], "summary": "Will get the digital inputs from the ONVIF device.", "operationId": "get-digital-inputs", @@ -121,7 +121,7 @@ const docTemplate = `{ "post": { "description": "Try to login into ONVIF supported camera.", "tags": [ - "camera" + "onvif" ], "summary": "Try to login into ONVIF supported camera.", "operationId": "camera-onvif-login", @@ -155,7 +155,7 @@ const docTemplate = `{ ], "description": "Will get the relay outputs from the ONVIF device.", "tags": [ - "camera" + "onvif" ], "summary": "Will get the relay outputs from the ONVIF device.", "operationId": "get-relay-outputs", @@ -189,7 +189,7 @@ const docTemplate = `{ ], "description": "Will trigger the relay output from the ONVIF device.", "tags": [ - "camera" + "onvif" ], "summary": "Will trigger the relay output from the ONVIF device.", "operationId": "trigger-relay-output", @@ -225,7 +225,7 @@ const docTemplate = `{ "post": { "description": "Panning or/and tilting the camera using a direction (x,y).", "tags": [ - "camera" + "onvif" ], "summary": "Panning or/and tilting the camera.", "operationId": "camera-onvif-pantilt", @@ -254,7 +254,7 @@ const docTemplate = `{ "post": { "description": "Will return the ONVIF presets for the specific camera.", "tags": [ - "camera" + "onvif" ], "summary": "Will return the ONVIF presets for the specific camera.", "operationId": "camera-onvif-presets", @@ -283,7 +283,7 @@ const docTemplate = `{ "post": { "description": "Zooming in or out the camera.", "tags": [ - "camera" + "onvif" ], "summary": "Zooming in or out the camera.", "operationId": "camera-onvif-zoom", @@ -344,6 +344,36 @@ const docTemplate = `{ } } }, + "/api/camera/snapshot/base64": { + "get": { + "description": "Get a snapshot from the camera in base64.", + "tags": [ + "camera" + ], + "summary": "Get a snapshot from the camera in base64.", + "operationId": "snapshot-base64", + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/camera/snapshot/jpeg": { + "get": { + "description": "Get a snapshot from the camera in jpeg format.", + "tags": [ + "camera" + ], + "summary": "Get a snapshot from the camera in jpeg format.", + "operationId": "snapshot-jpeg", + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/camera/stop": { "post": { "description": "Stop the agent.", @@ -406,7 +436,7 @@ const docTemplate = `{ "get": { "description": "Get the current configuration.", "tags": [ - "general" + "config" ], "summary": "Get the current configuration.", "operationId": "config", @@ -419,7 +449,7 @@ const docTemplate = `{ "post": { "description": "Update the current configuration.", "tags": [ - "general" + "config" ], "summary": "Update the current configuration.", "operationId": "config", @@ -480,7 +510,7 @@ const docTemplate = `{ ], "description": "Will verify the hub connectivity.", "tags": [ - "general" + "persistence" ], "summary": "Will verify the hub connectivity.", "operationId": "verify-hub", @@ -569,7 +599,7 @@ const docTemplate = `{ ], "description": "Will verify the ONVIF connectivity.", "tags": [ - "general" + "onvif" ], "summary": "Will verify the ONVIF connectivity.", "operationId": "verify-onvif", @@ -603,7 +633,7 @@ const docTemplate = `{ ], "description": "Will verify the persistence.", "tags": [ - "general" + "persistence" ], "summary": "Will verify the persistence.", "operationId": "verify-persistence", diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index a110b578..d1796601 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -21,7 +21,7 @@ "post": { "description": "Will return the ONVIF capabilities for the specific camera.", "tags": [ - "camera" + "onvif" ], "summary": "Will return the ONVIF capabilities for the specific camera.", "operationId": "camera-onvif-capabilities", @@ -50,7 +50,7 @@ "post": { "description": "Will activate the desired ONVIF preset.", "tags": [ - "camera" + "onvif" ], "summary": "Will activate the desired ONVIF preset.", "operationId": "camera-onvif-gotopreset", @@ -84,7 +84,7 @@ ], "description": "Will get the digital inputs from the ONVIF device.", "tags": [ - "camera" + "onvif" ], "summary": "Will get the digital inputs from the ONVIF device.", "operationId": "get-digital-inputs", @@ -113,7 +113,7 @@ "post": { "description": "Try to login into ONVIF supported camera.", "tags": [ - "camera" + "onvif" ], "summary": "Try to login into ONVIF supported camera.", "operationId": "camera-onvif-login", @@ -147,7 +147,7 @@ ], "description": "Will get the relay outputs from the ONVIF device.", "tags": [ - "camera" + "onvif" ], "summary": "Will get the relay outputs from the ONVIF device.", "operationId": "get-relay-outputs", @@ -181,7 +181,7 @@ ], "description": "Will trigger the relay output from the ONVIF device.", "tags": [ - "camera" + "onvif" ], "summary": "Will trigger the relay output from the ONVIF device.", "operationId": "trigger-relay-output", @@ -217,7 +217,7 @@ "post": { "description": "Panning or/and tilting the camera using a direction (x,y).", "tags": [ - "camera" + "onvif" ], "summary": "Panning or/and tilting the camera.", "operationId": "camera-onvif-pantilt", @@ -246,7 +246,7 @@ "post": { "description": "Will return the ONVIF presets for the specific camera.", "tags": [ - "camera" + "onvif" ], "summary": "Will return the ONVIF presets for the specific camera.", "operationId": "camera-onvif-presets", @@ -275,7 +275,7 @@ "post": { "description": "Zooming in or out the camera.", "tags": [ - "camera" + "onvif" ], "summary": "Zooming in or out the camera.", "operationId": "camera-onvif-zoom", @@ -336,6 +336,36 @@ } } }, + "/api/camera/snapshot/base64": { + "get": { + "description": "Get a snapshot from the camera in base64.", + "tags": [ + "camera" + ], + "summary": "Get a snapshot from the camera in base64.", + "operationId": "snapshot-base64", + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/camera/snapshot/jpeg": { + "get": { + "description": "Get a snapshot from the camera in jpeg format.", + "tags": [ + "camera" + ], + "summary": "Get a snapshot from the camera in jpeg format.", + "operationId": "snapshot-jpeg", + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/camera/stop": { "post": { "description": "Stop the agent.", @@ -398,7 +428,7 @@ "get": { "description": "Get the current configuration.", "tags": [ - "general" + "config" ], "summary": "Get the current configuration.", "operationId": "config", @@ -411,7 +441,7 @@ "post": { "description": "Update the current configuration.", "tags": [ - "general" + "config" ], "summary": "Update the current configuration.", "operationId": "config", @@ -472,7 +502,7 @@ ], "description": "Will verify the hub connectivity.", "tags": [ - "general" + "persistence" ], "summary": "Will verify the hub connectivity.", "operationId": "verify-hub", @@ -561,7 +591,7 @@ ], "description": "Will verify the ONVIF connectivity.", "tags": [ - "general" + "onvif" ], "summary": "Will verify the ONVIF connectivity.", "operationId": "verify-onvif", @@ -595,7 +625,7 @@ ], "description": "Will verify the persistence.", "tags": [ - "general" + "persistence" ], "summary": "Will verify the persistence.", "operationId": "verify-persistence", diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index a8a4621b..7d5f4880 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -349,7 +349,7 @@ paths: $ref: '#/definitions/models.APIResponse' summary: Will return the ONVIF capabilities for the specific camera. tags: - - camera + - onvif /api/camera/onvif/gotopreset: post: description: Will activate the desired ONVIF preset. @@ -368,7 +368,7 @@ paths: $ref: '#/definitions/models.APIResponse' summary: Will activate the desired ONVIF preset. tags: - - camera + - onvif /api/camera/onvif/inputs: post: description: Will get the digital inputs from the ONVIF device. @@ -389,7 +389,7 @@ paths: - Bearer: [] summary: Will get the digital inputs from the ONVIF device. tags: - - camera + - onvif /api/camera/onvif/login: post: description: Try to login into ONVIF supported camera. @@ -408,7 +408,7 @@ paths: $ref: '#/definitions/models.APIResponse' summary: Try to login into ONVIF supported camera. tags: - - camera + - onvif /api/camera/onvif/outputs: post: description: Will get the relay outputs from the ONVIF device. @@ -429,7 +429,7 @@ paths: - Bearer: [] summary: Will get the relay outputs from the ONVIF device. tags: - - camera + - onvif /api/camera/onvif/outputs/{output}: post: description: Will trigger the relay output from the ONVIF device. @@ -455,7 +455,7 @@ paths: - Bearer: [] summary: Will trigger the relay output from the ONVIF device. tags: - - camera + - onvif /api/camera/onvif/pantilt: post: description: Panning or/and tilting the camera using a direction (x,y). @@ -474,7 +474,7 @@ paths: $ref: '#/definitions/models.APIResponse' summary: Panning or/and tilting the camera. tags: - - camera + - onvif /api/camera/onvif/presets: post: description: Will return the ONVIF presets for the specific camera. @@ -493,7 +493,7 @@ paths: $ref: '#/definitions/models.APIResponse' summary: Will return the ONVIF presets for the specific camera. tags: - - camera + - onvif /api/camera/onvif/zoom: post: description: Zooming in or out the camera. @@ -512,7 +512,7 @@ paths: $ref: '#/definitions/models.APIResponse' summary: Zooming in or out the camera. tags: - - camera + - onvif /api/camera/record: post: description: Make a recording. @@ -537,6 +537,26 @@ paths: summary: Restart the agent. tags: - camera + /api/camera/snapshot/base64: + get: + description: Get a snapshot from the camera in base64. + operationId: snapshot-base64 + responses: + "200": + description: "" + summary: Get a snapshot from the camera in base64. + tags: + - camera + /api/camera/snapshot/jpeg: + get: + description: Get a snapshot from the camera in jpeg format. + operationId: snapshot-jpeg + responses: + "200": + description: "" + summary: Get a snapshot from the camera in jpeg format. + tags: + - camera /api/camera/stop: post: description: Stop the agent. @@ -586,7 +606,7 @@ paths: description: "" summary: Get the current configuration. tags: - - general + - config post: description: Update the current configuration. operationId: config @@ -602,7 +622,7 @@ paths: description: "" summary: Update the current configuration. tags: - - general + - config /api/dashboard: get: description: Get all information showed on the dashboard. @@ -643,7 +663,7 @@ paths: - Bearer: [] summary: Will verify the hub connectivity. tags: - - general + - persistence /api/latest-events: post: description: Get the latest recordings (events) from the recordings directory. @@ -700,7 +720,7 @@ paths: - Bearer: [] summary: Will verify the ONVIF connectivity. tags: - - general + - onvif /api/persistence/verify: post: description: Will verify the persistence. @@ -721,7 +741,7 @@ paths: - Bearer: [] summary: Will verify the persistence. tags: - - general + - persistence securityDefinitions: Bearer: in: header diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 9d0a1aeb..38a4ad38 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -618,9 +618,8 @@ func Base64Image(captureDevice *Capture, communication *models.Communication) st bytes, _ := utils.ImageToBytes(&img) encodedImage = base64.StdEncoding.EncodeToString(bytes) break - } else { - break } + break } } else { break @@ -629,6 +628,41 @@ func Base64Image(captureDevice *Capture, communication *models.Communication) st return encodedImage } +func JpegImage(captureDevice *Capture, communication *models.Communication) image.YCbCr { + // We'll try to get a snapshot from the camera. + var queue *packets.Queue + var cursor *packets.QueueCursor + + // We'll pick the right client and decoder. + rtspClient := captureDevice.RTSPSubClient + if rtspClient != nil { + queue = communication.SubQueue + cursor = queue.Latest() + } else { + rtspClient = captureDevice.RTSPClient + queue = communication.Queue + cursor = queue.Latest() + } + + // We'll try to have a keyframe, if not we'll return an empty string. + var image image.YCbCr + for { + if queue != nil && cursor != nil && rtspClient != nil { + pkt, err := cursor.ReadPacket() + if err == nil { + if !pkt.IsKeyFrame { + continue + } + image, _ = (*rtspClient).DecodePacket(pkt) + break + } + } else { + break + } + } + return image +} + func convertPTS(v time.Duration) uint64 { return uint64(v.Milliseconds()) } diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 995538b4..c4e1fca4 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -678,7 +678,7 @@ func HandleLiveStreamHD(livestreamCursor *packets.QueueCursor, configuration *mo // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags general +// @Tags persistence // @Param config body models.Config true "Config" // @Summary Will verify the hub connectivity. // @Description Will verify the hub connectivity. @@ -748,7 +748,7 @@ func VerifyHub(c *gin.Context) { // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags general +// @Tags persistence // @Param config body models.Config true "Config" // @Summary Will verify the persistence. // @Description Will verify the persistence. diff --git a/machinery/src/components/Kerberos.go b/machinery/src/components/Kerberos.go index 8156da0b..7a6f4776 100644 --- a/machinery/src/components/Kerberos.go +++ b/machinery/src/components/Kerberos.go @@ -588,10 +588,47 @@ func MakeRecording(c *gin.Context, communication *models.Communication) { }) } +// GetSnapshotBase64 godoc +// @Router /api/camera/snapshot/base64 [get] +// @ID snapshot-base64 +// @Tags camera +// @Summary Get a snapshot from the camera in base64. +// @Description Get a snapshot from the camera in base64. +// @Success 200 +func GetSnapshotBase64(c *gin.Context, captureDevice *capture.Capture, configuration *models.Configuration, communication *models.Communication) { + // We'll try to get a snapshot from the camera. + base64Image := capture.Base64Image(captureDevice, communication) + if base64Image != "" { + communication.Image = base64Image + } + + c.JSON(200, gin.H{ + "base64": communication.Image, + }) +} + +// GetSnapshotJpeg godoc +// @Router /api/camera/snapshot/jpeg [get] +// @ID snapshot-jpeg +// @Tags camera +// @Summary Get a snapshot from the camera in jpeg format. +// @Description Get a snapshot from the camera in jpeg format. +// @Success 200 +func GetSnapshotRaw(c *gin.Context, captureDevice *capture.Capture, configuration *models.Configuration, communication *models.Communication) { + // We'll try to get a snapshot from the camera. + image := capture.JpegImage(captureDevice, communication) + + // encode image to jpeg + bytes, _ := utils.ImageToBytes(&image) + + // Return image/jpeg + c.Data(200, "image/jpeg", bytes) +} + // GetConfig godoc // @Router /api/config [get] // @ID config -// @Tags general +// @Tags config // @Summary Get the current configuration. // @Description Get the current configuration. // @Success 200 @@ -613,7 +650,7 @@ func GetConfig(c *gin.Context, captureDevice *capture.Capture, configuration *mo // UpdateConfig godoc // @Router /api/config [post] // @ID config -// @Tags general +// @Tags config // @Param config body models.Config true "Configuration" // @Summary Update the current configuration. // @Description Update the current configuration. diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 91804aa6..514769bf 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -898,7 +898,7 @@ func GetPTZFunctionsFromDevice(configurations ptz.GetConfigurationsResponse) ([] // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags general +// @Tags onvif // @Param cameraConfig body models.IPCamera true "Camera Config" // @Summary Will verify the ONVIF connectivity. // @Description Will verify the ONVIF connectivity. @@ -1018,7 +1018,7 @@ func GetInputOutputs() ([]ONVIFEvents, error) { eventsArray = append(eventsArray, *value) } for _, value := range eventsArray { - log.Log.Info("onvif.main.GetInputOutputs(): " + value.Key + " - " + value.Value + " (" + strconv.FormatInt(value.Timestamp, 10) + ")") + log.Log.Debug("onvif.main.GetInputOutputs(): " + value.Key + " - " + value.Value + " (" + strconv.FormatInt(value.Timestamp, 10) + ")") } return eventsArray, nil } diff --git a/machinery/src/routers/http/methods.go b/machinery/src/routers/http/methods.go index 49daf3a7..acd1ac40 100644 --- a/machinery/src/routers/http/methods.go +++ b/machinery/src/routers/http/methods.go @@ -20,7 +20,7 @@ func Login() {} // LoginToOnvif godoc // @Router /api/camera/onvif/login [post] // @ID camera-onvif-login -// @Tags camera +// @Tags onvif // @Param config body models.OnvifCredentials true "OnvifCredentials" // @Summary Try to login into ONVIF supported camera. // @Description Try to login into ONVIF supported camera. @@ -64,7 +64,7 @@ func LoginToOnvif(c *gin.Context) { // GetOnvifCapabilities godoc // @Router /api/camera/onvif/capabilities [post] // @ID camera-onvif-capabilities -// @Tags camera +// @Tags onvif // @Param config body models.OnvifCredentials true "OnvifCredentials" // @Summary Will return the ONVIF capabilities for the specific camera. // @Description Will return the ONVIF capabilities for the specific camera. @@ -108,7 +108,7 @@ func GetOnvifCapabilities(c *gin.Context) { // DoOnvifPanTilt godoc // @Router /api/camera/onvif/pantilt [post] // @ID camera-onvif-pantilt -// @Tags camera +// @Tags onvif // @Param panTilt body models.OnvifPanTilt true "OnvifPanTilt" // @Summary Panning or/and tilting the camera. // @Description Panning or/and tilting the camera using a direction (x,y). @@ -182,7 +182,7 @@ func DoOnvifPanTilt(c *gin.Context) { // DoOnvifZoom godoc // @Router /api/camera/onvif/zoom [post] // @ID camera-onvif-zoom -// @Tags camera +// @Tags onvif // @Param zoom body models.OnvifZoom true "OnvifZoom" // @Summary Zooming in or out the camera. // @Description Zooming in or out the camera. @@ -255,7 +255,7 @@ func DoOnvifZoom(c *gin.Context) { // GetOnvifPresets godoc // @Router /api/camera/onvif/presets [post] // @ID camera-onvif-presets -// @Tags camera +// @Tags onvif // @Param config body models.OnvifCredentials true "OnvifCredentials" // @Summary Will return the ONVIF presets for the specific camera. // @Description Will return the ONVIF presets for the specific camera. @@ -306,7 +306,7 @@ func GetOnvifPresets(c *gin.Context) { // GoToOnvifPReset godoc // @Router /api/camera/onvif/gotopreset [post] // @ID camera-onvif-gotopreset -// @Tags camera +// @Tags onvif // @Param config body models.OnvifPreset true "OnvifPreset" // @Summary Will activate the desired ONVIF preset. // @Description Will activate the desired ONVIF preset. @@ -361,7 +361,7 @@ func GoToOnvifPreset(c *gin.Context) { // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags camera +// @Tags onvif // @Param config body models.OnvifCredentials true "OnvifCredentials" // @Summary Will get the digital inputs from the ONVIF device. // @Description Will get the digital inputs from the ONVIF device. @@ -430,7 +430,7 @@ func DoGetDigitalInputs(c *gin.Context) { // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags camera +// @Tags onvif // @Param config body models.OnvifCredentials true "OnvifCredentials" // @Summary Will get the relay outputs from the ONVIF device. // @Description Will get the relay outputs from the ONVIF device. @@ -499,7 +499,7 @@ func DoGetRelayOutputs(c *gin.Context) { // @securityDefinitions.apikey Bearer // @in header // @name Authorization -// @Tags camera +// @Tags onvif // @Param config body models.OnvifCredentials true "OnvifCredentials" // @Param output path string true "Output" // @Summary Will trigger the relay output from the ONVIF device. diff --git a/machinery/src/routers/http/routes.go b/machinery/src/routers/http/routes.go index 0ce02c9d..176cdd87 100644 --- a/machinery/src/routers/http/routes.go +++ b/machinery/src/routers/http/routes.go @@ -85,6 +85,16 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect components.MakeRecording(c, communication) }) + api.GET("/camera/snapshot/jpeg", func(c *gin.Context) { + components.GetSnapshotRaw(c, captureDevice, configuration, communication) + }) + + api.GET("/camera/snapshot/base64", func(c *gin.Context) { + components.GetSnapshotBase64(c, captureDevice, configuration, communication) + }) + + // Onvif specific methods. Doesn't require any authorization. + api.POST("/camera/onvif/login", LoginToOnvif) api.POST("/camera/onvif/capabilities", GetOnvifCapabilities) api.POST("/camera/onvif/presets", GetOnvifPresets) diff --git a/machinery/src/routers/websocket/main.go b/machinery/src/routers/websocket/main.go index dcd4bf56..1db43828 100644 --- a/machinery/src/routers/websocket/main.go +++ b/machinery/src/routers/websocket/main.go @@ -70,6 +70,7 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu connection.Socket = conn sockets[clientID] = connection sockets[clientID].Cancels = make(map[string]context.CancelFunc) + log.Log.Info("routers.websocket.main.WebsocketHandler(): " + clientID + ": connected.") } // Continuously read messages @@ -98,7 +99,7 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication, captu if communication.CameraConnected { _, exists := sockets[clientID].Cancels["stream-sd"] if exists { - log.Log.Info("routers.websocket.main.WebsocketHandler(): already streaming sd for " + clientID) + log.Log.Debug("routers.websocket.main.WebsocketHandler(): already streaming sd for " + clientID) } else { startStream := Message{ ClientID: clientID, From 242ff48ab66fe24118e0414bde5d13dc8596e92f Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 28 Dec 2023 10:55:11 +0100 Subject: [PATCH 69/81] add more description error with onvif invalid credentials + send capabilitites as part of onvif/login or verify --- machinery/docs/docs.go | 6 +-- machinery/docs/swagger.json | 6 +-- machinery/docs/swagger.yaml | 6 +-- machinery/src/cloud/Cloud.go | 6 +-- machinery/src/onvif/main.go | 70 +++++++++++++++++++-------- machinery/src/routers/http/methods.go | 36 +++++++++----- machinery/src/routers/mqtt/main.go | 2 +- 7 files changed, 87 insertions(+), 45 deletions(-) diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index da8980b6..e1d6ff15 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -605,12 +605,12 @@ const docTemplate = `{ "operationId": "verify-onvif", "parameters": [ { - "description": "Camera Config", - "name": "cameraConfig", + "description": "OnvifCredentials", + "name": "config", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.IPCamera" + "$ref": "#/definitions/models.OnvifCredentials" } } ], diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index d1796601..a4e7d7f2 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -597,12 +597,12 @@ "operationId": "verify-onvif", "parameters": [ { - "description": "Camera Config", - "name": "cameraConfig", + "description": "OnvifCredentials", + "name": "config", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.IPCamera" + "$ref": "#/definitions/models.OnvifCredentials" } } ], diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index 7d5f4880..6b5fa6ba 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -705,12 +705,12 @@ paths: description: Will verify the ONVIF connectivity. operationId: verify-onvif parameters: - - description: Camera Config + - description: OnvifCredentials in: body - name: cameraConfig + name: config required: true schema: - $ref: '#/definitions/models.IPCamera' + $ref: '#/definitions/models.OnvifCredentials' responses: "200": description: OK diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index c4e1fca4..c9b489ee 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -235,7 +235,7 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models. var pullPointAddress string if config.Capture.IPCamera.ONVIFXAddr != "" { cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { pullPointAddress, err = onvif.CreatePullPointSubscription(device) if err != nil { @@ -260,7 +260,7 @@ loop: var onvifEventsList []byte if config.Capture.IPCamera.ONVIFXAddr != "" { cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { configurations, err := onvif.GetPTZConfigurationsFromDevice(device) if err == nil { @@ -553,7 +553,7 @@ loop: if pullPointAddress != "" { cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { onvif.UnsubscribePullPoint(device, pullPointAddress) } diff --git a/machinery/src/onvif/main.go b/machinery/src/onvif/main.go index 514769bf..ac7c945b 100644 --- a/machinery/src/onvif/main.go +++ b/machinery/src/onvif/main.go @@ -54,7 +54,7 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode // Connect to Onvif device cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := ConnectToOnvifDevice(&cameraConfiguration) if err == nil { // Get token from the first profile @@ -189,7 +189,7 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode log.Log.Debug("onvif.HandleONVIFActions(): finished") } -func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, error) { +func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, device.GetCapabilitiesResponse, error) { log.Log.Debug("onvif.ConnectToOnvifDevice(): started") dev, err := onvif.NewDevice(onvif.DeviceParams{ Xaddr: cameraConfiguration.ONVIFXAddr, @@ -197,6 +197,8 @@ func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, Password: cameraConfiguration.ONVIFPassword, AuthMode: "both", }) + + var capabilities device.GetCapabilitiesResponse if err != nil { log.Log.Debug("onvif.ConnectToOnvifDevice(): " + err.Error()) } else { @@ -220,7 +222,6 @@ func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, if err != nil { log.Log.Error("onvif.ConnectToOnvifDevice(): " + err.Error()) } else { - var capabilities device.GetCapabilitiesResponse if err := decodedXML.DecodeElement(&capabilities, et); err != nil { log.Log.Error("onvif.ConnectToOnvifDevice(): " + err.Error()) } else { @@ -231,7 +232,7 @@ func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*onvif.Device, log.Log.Info("onvif.ConnectToOnvifDevice(): successfully connected to device") } log.Log.Debug("onvif.ConnectToOnvifDevice(): finished") - return dev, err + return dev, capabilities, err } func GetTokenFromProfile(device *onvif.Device, profileId int) (xsdonvif.ReferenceToken, error) { @@ -302,7 +303,7 @@ func GetPositionFromDevice(configuration models.Configuration) (xsdonvif.PTZVect var position xsdonvif.PTZVector // Connect to Onvif device cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := ConnectToOnvifDevice(&cameraConfiguration) if err == nil { // Get token from the first profile @@ -899,28 +900,52 @@ func GetPTZFunctionsFromDevice(configurations ptz.GetConfigurationsResponse) ([] // @in header // @name Authorization // @Tags onvif -// @Param cameraConfig body models.IPCamera true "Camera Config" +// @Param config body models.OnvifCredentials true "OnvifCredentials" // @Summary Will verify the ONVIF connectivity. // @Description Will verify the ONVIF connectivity. // @Success 200 {object} models.APIResponse func VerifyOnvifConnection(c *gin.Context) { - var cameraConfig models.IPCamera - err := c.BindJSON(&cameraConfig) - if err == nil { - device, err := ConnectToOnvifDevice(&cameraConfig) + var onvifCredentials models.OnvifCredentials + err := c.BindJSON(&onvifCredentials) + + if err == nil && onvifCredentials.ONVIFXAddr != "" { + + configuration := &models.Configuration{ + Config: models.Config{ + Capture: models.Capture{ + IPCamera: models.IPCamera{ + ONVIFXAddr: onvifCredentials.ONVIFXAddr, + ONVIFUsername: onvifCredentials.ONVIFUsername, + ONVIFPassword: onvifCredentials.ONVIFPassword, + }, + }, + }, + } + + cameraConfiguration := configuration.Config.Capture.IPCamera + device, capabilities, err := ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - log.Log.Info("onvif.main.VerifyOnvifConnection(): successfully verified the ONVIF connection") - c.JSON(200, models.APIResponse{ - Data: device, - }) + // Get token from the first profile + token, err := GetTokenFromProfile(device, 0) + if err == nil { + c.JSON(200, gin.H{ + "device": device, + "capabilities": capabilities, + "token": token, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } } else { - c.JSON(400, models.APIResponse{ - Message: "onvif.main.VerifyOnvifConnection(): s went wrong while verifying the ONVIF connection " + err.Error(), + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), }) } } else { - c.JSON(400, models.APIResponse{ - Message: "onvif.main.VerifyOnvifConnection(): s went wrong while receiving the config " + err.Error(), + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), }) } } @@ -1264,5 +1289,12 @@ func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElemen } } } - return nil, nil, errors.New("getXMLNode(): " + err.Error()) + + // Check for authorisation error + // - The action requested requires authorization and the sender is not authorized + if strings.Contains(xmlBody, "not authorized") { + return nil, nil, errors.New("getXMLNode(): not authorized, make sure you have the correct credentials") + } else { + return nil, nil, errors.New("getXMLNode(): " + err.Error()) + } } diff --git a/machinery/src/routers/http/methods.go b/machinery/src/routers/http/methods.go index acd1ac40..0e31195a 100644 --- a/machinery/src/routers/http/methods.go +++ b/machinery/src/routers/http/methods.go @@ -44,11 +44,21 @@ func LoginToOnvif(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, capabilities, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - c.JSON(200, gin.H{ - "device": device, - }) + // Get token from the first profile + token, err := onvif.GetTokenFromProfile(device, 0) + if err == nil { + c.JSON(200, gin.H{ + "device": device, + "capabilities": capabilities, + "token": token, + }) + } else { + c.JSON(400, gin.H{ + "data": "Something went wrong: " + err.Error(), + }) + } } else { c.JSON(400, gin.H{ "data": "Something went wrong: " + err.Error(), @@ -88,10 +98,10 @@ func GetOnvifCapabilities(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + _, capabilities, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { c.JSON(200, gin.H{ - "capabilities": onvif.GetCapabilitiesFromDevice(device), + "capabilities": capabilities, }) } else { c.JSON(400, gin.H{ @@ -132,7 +142,7 @@ func DoOnvifPanTilt(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { // Get token from the first profile @@ -206,7 +216,7 @@ func DoOnvifZoom(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { // Get token from the first profile @@ -279,7 +289,7 @@ func GetOnvifPresets(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { presets, err := onvif.GetPresetsFromDevice(device) if err == nil { @@ -330,7 +340,7 @@ func GoToOnvifPreset(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { err := onvif.GoToPresetFromDevice(device, onvifPreset.Preset) if err == nil { @@ -385,7 +395,7 @@ func DoGetDigitalInputs(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + _, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { // Get the digital inputs and outputs from the device inputOutputs, err := onvif.GetInputOutputs() @@ -454,7 +464,7 @@ func DoGetRelayOutputs(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + _, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { // Get the digital inputs and outputs from the device inputOutputs, err := onvif.GetInputOutputs() @@ -527,7 +537,7 @@ func DoTriggerRelayOutput(c *gin.Context) { } cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { err := onvif.TriggerRelayOutput(device, output) if err == nil { diff --git a/machinery/src/routers/mqtt/main.go b/machinery/src/routers/mqtt/main.go index f7311b0a..e2458f62 100644 --- a/machinery/src/routers/mqtt/main.go +++ b/machinery/src/routers/mqtt/main.go @@ -518,7 +518,7 @@ func HandleTriggerRelay(mqttClient mqtt.Client, hubKey string, payload models.Pa token := triggerRelayPayload.Token // Connect to Onvif device cameraConfiguration := configuration.Config.Capture.IPCamera - device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { // Trigger relay output err := onvif.TriggerRelayOutput(device, token) From 77449a29e7670acb1650cd12f4a881e907bdd145 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 28 Dec 2023 11:24:36 +0100 Subject: [PATCH 70/81] add h264 and h265 discussion --- README.md | 80 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 58c945d7..55abeb9d 100644 --- a/README.md +++ b/README.md @@ -46,27 +46,32 @@ There are a myriad of cameras out there (USB, IP and other cameras), and it migh ### Introduction -3. [A world of Kerberos Agents](#a-world-of-kerberos-agents) +1. [A world of Kerberos Agents](#a-world-of-kerberos-agents) ### Running and automation -4. [How to run and deploy a Kerberos Agent](#how-to-run-and-deploy-a-kerberos-agent) -5. [Access the Kerberos Agent](#access-the-kerberos-agent) -6. [Configure and persist with volume mounts](#configure-and-persist-with-volume-mounts) -7. [Configure with environment variables](#configure-with-environment-variables) +1. [How to run and deploy a Kerberos Agent](#how-to-run-and-deploy-a-kerberos-agent) +2. [Access the Kerberos Agent](#access-the-kerberos-agent) +3. [Configure and persist with volume mounts](#configure-and-persist-with-volume-mounts) +4. [Configure with environment variables](#configure-with-environment-variables) + +### Insights + +1. [Encryption](#encryption) +2. [H264 vs H265](#h264-vs-h265) ### Contributing -8. [Contribute with Codespaces](#contribute-with-codespaces) -9. [Develop and build](#develop-and-build) -10. [Building from source](#building-from-source) -11. [Building for Docker](#building-for-docker) +1. [Contribute with Codespaces](#contribute-with-codespaces) +2. [Develop and build](#develop-and-build) +3. [Building from source](#building-from-source) +4. [Building for Docker](#building-for-docker) ### Varia -12. [Support our project](#support-our-project) -13. [What is new?](#what-is-new) -14. [Contributors](#contributors) +1. [Support our project](#support-our-project) +1. [What is new?](#what-is-new) +1. [Contributors](#contributors) ## Quickstart - Docker @@ -151,20 +156,6 @@ The default username and password for the Kerberos Agent is: **_Please note that you change the username and password for a final installation, see [Configure with environment variables](#configure-with-environment-variables) below._** -## Encryption - -You can encrypt your recordings and outgoing MQTT messages with your own AES and RSA keys by enabling the encryption settings. Once enabled all your recordings will be encrypted using AES-256-CBC and your symmetric key. You can either use the default `openssl` toolchain to decrypt the recordings with your AES key, as following: - - openssl aes-256-cbc -d -md md5 -in encrypted.mp4 -out decrypted.mp4 -k your-key-96ab185xxxxxxxcxxxxxxxx6a59c62e8 - -, and additionally you can decrypt a folder of recordings, using the Kerberos Agent binary as following: - - go run main.go -action decrypt ./data/recordings your-key-96ab185xxxxxxxcxxxxxxxx6a59c62e8 - -or for a single file: - - go run main.go -action decrypt ./data/recordings/video.mp4 your-key-96ab185xxxxxxxcxxxxxxxx6a59c62e8 - ## Configure and persist with volume mounts An example of how to mount a host directory is shown below using `docker`, but is applicable for [all the deployment models and tools described above](#running-and-automating-a-kerberos-agent). @@ -253,6 +244,43 @@ Next to attaching the configuration file, it is also possible to override the co | `AGENT_ENCRYPTION_PRIVATE_KEY` | The private key (assymetric/RSA) to decryptand sign requests send over MQTT. | "" | | `AGENT_ENCRYPTION_SYMMETRIC_KEY` | The symmetric key (AES) to encrypt and decrypt request send over MQTT. | "" | +## Encryption + +You can encrypt your recordings and outgoing MQTT messages with your own AES and RSA keys by enabling the encryption settings. Once enabled all your recordings will be encrypted using AES-256-CBC and your symmetric key. You can either use the default `openssl` toolchain to decrypt the recordings with your AES key, as following: + + openssl aes-256-cbc -d -md md5 -in encrypted.mp4 -out decrypted.mp4 -k your-key-96ab185xxxxxxxcxxxxxxxx6a59c62e8 + +, and additionally you can decrypt a folder of recordings, using the Kerberos Agent binary as following: + + go run main.go -action decrypt ./data/recordings your-key-96ab185xxxxxxxcxxxxxxxx6a59c62e8 + +or for a single file: + + go run main.go -action decrypt ./data/recordings/video.mp4 your-key-96ab185xxxxxxxcxxxxxxxx6a59c62e8 + +## H264 vs H265 + +If we talk about video encoding there are 2 major video encoders on the market: H264 and H265. Depending on your use cases you might use one over the other. We will a (not complete) overview of the advantages and disadvantages of each in the field of video surveillance and video analytics. If you would like to know more, you should look for additional resources. + +- H264 (also known as AVC or MPEG-4 Part 10) + - Is the most common one and widely supported for IP cameras. + - Widely supported in browsers and other type of applications. + - Can be embedded in commercial and 3rd party applications. + - Different levels of compression (high, medium, low, ..) + - Better quality / compression ratio. + - Does support technologies such as WebRTC + +- H265 (also known as HEVC) + - Is not supported on legacy cameras, though becoming rapidly available on newer IP camera version. + - Might not always be supported due to licensing. For example not supported in browers on a Linux distro. + - Requires licensing when embedding in a commercial product (be careful). + - Higher levels of compression (50% more than h264). + - H265 shows artifacts in motion based environments (which is less with H264). + - Recording the same video (resolution, duration and FPS) in H264 and H265 will result in approx 50% the file size. + - Not supported in technologies such as WebRTC + +Conclusion: depending on the use case you might choose one over the other, and you can use both at the same time. For example you can use H264 (main stream) for livestreaming, and H265 for recording. Depending on your use case where you wish to play recordings in a cross-platform and cross-browser you might opt for H264 for better support. + ## Contribute with Codespaces One of the major blockers for letting you contribute to an Open Source project is to setup your local development machine. Why? Because you might have already some tools and libraries installed that are used for other projects, and the libraries you would need for Kerberos Agent, for example FFmpeg, might require a different version. Welcome to the dependency hell.. From 3ac34a366f56dd28f87207da0f09905ee97cdc70 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 28 Dec 2023 11:29:33 +0100 Subject: [PATCH 71/81] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 55abeb9d..5e3a743a 100644 --- a/README.md +++ b/README.md @@ -260,18 +260,18 @@ or for a single file: ## H264 vs H265 -If we talk about video encoding there are 2 major video encoders on the market: H264 and H265. Depending on your use cases you might use one over the other. We will a (not complete) overview of the advantages and disadvantages of each in the field of video surveillance and video analytics. If you would like to know more, you should look for additional resources. +If we talk about video encoders and decoders (codecs) there are 2 major video encoders on the market: H264 and H265. Taking into account your use cases you might use one over the other. We will provide an (not complete) overview of the advantages and disadvantages of each codec in the field of video surveillance and video analytics. If you would like to know more, you should look for additional resources on the internet (or if you like to read physical items, books still exists). - H264 (also known as AVC or MPEG-4 Part 10) - - Is the most common one and widely supported for IP cameras. - - Widely supported in browsers and other type of applications. + - Is the most common one and most widely supported for IP cameras. + - Supported in the majority browsers, operating system and third-party applications. - Can be embedded in commercial and 3rd party applications. - Different levels of compression (high, medium, low, ..) - - Better quality / compression ratio. + - Better quality / compression ratio, shows less artifacts at medium compression ratios. - Does support technologies such as WebRTC - H265 (also known as HEVC) - - Is not supported on legacy cameras, though becoming rapidly available on newer IP camera version. + - Is not supported on legacy cameras, though becoming rapidly available on "newer" IP cameras. - Might not always be supported due to licensing. For example not supported in browers on a Linux distro. - Requires licensing when embedding in a commercial product (be careful). - Higher levels of compression (50% more than h264). @@ -279,7 +279,7 @@ If we talk about video encoding there are 2 major video encoders on the market: - Recording the same video (resolution, duration and FPS) in H264 and H265 will result in approx 50% the file size. - Not supported in technologies such as WebRTC -Conclusion: depending on the use case you might choose one over the other, and you can use both at the same time. For example you can use H264 (main stream) for livestreaming, and H265 for recording. Depending on your use case where you wish to play recordings in a cross-platform and cross-browser you might opt for H264 for better support. +Conclusion: depending on the use case you might choose one over the other, and you can use both at the same time. For example you can use H264 (main stream) for livestreaming, and H265 (sub stream) for recording. If you wish to play recordings in a cross-platform and cross-browser environment, you might opt for H264 for better support. ## Contribute with Codespaces From 60bb9a521c3b8f84c3980a1e28f858fba9427f8c Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 28 Dec 2023 11:32:46 +0100 Subject: [PATCH 72/81] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5e3a743a..8016dd4b 100644 --- a/README.md +++ b/README.md @@ -260,11 +260,11 @@ or for a single file: ## H264 vs H265 -If we talk about video encoders and decoders (codecs) there are 2 major video encoders on the market: H264 and H265. Taking into account your use cases you might use one over the other. We will provide an (not complete) overview of the advantages and disadvantages of each codec in the field of video surveillance and video analytics. If you would like to know more, you should look for additional resources on the internet (or if you like to read physical items, books still exists). +If we talk about video encoders and decoders (codecs) there are 2 major video codecs on the market: H264 and H265. Taking into account your use case, you might use one over the other. We will provide an (not complete) overview of the advantages and disadvantages of each codec in the field of video surveillance and video analytics. If you would like to know more, you should look for additional resources on the internet (or if you like to read physical items, books still exists nowadays). - H264 (also known as AVC or MPEG-4 Part 10) - Is the most common one and most widely supported for IP cameras. - - Supported in the majority browsers, operating system and third-party applications. + - Supported in the majority of browsers, operating system and third-party applications. - Can be embedded in commercial and 3rd party applications. - Different levels of compression (high, medium, low, ..) - Better quality / compression ratio, shows less artifacts at medium compression ratios. @@ -274,7 +274,7 @@ If we talk about video encoders and decoders (codecs) there are 2 major video en - Is not supported on legacy cameras, though becoming rapidly available on "newer" IP cameras. - Might not always be supported due to licensing. For example not supported in browers on a Linux distro. - Requires licensing when embedding in a commercial product (be careful). - - Higher levels of compression (50% more than h264). + - Higher levels of compression (50% more than H264). - H265 shows artifacts in motion based environments (which is less with H264). - Recording the same video (resolution, duration and FPS) in H264 and H265 will result in approx 50% the file size. - Not supported in technologies such as WebRTC From e94a9a1000082dfafc00e04ac30a7cfe834f219a Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Thu, 28 Dec 2023 16:33:39 +0100 Subject: [PATCH 73/81] update base image --- .circleci/config.yml | 2 +- .devcontainer/Dockerfile | 2 +- Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2dc53505..1b800250 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2 jobs: machinery: docker: - - image: kerberos/base:91ab4d4 + - image: kerberos/base:0a50dc9 working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}} steps: - checkout diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1887f3db..2f186291 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,2 +1,2 @@ -FROM kerberos/devcontainer:b2bc659 +FROM kerberos/devcontainer:0a50dc9 LABEL AUTHOR=Kerberos.io diff --git a/Dockerfile b/Dockerfile index ce64f810..b73b2b4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -FROM kerberos/base:dc12d68 AS build-machinery +FROM kerberos/base:0a50dc9 AS build-machinery LABEL AUTHOR=Kerberos.io ENV GOROOT=/usr/local/go From b5415284e2450e8b02d179adcf43a401f5007495 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 29 Dec 2023 08:10:01 +0100 Subject: [PATCH 74/81] rename + add conceptual hidden function (not yet added) --- machinery/src/models/MQTT.go | 27 +++++++++++++++++++++++++-- machinery/src/routers/mqtt/main.go | 4 ++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/machinery/src/models/MQTT.go b/machinery/src/models/MQTT.go index a58215ad..15940015 100644 --- a/machinery/src/models/MQTT.go +++ b/machinery/src/models/MQTT.go @@ -27,8 +27,29 @@ func PackageMQTTMessage(configuration *Configuration, msg Message) ([]byte, erro msg.DeviceId = msg.Payload.DeviceId msg.Timestamp = time.Now().Unix() - // At the moment we don't do the encryption part, but we'll implement it - // once the legacy methods (subscriptions are moved). + // We'll hide the message (by default in latest version) + // We will encrypt using the Kerberos Hub private key if set. + /*msg.Hidden = false + if configuration.Config.HubPrivateKey != "" { + msg.Hidden = true + pload := msg.Payload + // Pload to base64 + data, err := json.Marshal(pload) + if err != nil { + msg.Hidden = false + } else { + k := configuration.Config.Encryption.SymmetricKey + encryptedValue, err := encryption.AesEncrypt(data, k) + if err == nil { + data := base64.StdEncoding.EncodeToString(encryptedValue) + msg.Payload.HiddenValue = data + msg.Payload.Value = make(map[string]interface{}) + } + } + }*/ + + // Next to hiding the message, we can also encrypt it using your own private key. + // Which is not stored in a remote environment (hence you are the only one owning it). msg.Encrypted = false if configuration.Config.Encryption != nil && configuration.Config.Encryption.Enabled == "true" { msg.Encrypted = true @@ -92,6 +113,7 @@ type Message struct { DeviceId string `json:"device_id"` Timestamp int64 `json:"timestamp"` Encrypted bool `json:"encrypted"` + Hidden bool `json:"hidden"` PublicKey string `json:"public_key"` Fingerprint string `json:"fingerprint"` Payload Payload `json:"payload"` @@ -104,6 +126,7 @@ type Payload struct { DeviceId string `json:"device_id"` Signature string `json:"signature"` EncryptedValue string `json:"encrypted_value"` + HiddenValue string `json:"hidden_value"` Value map[string]interface{} `json:"value"` } diff --git a/machinery/src/routers/mqtt/main.go b/machinery/src/routers/mqtt/main.go index e2458f62..32b9ce3c 100644 --- a/machinery/src/routers/mqtt/main.go +++ b/machinery/src/routers/mqtt/main.go @@ -149,8 +149,8 @@ func MQTTListenerHandler(mqttClient mqtt.Client, hubKey string, configDirectory if hubKey == "" { log.Log.Info("routers.mqtt.main.MQTTListenerHandler(): no hub key provided, not subscribing to kerberos/hub/{hubkey}") } else { - topicOnvif := fmt.Sprintf("kerberos/agent/%s", hubKey) - mqttClient.Subscribe(topicOnvif, 1, func(c mqtt.Client, msg mqtt.Message) { + agentListener := fmt.Sprintf("kerberos/agent/%s", hubKey) + mqttClient.Subscribe(agentListener, 1, func(c mqtt.Client, msg mqtt.Message) { // Decode the message, we are expecting following format. // { From 78e7fb595a9389bbf9214aad6f2c06bb9d194897 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 29 Dec 2023 11:37:32 +0100 Subject: [PATCH 75/81] make sure to set onvifEventsList = []byte("[]") --- machinery/src/cloud/Cloud.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index c9b489ee..8b855b97 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -325,10 +325,12 @@ loop: } else { log.Log.Error("cloud.HandleHeartBeat(): error while connecting to ONVIF device: " + err.Error()) onvifPresetsList = []byte("[]") + onvifEventsList = []byte("[]") } } else { log.Log.Debug("cloud.HandleHeartBeat(): ONVIF is not enabled.") onvifPresetsList = []byte("[]") + onvifEventsList = []byte("[]") } // We'll capture some more metrics, and send it to Hub, if not in offline mode ofcourse ;) ;) From 957d2fd0954e483b5545de926bea19f1771d3ed5 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Fri, 29 Dec 2023 14:59:34 +0100 Subject: [PATCH 76/81] Update Cloud.go --- machinery/src/cloud/Cloud.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 8b855b97..a94fe677 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -304,9 +304,6 @@ loop: log.Log.Error("cloud.HandleHeartBeat(): error while marshalling events: " + err.Error()) onvifEventsList = []byte("[]") } - } else if len(events) == 0 { - log.Log.Debug("cloud.HandleHeartBeat(): no events found.") - onvifEventsList = []byte("[]") } else if err != nil { log.Log.Error("cloud.HandleHeartBeat(): error while getting events: " + err.Error()) onvifEventsList = []byte("[]") @@ -316,6 +313,9 @@ loop: if err != nil { log.Log.Error("cloud.HandleHeartBeat(): error while creating pull point subscription: " + err.Error()) } + } else if len(events) == 0 { + log.Log.Debug("cloud.HandleHeartBeat(): no events found.") + onvifEventsList = []byte("[]") } } else { log.Log.Debug("cloud.HandleHeartBeat(): no pull point address found.") From 113b02d665589aaa4adf5296d876f81554f258f6 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 30 Dec 2023 09:18:46 +0100 Subject: [PATCH 77/81] Update Cloud.go --- machinery/src/cloud/Cloud.go | 1 - 1 file changed, 1 deletion(-) diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index a94fe677..1aa4de74 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -343,7 +343,6 @@ loop: username := "" vaultURI := "" - username = config.S3.Username if config.Cloud == "s3" && config.S3 != nil && config.S3.Publickey != "" { username = config.S3.Username key = config.S3.Publickey From f2aa3d91768a17072f01acf4645632c9e129ceda Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sat, 30 Dec 2023 22:07:45 +0100 Subject: [PATCH 78/81] onvif is enabled, currently expects ptz, which is not the case --- machinery/src/cloud/Cloud.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 1aa4de74..4c761c3d 100644 --- a/machinery/src/cloud/Cloud.go +++ b/machinery/src/cloud/Cloud.go @@ -262,9 +262,9 @@ loop: cameraConfiguration := configuration.Config.Capture.IPCamera device, _, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { + onvifEnabled = "true" configurations, err := onvif.GetPTZConfigurationsFromDevice(device) if err == nil { - onvifEnabled = "true" _, canZoom, canPanTilt := onvif.GetPTZFunctionsFromDevice(configurations) if canZoom { onvifZoom = "true" From 5261c1cbfc388972249053dbca77169745638e5c Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Sun, 31 Dec 2023 15:46:25 +0100 Subject: [PATCH 79/81] debug condition --- machinery/src/conditions/timewindow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machinery/src/conditions/timewindow.go b/machinery/src/conditions/timewindow.go index 1c90076f..5a0b49c4 100644 --- a/machinery/src/conditions/timewindow.go +++ b/machinery/src/conditions/timewindow.go @@ -27,7 +27,7 @@ func IsWithinTimeInterval(loc *time.Location, configuration *models.Configuratio currentTimeInSeconds := hour*60*60 + minute*60 + second if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) || (currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) { - log.Log.Info("conditions.timewindow.IsWithinTimeInterval(): time interval valid, enabling recording.") + log.Log.Debug("conditions.timewindow.IsWithinTimeInterval(): time interval valid, enabling recording.") } else { log.Log.Info("conditions.timewindow.IsWithinTimeInterval(): time interval not valid, disabling recording.") enabled = false From fb23815210f6a4b4d994c624e190249f181ec846 Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 1 Jan 2024 19:31:58 +0100 Subject: [PATCH 80/81] add support for H265 in UI --- machinery/src/capture/main.go | 2 +- ui/public/locales/de/translation.json | 4 ++-- ui/public/locales/en/translation.json | 4 ++-- ui/public/locales/es/translation.json | 4 ++-- ui/public/locales/fr/translation.json | 4 ++-- ui/public/locales/hi/translation.json | 4 ++-- ui/public/locales/it/translation.json | 4 ++-- ui/public/locales/ja/translation.json | 4 ++-- ui/public/locales/nl/translation.json | 4 ++-- ui/public/locales/pl/translation.json | 4 ++-- ui/public/locales/pt/translation.json | 4 ++-- ui/public/locales/ru/translation.json | 4 ++-- ui/public/locales/zh/translation.json | 4 ++-- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 38a4ad38..758e83f8 100644 --- a/machinery/src/capture/main.go +++ b/machinery/src/capture/main.go @@ -557,7 +557,7 @@ func VerifyCamera(c *gin.Context) { videoIdx := -1 audioIdx := -1 for i, stream := range streams { - if stream.Name == "H264" && videoIdx < 0 { + if (stream.Name == "H264" || stream.Name == "H265") && videoIdx < 0 { videoIdx = i } else if stream.Name == "PCM_MULAW" && audioIdx < 0 { audioIdx = i diff --git a/ui/public/locales/de/translation.json b/ui/public/locales/de/translation.json index 4b154b4b..fc7b1cb7 100644 --- a/ui/public/locales/de/translation.json +++ b/ui/public/locales/de/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Kamera", "description_camera": "Diese Einstellungen sind notwendig um eine Verbindung mit der Kamera herzustellen", - "only_h264": "Aktuell werden nur H264 RTSP kompatible Kameras unterstützt", + "only_h264": "Aktuell werden nur H264/H265 RTSP kompatible Kameras unterstützt", "rtsp_url": "RTSP URL", - "rtsp_h264": "H264 RTSP URL der Kamera", + "rtsp_h264": "H264/H265 RTSP URL der Kamera", "sub_rtsp_url": "RTSP url für die Live Übertragung.", "sub_rtsp_h264": "Ergänzende URL der Kamera mit geringerer Auflösung für die Live Übertragung.", "onvif": "ONVIF", diff --git a/ui/public/locales/en/translation.json b/ui/public/locales/en/translation.json index 13c6f375..9719153f 100644 --- a/ui/public/locales/en/translation.json +++ b/ui/public/locales/en/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Camera", "description_camera": "Camera settings are required to make a connection to your camera of choice.", - "only_h264": "Currently only H264 RTSP streams are supported.", + "only_h264": "Currently only H264/H265 RTSP streams are supported.", "rtsp_url": "RTSP url", - "rtsp_h264": "A H264 RTSP connection to your camera.", + "rtsp_h264": "A H264/H265 RTSP connection to your camera.", "sub_rtsp_url": "Sub RTSP url (used for livestreaming)", "sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.", "onvif": "ONVIF", diff --git a/ui/public/locales/es/translation.json b/ui/public/locales/es/translation.json index eb492d62..4d6404cd 100644 --- a/ui/public/locales/es/translation.json +++ b/ui/public/locales/es/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Camera", "description_camera": "Camera settings are required to make a connection to your camera of choice.", - "only_h264": "Currently only H264 RTSP streams are supported.", + "only_h264": "Currently only H264/H265 RTSP streams are supported.", "rtsp_url": "RTSP url", - "rtsp_h264": "A H264 RTSP connection to your camera.", + "rtsp_h264": "A H264/H265 RTSP connection to your camera.", "sub_rtsp_url": "Sub RTSP url (used for livestreaming)", "sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.", "onvif": "ONVIF", diff --git a/ui/public/locales/fr/translation.json b/ui/public/locales/fr/translation.json index 8070c6c7..4df6d405 100644 --- a/ui/public/locales/fr/translation.json +++ b/ui/public/locales/fr/translation.json @@ -98,9 +98,9 @@ "camera": { "camera": "Caméra", "description_camera": "Les paramètres de la caméra sont requis pour établir une connexion à la caméra de votre choix.", - "only_h264": "Actuellement, seuls les flux RTSP H264 sont pris en charge.", + "only_h264": "Actuellement, seuls les flux RTSP H264/H265 sont pris en charge.", "rtsp_url": "URL RTSP", - "rtsp_h264": "Une connexion RTSP H264 à votre caméra.", + "rtsp_h264": "Une connexion RTSP H264/H265 à votre caméra.", "sub_rtsp_url": "URL RTSP secondaire (utilisé pour le direct)", "sub_rtsp_h264": "Une connexion RTSP secondaire vers le flux basse résolution de votre caméra.", "onvif": "ONVIF", diff --git a/ui/public/locales/hi/translation.json b/ui/public/locales/hi/translation.json index 8452f5d3..964c5d79 100644 --- a/ui/public/locales/hi/translation.json +++ b/ui/public/locales/hi/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "कैमरा", "description_camera": "आपकी पसंद के कैमरे से कनेक्शन बनाने के लिए कैमरा सेटिंग्स की आवश्यकता होती है।", - "only_h264": "वर्तमान में केवल H264 RTSP स्ट्रीम समर्थित हैं।", + "only_h264": "वर्तमान में केवल H264/H265 RTSP स्ट्रीम समर्थित हैं।", "rtsp_url": "RTSP URL", - "rtsp_h264": "आपके कैमरे से H264 RTSP कनेक्शन।", + "rtsp_h264": "आपके कैमरे से H264/H265 RTSP कनेक्शन।", "sub_rtsp_url": "दुसरी RTSP URL (लाइवस्ट्रीमिंग के लिए प्रयुक्त)", "sub_rtsp_h264": "आपके कैमरे के कम रिज़ॉल्यूशन के लिए एक दुसरी RTSP कनेक्शन।", "onvif": "ONVIF", diff --git a/ui/public/locales/it/translation.json b/ui/public/locales/it/translation.json index 6e04d1c7..6b2664e2 100644 --- a/ui/public/locales/it/translation.json +++ b/ui/public/locales/it/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Videocamera", "description_camera": "Le impostazioni della fotocamera sono necessarie per stabilire una connessione con la videocamera scelta.", - "only_h264": "Al momento sono supportati solo streams RTSP H264.", + "only_h264": "Al momento sono supportati solo streams RTSP H264/H265.", "rtsp_url": "Url RTSP", - "rtsp_h264": "Connessione RTSP H264 alla videocamera.", + "rtsp_h264": "Connessione RTSP H264/H265 alla videocamera.", "sub_rtsp_url": "Sub-url RTSP (per lo streaming in diretta)", "sub_rtsp_h264": "URL RTSP supplementare della videocamera con risoluzione inferiore per lo streaming in diretta.", "onvif": "ONVIF", diff --git a/ui/public/locales/ja/translation.json b/ui/public/locales/ja/translation.json index 8307d55f..c59b2c5a 100644 --- a/ui/public/locales/ja/translation.json +++ b/ui/public/locales/ja/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "カメラ", "description_camera": "選択したカメラに接続するには、カメラの設定が必要です。", - "only_h264": "現在、H264 RTSP ストリームのみがサポートされています。", + "only_h264": "現在、H264/H265 RTSP ストリームのみがサポートされています。", "rtsp_url": "RTSP URL", - "rtsp_h264": "カメラへの H264 RTSP 接続。", + "rtsp_h264": "カメラへの H264/H265 RTSP 接続。", "sub_rtsp_url": "Sub RTSP url (ライブストリーミングに使用)", "sub_rtsp_h264": "カメラの低解像度へのセカンダリ RTSP 接続。", "onvif": "ONVIF", diff --git a/ui/public/locales/nl/translation.json b/ui/public/locales/nl/translation.json index 3f89b2e9..595617b1 100644 --- a/ui/public/locales/nl/translation.json +++ b/ui/public/locales/nl/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Camera", "description_camera": "Camera settings are required to make a connection to your camera of choice.", - "only_h264": "Momenteel worden enkel H264 RTSP streams gesupporteerd", + "only_h264": "Momenteel worden enkel H264/H265 RTSP streams gesupporteerd", "rtsp_url": "RTSP url", - "rtsp_h264": "Een H264 RTSP connectie met jouw camera.", + "rtsp_h264": "Een H264/H265 RTSP connectie met jouw camera.", "sub_rtsp_url": "Sub RTSP url (used for livestreaming)", "sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.", "onvif": "ONVIF", diff --git a/ui/public/locales/pl/translation.json b/ui/public/locales/pl/translation.json index 5690c145..8bdd7033 100644 --- a/ui/public/locales/pl/translation.json +++ b/ui/public/locales/pl/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Camera", "description_camera": "Camera settings are required to make a connection to your camera of choice.", - "only_h264": "Currently only H264 RTSP streams are supported.", + "only_h264": "Currently only H264/H265 RTSP streams are supported.", "rtsp_url": "RTSP url", - "rtsp_h264": "A H264 RTSP connection to your camera.", + "rtsp_h264": "A /H265 RTSP connection to your camera.", "sub_rtsp_url": "Sub RTSP url (used for livestreaming)", "sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.", "onvif": "ONVIF", diff --git a/ui/public/locales/pt/translation.json b/ui/public/locales/pt/translation.json index 7b4e0040..f7a9bd31 100644 --- a/ui/public/locales/pt/translation.json +++ b/ui/public/locales/pt/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Câmera", "description_camera": "As configurações da câmera são necessárias para fazer uma conexão com a câmera de sua escolha.", - "only_h264": "Atualmente, apenas streams H264 RTSP são suportados.", + "only_h264": "Atualmente, apenas streams H264/H265 RTSP são suportados.", "rtsp_url": "Url RTSP", - "rtsp_h264": "Uma conexão H264 RTSP para sua câmera.", + "rtsp_h264": "Uma conexão H264/H265 RTSP para sua câmera.", "sub_rtsp_url": "Sub RTSP URL(usado para transmissão ao vivo)", "sub_rtsp_h264": "Uma conexão RTSP secundária para a baixa resolução de sua câmera.", "onvif": "ONVIF", diff --git a/ui/public/locales/ru/translation.json b/ui/public/locales/ru/translation.json index 3b2f3b1a..66d55cd2 100644 --- a/ui/public/locales/ru/translation.json +++ b/ui/public/locales/ru/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "Камера", "description_camera": "Настройки камеры необходимы для установки соединения с выбранной камерой.", - "only_h264": "В настоящее время поддерживаются только потоки H264 RTSP.", + "only_h264": "В настоящее время поддерживаются только потоки H264/H265 RTSP.", "rtsp_url": "Адрес основного потока RTSP", - "rtsp_h264": "Подключение к камере по протоколу H264 RTSP.", + "rtsp_h264": "Подключение к камере по протоколу H264/H265 RTSP.", "sub_rtsp_url": "Адрес дополнительного потока RTSP (используется для прямой трансляции)", "sub_rtsp_h264": "Дополнительное RTSP-соединение с низким разрешением камеры.", "onvif": "ONVIF", diff --git a/ui/public/locales/zh/translation.json b/ui/public/locales/zh/translation.json index 8856be75..fb1006c1 100644 --- a/ui/public/locales/zh/translation.json +++ b/ui/public/locales/zh/translation.json @@ -99,9 +99,9 @@ "camera": { "camera": "相机", "description_camera": "需要相机设置才能连接到您选择的相机。", - "only_h264": "目前仅支持 H264 RTSP 流。", + "only_h264": "目前仅支持 H264/H265 RTSP 流。", "rtsp_url": "RTSP 网址", - "rtsp_h264": "与摄像机的 H264 RTSP 连接。", + "rtsp_h264": "与摄像机的 H264/H265 RTSP 连接。", "sub_rtsp_url": "子 RTSP 网址(用于直播)", "sub_rtsp_h264": "与低分辨率相机的辅助 RTSP 连接。", "onvif": "ONVIF", From 775c1b7051f3c6a21621a13bc7b3e8563551170c Mon Sep 17 00:00:00 2001 From: Cedric Verstraeten Date: Mon, 1 Jan 2024 19:36:14 +0100 Subject: [PATCH 81/81] show correct error message for failing onvif --- ui/src/actions/agent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/actions/agent.js b/ui/src/actions/agent.js index 4cac6b72..d933e62a 100644 --- a/ui/src/actions/agent.js +++ b/ui/src/actions/agent.js @@ -53,9 +53,9 @@ export const verifyOnvif = (config, onSuccess, onError) => { } }, (error) => { - const { message } = error; + const { data } = error; if (onError) { - onError(message); + onError(data); } } );