mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-18 03:28:19 +01:00
Add support for Luckfox PicoKVM
Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
279
native_audio.go
Normal file
279
native_audio.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
audioCmd *exec.Cmd
|
||||
audioCmdLock = &sync.Mutex{}
|
||||
)
|
||||
|
||||
var audioSocketConn net.Conn
|
||||
|
||||
var audioOngoingRequests = make(map[int32]chan *CtrlResponse)
|
||||
|
||||
var audioLock = &sync.Mutex{}
|
||||
|
||||
func CallAudioCtrlAction(action string, params map[string]interface{}) (*CtrlResponse, error) {
|
||||
audioLock.Lock()
|
||||
defer audioLock.Unlock()
|
||||
ctrlAction := CtrlAction{
|
||||
Action: action,
|
||||
Seq: seq,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
responseChan := make(chan *CtrlResponse)
|
||||
audioOngoingRequests[seq] = responseChan
|
||||
seq++
|
||||
|
||||
jsonData, err := json.Marshal(ctrlAction)
|
||||
if err != nil {
|
||||
delete(audioOngoingRequests, ctrlAction.Seq)
|
||||
return nil, fmt.Errorf("error marshaling ctrl action: %w", err)
|
||||
}
|
||||
|
||||
scopedLogger := audioLogger.With().
|
||||
Str("action", ctrlAction.Action).
|
||||
Interface("params", ctrlAction.Params).Logger()
|
||||
|
||||
scopedLogger.Debug().Msg("sending audio ctrl action")
|
||||
|
||||
err = WriteAudioCtrlMessage(jsonData)
|
||||
if err != nil {
|
||||
delete(audioOngoingRequests, ctrlAction.Seq)
|
||||
return nil, ErrorfL(&scopedLogger, "error writing audio ctrl message", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case response := <-responseChan:
|
||||
delete(audioOngoingRequests, seq)
|
||||
if response.Error != "" {
|
||||
return nil, ErrorfL(
|
||||
&scopedLogger,
|
||||
"error audio response: %s",
|
||||
errors.New(response.Error),
|
||||
)
|
||||
}
|
||||
return response, nil
|
||||
case <-time.After(5 * time.Second):
|
||||
close(responseChan)
|
||||
delete(audioOngoingRequests, seq)
|
||||
return nil, ErrorfL(&scopedLogger, "timeout waiting for response", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteAudioCtrlMessage(message []byte) error {
|
||||
if audioSocketConn == nil {
|
||||
return fmt.Errorf("audio socket not conn ected")
|
||||
}
|
||||
_, err := audioSocketConn.Write(message)
|
||||
return err
|
||||
}
|
||||
|
||||
var audioCtrlSocketListener net.Listener
|
||||
|
||||
var audioCtrlClientConnected = make(chan struct{})
|
||||
|
||||
func waitAudioCtrlClientConnected() {
|
||||
<-audioCtrlClientConnected
|
||||
}
|
||||
|
||||
func StartAudioSocketServer(socketPath string, handleClient func(net.Conn), isCtrl bool) net.Listener {
|
||||
scopedLogger := audioLogger.With().
|
||||
Str("socket_path", socketPath).
|
||||
Logger()
|
||||
|
||||
// Remove the socket file if it already exists
|
||||
if _, err := os.Stat(socketPath); err == nil {
|
||||
if err := os.Remove(socketPath); err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("failed to remove existing socket file")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unixpacket", socketPath)
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("failed to start server")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
scopedLogger.Info().Msg("server listening")
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("failed to accept socket")
|
||||
continue
|
||||
}
|
||||
if isCtrl {
|
||||
// check if the channel is closed
|
||||
select {
|
||||
case <-audioCtrlClientConnected:
|
||||
scopedLogger.Debug().Msg("audio ctrl client reconnected")
|
||||
default:
|
||||
close(audioCtrlClientConnected)
|
||||
scopedLogger.Debug().Msg("first audio ctrl socket client connected")
|
||||
}
|
||||
}
|
||||
|
||||
//conn.Write([]byte("[handleAudioCtrlClient]audio sock test"))
|
||||
go handleClient(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
return listener
|
||||
}
|
||||
|
||||
func StartAudioCtrlSocketServer() {
|
||||
audioCtrlSocketListener = StartAudioSocketServer("/var/run/kvm_audio.sock", handleAudioCtrlClient, true)
|
||||
audioLogger.Debug().Msg("audio ctrl sock started")
|
||||
}
|
||||
|
||||
func handleAudioCtrlClient(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
scopedLogger := audioLogger.With().
|
||||
Str("addr", conn.RemoteAddr().String()).
|
||||
Str("type", "audio_ctrl").
|
||||
Logger()
|
||||
|
||||
scopedLogger.Info().Msg("audio socket client connected")
|
||||
if audioSocketConn != nil {
|
||||
scopedLogger.Debug().Msg("closing existing audio socket connection")
|
||||
audioSocketConn.Close()
|
||||
}
|
||||
|
||||
audioSocketConn = conn
|
||||
|
||||
readBuf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := conn.Read(readBuf)
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("error reading from audio sock")
|
||||
break
|
||||
}
|
||||
readMsg := string(readBuf[:n])
|
||||
|
||||
audioResp := CtrlResponse{}
|
||||
err = json.Unmarshal([]byte(readMsg), &audioResp)
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Str("data", readMsg).Msg("error parsing audio sock msg")
|
||||
continue
|
||||
}
|
||||
scopedLogger.Trace().Interface("data", audioResp).Msg("audio sock msg")
|
||||
|
||||
if audioResp.Seq != 0 {
|
||||
responseChan, ok := audioOngoingRequests[audioResp.Seq]
|
||||
if ok {
|
||||
responseChan <- &audioResp
|
||||
}
|
||||
}
|
||||
switch audioResp.Event {
|
||||
case "audio_input_state":
|
||||
HandleAudioStateMessage(audioResp)
|
||||
}
|
||||
}
|
||||
|
||||
scopedLogger.Debug().Msg("audio sock disconnected")
|
||||
}
|
||||
|
||||
func startAudioBinaryWithLock(binaryPath string) (*exec.Cmd, error) {
|
||||
audioCmdLock.Lock()
|
||||
defer audioCmdLock.Unlock()
|
||||
|
||||
cmd, err := startAudioBinary(binaryPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
audioCmd = cmd
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func restartAudioBinary(binaryPath string) error {
|
||||
time.Sleep(10 * time.Second)
|
||||
// restart the binary
|
||||
audioLogger.Info().Msg("restarting audio_video binary")
|
||||
cmd, err := startAudioBinary(binaryPath)
|
||||
if err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("failed to restart binary")
|
||||
}
|
||||
audioCmd = cmd
|
||||
return err
|
||||
}
|
||||
|
||||
func superviseAudioBinary(binaryPath string) error {
|
||||
audioCmdLock.Lock()
|
||||
defer audioCmdLock.Unlock()
|
||||
|
||||
if audioCmd == nil || audioCmd.Process == nil {
|
||||
return restartAudioBinary(binaryPath)
|
||||
}
|
||||
|
||||
err := audioCmd.Wait()
|
||||
|
||||
if err == nil {
|
||||
audioLogger.Info().Err(err).Msg("kvm_audio binary exited with no error")
|
||||
} else if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
audioLogger.Warn().Int("exit_code", exiterr.ExitCode()).Msg("kvm_audio binary exited with error")
|
||||
} else {
|
||||
audioLogger.Warn().Err(err).Msg("kvm_audio binary exited with unknown error")
|
||||
}
|
||||
|
||||
return restartAudioBinary(binaryPath)
|
||||
}
|
||||
|
||||
func ExtractAndRunAudioBin() error {
|
||||
binaryPath := "/userdata/picokvm/bin/kvm_audio"
|
||||
|
||||
// Make the binary executable
|
||||
if err := os.Chmod(binaryPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to make binary executable: %w", err)
|
||||
}
|
||||
// Run the binary in the background
|
||||
cmd, err := startAudioBinaryWithLock(binaryPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start binary: %w", err)
|
||||
}
|
||||
|
||||
// check if the binary is still running every 10 seconds
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-appCtx.Done():
|
||||
audioLogger.Info().Msg("stopping audio binary supervisor")
|
||||
return
|
||||
default:
|
||||
err := superviseAudioBinary(binaryPath)
|
||||
if err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("failed to supervise audio binary")
|
||||
time.Sleep(1 * time.Second) // Add a short delay to prevent rapid successive calls
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-appCtx.Done()
|
||||
audioLogger.Info().Int("pid", cmd.Process.Pid).Msg("killing process")
|
||||
err := cmd.Process.Kill()
|
||||
if err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("failed to kill process")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
audioLogger.Info().Int("pid", cmd.Process.Pid).Msg("kvm_audio binary started")
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user