feature: provide HDMI status in /api/vm/hdmi (#859)

* feature: provide HDMI status in  /api/vm/hdmi

* fix: return false when HDMI idle in  /api/vm/hdmi
This commit is contained in:
watermeko
2026-08-07 09:43:22 +08:00
committed by GitHub
parent 761b02ed7e
commit 757e0f61c5
10 changed files with 165 additions and 25 deletions

View File

@@ -157,7 +157,7 @@ printf '0\n' > "$STAGE/kvm/now_fps"
printf '60\n' > "$STAGE/kvm/qlty"
printf '1920\n' > "$STAGE/kvm/width"
printf '1080\n' > "$STAGE/kvm/height"
printf '1\n' > "$STAGE/kvm/state"
printf '0\n' > "$STAGE/kvm/state"
printf 'mjpeg\n' > "$STAGE/kvm/type"
printf '0' > "$STAGE/kvm/res"

View File

@@ -116,6 +116,16 @@ func (k *KvmVision) SetHDMI(enable bool) int {
return result
}
func (k *KvmVision) HasHDMISignal() bool {
k.mutex.RLock()
defer k.mutex.RUnlock()
if k.closed {
return false
}
return C.kvmv_hdmi_signal_active() != 0
}
func (k *KvmVision) SetGop(gop uint8) {
k.mutex.RLock()
defer k.mutex.RUnlock()

Binary file not shown.

View File

@@ -21,6 +21,7 @@ require (
github.com/spf13/viper v1.19.0
github.com/unrolled/secure v1.15.0
golang.org/x/crypto v0.45.0
golang.org/x/sys v0.41.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -79,7 +80,6 @@ require (
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

View File

@@ -66,6 +66,7 @@ void set_h264_gop(uint8_t _gop);
void set_frame_detact(uint8_t _frame_detact);
void kvmv_deinit();
uint8_t kvmv_hdmi_control(uint8_t _en);
uint8_t kvmv_hdmi_signal_active(void);
#ifdef __cplusplus
}

View File

@@ -99,6 +99,7 @@ type GetOLEDRsp struct {
type GetGetHdmiStateRsp struct {
Enabled bool `json:"enabled"`
Signal bool `json:"signal"`
IdleTimeout int `json:"idleTimeout"`
}

View File

@@ -27,6 +27,7 @@ var (
setHDMI = func(enabled bool) { common.GetKvmVision().SetHDMI(enabled) }
isHdmiDisabled = utils.IsHdmiDisabled
getHdmiIdleTimeout = utils.GetHDMIIdleTimeout
getHdmiSignal = func() bool { return common.GetKvmVision().HasHDMISignal() }
)
func (s *Service) ResetHdmi(c *gin.Context) {
@@ -63,10 +64,12 @@ func (s *Service) DisableHdmi(c *gin.Context) {
func (s *Service) GetHdmiState(c *gin.Context) {
var rsp proto.Response
enabled := !isHdmiDisabled()
rsp.OkRspWithData(c, &proto.GetGetHdmiStateRsp{
Enabled: !utils.IsHdmiDisabled(),
IdleTimeout: utils.GetHDMIIdleTimeout(),
Enabled: enabled,
Signal: enabled && getHdmiSignal(),
IdleTimeout: getHdmiIdleTimeout(),
})
log.Debug("get hdmi state")

View File

@@ -74,9 +74,10 @@ void set_h264_gop(uint8_t _gop);
void set_frame_detact(uint8_t _frame_detact);
void kvmv_deinit();
uint8_t kvmv_hdmi_control(uint8_t _en);
uint8_t kvmv_hdmi_signal_active(void);
#ifdef __cplusplus
}
#endif
#endif // KVM_VISION_H_
#endif // KVM_VISION_H_

View File

@@ -13,6 +13,9 @@
#include "vi_state_shared.hpp"
#include "internal/vi_state_writer.hpp"
#include <errno.h>
#include <sys/stat.h>
#define default_venc_chn 1
#define VENC_MJPEG 0
@@ -43,6 +46,7 @@
#define vi_height_path "/kvmapp/kvm/height"
#define hdmi_mode_path "/etc/kvm/hdmi_mode"
#define hdmi_state_path "/proc/lt_int"
#define hdmi_signal_file_path "/kvmapp/kvm/state"
#define watchdog_mode_path "/etc/kvm/watchdog"
#define watchdog_temp_path "/tmp/watchdog"
#define watchdog_file "/tmp/nanokvm_wd"
@@ -53,6 +57,7 @@
#define LT6911_WRITE 0x00
pthread_mutex_t vi_mutex;
pthread_mutex_t hdmi_signal_mutex = PTHREAD_MUTEX_INITIALIZER;
static char NanoKVM_edit[] = {
0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x41,0x0C,0x33,0xC2,0x66,0xBA,0x00,0x00,
@@ -77,7 +82,6 @@ typedef struct {
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 = 0;
uint8_t display;
@@ -125,6 +129,83 @@ uint8_t kvmv_data_buffer_index = 0;
uint8_t debug_en = 0;
uint8_t last_vi_state_code = 0;
uint32_t last_vi_state_refresh_ms = 0;
uint8_t hdmi_capture_enabled = 0;
uint8_t hdmi_signal_active = 0;
void debug(const char *format, ...);
static bool write_hdmi_signal_file(uint8_t active)
{
char temp_path[] = "/kvmapp/kvm/.state.tmp.XXXXXX";
const char *state = active != 0 ? "1\n" : "0\n";
const size_t state_size = 2;
int fd = mkstemp(temp_path);
if(fd < 0){
debug("[hdmi] failed to create state file: %d\n", errno);
return false;
}
int failure = 0;
if(fchmod(fd, 0644) != 0){
failure = errno;
}
size_t written = 0;
while(failure == 0 && written < state_size){
ssize_t result = write(fd, state + written, state_size - written);
if(result > 0){
written += (size_t)result;
} else if(result < 0 && errno == EINTR){
continue;
} else {
failure = result < 0 ? errno : EIO;
}
}
if(failure == 0 && fsync(fd) != 0){
failure = errno;
}
if(close(fd) != 0 && failure == 0){
failure = errno;
}
if(failure == 0 && rename(temp_path, hdmi_signal_file_path) != 0){
failure = errno;
}
if(failure != 0){
unlink(temp_path);
debug("[hdmi] failed to publish state file: %d\n", failure);
return false;
}
return true;
}
static void set_hdmi_signal_state(uint8_t active)
{
pthread_mutex_lock(&hdmi_signal_mutex);
uint8_t enabled = __atomic_load_n(&hdmi_capture_enabled, __ATOMIC_ACQUIRE);
uint8_t next = enabled != 0 && active != 0 ? 1 : 0;
uint8_t previous = __atomic_exchange_n(&hdmi_signal_active, next, __ATOMIC_ACQ_REL);
if(previous != next){
write_hdmi_signal_file(next);
}
pthread_mutex_unlock(&hdmi_signal_mutex);
}
static void set_hdmi_capture_enabled(uint8_t enabled)
{
pthread_mutex_lock(&hdmi_signal_mutex);
__atomic_store_n(&hdmi_capture_enabled, enabled != 0 ? 1 : 0, __ATOMIC_RELEASE);
__atomic_store_n(&hdmi_signal_active, 0, __ATOMIC_RELEASE);
write_hdmi_signal_file(0);
pthread_mutex_unlock(&hdmi_signal_mutex);
}
static void set_hdmi_detection_state(uint8_t active)
{
__atomic_store_n(&kvmv_cfg.hdmi_cable_state, active != 0 ? 1 : 0, __ATOMIC_RELEASE);
set_hdmi_signal_state(active);
}
void debug(const char *format, ...)
{
if(debug_en){
@@ -136,8 +217,9 @@ uint8_t refresh_vi_state()
{
// The driver read blocks for about a second; mark the deadline before it so
// that the publication cadence measures wall-clock time, not read time plus it.
last_vi_state_refresh_ms = vi_state_shared::monotonic_ms();
last_vi_state_refresh_ms = vi_state_shared::monotonic_ms();
last_vi_state_code = vi_state_shared::refresh();
set_hdmi_detection_state(last_vi_state_code == 1 || last_vi_state_code >= 3);
return last_vi_state_code;
}
@@ -1176,7 +1258,7 @@ void* vi_subsystem_detection(void * arg)
if(lt6911_get_hdmi_res()){
// hdmi get res
debug("[hdmi] C HDMI cable insertion!\n");
kvmv_cfg.hdmi_cable_state = 1;
set_hdmi_detection_state(1);
kvmv_cfg.hdmi_res_type = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height);
if (kvmv_cfg.hdmi_res_type == NEW_RES) kvmv_cfg.reopen_cam_flag = 1;
else if (kvmv_cfg.hdmi_res_type == UNKNOWN_RES){
@@ -1188,7 +1270,7 @@ void* vi_subsystem_detection(void * arg)
} else {
// HDMI res = 0*0/x*0
debug("[hdmi] C HDMI cable unplugged!\n");
kvmv_cfg.hdmi_cable_state = 0;
set_hdmi_detection_state(0);
}
lt6911_disable();
}
@@ -1201,7 +1283,7 @@ void* vi_subsystem_detection(void * arg)
if(lt6911_get_hdmi_res()){
// hdmi get res
debug("[hdmi] UXC HDMI cable insertion!\n");
kvmv_cfg.hdmi_cable_state = 1;
set_hdmi_detection_state(1);
kvmv_cfg.hdmi_res_type = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height);
if (kvmv_cfg.hdmi_res_type == NEW_RES) kvmv_cfg.reopen_cam_flag = 1;
else if (kvmv_cfg.hdmi_res_type == UNKNOWN_RES){
@@ -1213,7 +1295,7 @@ void* vi_subsystem_detection(void * arg)
} else {
// HDMI res = 0*0/x*0
debug("[hdmi] UXC HDMI cable unplugged!\n");
kvmv_cfg.hdmi_cable_state = 0;
set_hdmi_detection_state(0);
}
lt6911_disable();
}
@@ -1226,7 +1308,7 @@ void* vi_subsystem_detection(void * arg)
if(lt6911_get_hdmi_res()){
// hdmi get res
debug("[hdmi] D HDMI cable insertion!\n");
kvmv_cfg.hdmi_cable_state = 1;
set_hdmi_detection_state(1);
kvmv_cfg.hdmi_res_type = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height);
if (kvmv_cfg.hdmi_res_type == NEW_RES) kvmv_cfg.reopen_cam_flag = 1;
else if (kvmv_cfg.hdmi_res_type == UNKNOWN_RES){
@@ -1238,7 +1320,7 @@ void* vi_subsystem_detection(void * arg)
} else {
// HDMI res = 0*0/x*0
debug("[hdmi] D HDMI cable unplugged!\n");
kvmv_cfg.hdmi_cable_state = 0;
set_hdmi_detection_state(0);
}
lt6911_disable();
}
@@ -1639,7 +1721,7 @@ void kvmv_init(uint8_t _debug_info_en)
uint8_t check_kvmv(uint8_t _try_num)
{
if(kvmv_cfg.hdmi_cable_state == 0){
if(__atomic_load_n(&kvmv_cfg.hdmi_cable_state, __ATOMIC_ACQUIRE) == 0){
debug("[kvmv]HDMI Cable not exist!\n");
return 0;
}
@@ -1788,17 +1870,8 @@ int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _ql
}
}
}
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;
delete img;
debug("[kvmv]can`t get img...\n");
continue;
// pthread_mutex_unlock(&vi_mutex);
@@ -1905,6 +1978,7 @@ void free_all_kvmv_data()
void kvmv_deinit()
{
set_hdmi_capture_enabled(0);
pthread_mutex_destroy(&vi_mutex);
kvmv_cfg.try_exit_thread = 1;
cam->close();
@@ -1914,6 +1988,10 @@ void kvmv_deinit()
uint8_t kvmv_hdmi_control(uint8_t _en)
{
if(_en == 0){
set_hdmi_capture_enabled(0);
}
if(kvmv_cfg.hw_version == 0){
FILE *fp;
@@ -1935,6 +2013,9 @@ uint8_t kvmv_hdmi_control(uint8_t _en)
}
if(kvmv_cfg.hw_version != 'p'){
debug("[kvmv]Hardware not support!\n");
if(_en != 0){
set_hdmi_capture_enabled(1);
}
return -1;
}
if(access("/sys/class/gpio/gpio451/value", F_OK) != 0){
@@ -1954,7 +2035,15 @@ uint8_t kvmv_hdmi_control(uint8_t _en)
// Reopening it here or from kvmv_read_img can exhaust the carveout
// heap while the HDMI detector is transitioning.
kvmv_cfg.fresh_frame_count = fresh_frame_discard_count;
set_hdmi_capture_enabled(1);
return 0;
}
return -1;
}
uint8_t kvmv_hdmi_signal_active()
{
uint8_t enabled = __atomic_load_n(&hdmi_capture_enabled, __ATOMIC_ACQUIRE);
uint8_t active = __atomic_load_n(&hdmi_signal_active, __ATOMIC_ACQUIRE);
return enabled != 0 && active != 0 ? 1 : 0;
}

View File

@@ -1,12 +1,47 @@
#include "config.h"
#include "system_init.h"
#include <errno.h>
#include <sys/stat.h>
using namespace maix;
using namespace maix::sys;
extern kvm_sys_state_t kvm_sys_state;
extern kvm_oled_state_t kvm_oled_state;
static void write_hdmi_state(uint8_t active)
{
char temp_path[] = "/kvmapp/kvm/.state.init.XXXXXX";
const char *state = active != 0 ? "1\n" : "0\n";
int fd = mkstemp(temp_path);
if(fd < 0){
printf("failed to create HDMI state file: %d\n", errno);
return;
}
int failure = 0;
if(fchmod(fd, 0644) != 0){
failure = errno;
}
if(failure == 0 && write(fd, state, 2) != 2){
failure = errno != 0 ? errno : EIO;
}
if(failure == 0 && fsync(fd) != 0){
failure = errno;
}
if(close(fd) != 0 && failure == 0){
failure = errno;
}
if(failure == 0 && rename(temp_path, "/kvmapp/kvm/state") != 0){
failure = errno;
}
if(failure != 0){
unlink(temp_path);
printf("failed to publish HDMI state file: %d\n", failure);
}
}
uint8_t get_hdmi_version()
{
FILE *fp;
@@ -101,7 +136,7 @@ void new_app_init(void)
system("echo 2000 > /kvmapp/kvm/qlty");
system("echo 720 > /kvmapp/kvm/res");
system("echo h264 > /kvmapp/kvm/type");
system("echo 0 > /kvmapp/kvm/state");
write_hdmi_state(0);
system("touch /etc/kvm/frame_detact");
// rm jpg_stream & kvm_stream