diff --git a/internal/homekit/api.go b/internal/homekit/api.go index abd8e97c3..102525ecd 100644 --- a/internal/homekit/api.go +++ b/internal/homekit/api.go @@ -72,7 +72,7 @@ func discovery() ([]*api.Source, error) { log.Trace().Msgf("[homekit] mdns=%s", entry) category := entry.Info[hap.TXTCategory] - if entry.Complete() && (category == hap.CategoryCamera || category == hap.CategoryDoorbell) { + if entry.Complete() && (category == hap.CategoryBridge || category == hap.CategoryCamera || category == hap.CategoryDoorbell) { source := &api.Source{ Name: entry.Name, Info: entry.Info[hap.TXTModel], diff --git a/internal/homekit/homekit.go b/internal/homekit/homekit.go index 743aeab9c..034587fbe 100644 --- a/internal/homekit/homekit.go +++ b/internal/homekit/homekit.go @@ -5,6 +5,7 @@ import ( "io" "net" "net/http" + "strconv" "strings" "github.com/AlexxIT/go2rtc/internal/api" @@ -143,6 +144,18 @@ func streamHandler(rawURL string) (core.Producer, error) { if client != nil && rawQuery != "" { query := streams.ParseQuery(rawQuery) client.Bitrate = parseBitrate(query.Get("bitrate")) + + if widthStr := query.Get("width"); widthStr != "" { + if width, err := strconv.Atoi(widthStr); err == nil { + client.Width = width + } + } + + if heightStr := query.Get("height"); heightStr != "" { + if height, err := strconv.Atoi(heightStr); err == nil { + client.Height = height + } + } } return client, err diff --git a/pkg/hap/camera/s113_speaker.go b/pkg/hap/camera/s113_speaker.go new file mode 100644 index 000000000..2861426cc --- /dev/null +++ b/pkg/hap/camera/s113_speaker.go @@ -0,0 +1,3 @@ +package camera + +const TypeSpeaker = "113" diff --git a/pkg/hap/camera/stream.go b/pkg/hap/camera/stream.go index 23d53c399..7450d132a 100644 --- a/pkg/hap/camera/stream.go +++ b/pkg/hap/camera/stream.go @@ -16,7 +16,7 @@ type Stream struct { func NewStream( client *hap.Client, videoCodec *VideoCodec, audioCodec *AudioCodec, - videoSession, audioSession *srtp.Session, bitrate int, + videoSession, audioSession *srtp.Session, bitrate int, width int, height int, ) (*Stream, error) { stream := &Stream{ id: core.RandString(16, 0), @@ -46,6 +46,12 @@ func NewStream( MaxMTU: []uint16{1378}, }, } + + if width != 0 && height != 0 { + videoCodec.VideoAttrs[0].Width = uint16(width) + videoCodec.VideoAttrs[0].Height = uint16(height) + } + audioCodec.RTPParams = []RTPParams{ { PayloadType: 110, diff --git a/pkg/hap/tlv8/tlv8.go b/pkg/hap/tlv8/tlv8.go index 41a6de58c..f724be624 100644 --- a/pkg/hap/tlv8/tlv8.go +++ b/pkg/hap/tlv8/tlv8.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "encoding/binary" "errors" - "fmt" "io" "math" "reflect" @@ -186,57 +185,32 @@ func Unmarshal(data []byte, v any) error { } func unmarshalStruct(b []byte, value reflect.Value) error { - var waitSlice bool - - for len(b) >= 2 { - t := b[0] - l := int(b[1]) - - // array item divider - if t == 0 && l == 0 { - b = b[2:] - waitSlice = true - continue - } - - var v []byte - - for { - if len(b) < 2+l { - return errors.New("tlv8: wrong size: " + value.Type().Name()) - } - - v = append(v, b[2:2+l]...) - b = b[2+l:] - - // if size == 255 and same tag - continue read big payload - if l < 255 || len(b) < 2 || b[0] != t { - break - } - - l = int(b[1]) - } - - tag := strconv.Itoa(int(t)) - - valueField, ok := getStructField(value, tag) - if !ok { - return fmt.Errorf("tlv8: can't find T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name()) - } + if len(b) < 2 { + return nil // End of payload, nothing left to process + } - if waitSlice { - if valueField.Kind() != reflect.Slice { - return fmt.Errorf("tlv8: should be slice T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name()) - } - waitSlice = false - } + t := b[0] // Type + l := int(b[1]) // Length + v := b[2 : 2+l] // Value (can be empty if l == 0) + remainder := b[2+l:] // Move cursor to the next TLV item + tag := strconv.Itoa(int(t)) + valueField, ok := getStructField(value, tag) + + // Accumulate fragments if the value length is 255 + for l == 255 && len(remainder) >= 2 && remainder[0] == t { + l = int(remainder[1]) + v = append(v, remainder[2:2+l]...) + remainder = remainder[2+l:] + } + if ok { if err := unmarshalValue(v, valueField); err != nil { return err } } - return nil + // Recursively process the remaining payload + return unmarshalStruct(remainder, value) } func unmarshalValue(v []byte, value reflect.Value) error { diff --git a/pkg/homekit/helpers.go b/pkg/homekit/helpers.go index a1719671c..c97c19312 100644 --- a/pkg/homekit/helpers.go +++ b/pkg/homekit/helpers.go @@ -39,9 +39,9 @@ func videoToMedia(codecs []camera.VideoCodec) *core.Media { var audioCodecs = [...]string{core.CodecPCMU, core.CodecPCMA, core.CodecELD, core.CodecOpus} var audioSampleRates = [...]uint32{8000, 16000, 24000} -func audioToMedia(codecs []camera.AudioCodec) *core.Media { +func audioToMedia(codecs []camera.AudioCodec, direction string) *core.Media { media := &core.Media{ - Kind: core.KindAudio, Direction: core.DirectionRecvonly, + Kind: core.KindAudio, Direction: direction, } for _, codec := range codecs { diff --git a/pkg/homekit/producer.go b/pkg/homekit/producer.go index 451b98822..ae8886630 100644 --- a/pkg/homekit/producer.go +++ b/pkg/homekit/producer.go @@ -30,6 +30,8 @@ type Client struct { stream *camera.Stream + Width int + Height int Bitrate int // in bits/s } @@ -102,7 +104,7 @@ func (c *Client) GetMedias() []*core.Media { c.Medias = []*core.Media{ videoToMedia(c.videoConfig.Codecs), - audioToMedia(c.audioConfig.Codecs), + audioToMedia(c.audioConfig.Codecs, core.DirectionRecvonly), { Kind: core.KindVideo, Direction: core.DirectionRecvonly, @@ -116,9 +118,46 @@ func (c *Client) GetMedias() []*core.Media { }, } + if acc.GetService(camera.TypeSpeaker) != nil { + c.Medias = append(c.Medias, + audioToMedia(c.audioConfig.Codecs, core.DirectionSendonly), + &core.Media{ + Kind: core.KindAudio, + Direction: core.DirectionSendonly, + Codecs: []*core.Codec{ + { + Name: core.CodecOpus, + ClockRate: 48000, + Channels: 2, + }, + }, + }, + ) + } + return c.Medias } +func (c *Client) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error { + switch codec.Name { + case core.CodecOpus: + sender := core.NewSender(media, track.Codec) + + sender.Handler = func(packet *rtp.Packet) { + if c.audioSession != nil { + if n, err := c.audioSession.WriteRTP(packet); err == nil { + c.Send += n + } + } + } + + sender.HandleRTP(track) + c.Senders = append(c.Senders, sender) + } + + return nil +} + func (c *Client) Start() error { if c.Receivers == nil { return errors.New("producer without tracks") @@ -138,7 +177,7 @@ func (c *Client) Start() error { c.audioSession = &srtp.Session{Local: c.srtpEndpoint()} var err error - c.stream, err = camera.NewStream(c.hap, videoCodec, audioCodec, c.videoSession, c.audioSession, c.Bitrate) + c.stream, err = camera.NewStream(c.hap, videoCodec, audioCodec, c.videoSession, c.audioSession, c.Bitrate, c.Width, c.Height) if err != nil { return err } @@ -146,6 +185,12 @@ func (c *Client) Start() error { c.srtp.AddSession(c.videoSession) c.srtp.AddSession(c.audioSession) + c.videoSession.PayloadType = videoCodec.RTPParams[0].PayloadType + c.videoSession.RTCPInterval = toDuration(videoCodec.RTPParams[0].RTCPInterval) + + c.audioSession.PayloadType = audioCodec.RTPParams[0].PayloadType + c.audioSession.RTCPInterval = toDuration(audioCodec.RTPParams[0].RTCPInterval) + deadline := time.NewTimer(core.ConnDeadline) if videoTrack != nil { @@ -169,10 +214,6 @@ func (c *Client) Start() error { } } - if c.audioSession.OnReadRTP != nil { - c.audioSession.OnReadRTP = timekeeper(c.audioSession.OnReadRTP) - } - <-deadline.C return nil @@ -226,30 +267,3 @@ func (c *Client) srtpEndpoint() *srtp.Endpoint { 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) - } -}