Enhance video resolution handling by adding model-specific logic and updating subtype parsing
This commit is contained in:
@@ -188,6 +188,7 @@ func buildStreamURL(cam *wyze.Camera) string {
|
|||||||
query.Set("uid", cam.P2PID)
|
query.Set("uid", cam.P2PID)
|
||||||
query.Set("enr", cam.ENR)
|
query.Set("enr", cam.ENR)
|
||||||
query.Set("mac", cam.MAC)
|
query.Set("mac", cam.MAC)
|
||||||
|
query.Set("model", cam.ProductModel)
|
||||||
|
|
||||||
if cam.DTLS == 1 {
|
if cam.DTLS == 1 {
|
||||||
query.Set("dtls", "true")
|
query.Set("dtls", "true")
|
||||||
|
|||||||
+84
-13
@@ -15,10 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FrameSize1080P = 0
|
FrameSize1080P = 0
|
||||||
FrameSize360P = 1
|
FrameSize360P = 1
|
||||||
FrameSize720P = 2
|
FrameSize720P = 2
|
||||||
FrameSize2K = 3
|
FrameSize2K = 3
|
||||||
|
FrameSizeFloodlight = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -51,6 +52,8 @@ const (
|
|||||||
KCmdAuthSuccess = 10009
|
KCmdAuthSuccess = 10009
|
||||||
KCmdControlChannel = 10010
|
KCmdControlChannel = 10010
|
||||||
KCmdControlChannelResp = 10011
|
KCmdControlChannelResp = 10011
|
||||||
|
KCmdSetResolutionDB = 10052
|
||||||
|
KCmdSetResolutionDBRes = 10053
|
||||||
KCmdSetResolution = 10056
|
KCmdSetResolution = 10056
|
||||||
KCmdSetResolutionResp = 10057
|
KCmdSetResolutionResp = 10057
|
||||||
)
|
)
|
||||||
@@ -58,10 +61,11 @@ const (
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
conn *tutk.Conn
|
conn *tutk.Conn
|
||||||
|
|
||||||
host string
|
host string
|
||||||
uid string
|
uid string
|
||||||
enr string
|
enr string
|
||||||
mac string
|
mac string
|
||||||
|
model string
|
||||||
|
|
||||||
authKey string
|
authKey string
|
||||||
verbose bool
|
verbose bool
|
||||||
@@ -99,6 +103,7 @@ func Dial(rawURL string) (*Client, error) {
|
|||||||
uid: query.Get("uid"),
|
uid: query.Get("uid"),
|
||||||
enr: query.Get("enr"),
|
enr: query.Get("enr"),
|
||||||
mac: query.Get("mac"),
|
mac: query.Get("mac"),
|
||||||
|
model: query.Get("model"),
|
||||||
verbose: query.Get("verbose") == "true",
|
verbose: query.Get("verbose") == "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,20 +153,44 @@ func (c *Client) GetBackchannelCodec() (codecID uint16, sampleRate uint32, chann
|
|||||||
return c.audioCodecID, c.audioSampleRate, c.audioChannels
|
return c.audioCodecID, c.audioSampleRate, c.audioChannels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetResolution(sd bool) error {
|
func (c *Client) SetResolution(quality byte) error {
|
||||||
var frameSize uint8
|
var frameSize uint8
|
||||||
var bitrate uint16
|
var bitrate uint16
|
||||||
|
|
||||||
if sd {
|
switch quality {
|
||||||
|
case 0: // Auto/HD - use model's best
|
||||||
|
frameSize = c.hdFrameSize()
|
||||||
|
bitrate = BitrateMax
|
||||||
|
case FrameSize360P: // 1 = SD/360P
|
||||||
frameSize = FrameSize360P
|
frameSize = FrameSize360P
|
||||||
bitrate = BitrateSD
|
bitrate = BitrateSD
|
||||||
} else {
|
case FrameSize720P: // 2 = 720P
|
||||||
frameSize = FrameSize2K
|
frameSize = FrameSize720P
|
||||||
|
bitrate = BitrateMax
|
||||||
|
case FrameSize2K: // 3 = 2K
|
||||||
|
if c.is2K() {
|
||||||
|
frameSize = FrameSize2K
|
||||||
|
} else {
|
||||||
|
frameSize = c.hdFrameSize()
|
||||||
|
}
|
||||||
|
bitrate = BitrateMax
|
||||||
|
case FrameSizeFloodlight: // 4 = Floodlight
|
||||||
|
frameSize = c.hdFrameSize()
|
||||||
|
bitrate = BitrateMax
|
||||||
|
default:
|
||||||
|
frameSize = quality
|
||||||
bitrate = BitrateMax
|
bitrate = BitrateMax
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[Wyze] SetResolution: sd=%v frameSize=%d bitrate=%d\n", sd, frameSize, bitrate)
|
fmt.Printf("[Wyze] SetResolution: quality=%d frameSize=%d bitrate=%d model=%s\n", quality, frameSize, bitrate, c.model)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use K10052 (doorbell format) for certain models
|
||||||
|
if c.useDoorbellResolution() {
|
||||||
|
k10052 := c.buildK10052(frameSize, bitrate)
|
||||||
|
_, err := c.conn.WriteAndWaitIOCtrl(KCmdSetResolutionDB, k10052, KCmdSetResolutionDBRes, 5*time.Second)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
k10056 := c.buildK10056(frameSize, bitrate)
|
k10056 := c.buildK10056(frameSize, bitrate)
|
||||||
@@ -379,6 +408,18 @@ func (c *Client) buildK10010(mediaType byte, enabled bool) []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) buildK10052(frameSize uint8, bitrate uint16) []byte {
|
||||||
|
b := make([]byte, 22)
|
||||||
|
copy(b, "HL") // magic
|
||||||
|
b[2] = 5 // version
|
||||||
|
binary.LittleEndian.PutUint16(b[4:], KCmdSetResolutionDB) // 10052
|
||||||
|
binary.LittleEndian.PutUint16(b[6:], 6) // payload len
|
||||||
|
binary.LittleEndian.PutUint16(b[16:], bitrate) // bitrate (2 bytes)
|
||||||
|
b[18] = frameSize + 1 // frame size (1 byte)
|
||||||
|
// b[19] = fps, b[20:22] = zeros
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) buildK10056(frameSize uint8, bitrate uint16) []byte {
|
func (c *Client) buildK10056(frameSize uint8, bitrate uint16) []byte {
|
||||||
b := make([]byte, 21)
|
b := make([]byte, 21)
|
||||||
copy(b, "HL") // magic
|
copy(b, "HL") // magic
|
||||||
@@ -493,3 +534,33 @@ func (c *Client) parseK10009(data []byte) (*AuthResponse, error) {
|
|||||||
|
|
||||||
return &AuthResponse{}, nil
|
return &AuthResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) useDoorbellResolution() bool {
|
||||||
|
switch c.model {
|
||||||
|
case "WYZEDB3", "WVOD1", "HL_WCO2", "WYZEC1":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) hdFrameSize() uint8 {
|
||||||
|
if c.isFloodlight() {
|
||||||
|
return FrameSizeFloodlight
|
||||||
|
}
|
||||||
|
if c.is2K() {
|
||||||
|
return FrameSize2K
|
||||||
|
}
|
||||||
|
return FrameSize1080P
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) is2K() bool {
|
||||||
|
switch c.model {
|
||||||
|
case "HL_CAM3P", "HL_PANP", "HL_CAM4", "HL_DB2", "HL_CFL2":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) isFloodlight() bool {
|
||||||
|
return c.model == "HL_CFL2"
|
||||||
|
}
|
||||||
|
|||||||
+13
-4
@@ -29,9 +29,18 @@ func NewProducer(rawURL string) (*Producer, error) {
|
|||||||
u, _ := url.Parse(rawURL)
|
u, _ := url.Parse(rawURL)
|
||||||
query := u.Query()
|
query := u.Query()
|
||||||
|
|
||||||
sd := query.Get("subtype") == "sd"
|
// 0 = HD (default), 1 = SD/360P, 2 = 720P, 3 = 2K, 4 = Floodlight
|
||||||
|
var quality byte
|
||||||
|
switch s := query.Get("subtype"); s {
|
||||||
|
case "", "hd":
|
||||||
|
quality = 0
|
||||||
|
case "sd":
|
||||||
|
quality = FrameSize360P
|
||||||
|
default:
|
||||||
|
quality = core.ParseByte(s)
|
||||||
|
}
|
||||||
|
|
||||||
medias, err := probe(client, sd)
|
medias, err := probe(client, quality)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = client.Close()
|
_ = client.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -132,8 +141,8 @@ func (p *Producer) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func probe(client *Client, sd bool) ([]*core.Media, error) {
|
func probe(client *Client, quality byte) ([]*core.Media, error) {
|
||||||
_ = client.SetResolution(sd)
|
_ = client.SetResolution(quality)
|
||||||
_ = client.SetDeadline(time.Now().Add(core.ProbeTimeout))
|
_ = client.SetDeadline(time.Now().Add(core.ProbeTimeout))
|
||||||
|
|
||||||
var vcodec, acodec *core.Codec
|
var vcodec, acodec *core.Codec
|
||||||
|
|||||||
Reference in New Issue
Block a user