Compare commits

..

31 Commits
2.2.2 ... 2.2.4

Author SHA1 Message Date
BuGu
7a9f5f9e6e Merge branch 'main' of https://github.com/sipeed/NanoKVM 2025-03-21 16:22:03 +08:00
BuGu
cffa9a97f3 Delete S03usbdev 2025-03-21 16:21:22 +08:00
wj-xiao
1bf986d41b update libkvm.so 2025-03-21 16:19:05 +08:00
BuGu
eb343b0d31 Fix build script issues 2025-03-21 16:17:47 +08:00
BuGu
628cb3b8c9 Fix abnormal updates of some models
Add function: If the program does not execute after the update, execute a restart
2025-03-21 16:16:53 +08:00
BuGu
ebd391ec26 Reset USB on new app launch. 2025-03-21 12:02:03 +08:00
BuGu
a3b577e563 Fix USB initialization issue. 2025-03-21 11:03:18 +08:00
BuGu
f60adcc797 Merge branch 'main' of https://github.com/sipeed/NanoKVM 2025-03-21 11:03:03 +08:00
BuGu
ee77f7bed1 Fix USB initialization issue. 2025-03-21 11:03:01 +08:00
wj-xiao
6ef83cb22f feat: support setting H.264 GOP
perf: merge H.264 SPS and PPS into I-frame
perf: refactor MJPEG frame detection
perf: update log timestamp to millisecond precision
perf: update script `update-nanokvm.py`
chore: bump Go to 1.23
chore: bump golang.org/x/net to v0.37.0
2025-03-20 18:31:09 +08:00
BuGu
1a3fd15fdf Merge branch 'main' of https://github.com/sipeed/NanoKVM 2025-03-20 18:02:57 +08:00
wenjie
3e5a46570c Merge pull request #392 from jobsidi/patch-1
Update README.md
2025-03-20 03:00:20 -07:00
wenjie
2c4fbf7179 Merge pull request #382 from mekaris/feature/serial_greater_support
Add more serial features
2025-03-20 02:59:30 -07:00
wenjie
5409fb783c Merge pull request #380 from reishoku/reishoku.202503.improve_update-nanokvm-python3
Improve and optimize update-nanokvm.py
2025-03-20 02:58:53 -07:00
BuGu
4cec610aba Fix the issue of build script copying errors 2025-03-20 17:43:33 +08:00
BuGu
57d88d7889 New products default to disabling mDNS 2025-03-20 17:42:53 +08:00
BuGu
7f305ae3cf Fix deadlock issue when HDMI res error occurs 2025-03-20 17:28:05 +08:00
BuGu
eb4ac33fcd Restore frame difference detection function
Optimize the detection algorithm to reduce the detection time to 3-4ms per frame, using set_frame_detact (xx); Dynamically set the number of detection interval frames
2025-03-20 16:43:42 +08:00
BuGu
7a3c6c1690 Add production testing version restrictions 2025-03-20 16:40:33 +08:00
jobsidi
ca63609545 Update README.md
fixed link
2025-03-17 18:56:53 +01:00
mekaris
bcaa8896ee Add more serial features 2025-03-15 00:14:39 +01:00
KOSHIKAWA Kenichi
13b19e3889 Improve update-nanokvm script 2025-03-15 05:44:05 +09:00
BuGu
99d1fc0a64 Modify the method for retrieving I-frames in H.264.
Use a single data packet to replace the SPS + PPS + I three packets retrieval.
Note: This modification may require synchronization support from the server.
2025-03-12 17:34:35 +08:00
BuGu
86b7d676dd Restore S30eth
The new script has rendered the static address unavailable; reverting to the previous version.
2025-03-12 17:18:13 +08:00
BuGu
1adf6edffc Merge pull request #355 from BlwAvg/init-cleanup
Minor Init.d Cleanup
2025-03-12 16:24:28 +08:00
wj-xiao
a16af99648 update CHANGELOG.md 2025-03-11 11:52:55 +08:00
BlwAvg
9fdcb428c0 No more spacing issues! Should be consistent 2025-02-27 13:12:57 -07:00
BlwAvg
cee9bbb079 i dont know how to space things still 2025-02-27 13:11:04 -07:00
BlwAvg
fcc09e2ef0 spacing cont... 2025-02-27 13:07:44 -07:00
BlwAvg
5d96a8060f spacing 2025-02-27 13:02:14 -07:00
BlwAvg
023fb4b68b S03usbdev: Removed erroneous cat command
S30eth: Refactored to remove subshell

S30eth: Fixed spelling error for /etc/resolv.conf (not /etc/resolve.conf)

S50shd: Optimized the sshd script

S95nanokvm: Checked for IPTables rules before implementing to prevent duplicates.
2025-02-27 12:43:36 -07:00
44 changed files with 764 additions and 601 deletions

View File

@@ -1,3 +1,20 @@
## 2.2.2 [58d5ab2](https://github.com/sipeed/NanoKVM/commit/58d5ab2d37244b1e1a68b925a5c23c324c489ad3) (2025-03-11)
* feat: add watchdog for NanoKVM-Server
* feat: add support for UE chip
* feat: support system reboot
* feat: support enable/disable mDNS
* fix: resolve UE chip cannot start server
* perf: refactor automatic resolution detection
* perf: add output prompt for unsupported resolutions
* perf: add lock to kvmv_read_img
* perf: configurable VENC automatic recycling feature
* perf: add maximum limit for vi
* perf: add tooltips to menu bar (thanks to [@S33G](https://github.com/S33G))
* perf: menu bar is now draggable (thanks to [@forumi0721](https://github.com/forumi0721))
* perf: image list support auto-refresh (thanks to [@forumi0721](https://github.com/forumi0721))
* perf: update translations
## 2.2.1 [b5e48a0](https://github.com/sipeed/NanoKVM/commit/b5e48a07e82df3aedd60442342ae50b95684a697) (2025-02-21)
* fix: mounted image were not being detected correctly

View File

@@ -107,7 +107,7 @@ The NanoKVM image is built on LicheeRV Nano SDK and MaixCDK, and is compatible w
Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the multimedia subsystem, which NanoKVM will use for video image acquisition and processing.
+ [NanoKVM-A Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf)
+ [NanoKVM-B Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_HDMI_MIPI_31011.pdf)
+ [NanoKVM-B Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_B_30131.pdf)
+ [NanoKVM img](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM)
## Roadmap

View File

@@ -3,67 +3,71 @@
# sshd Starts sshd.
#
# Make sure the ssh-keygen progam exists
[ -f /usr/bin/ssh-keygen ] || exit 0
# Ensure required binaries exist
[ -x /usr/bin/ssh-keygen ] || exit 0
[ -x /usr/sbin/sshd ] || exit 1
PIDFILE="/var/run/sshd.pid"
umask 077
startssh() {
/usr/bin/ssh-keygen -A
# Generate SSH keys if they do not exist
[ ! -f /etc/ssh/ssh_host_rsa_key ] && /usr/bin/ssh-keygen -A
printf "Starting sshd: "
/usr/sbin/sshd
touch /var/lock/sshd
echo "OK"
printf "Starting sshd: "
/usr/sbin/sshd
touch /var/lock/sshd
echo "OK"
}
start() {
# Create any missing keys
if [ -e /etc/kvm/ssh_stop ]
then
if [ -e /boot/start_ssh_once ]
then
rm /boot/start_ssh_once
startssh
else
echo "SSH does not start"
fi
else
startssh
fi
if [ -e /etc/kvm/ssh_stop ]; then
if [ -e /boot/start_ssh_once ]; then
rm -f /boot/start_ssh_once
startssh
else
echo "SSH does not start"
exit 0
fi
else
startssh
fi
}
stop() {
printf "Stopping sshd: "
killall sshd
rm -f /var/lock/sshd
echo "OK"
printf "Stopping sshd: "
killall sshd 2>/dev/null
rm -f /var/lock/sshd
echo "OK"
}
restart() {
stop
start
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
permanent_on)
rm /etc/kvm/ssh_stop
start
;;
permanent_off)
touch /etc/kvm/ssh_stop
stop
;;
*)
echo "Usage: $0 {start|stop|restart|permanent_on|permanent_off}"
exit 1
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
permanent_on)
rm -f /etc/kvm/ssh_stop
start
;;
permanent_off)
touch /etc/kvm/ssh_stop
stop
;;
*)
echo "Usage: $0 {start|stop|restart|permanent_on|permanent_off}"
exit 1
esac
exit $?
exit 0

View File

@@ -1,42 +1,57 @@
#!/bin/sh
# nanokvm Rev3.0
# nanokvm Rev3.1
case "$1" in
start)
echo -n kvm > /boot/hostname.prefix
cp /mnt/data/sensor_cfg.ini.LT /mnt/data/sensor_cfg.ini
str_value=$(cat /sys/class/cvi-base/base_uid | awk '{print $2}')
first_uint=$(echo $str_value | cut -d'_' -f1)
second_uint=$(echo $str_value | cut -d'_' -f2)
result="$first_uint$second_uint"
echo $result > /device_key
# Copy sensor config if the file exists
[ -f /mnt/data/sensor_cfg.ini.LT ] && cp /mnt/data/sensor_cfg.ini.LT /mnt/data/sensor_cfg.ini
# Generate unique device key
if [ -f /sys/class/cvi-base/base_uid ]; then
str_value=$(awk '{print $2}' /sys/class/cvi-base/base_uid)
first_uint=$(echo "$str_value" | cut -d'_' -f1)
second_uint=$(echo "$str_value" | cut -d'_' -f2)
echo "$first_uint$second_uint" > /device_key
fi
# Set iptables rules (skip if already present)
iptables -C INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT 2>/dev/null || \
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -C OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT 2>/dev/null || \
iptables -A OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT
iptables -C OUTPUT -o eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT 2>/dev/null || \
iptables -A OUTPUT -o eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -C INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT 2>/dev/null || \
iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
iptables -C OUTPUT -o eth0 -p tcp --sport 8000 -m state --state ESTABLISHED -j DROP 2>/dev/null || \
iptables -A OUTPUT -o eth0 -p tcp --sport 8000 -m state --state ESTABLISHED -j DROP
# Start services
cp -r /kvmapp/kvm_system /tmp/
/tmp/kvm_system/kvm_system &
cp -r /kvmapp/server /tmp/
/tmp/server/NanoKVM-Server &
;;
;;
stop)
killall kvm_system
killall NanoKVM-Server
rm -r /tmp/kvm_system
rm -r /tmp/server
rm -r /tmp/kvm_system /tmp/server
echo "OK"
;;
;;
restart)
killall kvm_system
killall NanoKVM-Server
rm -r /tmp/kvm_system
rm -r /tmp/server
rm -r /tmp/kvm_system /tmp/server
cp -r /kvmapp/kvm_system /tmp/
/tmp/kvm_system/kvm_system &
@@ -44,7 +59,11 @@ case "$1" in
/tmp/server/NanoKVM-Server &
sync
echo "OK"
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac

View File

@@ -2,41 +2,38 @@ import os
import shutil
import time
import zipfile
import pathlib
import requests
temporary = "/root/.kvm-cache/"
temporary: str = "/root/.kvm-cache/"
def mkdir():
is_exists = os.path.exists(temporary)
if is_exists:
def mkdir() -> None:
if pathlib.Path(temporary).exists():
shutil.rmtree(temporary)
os.mkdir(temporary)
print(f"create temporary directory {temporary}")
pathlib.Path(temporary).mkdir()
print(f"Created temporary directory {temporary}")
def read(file):
with open(file, "r") as f:
content = f.read()
return content.replace("\n", "")
def read(file: str) -> str:
return pathlib.Path(file).read_text().replace("\n", "")
def download_firmware():
print("download firmware...")
def download_firmware() -> None:
print("Downloading firmware...")
now = int(time.time())
url = f"https://cdn.sipeed.com/nanokvm/latest.zip?n={now}"
print(f"download from {url}")
print(f"Downloading firmware from {url}")
response = requests.get(url)
if response.status_code != 200:
raise Exception(f"download firmware failed, status: {response.status_code}")
raise Exception(f"Failed to download firmware, status: {response.status_code}")
content_type = response.headers.get("content-type")
if content_type != "application/zip":
raise Exception(f"download firmware failed, content_type: {content_type}")
raise Exception(f"Failed to download firmware, content_type: {content_type}")
zip_file = f"{temporary}/latest.zip"
with open(zip_file, "wb") as f:
@@ -46,49 +43,23 @@ def download_firmware():
with zipfile.ZipFile(zip_file, "r") as f:
f.extractall(temporary)
print("download firmware done")
print("Completed downloading firmware.")
def download_lib():
print("download lib...")
def update() -> None:
backup_dir = pathlib.Path("/root/old")
firmware_dir = pathlib.Path("/kvmapp")
device_key = read("/device_key")
if backup_dir.exists():
shutil.rmtree(backup_dir)
url = f"https://maixvision.sipeed.com/api/v1/nanokvm/encryption?uid={device_key}"
headers = {"token": "MaixVision2024"}
response = requests.get(url, headers=headers, stream=True)
if firmware_dir.exists():
firmware_dir.rename(backup_dir)
if response.status_code != 200:
raise Exception(f"download lib failed, status: {response.status_code}")
content_type = response.headers.get("content-type")
if content_type != "application/octet-stream":
raise Exception(f"download lib failed, content_type: {content_type}")
lib_file = f"{temporary}/libmaixcam_lib.so"
with open(lib_file, "wb") as f:
f.write(response.content)
lib_dir = f"{temporary}/latest/kvm_system/dl_lib/"
shutil.copy(lib_file, lib_dir)
print("download lib done")
pathlib.Path(f"{temporary}/latest").rename(firmware_dir)
def update():
backup_dir = "/root/old"
firmware_dir = "/kvmapp"
if os.path.exists(backup_dir):
shutil.rmtree(backup_dir)
if os.path.exists(firmware_dir):
shutil.move(firmware_dir, backup_dir)
shutil.move(f"{temporary}/latest", firmware_dir)
def change_permissions():
def change_permissions() -> None:
for root, dirs, files in os.walk("/kvmapp"):
os.chmod(root, 0o755)
@@ -97,23 +68,22 @@ def change_permissions():
os.chmod(file_path, 0o755)
print("change permissions done")
def main():
def main() -> None:
try:
print("stop service...")
print("Stopping nanokvm service...")
os.system("/etc/init.d/S95nanokvm stop")
print("start update......")
print("Staring update...")
mkdir()
download_firmware()
# download_lib()
update()
change_permissions()
version = read("/kvmapp/version")
print(f"update to {version} success.")
print(f"Successfully updated to version: {version}")
print("restart service\nthe nanokvm will reboot")
except Exception as e:
print(f"update failed\n{e}")
@@ -123,4 +93,4 @@ def main():
if __name__ == "__main__":
main()
main()

View File

@@ -20,9 +20,7 @@ var (
kvmVisionOnce sync.Once
)
type KvmVision struct {
mutex sync.Mutex
}
type KvmVision struct{}
func GetKvmVision() *KvmVision {
kvmVisionOnce.Do(func() {
@@ -44,9 +42,6 @@ func GetKvmVision() *KvmVision {
}
func (k *KvmVision) ReadMjpeg(width uint16, height uint16, quality uint16) (data []byte, result int) {
k.mutex.Lock()
defer k.mutex.Unlock()
var (
kvmData *C.uint8_t
dataSize C.uint32_t
@@ -72,10 +67,7 @@ func (k *KvmVision) ReadMjpeg(width uint16, height uint16, quality uint16) (data
return
}
func (k *KvmVision) ReadH264(width uint16, height uint16, bitRate uint16) (data []byte, sps []byte, pps []byte, result int) {
k.mutex.Lock()
defer k.mutex.Unlock()
func (k *KvmVision) ReadH264(width uint16, height uint16, bitRate uint16) (data []byte, result int) {
var (
kvmData *C.uint8_t
dataSize C.uint32_t
@@ -97,48 +89,10 @@ func (k *KvmVision) ReadH264(width uint16, height uint16, bitRate uint16) (data
data = C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize))
if result == 3 {
sps, _ = k.ReadH264SPS()
pps, _ = k.ReadH264PPS()
}
log.Debugf("read kvm image: %v", result)
return
}
func (k *KvmVision) ReadH264SPS() ([]byte, int) {
var (
kvmData *C.uint8_t
dataSize C.uint32_t
)
result := int(C.kvmv_get_sps_frame(&kvmData, &dataSize))
if result < 0 {
log.Errorf("failed to read sps: %v", result)
return nil, result
}
data := C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize))
return data, result
}
func (k *KvmVision) ReadH264PPS() ([]byte, int) {
var (
kvmData *C.uint8_t
dataSize C.uint32_t
)
result := int(C.kvmv_get_pps_frame(&kvmData, &dataSize))
if result < 0 {
log.Errorf("failed to read pps: %v", result)
return nil, result
}
data := C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize))
return data, result
}
func (k *KvmVision) SetHDMI(enable bool) int {
hdmiEnable := C.uint8_t(0)
if enable {
@@ -154,10 +108,17 @@ func (k *KvmVision) SetHDMI(enable bool) int {
return result
}
func (k *KvmVision) Close() {
k.mutex.Lock()
defer k.mutex.Unlock()
func (k *KvmVision) SetGop(gop uint8) {
_gop := C.uint8_t(gop)
C.set_h264_gop(_gop)
}
func (k *KvmVision) SetFrameDetect(frame uint8) {
_frame := C.uint8_t(frame)
C.set_frame_detact(_frame)
}
func (k *KvmVision) Close() {
C.kvmv_deinit()
log.Debugf("stop kvm vision...")
}

View File

@@ -8,6 +8,7 @@ type Screen struct {
FPS int
Quality uint16
BitRate uint16
GOP uint8
}
var (
@@ -46,6 +47,7 @@ func GetScreen() *Screen {
Quality: 80,
FPS: 30,
BitRate: 3000,
GOP: 30,
}
})
@@ -70,6 +72,9 @@ func SetScreen(key string, value int) {
case "fps":
screen.FPS = validateFPS(value)
case "gop":
screen.GOP = uint8(value)
}
}

Binary file not shown.

View File

@@ -1,6 +1,8 @@
module NanoKVM-Server
go 1.22.1
go 1.23.0
toolchain go1.23.2
require (
github.com/creack/pty v1.1.24
@@ -15,7 +17,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.19.0
github.com/unrolled/secure v1.15.0
golang.org/x/crypto v0.31.0
golang.org/x/crypto v0.36.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -71,9 +73,9 @@ require (
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

View File

@@ -160,17 +160,25 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -50,20 +50,20 @@ void set_venc_auto_recyc(uint8_t _enable);
-5: Retrieving image, please wait
-4: Modifying image resolution, please wait
-3: img buffer full
-2: VENC Errorl
-2: VENC Error
-1: No images were acquired
0: Acquire MJPEG encoded images
1: Acquire H264 encoded images(SPS)
2: Acquire H264 encoded images(PPS)
1: Acquire H264 encoded images(SPS)[Deprecated]
2: Acquire H264 encoded images(PPS)[Deprecated]
3: Acquire H264 encoded images(I)
4: Acquire H264 encoded images(P)
5: IMG not changed
**********************************************************************************/
int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int kvmv_get_sps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int kvmv_get_pps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int free_kvmv_data(uint8_t ** _pp_kvm_data);
void free_all_kvmv_data();
void set_h264_gop(uint8_t _gop);
void set_frame_detact(uint8_t _frame_detact);
void kvmv_deinit();
uint8_t kvmv_hdmi_control(uint8_t _en);
@@ -71,4 +71,4 @@ uint8_t kvmv_hdmi_control(uint8_t _en);
}
#endif
#endif // KVM_VISION_H_
#endif // KVM_VISION_H_

View File

@@ -22,7 +22,7 @@ func (f *formatter) Format(entry *logrus.Entry) ([]byte, error) {
buffer = &bytes.Buffer{}
}
now := entry.Time.Format("2006-01-02 15:04:05")
now := entry.Time.Format("2006-01-02 15:04:05.000")
if entry.HasCaller() {
fileName := filepath.Base(entry.Caller.File)

View File

@@ -1,9 +1,9 @@
package proto
type GetFrameDetectRsp struct {
Enabled bool `json:"enabled"`
type UpdateFrameDetectReq struct {
Enabled bool `validate:"omitempty"`
}
type UpdateFrameDetectRsp struct {
Enabled bool `json:"enabled"`
type StopFrameDetectReq struct {
Duration int `validate:"omitempty"`
}

View File

@@ -12,8 +12,7 @@ func streamRouter(r *gin.Engine) {
api := r.Group("/api").Use(middleware.CheckToken())
api.GET("/stream/mjpeg", mjpeg.Connect) // mjpeg stream
api.GET("/stream/mjpeg/detect", mjpeg.GetFrameDetect) // get frame detect state
api.POST("/stream/mjpeg/detect", mjpeg.UpdateFrameDetect) // update frame detect state
api.POST("/stream/mjpeg/detect", mjpeg.UpdateFrameDetect) // update frame detect
api.POST("/stream/mjpeg/detect/stop", mjpeg.StopFrameDetect) // temporary stop frame detect
api.GET("/stream/h264", h264.Connect) // h264 stream

View File

@@ -2,6 +2,7 @@ package h264
import (
"encoding/json"
"sync"
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v4"
@@ -9,8 +10,9 @@ import (
)
type Client struct {
ws *websocket.Conn
pc *webrtc.PeerConnection
ws *websocket.Conn
pc *webrtc.PeerConnection
mutex sync.Mutex
}
type Message struct {
@@ -36,9 +38,7 @@ func (c *Client) addTrack() {
return
}
mutex.Lock()
trackMap[c.ws] = videoTrack
mutex.Unlock()
}
// register callback events
@@ -61,13 +61,9 @@ func (c *Client) register() {
// ICE connection state has changed
c.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
if state == webrtc.ICEConnectionStateConnected && !isSending {
mutex.Lock()
if !isSending {
// start sending h264 data
go send()
isSending = true
}
mutex.Unlock()
// start sending h264 data
go send()
isSending = true
}
log.Debugf("ice connection state has changed to %s", state.String())
@@ -81,13 +77,11 @@ func (c *Client) readMessage() {
for {
_, raw, err := c.ws.ReadMessage()
if err != nil {
mutex.Lock()
delete(trackMap, c.ws)
if len(trackMap) == 0 && isSending {
if isSending && len(trackMap) == 0 {
// stop sending when all websocket connections are closed
exitSig <- true
isSending = false
}
mutex.Unlock()
log.Debugf("failed to read message: %s", err)
return
@@ -144,9 +138,6 @@ func (c *Client) readMessage() {
return
}
case "heartbeat":
_ = c.sendMessage("heartbeat", "")
default:
log.Debugf("unhandled message event: %s", message.Event)
}
@@ -155,8 +146,8 @@ func (c *Client) readMessage() {
// send websocket message
func (c *Client) sendMessage(event string, data string) error {
mutex.RLock()
defer mutex.RUnlock()
c.mutex.Lock()
defer c.mutex.Unlock()
message := &Message{
Event: event,

View File

@@ -4,6 +4,7 @@ import (
"NanoKVM-Server/config"
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
@@ -18,9 +19,7 @@ var (
},
}
trackMap = make(map[*websocket.Conn]*webrtc.TrackLocalStaticSample)
mutex = sync.RWMutex{}
isSending = false
exitSig = make(chan bool, 1)
)
func Connect(c *gin.Context) {
@@ -35,6 +34,9 @@ func Connect(c *gin.Context) {
log.Debugf("h264 websocket disconnected")
}()
var zeroTime time.Time
_ = wsConn.SetReadDeadline(zeroTime)
conf := config.GetInstance()
var iceServers []webrtc.ICEServer
@@ -67,8 +69,9 @@ func Connect(c *gin.Context) {
}()
client := &Client{
ws: wsConn,
pc: peerConn,
ws: wsConn,
pc: peerConn,
mutex: sync.Mutex{},
}
client.addTrack()

View File

@@ -2,7 +2,6 @@ package h264
import (
"NanoKVM-Server/common"
"NanoKVM-Server/service/stream"
"time"
"github.com/pion/webrtc/v4/pkg/media"
@@ -18,63 +17,45 @@ func send() {
defer ticker.Stop()
vision := common.GetKvmVision()
for {
select {
case <-ticker.C:
height := screen.Height
width, ok := common.ResolutionMap[height]
if !ok {
width = 0
height = 0
}
bitRate := screen.BitRate
if _, ok := common.BitRateMap[bitRate]; !ok {
bitRate = 3000
}
data, sps, pps, result := vision.ReadH264(width, height, bitRate)
if result < 0 {
continue
}
if result == 3 {
writeSample(sps, duration)
writeSample(pps, duration)
}
writeSample(data, duration)
stream.GetFrameRateCounter().Update()
if screen.FPS != fps {
fps = screen.FPS
duration = time.Second / time.Duration(fps)
ticker.Reset(duration)
}
case <-exitSig:
mutex.Lock()
isSending = false
mutex.Unlock()
for range ticker.C {
if !isSending && len(trackMap) == 0 {
return
}
}
}
func writeSample(data []byte, duration time.Duration) {
sample := media.Sample{
Data: data,
Duration: duration,
}
height := screen.Height
width, ok := common.ResolutionMap[height]
if !ok {
width = 0
height = 0
}
mutex.RLock()
defer mutex.RUnlock()
bitRate := screen.BitRate
if _, ok := common.BitRateMap[bitRate]; !ok {
bitRate = 3000
}
for _, track := range trackMap {
if err := track.WriteSample(sample); err != nil {
log.Errorf("failed to send h264 data: %s", err)
data, result := vision.ReadH264(width, height, bitRate)
if result < 0 {
continue
}
sample := media.Sample{
Data: data,
Duration: duration,
}
for _, track := range trackMap {
if err := track.WriteSample(sample); err != nil {
log.Errorf("failed to send h264 data: %s", err)
}
}
log.Debugf("send h264 data: %d", len(data))
if screen.FPS != fps {
fps = screen.FPS
duration = time.Second / time.Duration(fps)
ticker.Reset(duration)
}
}
log.Debugf("send h264 data: %d", len(data))
}

View File

@@ -1,132 +1,55 @@
package mjpeg
import (
"errors"
"os"
"NanoKVM-Server/common"
"NanoKVM-Server/proto"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"NanoKVM-Server/proto"
)
const (
frameDetect = "/etc/kvm/frame_detact"
frameDetectTmp = "/etc/kvm/frame_detact_tmp"
)
type UpdateFrameDetectRsp struct {
Enabled bool `json:"enabled"`
}
func GetFrameDetect(c *gin.Context) {
var rsp proto.Response
isEnabled, err := isFrameDetectEnabled()
if err != nil {
rsp.ErrRsp(c, -1, "unknown frame detect status")
return
}
rsp.OkRspWithData(c, &proto.GetFrameDetectRsp{
Enabled: isEnabled,
})
log.Debugf("get frame detect success, enabled: %t", isEnabled)
}
const FrameDetectInterval uint8 = 60
func UpdateFrameDetect(c *gin.Context) {
var req proto.UpdateFrameDetectReq
var rsp proto.Response
isEnabled, err := isFrameDetectEnabled()
if err != nil {
rsp.ErrRsp(c, -1, "unknown frame status")
if err := proto.ParseFormRequest(c, &req); err != nil {
rsp.ErrRsp(c, -1, "invalid parameters")
return
}
if isEnabled {
_ = os.Remove(frameDetect)
_ = os.Remove(frameDetectTmp)
} else {
file, err2 := os.OpenFile(frameDetect, os.O_CREATE|os.O_TRUNC, 0o644)
if err2 != nil {
rsp.ErrRsp(c, -3, "enable frame detect failed")
return
}
defer func() {
_ = file.Close()
}()
var frame uint8 = 0
if req.Enabled {
frame = FrameDetectInterval
}
isEnabled, err = isFrameDetectEnabled()
if err != nil {
rsp.ErrRsp(c, -4, "unknown frame status")
return
}
common.GetKvmVision().SetFrameDetect(frame)
rsp.OkRspWithData(c, &UpdateFrameDetectRsp{
Enabled: isEnabled,
})
log.Debugf("update frame detect success, enabled: %t", isEnabled)
rsp.OkRsp(c)
log.Debugf("update frame detect: %t", req.Enabled)
}
func StopFrameDetect(c *gin.Context) {
var req proto.StopFrameDetectReq
var rsp proto.Response
exist, err := isFileExist(frameDetect)
if err != nil {
rsp.ErrRsp(c, -1, "unknown frame status")
if err := proto.ParseFormRequest(c, &req); err != nil {
rsp.ErrRsp(c, -1, "invalid parameters")
return
}
if !exist {
rsp.OkRsp(c)
return
duration := 10 * time.Second
if req.Duration > 0 {
duration = time.Duration(req.Duration) * time.Second
}
err = os.Rename(frameDetect, frameDetectTmp)
if err != nil {
rsp.ErrRsp(c, -2, "stop operation failed")
return
}
vision := common.GetKvmVision()
go func() {
time.Sleep(20 * time.Second)
_ = os.Rename(frameDetectTmp, frameDetect)
log.Debug("frame detect started")
}()
vision.SetFrameDetect(0)
time.Sleep(duration)
vision.SetFrameDetect(FrameDetectInterval)
rsp.OkRsp(c)
log.Debug("frame detect stopped")
}
func isFrameDetectEnabled() (bool, error) {
exist, err := isFileExist(frameDetect)
if err != nil {
return false, err
}
if exist {
return true, nil
}
exist, err = isFileExist(frameDetectTmp)
if err != nil {
return false, err
}
return exist, nil
}
func isFileExist(name string) (bool, error) {
_, err := os.Stat(name)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
return false, nil
}
return true, nil
}

View File

@@ -11,8 +11,8 @@ import (
)
var (
chanMap = make(map[*gin.Context]chan struct{})
mutex = sync.RWMutex{}
chanMap = make(map[*gin.Context]bool)
mutex = sync.Mutex{}
exitSig = make(chan bool, 1)
)
@@ -23,13 +23,13 @@ func Connect(c *gin.Context) {
c.Header("Pragma", "no-cache")
mutex.Lock()
chanMap[c] = make(chan struct{}, 1)
chanMap[c] = true
if len(chanMap) == 1 {
go send()
}
mutex.Unlock()
<-chanMap[c]
<-c.Request.Context().Done()
mutex.Lock()
delete(chanMap, c)
@@ -63,14 +63,13 @@ func send() {
}
data, result := vision.ReadMjpeg(width, height, quality)
if result < 0 {
if result < 0 || result == 5 {
continue
}
for c, ch := range chanMap {
for c := range chanMap {
if err := write(c, data); err != nil {
log.Debugf("failed to write mjpeg data: %s", err)
close(ch)
}
}
log.Debugf("send mjpeg data: %d", len(data))

View File

@@ -4,6 +4,7 @@ import (
"NanoKVM-Server/common"
"fmt"
"os"
"strconv"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
@@ -22,30 +23,34 @@ func (s *Service) SetScreen(c *gin.Context) {
var req proto.SetScreenReq
var rsp proto.Response
if err := proto.ParseFormRequest(c, &req); err != nil {
err := proto.ParseFormRequest(c, &req)
if err != nil {
rsp.ErrRsp(c, -1, "invalid arguments")
return
}
file, ok := screenFileMap[req.Type]
if !ok {
rsp.ErrRsp(c, -2, "invalid arguments")
return
}
data := fmt.Sprintf("%d", req.Value)
if req.Type == "type" {
switch req.Type {
case "type":
data := "h264"
if req.Value == 0 {
data = "mjpeg"
} else {
data = "h264"
}
err = writeScreen("type", data)
case "gop":
gop := 30
if req.Value >= 1 && req.Value <= 100 {
gop = req.Value
}
common.GetKvmVision().SetGop(uint8(gop))
default:
data := strconv.Itoa(req.Value)
err = writeScreen(req.Type, data)
}
err := os.WriteFile(file, []byte(data), 0o666)
if err != nil {
log.Errorf("write kvm %s failed: %s", file, err)
rsp.ErrRsp(c, -3, "update screen failed")
rsp.ErrRsp(c, -2, "update screen failed")
return
}
@@ -54,3 +59,18 @@ func (s *Service) SetScreen(c *gin.Context) {
log.Debugf("update screen: %+v", req)
rsp.OkRsp(c)
}
func writeScreen(key string, value string) error {
file, ok := screenFileMap[key]
if !ok {
return fmt.Errorf("invalid argument %s", key)
}
err := os.WriteFile(file, []byte(value), 0o666)
if err != nil {
log.Errorf("write kvm %s failed: %s", file, err)
return err
}
return nil
}

View File

@@ -61,17 +61,17 @@ void set_venc_auto_recyc(uint8_t _enable);
-2: VENC Errorl
-1: No images were acquired
0: Acquire MJPEG encoded images
1: Acquire H264 encoded images(SPS)
2: Acquire H264 encoded images(PPS)
1: Acquire H264 encoded images(SPS)[Deprecated]
2: Acquire H264 encoded images(PPS)[Deprecated]
3: Acquire H264 encoded images(I)
4: Acquire H264 encoded images(P)
5: IMG not changed
**********************************************************************************/
int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int kvmv_get_sps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int kvmv_get_pps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int free_kvmv_data(uint8_t ** _pp_kvm_data);
void free_all_kvmv_data();
void set_h264_gop(uint8_t _gop);
void set_frame_detact(uint8_t _frame_detact);
void kvmv_deinit();
uint8_t kvmv_hdmi_control(uint8_t _en);

View File

@@ -22,6 +22,8 @@
#define vi_max_width 1920
#define vi_max_height 1080
#define Farame_sample_size 76800 // 320*240
#define default_vi_width 1920
#define default_vi_height 1080
#define default_vpss_width 1920
@@ -32,12 +34,14 @@
#define default_h264_gop 30
#define kvmv_data_buffer_size 4
#define Try_rounds_HDMI_err_res 5
#define vi_width_path "/kvmapp/kvm/width"
#define vi_height_path "/kvmapp/kvm/height"
#define hdmi_mode_path "/etc/kvm/hdmi_mode"
#define hdmi_state_path "/proc/lt_int"
#define watchdog_mode_path "/etc/kvm/watchdog"
#define watchdog_temp_path "/tmp/watchdog"
#define watchdog_file "/tmp/nanokvm_wd"
#define LT6911_ADDR 0x2B
@@ -71,7 +75,7 @@ typedef struct {
uint16_t qlty;
uint8_t cam_state = 0;
uint8_t stream_stop;
uint8_t frame_detact;
uint8_t frame_detact = 0;
uint8_t display;
uint8_t reinit_flag = 1;
uint8_t reopen_cam_flag = 0;
@@ -86,6 +90,7 @@ typedef struct {
uint8_t hdmi_mode = 0;
uint8_t hdmi_res_type = 0;
uint8_t hdmi_res_err = 0;
uint8_t hdmi_try_rounds = 0;
uint8_t vi_detect_state = 0;
uint8_t venc_auto_recyc = 0;
} kvmv_cfg_t;
@@ -109,8 +114,6 @@ camera::Camera *cam = new camera::Camera(default_vpss_width, default_vpss_height
kvmv_cfg_t kvmv_cfg;
kvmv_data_t kvmv_data_buffer[kvmv_data_buffer_size];
kvmv_data_t kvmv_SPS_buffer = {0};
kvmv_data_t kvmv_PPS_buffer = {0};
uint8_t kvmv_data_buffer_index = 0;
@@ -307,9 +310,10 @@ int get_hdmi_mode(void)
return 0;
}
uint8_t watchdog_sf_is_open()
uint8_t watchdog_sf_is_open(void)
{
if(access(watchdog_mode_path, F_OK) == 0) return 1;
else if(access(watchdog_temp_path, F_OK) == 0) return 1;
else return 0;
}
@@ -1015,7 +1019,7 @@ void* watchdog_sf_feed(void * arg)
debug("[kvmv] Ion memory is full reboot now!\n");
system("reboot");
}
debug("[kvmv] watchdog_sf_feed now!\n");
// debug("[kvmv] watchdog_sf_feed now!\n");
vision_update_watchdog();
}
}
@@ -1065,6 +1069,7 @@ void* vi_subsystem_detection(void * arg)
uint8_t rising_times = 0;
uint8_t falling_times = 0;
uint8_t cam_need_restart = 0;
uint8_t auto_change_mode = 0;
kvmv_cfg.thread_is_running = 1;
if(access("/proc/lt_int", F_OK) != 0){
time::sleep_ms(10);
@@ -1128,15 +1133,10 @@ void* vi_subsystem_detection(void * arg)
kvmv_cfg.hdmi_res_type = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height);
if (kvmv_cfg.hdmi_res_type == NEW_RES) kvmv_cfg.reopen_cam_flag = 1;
else if (kvmv_cfg.hdmi_res_type == UNKNOWN_RES){
kvmv_cfg.vi_detect_state = 1;
if(auto_try_res() == 1){
printf("[hdmi] auto get res\n");
kvmv_cfg.hdmi_res_err = NORMAL_RES;
} else {
// Potential deadlock may occur
kvmv_cfg.hdmi_res_err = ERROR_RES;
}
kvmv_cfg.vi_detect_state = 0;
/* Move HDMI resolution modification directly
to mode1 to solve deadlock problem */
auto_change_mode = 1;
set_hdmi_mode(1);
}
} else {
// HDMI res = 0*0/x*0
@@ -1158,15 +1158,10 @@ void* vi_subsystem_detection(void * arg)
kvmv_cfg.hdmi_res_type = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height);
if (kvmv_cfg.hdmi_res_type == NEW_RES) kvmv_cfg.reopen_cam_flag = 1;
else if (kvmv_cfg.hdmi_res_type == UNKNOWN_RES){
kvmv_cfg.vi_detect_state = 1;
if(auto_try_res() == 1){
printf("[hdmi] auto get res\n");
kvmv_cfg.hdmi_res_err = NORMAL_RES;
} else {
// Potential deadlock may occur
kvmv_cfg.hdmi_res_err = ERROR_RES;
}
kvmv_cfg.vi_detect_state = 0;
/* Move HDMI resolution modification directly
to mode1 to solve deadlock problem */
auto_change_mode = 1;
set_hdmi_mode(1);
}
} else {
// HDMI res = 0*0/x*0
@@ -1197,20 +1192,35 @@ void* vi_subsystem_detection(void * arg)
kvmv_cfg.vi_detect_state = 1;
}
if(kvmv_cfg.vi_detect_state == 1){
try_res = auto_try_res();
if (try_res == 1) {
kvmv_cfg.hdmi_res_err = NORMAL_RES;
kvmv_cfg.hdmi_try_rounds = 0;
kvmv_cfg.vi_detect_state = 2;
} else if (try_res == 0) {
printf("[kvmv] Suitable resolution not found, switching to manual input mode automatically\n");
kvmv_cfg.vi_detect_state = 1;
set_hdmi_mode(2);
/* There may be a situation where the correct resolution cannot be recognized
after one round of detection. By default, Try_rounds_HDMI_err_res rounds will be detected,
and if it cannot be detected, jump to the next mode */
kvmv_cfg.hdmi_res_err = ERROR_RES;
kvmv_cfg.hdmi_try_rounds++;
if(kvmv_cfg.hdmi_try_rounds >= Try_rounds_HDMI_err_res){
kvmv_cfg.hdmi_try_rounds = 0;
kvmv_cfg.vi_detect_state = 1;
// printf("[kvmv] Suitable resolution not found, switching to manual input mode automatically\n");
if(auto_change_mode == 1){
auto_change_mode = 0;
set_hdmi_mode(0);
} else {
set_hdmi_mode(2);
}
}
} else if (try_res == 2) {
kvmv_cfg.hdmi_try_rounds = 0;
// Cannot obtain HDMI input / No signal on HDMI
}
} else if (kvmv_cfg.vi_detect_state == 2){
// Low-frequency detection of HDMI status, no log output
printf("[kvmv] kvmv_cfg.vi_detect_state == 2\n");
printf("[kvmv] kvmv_cfg.vi_detect_state == 2\n");
err_code = get_vi_state();
if (err_code != 1) {
kvmv_cfg.vi_detect_state = 1;
@@ -1332,6 +1342,31 @@ int sync_vi_res()
return res;
}
uint8_t frame_changed(image::Image *raw)
{
static int raw_size = 0;
static uint8_t Farame_sample[Farame_sample_size] = {0};
uint8_t ret = 0;
uint8_t sample_byte;
if(raw->data_size() != raw_size){
raw_size = raw->data_size();
ret = 1;
}
int Detection_Pixel_Interval = raw_size/(Farame_sample_size*1.5);
for(int i = 0; i < Farame_sample_size; i ++){
// printf("[kvmv] i = %d\n", i);
if(i >= raw_size){
ret = 0;
}
sample_byte = *(uint8_t*)(raw->data()+(i*Detection_Pixel_Interval));
if(sample_byte != Farame_sample[i]){
Farame_sample[i] = sample_byte;
ret = 1;
}
}
return ret;
}
void jpg_dump(kvmv_data_t* dump_to, image::Image *raw)
{
dump_to->p_img_data = (uint8_t *)malloc(raw->data_size());
@@ -1376,29 +1411,17 @@ int h264_stream_dump(kvmv_data_t* dump_to, mmf_stream_t* dump_from)
static int8_t I_Frame_index = -1;
// debug("[kvmv]dump_from->count = %d\n", dump_from->count);
if (dump_from->count == 3) {
// debug("[kvmv]dump I-Frame\r\n");
if(kvmv_SPS_buffer.p_img_data != NULL || kvmv_PPS_buffer.p_img_data != NULL)
return IMG_BUFFER_FULL;
kvmv_SPS_buffer.p_img_data = (uint8_t *)malloc(dump_from->data_size[0]);
kvmv_PPS_buffer.p_img_data = (uint8_t *)malloc(dump_from->data_size[1]);
dump_to->p_img_data = (uint8_t *)malloc(dump_from->data_size[2]);
dump_to->p_img_data = (uint8_t *)malloc(dump_from->data_size[0]+dump_from->data_size[1]+dump_from->data_size[2]);
dump_to->img_data_size = dump_from->data_size[0]+dump_from->data_size[1]+dump_from->data_size[2];
dump_to->img_data_type = IMG_H264_TYPE_IF;
memcpy(dump_to->p_img_data, dump_from->data[0], dump_from->data_size[0]);
memcpy(dump_to->p_img_data+dump_from->data_size[0], dump_from->data[1], dump_from->data_size[1]);
memcpy(dump_to->p_img_data+dump_from->data_size[0]+dump_from->data_size[1], dump_from->data[2], dump_from->data_size[2]);
kvmv_SPS_buffer.img_data_size = dump_from->data_size[0];
kvmv_PPS_buffer.img_data_size = dump_from->data_size[1];
dump_to->img_data_size = dump_from->data_size[2];
kvmv_SPS_buffer.img_data_type = IMG_H264_TYPE_SPS;
kvmv_PPS_buffer.img_data_type = IMG_H264_TYPE_PPS;
dump_to->img_data_type = IMG_H264_TYPE_IF;
memcpy(kvmv_SPS_buffer.p_img_data, dump_from->data[0], dump_from->data_size[0]);
memcpy(kvmv_PPS_buffer.p_img_data, dump_from->data[1], dump_from->data_size[1]);
memcpy(dump_to->p_img_data, dump_from->data[2], dump_from->data_size[2]);
debug("[kvmv]SPS size = %d\n", kvmv_SPS_buffer.img_data_size);
debug("[kvmv]PPS size = %d\n", kvmv_PPS_buffer.img_data_size);
debug("[kvmv]I-Frame size = %d\n", dump_to->img_data_size);
debug("[kvmv]SPS size = %d\n", dump_from->data_size[0]);
debug("[kvmv]PPS size = %d\n", dump_from->data_size[1]);
debug("[kvmv]I-Frame size = %d\n", dump_from->data_size[2]);
return IMG_H264_TYPE_IF;
@@ -1419,18 +1442,17 @@ int h264_stream_dump(kvmv_data_t* dump_to, mmf_stream_t* dump_from)
void set_h264_gop(uint8_t _gop)
{
kvm_venc.enc_h264_init = 0; // call
kvmvenc_gop = maxmin_data(100, 10, (int)_gop);
kvmvenc_gop = maxmin_data(100, 1, (int)_gop);
debug("[kvmv] set_h264_gop = %d\n", kvmvenc_gop);
}
// uint8_t get_h264_gop(void)
// {
// return kvm_venc.kvm_venc_cfg.gop;
// }
// uint8_t get_hdmi_width(void)
// {
// return kvm_venc.kvm_venc_cfg.gop;
// }
void set_frame_detact(uint8_t _frame_detact)
{
uint8_t frame_detact = maxmin_data(100, 0, (int)_frame_detact);
kvmv_cfg.frame_detact = frame_detact;
debug("[kvmv] set_frame_detact = %d\n", kvmv_cfg.frame_detact);
kvmv_cfg.stream_stop = 0;
}
int8_t raw_to_h264(image::Image *raw, kvmv_data_t* ret_stream, uint16_t _qlty)
{
@@ -1513,8 +1535,6 @@ void kvmv_init(uint8_t _debug_info_en)
for(int i = 0; i < kvmv_data_buffer_size; i++){
kvmv_data_buffer[i].p_img_data = NULL;
}
kvmv_SPS_buffer.p_img_data = NULL;
kvmv_PPS_buffer.p_img_data = NULL;
kvmv_cfg.try_exit_thread = 0;
// debug("[kvmv]kvmv_init - 2\r\n");
@@ -1585,13 +1605,15 @@ void set_venc_auto_recyc(uint8_t _enable)
-2: VENC Errorl
-1: No images were acquired
0: Acquire MJPEG encoded images
1: Acquire H264 encoded images(SPS)
2: Acquire H264 encoded images(PPS)
1: Acquire H264 encoded images(SPS)[Deprecated]
2: Acquire H264 encoded images(PPS)[Deprecated]
3: Acquire H264 encoded images(I)
4: Acquire H264 encoded images(P)
5: IMG not changed
**********************************************************************************/
int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size)
{
static uint8_t frame_undetact_count = 0;
// uint64_t __attribute__((unused)) start_time = time::time_ms();
debug("[kvmv]kvmv_read_img type = %d...\n", _type);
struct timespec ts;
@@ -1648,6 +1670,27 @@ int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _ql
// debug("[kvmv]befor read img: %d \r\n", (int)(time::time_ms() - start_time));
image::Image *img = cam->read();
// debug("[kvmv]read img: %d \r\n", (int)(time::time_ms() - start_time));
if(_type == VENC_MJPEG && kvmv_cfg.frame_detact != 0){
if(kvmv_cfg.stream_stop == 0){
frame_undetact_count++;
} else {
frame_undetact_count = kvmv_cfg.frame_detact;
}
if(frame_undetact_count == kvmv_cfg.frame_detact){
frame_undetact_count = 0;
if(frame_changed(img) == 0){
debug("[kvmv]frame not changed...\n");
kvmv_cfg.stream_stop = 1;
delete img;
pthread_mutex_unlock(&vi_mutex);
return 5;
} else {
kvmv_cfg.stream_stop = 0;
}
}
}
if(img != NULL){
if(kvmv_cfg.cam_state == 0) {
kvmv_cfg.cam_state = 1;
@@ -1729,30 +1772,6 @@ int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _ql
return IMG_NOT_EXIST;
}
int kvmv_get_sps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size)
{
if(kvmv_SPS_buffer.p_img_data == NULL){
return IMG_NOT_EXIST;
} else {
*_pp_kvm_data = kvmv_SPS_buffer.p_img_data;
*_p_kvmv_data_size = kvmv_SPS_buffer.img_data_size;
return IMG_H264_TYPE_SPS;
}
return IMG_NOT_EXIST;
}
int kvmv_get_pps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size)
{
if(kvmv_PPS_buffer.p_img_data == NULL){
return IMG_NOT_EXIST;
} else {
*_pp_kvm_data = kvmv_PPS_buffer.p_img_data;
*_p_kvmv_data_size = kvmv_PPS_buffer.img_data_size;
return IMG_H264_TYPE_PPS;
}
return IMG_NOT_EXIST;
}
int free_kvmv_data(uint8_t ** _pp_kvm_data)
{
// debug("[kvmv]free_kvmv_data - 1\r\n");
@@ -1765,12 +1784,6 @@ int free_kvmv_data(uint8_t ** _pp_kvm_data)
// debug("[kvmv]free_kvmv_data - 3\r\n");
kvmv_data_buffer[i].p_img_data = NULL;
uint8_t _type = kvmv_data_buffer[i].img_data_type;
if(_type == IMG_H264_TYPE_IF){
free(kvmv_SPS_buffer.p_img_data);
free(kvmv_PPS_buffer.p_img_data);
kvmv_SPS_buffer.p_img_data = NULL;
kvmv_PPS_buffer.p_img_data = NULL;
}
return _type;
} else {
return IMG_NOT_EXIST;
@@ -1788,10 +1801,6 @@ void free_all_kvmv_data()
kvmv_data_buffer[i].p_img_data = NULL;
}
}
if(kvmv_SPS_buffer.p_img_data != NULL) free(kvmv_SPS_buffer.p_img_data);
if(kvmv_PPS_buffer.p_img_data != NULL) free(kvmv_PPS_buffer.p_img_data);
kvmv_SPS_buffer.p_img_data = NULL;
kvmv_PPS_buffer.p_img_data = NULL;
}
void kvmv_deinit()
@@ -1844,24 +1853,3 @@ uint8_t kvmv_hdmi_control(uint8_t _en)
}
return -1;
}
/*
todo list:
- [x] 添加芯片型号
- [-] 写一个不支持的分辨率列表,如果不支持就发出错误提醒,不输出/不获取图像
- mode0:
- 获取到支持的分辨率:直接初始化
- 不支持的分辨率:输出对应错误
- 不在列表的分辨率:自动进入模式1
- mode1:
- 根据/proc/cvitek
- [ ] 写一个文档说明获取USB HDMI ETH WiFi的状态
- [ ] mode0:添加功能,获取到一个错误的res自动切换为探索模式
- [ ] add mode3:仅i2c获取模式
- [ ] 创建守护server进程
- [ ] mDNS默认配置问题
- [ ] 关于看门狗:kvm_vision和kvm_system相互在/tmp里狗叫,同时vision中添加检测是否read卡死,system中检测ion是否够用,否则重启
VI_SDK_IOC_S_CTRL - vi_sdk_enable_chn NG, No buffer space available ion是否够用也放在vision里吧,前面是log
*/

View File

@@ -149,9 +149,8 @@ then
if [ "$Project_Name" = "kvm_system" ]
then
SOURCE_PATH="$Project_PATH/dist/kvm_system_release/kvm_system"
TARGET_PATH="$KVMAPP_PATH/kvm_system/kvm_system"
TARGET_PATH="$KVMAPP_PATH/kvm_system"
chmod +x $SOURCE_PATH
rm -f $TARGET_PATH
else
SOURCE_PATH="$Project_PATH/dist/kvm_vision_test_release/dl_lib"
TARGET_PATH="$KVMAPP_PATH/server/"

View File

@@ -37,6 +37,7 @@
#define OLED_SLEEP_DELAY_MIN 10
#define OLED_SLEEP_DELAY_DEFAULT 30
#define KVM_WD_COUNT_MAX 10
#define RM_Watchdog_times 60
typedef struct {
int8_t page = 0;
@@ -97,6 +98,7 @@ typedef struct {
uint8_t oled_sleep_param = 0;
uint8_t oled_sleep_state = 0; // 0:wakeup; 1:sleep;
uint64_t oled_sleep_start = 0;
uint64_t ue_patch_state = 0;
} kvm_oled_state_t;
#endif // CONFIG_H_

View File

@@ -505,3 +505,15 @@ void oled_auto_sleep(void)
}
}
}
void kvm_show_UE(void)
{
if(kvm_hw_ver != 2){
OLED_ShowString(0, 0, "HDMI: UE", 16);
} else {
OLED_Revolve();
OLED_ShowString(0, 0, "HDMI: UE", 16);
// OLED_ShowString_AlignRight(AlignRightEND_P, 3, " FPS", 4);
// kvm_init_pcie_ui();
}
}

View File

@@ -7,5 +7,6 @@ void kvm_main_ui_disp(uint8_t first_disp, uint8_t subpage_changed);
void kvm_wifi_config_ui_disp(uint8_t first_disp, uint8_t subpage_changed);
void oled_auto_sleep_time_update(void);
void oled_auto_sleep(void);
void kvm_show_UE(void);
#endif // OLED_UI_H_

View File

@@ -7,6 +7,49 @@ using namespace maix::sys;
extern kvm_sys_state_t kvm_sys_state;
extern kvm_oled_state_t kvm_oled_state;
uint8_t get_hdmi_version()
{
FILE *fp;
uint8_t RW_Data[2];
system("/kvmapp/system/init.d/S15kvmhwd get_hdmi_version");
if(access("/etc/kvm/hdmi_version", F_OK) == 0){
fp = fopen("/etc/kvm/hdmi_version", "r");
fread(RW_Data, sizeof(char), 2, fp);
fclose(fp);
if(RW_Data[0] == 'u'){
// 6911uxc
if(RW_Data[1] == 'e'){
return 2;
} else if(RW_Data[1] == 'x') {
return 1;
} else {
return 1;
}
} else {
// 6911c
return 0;
}
} else {
return 0;
}
}
void Production_testing_patch(void)
{
// Product UE version detecte
if(get_hdmi_version() == 2){
printf("ue_patch_state = 1;\n");
kvm_oled_state.ue_patch_state = 1;
} else {
printf("ue_patch_state = 0;\n");
kvm_oled_state.ue_patch_state = 0;
}
// New products default to disabling mDNS functionality
system("rm -f /etc/init.d/S50ssdpd");
system("sync");
}
void new_app_init(void)
{
// Update the necessary scripts
@@ -87,6 +130,9 @@ void new_app_init(void)
else if(RW_Data[0] == 'u') hdmi_ver = 2;
}
system("/etc/init.d/S03usbdev stop_start");
create_temp_watchdog();
if(hdmi_ver == 2){
if(RW_Data_1[0] != '/'){
system("cp /kvmapp/system/ko/soph_mipi_rx.ko /mnt/system/ko/soph_mipi_rx.ko");

View File

@@ -2,8 +2,8 @@
#define SYSTEM_INIT_H_
#include "config.h"
void init_upadte(void);
void new_img_init(void);
void new_app_init(void);
void Production_testing_patch(void);
#endif // SYSTEM_INIT_H_

View File

@@ -470,9 +470,43 @@ uint8_t ion_free_space(void)
return 0;
}
uint8_t watchdog_sf_is_open()
int create_temp_watchdog(void)
{
FILE *file;
file = fopen(watchdog_temp_path, "w");
if (file == NULL) {
printf("[kvmv] Temp watchdog create error\n");
return -1;
}
// fprintf(file, "%s", 'v');
fclose(file);
return 1;
}
void rm_temp_watchdog(void)
{
if(access(watchdog_temp_path, F_OK) == 0) {
remove(watchdog_temp_path);
}
}
void auto_remove_temp_watchdog(void)
{
static uint8_t run_times = 0;
static uint8_t temp_watchdog_removed = 0;
if(temp_watchdog_removed) return;
if(run_times++ >= RM_Watchdog_times){
run_times = 0;
temp_watchdog_removed = 1;
rm_temp_watchdog();
}
}
uint8_t watchdog_sf_is_open(void)
{
if(access(watchdog_mode_path, F_OK) == 0) return 1;
if(access(watchdog_temp_path, F_OK) == 0) return 1;
else return 0;
}

View File

@@ -14,6 +14,7 @@ enum ip_addr_t
#define NIC_STATE_NO_EXIST -2
#define watchdog_mode_path "/etc/kvm/watchdog"
#define watchdog_temp_path "/tmp/watchdog"
#define watchdog_file "/tmp/nanokvm_wd"
// net_port
@@ -34,7 +35,10 @@ void kvm_update_rndis_state(void);
void kvm_update_tailscale_state(void);
uint8_t ion_free_space(void);
int get_nic_state(const char* interface_name);
uint8_t watchdog_sf_is_open();
int create_temp_watchdog(void);
void rm_temp_watchdog(void);
void auto_remove_temp_watchdog(void);
uint8_t watchdog_sf_is_open(void);
int check_watchdog();
#endif // SYSTEM_STATE_H_

View File

@@ -7,13 +7,6 @@ using namespace maix::peripheral;
kvm_sys_state_t kvm_sys_state;
kvm_oled_state_t kvm_oled_state;
/*
init.d:
/etc/init.d/S00kmod
/etc/init.d/S01fs
*/
void build_recovery(void)
{
uint8_t need_recovery = 0;
@@ -33,6 +26,13 @@ void* thread_oled_handle(void * arg)
OLED_ColorTurn(0); //0正常显示 1 反色显示
OLED_DisplayTurn(0); //0正常显示 1 屏幕翻转显示
OLED_Clear();
if(kvm_oled_state.ue_patch_state == 1){
kvm_show_UE();
while(kvm_sys_state.oled_thread_running){
time::sleep_ms(100);
}
OLED_Clear();
}
while(kvm_sys_state.oled_thread_running)
{
@@ -167,6 +167,7 @@ void* thread_sys_handle(void * arg)
}
time::sleep_ms(STATE_DELAY);
auto_remove_temp_watchdog();
}
kvm_sys_state.sys_thread_running = 0;
}
@@ -196,6 +197,12 @@ int main(int argc, char* argv[])
new_app_init();
}
// only for production testing
if (access("/tmp/S49kvmtest", F_OK) == 0){
printf("Production testing patch\n");
Production_testing_patch();
}
system("sync");
OLED_state = oled_exist();

View File

@@ -50,25 +50,7 @@ int main(int argc, char* argv[])
// ret = kvmv_read_img(1920, 1080, 0, 60, &p_kvmv_img_data, &kvmv_img_data_size);
printf("kvmv_read_img ret = %d\n", ret);
if(ret == IMG_H264_TYPE_IF){
uint8_t* p_kvmv_sps_data;
uint8_t* p_kvmv_pps_data;
uint32_t kvmv_sps_data_size;
uint32_t kvmv_pps_data_size;
ret = kvmv_get_sps_frame(&p_kvmv_sps_data, &kvmv_sps_data_size);
if(ret == IMG_H264_TYPE_SPS){
printf("Get SPS data size = %d\n", kvmv_sps_data_size);
} else {
printf("Get SPS data error!\n");
}
ret = kvmv_get_pps_frame(&p_kvmv_pps_data, &kvmv_pps_data_size);
if(ret == IMG_H264_TYPE_PPS){
printf("Get PPS data size = %d\n", kvmv_pps_data_size);
} else {
printf("Get PPS data error!\n");
}
}
// send...
// if(ret >= 0){
free_kvmv_data(&p_kvmv_img_data);

View File

@@ -1,16 +1,17 @@
import { http } from '@/lib/http.ts';
// get whether the frame detect is enabled
export function getFrameDetect() {
return http.get('/api/stream/mjpeg/detect');
}
// enable/disable frame detect
export function updateFrameDetect() {
return http.post('/api/stream/mjpeg/detect');
export function updateFrameDetect(enabled: boolean) {
const data = {
enabled
};
return http.post('/api/stream/mjpeg/detect', data);
}
// pause frame detect for a while (prevent a black screen when opening the page for the first time)
export function stopFrameDetect() {
return http.post('/api/stream/mjpeg/detect/stop');
export function stopFrameDetect(duration: number) {
const data = {
duration
};
return http.post('/api/stream/mjpeg/detect/stop', data);
}

View File

@@ -133,6 +133,16 @@ const en = {
serialPort: 'Serial Port',
serialPortPlaceholder: 'Please enter the serial port',
baudrate: 'Baud rate',
parity: 'Parity',
parityNone: 'None',
parityEven: 'Even',
parityOdd: 'Odd',
flowControl: 'Flow control',
flowControlNone: 'None',
flowControlSoft: 'Soft',
flowControlHard: 'Hard',
dataBits: 'Data bits',
stopBits: 'Stop bits',
confirm: 'Ok'
},
wol: {

View File

@@ -122,6 +122,16 @@ const pl = {
serialPort: 'Port szeregowy',
serialPortPlaceholder: 'Wprowadź port szeregowy',
baudrate: 'Szybkość transmisji',
parity: 'Kontrola parzystości',
parityNone: 'Brak kontroli',
parityEven: 'Parzystość',
parityOdd: 'Nieparzystość',
flowControl: 'Kontrola przepływu',
flowControlNone: 'Brak kontroli',
flowControlSoft: 'Miękka',
flowControlHard: 'Twarda',
dataBits: 'Bity danych',
stopBits: 'Bity stopu',
confirm: 'Ok'
},
wol: {

View File

@@ -5,6 +5,8 @@ const VIDEO_MODE_KEY = 'nano-kvm-vide-mode';
const WEB_RESOLUTION_KEY = 'nano-kvm-web-resolution';
const FPS_KEY = 'nano-kvm-fps';
const QUALITY_KEY = 'nano-kvm-quality';
const GOP_KEY = 'nano-kvm-gop';
const FRAME_DETECT_KEY = 'nano-kvm-frame-detect';
const MOUSE_STYLE_KEY = 'nano-kvm-mouse-style';
const MOUSE_MODE_KEY = 'nano-kvm-mouse-mode';
const SKIP_UPDATE_KEY = 'nano-kvm-check-update';
@@ -92,6 +94,24 @@ export function setQuality(quality: number) {
localStorage.setItem(QUALITY_KEY, String(quality));
}
export function getGop() {
const gop = localStorage.getItem(GOP_KEY);
return gop ? Number(gop) : null;
}
export function setGop(gop: number) {
localStorage.setItem(GOP_KEY, String(gop));
}
export function getFrameDetect(): boolean {
const enabled = localStorage.getItem(FRAME_DETECT_KEY);
return enabled ? Boolean(enabled) : true;
}
export function setFrameDetect(enabled: boolean) {
localStorage.setItem(FRAME_DETECT_KEY, String(enabled));
}
export function getMouseStyle() {
return localStorage.getItem(MOUSE_STYLE_KEY);
}

View File

@@ -1,31 +1,39 @@
import { useEffect, useState } from 'react';
import { Tooltip } from 'antd';
import clsx from 'clsx';
import { LoaderCircleIcon, Tally4Icon, Tally5Icon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { getFrameDetect, updateFrameDetect } from '@/api/stream.ts';
import * as api from '@/api/stream.ts';
import * as ls from '@/lib/localstorage.ts';
export const FrameDetect = () => {
const { t } = useTranslation();
const [isEnabled, setIsEnabled] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isEnabled, setIsEnabled] = useState(false);
useEffect(() => {
getFrameDetect().then((rsp) => {
if (rsp.code === 0) {
setIsEnabled(rsp.data.enabled);
}
});
const enabled = ls.getFrameDetect();
if (enabled) {
setIsEnabled(true);
} else {
api.updateFrameDetect(false);
}
}, []);
function update() {
if (isLoading) return;
setIsLoading(true);
updateFrameDetect()
const enabled = !isEnabled;
api
.updateFrameDetect(enabled)
.then((rsp) => {
if (rsp.code === 0) {
setIsEnabled(rsp.data.enabled);
setIsEnabled(enabled);
ls.setFrameDetect(enabled);
}
})
.finally(() => {
@@ -41,18 +49,20 @@ export const FrameDetect = () => {
>
{isLoading ? (
<LoaderCircleIcon className="animate-spin" size={18} />
) : isEnabled ? (
<div>
<Tally4Icon color="#22c55e" size={18} className="block group-hover:hidden" />
<Tally5Icon color="#ef4444" size={18} className="hidden group-hover:block" />
</div>
) : (
<div>
<Tally5Icon size={18} className="block group-hover:hidden" />
<Tally4Icon color="#22c55e" size={18} className="hidden group-hover:block" />
</div>
<>
{isEnabled ? <Tally4Icon color="#22c55e" size={18} /> : <Tally5Icon size={18} />}
<span
className={clsx(
'select-none text-sm',
isEnabled ? 'group-hover:text-red-500' : 'group-hover:text-green-500'
)}
>
{t('screen.frameDetect')}
</span>
</>
)}
<span className="select-none text-sm">{t('screen.frameDetect')}</span>
</div>
</Tooltip>
);

View File

@@ -0,0 +1,57 @@
import { Popover } from 'antd';
import { CheckIcon, SquareKanbanIcon } from 'lucide-react';
import { updateScreen } from '@/api/vm.ts';
import { setGop as setCookie } from '@/lib/localstorage';
type GopProps = {
gop: number;
setGop: (gop: number) => void;
};
const gopList = [
{ key: 10, label: '10' },
{ key: 30, label: '30' },
{ key: 50, label: '50' },
{ key: 100, label: '100' }
];
export const Gop = ({ gop, setGop }: GopProps) => {
async function update(value: number) {
if (value === gop) return;
const rsp = await updateScreen('gop', value);
if (rsp.code !== 0) {
return;
}
setGop(value);
setCookie(value);
}
const content = (
<>
{gopList.map((item) => (
<div
key={item.key}
className="flex cursor-pointer select-none items-center rounded py-1 pl-1 pr-6 hover:bg-neutral-700/70"
onClick={() => update(item.key)}
>
<div className="flex h-[14px] w-[20px] items-end text-blue-500">
{item.key === gop && <CheckIcon size={14} />}
</div>
<span>{item.label}</span>
</div>
))}
</>
);
return (
<Popover content={content} placement="rightTop">
<div className="flex h-[30px] cursor-pointer items-center space-x-2 rounded px-3 text-neutral-300 hover:bg-neutral-700/70">
<SquareKanbanIcon size={18} />
<span className="select-none text-sm">GOP</span>
</div>
</Popover>
);
};

View File

@@ -11,6 +11,7 @@ import { MenuItem } from '@/components/menu-item.tsx';
import { BitRateMap, QualityMap } from './constants.ts';
import { Fps } from './fps';
import { FrameDetect } from './frame-detect';
import { Gop } from './gop.tsx';
import { Quality } from './quality';
import { Reset } from './reset.tsx';
import { Resolution } from './resolution';
@@ -24,37 +25,47 @@ export const Screen = () => {
const [fps, setFps] = useState(30);
const [quality, setQuality] = useState(2);
const [gop, setGop] = useState(30);
useEffect(() => {
updateScreen('type', videoMode === 'mjpeg' ? 0 : 1);
updateScreen('resolution', resolution!.height);
updateQuality();
updateFps();
updateGop();
}, []);
function updateQuality() {
async function updateQuality() {
const cookieQuality = ls.getQuality();
if (!cookieQuality) return;
const key = cookieQuality >= 1 && cookieQuality <= 4 ? cookieQuality : 2;
const value = videoMode === 'mjpeg' ? QualityMap.get(key)! : BitRateMap.get(key)!;
updateScreen('quality', value).then((rsp) => {
if (rsp.code === 0) {
setQuality(key);
}
});
const rsp = await updateScreen('quality', value);
if (rsp.code === 0) {
setQuality(key);
}
}
function updateFps() {
async function updateFps() {
const cookieFps = ls.getFps();
if (!cookieFps) return;
updateScreen('fps', cookieFps).then((rsp) => {
if (rsp.code === 0) {
setFps(cookieFps);
}
});
const rsp = await updateScreen('fps', cookieFps);
if (rsp.code === 0) {
setFps(cookieFps);
}
}
async function updateGop() {
const cookieGop = ls.getGop();
if (!cookieGop) return;
const rsp = await updateScreen('gop', cookieGop);
if (rsp.code === 0) {
setGop(cookieGop);
}
}
const content = (
@@ -63,6 +74,7 @@ export const Screen = () => {
<Resolution />
<Quality quality={quality} setQuality={setQuality} />
<Fps fps={fps} setFps={setFps} />
{videoMode === 'h264' && <Gop gop={gop} setGop={setGop} />}
{videoMode === 'mjpeg' && <FrameDetect />}
<Reset />
</div>

View File

@@ -55,9 +55,9 @@ export const Resolution = () => {
</div>
) : (
<>
<span className="flex w-[32px]">{res.width}</span>
<span>x</span>
<span className="w-[36px]">{res.height}</span>
<span>{res.width}</span>
<span className="px-1">x</span>
<span>{res.height}</span>
</>
)}
</div>

View File

@@ -1,5 +1,5 @@
import { ChangeEvent, useState } from 'react';
import { Button, Input, InputNumber, Modal, Radio, RadioChangeEvent } from 'antd';
import { Button, Input, InputNumber, Modal, Radio, RadioChangeEvent, Select } from 'antd';
import { useSetAtom } from 'jotai';
import { SquareTerminalIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
@@ -13,6 +13,10 @@ export const SerialPort = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [port, setPort] = useState('');
const [baudrate, setBaudrate] = useState(115200);
const [parity, setParity] = useState<string>('none');
const [flowControl, setFlowControl] = useState<string>('none');
const [dataBits, setDataBits] = useState(8);
const [stopBits, setStopBits] = useState(1);
function openModal() {
setIsKeyboardEnable(false);
@@ -45,7 +49,7 @@ export const SerialPort = () => {
}
setIsModalOpen(false);
window.open(`/#terminal?port=${port}&baud=${baudrate}`, '_blank');
window.open(`/#terminal?port=${port}&baud=${baudrate}&parity=${parity}&flowControl=${flowControl}&dataBits=${dataBits}&stopBits=${stopBits}`, '_blank');
}
return (
@@ -92,6 +96,71 @@ export const SerialPort = () => {
/>
</div>
<div className="mt-7 flex items-center space-x-[20px]">
<div className="flex w-[80px] justify-end text-neutral-400">{t('terminal.parity')}</div>
<div className="w-1/2">
<Select
defaultValue="none"
value={parity}
onChange={setParity}
options={[
{ label: t('terminal.parityNone'), value: 'none' },
{ label: t('terminal.parityEven'), value: 'even' },
{ label: t('terminal.parityOdd'), value: 'odd' }
]}
/>
</div>
</div>
<div className="mt-7 flex items-center space-x-[20px]">
<div className="flex w-[80px] justify-end text-neutral-400">{t('terminal.flowControl')}</div>
<div className="w-1/2">
<Select
defaultValue="none"
value={flowControl}
onChange={setFlowControl}
options={[
{ label: t('terminal.flowControlNone'), value: 'none' },
{ label: t('terminal.flowControlSoft'), value: 'soft' },
{ label: t('terminal.flowControlHard'), value: 'hard' }
]}
/>
</div>
</div>
<div className="mt-7 flex items-center space-x-[20px]">
<div className="flex w-[80px] justify-end text-neutral-400">{t('terminal.dataBits')}</div>
<div className="w-1/2">
<Select
defaultValue={8}
value={dataBits}
onChange={setDataBits}
options={[
{ label: '5', value: 5 },
{ label: '6', value: 6 },
{ label: '7', value: 7 },
{ label: '8', value: 8 }
]}
/>
</div>
</div>
<div className="mt-7 flex items-center space-x-[20px]">
<div className="flex w-[80px] justify-end text-neutral-400">{t('terminal.stopBits')}</div>
<div className="w-1/2">
<Select
defaultValue={1}
value={stopBits}
onChange={setStopBits}
options={[
{ label: '1', value: 1 },
{ label: '2', value: 2 }
]}
/>
</div>
</div>
<div className="mb-3 mt-12 flex justify-center">
<Button type="primary" onClick={submit}>
{t('terminal.confirm')}
@@ -100,4 +169,4 @@ export const SerialPort = () => {
</Modal>
</>
);
};
};

View File

@@ -15,7 +15,6 @@ export const H264 = () => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let heartbeatTimer: any;
const videoElement = document.getElementById('screen') as HTMLVideoElement;
const url = `${getBaseUrl('ws')}/api/stream/h264`;
@@ -52,10 +51,6 @@ export const H264 = () => {
ws.send(JSON.stringify({ event: 'offer', data: JSON.stringify(offer) }));
})
.catch(console.log);
heartbeatTimer = setInterval(() => {
ws.send(JSON.stringify({ event: 'heartbeat', data: '' }));
}, 60 * 1000);
};
ws.onmessage = (event) => {
@@ -75,9 +70,6 @@ export const H264 = () => {
pc.addIceCandidate(data).catch(console.log);
break;
case 'heartbeat':
break;
default:
console.log('unhandled event: ', msg.event);
}
@@ -91,9 +83,6 @@ export const H264 = () => {
}, 15 * 1000);
return () => {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
}
ws.close();
pc.close();
};

View File

@@ -5,6 +5,7 @@ import { useAtomValue } from 'jotai';
import MonitorXIcon from '@/assets/images/monitor-x.svg';
import { stopFrameDetect } from '@/api/stream.ts';
import { getFrameDetect } from '@/lib/localstorage.ts';
import { getBaseUrl } from '@/lib/service.ts';
import { mouseStyleAtom } from '@/jotai/mouse.ts';
import { resolutionAtom } from '@/jotai/screen.ts';
@@ -15,7 +16,10 @@ export const Mjpeg = () => {
useEffect(() => {
// stop frame detect for a while
stopFrameDetect();
const enabled = getFrameDetect();
if (enabled) {
stopFrameDetect(10);
}
}, [resolution]);
return (

View File

@@ -48,9 +48,13 @@ export const Terminal = () => {
const searchParams = new URLSearchParams(urls[1]);
const port = searchParams.get('port');
const baud = searchParams.get('baud');
const parity = searchParams.get('parity');
const flowControl = searchParams.get('flowControl');
const dataBits = searchParams.get('dataBits');
const stopBits = searchParams.get('stopBits');
if (!port || !baud) return;
ws.send(`picocom ${port} -b ${baud}\r`);
ws.send(`picocom ${port} --baud ${baud} --parity ${parity} --flow ${flowControl} --databits ${dataBits} --stopbits ${stopBits}\r`);
};
const resizeScreen = () => {