homebridge / HAP-NodeJS

Node.js implementation of the HomeKit Accessory Protocol (HAP)
Apache License 2.0
2.68k stars 630 forks source link

Discussion: Current state of IP Cameras (Secure Video & custom RTP implementation) #741

Closed Supereg closed 2 years ago

Supereg commented 4 years ago

I wanted to start a discussion about the current state of IP cameras and some crazy ideas from my side 🙃

At the time of writing our implementation looks like the following

Generally the RTP implementation is a key point for a reliable implementation of IP cameras. I though of starting a project trying to create a custom RTP implementation as required by the HAP spec. Meaning adhering to RFC 3550, RFC 3551, RFC 4585, RFC 5104, multiplexing RTP and RTCP defined in RFC 5761, payload formats for H.264 (RFC 6184) and Opus voice (RFC 7587) and securing the connection according to RFC 3711 and RFC 6188. Then implementing a video/audio pipeline using gstreamer for better bitrate control. I would focus my work mainly on a Raspberry Pi and it's PiCam though keeping the API as abstract as possible.

I wanted to start this discussion to get some feedback from people with more experience, who maybe dissuade me and prevent me wasting my time lol 😅 I currently have a basic understanding of the fundamental RTP RFCs, though no concrete understanding for stuff like SRTP. I also don't know how time intensive this would be, how motivated I can stay and even wether I can manage to invest that much time into this. I just wanted to get my crazy idea out and start a conversation with people, collecting some knowledge and/or experience.

adriancable commented 4 years ago

@Supereg - have spent quite a bit of time on this so will share what I know.

  1. 'reconfigureStream' works fine as far as HAP-NodeJS is concerned. On the ffmpeg side, a small (~ 30 loc) modification is required to ffmpeg.c to handle on-demand bitrate changes on the fly. This works very well.
  2. One way audio also works fine, as long as you are using AAC-ELD, and not Opus. I have Nest cameras with audio working perfectly - no choppy and no breaks. I suspect, but am not sure, that Opus audio support on the HomeKit side is broken (see #4 below).
  3. The way I have tied everything together is implementing an RTSP server on my side, which provides multiplexed audio, video and RTCP streams. If this is done right (most importantly the NTP/RTP timing sync info in the RTCP packets), then the single RTSP stream can be used as an input to ffmpeg, with the outputs to the HomeKit SRTP audio and video UDP ports, and "everything just works".
  4. I have implemented RTSP -> SRTP pipelines using both ffmpeg and gstreamer. They both work similarly well but gstreamer has much higher CPU usage for the decoder/rescaler than ffmpeg. Note that Opus audio is similarly broken up whether using gstreamer or ffmpeg, which suggests to me the implementation issue is on the HomeKit device side.

Hope this helps.

Addendum: I am just waiting for Homebridge to update to using HAP-NodeJS 0.5.x since I need support for bridged cameras. Then I'll publish my updated homebridge-nest plug-in which shows how to do all of the above, so you can crib code from that if you wish.

Supereg commented 4 years ago

Thanks for sharing your experience. I don't think opus transmission is broken on the HomeKit side rather that there is some specialty regarding Opus voice transmission. That's my estimation based on the facts I have.

Is there any particular reason homebridge still stays on 0.4.x 😅. I'm definitely interested checking out work, you got me hooked up (and stopped me probably wasting time. at least for now lol)

dgreif commented 4 years ago

@adriancable I'm glad to see other diving this deep into video streaming with homebridge. I'm the developer of the homebridge-ring plugin and it sounds like we have faced a lot of the same challenges. The video side for Ring was fairly straightforward since they provide an h264 srtp stream which I can proxy to homebridge. For audio, they provide PCMU which I am using ffmpeg to transcode before passing along. I tried both OPUS and AAC-ELD for audio and found that OPUS was very choppy (cut out for 1 second out of every 3 seconds), where AAC-ELD seems to work well all the time. The downside to using AAC-ELD with ffmpeg is it requires a custom built version since most ffmpeg binaries don't include the correct codec. If opus was working better, we could use any of the static ffmpeg installers that are easily accessible from npm (like https://www.npmjs.com/package/@ffmpeg-installer/ffmpeg).

@Supereg I think homebridge could definitely benefit from a set of proxying/transcoding tools to make camera streaming easier for new plugins. Ring uses SIP and provides an srtp stream which I proxy, but most other cameras seem to use RTSP. I have some useful proxying functions which I have built into homebridge-ring, but they are pretty specific to my use case at this point. https://github.com/dgreif/ring/blob/master/api/rtp-utils.ts

adriancable commented 4 years ago

@dgreif - thanks for your note! Good to connect with a fellow HomeKit video "expert" (in as much as any of us are such a thing :-)

I don't like AAC-ELD mainly because of the libfdk-aac licensing issues. Same with x264 but at least they provide common sense exemptions that cover most of us. I would like the option of switching to Opus instead if I could get it working properly. I never got to the bottom of the choppiness issue but it happens with both ffmpeg and gstreamer using libopus. Since libopus is the "sanctioned" codec I suspect the issue is on the HomeKit side, unless we're all just setting some encoder parameters wrong, which is of course possible. (NB: I am led to believe, but have not confirmed myself, that Apple Watch only supports AAC-ELD, and not Opus.)

Interestingly, if you look at the HomeKit enums for audio formats you will see that PCMU is there:

export enum AudioCodecTypes { PCMU = 0x00, PCMA = 0x01, AACELD = 0x02, OPUS = 0x03 }

But, it doesn't seem to work. If you request it (I think I tried PCMU/8000), HomeKit returns asking for Opus I think.

dgreif commented 4 years ago

@adriancable I did try configuring the audio codec with 0x00 and had the same result (it selected opus or AAC-ELD, can't remember which). Here is the list from the official HAP doc: image

I tried playing around with AMR, but didn't have any luck. I'm not sure if it's actually supported yet or if it's a future feature.

adriancable commented 4 years ago

@dgreif - when you requested AMR, did it actually select AMR or did you get back Opus/AAC-ELD?

dgreif commented 4 years ago

@adriancable I just tried 0, 1, 5 and 6 for audio codec type. In all four cases, it looks like HomeKit never does a set on SelectedStreamConfiguration, which I would guess means HomeKit is rejecting the configuration options. Only 2 and 3 (AAC-ELD and Opus) work to get a stream configuration.

Supereg commented 4 years ago

HAP does only support AAC-ELD and Opus. HAP tends to favor opus:

11.8.2 Mandatory Audio RTP service settings

  • An IP camera must support AAC-ELD or Opus at 16K or 24K sample rates.
  • AAC-ELD or Opus must be supported in Variable Bit Rate mode.
  • The block size for AAC-ELD must be 480 samples


NB: I am led to believe, but have not confirmed myself, that Apple Watch only supports AAC-ELD, and not Opus.

Anybody having information of the behavior of the Apple Watch regarding codec selection?

Supereg commented 4 years ago

A little fun fact I wanted to throw in, as I'm currently in the process of reverse engineering secure video and found some stuff in the homed binary. HAP supports the following entries as AudioCodec values. Not listed in the spec are (PCMU, PCMA, MSBC). Oddly only codecs being used is AAC and OPUS.

  case 0:
    pcVar2 = &cf_HMDAudioCodecGroupTypePCMU;
    break;
  case 1:
    pcVar2 = &cf_HMDAudioCodecGroupTypePCMA;
    break;
  case 2:
    pcVar2 = &cf_HMDAudioCodecGroupTypeAACELD;
    break;
  case 3:
    pcVar2 = &cf_HMDAudioCodecGroupTypeOpus;
    break;
  case 4:
    pcVar2 = &cf_HMDAudioCodecGroupTypeMSBC;
    break;
  case 5:
    pcVar2 = &cf_HMDAudioCodecGroupTypeAMR;
    break;
  case 6:
    pcVar2 = &cf_HMDAudioCodecGroupTypeAMRWB;
    break;
adriancable commented 4 years ago

@Supereg - this is a fun fact. Thanks for this!

Based on your work so far, what do you think the chances are of supporting HomeKit Secure Video in HAP-NodeJS?

Supereg commented 4 years ago

That's a tough question, but I would say it is probably possible (I'm not on a dead end, yet lol). Looking into the binary you can get a pretty good understanding of the structure of the new tlv characteristics (somewhat 'easily'). HAP does however deliver the contents of a recording via DataStreams. And currently I think this is the most tricky part. We do not know what protocols and behaviors are expected for the datastream messages (could be harder to get this information, as this is not really easy to extract from the binary) and how exactly the video is delivered (side note. it seems like HAP uses fragmented mp4 with either h264 or h265 as video codec and AAC_ELD or AAC_LC as audio codec [no opus lol]). Also some meanings of the tlv entries are unknown (to me). I would say that if we get any HomeKit secure video enabled cameras into our hands it would be definitely simpler and possible (we can then dig deeper into the behavior part). However those aren't really out yet and secondly they are pretty expensive 😅.

I try to create some place (maybe an issue?, don't know what's best for such things) where I can publish my current findings. Until then I started pushing some experimental code into my fork HAP-NodeJS/secure-video. Nothing fancy yet, just some random experimental stuff 😅

adriancable commented 4 years ago

@Supereg - it's very likely I misunderstand, but I thought that on the accessory side there weren't a great deal of differences. Yes, there is a new UI letting people browse through recorded video, but my understanding was this was just between iCloud and iOS and didn't involve the accessory. My assumption is that the DataStream stuff relates to this, and so it isn't necessary for the accessory to implement any of it. I assumed that the accessory delivered video to the iOS endpoint via RTP as previously, but that data then got sent by the iOS endpoint to iCloud for storage. Is this wrong?

codyc1515 commented 4 years ago

I have been told by a reliable source that the Eve Secure Video Camera will be launching around March - April next year. The same source also told me that the first Secure Video devices will start appearing around that time. I am not so sure if the latter part is correct, but I guess we will see.

@Supereg I have a Netatmo Presence camera which is meant to get HomeKit Secure Video. I also intend to get the Netatmo Video Doorbell when it launches, which is promised to launch with Secure Video. Please let me know, once the update is released, if there is any way that I can help by using my devices to figure out how this stuff works.

@adriancable I don't think this is correct. Although it does not appear to be announced in public yet anywhere in the latest iOS and macOS, as well as probably audioOS and tvOS, releases there is a HomeAI.framework which performs AI functions for HomeKit. It detects animals, people and vehicles. To do this, instead of doing it on the camera, it would need to be streaming to the iOS / macOS device constantly. That is where I believe that the data streams come in to send the stream from the camera to the hub. If you assume that the detection is done on the iOS / macOS device then that device would be constantly processing video which would drain the battery rapidly. So I don't believe it would work this way.

KhaosT commented 4 years ago

Yeah if you check -[HMDCameraAudioParameterSelection _generateAllCombinations:], you can see on homed will only pick AAC-ELD or Opus as audio codec.

For HomeKit Secure Video, it's not as smart as most people imagined... As of right now, think it more like a MobileNetV1 powered notification suppression system with the ability to review clips. On high level, it works as following: when a motion/doorbell press occurs, the accessory starts recording video clip, the (HAP) notification is delivered to HomeKit controllers (iOS/tvOS devices), one of the device will start the HDS to retrieve the clip from the accessory, and analyze the clip to see if there is anything interesting. If certain things are found in the clip (like people, animal, etc), the clip will be saved in iCloud, and a notification will be shown on the iOS device with the clip.

Too bad they went with this route instead of having Apple TV/HomePod to continuously stream video feed from the existing camera profile 😅 For HAP-NodeJS to "support" HomeKit Secure Video, I guess we'll need to implement the clips managing part (like offer hooks for developer to provide clips) and transmission part. Thankfully @Supereg already implemented HDS so we are not starting from zero 🙏

KhaosT commented 4 years ago

On HDS clip transmission side, it's going with file type ipcamera.recording, homed will write over HDS to ask for the clip, and assemble it ^^

KhaosT commented 4 years ago

The clip itself appears to be something like AVFragmentedAsset, wrapped in moov box.

Supereg commented 4 years ago

@adriancable KhaosT pretty much explained how it works. RTP is only used for live streaming (as it is a live streaming protocol). It is limited in quality, max frame rates etc by current network conditions (as it needs to be real time). Recordings can be much better in quality. The camera device will create a recording for a motion or doorbell button event and then send the recording via data stream to the connected controller. The controller would then start analyzing the content (using the HomeAI.framework) and then saving the clip into iCloud (if it detected stuff like persons, animals etc, whatever you have configured in the Home app). So for accessory developers this would mean that they need to implement recordings additionally to support the expected fragmented mp4 format. But let's see what we can abstract in the API.

@codyc1515 Eve will ship a camera? Nice. And I would be more than interested to play around with anyones devices. So it will be well appreciated if you ping when your devices start supporting secure video.

@KhaosT thanks for the hint about the HDS protocol/topic. One keyword more to search for. I currently only found HMDCameraRecordingBulkSendSessionInitiator and HMDCameraRecordingBulkSendDataReadEvent which seem to be related to HDS and secure video, but couldn't dig into it yet.

The great thing is we have already a somewhat reliable implementation of HDS (an implementation according to the spec; why couldn't they announce earlier that they would publish this publicly -.-). And we have the Siri profile which gives us an example of how such a HDS protocol might look like. But let's see where we get.

@adriancable (or anybody) could have a look over at my fork in the IPCamera_accessory.ts and check why the hell HomeKit controllers (iPhone, iPad, Mac) running the latest 13.2.2 won't accept the stream, however older clients will (I remember the same code working on a pi [using h264_omx though] on older iOS versions).

llemtt commented 4 years ago

IIRC HomeKit Secure Video requires a (plugged?) hub to work, so AI analysis could definitely be carried out there, on cameras (but I doubt on those because of very limited resources) or the cloud. Eventually all together but not on mobile clients (battery is always a limiting resource), somehow like Siri works (mobile clients only do audio preprocessing).

Lot of cameras continuously record in a RAM buffer so that on event (motion, button, sensor,...) they can send a videoclip that begins before the event has occurred, this can save a lot of processing.

Supereg commented 4 years ago

Does anybody have still macOS Mojave running and can overseen me the /System/Library/PrivateFrameworks/HomeKitDaemon.framework/Versions/Current/HomeKitDaemon binary. Nothing important, would just be interested to compare some things.

Samfox2 commented 4 years ago

Logitech Circle 2 now supports Homekit secure video: https://blog.logitech.com/2019/11/14/circle-2-now-works-with-apple-homekit-secure-video/

KhaosT commented 4 years ago

Here are those configuration related TLVs for recording service from the camera.

supported-camera-recording-configuration

01
04
401F0000 // preBufferLength

02
08
0100000000000000

03 // containerConfigurations
0B

01 // Container
01
00 // HMDCameraRecordingMediaContainerTypeFragmentedMP4

02 // parameters
06

01 // fragmentLength
04
A00F0000 // 4000

info    17:41:45.954050 -0800   homed   [Living Room Circle/368] Updating supported recording configuration to:
info    17:41:45.954143 -0800   homed   [Living Room Circle/368]       {
info    17:41:45.954181 -0800   homed   [Living Room Circle/368]           tlvDatablob = {length = 29, bytes = 0x0104401f 00000208 01000000 00000000 ... 02060104 a00f0000 }
info    17:41:45.954220 -0800   homed   [Living Room Circle/368]           prebufferLength = 8000
info    17:41:45.954240 -0800   homed   [Living Room Circle/368]           eventTriggerOptions = [HMDCameraRecordingEventTriggerOptionsMotion]
info    17:41:45.954490 -0800   homed   [Living Room Circle/368]           containerConfigurations =
info    17:41:45.954510 -0800   homed   [Living Room Circle/368]           [
info    17:41:45.954547 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.954667 -0800   homed   [Living Room Circle/368]               tlvDatablob = {length = 11, bytes = 0x01010002060104a00f0000}
info    17:41:45.954706 -0800   homed   [Living Room Circle/368]               container =
info    17:41:45.954727 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.954937 -0800   homed   [Living Room Circle/368]                   type = HMDCameraRecordingMediaContainerTypeFragmentedMP4
info    17:41:45.954958 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.954977 -0800   homed   [Living Room Circle/368]               parameters =
info    17:41:45.954995 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.955026 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 6, bytes = 0x0104a00f0000}
info    17:41:45.955045 -0800   homed   [Living Room Circle/368]                   fragmentLength = 4000
info    17:41:45.955064 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.955085 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.955115 -0800   homed   [Living Room Circle/368]           ]
info    17:41:45.955133 -0800   homed   [Living Room Circle/368]       }

supported-video-recording-configuration

01
68

01
01
00

02
0B
0101010201000000020102

03
0B
010280070202380403011E

00
00

03
0B
010200050202D00203011E

00
00

03
0B
010280020202680103011E

00
00

03
0B
010280070202380403010F

00
00

03
0B
010200050202D00203010F

00
00

03
0B
010280020202680103010F

info    17:41:45.955246 -0800   homed   [Living Room Circle/368] Updating supported video configuration to:
info    17:41:45.955717 -0800   homed   [Living Room Circle/368]       {
info    17:41:45.955753 -0800   homed   [Living Room Circle/368]           tlvDatablob = {length = 106, bytes = 0x01680101 00020b01 01010201 00000002 ... 02020268 0103010f }
info    17:41:45.955802 -0800   homed   [Living Room Circle/368]           codecConfigurations =
info    17:41:45.955861 -0800   homed   [Living Room Circle/368]           [
info    17:41:45.955911 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.955947 -0800   homed   [Living Room Circle/368]               tlvDatablob = {length = 104, bytes = 0x01010002 0b010101 02010000 00020102 ... 02020268 0103010f }
info    17:41:45.955994 -0800   homed   [Living Room Circle/368]               codec =
info    17:41:45.956059 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.956193 -0800   homed   [Living Room Circle/368]                   type = HMDCameraRecordingVideoCodecTypeH264
info    17:41:45.956220 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.956256 -0800   homed   [Living Room Circle/368]               parameters =
info    17:41:45.956380 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.956443 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 11, bytes = 0x0101010201000000020102}
info    17:41:45.956619 -0800   homed   [Living Room Circle/368]                   h264Profile =
info    17:41:45.956674 -0800   homed   [Living Room Circle/368]                   [
info    17:41:45.956720 -0800   homed   [Living Room Circle/368]                   {
info    17:41:45.956742 -0800   homed   [Living Room Circle/368]                       h264Profile = HMDCameraRecordingH264ProfileTypeMain
info    17:41:45.956763 -0800   homed   [Living Room Circle/368]                   }
info    17:41:45.956809 -0800   homed   [Living Room Circle/368]                   ]
info    17:41:45.956845 -0800   homed   [Living Room Circle/368]                   levels =
info    17:41:45.956894 -0800   homed   [Living Room Circle/368]                   [
info    17:41:45.956912 -0800   homed   [Living Room Circle/368]                   {
info    17:41:45.956957 -0800   homed   [Living Room Circle/368]                       h264Level = HMDCameraRecordingH264LevelType_3_1
info    17:41:45.956997 -0800   homed   [Living Room Circle/368]                   }
info    17:41:45.957065 -0800   homed   [Living Room Circle/368]                   {
info    17:41:45.957199 -0800   homed   [Living Room Circle/368]                       h264Level = HMDCameraRecordingH264LevelType_4
info    17:41:45.957251 -0800   homed   [Living Room Circle/368]                   }
info    17:41:45.957269 -0800   homed   [Living Room Circle/368]                   ]
info    17:41:45.957313 -0800   homed   [Living Room Circle/368]                   bitRate = (null)
info    17:41:45.957332 -0800   homed   [Living Room Circle/368]                   iFrameInterval = (null)
info    17:41:45.957397 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.957434 -0800   homed   [Living Room Circle/368]               attributes =
info    17:41:45.957482 -0800   homed   [Living Room Circle/368]               [
info    17:41:45.957536 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.957615 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 11, bytes = 0x010280070202380403011e}
info    17:41:45.957642 -0800   homed   [Living Room Circle/368]                   imageWidth = 1920
info    17:41:45.957661 -0800   homed   [Living Room Circle/368]                   imageHeight = 1080
info    17:41:45.957679 -0800   homed   [Living Room Circle/368]                   frameRate = 30
info    17:41:45.957723 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.957769 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.957811 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 11, bytes = 0x010200050202d00203011e}
info    17:41:45.957861 -0800   homed   [Living Room Circle/368]                   imageWidth = 1280
info    17:41:45.958059 -0800   homed   [Living Room Circle/368]                   imageHeight = 720
info    17:41:45.958107 -0800   homed   [Living Room Circle/368]                   frameRate = 30
info    17:41:45.958125 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.958144 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.958162 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 11, bytes = 0x010280020202680103011e}
info    17:41:45.958192 -0800   homed   [Living Room Circle/368]                   imageWidth = 640
info    17:41:45.958210 -0800   homed   [Living Room Circle/368]                   imageHeight = 360
info    17:41:45.958231 -0800   homed   [Living Room Circle/368]                   frameRate = 30
info    17:41:45.958374 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.958426 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.958752 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 11, bytes = 0x010280070202380403010f}
info    17:41:45.958773 -0800   homed   [Living Room Circle/368]                   imageWidth = 1920
info    17:41:45.958792 -0800   homed   [Living Room Circle/368]                   imageHeight = 1080
info    17:41:45.958863 -0800   homed   [Living Room Circle/368]                   frameRate = 15
info    17:41:45.958911 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.958950 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.958998 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 11, bytes = 0x010200050202d00203010f}
info    17:41:45.959035 -0800   homed   [Living Room Circle/368]                   imageWidth = 1280
info    17:41:45.959079 -0800   homed   [Living Room Circle/368]                   imageHeight = 720
info    17:41:45.959117 -0800   homed   [Living Room Circle/368]                   frameRate = 15
info    17:41:45.959169 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.959205 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.959253 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 11, bytes = 0x010280020202680103010f}
info    17:41:45.959289 -0800   homed   [Living Room Circle/368]                   imageWidth = 640
info    17:41:45.959334 -0800   homed   [Living Room Circle/368]                   imageHeight = 360
info    17:41:45.959370 -0800   homed   [Living Room Circle/368]                   frameRate = 15
info    17:41:45.959394 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.959554 -0800   homed   [Living Room Circle/368]               ]
info    17:41:45.959573 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.959601 -0800   homed   [Living Room Circle/368]           ]
info    17:41:45.959622 -0800   homed   [Living Room Circle/368]       }

supported-audio-recording-configuration

01
0E

01
01
00

02
09

01
01
01

02
01
00

03
01
03

info    17:41:45.959688 -0800   homed   [Living Room Circle/368] Updating supported audio configuration to:
info    17:41:45.959729 -0800   homed   [Living Room Circle/368]       {
info    17:41:45.959747 -0800   homed   [Living Room Circle/368]           tlvDatablob = {length = 16, bytes = 0x010e0101000209010101020100030103}
info    17:41:45.959775 -0800   homed   [Living Room Circle/368]           codecConfigurations =
info    17:41:45.959794 -0800   homed   [Living Room Circle/368]           [
info    17:41:45.959812 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.960041 -0800   homed   [Living Room Circle/368]               tlvDatablob = {length = 14, bytes = 0x0101000209010101020100030103}
info    17:41:45.960145 -0800   homed   [Living Room Circle/368]               codec =
info    17:41:45.960191 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.960284 -0800   homed   [Living Room Circle/368]                   type = HMDCameraRecordingAudioCodecTypeAACLC
info    17:41:45.960303 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.960413 -0800   homed   [Living Room Circle/368]               parameters =
info    17:41:45.960500 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.960548 -0800   homed   [Living Room Circle/368]                   tlvDatablob = {length = 9, bytes = 0x010101020100030103}
info    17:41:45.960568 -0800   homed   [Living Room Circle/368]                   channels = 1
info    17:41:45.960621 -0800   homed   [Living Room Circle/368]                   bitRateModes =
info    17:41:45.960680 -0800   homed   [Living Room Circle/368]                   [
info    17:41:45.960705 -0800   homed   [Living Room Circle/368]                   {
info    17:41:45.960721 -0800   homed   [Living Room Circle/368]                       type = HMDCameraRecordingAudioBitRateModeTypeVariable
info    17:41:45.960737 -0800   homed   [Living Room Circle/368]                   }
info    17:41:45.960765 -0800   homed   [Living Room Circle/368]                   ]
info    17:41:45.960797 -0800   homed   [Living Room Circle/368]                   audioSampleRates =
info    17:41:45.960821 -0800   homed   [Living Room Circle/368]                   [
info    17:41:45.960878 -0800   homed   [Living Room Circle/368]                   {
info    17:41:45.960923 -0800   homed   [Living Room Circle/368]                       type = HMDCameraRecordingAudioSampleRateType_32
info    17:41:45.960988 -0800   homed   [Living Room Circle/368]                   }
info    17:41:45.961027 -0800   homed   [Living Room Circle/368]                   ]
info    17:41:45.961043 -0800   homed   [Living Room Circle/368]                   maxAudioBitRate = (null)
info    17:41:45.961083 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.961116 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.961155 -0800   homed   [Living Room Circle/368]           ]
info    17:41:45.961200 -0800   homed   [Living Room Circle/368]       }

info    17:41:45.961292 -0800   homed   [Living Room Circle/368] Preferred prebuffer length is: 4000 accessory supports pre-buffer length: 8000
info    17:41:45.961486 -0800   homed   [Living Room Circle/368] Preferred video parameters: (
    "resolution: HMDCameraRecordingVideoResolutionType1920x1080px, frame rate: 30fps, bit rate: 800kbps key frame interval: 4000ms h264 level: HMDCameraRecordingH264LevelType_4",
    "resolution: HMDCameraRecordingVideoResolutionType1920x1080px, frame rate: 24fps, bit rate: 800kbps key frame interval: 4000ms h264 level: HMDCameraRecordingH264LevelType_4",
    "resolution: HMDCameraRecordingVideoResolutionType1920x1080px, frame rate: 15fps, bit rate: 500kbps key frame interval: 4000ms h264 level: HMDCameraRecordingH264LevelType_4",
    "resolution: HMDCameraRecordingVideoResolutionType1280x720px, frame rate: 30fps, bit rate: 800kbps key frame interval: 4000ms h264 level: HMDCameraRecordingH264LevelType_3_1",
    "resolution: HMDCameraRecordingVideoResolutionType1280x720px, frame rate: 24fps, bit rate: 800kbps key frame interval: 4000ms h264 level: HMDCameraRecordingH264LevelType_3_1",
    "resolution: HMDCameraRecordingVideoResolutionType1280x720px, frame rate: 15fps, bit rate: 500kbps key frame interval: 4000ms h264 level: HMDCameraRecordingH264LevelType_3_1"
)
info    17:41:45.961554 -0800   homed   [Living Room Circle/368] Preferred audio parameters: (
    "sample rate: HMDCameraRecordingAudioSampleRateType_32kHz bit rate: 64kbps",
    "sample rate: HMDCameraRecordingAudioSampleRateType_44_1kHz bit rate: 64kbps",
    "sample rate: HMDCameraRecordingAudioSampleRateType_48kHz bit rate: 64kbps",
    "sample rate: HMDCameraRecordingAudioSampleRateType_24kHz bit rate: 32kbps",
    "sample rate: HMDCameraRecordingAudioSampleRateType_16kHz bit rate: 16kbps"
)

selected-camera-recording-configuration
----
01 // General Configuration
1D

01 // prebufferLength
04
A00F0000 // 4000

02 // eventTriggerOptions
08
0100000000000000 // HMDCameraRecordingEventTriggerOptionsMotion

03 // containerConfigurations
0B

01 // container type
01
00 // HMDCameraRecordingMediaContainerTypeFragmentedMP4

02 // params
06

01 // fragmentLength
04
A00F0000 
----
02 // Video Configuration
24

01 // Codec
01
00 // H264

02 // Params
12

01 // Profile
01
01

02 // Level
01
02

03 // bitRate
04
20030000

04 // iFrameInterval
04
A00F0000

03 // attributes
0B

01 // width
02
8007 // 1920

02 // height
02
3804 // 1080

03 //frameRate
01
1E // 30
----
03 // Audio Configuration
14

01 // Codec
01
00 // HMDCameraRecordingAudioCodecTypeAACLC

02 // parameters
0F

01 // channels
01
01 // 1

02 // bitRateModes
01
00 // HMDCameraRecordingAudioBitRateModeTypeVariable

03 // audioSampleRates
01
03 // HMDCameraRecordingAudioSampleRateType_32

04 // maxAudioBitRate
04
40000000 // 64

info    17:41:45.961716 -0800   homed   [Living Room Circle/368] Setting selected general configuration:
info    17:41:45.961760 -0800   homed   [Living Room Circle/368]       {
info    17:41:45.961810 -0800   homed   [Living Room Circle/368]           tlvDatablob = (null)
info    17:41:45.961862 -0800   homed   [Living Room Circle/368]           prebufferLength = 4000
info    17:41:45.961898 -0800   homed   [Living Room Circle/368]           eventTriggerOptions = [HMDCameraRecordingEventTriggerOptionsMotion]
info    17:41:45.961922 -0800   homed   [Living Room Circle/368]           containerConfigurations =
info    17:41:45.961970 -0800   homed   [Living Room Circle/368]           [
info    17:41:45.962035 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.962077 -0800   homed   [Living Room Circle/368]               tlvDatablob = (null)
info    17:41:45.962122 -0800   homed   [Living Room Circle/368]               container =
info    17:41:45.962147 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.962183 -0800   homed   [Living Room Circle/368]                   type = HMDCameraRecordingMediaContainerTypeFragmentedMP4
info    17:41:45.962242 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.962276 -0800   homed   [Living Room Circle/368]               parameters =
info    17:41:45.962356 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.962436 -0800   homed   [Living Room Circle/368]                   tlvDatablob = (null)
info    17:41:45.962477 -0800   homed   [Living Room Circle/368]                   fragmentLength = 4000
info    17:41:45.962501 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.962577 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.962620 -0800   homed   [Living Room Circle/368]           ]
info    17:41:45.962637 -0800   homed   [Living Room Circle/368]       }

info    17:41:45.962893 -0800   homed   [Living Room Circle/368] Setting selected video configuration:
info    17:41:45.962929 -0800   homed   [Living Room Circle/368]       {
info    17:41:45.962975 -0800   homed   [Living Room Circle/368]           tlvDatablob = (null)
info    17:41:45.963055 -0800   homed   [Living Room Circle/368]           codec =
info    17:41:45.963089 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.963129 -0800   homed   [Living Room Circle/368]               type = HMDCameraRecordingVideoCodecTypeH264
info    17:41:45.963161 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.963211 -0800   homed   [Living Room Circle/368]           parameters =
info    17:41:45.963233 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.963267 -0800   homed   [Living Room Circle/368]               tlvDatablob = (null)
info    17:41:45.963300 -0800   homed   [Living Room Circle/368]               h264Profile =
info    17:41:45.963334 -0800   homed   [Living Room Circle/368]               [
info    17:41:45.963350 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.963375 -0800   homed   [Living Room Circle/368]                   h264Profile = HMDCameraRecordingH264ProfileTypeMain
info    17:41:45.963392 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.963408 -0800   homed   [Living Room Circle/368]               ]
info    17:41:45.963454 -0800   homed   [Living Room Circle/368]               levels =
info    17:41:45.963470 -0800   homed   [Living Room Circle/368]               [
info    17:41:45.963502 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.963562 -0800   homed   [Living Room Circle/368]                   h264Level = HMDCameraRecordingH264LevelType_4
info    17:41:45.963603 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.963635 -0800   homed   [Living Room Circle/368]               ]
info    17:41:45.963653 -0800   homed   [Living Room Circle/368]               bitRate = 800
info    17:41:45.963681 -0800   homed   [Living Room Circle/368]               iFrameInterval = 4000
info    17:41:45.963715 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.963731 -0800   homed   [Living Room Circle/368]           attributes =
info    17:41:45.963754 -0800   homed   [Living Room Circle/368]           [
info    17:41:45.963863 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.963936 -0800   homed   [Living Room Circle/368]               tlvDatablob = (null)
info    17:41:45.963956 -0800   homed   [Living Room Circle/368]               imageWidth = 1920
info    17:41:45.963972 -0800   homed   [Living Room Circle/368]               imageHeight = 1080
info    17:41:45.963990 -0800   homed   [Living Room Circle/368]               frameRate = 30
info    17:41:45.964221 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.964237 -0800   homed   [Living Room Circle/368]           ]
info    17:41:45.964256 -0800   homed   [Living Room Circle/368]       }

info    17:41:45.964272 -0800   homed   [Living Room Circle/368] Setting selected audio configuration:
info    17:41:45.964299 -0800   homed   [Living Room Circle/368]       {
info    17:41:45.964315 -0800   homed   [Living Room Circle/368]           tlvDatablob = (null)
info    17:41:45.964332 -0800   homed   [Living Room Circle/368]           codec =
info    17:41:45.964348 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.964393 -0800   homed   [Living Room Circle/368]               type = HMDCameraRecordingAudioCodecTypeAACLC
info    17:41:45.964416 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.964437 -0800   homed   [Living Room Circle/368]           parameters =
info    17:41:45.964454 -0800   homed   [Living Room Circle/368]           {
info    17:41:45.964484 -0800   homed   [Living Room Circle/368]               tlvDatablob = (null)
info    17:41:45.964521 -0800   homed   [Living Room Circle/368]               channels = 1
info    17:41:45.964543 -0800   homed   [Living Room Circle/368]               bitRateModes =
info    17:41:45.964564 -0800   homed   [Living Room Circle/368]               [
info    17:41:45.964592 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.964631 -0800   homed   [Living Room Circle/368]                   type = HMDCameraRecordingAudioBitRateModeTypeVariable
info    17:41:45.964657 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.964677 -0800   homed   [Living Room Circle/368]               ]
info    17:41:45.964708 -0800   homed   [Living Room Circle/368]               audioSampleRates =
info    17:41:45.964728 -0800   homed   [Living Room Circle/368]               [
info    17:41:45.964766 -0800   homed   [Living Room Circle/368]               {
info    17:41:45.964790 -0800   homed   [Living Room Circle/368]                   type = HMDCameraRecordingAudioSampleRateType_32
info    17:41:45.964822 -0800   homed   [Living Room Circle/368]               }
info    17:41:45.964839 -0800   homed   [Living Room Circle/368]               ]
info    17:41:45.964855 -0800   homed   [Living Room Circle/368]               maxAudioBitRate = 64
info    17:41:45.964892 -0800   homed   [Living Room Circle/368]           }
info    17:41:45.964920 -0800   homed   [Living Room Circle/368]       }
KhaosT commented 4 years ago

And the attribute database:

{
  "accessories": [
    {
      "aid": 1,
      "services": [
        {
          "linked": [],
          "characteristics": [
            {
              "perms": [
                "pw"
              ],
              "type": "14",
              "iid": 2,
              "format": "bool"
            },
            {
              "ev": false,
              "value": "Logitech",
              "perms": [
                "pr"
              ],
              "type": "20",
              "iid": 3,
              "format": "string"
            },
            {
              "ev": false,
              "value": "Circle 2",
              "perms": [
                "pr"
              ],
              "type": "21",
              "iid": 4,
              "format": "string"
            },
            {
              "ev": false,
              "value": "Circle 2 8628",
              "perms": [
                "pr"
              ],
              "type": "23",
              "iid": 5,
              "format": "string"
            },
            {
              "ev": false,
              "value": "",
              "perms": [
                "pr"
              ],
              "type": "30",
              "iid": 6,
              "format": "string"
            },
            {
              "ev": false,
              "value": "8.0.48",
              "perms": [
                "pr"
              ],
              "type": "52",
              "iid": 7,
              "format": "string"
            },
            {
              "ev": false,
              "value": "8",
              "perms": [
                "pr"
              ],
              "type": "53",
              "iid": 8,
              "format": "string"
            },
            {
              "ev": false,
              "value": "3.0;17A97",
              "perms": [
                "pr",
                "hd"
              ],
              "type": "34AB8811-AC7F-4340-BAC3-FD6A85F9943B",
              "iid": 289,
              "format": "string"
            }
          ],
          "primary": false,
          "type": "3E",
          "iid": 1,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "ev": false,
              "value": "1.1.0",
              "perms": [
                "pr"
              ],
              "type": "37",
              "iid": 307,
              "format": "string"
            }
          ],
          "primary": false,
          "type": "A2",
          "iid": 305,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "format": "uint8",
              "iid": 322,
              "perms": [
                "pr",
                "pw",
                "ev",
                "tw"
              ],
              "maxValue": 1,
              "minValue": 0,
              "value": 1,
              "type": "B0",
              "ev": false,
              "minStep": 1
            },
            {
              "ev": false,
              "value": "AQEA",
              "perms": [
                "pr",
                "ev"
              ],
              "type": "120",
              "iid": 10,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "AX0BAQACEQEBAQIBAAAAAgECAwEABAEAAwsBAgAFAgLQAgMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAuABAgIOAQMBHgAAAwsBAkABAgK0AAMBHgAAAwsBAoACAgLgAQMBHgAAAwsBAuABAgJoAQMBHgAAAwsBAkABAgLwAAMBHg==",
              "perms": [
                "pr"
              ],
              "type": "114",
              "iid": 11,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "ARMBAQMCDgEBAQIBAAMBAAAAAwEBAgEA",
              "perms": [
                "pr"
              ],
              "type": "115",
              "iid": 12,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "AgEAAAACAQEAAAIBAg==",
              "perms": [
                "pr"
              ],
              "type": "116",
              "iid": 13,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "",
              "perms": [
                "pr",
                "pw"
              ],
              "type": "118",
              "iid": 14,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "",
              "perms": [
                "pr",
                "pw"
              ],
              "type": "117",
              "iid": 15,
              "format": "tlv8"
            }
          ],
          "primary": true,
          "type": "110",
          "iid": 9,
          "hidden": false
        },
        {
          "linked": [
            352,
            40
          ],
          "characteristics": [
            {
              "format": "uint8",
              "iid": 369,
              "perms": [
                "pr",
                "pw",
                "ev",
                "tw"
              ],
              "maxValue": 1,
              "minValue": 0,
              "value": 0,
              "type": "B0",
              "ev": false,
              "minStep": 1
            },
            {
              "ev": false,
              "value": "AQRAHwAAAggBAAAAAAAAAAMLAQEAAgYBBKAPAAA=",
              "perms": [
                "pr",
                "ev"
              ],
              "type": "205",
              "iid": 370,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "AWgBAQACCwEBAQIBAAAAAgECAwsBAoAHAgI4BAMBHgAAAwsBAgAFAgLQAgMBHgAAAwsBAoACAgJoAQMBHgAAAwsBAoAHAgI4BAMBDwAAAwsBAgAFAgLQAgMBDwAAAwsBAoACAgJoAQMBDw==",
              "perms": [
                "pr",
                "ev"
              ],
              "type": "206",
              "iid": 371,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "AQ4BAQACCQEBAQIBAAMBAw==",
              "perms": [
                "pr",
                "ev"
              ],
              "type": "207",
              "iid": 372,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "",
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "type": "209",
              "iid": 373,
              "format": "tlv8"
            },
            {
              "format": "uint8",
              "iid": 374,
              "perms": [
                "pr",
                "pw",
                "ev",
                "tw"
              ],
              "maxValue": 1,
              "minValue": 0,
              "value": 0,
              "type": "226",
              "ev": false,
              "minStep": 1
            }
          ],
          "primary": false,
          "type": "204",
          "iid": 368,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "ev": false,
              "value": "Microphone",
              "perms": [
                "pr"
              ],
              "type": "23",
              "iid": 257,
              "format": "string"
            },
            {
              "ev": false,
              "value": 1,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "type": "11A",
              "iid": 35,
              "format": "bool"
            },
            {
              "format": "uint8",
              "unit": "percentage",
              "iid": 36,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "maxValue": 100,
              "minValue": 0,
              "value": 100,
              "type": "119",
              "ev": false,
              "minStep": 1
            }
          ],
          "primary": false,
          "type": "112",
          "iid": 34,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "ev": false,
              "value": "Speaker",
              "perms": [
                "pr"
              ],
              "type": "23",
              "iid": 273,
              "format": "string"
            },
            {
              "ev": false,
              "value": 0,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "type": "11A",
              "iid": 38,
              "format": "bool"
            },
            {
              "format": "uint8",
              "unit": "percentage",
              "iid": 39,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "maxValue": 100,
              "minValue": 0,
              "value": 100,
              "type": "119",
              "ev": false,
              "minStep": 1
            }
          ],
          "primary": false,
          "type": "113",
          "iid": 37,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "ev": false,
              "value": 1,
              "perms": [
                "pr",
                "ev"
              ],
              "type": "22",
              "iid": 41,
              "format": "bool"
            },
            {
              "ev": false,
              "value": 1,
              "perms": [
                "pr",
                "ev"
              ],
              "type": "75",
              "iid": 42,
              "format": "bool"
            }
          ],
          "primary": false,
          "type": "85",
          "iid": 40,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "ev": false,
              "value": "AQMBAQA=",
              "perms": [
                "pr"
              ],
              "type": "130",
              "iid": 353,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "",
              "perms": [
                "pr",
                "pw",
                "wr"
              ],
              "type": "131",
              "iid": 354,
              "format": "tlv8"
            },
            {
              "ev": false,
              "value": "1.0",
              "perms": [
                "pr"
              ],
              "type": "37",
              "iid": 355,
              "format": "string"
            }
          ],
          "primary": false,
          "type": "129",
          "iid": 352,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "format": "string",
              "iid": 936,
              "perms": [
                "pr"
              ],
              "value": "Operating Mode (8628)",
              "type": "23",
              "ev": false,
              "description": "Operating Mode"
            },
            {
              "format": "uint8",
              "iid": 949,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "maxValue": 1,
              "minValue": 0,
              "value": 1,
              "type": "223",
              "ev": false,
              "description": "Event Snapshots Active",
              "minStep": 1
            },
            {
              "format": "uint8",
              "iid": 950,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "maxValue": 1,
              "minValue": 0,
              "value": 1,
              "type": "225",
              "ev": false,
              "description": "Periodic Snapshots Active",
              "minStep": 1
            },
            {
              "format": "uint8",
              "iid": 947,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "maxValue": 1,
              "minValue": 0,
              "value": 1,
              "type": "21B",
              "ev": false,
              "description": "Homekit Active",
              "minStep": 1
            },
            {
              "format": "uint8",
              "iid": 948,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "maxValue": 1,
              "minValue": 0,
              "value": 1,
              "type": "21D",
              "ev": false,
              "description": "Mode Indicator",
              "minStep": 1
            },
            {
              "format": "bool",
              "iid": 945,
              "perms": [
                "pr",
                "pw",
                "ev"
              ],
              "value": 1,
              "type": "11B",
              "ev": false,
              "description": "\"Night Vision LED\""
            }
          ],
          "primary": false,
          "type": "21A",
          "iid": 928,
          "hidden": false
        },
        {
          "linked": [],
          "characteristics": [
            {
              "format": "uint8",
              "iid": 1041,
              "perms": [
                "pr",
                "ev"
              ],
              "maxValue": 100,
              "minValue": 0,
              "value": 85,
              "type": "84E14E8A-CF28-3DA8-A949-C505248B9CD9",
              "ev": false,
              "description": "WiFi Signal Strength",
              "minStep": 1
            },
            {
              "format": "string",
              "maxLen": 4,
              "iid": 1042,
              "perms": [
                "pr"
              ],
              "value": "XZ",
              "type": "2C063F78-398F-DBB0-9E48-8F2DC1FBDA2E",
              "ev": false,
              "description": "WiFi Country Code"
            },
            {
              "format": "uint16",
              "iid": 1046,
              "perms": [
                "pr",
                "ev"
              ],
              "value": 134,
              "type": "E9DB48C2-954C-4BBC-8A4B-43BD90131F81",
              "ev": false,
              "description": "WiFi Channel"
            },
            {
              "format": "uint8",
              "iid": 1043,
              "perms": [
                "pr",
                "pw"
              ],
              "maxValue": 100,
              "minValue": 0,
              "value": 60,
              "type": "27BE48D7-AB48-1684-1540-1F1E3C9BA0FA",
              "ev": false,
              "description": "Motion Detector Sensitivity",
              "minStep": 1
            },
            {
              "description": "Submit Diagnostics to Logitech",
              "perms": [
                "pw"
              ],
              "type": "F55B9EB2-6120-74B9-544C-D40A55E5CC5A",
              "iid": 1044,
              "format": "bool"
            },
            {
              "format": "bool",
              "iid": 1045,
              "perms": [
                "pr",
                "pw"
              ],
              "value": 0,
              "type": "A9896218-6661-D7B1-954D-7CDEEAB302D8",
              "ev": false,
              "description": "Persistent Log"
            },
            {
              "format": "string",
              "maxLen": 18,
              "iid": 1047,
              "perms": [
                "pr"
              ],
              "value": "-",
              "type": "3BF3E754-1DE6-BA9E-6243-D25C272A72F5",
              "ev": false,
              "description": "MAC Address"
            },
            {
              "description": "Check for New Firmware",
              "perms": [
                "pw"
              ],
              "type": "AB9B9865-4D5C-42AB-0F4B-C5F0EC84E5EB",
              "iid": 1048,
              "format": "bool"
            }
          ],
          "primary": false,
          "type": "68163DDE-599E-C8BC-7B45-77DA153E1AE7",
          "iid": 1025,
          "hidden": false
        }
      ]
    }
  ]
}
Supereg commented 4 years ago

Thanks 🙏. TLV structure was pretty much figured out, only thing was length of number based types. But that's gonna be solved now.

Any chance that you could check who sends a HDS message first if you connect to the circle via a DataStream? So if there are any session initiator messages, and if they possibly come from the circle? Additionally pretty much any information about HDS would be helpful.

KhaosT commented 4 years ago

For HDS part, it might be easier to spin up a dummy accessory and see what HomeKit Controller send to accessory. Based on logging messages, it looks like controller will initiate bulk send, and accessory will send fragmented MP4 over that session, then be done with it...

info 23:47:28.575941 -0800 homed [Apartment/Circle 2 8628] Starting bulk session with identifier: 541 debug 23:47:28.576146 -0800 homed [Apartment/Circle 2 8628] DataStream Sending Message debug 23:47:28.577567 -0800 homed [Apartment/Circle 2 8628] DataStream Socket wrote bytes. debug 23:47:28.591800 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 22 bytes debug 23:47:28.613742 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 62 bytes info 23:47:28.613781 -0800 homed [Apartment/Circle 2 8628] Received bulk send open response for session with identifier: 541 error: (null) debug 23:47:28.692997 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 35 bytes debug 23:47:28.693619 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 1294 bytes debug 23:47:28.715279 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 35 bytes debug 23:47:28.817725 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262292 bytes debug 23:47:28.827828 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 26 bytes debug 23:47:28.886049 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 162143 bytes debug 23:47:31.503115 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 35 bytes debug 23:47:31.613221 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262327 bytes debug 23:47:31.680872 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 187467 bytes debug 23:47:35.500003 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 35 bytes debug 23:47:35.604199 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262327 bytes debug 23:47:35.681335 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 192514 bytes debug 23:47:39.502167 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 26 bytes debug 23:47:39.609214 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262336 bytes debug 23:47:39.797597 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262548 bytes debug 23:47:39.811016 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 39 bytes debug 23:47:43.496608 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 35 bytes debug 23:47:43.664800 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262548 bytes debug 23:47:43.720495 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 118650 bytes debug 23:47:47.514769 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 27 bytes debug 23:47:47.670506 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262556 bytes debug 23:47:47.733777 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 134518 bytes debug 23:47:51.498487 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 35 bytes debug 23:47:51.645516 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262327 bytes debug 23:47:51.714287 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 160157 bytes debug 23:47:55.502361 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 26 bytes debug 23:47:55.621022 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262336 bytes debug 23:47:55.702399 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 198742 bytes debug 23:47:59.511952 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 35 bytes debug 23:47:59.635583 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 262293 bytes debug 23:47:59.639965 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 25 bytes debug 23:47:59.730637 -0800 homed [Apartment/Circle 2 8628] DataStream Socket read 201965 bytes info 23:48:00.433083 -0800 homed [Apartment/Circle 2 8628] Sending 'close' for sid=541 with reason 0 debug 23:48:00.434078 -0800 homed [Apartment/Circle 2 8628] DataStream Sending Message debug 23:48:00.444047 -0800 homed [Apartment/Circle 2 8628] DataStream Socket wrote bytes.

info 23:47:28.574852 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Created recording session with identifier: 7F712FC4-BD99-40D5-8FF9-08FC889B6783 and context manager with identifier: 6DD5BE61-D4B9-53AB-863D-7D2F23731617 info 23:47:28.575133 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Starting bulk send session info 23:47:28.613999 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Created bulk send session: <HMDDataStreamBulkSendSession: 0x105d0c580> debug 23:47:28.693886 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaInitialization, Sequence Number: <HMDStreamDataSequenceNumber, Value: 1>, Data Length: 1142> debug 23:47:28.889538 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 2>, Data Length: 424130> debug 23:47:31.685710 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 3>, Data Length: 449463> debug 23:47:35.684468 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 4>, Data Length: 454510> debug 23:47:39.817153 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 5>, Data Length: 524403> debug 23:47:43.729171 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 6>, Data Length: 380867> debug 23:47:47.736974 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 7>, Data Length: 396735> debug 23:47:51.718627 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 8>, Data Length: 422153> debug 23:47:55.706338 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 9>, Data Length: 460738> debug 23:47:59.734779 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Assembled fragment: <HMDStreamData, Type: mediaFragment, Sequence Number: <HMDStreamDataSequenceNumber, Value: 10>, Data Length: 463952> info 23:48:00.430214 -0800 homed [Apartment/Circle 2 8628/6DD5BE61-D4B9-53AB-863D-7D2F23731617] Closing current session with reason: 0

Supereg commented 4 years ago

Hm okay, then I just didn't have a correct setup running, since I didn't receive anything over HDS. Checking again later. Thanks

KhaosT commented 4 years ago

@Supereg Make sure you have a Home Hub in the account you are using. Only Home Hub will try to establish HDS connection in this case.

Supereg commented 4 years ago

HDS connection gets established. Just nothing is sent (except the hello message). But will try to copy above tlv data and try again.

KhaosT commented 4 years ago

On HAP side, did you get the selected-camera-recording-configuration write? After that have you tried to trigger a motion event and see what homed does?

Supereg commented 4 years ago

When exactly should the selected-camera-recording-configuration occurs, every time motion is detected? I think somewhere there lies my issues. You have already a version up and running? 😅

KhaosT commented 4 years ago

The actual payload over HDS seems like

{
  "metadata": {
    "dataSequenceNumber": 1,
    "dataType": "mediaInitialization", // or mediaFragment
    "isLastDataChunk": false,
    "dataChunkSequenceNumber": 1
  },
  "data": ""
}

I haven't write anything to actually try it. Write to selected-camera-recording-configuration happens after user modify the camera settings to allow recording.

KhaosT commented 4 years ago

It's a little unclear how the system deal with back pressure, or multiple motion happened within a short period of time 😝

Supereg commented 4 years ago

Does the HDS controller initiate a session for every motion detected? I assumed this would be similar to the Siri profile that the accessory opens a dataSend whenever there is a recording and decides by itself what it counts at motion.

Supereg commented 4 years ago

Okay I got something working. You will need to link the CameraRecording service to the DataStreamMangement service and to the motion sensor (I did not have the second one).

When pairing and enabling the camera to "stream and record" the selected configuration will be set.

On every motion detection the HDS controller will send the following HDS Request

header: //requestId, starting by zero
{
    "protocol": "dataSend",
    "topic": "open",
    "id": 0
}
message: //streamId, starting by 1
{
    "type": "ipcamera.recording",
    "target": "controller",
    "streamId": 1
}

I would assume we have to send a similar response as the one we would receive for the Siri profile

EDIT: I found my issue why selected configuration wouldn't be set. HAP spec seems to change the way tlv arrays/lists are encoded specifically for the recording stuff. Lists like h264 levels and profiles or the resolutions array need to be separated by an tlv of type "0x00" and length "0x00". So between every entry of the same type there needs to an "0x0000" encoded. The supportedVideoConfigurations tlv from the normal RTSPManagement just appends every single entry

Supereg commented 4 years ago

The actual payload over HDS seems like

{
  "metadata": {
    "dataSequenceNumber": 1,
    "dataType": "mediaInitialization", // or mediaFragment
    "isLastDataChunk": false,
    "dataChunkSequenceNumber": 1
  },
  "data": ""
}

@KhaosT how reliable are your findings regarding the HDS dataSend payload structure?

Supereg commented 4 years ago

Okay digging through some code I've got some assumptions. It seems like the dataSend data event, shares the overall structure with the Siri audio profile (which would make sense).

So a event message using the protocol dataSend and topic data would have the following structure, where packets is an array of dictionaries. The dictionaries seem to be specific to the profile.

{
    streamId: "...",
    packets: [
        ...
    ],
    endOfStream: false/true
}

For the individual packets entries I think the following is expected. Following the code it seems like HMD will handle the first chunk differently and seems to expect a format like this:

{
    sequenceNumber: 1,
    metadata: {
        dataType: "..." // mediaInitialization or mediaFragment ??
    },
    data: ..., // probably some binary data
    isLast: true/false
}

For the following chunks (sequenceNumber > 1) a format like this seems to be expected:

{
    metadata: {
        isLastDataChunk: true/false,
        dataChunkSequenceNumber: x
    },
    data: ...
}

The two above where taken out of HMDInitialStreamDataChunk and HMDStreamDataChunk Additionally there is HMDStreamDataSequenceNumber which has the same method signature and would parse a data dictionary the following way:

{
    metadata: {
        dataSequenceNumber: x,
    }
}

I'm not quite sure of I'm on the correct path. It seems the format used is a combination of the three above. I also noticed an error message saying "Asked to add non-last stream data chunk with maximum allowed sequence number: %@". Which seems to imply that the amount of chunks is sent somehow in the first event message maybe?

Supereg commented 4 years ago

Short question. Does anybody know if we can run a pairing sequence with an MFI certified HAP device? So can we write our custom client? Or is the MFI challenge a verification for both participants?

KhaosT commented 4 years ago

@Supereg what do you mean? Like proxy MFi challenge to a real device?

Supereg commented 4 years ago

yeah or rather do not request the MFI challenge at all?

Supereg commented 4 years ago

Straight from the category „WTF, 10/10 thought this would never happen“. ADK is now open source 🥳 https://github.com/apple/HomeKitADK Except the interesting parts (of course) concerning secure video and HDS in general are kept private, when checking the sources.

Nonetheless I'm making some progress with secure video (would have been really disappointing, when after all that work, Apple just released the spec lol 🌚🙃). Maybe I can show the first results in one to two weeks (no definite schedule, just a classic Soon™). Have some happy holidays and a good start into the new year 🥂

llemtt commented 4 years ago

https://www.apple.com/newsroom/2019/12/amazon-apple-google-and-the-zigbee-alliance-to-develop-connectivity-standard/

Supereg commented 4 years ago

For some people interested in HomeKit security: The Apple Platform Security Guide 2019 https://manuals.info.apple.com/MANUALS/1000/MA1902/en_US/apple-platform-security-guide.pdf

Also has some interesting tidbits regarding HomeKit routers.

Supereg commented 4 years ago

Some good news. The protocol for Secure-Video cameras is pretty much figured out, which means any HAP-NodeJS/homebridge plugin will soon be able to add support for secure-video.

For anyone interested, I created a little unofficial specification which should contain all known information (could be interesting for any other HAP implementations out there).

There are currently two steps missing (the second one is also a bit of sad news, which currently holds us back):

Have a nice day ~ Supereg

cynecx commented 4 years ago

@Supereg Is there some "poc" code I can find where I could plug in a chunks data stream? I am pretty new here, so is there some place I can look to find how to get-started with this? I am particularly interested in the second pain-point you are raising.

Supereg commented 4 years ago

I maybe just publish a mail I already sent to somebody who also already raised interest in this topic. At the end I briefly mention of kind of "poc" (still noting, this is something I wrote for myself, meaning ugly, undocumented code with stuff that "works on my machine"). Not providing support for anything on that branch, but could give some insights and maybe(?) be useful to somebody?

I tried to get back to working on this. But I'm still in my exam phase so no guarantees. But maybe some of you have more time :)


The goal is to create a fragmented mp4 file. How such a file is split and transmitted can be found in the examples section of my specifications repo. Also the mentioned http://mp4parser.com parse site is a nice way to get a rough understanding of the layout of an (f)mp4 file.

My spec (https://github.com/Supereg/secure-video-specification#recording-requirements) also references the RFC for HLS (Http Live Streaming, a standard defined by Apple which also can be used with fragmented mp4). The current understanding is that what the RFC 8216 (https://tools.ietf.org/html/rfc8216#section-3.3) specifies for fragmented MP4 is what secure video also expects (the link to the underlying ISO specification does not really work in the RFC; I put some working destination in my repo; this is basically the official definition from mpeg how an mp4 file can look like).

Of course the approach to dig through the RFCs and ISO files is pretty overwhelming and only just helps to get a rough understanding on how mp4 looks like and what fmp4 means. We could of course implement everything ourselves, but the goal would be to use an existing implementation like ffmpeg or gstreamer.

The stack overflow question you already found (https://stackoverflow.com/questions/8616855/how-to-output-fragmented-mp4-with-ffmpeg) didn’t really work for me personally (Maybe you have more luck?). It produces a file which seems to be somewhat fragmented mp4 but not really what HomeKit expects (it is playable using VLC, but QuickTime won’t play it. The example fmp4 footage I provided in my repo can be played normally using QuickTime, when concatenated; so this should also be a good indicator if a file of ours could work). As our current understanding is that HLS and Secure Video share the same specification of fragmented mp4, my idea was to try, if ffmpegs HLS muxer could maybe be already enough to create a prototype file (https://ffmpeg.org/ffmpeg-formats.html#hls-2: using the option hls_segment_type = ‚fmp4‘). Of course this would not really be useful for a concrete implementation but would be a starting point to dig deeper into ffmpeg on how exactly it generates fmp4 files for HLS.

Another approach could be using gstreamer. It seems that gstreamers mp4mux has an option ‚fragment-duration‘ (https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good/html/gst-plugins-good-plugins-mp4mux.html#GstMP4Mux--fragment-duration) which defines the duration of the fragment in milliseconds (which is perfect for Secure Video as we get the fragment size in milliseconds too).

One thing to note is that (I strongly assume) every mp4 fragment must start with a new keyframe, so a HomeHub can basically treat any fragment as individual video file (any flag every fragment individually as 'movement detected'), so basically there are no dependencies on key frames from previous fragments. I don’t know if an option like ‚fragment-duration‘ of gstreamers mp4muxer does already imply that (meaning this is basically not just a requirement of Secure Video but a requirement of fmp4 in general?) or I we need to configure that additionally.

Generally I think the gstreamer approach is currently the most promising one (though I don’t know how familiar your are with gstreamer), but couldn’t really get to trying it.

In my fork of hap-nodejs on the secure-video branch (https://github.com/Supereg/HAP-NodeJS/tree/secure-video/src/accessories) I have some basic sketches of a working Secure Video Accessory (IPCamera_accessory.ts and StreamController.ts). In this directory are also some additional tools like the mp4Decoder „thing“ and the writeFile.ts „thing“. They are not really intended for use by others but could give some inspirations for some starting points (but be warned, they are pretty sketchy and not well documented).

tokamac commented 4 years ago

It produces a file which seems to be somewhat fragmented mp4 but not really what HomeKit expects (it is playable using VLC, but QuickTime won’t play it.

In the stack overflow selected answer, user vbence says:

Important note: FFMpeg's muxer will set the Duration in both tkhd and mdhd atoms to 0xffffffff for each track. This causes problems in some players (for example Quicktime will not play such files). You should find a tool and change it to zero (0x00000000).

Have you tried and tested this before dropping the files in QuickTime?

Supereg commented 4 years ago

Have you tried and tested this before dropping the files in QuickTime?

That sound promising. Maybe someone can try that or I can maybe in some weeks.

cynecx commented 4 years ago

@Supereg I am currently on your secure-video branch and I am trying to get a simple stream working, however I am getting a black screen with your default ffmpeg command invocation. Is it actually working on your side?

Supereg commented 4 years ago

@Supereg I am currently on your secure-video branch and I am trying to get a simple stream working, however I am getting a black screen with your default ffmpeg command invocation. Is it actually working on your side?

Don’t know if actual RTP stream works (the black screen stuff sounds familiar), snapshots should definitely work though.

cynecx commented 4 years ago

Anyway I got the stream working (yeah instead of actually focusing on secure-video xD), what was missing in your ffmpeg command was -pix_fmt yuv420p.

iko3 commented 4 years ago

Thanks for all the efforts, but Is there any estimate on when Secure Video will be officially supported in the HAP-NodeJS library?