Files
kvm/audio.go
2025-09-16 20:36:48 +08:00

174 lines
4.2 KiB
Go

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() {
_, err := CallAudioCtrlAction("set_audio_enable", map[string]interface{}{"audio_enable": false})
if err != nil {
audioLogger.Error().Err(err).Msg("failed to set audio enable")
}
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.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
}