mirror of https://github.com/jetkvm/kvm.git
refactor(audio): standardize component logging names and cleanup
- Add component name constants for consistent logging across audio modules - Remove redundant debug logs and outdated comments - Delete obsolete naming standards documentation file
This commit is contained in:
parent
158437352c
commit
d3bbe1bf0a
|
@ -941,8 +941,6 @@ func cgoAudioReadEncode(buf []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip initialization check for now to avoid CGO compilation issues
|
// Skip initialization check for now to avoid CGO compilation issues
|
||||||
// Note: The C code already has comprehensive state tracking with capture_initialized,
|
|
||||||
// capture_initializing, playback_initialized, and playback_initializing flags.
|
|
||||||
|
|
||||||
// Direct CGO call with minimal overhead - unsafe.Pointer(&slice[0]) is safe for validated non-empty buffers
|
// Direct CGO call with minimal overhead - unsafe.Pointer(&slice[0]) is safe for validated non-empty buffers
|
||||||
n := C.jetkvm_audio_read_encode(unsafe.Pointer(&buf[0]))
|
n := C.jetkvm_audio_read_encode(unsafe.Pointer(&buf[0]))
|
||||||
|
@ -1084,72 +1082,6 @@ func ReturnBufferToPool(buf []byte) {
|
||||||
ReturnOptimalBuffer(buf)
|
ReturnOptimalBuffer(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: AudioFrameBatch is now defined in batch_audio.go
|
|
||||||
// This is kept here for reference but commented out to avoid conflicts
|
|
||||||
/*
|
|
||||||
// AudioFrameBatch represents a batch of audio frames for processing
|
|
||||||
type AudioFrameBatch struct {
|
|
||||||
// Buffer for batch processing
|
|
||||||
buffer []byte
|
|
||||||
// Number of frames in the batch
|
|
||||||
frameCount int
|
|
||||||
// Size of each frame
|
|
||||||
frameSize int
|
|
||||||
// Current position in the buffer
|
|
||||||
position int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAudioFrameBatch creates a new audio frame batch with the specified capacity
|
|
||||||
func NewAudioFrameBatch(maxFrames int) *AudioFrameBatch {
|
|
||||||
// Get cached config
|
|
||||||
cache := GetCachedConfig()
|
|
||||||
cache.Update()
|
|
||||||
|
|
||||||
// Calculate frame size based on cached config
|
|
||||||
frameSize := cache.GetMinReadEncodeBuffer()
|
|
||||||
|
|
||||||
// Create batch with buffer sized for maxFrames
|
|
||||||
return &AudioFrameBatch{
|
|
||||||
buffer: GetBufferFromPool(maxFrames * frameSize),
|
|
||||||
frameCount: 0,
|
|
||||||
frameSize: frameSize,
|
|
||||||
position: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFrame adds a frame to the batch
|
|
||||||
// Returns true if the batch is full after adding this frame
|
|
||||||
func (b *AudioFrameBatch) AddFrame(frame []byte) bool {
|
|
||||||
// Calculate position in buffer for this frame
|
|
||||||
pos := b.position
|
|
||||||
|
|
||||||
// Copy frame data to batch buffer
|
|
||||||
copy(b.buffer[pos:pos+len(frame)], frame)
|
|
||||||
|
|
||||||
// Update position and frame count
|
|
||||||
b.position += len(frame)
|
|
||||||
b.frameCount++
|
|
||||||
|
|
||||||
// Check if batch is full (buffer capacity reached)
|
|
||||||
return b.position >= len(b.buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets the batch for reuse
|
|
||||||
func (b *AudioFrameBatch) Reset() {
|
|
||||||
b.frameCount = 0
|
|
||||||
b.position = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release returns the batch buffer to the pool
|
|
||||||
func (b *AudioFrameBatch) Release() {
|
|
||||||
ReturnBufferToPool(b.buffer)
|
|
||||||
b.buffer = nil
|
|
||||||
b.frameCount = 0
|
|
||||||
b.frameSize = 0
|
|
||||||
b.position = 0
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ReadEncodeWithPooledBuffer reads audio data and encodes it using a buffer from the pool
|
// ReadEncodeWithPooledBuffer reads audio data and encodes it using a buffer from the pool
|
||||||
func ReadEncodeWithPooledBuffer() ([]byte, int, error) {
|
func ReadEncodeWithPooledBuffer() ([]byte, int, error) {
|
||||||
cache := GetCachedConfig()
|
cache := GetCachedConfig()
|
||||||
|
|
|
@ -15,6 +15,12 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Component name constants for logging
|
||||||
|
const (
|
||||||
|
AudioInputServerComponent = "audio-input-server"
|
||||||
|
AudioInputClientComponent = "audio-input-client"
|
||||||
|
)
|
||||||
|
|
||||||
// Constants are now defined in unified_ipc.go
|
// Constants are now defined in unified_ipc.go
|
||||||
var (
|
var (
|
||||||
maxFrameSize = GetConfig().MaxFrameSize // Maximum Opus frame size
|
maxFrameSize = GetConfig().MaxFrameSize // Maximum Opus frame size
|
||||||
|
|
|
@ -9,6 +9,11 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Component name constant for logging
|
||||||
|
const (
|
||||||
|
AudioInputIPCComponent = "audio-input-ipc"
|
||||||
|
)
|
||||||
|
|
||||||
// AudioInputIPCManager manages microphone input using IPC when enabled
|
// AudioInputIPCManager manages microphone input using IPC when enabled
|
||||||
type AudioInputIPCManager struct {
|
type AudioInputIPCManager struct {
|
||||||
metrics AudioInputMetrics
|
metrics AudioInputMetrics
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
// This should be called from main() when the subprocess is detected
|
// This should be called from main() when the subprocess is detected
|
||||||
func RunAudioInputServer() error {
|
func RunAudioInputServer() error {
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-server").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-server").Logger()
|
||||||
logger.Debug().Msg("audio input server subprocess starting")
|
|
||||||
|
|
||||||
// Parse OPUS configuration from environment variables
|
// Parse OPUS configuration from environment variables
|
||||||
bitrate, complexity, vbr, signalType, bandwidth, dtx := parseOpusConfig()
|
bitrate, complexity, vbr, signalType, bandwidth, dtx := parseOpusConfig()
|
||||||
|
@ -46,7 +45,7 @@ func RunAudioInputServer() error {
|
||||||
// Continue without playback - input functionality doesn't require it
|
// Continue without playback - input functionality doesn't require it
|
||||||
} else {
|
} else {
|
||||||
defer CGOAudioPlaybackClose()
|
defer CGOAudioPlaybackClose()
|
||||||
logger.Debug().Msg("CGO audio playback initialized successfully")
|
logger.Info().Msg("CGO audio playback initialized successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and start the IPC server
|
// Create and start the IPC server
|
||||||
|
@ -63,7 +62,7 @@ func RunAudioInputServer() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug().Msg("audio input server started, waiting for connections")
|
logger.Info().Msg("audio input server started, waiting for connections")
|
||||||
|
|
||||||
// Set up signal handling for graceful shutdown
|
// Set up signal handling for graceful shutdown
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
@ -77,16 +76,13 @@ func RunAudioInputServer() error {
|
||||||
case sig := <-sigChan:
|
case sig := <-sigChan:
|
||||||
logger.Info().Str("signal", sig.String()).Msg("received shutdown signal")
|
logger.Info().Str("signal", sig.String()).Msg("received shutdown signal")
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logger.Debug().Msg("context cancelled")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
logger.Debug().Msg("shutting down audio input server")
|
|
||||||
server.Stop()
|
server.Stop()
|
||||||
|
|
||||||
// Give some time for cleanup
|
// Give some time for cleanup
|
||||||
time.Sleep(GetConfig().DefaultSleepDuration)
|
time.Sleep(GetConfig().DefaultSleepDuration)
|
||||||
|
|
||||||
logger.Debug().Msg("audio input server subprocess stopped")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,14 +117,12 @@ func NewLatencyMonitor(config LatencyConfig, logger zerolog.Logger) *LatencyMoni
|
||||||
func (lm *LatencyMonitor) Start() {
|
func (lm *LatencyMonitor) Start() {
|
||||||
lm.wg.Add(1)
|
lm.wg.Add(1)
|
||||||
go lm.monitoringLoop()
|
go lm.monitoringLoop()
|
||||||
lm.logger.Debug().Msg("latency monitor started")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the latency monitor
|
// Stop stops the latency monitor
|
||||||
func (lm *LatencyMonitor) Stop() {
|
func (lm *LatencyMonitor) Stop() {
|
||||||
lm.cancel()
|
lm.cancel()
|
||||||
lm.wg.Wait()
|
lm.wg.Wait()
|
||||||
lm.logger.Debug().Msg("latency monitor stopped")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordLatency records a new latency measurement
|
// RecordLatency records a new latency measurement
|
||||||
|
@ -267,13 +265,11 @@ func (lm *LatencyMonitor) runOptimization() {
|
||||||
adaptiveThreshold := time.Duration(float64(lm.config.TargetLatency.Nanoseconds()) * (1.0 + lm.config.AdaptiveThreshold))
|
adaptiveThreshold := time.Duration(float64(lm.config.TargetLatency.Nanoseconds()) * (1.0 + lm.config.AdaptiveThreshold))
|
||||||
if metrics.Average > adaptiveThreshold {
|
if metrics.Average > adaptiveThreshold {
|
||||||
needsOptimization = true
|
needsOptimization = true
|
||||||
lm.logger.Debug().Dur("average_latency", metrics.Average).Dur("threshold", adaptiveThreshold).Msg("average latency above adaptive threshold")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if jitter is too high
|
// Check if jitter is too high
|
||||||
if metrics.Jitter > lm.config.JitterThreshold {
|
if metrics.Jitter > lm.config.JitterThreshold {
|
||||||
needsOptimization = true
|
needsOptimization = true
|
||||||
lm.logger.Debug().Dur("jitter", metrics.Jitter).Dur("threshold", lm.config.JitterThreshold).Msg("jitter above threshold")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if needsOptimization {
|
if needsOptimization {
|
||||||
|
@ -290,8 +286,6 @@ func (lm *LatencyMonitor) runOptimization() {
|
||||||
lm.logger.Error().Err(err).Msg("optimization callback failed")
|
lm.logger.Error().Err(err).Msg("optimization callback failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lm.logger.Debug().Interface("metrics", metrics).Msg("latency optimization triggered")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@ import (
|
||||||
"github.com/jetkvm/kvm/internal/logging"
|
"github.com/jetkvm/kvm/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Component name constant for logging
|
||||||
|
const (
|
||||||
|
AudioInputManagerComponent = "audio-input-manager"
|
||||||
|
)
|
||||||
|
|
||||||
// AudioInputMetrics holds metrics for microphone input
|
// AudioInputMetrics holds metrics for microphone input
|
||||||
// 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)
|
||||||
type AudioInputMetrics struct {
|
type AudioInputMetrics struct {
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
package audio
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Naming Standards Documentation
|
|
||||||
// This file documents the standardized naming conventions for audio components
|
|
||||||
// to ensure consistency across the entire audio system.
|
|
||||||
|
|
||||||
/*
|
|
||||||
STANDARDIZED NAMING CONVENTIONS:
|
|
||||||
|
|
||||||
1. COMPONENT HIERARCHY:
|
|
||||||
- Manager: High-level component that orchestrates multiple subsystems
|
|
||||||
- Supervisor: Process lifecycle management (start/stop/restart processes)
|
|
||||||
- Server: IPC server that handles incoming connections
|
|
||||||
- Client: IPC client that connects to servers
|
|
||||||
- Streamer: High-performance streaming component
|
|
||||||
|
|
||||||
2. NAMING PATTERNS:
|
|
||||||
Input Components:
|
|
||||||
- AudioInputManager (replaces: AudioInputManager) ✓
|
|
||||||
- AudioInputSupervisor (replaces: AudioInputSupervisor) ✓
|
|
||||||
- AudioInputServer (replaces: AudioInputServer) ✓
|
|
||||||
- AudioInputClient (replaces: AudioInputClient) ✓
|
|
||||||
- AudioInputStreamer (new: for consistency with OutputStreamer)
|
|
||||||
|
|
||||||
Output Components:
|
|
||||||
- AudioOutputSupervisor (replaces: AudioOutputSupervisor) ✓
|
|
||||||
- AudioOutputServer (replaces: AudioOutputServer) ✓
|
|
||||||
- AudioOutputClient (replaces: AudioOutputClient) ✓
|
|
||||||
|
|
||||||
3. IPC NAMING:
|
|
||||||
- AudioInputIPCManager (replaces: AudioInputIPCManager) ✓
|
|
||||||
- AudioOutputIPCManager (new: for consistency)
|
|
||||||
|
|
||||||
4. CONFIGURATION NAMING:
|
|
||||||
- InputIPCConfig (replaces: InputIPCConfig) ✓
|
|
||||||
- OutputIPCConfig (new: for consistency)
|
|
||||||
|
|
||||||
5. MESSAGE NAMING:
|
|
||||||
- InputIPCMessage (replaces: InputIPCMessage) ✓
|
|
||||||
- OutputIPCMessage (replaces: OutputIPCMessage) ✓
|
|
||||||
- InputMessageType (replaces: InputMessageType) ✓
|
|
||||||
- OutputMessageType (replaces: OutputMessageType) ✓
|
|
||||||
|
|
||||||
ISSUES IDENTIFIED:
|
|
||||||
1. Missing AudioOutputIPCManager for symmetry
|
|
||||||
2. Missing OutputIPCConfig for consistency
|
|
||||||
3. Component names in logging should be standardized
|
|
||||||
|
|
||||||
IMPLEMENTATION PLAN:
|
|
||||||
1. Create AudioOutputIPCManager for symmetry
|
|
||||||
2. Standardize all component logging names
|
|
||||||
3. Update all references consistently
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Component name constants for consistent logging
|
|
||||||
const (
|
|
||||||
// Input component names
|
|
||||||
AudioInputManagerComponent = "audio-input-manager"
|
|
||||||
AudioInputSupervisorComponent = "audio-input-supervisor"
|
|
||||||
AudioInputServerComponent = "audio-input-server"
|
|
||||||
AudioInputClientComponent = "audio-input-client"
|
|
||||||
AudioInputIPCComponent = "audio-input-ipc"
|
|
||||||
|
|
||||||
// Output component names
|
|
||||||
AudioOutputSupervisorComponent = "audio-output-supervisor"
|
|
||||||
AudioOutputServerComponent = "audio-output-server"
|
|
||||||
AudioOutputClientComponent = "audio-output-client"
|
|
||||||
AudioOutputIPCComponent = "audio-output-ipc"
|
|
||||||
|
|
||||||
// Common component names
|
|
||||||
AudioRelayComponent = "audio-relay"
|
|
||||||
AudioEventsComponent = "audio-events"
|
|
||||||
AudioMetricsComponent = "audio-metrics"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Interface definitions for consistent component behavior
|
|
||||||
type AudioManagerInterface interface {
|
|
||||||
Start() error
|
|
||||||
Stop()
|
|
||||||
IsRunning() bool
|
|
||||||
IsReady() bool
|
|
||||||
GetMetrics() interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AudioSupervisorInterface interface {
|
|
||||||
Start() error
|
|
||||||
Stop() error
|
|
||||||
IsRunning() bool
|
|
||||||
GetProcessPID() int
|
|
||||||
}
|
|
||||||
|
|
||||||
type AudioServerInterface interface {
|
|
||||||
Start() error
|
|
||||||
Stop()
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type AudioClientInterface interface {
|
|
||||||
Connect() error
|
|
||||||
Disconnect()
|
|
||||||
IsConnected() bool
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type AudioStreamerInterface interface {
|
|
||||||
Start() error
|
|
||||||
Stop()
|
|
||||||
GetStats() (processed, dropped int64, avgProcessingTime time.Duration)
|
|
||||||
}
|
|
|
@ -8,6 +8,11 @@ import (
|
||||||
"github.com/jetkvm/kvm/internal/logging"
|
"github.com/jetkvm/kvm/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Component name constant for logging
|
||||||
|
const (
|
||||||
|
AudioOutputIPCComponent = "audio-output-ipc"
|
||||||
|
)
|
||||||
|
|
||||||
// AudioOutputMetrics represents metrics for audio output operations
|
// AudioOutputMetrics represents metrics for audio output operations
|
||||||
type AudioOutputMetrics struct {
|
type AudioOutputMetrics struct {
|
||||||
// Atomic int64 field first for proper ARM32 alignment
|
// Atomic int64 field first for proper ARM32 alignment
|
||||||
|
@ -208,8 +213,6 @@ func (aom *AudioOutputIPCManager) SendConfig(config OutputIPCConfig) error {
|
||||||
return fmt.Errorf("output configuration validation failed: %w", err)
|
return fmt.Errorf("output configuration validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: AudioOutputServer doesn't have SendConfig method yet
|
|
||||||
// This is a placeholder for future implementation
|
|
||||||
aom.logger.Info().Interface("config", config).Msg("configuration received")
|
aom.logger.Info().Interface("config", config).Msg("configuration received")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
// This should be called from main() when the subprocess is detected
|
// This should be called from main() when the subprocess is detected
|
||||||
func RunAudioOutputServer() error {
|
func RunAudioOutputServer() error {
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger()
|
||||||
logger.Debug().Msg("audio output server subprocess starting")
|
|
||||||
|
|
||||||
// Parse OPUS configuration from environment variables
|
// Parse OPUS configuration from environment variables
|
||||||
bitrate, complexity, vbr, signalType, bandwidth, dtx := parseOpusConfig()
|
bitrate, complexity, vbr, signalType, bandwidth, dtx := parseOpusConfig()
|
||||||
|
@ -51,7 +50,7 @@ func RunAudioOutputServer() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug().Msg("audio output server started, waiting for connections")
|
logger.Info().Msg("audio output server started, waiting for connections")
|
||||||
|
|
||||||
// Set up signal handling for graceful shutdown
|
// Set up signal handling for graceful shutdown
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
@ -65,16 +64,13 @@ func RunAudioOutputServer() error {
|
||||||
case sig := <-sigChan:
|
case sig := <-sigChan:
|
||||||
logger.Info().Str("signal", sig.String()).Msg("received shutdown signal")
|
logger.Info().Str("signal", sig.String()).Msg("received shutdown signal")
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logger.Debug().Msg("context cancelled")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
logger.Debug().Msg("shutting down audio output server")
|
|
||||||
StopNonBlockingAudioStreaming()
|
StopNonBlockingAudioStreaming()
|
||||||
|
|
||||||
// Give some time for cleanup
|
// Give some time for cleanup
|
||||||
time.Sleep(GetConfig().DefaultSleepDuration)
|
time.Sleep(GetConfig().DefaultSleepDuration)
|
||||||
|
|
||||||
logger.Debug().Msg("audio output server subprocess stopped")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,11 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Component name constants for logging
|
||||||
|
const (
|
||||||
|
AudioOutputSupervisorComponent = "audio-output-supervisor"
|
||||||
|
)
|
||||||
|
|
||||||
// Restart configuration is now retrieved from centralized config
|
// Restart configuration is now retrieved from centralized config
|
||||||
func getMaxRestartAttempts() int {
|
func getMaxRestartAttempts() int {
|
||||||
return GetConfig().MaxRestartAttempts
|
return GetConfig().MaxRestartAttempts
|
||||||
|
|
|
@ -368,7 +368,6 @@ func ValidateFrameDuration(duration time.Duration) error {
|
||||||
cache := GetCachedConfig()
|
cache := GetCachedConfig()
|
||||||
|
|
||||||
// Convert frameSize (samples) to duration for comparison
|
// Convert frameSize (samples) to duration for comparison
|
||||||
// Note: This calculation should match how frameSize is converted to duration elsewhere
|
|
||||||
cachedFrameSize := int(cache.frameSize.Load())
|
cachedFrameSize := int(cache.frameSize.Load())
|
||||||
cachedSampleRate := int(cache.sampleRate.Load())
|
cachedSampleRate := int(cache.sampleRate.Load())
|
||||||
|
|
||||||
|
@ -476,9 +475,6 @@ func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: We're transitioning from individual cached values to using AudioConfigCache
|
|
||||||
// for better consistency and reduced maintenance overhead
|
|
||||||
|
|
||||||
// Global variable for backward compatibility
|
// Global variable for backward compatibility
|
||||||
var cachedMaxFrameSize int
|
var cachedMaxFrameSize int
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue