Total rework HAP pkg and HomeKit source
This commit is contained in:
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user