mirror of https://github.com/jetkvm/kvm.git
Merge b844a8a3e8 into 74e64f69a7
This commit is contained in:
commit
3aea87709c
|
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
#define VIDEO_DEV "/dev/video0"
|
#define VIDEO_DEV "/dev/video0"
|
||||||
#define SUB_DEV "/dev/v4l-subdev2"
|
#define SUB_DEV "/dev/v4l-subdev2"
|
||||||
|
#define SLEEP_MODE_FILE "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode"
|
||||||
|
|
||||||
#define RK_ALIGN(x, a) (((x) + (a)-1) & ~((a)-1))
|
#define RK_ALIGN(x, a) (((x) + (a)-1) & ~((a)-1))
|
||||||
#define RK_ALIGN_2(x) RK_ALIGN(x, 2)
|
#define RK_ALIGN_2(x) RK_ALIGN(x, 2)
|
||||||
|
|
@ -39,6 +40,7 @@ int sub_dev_fd = -1;
|
||||||
#define VENC_CHANNEL 0
|
#define VENC_CHANNEL 0
|
||||||
MB_POOL memPool = MB_INVALID_POOLID;
|
MB_POOL memPool = MB_INVALID_POOLID;
|
||||||
|
|
||||||
|
bool sleep_mode_available = false;
|
||||||
bool should_exit = false;
|
bool should_exit = false;
|
||||||
float quality_factor = 1.0f;
|
float quality_factor = 1.0f;
|
||||||
|
|
||||||
|
|
@ -51,6 +53,34 @@ RK_U64 get_us()
|
||||||
return (RK_U64)time.tv_sec * 1000000 + (RK_U64)time.tv_nsec / 1000; /* microseconds */
|
return (RK_U64)time.tv_sec * 1000000 + (RK_U64)time.tv_nsec / 1000; /* microseconds */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ensure_sleep_mode_disabled()
|
||||||
|
{
|
||||||
|
if (!sleep_mode_available)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = open(SLEEP_MODE_FILE, O_RDWR);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
log_error("Failed to open sleep mode file: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
write(fd, "0", 1);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void detect_sleep_mode()
|
||||||
|
{
|
||||||
|
if (access(SLEEP_MODE_FILE, F_OK) != 0) {
|
||||||
|
sleep_mode_available = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sleep_mode_available = true;
|
||||||
|
ensure_sleep_mode_disabled();
|
||||||
|
}
|
||||||
|
|
||||||
double calculate_bitrate(float bitrate_factor, int width, int height)
|
double calculate_bitrate(float bitrate_factor, int width, int height)
|
||||||
{
|
{
|
||||||
const int32_t base_bitrate_high = 2000;
|
const int32_t base_bitrate_high = 2000;
|
||||||
|
|
@ -192,6 +222,8 @@ pthread_t *format_thread = NULL;
|
||||||
|
|
||||||
int video_init()
|
int video_init()
|
||||||
{
|
{
|
||||||
|
detect_sleep_mode();
|
||||||
|
|
||||||
if (RK_MPI_SYS_Init() != RK_SUCCESS)
|
if (RK_MPI_SYS_Init() != RK_SUCCESS)
|
||||||
{
|
{
|
||||||
log_error("RK_MPI_SYS_Init failed");
|
log_error("RK_MPI_SYS_Init failed");
|
||||||
|
|
@ -301,11 +333,29 @@ static void *venc_read_stream(void *arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t detected_width, detected_height;
|
uint32_t detected_width, detected_height;
|
||||||
bool detected_signal = false, streaming_flag = false;
|
bool detected_signal = false, streaming_flag = false, streaming_stopped = true;
|
||||||
|
|
||||||
pthread_t *streaming_thread = NULL;
|
pthread_t *streaming_thread = NULL;
|
||||||
pthread_mutex_t streaming_mutex = PTHREAD_MUTEX_INITIALIZER;
|
pthread_mutex_t streaming_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
bool get_streaming_flag()
|
||||||
|
{
|
||||||
|
log_info("getting streaming flag");
|
||||||
|
pthread_mutex_lock(&streaming_mutex);
|
||||||
|
bool flag = streaming_flag;
|
||||||
|
pthread_mutex_unlock(&streaming_mutex);
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_streaming_flag(bool flag)
|
||||||
|
{
|
||||||
|
log_info("setting streaming flag to %d", flag);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&streaming_mutex);
|
||||||
|
streaming_flag = flag;
|
||||||
|
pthread_mutex_unlock(&streaming_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
void write_buffer_to_file(const uint8_t *buffer, size_t length, const char *filename)
|
void write_buffer_to_file(const uint8_t *buffer, size_t length, const char *filename)
|
||||||
{
|
{
|
||||||
FILE *file = fopen(filename, "wb");
|
FILE *file = fopen(filename, "wb");
|
||||||
|
|
@ -319,6 +369,8 @@ void *run_video_stream(void *arg)
|
||||||
|
|
||||||
log_info("running video stream");
|
log_info("running video stream");
|
||||||
|
|
||||||
|
streaming_stopped = false;
|
||||||
|
|
||||||
while (streaming_flag)
|
while (streaming_flag)
|
||||||
{
|
{
|
||||||
if (detected_signal == false)
|
if (detected_signal == false)
|
||||||
|
|
@ -401,7 +453,7 @@ void *run_video_stream(void *arg)
|
||||||
{
|
{
|
||||||
log_error("get mb blk failed!");
|
log_error("get mb blk failed!");
|
||||||
close(video_dev_fd);
|
close(video_dev_fd);
|
||||||
return ;
|
return (void *)errno;
|
||||||
}
|
}
|
||||||
log_info("Got memory block for buffer %d", i);
|
log_info("Got memory block for buffer %d", i);
|
||||||
|
|
||||||
|
|
@ -551,8 +603,11 @@ void *run_video_stream(void *arg)
|
||||||
log_info("closing video capture device %s", VIDEO_DEV);
|
log_info("closing video capture device %s", VIDEO_DEV);
|
||||||
close(video_dev_fd);
|
close(video_dev_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("video stream thread exiting");
|
log_info("video stream thread exiting");
|
||||||
|
|
||||||
|
streaming_stopped = true;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,56 +637,75 @@ void video_shutdown()
|
||||||
log_info("Destroyed streaming mutex");
|
log_info("Destroyed streaming mutex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void video_start_streaming()
|
void video_start_streaming()
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&streaming_mutex);
|
log_info("starting video streaming");
|
||||||
if (streaming_thread != NULL)
|
if (streaming_thread != NULL)
|
||||||
{
|
{
|
||||||
|
if (streaming_stopped == true) {
|
||||||
|
log_error("video streaming already stopped but streaming_thread is not NULL");
|
||||||
|
assert(streaming_stopped == true);
|
||||||
|
}
|
||||||
log_warn("video streaming already started");
|
log_warn("video streaming already started");
|
||||||
goto cleanup;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_t *new_thread = malloc(sizeof(pthread_t));
|
pthread_t *new_thread = malloc(sizeof(pthread_t));
|
||||||
if (new_thread == NULL)
|
if (new_thread == NULL)
|
||||||
{
|
{
|
||||||
log_error("Failed to allocate memory for streaming thread");
|
log_error("Failed to allocate memory for streaming thread");
|
||||||
goto cleanup;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
streaming_flag = true;
|
set_streaming_flag(true);
|
||||||
int result = pthread_create(new_thread, NULL, run_video_stream, NULL);
|
int result = pthread_create(new_thread, NULL, run_video_stream, NULL);
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
log_error("Failed to create streaming thread: %s", strerror(result));
|
log_error("Failed to create streaming thread: %s", strerror(result));
|
||||||
streaming_flag = false;
|
set_streaming_flag(false);
|
||||||
free(new_thread);
|
free(new_thread);
|
||||||
goto cleanup;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only set streaming_thread after successful creation, and before unlocking the mutex
|
// Only set streaming_thread after successful creation
|
||||||
streaming_thread = new_thread;
|
streaming_thread = new_thread;
|
||||||
cleanup:
|
|
||||||
pthread_mutex_unlock(&streaming_mutex);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void video_stop_streaming()
|
void video_stop_streaming()
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&streaming_mutex);
|
if (streaming_thread == NULL) {
|
||||||
if (streaming_thread != NULL)
|
log_info("video streaming already stopped");
|
||||||
{
|
return;
|
||||||
streaming_flag = false;
|
|
||||||
log_info("stopping video streaming");
|
|
||||||
// wait 100ms for the thread to exit
|
|
||||||
usleep(1000000);
|
|
||||||
log_info("waiting for video streaming thread to exit");
|
|
||||||
pthread_join(*streaming_thread, NULL);
|
|
||||||
free(streaming_thread);
|
|
||||||
streaming_thread = NULL;
|
|
||||||
log_info("video streaming stopped");
|
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&streaming_mutex);
|
|
||||||
|
log_info("stopping video streaming");
|
||||||
|
set_streaming_flag(false);
|
||||||
|
|
||||||
|
log_info("waiting for video streaming thread to exit");
|
||||||
|
int attempts = 0;
|
||||||
|
while (!streaming_stopped && attempts < 30) {
|
||||||
|
usleep(100000); // 100ms
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
if (!streaming_stopped) {
|
||||||
|
log_error("video streaming thread did not exit after 30s");
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_join(*streaming_thread, NULL);
|
||||||
|
free(streaming_thread);
|
||||||
|
streaming_thread = NULL;
|
||||||
|
|
||||||
|
log_info("video streaming stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
void video_restart_streaming()
|
||||||
|
{
|
||||||
|
if (get_streaming_flag() == true)
|
||||||
|
{
|
||||||
|
log_info("restarting video streaming");
|
||||||
|
video_stop_streaming();
|
||||||
|
}
|
||||||
|
video_start_streaming();
|
||||||
}
|
}
|
||||||
|
|
||||||
void *run_detect_format(void *arg)
|
void *run_detect_format(void *arg)
|
||||||
|
|
@ -650,6 +724,8 @@ void *run_detect_format(void *arg)
|
||||||
|
|
||||||
while (!should_exit)
|
while (!should_exit)
|
||||||
{
|
{
|
||||||
|
ensure_sleep_mode_disabled();
|
||||||
|
|
||||||
memset(&dv_timings, 0, sizeof(dv_timings));
|
memset(&dv_timings, 0, sizeof(dv_timings));
|
||||||
if (ioctl(sub_dev_fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) != 0)
|
if (ioctl(sub_dev_fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) != 0)
|
||||||
{
|
{
|
||||||
|
|
@ -693,18 +769,8 @@ void *run_detect_format(void *arg)
|
||||||
detected_height = dv_timings.bt.height;
|
detected_height = dv_timings.bt.height;
|
||||||
detected_signal = true;
|
detected_signal = true;
|
||||||
video_report_format(true, NULL, detected_width, detected_height, frames_per_second);
|
video_report_format(true, NULL, detected_width, detected_height, frames_per_second);
|
||||||
pthread_mutex_lock(&streaming_mutex);
|
|
||||||
if (streaming_flag == true)
|
video_restart_streaming();
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&streaming_mutex);
|
|
||||||
log_info("restarting on going video streaming");
|
|
||||||
video_stop_streaming();
|
|
||||||
video_start_streaming();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&streaming_mutex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&ev, 0, sizeof(ev));
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
|
@ -731,19 +797,7 @@ void video_set_quality_factor(float factor)
|
||||||
quality_factor = factor;
|
quality_factor = factor;
|
||||||
|
|
||||||
// TODO: update venc bitrate without stopping streaming
|
// TODO: update venc bitrate without stopping streaming
|
||||||
|
video_restart_streaming();
|
||||||
pthread_mutex_lock(&streaming_mutex);
|
|
||||||
if (streaming_flag == true)
|
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&streaming_mutex);
|
|
||||||
log_info("restarting on going video streaming due to quality factor change");
|
|
||||||
video_stop_streaming();
|
|
||||||
video_start_streaming();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&streaming_mutex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float video_get_quality_factor() {
|
float video_get_quality_factor() {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ import (
|
||||||
|
|
||||||
const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode"
|
const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode"
|
||||||
|
|
||||||
|
// DefaultEDID is the default EDID for the video stream.
|
||||||
|
const DefaultEDID = "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"
|
||||||
|
|
||||||
// VideoState is the state of the video stream.
|
// VideoState is the state of the video stream.
|
||||||
type VideoState struct {
|
type VideoState struct {
|
||||||
Ready bool `json:"ready"`
|
Ready bool `json:"ready"`
|
||||||
|
|
@ -87,6 +90,10 @@ func (n *Native) VideoSetEDID(edid string) error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
||||||
|
if edid == "" {
|
||||||
|
edid = DefaultEDID
|
||||||
|
}
|
||||||
|
|
||||||
return videoSetEDID(edid)
|
return videoSetEDID(edid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,6 @@ func rpcGetEDID() (string, error) {
|
||||||
func rpcSetEDID(edid string) error {
|
func rpcSetEDID(edid string) error {
|
||||||
if edid == "" {
|
if edid == "" {
|
||||||
logger.Info().Msg("Restoring EDID to default")
|
logger.Info().Msg("Restoring EDID to default")
|
||||||
edid = "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"
|
|
||||||
} else {
|
} else {
|
||||||
logger.Info().Str("edid", edid).Msg("Setting EDID")
|
logger.Info().Str("edid", edid).Msg("Setting EDID")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,11 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
nativeInstance.Start()
|
nativeInstance.Start()
|
||||||
|
if err := nativeInstance.VideoSetEDID(config.EdidString); err != nil {
|
||||||
|
nativeLogger.Warn().Err(err).Msg("error setting EDID")
|
||||||
|
}
|
||||||
|
|
||||||
if os.Getenv("JETKVM_CRASH_TESTING") == "1" {
|
if os.Getenv("JETKVM_CRASH_TESTING") == "1" {
|
||||||
nativeInstance.DoNotUseThisIsForCrashTestingOnly()
|
nativeInstance.DoNotUseThisIsForCrashTestingOnly()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { SettingsItem } from "@components/SettingsItem";
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
|
import { SettingsSectionHeader } from "@components/SettingsSectionHeader";
|
||||||
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
|
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
|
||||||
|
import { Checkbox } from "@components/Checkbox";
|
||||||
|
|
||||||
import notifications from "../notifications";
|
import notifications from "../notifications";
|
||||||
import { UsbInfoSetting } from "../components/UsbInfoSetting";
|
import { UsbInfoSetting } from "../components/UsbInfoSetting";
|
||||||
|
|
@ -15,6 +17,7 @@ export default function SettingsHardwareRoute() {
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const { setDisplayRotation } = useSettingsStore();
|
const { setDisplayRotation } = useSettingsStore();
|
||||||
|
const [powerSavingEnabled, setPowerSavingEnabled] = useState(false);
|
||||||
|
|
||||||
const handleDisplayRotationChange = (rotation: string) => {
|
const handleDisplayRotationChange = (rotation: string) => {
|
||||||
setDisplayRotation(rotation);
|
setDisplayRotation(rotation);
|
||||||
|
|
@ -58,6 +61,21 @@ export default function SettingsHardwareRoute() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePowerSavingChange = (enabled: boolean) => {
|
||||||
|
setPowerSavingEnabled(enabled);
|
||||||
|
const duration = enabled ? 90 : -1;
|
||||||
|
send("setVideoSleepMode", { duration }, (resp: JsonRpcResponse) => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
notifications.error(
|
||||||
|
`Failed to set power saving mode: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
setPowerSavingEnabled(!enabled); // Revert on error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notifications.success(`Power saving mode ${enabled ? "enabled" : "disabled"}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
send("getBacklightSettings", {}, (resp: JsonRpcResponse) => {
|
send("getBacklightSettings", {}, (resp: JsonRpcResponse) => {
|
||||||
if ("error" in resp) {
|
if ("error" in resp) {
|
||||||
|
|
@ -70,6 +88,17 @@ export default function SettingsHardwareRoute() {
|
||||||
});
|
});
|
||||||
}, [send, setBacklightSettings]);
|
}, [send, setBacklightSettings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
send("getVideoSleepMode", {}, (resp: JsonRpcResponse) => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
console.error("Failed to get power saving mode:", resp.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = resp.result as { enabled: boolean; duration: number };
|
||||||
|
setPowerSavingEnabled(result.duration > 0);
|
||||||
|
});
|
||||||
|
}, [send]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
|
|
@ -167,6 +196,26 @@ export default function SettingsHardwareRoute() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<FeatureFlag minAppVersion="0.4.9">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="h-px w-full bg-slate-800/10 dark:bg-slate-300/20" />
|
||||||
|
<SettingsSectionHeader
|
||||||
|
title="Power Saving"
|
||||||
|
description="Reduce power consumption when not in use"
|
||||||
|
/>
|
||||||
|
<SettingsItem
|
||||||
|
badge="Experimental"
|
||||||
|
title="HDMI Sleep Mode"
|
||||||
|
description="Turn off capture after 90 seconds of inactivity"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={powerSavingEnabled}
|
||||||
|
onChange={(e) => handlePowerSavingChange(e.target.checked)}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
</div>
|
||||||
|
</FeatureFlag>
|
||||||
|
|
||||||
<FeatureFlag minAppVersion="0.3.8">
|
<FeatureFlag minAppVersion="0.3.8">
|
||||||
<UsbDeviceSetting />
|
<UsbDeviceSetting />
|
||||||
</FeatureFlag>
|
</FeatureFlag>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue