diff --git a/config.go b/config.go index 36df92da..26f54a45 100644 --- a/config.go +++ b/config.go @@ -106,6 +106,7 @@ type Config struct { NetworkConfig *types.NetworkConfig `json:"network_config"` DefaultLogLevel string `json:"default_log_level"` VideoSleepAfterSec int `json:"video_sleep_after_sec"` + VideoQualityFactor float64 `json:"video_quality_factor"` } func (c *Config) GetDisplayRotation() uint16 { diff --git a/internal/native/cgo/ctrl.c b/internal/native/cgo/ctrl.c index dd285859..0c10ee15 100644 --- a/internal/native/cgo/ctrl.c +++ b/internal/native/cgo/ctrl.c @@ -405,8 +405,8 @@ char *jetkvm_video_log_status() { return (char *)videoc_log_status(); } -int jetkvm_video_init() { - return video_init(); +int jetkvm_video_init(float factor) { + return video_init(factor); } void jetkvm_video_shutdown() { diff --git a/internal/native/cgo/ctrl.h b/internal/native/cgo/ctrl.h index 430e4c21..774ee147 100644 --- a/internal/native/cgo/ctrl.h +++ b/internal/native/cgo/ctrl.h @@ -52,7 +52,7 @@ const char *jetkvm_ui_get_lvgl_version(); const char *jetkvm_ui_event_code_to_name(int code); -int jetkvm_video_init(); +int jetkvm_video_init(float quality_factor); void jetkvm_video_shutdown(); void jetkvm_video_start(); void jetkvm_video_stop(); diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 97caaea6..1c88adcf 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -220,10 +220,15 @@ static int32_t buf_init() pthread_t *format_thread = NULL; -int video_init() +int video_init(float factor) { detect_sleep_mode(); + if (factor < 0 || factor > 1) { + factor = 1.0f; + } + quality_factor = factor; + if (RK_MPI_SYS_Init() != RK_SUCCESS) { log_error("RK_MPI_SYS_Init failed"); diff --git a/internal/native/cgo/video.h b/internal/native/cgo/video.h index e9309be4..6fa00ca4 100644 --- a/internal/native/cgo/video.h +++ b/internal/native/cgo/video.h @@ -6,7 +6,7 @@ * * @return int 0 on success, -1 on failure */ -int video_init(); +int video_init(float quality_factor); /** * @brief Shutdown the video subsystem diff --git a/internal/native/cgo_linux.go b/internal/native/cgo_linux.go index 8cd6d489..850da0e8 100644 --- a/internal/native/cgo_linux.go +++ b/internal/native/cgo_linux.go @@ -129,11 +129,13 @@ func uiTick() { C.jetkvm_ui_tick() } -func videoInit() error { +func videoInit(factor float64) error { cgoLock.Lock() defer cgoLock.Unlock() - ret := C.jetkvm_video_init() + factorC := C.float(factor) + + ret := C.jetkvm_video_init(factorC) if ret != 0 { return fmt.Errorf("failed to initialize video: %d", ret) } diff --git a/internal/native/native.go b/internal/native/native.go index b89b37a3..2a9055ce 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -15,6 +15,7 @@ type Native struct { systemVersion *semver.Version appVersion *semver.Version displayRotation uint16 + defaultQualityFactor float64 onVideoStateChange func(state VideoState) onVideoFrameReceived func(frame []byte, duration time.Duration) onIndevEvent func(event string) @@ -22,12 +23,14 @@ type Native struct { sleepModeSupported bool videoLock sync.Mutex screenLock sync.Mutex + extraLock sync.Mutex } type NativeOptions struct { SystemVersion *semver.Version AppVersion *semver.Version DisplayRotation uint16 + DefaultQualityFactor float64 OnVideoStateChange func(state VideoState) OnVideoFrameReceived func(frame []byte, duration time.Duration) OnIndevEvent func(event string) @@ -65,6 +68,11 @@ func NewNative(opts NativeOptions) *Native { sleepModeSupported := isSleepModeSupported() + defaultQualityFactor := opts.DefaultQualityFactor + if defaultQualityFactor < 0 || defaultQualityFactor > 1 { + defaultQualityFactor = 1.0 + } + return &Native{ ready: make(chan struct{}), l: nativeLogger, @@ -72,6 +80,7 @@ func NewNative(opts NativeOptions) *Native { systemVersion: opts.SystemVersion, appVersion: opts.AppVersion, displayRotation: opts.DisplayRotation, + defaultQualityFactor: defaultQualityFactor, onVideoStateChange: onVideoStateChange, onVideoFrameReceived: onVideoFrameReceived, onIndevEvent: onIndevEvent, @@ -97,7 +106,7 @@ func (n *Native) Start() { n.initUI() go n.tickUI() - if err := videoInit(); err != nil { + if err := videoInit(n.defaultQualityFactor); err != nil { n.l.Error().Err(err).Msg("failed to initialize video") } diff --git a/internal/native/video.go b/internal/native/video.go index bc5ebaf8..c556a938 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -1,7 +1,9 @@ package native import ( + "fmt" "os" + "time" ) const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode" @@ -9,6 +11,8 @@ const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mod // DefaultEDID is the default EDID for the video stream. const DefaultEDID = "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b" +var extraLockTimeout = 5 * time.Second + // VideoState is the state of the video stream. type VideoState struct { Ready bool `json:"ready"` @@ -69,12 +73,32 @@ func (n *Native) VideoSleepModeSupported() bool { return n.sleepModeSupported } +// useExtraLock uses the extra lock to execute a function. +// if the lock is currently held by another goroutine, returns an error. +// +// it's used to ensure that only one change is made to the video stream at a time. +// as the change usually requires to restart video streaming +// TODO: check video streaming status instead of using a hardcoded timeout +func (n *Native) useExtraLock(fn func() error) error { + if !n.extraLock.TryLock() { + return fmt.Errorf("the previous change hasn't been completed yet") + } + err := fn() + if err == nil { + time.Sleep(extraLockTimeout) + } + n.extraLock.Unlock() + return err +} + // VideoSetQualityFactor sets the quality factor for the video stream. func (n *Native) VideoSetQualityFactor(factor float64) error { n.videoLock.Lock() defer n.videoLock.Unlock() - return videoSetStreamQualityFactor(factor) + return n.useExtraLock(func() error { + return videoSetStreamQualityFactor(factor) + }) } // VideoGetQualityFactor gets the quality factor for the video stream. @@ -94,7 +118,9 @@ func (n *Native) VideoSetEDID(edid string) error { edid = DefaultEDID } - return videoSetEDID(edid) + return n.useExtraLock(func() error { + return videoSetEDID(edid) + }) } // VideoGetEDID gets the EDID for the video stream. diff --git a/native.go b/native.go index 39c1fa0a..ac393577 100644 --- a/native.go +++ b/native.go @@ -17,9 +17,10 @@ var ( func initNative(systemVersion *semver.Version, appVersion *semver.Version) { nativeInstance = native.NewNative(native.NativeOptions{ - SystemVersion: systemVersion, - AppVersion: appVersion, - DisplayRotation: config.GetDisplayRotation(), + SystemVersion: systemVersion, + AppVersion: appVersion, + DisplayRotation: config.GetDisplayRotation(), + DefaultQualityFactor: config.VideoQualityFactor, OnVideoStateChange: func(state native.VideoState) { lastVideoState = state triggerVideoStateUpdate() @@ -60,9 +61,13 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) { }) nativeInstance.Start() - if err := nativeInstance.VideoSetEDID(config.EdidString); err != nil { - nativeLogger.Warn().Err(err).Msg("error setting EDID") - } + go func() { + for { + if err := nativeInstance.VideoSetEDID(config.EdidString); err != nil { + nativeLogger.Warn().Err(err).Msg("error setting EDID") + } + } + }() if os.Getenv("JETKVM_CRASH_TESTING") == "1" { nativeInstance.DoNotUseThisIsForCrashTestingOnly()