mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-19 09:52:32 +01:00
Add support for Luckfox PicoKVM
Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
170
audio.go
Normal file
170
audio.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
var (
|
||||
audioListener *net.UDPConn
|
||||
currentPort int
|
||||
mutex sync.Mutex
|
||||
portList = []int{3333}
|
||||
portIndex = 0
|
||||
)
|
||||
|
||||
const (
|
||||
maxAudioFrameSize = 1500
|
||||
frameDurationMs = 20
|
||||
timestampRate = 48000
|
||||
timestampStep = timestampRate * frameDurationMs / 1000
|
||||
)
|
||||
|
||||
func waitUDPPortReleased(port int, retries int, delay time.Duration) error {
|
||||
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
for i := 0; i < retries; i++ {
|
||||
conn, err := net.ListenPacket("udp", addr)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
time.Sleep(delay)
|
||||
}
|
||||
return fmt.Errorf("port %d still in use after %d retries", port, retries)
|
||||
}
|
||||
|
||||
func getNextAvailablePort() int {
|
||||
for i := 0; i < len(portList); i++ {
|
||||
port := portList[portIndex]
|
||||
portIndex = (portIndex + 1) % len(portList)
|
||||
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
conn, err := net.ListenPacket("udp", addr)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return port
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func StartNtpAudioServer(handleClient func(net.Conn)) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if audioListener != nil || lastAudioState.Ready {
|
||||
StopNtpAudioServer()
|
||||
}
|
||||
|
||||
port := getNextAvailablePort()
|
||||
if port == 0 {
|
||||
audioLogger.Error().Msg("no available ports to start audio server")
|
||||
return
|
||||
}
|
||||
|
||||
listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: port})
|
||||
if err != nil {
|
||||
audioLogger.Error().Err(err).Msg("failed to start server on port %d")
|
||||
return
|
||||
}
|
||||
|
||||
audioListener = listener
|
||||
currentPort = port
|
||||
|
||||
if config.AudioMode == "usb" {
|
||||
_, err := CallAudioCtrlAction("set_audio_mode", map[string]interface{}{"audio_mode": "usb", "rtp_port": port})
|
||||
if err != nil {
|
||||
audioLogger.Error().Err(err).Msg("failed to set audio mode")
|
||||
}
|
||||
_, err = CallAudioCtrlAction("set_audio_enable", map[string]interface{}{"audio_enable": true})
|
||||
if err != nil {
|
||||
audioLogger.Error().Err(err).Msg("failed to set audio enable")
|
||||
}
|
||||
} else {
|
||||
_, err := CallAudioCtrlAction("set_audio_mode", map[string]interface{}{"audio_mode": "hdmi", "rtp_port": port})
|
||||
if err != nil {
|
||||
audioLogger.Error().Err(err).Msg("failed to set audio mode")
|
||||
}
|
||||
_, err = CallAudioCtrlAction("set_audio_enable", map[string]interface{}{"audio_enable": true})
|
||||
if err != nil {
|
||||
audioLogger.Error().Err(err).Msg("failed to set audio enable")
|
||||
}
|
||||
}
|
||||
|
||||
go handleClient(listener)
|
||||
}
|
||||
|
||||
func StopNtpAudioServer() {
|
||||
CallAudioCtrlAction("set_audio_enable", map[string]interface{}{"audio_enable": false})
|
||||
|
||||
if audioListener != nil {
|
||||
audioListener.Close()
|
||||
audioListener = nil
|
||||
}
|
||||
|
||||
if currentPort != 0 {
|
||||
if err := waitUDPPortReleased(currentPort, 10, 200*time.Millisecond); err != nil {
|
||||
audioLogger.Error().Err(err).Msg("port not released")
|
||||
}
|
||||
currentPort = 0
|
||||
}
|
||||
|
||||
audioLogger.Info().Msg("audio server stopped")
|
||||
}
|
||||
|
||||
func handleAudioClient(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
audioLogger.Info().Msg("native audio socket client connected")
|
||||
inboundPacket := make([]byte, maxAudioFrameSize)
|
||||
var timestamp uint32
|
||||
var packet rtp.Packet
|
||||
|
||||
for {
|
||||
n, err := conn.Read(inboundPacket)
|
||||
if err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("error during read")
|
||||
return
|
||||
}
|
||||
|
||||
if currentSession != nil {
|
||||
if err := packet.Unmarshal(inboundPacket[:n]); err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("error unmarshalling audio socket packet")
|
||||
continue
|
||||
}
|
||||
|
||||
timestamp += timestampStep
|
||||
packet.Header.Timestamp = timestamp
|
||||
buf, err := packet.Marshal()
|
||||
if err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("error marshalling packet")
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := currentSession.AudioTrack.Write(buf); err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("error writing sample")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type AudioInputState struct {
|
||||
Ready bool `json:"ready"`
|
||||
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
|
||||
}
|
||||
|
||||
var lastAudioState AudioInputState
|
||||
|
||||
func HandleAudioStateMessage(event CtrlResponse) {
|
||||
audioState := AudioInputState{}
|
||||
err := json.Unmarshal(event.Data, &audioState)
|
||||
if err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("Error parsing audio state json")
|
||||
return
|
||||
}
|
||||
lastAudioState = audioState
|
||||
}
|
||||
Reference in New Issue
Block a user