diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..69d4730
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,74 @@
+## 2.2.1 [b5e48a0](https://github.com/sipeed/NanoKVM/commit/b5e48a07e82df3aedd60442342ae50b95684a697) (2025-02-21)
+
+* fix: mounted image were not being detected correctly
+* perf: add support for CD-ROM mode when mounting image (thanks to [@scpcom](https://github.com/scpcom))
+* perf: add a loading state during login
+* perf: add changelog link in settings
+* perf: update translation and cleanup the code (thanks to [@ChokunPlayZ](https://github.com/ChokunPlayZ) [@Stoufiler](https://github.com/Stoufiler) [@polyzium](https://github.com/polyzium) [@Jonher937](https://github.com/Jonher937) [@S33G](https://github.com/S33G))
+
+## 2.2.0 [0dbf8c0](https://github.com/sipeed/NanoKVM/commit/0dbf8c007f2d0183d0f0601c3da6d3c3fccd8b31) (2025-02-17)
+
+NanoKVM [Image v1.4.0](https://github.com/sipeed/NanoKVM/releases/tag/v1.4.0) has been released!
+
+> Please refer to the [wiki](https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/system/introduction.html) for more details about the image and application.
+
+* fix: improve password update notification logic (thanks to [@li20034](https://github.com/li20034))
+* perf: increase update wait time to 10s (from 6s)
+* perf: update Korean translation (thanks to [@forumi0721](https://github.com/forumi0721))
+* perf: update Traditional Chinese translation (thanks to [@protonchang](https://github.com/protonchang))
+* refactor: update `libkvm.so` and `libkvm_mmf.so` libraries
+
+## 2.1.6 [6eb4a4e](https://github.com/sipeed/NanoKVM/commit/6eb4a4ea6254f465a47f9881d13934c686649061) (2025-02-14)
+
+* feat: support downloading image from online URL (thanks to [@Itxaka](https://github.com/Itxaka))
+* feat: add keyboard shortcut `Ctrl+Alt+Del` (thanks to [@CaffeeLake](https://github.com/CaffeeLake))
+* fix: fix the CSRF issue
+* perf: add an option to configure custom ICE servers (thanks to [@VMFortress](https://github.com/VMFortress))
+* perf: removed unnecessary modifications to DNS configuration
+* perf: add an SSH enable/disable toggle in the web UI
+* perf: add a Tailscale enable/disable toggle in the web UI
+* perf: download Tailscale installation package from the official source
+* perf: automatic enable/disable GOMEMLIMIT on tailscale start/stop
+* perf: add JWT configuration
+ * secretKey: customize secret key. If empty, generated a random 64-byte secret key by default
+ * refreshTokenDuration: customize token expiration time
+ * revokeTokensOnLogout: invalidate all JWT tokens on logout
+* perf: implement secure password storage using bcrypt hashing
+* perf: implement integrity checks for online updates
+* refactor: refactor HDMI module and remove the dependency `libmaixcam_lib.so`
+* refactor: web terminal use pty instead of SSH
+* refactor: move Tailscale APIs from the `network` module to the `extensions` module
+
+## 2.1.5 [85f6447](https://github.com/sipeed/NanoKVM/commit/85f6447a16cc2591c6459b7d3dfda4d4cb75e98c) (2025-01-14)
+
+* feat: add HDMI reset for NanoKVM-PCIe
+* fix: remove unnecessary lock acquisition during HID reset
+* refactor: refactor Tailscale
+
+## 2.1.4 [d7ca7c4](https://github.com/sipeed/NanoKVM/commit/d7ca7c453d821ad099bf79b463969419041279cb) (2025-01-10)
+
+* feat: support configuring OLED sleep settings
+* feat: support setting the `GOMEMLIMIT` environment variable
+* fix: fix Wi-Fi configuration
+* perf: password changes now update both the web user and the system root user
+* perf: add MAC address verification for Wake-on-LAN
+* refactor: a lot update to web UI
+* refactor: refactor Tailscale
+
+## 2.1.3 [26078fe](https://github.com/sipeed/NanoKVM/commit/26078fe46e43d4543d7b09901b4992e4fbe4f01f) (2024-12-27)
+
+* feat: add API to retrieve Wi-Fi information
+* fix: fix keyboard modifier keys
+* fix: update keyboard and mouse HID codes
+* fix: update hardware version information
+
+## 2.1.2 [5a39562](https://github.com/sipeed/NanoKVM/commit/5a39562f2d32695933f4e7e86866136236cc9903) (2024-12-04)
+
+* feat: add hardware version to configuration
+* feat: add Wi-Fi configuration support for NanoKVM-PCIe
+* perf: update web UI
+* chore: add dependency libraries
+
+## 2.1.1 [74a303b](https://github.com/sipeed/NanoKVM/commit/74a303bd5cbb58f9d8ddd81abaaf4919dbbfb71b) (2024-11-06)
+
+* feat: support h264
diff --git a/README.md b/README.md
index 7b2c8b9..45802c2 100644
--- a/README.md
+++ b/README.md
@@ -15,19 +15,25 @@ English | [中文](./README_ZH.md) | [日本語](./README_JA.md)
-> Your NanoKVM Power by RISC-V !
+> Your NanoKVM is powered by [RISC-V](https://en.wikipedia.org/wiki/RISC-V)!
> If you have any issues or suggestions, creating issue here, or tell us with [MaixHub Discussion](https://maixhub.com/discussion/nanokvm).
## Opensource & Contribution
+### Update 2025.02.19
+All components of NanoKVM were open-sourced, including the [front-end](https://github.com/sipeed/NanoKVM/tree/main/web), [back-end](https://github.com/sipeed/NanoKVM/tree/main/server), [kvm_vision](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm), [kvm_mmf](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm_mmf), [kvm_system](https://github.com/sipeed/NanoKVM/tree/main/support), the [kvmapp update package](https://github.com/sipeed/NanoKVM-System/tree/main/kvmapp), [system sdk](https://github.com/sipeed/LicheeRV-Nano-Build/tree/NanoKVM), and [packaging methods](https://github.com/sipeed/LicheeRV-Nano-Build/blob/NanoKVM/kvm/NanoKVM_img.sh).
+
+### Update 2025.02.14
+Newest APP v2.1.6 add most security enhancements&bug fix, recommend regular users upgrade to this version or newer.
+
### Update 2025.02.05
For users concerned about security, you can read this issue: https://github.com/sipeed/NanoKVM/issues/301 , you will find explanations addressing all security concerns.
GitHub, as an open and transparent platform, provides a space for discussing so-called "backdoor" concerns. Open-source makes products more secure!
### Update 2024.10.18
-We have opensourced the backend code in 10.8, and thank you for Civil PR it immediately, we will give you newest NanoKVM-PCIe as a reward!
-The mid-Oct batch is starting testing and packaging, most order before 10.1 will ship out next and next next week.
+We have open-sourced the backend code in 10.8, and thank you for civil PR it immediately, we will give you newest NanoKVM-PCIe as a reward!
+The mid-Oct batch is starting testing and packaging, most orders before 10.1 will ship out next and next next week.
The aliexpress shipping date is change to Dec since 10.1, it is a conservative delivery time. the order in Oct. should send out during 11.15~12.15.
## Introduction
@@ -37,33 +43,33 @@ The Lichee NanoKVM is available in two versions:
NanoKVM Lite is a basic configuration that is suitable for individual users with certain DIY capabilities and enterprise users with bulk requirements.
The NanoKVM Full is a full version with a sophisticated case and complete accessories, as well as a built-in system mirror card that is ready to use at boot, and is recommended for individual users.
-
+
## Tech Specs
-| Products | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 |
-| --- | --- | --- | --- |
-| Compute Units | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
-| Resolution | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
-| Video encoding | MJPEG, H264(developing) | MJPEG, H264(developing) | MJPEG, H264 |
-| Video delay | 90~230ms | 90~230ms | 100~230ms |
-| UEFI/BIOS | ✓ | ✓ | ✓ |
-| Virtual HID | ✓ | ✓ | ✓ |
-| Virtual CD-ROM | ✓ | ✓ | ✓ |
-| IPMI | ✓ | ✓ | ✓ |
-| Wake-on-LAN | ✓ | ✓ | ✓ |
-| ETH | 100M/10M | 100M/10M | 1000M/100M/10M |
-| ATX power control | None,Users can connect by their own | USB interface IO control board | RJ-45 interface IO control board |
-| OLED | None,Users can connect by their own | 128x64 0.96" white | 128x32 0.91" white |
-| UART | 2 | 2 | 1 |
-| TF Card | None | ✓ | ✓ |
-| Expansion | None | PoE | WiFi/LTE |
-| Power consumption | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V |
-| Power input | PC USB can be powered | PC USB or auxiliary power supply | DC 5V 3A power supply is required |
-| Cooling | Silent fanless | Silent fanless | Fan cooling |
-| Size | 23x37x15mm
~1/30 PiKVM V4 size | 40x36x36mm
~1/7 PiKVM V4 size | 120x68x44mm |
+| Products | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 |
+|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |
+| Compute Units | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
+| Resolution | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
+| Video encoding | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 |
+| Video delay | 90~230ms | 90~230ms | 100~230ms |
+| UEFI/BIOS | ✓ | ✓ | ✓ |
+| Virtual HID | ✓ | ✓ | ✓ |
+| Virtual CD-ROM | ✓ | ✓ | ✓ |
+| IPMI | ✓ | ✓ | ✓ |
+| Wake-on-LAN | ✓ | ✓ | ✓ |
+| ETH | 100M/10M | 100M/10M | 1000M/100M/10M |
+| ATX power control | None,Users can connect by their own | USB interface IO control board | RJ-45 interface IO control board |
+| OLED | None,Users can connect by their own | 128x64 0.96" white | 128x32 0.91" white |
+| UART | 2 | 2 | 1 |
+| TF Card | None | ✓ | ✓ |
+| Expansion | None | PoE | WiFi/LTE |
+| Power consumption | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V |
+| Power input | PC USB can be powered | PC USB or auxiliary power supply | DC 5V 3A power supply is required |
+| Cooling | Silent fanless | Silent fanless | Fan cooling |
+| Size | 23x37x15mm ~1/30 PiKVM V4 size | 40x36x36mm ~1/7 PiKVM V4 size | 120x68x44mm |
-
+
## Hardware platform NanoKVM
@@ -73,7 +79,7 @@ The NanoKVM Lite is constructed by the LicheeRV Nano and HDMItoCSI board, and th
The NanoKVM image is built on LicheeRV Nano SDK and MaixCDK, and is compatible with materials that use the LicheeRV Nano, opposite the KVM software cannot be used with the LicheeRV Nano or other SG2002 products. If you would like to build an HDMI input application on LicheeRV Nano or MaixCam, please contact us for technical support.
-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
+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)
@@ -85,21 +91,28 @@ Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the mu
**Enhancements:**
-- [ ] Refactor the HDMI module and remove the libmaixcam_lib.so dependency
-- [ ] Disable SSH by default and add an enable/disable toggle in the web UI
-- [ ] Disable Tailscale by default and add an enable/disable toggle in the web UI
-- [ ] Add JWT configuration options to the configuration file
+- [x] Refactor the HDMI module and remove the libmaixcam_lib.so dependency
+- [x] Disable SSH by default and add an enable/disable toggle in the web UI
+- [x] Disable Tailscale by default and add an enable/disable toggle in the web UI
+- [x] Add JWT configuration options to the configuration file
+- [x] Secure password storage with bcrypt
**Distribution:**
-- [ ] Distribute applications via GitHub
-- [ ] Implement integrity checks for online updates
+- [x] Distribute applications via GitHub
+- [x] Implement integrity checks for online updates
- [ ] Support offline updates
**Bug Fixes:**
-- [ ] Fix the DNS issue
-- [ ] Fix the CSRF vulnerability
+- [x] Fix the DNS issue
+- [x] Fix the CSRF vulnerability
+
+**Features**
+
+- [ ] [79](https://github.com/sipeed/NanoKVM/issues/79) Support Zerotier
+- [ ] [99](https://github.com/sipeed/NanoKVM/issues/99) Support WireGuard
+- [ ] [249](https://github.com/sipeed/NanoKVM/issues/249) Add Mouse Jiggler
## Where to buy
@@ -107,11 +120,7 @@ Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the mu
* [淘宝](https://item.taobao.com/item.htm?id=811206560480)
* [Preorder (anyother country that not support in Aliexpress or Taobao)](https://sipeed.com/nanokvm)
-
## Community
* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm)
* QQ group: 703230713
-
-
-
diff --git a/README_JA.md b/README_JA.md
index 2555f89..1bdb4a4 100644
--- a/README_JA.md
+++ b/README_JA.md
@@ -41,7 +41,7 @@ NanoKVM Fullは完全版で、洗練されたケースと完全なアクセサ
| --- | --- | --- | --- |
| 計算ユニット | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
| 解像度 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
-| ビデオエンコーディング | MJPEG, H264(開発中) | MJPEG, H264(開発中) | MJPEG, H264 |
+| ビデオエンコーディング | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 |
| ビデオ遅延 | 90~230ms | 90~230ms | 100~230ms |
| UEFI/BIOS | ✓ | ✓ | ✓ |
| 仮想HID | ✓ | ✓ | ✓ |
diff --git a/README_ZH.md b/README_ZH.md
index 4012b82..a4f0730 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -33,7 +33,7 @@ NanoKVM Full 为完整版配置,带精致外壳和完整配件,内置开机
| --- | --- | --- | --- |
| 计算单元 | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
| 分辨率 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
-| 视频编码 | MJPEG, H264(developing) | MJPEG, H264(developing) | MJPEG, H264 |
+| 视频编码 | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 |
| 视频延迟 | 90~230ms | 90~230ms | 100~230ms |
| UEFI/BIOS | ✓ | ✓ | ✓ |
| 模拟USB键鼠 | ✓ | ✓ | ✓ |
diff --git a/server/README.md b/server/README.md
index 831bfdb..0dc544e 100644
--- a/server/README.md
+++ b/server/README.md
@@ -44,9 +44,13 @@ logger:
# Note: Only disable authentication in development environment
authentication: enable
-# JWT secret key configuration
-# If left empty, a random key will be generated on each server start
-secretKey: ""
+jwt:
+ # JWT secret key. If left empty, a random 64-byte key will be generated automatically.
+ secretKey: ""
+ # JWT token expiration time in seconds. Default: 2678400 (31 days)
+ refreshTokenDuration: 2678400
+ # Invalidate all JWT tokens when the user logs out. Default: true
+ revokeTokensOnLogout: true
# Address for custom STUN server
stun: stun.l.google.com:19302
@@ -80,6 +84,15 @@ Note: Use Linux operating system (x86-64). This build process is not compatible
3. Run `patchelf --add-rpath \$ORIGIN/dl_lib NanoKVM-Server` to modify the RPATH of the executable file.
4. Deploy the Application
- 1. Before deploying, update the application to the latest version in your browser. Follow the instructions [here](https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/system/updating.html).
+ 1. File uploads requires SSH. Please enable it in the Web Settings: `Settings > SSH`;
2. Replace the original file in the NanoKVM `/kvmapp/server/` directory with the newly compiled `NanoKVM-Server`.
- 3. Restart the service on NanoKVM by executing `/etc/init.d/S95nanokvm restart`.
\ No newline at end of file
+ 3. Restart the service on NanoKVM by executing `/etc/init.d/S95nanokvm restart`.
+
+## Manually Update
+
+> File uploads requires SSH. Please enable it in the Web Settings: `Settings > SSH`;
+
+1. Download the latest application from [GitHub](https://github.com/sipeed/NanoKVM/releases);
+2. Unzip the downloaded file and rename the unzipped folder to `kvmapp`;
+3. Back up the existing `/kvmapp` directory on your NanoKVM, then replace it with the new `kvmapp` folder;
+4. Run `/etc/init.d/S95nanokvm restart` on your NanoKVM to restart the service.
diff --git a/server/README_ZH.md b/server/README_ZH.md
index 1050fa2..acfe6dd 100644
--- a/server/README_ZH.md
+++ b/server/README_ZH.md
@@ -42,9 +42,13 @@ logger:
# 注意:生产环境中请勿使用 disable。
authentication: enable
-# JWT 密钥
-# 如果不设置,则每次服务启动时使用随机生成的密钥。
-secretKey: ""
+jwt:
+ # jwt 密钥。设置为空则使用随机生成的64位密钥
+ secretKey: ""
+ # jwt token 过期时间(单位:秒),默认为2678400(31天)
+ refreshTokenDuration: 2678400
+ # 在帐号登出时是否使所有 jwt token 失效。默认为 true
+ revokeTokensOnLogout: true
stun: stun.l.google.com:19302
@@ -75,6 +79,15 @@ turn:
3. 执行 `patchelf --add-rpath \$ORIGIN/dl_lib NanoKVM-Server` 修改可执行文件的 RPATH 属性。
4. 部署
- 1. 部署前,请先在浏览器中将应用[更新](https://wiki.sipeed.com/hardware/zh/kvm/NanoKVM/system/updating.html)到最新版本;
- 2. 使用编译生成的 `NanoKVM-Server` 文件,替换 NanoKVM 中 `/kvmapp/server/` 目录下的原始文件;
+ 1. 上传文件需要启用 SSH 功能。请在 Web `设置 - SSH` 中检查 SSH 是否已经启用;
+ 2. 使用编译生成的 `NanoKVM-Server` 文件,替换 NanoKVM 中 `/kvmapp/server/` 目录下的原始文件;
3. 在 NanoKVM 中执行 `/etc/init.d/S95nanokvm restart` 重启服务。
+
+## 手动更新
+
+> 请确保已经在 Web 界面的 `设置 - SSH` 中启用了 SSH 功能,以便上传文件。
+
+1. 从 [GitHub](https://github.com/sipeed/NanoKVM/releases) 下载最新的应用安装包;
+2. 解压缩下载的安装包,并将解压后的文件夹重命名为 `kvmapp`;
+3. 备份 NanoKVM 系统中的 `/kvmapp` 目录,然后用解压后的 `kvmapp` 文件夹替换现有目录。
+4. 在 NanoKVM 中执行 `/etc/init.d/S95nanokvm restart` 重启服务。
\ No newline at end of file
diff --git a/server/common/cgo.go b/server/common/cgo.go
index f618eee..6289d30 100644
--- a/server/common/cgo.go
+++ b/server/common/cgo.go
@@ -139,7 +139,7 @@ func (k *KvmVision) ReadH264PPS() ([]byte, int) {
return data, result
}
-func (k *KvmVision) EnableHdmi(enable bool) int {
+func (k *KvmVision) SetHDMI(enable bool) int {
hdmiEnable := C.uint8_t(0)
if enable {
hdmiEnable = C.uint8_t(1)
diff --git a/server/config/config.go b/server/config/config.go
index f1367a8..ab982b7 100644
--- a/server/config/config.go
+++ b/server/config/config.go
@@ -2,28 +2,24 @@ package config
import (
"bytes"
- "crypto/rand"
- "encoding/base64"
"errors"
- "fmt"
"log"
"os"
"sync"
- "time"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
var (
- config Config
- once sync.Once
+ instance Config
+ once sync.Once
)
func GetInstance() *Config {
once.Do(initialize)
- return &config
+ return &instance
}
func initialize() {
@@ -43,20 +39,16 @@ func initialize() {
log.Fatalf("Failed to validate configuration!")
}
- if err := viper.Unmarshal(&config); err != nil {
+ if err := viper.Unmarshal(&instance); err != nil {
log.Fatalf("Failed to parse configuration: %s", err)
}
- if config.Authentication == "disable" {
+ checkDefaultValue()
+
+ if instance.Authentication == "disable" {
log.Println("NOTICE: Authentication is disabled! Please ensure your service is secure!")
}
- if config.SecretKey == "" {
- config.SecretKey = generateRandomString()
- }
-
- config.Hardware = getHardware()
-
log.Println("config loaded successfully")
}
@@ -128,16 +120,3 @@ func validate() error {
return readByDefault()
}
-
-// Generate random string for secret key.
-func generateRandomString() string {
- b := make([]byte, 64)
- _, err := rand.Read(b)
- if err != nil {
- currentTime := time.Now().UnixNano()
- timeString := fmt.Sprintf("%d", currentTime)
- return fmt.Sprintf("%064s", timeString)
- }
-
- return base64.URLEncoding.EncodeToString(b)
-}
diff --git a/server/config/default.go b/server/config/default.go
index 9ec3075..d58ebe5 100644
--- a/server/config/default.go
+++ b/server/config/default.go
@@ -14,6 +14,11 @@ var defaultConfig = &Config{
Level: "info",
File: "stdout",
},
+ JWT: JWT{
+ SecretKey: "",
+ RefreshTokenDuration: 2678400,
+ RevokeTokensOnLogout: true,
+ },
Stun: "stun.l.google.com:19302",
Turn: Turn{
TurnAddr: "turn.cloudflare.com:3478",
@@ -22,3 +27,24 @@ var defaultConfig = &Config{
},
Authentication: "enable",
}
+
+func checkDefaultValue() {
+ if instance.JWT.SecretKey == "" {
+ instance.JWT.SecretKey = generateRandomSecretKey()
+ instance.JWT.RevokeTokensOnLogout = true
+ }
+
+ if instance.JWT.RefreshTokenDuration == 0 {
+ instance.JWT.RefreshTokenDuration = 2678400
+ }
+
+ if instance.Stun == "" {
+ instance.Stun = "stun.l.google.com:19302"
+ }
+
+ if instance.Authentication == "" {
+ instance.Authentication = "enable"
+ }
+
+ instance.Hardware = getHardware()
+}
diff --git a/server/config/hardware.go b/server/config/hardware.go
index 981324f..79d4e2f 100644
--- a/server/config/hardware.go
+++ b/server/config/hardware.go
@@ -85,7 +85,7 @@ func getHardware() (h Hardware) {
default:
h = HWAlpha
- log.Error("Unsupported hardware version: %s", version)
+ log.Errorf("Unsupported hardware version: %s", version)
}
return
diff --git a/server/config/jwt.go b/server/config/jwt.go
new file mode 100644
index 0000000..9edd85b
--- /dev/null
+++ b/server/config/jwt.go
@@ -0,0 +1,28 @@
+package config
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "time"
+)
+
+// RegenerateSecretKey regenerate secret key when logout
+func RegenerateSecretKey() {
+ if instance.JWT.RevokeTokensOnLogout {
+ instance.JWT.SecretKey = generateRandomSecretKey()
+ }
+}
+
+// Generate random string for secret key.
+func generateRandomSecretKey() string {
+ b := make([]byte, 64)
+ _, err := rand.Read(b)
+ if err != nil {
+ currentTime := time.Now().UnixNano()
+ timeString := fmt.Sprintf("%d", currentTime)
+ return fmt.Sprintf("%064s", timeString)
+ }
+
+ return base64.URLEncoding.EncodeToString(b)
+}
diff --git a/server/config/types.go b/server/config/types.go
index 3ad492e..2f8f069 100644
--- a/server/config/types.go
+++ b/server/config/types.go
@@ -6,7 +6,7 @@ type Config struct {
Cert Cert `yaml:"cert"`
Logger Logger `yaml:"logger"`
Authentication string `yaml:"authentication"`
- SecretKey string `yaml:"secretKey"`
+ JWT JWT `yaml:"jwt"`
Stun string `yaml:"stun"`
Turn Turn `yaml:"turn"`
@@ -28,6 +28,12 @@ type Cert struct {
Key string `yaml:"key"`
}
+type JWT struct {
+ SecretKey string `yaml:"secretKey"`
+ RefreshTokenDuration uint64 `yaml:"refreshTokenDuration"`
+ RevokeTokensOnLogout bool `yaml:"revokeTokensOnLogout"`
+}
+
type Turn struct {
TurnAddr string `yaml:"turnAddr"`
TurnUser string `yaml:"turnUser"`
diff --git a/server/dl_lib/libkvm.so b/server/dl_lib/libkvm.so
index 68045f9..a79e1d9 100644
Binary files a/server/dl_lib/libkvm.so and b/server/dl_lib/libkvm.so differ
diff --git a/server/dl_lib/libkvm_mmf.so b/server/dl_lib/libkvm_mmf.so
new file mode 100644
index 0000000..992c7ab
Binary files /dev/null and b/server/dl_lib/libkvm_mmf.so differ
diff --git a/server/dl_lib/libmaixcam_lib.so b/server/dl_lib/libmaixcam_lib.so
deleted file mode 100644
index fa47a05..0000000
Binary files a/server/dl_lib/libmaixcam_lib.so and /dev/null differ
diff --git a/server/go.mod b/server/go.mod
index 7637c82..b123016 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -3,6 +3,7 @@ module NanoKVM-Server
go 1.22.1
require (
+ github.com/creack/pty v1.1.24
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/validator/v10 v10.20.0
diff --git a/server/go.sum b/server/go.sum
index 9f44074..271cc14 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -6,6 +6,8 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
+github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -156,28 +158,17 @@ go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTV
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
-golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
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/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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
-golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
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/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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
-golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
-golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
diff --git a/server/include/kvm_vision.h b/server/include/kvm_vision.h
index 2fa2a25..f4b013c 100644
--- a/server/include/kvm_vision.h
+++ b/server/include/kvm_vision.h
@@ -37,6 +37,7 @@ void kvmv_init(uint8_t _debug_info_en);
* @param _pp_kvm_data @output: Encode data
* @param _p_kvmv_data_size @output: Encode data size
* @return
+ -4: Modifying image resolution, please wait
-3: img buffer full
-2: VENC Error
-1: No images were acquired
diff --git a/server/main.go b/server/main.go
index 4dbdee7..8db3e77 100644
--- a/server/main.go
+++ b/server/main.go
@@ -8,9 +8,11 @@ import (
"NanoKVM-Server/router"
"NanoKVM-Server/utils"
"fmt"
+ "log"
"os"
"os/signal"
"syscall"
+ "time"
"github.com/gin-gonic/gin"
cors "github.com/rs/cors/wrapper/gin"
@@ -19,33 +21,51 @@ import (
func main() {
initialize()
defer dispose()
- signalHandler()
- gin.SetMode(gin.ReleaseMode)
- r := gin.New()
- r.Use(gin.Recovery())
- r.Use(cors.AllowAll())
-
- router.Init(r)
-
- run(r)
+ run()
}
func initialize() {
logger.Init()
+ // init screen parameters
_ = common.GetScreen()
- _ = common.GetKvmVision()
+
+ // init HDMI
+ vision := common.GetKvmVision()
+ vision.SetHDMI(false)
+ time.Sleep(10 * time.Millisecond)
+ vision.SetHDMI(true)
utils.InitGoMemLimit()
+
+ sigChan := make(chan os.Signal, 1)
+ signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
+ go func() {
+ sig := <-sigChan
+ log.Printf("\nReceived signal: %v\n", sig)
+
+ dispose()
+ os.Exit(0)
+ }()
}
-func run(r *gin.Engine) {
+func run() {
conf := config.GetInstance()
+ gin.SetMode(gin.ReleaseMode)
+ r := gin.New()
+ r.Use(gin.Recovery())
+
+ if conf.Authentication == "disable" {
+ r.Use(cors.AllowAll())
+ }
+
+ router.Init(r)
+
httpAddr := fmt.Sprintf(":%d", conf.Port.Http)
httpsAddr := fmt.Sprintf(":%d", conf.Port.Https)
- fmt.Printf("proto: %s, port: %d %d\n", conf.Proto, conf.Port.Http, conf.Port.Https)
+ log.Printf("proto: %s, port: %d %d\n", conf.Proto, conf.Port.Http, conf.Port.Https)
if conf.Proto == "https" {
r.Use(middleware.Tls())
@@ -69,16 +89,3 @@ func run(r *gin.Engine) {
func dispose() {
common.GetKvmVision().Close()
}
-
-func signalHandler() {
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
-
- go func() {
- sig := <-sigChan
- fmt.Printf("\nReceived signal: %v\n", sig)
-
- dispose()
- os.Exit(0)
- }()
-}
diff --git a/server/middleware/jwt.go b/server/middleware/jwt.go
index 61d7616..8602881 100644
--- a/server/middleware/jwt.go
+++ b/server/middleware/jwt.go
@@ -16,10 +16,6 @@ type Token struct {
jwt.RegisteredClaims
}
-const (
- ExpireDuration = 31 * 24 * time.Hour
-)
-
func CheckToken() gin.HandlerFunc {
return func(c *gin.Context) {
conf := config.GetInstance()
@@ -46,24 +42,25 @@ func CheckToken() gin.HandlerFunc {
func GenerateJWT(username string) (string, error) {
conf := config.GetInstance()
+ expireDuration := time.Duration(conf.JWT.RefreshTokenDuration) * time.Second
+
claims := Token{
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
- ExpiresAt: jwt.NewNumericDate(time.Now().Add(ExpireDuration)),
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(expireDuration)),
},
}
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
- return t.SignedString([]byte(conf.SecretKey))
+ return t.SignedString([]byte(conf.JWT.SecretKey))
}
func ParseJWT(jwtToken string) (*Token, error) {
conf := config.GetInstance()
t, err := jwt.ParseWithClaims(jwtToken, &Token{}, func(token *jwt.Token) (interface{}, error) {
- secretKey := conf.SecretKey
- return []byte(secretKey), nil
+ return []byte(conf.JWT.SecretKey), nil
})
if err != nil {
log.Debugf("parse jwt error: %s", err)
diff --git a/server/proto/download.go b/server/proto/download.go
index fa122ae..461fb9f 100644
--- a/server/proto/download.go
+++ b/server/proto/download.go
@@ -1,7 +1,7 @@
package proto
type ImageEnabledRsp struct {
- Enabled bool `json:"enabled"`
+ Enabled bool `json:"enabled"`
}
type StatusImageRsp struct {
diff --git a/server/proto/storage.go b/server/proto/storage.go
index 75a6124..33c25cf 100644
--- a/server/proto/storage.go
+++ b/server/proto/storage.go
@@ -5,9 +5,14 @@ type GetImagesRsp struct {
}
type MountImageReq struct {
- File string `json:"file" validate:"omitempty"`
+ File string `json:"file" validate:"omitempty"`
+ Cdrom bool `json:"cdrom" validate:"omitempty"`
}
type GetMountedImageRsp struct {
File string `json:"file"`
}
+
+type GetCdRomRsp struct {
+ Cdrom int64 `json:"cdrom"`
+}
diff --git a/server/proto/vm.go b/server/proto/vm.go
index 75c7a9c..7529a8e 100644
--- a/server/proto/vm.go
+++ b/server/proto/vm.go
@@ -79,3 +79,7 @@ type GetOLEDRsp struct {
Exist bool `json:"exist"`
Sleep int `json:"sleep"`
}
+
+type GetSSHStateRsp struct {
+ Enabled bool `json:"enabled"`
+}
diff --git a/server/router/auth.go b/server/router/auth.go
index 5115a92..aa237fa 100644
--- a/server/router/auth.go
+++ b/server/router/auth.go
@@ -17,4 +17,5 @@ func authRouter(r *gin.Engine) {
api.GET("/auth/password", service.IsPasswordUpdated) // is password updated
api.GET("/auth/account", service.GetAccount) // get account
api.POST("/auth/password", service.ChangePassword) // change password
+ api.POST("/auth/logout", service.Logout) // logout
}
diff --git a/server/router/extensions.go b/server/router/extensions.go
new file mode 100644
index 0000000..696a94d
--- /dev/null
+++ b/server/router/extensions.go
@@ -0,0 +1,25 @@
+package router
+
+import (
+ "NanoKVM-Server/middleware"
+ "NanoKVM-Server/service/extensions/tailscale"
+
+ "github.com/gin-gonic/gin"
+)
+
+func extensionsRouter(r *gin.Engine) {
+ api := r.Group("/api/extensions").Use(middleware.CheckToken())
+
+ ts := tailscale.NewService()
+
+ api.POST("/tailscale/install", ts.Install) // install tailscale
+ api.POST("/tailscale/uninstall", ts.Uninstall) // uninstall tailscale
+ api.GET("/tailscale/status", ts.GetStatus) // get tailscale status
+ api.POST("/tailscale/up", ts.Up) // run tailscale up
+ api.POST("/tailscale/down", ts.Down) // run tailscale down
+ api.POST("/tailscale/login", ts.Login) // tailscale login
+ api.POST("/tailscale/logout", ts.Logout) // tailscale logout
+ api.POST("/tailscale/start", ts.Start) // tailscale start
+ api.POST("/tailscale/stop", ts.Stop) // tailscale stop
+ api.POST("/tailscale/restart", ts.Restart) // tailscale restart
+}
diff --git a/server/router/network.go b/server/router/network.go
index 906c2bb..08e4a6f 100644
--- a/server/router/network.go
+++ b/server/router/network.go
@@ -18,15 +18,5 @@ func networkRouter(r *gin.Engine) {
api.GET("/network/wol/mac", service.GetMac) // get mac list
api.DELETE("/network/wol/mac", service.DeleteMac) // delete mac
- api.POST("/network/tailscale/install", service.TsInstall) // install tailscale
- api.POST("/network/tailscale/uninstall", service.TsUninstall) // uninstall tailscale
- api.GET("/network/tailscale/status", service.GetTsStatus) // get tailscale status
- api.POST("/network/tailscale/up", service.TsUp) // run tailscale up
- api.POST("/network/tailscale/down", service.TsDown) // run tailscale down
- api.POST("/network/tailscale/login", service.TsLogin) // tailscale login
- api.POST("/network/tailscale/logout", service.TsLogout) // tailscale logout
- api.POST("/network/tailscale/stop", service.TsStop) // tailscale stop
- api.POST("/network/tailscale/restart", service.TsRestart) // tailscale restart
-
api.GET("/network/wifi", service.GetWifi) // get Wi-Fi information
}
diff --git a/server/router/router.go b/server/router/router.go
index 2c0e918..8b6148a 100644
--- a/server/router/router.go
+++ b/server/router/router.go
@@ -38,4 +38,5 @@ func server(r *gin.Engine) {
hidRouter(r)
wsRouter(r)
downloadRouter(r)
+ extensionsRouter(r)
}
diff --git a/server/router/storage.go b/server/router/storage.go
index f7fdf89..d29a327 100644
--- a/server/router/storage.go
+++ b/server/router/storage.go
@@ -14,4 +14,5 @@ func storageRouter(r *gin.Engine) {
api.GET("/storage/image", service.GetImages) // get image list
api.GET("/storage/image/mounted", service.GetMountedImage) // get mounted image
api.POST("/storage/image/mount", service.MountImage) // mount image
+ api.GET("/storage/cdrom", service.GetCdRom) // get CD-ROM flag
}
diff --git a/server/router/vm.go b/server/router/vm.go
index d8bcb94..1dced36 100644
--- a/server/router/vm.go
+++ b/server/router/vm.go
@@ -35,4 +35,8 @@ func vmRouter(r *gin.Engine) {
api.POST("/vm/oled", service.SetOLED) // set OLED configuration
api.POST("/vm/hdmi/reset", service.ResetHdmi) // reset hdmi (pcie only)
+
+ api.GET("/vm/ssh", service.GetSSHState) // get SSH state
+ api.POST("/vm/ssh/enable", service.EnableSSH) // enable SSH
+ api.POST("/vm/ssh/disable", service.DisableSSH) // disable SSH
}
diff --git a/server/service/application/application.go b/server/service/application/application.go
deleted file mode 100644
index 445dd20..0000000
--- a/server/service/application/application.go
+++ /dev/null
@@ -1,211 +0,0 @@
-package application
-
-import (
- "fmt"
- "io"
- "net/http"
- "os"
- "os/exec"
- "strings"
- "time"
-
- "github.com/gin-gonic/gin"
- log "github.com/sirupsen/logrus"
-
- "NanoKVM-Server/proto"
- "NanoKVM-Server/utils"
-)
-
-const (
- maxTries = 3
-)
-
-func (s *Service) GetVersion(c *gin.Context) {
- var rsp proto.Response
- log.Debugf("get version api triggered")
-
- // current version
- currentVersion := "1.0.0"
- content, err := os.ReadFile(versionFile)
- if err == nil {
- currentVersion = strings.ReplaceAll(string(content), "\n", "")
- }
- log.Debugf("current version: %s", currentVersion)
-
- // latest version
- url := fmt.Sprintf("%s?now=%d", versionURL, time.Now().Unix())
- resp, err := http.Get(url)
- if err != nil {
- log.Debugf("get latest version failed: %v", err)
- rsp.ErrRsp(c, -2, "Unable to access sipeed.com. Please check your network.")
- return
- }
- defer func() {
- _ = resp.Body.Close()
- }()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- log.Errorf("read body failed: %v", err)
- rsp.ErrRsp(c, -4, "read body failed")
- return
- }
-
- if resp.StatusCode != http.StatusOK {
- log.Errorf("server responded with status code: %d", resp.StatusCode)
- rsp.ErrRsp(c, -3, fmt.Sprintf("get version failed, server returns status code: %d, body: %s", resp.StatusCode, string(body)))
- return
- }
-
- latestVersion := strings.ReplaceAll(string(body), "\n", "")
-
- rsp.OkRspWithData(c, &proto.GetVersionRsp{
- Current: currentVersion,
- Latest: latestVersion,
- })
-}
-
-func (s *Service) Update(c *gin.Context) {
- var rsp proto.Response
- log.Debugf("update application api triggered")
-
- if err := updateApp(); err != nil {
- log.Debugf("update failed: %s", err)
- rsp.ErrRsp(c, -1, fmt.Sprintf("update failed: %s", err))
- return
- }
-
- rsp.OkRsp(c)
- log.Debugf("update application success")
-
- // Sleep for a second before restarting the device
- time.Sleep(1 * time.Second)
-
- _ = exec.Command("sh", "-c", "/etc/init.d/S95nanokvm restart").Run()
-}
-
-func updateApp() error {
- log.Debugf("update application")
- _ = os.RemoveAll(temporary)
- _ = os.MkdirAll(temporary, 0o755)
- defer func() {
- _ = os.RemoveAll(temporary)
- }()
-
- if err := downloadLib(); err != nil {
- log.Errorf("download lib failed: %s", err)
- return err
- }
-
- if err := downloadApp(); err != nil {
- log.Errorf("download app failed: %s", err)
- return err
- }
-
- err := utils.MoveFile(temporary+"/"+libName, temporary+"/latest/kvm_system/dl_lib/"+libName) // move lib
- if err != nil {
- log.Errorf("rename lib failed: %s", err)
- return err
- }
-
- // backup old version
- err = os.RemoveAll(backup)
- if err != nil {
- log.Errorf("remove backup failed: %s", err)
- return err
- }
-
- err = utils.MoveFilesRecursively(workspace, backup)
- if err != nil {
- log.Errorf("backuping old libraries failed: %s", err)
- return err
- }
-
- // update
- err = utils.MoveFilesRecursively(temporary+"/latest", workspace)
- if err != nil {
- log.Errorf("failed to move update back in place: %s", err)
- return err
- }
-
- // modify permissions
- err = utils.ChmodRecursively(workspace, 0o755)
- if err != nil {
- log.Errorf("chmod failed: %s", err)
- return err
- }
-
- return nil
-}
-
-func downloadApp() error {
- var err error
- log.Debugf("downloading latest application...")
- url := fmt.Sprintf("%s?now=%d", applicationURL, time.Now().Unix())
- for i := range maxTries {
- log.Debugf("attempt #%d/%d", i+1, maxTries)
- if i > 0 {
- time.Sleep(time.Second * 3) // wait for 3 seconds before retrying the download attempt
- }
-
- var req *http.Request
- req, err = http.NewRequest("GET", url, nil)
- if err != nil {
- log.Errorf("new request err: %s", err)
- continue
- }
-
- zipFile := temporary + "/latest.zip"
- log.Debugf("update will be saved to: %s", zipFile)
- err = utils.Download(req, zipFile)
- if err != nil {
- log.Errorf("downloading latest application failed, try again...")
- continue
- }
-
- err = utils.Unzip(zipFile, temporary)
- if err != nil {
- log.Errorf("unzip app failed: %s", err)
- continue
- }
-
- return nil
- }
- return err
-}
-
-func downloadLib() error {
- log.Debugf("downloading libs...")
- content, err := os.ReadFile("/device_key")
- if err != nil {
- log.Errorf("error reading device key: %s", err)
- return err
- }
- deviceKey := strings.ReplaceAll(string(content), "\n", "")
-
- for i := range maxTries {
- log.Debugf("attempt #%d/%d", i+1, maxTries)
- if i > 0 {
- time.Sleep(time.Second * 3) // wait for 3 seconds before retrying the download attempt
- }
-
- var req *http.Request
- url := fmt.Sprintf("%s?uid=%s", libURL, deviceKey)
- req, err = http.NewRequest("GET", url, nil)
- if err != nil {
- log.Errorf("error creating new request: %s", err)
- continue
- }
- req.Header.Set("token", "MaixVision2024")
-
- target := fmt.Sprintf("%s/%s", temporary, libName)
-
- err = utils.Download(req, target)
- if err != nil {
- log.Errorf("downloading lib failed: %s", err)
- continue
- }
- return nil
- }
- return err
-}
diff --git a/server/service/application/service.go b/server/service/application/service.go
index 62ba4f7..be29bd1 100644
--- a/server/service/application/service.go
+++ b/server/service/application/service.go
@@ -1,15 +1,11 @@
package application
const (
- versionURL = "https://cdn.sipeed.com/nanokvm/latest"
- applicationURL = "https://cdn.sipeed.com/nanokvm/latest.zip"
- libURL = "https://maixvision.sipeed.com/api/v1/nanokvm/encryption"
+ BaseURL = "https://cdn.sipeed.com/nanokvm"
- temporary = "/root/.kvmcache"
- workspace = "/kvmapp"
- backup = "/root/old"
- libName = "libmaixcam_lib.so"
- versionFile = "/kvmapp/version"
+ AppDir = "/kvmapp"
+ BackupDir = "/root/old"
+ CacheDir = "/root/.kvmcache"
)
type Service struct{}
diff --git a/server/service/application/update.go b/server/service/application/update.go
new file mode 100644
index 0000000..6b53fc3
--- /dev/null
+++ b/server/service/application/update.go
@@ -0,0 +1,222 @@
+package application
+
+import (
+ "crypto/sha512"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ log "github.com/sirupsen/logrus"
+
+ "NanoKVM-Server/proto"
+ "NanoKVM-Server/utils"
+)
+
+const (
+ maxTries = 3
+)
+
+type Latest struct {
+ Version string `json:"version"`
+ Name string `json:"name"`
+ Sha512 string `json:"sha512"`
+ Size uint `json:"size"`
+}
+
+func (s *Service) GetVersion(c *gin.Context) {
+ var rsp proto.Response
+
+ // current version
+ currentVersion := "1.0.0"
+
+ versionFile := fmt.Sprintf("%s/version", AppDir)
+ if version, err := os.ReadFile(versionFile); err == nil {
+ currentVersion = strings.ReplaceAll(string(version), "\n", "")
+ }
+
+ log.Debugf("current version: %s", currentVersion)
+
+ // latest version
+ latest, err := getLatest()
+ if err != nil {
+ rsp.ErrRsp(c, -1, "get latest version failed")
+ return
+ }
+
+ rsp.OkRspWithData(c, &proto.GetVersionRsp{
+ Current: currentVersion,
+ Latest: latest.Version,
+ })
+}
+
+func (s *Service) Update(c *gin.Context) {
+ var rsp proto.Response
+
+ if err := update(); err != nil {
+ rsp.ErrRsp(c, -1, fmt.Sprintf("update failed: %s", err))
+ return
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("update application success")
+
+ // Sleep for a second before restarting the device
+ time.Sleep(1 * time.Second)
+
+ _ = exec.Command("sh", "-c", "/etc/init.d/S95nanokvm restart").Run()
+}
+
+func update() error {
+ _ = os.RemoveAll(CacheDir)
+ _ = os.MkdirAll(CacheDir, 0o755)
+ defer func() {
+ _ = os.RemoveAll(CacheDir)
+ }()
+
+ // get latest information
+ latest, err := getLatest()
+ if err != nil {
+ return err
+ }
+
+ // download
+ url := fmt.Sprintf("%s/%s", BaseURL, latest.Name)
+ target := fmt.Sprintf("%s/%s", CacheDir, latest.Name)
+
+ if err := download(url, target); err != nil {
+ log.Errorf("download app failed: %s", err)
+ return err
+ }
+
+ // check sha512
+ if err := checksum(target, latest.Sha512); err != nil {
+ log.Errorf("check sha512 failed: %s", err)
+ return err
+ }
+
+ // decompress
+ dir, err := utils.UnTarGz(target, CacheDir)
+ log.Debugf("untar: %s", dir)
+ if err != nil {
+ log.Errorf("decompress app failed: %s", err)
+ return err
+ }
+
+ // backup old version
+ if err := os.RemoveAll(BackupDir); err != nil {
+ log.Errorf("remove backup failed: %s", err)
+ return err
+ }
+
+ if err := utils.MoveFilesRecursively(AppDir, BackupDir); err != nil {
+ log.Errorf("backup app failed: %s", err)
+ return err
+ }
+
+ // update
+ if err := utils.MoveFilesRecursively(dir, AppDir); err != nil {
+ log.Errorf("failed to move update back in place: %s", err)
+ return err
+ }
+
+ // modify permissions
+ if err := utils.ChmodRecursively(AppDir, 0o755); err != nil {
+ log.Errorf("chmod failed: %s", err)
+ return err
+ }
+
+ return nil
+}
+
+func getLatest() (*Latest, error) {
+ url := fmt.Sprintf("%s/latest.json?now=%d", BaseURL, time.Now().Unix())
+
+ resp, err := http.Get(url)
+ if err != nil {
+ log.Debugf("failed to request version: %v", err)
+ return nil, err
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ log.Errorf("failed to read response: %v", err)
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ log.Errorf("server responded with status code: %d", resp.StatusCode)
+ return nil, err
+ }
+
+ var latest Latest
+ if err := json.Unmarshal(body, &latest); err != nil {
+ log.Errorf("failed to unmarshal response: %s", err)
+ return nil, err
+ }
+
+ log.Debugf("get application latest version: %s", latest.Version)
+ return &latest, nil
+}
+
+func download(url string, target string) (err error) {
+ for i := range maxTries {
+ log.Debugf("attempt #%d/%d", i+1, maxTries)
+ if i > 0 {
+ time.Sleep(time.Second * 3) // wait for 3 seconds before retrying the download attempt
+ }
+
+ var req *http.Request
+ req, err = http.NewRequest("GET", url, nil)
+ if err != nil {
+ log.Errorf("new request err: %s", err)
+ continue
+ }
+
+ log.Debugf("update will be saved to: %s", target)
+ err = utils.Download(req, target)
+ if err != nil {
+ log.Errorf("downloading latest application failed, try again...")
+ continue
+ }
+ return nil
+ }
+ return err
+}
+
+func checksum(filePath string, expectedHash string) error {
+ file, err := os.Open(filePath)
+ if err != nil {
+ log.Errorf("failed to open file %s: %v", filePath, err)
+ return err
+ }
+ defer func() {
+ _ = file.Close()
+ }()
+
+ hasher := sha512.New()
+
+ _, err = io.Copy(hasher, file)
+ if err != nil {
+ log.Errorf("failed to copy file contents to hasher: %v", err)
+ return err
+ }
+
+ hash := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
+
+ if hash != expectedHash {
+ log.Errorf("invalid sha512 %s", hash)
+ return fmt.Errorf("invalid sha512 %s", hash)
+ }
+
+ return nil
+}
diff --git a/server/utils/account.go b/server/service/auth/account.go
similarity index 53%
rename from server/utils/account.go
rename to server/service/auth/account.go
index 80d84f3..0305965 100644
--- a/server/utils/account.go
+++ b/server/service/auth/account.go
@@ -1,42 +1,28 @@
-package utils
+package auth
import (
+ "NanoKVM-Server/utils"
"encoding/json"
"errors"
"os"
"path/filepath"
log "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/bcrypt"
)
const AccountFile = "/etc/kvm/pwd"
type Account struct {
Username string `json:"username"`
- Password string `json:"password"`
-}
-
-func IsAccountExist() bool {
- if _, err := os.Stat(AccountFile); err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return false
- }
- return false
- }
-
- return true
+ Password string `json:"password"` // should be named HashedPassword for clarity
}
func GetAccount() (*Account, error) {
- // use default account
if _, err := os.Stat(AccountFile); err != nil {
if errors.Is(err, os.ErrNotExist) {
- return &Account{
- Username: "admin",
- Password: "admin",
- }, nil
+ return getDefaultAccount(), nil
}
-
return nil, err
}
@@ -46,26 +32,18 @@ func GetAccount() (*Account, error) {
}
var account Account
- err = json.Unmarshal(content, &account)
- if err != nil {
+ if err = json.Unmarshal(content, &account); err != nil {
log.Errorf("unmarshal account failed: %s", err)
return nil, err
}
- password, err := DecodeDecrypt(account.Password)
- if err != nil {
- return nil, err
- }
-
- account.Password = password
-
return &account, nil
}
-func SetAccount(username string, password string) error {
+func SetAccount(username string, hashedPassword string) error {
account, err := json.Marshal(&Account{
Username: username,
- Password: password,
+ Password: hashedPassword,
})
if err != nil {
log.Errorf("failed to marshal account information to json: %s", err)
@@ -87,6 +65,35 @@ func SetAccount(username string, password string) error {
return nil
}
+func CompareAccount(username string, plainPassword string) bool {
+ account, err := GetAccount()
+ if err != nil {
+ return false
+ }
+
+ if username != account.Username {
+ return false
+ }
+
+ hashedPassword, err := utils.DecodeDecrypt(plainPassword)
+ if err != nil || hashedPassword == "" {
+ return false
+ }
+
+ err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(hashedPassword))
+ if err != nil {
+ // Compatible with old versions
+ accountHashedPassword, _ := utils.DecodeDecrypt(account.Password)
+ if accountHashedPassword == hashedPassword {
+ return true
+ }
+
+ return false
+ }
+
+ return true
+}
+
func DelAccount() error {
if err := os.Remove(AccountFile); err != nil {
log.Errorf("failed to delete password: %s", err)
@@ -95,3 +102,12 @@ func DelAccount() error {
return nil
}
+
+func getDefaultAccount() *Account {
+ hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
+
+ return &Account{
+ Username: "admin",
+ Password: string(hashedPassword),
+ }
+}
diff --git a/server/service/auth/login.go b/server/service/auth/login.go
index 7814359..8e9ba28 100644
--- a/server/service/auth/login.go
+++ b/server/service/auth/login.go
@@ -4,9 +4,6 @@ import (
"NanoKVM-Server/config"
"NanoKVM-Server/middleware"
"NanoKVM-Server/proto"
- "NanoKVM-Server/utils"
- "fmt"
- "os"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
@@ -16,11 +13,6 @@ func (s *Service) Login(c *gin.Context) {
var req proto.LoginReq
var rsp proto.Response
- if !isLibExist() {
- rsp.ErrRsp(c, -6, "Lib not exist! Please connect to internet and update.")
- return
- }
-
// authentication disabled
conf := config.GetInstance()
if conf.Authentication == "disable" {
@@ -35,26 +27,14 @@ func (s *Service) Login(c *gin.Context) {
return
}
- passwordDecrypt, err := utils.DecodeDecrypt(req.Password)
- if err != nil {
- rsp.ErrRsp(c, -2, "decrypt password failed")
- return
- }
-
- account, err := utils.GetAccount()
- if err != nil {
- rsp.ErrRsp(c, -3, "get account failed")
- return
- }
-
- if req.Username != account.Username || passwordDecrypt != account.Password {
- rsp.ErrRsp(c, -4, "invalid username or password")
+ if ok := CompareAccount(req.Username, req.Password); !ok {
+ rsp.ErrRsp(c, -2, "invalid username or password")
return
}
token, err := middleware.GenerateJWT(req.Username)
if err != nil {
- rsp.ErrRsp(c, -5, "generate token failed")
+ rsp.ErrRsp(c, -3, "generate token failed")
return
}
@@ -65,10 +45,21 @@ func (s *Service) Login(c *gin.Context) {
log.Debugf("login success, username: %s", req.Username)
}
+func (s *Service) Logout(c *gin.Context) {
+ conf := config.GetInstance()
+
+ if conf.JWT.RevokeTokensOnLogout {
+ config.RegenerateSecretKey()
+ }
+
+ var rsp proto.Response
+ rsp.OkRsp(c)
+}
+
func (s *Service) GetAccount(c *gin.Context) {
var rsp proto.Response
- account, err := utils.GetAccount()
+ account, err := GetAccount()
if err != nil {
rsp.ErrRsp(c, -1, "get account failed")
return
@@ -79,9 +70,3 @@ func (s *Service) GetAccount(c *gin.Context) {
})
log.Debugf("get account successful")
}
-
-func isLibExist() bool {
- libPath := fmt.Sprintf("/kvmapp/kvm_system/dl_lib/libmaixcam_lib.so")
- _, err := os.Stat(libPath)
- return err == nil
-}
diff --git a/server/service/auth/password.go b/server/service/auth/password.go
index 98ff20b..22c6016 100644
--- a/server/service/auth/password.go
+++ b/server/service/auth/password.go
@@ -3,6 +3,7 @@ package auth
import (
"NanoKVM-Server/proto"
"NanoKVM-Server/utils"
+ "errors"
"io"
"os"
"os/exec"
@@ -10,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/bcrypt"
)
func (s *Service) ChangePassword(c *gin.Context) {
@@ -21,23 +23,28 @@ func (s *Service) ChangePassword(c *gin.Context) {
return
}
- err := utils.SetAccount(req.Username, req.Password)
- if err != nil {
- rsp.ErrRsp(c, -2, "failed to save password")
+ password, err := utils.DecodeDecrypt(req.Password)
+ if err != nil || password == "" {
+ rsp.ErrRsp(c, -2, "invalid password")
return
}
- account, err := utils.GetAccount()
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
- rsp.ErrRsp(c, -3, "failed to get password")
+ rsp.ErrRsp(c, -3, "failed to hash password")
+ return
+ }
+
+ if err = SetAccount(req.Username, string(hashedPassword)); err != nil {
+ rsp.ErrRsp(c, -4, "failed to save password")
return
}
// change root password
- err = changeRootPassword(account.Password)
+ err = changeRootPassword(password)
if err != nil {
- _ = utils.DelAccount()
- rsp.ErrRsp(c, -4, "failed to change password")
+ _ = DelAccount()
+ rsp.ErrRsp(c, -5, "failed to change password")
return
}
@@ -48,24 +55,26 @@ func (s *Service) ChangePassword(c *gin.Context) {
func (s *Service) IsPasswordUpdated(c *gin.Context) {
var rsp proto.Response
- isUpdated := false
-
- if utils.IsAccountExist() {
- account, err := utils.GetAccount()
- if err != nil {
- rsp.ErrRsp(c, -1, "failed to get password")
- return
- }
-
- if account != nil && account.Password != "admin" {
- isUpdated = true
- }
+ if _, err := os.Stat(AccountFile); err != nil {
+ rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{
+ IsUpdated: false,
+ })
+ return
}
+ account, err := GetAccount()
+ if err != nil || account == nil {
+ rsp.ErrRsp(c, -1, "failed to get password")
+ return
+ }
+
+ err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte("admin"))
+
rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{
- IsUpdated: isUpdated,
+ // If the hash is not valid, still assume it's not updated
+ // The error we want to see is password and hash not matching
+ IsUpdated: errors.Is(err, bcrypt.ErrMismatchedHashAndPassword),
})
- log.Debugf("is password updated: %t", isUpdated)
}
func changeRootPassword(password string) error {
diff --git a/server/service/network/tailscale/cli.go b/server/service/extensions/tailscale/cli.go
similarity index 95%
rename from server/service/network/tailscale/cli.go
rename to server/service/extensions/tailscale/cli.go
index 2b68e08..1fa4cc9 100644
--- a/server/service/network/tailscale/cli.go
+++ b/server/service/extensions/tailscale/cli.go
@@ -69,11 +69,13 @@ func (c *Cli) Stop() error {
return err
}
+ _ = os.Remove(ConfigPath)
+
return os.Remove(ScriptPath)
}
func (c *Cli) Up() error {
- command := "tailscale up"
+ command := "tailscale up --accept-dns=false"
return exec.Command("sh", "-c", command).Run()
}
@@ -111,7 +113,7 @@ func (c *Cli) Status() (*TsStatus, error) {
}
func (c *Cli) Login() (string, error) {
- command := "tailscale login --timeout=10m"
+ command := "tailscale login --accept-dns=false --timeout=10m"
cmd := exec.Command("sh", "-c", command)
stderr, err := cmd.StderrPipe()
diff --git a/server/service/extensions/tailscale/install.go b/server/service/extensions/tailscale/install.go
new file mode 100644
index 0000000..6ccebff
--- /dev/null
+++ b/server/service/extensions/tailscale/install.go
@@ -0,0 +1,118 @@
+package tailscale
+
+import (
+ "NanoKVM-Server/utils"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+
+ log "github.com/sirupsen/logrus"
+)
+
+const (
+ OriginalURL = "https://pkgs.tailscale.com/stable/tailscale_latest_riscv64.tgz"
+ Workspace = "/root/.tailscale"
+)
+
+func isInstalled() bool {
+ _, err1 := os.Stat(TailscalePath)
+ _, err2 := os.Stat(TailscaledPath)
+
+ return err1 == nil && err2 == nil
+}
+
+func install() error {
+ _ = os.MkdirAll(Workspace, 0o755)
+ defer func() {
+ _ = os.RemoveAll(Workspace)
+ }()
+
+ tarFile := fmt.Sprintf("%s/tailscale_riscv64.tgz", Workspace)
+
+ // download
+ if err := download(tarFile); err != nil {
+ log.Errorf("failed to download tailscale: %s", err)
+ return err
+ }
+
+ // decompress
+ dir, err := utils.UnTarGz(tarFile, Workspace)
+ if err != nil {
+ log.Errorf("failed to decompress tailscale: %s", err)
+ return err
+ }
+
+ // move
+ tailscalePath := fmt.Sprintf("%s/tailscale", dir)
+ err = utils.MoveFile(tailscalePath, TailscalePath)
+ if err != nil {
+ log.Errorf("failed to move tailscale: %s", err)
+ return err
+ }
+
+ tailscaledPath := fmt.Sprintf("%s/tailscaled", dir)
+ err = utils.MoveFile(tailscaledPath, TailscaledPath)
+ if err != nil {
+ log.Errorf("failed to move tailscaled: %s", err)
+ return err
+ }
+
+ log.Debugf("install tailscale successfully")
+ return nil
+}
+
+func download(target string) error {
+ url, err := getDownloadURL()
+ if err != nil {
+ log.Errorf("failed to get Tailscale download url: %s", err)
+ return err
+ }
+
+ resp, err := http.Get(url)
+ if err != nil {
+ log.Errorf("failed to download Tailscale: %s", err)
+ return err
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+ }
+
+ out, err := os.Create(target)
+ if err != nil {
+ log.Errorf("failed to create file: %s", err)
+ return err
+ }
+ defer func() {
+ _ = out.Close()
+ }()
+
+ _, err = io.Copy(out, resp.Body)
+ if err != nil {
+ log.Errorf("failed to copy response body to file: %s", err)
+ return err
+ }
+
+ log.Debugf("download Tailscale successfully")
+ return nil
+}
+
+func getDownloadURL() (string, error) {
+ resp, err := (&http.Client{}).Get(OriginalURL)
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
+ return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+ }
+
+ return resp.Request.URL.String(), nil
+}
diff --git a/server/service/extensions/tailscale/service.go b/server/service/extensions/tailscale/service.go
new file mode 100644
index 0000000..2881666
--- /dev/null
+++ b/server/service/extensions/tailscale/service.go
@@ -0,0 +1,235 @@
+package tailscale
+
+import (
+ "NanoKVM-Server/proto"
+ "NanoKVM-Server/utils"
+ "net"
+ "os"
+
+ "github.com/gin-gonic/gin"
+ log "github.com/sirupsen/logrus"
+)
+
+type Service struct{}
+
+const (
+ TailscalePath = "/usr/bin/tailscale"
+ TailscaledPath = "/usr/sbin/tailscaled"
+ ConfigPath = "etc/sysctl.d/99-tailscale.conf"
+)
+
+var StateMap = map[string]proto.TailscaleState{
+ "NoState": proto.TailscaleNotRunning,
+ "Starting": proto.TailscaleNotRunning,
+ "NeedsLogin": proto.TailscaleNotLogin,
+ "Running": proto.TailscaleRunning,
+ "Stopped": proto.TailscaleStopped,
+}
+
+func NewService() *Service {
+ return &Service{}
+}
+
+func (s *Service) Install(c *gin.Context) {
+ var rsp proto.Response
+
+ if !isInstalled() {
+ if err := install(); err != nil {
+ rsp.ErrRsp(c, -1, "install failed")
+ return
+ }
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("install tailscale successfully")
+}
+
+func (s *Service) Uninstall(c *gin.Context) {
+ var rsp proto.Response
+
+ _ = NewCli().Stop()
+ _ = utils.DelGoMemLimit()
+
+ _ = os.Remove(TailscalePath)
+ _ = os.Remove(TailscaledPath)
+ _ = os.Remove(ConfigPath)
+
+ rsp.OkRsp(c)
+ log.Debugf("uninstall tailscale successfully")
+}
+
+func (s *Service) Start(c *gin.Context) {
+ var rsp proto.Response
+
+ err := NewCli().Start()
+ if err != nil {
+ rsp.ErrRsp(c, -1, "start failed")
+ log.Errorf("failed to run tailscale start: %s", err)
+ return
+ }
+
+ if !utils.IsGoMemLimitExist() {
+ _ = utils.SetGoMemLimit(50)
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("tailscale start successfully")
+}
+
+func (s *Service) Restart(c *gin.Context) {
+ var rsp proto.Response
+
+ err := NewCli().Restart()
+ if err != nil {
+ rsp.ErrRsp(c, -1, "restart failed")
+ log.Errorf("failed to run tailscale restart: %s", err)
+ return
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("tailscale restart successfully")
+}
+
+func (s *Service) Stop(c *gin.Context) {
+ var rsp proto.Response
+
+ err := NewCli().Stop()
+ if err != nil {
+ rsp.ErrRsp(c, -1, "stop failed")
+ log.Errorf("failed to run tailscale stop: %s", err)
+ return
+ }
+
+ _ = utils.DelGoMemLimit()
+
+ rsp.OkRsp(c)
+ log.Debugf("tailscale stop successfully")
+}
+
+func (s *Service) Up(c *gin.Context) {
+ var rsp proto.Response
+
+ err := NewCli().Up()
+ if err != nil {
+ rsp.ErrRsp(c, -1, "tailscale up failed")
+ log.Errorf("failed to run tailscale up: %s", err)
+ return
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("run tailscale up successfully")
+}
+
+func (s *Service) Down(c *gin.Context) {
+ var rsp proto.Response
+
+ err := NewCli().Down()
+ if err != nil {
+ rsp.ErrRsp(c, -1, "tailscale down failed")
+ log.Errorf("failed to run tailscale down: %s", err)
+ return
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("run tailscale down successfully")
+}
+
+func (s *Service) Login(c *gin.Context) {
+ var rsp proto.Response
+
+ // check tailscale status
+ cli := NewCli()
+ status, err := cli.Status()
+ if err != nil {
+ _ = cli.Start()
+ status, err = cli.Status()
+ }
+
+ if err != nil {
+ log.Errorf("failed to get tailscale status: %s", err)
+ rsp.ErrRsp(c, -1, "unknown status")
+ return
+ }
+
+ if status.BackendState == "Running" {
+ rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{})
+ return
+ }
+
+ // get login url
+ url, err := cli.Login()
+ if err != nil {
+ log.Errorf("failed to run tailscale login: %s", err)
+ rsp.ErrRsp(c, -2, "login failed")
+ return
+ }
+
+ if !utils.IsGoMemLimitExist() {
+ _ = utils.SetGoMemLimit(50)
+ }
+
+ rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{
+ Url: url,
+ })
+
+ log.Debugf("tailscale login url: %s", url)
+}
+
+func (s *Service) Logout(c *gin.Context) {
+ var rsp proto.Response
+
+ err := NewCli().Logout()
+ if err != nil {
+ rsp.ErrRsp(c, -1, "logout failed")
+ log.Errorf("failed to run tailscale logout: %s", err)
+ return
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("tailscale logout successfully")
+}
+
+func (s *Service) GetStatus(c *gin.Context) {
+ var rsp proto.Response
+
+ if !isInstalled() {
+ rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{
+ State: proto.TailscaleNotInstall,
+ })
+ return
+ }
+
+ status, err := NewCli().Status()
+ if err != nil {
+ log.Debugf("failed to get tailscale status: %s", err)
+ rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{
+ State: proto.TailscaleNotRunning,
+ })
+ return
+ }
+
+ state, ok := StateMap[status.BackendState]
+ if !ok {
+ log.Errorf("unknown tailscale state: %s", status.BackendState)
+ rsp.ErrRsp(c, -1, "unknown state")
+ return
+ }
+
+ ipv4 := ""
+ for _, tailscaleIp := range status.Self.TailscaleIPs {
+ ip := net.ParseIP(tailscaleIp)
+ if ip != nil && ip.To4() != nil {
+ ipv4 = ip.String()
+ }
+ }
+
+ data := proto.GetTailscaleStatusRsp{
+ State: state,
+ IP: ipv4,
+ Name: status.Self.HostName,
+ Account: status.CurrentTailnet.Name,
+ }
+
+ rsp.OkRspWithData(c, &data)
+ log.Debugf("get tailscale status successfully")
+}
diff --git a/server/service/network/service.go b/server/service/network/service.go
index 1003f3e..6b02b6b 100644
--- a/server/service/network/service.go
+++ b/server/service/network/service.go
@@ -1,49 +1,7 @@
package network
-import (
- "NanoKVM-Server/service/network/tailscale"
-
- "github.com/gin-gonic/gin"
-)
-
type Service struct{}
func NewService() *Service {
return &Service{}
}
-
-func (s *Service) TsInstall(c *gin.Context) {
- tailscale.Install(c)
-}
-
-func (s *Service) TsUninstall(c *gin.Context) {
- tailscale.Uninstall(c)
-}
-
-func (s *Service) GetTsStatus(c *gin.Context) {
- tailscale.GetStatus(c)
-}
-
-func (s *Service) TsUp(c *gin.Context) {
- tailscale.Up(c)
-}
-
-func (s *Service) TsDown(c *gin.Context) {
- tailscale.Down(c)
-}
-
-func (s *Service) TsLogin(c *gin.Context) {
- tailscale.Login(c)
-}
-
-func (s *Service) TsLogout(c *gin.Context) {
- tailscale.Logout(c)
-}
-
-func (s *Service) TsStop(c *gin.Context) {
- tailscale.Stop(c)
-}
-
-func (s *Service) TsRestart(c *gin.Context) {
- tailscale.Restart(c)
-}
diff --git a/server/service/network/tailscale/account.go b/server/service/network/tailscale/account.go
deleted file mode 100644
index 4a12f5c..0000000
--- a/server/service/network/tailscale/account.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package tailscale
-
-import (
- "NanoKVM-Server/proto"
-
- "github.com/gin-gonic/gin"
- log "github.com/sirupsen/logrus"
-)
-
-func Login(c *gin.Context) {
- var rsp proto.Response
-
- // check tailscale status
- cli := NewCli()
- status, err := cli.Status()
- if err != nil {
- _ = cli.Start()
- status, err = cli.Status()
- }
-
- if err != nil {
- log.Errorf("failed to get tailscale status: %s", err)
- rsp.ErrRsp(c, -1, "unknown status")
- return
- }
-
- if status.BackendState == "Running" {
- rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{})
- return
- }
-
- // get login url
- url, err := cli.Login()
- if err != nil {
- log.Errorf("failed to run tailscale login: %s", err)
- rsp.ErrRsp(c, -2, "login failed")
- return
- }
-
- rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{
- Url: url,
- })
-
- log.Debugf("tailscale login url: %s", url)
-}
-
-func Logout(c *gin.Context) {
- var rsp proto.Response
-
- err := NewCli().Logout()
- if err != nil {
- rsp.ErrRsp(c, -1, "logout failed")
- log.Errorf("failed to run tailscale logout: %s", err)
- return
- }
-
- rsp.OkRsp(c)
- log.Debugf("tailscale logout successfully")
-}
-
-func Restart(c *gin.Context) {
- var rsp proto.Response
-
- err := NewCli().Restart()
- if err != nil {
- rsp.ErrRsp(c, -1, "restart failed")
- log.Errorf("failed to run tailscale restart: %s", err)
- return
- }
-
- rsp.OkRsp(c)
- log.Debugf("tailscale restart successfully")
-}
-
-func Stop(c *gin.Context) {
- var rsp proto.Response
-
- err := NewCli().Stop()
- if err != nil {
- rsp.ErrRsp(c, -1, "stop failed")
- log.Errorf("failed to run tailscale stop: %s", err)
- return
- }
-
- rsp.OkRsp(c)
- log.Debugf("tailscale stop successfully")
-}
diff --git a/server/service/network/tailscale/install.go b/server/service/network/tailscale/install.go
deleted file mode 100644
index 4174b7b..0000000
--- a/server/service/network/tailscale/install.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package tailscale
-
-import (
- "NanoKVM-Server/proto"
- "NanoKVM-Server/utils"
- "fmt"
- "net/http"
- "os"
-
- "github.com/gin-gonic/gin"
- log "github.com/sirupsen/logrus"
-)
-
-const (
- DownloadUrl = "https://cdn.sipeed.com/nanokvm/resources/tailscale_riscv64.zip"
- Workspace = "/root/.tailscale"
-
- TailscalePath = "/usr/bin/tailscale"
- TailscaledPath = "/usr/sbin/tailscaled"
-)
-
-func Install(c *gin.Context) {
- var rsp proto.Response
-
- if IsInstalled() {
- rsp.OkRsp(c)
- return
- }
-
- _ = os.MkdirAll(Workspace, 0o755)
- defer func() {
- _ = os.RemoveAll(Workspace)
- }()
-
- // download
- req, err := http.NewRequest("GET", DownloadUrl, nil)
- if err != nil {
- rsp.ErrRsp(c, -1, "request failed")
- log.Errorf("failed to create request: %s", err)
- return
- }
-
- zipPath := fmt.Sprintf("%s/tailscale_riscv64.zip", Workspace)
- err = utils.Download(req, zipPath)
- if err != nil {
- rsp.ErrRsp(c, -2, "download failed")
- log.Errorf("failed to download tailscale: %s", err)
- return
- }
-
- // install
- err = utils.Unzip(zipPath, Workspace)
- if err != nil {
- rsp.ErrRsp(c, -3, "unzip failed")
- log.Errorf("failed to unzip tailscale: %s", err)
- return
- }
-
- tailscalePath := fmt.Sprintf("%s/tailscale_riscv64/tailscale", Workspace)
- err = utils.MoveFile(tailscalePath, TailscalePath)
- if err != nil {
- rsp.ErrRsp(c, -4, "install failed")
- log.Errorf("failed to move tailscale: %s", err)
- return
- }
-
- tailscaledPath := fmt.Sprintf("%s/tailscale_riscv64/tailscaled", Workspace)
- err = utils.MoveFile(tailscaledPath, TailscaledPath)
- if err != nil {
- rsp.ErrRsp(c, -5, "install failed")
- log.Errorf("failed to move tailscaled: %s", err)
- return
- }
-
- rsp.OkRsp(c)
- log.Debugf("install tailscale successfully")
-}
-
-func Uninstall(c *gin.Context) {
- var rsp proto.Response
-
- _ = NewCli().Stop()
- _ = utils.DelGoMemLimit()
-
- _ = os.Remove(TailscalePath)
- _ = os.Remove(TailscaledPath)
-
- rsp.OkRsp(c)
- log.Debugf("uninstall tailscale successfully")
-}
-
-func IsInstalled() bool {
- _, err1 := os.Stat(TailscalePath)
- _, err2 := os.Stat(TailscaledPath)
-
- return err1 == nil && err2 == nil
-}
diff --git a/server/service/network/tailscale/status.go b/server/service/network/tailscale/status.go
deleted file mode 100644
index 7141042..0000000
--- a/server/service/network/tailscale/status.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package tailscale
-
-import (
- "NanoKVM-Server/proto"
- "net"
-
- "github.com/gin-gonic/gin"
- log "github.com/sirupsen/logrus"
-)
-
-var StateMap = map[string]proto.TailscaleState{
- "NoState": proto.TailscaleNotRunning,
- "Starting": proto.TailscaleNotRunning,
- "NeedsLogin": proto.TailscaleNotLogin,
- "Running": proto.TailscaleRunning,
- "Stopped": proto.TailscaleStopped,
-}
-
-func GetStatus(c *gin.Context) {
- var rsp proto.Response
-
- if !IsInstalled() {
- rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{
- State: proto.TailscaleNotInstall,
- })
- return
- }
-
- status, err := NewCli().Status()
- if err != nil {
- log.Debugf("failed to get tailscale status: %s", err)
- rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{
- State: proto.TailscaleNotRunning,
- })
- return
- }
-
- state, ok := StateMap[status.BackendState]
- if !ok {
- log.Errorf("unknown tailscale state: %s", status.BackendState)
- rsp.ErrRsp(c, -1, "unknown state")
- return
- }
-
- ipv4 := ""
- for _, tailscaleIp := range status.Self.TailscaleIPs {
- ip := net.ParseIP(tailscaleIp)
- if ip != nil && ip.To4() != nil {
- ipv4 = ip.String()
- }
- }
-
- data := proto.GetTailscaleStatusRsp{
- State: state,
- IP: ipv4,
- Name: status.Self.HostName,
- Account: status.CurrentTailnet.Name,
- }
-
- rsp.OkRspWithData(c, &data)
- log.Debugf("get tailscale status successfully")
-}
-
-func Up(c *gin.Context) {
- var rsp proto.Response
-
- err := NewCli().Up()
- if err != nil {
- rsp.ErrRsp(c, -1, "tailscale up failed")
- log.Errorf("failed to run tailscale up: %s", err)
- return
- }
-
- rsp.OkRsp(c)
- log.Debugf("run tailscale up successfully")
-}
-
-func Down(c *gin.Context) {
- var rsp proto.Response
-
- err := NewCli().Down()
- if err != nil {
- rsp.ErrRsp(c, -1, "tailscale down failed")
- log.Errorf("failed to run tailscale down: %s", err)
- return
- }
-
- rsp.OkRsp(c)
- log.Debugf("run tailscale down successfully")
-}
diff --git a/server/service/storage/image.go b/server/service/storage/image.go
index cd0884e..63360ed 100644
--- a/server/service/storage/image.go
+++ b/server/service/storage/image.go
@@ -1,11 +1,12 @@
package storage
import (
- "errors"
"os"
"os/exec"
"path/filepath"
+ "strconv"
"strings"
+ "time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
@@ -18,9 +19,7 @@ const (
imageNone = "/dev/mmcblk0p3"
cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom"
mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file"
- removableFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/removable"
roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro"
- roDisk = "/boot/usb.disk0.ro"
)
func (s *Service) GetImages(c *gin.Context) {
@@ -61,55 +60,43 @@ func (s *Service) MountImage(c *gin.Context) {
return
}
+ // cdrom and ro flag
+ // set to 0 when unmount image
+ // set to 1 when mount image and the CD-ROM is enabled
+ if req.File == "" || req.Cdrom {
+ flag := "0"
+ if req.File != "" && req.Cdrom {
+ flag = "1"
+ }
+
+ // unmount
+ if err := os.WriteFile(mountDevice, []byte("\n"), 0o666); err != nil {
+ log.Errorf("unmount file failed: %s", err)
+ rsp.ErrRsp(c, -2, "unmount image failed")
+ return
+ }
+
+ // ro flag
+ if err := os.WriteFile(roFlag, []byte(flag), 0o666); err != nil {
+ log.Errorf("set ro flag failed: %s", err)
+ rsp.ErrRsp(c, -2, "set ro flag failed")
+ return
+ }
+
+ // cdrom flag
+ if err := os.WriteFile(cdromFlag, []byte(flag), 0o666); err != nil {
+ log.Errorf("set cdrom flag failed: %s", err)
+ rsp.ErrRsp(c, -2, "set cdrom flag failed")
+ return
+ }
+ }
+
+ // mount
image := req.File
if image == "" {
image = imageNone
}
- imageRemovable := "1"
-
- imageRo := "0"
- isImageRo, _ := isFlagExist(roDisk)
- if isImageRo {
- imageRo = "1"
- }
-
- imageLow := strings.ToLower(image)
- imageCdrom := "0"
- if strings.HasSuffix(imageLow, ".iso") {
- imageRo = "1"
- imageCdrom = "1"
- }
-
- // unmount
- if err := os.WriteFile(mountDevice, []byte("\n"), 0o666); err != nil {
- log.Errorf("unmount file failed: %s", err)
- rsp.ErrRsp(c, -2, "unmount image failed")
- return
- }
-
- // removable flag
- if err := os.WriteFile(removableFlag, []byte(imageRemovable), 0o666); err != nil {
- log.Errorf("set removable flag failed: %s", err)
- rsp.ErrRsp(c, -2, "set removable flag failed")
- return
- }
-
- // ro flag
- if err := os.WriteFile(roFlag, []byte(imageRo), 0o666); err != nil {
- log.Errorf("set ro flag failed: %s", err)
- rsp.ErrRsp(c, -2, "set ro flag failed")
- return
- }
-
- // cdrom flag
- if err := os.WriteFile(cdromFlag, []byte(imageCdrom), 0o666); err != nil {
- log.Errorf("set cdrom flag failed: %s", err)
- rsp.ErrRsp(c, -2, "set cdrom flag failed")
- return
- }
-
- // mount
if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil {
log.Errorf("mount file %s failed: %s", image, err)
rsp.ErrRsp(c, -2, "mount image failed")
@@ -128,6 +115,7 @@ func (s *Service) MountImage(c *gin.Context) {
rsp.ErrRsp(c, -2, "execute command failed")
return
}
+ time.Sleep(100 * time.Millisecond)
}
rsp.OkRsp(c)
@@ -155,17 +143,25 @@ func (s *Service) GetMountedImage(c *gin.Context) {
rsp.OkRspWithData(c, data)
}
-func isFlagExist(flag string) (bool, error) {
- _, err := os.Stat(flag)
+func (s *Service) GetCdRom(c *gin.Context) {
+ var rsp proto.Response
- if err == nil {
- return true, nil
+ content, err := os.ReadFile(cdromFlag)
+ if err != nil {
+ rsp.ErrRsp(c, -1, "read failed")
+ return
}
- if errors.Is(err, os.ErrNotExist) {
- return false, nil
+ flag := strings.ReplaceAll(string(content), "\n", "")
+ flatInt, err := strconv.ParseInt(flag, 10, 64)
+ if err != nil {
+ rsp.ErrRsp(c, -2, "parse failed")
+ return
}
- log.Errorf("check file %s err: %s", flag, err)
- return false, err
+ data := &proto.GetCdRomRsp{
+ Cdrom: flatInt,
+ }
+
+ rsp.OkRspWithData(c, data)
}
diff --git a/server/service/stream/frame-rate.go b/server/service/stream/frame-rate.go
index a4ffed3..f5de521 100644
--- a/server/service/stream/frame-rate.go
+++ b/server/service/stream/frame-rate.go
@@ -42,7 +42,7 @@ func GetFrameRateCounter() *FrameRateCounter {
data := fmt.Sprintf("%d", counter.fps)
err := os.WriteFile("/kvmapp/kvm/now_fps", []byte(data), 0o666)
if err != nil {
- log.Errorf("failed to wirte fps: %s", err)
+ log.Errorf("failed to write fps: %s", err)
}
}
}()
diff --git a/server/service/stream/mjpeg/frame-detect.go b/server/service/stream/mjpeg/frame-detect.go
index cb4fc8a..fa89358 100644
--- a/server/service/stream/mjpeg/frame-detect.go
+++ b/server/service/stream/mjpeg/frame-detect.go
@@ -93,11 +93,11 @@ func StopFrameDetect(c *gin.Context) {
go func() {
time.Sleep(20 * time.Second)
_ = os.Rename(frameDetectTmp, frameDetect)
- log.Debugf("frame detect started")
+ log.Debug("frame detect started")
}()
rsp.OkRsp(c)
- log.Debugf("frame detect stoped")
+ log.Debug("frame detect stopped")
}
func isFrameDetectEnabled() (bool, error) {
diff --git a/server/service/stream/mjpeg/mjpeg.go b/server/service/stream/mjpeg/mjpeg.go
index 501694c..32ba210 100644
--- a/server/service/stream/mjpeg/mjpeg.go
+++ b/server/service/stream/mjpeg/mjpeg.go
@@ -3,7 +3,6 @@ package mjpeg
import (
"NanoKVM-Server/common"
"NanoKVM-Server/service/stream"
- "fmt"
"sync"
"time"
@@ -18,7 +17,7 @@ var (
)
func Connect(c *gin.Context) {
- c.Header("Content-Type", fmt.Sprintf("multipart/x-mixed-replace; boundary=frame"))
+ c.Header("Content-Type", "multipart/x-mixed-replace; boundary=frame")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("Pragma", "no-cache")
diff --git a/server/service/vm/gpio.go b/server/service/vm/gpio.go
index b9e0283..e34f606 100644
--- a/server/service/vm/gpio.go
+++ b/server/service/vm/gpio.go
@@ -46,7 +46,7 @@ func (s *Service) SetGpio(c *gin.Context) {
return
}
- log.Debugf("gpio %s set sucessfully", device)
+ log.Debugf("gpio %s set successfully", device)
rsp.OkRsp(c)
}
diff --git a/server/service/vm/hdmi.go b/server/service/vm/hdmi.go
index 4b14db3..40a8e2c 100644
--- a/server/service/vm/hdmi.go
+++ b/server/service/vm/hdmi.go
@@ -14,9 +14,9 @@ func (s *Service) ResetHdmi(c *gin.Context) {
vision := common.GetKvmVision()
- vision.EnableHdmi(false)
+ vision.SetHDMI(false)
time.Sleep(1 * time.Second)
- vision.EnableHdmi(true)
+ vision.SetHDMI(true)
rsp.OkRsp(c)
log.Debug("reset hdmi")
diff --git a/server/service/vm/info.go b/server/service/vm/info.go
index fb9b32b..ebc92c7 100644
--- a/server/service/vm/info.go
+++ b/server/service/vm/info.go
@@ -18,6 +18,7 @@ var imageVersionMap = map[string]string{
"2024-07-23-20-18-587710.img": "v1.1.0",
"2024-08-08-19-44-bef2ca.img": "v1.2.0",
"2024-11-13-09-59-9c961a.img": "v1.3.0",
+ "2025-02-17-16-59-3649fe.img": "v1.4.0",
}
func (s *Service) GetInfo(c *gin.Context) {
diff --git a/server/service/vm/memory.go b/server/service/vm/memory.go
index fd350f5..9e7fa98 100644
--- a/server/service/vm/memory.go
+++ b/server/service/vm/memory.go
@@ -30,7 +30,7 @@ func (s *Service) SetMemoryLimit(c *gin.Context) {
}
rsp.OkRsp(c)
- log.Debug("set memory limit successful, enabled: %t, limit: %s", req.Enabled, req.Limit)
+ log.Debugf("set memory limit successful, enabled: %t, limit: %d", req.Enabled, req.Limit)
}
func (s *Service) GetMemoryLimit(c *gin.Context) {
diff --git a/server/service/vm/script.go b/server/service/vm/script.go
index d16e3ee..9c93b96 100644
--- a/server/service/vm/script.go
+++ b/server/service/vm/script.go
@@ -112,7 +112,7 @@ func (s *Service) RunScript(c *gin.Context) {
}
if err != nil {
- log.Errorf("run script %s faile: %s", req.Name, err)
+ log.Errorf("run script %s failed: %s", req.Name, err.Error())
rsp.ErrRsp(c, -2, "run script failed")
return
}
diff --git a/server/service/vm/ssh.go b/server/service/vm/ssh.go
new file mode 100644
index 0000000..fb854d4
--- /dev/null
+++ b/server/service/vm/ssh.go
@@ -0,0 +1,67 @@
+package vm
+
+import (
+ "NanoKVM-Server/proto"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+
+ "github.com/gin-gonic/gin"
+ log "github.com/sirupsen/logrus"
+)
+
+const (
+ SSHScript = "/etc/init.d/S50sshd"
+ SSHStopFlag = "/etc/kvm/ssh_stop"
+)
+
+func (s *Service) GetSSHState(c *gin.Context) {
+ var rsp proto.Response
+
+ enabled := isSSHEnabled()
+ rsp.OkRspWithData(c, &proto.GetSSHStateRsp{
+ Enabled: enabled,
+ })
+}
+
+func (s *Service) EnableSSH(c *gin.Context) {
+ var rsp proto.Response
+
+ command := fmt.Sprintf("%s permanent_on", SSHScript)
+ err := exec.Command("sh", "-c", command).Run()
+ if err != nil {
+ log.Errorf("failed to run SSH script: %s", err)
+ rsp.ErrRsp(c, -1, "operation failed")
+ return
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("SSH enabled")
+}
+
+func (s *Service) DisableSSH(c *gin.Context) {
+ var rsp proto.Response
+
+ command := fmt.Sprintf("%s permanent_off", SSHScript)
+ err := exec.Command("sh", "-c", command).Run()
+ if err != nil {
+ log.Errorf("failed to run SSH script: %s", err)
+ rsp.ErrRsp(c, -1, "operation failed")
+ return
+ }
+
+ rsp.OkRsp(c)
+ log.Debugf("SSH disabled")
+}
+
+func isSSHEnabled() bool {
+ _, err := os.Stat(SSHStopFlag)
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/server/service/vm/terminal.go b/server/service/vm/terminal.go
index 14775ed..0b6f08e 100644
--- a/server/service/vm/terminal.go
+++ b/server/service/vm/terminal.go
@@ -2,17 +2,15 @@ package vm
import (
"encoding/json"
- "io"
"net/http"
"os"
+ "os/exec"
"time"
+ "github.com/creack/pty"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
- "golang.org/x/crypto/ssh"
-
- "NanoKVM-Server/utils"
)
const (
@@ -20,244 +18,93 @@ const (
maxMessageSize = 1024
)
-type WindowSize struct {
- Height int `json:"height"`
- Width int `json:"width"`
-}
-
-type SshClient struct {
- conn *websocket.Conn
- addr string
- user string
- password string
- client *ssh.Client
- session *ssh.Session
- sessionIn io.WriteCloser
- sessionOut io.Reader
- closeSig chan struct{}
+type WinSize struct {
+ Rows uint16 `json:"rows"`
+ Cols uint16 `json:"cols"`
}
var upgrader = websocket.Upgrader{
ReadBufferSize: maxMessageSize,
WriteBufferSize: maxMessageSize,
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
}
-var terminalModes = ssh.TerminalModes{
- ssh.ECHO: 1,
- ssh.TTY_OP_ISPEED: 14400,
- ssh.TTY_OP_OSPEED: 14400,
-}
-
-func (s *SshClient) getWindowSize() (size *WindowSize, err error) {
- _ = s.conn.SetReadDeadline(time.Now().Add(messageWait))
-
- msgType, msg, err := s.conn.ReadMessage()
+func (s *Service) Terminal(c *gin.Context) {
+ ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
- log.Errorf("ws read message failed: %s", err)
+ log.Errorf("failed to init websocket: %s", err)
return
}
- if msgType != websocket.BinaryMessage {
- log.Errorf("ws message type is not binary")
- return
- }
-
- size = new(WindowSize)
- if err = json.Unmarshal(msg, size); err != nil {
- log.Errorf("unmarshal ws message failed: %s", err)
- return
- }
-
- return
-}
-
-func (s *SshClient) wsWrite() error {
defer func() {
- s.closeSig <- struct{}{}
+ _ = ws.Close()
}()
+ cmd := exec.Command("/bin/sh")
+ ptmx, err := pty.Start(cmd)
+ if err != nil {
+ log.Errorf("failed to start pty: %s", err)
+ return
+ }
+ defer func() {
+ _ = ptmx.Close()
+ _ = cmd.Process.Kill()
+ }()
+
+ go wsWrite(ws, ptmx)
+ wsRead(ws, ptmx)
+}
+
+// pty to ws
+func wsWrite(ws *websocket.Conn, ptmx *os.File) {
data := make([]byte, maxMessageSize)
for {
- time.Sleep(10 * time.Millisecond)
-
- n, err := s.sessionOut.Read(data)
+ n, err := ptmx.Read(data)
if err != nil {
- return err
+ return
}
if n > 0 {
- _ = s.conn.SetWriteDeadline(time.Now().Add(messageWait))
+ _ = ws.SetWriteDeadline(time.Now().Add(messageWait))
- err = s.conn.WriteMessage(websocket.BinaryMessage, data[:n])
+ err = ws.WriteMessage(websocket.BinaryMessage, data[:n])
if err != nil {
log.Errorf("write ws message failed: %s", err)
- return err
+ return
}
}
}
}
-func (s *SshClient) wsRead() error {
- defer func() {
- s.closeSig <- struct{}{}
- }()
-
+// ws to pty
+func wsRead(ws *websocket.Conn, ptmx *os.File) {
var zeroTime time.Time
- _ = s.conn.SetReadDeadline(zeroTime)
+ _ = ws.SetReadDeadline(zeroTime)
for {
- msgType, connReader, err := s.conn.NextReader()
+ msgType, p, err := ws.ReadMessage()
if err != nil {
- return err
+ return
}
- if msgType != websocket.BinaryMessage {
- _, err = io.Copy(s.sessionIn, connReader)
- if err != nil {
- log.Errorf("copy ws message failed: %s", err)
- return err
+
+ // resize message
+ if msgType == websocket.BinaryMessage {
+ var winSize WinSize
+ if err := json.Unmarshal(p, &winSize); err == nil {
+ _ = pty.Setsize(ptmx, &pty.Winsize{
+ Rows: winSize.Rows,
+ Cols: winSize.Cols,
+ })
}
continue
}
- data := make([]byte, maxMessageSize)
- n, err := connReader.Read(data)
+ _, err = ptmx.Write(p)
if err != nil {
- log.Errorf("read ws message failed: %s", err)
- return err
- }
-
- var size WindowSize
- err = json.Unmarshal(data[:n], &size)
- if err != nil {
- log.Errorf("unmarshal ws message failed: %s", err)
- return err
- }
-
- err = s.session.WindowChange(size.Height, size.Width)
- if err != nil {
- log.Errorf("ws window change failed: %s", err)
- return err
+ log.Errorf("failed to write to pty: %s", err)
+ return
}
}
}
-
-func (s *SshClient) bridgeWSAndSSH() {
- defer func() {
- _ = s.conn.Close()
- if r := recover(); r != nil {
- log.Debugf("terminal recover: %s", r)
- }
- }()
-
- size, err := s.getWindowSize()
- if err != nil {
- return
- }
-
- auth := ssh.Password(s.password)
- config := &ssh.ClientConfig{
- User: s.user,
- Auth: []ssh.AuthMethod{auth},
- HostKeyCallback: ssh.InsecureIgnoreHostKey(),
- }
-
- s.client, err = ssh.Dial("tcp", s.addr, config)
- if err != nil {
- // log.Errorf("init ssh failed: %s", err)
- return
- }
- defer func() {
- _ = s.client.Close()
- }()
-
- s.session, err = s.client.NewSession()
- if err != nil {
- log.Errorf("open ssh session failed: %s", err)
- return
- }
- defer s.session.Close()
-
- s.session.Stderr = os.Stderr
- s.sessionOut, err = s.session.StdoutPipe()
- if err != nil {
- log.Errorf("open ssh session out failed: %s", err)
- return
- }
-
- s.sessionIn, err = s.session.StdinPipe()
- if err != nil {
- log.Errorf("open ssh session in failed: %s", err)
- return
- }
- defer func() {
- _ = s.sessionIn.Close()
- }()
-
- if err = s.session.RequestPty("xterm", size.Height, size.Width, terminalModes); err != nil {
- log.Errorf("oen session request pty failed: %s", err)
- return
- }
- if err = s.session.Shell(); err != nil {
- log.Errorf("open ssh shell failed: %s", err)
- return
- }
-
- go func() {
- _ = s.wsRead()
- }()
-
- go func() {
- _ = s.wsWrite()
- }()
-
- <-s.closeSig
-}
-
-func getRootPassword() string {
- if !utils.IsAccountExist() {
- return "root"
- }
-
- account, err := utils.GetAccount()
- if err != nil {
- return "root"
- }
-
- if account == nil || account.Password == "" {
- return "root"
- }
-
- return account.Password
-}
-
-func (s *Service) Terminal(c *gin.Context) {
- user := c.Query("u")
- if user == "" {
- user = "root"
- }
-
- password, _ := utils.Decrypt(c.Query("t"))
- if password == "" {
- password = getRootPassword()
- }
-
- upgrader.CheckOrigin = func(r *http.Request) bool {
- return true
- }
-
- conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
- if err != nil {
- log.Errorf("init websocket failed: %s", err)
- return
- }
-
- sshClient := &SshClient{
- conn: conn,
- addr: "127.0.0.1:22",
- user: user,
- password: password,
- closeSig: make(chan struct{}, 1),
- }
-
- go sshClient.bridgeWSAndSSH()
-}
diff --git a/server/service/ws/ws.go b/server/service/ws/ws.go
index 25bc010..d24ea5a 100644
--- a/server/service/ws/ws.go
+++ b/server/service/ws/ws.go
@@ -71,12 +71,12 @@ func (c *WsClient) Read() error {
return err
}
- log.Debugf("receive message: %s", message)
+ log.Debugf("received message: %s", message)
var event []int
err = json.Unmarshal(message, &event)
if err != nil {
- log.Debugf("receive invalid message: %s", message)
+ log.Debugf("received invalid message: %s", message)
continue
}
diff --git a/server/utils/encrypt.go b/server/utils/encrypt.go
index 9b6af58..ad94d97 100644
--- a/server/utils/encrypt.go
+++ b/server/utils/encrypt.go
@@ -7,30 +7,24 @@ import (
log "github.com/sirupsen/logrus"
)
-const EncryptSecretKey = "nanokvm-sipeed-2024"
+// SecretKey is only used to prevent the data from being transmitted in plaintext.
+const SecretKey = "nanokvm-sipeed-2024"
func Decrypt(ciphertext string) (string, error) {
- defer func() {
- if err := recover(); err != nil {
- log.Errorf("decrypt failed: %s", err)
- }
- }()
-
if ciphertext == "" {
return "", nil
}
- decrypt := aes256.Decrypt(ciphertext, EncryptSecretKey)
-
+ decrypt := aes256.Decrypt(ciphertext, SecretKey)
return decrypt, nil
}
-func DecodeDecrypt(ciphertext string) (string, error) {
- decode, err := url.QueryUnescape(ciphertext)
+func DecodeDecrypt(data string) (string, error) {
+ ciphertext, err := url.QueryUnescape(data)
if err != nil {
log.Errorf("decode ciphertext failed: %s", err)
return "", err
}
- return Decrypt(decode)
+ return Decrypt(ciphertext)
}
diff --git a/server/utils/http.go b/server/utils/http.go
index cb45891..8f77fc7 100644
--- a/server/utils/http.go
+++ b/server/utils/http.go
@@ -41,8 +41,8 @@ func Download(req *http.Request, target string) error {
}
contentType := resp.Header.Get("Content-Type")
- if contentType != "application/octet-stream" && contentType != "application/zip" {
- log.Debugf("unexpected content-type, it should be either octet-stream or zip, but got: %s", contentType)
+ if contentType != "application/octet-stream" && contentType != "application/zip" && contentType != "application/gzip" {
+ log.Debugf("unexpected content-type, it should be either octet-stream or (g)zip, but got: %s", contentType)
return errors.New("unsupported content type")
}
diff --git a/server/utils/untar.go b/server/utils/untar.go
new file mode 100644
index 0000000..26342c6
--- /dev/null
+++ b/server/utils/untar.go
@@ -0,0 +1,82 @@
+package utils
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+func UnTarGz(srcFile string, destDir string) (string, error) {
+ if err := os.MkdirAll(destDir, 0755); err != nil {
+ return "", err
+ }
+
+ fr, err := os.Open(srcFile)
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ _ = fr.Close()
+ }()
+
+ gr, err := gzip.NewReader(fr)
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ _ = gr.Close()
+ }()
+
+ tr := tar.NewReader(gr)
+
+ targetFile := ""
+ for {
+ header, err := tr.Next()
+
+ if err == io.EOF {
+ break
+ }
+
+ if err != nil {
+ return "", err
+ }
+
+ if targetFile == "" {
+ parts := strings.Split(header.Name, "/")
+ if len(parts) > 0 {
+ targetFile = filepath.Join(destDir, parts[0])
+ }
+ }
+
+ filename := filepath.Join(destDir, header.Name)
+
+ switch header.Typeflag {
+ case tar.TypeDir:
+ if err := os.MkdirAll(filename, os.FileMode(header.Mode)); err != nil {
+ return "", err
+ }
+
+ case tar.TypeReg:
+ file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
+ if err != nil {
+ return "", err
+ }
+
+ if _, err := io.Copy(file, tr); err != nil {
+ _ = file.Close()
+ return "", err
+ }
+ _ = file.Close()
+
+ case tar.TypeSymlink:
+ if err := os.Symlink(header.Linkname, filename); err != nil {
+ return "", err
+ }
+ }
+ }
+
+ return targetFile, nil
+}
diff --git a/support/kvm_system/README.md b/support/kvm_system/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/support/kvm_system/main/lib/system_init/system_init.cpp b/support/kvm_system/main/lib/system_init/system_init.cpp
index 8102fc1..95af168 100644
--- a/support/kvm_system/main/lib/system_init/system_init.cpp
+++ b/support/kvm_system/main/lib/system_init/system_init.cpp
@@ -8,9 +8,9 @@ using namespace maix::sys;
extern kvm_sys_state_t kvm_sys_state;
extern kvm_oled_state_t kvm_oled_state;
-void new_app_init(uint8_t safe_update)
+void new_app_init(void)
{
- // first start
+ // Update the necessary scripts
system("cp -f /kvmapp/system/update-nanokvm.py /etc/kvm/");
system("rm /etc/init.d/S02udisk");
system("cp /kvmapp/system/init.d/S01fs /etc/init.d/");
@@ -18,18 +18,26 @@ void new_app_init(uint8_t safe_update)
system("cp /kvmapp/system/init.d/S15kvmhwd /etc/init.d/");
system("cp /kvmapp/system/init.d/S30eth /etc/init.d/");
system("cp /kvmapp/system/init.d/S50sshd /etc/init.d/");
- if(kvm_wifi_exist()) system("cp /kvmapp/system/init.d/S30wifi /etc/init.d/");
- else system("rm /etc/init.d/S30wifi");
- system("cp /kvmapp/system/init.d/S98tailscaled /etc/init.d/");
- if(safe_update == 0){
- system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/"); // /kvmapp/system/init.d/S95nanokvm还没跑完直接覆盖可能出问题
+ if(kvm_wifi_exist()) {
+ system("cp /kvmapp/system/init.d/S30wifi /etc/init.d/");
+ } else {
+ system("rm /etc/init.d/S30wifi");
}
- // system("rm /etc/init.d/S04backlight");
- // system("rm /etc/init.d/S05tp");
- // system("rm /etc/init.d/S40bluetoothd");
- // system("rm /etc/init.d/S99*");
+ // PCIe Patch
+ // system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/");
+ if(access("/kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so", F_OK) != 0){
+ system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/");
+ }
+
+ // Remove unnecessary components to speed up boot time
+ system("rm /etc/init.d/S04backlight");
+ system("rm /etc/init.d/S05tp");
+ system("rm /etc/init.d/S40bluetoothd");
+ system("rm /etc/init.d/S50ssdpd");
+ system("rm /etc/init.d/S99*");
+ // Add necessary configuration files for program execution
system("mkdir /kvmapp/kvm");
system("mkdir /etc/kvm");
system("echo 0 > /kvmapp/kvm/now_fps");
@@ -42,9 +50,8 @@ void new_app_init(uint8_t safe_update)
// rm jpg_stream & kvm_stream
system("rm -r /kvmapp/jpg_stream");
- system("rm /kvmapp/kvm_system/kvm_stream");
+ system("rm /kvmapp/kvm_system/kvm_stream"); // Cannot delete temporarily; the old production test script uses this file to determine if the download is complete
- // rm kvm_new_app
system("rm /kvmapp/kvm_new_app");
system("sync");
// system("/etc/init.d/S95nanokvm restart");
@@ -106,9 +113,15 @@ void new_app_init(uint8_t safe_update)
if(access("/root/old/kvm_new_img", F_OK) == 0){
system("rm -r /root/old");
}
+
+ system("sync");
+ system("killall NanoKVM-Server");
+ system("rm -r /tmp/server");
+ system("cp -r /kvmapp/server /tmp/");
+ system("/tmp/server/NanoKVM-Server &");
}
-void update_resolv_conf(void)
+void build_complete_resolv(void)
{
FILE *fp = NULL;
// fp = fopen("/boot/resolv.conf", "w+");
@@ -122,121 +135,8 @@ void update_resolv_conf(void)
system("cp -vf /boot/resolv.conf /etc/resolv.conf");
}
-void init_upadte(void)
+void new_img_init(void)
{
- if(access("/kvmapp/kvm_new_app", F_OK) == 0){
- new_app_init(0);
- }
-
- // PCIe Patch
- if(access("/kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so", F_OK) == 0){
- system("cp -f /kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so /kvmapp/kvm_system/dl_lib/");
- new_app_init(1);
- }
-
- // DNS Patch
- system("tailscale set --accept-dns=false");
- if(access("/boot/resolv.conf", F_OK) != 0){
- // 不存在直接覆盖
- update_resolv_conf();
- } else {
- // 存在则检测是否拷贝/DNS是否正确
- uint8_t RW_Data[10] = {0};
- FILE *fp;
- fp = popen("cat /etc/resolv.conf | grep 119.29.29.29", "r");
- fgets((char*)RW_Data, 2, fp);
- pclose(fp);
- if(RW_Data[0] != 'n') {
- update_resolv_conf();
- }
- }
-
- // libmaixcam_lib.so
- if(access("/kvmapp/kvm_system/dl_lib/libmaixcam_lib.so", F_OK) == 0){
- // exist
- system("cp -f /kvmapp/kvm_system/dl_lib/libmaixcam_lib.so /kvmapp/server/dl_lib");
- } else {
- printf("kvm_system not exist lib!\n");
- }
- system("sync");
- system("killall NanoKVM-Server");
- system("rm -r /tmp/server");
- system("cp -r /kvmapp/server /tmp/");
- system("/tmp/server/NanoKVM-Server &");
-}
-
-void first_start(void)
-{
- if(access("/kvmapp/kvm_new_img", F_OK) == 0){
- // init oled
- OLED_state = oled_exist();
- if(OLED_state){
- printf("oled exist\r\n");
- } else {
- printf("oled not exist\r\n");
- }
- if(OLED_state){
- OLED_Init();
- OLED_ColorTurn(0); //0正常显示 1 反色显示
- OLED_DisplayTurn(0); //0正常显示 1 屏幕翻转显示
- // OLED_INIT_UI();
-
- if(kvm_hw_ver == 2){
- OLED_Revolve();
- }
- }
- uint8_t update_begin = 0;
- while(1){
- if(kvm_sys_state.eth_state == NIC_STATE_UP){
- OLED_Clear();
- if(kvm_hw_ver != 2){
- if(update_begin == 0){
- update_begin = 1;
- OLED_ShowString(1, 2, "Auto-updating...", 16);
- OLED_ShowStringtoend(1, 4, (char*)kvm_sys_state.eth_addr, 16, 0);
- system("python /kvmapp/system/update-nanokvm.py");
- } else {
- OLED_ShowString(1, 0, "Auto-update fail", 16);
- OLED_ShowString(1, 2, "Please open the ", 16);
- OLED_ShowStringtoend(1, 4, (char*)kvm_sys_state.eth_addr, 16, 0);
- OLED_ShowString(1, 6, "update KVM APP ", 16);
- }
- } else {
- // PCIe
- if(update_begin == 0){
- update_begin = 1;
- OLED_ShowString(0, 1, "Updating...", 8);
- OLED_ShowString_AlignRight(AlignRightEND_P, 2, (char*)kvm_sys_state.eth_addr, 4);
- system("python /kvmapp/system/update-nanokvm.py");
- } else {
- OLED_ShowString(0, 0, "Update fail", 8);
- OLED_ShowString(0, 1, "Please open", 8);
- OLED_ShowString_AlignRight(AlignRightEND_P, 2, (char*)kvm_sys_state.eth_addr, 4);
- OLED_ShowString(0, 3, "update APP" , 8);
- }
- }
- while(1){
- time::sleep_ms(1000);
- if(kvm_sys_state.eth_state == NIC_STATE_DOWN) {
- OLED_Clear();
- break;
- }
- }
- } else {
- get_ip_addr(ETH_ROUTE);
- if(kvm_hw_ver != 2){
- OLED_ShowString(1, 2, " Please connect ", 16);
- OLED_ShowString(1, 4, " to the network ", 16);
- OLED_ShowString(1, 6, " to update app ", 16);
- } else {
- // PCIe
- OLED_ShowString(0, 0, "Please link", 8);
- OLED_ShowString(0, 1, "to network", 8);
- OLED_ShowString(0, 2, "update APP" , 8);
- }
-
- time::sleep_ms(10);
- }
- }
- }
+ build_complete_resolv();
+ system("rm /kvmapp/kvm_new_img");
}
\ No newline at end of file
diff --git a/support/kvm_system/main/lib/system_init/system_init.h b/support/kvm_system/main/lib/system_init/system_init.h
index b0a9e7c..9f84f17 100644
--- a/support/kvm_system/main/lib/system_init/system_init.h
+++ b/support/kvm_system/main/lib/system_init/system_init.h
@@ -2,9 +2,8 @@
#define SYSTEM_INIT_H_
#include "config.h"
-void new_app_init(uint8_t safe_update);
-void update_resolv_conf(void);
void init_upadte(void);
-void first_start(void);
+void new_img_init(void);
+void new_app_init(void);
#endif // SYSTEM_INIT_H_
diff --git a/support/kvm_system/main/src/main.cpp b/support/kvm_system/main/src/main.cpp
index 2476842..31c010d 100644
--- a/support/kvm_system/main/src/main.cpp
+++ b/support/kvm_system/main/src/main.cpp
@@ -186,9 +186,17 @@ int main(int argc, char* argv[])
pthread_t display_thread;
pthread_t key_thread;
- // first_start();
+ // Execute only when it's a new image
+ if(access("/kvmapp/kvm_new_img", F_OK) == 0){
+ new_img_init();
+ }
- init_upadte();
+ // Execute only when it's a new app
+ if(access("/kvmapp/kvm_new_app", F_OK) == 0){
+ new_app_init();
+ }
+
+ system("sync");
OLED_state = oled_exist();
if(OLED_state){
diff --git a/vision/components/kvm/CMakeLists.txt b/vision/components/kvm/CMakeLists.txt
new file mode 100644
index 0000000..a97185c
--- /dev/null
+++ b/vision/components/kvm/CMakeLists.txt
@@ -0,0 +1,6 @@
+list(APPEND ADD_REQUIREMENTS vision basic peripheral)
+list(APPEND ADD_INCLUDE "include")
+
+append_srcs_dir(ADD_SRCS "src")
+
+register_component(DYNAMIC)
\ No newline at end of file
diff --git a/vision/components/kvm/Kconfig b/vision/components/kvm/Kconfig
new file mode 100644
index 0000000..e69de29
diff --git a/vision/components/kvm/include/kvm_vision.h b/vision/components/kvm/include/kvm_vision.h
new file mode 100644
index 0000000..8e10fa7
--- /dev/null
+++ b/vision/components/kvm/include/kvm_vision.h
@@ -0,0 +1,71 @@
+
+
+#ifndef KVM_VISION_H_
+#define KVM_VISION_H_
+
+#include "kvm_mmf.hpp"
+#include "maix_camera.hpp"
+#include "maix_basic.hpp"
+
+#include "maix_i2c.hpp"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include /* low-level i/o */
+#include
+#include
+#include
+#include
+#include
+#include
+
+// #include "kvm_mmf.hpp"
+
+#define IMG_BUFFER_FULL -3
+#define IMG_VENC_ERROR -2
+#define IMG_NOT_EXIST -1
+#define IMG_MJPEG_TYPE 0
+#define IMG_H264_TYPE_SPS 1
+#define IMG_H264_TYPE_PPS 2
+#define IMG_H264_TYPE_IF 3
+#define IMG_H264_TYPE_PF 4
+
+void kvmv_init(uint8_t _debug_info_en = 0);
+/**********************************************************************************
+ * @name kvmv_read_img
+ * @author Sipeed BuGu
+ * @date 2024/10/25
+ * @version R1.0
+ * @brief Acquire the encoded image with auto init
+ * @param _width @input: Output image width
+ * @param _height @input: Output image height
+ * @param _type @input: Encode type
+ * @param _qlty @input: MJPEG: (50-100) | H264: (500-10000)
+ * @param _pp_kvm_data @output: Encode data
+ * @param _p_kvmv_data_size @output: Encode data size
+ * @return
+ -4: Modifying image resolution, please wait
+ -3: img buffer full
+ -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)
+ 3: Acquire H264 encoded images(I)
+ 4: Acquire H264 encoded images(P)
+ **********************************************************************************/
+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 kvmv_deinit();
+uint8_t kvmv_hdmi_control(uint8_t _en);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KVM_VISION_H_
\ No newline at end of file
diff --git a/vision/components/kvm/src/kvm_vision.cpp b/vision/components/kvm/src/kvm_vision.cpp
new file mode 100644
index 0000000..5d8f652
--- /dev/null
+++ b/vision/components/kvm/src/kvm_vision.cpp
@@ -0,0 +1,1594 @@
+/**
+ * 待解决的问题:
+ * // 分辨率跟随输出
+ * // 自动切换分辨率
+ * // HDMI分辨率问题
+ * // H264输入空图,VENC容易炸问题
+ * // MJPEG/H264切换时卡退
+ * // 再加一条手动清空全部内存
+ * // deinit时free全部内存
+ * // free 错内存时会炸的问题
+ */
+#include "kvm_vision.h"
+
+#define default_venc_chn 1
+
+#define VENC_MJPEG 0
+#define VENC_H264 1
+
+#define KVMV_MAX_TRY_NUM 2
+#define vi_min_width 32
+#define vi_min_height 3
+
+#define default_vi_width 1920
+#define default_vi_height 1080
+#define default_vpss_width 1920
+#define default_vpss_height 1080
+#define default_venc_type VENC_MJPEG
+#define default_mjpeg_qlty 60
+#define default_h264_qlty 1000
+#define default_h264_gop 30
+
+#define kvmv_data_buffer_size 4
+
+#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 LT6911_ADDR 0x2B
+#define LT6911_READ 0xFF
+#define LT6911_WRITE 0x00
+
+static char NanoKVM_edit[] = {
+ 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x41,0x0C,0x33,0xC2,0x66,0xBA,0x00,0x00,
+ 0x2B,0x1F,0x01,0x04,0xA5,0x50,0x22,0x78,0x3B,0xCC,0xE5,0xAB,0x51,0x48,0xA6,0x26,
+ 0x0C,0x50,0x54,0xBF,0xEF,0x00,0xD1,0xC0,0xB3,0x00,0x95,0x00,0x81,0x80,0x81,0x40,
+ 0x81,0xC0,0x01,0x01,0x01,0x01,0xD8,0x59,0x00,0x60,0xA3,0x38,0x28,0x40,0xA0,0x10,
+ 0x3A,0x10,0x20,0x4F,0x31,0x00,0x00,0x1A,0x00,0x00,0x00,0xFF,0x00,0x55,0x4B,0x30,
+ 0x32,0x31,0x34,0x33,0x30,0x34,0x37,0x37,0x31,0x38,0x00,0x00,0x00,0xFC,0x00,0x50,
+ 0x48,0x4C,0x20,0x33,0x34,0x32,0x45,0x32,0x0A,0x20,0x20,0x20,0x00,0x00,0x00,0xFD,
+ 0x00,0x30,0x4B,0x63,0x63,0x1E,0x01,0x0A,0x20,0x20,0x20,0x20,0x20,0x20,0x01,0x8E
+};
+
+using namespace maix;
+using namespace maix::sys;
+using namespace maix::peripheral;
+i2c::I2C LT6911_i2c(4, i2c::Mode::MASTER);
+
+typedef struct {
+ uint16_t vi_width = default_vi_width;
+ uint16_t vi_height = default_vi_height;
+ uint16_t vpss_width = default_vpss_width;
+ uint16_t vpss_height = default_vpss_height;
+ uint8_t venc_type;
+ uint16_t qlty;
+ uint8_t cam_state = 0;
+ uint8_t stream_stop;
+ uint8_t frame_detact;
+ uint8_t display;
+ uint8_t reinit_flag = 1;
+ uint8_t reopen_cam_flag = 0;
+ uint8_t hdmi_cable_state = 0;
+ uint8_t try_exit_thread = 0;
+ uint8_t thread_is_running = 0;
+ uint8_t Auto_res = 0;
+ uint8_t hdmi_version = 0;
+ uint8_t hw_version = 0;
+ uint8_t hdmi_stop_flag = 0;
+ uint8_t hdmi_reading_flag = 0;
+ uint8_t hdmi_mode = 0;
+ uint8_t vi_detect_state = 0;
+} kvmv_cfg_t;
+
+typedef struct {
+ uint8_t* p_img_data = NULL;
+ uint8_t img_data_type = 0;
+ uint32_t img_data_size = 0;
+} kvmv_data_t;
+
+typedef struct {
+ uint8_t mmf_venc_chn;
+ uint8_t enc_h264_running;
+ uint8_t enc_h264_init;
+ mmf_venc_cfg_t kvm_venc_cfg;
+} kvm_venc_t;
+
+camera::Camera *cam = new camera::Camera(default_vpss_width, default_vpss_height, image::FMT_YVU420SP);
+// camera::Camera *cam = new camera::Camera(320, 240, image::FMT_RGB888);
+
+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;
+
+uint8_t debug_en = 0;
+void debug(const char *format, ...)
+{
+ if(debug_en){
+ printf(format);
+ }
+}
+
+uint8_t to_roll(int8_t _input)
+{
+ if(_input < 0) return _input + kvmv_data_buffer_size;
+ if(_input >= kvmv_data_buffer_size) return _input - kvmv_data_buffer_size;
+ return _input;
+}
+
+int maxmin_data(int _max, int _min, int _data)
+{
+ if(_data > _max) return _max;
+ if(_data < _min) return _min;
+ return _data;
+}
+
+kvmv_data_t* get_save_buffer()
+{
+ kvmv_data_buffer_index = to_roll(kvmv_data_buffer_index + 1);
+ // debug("[kvmv]kvmv_data_buffer_index = %d\n", kvmv_data_buffer_index);
+ // debug("[kvmv]kvmv_data_buffer.p_img_data = %d\n", kvmv_data_buffer[kvmv_data_buffer_index].p_img_data);
+ if(kvmv_data_buffer[kvmv_data_buffer_index].p_img_data == NULL){
+ return &kvmv_data_buffer[kvmv_data_buffer_index];
+ }
+ return NULL;
+}
+
+// ====HDMI RES==================================================
+
+uint16_t hdmi_res_list[][2] = {
+ {1920, 1080},
+ {1600, 900},
+ {1440, 1080},
+ {1440, 900},
+ {1280, 1024},
+ {1280, 960},
+ {1280, 800},
+ {1280, 720},
+ {1152, 864},
+ {1024, 768},
+ {800, 600},
+};
+
+/* return 0 : VI not init;
+ * return 1 : HDMI and CSI status are normal;
+ * return 2 : HDMI abnormal;
+ * return 3 : CSI abnormal: width too small;
+ * return 4 : CSI abnormal: width too large;
+ * return 5 : CSI abnormal: height too small;
+ * return 6 : CSI abnormal: height too large;
+ * return 7 : CSI abnormal: Unknown reason;
+ */
+uint8_t get_vi_state()
+{
+ char VI_State[10]={0};
+ char cmd[100] = "cat /proc/cvitek/vi_dbg | grep -A 17 VIDevFPS | awk '{print $3}'";
+ uint8_t FPS[2];
+ uint8_t VIWHGTLSCnt[4];
+ FILE* fp = popen( cmd, "r" );
+ uint8_t ret = 0;
+
+ if (fgets(VI_State, sizeof(VI_State), fp) != NULL){
+ FPS[0] = atoi(VI_State);
+ // debug("VIDevFPS = %d\n", FPS[0]);
+ } else {
+ pclose(fp);
+ return ret; // VI not init;
+ }
+ if (fgets(VI_State, sizeof(VI_State), fp) != NULL){
+ FPS[1] = atoi(VI_State);
+ // debug("VIFPS = %d\n", FPS[1]);
+ }
+ if (FPS[0] == 0){
+ ret = 2; // HDMI not OK;
+ } else if (FPS[1] == 0){
+ ret = 3; // HDMI OK ; CSI not;
+ } else {
+ ret = 1; // HDMI CSI OK;
+ }
+ if(ret == 3){
+ // Ignore other information
+ uint8_t count = 0;
+ for(count = 0; count < 13; count ++){
+ fgets(VI_State, sizeof(VI_State), fp);
+ }
+ // Check if the resolution might be set incorrectly
+ for(count = 0; count < 4; count ++){
+ if (fgets(VI_State, sizeof(VI_State), fp) != NULL){
+ // debug("VI_State = %s", VI_State);
+ VIWHGTLSCnt[count] = atoi(VI_State);
+ // printf("count = %d, val = %d\n", count, atoi(VI_State));
+ }
+ }
+
+ if(VIWHGTLSCnt[0] != 0) ret = 3; // The vi width setting value is too small
+ else if(VIWHGTLSCnt[1] != 0) ret = 4; // The vi width setting value is too large
+ else if(VIWHGTLSCnt[2] != 0) ret = 5; // The vi height setting value is too small
+ else if(VIWHGTLSCnt[3] != 0) ret = 6; // The vi height setting value is too large
+ else ret = 7; // printf("[kvmv] Unexpected situation\n");
+ }
+ pclose(fp);
+ return ret;
+}
+
+int get_hdmi_mode(void)
+{
+ if(access(hdmi_mode_path, F_OK) == 0){
+ // exist
+ FILE *fp;
+ int file_size;
+ uint8_t tmp8;
+ uint8_t RW_Data[2];
+
+ fp = fopen(hdmi_mode_path, "r");
+ fread(RW_Data, sizeof(char), 1, fp);
+ fclose(fp);
+ RW_Data[2] = 0;
+ tmp8 = atoi((char*)RW_Data);
+ if(tmp8 > 2) {
+ tmp8 = 0;
+ char Cmd[100]={0};
+ sprintf(Cmd, "echo 0 > %s", hdmi_mode_path);
+ system(Cmd);
+ }
+ if(tmp8 != kvmv_cfg.hdmi_mode){
+ kvmv_cfg.hdmi_mode = tmp8;
+ debug("[kvmv] hdmi mode = %d\n", kvmv_cfg.hdmi_mode);
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ kvmv_cfg.hdmi_mode = 0;
+ return 0;
+}
+
+int get_manual_resolution(void)
+{
+ uint8_t RW_Data[35];
+ FILE *fp;
+ int file_size;
+ uint16_t tmp_width, tmp_height;
+ int res = 0;
+
+ // get res
+ if(access("/kvmapp/kvm/width", F_OK) == 0){
+ fp = fopen("/kvmapp/kvm/width", "r");
+ fseek(fp, 0, SEEK_END);
+ file_size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ fread(RW_Data, sizeof(char), file_size, fp);
+ fclose(fp);
+ RW_Data[file_size] = 0;
+ tmp_width = atoi((char*)RW_Data);
+ } else {
+ tmp_width = 1920;
+ }
+ if(access("/kvmapp/kvm/height", F_OK) == 0){
+ fp = fopen("/kvmapp/kvm/height", "r");
+ fseek(fp, 0, SEEK_END);
+ file_size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ fread(RW_Data, sizeof(char), file_size, fp);
+ fclose(fp);
+ RW_Data[file_size] = 0;
+ tmp_height = atoi((char*)RW_Data);
+ } else {
+ tmp_height = 1080;
+ }
+
+ // res min limit
+ if(tmp_width < vi_min_width){
+ tmp_width = vi_min_width;
+ char Cmd[100]={0};
+ sprintf(Cmd, "echo %d > %s", vi_min_width, vi_width_path);
+ system(Cmd);
+ }
+ if(tmp_height < vi_min_height){
+ tmp_height = vi_min_height;
+ char Cmd[100]={0};
+ sprintf(Cmd, "echo %d > %s", vi_min_height, vi_height_path);
+ system(Cmd);
+ }
+
+ // res change ?
+ if(kvmv_cfg.vi_width != tmp_width){
+ kvmv_cfg.vi_width = tmp_width;
+ printf("[kvmk] get new width = %d\n", kvmv_cfg.vi_width);
+ res = 1;
+ }
+ if(kvmv_cfg.vi_height != tmp_height){
+ kvmv_cfg.vi_height = tmp_height;
+ printf("[kvmk] get new height = %d\n", kvmv_cfg.vi_height);
+ res = 1;
+ }
+ return res;
+}
+
+void lt6911_enable()
+{
+ uint8_t buf[2];
+ buf[0] = 0xff;
+ buf[1] = 0x80;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ buf[0] = 0xee;
+ buf[1] = 0x01;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ if(kvmv_cfg.hdmi_version == 1){
+ // disable watchdog
+ buf[0] = 0x10;
+ buf[1] = 0x00;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+ }
+}
+
+void lt6911_disable()
+{
+ uint8_t buf[2];
+ buf[0] = 0xff;
+ buf[1] = 0x80;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ buf[0] = 0xee;
+ buf[1] = 0x00;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+}
+
+void lt6911_get_hdmi_errer()
+{
+ uint8_t buf[6];
+
+ buf[0] = 0xff;
+ buf[1] = 0xC0;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ buf[0] = 0x20;
+ buf[1] = 0x01;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ time::sleep_ms(100);
+
+ buf[0] = 0x24;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+
+ maix::Bytes *dat = LT6911_i2c.readfrom(LT6911_ADDR, 6);
+
+ buf[0] = 0x20;
+ buf[1] = 0x07;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ for(int i = 0; i < 6; i++){
+ buf[i] = (uint8_t)dat->data[i];
+ }
+ delete dat;
+
+ debug("hdmi_errer_code = %x, %x, %x, %x, %x, %x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
+}
+
+uint8_t lt6911_get_hdmi_res()
+{
+ uint8_t buf[2];
+ uint8_t revbuf[4];
+ uint16_t Vactive;
+ uint16_t Hactive;
+
+
+ if(kvmv_cfg.hdmi_version == 0){
+ // LT6911C
+ buf[0] = 0xff;
+ buf[1] = 0xd2;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ buf[0] = 0x83;
+ buf[1] = 0x11;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ time::sleep_ms(5);
+
+ // Vactive
+ buf[0] = 0x96;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2);
+
+ // Hactive
+ buf[0] = 0x8b;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2);
+
+ revbuf[0] = (uint8_t)dat0->data[0];
+ revbuf[1] = (uint8_t)dat0->data[1];
+ revbuf[2] = (uint8_t)dat1->data[0];
+ revbuf[3] = (uint8_t)dat1->data[1];
+
+ Vactive = (revbuf[0] << 8)|revbuf[1];
+ Hactive = (revbuf[2] << 8)|revbuf[3];
+ Hactive *= 2;
+
+ debug("[hdmi]HDMI res modification event\n");
+ debug("[hdmi]new res: %d * %d\n", Hactive, Vactive);
+
+ delete dat0;
+ delete dat1;
+
+ if (Vactive != 0 && Hactive != 0){
+ return 1;
+ } else {
+ // system("echo 0 > %s", vi_height_path);
+ // system("echo 0 > %s", vi_width_path);
+ return 0;
+ }
+ } else if (kvmv_cfg.hdmi_version == 1){
+ // LT6911UXC
+ buf[0] = 0xff;
+ buf[1] = 0x86;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ buf[0] = 0xff;
+ buf[1] = 0x86;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ // HDMI signal disappear/stable
+ buf[0] = 0xA3;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 1);
+
+ revbuf[0] = (uint8_t)dat0->data[0];
+ delete dat0;
+
+ debug("[hdmi]HDMI-UXC res modification event\n");
+
+ if(revbuf[0] == 0x55) return 1;
+ else if(revbuf[0] == 0x88) return 0;
+ else return 0;
+ } else {
+ return 0;
+ }
+}
+
+void lt6911_get_hdmi_clk()
+{
+ uint8_t buf[2];
+ uint8_t revbuf[3];
+ uint32_t clk;
+
+ buf[0] = 0xff;
+ buf[1] = 0xa0;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ buf[0] = 0x34;
+ buf[1] = 0x0b;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ time::sleep_ms(50);
+
+ // clk
+ buf[0] = 0xff;
+ buf[1] = 0xb8;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ buf[0] = 0xb1;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 3);
+
+ revbuf[0] = (uint8_t)dat0->data[0];
+ revbuf[1] = (uint8_t)dat0->data[1];
+ revbuf[2] = (uint8_t)dat0->data[2];
+ revbuf[0] &= 0x07;
+
+ clk = revbuf[0];
+ clk <<= 8;
+ clk |= revbuf[1];
+ clk <<= 8;
+ clk |= revbuf[2];
+
+ debug("[hdmi]HDMI CLK = %d\n", clk);
+
+ delete dat0;
+}
+
+uint8_t lt6911_get_csi_res(uint16_t *p_width, uint16_t *p_height)
+{
+ uint8_t ret = 0;
+ uint8_t buf[2];
+ uint8_t revbuf[4];
+ static uint16_t old_Vactive;
+ static uint16_t old_Hactive;
+ uint16_t Vactive;
+ uint16_t Hactive;
+ char Cmd[100]={0};
+
+
+ if(kvmv_cfg.hdmi_version == 0){
+ // LT6911C
+ buf[0] = 0xff;
+ buf[1] = 0xc2;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ // Vactive
+ buf[0] = 0x06;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2);
+
+ // Hactive
+ buf[0] = 0x38;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2);
+
+ revbuf[0] = (uint8_t)dat0->data[0];
+ revbuf[1] = (uint8_t)dat0->data[1];
+ revbuf[2] = (uint8_t)dat1->data[0];
+ revbuf[3] = (uint8_t)dat1->data[1];
+
+ delete dat0;
+ delete dat1;
+
+ Vactive = (revbuf[0] << 8)|revbuf[1];
+ Hactive = (revbuf[2] << 8)|revbuf[3];
+ } else if(kvmv_cfg.hdmi_version == 1) {
+ // LT6911UXC
+ debug("[hdmi]UXC get csi res\n");
+ buf[0] = 0xff;
+ buf[1] = 0x85;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+
+ // Vactive
+ buf[0] = 0xF0;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2);
+
+ // Hactive
+ buf[0] = 0xEA;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+ maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2);
+
+ revbuf[0] = (uint8_t)dat0->data[0];
+ revbuf[1] = (uint8_t)dat0->data[1];
+ revbuf[2] = (uint8_t)dat1->data[0];
+ revbuf[3] = (uint8_t)dat1->data[1];
+
+ delete dat0;
+ delete dat1;
+
+ Vactive = (revbuf[0] << 8)|revbuf[1];
+ Hactive = (revbuf[2] << 8)|revbuf[3];
+ } else {
+ return ret;
+ }
+
+ if(old_Hactive != Hactive || old_Vactive != Vactive){
+ old_Hactive = Hactive;
+ old_Vactive = Vactive;
+ // p_kvm_cfg->width = Hactive;
+ // p_kvm_cfg->height = Vactive;
+ *p_width = Hactive;
+ *p_height = Vactive;
+ ret = 1;
+ }
+
+ debug("[hdmi]CSI res: %d * %d; new res = %d\n", Hactive, Vactive, ret);
+ // setenv("KVM_CSI_HEIGHT", to_string(Hactive).c_str(), 1);
+ // setenv("KVM_CSI_WIDTH", to_string(Vactive).c_str(), 1);
+
+ sprintf(Cmd, "echo %d > %s", Hactive, vi_width_path);
+ system(Cmd);
+ sprintf(Cmd, "echo %d > %s", Vactive, vi_height_path);
+ system(Cmd);
+ system("sync");
+
+
+ return ret;
+}
+
+void lt6911_write_reg(uint8_t reg, uint8_t val)
+{
+ uint8_t buf[2];
+ buf[0] = reg;
+ buf[1] = val;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 2);
+}
+
+void lt6911_read_reg(uint8_t reg)
+{
+ uint8_t buf[16];
+ buf[0] = reg;
+ LT6911_i2c.writeto(LT6911_ADDR, buf, 1);
+
+ maix::Bytes *dat = LT6911_i2c.readfrom(LT6911_ADDR, 16);
+
+ for(int i = 0; i < 16; i++){
+ buf[i] = (uint8_t)dat->data[i];
+ }
+
+ delete dat;
+
+ debug("[hdmi]%3x %3x %3x %3x %3x %3x %3x %3x |%3x %3x %3x %3x %3x %3x %3x %3x \n", \
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], \
+ buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);
+}
+
+uint8_t lt6911_read_one_reg(uint8_t reg)
+{
+ uint8_t ret;
+ ret = reg;
+ LT6911_i2c.writeto(LT6911_ADDR, &ret, 1);
+
+ maix::Bytes *dat = LT6911_i2c.readfrom(LT6911_ADDR, 1);
+
+ ret = (uint8_t)dat->data[0];
+
+ delete dat;
+ return ret;
+}
+
+void lt6911_write_edid(void)
+{
+ uint8_t i, j;
+ uint8_t buf[2];
+ lt6911_enable();
+
+ // to 90
+ lt6911_write_reg(0xff, 0x90);
+ buf[0] = lt6911_read_one_reg(0x02);
+ buf[0] &= 0xDF;
+ lt6911_write_reg(0x02, buf[0]);
+ buf[0] |= 0x20;
+ lt6911_write_reg(0x02, buf[0]);
+
+ // to 80
+ lt6911_write_reg(0xff, 0x80);
+ // wren enable
+ lt6911_write_reg(0x5A, 0x86);
+ lt6911_write_reg(0x5A, 0x82);
+
+ for(i = 0; i < 16; i++){
+ // 写wren命令(为一个pulse,此时不需要考虑wrrd_mode,spi_paddr[1:0]的值)
+ lt6911_write_reg(0x5A, 0x86);
+ lt6911_write_reg(0x5A, 0x82);
+
+ // 配置spi_len[3:0]= 15,可配置,spi内部加1,即配置一次写入16个字节
+ lt6911_write_reg(0x5E, 0xEF);
+ lt6911_write_reg(0x5A, 0xA2);
+ lt6911_write_reg(0x5A, 0x82);
+
+ lt6911_write_reg(0x58, 0x01);
+
+ if(i < 8) {
+ for(j = 0; j < 16; j++){
+ lt6911_write_reg(0x59, NanoKVM_edit[i*16+j]);
+ }
+ } else {
+ for(j = 0; j < 16; j++){
+ lt6911_write_reg(0x59, 0x00);
+ }
+ }
+
+ // 把fifo数据写到flash(当wrrd_mode = 1,spi_paddr= 2’b10,addr[23:0](地址在写入过程中需要保持不变)准备好的情况下,给一个spi_sta的pulse,就开始写入flash)
+ lt6911_write_reg(0x5B, 0x00);
+ lt6911_write_reg(0x5C, 0x21);
+ lt6911_write_reg(0x5D, i*16);
+ lt6911_write_reg(0x5E, 0xE0);
+ lt6911_write_reg(0x5A, 0x92);
+ lt6911_write_reg(0x5A, 0x82);
+
+ }
+
+ lt6911_write_reg(0x5A, 0x8A);
+ lt6911_write_reg(0x5A, 0x82);
+
+ lt6911_disable();
+ debug("[hdmi]lt6911_write_edid OK\n");
+}
+
+void lt6911_read_edid(void)
+{
+ uint8_t i, j;
+ lt6911_enable();
+
+ // to 80
+ lt6911_write_reg(0xff, 0x80);
+ lt6911_write_reg(0xEE, 0x01);
+
+ // configure parameter
+ lt6911_write_reg(0x5A, 0x80);
+ lt6911_write_reg(0x5E, 0xC0);
+ lt6911_write_reg(0x58, 0x00);
+ lt6911_write_reg(0x59, 0x51);
+ lt6911_write_reg(0x5A, 0x90);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+
+ for(i = 0; i < 16; i++){
+ // to 90
+ lt6911_write_reg(0xff, 0x90);
+ // fifo rst_n
+ lt6911_write_reg(0x02, 0xdf);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x02, 0xff);
+
+ // wren
+ // to 80
+ lt6911_write_reg(0xff, 0x80);
+ // fifo rst_n
+ lt6911_write_reg(0x5A, 0x84);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+
+ // flash to fifo
+ lt6911_write_reg(0x5E, 0x6F);
+ lt6911_write_reg(0x5A, 0xA0);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+ lt6911_write_reg(0x5B, 0x00);
+ lt6911_write_reg(0x5C, 0x21);
+ lt6911_write_reg(0x5D, 16*i);
+ lt6911_write_reg(0x5A, 0x90);
+ time::sleep_ms(5);
+ lt6911_write_reg(0x5A, 0x80);
+ lt6911_write_reg(0x58, 0x01);
+
+ // for(j = 0; j < 16; j++){
+ debug("[hdmi] EDID: ");
+ lt6911_read_reg(0x5F);
+ // }
+ time::sleep_ms(10);
+ }
+
+ // wrdi
+ lt6911_write_reg(0x5A, 0x88);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+
+ lt6911_disable();
+ debug("[hdmi]lt6911_read_edid OK\n");
+}
+
+void lt6911_read_fw(void)
+{
+ uint32_t i, j;
+ uint8_t buf[3];
+ lt6911_enable();
+
+ // to 80
+ lt6911_write_reg(0xff, 0x80);
+ lt6911_write_reg(0xEE, 0x01);
+
+ // configure parameter
+ lt6911_write_reg(0x5A, 0x80);
+ lt6911_write_reg(0x5E, 0xC0);
+ lt6911_write_reg(0x58, 0x00);
+ lt6911_write_reg(0x59, 0x51);
+ lt6911_write_reg(0x5A, 0x90);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+
+ for(i = 0x1; i < 50000; i++){
+ buf[0] = ((i*16) & 0xFF0000) >> 16;
+ buf[1] = ((i*16) & 0xFF00) >> 8;
+ buf[2] = ((i*16) & 0xFF);
+ // to 90
+ lt6911_write_reg(0xff, 0x90);
+ // fifo rst_n
+ lt6911_write_reg(0x02, 0xdf);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x02, 0xff);
+
+ // wren
+ // to 80
+ lt6911_write_reg(0xff, 0x80);
+ // fifo rst_n
+ lt6911_write_reg(0x5A, 0x84);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+
+ // flash to fifo
+ lt6911_write_reg(0x5E, 0x6F);
+ lt6911_write_reg(0x5A, 0xA0);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+ lt6911_write_reg(0x5B, buf[0]);
+ lt6911_write_reg(0x5C, buf[1]);
+ lt6911_write_reg(0x5D, buf[2]);
+ lt6911_write_reg(0x5A, 0x90);
+ time::sleep_ms(5);
+ lt6911_write_reg(0x5A, 0x80);
+ lt6911_write_reg(0x58, 0x01);
+
+ // for(j = 0; j < 16; j++){
+ debug("[hdmi]REG %6x: ", i*16);
+ lt6911_read_reg(0x5F);
+ // }
+ time::sleep_ms(10);
+ }
+
+ // wrdi
+ lt6911_write_reg(0x5A, 0x88);
+ time::sleep_ms(1);
+ lt6911_write_reg(0x5A, 0x80);
+
+ lt6911_disable();
+ debug("[hdmi]lt6911_read_edid OK\n");
+}
+
+// ==============================================================
+
+void* vi_subsystem_detection(void * arg)
+{
+ uint64_t __attribute__((unused)) int_time;
+
+ FILE *fp;
+ uint8_t RW_Data[2];
+ uint8_t file_size;
+ uint8_t tmp8;
+ uint8_t rising_times = 0;
+ uint8_t falling_times = 0;
+ uint8_t cam_need_restart = 0;
+ kvmv_cfg.thread_is_running = 1;
+ if(access("/proc/lt_int", F_OK) != 0){
+ time::sleep_ms(10);
+ debug("[hdmi]/proc/lt_int not ok\n");
+ }
+
+ if(access("/etc/kvm/hdmi_version", F_OK) == 0){
+ fp = fopen("/etc/kvm/hdmi_version", "r");
+ fread(RW_Data, sizeof(char), 1, fp);
+ fclose(fp);
+ if(RW_Data[0] == 'u'){
+ // 6911uxc
+ kvmv_cfg.hdmi_version = 1;
+ debug("[hdmi]HDMI-UXC exist!\n");
+ } else {
+ // 6911c
+ kvmv_cfg.hdmi_version = 0;
+ debug("[hdmi]HDMI-C exist!\n");
+ }
+ RW_Data[0] = 0;
+ } else {
+ kvmv_cfg.hdmi_version = 0;
+ }
+
+ // while(!app::need_exit())
+ uint8_t while_count_get_hdmi_mode = 0;
+ uint8_t while_count_detect_res = 0;
+ uint8_t auto_trying_times = 0;
+ while(true)
+ {
+ uint8_t get_new_hdmi_mode = 0;
+ if(kvmv_cfg.try_exit_thread == 1)
+ break;
+
+ // Low-frequency detection mode change
+ if (while_count_get_hdmi_mode >= 100){
+ while_count_get_hdmi_mode = 0;
+ get_new_hdmi_mode = get_hdmi_mode();
+ } else {
+ while_count_get_hdmi_mode ++;
+ }
+
+ switch (kvmv_cfg.hdmi_mode){
+ case 0:
+ // Switching to Mode 0 requires restarting HDMI (effective only for PCIe version)
+ // Handling of automatic detection situations
+ if(get_new_hdmi_mode == 1){
+ kvmv_cfg.vi_detect_state = 0;
+ // reset hdmi_state
+ char Cmd[100]={0};
+ sprintf(Cmd, "echo 0 > %s", hdmi_state_path);
+ system(Cmd);
+ // reset hdmi
+ kvmv_hdmi_control(0);
+ time::sleep_ms(10);
+ kvmv_hdmi_control(1);
+ }
+ // Initialize VI with I2C information after HDMI insertion
+ fp = fopen(hdmi_state_path, "r+");
+ if(fp != NULL){
+ // fseek(fp, 0, SEEK_END);
+ // file_size = ftell(fp);
+ // fseek(fp, 0, SEEK_SET);
+ fread(RW_Data, sizeof(char), 2, fp);
+ tmp8 = atoi((char*)RW_Data);
+ // debug("[hdmi]UXC tmp8 = %d\n", tmp8);
+ if(tmp8 != 0){
+ // reset hdmi_state
+ fputs("0", fp);
+ // count edge ints
+ rising_times = tmp8%10; // RISING times
+ falling_times = tmp8/10; // FALLING times
+
+ if(kvmv_cfg.hdmi_stop_flag != 1){
+ kvmv_cfg.hdmi_reading_flag = 1;
+ if(kvmv_cfg.hdmi_version == 0){
+ // LT6911C
+ if(rising_times != 0){
+ lt6911_enable();
+ if(lt6911_get_hdmi_res()){
+ // hdmi get res
+ debug("[hdmi] C HDMI cable insertion!\n");
+ kvmv_cfg.hdmi_cable_state = 1;
+ kvmv_cfg.reopen_cam_flag = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height);
+ } else {
+ // HDMI res = 0*0/x*0
+ debug("[hdmi] C HDMI cable unplugged!\n");
+ kvmv_cfg.hdmi_cable_state = 0;
+ }
+ lt6911_disable();
+ }
+ } else if (kvmv_cfg.hdmi_version == 1){
+ // LT6911UXC
+ debug("[hdmi] UXC int\n");
+ if(falling_times != 0){
+ debug("[hdmi] UXC int && \n");
+ lt6911_enable();
+ if(lt6911_get_hdmi_res()){
+ // hdmi get res
+ debug("[hdmi] UXC HDMI cable insertion!\n");
+ kvmv_cfg.hdmi_cable_state = 1;
+ kvmv_cfg.reopen_cam_flag = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height);
+ } else {
+ // HDMI res = 0*0/x*0
+ debug("[hdmi] UXC HDMI cable unplugged!\n");
+ kvmv_cfg.hdmi_cable_state = 0;
+ }
+ lt6911_disable();
+ }
+ }
+ kvmv_cfg.hdmi_reading_flag = 0;
+ }
+ }
+ fclose(fp);
+ }
+ break;
+ /* Mode 1 & 2 will disable API access to the image during detection,
+ and will output -4: Modifying image resolution, please wait.*/
+ case 1: // Automatically trying common resolutions
+ /* kvmv_cfg.vi_detect_state :
+ * 0: HDMI standard mode, detection program does not interfere with the camera
+ * 1: Preparing / Testing in progress
+ * 2: Test completed: Suitable resolution found,
+ */
+ if(get_new_hdmi_mode == 1){
+ kvmv_cfg.vi_detect_state = 1;
+ auto_trying_times = 0;
+ }
+ if(kvmv_cfg.vi_detect_state == 1){
+ uint8_t err_code = get_vi_state();
+ debug("[kvmv] get_vi_state() = %d\n", err_code);
+ switch(err_code){
+ case 0:
+ // shouldn't be possible to run here
+ printf("[kvmv] VI not init\n");
+ break;
+ case 1:
+ printf("[kvmv] VI subsystem is normal\n");
+ kvmv_cfg.vi_detect_state = 2;
+ break;
+ case 2:
+ // HDMI not detected or resolution not supported; interval checks will continue
+ printf("[kvmv] Cannot obtain HDMI input\n");
+ break;
+ case 3: // width too small
+ case 4: // width too large
+ case 5: // height too small
+ case 6: // height too large
+ // CSI abnormal due to resolution error
+ // The test list is short; sequential testing can be performed
+ if(auto_trying_times < sizeof(hdmi_res_list)/4){
+ char Cmd[100]={0};
+ printf("[kvmv] Trying %d * %d res ..\n", hdmi_res_list[auto_trying_times][0], hdmi_res_list[auto_trying_times][1]);
+ sprintf(Cmd, "echo %d > %s", hdmi_res_list[auto_trying_times][0], vi_width_path);
+ system(Cmd);
+ sprintf(Cmd, "echo %d > %s", hdmi_res_list[auto_trying_times][1], vi_height_path);
+ system(Cmd);
+ printf("[kvmv] restart cam...\n");
+ cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP);
+ auto_trying_times++;
+ time::sleep_ms(10);
+ } else {
+ printf("[kvmv] Suitable resolution not found, switching to manual input mode automatically\n");
+ kvmv_cfg.vi_detect_state = 1;
+ char Cmd[100]={0};
+ sprintf(Cmd, "echo 2 > %s", hdmi_mode_path);
+ system(Cmd);
+ while_count_get_hdmi_mode = 100; // Take effect immediately
+ }
+ break;
+ case 7: // Unknown reason
+ printf("[kvmv] CSI abnormal: Unknown reason\n");
+ break;
+ }
+ } else if (kvmv_cfg.vi_detect_state == 2){
+ // Low-frequency detection of HDMI status, no log output
+ uint8_t err_code = get_vi_state();
+ if (err_code != 1) {
+ kvmv_cfg.vi_detect_state = 1;
+ auto_trying_times = 0;
+ }
+ time::sleep_ms(1000);
+ } else {
+ kvmv_cfg.vi_detect_state = 1;
+ }
+ break;
+ case 2:
+ // Manually initialize VI.
+ if (while_count_detect_res >= 100){
+ while_count_detect_res = 0;
+ if (kvmv_cfg.vi_detect_state == 1){
+ // detect_res
+ if (get_manual_resolution()) {
+ printf("[kvmv] restart cam...\n");
+ cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP);
+ }
+
+ // dbg info
+ uint8_t err_code = get_vi_state();
+ switch(err_code){
+ case 0:
+ debug("[kvmv] VI not init\n");
+ break;
+ case 1:
+ debug("[kvmv] HDMI and CSI status are normal\n");
+ kvmv_cfg.vi_detect_state = 2;
+ break;
+ case 2:
+ debug("[kvmv] HDMI abnormal\n");
+ break;
+ case 3:
+ debug("[kvmv] CSI abnormal: width too small\n");
+ break;
+ case 4:
+ debug("[kvmv] CSI abnormal: width too large\n");
+ break;
+ case 5:
+ debug("[kvmv] CSI abnormal: height too small\n");
+ break;
+ case 6:
+ debug("[kvmv] CSI abnormal: height too large\n");
+ break;
+ case 7:
+ debug("[kvmv] CSI abnormal: Unknown reason\n");
+ break;
+ }
+ } else if (kvmv_cfg.vi_detect_state == 2){
+ // detection of HDMI status, no log output
+ uint8_t err_code = get_vi_state();
+ if (err_code != 1) kvmv_cfg.vi_detect_state = 1;
+ } else {
+ kvmv_cfg.vi_detect_state = 1;
+ }
+
+ } else {
+ while_count_detect_res ++;
+ }
+ break;
+
+ default:
+ debug("Non-existent hdmi state = %d\n", kvmv_cfg.hdmi_mode);
+ break;
+ }
+
+ time::sleep_ms(10);
+ }
+ kvmv_cfg.thread_is_running = 0;
+}
+
+int sync_vi_res()
+{
+ int res = 0;
+ uint8_t RW_Data[35];
+ FILE *fp;
+ int file_size;
+ uint16_t tmp16;
+
+ // vi_width:
+ if (access(vi_width_path, F_OK) != 0){
+ kvmv_cfg.vi_width = default_vi_width;
+ kvmv_cfg.vi_height = default_vi_height;
+ res = -1;
+ return res;
+ } else {
+ fp = fopen(vi_width_path, "r");
+ fseek(fp, 0, SEEK_END);
+ file_size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ fread(RW_Data, sizeof(char), file_size, fp);
+ fclose(fp);
+ RW_Data[file_size] = 0;
+ tmp16 = atoi((char*)RW_Data);
+ if(tmp16 != kvmv_cfg.vi_width){
+ kvmv_cfg.vi_width = tmp16;
+ debug("[hdmi] Get new HDMI width = %d\r\n", kvmv_cfg.vi_width);
+ res = 1;
+ }
+ }
+ // vi_height:
+ if (access(vi_height_path, F_OK) != 0){
+ kvmv_cfg.vi_height = default_vi_height;
+ res = -1;
+ return res;
+ } else {
+ fp = fopen(vi_height_path, "r");
+ fseek(fp, 0, SEEK_END);
+ file_size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ fread(RW_Data, sizeof(char), file_size, fp);
+ fclose(fp);
+ RW_Data[file_size] = 0;
+ tmp16 = atoi((char*)RW_Data);
+ if(tmp16 != kvmv_cfg.vi_height){
+ kvmv_cfg.vi_height = tmp16;
+ debug("[hdmi] Get new HDMI height = %d\r\n", kvmv_cfg.vi_height);
+ res = 1;
+ }
+ }
+ return res;
+}
+
+void jpg_dump(kvmv_data_t* dump_to, image::Image *raw)
+{
+ dump_to->p_img_data = (uint8_t *)malloc(raw->data_size());
+ dump_to->img_data_size = raw->data_size();
+ dump_to->img_data_type = VENC_MJPEG;
+ memcpy(dump_to->p_img_data, (uint8_t *)raw->data(), raw->data_size());
+}
+
+uint8_t kvmvenc_gop = default_h264_gop;
+kvm_venc_t kvm_venc;
+mmf_venc_cfg_t cfg;
+void init_venc_h264(uint16_t _width, uint16_t _height, uint16_t _qlty)
+{
+ cfg.type = 2; //1, h265, 2, h264
+ cfg.w = _width;
+ cfg.h = _height;
+ cfg.fmt = mmf_invert_format_to_mmf(image::Format::FMT_YVU420SP);
+ cfg.jpg_quality = 0; // unused
+ cfg.gop = kvmvenc_gop;
+ cfg.intput_fps = 60;
+ cfg.output_fps = 60;
+ cfg.bitrate = _qlty; // 码率
+
+ kvm_venc.mmf_venc_chn = default_venc_chn;
+ kvm_venc.enc_h264_init = 0;
+ kvm_venc.kvm_venc_cfg = cfg;
+
+ // if(mmf_vdec_is_used(kvm_venc.mmf_venc_chn)){
+ mmf_del_venc_channel(kvm_venc.mmf_venc_chn);
+ // }
+ if (0 != mmf_add_venc_channel(kvm_venc.mmf_venc_chn, &kvm_venc.kvm_venc_cfg)) {
+ err::check_raise(err::ERR_RUNTIME, "mmf venc init failed!");
+ }
+ kvm_venc.enc_h264_init = 1;
+
+ // init_kvm_h264_stream(&kvm_h264_stream, mmf_stream_buf);
+ // init_h264_stream_struct(&kvm_h264_stream);
+}
+
+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]);
+
+ 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);
+
+ return IMG_H264_TYPE_IF;
+
+ } else if (dump_from->count == 1) {
+ // debug("[kvmv]dump P-Frame\r\n");
+ I_Frame_index = -1;
+ dump_to->p_img_data = (uint8_t *)malloc(dump_from->data_size[0]);
+ dump_to->img_data_size = dump_from->data_size[0];
+ dump_to->img_data_type = IMG_H264_TYPE_PF;
+ memcpy(dump_to->p_img_data, dump_from->data[0], dump_from->data_size[0]);
+ return IMG_H264_TYPE_PF;
+ } else {
+ debug("[kvmv]venc error!\r\n");
+ return IMG_VENC_ERROR;
+ }
+}
+
+void set_h264_gop(uint8_t _gop)
+{
+ kvm_venc.enc_h264_init = 0; // call
+ kvmvenc_gop = maxmin_data(100, 10, (int)_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;
+// }
+
+int8_t raw_to_h264(image::Image *raw, kvmv_data_t* ret_stream, uint16_t _qlty)
+{
+ uint64_t __attribute__((unused)) start_time;
+ uint64_t __attribute__((unused)) frame_time;
+ int8_t ret = 0;
+ static uint8_t P_Frame_Count = 0;
+ // start_time = time::time_ms();
+ // log::info("getimg: %d \r\n", (int)(time::time_ms()));
+ mmf_stream_t _stream = {0};
+ if(kvm_venc.enc_h264_init != 1 || raw->width() != kvm_venc.kvm_venc_cfg.w || raw->height() != kvm_venc.kvm_venc_cfg.h || _qlty != kvm_venc.kvm_venc_cfg.bitrate){
+ debug("[kvmv]init_venc_h264 enc_h264_init = %d; raw->width() = %d | %d raw->height() = %d | %d \n",
+ kvm_venc.enc_h264_init,
+ raw->width(), kvm_venc.kvm_venc_cfg.w,
+ raw->height(), kvm_venc.kvm_venc_cfg.h);
+
+ init_venc_h264(raw->width(), raw->height(), _qlty);
+ debug("[kvmv]init_venc_h264 finish enc_h264_init = %d; raw->width() = %d | %d raw->height() = %d | %d \n",
+ kvm_venc.enc_h264_init,
+ raw->width(), kvm_venc.kvm_venc_cfg.w,
+ raw->height(), kvm_venc.kvm_venc_cfg.h);
+ // if(kvm_venc.enc_h264_init == 1){
+ // init_venc_h264(raw->width(), raw->height(), _qlty);
+ // } else {
+ // init_venc_h264(default_vpss_width, default_vpss_height, default_h264_qlty);
+ // }
+ }
+ // log::info("init(): %d \r\n", (int)(time::time_ms() - start_time));
+ if (mmf_venc_push(kvm_venc.mmf_venc_chn, (uint8_t *)raw->data(), raw->width(), raw->height(), mmf_invert_format_to_mmf(raw->format()))) {
+ mmf_del_venc_channel(kvm_venc.mmf_venc_chn);
+ kvm_venc.enc_h264_init = 0;
+ // rtmp->unlock();
+ debug("[kvmv]mmf venc push failed!\n");
+ // err::check_raise(err::ERR_RUNTIME, "mmf venc push failed!\r\n");
+ return -1;
+ }
+ // log::info("push(): %d \r\n", (int)(time::time_ms() - start_time));
+ if (mmf_venc_pop(kvm_venc.mmf_venc_chn, &_stream)) {
+ // log::error("mmf_venc_pop failed\n");
+ mmf_venc_free(kvm_venc.mmf_venc_chn);
+ mmf_del_venc_channel(kvm_venc.mmf_venc_chn);
+ kvm_venc.enc_h264_init = 0;
+ debug("[kvmv]mmf venc push failed!\n");
+ // rtmp->unlock();
+ return -1;
+ }
+ // log::info("pop(): %d \r\n", (int)(time::time_ms() - start_time));
+ ret = h264_stream_dump(ret_stream, &_stream);
+ mmf_venc_free(kvm_venc.mmf_venc_chn);
+ // log::info("dump(): %d \r\n", (int)(time::time_ms() - start_time));
+
+ // debug("[kvmv]_stream.data[0][4] = %d;\n", _stream.data[0][4]);
+ debug("[kvmv]Frame size = %d;\n", ret_stream->img_data_size);
+
+ if(ret == IMG_H264_TYPE_IF){
+ debug("[kvmv]================ GOP = %d ================\n", kvm_venc.kvm_venc_cfg.gop);
+ debug("[kvmv]SPS; PPS; I-Frame, I-Frame size = %d\n", ret_stream->img_data_size);
+ P_Frame_Count = 0;
+
+ } else if(ret == IMG_H264_TYPE_PF){
+ debug("[kvmv]P-Frame size = %d, P-count = %d\n", ret_stream->img_data_size, P_Frame_Count);
+ P_Frame_Count++;
+ }
+ debug("[kvmv]dump ret = %d\n", ret);
+ return ret;
+}
+
+void kvmv_init(uint8_t _debug_info_en)
+{
+ pthread_t thread;
+ if(_debug_info_en == 0) debug_en = 0;
+ else debug_en = 1;
+
+ // debug("[kvmv]kvmv_init - 1\r\n");
+
+ cam->hmirror(1);
+ cam->vflip(1);
+ cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP);
+ 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");
+
+ if(kvmv_cfg.thread_is_running == 1){
+ debug("[kvmv]thread is running!\r\n");
+ } else {
+ if (0 != pthread_create(&thread, NULL, vi_subsystem_detection, NULL)) {
+ debug("[kvmv]create thread failed!\r\n");
+ // return -1;
+ }
+ }
+ // debug("[kvmv]kvmv_init - 3\r\n");
+}
+
+uint8_t check_kvmv(uint8_t _try_num)
+{
+ if(kvmv_cfg.hdmi_cable_state == 0){
+ debug("[kvmv]HDMI Cable not exist!\n");
+ return 0;
+ }
+ if(_try_num >= KVMV_MAX_TRY_NUM){
+ debug("[kvmv]try_num >= KVMV_MAX_TRY_NUM!\n");
+ return 0;
+ }
+ // if(sync_vi_res() != 0){
+ if(kvmv_cfg.reopen_cam_flag == 1){
+ // vi size changed
+ kvmv_cfg.reopen_cam_flag = 0;
+ // cam->open(kvmv_cfg.vpss_width, kvmv_cfg.vpss_height, image::FMT_YVU420SP, 3);
+ cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP);
+ debug("[kvmv]vi size changed, try again\n");
+ return 1;
+ }
+ debug("[kvmv]just try again\n");
+ return 1;
+}
+
+
+/**********************************************************************************
+ * @name kvmv_read_img
+ * @author Sipeed BuGu
+ * @date 2024/10/25
+ * @version R1.0
+ * @brief Acquire the encoded image with auto init
+ * @param _width @input: Output image width
+ * @param _height @input: Output image height
+ * @param _type @input: Encode type
+ * @param _qlty @input: MJPEG: (50-100) | H264: (500-10000)
+ * @param _pp_kvm_data @output: Encode data
+ * @param _p_kvmv_data_size @output: Encode data size
+ * @return
+ -4: Modifying image resolution, please wait
+ -3: img buffer full
+ -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)
+ 3: Acquire H264 encoded images(I)
+ 4: Acquire H264 encoded images(P)
+ **********************************************************************************/
+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)
+{
+ // uint64_t __attribute__((unused)) start_time = time::time_ms();
+ if (kvmv_cfg.vi_detect_state == 1){
+ return -4;
+ }
+ uint8_t try_num = 0;
+ do {
+ if(kvmv_cfg.vpss_width != _width || kvmv_cfg.vpss_height != _height){
+ if(_width == 0 || _height == 0){
+ // Follow the HDMI output
+ kvmv_cfg.vpss_width = kvmv_cfg.vi_width;
+ kvmv_cfg.vpss_height = kvmv_cfg.vi_height;
+
+ if(kvmv_cfg.Auto_res == 0){
+ kvmv_cfg.Auto_res = 1;
+ cam->set_resolution(kvmv_cfg.vpss_width, kvmv_cfg.vpss_height);
+ kvmv_cfg.reinit_flag = 1;
+ }
+
+ } else {
+ kvmv_cfg.Auto_res = 0;
+ // Set the output
+ kvmv_cfg.vpss_width = _width;
+ kvmv_cfg.vpss_height = _height;
+
+ cam->set_resolution(kvmv_cfg.vpss_width, kvmv_cfg.vpss_height);
+ kvmv_cfg.reinit_flag = 1;
+ }
+ }
+
+ //
+ if (kvmv_cfg.reinit_flag == 1) {
+ cam->hmirror(1);
+ cam->vflip(1);
+ kvmv_cfg.reinit_flag = 0;
+ }
+ // 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(img != NULL){
+ if(kvmv_cfg.cam_state == 0) {
+ kvmv_cfg.cam_state = 1;
+ kvmv_cfg.hdmi_cable_state = 1;
+ system("echo 1 > /kvmapp/kvm/state");
+ }
+ } else {
+ if(kvmv_cfg.cam_state == 1) {
+ kvmv_cfg.cam_state = 0;
+ system("echo 0 > /kvmapp/kvm/state");
+ }
+ delete img;
+ debug("[kvmv]can`t get img...\n");
+ continue;
+ }
+ // debug("[kvmv]cheak img null?: %d \r\n", (int)(time::time_ms() - start_time));
+
+ // img exist
+ // Encode
+ if(_type == VENC_H264 && kvmv_cfg.venc_type != _type){
+ kvm_venc.enc_h264_init = 1;
+ // debug("[kvmv] change to h264\n");
+ }
+ kvmv_cfg.venc_type = _type;
+
+ if(kvmv_cfg.venc_type == VENC_MJPEG){
+ image::Image *jpg = img->to_jpeg(maxmin_data(99, 51, (int)_qlty));
+ kvmv_data_t* p_kvmv_data = get_save_buffer();
+ if(p_kvmv_data == NULL){
+ // buffer full
+ delete jpg;
+ delete img;
+ debug("[kvmv]jpg buffer full\n");
+ *_pp_kvm_data = NULL;
+ return IMG_BUFFER_FULL;
+ }
+ jpg_dump(p_kvmv_data, jpg);
+ delete jpg;
+ delete img;
+ *_pp_kvm_data = p_kvmv_data->p_img_data;
+ *_p_kvmv_data_size = p_kvmv_data->img_data_size;
+ return IMG_MJPEG_TYPE;
+ } else if (kvmv_cfg.venc_type == VENC_H264){
+ int ret;
+ kvmv_data_t* p_kvmv_data = get_save_buffer();
+ if(p_kvmv_data == NULL){
+ // buffer full
+ delete img;
+ *_pp_kvm_data = NULL;
+ return IMG_BUFFER_FULL;
+ }
+ // debug("[kvmv]get_save_buffer: %d \r\n", (int)(time::time_ms() - start_time));
+ ret = raw_to_h264(img, p_kvmv_data, maxmin_data(10000, 500, (int)_qlty));
+ // debug("[kvmv]venc raw_to_h264: %d \r\n", (int)(time::time_ms() - start_time));
+ delete img;
+ *_pp_kvm_data = p_kvmv_data->p_img_data;
+ *_p_kvmv_data_size = p_kvmv_data->img_data_size;
+ return ret;
+ }
+ } while (check_kvmv(try_num++));
+ // debug("[kvmv]return: %d \r\n", (int)(time::time_ms() - start_time));
+ *_pp_kvm_data = NULL;
+ 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");
+ for(int i = 0; i < kvmv_data_buffer_size; i++){
+ if(*_pp_kvm_data == kvmv_data_buffer[i].p_img_data){
+ // debug("[kvmv]free buffer : %d\n", *_pp_kvm_data);
+ if (*_pp_kvm_data != NULL){
+ // debug("[kvmv]free_kvmv_data - 2\r\n");
+ free(*_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;
+ }
+ }
+ }
+ return IMG_NOT_EXIST;
+}
+
+void free_all_kvmv_data()
+{
+ for(int i = 0; i <= kvmv_data_buffer_size; i++){
+ if(kvmv_data_buffer[i].p_img_data != NULL){
+ free(kvmv_data_buffer[i].p_img_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()
+{
+ kvmv_cfg.try_exit_thread = 1;
+ cam->close();
+ mmf_deinit();
+ free_all_kvmv_data();
+}
+
+uint8_t kvmv_hdmi_control(uint8_t _en)
+{
+ if(kvmv_cfg.hw_version == 0){
+
+ FILE *fp;
+ uint8_t RW_Data[2];
+ if(access("/etc/kvm/hw", F_OK) == 0){
+ fp = fopen("/etc/kvm/hw", "r");
+ fread(RW_Data, sizeof(char), 1, fp);
+ fclose(fp);
+ switch(RW_Data[0]){
+ case 'a':
+ case 'b':
+ case 'p':
+ kvmv_cfg.hw_version = RW_Data[0];
+ break;
+ default :
+ kvmv_cfg.hw_version = 'a';
+ }
+ }
+ }
+ if(kvmv_cfg.hw_version != 'p'){
+ debug("[kvmv]Hardware not support!\n");
+ return -1;
+ }
+ if(access("/sys/class/gpio/gpio451/value", F_OK) != 0){
+ system("echo 451 > /sys/class/gpio/export");
+ system("echo out > /sys/class/gpio/gpio451/direction");
+ }
+ if(_en == 0){
+ kvmv_cfg.hdmi_stop_flag = 1;
+ while(kvmv_cfg.hdmi_reading_flag == 1) time::sleep_ms(10);
+ system("echo 0 > /sys/class/gpio/gpio451/value");
+ return 0;
+ } else {
+ kvmv_cfg.hdmi_stop_flag = 0;
+ system("echo 1 > /sys/class/gpio/gpio451/value");
+ return 0;
+ }
+ return -1;
+}
diff --git a/vision/components/kvm_mmf/CMakeLists.txt b/vision/components/kvm_mmf/CMakeLists.txt
new file mode 100644
index 0000000..a0e5813
--- /dev/null
+++ b/vision/components/kvm_mmf/CMakeLists.txt
@@ -0,0 +1,200 @@
+
+list(APPEND ADD_REQUIREMENTS basic ini)
+list(APPEND ADD_INCLUDE "include")
+
+# sophgo-middleware
+set(middleware_version_str "${CONFIG_SOPHGO_MIDDLEWARE_VERSION_MAJOR}.${CONFIG_SOPHGO_MIDDLEWARE_VERSION_MINOR}.${CONFIG_SOPHGO_MIDDLEWARE_VERSION_PATCH}")
+set(middleware_unzip_path "${DL_EXTRACTED_PATH}/sophgo-middleware")
+set(middleware_src_path "${middleware_unzip_path}/sophgo-middleware-${middleware_version_str}")
+
+
+set(source_dir "src")
+append_srcs_dir(ADD_SRCS "${source_dir}")
+
+# middleware
+set(middleware_include_dir .
+ ${middleware_src_path}/v2/component/panel/${CONFIG_SOPHGO_MIDDLEWARE_CHIP}
+ ${middleware_src_path}/v2/include
+ ${middleware_src_path}/v2/include/isp/${CONFIG_SOPHGO_MIDDLEWARE_CHIP}
+ ${middleware_src_path}/v2/sample/common
+ ${middleware_src_path}/v2/uapi
+ ${middleware_src_path}/v2/3rdparty/inih
+ ${middleware_src_path}/v2/modules/ive/include/
+)
+list(APPEND ADD_INCLUDE ${middleware_include_dir})
+set_property(SOURCE ${middleware_include_dir} PROPERTY GENERATED 1)
+
+append_srcs_dir(middleware_src_dir ${middleware_src_path}/v2/sample/common
+ ${middleware_src_path}/v2/component/isp/sensor/sg200x/lontium_lt6911
+ )
+list(APPEND ADD_SRCS ${middleware_src_dir}
+ "${source_dir}/kvm_mmf.cpp")
+set_property(SOURCE ${middleware_src_dir} PROPERTY GENERATED 1)
+
+set(middleware_static_lib_file "")
+ list(APPEND ADD_STATIC_LIB ${middleware_static_lib_file})
+set_property(SOURCE ${middleware_static_lib_file} PROPERTY GENERATED 1)
+
+if(${middleware_version_str} VERSION_EQUAL "0.0.4")
+ set(mmf_lib_dir ${middleware_src_path}/v2/lib)
+else()
+ set(mmf_lib_dir ${middleware_src_path}/v2/${CONFIG_SOPHGO_MIDDLEWARE_CHIP}/lib_${CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY}_riscv64)
+endif()
+if(CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY STREQUAL "musl")
+ if(${middleware_version_str} VERSION_EQUAL "0.0.4")
+ set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_ive.so
+ ${mmf_lib_dir}/libcvi_bin.so
+ ${mmf_lib_dir}/libaaccomm2.so
+ ${mmf_lib_dir}/libaacdec2.so
+ ${mmf_lib_dir}/libaacenc2.so
+ ${mmf_lib_dir}/libaacsbrdec2.so
+ ${mmf_lib_dir}/libaacsbrenc2.so
+ ${mmf_lib_dir}/libae.so
+ ${mmf_lib_dir}/libaf.so
+ ${mmf_lib_dir}/libawb.so
+ ${mmf_lib_dir}/libcvi_audio.so
+ ${mmf_lib_dir}/libcvi_bin_isp.so
+ ${mmf_lib_dir}/libcvi_bin.so
+ ${mmf_lib_dir}/libcvi_ispd2.so
+ ${mmf_lib_dir}/libcvi_RES1.so
+ ${mmf_lib_dir}/libcvi_ssp.so
+ ${mmf_lib_dir}/libcvi_VoiceEngine.so
+ ${mmf_lib_dir}/libcvi_vqe.so
+ ${mmf_lib_dir}/libdnvqe.so
+ ${mmf_lib_dir}/libisp_algo.so
+ ${mmf_lib_dir}/libisp.so
+ ${mmf_lib_dir}/libmipi_tx.so
+ ${mmf_lib_dir}/libmisc.so
+ ${mmf_lib_dir}/libosdc.so
+ ${mmf_lib_dir}/libraw_dump.so
+ ${mmf_lib_dir}/libsys.so
+ ${mmf_lib_dir}/libvdec.so
+ ${mmf_lib_dir}/libvenc.so
+ ${mmf_lib_dir}/libvpu.so
+ ${mmf_lib_dir}/libjson-c.so.5
+ ${mmf_lib_dir}/libtinyalsa.so
+ ${mmf_lib_dir}/3rd/libcli.so
+ ${mmf_lib_dir}/3rd/libini.so)
+ else()
+ set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_ive.so
+ ${mmf_lib_dir}/libcvi_bin.so
+ ${mmf_lib_dir}/libaaccomm2.so
+ ${mmf_lib_dir}/libaacdec2.so
+ ${mmf_lib_dir}/libaacenc2.so
+ ${mmf_lib_dir}/libaacsbrdec2.so
+ ${mmf_lib_dir}/libaacsbrenc2.so
+ ${mmf_lib_dir}/libae.so
+ ${mmf_lib_dir}/libaf.so
+ ${mmf_lib_dir}/libawb.so
+ ${mmf_lib_dir}/libcipher.so
+ ${mmf_lib_dir}/libcvi_audio.so
+ ${mmf_lib_dir}/libcvi_bin_isp.so
+ ${mmf_lib_dir}/libcvi_bin.so
+ ${mmf_lib_dir}/libcvi_ispd2.so
+ ${mmf_lib_dir}/libcvi_RES1.so
+ ${mmf_lib_dir}/libcvi_ssp.so
+ ${mmf_lib_dir}/libcvi_VoiceEngine.so
+ ${mmf_lib_dir}/libcvi_vqe.so
+ ${mmf_lib_dir}/libdnvqe.so
+ ${mmf_lib_dir}/libisp_algo.so
+ ${mmf_lib_dir}/libisp.so
+ ${mmf_lib_dir}/libmipi_tx.so
+ ${mmf_lib_dir}/libmisc.so
+ ${mmf_lib_dir}/libosdc.so
+ ${mmf_lib_dir}/libraw_dump.so
+ # ${mmf_lib_dir}/libraw_replay.so
+ # ${mmf_lib_dir}/libsns_full.so
+ # ${mmf_lib_dir}/libsns_gc4653.so
+ ${mmf_lib_dir}/libsys.so
+ ${mmf_lib_dir}/libvdec.so
+ ${mmf_lib_dir}/libvenc.so
+ ${mmf_lib_dir}/libvpu.so
+ ${mmf_lib_dir}/libz.so
+ ${mmf_lib_dir}/libtinyalsa.so
+ ${mmf_lib_dir}/3rd/libcli.so
+ ${mmf_lib_dir}/3rd/libini.so
+ ${mmf_lib_dir}/3rd/libjson-c.so.5)
+ endif()
+elseif(CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY STREQUAL "glibc")
+ set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_vb.so
+ ${mmf_lib_dir}/libcvi_sys.so
+ ${mmf_lib_dir}/libcvi_debug.so
+ )
+else()
+ message(FATAL_ERROR "No sophgo middleware c library support, please check menuoconfig to select c library")
+endif()
+list(APPEND ADD_DYNAMIC_LIB ${middleware_dynamic_lib_file})
+set_property(SOURCE ${middleware_dynamic_lib_file} PROPERTY GENERATED 1)
+
+list(APPEND ADD_DEFINITIONS_PRIVATE -DSENSOR_LONTIUM_LT6911
+ -DSENSOR2_TYPE=LONTIUM_LT6911_2M_60FPS_8BIT)
+
+
+register_component(DYNAMIC)
+
+
+
+# Config enable component2 or not in Kconfig
+################# Add include #################
+# list(APPEND ADD_INCLUDE "include"
+# )
+# list(APPEND ADD_PRIVATE_INCLUDE "include_private")
+###############################################
+
+############## Add source files ###############
+# list(APPEND ADD_SRCS "src/lib2.c"
+# )
+# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c")
+# FILE(GLOB EXTRA_SRC "src/*.c")
+# list(APPEND ADD_SRCS ${EXTRA_SRC})
+# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS
+# append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS
+# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c")
+# set(ADD_ASM_SRCS "src/asm.S")
+# list(APPEND ADD_SRCS ${ADD_ASM_SRCS})
+# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language
+# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB")
+###############################################
+
+
+###### Add required/dependent components ######
+# list(APPEND ADD_REQUIREMENTS basic ini)
+###############################################
+
+###### Add link search path for requirements/libs ######
+# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib")
+# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here
+# set (OpenCV_DIR opencv/lib/cmake/opencv4)
+# find_package(OpenCV REQUIRED)
+###############################################
+
+############ Add static libs ##################
+# list(APPEND ADD_STATIC_LIB "lib/libtest.a")
+###############################################
+
+############ Add dynamic libs ##################
+# list(APPEND ADD_DYNAMIC_LIB "lib/arch/v831/libmaix_nn.so"
+# "lib/arch/v831/libmaix_cam.so"
+# )
+###############################################
+
+#### Add compile option for this component ####
+#### Just for this component, won't affect other
+#### modules, including component that depend
+#### on this component
+# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1)
+
+#### Add compile option for this component
+#### and components denpend on this component
+# list(APPEND ADD_DEFINITIONS -DAAAAA222=1
+# -DAAAAA333=1)
+###############################################
+
+############ Add static libs ##################
+#### Update parent's variables like CMAKE_C_LINK_FLAGS
+# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE)
+###############################################
+
+# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib
+# register_component()
+
diff --git a/vision/components/kvm_mmf/Kconfig b/vision/components/kvm_mmf/Kconfig
new file mode 100644
index 0000000..b4b5a07
--- /dev/null
+++ b/vision/components/kvm_mmf/Kconfig
@@ -0,0 +1,32 @@
+# sophgo middleware version major minor patch
+menu "sophgo middleware version"
+config SOPHGO_MIDDLEWARE_CHIP
+ string "sophgo chip"
+ default "cv181x"
+ help
+ sophgo chip. cv180x or cv181x
+
+config SOPHGO_MIDDLEWARE_C_LIBRARY
+ string "sophgo c standard library"
+ default "musl"
+ help
+ sophgo c standard library. glibc or musl
+
+config SOPHGO_MIDDLEWARE_VERSION_MAJOR
+ int "sophgo middleware package major version"
+ default 0
+ help
+ sophgo middleware package major version, 0 means auto select according to board
+
+config SOPHGO_MIDDLEWARE_VERSION_MINOR
+ int "sophgo middleware package minor version"
+ default 0
+ help
+ sophgo middleware package minor version
+
+config SOPHGO_MIDDLEWARE_VERSION_PATCH
+ int "sophgo middleware package patch version"
+ default 4
+ help
+ sophgo middleware package patch version
+endmenu
diff --git a/vision/components/kvm_mmf/include/kvm_mmf.hpp b/vision/components/kvm_mmf/include/kvm_mmf.hpp
new file mode 100644
index 0000000..b98dd12
--- /dev/null
+++ b/vision/components/kvm_mmf/include/kvm_mmf.hpp
@@ -0,0 +1,68 @@
+#ifndef __KVM_MMF_HPP__
+#define __KVM_MMF_HPP__
+
+#include "stdint.h"
+
+typedef struct {
+ uint8_t *data[8];
+ int data_size[8];
+ int count;
+} mmf_stream_t;
+
+typedef struct {
+ uint8_t type; // 0, jpg; 1, h265; 2, h264
+ int w;
+ int h;
+ int fmt;
+ uint8_t jpg_quality; // jpeg
+ int gop; // h264
+ int intput_fps; // h264
+ int output_fps; // h264
+ int bitrate; // h264
+} mmf_venc_cfg_t;
+
+// init sys
+int mmf_init(void);
+int mmf_deinit(void);
+int mmf_try_deinit(bool force);
+bool mmf_is_init(void);
+
+// manage vi channels(vi->vpssgroup->vpss->frame)
+int mmf_get_vi_unused_channel(void);
+int mmf_vi_init(void);
+int mmf_vi_deinit(void);
+int mmf_add_vi_channel_with_enc(int ch, int width, int height, int format);
+int mmf_add_vi_channel(int ch, int width, int height, int format);
+int mmf_del_vi_channel(int ch);
+int mmf_del_vi_channel_all(void);
+int mmf_reset_vi_channel(int ch, int width, int height, int format);
+bool mmf_vi_chn_is_open(int ch);
+int mmf_vi_aligned_width(int ch);
+void mmf_set_vi_hmirror(int ch, bool en);
+void mmf_set_vi_vflip(int ch, bool en);
+void mmf_get_vi_hmirror(int ch, bool *en);
+void mmf_get_vi_vflip(int ch, bool *en);
+
+// get vi frame
+int mmf_vi_frame_pop(int ch, void **data, int *len, int *width, int *height, int *format);
+void mmf_vi_frame_free(int ch);
+
+// invert format
+int mmf_invert_format_to_maix(int mmf_format);
+int mmf_invert_format_to_mmf(int maix_format);
+
+// venc
+int mmf_enc_jpg_init(int ch, int w, int h, int format, int quality);
+int mmf_enc_jpg_deinit(int ch);
+int mmf_enc_jpg_push(int ch, uint8_t *data, int w, int h, int format);
+int mmf_enc_jpg_push_with_quality(int ch, uint8_t *data, int w, int h, int format, int quality);
+int mmf_enc_jpg_pop(int ch, uint8_t **data, int *size);
+int mmf_enc_jpg_free(int ch);
+int mmf_add_venc_channel(int ch, mmf_venc_cfg_t *cfg);
+int mmf_del_venc_channel(int ch);
+int mmf_del_venc_channel_all();
+int mmf_venc_push(int ch, uint8_t *data, int w, int h, int format);
+int mmf_venc_pop(int ch, mmf_stream_t *stream);
+int mmf_venc_free(int ch);
+
+#endif // __KVM_MMF_HPP__
diff --git a/vision/components/kvm_mmf/src/kvm_mmf.cpp b/vision/components/kvm_mmf/src/kvm_mmf.cpp
new file mode 100644
index 0000000..054d812
--- /dev/null
+++ b/vision/components/kvm_mmf/src/kvm_mmf.cpp
@@ -0,0 +1,2352 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "math.h"
+#include
+
+#include /* low-level i/o */
+#include "cvi_buffer.h"
+#include "cvi_ae_comm.h"
+#include "cvi_awb_comm.h"
+#include "cvi_comm_isp.h"
+#include "cvi_comm_sns.h"
+#include "cvi_ae.h"
+#include "cvi_awb.h"
+#include "cvi_isp.h"
+#include "cvi_sns_ctrl.h"
+#include "cvi_ive.h"
+#include "cvi_sys.h"
+#include "sample_comm.h"
+#include "kvm_mmf.hpp"
+#include "global_config.h"
+
+#define MMF_VI_MAX_CHN 2 // manually limit the max channel number of vi
+#define MMF_RGN_MAX_NUM 16
+#define MMF_VENC_MAX_CHN 4
+
+#define MMF_VB_VI_ID 0
+
+#if VPSS_MAX_PHY_CHN_NUM < MMF_VI_MAX_CHN
+#error "VPSS_MAX_PHY_CHN_NUM < MMF_VI_MAX_CHN"
+#endif
+
+typedef struct {
+ uint8_t ch;
+ SIZE_S input;
+ SIZE_S output;
+ int fps;
+ uint8_t depth;
+ uint8_t fit; // fit = 0, width to new width, height to new height, may be stretch
+ // fit = 1, keep aspect ratio, fill blank area with black color
+ // fit = 2, keep aspect ratio, crop image to fit new size
+ int input_fmt;
+ int output_fmt;
+} vpss_info_t;
+
+typedef struct {
+ uint8_t ch;
+ uint8_t type; // 0, jpg; 1, h265; 2, h264
+ uint8_t is_inited;
+ uint8_t is_used;
+ uint8_t is_running;
+ uint8_t use_vpss;
+ VIDEO_FRAME_INFO_S *capture_frame;
+ VENC_STREAM_S capture_stream;
+ mmf_venc_cfg_t cfg;
+ vpss_info_t vpss;
+ uint32_t pool_id;
+} venc_info_t;
+
+typedef enum {
+ MMF_MOD_VO,
+ MMF_MOD_VI,
+ MMF_MOD_VPSS,
+ MMF_MOD_VENC,
+ MMF_MOD_VDEC,
+ MMF_MOD_REGION,
+} mmf_mod_type_t;
+
+typedef struct {
+ char name[15];
+ uint8_t is_used;
+ mmf_mod_type_t mod;
+ uint32_t pool_id;
+ uint32_t size;
+ uint32_t max_num;
+} mmf_vb_pool_t;
+
+typedef struct {
+ int mmf_used_cnt;
+ bool vi_is_inited;
+ bool vi_chn_is_inited[MMF_VI_MAX_CHN];
+ int vi_chn_pool_id[MMF_VI_MAX_CHN];
+ SIZE_S vi_size;
+ VIDEO_FRAME_INFO_S vi_frame[MMF_VI_MAX_CHN];
+ VB_CONFIG_S vb_conf;
+
+ int ive_is_init;
+ IVE_HANDLE ive_handle;
+ IVE_IMAGE_S ive_rgb2yuv_rgb_img;
+ IVE_IMAGE_S ive_rgb2yuv_yuv_img;
+ int ive_rgb2yuv_w;
+ int ive_rgb2yuv_h;
+
+ bool rgn_is_init[MMF_RGN_MAX_NUM];
+ bool rgn_is_bind[MMF_RGN_MAX_NUM];
+ RGN_TYPE_E rgn_type[MMF_RGN_MAX_NUM];
+ int rgn_id[MMF_RGN_MAX_NUM];
+ MOD_ID_E rgn_mod_id[MMF_RGN_MAX_NUM];
+ CVI_S32 rgn_dev_id[MMF_RGN_MAX_NUM];
+ CVI_S32 rgn_chn_id[MMF_RGN_MAX_NUM];
+ uint8_t* rgn_canvas_data[MMF_RGN_MAX_NUM];
+ int rgn_canvas_w[MMF_RGN_MAX_NUM];
+ int rgn_canvas_h[MMF_RGN_MAX_NUM];
+ int rgn_canvas_format[MMF_RGN_MAX_NUM];
+
+ int enc_jpg_is_init;
+ VENC_STREAM_S enc_jpeg_frame;
+ int enc_jpg_frame_w;
+ int enc_jpg_frame_h;
+ int enc_jpg_frame_fmt;
+ int enc_jpg_running;
+ int enc_jpg_quality;
+ VIDEO_FRAME_INFO_S *enc_jpg_frame;
+ int enc_jpg_input_pool_id;
+ int enc_jpg_output_pool_id;
+
+ int vb_of_vi_is_config : 1;
+ int vb_of_private_is_config : 1;
+ int vb_size_of_vi;
+ int vb_count_of_vi;
+ int vb_size_of_private;
+ int vb_count_of_private;
+
+
+ SAMPLE_SNS_TYPE_E sensor_type;
+
+ venc_info_t venc[MMF_VENC_MAX_CHN];
+ uint8_t h265_or_h264_is_used;
+
+ mmf_vb_pool_t vb_pool[VB_MAX_COMM_POOLS];
+} priv_t;
+
+typedef struct {
+ int enc_jpg_enable : 1;
+ bool vi_hmirror[MMF_VI_MAX_CHN];
+ bool vi_vflip[MMF_VI_MAX_CHN];
+} g_priv_t;
+
+static priv_t priv;
+static g_priv_t g_priv;
+
+#define MODULE_NAME "soph_vi"
+
+static int _is_module_in_use(const char *module_name) {
+ FILE *fp;
+ char buffer[256];
+
+ fp = fopen("/proc/modules", "r");
+ if (fp == NULL) {
+ perror("fopen");
+ return -1;
+ }
+
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ char mod_name[256];
+ int usage_count;
+
+ sscanf(buffer, "%255s %*s %d", mod_name, &usage_count);
+
+ if (strcmp(mod_name, module_name) == 0) {
+ fclose(fp);
+ return usage_count > 0;
+ }
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+static int reinit_soph_vb(void)
+{
+ printf("mmf insmod..\r\n");
+ system("rmmod soph_ive soph_vc_driver soph_rgn soph_dwa soph_vpss soph_vi soph_snsr_i2c soph_mipi_rx soph_fast_image soph_rtos_cmdqu soph_base");
+ // system("insmod /mnt/system/ko/soph_sys.ko");
+ system("insmod /mnt/system/ko/soph_base.ko");
+ system("insmod /mnt/system/ko/soph_rtos_cmdqu.ko");
+ system("insmod /mnt/system/ko/soph_fast_image.ko");
+ system("insmod /mnt/system/ko/soph_mipi_rx.ko");
+ system("insmod /mnt/system/ko/soph_snsr_i2c.ko");
+ system("insmod /mnt/system/ko/soph_vi.ko");
+ system("insmod /mnt/system/ko/soph_vpss.ko");
+ system("insmod /mnt/system/ko/soph_dwa.ko");
+ system("insmod /mnt/system/ko/soph_rgn.ko");
+ system("insmod /mnt/system/ko/soph_vc_driver.ko");
+ system("insmod /mnt/system/ko/soph_ive.ko");
+
+ return 0;
+}
+
+static int _get_vb_pool_cnt(void)
+{
+ FILE *file;
+ char line[1024];
+ int poolIdCount = 0;
+
+ file = fopen("/proc/cvitek/vb", "r");
+ if (file == NULL) {
+ perror("can not open /proc/cvitek/vb");
+ return 0;
+ }
+
+ while (fgets(line, sizeof(line), file)) {
+ if (strstr(line, "PoolId(") != NULL) {
+ poolIdCount++;
+ }
+ }
+
+ fclose(file);
+ return poolIdCount;
+}
+
+static int _create_vb_pool(char *name, mmf_mod_type_t mod, uint32_t size, uint32_t max_num)
+{
+ uint32_t pool_id = -1;
+ VB_POOL_CONFIG_S stVbPoolCfg;
+ stVbPoolCfg.u32BlkCnt = max_num;
+ stVbPoolCfg.u32BlkSize = size;
+ stVbPoolCfg.enRemapMode = VB_REMAP_MODE_CACHED;
+
+ if (max_num == 0 || size == 0) {
+ return -1;
+ }
+
+ pool_id = CVI_VB_CreatePool(&stVbPoolCfg);
+ if (pool_id == VB_INVALID_POOLID || pool_id >= VB_MAX_COMM_POOLS) {
+ return -2;
+ }
+
+ mmf_vb_pool_t *info = (mmf_vb_pool_t *)&priv.vb_pool[pool_id];
+ info->pool_id = pool_id;
+ strncpy(info->name, name, sizeof(info->name));
+ info->mod = mod;
+ info->size = size;
+ info->max_num = max_num;
+ info->is_used = 1;
+
+ return pool_id;
+}
+
+static int _destroy_vb_pool(uint32_t pool_id)
+{
+ CVI_S32 s32Ret;
+ mmf_vb_pool_t *info = (mmf_vb_pool_t *)&priv.vb_pool[pool_id];
+ if (info->is_used) {
+ s32Ret = CVI_VB_DestroyPool(pool_id);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VB_DestroyPool : %d fail!\n", pool_id);
+ return -1;
+ }
+ memset(info, 0, sizeof(mmf_vb_pool_t));
+ }
+
+ return 0;
+}
+
+__attribute__((unused)) static void _list_vb_pool(void)
+{
+ printf("====== VB POOL =======\r\n");
+ for (int pool_id = 0; pool_id < VB_MAX_COMM_POOLS; pool_id ++) {
+ mmf_vb_pool_t *info = (mmf_vb_pool_t *)&priv.vb_pool[pool_id];
+ if (info->is_used) {
+ printf("[%d] name:%s size:%d num:%d\r\n", pool_id, info->name, info->size, info->max_num);
+ }
+ }
+ printf("\r\n");
+}
+
+static SAMPLE_VI_CONFIG_S g_stViConfig;
+static SAMPLE_INI_CFG_S g_stIniCfg;
+static CVI_S32 _mmf_vpss_deinit_new(VPSS_GRP VpssGrp);
+
+static int _free_leak_memory_of_ion(void)
+{
+ #define MAX_LINE_LENGTH 256
+ FILE *fp;
+ char line[MAX_LINE_LENGTH];
+ char alloc_buf_size_str[20], phy_addr_str[20], buffer_name[20];
+ int alloc_buf_size;
+ uint64_t phy_addr;
+
+ fp = fopen("/sys/kernel/debug/ion/cvi_carveout_heap_dump/summary", "r");
+ if (fp == NULL) {
+ fprintf(stderr, "Error opening file\n");
+ return 1;
+ }
+
+ while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
+ if (sscanf(line, "%*d %s %s %*d %s", alloc_buf_size_str, phy_addr_str, buffer_name) == 3) {
+ printf("[ION] %s %s %s\r\n", alloc_buf_size_str, phy_addr_str, buffer_name);
+ // FIXME: release jpeg_ion
+ if (strcmp(buffer_name, "VI_DMA_BUF")
+ && strcmp(buffer_name, "ISP_SHARED_BUFFER_0"))
+ continue;
+ struct sys_ion_data_new ion_data = {
+ .cached = 1,
+ .dmabuf_fd = (uint32_t)-1,
+ };
+
+ alloc_buf_size = atoi(alloc_buf_size_str);
+ phy_addr = (unsigned int)strtol(phy_addr_str, NULL, 16);
+
+ ion_data.size = alloc_buf_size;
+ ion_data.addr_p = phy_addr;
+ memset(ion_data.name, 0, sizeof(ion_data.name));
+ strcpy((char *)ion_data.name, buffer_name);
+
+ printf("alloc_buf_size(%s): %d, phy_addr(%s): %#lx, buffer_name: %s\n",
+ alloc_buf_size_str, alloc_buf_size, phy_addr_str, phy_addr, buffer_name);
+
+ printf("ion_data.size:%d, ion_data.addr_p:%#x, ion_data.name:%s\r\n", ion_data.size, (int)ion_data.addr_p, ion_data.name);
+
+ int res = ionFree(&ion_data);
+ if (res) {
+ printf("ionFree failed! res:%#x\r\n", res);
+ mmf_deinit();
+ return -1;
+ }
+
+ }
+ }
+
+ fclose(fp);
+
+ return 0;
+}
+
+static int _free_leak_memory_of_vb(void) {
+ #define MAX_LINE_LENGTH 256
+ FILE *fp;
+ char line[MAX_LINE_LENGTH];
+ int pool_id = 0;
+ uint64_t phy_addr = 0;
+ int blk_cnt = 0;
+ int blk_size = 0;
+ int free_cnt = 0;
+
+ fp = fopen("/proc/cvitek/vb", "r");
+ if (fp == NULL) {
+ fprintf(stderr, "Error opening file\n");
+ return 1;
+ }
+
+ while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
+
+ if (strstr(line, "PoolId :")) {
+ sscanf(line, "%*s : %d", &pool_id);
+ } else if (strstr(line, "PhysAddr :")) {
+ sscanf(line, "%*s : %lx", &phy_addr);
+ } else if (strstr(line, "BlkSz :")) {
+ sscanf(line, "%*s : %d", &blk_size);
+ } else if (strstr(line, "BlkCnt : ")) {
+ sscanf(line, "%*s : %d", &blk_cnt);
+ } else if (strstr(line, "Free :")) {
+ sscanf(line, "%*s : %d", &free_cnt);
+
+ CVI_SYS_Exit();
+ CVI_VB_Exit();
+
+ if (free_cnt != blk_cnt) {
+ printf("relese PoolId: %d, PhysAddr: 0x%lx, BlkSize: %d, BlkCnt: %d Free: %d\n",
+ pool_id, phy_addr, blk_size, blk_cnt, free_cnt);
+ for (int i = 0; i < blk_cnt; i ++) {
+ uint64_t try_release_phy_addr = phy_addr + i * blk_size;
+ printf("try release poolid:%d phy:%#lx\r\n", pool_id, try_release_phy_addr);
+ VB_BLK blk = CVI_VB_PhysAddr2Handle(try_release_phy_addr);
+ if (0 != CVI_VB_ReleaseBlock(blk)) {
+ printf("release poolid:%d phy:%#lx failed!\r\n", pool_id, try_release_phy_addr);
+ }
+ }
+ }
+ }
+ }
+
+ fclose(fp);
+
+ return 0;
+}
+
+static inline CVI_VOID VENC_GetPicBufferConfig2(CVI_U32 u32Width, CVI_U32 u32Height,
+ PIXEL_FORMAT_E enPixelFormat, DATA_BITWIDTH_E enBitWidth, COMPRESS_MODE_E enCmpMode,
+ VB_CAL_CONFIG_S *pstVbCfg)
+{
+ CVI_U32 u32AlignWidth = ALIGN(u32Width, VENC_ALIGN_W);
+ CVI_U32 u32AlignHeight = u32Height;
+ CVI_U32 u32Align = VENC_ALIGN_W;
+
+ COMMON_GetPicBufferConfig(u32AlignWidth, u32AlignHeight, enPixelFormat,
+ enBitWidth, enCmpMode, u32Align, pstVbCfg);
+}
+
+static VIDEO_FRAME_INFO_S *_mmf_alloc_frame(int id, SIZE_S stSize, PIXEL_FORMAT_E enPixelFormat)
+{
+ VIDEO_FRAME_INFO_S *pstVideoFrame;
+ VIDEO_FRAME_S *pstVFrame;
+ VB_BLK blk;
+ VB_CAL_CONFIG_S stVbCfg;
+
+ pstVideoFrame = (VIDEO_FRAME_INFO_S *)calloc(sizeof(*pstVideoFrame), 1);
+ if (pstVideoFrame == NULL) {
+ SAMPLE_PRT("Failed to allocate VIDEO_FRAME_INFO_S\n");
+ return NULL;
+ }
+
+ memset(&stVbCfg, 0, sizeof(stVbCfg));
+ VENC_GetPicBufferConfig2(stSize.u32Width,
+ stSize.u32Height,
+ enPixelFormat,
+ DATA_BITWIDTH_8,
+ COMPRESS_MODE_NONE,
+ &stVbCfg);
+
+ pstVFrame = &pstVideoFrame->stVFrame;
+
+ pstVFrame->enCompressMode = COMPRESS_MODE_NONE;
+ pstVFrame->enPixelFormat = enPixelFormat;
+ pstVFrame->enVideoFormat = VIDEO_FORMAT_LINEAR;
+ pstVFrame->enColorGamut = COLOR_GAMUT_BT709;
+ pstVFrame->u32Width = stSize.u32Width;
+ pstVFrame->u32Height = stSize.u32Height;
+ pstVFrame->u32TimeRef = 0;
+ pstVFrame->u64PTS = 0;
+ pstVFrame->enDynamicRange = DYNAMIC_RANGE_SDR8;
+
+ blk = CVI_VB_GetBlock(id, stVbCfg.u32VBSize);
+ if (blk == VB_INVALID_HANDLE) {
+ SAMPLE_PRT("Can't acquire vb block. id: %d size:%d\n", id, stVbCfg.u32VBSize);
+ free(pstVideoFrame);
+ return NULL;
+ }
+
+ pstVideoFrame->u32PoolId = CVI_VB_Handle2PoolId(blk);
+ pstVFrame->u64PhyAddr[0] = CVI_VB_Handle2PhysAddr(blk);
+ pstVFrame->u32Stride[0] = stVbCfg.u32MainStride;
+ pstVFrame->u32Length[0] = stVbCfg.u32MainYSize;
+ pstVFrame->pu8VirAddr[0] = (CVI_U8 *)CVI_SYS_MmapCache(pstVFrame->u64PhyAddr[0], stVbCfg.u32VBSize);
+
+ if (stVbCfg.plane_num > 1) {
+ pstVFrame->u64PhyAddr[1] = ALIGN(pstVFrame->u64PhyAddr[0] + stVbCfg.u32MainYSize, stVbCfg.u16AddrAlign);
+ pstVFrame->u32Stride[1] = stVbCfg.u32CStride;
+ pstVFrame->u32Length[1] = stVbCfg.u32MainCSize;
+ pstVFrame->pu8VirAddr[1] = (CVI_U8 *)pstVFrame->pu8VirAddr[0] + pstVFrame->u32Length[0];
+ }
+
+ if (stVbCfg.plane_num > 2) {
+ pstVFrame->u64PhyAddr[2] = ALIGN(pstVFrame->u64PhyAddr[1] + stVbCfg.u32MainCSize, stVbCfg.u16AddrAlign);
+ pstVFrame->u32Stride[2] = stVbCfg.u32CStride;
+ pstVFrame->u32Length[2] = stVbCfg.u32MainCSize;
+ pstVFrame->pu8VirAddr[2] = (CVI_U8 *)pstVFrame->pu8VirAddr[1] + pstVFrame->u32Length[1];
+ }
+
+ // CVI_VENC_TRACE("phy addr(%#llx, %#llx, %#llx), Size %x\n", (long long)pstVFrame->u64PhyAddr[0]
+ // , (long long)pstVFrame->u64PhyAddr[1], (long long)pstVFrame->u64PhyAddr[2], stVbCfg.u32VBSize);
+ // CVI_VENC_TRACE("vir addr(%p, %p, %p), Size %x\n", pstVFrame->pu8VirAddr[0]
+ // , pstVFrame->pu8VirAddr[1], pstVFrame->pu8VirAddr[2], stVbCfg.u32MainSize);
+
+ return pstVideoFrame;
+}
+
+static CVI_S32 _mmf_free_frame(VIDEO_FRAME_INFO_S *pstVideoFrame)
+{
+ VIDEO_FRAME_S *pstVFrame = &pstVideoFrame->stVFrame;
+ VB_BLK blk;
+
+ if (pstVFrame->pu8VirAddr[0])
+ CVI_SYS_Munmap((CVI_VOID *)pstVFrame->pu8VirAddr[0], pstVFrame->u32Length[0]);
+ if (pstVFrame->pu8VirAddr[1])
+ CVI_SYS_Munmap((CVI_VOID *)pstVFrame->pu8VirAddr[1], pstVFrame->u32Length[1]);
+ if (pstVFrame->pu8VirAddr[2])
+ CVI_SYS_Munmap((CVI_VOID *)pstVFrame->pu8VirAddr[2], pstVFrame->u32Length[2]);
+
+ blk = CVI_VB_PhysAddr2Handle(pstVFrame->u64PhyAddr[0]);
+ if (blk != VB_INVALID_HANDLE) {
+ CVI_VB_ReleaseBlock(blk);
+ }
+
+ free(pstVideoFrame);
+
+ return CVI_SUCCESS;
+}
+
+static int cvi_ive_init(void)
+{
+ CVI_S32 s32Ret;
+ if (priv.ive_is_init)
+ return 0;
+
+ priv.ive_rgb2yuv_w = 640;
+ priv.ive_rgb2yuv_h = 480;
+ priv.ive_handle = CVI_IVE_CreateHandle();
+ if (priv.ive_handle == NULL) {
+ printf("CVI_IVE_CreateHandle failed!\n");
+ return -1;
+ }
+
+ s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img, IVE_IMAGE_TYPE_U8C3_PACKAGE, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("Create src image failed!\n");
+ CVI_IVE_DestroyHandle(priv.ive_handle);
+ return -1;
+ }
+
+ s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img, IVE_IMAGE_TYPE_YUV420SP, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("Create src image failed!\n");
+ CVI_IVE_DestroyHandle(priv.ive_handle);
+ return -1;
+ }
+
+ priv.ive_is_init = 1;
+ return 0;
+}
+
+static int cvi_ive_deinit(void)
+{
+ if (priv.ive_is_init == 0)
+ return 0;
+
+ CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img);
+ CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img);
+ CVI_IVE_DestroyHandle(priv.ive_handle);
+
+ priv.ive_is_init = 0;
+ return 0;
+}
+
+static int cvi_rgb2nv21(uint8_t *src, int input_w, int input_h)
+{
+ CVI_S32 s32Ret;
+
+ int width = ALIGN(input_w, DEFAULT_ALIGN);
+ int height = input_h;
+
+ if (width != priv.ive_rgb2yuv_w || height != priv.ive_rgb2yuv_h) {
+ CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img);
+ CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img);
+ priv.ive_rgb2yuv_w = width;
+ priv.ive_rgb2yuv_h = height;
+ printf("reinit rgb2nv21 buffer, buff w:%d h:%d\n", priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h);
+ s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img, IVE_IMAGE_TYPE_U8C3_PACKAGE, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("Create src image failed!\n");
+ return -1;
+ }
+
+ s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img, IVE_IMAGE_TYPE_YUV420SP, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("Create src image failed!\n");
+ return -1;
+ }
+ }
+
+ if (width != input_w) {
+ for (int h = 0; h < height; h++) {
+ memcpy((uint8_t *)priv.ive_rgb2yuv_rgb_img.u64VirAddr[0] + width * h * 3, (uint8_t *)src + input_w * h * 3, input_w * 3);
+ }
+ } else {
+ memcpy((uint8_t *)priv.ive_rgb2yuv_rgb_img.u64VirAddr[0], (uint8_t *)src, width * height * 3);
+ }
+
+ IVE_CSC_CTRL_S stCtrl;
+ stCtrl.enMode = IVE_CSC_MODE_VIDEO_BT601_RGB2YUV;
+ s32Ret = CVI_IVE_CSC(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img, &priv.ive_rgb2yuv_yuv_img, &stCtrl, 1);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("Run HW IVE CSC YUV2RGB failed!\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int _try_release_sys(void)
+{
+ CVI_S32 s32Ret = CVI_FAILURE;
+ SAMPLE_INI_CFG_S stIniCfg;
+ SAMPLE_VI_CONFIG_S stViConfig;
+ if (SAMPLE_COMM_VI_ParseIni(&stIniCfg)) {
+ SAMPLE_PRT("Parse complete\n");
+ return s32Ret;
+ }
+
+ priv.sensor_type = stIniCfg.enSnsType[0];
+
+ s32Ret = CVI_VI_SetDevNum(stIniCfg.devNum);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("VI_SetDevNum failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ s32Ret = SAMPLE_COMM_VI_IniToViCfg(&stIniCfg, &stViConfig);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("SAMPLE_COMM_VI_IniToViCfg failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ s32Ret = SAMPLE_COMM_VI_DestroyIsp(&stViConfig);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("SAMPLE_COMM_VI_DestroyIsp failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ s32Ret = SAMPLE_COMM_VI_DestroyVi(&stViConfig);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("SAMPLE_COMM_VI_DestroyVi failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ SAMPLE_COMM_SYS_Exit();
+ return s32Ret;
+}
+
+int _try_release_vi(void)
+{
+ CVI_S32 s32Ret = CVI_FAILURE;
+ s32Ret = mmf_del_vi_channel_all();
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("mmf_del_vi_channel_all failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+ return s32Ret;
+}
+
+int _try_release_venc_all(void)
+{
+ for (int ch = 0; ch < VENC_MAX_CHN_NUM; ch ++) {
+ CVI_VENC_StopRecvFrame(ch);
+ CVI_VENC_ResetChn(ch);
+ CVI_VENC_DestroyChn(ch);
+ }
+ return 0;
+}
+
+int _try_release_vpss_all(void)
+{
+ for (int ch = 0; ch < 4; ch ++) {
+ _mmf_vpss_deinit_new(ch);
+ }
+ return 0;
+}
+
+static void _mmf_sys_exit(void)
+{
+ if (g_stViConfig.s32WorkingViNum != 0) {
+ SAMPLE_COMM_VI_DestroyIsp(&g_stViConfig);
+ SAMPLE_COMM_VI_DestroyVi(&g_stViConfig);
+ }
+ SAMPLE_COMM_SYS_Exit();
+}
+
+static CVI_S32 _mmf_sys_init(SIZE_S stSize)
+{
+ VB_CONFIG_S stVbConf;
+ CVI_U32 u32BlkSize, u32BlkRotSize;
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ COMPRESS_MODE_E enCompressMode = COMPRESS_MODE_NONE;
+
+ memset(&stVbConf, 0, sizeof(VB_CONFIG_S));
+ memcpy(&stVbConf, &priv.vb_conf, sizeof(VB_CONFIG_S));
+
+ // vi
+ u32BlkSize = COMMON_GetPicBufferSize(stSize.u32Width, stSize.u32Height, PIXEL_FORMAT_UYVY,
+ DATA_BITWIDTH_12, enCompressMode, DEFAULT_ALIGN);
+ u32BlkRotSize = COMMON_GetPicBufferSize(stSize.u32Height, stSize.u32Width, PIXEL_FORMAT_UYVY,
+ DATA_BITWIDTH_12, enCompressMode, DEFAULT_ALIGN);
+ u32BlkSize = MAX(u32BlkSize, u32BlkRotSize);
+ stVbConf.astCommPool[MMF_VB_VI_ID].u32BlkSize = u32BlkSize;
+ stVbConf.astCommPool[MMF_VB_VI_ID].u32BlkCnt = 4;
+ stVbConf.astCommPool[MMF_VB_VI_ID].enRemapMode = VB_REMAP_MODE_CACHED;
+ stVbConf.u32MaxPoolCnt = 1;
+
+ s32Ret = SAMPLE_COMM_SYS_Init(&stVbConf);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("system init failed with %#x\n", s32Ret);
+ goto error;
+ }
+
+ return s32Ret;
+error:
+ _mmf_sys_exit();
+ return s32Ret;
+}
+
+static CVI_S32 _mmf_vpss_deinit(VPSS_GRP VpssGrp, VPSS_CHN VpssChn)
+{
+ CVI_BOOL abChnEnable[VPSS_MAX_PHY_CHN_NUM] = {0};
+ CVI_S32 s32Ret = CVI_SUCCESS;
+
+ /*start vpss*/
+ abChnEnable[VpssChn] = CVI_TRUE;
+ s32Ret = SAMPLE_COMM_VPSS_Stop(VpssGrp, abChnEnable);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("init vpss group failed. s32Ret: 0x%x !\n", s32Ret);
+ }
+
+ return s32Ret;
+}
+
+static CVI_S32 _mmf_vpss_deinit_new(VPSS_GRP VpssGrp)
+{
+ CVI_S32 s32Ret = CVI_SUCCESS;
+
+ s32Ret = CVI_VPSS_StopGrp(VpssGrp);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("Vpss Stop Grp %d failed! Please check param\n", VpssGrp);
+ return CVI_FAILURE;
+ }
+
+ s32Ret = CVI_VPSS_DestroyGrp(VpssGrp);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("Vpss Destroy Grp %d failed! Please check\n", VpssGrp);
+ return CVI_FAILURE;
+ }
+
+ return s32Ret;
+}
+
+// fit = 0, width to new width, height to new height, may be stretch
+// fit = 1, keep aspect ratio, fill blank area with black color
+// fit = other, keep aspect ratio, crop image to fit new size
+static CVI_S32 _mmf_vpss_init(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, SIZE_S stSizeIn, SIZE_S stSizeOut, PIXEL_FORMAT_E formatIn, PIXEL_FORMAT_E formatOut,
+int fps, int depth, bool mirror, bool flip, int fit)
+{
+ VPSS_GRP_ATTR_S stVpssGrpAttr;
+ VPSS_CROP_INFO_S stGrpCropInfo;
+ CVI_BOOL abChnEnable[VPSS_MAX_PHY_CHN_NUM] = {0};
+ VPSS_CHN_ATTR_S astVpssChnAttr[VPSS_MAX_PHY_CHN_NUM];
+ CVI_S32 s32Ret = CVI_SUCCESS;
+
+ memset(&stVpssGrpAttr, 0, sizeof(VPSS_GRP_ATTR_S));
+ stVpssGrpAttr.stFrameRate.s32SrcFrameRate = -1;
+ stVpssGrpAttr.stFrameRate.s32DstFrameRate = -1;
+ stVpssGrpAttr.enPixelFormat = formatIn;
+ stVpssGrpAttr.u32MaxW = stSizeIn.u32Width;
+ stVpssGrpAttr.u32MaxH = stSizeIn.u32Height;
+ stVpssGrpAttr.u8VpssDev = 0;
+
+ CVI_FLOAT corp_scale_w = (CVI_FLOAT)stSizeIn.u32Width / stSizeOut.u32Width;
+ CVI_FLOAT corp_scale_h = (CVI_FLOAT)stSizeIn.u32Height / stSizeOut.u32Height;
+ CVI_U32 crop_w = -1, crop_h = -1;
+ if (fit == 0) {
+ memset(astVpssChnAttr, 0, sizeof(VPSS_CHN_ATTR_S) * VPSS_MAX_PHY_CHN_NUM);
+ astVpssChnAttr[VpssChn].u32Width = stSizeOut.u32Width;
+ astVpssChnAttr[VpssChn].u32Height = stSizeOut.u32Height;
+ astVpssChnAttr[VpssChn].enVideoFormat = VIDEO_FORMAT_LINEAR;
+ astVpssChnAttr[VpssChn].enPixelFormat = formatOut;
+ astVpssChnAttr[VpssChn].stFrameRate.s32SrcFrameRate = fps;
+ astVpssChnAttr[VpssChn].stFrameRate.s32DstFrameRate = fps;
+ astVpssChnAttr[VpssChn].u32Depth = depth;
+ astVpssChnAttr[VpssChn].bMirror = mirror;
+ astVpssChnAttr[VpssChn].bFlip = flip;
+ astVpssChnAttr[VpssChn].stAspectRatio.enMode = ASPECT_RATIO_MANUAL;
+ astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.s32X = 0;
+ astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.s32Y = 0;
+ astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.u32Width = stSizeOut.u32Width;
+ astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.u32Height = stSizeOut.u32Height;
+ astVpssChnAttr[VpssChn].stAspectRatio.bEnableBgColor = CVI_TRUE;
+ astVpssChnAttr[VpssChn].stAspectRatio.u32BgColor = COLOR_RGB_BLACK;
+ astVpssChnAttr[VpssChn].stNormalize.bEnable = CVI_FALSE;
+
+ stGrpCropInfo.bEnable = false;
+ } else if (fit == 1) {
+ memset(astVpssChnAttr, 0, sizeof(VPSS_CHN_ATTR_S) * VPSS_MAX_PHY_CHN_NUM);
+ astVpssChnAttr[VpssChn].u32Width = stSizeOut.u32Width;
+ astVpssChnAttr[VpssChn].u32Height = stSizeOut.u32Height;
+ astVpssChnAttr[VpssChn].enVideoFormat = VIDEO_FORMAT_LINEAR;
+ astVpssChnAttr[VpssChn].enPixelFormat = formatOut;
+ astVpssChnAttr[VpssChn].stFrameRate.s32SrcFrameRate = fps;
+ astVpssChnAttr[VpssChn].stFrameRate.s32DstFrameRate = fps;
+ astVpssChnAttr[VpssChn].u32Depth = depth;
+ astVpssChnAttr[VpssChn].bMirror = mirror;
+ astVpssChnAttr[VpssChn].bFlip = flip;
+ astVpssChnAttr[VpssChn].stAspectRatio.enMode = ASPECT_RATIO_AUTO;
+ astVpssChnAttr[VpssChn].stAspectRatio.bEnableBgColor = CVI_TRUE;
+ astVpssChnAttr[VpssChn].stAspectRatio.u32BgColor = COLOR_RGB_BLACK;
+ astVpssChnAttr[VpssChn].stNormalize.bEnable = CVI_FALSE;
+
+ stGrpCropInfo.bEnable = false;
+ } else {
+ memset(astVpssChnAttr, 0, sizeof(VPSS_CHN_ATTR_S) * VPSS_MAX_PHY_CHN_NUM);
+ astVpssChnAttr[VpssChn].u32Width = stSizeOut.u32Width;
+ astVpssChnAttr[VpssChn].u32Height = stSizeOut.u32Height;
+ astVpssChnAttr[VpssChn].enVideoFormat = VIDEO_FORMAT_LINEAR;
+ astVpssChnAttr[VpssChn].enPixelFormat = formatOut;
+ astVpssChnAttr[VpssChn].stFrameRate.s32SrcFrameRate = fps;
+ astVpssChnAttr[VpssChn].stFrameRate.s32DstFrameRate = fps;
+ astVpssChnAttr[VpssChn].u32Depth = depth;
+ astVpssChnAttr[VpssChn].bMirror = mirror;
+ astVpssChnAttr[VpssChn].bFlip = flip;
+ astVpssChnAttr[VpssChn].stAspectRatio.enMode = ASPECT_RATIO_AUTO;
+ astVpssChnAttr[VpssChn].stAspectRatio.bEnableBgColor = CVI_TRUE;
+ astVpssChnAttr[VpssChn].stAspectRatio.u32BgColor = COLOR_RGB_BLACK;
+ astVpssChnAttr[VpssChn].stNormalize.bEnable = CVI_FALSE;
+
+ crop_w = corp_scale_w < corp_scale_h ? stSizeOut.u32Width * corp_scale_w: stSizeOut.u32Width * corp_scale_h;
+ crop_h = corp_scale_w < corp_scale_h ? stSizeOut.u32Height * corp_scale_w: stSizeOut.u32Height * corp_scale_h;
+ if (corp_scale_h < 0 || corp_scale_w < 0) {
+ SAMPLE_PRT("crop scale error. corp_scale_w: %f, corp_scale_h: %f\n", corp_scale_w, corp_scale_h);
+ goto error;
+ }
+
+ stGrpCropInfo.bEnable = true;
+ stGrpCropInfo.stCropRect.s32X = (stSizeIn.u32Width - crop_w) / 2;
+ stGrpCropInfo.stCropRect.s32Y = (stSizeIn.u32Height - crop_h) / 2;
+ stGrpCropInfo.stCropRect.u32Width = crop_w;
+ stGrpCropInfo.stCropRect.u32Height = crop_h;
+ }
+
+ /*start vpss*/
+ abChnEnable[0] = CVI_TRUE;
+ s32Ret = SAMPLE_COMM_VPSS_Init(VpssGrp, abChnEnable, &stVpssGrpAttr, astVpssChnAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("init vpss group failed. s32Ret: 0x%x ! retry!!!\n", s32Ret);
+ s32Ret = SAMPLE_COMM_VPSS_Stop(VpssGrp, abChnEnable);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("stop vpss group failed. s32Ret: 0x%x !\n", s32Ret);
+ }
+ s32Ret = SAMPLE_COMM_VPSS_Init(VpssGrp, abChnEnable, &stVpssGrpAttr, astVpssChnAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("retry to init vpss group failed. s32Ret: 0x%x !\n", s32Ret);
+ return s32Ret;
+ } else {
+ SAMPLE_PRT("retry to init vpss group ok!\n");
+ }
+ }
+
+ if (crop_w != 0 && crop_h != 0) {
+ s32Ret = CVI_VPSS_SetChnCrop(VpssGrp, VpssChn, &stGrpCropInfo);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("set vpss group crop failed. s32Ret: 0x%x !\n", s32Ret);
+ goto error;
+ }
+ }
+
+ s32Ret = SAMPLE_COMM_VPSS_Start(VpssGrp, abChnEnable, &stVpssGrpAttr, astVpssChnAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("start vpss group failed. s32Ret: 0x%x !\n", s32Ret);
+ goto error;
+ }
+
+ return s32Ret;
+error:
+ _mmf_vpss_deinit(VpssGrp, VpssChn);
+ return s32Ret;
+}
+
+static CVI_S32 _mmf_init(void)
+{
+ MMF_VERSION_S stVersion;
+ SAMPLE_INI_CFG_S stIniCfg;
+ SAMPLE_VI_CONFIG_S stViConfig;
+
+ PIC_SIZE_E enPicSize;
+ SIZE_S stSize;
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ LOG_LEVEL_CONF_S log_conf;
+
+ int old_pool_cnt = _get_vb_pool_cnt();
+ if (old_pool_cnt > 0) {
+ if (_is_module_in_use("soph_vi") == 0) {
+ reinit_soph_vb();
+ } else {
+ printf("You may have repeatedly initialized maix multi-media!");
+ }
+ }
+
+ CVI_SYS_GetVersion(&stVersion);
+ SAMPLE_PRT("maix multi-media version:%s\n", stVersion.version);
+
+ log_conf.enModId = CVI_ID_LOG;
+ log_conf.s32Level = CVI_DBG_DEBUG;
+ CVI_LOG_SetLevelConf(&log_conf);
+
+ // Get config from ini if found.
+ if (SAMPLE_COMM_VI_ParseIni(&stIniCfg)) {
+ SAMPLE_PRT("Parse complete\n");
+ }
+
+ //Set sensor number
+ CVI_VI_SetDevNum(stIniCfg.devNum);
+
+ /************************************************
+ * step1: Config VI
+ ************************************************/
+ s32Ret = SAMPLE_COMM_VI_IniToViCfg(&stIniCfg, &stViConfig);
+ if (s32Ret != CVI_SUCCESS)
+ return s32Ret;
+
+ memcpy(&g_stViConfig, &stViConfig, sizeof(SAMPLE_VI_CONFIG_S));
+ memcpy(&g_stIniCfg, &stIniCfg, sizeof(SAMPLE_INI_CFG_S));
+
+ /************************************************
+ * step2: Get input size
+ ************************************************/
+ s32Ret = SAMPLE_COMM_VI_GetSizeBySensor(stIniCfg.enSnsType[0], &enPicSize);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("SAMPLE_COMM_VI_GetSizeBySensor failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ s32Ret = SAMPLE_COMM_SYS_GetPicSize(enPicSize, &stSize);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("SAMPLE_COMM_SYS_GetPicSize failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ /************************************************
+ * step3: Init modules
+ ************************************************/
+ if (0 != _free_leak_memory_of_ion()) {
+ SAMPLE_PRT("free leak ion memory error\n");
+ }
+
+ if (0 != _free_leak_memory_of_vb()) {
+ SAMPLE_PRT("free leak vb memory error\n");
+ }
+
+ s32Ret = _mmf_sys_init(stSize);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("sys init failed. s32Ret: 0x%x !\n", s32Ret);
+ goto _need_exit_sys_and_deinit_vi;
+ }
+
+ s32Ret = SAMPLE_PLAT_VI_INIT(&stViConfig);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("vi init failed. s32Ret: 0x%x !\n", s32Ret);
+ SAMPLE_PRT("Please try to check if the camera is working.\n");
+ goto _need_exit_sys_and_deinit_vi;
+ }
+
+ priv.vi_size.u32Width = stSize.u32Width;
+ priv.vi_size.u32Height = stSize.u32Height;
+
+ return s32Ret;
+
+_need_exit_sys_and_deinit_vi:
+ _mmf_sys_exit();
+
+ return s32Ret;
+}
+
+static void _mmf_deinit(void)
+{
+ UNUSED(cvi_ive_deinit);
+ mmf_del_vi_channel_all();
+ mmf_del_venc_channel_all();
+ mmf_enc_jpg_deinit(0);
+ _try_release_venc_all();
+ _try_release_vpss_all();
+ mmf_vi_deinit();
+ // mmf_del_region_channel_all(); // need not release
+ _mmf_sys_exit();
+}
+
+static int _vi_get_unused_ch() {
+ for (int i = 0; i < MMF_VI_MAX_CHN; i++) {
+ if (priv.vi_chn_is_inited[i] == false) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int mmf_init(void)
+{
+ if (priv.mmf_used_cnt) {
+ priv.mmf_used_cnt ++;
+ // printf("maix multi-media already inited(cnt:%d)\n", priv.mmf_used_cnt);
+ return 0;
+ }
+
+ if (_try_release_sys() != CVI_SUCCESS) {
+ printf("try release sys failed\n");
+ return -1;
+ } else {
+ printf("try release sys ok\n");
+ }
+
+ if (_mmf_init() != CVI_SUCCESS) {
+ printf("maix multi-media init failed\n");
+ return -1;
+ } else {
+ printf("maix multi-media init ok\n");
+ }
+
+ UNUSED(cvi_ive_init);
+ priv.mmf_used_cnt = 1;
+
+ if (_try_release_vi() != CVI_SUCCESS) {
+ printf("try release vio failed\n");
+ return -1;
+ } else {
+ printf("try release vio ok\n");
+ }
+
+ if (_try_release_venc_all() != CVI_SUCCESS) {
+ printf("try release venc failed\n");
+ return -1;
+ } else {
+ printf("try release venc ok\n");
+ }
+
+ return 0;
+}
+
+bool mmf_is_init(void)
+{
+ return priv.mmf_used_cnt > 0 ? true : false;
+}
+
+int mmf_try_deinit(bool force) {
+ if (!priv.mmf_used_cnt) {
+ return 0;
+ }
+
+ if (force) {
+ priv.mmf_used_cnt = 0;
+ printf("maix multi-media driver destroyed.\n");
+ _mmf_deinit();
+ } else {
+ priv.mmf_used_cnt --;
+ if (priv.mmf_used_cnt) {
+ return 0;
+ } else {
+ printf("maix multi-media driver destroyed.\n");
+ _mmf_deinit();
+ }
+ }
+ return 0;
+}
+
+int mmf_deinit(void) {
+ return mmf_try_deinit(false);
+}
+
+int mmf_get_vi_unused_channel(void) {
+ return _vi_get_unused_ch();
+}
+
+static CVI_S32 _mmf_vpss_chn_init(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, int width, int height, PIXEL_FORMAT_E format, int fps, int depth, bool mirror, bool flip, int fit)
+{
+#if 1
+ VPSS_GRP_ATTR_S stGrpAttr;
+ VPSS_CROP_INFO_S stChnCropInfo;
+ VPSS_CHN_ATTR_S chn_attr = {0};
+ CVI_S32 s32Ret = CVI_SUCCESS;
+
+ s32Ret = CVI_VPSS_GetGrpAttr(VpssGrp, &stGrpAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_GetGrpAttr failed. s32Ret: 0x%x !\n", s32Ret);
+ return s32Ret;
+ }
+ CVI_FLOAT corp_scale_w = (CVI_FLOAT)stGrpAttr.u32MaxW / width;
+ CVI_FLOAT corp_scale_h = (CVI_FLOAT)stGrpAttr.u32MaxH / height;
+ CVI_U32 crop_w = -1, crop_h = -1;
+ if (fit == 0) {
+ chn_attr.u32Width = width;
+ chn_attr.u32Height = height;
+ chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR;
+ chn_attr.enPixelFormat = format;
+ chn_attr.stFrameRate.s32SrcFrameRate = fps;
+ chn_attr.stFrameRate.s32DstFrameRate = fps;
+ chn_attr.u32Depth = depth;
+ chn_attr.bMirror = mirror;
+ chn_attr.bFlip = flip;
+ chn_attr.stAspectRatio.enMode = ASPECT_RATIO_MANUAL;
+ chn_attr.stAspectRatio.stVideoRect.s32X = 0;
+ chn_attr.stAspectRatio.stVideoRect.s32Y = 0;
+ chn_attr.stAspectRatio.stVideoRect.u32Width = width;
+ chn_attr.stAspectRatio.stVideoRect.u32Height = height;
+ chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE;
+ chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK;
+ chn_attr.stNormalize.bEnable = CVI_FALSE;
+
+ stChnCropInfo.bEnable = false;
+ } else if (fit == 1) {
+ chn_attr.u32Width = width;
+ chn_attr.u32Height = height;
+ chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR;
+ chn_attr.enPixelFormat = format;
+ chn_attr.stFrameRate.s32SrcFrameRate = fps;
+ chn_attr.stFrameRate.s32DstFrameRate = fps;
+ chn_attr.u32Depth = depth;
+ chn_attr.bMirror = mirror;
+ chn_attr.bFlip = flip;
+ chn_attr.stAspectRatio.enMode = ASPECT_RATIO_AUTO;
+ chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE;
+ chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK;
+ chn_attr.stNormalize.bEnable = CVI_FALSE;
+
+ stChnCropInfo.bEnable = false;
+ } else {
+ chn_attr.u32Width = width;
+ chn_attr.u32Height = height;
+ chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR;
+ chn_attr.enPixelFormat = format;
+ chn_attr.stFrameRate.s32SrcFrameRate = fps;
+ chn_attr.stFrameRate.s32DstFrameRate = fps;
+ chn_attr.u32Depth = depth;
+ chn_attr.bMirror = mirror;
+ chn_attr.bFlip = flip;
+ chn_attr.stAspectRatio.enMode = ASPECT_RATIO_AUTO;
+ chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE;
+ chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK;
+ chn_attr.stNormalize.bEnable = CVI_FALSE;
+
+ crop_w = corp_scale_w < corp_scale_h ? width * corp_scale_w: width * corp_scale_h;
+ crop_h = corp_scale_w < corp_scale_h ? height * corp_scale_w: height * corp_scale_h;
+ if (corp_scale_h < 0 || corp_scale_w < 0) {
+ SAMPLE_PRT("crop scale error. corp_scale_w: %f, corp_scale_h: %f\n", corp_scale_w, corp_scale_h);
+ return -1;
+ }
+
+ stChnCropInfo.bEnable = true;
+ stChnCropInfo.stCropRect.s32X = (stGrpAttr.u32MaxW - crop_w) / 2;
+ stChnCropInfo.stCropRect.s32Y = (stGrpAttr.u32MaxH - crop_h) / 2;
+ stChnCropInfo.stCropRect.u32Width = crop_w;
+ stChnCropInfo.stCropRect.u32Height = crop_h;
+ }
+
+ if (crop_w != 0 && crop_h != 0) {
+ s32Ret = CVI_VPSS_SetChnCrop(VpssGrp, VpssChn, &stChnCropInfo);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("set vpss group crop failed. s32Ret: 0x%x !\n", s32Ret);
+ return -1;
+ }
+ }
+
+ s32Ret = CVI_VPSS_SetChnAttr(VpssGrp, VpssChn, &chn_attr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_SetChnAttr failed with %#x\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ s32Ret = CVI_VPSS_EnableChn(VpssGrp, VpssChn);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_EnableChn failed with %#x\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ return s32Ret;
+#else
+ CVI_S32 s32Ret;
+ VPSS_CHN_ATTR_S chn_attr = {0};
+ chn_attr.u32Width = width;
+ chn_attr.u32Height = height;
+ chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR;
+ chn_attr.enPixelFormat = format;
+ chn_attr.stFrameRate.s32SrcFrameRate = fps;
+ chn_attr.stFrameRate.s32DstFrameRate = fps;
+ chn_attr.u32Depth = depth;
+ chn_attr.bMirror = mirror;
+ chn_attr.bFlip = flip;
+ chn_attr.stAspectRatio.enMode = ASPECT_RATIO_MANUAL;
+ chn_attr.stAspectRatio.stVideoRect.s32X = 0;
+ chn_attr.stAspectRatio.stVideoRect.s32Y = 0;
+ chn_attr.stAspectRatio.stVideoRect.u32Width = width;
+ chn_attr.stAspectRatio.stVideoRect.u32Height = height;
+ chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE;
+ chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK;
+ chn_attr.stNormalize.bEnable = CVI_FALSE;
+
+ s32Ret = CVI_VPSS_SetChnAttr(VpssGrp, VpssChn, &chn_attr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_SetChnAttr failed with %#x\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ s32Ret = CVI_VPSS_EnableChn(VpssGrp, VpssChn);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_EnableChn failed with %#x\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ return CVI_SUCCESS;
+#endif
+}
+
+static CVI_S32 _mmf_vpss_chn_deinit(VPSS_GRP VpssGrp, VPSS_CHN VpssChn)
+{
+ return CVI_VPSS_DisableChn(VpssGrp, VpssChn);
+}
+
+static CVI_S32 _mmf_vpss_init_new(VPSS_GRP VpssGrp, CVI_U32 width, CVI_U32 height, PIXEL_FORMAT_E format)
+{
+ VPSS_GRP_ATTR_S stVpssGrpAttr;
+ CVI_S32 s32Ret = CVI_SUCCESS;
+
+ memset(&stVpssGrpAttr, 0, sizeof(VPSS_GRP_ATTR_S));
+ stVpssGrpAttr.stFrameRate.s32SrcFrameRate = -1;
+ stVpssGrpAttr.stFrameRate.s32DstFrameRate = -1;
+ stVpssGrpAttr.enPixelFormat = format;
+ stVpssGrpAttr.u32MaxW = width;
+ stVpssGrpAttr.u32MaxH = height;
+ stVpssGrpAttr.u8VpssDev = 0;
+
+ s32Ret = CVI_VPSS_CreateGrp(VpssGrp, &stVpssGrpAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_CreateGrp(grp:%d) retry(%#x)!\n", VpssGrp, s32Ret);
+ CVI_VPSS_DestroyGrp(VpssGrp);
+
+ s32Ret = CVI_VPSS_CreateGrp(VpssGrp, &stVpssGrpAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_CreateGrp(grp:%d) failed with %#x!\n", VpssGrp, s32Ret);
+ return CVI_FAILURE;
+ }
+ }
+
+ s32Ret = CVI_VPSS_ResetGrp(VpssGrp);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_ResetGrp(grp:%d) failed with %#x!%d\n", VpssGrp, s32Ret, CVI_ERR_VPSS_ILLEGAL_PARAM);
+ return CVI_FAILURE;
+ }
+
+ s32Ret = CVI_VPSS_StartGrp(VpssGrp);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_StartGrp failed with %#x\n", s32Ret);
+ return CVI_FAILURE;
+ }
+ return s32Ret;
+}
+
+int mmf_vi_init(void)
+{
+ if (priv.vi_is_inited) {
+ return 0;
+ }
+
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ s32Ret = _mmf_vpss_init_new(0, priv.vi_size.u32Width, priv.vi_size.u32Height, PIXEL_FORMAT_UYVY); // PIXEL_FORMAT_UYVY PIXEL_FORMAT_NV21
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("_mmf_vpss_init_new failed. s32Ret: 0x%x !\n", s32Ret);
+ }
+
+ priv.vi_is_inited = true;
+
+ return s32Ret;
+}
+
+int mmf_vi_deinit(void)
+{
+ if (!priv.vi_is_inited) {
+ return 0;
+ }
+
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ s32Ret = _mmf_vpss_deinit_new(0);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("_mmf_vpss_deinit_new failed with %#x!\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ priv.vi_is_inited = false;
+
+ return s32Ret;
+}
+
+static int _mmf_add_vi_channel(int ch, int width, int height, int format) {
+ uint32_t pool_size_out = 0;
+ int pool_id = -1;
+
+ if (!priv.mmf_used_cnt) {
+ printf("%s: maix multi-media or vi not inited\n", __func__);
+ return -1;
+ }
+
+ if (!priv.vi_is_inited) {
+ if (0 != mmf_vi_init()) {
+ printf("mmf_vi_init failed!\r\n");
+ return -1;
+ }
+ }
+
+ if (width <= 0 || height <= 0) {
+ printf("invalid width or height\n");
+ return -1;
+ }
+
+ if (format != PIXEL_FORMAT_NV21
+ && format != PIXEL_FORMAT_RGB_888) {
+ printf("invalid format\n");
+ return -1;
+ }
+
+ // if ((format == PIXEL_FORMAT_RGB_888 && width * height * 3 > 640 * 640 * 3)
+ // || (format == PIXEL_FORMAT_RGB_888 && width * height * 3 / 2 > 2560 * 1440 * 3 / 2)) {
+ // printf("camera size is too large, for NV21, maximum resolution 2560x1440, for RGB888, maximum resolution 640x640!\n");
+ // return -1;
+ // }
+
+ if (mmf_vi_chn_is_open(ch)) {
+ printf("vi ch %d already open\n", ch);
+ return -1;
+ }
+
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ int fps = 30;
+ int depth = 2;
+ int width_out = ALIGN(width, DEFAULT_ALIGN);
+ int height_out = height;
+ PIXEL_FORMAT_E format_out = (PIXEL_FORMAT_E)format;
+ bool mirror = !g_priv.vi_hmirror[ch];
+ bool flip = !g_priv.vi_vflip[ch];
+ s32Ret = _mmf_vpss_chn_deinit(0, ch);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("_mmf_vpss_chn_deinit failed with %#x!\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ s32Ret = _mmf_vpss_chn_init(0, ch, width_out, height_out, format_out, fps, depth, mirror, flip, 2);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("_mmf_vpss_chn_init failed with %#x!\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ s32Ret = SAMPLE_COMM_VI_Bind_VPSS(0, ch, 0);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("vi bind vpss failed. s32Ret: 0x%x !\n", s32Ret);
+ goto _need_deinit_vpss_chn;
+ }
+
+ char name[20];
+ snprintf(name, 20, "vi_vpss%.1d", ch);
+ pool_size_out = COMMON_GetPicBufferSize(width_out, height_out, format_out, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN);
+ pool_id = _create_vb_pool(name, MMF_MOD_VI, pool_size_out, 2);
+ if (pool_id < 0) {
+ printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id);
+ goto _need_deinit_vpss_chn;
+ }
+
+ s32Ret = CVI_VPSS_AttachVbPool(0, ch, pool_id);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_AttachVbPool failed. s32Ret: 0x%x !\n", s32Ret);
+ goto _need_deinit_vpss_chn;
+ }
+
+ // VIDEO_FRAME_INFO_S frame;
+ // if ((s32Ret = CVI_VPSS_GetChnFrame(0, ch, &frame, 3000)) != CVI_SUCCESS) {
+ // SAMPLE_PRT("vi get frame failed: 0x%x !\n", s32Ret);
+ // if ((s32Ret = SAMPLE_COMM_VI_UnBind_VPSS(0, ch, 0)) != CVI_SUCCESS) {
+ // SAMPLE_PRT("vi unbind vpss failed. s32Ret: 0x%x !\n", s32Ret);
+ // }
+ // goto _need_deinit_vpss_chn;
+ // }
+ // CVI_VPSS_ReleaseChnFrame(0, ch, &frame);
+
+ priv.vi_chn_pool_id[ch] = pool_id;
+ priv.vi_chn_is_inited[ch] = true;
+
+ return 0;
+_need_deinit_vpss_chn:
+ _mmf_vpss_chn_deinit(0, ch);
+ return -1;
+}
+
+int mmf_add_vi_channel(int ch, int width, int height, int format) {
+ printf("mmf_add_vi_channel..\r\n");
+ return _mmf_add_vi_channel(ch, width, height, format);
+}
+
+int mmf_del_vi_channel(int ch) {
+ if (ch < 0 || ch >= MMF_VI_MAX_CHN) {
+ printf("[%d] invalid ch %d\n", __LINE__, ch);
+ return -1;
+ }
+
+ if (priv.vi_chn_is_inited[ch] == false) {
+ return 0;
+ }
+
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ s32Ret = SAMPLE_COMM_VI_UnBind_VPSS(0, ch, 0);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("vi unbind vpss failed. s32Ret: 0x%x !\n", s32Ret);
+ // return -1; // continue to deinit vpss
+ }
+
+ if (0 != _mmf_vpss_chn_deinit(0, ch)) {
+ SAMPLE_PRT("_mmf_vpss_chn_deinit failed. s32Ret: 0x%x !\n", s32Ret);
+ }
+
+ _destroy_vb_pool(priv.vi_chn_pool_id[ch]);
+
+ priv.vi_chn_pool_id[ch] = -1;
+ priv.vi_chn_is_inited[ch] = false;
+ return s32Ret;
+}
+
+int mmf_del_vi_channel_all() {
+ for (int i = 0; i < MMF_VI_MAX_CHN; i++) {
+ if (priv.vi_chn_is_inited[i] == true) {
+ mmf_del_vi_channel(i);
+ }
+ }
+ return 0;
+}
+
+bool mmf_vi_chn_is_open(int ch) {
+ if (ch < 0 || ch >= MMF_VI_MAX_CHN) {
+ return false;
+ }
+
+ return priv.vi_chn_is_inited[ch];
+}
+
+int mmf_reset_vi_channel(int ch, int width, int height, int format)
+{
+ mmf_del_vi_channel(ch);
+ return mmf_add_vi_channel(ch, width, height, format);
+}
+
+int mmf_vi_aligned_width(int ch) {
+ UNUSED(ch);
+ return DEFAULT_ALIGN;
+}
+
+int mmf_vi_frame_pop(int ch, void **data, int *len, int *width, int *height, int *format) {
+ if (!priv.vi_chn_is_inited[ch]) {
+ // printf("vi ch %d not open\n", ch);
+ return -1;
+ }
+ if (ch < 0 || ch >= MMF_VI_MAX_CHN) {
+ printf("[%d] invalid ch %d\n", __LINE__, ch);
+ return -1;
+ }
+ if (data == NULL || len == NULL || width == NULL || height == NULL || format == NULL) {
+ printf("invalid param\n");
+ return -1;
+ }
+
+ int ret = -1;
+ VIDEO_FRAME_INFO_S *frame = &priv.vi_frame[ch];
+ if (CVI_VPSS_GetChnFrame(0, ch, frame, 1000) == 0) {
+ int image_size = frame->stVFrame.u32Length[0]
+ + frame->stVFrame.u32Length[1]
+ + frame->stVFrame.u32Length[2];
+ CVI_VOID *vir_addr;
+ vir_addr = CVI_SYS_MmapCache(frame->stVFrame.u64PhyAddr[0], image_size);
+ CVI_SYS_IonInvalidateCache(frame->stVFrame.u64PhyAddr[0], vir_addr, image_size);
+
+ frame->stVFrame.pu8VirAddr[0] = (CVI_U8 *)vir_addr; // save virtual address for munmap
+ // printf("width: %d, height: %d, total_buf_length: %d, phy:%#lx vir:%p\n",
+ // frame->stVFrame.u32Width,
+ // frame->stVFrame.u32Height, image_size,
+ // frame->stVFrame.u64PhyAddr[0], vir_addr);
+
+ *data = vir_addr;
+ *len = image_size;
+ *width = frame->stVFrame.u32Width;
+ *height = frame->stVFrame.u32Height;
+ *format = frame->stVFrame.enPixelFormat;
+ return 0;
+ }
+ return ret;
+}
+
+void mmf_vi_frame_free(int ch) {
+ VIDEO_FRAME_INFO_S *frame = &priv.vi_frame[ch];
+ int image_size = frame->stVFrame.u32Length[0]
+ + frame->stVFrame.u32Length[1]
+ + frame->stVFrame.u32Length[2];
+ CVI_SYS_Munmap(frame->stVFrame.pu8VirAddr[0], image_size);
+ if (CVI_VPSS_ReleaseChnFrame(0, ch, frame) != 0) {
+ SAMPLE_PRT("CVI_VI_ReleaseChnFrame NG\n");
+ }
+}
+
+int mmf_region_frame_push(int ch, void *data, int len)
+{
+ CVI_S32 s32Ret;
+ RGN_CANVAS_INFO_S stCanvasInfo;
+
+ if (ch < 0 || ch >= MMF_RGN_MAX_NUM) {
+ SAMPLE_PRT("Handle ch is illegal %d!\n", ch);
+ return CVI_FAILURE;
+ }
+
+ if (!priv.rgn_is_init[ch]) {
+ return 0;
+ }
+
+ if (!priv.rgn_is_bind[ch]) {
+ return 0;
+ }
+
+ s32Ret = CVI_RGN_GetCanvasInfo(ch, &stCanvasInfo);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_RGN_GetCanvasInfo failed with %#x!\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ if (stCanvasInfo.enPixelFormat == PIXEL_FORMAT_ARGB_8888) {
+ if (!data || (CVI_U32)len != stCanvasInfo.stSize.u32Width * stCanvasInfo.stSize.u32Height * 4) {
+ printf("Param is error!\r\n");
+ return CVI_FAILURE;
+ }
+ memcpy(stCanvasInfo.pu8VirtAddr, data, len);
+ } else {
+ printf("Not support format!\r\n");
+ return CVI_FAILURE;
+ }
+
+ s32Ret = CVI_RGN_UpdateCanvas(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_RGN_UpdateCanvas failed with %#x!\n", s32Ret);
+ return CVI_FAILURE;
+ }
+ return s32Ret;
+}
+
+int mmf_enc_jpg_init(int ch, int w, int h, int format, int quality)
+{
+ if (priv.enc_jpg_is_init)
+ return 0;
+
+ if (quality <= 50) {
+ printf("quality range is (50, 100]\n");
+ return -1;
+ }
+
+ if (mmf_init()) {
+ return -1;
+ }
+
+ // if ((format == PIXEL_FORMAT_RGB_888 && w * h * 3 > 640 * 640 * 3)
+ // || (format == PIXEL_FORMAT_RGB_888 && w * h * 3 / 2 > 2560 * 1440 * 3 / 2)) {
+ // printf("image size is too large, for NV21, maximum resolution 2560x1440, for RGB888, maximum resolution 640x640!\n");
+ // return -1;
+ // }
+
+ CVI_S32 s32Ret = CVI_SUCCESS;
+
+ VENC_CHN_ATTR_S stVencChnAttr;
+ memset(&stVencChnAttr, 0, sizeof(VENC_CHN_ATTR_S));
+ stVencChnAttr.stVencAttr.enType = PT_JPEG;
+ stVencChnAttr.stVencAttr.u32MaxPicWidth = w;
+ stVencChnAttr.stVencAttr.u32MaxPicHeight = h;
+ stVencChnAttr.stVencAttr.u32PicWidth = w;
+ stVencChnAttr.stVencAttr.u32PicHeight = h;
+ stVencChnAttr.stVencAttr.bEsBufQueueEn = CVI_FALSE;
+ stVencChnAttr.stVencAttr.bIsoSendFrmEn = CVI_FALSE;
+ stVencChnAttr.stVencAttr.bByFrame = 1;
+ stVencChnAttr.stRcAttr.enRcMode = VENC_RC_MODE_MJPEGFIXQP;
+
+ s32Ret = CVI_VENC_CreateChn(ch, &stVencChnAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_CreateChn [%d] failed with %#x\n", ch, s32Ret);
+ return s32Ret;
+ }
+
+ s32Ret = CVI_VENC_ResetChn(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_CreateChn [%d] failed with %#x\n", ch, s32Ret);
+ return s32Ret;
+ }
+
+ s32Ret = CVI_VENC_DestroyChn(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_destroyChn [%d] failed with %#x\n", ch, s32Ret);
+ }
+
+ s32Ret = CVI_VENC_CreateChn(ch, &stVencChnAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_CreateChn [%d] failed with %#x\n", ch, s32Ret);
+ return s32Ret;
+ }
+
+ VENC_JPEG_PARAM_S stJpegParam;
+ memset(&stJpegParam, 0, sizeof(VENC_JPEG_PARAM_S));
+ s32Ret = CVI_VENC_GetJpegParam(ch, &stJpegParam);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_GetJpegParam failed with %#x\n", s32Ret);
+ CVI_VENC_DestroyChn(ch);
+ return s32Ret;
+ }
+ stJpegParam.u32Qfactor = quality;
+ s32Ret = CVI_VENC_SetJpegParam(ch, &stJpegParam);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_SetJpegParam failed with %#x\n", s32Ret);
+ CVI_VENC_DestroyChn(ch);
+ return s32Ret;
+ }
+
+ switch (format) {
+ case PIXEL_FORMAT_RGB_888:
+ {
+ s32Ret = _mmf_vpss_init(2, ch, (SIZE_S){(CVI_U32)w, (CVI_U32)h}, (SIZE_S){(CVI_U32)w, (CVI_U32)h}, PIXEL_FORMAT_RGB_888, PIXEL_FORMAT_YUV_PLANAR_420, 30, 0, CVI_FALSE, CVI_FALSE, 0);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("VPSS init failed with %#x\n", s32Ret);
+ CVI_VENC_StopRecvFrame(ch);
+ CVI_VENC_DestroyChn(ch);
+ return s32Ret;
+ }
+
+ s32Ret = SAMPLE_COMM_VPSS_Bind_VENC(2, ch, ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("VPSS bind VENC failed with %#x\n", s32Ret);
+ _mmf_vpss_deinit(2, ch);
+ CVI_VENC_StopRecvFrame(ch);
+ CVI_VENC_DestroyChn(ch);
+ return s32Ret;
+ }
+
+ uint32_t input_size = 0, output_size = 0;
+ input_size = COMMON_GetPicBufferSize(w, h, (PIXEL_FORMAT_E)format, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN);
+ output_size = COMMON_GetPicBufferSize(w, h, (PIXEL_FORMAT_E)PIXEL_FORMAT_YUV_PLANAR_420, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN);
+ int pool_id = _create_vb_pool((char *)"enc_jpeg_in", MMF_MOD_VENC, input_size, 1);
+ if (pool_id < 0) {
+ printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id);
+ return -1;
+ }
+ priv.enc_jpg_input_pool_id = pool_id;
+
+ pool_id = _create_vb_pool((char *)"enc_jpeg_out", MMF_MOD_VENC, output_size, 1);
+ if (pool_id < 0) {
+ printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id);
+ return -1;
+ }
+ priv.enc_jpg_output_pool_id = pool_id;
+
+ s32Ret = CVI_VPSS_AttachVbPool(2, ch, priv.enc_jpg_output_pool_id);
+ if (s32Ret != CVI_SUCCESS) {
+ SAMPLE_PRT("CVI_VPSS_AttachVbPool failed. s32Ret: 0x%x !\n", s32Ret);
+ _destroy_vb_pool(priv.enc_jpg_output_pool_id);
+ _destroy_vb_pool(priv.enc_jpg_input_pool_id);
+ return CVI_FAILURE;
+ }
+ }
+ break;
+ case PIXEL_FORMAT_NV21:
+ {
+ uint32_t size = COMMON_GetPicBufferSize(w, h, (PIXEL_FORMAT_E)format, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN);
+ int pool_id = _create_vb_pool((char *)"enc_jpeg_in", MMF_MOD_VENC, size, 1);
+ if (pool_id < 0) {
+ printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id);
+ return -1;
+ }
+ priv.enc_jpg_input_pool_id = pool_id;
+ }
+ break;
+ default:
+ printf("unknown format!\n");
+ CVI_VENC_StopRecvFrame(ch);
+ CVI_VENC_DestroyChn(ch);
+ return -1;
+ }
+
+ VENC_RECV_PIC_PARAM_S stRecvParam;
+ stRecvParam.s32RecvPicNum = -1;
+ s32Ret = CVI_VENC_StartRecvFrame(ch, &stRecvParam);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_StartRecvPic failed with %#x\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ if (priv.enc_jpg_frame) {
+ _mmf_free_frame(priv.enc_jpg_frame);
+ priv.enc_jpg_frame = NULL;
+ }
+
+ priv.enc_jpg_frame_w = w;
+ priv.enc_jpg_frame_h = h;
+ priv.enc_jpg_frame_fmt = format;
+ priv.enc_jpg_quality = quality;
+ priv.enc_jpg_is_init = 1;
+ priv.enc_jpg_running = 0;
+
+ return s32Ret;
+}
+
+int mmf_enc_jpg_deinit(int ch)
+{
+ if (!priv.enc_jpg_is_init)
+ return 0;
+
+ CVI_S32 s32Ret = CVI_SUCCESS;
+
+ if (!mmf_enc_jpg_pop(ch, NULL, NULL)) {
+ mmf_enc_jpg_free(ch);
+ }
+
+ switch (priv.enc_jpg_frame_fmt) {
+ case PIXEL_FORMAT_RGB_888:
+ s32Ret = SAMPLE_COMM_VPSS_UnBind_VENC(2, ch, ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("VPSS unbind VENC failed with %d\n", s32Ret);
+ }
+
+ s32Ret = _mmf_vpss_deinit(2, ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("VPSS deinit failed with %d\n", s32Ret);
+ }
+ break;
+ case PIXEL_FORMAT_NV21:
+ break;
+ default:
+ break;
+ }
+
+ s32Ret = CVI_VENC_StopRecvFrame(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_StopRecvPic failed with %d\n", s32Ret);
+ }
+
+ s32Ret = CVI_VENC_ResetChn(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_ResetChn vechn[%d] failed with %#x!\n",
+ ch, s32Ret);
+ }
+
+ s32Ret = CVI_VENC_DestroyChn(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_DestroyChn [%d] failed with %d\n", ch, s32Ret);
+ }
+
+ if (priv.enc_jpg_frame) {
+ _mmf_free_frame(priv.enc_jpg_frame);
+ priv.enc_jpg_frame = NULL;
+ }
+
+ switch (priv.enc_jpg_frame_fmt) {
+ case PIXEL_FORMAT_RGB_888:
+ _destroy_vb_pool(priv.enc_jpg_output_pool_id);
+ priv.enc_jpg_output_pool_id = -1;
+ _destroy_vb_pool(priv.enc_jpg_input_pool_id);
+ priv.enc_jpg_output_pool_id = -1;
+ break;
+ case PIXEL_FORMAT_NV21:
+ _destroy_vb_pool(priv.enc_jpg_input_pool_id);
+ priv.enc_jpg_output_pool_id = -1;
+ break;
+ default:
+ break;
+ }
+
+ if (mmf_deinit()) {
+ return -1;
+ }
+
+ priv.enc_jpg_frame_w = 0;
+ priv.enc_jpg_frame_h = 0;
+ priv.enc_jpg_frame_fmt = 0;
+ priv.enc_jpg_quality = -1;
+ priv.enc_jpg_is_init = 0;
+ priv.enc_jpg_running = 0;
+
+ return s32Ret;
+}
+
+int mmf_enc_jpg_push_with_quality(int ch, uint8_t *data, int w, int h, int format, int quality)
+{
+ UNUSED(ch);
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (priv.enc_jpg_running) {
+ return s32Ret;
+ }
+
+ int real_format = format;
+ if (format == PIXEL_FORMAT_UINT8_C1) {
+ format = PIXEL_FORMAT_NV21;
+ }
+
+ SIZE_S stSize = {(CVI_U32)w, (CVI_U32)h};
+ if (priv.enc_jpg_frame == NULL || priv.enc_jpg_frame_w != w || priv.enc_jpg_frame_h != h || priv.enc_jpg_frame_fmt != format
+ || priv.enc_jpg_quality != quality) {
+ mmf_enc_jpg_deinit(ch);
+ mmf_enc_jpg_init(ch, w, h, format, quality);
+ priv.enc_jpg_frame_w = w;
+ priv.enc_jpg_frame_h = h;
+ priv.enc_jpg_frame_fmt = format;
+ priv.enc_jpg_quality = quality;
+ if (priv.enc_jpg_frame) {
+ _mmf_free_frame(priv.enc_jpg_frame);
+ priv.enc_jpg_frame = NULL;
+ }
+ priv.enc_jpg_frame = (VIDEO_FRAME_INFO_S *)_mmf_alloc_frame(priv.enc_jpg_input_pool_id, stSize, (PIXEL_FORMAT_E)format);
+ if (!priv.enc_jpg_frame) {
+ printf("Alloc frame failed!\r\n");
+ return -1;
+ }
+ }
+
+ switch (real_format) {
+ case PIXEL_FORMAT_UINT8_C1:
+ if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w) {
+ for (int height = 0; height < h; height ++) {
+ memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * height,
+ ((uint8_t *)data) + w * height, w);
+ }
+
+ for (int height = priv.enc_jpg_frame->stVFrame.u32Height; height < h / 2; height ++) {
+ memset((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * height,
+ 128, w);
+ }
+ } else {
+ memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h);
+ memset(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + w * h, 128, w * h / 2);
+ }
+ CVI_SYS_IonFlushCache(priv.enc_jpg_frame->stVFrame.u64PhyAddr[0],
+ priv.enc_jpg_frame->stVFrame.pu8VirAddr[0],
+ priv.enc_jpg_frame->stVFrame.u32Stride[0] * h * 3 / 2);
+ s32Ret = CVI_VENC_SendFrame(ch, priv.enc_jpg_frame, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_SendFrame failed with %d\n", s32Ret);
+ return s32Ret;
+ }
+ break;
+ case PIXEL_FORMAT_RGB_888:
+ {
+ if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w * 3) {
+ for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height; h++) {
+ memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h, ((uint8_t *)data) + w * h * 3, w * 3);
+ }
+ } else {
+ memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], data, w * h * 3);
+ }
+
+ s32Ret = CVI_VPSS_SendFrame(2, priv.enc_jpg_frame, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VPSS_SendFrame failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+ }
+ break;
+ case PIXEL_FORMAT_NV21:
+ if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w) {
+ for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height * 3 / 2; h ++) {
+ memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h,
+ ((uint8_t *)data) + w * h, w);
+ }
+ } else {
+ memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h * 3 / 2);
+ }
+
+ s32Ret = CVI_VENC_SendFrame(ch, priv.enc_jpg_frame, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_SendFrame failed with %d\n", s32Ret);
+ return s32Ret;
+ }
+ break;
+ default: return -1;
+ }
+
+ priv.enc_jpg_running = 1;
+
+ return s32Ret;
+}
+
+int mmf_enc_jpg_push(int ch, uint8_t *data, int w, int h, int format)
+{
+ UNUSED(ch);
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (priv.enc_jpg_running) {
+ return s32Ret;
+ }
+
+ SIZE_S stSize = {(CVI_U32)w, (CVI_U32)h};
+ if (priv.enc_jpg_frame == NULL || priv.enc_jpg_frame_w != w || priv.enc_jpg_frame_h != h || priv.enc_jpg_frame_fmt != format) {
+ mmf_enc_jpg_deinit(ch);
+ mmf_enc_jpg_init(ch, w, h, format, 80);
+ priv.enc_jpg_frame_w = w;
+ priv.enc_jpg_frame_h = h;
+ priv.enc_jpg_frame_fmt = format;
+ if (priv.enc_jpg_frame) {
+ _mmf_free_frame(priv.enc_jpg_frame);
+ priv.enc_jpg_frame = NULL;
+ }
+ priv.enc_jpg_frame = (VIDEO_FRAME_INFO_S *)_mmf_alloc_frame(priv.enc_jpg_input_pool_id, stSize, (PIXEL_FORMAT_E)format);
+ if (!priv.enc_jpg_frame) {
+ printf("Alloc frame failed!\r\n");
+ return -1;
+ }
+ }
+
+ switch (format) {
+ case PIXEL_FORMAT_RGB_888:
+ {
+ if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w * 3) {
+ for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height; h++) {
+ memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h, ((uint8_t *)data) + w * h * 3, w * 3);
+ }
+ } else {
+ memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], data, w * h * 3);
+ }
+
+ s32Ret = CVI_VPSS_SendFrame(2, priv.enc_jpg_frame, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VPSS_SendFrame failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+ }
+ break;
+ case PIXEL_FORMAT_NV21:
+ if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w) {
+ for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height * 3 / 2; h ++) {
+ memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h,
+ ((uint8_t *)data) + w * h, w);
+ }
+ } else {
+ memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h * 3 / 2);
+ }
+
+ s32Ret = CVI_VENC_SendFrame(ch, priv.enc_jpg_frame, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_SendFrame failed with %d\n", s32Ret);
+ return s32Ret;
+ }
+ break;
+ default: return -1;
+ }
+
+ priv.enc_jpg_running = 1;
+
+ return s32Ret;
+}
+
+int mmf_enc_jpg_pop(int ch, uint8_t **data, int *size)
+{
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (!priv.enc_jpg_running) {
+ return s32Ret;
+ }
+
+ priv.enc_jpeg_frame.pstPack = (VENC_PACK_S *)malloc(sizeof(VENC_PACK_S) * 1);
+ if (!priv.enc_jpeg_frame.pstPack) {
+ printf("Malloc failed!\r\n");
+ return -1;
+ }
+
+ VENC_CHN_STATUS_S stStatus;
+ s32Ret = CVI_VENC_QueryStatus(ch, &stStatus);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_QueryStatus failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ if (stStatus.u32CurPacks > 0) {
+ s32Ret = CVI_VENC_GetStream(ch, &priv.enc_jpeg_frame, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_GetStream failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+ } else {
+ printf("CVI_VENC_QueryStatus find not pack\r\n");
+ return -1;
+ }
+
+ if (data)
+ *data = priv.enc_jpeg_frame.pstPack[0].pu8Addr;
+ if (size)
+ *size = priv.enc_jpeg_frame.pstPack[0].u32Len;
+
+ return s32Ret;
+}
+
+int mmf_enc_jpg_free(int ch)
+{
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (!priv.enc_jpg_running) {
+ return s32Ret;
+ }
+
+ s32Ret = CVI_VENC_ReleaseStream(ch, &priv.enc_jpeg_frame);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_ReleaseStream failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ if (priv.enc_jpeg_frame.pstPack) {
+ free(priv.enc_jpeg_frame.pstPack);
+ priv.enc_jpeg_frame.pstPack = NULL;
+ }
+
+ priv.enc_jpg_running = 0;
+ return s32Ret;
+}
+
+int mmf_invert_format_to_mmf(int maix_format) {
+ switch (maix_format) {
+ case 0:
+ return PIXEL_FORMAT_RGB_888;
+ case 1:
+ return PIXEL_FORMAT_BGR_888;
+ case 3:
+ return PIXEL_FORMAT_ARGB_8888;
+ case 8:
+ return PIXEL_FORMAT_NV21;
+ case 12:
+ return PIXEL_FORMAT_UINT8_C1;
+ default:
+ return -1;
+ }
+}
+
+void mmf_set_vi_hmirror(int ch, bool en)
+{
+ if (ch > MMF_VI_MAX_CHN) {
+ printf("invalid ch, must be [0, %d)\r\n", ch);
+ return;
+ }
+
+ g_priv.vi_hmirror[ch] = en;
+}
+
+void mmf_get_vi_hmirror(int ch, bool *en)
+{
+ if (ch > MMF_VI_MAX_CHN) {
+ printf("invalid ch, must be [0, %d)\r\n", ch);
+ return;
+ }
+
+ *en = (bool)g_priv.vi_hmirror[ch];
+}
+
+void mmf_set_vi_vflip(int ch, bool en)
+{
+ if (ch > MMF_VI_MAX_CHN) {
+ printf("invalid ch, must be [0, %d)\r\n", ch);
+ return;
+ }
+
+ g_priv.vi_vflip[ch] = en;
+}
+
+void mmf_get_vi_vflip(int ch, bool *en)
+{
+ if (ch > MMF_VI_MAX_CHN) {
+ printf("invalid ch, must be [0, %d)\r\n", ch);
+ return;
+ }
+
+ *en = (bool)g_priv.vi_vflip[ch];
+}
+
+int mmf_add_venc_channel(int ch, mmf_venc_cfg_t *cfg) {
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (ch >= MMF_VENC_MAX_CHN || priv.venc[ch].is_used) {
+ printf("Invalid venc ch:%d\r\n", ch);
+ return -1;
+ }
+
+ switch (cfg->type) {
+ case 2:
+ {
+ VENC_CHN_ATTR_S stVencChnAttr;
+ memset(&stVencChnAttr, 0, sizeof(VENC_CHN_ATTR_S));
+ stVencChnAttr.stVencAttr.enType = PT_H264;
+ stVencChnAttr.stVencAttr.u32MaxPicWidth = cfg->w;
+ stVencChnAttr.stVencAttr.u32MaxPicHeight = cfg->h;
+ stVencChnAttr.stVencAttr.u32BufSize = 1024 * 1024; // 1024Kb
+ stVencChnAttr.stVencAttr.bByFrame = CVI_TRUE;
+ stVencChnAttr.stVencAttr.u32PicWidth = cfg->w;
+ stVencChnAttr.stVencAttr.u32PicHeight = cfg->h;
+ stVencChnAttr.stVencAttr.bEsBufQueueEn = CVI_TRUE;
+ stVencChnAttr.stVencAttr.bIsoSendFrmEn = CVI_TRUE;
+ stVencChnAttr.stGopAttr.enGopMode = VENC_GOPMODE_NORMALP;
+ stVencChnAttr.stGopAttr.stNormalP.s32IPQpDelta = 2;
+ stVencChnAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
+ stVencChnAttr.stRcAttr.stH264Cbr.u32Gop = cfg->gop;
+ stVencChnAttr.stRcAttr.stH264Cbr.u32StatTime = 2;
+ stVencChnAttr.stRcAttr.stH264Cbr.u32SrcFrameRate = cfg->intput_fps;
+ stVencChnAttr.stRcAttr.stH264Cbr.fr32DstFrameRate = cfg->output_fps;
+ stVencChnAttr.stRcAttr.stH264Cbr.u32BitRate = cfg->bitrate;
+ stVencChnAttr.stRcAttr.stH264Cbr.bVariFpsEn = 0;
+ s32Ret = CVI_VENC_CreateChn(ch, &stVencChnAttr);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_CreateChn [%d] failed with %d\n", ch, s32Ret);
+ return s32Ret;
+ }
+
+ VENC_RECV_PIC_PARAM_S stRecvParam;
+ stRecvParam.s32RecvPicNum = -1;
+ s32Ret = CVI_VENC_StartRecvFrame(ch, &stRecvParam);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_StartRecvPic failed with %d\n", s32Ret);
+ return CVI_FAILURE;
+ }
+
+ VENC_RC_PARAM_S stRcParam;
+ s32Ret = CVI_VENC_GetRcParam(ch, &stRcParam);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_GetRcParam failed with %d\n", s32Ret);
+ return s32Ret;
+ }
+ stRcParam.s32FirstFrameStartQp = 35;
+ stRcParam.s32InitialDelay = 1000;
+ stRcParam.stParamH264Cbr.u32MinIprop = 1;
+ stRcParam.stParamH264Cbr.u32MaxIprop = 10;
+ stRcParam.stParamH264Cbr.u32MaxQp = 51;
+ stRcParam.stParamH264Cbr.u32MinQp = 20;
+ stRcParam.stParamH264Cbr.u32MaxIQp = 51;
+ stRcParam.stParamH264Cbr.u32MinIQp = 20;
+ s32Ret = CVI_VENC_SetRcParam(ch, &stRcParam);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_SetRcParam failed with %d\n", s32Ret);
+ return s32Ret;
+ }
+
+ VENC_FRAMELOST_S stFL;
+ s32Ret = CVI_VENC_GetFrameLostStrategy(ch, &stFL);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_GetFrameLostStrategy failed with %d\n", s32Ret);
+ return s32Ret;
+ }
+ stFL.enFrmLostMode = FRMLOST_PSKIP;
+ s32Ret = CVI_VENC_SetFrameLostStrategy(ch, &stFL);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_SetFrameLostStrategy failed with %d\n", s32Ret);
+ return s32Ret;
+ }
+
+ break;
+ }
+ default: printf("Only support h264 encode! type:%d\r\n", cfg->type);
+ return -1;
+ }
+
+ char name[20];
+ snprintf(name, 20, "venc%.1d", ch);
+ uint32_t size = VDEC_GetPicBufferSize((PAYLOAD_TYPE_E)cfg->type, cfg->w, cfg->h, (PIXEL_FORMAT_E)cfg->fmt, DATA_BITWIDTH_8, COMPRESS_MODE_NONE);
+ int pool_id = _create_vb_pool(name, MMF_MOD_VENC, size, 1);
+ if (pool_id < 0) {
+ printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id);
+ return CVI_FAILURE;
+ }
+
+ venc_info_t *info = (venc_info_t *)&priv.venc[ch];
+ info->ch = ch;
+ info->type = cfg->type;
+ info->pool_id = pool_id;
+ memcpy(&info->cfg, cfg, sizeof(mmf_venc_cfg_t));
+ info->capture_frame = (VIDEO_FRAME_INFO_S *)_mmf_alloc_frame(info->pool_id, (SIZE_S){(CVI_U32)cfg->w, (CVI_U32)cfg->h}, (PIXEL_FORMAT_E)cfg->fmt);
+ if (!info->capture_frame) {
+ printf("Alloc frame failed!\r\n");
+ CVI_VENC_DestroyChn(ch);
+ _destroy_vb_pool(pool_id);
+ return -1;
+ }
+ info->is_used = 1;
+ info->is_inited = 1;
+ priv.h265_or_h264_is_used = 1;
+
+ return 0;
+}
+
+int mmf_del_venc_channel(int ch) {
+ if (!priv.venc[ch].is_inited) {
+ return 0;
+ }
+
+ mmf_stream_t stream;
+ if (!mmf_venc_pop(ch, &stream)) {
+ mmf_venc_free(ch);
+ }
+
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ s32Ret = CVI_VENC_StopRecvFrame(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_StopRecvPic failed with %d\n", s32Ret);
+ }
+
+ s32Ret = CVI_VENC_ResetChn(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_ResetChn vechn[%d] failed with %#x!\n", ch, s32Ret);
+ }
+
+ s32Ret = CVI_VENC_DestroyChn(ch);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_DestroyChn [%d] failed with %d\n", ch, s32Ret);
+ }
+
+ if (priv.venc[ch].capture_frame) {
+ _mmf_free_frame(priv.venc[ch].capture_frame);
+ priv.venc[ch].capture_frame = NULL;
+ }
+
+ _destroy_vb_pool(priv.venc[ch].pool_id);
+
+ if (priv.venc[ch].type == 2 || priv.venc[ch].type == 1) {
+ priv.h265_or_h264_is_used = 0;
+ }
+ priv.venc[ch].is_inited = 0;
+ priv.venc[ch].is_used = 0;
+
+ return 0;
+}
+
+int mmf_del_venc_channel_all() {
+ for (int i = 0; i < MMF_VENC_MAX_CHN; i ++) {
+ mmf_del_venc_channel(i);
+ }
+ return 0;
+}
+
+int mmf_venc_push(int ch, uint8_t *data, int w, int h, int format) {
+ int res = 0;
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (ch >= MMF_VENC_MAX_CHN || data == NULL
+ || (format != PIXEL_FORMAT_NV21 && format != PIXEL_FORMAT_RGB_888)) {
+ printf("Invalid param. ch:%d data:%p format:%d\r\n", ch, data, format);
+ return -1;
+ }
+
+ venc_info_t *info = (venc_info_t *)&priv.venc[ch];
+ VIDEO_FRAME_INFO_S *frame_info = (VIDEO_FRAME_INFO_S *)info->capture_frame;
+ if (frame_info == NULL) {
+ printf("frame info is null!\r\n");
+ return -1;
+ }
+
+ switch (format) {
+ case PIXEL_FORMAT_NV21:
+ {
+ if (frame_info->stVFrame.u32Stride[0] != (CVI_U32)w) {
+ for (int h0 = 0; h0 < h * 3 / 2; h0 ++) {
+ memcpy((uint8_t *)frame_info->stVFrame.pu8VirAddr[0] + frame_info->stVFrame.u32Stride[0] * h,
+ ((uint8_t *)data) + w * h0, w);
+ }
+ } else {
+ memcpy((uint8_t *)frame_info->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h * 3 / 2);
+ }
+ }
+ break;
+ default:
+ printf("Not support format:%d\r\n", format);
+ return -1;
+ }
+
+ s32Ret = CVI_VENC_SendFrame(ch, frame_info, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_SendFrame failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ info->is_running = 1;
+
+ return res;
+}
+
+int mmf_venc_pop(int ch, mmf_stream_t *stream) {
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (ch >= MMF_VENC_MAX_CHN || !priv.venc[ch].is_inited) {
+ printf("Invalid venc ch:%d\r\n", ch);
+ return -1;
+ }
+
+ venc_info_t *info = (venc_info_t *)&priv.venc[ch];
+ VENC_STREAM_S *venc_stream = (VENC_STREAM_S *)&priv.venc[ch].capture_stream;
+ if (!info->is_running) {
+ return s32Ret;
+ }
+
+ int fd = CVI_VENC_GetFd(ch);
+ if (fd < 0) {
+ printf("CVI_VENC_GetFd failed with %d\n", fd);
+ return -1;
+ }
+
+ fd_set readFds;
+ struct timeval timeoutVal;
+ FD_ZERO(&readFds);
+ FD_SET(fd, &readFds);
+ timeoutVal.tv_sec = 0;
+ timeoutVal.tv_usec = 80*1000;
+ s32Ret = select(fd + 1, &readFds, NULL, NULL, &timeoutVal);
+ if (s32Ret < 0) {
+ if (errno == EINTR) {
+ printf("VencChn(%d) select failed!\n", ch);
+ return -1;
+ }
+ } else if (s32Ret == 0) {
+ printf("VencChn(%d) select timeout!\n", ch);
+ return -1;
+ }
+
+ venc_stream->pstPack = (VENC_PACK_S *)malloc(sizeof(VENC_PACK_S) * 8);
+ if (!venc_stream->pstPack) {
+ printf("Malloc failed!\r\n");
+ return -1;
+ }
+
+
+ VENC_CHN_STATUS_S stStatus;
+ s32Ret = CVI_VENC_QueryStatus(ch, &stStatus);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_QueryStatus failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ if (stStatus.u32CurPacks > 0) {
+ s32Ret = CVI_VENC_GetStream(ch, venc_stream, 1000);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_GetStream failed with %#x\n", s32Ret);
+ free(venc_stream->pstPack);
+ return s32Ret;
+ }
+ } else {
+ printf("CVI_VENC_QueryStatus find not pack\r\n");
+ free(venc_stream->pstPack);
+ return -1;
+ }
+
+ if (stream) {
+ stream->count = venc_stream->u32PackCount;
+ if (stream->count > 8) {
+ printf("pack count is too large! cnt:%d\r\n", stream->count);
+ free(venc_stream->pstPack);
+ return -1;
+ }
+ for (int i = 0; i < stream->count; i++) {
+ stream->data[i] = venc_stream->pstPack[i].pu8Addr + venc_stream->pstPack[i].u32Offset;
+ stream->data_size[i] = venc_stream->pstPack[i].u32Len - venc_stream->pstPack[i].u32Offset;
+ }
+ }
+
+ return 0;
+}
+
+int mmf_venc_free(int ch) {
+ CVI_S32 s32Ret = CVI_SUCCESS;
+ if (ch >= MMF_VENC_MAX_CHN || !priv.venc[ch].is_inited) {
+ printf("Invalid venc ch:%d\r\n", ch);
+ return -1;
+ }
+
+ venc_info_t *info = (venc_info_t *)&priv.venc[ch];
+ VENC_STREAM_S *venc_stream = (VENC_STREAM_S *)&priv.venc[ch].capture_stream;
+ if (!info->is_running) {
+ return s32Ret;
+ }
+
+ s32Ret = CVI_VENC_ReleaseStream(ch, venc_stream);
+ if (s32Ret != CVI_SUCCESS) {
+ printf("CVI_VENC_ReleaseStream failed with %#x\n", s32Ret);
+ return s32Ret;
+ }
+
+ if (venc_stream->pstPack) {
+ free(venc_stream->pstPack);
+ venc_stream->pstPack = NULL;
+ }
+
+ info->is_running = 0;
+ return s32Ret;
+}
+
+
+
+
\ No newline at end of file
diff --git a/vision/components/vision/CMakeLists.txt b/vision/components/vision/CMakeLists.txt
new file mode 100644
index 0000000..c683409
--- /dev/null
+++ b/vision/components/vision/CMakeLists.txt
@@ -0,0 +1,77 @@
+# Config enable component2 or not in Kconfig
+################# Add include #################
+list(APPEND ADD_INCLUDE "include"
+ "include/base"
+)
+if(PLATFORM_LINUX)
+ list(APPEND ADD_PRIVATE_INCLUDE "port/linux")
+elseif(PLATFORM_MAIXCAM)
+ list(APPEND ADD_PRIVATE_INCLUDE "port/maixcam")
+endif()
+list(APPEND ADD_PRIVATE_INCLUDE "include_private")
+###############################################
+
+############## Add source files ###############
+# list(APPEND ADD_SRCS "src/lib2.c"
+# )
+# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c")
+# FILE(GLOB EXTRA_SRC "src/*.c")
+# list(APPEND ADD_SRCS ${EXTRA_SRC})
+# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS
+append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS
+if(PLATFORM_LINUX)
+ append_srcs_dir(ADD_SRCS "port/linux")
+elseif(PLATFORM_MAIXCAM)
+ append_srcs_dir(ADD_SRCS "port/maixcam")
+endif()
+# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c")
+# set(ADD_ASM_SRCS "src/asm.S")
+# list(APPEND ADD_SRCS ${ADD_ASM_SRCS})
+# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language
+# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB")
+###############################################
+
+
+###### Add required/dependent components ######
+list(APPEND ADD_REQUIREMENTS basic opencv opencv_freetype websocket peripheral)
+list(APPEND ADD_REQUIREMENTS omv)
+list(APPEND ADD_REQUIREMENTS kvm_mmf)
+###############################################
+
+###### Add link search path for requirements/libs ######
+# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib")
+# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here
+# set (OpenCV_DIR opencv/lib/cmake/opencv4)
+# find_package(OpenCV REQUIRED)
+###############################################
+
+############ Add static libs ##################
+# list(APPEND ADD_STATIC_LIB "lib/libtest.a")
+###############################################
+
+############ Add dynamic libs ##################
+# list(APPEND ADD_DYNAMIC_LIB "lib/arch/v831/libmaix_nn.so"
+# "lib/arch/v831/libmaix_cam.so"
+# )
+###############################################
+
+#### Add compile option for this component ####
+#### Just for this component, won't affect other
+#### modules, including component that depend
+#### on this component
+# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1)
+
+#### Add compile option for this component
+#### and components denpend on this component
+# list(APPEND ADD_DEFINITIONS -DAAAAA222=1
+# -DAAAAA333=1)
+###############################################
+
+############ Add static libs ##################
+#### Update parent's variables like CMAKE_C_LINK_FLAGS
+# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE)
+###############################################
+
+# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib
+register_component()
+
diff --git a/vision/components/vision/Kconfig b/vision/components/vision/Kconfig
new file mode 100644
index 0000000..e69de29
diff --git a/vision/components/vision/include/base/maix_camera_base.hpp b/vision/components/vision/include/base/maix_camera_base.hpp
new file mode 100644
index 0000000..2e4eb79
--- /dev/null
+++ b/vision/components/vision/include/base/maix_camera_base.hpp
@@ -0,0 +1,109 @@
+/**
+ * @file maix_camera_base.hpp
+ * @brief Maix camera SDL implementation
+ * @author neucrack@sipeed.com
+ * @license Apache 2.0 Sipeed Ltd
+ * @update date 2023-10-23 Create by neucrack
+*/
+
+#pragma once
+
+#include
+#include "maix_image.hpp"
+#include "maix_err.hpp"
+
+namespace maix::camera
+{
+ class CameraBase
+ {
+ public:
+ /**
+ * @brief Construct a new Camera object
+ * @param device camera device name, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device
+ * @param width camera width, by default(value is -1) means auto detect,
+ * if width > max device supported width, will auto set to max device supported width
+ * @param height camera height, by default(value is -1) means auto detect,
+ * if height > max device supported height, will auto set to max device supported height
+ * @param format camera format, by default(value is FMT_RGB888)
+ * @param buff_num camera buffer number, by default(value is 3)
+ */
+ CameraBase(const char *device = nullptr, int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3){};
+
+ /**
+ * @brief Judge if the given format is supported by the camera
+ */
+ virtual bool is_support_format(image::Format format) = 0;
+
+ /**
+ * @brief open camera device
+ * @param width camera width, by default(value is -1) means auto detect,
+ * if width > max device supported width, will auto set to max device supported width
+ * @param height camera height, by default(value is -1) means auto detect,
+ * if height > max device supported height, will auto set to max device supported height
+ * @param format camera format, by default(value is FMT_RGB888)
+ * @param buff_num camera buffer number, by default(value is 3)
+ * @return error code
+ */
+ virtual err::Err open(int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3) = 0;
+
+ /**
+ * @brief read a frame from camera
+ * @param buff buffer to store image data, if NULL, will alloc a new buffer
+ * @return image data
+ */
+ virtual image::Image *read(void *buff = NULL, size_t buff_size = 0) = 0;
+
+ /**
+ * @brief close camera device
+ * @return none
+ */
+ virtual void close() = 0;
+
+ /**
+ * Add a new channel and return a new Camera object, you can use close() to close this channel.
+ * @param width camera width, default is -1, means auto, mostly means max width of camera support
+ * @param height camera height, default is -1, means auto, mostly means max height of camera support
+ * @param format camera output format, default is RGB888
+ * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame,
+ * more than one buffer will accelerate image read speed, but will cost more memory.
+ * @return new Camera object
+ */
+ virtual camera::CameraBase *add_channel(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int buff_num = 3) = 0;
+
+ /**
+ * @brief clear all buffer
+ * @return none
+ */
+ virtual void clear_buff() = 0;
+
+ /**
+ * @brief check camera device is opened or not
+ * @return opened or not, bool type
+ */
+ virtual bool is_opened() = 0;
+
+ /**
+ * Get camera supported channels(layers)
+ */
+ virtual int get_ch_nums() = 0;
+
+ /**
+ * Get channel number of camera.
+ */
+ virtual int get_channel() = 0;
+
+ /**
+ * Set/Get camera mirror
+ * @param en enable/disable mirror
+ */
+ virtual int hmirror(int en) = 0;
+
+ /**
+ * Set/Get camera flip
+ * @param en enable/disable flip
+ */
+ virtual int vflip(int en) = 0;
+ };
+
+}
+
diff --git a/vision/components/vision/include/maix_camera.hpp b/vision/components/vision/include/maix_camera.hpp
new file mode 100644
index 0000000..ef15de3
--- /dev/null
+++ b/vision/components/vision/include/maix_camera.hpp
@@ -0,0 +1,305 @@
+/**
+ * @author neucrack@sipeed, lxowalle@sipeed
+ * @copyright Sipeed Ltd 2023-
+ * @license Apache 2.0
+ * @update 2023.9.8: Add framework, create this file.
+ */
+
+#pragma once
+
+#include "maix_tensor.hpp"
+#include "maix_log.hpp"
+#include "maix_image.hpp"
+#include "maix_err.hpp"
+#include "maix_camera_base.hpp"
+#include
+#include