minor improvements
This commit is contained in:
+18
-1
@@ -211,7 +211,7 @@ func (c *Client) StartAudio() error {
|
||||
}
|
||||
|
||||
func (c *Client) StartIntercom() error {
|
||||
if c.conn.IsBackchannelReady() {
|
||||
if c.conn == nil || !c.conn.IsBackchannelReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -223,6 +223,17 @@ func (c *Client) StartIntercom() error {
|
||||
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) {
|
||||
return c.conn.AVRecvFrameData()
|
||||
}
|
||||
@@ -270,10 +281,16 @@ func (c *Client) Close() error {
|
||||
fmt.Printf("[Wyze] Closing connection\n")
|
||||
}
|
||||
|
||||
c.StopIntercom()
|
||||
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[Wyze] Connection closed\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+35
-33
@@ -443,38 +443,10 @@ Offset Size Field Description
|
||||
[31] 1 TwoWayAudio 0x01 if intercom supported
|
||||
[32-35] 4 Reserved
|
||||
[36-39] 4 BufferConfig 0x00000004
|
||||
[40-43] 4 Capabilities 0x001F07FB (see below)
|
||||
[40-43] 4 Capabilities 0x001F07FB
|
||||
[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
|
||||
@@ -515,9 +487,21 @@ Offset Size Field Description
|
||||
[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)
|
||||
|
||||
@@ -543,7 +527,7 @@ Offset Size Field Description
|
||||
──────────────────────────────────────────────────────────────
|
||||
[0-15] 16 HLHeader CommandID = 10002, PayloadLen = 22
|
||||
[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
|
||||
[37] 1 AudioFlag 1 = enable audio stream
|
||||
```
|
||||
@@ -620,6 +604,22 @@ Offset Size Field Description
|
||||
| 0xF0 (240) | Maximum |
|
||||
| 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
|
||||
@@ -1155,12 +1155,14 @@ authkey = b64.replace('+', 'Z').replace('/', '9').replace('=', 'A')
|
||||
|
||||
| Command | ID | Description |
|
||||
|---------|-----|-------------|
|
||||
| KCmdAuth | 10000 | Auth request |
|
||||
| KCmdAuth | 10000 | Auth request (with JSON) |
|
||||
| KCmdChallenge | 10001 | Challenge from camera |
|
||||
| KCmdChallengeResp | 10002 | Challenge response |
|
||||
| KCmdAuthResult | 10003 | Auth result (JSON) |
|
||||
| KCmdControlChannel | 10010 | Start/stop media |
|
||||
| KCmdControlChannelResp | 10011 | Control response |
|
||||
| KCmdSetResolutionDB | 10052 | Set resolution (doorbell) |
|
||||
| KCmdSetResolutionDBResp | 10053 | Resolution response (doorbell) |
|
||||
| KCmdSetResolution | 10056 | Set resolution/bitrate |
|
||||
| KCmdSetResolutionResp | 10057 | Resolution response |
|
||||
|
||||
|
||||
+16
-13
@@ -222,17 +222,19 @@ func (c *Conn) AVServStart() error {
|
||||
|
||||
func (c *Conn) AVServStop() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
serverConn := c.serverConn
|
||||
c.serverConn = nil
|
||||
// Reset audio TX state
|
||||
c.audioSeq = 0
|
||||
c.audioFrameNo = 0
|
||||
c.mu.Unlock()
|
||||
|
||||
if c.serverConn != nil {
|
||||
err := c.serverConn.Close()
|
||||
c.serverConn = nil
|
||||
return err
|
||||
if serverConn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
go serverConn.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -339,8 +341,13 @@ func (c *Conn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint16,
|
||||
frame := c.buildIOCtrlFrame(payload)
|
||||
var t *time.Timer
|
||||
t = time.AfterFunc(1, func() {
|
||||
if _, err := c.clientConn.Write(frame); err == nil && t != nil {
|
||||
t.Reset(time.Second)
|
||||
c.mu.RLock()
|
||||
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()
|
||||
@@ -399,10 +406,6 @@ func (c *Conn) Close() error {
|
||||
c.clientConn.Close()
|
||||
c.clientConn = nil
|
||||
}
|
||||
if c.serverConn != nil {
|
||||
c.serverConn.Close()
|
||||
c.serverConn = nil
|
||||
}
|
||||
if c.frames != nil {
|
||||
c.frames.Close()
|
||||
}
|
||||
@@ -705,7 +708,7 @@ func (c *Conn) handleSpeakerAVLogin() error {
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read av login: %w", err)
|
||||
|
||||
+50
-4
@@ -2,6 +2,7 @@ package tutk
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/dtls/v3"
|
||||
@@ -42,6 +43,9 @@ func buildDtlsConfig(psk []byte, isServer bool) *dtls.Config {
|
||||
type ChannelAdapter struct {
|
||||
conn *Conn
|
||||
channel uint8
|
||||
|
||||
mu sync.Mutex
|
||||
readDeadline time.Time
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
case data := <-buf:
|
||||
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
|
||||
}
|
||||
|
||||
func (a *ChannelAdapter) Close() error { return nil }
|
||||
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) Close() error { return nil }
|
||||
func (a *ChannelAdapter) LocalAddr() net.Addr { return &net.UDPAddr{} }
|
||||
|
||||
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 }
|
||||
|
||||
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/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/aac"
|
||||
)
|
||||
@@ -282,6 +283,8 @@ type FrameHandler struct {
|
||||
audioTS tsTracker
|
||||
output chan *Packet
|
||||
verbose bool
|
||||
closed bool
|
||||
closeMu sync.Mutex
|
||||
}
|
||||
|
||||
func NewFrameHandler(verbose bool) *FrameHandler {
|
||||
@@ -297,6 +300,13 @@ func (h *FrameHandler) Recv() <-chan *Packet {
|
||||
}
|
||||
|
||||
func (h *FrameHandler) Close() {
|
||||
h.closeMu.Lock()
|
||||
defer h.closeMu.Unlock()
|
||||
|
||||
if h.closed {
|
||||
return
|
||||
}
|
||||
h.closed = true
|
||||
close(h.output)
|
||||
}
|
||||
|
||||
@@ -540,6 +550,13 @@ func (h *FrameHandler) handleAudio(payload []byte, fi *FrameInfo) {
|
||||
}
|
||||
|
||||
func (h *FrameHandler) queue(pkt *Packet) {
|
||||
h.closeMu.Lock()
|
||||
defer h.closeMu.Unlock()
|
||||
|
||||
if h.closed {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case h.output <- pkt:
|
||||
default:
|
||||
@@ -548,7 +565,11 @@ func (h *FrameHandler) queue(pkt *Packet) {
|
||||
case <-h.output:
|
||||
default:
|
||||
}
|
||||
h.output <- pkt
|
||||
select {
|
||||
case h.output <- pkt:
|
||||
default:
|
||||
// Queue still full, drop this packet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user