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 | 572 ++++++++++++++++----------- 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, 1345 insertions(+), 876 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 5bd543e..4756d34 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 d40a558..2496b75 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 0000000..da6c7ca --- /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 0000000..45573e2 --- /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 0000000..590a30e --- /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 ca4372b..46b93bd 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 2b2c794..265d975 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,240 +115,362 @@ 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) - - // 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) + // 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)) } - log.Log.Debug("RunAgent: finished") + // TODO add the substream + another check if the resolution changed. - // Clean up, force garbage collection - runtime.GC() + // 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 + + 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) + + // Handle the camera stream + //go capture.HandleStream(infile, queue, communication) + go rtspClient.Start(context.Background(), queue, communication) + + // 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) + } + + // 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 + } + + 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/packets/buf.go b/machinery/src/packets/buf.go new file mode 100644 index 0000000..fdb0748 --- /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 0000000..9412897 --- /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 0000000..a0de723 --- /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 0000000..75b06f3 --- /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 0000000..eb13b46 --- /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 130d0a9..0000000 --- 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 1093487..0000000 --- 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 21c8e20..0000000 --- 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 4d1bc03..0000000 --- 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 67ee4dc..3f6830b 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 da6c7ca..712d7f5 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 9412897..e056ccb 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 3f6830b..d36e8df 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 712d7f5..13f7517 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 } - isKeyFrame := h264.IDRPresent(au) + // 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) + 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} | 308 +- 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, 6859 insertions(+), 163 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 4756d34..1fd45f4 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 2496b75..81f19b4 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 13f7517..a127b63 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 - DecoderMutex *sync.Mutex - Decoder *rtph264.Decoder - FrameDecoder *h264Decoder + 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 + + 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 + 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, + }) - 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 { - panic(err) - } - g.Decoder = rtpDec + // 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 31de56d..b28c428 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 265d975..7070aa2 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 0000000..e192f17 --- /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 0000000..1a6efb3 --- /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 0000000..e039d8e --- /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 0000000..432ff1d --- /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 0000000..8b9a217 --- /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 0000000..e9eb513 --- /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 e056ccb..c2e5f4e 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 0000000..4a09f0a --- /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 0000000..c4957c9 --- /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 0000000..ec7eedf --- /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 0000000..b40978b --- /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 0000000..3d7660d --- /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 0000000..87f024b --- /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 0000000..30d5e92 --- /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 0000000..99ebb55 --- /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 0000000..2e709f9 --- /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 a127b63..f2d5f59 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 8108d18..f08d09a 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 b28c428..0b0e0cd 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 7070aa2..707babf 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 8b9a217..0474141 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 f2d5f59..3255f06 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 0b0e0cd..7c18802 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 0000000..e192f17 --- /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 0000000..1a6efb3 --- /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 0000000..e039d8e --- /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 0000000..432ff1d --- /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 0000000..ed53b9e --- /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 0000000..52f57b8 --- /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 0474141..ce007b2 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 1fd45f4..3c6fc3e 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 81f19b4..3b38c2c 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 3255f06..4968667 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 7c18802..6654f38 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 4968667..5c6c5a7 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 45573e2..9420d12 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 6654f38..77561ee 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 707babf..6f85aca 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 e192f17..0000000 --- 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 1a6efb3..0000000 --- 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 e039d8e..0000000 --- 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 432ff1d..0000000 --- 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 ed53b9e..0000000 --- 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 52f57b8..0000000 --- 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 e192f17..0000000 --- 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 1a6efb3..0000000 --- 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 e039d8e..0000000 --- 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 432ff1d..0000000 --- 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 ce007b2..0000000 --- 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 e9eb513..0000000 --- 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 4a09f0a..0000000 --- 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 c4957c9..0000000 --- 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 ec7eedf..0000000 --- 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 b40978b..0000000 --- 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 3d7660d..0000000 --- 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 87f024b..0000000 --- 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 30d5e92..0000000 --- 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 99ebb55..0000000 --- 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 2e709f9..0000000 --- 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 f08d09a..cee77d0 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 77561ee..e12056f 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 6f85aca..7d23ead 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 664a8a1..8eff4bb 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 c9723b8..e5e46db 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 ca8d6d0..9d847ba 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 eb88d39..3f98456 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 9420d12..8b8f6e2 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 e12056f..7489437 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 7d23ead..bac3591 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 b815946..01ffbd6 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 31b0772..c01531d 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 509dc1b..5052454 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 9d847ba..675842b 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 5c6c5a7..8648cf7 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 8b8f6e2..9420d12 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 7489437..7b566cd 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 8648cf7..3cc4d49 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 7b566cd..7489437 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 3cc4d49..93b246d 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 93b246d..0b1ec2b 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 d36e8df..df71b4e 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 | 55 ++++++++++++++++------------------- machinery/src/capture/main.go | 12 ++++---- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/machinery/src/capture/Joy4.go b/machinery/src/capture/Joy4.go index 9420d12..ea64cf8 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. - 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 := packets.Packet{ + IsKeyFrame: avpkt.IsKeyFrame, + Idx: int8(avpkt.Idx), + CompositionTime: avpkt.CompositionTime, + Time: avpkt.Time, + Data: avpkt.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, - } + 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) + 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 7489437..7b566cd 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 0b1ec2b..c45a61e 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 ea64cf8..59aef5b 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 590a30e..d3d31a9 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 7b566cd..e12056f 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 c01531d..f5bfb0d 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 675842b..9e65bee 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 c45a61e..849eb5c 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 bac3591..1fa9cae 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 849eb5c..c2f7c54 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 cee77d0..0000000 --- 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 e12056f..28ee6b8 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 1fa9cae..c3a2ee2 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 8eff4bb..5ab042d 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 c3a2ee2..81b7076 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 28ee6b8..ec2bb98 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 81b7076..b4d23f2 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 c2f7c54..d46b4e7 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 75b06f3..277f49d 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 b4d23f2..08787e7 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 5ab042d..87fb4f6 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 107582b..e8a004a 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 01ffbd6..67b9d7e 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 d46b4e7..b061c25 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 277f49d..d3f463b 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 | 56 ++++++++++++++++++---------- machinery/src/components/Kerberos.go | 10 +++-- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index b061c25..fdbd34d 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 d3d31a9..f135e97 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 4e24d2f..5d693a9 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") - - 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) +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) } + + 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 08787e7..39a6dec 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 fdbd34d..44a2759 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 44a2759..b2b1627 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 ec2bb98..53c63de 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) + + // 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 c2e5f4e..62390ad 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 b2b1627..2eb4e07 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 39a6dec..8f4f620 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 d3f463b..0b48acb 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 df71b4e..1e33951 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 8f4f620..4e772bc 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 4e772bc..e7af376 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 e7af376..12db631 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 12db631..2e30f97 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 e8a004a..f077602 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 9e65bee..f70d588 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 2eb4e07..b4c1269 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 f135e97..eb8f444 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 53c63de..e7d8bec 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 2e30f97..d4f7b2b 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 b4c1269..f5e8f63 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 e7d8bec..4b9e7ea 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 3c6fc3e..d9b32a4 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 3b38c2c..ec40119 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 f5e8f63..e5417f1 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 59aef5b..0000000 --- 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 e5417f1..84bc980 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 4b9e7ea..ac604c5 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 ac604c5..ae2f15f 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 84bc980..6d0d5f6 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 8bb517e..ce64f81 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 | 56 +++++++++++----------------- 4 files changed, 71 insertions(+), 43 deletions(-) diff --git a/machinery/src/capture/Gortsplib.go b/machinery/src/capture/Gortsplib.go index 6d0d5f6..31b34a7 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 eb8f444..1c94f54 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 d4f7b2b..bf759ec 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 87fb4f6..5ff32d6 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 - // 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) - } + 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 31b34a7..2295093 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 ae2f15f..6971c22 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 46b93bd..c5fb04f 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 fab0417..9e2f994 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 err != nil { + log.Log.Error("ContinuousPanTiltZoom: " + err.Error()) } - if errStop == nil { - return err - } else { - return errStop - } + 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 9e2f994..bced5b5 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 2295093..e9e21e9 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 c5fb04f..2a49bfe 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 bf759ec..927160e 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 bced5b5..22af0f9 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 e9e21e9..0d59cc3 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 d9b32a4..229b83c 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 ec40119..24e4cc6 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 0d59cc3..c3af421 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 c3af421..d1e25d0 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 62f86fb..a871388 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 af723ec..16d5786 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 229b83c..0b86e30 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 24e4cc6..8cd94df 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 d1e25d0..2a9ed8a 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 | 88 +++++++++++-------------- 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, 67 insertions(+), 62 deletions(-) diff --git a/machinery/src/capture/main.go b/machinery/src/capture/main.go index 6971c22..0f1c800 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, - } -} - -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 + // We'll pick the right client and decoder. + rtspClient := captureDevice.RTSPSubClient + if rtspClient != nil { + queue = communication.SubQueue + cursor = queue.Latest() } else { - return 0, errors.New("unsupport SeekEnd") + 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 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 + } + } + + return encodedImage } func convertPTS(v time.Duration) uint64 { diff --git a/machinery/src/cloud/Cloud.go b/machinery/src/cloud/Cloud.go index 2a49bfe..cc101b4 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 5ff32d6..d54742f 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 67b9d7e..8d3c7a0 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 f70d588..22aefdb 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 045d0d0..2708a42 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 18ec24c..b5191f1 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 796543b..144eefd 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 2d50c8d..d195b44 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 927160e..2f81e26 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 8ca54af..435c684 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 8d3c7a0..0a94e76 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 | 187 ++++----------------------- 7 files changed, 493 insertions(+), 173 deletions(-) diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index b5191f1..0f85db4 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 144eefd..5ca0444 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 d195b44..f9ffdae 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 cc101b4..0d40dac 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 2f81e26..56723bf 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 22af0f9..2c381a1 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 0a94e76..41f74c3 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,133 +35,44 @@ 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(), - }) - } + 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) + }) + + // 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) }) @@ -205,21 +85,6 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect components.MakeRecording(c, communication) }) - api.POST("/onvif/verify", func(c *gin.Context) { - onvif.VerifyOnvifConnection(c) - }) - - api.POST("/hub/verify", func(c *gin.Context) { - cloud.VerifyHub(c) - }) - - api.POST("/persistence/verify", func(c *gin.Context) { - cloud.VerifyPersistence(c, configDirectory) - }) - - // Camera specific methods. Doesn't require any authorization. - // These are available for anyone, but require the agent, to reach - // the camera. 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 | 86 +++++++------------ 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, 127 insertions(+), 220 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 3f98456..d450533 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 778f64e..0000000 --- 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 0f1c800..0c0bef7 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 56723bf..57375f7 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 9e8652f..0000000 --- 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 435c684..0000000 --- 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 d54742f..e24dfa5 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 detectMotion { - // 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(), + // 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 0000000..e45a3dc --- /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 0000000..a53baa6 --- /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 2c381a1..cc19bb7 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 2a9ed8a..d101c30 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 0c0bef7..970009d 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 970009d..9d0a1ae 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 e24dfa5..8f14f46 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 0000000..e2bf2f6 --- /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 e45a3dc..1c90076 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 a53baa6..934b6e2 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 0f85db4..4da1670 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 5ca0444..cc96c14 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 f9ffdae..6033d7c 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 0b86e30..1058532 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 8cd94df..e7dd229 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 cc19bb7..c790a38 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 fa0aaa7..f0faf81 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 41f74c3..3d47bbf 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 b20ac8b..019adf9 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 4da1670..cbbb6e2 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 cc96c14..c8327de 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 6033d7c..8dffd0c 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 d450533..4339c99 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 3d4e8a1..9c1f995 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 11c562e..fd3f274 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 0000000..a8a24ae --- /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 c790a38..68eda3f 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 0000000..a025423 --- /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 0000000..63aceb3 --- /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 0000000..e4f4c3d --- /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 0000000..135daba --- /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 0000000..04c8a0e --- /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 62390ad..d385bbc 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 f0faf81..f438390 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 cbbb6e2..a1cfe72 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 c8327de..a110b57 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 8dffd0c..a8a4621 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 1058532..28538df 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 e7dd229..013a6ee 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 d101c30..eb80af3 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 0d40dac..b00132c 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 68eda3f..43eeb31 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 GetTokenFromProfile(device *onvif.Device, profileId int) (xsd.ReferenceToken, error) { +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 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 f438390..99dde8f 100644 --- a/machinery/src/routers/http/methods.go +++ b/machinery/src/routers/http/methods.go @@ -361,23 +361,23 @@ 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, }, }, }, @@ -386,7 +386,136 @@ func DoGetDigitalInputs(c *gin.Context) { cameraConfiguration := configuration.Config.Capture.IPCamera device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) if err == nil { - outputs, err := onvif.GetRelayOutputs(device) + 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, + }, + }, + }, + } + + cameraConfiguration := configuration.Config.Capture.IPCamera + device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration) + if err == nil { + 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 3d47bbf..0ce02c9 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 28538df..331c1d9 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 331c1d9..97a339b 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 013a6ee..9afabb5 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 43eeb31..cb19e14 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 | 398 +++++++++++++++----------- machinery/src/components/Kerberos.go | 4 +- machinery/src/log/main.go | 34 ++- machinery/src/models/MQTT.go | 10 +- machinery/src/onvif/main.go | 297 +++++++++++-------- machinery/src/routers/http/methods.go | 55 ++-- machinery/src/routers/mqtt/main.go | 64 ++--- machinery/src/webrtc/main.go | 60 ++-- 10 files changed, 547 insertions(+), 391 deletions(-) diff --git a/machinery/main.go b/machinery/main.go index 4339c99..48bb9e2 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 eb80af3..d9db09f 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 b00132c..298cf9b 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 { - // Generate a random name. - timestamp := time.Now().Unix() - fileName := strconv.FormatInt(timestamp, 10) + - "_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4" + if provider != "" || directory != "" { - // 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() + // 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() + + 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}, + } + client = &http.Client{Transport: tr} + } else { + client = &http.Client{} + } + + 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: 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: 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}, - } - 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.", - }) - } - } - } - } else { - c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Upload of fake recording failed: " + err.Error(), - }) - } - } else { - c.JSON(400, models.APIResponse{ - Data: "VerifyPersistence: Something went wrong while creating /storage POST request." + err.Error(), - }) - } } 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 57375f7..8156da0 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 fd3f274..cd909d1 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 a8aff43..2504351 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 cb19e14..6de7001 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() + 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) - stringBody := string(bs) - decodedXML, et, err := getXMLNode(stringBody, "PullMessagesResponse") - if err != nil { - log.Log.Error("onvif.main.GetEventMessages(pullMessages): " + err.Error()) + // 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" + } + + // 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 { - 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) - } + 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) - // 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, - }) - } - } + // 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 99dde8f..e76de2b 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 60e4e29..4e422d8 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 1e33951..db0f7fb 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 019adf9..58c945d 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 48bb9e2..d49e4c8 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 6de7001..f76cd0b 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 22aefdb..dcd4bf5 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 298cf9b..d2ff0ac 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 2504351..a58215a 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 f76cd0b..3c215ac 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 e76de2b..5b129aa 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 4e422d8..f7311b0 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 5b129aa..49daf3a 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 d2ff0ac..995538b 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 3c215ac..91804aa 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 a1cfe72..da8980b 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 a110b57..d179660 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 a8a4621..7d5f488 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 9d0a1ae..38a4ad3 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 995538b..c4e1fca 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 8156da0..7a6f477 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 91804aa..514769b 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 49daf3a..acd1ac4 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 0ce02c9..176cdd8 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 dcd4bf5..1db4382 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 da8980b..e1d6ff1 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 d179660..a4e7d7f 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 7d5f488..6b5fa6b 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 c4e1fca..c9b489e 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 514769b..ac7c945 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 acd1ac4..0e31195 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 f7311b0..e2458f6 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 58c945d..55abeb9 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 55abeb9..5e3a743 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 5e3a743..8016dd4 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 2dc5350..1b80025 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 1887f3d..2f18629 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 ce64f81..b73b2b4 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 a58215a..1594001 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 e2458f6..32b9ce3 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 c9b489e..8b855b9 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 8b855b9..a94fe67 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 a94fe67..1aa4de7 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 1aa4de7..4c761c3 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 1c90076..5a0b49c 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 38a4ad3..758e83f 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 4b154b4..fc7b1cb 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 13c6f37..9719153 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 eb492d6..4d6404c 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 8070c6c..4df6d40 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 8452f5d..964c5d7 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 6e04d1c..6b2664e 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 8307d55..c59b2c5 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 3f89b2e..595617b 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 5690c14..8bdd703 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 7b4e004..f7a9bd3 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 3b2f3b1..66d55cd 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 8856be7..fb1006c 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 4cac6b7..d933e62 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); } } );