five
This commit is contained in:
parent
7eec0f88e4
commit
403dfc5092
5 changed files with 179 additions and 262 deletions
38
actioncam.go
38
actioncam.go
|
|
@ -15,10 +15,10 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
|
||||
"github.com/jonas-koeritz/actioncam/libipcamera"
|
||||
"github.com/jonas-koeritz/actioncam/rtsp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -255,39 +255,13 @@ func main() {
|
|||
Short: "Start an RTSP-Server serving the cameras preview.",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var port_string = os.Getenv("PORT");
|
||||
|
||||
if(port_string == "") {
|
||||
port_string = "8554"
|
||||
}
|
||||
|
||||
port, erra := strconv.Atoi(port_string);
|
||||
if erra != nil {
|
||||
log.Printf("ERROR: %s\n", erra)
|
||||
return
|
||||
}
|
||||
|
||||
var host = os.Getenv("HOST");
|
||||
if(host == "") {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
relay := libipcamera.CreateRTPRelay(applicationContext)
|
||||
rtspServer, err := rtsp.CreateServer(applicationContext, host, port, relay)
|
||||
if rtspServer != nil {
|
||||
defer rtspServer.Close()
|
||||
}
|
||||
camera.StartPreviewStream()
|
||||
|
||||
|
||||
|
||||
if err := camera.StartPreviewStream(); err != nil {
|
||||
log.Printf("ERROR preview: %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("Created RTSP Server\n")
|
||||
|
||||
if err != nil {
|
||||
log.Printf("ERROR starting RTSP Server: %s\n", err)
|
||||
}
|
||||
server := rtsp.CreateServer(applicationContext, "0.0.0.0", 8554, relay)
|
||||
log.Println("Press Ctrl+C to exit")
|
||||
<-applicationContext.Done()
|
||||
server.Stop()
|
||||
},
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
|
|
|
|||
21
go.mod
21
go.mod
|
|
@ -1,27 +1,28 @@
|
|||
module github.com/jonas-koeritz/actioncam
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
github.com/bluenviron/gortsplib/v4 v4.16.2 // or current v4
|
||||
github.com/icza/bitio v1.0.0
|
||||
github.com/pion/rtp v1.8.21 // or any recent v1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bluenviron/mediacommon/v2 v2.4.1 // indirect
|
||||
github.com/bluenviron/gortsplib/v5 v5.2.0 // indirect
|
||||
github.com/bluenviron/mediacommon/v2 v2.5.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/pion/logging v0.2.3 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.15 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.15 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.6 // indirect
|
||||
github.com/pion/rtcp v1.2.16 // indirect
|
||||
github.com/pion/rtp v1.8.25 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.16 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.8 // indirect
|
||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
)
|
||||
|
|
|
|||
44
go.sum
44
go.sum
|
|
@ -23,10 +23,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/bluenviron/gortsplib/v4 v4.16.2 h1:10HaMsorjW13gscLp3R7Oj41ck2i1EHIUYCNWD2wpkI=
|
||||
github.com/bluenviron/gortsplib/v4 v4.16.2/go.mod h1:Vm07yUMys9XKnuZJLfTT8zluAN2n9ZOtz40Xb8RKh+8=
|
||||
github.com/bluenviron/mediacommon/v2 v2.4.1 h1:PsKrO/c7hDjXxiOGRUBsYtMGNb4lKWIFea6zcOchoVs=
|
||||
github.com/bluenviron/mediacommon/v2 v2.4.1/go.mod h1:a6MbPmXtYda9mKibKVMZlW20GYLLrX2R7ZkUE+1pwV0=
|
||||
github.com/bluenviron/gortsplib/v5 v5.2.0 h1:yk0H9Z1Z+H41/x5hDt84rKm6+MNA483NsRXPYe+or/A=
|
||||
github.com/bluenviron/gortsplib/v5 v5.2.0/go.mod h1:UYCbHEb0T49kBDgIlTJaZOchD2f5g1JigFmmxQfW7vY=
|
||||
github.com/bluenviron/mediacommon/v2 v2.5.1 h1:qB2fb5c0xyl5OB2gfSfulpEJn7Cdm3vI2n8wjiLMxKI=
|
||||
github.com/bluenviron/mediacommon/v2 v2.5.1/go.mod h1:zy1fODPuS/kBd93ftgJS1Jhvjq7LFWfAo32KP7By9AE=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
|
|
@ -36,7 +36,6 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
|
|||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
|
|
@ -72,6 +71,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
|
|
@ -132,23 +133,22 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
|||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
||||
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/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.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||
github.com/pion/rtp v1.8.21 h1:3yrOwmZFyUpcIosNcWRpQaU+UXIJ6yxLuJ8Bx0mw37Y=
|
||||
github.com/pion/rtp v1.8.21/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||
github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk=
|
||||
github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
|
||||
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
|
||||
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
||||
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
||||
github.com/pion/rtp v1.8.25 h1:b8+y44GNbwOJTYWuVan7SglX/hMlicVCAtL50ztyZHw=
|
||||
github.com/pion/rtp v1.8.25/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
|
||||
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
||||
github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM=
|
||||
github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg=
|
||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
|
|
@ -184,8 +184,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
|
|
@ -232,8 +230,8 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
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=
|
||||
|
|
@ -255,8 +253,8 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
|
@ -311,8 +309,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
|
|||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
|||
|
|
@ -4,136 +4,101 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Frame is a decoded RTP packet buffer forwarded to the RTSP server
|
||||
type Frame struct {
|
||||
Payload []byte
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// RTPRelay holds information on the relaying stream listener
|
||||
type RTPRelay struct {
|
||||
close bool
|
||||
listener net.PacketConn
|
||||
context context.Context
|
||||
Output chan Frame // 🚨 NEW: channel to push decoded RTP frames
|
||||
Context context.Context
|
||||
Frames chan Frame
|
||||
}
|
||||
|
||||
var closeFlag bool
|
||||
|
||||
// CreateRTPRelay creates a UDP listener that handles live data
|
||||
// and pushes RTP-ready payloads into Output channel.
|
||||
func CreateRTPRelay(ctx context.Context) *RTPRelay {
|
||||
conn, err := net.ListenPacket("udp", ":6669")
|
||||
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %s\n", err)
|
||||
}
|
||||
|
||||
closeFlag = false
|
||||
relay := RTPRelay{
|
||||
relay := &RTPRelay{
|
||||
close: false,
|
||||
listener: conn,
|
||||
context: ctx,
|
||||
Output: make(chan Frame, 100), // buffered for smoother streaming
|
||||
Context: ctx,
|
||||
Frames: make(chan Frame, 30),
|
||||
}
|
||||
|
||||
go handleCameraStream(relay, conn)
|
||||
go relay.readLoop()
|
||||
|
||||
return &relay
|
||||
return relay
|
||||
}
|
||||
|
||||
func handleCameraStream(relay RTPRelay, conn net.PacketConn) {
|
||||
buffer := make([]byte, 2048)
|
||||
packetReader := bytes.NewReader(buffer)
|
||||
|
||||
func (r *RTPRelay) readLoop() {
|
||||
buf := make([]byte, 2048)
|
||||
packetReader := bytes.NewReader(buf)
|
||||
header := streamHeader{}
|
||||
var payload []byte
|
||||
|
||||
var sequenceNumber uint16
|
||||
var elapsed uint32
|
||||
|
||||
frameBuffer := bytes.Buffer{}
|
||||
packetBuffer := bytes.Buffer{}
|
||||
|
||||
T:
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
r.listener.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
select {
|
||||
case <-relay.context.Done():
|
||||
log.Println("Context Done")
|
||||
relay.listener.Close()
|
||||
close(relay.Output)
|
||||
break T
|
||||
case <-r.Context.Done():
|
||||
close(r.Frames)
|
||||
return
|
||||
default:
|
||||
if closeFlag {
|
||||
relay.listener.Close()
|
||||
close(relay.Output)
|
||||
break T
|
||||
}
|
||||
|
||||
conn.ReadFrom(buffer)
|
||||
packetReader.Reset(buffer)
|
||||
_, _, err := r.listener.ReadFrom(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
packetReader.Reset(buf)
|
||||
|
||||
binary.Read(packetReader, binary.BigEndian, &header)
|
||||
|
||||
if header.Magic != 0xBCDE {
|
||||
log.Printf("Received message with invalid magic (%x).", header.Magic)
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
if header.Length > 0 {
|
||||
payload = make([]byte, header.Length)
|
||||
_, err := io.ReadFull(packetReader, payload)
|
||||
if err != nil {
|
||||
log.Printf("Read Error: %s\n", err)
|
||||
break
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
payload = []byte{}
|
||||
}
|
||||
|
||||
switch header.MessageType {
|
||||
case 0x0001: // H.264 Data
|
||||
case 0x0001:
|
||||
frameBuffer.Write(payload)
|
||||
|
||||
case 0x0002: // Time
|
||||
// Append full frame
|
||||
packetBuffer.Write(frameBuffer.Bytes())
|
||||
|
||||
// Push to RTSP server (in-memory)
|
||||
relay.Output <- Frame{Payload: append([]byte{}, packetBuffer.Bytes()...)}
|
||||
|
||||
// Reset the next packet
|
||||
packetBuffer.Reset()
|
||||
packetBuffer.Write([]byte{0x80, 0x63})
|
||||
binary.Write(&packetBuffer, binary.BigEndian, sequenceNumber+1)
|
||||
binary.Write(&packetBuffer, binary.BigEndian, (uint32)(elapsed)*90)
|
||||
binary.Write(&packetBuffer, binary.BigEndian, (uint64(0)))
|
||||
|
||||
case 0x0002:
|
||||
// Emit a full H264 frame
|
||||
if frameBuffer.Len() > 0 {
|
||||
cp := append([]byte{}, frameBuffer.Bytes()...)
|
||||
r.Frames <- Frame{Data: cp}
|
||||
frameBuffer.Reset()
|
||||
sequenceNumber++
|
||||
elapsed = binary.LittleEndian.Uint32(payload[12:])
|
||||
|
||||
default:
|
||||
log.Printf("Received Unknown Message: %+v\n", header)
|
||||
log.Printf("Payload:\n%s\n", hex.Dump(payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(relay.Output)
|
||||
}
|
||||
|
||||
// Stop stops listening for packets
|
||||
func (r *RTPRelay) Stop() {
|
||||
closeFlag = true
|
||||
r.close = true
|
||||
r.listener.Close()
|
||||
close(r.Frames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,140 +2,121 @@ package rtsp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"strconv"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v5"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
"github.com/pion/rtp"
|
||||
"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/jonas-koeritz/actioncam/libipcamera"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
type Handler struct {
|
||||
server *gortsplib.Server
|
||||
stream *gortsplib.ServerStream
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (h *Handler) OnDescribe(_ *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return &base.Response{StatusCode: base.StatusOK}, h.stream, nil
|
||||
}
|
||||
|
||||
func (h *Handler) OnSetup(_ *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return &base.Response{StatusCode: base.StatusOK}, h.stream, nil
|
||||
}
|
||||
|
||||
func (h *Handler) OnPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||
return &base.Response{StatusCode: base.StatusOK}, nil
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
server *gortsplib.Server
|
||||
stream *gortsplib.ServerStream
|
||||
media *description.Media
|
||||
gs *gortsplib.Server
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func CreateServer(parentCtx context.Context, host string, port int, relay *libipcamera.RTPRelay) (*Server, error) {
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
func CreateServer(ctx context.Context, host string, port int, relay *libipcamera.RTPRelay) *Server {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
// Create the RTSP server instance first
|
||||
h := &handler{}
|
||||
gs := &gortsplib.Server{
|
||||
h := &Handler{}
|
||||
srv := &Server{cancel: cancel}
|
||||
|
||||
h.server = &gortsplib.Server{
|
||||
Handler: h,
|
||||
RTSPAddress: host + ":" + strconv.Itoa(port),
|
||||
UDPRTPAddress: ":8000",
|
||||
UDPRTCPAddress: ":8001",
|
||||
RTSPAddress: fmt.Sprintf("%s:%d", host, port),
|
||||
}
|
||||
|
||||
// Now create the H264 stream bound to this server
|
||||
stream, media, err := newH264Stream(gs)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("create H264 stream: %w", err)
|
||||
if err := h.server.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Final server struct assembly
|
||||
srv := &Server{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
gs: gs,
|
||||
stream: stream,
|
||||
media: media,
|
||||
h.mu.Lock()
|
||||
desc := &description.Session{
|
||||
Medias: []*description.Media{{
|
||||
Type: description.MediaTypeVideo,
|
||||
Formats: []format.Format{&format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}},
|
||||
}},
|
||||
}
|
||||
h.srv = srv // link handler to server instance
|
||||
h.stream = &gortsplib.ServerStream{Server: h.server, Desc: desc}
|
||||
if err := h.stream.Initialize(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv.server = h.server
|
||||
srv.stream = h.stream
|
||||
h.mu.Unlock()
|
||||
|
||||
// Start pumping RTP frames from relay into RTSP
|
||||
// Pump frames -> RTP
|
||||
srv.wg.Add(1)
|
||||
go srv.relayPump(relay)
|
||||
|
||||
// Start the RTSP server
|
||||
go func() {
|
||||
log.Printf("RTSP server ready on rtsp://%s:%d/", host, port)
|
||||
if err := gs.StartAndWait(); err != nil {
|
||||
log.Printf("RTSP stopped: %v", err)
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func (s *Server) Close() {
|
||||
s.cancel()
|
||||
if s.gs != nil {
|
||||
s.gs.Close()
|
||||
}
|
||||
s.wg.Wait()
|
||||
if s.stream != nil {
|
||||
s.stream.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Relay pump: consumes RTP from channel
|
||||
func (s *Server) relayPump(relay *libipcamera.RTPRelay) {
|
||||
defer s.wg.Done()
|
||||
defer srv.wg.Done()
|
||||
var seq uint16
|
||||
var ts uint32
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
case <-cctx.Done():
|
||||
return
|
||||
case frame, ok := <-relay.Output:
|
||||
case frame, ok := <-relay.Frames:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// Build RTP packet
|
||||
var pkt rtp.Packet
|
||||
if err := pkt.Unmarshal(frame.Payload); err != nil {
|
||||
continue
|
||||
pkt := &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: seq,
|
||||
Timestamp: ts,
|
||||
},
|
||||
Payload: frame.Data,
|
||||
}
|
||||
s.stream.WritePacketRTP(s.media, &pkt)
|
||||
seq++
|
||||
ts += 3600
|
||||
h.stream.WritePacketRTP(h.stream.Desc.Medias[0], pkt)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("RTSP server ready at rtsp://%s:%d/", host, port)
|
||||
return srv
|
||||
}
|
||||
|
||||
// Handler below…
|
||||
type handler struct{ srv *Server }
|
||||
|
||||
func (h *handler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
return &base.Response{StatusCode: base.StatusOK}, h.srv.stream, nil
|
||||
}
|
||||
|
||||
func (h *handler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
return &base.Response{StatusCode: base.StatusOK}, h.srv.stream, nil
|
||||
}
|
||||
|
||||
func (h *handler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||
return &base.Response{StatusCode: base.StatusOK}, nil
|
||||
}
|
||||
|
||||
// Build simple baseline H264 SDP
|
||||
func newH264Stream(gs *gortsplib.Server) (*gortsplib.ServerStream, *description.Media, error) {
|
||||
h264 := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
SPS: []byte{0x67, 0x42, 0xC0, 0x1F, 0x96, 0x54, 0x05, 0x01, 0xED, 0x00, 0xF0, 0x88, 0x45, 0x80},
|
||||
PPS: []byte{0x68, 0xCE, 0x38, 0x80},
|
||||
}
|
||||
|
||||
media := &description.Media{
|
||||
Type: description.MediaTypeVideo,
|
||||
Formats: []format.Format{h264},
|
||||
}
|
||||
|
||||
desc := &description.Session{
|
||||
Medias: []*description.Media{media},
|
||||
}
|
||||
|
||||
// REQUIRED: link stream to the running server
|
||||
stream := gortsplib.NewServerStream(gs, desc)
|
||||
|
||||
return stream, media, nil
|
||||
func (s *Server) Stop() {
|
||||
s.cancel()
|
||||
s.server.Close()
|
||||
s.stream.Close()
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue