diff --git a/pkg/xiaomi/backchannel.go b/pkg/xiaomi/backchannel.go index a6f57b81..17242b8e 100644 --- a/pkg/xiaomi/backchannel.go +++ b/pkg/xiaomi/backchannel.go @@ -23,7 +23,8 @@ func (p *Producer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv case core.CodecPCMA: var buf []byte - if p.model == "isa.camera.hlc6" { + switch p.model { + case "isa.camera.hlc6", "isa.camera.df3": dst := &core.Codec{Name: core.CodecPCML, ClockRate: 8000} transcode := pcm.Transcode(dst, track.Codec) @@ -36,7 +37,7 @@ func (p *Producer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv buf = buf[size:] } } - } else { + default: sender.Handler = func(pkt *rtp.Packet) { buf = append(buf, pkt.Payload...) const size = 8000 * 0.040 // 8bit 40 ms diff --git a/pkg/xiaomi/producer.go b/pkg/xiaomi/producer.go index f2ce4eda..805a3f86 100644 --- a/pkg/xiaomi/producer.go +++ b/pkg/xiaomi/producer.go @@ -140,13 +140,11 @@ func probe(client *miss.Client, channel, quality, audio uint8) ([]*core.Media, e Codecs: []*core.Codec{acodec}, }) - if client.Protocol() == "cs2+udp" { - medias = append(medias, &core.Media{ - Kind: core.KindAudio, - Direction: core.DirectionSendonly, - Codecs: []*core.Codec{acodec.Clone()}, - }) - } + medias = append(medias, &core.Media{ + Kind: core.KindAudio, + Direction: core.DirectionSendonly, + Codecs: []*core.Codec{acodec.Clone()}, + }) } return medias, nil diff --git a/pkg/xiaomi/tutk/conn.go b/pkg/xiaomi/tutk/conn.go index 37489ffa..1b0ea56e 100644 --- a/pkg/xiaomi/tutk/conn.go +++ b/pkg/xiaomi/tutk/conn.go @@ -65,6 +65,7 @@ type Conn struct { seqSendCmd1 uint16 seqSendCmd2 uint16 seqSendCnt uint16 + seqSendAud uint16 seqRecvPkt0 uint16 seqRecvPkt1 uint16 @@ -249,7 +250,7 @@ func (c *Conn) ReadPacket() ([]byte, error) { } func (c *Conn) WritePacket(data []byte) error { - panic("not implemented") + return c.WriteCh1(c.oldMsgAud(data)) } func genSID() []byte { diff --git a/pkg/xiaomi/tutk/proto.go b/pkg/xiaomi/tutk/proto.go index 86cf95af..a440900a 100644 --- a/pkg/xiaomi/tutk/proto.go +++ b/pkg/xiaomi/tutk/proto.go @@ -75,7 +75,10 @@ const ( msgMediaFrame msgMediaLost msgCh5 + msgUnknown0007 + msgUnknown0008 msgUnknown0010 + msgUnknown0013 msgUnknown0a08 msgDafang0012 msgDafang0071 @@ -110,16 +113,29 @@ func (c *Conn) handleMsg(msg []byte) int8 { } func (c *Conn) handleCh1(cmd []byte) int8 { + // Channel 1 used for two-way audio. It's important: + // - answer on 0000 command with exact config response (can't set simple proto) + // - send 0012 command at start + // - respond on every 0008 command for smooth playback switch cid := string(cmd[:2]); cid { - case "\x00\x00": + case "\x00\x00": // client start _ = c.WriteCh1(c.msgAck0000(cmd)) + _ = c.WriteCh1(c.msg0012()) return msgClientStart00 - case "\x00\x20": + case "\x00\x07": // time sync without data + _ = c.WriteCh1(c.msgAck0007(cmd)) + return msgUnknown0007 + case "\x00\x08": // time sync with data + _ = c.WriteCh1(c.msgAck0008(cmd)) + return msgUnknown0008 + case "\x00\x13": // ack for 0012 + return msgUnknown0013 + case "\x00\x20": // client start2 //_ = c.WriteCh1(c.msgAck0020(cmd)) return msgClientStart20 - case "\x09\x00": // skip + case "\x09\x00": // counters sync return msgCounters - case "\x0a\x08": + case "\x0a\x08": // unknown _ = c.WriteCh1(c.msgAck0A08(cmd)) return msgUnknown0a08 } @@ -143,8 +159,9 @@ const msgHhrSize = 28 const cmdHdrSize = 24 func (c *Conn) msgAck0000(msg28 []byte) []byte { - const cmdDataSize = 36 - + // <- 000008000000000000000000000000001a0200004f47c714 ... 00000000000000000100000004000000fb071f00000000000000000000000300 + // -> 00140b00000000000000000000000000200000004f47c714 00000000000000000100000004000000fb071f00000000000000000000000300 + const cmdDataSize = 32 msg := c.msg(msgHhrSize + cmdHdrSize + cmdDataSize) cmd := msg[msgHhrSize:] @@ -152,9 +169,9 @@ func (c *Conn) msgAck0000(msg28 []byte) []byte { cmd[16] = cmdDataSize copy(cmd[20:], msg28[20:24]) // request id (random) - // It's better not to answer anything, so camera won't send anything to this channel. - //data := cmd[cmdHdrSize:] - //copy(data, msg28[len(msg28)-32:]) + // Important to answer with same data. + data := cmd[cmdHdrSize:] + copy(data, msg28[len(msg28)-32:]) return msg } @@ -179,8 +196,46 @@ func (c *Conn) msgAck0000(msg28 []byte) []byte { // return msg //} +func (c *Conn) msg0012() []byte { + // -> 00120b000000000000000000000000000c00000000000000020000000100000001000000 + const dataSize = 12 + msg := c.msg(msgHhrSize + cmdHdrSize + dataSize) + cmd := msg[msgHhrSize:] + + copy(cmd, "\x00\x12\x0b\x00") + cmd[16] = dataSize + data := cmd[cmdHdrSize:] + + data[0] = 2 + data[4] = 1 + data[9] = 1 + return msg +} + +func (c *Conn) msgAck0007(msg28 []byte) []byte { + // <- 000708000000000000000000000000000c00000001000000000000001c551f7a00000000 + // -> 010a0b00000000000000000000000000000000000100000000000000 + msg := c.msg(msgHhrSize + 28) + cmd := msg[msgHhrSize:] + copy(cmd, "\x01\x0a\x0b\x00") + cmd[20] = 1 + return msg +} + +func (c *Conn) msgAck0008(msg28 []byte) []byte { + // <- 000808000000000000000000000000000000f9f0010000000200000050f31f7a + // -> 01090b0000000000000000000000000000000000010000000200000050f31f7a + msg := c.msg(msgHhrSize + 28) + cmd := msg[msgHhrSize:] + copy(cmd, "\x01\x09\x0b\x00") + copy(cmd[20:], msg28[20:]) + return msg +} + func (c *Conn) msgAck0A08(msg28 []byte) []byte { - msg := c.msg(48) + // <- 0a080b005b0000000b51590002000000 + // -> 0b000b00000001000b5103000300000000000000 + msg := c.msg(msgHhrSize + 20) cmd := msg[msgHhrSize:] copy(cmd, "\x0b\x00\x0b\x00") copy(cmd[8:], msg28[8:10]) diff --git a/pkg/xiaomi/tutk/proto_new.go b/pkg/xiaomi/tutk/proto_new.go index 8f84b91b..e7a08080 100644 --- a/pkg/xiaomi/tutk/proto_new.go +++ b/pkg/xiaomi/tutk/proto_new.go @@ -186,6 +186,6 @@ func (c *Conn) msgAckCounters() []byte { binary.LittleEndian.PutUint16(cmd[18:], c.seqSendCnt) c.seqSendCnt++ - binary.LittleEndian.PutUint16(cmd[20:], uint16(time.Now().UnixNano())) + binary.LittleEndian.PutUint16(cmd[20:], uint16(time.Now().UnixMilli())) return msg } diff --git a/pkg/xiaomi/tutk/proto_old.go b/pkg/xiaomi/tutk/proto_old.go index 7ac4f803..875c67a9 100644 --- a/pkg/xiaomi/tutk/proto_old.go +++ b/pkg/xiaomi/tutk/proto_old.go @@ -3,11 +3,12 @@ package tutk import ( "encoding/binary" "fmt" + "time" ) func (c *Conn) oldMsgCtrl(ctrlType uint16, ctrlData []byte) []byte { - size := msgHhrSize + cmdHdrSize + 4 + uint16(len(ctrlData)) - msg := c.msg(size) + dataSize := 4 + uint16(len(ctrlData)) + msg := c.msg(msgHhrSize + cmdHdrSize + dataSize) cmd := msg[msgHhrSize:] copy(cmd, "\x00\x70\x0b\x00") @@ -15,7 +16,7 @@ func (c *Conn) oldMsgCtrl(ctrlType uint16, ctrlData []byte) []byte { binary.LittleEndian.PutUint16(cmd[4:], c.seqSendCmd1) c.seqSendCmd1++ - binary.LittleEndian.PutUint16(cmd[16:], size-52) + binary.LittleEndian.PutUint16(cmd[16:], dataSize) //binary.LittleEndian.PutUint32(cmd[20:], uint32(time.Now().UnixMilli())) data := cmd[cmdHdrSize:] @@ -24,6 +25,43 @@ func (c *Conn) oldMsgCtrl(ctrlType uint16, ctrlData []byte) []byte { return msg } +const pktHdrSize = 32 + +func (c *Conn) oldMsgAud(pkt []byte) []byte { + // -> 01030b001d0000008802000000002800b0020bf501000000 ... 4f4455412000000088020000030400001d000000000000000bf51f7a9b0100000000000000000000 + hdr := pkt[:pktHdrSize] + payload := pkt[pktHdrSize:] + + n := uint16(len(payload)) + dataSize := n + 8 + 32 + msg := c.msg(msgHhrSize + cmdHdrSize + dataSize) + + // 0 01030b00 command + version + // 4 1d000000 seq + // 8 8802 media size (648) + // 10 00000000 + // 14 2800 tail (pkt header) size? + // 16 b002 size (648 + 8 + 32) + // 18 0bf5 random msg id (unixms) + // 20 01000000 fixed + cmd := msg[msgHhrSize:] + copy(cmd, "\x01\x03\x0b\x00") + binary.LittleEndian.PutUint16(cmd[4:], c.seqSendAud) + c.seqSendAud++ + binary.LittleEndian.PutUint16(cmd[8:], n) + cmd[14] = 0x28 // important! + binary.LittleEndian.PutUint16(cmd[16:], dataSize) + binary.LittleEndian.PutUint16(cmd[18:], uint16(time.Now().UnixMilli())) + cmd[20] = 1 + + data := cmd[cmdHdrSize:] + copy(data, payload) + copy(data[n:], "ODUA\x20\x00\x00\x00") + copy(data[n+8:], hdr) + + return msg +} + func (c *Conn) oldHandlerCh0() func([]byte) int8 { var waitSeq uint16 var waitSize uint32 @@ -34,13 +72,15 @@ func (c *Conn) oldHandlerCh0() func([]byte) int8 { // 4 00000000 fixed // 8 ac880100 total size // 12 6200 chunk seq - // 14 2000 ??? + // 14 2000 tail (pkt header) size? // 16 cc00 size // 18 0000 // 20 01000000 fixed switch cmd[0] { case 0x01: + var packetData []byte + switch cmd[1] { case 0x03: seq := binary.LittleEndian.Uint16(cmd[12:]) @@ -62,42 +102,41 @@ func (c *Conn) oldHandlerCh0() func([]byte) int8 { return msgMediaLost } + waitSeq = 0 + // create a buffer for the header and collected data - packetData := make([]byte, waitSize) + packetData = make([]byte, waitSize) // there's a header at the end - let's move it to the beginning copy(packetData, waitData[waitSize-32:]) copy(packetData[32:], waitData) - select { - case c.rawPkt <- packetData: - default: - c.err = fmt.Errorf("%s: media queue is full", "tutk") - return -1 - } - - waitSeq = 0 - return msgMediaFrame - case 0x04: + // This is audio from miss audio start command. MiHome not using miss commands. waitSize2 := binary.LittleEndian.Uint32(cmd[8:]) waitData2 := cmd[24:] if uint32(len(waitData2)) != waitSize2 { - return -1 // shouldn't happened for audio + return -1 // shouldn't happen for audio } - packetData := make([]byte, waitSize2) + packetData = make([]byte, waitSize2) copy(packetData, waitData2) - select { - case c.rawPkt <- packetData: - default: - c.err = fmt.Errorf("%s: media queue is full", "tutk") - return -1 - } - return msgMediaFrame + default: + return 0 } + // fix Dafang bug (timestamp in seconds) + binary.LittleEndian.PutUint64(packetData[16:], uint64(time.Now().UnixMilli())) + + select { + case c.rawPkt <- packetData: + default: + c.err = fmt.Errorf("%s: media queue is full", "tutk") + return -1 + } + return msgMediaFrame + case 0x00: switch cmd[1] { case 0x70: @@ -123,9 +162,9 @@ func (c *Conn) oldHandlerCh0() func([]byte) int8 { } func (c *Conn) msgAck0070(msg28 []byte) []byte { - // <- [104] 0402120a 58000300 08041200 e6e8 0000 0c000000e6e839da66b0dc14 00700800010000000000000000000000 3400 00007625a02f ... - // -> [ 52] 0402190a 24000400 07042100 e6e8 0000 0c000000e6e839da66b0dc14 00710800010000000000000000000000 0000 00007625a02f - msg := c.msg(52) + // <- 00700800010000000000000000000000340000007625a02f ... + // -> 00710800010000000000000000000000000000007625a02f + msg := c.msg(msgHhrSize + cmdHdrSize) cmd := msg[msgHhrSize:] copy(cmd, "\x00\x71\x0b\x00") @@ -137,14 +176,14 @@ func (c *Conn) msgAck0070(msg28 []byte) []byte { } func (c *Conn) msgAck0012(msg28 []byte) []byte { - // <- [64] 0402120a 30000000 08041200 e6e800000c000000e6e839da66b0dc14 001208000000000000000000000000000c00000000000000 020000000100000001000000 - // -> [72] 0402190a 38000300 07042100 e6e800000c000000e6e839da66b0dc14 00130b000000000000000000000000001400000000000000 0200000001000000010000000000000000000000 - const size = 72 - msg := c.msg(size) + // <- 001208000000000000000000000000000c00000000000000 020000000100000001000000 + // -> 00130b000000000000000000000000001400000000000000 0200000001000000010000000000000000000000 + const dataSize = 20 + msg := c.msg(msgHhrSize + cmdHdrSize + dataSize) cmd := msg[msgHhrSize:] copy(cmd, "\x00\x13\x0b\x00") - cmd[16] = size - 52 // data size + cmd[16] = dataSize data := cmd[cmdHdrSize:] copy(data, msg28[cmdHdrSize:])