Total rework HAP pkg and HomeKit source

This commit is contained in:
Alexey Khit
2023-07-23 22:22:36 +03:00
parent d73e9f6bcf
commit 6d82b1ce89
31 changed files with 2074 additions and 2015 deletions
+44
View File
@@ -0,0 +1,44 @@
package camera
const TypeSupportedVideoStreamConfiguration = "114"
type SupportedVideoStreamConfig struct {
Codecs []VideoCodecConfig `tlv8:"1"`
}
type VideoCodecConfig struct {
CodecType byte `tlv8:"1"`
CodecParams []VideoCodecParams `tlv8:"2"`
VideoAttrs []VideoAttrs `tlv8:"3"`
}
const (
VideoCodecTypeH264 = 0
VideoCodecProfileConstrainedBaseline = 0
VideoCodecProfileMain = 1
VideoCodecProfileHigh = 2
VideoCodecLevel31 = 0
VideoCodecLevel32 = 1
VideoCodecLevel40 = 2
VideoCodecPacketizationModeNonInterleaved = 0
VideoCodecCvoNotSuppported = 0
VideoCodecCvoSuppported = 1
)
type VideoCodecParams struct {
ProfileID byte `tlv8:"1"` // 0 - baseline, 1 - main, 2 - high
Level byte `tlv8:"2"` // 0 - 3.1, 1 - 3.2, 2 - 4.0
PacketizationMode byte `tlv8:"3"` // only 0 - non interleaved
CVOEnabled byte `tlv8:"4"` // 0 - not supported, 1 - supported
CVOID byte `tlv8:"5"` // ???
}
type VideoAttrs struct {
Width uint16 `tlv8:"1"`
Height uint16 `tlv8:"2"`
Framerate uint8 `tlv8:"3"`
}
+37
View File
@@ -0,0 +1,37 @@
package camera
const TypeSupportedAudioStreamConfiguration = "115"
type SupportedAudioStreamConfig struct {
Codecs []AudioCodecConfig `tlv8:"1"`
ComfortNoise byte `tlv8:"2"`
}
const (
AudioCodecTypePCMU = 0
AudioCodecTypePCMA = 1
AudioCodecTypeAACELD = 2
AudioCodecTypeOpus = 3
AudioCodecTypeMSBC = 4
AudioCodecTypeAMR = 5
AudioCodecTypeARMWB = 6
AudioCodecBitrateVariable = 0
AudioCodecBitrateConstant = 1
AudioCodecSampleRate8Khz = 0
AudioCodecSampleRate16Khz = 1
AudioCodecSampleRate24Khz = 2
)
type AudioCodecConfig struct {
CodecType byte `tlv8:"1"`
CodecParams []AudioCodecParams `tlv8:"2"`
}
type AudioCodecParams struct {
Channels byte `tlv8:"1"`
Bitrate byte `tlv8:"2"` // 0 - variable, 1 - constant
SampleRate byte `tlv8:"3"` // 0 - 8000, 1 - 16000, 2 - 24000
RTPTime byte `tlv8:"4"`
}
+52
View File
@@ -0,0 +1,52 @@
package camera
const TypeSelectedStreamConfiguration = "117"
type SelectedStreamConfig struct {
Control SessionControl `tlv8:"1"`
VideoParams SelectedVideoParams `tlv8:"2"`
AudioParams SelectedAudioParams `tlv8:"3"`
}
const (
SessionCommandEnd = 0
SessionCommandStart = 1
SessionCommandSuspend = 2
SessionCommandResume = 3
SessionCommandReconfigure = 4
)
type SessionControl struct {
Session string `tlv8:"1"`
Command byte `tlv8:"2"`
}
type SelectedVideoParams struct {
CodecType byte `tlv8:"1"` // only 0 - H264
CodecParams VideoCodecParams `tlv8:"2"`
VideoAttrs VideoAttrs `tlv8:"3"`
RTPParams VideoRTPParams `tlv8:"4"`
}
type VideoRTPParams struct {
PayloadType uint8 `tlv8:"1"`
SSRC uint32 `tlv8:"2"`
MaxBitrate uint16 `tlv8:"3"`
MinRTCPInterval float32 `tlv8:"4"`
MaxMTU uint16 `tlv8:"5"`
}
type SelectedAudioParams struct {
CodecType byte `tlv8:"1"` // 2 - AAC_ELD, 3 - OPUS, 5 - AMR, 6 - AMR_WB
CodecParams AudioCodecParams `tlv8:"2"`
RTPParams AudioRTPParams `tlv8:"3"`
ComfortNoise uint8 `tlv8:"4"`
}
type AudioRTPParams struct {
PayloadType uint8 `tlv8:"1"`
SSRC uint32 `tlv8:"2"`
MaxBitrate uint16 `tlv8:"3"`
MinRTCPInterval float32 `tlv8:"4"`
ComfortNoisePayloadType uint8 `tlv8:"6"`
}
+33
View File
@@ -0,0 +1,33 @@
package camera
const TypeSetupEndpoints = "118"
type SetupEndpoints struct {
SessionID []byte `tlv8:"1"`
ControllerAddr Addr `tlv8:"3"`
VideoCrypto CryptoSuite `tlv8:"4"`
AudioCrypto CryptoSuite `tlv8:"5"`
}
type Addr struct {
IPVersion byte `tlv8:"1"`
IPAddr string `tlv8:"2"`
VideoRTPPort uint16 `tlv8:"3"`
AudioRTPPort uint16 `tlv8:"4"`
}
type CryptoSuite struct {
CryptoType byte `tlv8:"1"`
MasterKey []byte `tlv8:"2"` // 16 (AES_CM_128) or 32 (AES_256_CM)
MasterSalt []byte `tlv8:"3"` // 14 byte
}
type SetupEndpointsResponse struct {
SessionID []byte `tlv8:"1"`
Status byte `tlv8:"2"`
AccessoryAddr Addr `tlv8:"3"`
VideoCrypto CryptoSuite `tlv8:"4"`
AudioCrypto CryptoSuite `tlv8:"5"`
VideoSSRC uint32 `tlv8:"6"`
AudioSSRC uint32 `tlv8:"7"`
}
+13
View File
@@ -0,0 +1,13 @@
package camera
const TypeStreamingStatus = "120"
type StreamingStatus struct {
Status byte `tlv8:"1"`
}
const (
StreamingStatusAvailable = 0
StreamingStatusBusy = 1
StreamingStatusUnavailable = 2
)
+19 -22
View File
@@ -2,23 +2,22 @@ package camera
import (
"errors"
"github.com/AlexxIT/go2rtc/pkg/hap"
"github.com/brutella/hap/characteristic"
"github.com/brutella/hap/rtp"
)
type Client struct {
client *hap.Conn
client *hap.Client
}
func NewClient(client *hap.Conn) *Client {
func NewClient(client *hap.Client) *Client {
return &Client{client: client}
}
func (c *Client) StartStream(ses *Session) (err error) {
func (c *Client) StartStream(ses *Session) error {
// Step 1. Check if camera ready (free) to stream
var srv *hap.Service
if srv, err = c.GetFreeStream(); err != nil {
srv, err := c.GetFreeStream()
if err != nil {
return err
}
if srv == nil {
@@ -26,7 +25,7 @@ func (c *Client) StartStream(ses *Session) (err error) {
}
if ses.Answer, err = c.SetupEndpoins(srv, ses.Offer); err != nil {
return
return err
}
return c.SetConfig(srv, ses.Config)
@@ -36,20 +35,20 @@ func (c *Client) StartStream(ses *Session) (err error) {
// Usual every HomeKit camera can stream only to two clients simultaniosly.
// So it has two similar services for streaming.
func (c *Client) GetFreeStream() (srv *hap.Service, err error) {
var accs []*hap.Accessory
if accs, err = c.client.GetAccessories(); err != nil {
accs, err := c.client.GetAccessories()
if err != nil {
return
}
for _, srv = range accs[0].Services {
for _, char := range srv.Characters {
if char.Type == characteristic.TypeStreamingStatus {
status := rtp.StreamingStatus{}
if char.Type == TypeStreamingStatus {
var status StreamingStatus
if err = char.ReadTLV8(&status); err != nil {
return
}
if status.Status == rtp.SessionStatusSuccess {
if status.Status == StreamingStatusAvailable {
return
}
}
@@ -59,11 +58,9 @@ func (c *Client) GetFreeStream() (srv *hap.Service, err error) {
return nil, nil
}
func (c *Client) SetupEndpoins(
srv *hap.Service, req *rtp.SetupEndpoints,
) (res *rtp.SetupEndpointsResponse, err error) {
func (c *Client) SetupEndpoins(srv *hap.Service, req *SetupEndpoints) (res *SetupEndpointsResponse, err error) {
// get setup endpoint character ID
char := srv.GetCharacter(characteristic.TypeSetupEndpoints)
char := srv.GetCharacter(TypeSetupEndpoints)
char.Event = nil
// encode new character value
if err = char.Write(req); err != nil {
@@ -79,7 +76,7 @@ func (c *Client) SetupEndpoins(
return
}
// decode new endpoint value
res = &rtp.SetupEndpointsResponse{}
res = &SetupEndpointsResponse{}
if err = char.ReadTLV8(res); err != nil {
return
}
@@ -87,13 +84,13 @@ func (c *Client) SetupEndpoins(
return
}
func (c *Client) SetConfig(srv *hap.Service, config *rtp.StreamConfiguration) (err error) {
func (c *Client) SetConfig(srv *hap.Service, config *SelectedStreamConfig) error {
// get setup endpoint character ID
char := srv.GetCharacter(characteristic.TypeSelectedStreamConfiguration)
char := srv.GetCharacter(TypeSelectedStreamConfiguration)
char.Event = nil
// encode new character value
if err = char.Write(config); err != nil {
panic(err)
if err := char.Write(config); err != nil {
return err
}
// write (put) new character value to device
return c.client.PutCharacters(char)
+30 -32
View File
@@ -1,75 +1,73 @@
package camera
import (
cryptorand "crypto/rand"
"crypto/rand"
"encoding/binary"
"github.com/brutella/hap/rtp"
)
type Session struct {
Offer *rtp.SetupEndpoints
Answer *rtp.SetupEndpointsResponse
Config *rtp.StreamConfiguration
Offer *SetupEndpoints
Answer *SetupEndpointsResponse
Config *SelectedStreamConfig
}
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
func NewSession(vp *SelectedVideoParams, ap *SelectedAudioParams) *Session {
vp.RTPParams = VideoRTPParams{
PayloadType: 99,
SSRC: RandomUint32(),
MaxBitrate: 2048,
MinRTCPInterval: 10,
MaxMTU: 1200, // like WebRTC
}
ap.RTP = rtp.RTPParams{
ap.RTPParams = AudioRTPParams{
PayloadType: 110,
Ssrc: RandomUint32(),
Bitrate: 32,
Interval: 10,
SSRC: RandomUint32(),
MaxBitrate: 32,
MinRTCPInterval: 10,
ComfortNoisePayloadType: 98,
MTU: 0,
}
sessionID := RandomBytes(16)
s := &Session{
Offer: &rtp.SetupEndpoints{
SessionId: sessionID,
Video: rtp.CryptoSuite{
Offer: &SetupEndpoints{
SessionID: sessionID,
VideoCrypto: CryptoSuite{
MasterKey: RandomBytes(16),
MasterSalt: RandomBytes(14),
},
Audio: rtp.CryptoSuite{
AudioCrypto: CryptoSuite{
MasterKey: RandomBytes(16),
MasterSalt: RandomBytes(14),
},
},
Config: &rtp.StreamConfiguration{
Command: rtp.SessionControlCommand{
Identifier: sessionID,
Type: rtp.SessionControlCommandTypeStart,
Config: &SelectedStreamConfig{
Control: SessionControl{
Session: string(sessionID),
Command: SessionCommandStart,
},
Video: *vp,
Audio: *ap,
VideoParams: *vp,
AudioParams: *ap,
},
}
return s
}
func (s *Session) SetLocalEndpoint(host string, port uint16) {
s.Offer.ControllerAddr = rtp.Addr{
s.Offer.ControllerAddr = Addr{
IPAddr: host,
VideoRtpPort: port,
AudioRtpPort: port,
VideoRTPPort: port,
AudioRTPPort: port,
}
}
func RandomBytes(size int) []byte {
data := make([]byte, size)
_, _ = cryptorand.Read(data)
_, _ = rand.Read(data)
return data
}
func RandomUint32() uint32 {
data := make([]byte, 4)
_, _ = cryptorand.Read(data)
_, _ = rand.Read(data)
return binary.BigEndian.Uint32(data)
}