feat: use native jetkvm-audio

This commit is contained in:
Qishuai Liu 2025-06-26 00:30:00 +09:00
parent c529c903d0
commit 28a8fa05cc
No known key found for this signature in database
4 changed files with 17 additions and 93 deletions

View File

@ -1,81 +1,14 @@
package kvm package kvm
import ( import (
"fmt"
"net"
"os/exec" "os/exec"
"sync"
"syscall"
"time"
) )
func startFFmpeg() (cmd *exec.Cmd, err error) { func runAudioClient() (cmd *exec.Cmd, err error) {
binaryPath := "/userdata/jetkvm/bin/ffmpeg" return startNativeBinary("/userdata/jetkvm/bin/jetkvm_audio")
// Run the binary in the background
cmd = exec.Command(binaryPath,
"-f", "alsa",
"-channels", "2",
"-sample_rate", "48000",
"-i", "hw:1,0",
"-c:a", "libopus",
"-b:a", "64k", // ought to be enough for anybody
"-vbr", "off",
"-frame_duration", "20",
"-compression_level", "2",
"-f", "rtp",
"rtp://127.0.0.1:3333")
nativeOutputLock := sync.Mutex{}
nativeStdout := &nativeOutput{
mu: &nativeOutputLock,
logger: nativeLogger.Info().Str("pipe", "stdout"),
}
nativeStderr := &nativeOutput{
mu: &nativeOutputLock,
logger: nativeLogger.Info().Str("pipe", "stderr"),
}
// Redirect stdout and stderr to the current process
cmd.Stdout = nativeStdout
cmd.Stderr = nativeStderr
// Set the process group ID so we can kill the process and its children when this process exits
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pdeathsig: syscall.SIGKILL,
}
// Start the command
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start binary: %w", err)
}
return
} }
func StartRtpAudioServer(handleClient func(net.Conn)) { func StartAudioServer() {
scopedLogger := nativeLogger.With(). nativeAudioSocketListener = StartNativeSocketServer("/var/run/jetkvm_audio.sock", handleAudioClient, false)
Logger() nativeLogger.Debug().Msg("native app audio sock started")
listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 3333})
if err != nil {
scopedLogger.Warn().Err(err).Msg("failed to start server")
return
}
scopedLogger.Info().Msg("server listening")
go func() {
for {
cmd, err := startFFmpeg()
if err != nil {
scopedLogger.Error().Err(err).Msg("failed to start ffmpeg")
}
err = cmd.Wait()
scopedLogger.Error().Err(err).Msg("ffmpeg exited, restarting")
time.Sleep(2 * time.Second)
}
}()
go handleClient(listener)
} }

View File

@ -77,7 +77,11 @@ func Main() {
// initialize usb gadget // initialize usb gadget
initUsbGadget() initUsbGadget()
StartRtpAudioServer(handleAudioClient)
StartAudioServer()
if _, err := runAudioClient(); err != nil {
logger.Warn().Err(err).Msg("failed to run audio client")
}
if err := setInitialVirtualMediaState(); err != nil { if err := setInitialVirtualMediaState(); err != nil {
logger.Warn().Err(err).Msg("failed to set initial virtual media state") logger.Warn().Err(err).Msg("failed to set initial virtual media state")

View File

@ -13,8 +13,6 @@ import (
"time" "time"
"github.com/jetkvm/kvm/resource" "github.com/jetkvm/kvm/resource"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media"
) )
@ -107,6 +105,7 @@ func WriteCtrlMessage(message []byte) error {
var nativeCtrlSocketListener net.Listener //nolint:unused var nativeCtrlSocketListener net.Listener //nolint:unused
var nativeVideoSocketListener net.Listener //nolint:unused var nativeVideoSocketListener net.Listener //nolint:unused
var nativeAudioSocketListener net.Listener //nolint:unused
var ctrlClientConnected = make(chan struct{}) var ctrlClientConnected = make(chan struct{})
@ -260,8 +259,6 @@ func handleAudioClient(conn net.Conn) {
scopedLogger.Info().Msg("native audio socket client connected") scopedLogger.Info().Msg("native audio socket client connected")
inboundPacket := make([]byte, maxAudioFrameSize) inboundPacket := make([]byte, maxAudioFrameSize)
var timestamp uint32
var packet rtp.Packet
for { for {
n, err := conn.Read(inboundPacket) n, err := conn.Read(inboundPacket)
if err != nil { if err != nil {
@ -270,20 +267,10 @@ func handleAudioClient(conn net.Conn) {
} }
if currentSession != nil { if currentSession != nil {
if err := packet.Unmarshal(inboundPacket[:n]); err != nil { if err := currentSession.AudioTrack.WriteSample(media.Sample{
scopedLogger.Warn().Err(err).Msg("error unmarshalling audio socket packet") Data: inboundPacket[:n],
continue Duration: 20 * time.Millisecond,
} }); err != nil {
timestamp += 960
packet.Header.Timestamp = timestamp
buf, err := packet.Marshal()
if err != nil {
scopedLogger.Warn().Err(err).Msg("error marshalling packet")
continue
}
if _, err := currentSession.AudioTrack.Write(buf); err != nil {
scopedLogger.Warn().Err(err).Msg("error writing sample") scopedLogger.Warn().Err(err).Msg("error writing sample")
} }
} }

View File

@ -18,7 +18,7 @@ import (
type Session struct { type Session struct {
peerConnection *webrtc.PeerConnection peerConnection *webrtc.PeerConnection
VideoTrack *webrtc.TrackLocalStaticSample VideoTrack *webrtc.TrackLocalStaticSample
AudioTrack *webrtc.TrackLocalStaticRTP AudioTrack *webrtc.TrackLocalStaticSample
ControlChannel *webrtc.DataChannel ControlChannel *webrtc.DataChannel
RPCChannel *webrtc.DataChannel RPCChannel *webrtc.DataChannel
HidChannel *webrtc.DataChannel HidChannel *webrtc.DataChannel
@ -137,7 +137,7 @@ func newSession(config SessionConfig) (*Session, error) {
return nil, err return nil, err
} }
session.AudioTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "kvm") session.AudioTrack, err = webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "kvm")
if err != nil { if err != nil {
return nil, err return nil, err
} }