Add support HomeKit server
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap"
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
||||
)
|
||||
|
||||
func NewAccessory(manuf, model, name, serial, firmware string) *hap.Accessory {
|
||||
acc := &hap.Accessory{
|
||||
AID: hap.DeviceAID,
|
||||
Services: []*hap.Service{
|
||||
hap.ServiceAccessoryInformation(manuf, model, name, serial, firmware),
|
||||
ServiceCameraRTPStreamManagement(),
|
||||
//hap.ServiceHAPProtocolInformation(),
|
||||
//ServiceMicrophone(),
|
||||
},
|
||||
}
|
||||
acc.InitIID()
|
||||
return acc
|
||||
}
|
||||
|
||||
func ServiceMicrophone() *hap.Service {
|
||||
return &hap.Service{
|
||||
Type: "112", // 'Microphone'
|
||||
Characters: []*hap.Character{
|
||||
{
|
||||
Type: "11A",
|
||||
Format: hap.FormatBool,
|
||||
Value: 0,
|
||||
Perms: hap.EVPRPW,
|
||||
//Descr: "Mute",
|
||||
},
|
||||
{
|
||||
Type: "119",
|
||||
Format: hap.FormatUInt8,
|
||||
Value: 100,
|
||||
Perms: hap.EVPRPW,
|
||||
//Descr: "Volume",
|
||||
//Unit: hap.UnitPercentage,
|
||||
//MinValue: 0,
|
||||
//MaxValue: 100,
|
||||
//MinStep: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ServiceCameraRTPStreamManagement() *hap.Service {
|
||||
val120, _ := tlv8.MarshalBase64(StreamingStatus{
|
||||
Status: StreamingStatusAvailable,
|
||||
})
|
||||
val114, _ := tlv8.MarshalBase64(SupportedVideoStreamConfig{
|
||||
Codecs: []VideoCodec{
|
||||
{
|
||||
CodecType: VideoCodecTypeH264,
|
||||
CodecParams: []VideoParams{
|
||||
{
|
||||
ProfileID: []byte{VideoCodecProfileMain},
|
||||
Level: []byte{VideoCodecLevel31, VideoCodecLevel40},
|
||||
},
|
||||
},
|
||||
VideoAttrs: []VideoAttrs{
|
||||
{Width: 1920, Height: 1080, Framerate: 30},
|
||||
{Width: 1280, Height: 720, Framerate: 30}, // important for iPhones
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
val115, _ := tlv8.MarshalBase64(SupportedAudioStreamConfig{
|
||||
Codecs: []AudioCodec{
|
||||
{
|
||||
CodecType: AudioCodecTypeOpus,
|
||||
CodecParams: []AudioParams{
|
||||
{
|
||||
Channels: 1,
|
||||
Bitrate: AudioCodecBitrateVariable,
|
||||
SampleRate: []byte{AudioCodecSampleRate16Khz},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ComfortNoise: 0,
|
||||
})
|
||||
val116, _ := tlv8.MarshalBase64(SupportedRTPConfig{
|
||||
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
|
||||
})
|
||||
|
||||
service := &hap.Service{
|
||||
Type: "110", // 'CameraRTPStreamManagement'
|
||||
Characters: []*hap.Character{
|
||||
{
|
||||
Type: TypeStreamingStatus,
|
||||
Format: hap.FormatTLV8,
|
||||
Value: val120,
|
||||
Perms: hap.EVPR,
|
||||
//Descr: "Streaming Status",
|
||||
},
|
||||
{
|
||||
Type: TypeSupportedVideoStreamConfiguration,
|
||||
Format: hap.FormatTLV8,
|
||||
Value: val114,
|
||||
Perms: hap.PR,
|
||||
//Descr: "Supported Video Stream Configuration",
|
||||
},
|
||||
{
|
||||
Type: TypeSupportedAudioStreamConfiguration,
|
||||
Format: hap.FormatTLV8,
|
||||
Value: val115,
|
||||
Perms: hap.PR,
|
||||
//Descr: "Supported Audio Stream Configuration",
|
||||
},
|
||||
{
|
||||
Type: TypeSupportedRTPConfiguration,
|
||||
Format: hap.FormatTLV8,
|
||||
Value: val116,
|
||||
Perms: hap.PR,
|
||||
//Descr: "Supported RTP Configuration",
|
||||
},
|
||||
{
|
||||
Type: "B0",
|
||||
Format: hap.FormatUInt8,
|
||||
Value: 1,
|
||||
Perms: hap.EVPRPW,
|
||||
//Descr: "Active",
|
||||
//MinValue: 0,
|
||||
//MaxValue: 1,
|
||||
//MinStep: 1,
|
||||
//ValidVal: []any{0, 1},
|
||||
},
|
||||
{
|
||||
Type: TypeSelectedStreamConfiguration,
|
||||
Format: hap.FormatTLV8,
|
||||
Value: "", // important empty
|
||||
Perms: hap.PRPW,
|
||||
//Descr: "Selected RTP Stream Configuration",
|
||||
},
|
||||
{
|
||||
Type: TypeSetupEndpoints,
|
||||
Format: hap.FormatTLV8,
|
||||
Value: "", // important empty
|
||||
Perms: hap.PRPW,
|
||||
//Descr: "Setup Endpoints",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/hap"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testTLV8 struct {
|
||||
name string
|
||||
value string
|
||||
actual any
|
||||
expect any
|
||||
noequal bool
|
||||
}
|
||||
|
||||
func (test testTLV8) run(t *testing.T) {
|
||||
if test.actual == nil {
|
||||
return
|
||||
}
|
||||
|
||||
src := &hap.Character{Value: test.value, Format: hap.FormatTLV8}
|
||||
err := src.ReadTLV8(test.actual)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, test.expect, test.actual)
|
||||
|
||||
dst := &hap.Character{Format: hap.FormatTLV8}
|
||||
err = dst.Write(test.actual)
|
||||
require.Nil(t, err)
|
||||
|
||||
a, _ := base64.StdEncoding.DecodeString(test.value)
|
||||
b, _ := base64.StdEncoding.DecodeString(dst.Value.(string))
|
||||
t.Logf("%x\n", a)
|
||||
t.Logf("%x\n", b)
|
||||
|
||||
if !test.noequal {
|
||||
require.Equal(t, test.value, dst.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAqaraG3(t *testing.T) {
|
||||
tests := []testTLV8{
|
||||
{
|
||||
name: "120",
|
||||
value: "AQEA",
|
||||
actual: &StreamingStatus{},
|
||||
expect: &StreamingStatus{
|
||||
Status: StreamingStatusAvailable,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "114",
|
||||
value: "AaoBAQACEQEBAQIBAAAAAgECAwEABAEAAwsBAoAHAgI4BAMBHgAAAwsBAgAFAgLQAgMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAuABAgIOAQMBHgAAAwsBAkABAgK0AAMBHgAAAwsBAgAFAgLAAwMBHgAAAwsBAgAEAgIAAwMBHgAAAwsBAoACAgLgAQMBHgAAAwsBAuABAgJoAQMBHgAAAwsBAkABAgLwAAMBHg==",
|
||||
actual: &SupportedVideoStreamConfig{},
|
||||
expect: &SupportedVideoStreamConfig{
|
||||
Codecs: []VideoCodec{
|
||||
{
|
||||
CodecType: VideoCodecTypeH264,
|
||||
CodecParams: []VideoParams{
|
||||
{
|
||||
ProfileID: []byte{VideoCodecProfileMain},
|
||||
Level: []byte{VideoCodecLevel31, VideoCodecLevel40},
|
||||
CVOEnabled: []byte{0},
|
||||
},
|
||||
},
|
||||
VideoAttrs: []VideoAttrs{
|
||||
{Width: 1920, Height: 1080, Framerate: 30},
|
||||
{Width: 1280, Height: 720, Framerate: 30},
|
||||
{Width: 640, Height: 360, Framerate: 30},
|
||||
{Width: 480, Height: 270, Framerate: 30},
|
||||
{Width: 320, Height: 180, Framerate: 30},
|
||||
{Width: 1280, Height: 960, Framerate: 30},
|
||||
{Width: 1024, Height: 768, Framerate: 30},
|
||||
{Width: 640, Height: 480, Framerate: 30},
|
||||
{Width: 480, Height: 360, Framerate: 30},
|
||||
{Width: 320, Height: 240, Framerate: 30},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "115",
|
||||
value: "AQ4BAQICCQEBAQIBAAMBAQIBAA==",
|
||||
actual: &SupportedAudioStreamConfig{},
|
||||
expect: &SupportedAudioStreamConfig{
|
||||
Codecs: []AudioCodec{
|
||||
{
|
||||
CodecType: AudioCodecTypeAACELD,
|
||||
CodecParams: []AudioParams{
|
||||
{
|
||||
Channels: 1,
|
||||
Bitrate: AudioCodecBitrateVariable,
|
||||
SampleRate: []byte{AudioCodecSampleRate16Khz},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ComfortNoise: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "116",
|
||||
value: "AgEAAAACAQEAAAIBAg==",
|
||||
actual: &SupportedRTPConfig{},
|
||||
expect: &SupportedRTPConfig{
|
||||
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoAES_CM_256_HMAC_SHA1_80, CryptoNone},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, test.run)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHomebridge(t *testing.T) {
|
||||
tests := []testTLV8{
|
||||
{
|
||||
name: "114",
|
||||
value: "AcUBAQACHQEBAAAAAQEBAAABAQICAQAAAAIBAQAAAgECAwEAAwsBAkABAgK0AAMBHgAAAwsBAkABAgLwAAMBDwAAAwsBAkABAgLwAAMBHgAAAwsBAuABAgIOAQMBHgAAAwsBAuABAgJoAQMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAoACAgLgAQMBHgAAAwsBAgAFAgLQAgMBHgAAAwsBAgAFAgLAAwMBHgAAAwsBAoAHAgI4BAMBHgAAAwsBAkAGAgKwBAMBHg==",
|
||||
actual: &SupportedVideoStreamConfig{},
|
||||
expect: &SupportedVideoStreamConfig{
|
||||
Codecs: []VideoCodec{
|
||||
{
|
||||
CodecType: VideoCodecTypeH264,
|
||||
CodecParams: []VideoParams{
|
||||
{
|
||||
ProfileID: []byte{VideoCodecProfileConstrainedBaseline, VideoCodecProfileMain, VideoCodecProfileHigh},
|
||||
Level: []byte{VideoCodecLevel31, VideoCodecLevel32, VideoCodecLevel40},
|
||||
},
|
||||
},
|
||||
VideoAttrs: []VideoAttrs{
|
||||
|
||||
{Width: 320, Height: 180, Framerate: 30},
|
||||
{Width: 320, Height: 240, Framerate: 15},
|
||||
{Width: 320, Height: 240, Framerate: 30},
|
||||
{Width: 480, Height: 270, Framerate: 30},
|
||||
{Width: 480, Height: 360, Framerate: 30},
|
||||
{Width: 640, Height: 360, Framerate: 30},
|
||||
{Width: 640, Height: 480, Framerate: 30},
|
||||
{Width: 1280, Height: 720, Framerate: 30},
|
||||
{Width: 1280, Height: 960, Framerate: 30},
|
||||
{Width: 1920, Height: 1080, Framerate: 30},
|
||||
{Width: 1600, Height: 1200, Framerate: 30},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "116",
|
||||
value: "AgEA",
|
||||
actual: &SupportedRTPConfig{},
|
||||
expect: &SupportedRTPConfig{
|
||||
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, test.run)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrypted(t *testing.T) {
|
||||
tests := []testTLV8{
|
||||
{
|
||||
name: "114",
|
||||
value: "AVIBAQACEwEBAQIBAAAAAgEBAAACAQIDAQADCwECAA8CAnAIAwEeAAADCwECgAcCAjgEAwEeAAADCwECAAUCAtACAwEeAAADCwECQAECAvAAAwEP",
|
||||
actual: &SupportedVideoStreamConfig{},
|
||||
expect: &SupportedVideoStreamConfig{
|
||||
Codecs: []VideoCodec{
|
||||
{
|
||||
CodecType: VideoCodecTypeH264,
|
||||
CodecParams: []VideoParams{
|
||||
{
|
||||
ProfileID: []byte{VideoCodecProfileMain},
|
||||
Level: []byte{VideoCodecLevel31, VideoCodecLevel32, VideoCodecLevel40},
|
||||
},
|
||||
},
|
||||
VideoAttrs: []VideoAttrs{
|
||||
{Width: 3840, Height: 2160, Framerate: 30},
|
||||
{Width: 1920, Height: 1080, Framerate: 30},
|
||||
{Width: 1280, Height: 720, Framerate: 30},
|
||||
{Width: 320, Height: 240, Framerate: 15},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "115",
|
||||
value: "AScBAQMCIgEBAQIBAAMBAAAAAwEAAAADAQEAAAMBAQAAAwECAAADAQICAQA=",
|
||||
actual: &SupportedAudioStreamConfig{},
|
||||
expect: &SupportedAudioStreamConfig{
|
||||
Codecs: []AudioCodec{
|
||||
{
|
||||
CodecType: AudioCodecTypeOpus,
|
||||
CodecParams: []AudioParams{
|
||||
{
|
||||
Channels: 1,
|
||||
Bitrate: AudioCodecBitrateVariable,
|
||||
SampleRate: []byte{
|
||||
AudioCodecSampleRate8Khz, AudioCodecSampleRate8Khz,
|
||||
AudioCodecSampleRate16Khz, AudioCodecSampleRate16Khz,
|
||||
AudioCodecSampleRate24Khz, AudioCodecSampleRate24Khz,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ComfortNoise: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "116",
|
||||
value: "AgEAAAACAQI=",
|
||||
actual: &SupportedRTPConfig{},
|
||||
expect: &SupportedRTPConfig{
|
||||
CryptoType: []byte{CryptoAES_CM_128_HMAC_SHA1_80, CryptoNone},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, test.run)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHass(t *testing.T) {
|
||||
tests := []testTLV8{
|
||||
{
|
||||
name: "114",
|
||||
value: "AdABAQACFQMBAAEBAAEBAQEBAgIBAAIBAQIBAgMMAQJAAQICtAADAg8AAwwBAkABAgLwAAMCDwADDAECQAECArQAAwIeAAMMAQJAAQIC8AADAh4AAwwBAuABAgIOAQMCHgADDAEC4AECAmgBAwIeAAMMAQKAAgICaAEDAh4AAwwBAoACAgLgAQMCHgADDAECAAQCAkACAwIeAAMMAQIABAICAAMDAh4AAwwBAgAFAgLQAgMCHgADDAECAAUCAsADAwIeAAMMAQKABwICOAQDAh4A",
|
||||
},
|
||||
{
|
||||
name: "115",
|
||||
value: "AQ4BAQMCCQEBAQIBAAMBAgEOAQEDAgkBAQECAQADAQECAQA=",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, test.run)
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ type RTPParams struct {
|
||||
PayloadType uint8 `tlv8:"1"`
|
||||
SSRC uint32 `tlv8:"2"`
|
||||
MaxBitrate uint16 `tlv8:"3"`
|
||||
MinRTCPInterval float32 `tlv8:"4"`
|
||||
RTCPInterval float32 `tlv8:"4"`
|
||||
MaxMTU []uint16 `tlv8:"5"`
|
||||
ComfortNoisePayloadType []uint8 `tlv8:"6"`
|
||||
}
|
||||
|
||||
@@ -32,19 +32,19 @@ func NewStream(
|
||||
|
||||
videoCodec.RTPParams = []RTPParams{
|
||||
{
|
||||
PayloadType: 99,
|
||||
SSRC: videoSession.Local.SSRC,
|
||||
MaxBitrate: 299,
|
||||
MinRTCPInterval: 0.5,
|
||||
MaxMTU: []uint16{1378},
|
||||
PayloadType: 99,
|
||||
SSRC: videoSession.Local.SSRC,
|
||||
MaxBitrate: 299,
|
||||
RTCPInterval: 0.5,
|
||||
MaxMTU: []uint16{1378},
|
||||
},
|
||||
}
|
||||
audioCodec.RTPParams = []RTPParams{
|
||||
{
|
||||
PayloadType: 110,
|
||||
SSRC: audioSession.Local.SSRC,
|
||||
MaxBitrate: 24,
|
||||
MinRTCPInterval: 5,
|
||||
PayloadType: 110,
|
||||
SSRC: audioSession.Local.SSRC,
|
||||
MaxBitrate: 24,
|
||||
RTCPInterval: 5,
|
||||
|
||||
ComfortNoisePayloadType: []uint8{13},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user