Cleanup: implement PR Review suggestions

This commit is contained in:
Alex P 2025-09-09 18:09:19 +00:00
parent 02acee0c75
commit eca1e6a80d
9 changed files with 88 additions and 126 deletions

View File

@ -230,7 +230,7 @@ func (ais *AudioInputSupervisor) SendFrameZeroCopy(frame *ZeroCopyAudioFrame) er
} }
// SendConfig sends a configuration update to the subprocess (convenience method) // SendConfig sends a configuration update to the subprocess (convenience method)
func (ais *AudioInputSupervisor) SendConfig(config InputIPCConfig) error { func (ais *AudioInputSupervisor) SendConfig(config UnifiedIPCConfig) error {
if ais.client == nil { if ais.client == nil {
return fmt.Errorf("client not initialized") return fmt.Errorf("client not initialized")
} }
@ -243,7 +243,7 @@ func (ais *AudioInputSupervisor) SendConfig(config InputIPCConfig) error {
} }
// SendOpusConfig sends a complete Opus encoder configuration to the audio input server // SendOpusConfig sends a complete Opus encoder configuration to the audio input server
func (ais *AudioInputSupervisor) SendOpusConfig(config InputIPCOpusConfig) error { func (ais *AudioInputSupervisor) SendOpusConfig(config UnifiedIPCOpusConfig) error {
if ais.client == nil { if ais.client == nil {
return fmt.Errorf("client not initialized") return fmt.Errorf("client not initialized")
} }

View File

@ -134,14 +134,12 @@ func (mp *GenericMessagePool) GetStats() (hitCount, missCount int64, hitRate flo
// Helper functions // Helper functions
// EncodeMessageHeader encodes a message header into a byte slice // EncodeMessageHeader encodes a message header into a provided byte slice
func EncodeMessageHeader(magic uint32, msgType uint8, length uint32, timestamp int64) []byte { func EncodeMessageHeader(header []byte, magic uint32, msgType uint8, length uint32, timestamp int64) {
header := make([]byte, 17)
binary.LittleEndian.PutUint32(header[0:4], magic) binary.LittleEndian.PutUint32(header[0:4], magic)
header[4] = msgType header[4] = msgType
binary.LittleEndian.PutUint32(header[5:9], length) binary.LittleEndian.PutUint32(header[5:9], length)
binary.LittleEndian.PutUint64(header[9:17], uint64(timestamp)) binary.LittleEndian.PutUint64(header[9:17], uint64(timestamp))
return header
} }
// EncodeAudioConfig encodes basic audio configuration to binary format // EncodeAudioConfig encodes basic audio configuration to binary format
@ -179,14 +177,12 @@ func WriteIPCMessage(conn net.Conn, msg IPCMessage, pool *GenericMessagePool, dr
defer pool.Put(optMsg) defer pool.Put(optMsg)
// Prepare header in pre-allocated buffer // Prepare header in pre-allocated buffer
header := EncodeMessageHeader(msg.GetMagic(), msg.GetType(), msg.GetLength(), msg.GetTimestamp()) EncodeMessageHeader(optMsg.header[:], msg.GetMagic(), msg.GetType(), msg.GetLength(), msg.GetTimestamp())
copy(optMsg.header[:], header)
// Set write deadline for timeout handling (more efficient than goroutines) // Set write deadline for timeout handling (more efficient than goroutines)
if deadline := time.Now().Add(Config.WriteTimeout); deadline.After(time.Now()) { if deadline := time.Now().Add(Config.WriteTimeout); deadline.After(time.Now()) {
if err := conn.SetWriteDeadline(deadline); err != nil { if err := conn.SetWriteDeadline(deadline); err != nil {
// If we can't set deadline, proceed without it // If we can't set deadline, proceed without it
// This maintains compatibility with connections that don't support deadlines
_ = err // Explicitly ignore error for linter _ = err // Explicitly ignore error for linter
} }
} }

View File

@ -27,27 +27,11 @@ var (
messagePoolSize = Config.MessagePoolSize // Pre-allocated message pool size messagePoolSize = Config.MessagePoolSize // Pre-allocated message pool size
) )
// Legacy aliases for backward compatibility
type InputMessageType = UnifiedMessageType
type InputIPCMessage = UnifiedIPCMessage
// Legacy constants for backward compatibility
const (
InputMessageTypeOpusFrame = MessageTypeOpusFrame
InputMessageTypeConfig = MessageTypeConfig
InputMessageTypeOpusConfig = MessageTypeOpusConfig
InputMessageTypeStop = MessageTypeStop
InputMessageTypeHeartbeat = MessageTypeHeartbeat
InputMessageTypeAck = MessageTypeAck
)
// Methods are now inherited from UnifiedIPCMessage
// OptimizedIPCMessage represents an optimized message with pre-allocated buffers // OptimizedIPCMessage represents an optimized message with pre-allocated buffers
type OptimizedIPCMessage struct { type OptimizedIPCMessage struct {
header [17]byte // Pre-allocated header buffer (headerSize = 17) header [17]byte // Pre-allocated header buffer (headerSize = 17)
data []byte // Reusable data buffer data []byte // Reusable data buffer
msg InputIPCMessage // Embedded message msg UnifiedIPCMessage // Embedded message
} }
// MessagePool manages a pool of reusable messages to reduce allocations // MessagePool manages a pool of reusable messages to reduce allocations
@ -109,7 +93,7 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
atomic.AddInt64(&mp.hitCount, 1) atomic.AddInt64(&mp.hitCount, 1)
// Reset message for reuse // Reset message for reuse
msg.data = msg.data[:0] msg.data = msg.data[:0]
msg.msg = InputIPCMessage{} msg.msg = UnifiedIPCMessage{}
return msg return msg
} }
mp.mutex.Unlock() mp.mutex.Unlock()
@ -120,7 +104,7 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
atomic.AddInt64(&mp.hitCount, 1) atomic.AddInt64(&mp.hitCount, 1)
// Reset message for reuse and ensure proper capacity // Reset message for reuse and ensure proper capacity
msg.data = msg.data[:0] msg.data = msg.data[:0]
msg.msg = InputIPCMessage{} msg.msg = UnifiedIPCMessage{}
// Ensure data buffer has sufficient capacity // Ensure data buffer has sufficient capacity
if cap(msg.data) < maxFrameSize { if cap(msg.data) < maxFrameSize {
msg.data = make([]byte, 0, maxFrameSize) msg.data = make([]byte, 0, maxFrameSize)
@ -148,7 +132,7 @@ func (mp *MessagePool) Put(msg *OptimizedIPCMessage) {
// Reset the message for reuse // Reset the message for reuse
msg.data = msg.data[:0] msg.data = msg.data[:0]
msg.msg = InputIPCMessage{} msg.msg = UnifiedIPCMessage{}
// First try to return to pre-allocated pool for fastest reuse // First try to return to pre-allocated pool for fastest reuse
mp.mutex.Lock() mp.mutex.Lock()
@ -168,10 +152,6 @@ func (mp *MessagePool) Put(msg *OptimizedIPCMessage) {
} }
} }
// Legacy aliases for backward compatibility
type InputIPCConfig = UnifiedIPCConfig
type InputIPCOpusConfig = UnifiedIPCOpusConfig
// AudioInputServer handles IPC communication for audio input processing // AudioInputServer handles IPC communication for audio input processing
type AudioInputServer struct { type AudioInputServer struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
@ -186,10 +166,10 @@ type AudioInputServer struct {
running bool running bool
// Triple-goroutine architecture // Triple-goroutine architecture
messageChan chan *InputIPCMessage // Buffered channel for incoming messages messageChan chan *UnifiedIPCMessage // Buffered channel for incoming messages
processChan chan *InputIPCMessage // Buffered channel for processing queue processChan chan *UnifiedIPCMessage // Buffered channel for processing queue
stopChan chan struct{} // Stop signal for all goroutines stopChan chan struct{} // Stop signal for all goroutines
wg sync.WaitGroup // Wait group for goroutine coordination wg sync.WaitGroup // Wait group for goroutine coordination
// Channel resizing support // Channel resizing support
channelMutex sync.RWMutex // Protects channel recreation channelMutex sync.RWMutex // Protects channel recreation
@ -246,8 +226,8 @@ func NewAudioInputServer() (*AudioInputServer, error) {
return &AudioInputServer{ return &AudioInputServer{
listener: listener, listener: listener,
messageChan: make(chan *InputIPCMessage, initialBufferSize), messageChan: make(chan *UnifiedIPCMessage, initialBufferSize),
processChan: make(chan *InputIPCMessage, initialBufferSize), processChan: make(chan *UnifiedIPCMessage, initialBufferSize),
stopChan: make(chan struct{}), stopChan: make(chan struct{}),
bufferSize: initialBufferSize, bufferSize: initialBufferSize,
lastBufferSize: initialBufferSize, lastBufferSize: initialBufferSize,
@ -405,7 +385,7 @@ func (ais *AudioInputServer) handleConnection(conn net.Conn) {
// //
// The function uses pooled buffers for efficient memory management and // The function uses pooled buffers for efficient memory management and
// ensures all messages conform to the JetKVM audio protocol specification. // ensures all messages conform to the JetKVM audio protocol specification.
func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error) { func (ais *AudioInputServer) readMessage(conn net.Conn) (*UnifiedIPCMessage, error) {
// Get optimized message from pool // Get optimized message from pool
optMsg := globalMessagePool.Get() optMsg := globalMessagePool.Get()
defer globalMessagePool.Put(optMsg) defer globalMessagePool.Put(optMsg)
@ -419,7 +399,7 @@ func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error
// Parse header using optimized access // Parse header using optimized access
msg := &optMsg.msg msg := &optMsg.msg
msg.Magic = binary.LittleEndian.Uint32(optMsg.header[0:4]) msg.Magic = binary.LittleEndian.Uint32(optMsg.header[0:4])
msg.Type = InputMessageType(optMsg.header[4]) msg.Type = UnifiedMessageType(optMsg.header[4])
msg.Length = binary.LittleEndian.Uint32(optMsg.header[5:9]) msg.Length = binary.LittleEndian.Uint32(optMsg.header[5:9])
msg.Timestamp = int64(binary.LittleEndian.Uint64(optMsg.header[9:17])) msg.Timestamp = int64(binary.LittleEndian.Uint64(optMsg.header[9:17]))
@ -450,7 +430,7 @@ func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error
} }
// Return a copy of the message (data will be copied by caller if needed) // Return a copy of the message (data will be copied by caller if needed)
result := &InputIPCMessage{ result := &UnifiedIPCMessage{
Magic: msg.Magic, Magic: msg.Magic,
Type: msg.Type, Type: msg.Type,
Length: msg.Length, Length: msg.Length,
@ -467,17 +447,17 @@ func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error
} }
// processMessage processes a received message // processMessage processes a received message
func (ais *AudioInputServer) processMessage(msg *InputIPCMessage) error { func (ais *AudioInputServer) processMessage(msg *UnifiedIPCMessage) error {
switch msg.Type { switch msg.Type {
case InputMessageTypeOpusFrame: case MessageTypeOpusFrame:
return ais.processOpusFrame(msg.Data) return ais.processOpusFrame(msg.Data)
case InputMessageTypeConfig: case MessageTypeConfig:
return ais.processConfig(msg.Data) return ais.processConfig(msg.Data)
case InputMessageTypeOpusConfig: case MessageTypeOpusConfig:
return ais.processOpusConfig(msg.Data) return ais.processOpusConfig(msg.Data)
case InputMessageTypeStop: case MessageTypeStop:
return fmt.Errorf("stop message received") return fmt.Errorf("stop message received")
case InputMessageTypeHeartbeat: case MessageTypeHeartbeat:
return ais.sendAck() return ais.sendAck()
default: default:
return fmt.Errorf("unknown message type: %d", msg.Type) return fmt.Errorf("unknown message type: %d", msg.Type)
@ -538,7 +518,7 @@ func (ais *AudioInputServer) processOpusConfig(data []byte) error {
} }
// Deserialize Opus configuration // Deserialize Opus configuration
config := InputIPCOpusConfig{ config := UnifiedIPCOpusConfig{
SampleRate: int(binary.LittleEndian.Uint32(data[0:4])), SampleRate: int(binary.LittleEndian.Uint32(data[0:4])),
Channels: int(binary.LittleEndian.Uint32(data[4:8])), Channels: int(binary.LittleEndian.Uint32(data[4:8])),
FrameSize: int(binary.LittleEndian.Uint32(data[8:12])), FrameSize: int(binary.LittleEndian.Uint32(data[8:12])),
@ -581,9 +561,9 @@ func (ais *AudioInputServer) sendAck() error {
return fmt.Errorf("no connection") return fmt.Errorf("no connection")
} }
msg := &InputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: inputMagicNumber, Magic: inputMagicNumber,
Type: InputMessageTypeAck, Type: MessageTypeAck,
Length: 0, Length: 0,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
} }
@ -595,7 +575,7 @@ func (ais *AudioInputServer) sendAck() error {
var globalInputServerMessagePool = NewGenericMessagePool(messagePoolSize) var globalInputServerMessagePool = NewGenericMessagePool(messagePoolSize)
// writeMessage writes a message to the connection using shared common utilities // writeMessage writes a message to the connection using shared common utilities
func (ais *AudioInputServer) writeMessage(conn net.Conn, msg *InputIPCMessage) error { func (ais *AudioInputServer) writeMessage(conn net.Conn, msg *UnifiedIPCMessage) error {
// Use shared WriteIPCMessage function with global message pool // Use shared WriteIPCMessage function with global message pool
return WriteIPCMessage(conn, msg, globalInputServerMessagePool, &ais.droppedFrames) return WriteIPCMessage(conn, msg, globalInputServerMessagePool, &ais.droppedFrames)
} }
@ -673,9 +653,9 @@ func (aic *AudioInputClient) Disconnect() {
if aic.conn != nil { if aic.conn != nil {
// Send stop message // Send stop message
msg := &InputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: inputMagicNumber, Magic: inputMagicNumber,
Type: InputMessageTypeStop, Type: MessageTypeStop,
Length: 0, Length: 0,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
} }
@ -700,9 +680,9 @@ func (aic *AudioInputClient) SendFrame(frame []byte) error {
} }
// Direct message creation without timestamp overhead // Direct message creation without timestamp overhead
msg := &InputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: inputMagicNumber, Magic: inputMagicNumber,
Type: InputMessageTypeOpusFrame, Type: MessageTypeOpusFrame,
Length: uint32(len(frame)), Length: uint32(len(frame)),
Data: frame, Data: frame,
} }
@ -736,9 +716,9 @@ func (aic *AudioInputClient) SendFrameZeroCopy(frame *ZeroCopyAudioFrame) error
} }
// Use zero-copy data directly // Use zero-copy data directly
msg := &InputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: inputMagicNumber, Magic: inputMagicNumber,
Type: InputMessageTypeOpusFrame, Type: MessageTypeOpusFrame,
Length: uint32(frameLen), Length: uint32(frameLen),
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Data: frame.Data(), // Zero-copy data access Data: frame.Data(), // Zero-copy data access
@ -748,7 +728,7 @@ func (aic *AudioInputClient) SendFrameZeroCopy(frame *ZeroCopyAudioFrame) error
} }
// SendConfig sends a configuration update to the audio input server // SendConfig sends a configuration update to the audio input server
func (aic *AudioInputClient) SendConfig(config InputIPCConfig) error { func (aic *AudioInputClient) SendConfig(config UnifiedIPCConfig) error {
aic.mtx.Lock() aic.mtx.Lock()
defer aic.mtx.Unlock() defer aic.mtx.Unlock()
@ -766,9 +746,9 @@ func (aic *AudioInputClient) SendConfig(config InputIPCConfig) error {
// Serialize config using common function // Serialize config using common function
data := EncodeAudioConfig(config.SampleRate, config.Channels, config.FrameSize) data := EncodeAudioConfig(config.SampleRate, config.Channels, config.FrameSize)
msg := &InputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: inputMagicNumber, Magic: inputMagicNumber,
Type: InputMessageTypeConfig, Type: MessageTypeConfig,
Length: uint32(len(data)), Length: uint32(len(data)),
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Data: data, Data: data,
@ -778,7 +758,7 @@ func (aic *AudioInputClient) SendConfig(config InputIPCConfig) error {
} }
// SendOpusConfig sends a complete Opus encoder configuration update to the audio input server // SendOpusConfig sends a complete Opus encoder configuration update to the audio input server
func (aic *AudioInputClient) SendOpusConfig(config InputIPCOpusConfig) error { func (aic *AudioInputClient) SendOpusConfig(config UnifiedIPCOpusConfig) error {
aic.mtx.Lock() aic.mtx.Lock()
defer aic.mtx.Unlock() defer aic.mtx.Unlock()
@ -795,9 +775,9 @@ func (aic *AudioInputClient) SendOpusConfig(config InputIPCOpusConfig) error {
// Serialize Opus configuration using common function // Serialize Opus configuration using common function
data := EncodeOpusConfig(config.SampleRate, config.Channels, config.FrameSize, config.Bitrate, config.Complexity, config.VBR, config.SignalType, config.Bandwidth, config.DTX) data := EncodeOpusConfig(config.SampleRate, config.Channels, config.FrameSize, config.Bitrate, config.Complexity, config.VBR, config.SignalType, config.Bandwidth, config.DTX)
msg := &InputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: inputMagicNumber, Magic: inputMagicNumber,
Type: InputMessageTypeOpusConfig, Type: MessageTypeOpusConfig,
Length: uint32(len(data)), Length: uint32(len(data)),
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Data: data, Data: data,
@ -815,9 +795,9 @@ func (aic *AudioInputClient) SendHeartbeat() error {
return fmt.Errorf("not connected to audio input server") return fmt.Errorf("not connected to audio input server")
} }
msg := &InputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: inputMagicNumber, Magic: inputMagicNumber,
Type: InputMessageTypeHeartbeat, Type: MessageTypeHeartbeat,
Length: 0, Length: 0,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
} }
@ -829,7 +809,7 @@ func (aic *AudioInputClient) SendHeartbeat() error {
// Global shared message pool for input IPC clients // Global shared message pool for input IPC clients
var globalInputMessagePool = NewGenericMessagePool(messagePoolSize) var globalInputMessagePool = NewGenericMessagePool(messagePoolSize)
func (aic *AudioInputClient) writeMessage(msg *InputIPCMessage) error { func (aic *AudioInputClient) writeMessage(msg *UnifiedIPCMessage) error {
// Increment total frames counter // Increment total frames counter
atomic.AddInt64(&aic.totalFrames, 1) atomic.AddInt64(&aic.totalFrames, 1)
@ -1093,9 +1073,9 @@ func (ais *AudioInputServer) startProcessorGoroutine() {
} }
// processMessageWithRecovery processes a message with enhanced error recovery // processMessageWithRecovery processes a message with enhanced error recovery
func (ais *AudioInputServer) processMessageWithRecovery(msg *InputIPCMessage, logger zerolog.Logger) error { func (ais *AudioInputServer) processMessageWithRecovery(msg *UnifiedIPCMessage, logger zerolog.Logger) error {
// Intelligent frame dropping: prioritize recent frames // Intelligent frame dropping: prioritize recent frames
if msg.Type == InputMessageTypeOpusFrame { if msg.Type == MessageTypeOpusFrame {
// Check if processing queue is getting full // Check if processing queue is getting full
processChan := ais.getProcessChan() processChan := ais.getProcessChan()
queueLen := len(processChan) queueLen := len(processChan)
@ -1172,7 +1152,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
// Calculate end-to-end latency using message timestamp // Calculate end-to-end latency using message timestamp
var latency time.Duration var latency time.Duration
if msg.Type == InputMessageTypeOpusFrame && msg.Timestamp > 0 { if msg.Type == MessageTypeOpusFrame && msg.Timestamp > 0 {
msgTime := time.Unix(0, msg.Timestamp) msgTime := time.Unix(0, msg.Timestamp)
latency = time.Since(msgTime) latency = time.Since(msgTime)
// Use exponential moving average for end-to-end latency tracking // Use exponential moving average for end-to-end latency tracking
@ -1291,14 +1271,14 @@ func GetGlobalMessagePoolStats() MessagePoolStats {
} }
// getMessageChan safely returns the current message channel // getMessageChan safely returns the current message channel
func (ais *AudioInputServer) getMessageChan() chan *InputIPCMessage { func (ais *AudioInputServer) getMessageChan() chan *UnifiedIPCMessage {
ais.channelMutex.RLock() ais.channelMutex.RLock()
defer ais.channelMutex.RUnlock() defer ais.channelMutex.RUnlock()
return ais.messageChan return ais.messageChan
} }
// getProcessChan safely returns the current process channel // getProcessChan safely returns the current process channel
func (ais *AudioInputServer) getProcessChan() chan *InputIPCMessage { func (ais *AudioInputServer) getProcessChan() chan *UnifiedIPCMessage {
ais.channelMutex.RLock() ais.channelMutex.RLock()
defer ais.channelMutex.RUnlock() defer ais.channelMutex.RUnlock()
return ais.processChan return ais.processChan

View File

@ -13,24 +13,6 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
// Legacy aliases for backward compatibility
type OutputIPCConfig = UnifiedIPCConfig
type OutputIPCOpusConfig = UnifiedIPCOpusConfig
type OutputMessageType = UnifiedMessageType
type OutputIPCMessage = UnifiedIPCMessage
// Legacy constants for backward compatibility
const (
OutputMessageTypeOpusFrame = MessageTypeOpusFrame
OutputMessageTypeConfig = MessageTypeConfig
OutputMessageTypeOpusConfig = MessageTypeOpusConfig
OutputMessageTypeStop = MessageTypeStop
OutputMessageTypeHeartbeat = MessageTypeHeartbeat
OutputMessageTypeAck = MessageTypeAck
)
// Methods are now inherited from UnifiedIPCMessage
// Global shared message pool for output IPC client header reading // Global shared message pool for output IPC client header reading
var globalOutputClientMessagePool = NewGenericMessagePool(Config.OutputMessagePoolSize) var globalOutputClientMessagePool = NewGenericMessagePool(Config.OutputMessagePoolSize)
@ -48,9 +30,9 @@ type AudioOutputServer struct {
logger zerolog.Logger logger zerolog.Logger
// Message channels // Message channels
messageChan chan *OutputIPCMessage // Buffered channel for incoming messages messageChan chan *UnifiedIPCMessage // Buffered channel for incoming messages
processChan chan *OutputIPCMessage // Buffered channel for processing queue processChan chan *UnifiedIPCMessage // Buffered channel for processing queue
wg sync.WaitGroup // Wait group for goroutine coordination wg sync.WaitGroup // Wait group for goroutine coordination
// Configuration // Configuration
socketPath string socketPath string
@ -65,8 +47,8 @@ func NewAudioOutputServer() (*AudioOutputServer, error) {
socketPath: socketPath, socketPath: socketPath,
magicNumber: Config.OutputMagicNumber, magicNumber: Config.OutputMagicNumber,
logger: logger, logger: logger,
messageChan: make(chan *OutputIPCMessage, Config.ChannelBufferSize), messageChan: make(chan *UnifiedIPCMessage, Config.ChannelBufferSize),
processChan: make(chan *OutputIPCMessage, Config.ChannelBufferSize), processChan: make(chan *UnifiedIPCMessage, Config.ChannelBufferSize),
} }
return server, nil return server, nil
@ -112,6 +94,7 @@ func (s *AudioOutputServer) Stop() {
if s.listener != nil { if s.listener != nil {
s.listener.Close() s.listener.Close()
s.listener = nil
} }
if s.conn != nil { if s.conn != nil {
@ -171,7 +154,7 @@ func (s *AudioOutputServer) handleConnection(conn net.Conn) {
} }
// readMessage reads a message from the connection // readMessage reads a message from the connection
func (s *AudioOutputServer) readMessage(conn net.Conn) (*OutputIPCMessage, error) { func (s *AudioOutputServer) readMessage(conn net.Conn) (*UnifiedIPCMessage, error) {
header := make([]byte, 17) header := make([]byte, 17)
if _, err := io.ReadFull(conn, header); err != nil { if _, err := io.ReadFull(conn, header); err != nil {
return nil, fmt.Errorf("failed to read header: %w", err) return nil, fmt.Errorf("failed to read header: %w", err)
@ -182,7 +165,7 @@ func (s *AudioOutputServer) readMessage(conn net.Conn) (*OutputIPCMessage, error
return nil, fmt.Errorf("invalid magic number: expected %d, got %d", s.magicNumber, magic) return nil, fmt.Errorf("invalid magic number: expected %d, got %d", s.magicNumber, magic)
} }
msgType := OutputMessageType(header[4]) msgType := UnifiedMessageType(header[4])
length := binary.LittleEndian.Uint32(header[5:9]) length := binary.LittleEndian.Uint32(header[5:9])
timestamp := int64(binary.LittleEndian.Uint64(header[9:17])) timestamp := int64(binary.LittleEndian.Uint64(header[9:17]))
@ -194,7 +177,7 @@ func (s *AudioOutputServer) readMessage(conn net.Conn) (*OutputIPCMessage, error
} }
} }
return &OutputIPCMessage{ return &UnifiedIPCMessage{
Magic: magic, Magic: magic,
Type: msgType, Type: msgType,
Length: length, Length: length,
@ -204,14 +187,14 @@ func (s *AudioOutputServer) readMessage(conn net.Conn) (*OutputIPCMessage, error
} }
// processMessage processes a received message // processMessage processes a received message
func (s *AudioOutputServer) processMessage(msg *OutputIPCMessage) error { func (s *AudioOutputServer) processMessage(msg *UnifiedIPCMessage) error {
switch msg.Type { switch msg.Type {
case OutputMessageTypeOpusConfig: case MessageTypeOpusConfig:
return s.processOpusConfig(msg.Data) return s.processOpusConfig(msg.Data)
case OutputMessageTypeStop: case MessageTypeStop:
s.logger.Info().Msg("Received stop message") s.logger.Info().Msg("Received stop message")
return nil return nil
case OutputMessageTypeHeartbeat: case MessageTypeHeartbeat:
s.logger.Debug().Msg("Received heartbeat") s.logger.Debug().Msg("Received heartbeat")
return nil return nil
default: default:
@ -228,7 +211,7 @@ func (s *AudioOutputServer) processOpusConfig(data []byte) error {
} }
// Decode Opus configuration // Decode Opus configuration
config := OutputIPCOpusConfig{ config := UnifiedIPCOpusConfig{
SampleRate: int(binary.LittleEndian.Uint32(data[0:4])), SampleRate: int(binary.LittleEndian.Uint32(data[0:4])),
Channels: int(binary.LittleEndian.Uint32(data[4:8])), Channels: int(binary.LittleEndian.Uint32(data[4:8])),
FrameSize: int(binary.LittleEndian.Uint32(data[8:12])), FrameSize: int(binary.LittleEndian.Uint32(data[8:12])),
@ -282,9 +265,9 @@ func (s *AudioOutputServer) SendFrame(frame []byte) error {
return fmt.Errorf("no client connected") return fmt.Errorf("no client connected")
} }
msg := &OutputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: s.magicNumber, Magic: s.magicNumber,
Type: OutputMessageTypeOpusFrame, Type: MessageTypeOpusFrame,
Length: uint32(len(frame)), Length: uint32(len(frame)),
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Data: frame, Data: frame,
@ -294,8 +277,9 @@ func (s *AudioOutputServer) SendFrame(frame []byte) error {
} }
// writeMessage writes a message to the connection // writeMessage writes a message to the connection
func (s *AudioOutputServer) writeMessage(conn net.Conn, msg *OutputIPCMessage) error { func (s *AudioOutputServer) writeMessage(conn net.Conn, msg *UnifiedIPCMessage) error {
header := EncodeMessageHeader(msg.Magic, uint8(msg.Type), msg.Length, msg.Timestamp) header := make([]byte, 17)
EncodeMessageHeader(header, msg.Magic, uint8(msg.Type), msg.Length, msg.Timestamp)
if _, err := conn.Write(header); err != nil { if _, err := conn.Write(header); err != nil {
return fmt.Errorf("failed to write header: %w", err) return fmt.Errorf("failed to write header: %w", err)
@ -415,8 +399,8 @@ func (c *AudioOutputClient) ReceiveFrame() ([]byte, error) {
return nil, fmt.Errorf("invalid magic number in IPC message: got 0x%x, expected 0x%x", magic, outputMagicNumber) return nil, fmt.Errorf("invalid magic number in IPC message: got 0x%x, expected 0x%x", magic, outputMagicNumber)
} }
msgType := OutputMessageType(optMsg.header[4]) msgType := UnifiedMessageType(optMsg.header[4])
if msgType != OutputMessageTypeOpusFrame { if msgType != MessageTypeOpusFrame {
return nil, fmt.Errorf("unexpected message type: %d", msgType) return nil, fmt.Errorf("unexpected message type: %d", msgType)
} }
@ -443,7 +427,7 @@ func (c *AudioOutputClient) ReceiveFrame() ([]byte, error) {
} }
// SendOpusConfig sends Opus configuration to the audio output server // SendOpusConfig sends Opus configuration to the audio output server
func (c *AudioOutputClient) SendOpusConfig(config OutputIPCOpusConfig) error { func (c *AudioOutputClient) SendOpusConfig(config UnifiedIPCOpusConfig) error {
c.mtx.Lock() c.mtx.Lock()
defer c.mtx.Unlock() defer c.mtx.Unlock()
@ -460,9 +444,9 @@ func (c *AudioOutputClient) SendOpusConfig(config OutputIPCOpusConfig) error {
// Serialize Opus configuration using common function // Serialize Opus configuration using common function
data := EncodeOpusConfig(config.SampleRate, config.Channels, config.FrameSize, config.Bitrate, config.Complexity, config.VBR, config.SignalType, config.Bandwidth, config.DTX) data := EncodeOpusConfig(config.SampleRate, config.Channels, config.FrameSize, config.Bitrate, config.Complexity, config.VBR, config.SignalType, config.Bandwidth, config.DTX)
msg := &OutputIPCMessage{ msg := &UnifiedIPCMessage{
Magic: c.magicNumber, Magic: c.magicNumber,
Type: OutputMessageTypeOpusConfig, Type: MessageTypeOpusConfig,
Length: uint32(len(data)), Length: uint32(len(data)),
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Data: data, Data: data,
@ -472,8 +456,9 @@ func (c *AudioOutputClient) SendOpusConfig(config OutputIPCOpusConfig) error {
} }
// writeMessage writes a message to the connection // writeMessage writes a message to the connection
func (c *AudioOutputClient) writeMessage(msg *OutputIPCMessage) error { func (c *AudioOutputClient) writeMessage(msg *UnifiedIPCMessage) error {
header := EncodeMessageHeader(msg.Magic, uint8(msg.Type), msg.Length, msg.Timestamp) header := make([]byte, 17)
EncodeMessageHeader(header, msg.Magic, uint8(msg.Type), msg.Length, msg.Timestamp)
if _, err := c.conn.Write(header); err != nil { if _, err := c.conn.Write(header); err != nil {
return fmt.Errorf("failed to write header: %w", err) return fmt.Errorf("failed to write header: %w", err)

View File

@ -389,7 +389,8 @@ func (s *UnifiedAudioServer) SendFrame(frame []byte) error {
// writeMessage writes a message to the connection // writeMessage writes a message to the connection
func (s *UnifiedAudioServer) writeMessage(conn net.Conn, msg *UnifiedIPCMessage) error { func (s *UnifiedAudioServer) writeMessage(conn net.Conn, msg *UnifiedIPCMessage) error {
header := EncodeMessageHeader(msg.Magic, uint8(msg.Type), msg.Length, msg.Timestamp) header := make([]byte, 17)
EncodeMessageHeader(header, msg.Magic, uint8(msg.Type), msg.Length, msg.Timestamp)
// Optimize: Use single write for header+data to reduce system calls // Optimize: Use single write for header+data to reduce system calls
if msg.Length > 0 && msg.Data != nil { if msg.Length > 0 && msg.Data != nil {

View File

@ -62,7 +62,7 @@ func (aim *AudioInputIPCManager) Start() error {
return err return err
} }
config := InputIPCConfig{ config := UnifiedIPCConfig{
SampleRate: Config.InputIPCSampleRate, SampleRate: Config.InputIPCSampleRate,
Channels: Config.InputIPCChannels, Channels: Config.InputIPCChannels,
FrameSize: Config.InputIPCFrameSize, FrameSize: Config.InputIPCFrameSize,
@ -72,7 +72,7 @@ func (aim *AudioInputIPCManager) Start() error {
if err := ValidateInputIPCConfig(config.SampleRate, config.Channels, config.FrameSize); err != nil { if err := ValidateInputIPCConfig(config.SampleRate, config.Channels, config.FrameSize); err != nil {
aim.logger.Warn().Err(err).Msg("invalid input IPC config from constants, using defaults") aim.logger.Warn().Err(err).Msg("invalid input IPC config from constants, using defaults")
// Use safe defaults if config validation fails // Use safe defaults if config validation fails
config = InputIPCConfig{ config = UnifiedIPCConfig{
SampleRate: 48000, SampleRate: 48000,
Channels: 2, Channels: 2,
FrameSize: 960, FrameSize: 960,

View File

@ -56,7 +56,7 @@ func (aom *AudioOutputIPCManager) Start() error {
aom.logComponentStarted(AudioOutputIPCComponent) aom.logComponentStarted(AudioOutputIPCComponent)
// Send initial configuration // Send initial configuration
config := OutputIPCConfig{ config := UnifiedIPCConfig{
SampleRate: Config.SampleRate, SampleRate: Config.SampleRate,
Channels: Config.Channels, Channels: Config.Channels,
FrameSize: int(Config.AudioQualityMediumFrameSize.Milliseconds()), FrameSize: int(Config.AudioQualityMediumFrameSize.Milliseconds()),
@ -202,7 +202,7 @@ func (aom *AudioOutputIPCManager) calculateFrameRate() float64 {
} }
// SendConfig sends configuration to the IPC server // SendConfig sends configuration to the IPC server
func (aom *AudioOutputIPCManager) SendConfig(config OutputIPCConfig) error { func (aom *AudioOutputIPCManager) SendConfig(config UnifiedIPCConfig) error {
if aom.server == nil { if aom.server == nil {
return fmt.Errorf("audio output server not initialized") return fmt.Errorf("audio output server not initialized")
} }

View File

@ -318,7 +318,7 @@ func (s *AudioOutputSupervisor) connectClient() {
} }
// SendOpusConfig sends Opus configuration to the audio output subprocess // SendOpusConfig sends Opus configuration to the audio output subprocess
func (s *AudioOutputSupervisor) SendOpusConfig(config OutputIPCOpusConfig) error { func (aos *AudioOutputSupervisor) SendOpusConfig(config UnifiedIPCOpusConfig) error {
if outputClient == nil { if outputClient == nil {
return fmt.Errorf("client not initialized") return fmt.Errorf("client not initialized")
} }

View File

@ -214,8 +214,8 @@ func SetAudioQuality(quality AudioQuality) {
// Send dynamic configuration update to running subprocess via IPC // Send dynamic configuration update to running subprocess via IPC
if supervisor.IsConnected() { if supervisor.IsConnected() {
// Convert AudioConfig to OutputIPCOpusConfig with complete Opus parameters // Convert AudioConfig to UnifiedIPCOpusConfig with complete Opus parameters
opusConfig := OutputIPCOpusConfig{ opusConfig := UnifiedIPCOpusConfig{
SampleRate: config.SampleRate, SampleRate: config.SampleRate,
Channels: config.Channels, Channels: config.Channels,
FrameSize: int(config.FrameSize.Milliseconds() * int64(config.SampleRate) / 1000), // Convert ms to samples FrameSize: int(config.FrameSize.Milliseconds() * int64(config.SampleRate) / 1000), // Convert ms to samples
@ -311,8 +311,8 @@ func SetMicrophoneQuality(quality AudioQuality) {
// Send dynamic configuration update to running subprocess via IPC // Send dynamic configuration update to running subprocess via IPC
if supervisor.IsConnected() { if supervisor.IsConnected() {
// Convert AudioConfig to InputIPCOpusConfig with complete Opus parameters // Convert AudioConfig to UnifiedIPCOpusConfig with complete Opus parameters
opusConfig := InputIPCOpusConfig{ opusConfig := UnifiedIPCOpusConfig{
SampleRate: config.SampleRate, SampleRate: config.SampleRate,
Channels: config.Channels, Channels: config.Channels,
FrameSize: int(config.FrameSize.Milliseconds() * int64(config.SampleRate) / 1000), // Convert ms to samples FrameSize: int(config.FrameSize.Milliseconds() * int64(config.SampleRate) / 1000), // Convert ms to samples