mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2026-04-22 23:57:20 +08:00
1856b7ace4
The HKSV recording was failing because: 1. The dataSend.data message structure was wrong - `packets` was a flat integer instead of an array of objects with `data` and `metadata` fields matching the HAP-NodeJS specification 2. Each video/audio frame was sent as a separate mediaFragment, but Home Hub expects GOP-based fragments (~2-4 seconds of accumulated data) 3. Large fragments were not chunked (max 256 KiB per chunk) Changes: - Fix HDS dataSend.data message structure to use proper packets array with nested data/metadata (dataType, dataSequenceNumber, dataChunkSequenceNumber, isLastDataChunk, dataTotalSize) - Add 256 KiB chunking for large media fragments - Buffer moof+mdat pairs in hksvConsumer and flush on keyframe boundaries (GOP-based fragmentation) - Pre-start consumer at pair-verify for instant init segment delivery - Add write-response support to HAP PUT handler for ch131 DataStream setup - Fix HAP service linking to match HAP-NodeJS reference - Add default SelectedCameraRecordingConfiguration (ch209) value - Start continuous motion generator at pair-verify with dedup protection
238 lines
5.5 KiB
Go
238 lines
5.5 KiB
Go
package camera
|
|
|
|
import (
|
|
"github.com/AlexxIT/go2rtc/pkg/hap"
|
|
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
|
)
|
|
|
|
func ServiceMotionSensor() *hap.Service {
|
|
return &hap.Service{
|
|
Type: "85",
|
|
Characters: []*hap.Character{
|
|
{
|
|
Type: "22",
|
|
Format: hap.FormatBool,
|
|
Value: false,
|
|
Perms: hap.EVPR,
|
|
},
|
|
{
|
|
Type: "75",
|
|
Format: hap.FormatBool,
|
|
Value: true,
|
|
Perms: hap.EVPR,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func ServiceCameraOperatingMode() *hap.Service {
|
|
return &hap.Service{
|
|
Type: "21A",
|
|
Characters: []*hap.Character{
|
|
{
|
|
Type: "21B",
|
|
Format: hap.FormatBool,
|
|
Value: true,
|
|
Perms: hap.EVPRPW,
|
|
},
|
|
{
|
|
Type: "223",
|
|
Format: hap.FormatBool,
|
|
Value: true,
|
|
Perms: hap.EVPRPW,
|
|
},
|
|
{
|
|
Type: "225",
|
|
Format: hap.FormatBool,
|
|
Value: true,
|
|
Perms: hap.EVPRPW,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func ServiceCameraEventRecordingManagement() *hap.Service {
|
|
val205, _ := tlv8.MarshalBase64(SupportedCameraRecordingConfiguration{
|
|
PrebufferLength: 4000,
|
|
EventTriggerOptions: 0x01, // motion
|
|
MediaContainerConfigurations: MediaContainerConfigurations{
|
|
MediaContainerType: 0, // fragmented MP4
|
|
MediaContainerParameters: MediaContainerParameters{
|
|
FragmentLength: 4000,
|
|
},
|
|
},
|
|
})
|
|
|
|
val206, _ := tlv8.MarshalBase64(SupportedVideoRecordingConfiguration{
|
|
CodecConfigs: []VideoRecordingCodecConfiguration{
|
|
{
|
|
CodecType: VideoCodecTypeH264,
|
|
CodecParams: VideoRecordingCodecParameters{
|
|
ProfileID: VideoCodecProfileHigh,
|
|
Level: VideoCodecLevel40,
|
|
Bitrate: 2000,
|
|
IFrameInterval: 4000,
|
|
},
|
|
CodecAttrs: VideoCodecAttributes{Width: 1920, Height: 1080, Framerate: 30},
|
|
},
|
|
{
|
|
CodecType: VideoCodecTypeH264,
|
|
CodecParams: VideoRecordingCodecParameters{
|
|
ProfileID: VideoCodecProfileMain,
|
|
Level: VideoCodecLevel31,
|
|
Bitrate: 1000,
|
|
IFrameInterval: 4000,
|
|
},
|
|
CodecAttrs: VideoCodecAttributes{Width: 1280, Height: 720, Framerate: 30},
|
|
},
|
|
},
|
|
})
|
|
|
|
val207, _ := tlv8.MarshalBase64(SupportedAudioRecordingConfiguration{
|
|
CodecConfigs: []AudioRecordingCodecConfiguration{
|
|
{
|
|
CodecType: AudioRecordingCodecTypeAACLC,
|
|
CodecParams: []AudioRecordingCodecParameters{
|
|
{
|
|
Channels: 1,
|
|
BitrateMode: []byte{AudioCodecBitrateVariable},
|
|
SampleRate: []byte{AudioRecordingSampleRate24Khz, AudioRecordingSampleRate32Khz, AudioRecordingSampleRate48Khz},
|
|
MaxAudioBitrate: []uint32{64},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Default selected recording configuration (Home Hub expects this to persist)
|
|
val209, _ := tlv8.MarshalBase64(SelectedCameraRecordingConfiguration{
|
|
GeneralConfig: SupportedCameraRecordingConfiguration{
|
|
PrebufferLength: 4000,
|
|
EventTriggerOptions: 0x01, // motion
|
|
MediaContainerConfigurations: MediaContainerConfigurations{
|
|
MediaContainerType: 0,
|
|
MediaContainerParameters: MediaContainerParameters{
|
|
FragmentLength: 4000,
|
|
},
|
|
},
|
|
},
|
|
VideoConfig: SupportedVideoRecordingConfiguration{
|
|
CodecConfigs: []VideoRecordingCodecConfiguration{
|
|
{
|
|
CodecType: VideoCodecTypeH264,
|
|
CodecParams: VideoRecordingCodecParameters{
|
|
ProfileID: VideoCodecProfileHigh,
|
|
Level: VideoCodecLevel40,
|
|
Bitrate: 2000,
|
|
IFrameInterval: 4000,
|
|
},
|
|
CodecAttrs: VideoCodecAttributes{Width: 1920, Height: 1080, Framerate: 30},
|
|
},
|
|
},
|
|
},
|
|
AudioConfig: SupportedAudioRecordingConfiguration{
|
|
CodecConfigs: []AudioRecordingCodecConfiguration{
|
|
{
|
|
CodecType: AudioRecordingCodecTypeAACLC,
|
|
CodecParams: []AudioRecordingCodecParameters{
|
|
{
|
|
Channels: 1,
|
|
BitrateMode: []byte{AudioCodecBitrateVariable},
|
|
SampleRate: []byte{AudioRecordingSampleRate24Khz},
|
|
MaxAudioBitrate: []uint32{64},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
return &hap.Service{
|
|
Type: "204",
|
|
Characters: []*hap.Character{
|
|
{
|
|
Type: "B0",
|
|
Format: hap.FormatUInt8,
|
|
Value: 0,
|
|
Perms: hap.EVPRPW,
|
|
},
|
|
{
|
|
Type: TypeSupportedCameraRecordingConfiguration,
|
|
Format: hap.FormatTLV8,
|
|
Value: val205,
|
|
Perms: hap.EVPR,
|
|
},
|
|
{
|
|
Type: TypeSupportedVideoRecordingConfiguration,
|
|
Format: hap.FormatTLV8,
|
|
Value: val206,
|
|
Perms: hap.EVPR,
|
|
},
|
|
{
|
|
Type: TypeSupportedAudioRecordingConfiguration,
|
|
Format: hap.FormatTLV8,
|
|
Value: val207,
|
|
Perms: hap.EVPR,
|
|
},
|
|
{
|
|
Type: TypeSelectedCameraRecordingConfiguration,
|
|
Format: hap.FormatTLV8,
|
|
Value: val209,
|
|
Perms: hap.EVPRPW,
|
|
},
|
|
{
|
|
Type: "226",
|
|
Format: hap.FormatUInt8,
|
|
Value: 0,
|
|
Perms: hap.EVPRPW,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func ServiceDataStreamManagement() *hap.Service {
|
|
val130, _ := tlv8.MarshalBase64(SupportedDataStreamTransportConfiguration{
|
|
Configs: []TransferTransportConfiguration{
|
|
{TransportType: 0}, // TCP
|
|
},
|
|
})
|
|
|
|
return &hap.Service{
|
|
Type: "129",
|
|
Characters: []*hap.Character{
|
|
{
|
|
Type: TypeSupportedDataStreamTransportConfiguration,
|
|
Format: hap.FormatTLV8,
|
|
Value: val130,
|
|
Perms: hap.PR,
|
|
},
|
|
{
|
|
Type: TypeSetupDataStreamTransport,
|
|
Format: hap.FormatTLV8,
|
|
Value: "",
|
|
Perms: []string{"pr", "pw", "wr"},
|
|
},
|
|
{
|
|
Type: "37",
|
|
Format: hap.FormatString,
|
|
Value: "1.0",
|
|
Perms: hap.PR,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func ServiceDoorbell() *hap.Service {
|
|
return &hap.Service{
|
|
Type: "121",
|
|
Characters: []*hap.Character{
|
|
{
|
|
Type: "73",
|
|
Format: hap.FormatUInt8,
|
|
Value: nil,
|
|
Perms: hap.EVPR,
|
|
},
|
|
},
|
|
}
|
|
}
|