package homekit import ( "errors" "fmt" "math/rand" "net" "net/url" "time" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/hap" "github.com/AlexxIT/go2rtc/pkg/hap/camera" "github.com/AlexxIT/go2rtc/pkg/srtp" "github.com/pion/rtp" ) type Client struct { core.SuperProducer hap *hap.Client srtp *srtp.Server videoConfig camera.SupportedVideoStreamConfig audioConfig camera.SupportedAudioStreamConfig videoSession *srtp.Session audioSession *srtp.Session stream *camera.Stream } func Dial(rawURL string, server *srtp.Server) (*Client, error) { u, err := url.Parse(rawURL) if err != nil { return nil, err } query := u.Query() conn := &hap.Client{ 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")), } if err = conn.Dial(); err != nil { return nil, err } client := &Client{ SuperProducer: core.SuperProducer{ Type: "HomeKit active producer", URL: conn.URL(), }, hap: conn, srtp: server, } return client, nil } func (c *Client) Conn() net.Conn { return c.hap.Conn } func (c *Client) GetMedias() []*core.Media { if c.Medias != nil { return c.Medias } acc, err := c.hap.GetFirstAccessory() if err != nil { return nil } char := acc.GetCharacter(camera.TypeSupportedVideoStreamConfiguration) if char == nil { return nil } if err = char.ReadTLV8(&c.videoConfig); err != nil { return nil } char = acc.GetCharacter(camera.TypeSupportedAudioStreamConfiguration) if char == nil { return nil } if err = char.ReadTLV8(&c.audioConfig); err != nil { return nil } c.URL = c.hap.URL() c.SDP = fmt.Sprintf("%+v\n%+v", c.videoConfig, c.audioConfig) c.Medias = []*core.Media{ videoToMedia(c.videoConfig.Codecs), audioToMedia(c.audioConfig.Codecs), { Kind: core.KindVideo, Direction: core.DirectionRecvonly, Codecs: []*core.Codec{ { Name: core.CodecJPEG, ClockRate: 90000, PayloadType: core.PayloadTypeRAW, }, }, }, } return c.Medias } func (c *Client) Start() error { if c.Receivers == nil { return errors.New("producer without tracks") } if c.Receivers[0].Codec.Name == core.CodecJPEG { return c.startMJPEG() } videoTrack := c.trackByKind(core.KindVideo) videoCodec := trackToVideo(videoTrack, &c.videoConfig.Codecs[0]) audioTrack := c.trackByKind(core.KindAudio) audioCodec := trackToAudio(audioTrack, &c.audioConfig.Codecs[0]) c.videoSession = &srtp.Session{Local: c.srtpEndpoint()} c.audioSession = &srtp.Session{Local: c.srtpEndpoint()} var err error c.stream, err = camera.NewStream(c.hap, videoCodec, audioCodec, c.videoSession, c.audioSession) if err != nil { return err } c.srtp.AddSession(c.videoSession) c.srtp.AddSession(c.audioSession) deadline := time.NewTimer(core.ConnDeadline) if videoTrack != nil { c.videoSession.OnReadRTP = func(packet *rtp.Packet) { deadline.Reset(core.ConnDeadline) videoTrack.WriteRTP(packet) c.Recv += len(packet.Payload) } if audioTrack != nil { c.audioSession.OnReadRTP = func(packet *rtp.Packet) { audioTrack.WriteRTP(packet) c.Recv += len(packet.Payload) } } } else { c.audioSession.OnReadRTP = func(packet *rtp.Packet) { deadline.Reset(core.ConnDeadline) audioTrack.WriteRTP(packet) c.Recv += len(packet.Payload) } } if c.audioSession.OnReadRTP != nil { c.audioSession.OnReadRTP = timekeeper(c.audioSession.OnReadRTP) } <-deadline.C return nil } func (c *Client) Stop() error { _ = c.SuperProducer.Close() if c.videoSession != nil && c.videoSession.Remote != nil { c.srtp.DelSession(c.videoSession) } if c.audioSession != nil && c.audioSession.Remote != nil { c.srtp.DelSession(c.audioSession) } return c.hap.Close() } func (c *Client) trackByKind(kind string) *core.Receiver { for _, receiver := range c.Receivers { if receiver.Codec.Kind() == kind { return receiver } } return nil } func (c *Client) startMJPEG() error { receiver := c.Receivers[0] for { b, err := c.hap.GetImage(1920, 1080) if err != nil { return err } c.Recv += len(b) packet := &rtp.Packet{ Header: rtp.Header{Timestamp: core.Now90000()}, Payload: b, } receiver.WriteRTP(packet) } } func (c *Client) srtpEndpoint() *srtp.Endpoint { return &srtp.Endpoint{ Addr: c.hap.LocalIP(), Port: uint16(c.srtp.Port()), MasterKey: []byte(core.RandString(16, 0)), MasterSalt: []byte(core.RandString(14, 0)), SSRC: rand.Uint32(), } } func timekeeper(handler core.HandlerFunc) core.HandlerFunc { const sampleRate = 16000 const sampleSize = 480 var send time.Duration var firstTime time.Time return func(packet *rtp.Packet) { now := time.Now() if send != 0 { elapsed := now.Sub(firstTime) * sampleRate / time.Second if send+sampleSize > elapsed { return // drop overflow frame } } else { firstTime = now } send += sampleSize packet.Timestamp = uint32(send) handler(packet) } }