diff --git a/cmd/homekit/api.go b/cmd/homekit/api.go index a5b3743d..53279b1a 100644 --- a/cmd/homekit/api.go +++ b/cmd/homekit/api.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/AlexxIT/go2rtc/cmd/app/store" "github.com/AlexxIT/go2rtc/cmd/streams" - "github.com/AlexxIT/go2rtc/pkg/homekit" - "github.com/AlexxIT/go2rtc/pkg/homekit/mdns" + "github.com/AlexxIT/go2rtc/pkg/hap" + "github.com/AlexxIT/go2rtc/pkg/hap/mdns" "net/http" "net/url" "strings" @@ -61,80 +61,75 @@ func apiHandler(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") pin := r.URL.Query().Get("pin") - - conn, err := homekit.Pair(id, pin) - if err != nil { - // log error - log.Error().Err(err).Caller().Send() - // response error - _, err = w.Write([]byte(err.Error())) - return - } - name := r.URL.Query().Get("name") - dict := store.GetDict("streams") - dict[name] = conn.URL() - if err = store.Set("streams", dict); err != nil { - // log error + if err := hkPair(id, pin, name); err != nil { log.Error().Err(err).Caller().Send() - // response error _, err = w.Write([]byte(err.Error())) } - streams.New(name, conn.URL()) - case "DELETE": src := r.URL.Query().Get("src") - dict := store.GetDict("streams") - for name, rawURL := range dict { - if name != src { - continue - } - - conn, err := homekit.Dial(rawURL.(string)) - if err != nil { - // log error - log.Error().Err(err).Caller().Send() - // response error - _, err = w.Write([]byte(err.Error())) - return - } - - go func() { - if err = conn.Handle(); err != nil { - log.Warn().Err(err).Caller().Send() - } - }() - - if err = conn.ListPairings(); err != nil { - // log error - log.Error().Err(err).Caller().Send() - // response error - _, err = w.Write([]byte(err.Error())) - return - } - - if err = conn.DeletePairing(conn.ClientID); err != nil { - // log error - log.Error().Err(err).Caller().Send() - // response error - _, err = w.Write([]byte(err.Error())) - } - - delete(dict, name) - - if err = store.Set("streams", dict); err != nil { - // log error - log.Error().Err(err).Msg("[api.homekit] store set") - // response error - _, err = w.Write([]byte(err.Error())) - } - - return + if err := hkDelete(src); err != nil { + log.Error().Err(err).Caller().Send() + _, err = w.Write([]byte(err.Error())) } } } +func hkPair(deviceID, pin, name string) (err error) { + var conn *hap.Conn + + if conn, err = hap.Pair(deviceID, pin); err != nil { + return + } + + streams.New(name, conn.URL()) + + dict := store.GetDict("streams") + dict[name] = conn.URL() + + return store.Set("streams", dict) +} + +func hkDelete(name string) (err error) { + dict := store.GetDict("streams") + for key, rawURL := range dict { + if key != name { + continue + } + + var conn *hap.Conn + + if conn, err = hap.NewConn(rawURL.(string)); err != nil { + return + } + + if err = conn.Dial(); err != nil { + return + } + + go func() { + if err = conn.Handle(); err != nil { + log.Warn().Err(err).Caller().Send() + } + }() + + if err = conn.ListPairings(); err != nil { + return + } + + if err = conn.DeletePairing(conn.ClientID); err != nil { + log.Error().Err(err).Caller().Send() + } + + delete(dict, name) + + return store.Set("streams", dict) + } + + return nil +} + type Device struct { ID string `json:"id"` Name string `json:"name"` diff --git a/cmd/homekit/homekit.go b/cmd/homekit/homekit.go index 2cceff55..e83fe429 100644 --- a/cmd/homekit/homekit.go +++ b/cmd/homekit/homekit.go @@ -3,6 +3,7 @@ package homekit import ( "github.com/AlexxIT/go2rtc/cmd/api" "github.com/AlexxIT/go2rtc/cmd/app" + "github.com/AlexxIT/go2rtc/cmd/srtp" "github.com/AlexxIT/go2rtc/cmd/streams" "github.com/AlexxIT/go2rtc/pkg/homekit" "github.com/AlexxIT/go2rtc/pkg/streamer" @@ -20,14 +21,12 @@ func Init() { var log zerolog.Logger func streamHandler(url string) (streamer.Producer, error) { - conn, err := homekit.Dial(url) + conn, err := homekit.NewClient(url, srtp.Server) if err != nil { return nil, err } - exit := make(chan error) - go func() { - //start goroutine for reading responses from camera - exit <- conn.Handle() - }() - return &Client{conn: conn, exit: exit}, nil + if err = conn.Dial();err!=nil{ + return nil, err + } + return conn, nil } diff --git a/cmd/srtp/srtp.go b/cmd/srtp/srtp.go index 8e19d4f4..7f251ee4 100644 --- a/cmd/srtp/srtp.go +++ b/cmd/srtp/srtp.go @@ -34,26 +34,15 @@ func Init() { log.Info().Str("addr", cfg.Mod.Listen).Msg("[srtp] listen") - _, Port, _ = net.SplitHostPort(cfg.Mod.Listen) - // run server go func() { - server = &srtp.Server{} - if err = server.Serve(conn); err != nil { + Server = &srtp.Server{} + if err = Server.Serve(conn); err != nil { log.Warn().Err(err).Msg("[srtp] serve") } }() } +var Server *srtp.Server + var log zerolog.Logger -var server *srtp.Server - -var Port string - -func AddSession(session *srtp.Session) { - server.AddSession(session) -} - -func RemoveSession(session *srtp.Session) { - server.RemoveSession(session) -} \ No newline at end of file diff --git a/go.mod b/go.mod index 06f5782a..f7d2f959 100644 --- a/go.mod +++ b/go.mod @@ -53,5 +53,7 @@ replace ( // windows support: https://github.com/brutella/dnssd/pull/35 github.com/brutella/dnssd v1.2.2 => github.com/rblenkinsopp/dnssd v1.2.3-0.20220516082132-0923f3c787a1 // RTP tlv8 fix - github.com/brutella/hap v0.0.17 => github.com/AlexxIT/hap v0.0.15-0.20220823033740-ce7d1564e657 + github.com/brutella/hap v0.0.17 => github.com/AlexxIT/hap v0.0.15-0.20221108133010-d8a45b7a7045 + // fix reading AAC config bytes + github.com/deepch/vdk v0.0.19 => github.com/AlexxIT/vdk v0.0.18-0.20221108193131-6168555b4f92 ) diff --git a/go.sum b/go.sum index 637290a7..8ec38e44 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -github.com/AlexxIT/hap v0.0.15-0.20220823033740-ce7d1564e657 h1:FUzXAJfm6sRLJ8T6vfzvy/Hm3aioX8+fbxgx2VZoI78= -github.com/AlexxIT/hap v0.0.15-0.20220823033740-ce7d1564e657/go.mod h1:c2vEL5pzjRWEx07sa32kTVjzI9bBVlstrwBwKe3DlJ0= +github.com/AlexxIT/hap v0.0.15-0.20221108133010-d8a45b7a7045 h1:xJf3FxQJReJSDyYXJfI1NUWv8tUEAGNV9xigLqNtmrI= +github.com/AlexxIT/hap v0.0.15-0.20221108133010-d8a45b7a7045/go.mod h1:QNA3sm16zE5uUyC8+E/gNkMvQWjqQLuxQKkU5PMi8N4= +github.com/AlexxIT/vdk v0.0.18-0.20221108193131-6168555b4f92 h1:cIeYMGaAirSZnrKRDTb5VgZDDYqPLhYiczElMg4sQW0= +github.com/AlexxIT/vdk v0.0.18-0.20221108193131-6168555b4f92/go.mod h1:7ydHfSkflMZxBXfWR79dMjrT54xzvLxnPaByOa9Jpzg= github.com/brutella/dnssd v1.2.3 h1:4fBLjZjPH7SbcHhEcIJhZcC9nOhIDZ0m3rn9bjl1/i0= github.com/brutella/dnssd v1.2.3/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= @@ -7,8 +9,6 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1 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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= @@ -245,6 +245,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD 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.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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/homekit/README.md b/pkg/hap/README.md similarity index 97% rename from pkg/homekit/README.md rename to pkg/hap/README.md index d0827519..94c4bfd8 100644 --- a/pkg/homekit/README.md +++ b/pkg/hap/README.md @@ -1,4 +1,4 @@ -# Homekit +# Home Accessory Protocol > PS. Character = Characteristic diff --git a/pkg/homekit/accessory.go b/pkg/hap/accessory.go similarity index 98% rename from pkg/homekit/accessory.go rename to pkg/hap/accessory.go index 1b14fc72..b86a4509 100644 --- a/pkg/homekit/accessory.go +++ b/pkg/hap/accessory.go @@ -1,4 +1,4 @@ -package homekit +package hap type Accessory struct { AID int `json:"aid"` diff --git a/pkg/homekit/camera/client.go b/pkg/hap/camera/client.go similarity index 81% rename from pkg/homekit/camera/client.go rename to pkg/hap/camera/client.go index 905c3e94..1bcc413d 100644 --- a/pkg/homekit/camera/client.go +++ b/pkg/hap/camera/client.go @@ -2,22 +2,22 @@ package camera import ( "errors" - "github.com/AlexxIT/go2rtc/pkg/homekit" + "github.com/AlexxIT/go2rtc/pkg/hap" "github.com/brutella/hap/characteristic" "github.com/brutella/hap/rtp" ) type Client struct { - client *homekit.Conn + client *hap.Conn } -func NewClient(client *homekit.Conn) *Client { +func NewClient(client *hap.Conn) *Client { return &Client{client: client} } -func (c *Client) StartStream2(ses *Session) (err error) { +func (c *Client) StartStream(ses *Session) (err error) { // Step 1. Check if camera ready (free) to stream - var srv *homekit.Service + var srv *hap.Service if srv, err = c.GetFreeStream(); err != nil { return err } @@ -35,8 +35,8 @@ func (c *Client) StartStream2(ses *Session) (err error) { // GetFreeStream search free streaming service. // Usual every HomeKit camera can stream only to two clients simultaniosly. // So it has two similar services for streaming. -func (c *Client) GetFreeStream() (srv *homekit.Service, err error) { - var accs []*homekit.Accessory +func (c *Client) GetFreeStream() (srv *hap.Service, err error) { + var accs []*hap.Accessory if accs, err = c.client.GetAccessories(); err != nil { return } @@ -60,7 +60,7 @@ func (c *Client) GetFreeStream() (srv *homekit.Service, err error) { } func (c *Client) SetupEndpoins( - srv *homekit.Service, req *rtp.SetupEndpoints, + srv *hap.Service, req *rtp.SetupEndpoints, ) (res *rtp.SetupEndpointsResponse, err error) { // get setup endpoint character ID char := srv.GetCharacter(characteristic.TypeSetupEndpoints) @@ -87,7 +87,7 @@ func (c *Client) SetupEndpoins( return } -func (c *Client) SetConfig(srv *homekit.Service, config *rtp.StreamConfiguration) (err error) { +func (c *Client) SetConfig(srv *hap.Service, config *rtp.StreamConfiguration) (err error) { // get setup endpoint character ID char := srv.GetCharacter(characteristic.TypeSelectedStreamConfiguration) char.Event = nil diff --git a/pkg/hap/camera/session.go b/pkg/hap/camera/session.go new file mode 100644 index 00000000..b046933b --- /dev/null +++ b/pkg/hap/camera/session.go @@ -0,0 +1,75 @@ +package camera + +import ( + cryptorand "crypto/rand" + "encoding/binary" + "github.com/brutella/hap/rtp" +) + +type Session struct { + Offer *rtp.SetupEndpoints + Answer *rtp.SetupEndpointsResponse + Config *rtp.StreamConfiguration +} + +func NewSession(vp *rtp.VideoParameters, ap *rtp.AudioParameters) *Session { + vp.RTP = rtp.RTPParams{ + PayloadType: 99, + Ssrc: RandomUint32(), + Bitrate: 2048, + Interval: 10, + MTU: 1200, // like WebRTC + } + ap.RTP = rtp.RTPParams{ + PayloadType: 110, + Ssrc: RandomUint32(), + Bitrate: 32, + Interval: 10, + ComfortNoisePayloadType: 98, + MTU: 0, + } + + sessionID := RandomBytes(16) + s := &Session{ + Offer: &rtp.SetupEndpoints{ + SessionId: sessionID, + Video: rtp.CryptoSuite{ + MasterKey: RandomBytes(16), + MasterSalt: RandomBytes(14), + }, + Audio: rtp.CryptoSuite{ + MasterKey: RandomBytes(16), + MasterSalt: RandomBytes(14), + }, + }, + Config: &rtp.StreamConfiguration{ + Command: rtp.SessionControlCommand{ + Identifier: sessionID, + Type: rtp.SessionControlCommandTypeStart, + }, + Video: *vp, + Audio: *ap, + }, + } + return s +} + +func (s *Session) SetLocalEndpoint(host string, port uint16) { + s.Offer.ControllerAddr = rtp.Addr{ + IPAddr: host, + VideoRtpPort: port, + AudioRtpPort: port, + } +} + +func RandomBytes(size int) []byte { + data := make([]byte, size) + _, _ = cryptorand.Read(data) + return data +} + +func RandomUint32() uint32 { + data := make([]byte, 4) + _, _ = cryptorand.Read(data) + return binary.BigEndian.Uint32(data) +} diff --git a/pkg/homekit/character.go b/pkg/hap/character.go similarity index 99% rename from pkg/homekit/character.go rename to pkg/hap/character.go index 0be4e14a..051fd046 100644 --- a/pkg/homekit/character.go +++ b/pkg/hap/character.go @@ -1,4 +1,4 @@ -package homekit +package hap import ( "bytes" diff --git a/pkg/homekit/conn.go b/pkg/hap/conn.go similarity index 96% rename from pkg/homekit/conn.go rename to pkg/hap/conn.go index a1417f57..f0607dd6 100644 --- a/pkg/homekit/conn.go +++ b/pkg/hap/conn.go @@ -1,4 +1,4 @@ -package homekit +package hap import ( "bufio" @@ -7,7 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "github.com/AlexxIT/go2rtc/pkg/homekit/mdns" + "github.com/AlexxIT/go2rtc/pkg/hap/mdns" "github.com/AlexxIT/go2rtc/pkg/streamer" "github.com/brutella/hap" "github.com/brutella/hap/chacha20poly1305" @@ -42,7 +42,7 @@ type Conn struct { httpResponse chan *bufio.Reader } -func Dial(rawURL string) (*Conn, error) { +func NewConn(rawURL string) (*Conn, error) { u, err := url.Parse(rawURL) if err != nil { return nil, err @@ -57,31 +57,9 @@ func Dial(rawURL string) (*Conn, error) { ClientPrivate: DecodeKey(query.Get("client_private")), } - if err = c.Dial(); err != nil { - return nil, err - } - return c, nil } -//func NewConn(rawURL string) (*Conn, error) { -// u, err := url.Parse(rawURL) -// if err != nil { -// return nil, err -// } -// -// query := u.Query() -// c := &Conn{ -// DeviceAddress: u.Host, -// DeviceID: query.Get("device_id"), -// DevicePublic: DecodeKey(query.Get("device_public")), -// ClientID: query.Get("client_id"), -// ClientPrivate: DecodeKey(query.Get("client_private")), -// } -// -// return c, nil -//} - func Pair(deviceID, pin string) (*Conn, error) { entry := mdns.GetEntry(deviceID) if entry == nil { diff --git a/pkg/homekit/helpers.go b/pkg/hap/helpers.go similarity index 84% rename from pkg/homekit/helpers.go rename to pkg/hap/helpers.go index 3ee616f6..95abf403 100644 --- a/pkg/homekit/helpers.go +++ b/pkg/hap/helpers.go @@ -1,4 +1,4 @@ -package homekit +package hap import ( "crypto/rand" @@ -29,13 +29,13 @@ func GenerateUUID() string { } type PairVerifyPayload struct { - Method byte `tlv8:"0"` - Identifier string `tlv8:"1"` - PublicKey []byte `tlv8:"3"` - EncryptedData []byte `tlv8:"5"` - State byte `tlv8:"6"` - Status byte `tlv8:"7"` - Signature []byte `tlv8:"10"` + Method byte `tlv8:"0,optional"` + Identifier string `tlv8:"1,optional"` + PublicKey []byte `tlv8:"3,optional"` + EncryptedData []byte `tlv8:"5,optional"` + State byte `tlv8:"6,optional"` + Status byte `tlv8:"7,optional"` + Signature []byte `tlv8:"10,optional"` } //func (c *Character) Unmarshal(value interface{}) error { diff --git a/pkg/homekit/http.go b/pkg/hap/http.go similarity index 99% rename from pkg/homekit/http.go rename to pkg/hap/http.go index 1297861b..4ef13d63 100644 --- a/pkg/homekit/http.go +++ b/pkg/hap/http.go @@ -1,4 +1,4 @@ -package homekit +package hap import ( "bufio" diff --git a/pkg/homekit/mdns/client.go b/pkg/hap/mdns/client.go similarity index 100% rename from pkg/homekit/mdns/client.go rename to pkg/hap/mdns/client.go diff --git a/pkg/homekit/mdns/server.go b/pkg/hap/mdns/server.go similarity index 100% rename from pkg/homekit/mdns/server.go rename to pkg/hap/mdns/server.go diff --git a/pkg/homekit/pairing.go b/pkg/hap/pairing.go similarity index 99% rename from pkg/homekit/pairing.go rename to pkg/hap/pairing.go index d68886df..7e8f5074 100644 --- a/pkg/homekit/pairing.go +++ b/pkg/hap/pairing.go @@ -1,4 +1,4 @@ -package homekit +package hap import ( "bufio" diff --git a/pkg/homekit/secure.go b/pkg/hap/secure.go similarity index 99% rename from pkg/homekit/secure.go rename to pkg/hap/secure.go index 2370c708..3990196c 100644 --- a/pkg/homekit/secure.go +++ b/pkg/hap/secure.go @@ -1,4 +1,4 @@ -package homekit +package hap import ( "encoding/binary" diff --git a/pkg/homekit/server.go b/pkg/hap/server.go similarity index 99% rename from pkg/homekit/server.go rename to pkg/hap/server.go index 9cb267c5..9fcf7e0d 100644 --- a/pkg/homekit/server.go +++ b/pkg/hap/server.go @@ -1,4 +1,4 @@ -package homekit +package hap import ( "bufio" diff --git a/pkg/homekit/camera/session.go b/pkg/homekit/camera/session.go deleted file mode 100644 index 0647f5ad..00000000 --- a/pkg/homekit/camera/session.go +++ /dev/null @@ -1,103 +0,0 @@ -package camera - -import ( - cryptorand "crypto/rand" - "encoding/binary" - "github.com/brutella/hap/rtp" -) - -type Session struct { - Offer *rtp.SetupEndpoints - Answer *rtp.SetupEndpointsResponse - Config *rtp.StreamConfiguration -} - -func NewSession() *Session { - sessionID := RandomBytes(16) - s := &Session{ - Offer: &rtp.SetupEndpoints{ - SessionId: sessionID, - Video: rtp.CryptoSuite{ - MasterKey: RandomBytes(16), - MasterSalt: RandomBytes(14), - }, - Audio: rtp.CryptoSuite{ - MasterKey: RandomBytes(16), - MasterSalt: RandomBytes(14), - }, - }, - Config: &rtp.StreamConfiguration{ - Command: rtp.SessionControlCommand{ - Identifier: sessionID, - Type: rtp.SessionControlCommandTypeStart, - }, - Video: rtp.VideoParameters{ - CodecType: rtp.VideoCodecType_H264, - CodecParams: rtp.VideoCodecParameters{ - Profiles: []rtp.VideoCodecProfile{ - {Id: rtp.VideoCodecProfileMain}, - }, - Levels: []rtp.VideoCodecLevel{ - {Level: rtp.VideoCodecLevel4}, - }, - Packetizations: []rtp.VideoCodecPacketization{ - {Mode: rtp.VideoCodecPacketizationModeNonInterleaved}, - }, - }, - Attributes: rtp.VideoCodecAttributes{ - Width: 1920, Height: 1080, Framerate: 30, - }, - RTP: rtp.RTPParams{ - PayloadType: 99, - Ssrc: RandomUint32(), - Bitrate: 299, - Interval: 0.5, - ComfortNoisePayloadType: 98, - MTU: 0, - }, - }, - Audio: rtp.AudioParameters{ - CodecType: rtp.AudioCodecType_AAC_ELD, - CodecParams: rtp.AudioCodecParameters{ - Channels: 1, - Bitrate: rtp.AudioCodecBitrateVariable, - Samplerate: rtp.AudioCodecSampleRate16Khz, - PacketTime: 30, - }, - RTP: rtp.RTPParams{ - PayloadType: 110, - Ssrc: RandomUint32(), - Bitrate: 24, - Interval: 5, - MTU: 13, - }, - ComfortNoise: false, - }, - }, - } - return s -} - -func (s *Session) SetLocalEndpoint(host string, port uint16) { - s.Offer.ControllerAddr = rtp.Addr{ - IPAddr: host, - VideoRtpPort: port, - AudioRtpPort: port, - } -} - -func (s *Session) SetVideo() { - -} - -func RandomBytes(size int) []byte { - data := make([]byte, size) - _, _ = cryptorand.Read(data) - return data -} - -func RandomUint32() uint32 { - data := make([]byte, 4) - _, _ = cryptorand.Read(data) - return binary.BigEndian.Uint32(data) -} diff --git a/cmd/homekit/client.go b/pkg/homekit/client.go similarity index 58% rename from cmd/homekit/client.go rename to pkg/homekit/client.go index 5e5216ac..67b3a26b 100644 --- a/cmd/homekit/client.go +++ b/pkg/homekit/client.go @@ -3,26 +3,61 @@ package homekit import ( "errors" "fmt" - "github.com/AlexxIT/go2rtc/cmd/srtp" - "github.com/AlexxIT/go2rtc/pkg/homekit" - "github.com/AlexxIT/go2rtc/pkg/homekit/camera" - pkg "github.com/AlexxIT/go2rtc/pkg/srtp" + "github.com/AlexxIT/go2rtc/pkg/hap" + "github.com/AlexxIT/go2rtc/pkg/hap/camera" + "github.com/AlexxIT/go2rtc/pkg/srtp" "github.com/AlexxIT/go2rtc/pkg/streamer" "github.com/brutella/hap/characteristic" "github.com/brutella/hap/rtp" "net" - "strconv" + "net/url" ) type Client struct { streamer.Element - conn *homekit.Conn + conn *hap.Conn exit chan error + server *srtp.Server + url string + medias []*streamer.Media tracks []*streamer.Track - sessions []*pkg.Session + sessions []*srtp.Session +} + +func NewClient(rawURL string, server *srtp.Server) (*Client, error) { + u, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + + query := u.Query() + c := &hap.Conn{ + DeviceAddress: u.Host, + DeviceID: query.Get("device_id"), + DevicePublic: hap.DecodeKey(query.Get("device_public")), + ClientID: query.Get("client_id"), + ClientPrivate: hap.DecodeKey(query.Get("client_private")), + } + + return &Client{conn: c, server: server}, nil +} + +func (c *Client) Dial() error { + if err := c.conn.Dial(); err != nil { + return err + } + + c.exit = make(chan error) + + go func() { + //start goroutine for reading responses from camera + c.exit <- c.conn.Handle() + }() + + return nil } func (c *Client) GetMedias() []*streamer.Media { @@ -56,28 +91,54 @@ func (c *Client) Start() error { return err } - // get our server SRTP port - port, err := strconv.Atoi(srtp.Port) - if err != nil { - return err + // TODO: set right config + vp := &rtp.VideoParameters{ + CodecType: rtp.VideoCodecType_H264, + CodecParams: rtp.VideoCodecParameters{ + Profiles: []rtp.VideoCodecProfile{ + {Id: rtp.VideoCodecProfileMain}, + }, + Levels: []rtp.VideoCodecLevel{ + {Level: rtp.VideoCodecLevel4}, + }, + Packetizations: []rtp.VideoCodecPacketization{ + {Mode: rtp.VideoCodecPacketizationModeNonInterleaved}, + }, + }, + Attributes: rtp.VideoCodecAttributes{ + Width: 1920, Height: 1080, Framerate: 30, + }, + } + + ap := &rtp.AudioParameters{ + CodecType: rtp.AudioCodecType_AAC_ELD, + CodecParams: rtp.AudioCodecParameters{ + Channels: 1, + Bitrate: rtp.AudioCodecBitrateVariable, + Samplerate: rtp.AudioCodecSampleRate16Khz, + // packet time=20 => AAC-ELD packet size=480 + // packet time=30 => AAC-ELD packet size=480 + // packet time=40 => AAC-ELD packet size=480 + // packet time=60 => AAC-LD packet size=960 + PacketTime: 40, + }, } // setup HomeKit stream session - hkSession := camera.NewSession() - hkSession.SetLocalEndpoint(host, uint16(port)) + hkSession := camera.NewSession(vp, ap) + hkSession.SetLocalEndpoint(host, c.server.Port()) // create client for processing camera accessory cam := camera.NewClient(c.conn) // try to start HomeKit stream - if err = cam.StartStream2(hkSession); err != nil { - panic(err) // TODO: fixme + if err = cam.StartStream(hkSession); err != nil { + return err } // SRTP Video Session - vs := &pkg.Session{ + vs := &srtp.Session{ LocalSSRC: hkSession.Config.Video.RTP.Ssrc, RemoteSSRC: hkSession.Answer.SsrcVideo, - Track: c.tracks[0], } if err = vs.SetKeys( hkSession.Offer.Video.MasterKey, hkSession.Offer.Video.MasterSalt, @@ -87,10 +148,9 @@ func (c *Client) Start() error { } // SRTP Audio Session - as := &pkg.Session{ + as := &srtp.Session{ LocalSSRC: hkSession.Config.Audio.RTP.Ssrc, RemoteSSRC: hkSession.Answer.SsrcAudio, - Track: &streamer.Track{}, } if err = as.SetKeys( hkSession.Offer.Audio.MasterKey, hkSession.Offer.Audio.MasterSalt, @@ -99,10 +159,19 @@ func (c *Client) Start() error { return err } - srtp.AddSession(vs) - srtp.AddSession(as) + for _, track := range c.tracks { + switch track.Codec.Name { + case streamer.CodecH264: + vs.Track = track + case streamer.CodecAAC: + as.Track = track + } + } - c.sessions = []*pkg.Session{vs, as} + c.server.AddSession(vs) + c.server.AddSession(as) + + c.sessions = []*srtp.Session{vs, as} return <-c.exit } @@ -111,7 +180,7 @@ func (c *Client) Stop() error { err := c.conn.Close() for _, session := range c.sessions { - srtp.RemoveSession(session) + c.server.RemoveSession(session) } return err @@ -121,16 +190,17 @@ func (c *Client) getMedias() []*streamer.Media { var medias []*streamer.Media accs, err := c.conn.GetAccessories() - acc := accs[0] if err != nil { - panic(err) + return nil } + acc := accs[0] + // get supported video config (not really necessary) char := acc.GetCharacter(characteristic.TypeSupportedVideoStreamConfiguration) v1 := &rtp.VideoStreamConfiguration{} if err = char.ReadTLV8(v1); err != nil { - panic(err) + return nil } for _, hkCodec := range v1.Codecs { @@ -139,8 +209,10 @@ func (c *Client) getMedias() []*streamer.Media { switch hkCodec.Type { case rtp.VideoCodecType_H264: codec.Name = streamer.CodecH264 + codec.FmtpLine = "profile-level-id=420029" default: - panic(fmt.Sprintf("unknown codec: %d", hkCodec.Type)) + fmt.Printf("unknown codec: %d", hkCodec.Type) + continue } media := &streamer.Media{ @@ -153,7 +225,7 @@ func (c *Client) getMedias() []*streamer.Media { char = acc.GetCharacter(characteristic.TypeSupportedAudioStreamConfiguration) v2 := &rtp.AudioStreamConfiguration{} if err = char.ReadTLV8(v2); err != nil { - panic(err) + return nil } for _, hkCodec := range v2.Codecs { @@ -165,7 +237,8 @@ func (c *Client) getMedias() []*streamer.Media { case rtp.AudioCodecType_AAC_ELD: codec.Name = streamer.CodecAAC default: - panic(fmt.Sprintf("unknown codec: %d", hkCodec.Type)) + fmt.Printf("unknown codec: %d", hkCodec.Type) + continue } switch hkCodec.Parameters.Samplerate { diff --git a/pkg/srtp/server.go b/pkg/srtp/server.go index 1563095a..264946a7 100644 --- a/pkg/srtp/server.go +++ b/pkg/srtp/server.go @@ -8,9 +8,19 @@ import ( // Server using same UDP port for SRTP and for SRTCP as the iPhone does // this is not really necessary but anyway type Server struct { + conn net.PacketConn sessions map[uint32]*Session } +func (s *Server) Port() uint16 { + addr := s.conn.LocalAddr().(*net.UDPAddr) + return uint16(addr.Port) +} + +func (s *Server) Close() error { + return s.conn.Close() +} + func (s *Server) AddSession(session *Session) { if s.sessions == nil { s.sessions = map[uint32]*Session{} @@ -23,6 +33,8 @@ func (s *Server) RemoveSession(session *Session) { } func (s *Server) Serve(conn net.PacketConn) error { + s.conn = conn + buf := make([]byte, 2048) for { n, addr, err := conn.ReadFrom(buf) diff --git a/pkg/srtp/session.go b/pkg/srtp/session.go index 4478b560..1bb8208d 100644 --- a/pkg/srtp/session.go +++ b/pkg/srtp/session.go @@ -37,6 +37,10 @@ func (s *Session) HandleRTP(data []byte) (err error) { return } + if s.Track == nil { + return + } + packet := &rtp.Packet{} if err = packet.Unmarshal(data); err != nil { return @@ -90,8 +94,8 @@ func GuessProfile(masterKey []byte) srtp.ProtectionProfile { switch len(masterKey) { case 16: return srtp.ProtectionProfileAes128CmHmacSha1_80 - //case 32: - // return srtp.ProtectionProfileAes256CmHmacSha1_80 + //case 32: + // return srtp.ProtectionProfileAes256CmHmacSha1_80 } return 0 }