minor improvements
This commit is contained in:
+18
-1
@@ -211,7 +211,7 @@ func (c *Client) StartAudio() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) StartIntercom() error {
|
func (c *Client) StartIntercom() error {
|
||||||
if c.conn.IsBackchannelReady() {
|
if c.conn == nil || !c.conn.IsBackchannelReady() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +223,17 @@ func (c *Client) StartIntercom() error {
|
|||||||
return c.conn.AVServStart()
|
return c.conn.AVServStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) StopIntercom() error {
|
||||||
|
if c.conn == nil || !c.conn.IsBackchannelReady() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
k10010 := c.buildK10010(MediaTypeReturnAudio, false)
|
||||||
|
c.conn.WriteAndWaitIOCtrl(KCmdControlChannel, k10010, KCmdControlChannelResp, 5*time.Second)
|
||||||
|
|
||||||
|
return c.conn.AVServStop()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ReadPacket() (*tutk.Packet, error) {
|
func (c *Client) ReadPacket() (*tutk.Packet, error) {
|
||||||
return c.conn.AVRecvFrameData()
|
return c.conn.AVRecvFrameData()
|
||||||
}
|
}
|
||||||
@@ -270,10 +281,16 @@ func (c *Client) Close() error {
|
|||||||
fmt.Printf("[Wyze] Closing connection\n")
|
fmt.Printf("[Wyze] Closing connection\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.StopIntercom()
|
||||||
|
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.verbose {
|
||||||
|
fmt.Printf("[Wyze] Connection closed\n")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-33
@@ -443,38 +443,10 @@ Offset Size Field Description
|
|||||||
[31] 1 TwoWayAudio 0x01 if intercom supported
|
[31] 1 TwoWayAudio 0x01 if intercom supported
|
||||||
[32-35] 4 Reserved
|
[32-35] 4 Reserved
|
||||||
[36-39] 4 BufferConfig 0x00000004
|
[36-39] 4 BufferConfig 0x00000004
|
||||||
[40-43] 4 Capabilities 0x001F07FB (see below)
|
[40-43] 4 Capabilities 0x001F07FB
|
||||||
[44-57] 14 Reserved
|
[44-57] 14 Reserved
|
||||||
```
|
```
|
||||||
|
|
||||||
### Capabilities Bitmask (0x001F07FB)
|
|
||||||
|
|
||||||
```
|
|
||||||
Bit Hex Name Description
|
|
||||||
──────────────────────────────────────────────────────────────
|
|
||||||
0 0x00000001 CYCLIC_FRAME_NUMBERING Frame numbers wrap around
|
|
||||||
1 0x00000002 CLEAN_BUF_ON_RESET Clear buffer on stream reset
|
|
||||||
3 0x00000008 TIMESTAMP_IN_FRAMEINFO Timestamps in FRAMEINFO struct
|
|
||||||
4 0x00000010 MULTI_CHANNEL Multiple AV channels supported
|
|
||||||
5 0x00000020 EXTENDED_FRAMEINFO 40-byte FRAMEINFO (vs 16-byte SDK)
|
|
||||||
6 0x00000040 RESEND_TIMEOUT Packet resend with timeout
|
|
||||||
7 0x00000080 DTLS_SUPPORT DTLS encryption supported
|
|
||||||
8 0x00000100 SPEAKER_CHANNEL Two-way audio / intercom
|
|
||||||
9 0x00000200 PTZ_CHANNEL PTZ control channel
|
|
||||||
10 0x00000400 PLAYBACK_CHANNEL SD card playback channel
|
|
||||||
16 0x00010000 AV_SECURITY_ENABLED Encrypted AV stream
|
|
||||||
17 0x00020000 RESEND_ENABLED Packet resend mechanism
|
|
||||||
18 0x00040000 DTLS_PSK DTLS with Pre-Shared Key
|
|
||||||
19 0x00080000 DTLS_ECDHE DTLS with ECDHE key exchange
|
|
||||||
20 0x00100000 CHACHA20_POLY1305 ChaCha20-Poly1305 cipher support
|
|
||||||
```
|
|
||||||
|
|
||||||
**0x001F07FB breakdown:**
|
|
||||||
```
|
|
||||||
0x001F07FB = 0b0000_0000_0001_1111_0000_0111_1111_1011
|
|
||||||
= Bits: 0,1,3,4,5,6,7,8,9,10,16,17,18,19,20
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. K-Command Authentication
|
## 7. K-Command Authentication
|
||||||
@@ -515,9 +487,21 @@ Offset Size Field Description
|
|||||||
[16+] var Payload Command-specific data
|
[16+] var Payload Command-specific data
|
||||||
```
|
```
|
||||||
|
|
||||||
### K10000 - Auth Request (16 bytes)
|
### K10000 - Auth Request (16 + JSON bytes)
|
||||||
|
|
||||||
Header only, no payload. Initiates authentication.
|
```
|
||||||
|
Offset Size Field Description
|
||||||
|
──────────────────────────────────────────────────────────────
|
||||||
|
[0-15] 16 HLHeader CommandID = 10000, PayloadLen = len(JSON)
|
||||||
|
[16+] var JSONPayload Audio codec preferences
|
||||||
|
```
|
||||||
|
|
||||||
|
**JSON Payload:**
|
||||||
|
```json
|
||||||
|
{"cameraInfo":{"audioEncoderList":[137,138,140]}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Where audioEncoderList contains supported codec IDs: 137=PCMU, 138=PCMA, 140=PCM.
|
||||||
|
|
||||||
### K10001 - Challenge (33+ bytes)
|
### K10001 - Challenge (33+ bytes)
|
||||||
|
|
||||||
@@ -543,7 +527,7 @@ Offset Size Field Description
|
|||||||
──────────────────────────────────────────────────────────────
|
──────────────────────────────────────────────────────────────
|
||||||
[0-15] 16 HLHeader CommandID = 10002, PayloadLen = 22
|
[0-15] 16 HLHeader CommandID = 10002, PayloadLen = 22
|
||||||
[16-31] 16 Response XXTEA-decrypted challenge
|
[16-31] 16 Response XXTEA-decrypted challenge
|
||||||
[32-35] 4 UIDPrefix First 4 bytes of UID
|
[32-35] 4 SessionID Random 4-byte session identifier
|
||||||
[36] 1 VideoFlag 1 = enable video stream
|
[36] 1 VideoFlag 1 = enable video stream
|
||||||
[37] 1 AudioFlag 1 = enable audio stream
|
[37] 1 AudioFlag 1 = enable audio stream
|
||||||
```
|
```
|
||||||
@@ -620,6 +604,22 @@ Offset Size Field Description
|
|||||||
| 0xF0 (240) | Maximum |
|
| 0xF0 (240) | Maximum |
|
||||||
| 0x3C (60) | SD quality |
|
| 0x3C (60) | SD quality |
|
||||||
|
|
||||||
|
### K10052 - Set Resolution Doorbell (22 bytes)
|
||||||
|
|
||||||
|
Used by doorbell models (WYZEDB3, WVOD1, HL_WCO2, WYZEC1) instead of K10056:
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Size Field Description
|
||||||
|
──────────────────────────────────────────────────────────────
|
||||||
|
[0-15] 16 HLHeader CommandID = 10052, PayloadLen = 6
|
||||||
|
[16-17] 2 Bitrate KB/s value (LE)
|
||||||
|
[18] 1 FrameSize Resolution + 1 (see table above)
|
||||||
|
[19] 1 FPS Frames per second, 0 = auto
|
||||||
|
[20-21] 2 Reserved Zero-filled
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** K10052 has a different field order than K10056 (bitrate before frameSize).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. AV Frame Structure
|
## 9. AV Frame Structure
|
||||||
@@ -1155,12 +1155,14 @@ authkey = b64.replace('+', 'Z').replace('/', '9').replace('=', 'A')
|
|||||||
|
|
||||||
| Command | ID | Description |
|
| Command | ID | Description |
|
||||||
|---------|-----|-------------|
|
|---------|-----|-------------|
|
||||||
| KCmdAuth | 10000 | Auth request |
|
| KCmdAuth | 10000 | Auth request (with JSON) |
|
||||||
| KCmdChallenge | 10001 | Challenge from camera |
|
| KCmdChallenge | 10001 | Challenge from camera |
|
||||||
| KCmdChallengeResp | 10002 | Challenge response |
|
| KCmdChallengeResp | 10002 | Challenge response |
|
||||||
| KCmdAuthResult | 10003 | Auth result (JSON) |
|
| KCmdAuthResult | 10003 | Auth result (JSON) |
|
||||||
| KCmdControlChannel | 10010 | Start/stop media |
|
| KCmdControlChannel | 10010 | Start/stop media |
|
||||||
| KCmdControlChannelResp | 10011 | Control response |
|
| KCmdControlChannelResp | 10011 | Control response |
|
||||||
|
| KCmdSetResolutionDB | 10052 | Set resolution (doorbell) |
|
||||||
|
| KCmdSetResolutionDBResp | 10053 | Resolution response (doorbell) |
|
||||||
| KCmdSetResolution | 10056 | Set resolution/bitrate |
|
| KCmdSetResolution | 10056 | Set resolution/bitrate |
|
||||||
| KCmdSetResolutionResp | 10057 | Resolution response |
|
| KCmdSetResolutionResp | 10057 | Resolution response |
|
||||||
|
|
||||||
|
|||||||
+16
-13
@@ -222,17 +222,19 @@ func (c *Conn) AVServStart() error {
|
|||||||
|
|
||||||
func (c *Conn) AVServStop() error {
|
func (c *Conn) AVServStop() error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
serverConn := c.serverConn
|
||||||
|
c.serverConn = nil
|
||||||
// Reset audio TX state
|
// Reset audio TX state
|
||||||
c.audioSeq = 0
|
c.audioSeq = 0
|
||||||
c.audioFrameNo = 0
|
c.audioFrameNo = 0
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
if c.serverConn != nil {
|
if serverConn == nil {
|
||||||
err := c.serverConn.Close()
|
return nil
|
||||||
c.serverConn = nil
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go serverConn.Close()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,8 +341,13 @@ func (c *Conn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint16,
|
|||||||
frame := c.buildIOCtrlFrame(payload)
|
frame := c.buildIOCtrlFrame(payload)
|
||||||
var t *time.Timer
|
var t *time.Timer
|
||||||
t = time.AfterFunc(1, func() {
|
t = time.AfterFunc(1, func() {
|
||||||
if _, err := c.clientConn.Write(frame); err == nil && t != nil {
|
c.mu.RLock()
|
||||||
t.Reset(time.Second)
|
conn := c.clientConn
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if conn != nil {
|
||||||
|
if _, err := conn.Write(frame); err == nil && t != nil {
|
||||||
|
t.Reset(time.Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
@@ -399,10 +406,6 @@ func (c *Conn) Close() error {
|
|||||||
c.clientConn.Close()
|
c.clientConn.Close()
|
||||||
c.clientConn = nil
|
c.clientConn = nil
|
||||||
}
|
}
|
||||||
if c.serverConn != nil {
|
|
||||||
c.serverConn.Close()
|
|
||||||
c.serverConn = nil
|
|
||||||
}
|
|
||||||
if c.frames != nil {
|
if c.frames != nil {
|
||||||
c.frames.Close()
|
c.frames.Close()
|
||||||
}
|
}
|
||||||
@@ -705,7 +708,7 @@ func (c *Conn) handleSpeakerAVLogin() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
c.serverConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
c.serverConn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||||
n, err := c.serverConn.Read(buf)
|
n, err := c.serverConn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read av login: %w", err)
|
return fmt.Errorf("read av login: %w", err)
|
||||||
|
|||||||
+50
-4
@@ -2,6 +2,7 @@ package tutk
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/dtls/v3"
|
"github.com/pion/dtls/v3"
|
||||||
@@ -42,6 +43,9 @@ func buildDtlsConfig(psk []byte, isServer bool) *dtls.Config {
|
|||||||
type ChannelAdapter struct {
|
type ChannelAdapter struct {
|
||||||
conn *Conn
|
conn *Conn
|
||||||
channel uint8
|
channel uint8
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
readDeadline time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChannelAdapter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
func (a *ChannelAdapter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
@@ -52,6 +56,29 @@ func (a *ChannelAdapter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|||||||
buf = a.conn.serverBuf
|
buf = a.conn.serverBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.mu.Lock()
|
||||||
|
deadline := a.readDeadline
|
||||||
|
a.mu.Unlock()
|
||||||
|
|
||||||
|
if !deadline.IsZero() {
|
||||||
|
timeout := time.Until(deadline)
|
||||||
|
if timeout <= 0 {
|
||||||
|
return 0, nil, &timeoutError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(timeout)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case data := <-buf:
|
||||||
|
return copy(p, data), a.conn.addr, nil
|
||||||
|
case <-timer.C:
|
||||||
|
return 0, nil, &timeoutError{}
|
||||||
|
case <-a.conn.ctx.Done():
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case data := <-buf:
|
case data := <-buf:
|
||||||
return copy(p, data), a.conn.addr, nil
|
return copy(p, data), a.conn.addr, nil
|
||||||
@@ -67,8 +94,27 @@ func (a *ChannelAdapter) WriteTo(p []byte, _ net.Addr) (int, error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChannelAdapter) Close() error { return nil }
|
func (a *ChannelAdapter) Close() error { return nil }
|
||||||
func (a *ChannelAdapter) LocalAddr() net.Addr { return &net.UDPAddr{} }
|
func (a *ChannelAdapter) LocalAddr() net.Addr { return &net.UDPAddr{} }
|
||||||
func (a *ChannelAdapter) SetDeadline(time.Time) error { return nil }
|
|
||||||
func (a *ChannelAdapter) SetReadDeadline(time.Time) error { return nil }
|
func (a *ChannelAdapter) SetDeadline(t time.Time) error {
|
||||||
|
a.mu.Lock()
|
||||||
|
a.readDeadline = t
|
||||||
|
a.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ChannelAdapter) SetReadDeadline(t time.Time) error {
|
||||||
|
a.mu.Lock()
|
||||||
|
a.readDeadline = t
|
||||||
|
a.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ChannelAdapter) SetWriteDeadline(time.Time) error { return nil }
|
func (a *ChannelAdapter) SetWriteDeadline(time.Time) error { return nil }
|
||||||
|
|
||||||
|
type timeoutError struct{}
|
||||||
|
|
||||||
|
func (e *timeoutError) Error() string { return "i/o timeout" }
|
||||||
|
func (e *timeoutError) Timeout() bool { return true }
|
||||||
|
func (e *timeoutError) Temporary() bool { return true }
|
||||||
|
|||||||
+22
-1
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/aac"
|
"github.com/AlexxIT/go2rtc/pkg/aac"
|
||||||
)
|
)
|
||||||
@@ -282,6 +283,8 @@ type FrameHandler struct {
|
|||||||
audioTS tsTracker
|
audioTS tsTracker
|
||||||
output chan *Packet
|
output chan *Packet
|
||||||
verbose bool
|
verbose bool
|
||||||
|
closed bool
|
||||||
|
closeMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFrameHandler(verbose bool) *FrameHandler {
|
func NewFrameHandler(verbose bool) *FrameHandler {
|
||||||
@@ -297,6 +300,13 @@ func (h *FrameHandler) Recv() <-chan *Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *FrameHandler) Close() {
|
func (h *FrameHandler) Close() {
|
||||||
|
h.closeMu.Lock()
|
||||||
|
defer h.closeMu.Unlock()
|
||||||
|
|
||||||
|
if h.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.closed = true
|
||||||
close(h.output)
|
close(h.output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,6 +550,13 @@ func (h *FrameHandler) handleAudio(payload []byte, fi *FrameInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *FrameHandler) queue(pkt *Packet) {
|
func (h *FrameHandler) queue(pkt *Packet) {
|
||||||
|
h.closeMu.Lock()
|
||||||
|
defer h.closeMu.Unlock()
|
||||||
|
|
||||||
|
if h.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case h.output <- pkt:
|
case h.output <- pkt:
|
||||||
default:
|
default:
|
||||||
@@ -548,7 +565,11 @@ func (h *FrameHandler) queue(pkt *Packet) {
|
|||||||
case <-h.output:
|
case <-h.output:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
h.output <- pkt
|
select {
|
||||||
|
case h.output <- pkt:
|
||||||
|
default:
|
||||||
|
// Queue still full, drop this packet
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user