Files
go2rtc/pkg/homekit/client.go
2023-09-10 16:10:00 +03:00

252 lines
5.0 KiB
Go

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)
}
}