Skip to main content

Audio Requirements

Required Format

Raw PCM, 16,000 Hz, 16-bit Signed Little-Endian, Mono

Technical Specifications

ParameterValueDescription
FormatPCM (Pulse Code Modulation)Uncompressed linear audio
Sample Rate16,000 Hz16,000 samples per second
Bit Depth16-bitSigned 16-bit integers
Channels1 (Mono)Single audio channel
EndiannessLittle-EndianLeast significant byte first
EncodingSigned IntegerTwo’s complement representation
Byte OrderLow byte, High byteFor each 16-bit sample

Sample Format Details

16-bit Signed Integer Range

ValueHexBinaryDescription
327670x7FFF0111111111111111Maximum positive
10x00010000000000000001Smallest positive
00x00000000000000000000Digital silence
-10xFFFF1111111111111111Smallest negative
-327680x80001000000000000000Maximum negative

Little-Endian Byte Order

For a 16-bit sample value of 0x1234:
  • Low byte (LSB): 0x34 (stored first)
  • High byte (MSB): 0x12 (stored second)
Memory layout:
Address: 0x00  0x01  0x02  0x03
Value:   0x34  0x12  0x78  0x56
Sample:  ←─0x1234──┘  ←─0x5678──┘

Data Rate Calculations

Bandwidth Requirements

Sample Rate: 16,000 Hz
Bit Depth: 16 bits = 2 bytes
Channels: 1 (Mono)

Bytes per second = 16,000 × 2 × 1 = 32,000 bytes/sec
Bits per second = 32,000 × 8 = 256,000 bps = 256 kbps

Buffer Size Recommendations

DurationSamplesBytesUse Case
10ms160320Minimal latency
20ms320640Recommended
50ms8001,600Standard buffer
100ms1,6003,200Large buffer
Recommended: 20ms chunks (640 bytes) provide the best balance of latency and reliability.

Implementation Examples

  • Flutter/Dart Configuration
  • JavaScript Web Audio API
  • Python Audio Processing
// Configure FlutterSound for recording
await _recorder.startRecorder(
  codec: Codec.pcm16,           // 16-bit PCM
  sampleRate: 16000,            // 16kHz
  numChannels: 1,               // Mono
  bitRate: 256000,              // 256 kbps
);

// Configure FlutterSound for playback
await _player.startPlayerFromStream(
  codec: Codec.pcm16,           // 16-bit PCM
  numChannels: 1,               // Mono
  sampleRate: 16000,            // 16kHz
);
Audio Stream Processing:
_recorderSubscription = stream?.listen((Uint8List buffer) {
  // Verify buffer is correctly formatted
  assert(buffer.length % 2 == 0, 'Buffer must be even length');
  
  // Send to WebSocket
  _channel?.sink.add(buffer);
});

Audio Quality Guidelines

Recording Best Practices

Microphone Setup

Use close-talking microphone 6-12 inches from speaker

Environment

Record in quiet environment with minimal background noise

Gain Control

Avoid automatic gain control (AGC) for consistent levels

Sample Quality

Maintain signal levels between -12dB to -6dB for optimal quality

Audio Processing Chain

Validation and Testing

Format Validation

function validateAudioFormat(audioBuffer) {
  // Check if buffer length is even (16-bit samples)
  if (audioBuffer.byteLength % 2 !== 0) {
    throw new Error('Invalid audio buffer: length must be even');
  }
  
  // Check for reasonable buffer size
  const minSize = 160; // 10ms at 16kHz
  const maxSize = 8000; // ~250ms at 16kHz
  
  if (audioBuffer.byteLength < minSize) {
    throw new Error('Audio buffer too small');
  }
  
  if (audioBuffer.byteLength > maxSize) {
    throw new Error('Audio buffer too large');
  }
  
  return true;
}

Audio Level Monitoring

class AudioLevelMonitor {
  static double calculateRMS(Uint8List pcmData) {
    double sum = 0.0;
    final samples = pcmData.length ~/ 2;
    
    for (int i = 0; i < pcmData.length; i += 2) {
      // Convert little-endian bytes to 16-bit signed int
      final sample = (pcmData[i + 1] << 8) | pcmData[i];
      final signedSample = sample > 32767 ? sample - 65536 : sample;
      sum += signedSample * signedSample;
    }
    
    return sqrt(sum / samples);
  }
  
  static double rmsToDecibels(double rms) {
    return 20 * log(rms / 32767) / ln10;
  }
}

Common Issues and Solutions

Symptoms:
  • Crackling or popping sounds
  • Metallic audio quality
  • Clipped audio
Causes:
  • Incorrect sample rate conversion
  • Audio clipping (levels too high)
  • Wrong endianness
  • Incorrect bit depth
Solutions:
// Ensure proper audio configuration
await recorder.startRecorder(
  codec: Codec.pcm16,        // Correct: 16-bit PCM
  sampleRate: 16000,         // Correct: 16kHz
  numChannels: 1,            // Correct: Mono
);
Symptoms:
  • No sound transmitted or received
  • Zero bytes in audio buffers
  • WebSocket receives empty data
Debug Steps:
// Check audio permissions
final status = await Permission.microphone.status;
print('Microphone permission: $status');

// Monitor audio data
stream?.listen((buffer) {
  print('Audio buffer size: ${buffer?.length} bytes');
  if (buffer?.isNotEmpty == true) {
    final level = AudioLevelMonitor.calculateRMS(buffer!);
    print('Audio level: ${AudioLevelMonitor.rmsToDecibels(level)} dB');
  }
});
Server Error:
{
  "type": "error",
  "error": {
    "code": "INVALID_AUDIO_FORMAT",
    "message": "Audio must be 16-bit PCM at 16kHz mono"
  }
}
Checklist:
  • ✅ Sample rate is exactly 16,000 Hz
  • ✅ Bit depth is 16-bit (not 8-bit or 24-bit)
  • ✅ Channel count is 1 (mono)
  • ✅ Endianness is little-endian
  • ✅ Format is raw PCM (not compressed)

Testing Audio Format

Test Audio Generation

import numpy as np
import wave

def generate_test_tone(frequency=440, duration=1.0):
    """Generate a test tone in correct format"""
    sample_rate = 16000
    samples = int(sample_rate * duration)
    
    # Generate sine wave
    t = np.linspace(0, duration, samples, False)
    audio = np.sin(2 * np.pi * frequency * t)
    
    # Convert to 16-bit PCM
    audio_16bit = (audio * 32767).astype(np.int16)
    
    return audio_16bit.tobytes()

# Test with 440Hz tone for 1 second
test_audio = generate_test_tone()
print(f"Generated {len(test_audio)} bytes of test audio")

Format Verification

function verifyAudioFormat(audioBuffer) {
  const view = new DataView(audioBuffer);
  const samples = audioBuffer.byteLength / 2;
  
  console.log(`Audio buffer: ${audioBuffer.byteLength} bytes`);
  console.log(`Sample count: ${samples}`);
  console.log(`Duration: ${samples / 16000} seconds`);
  
  // Check first few samples
  for (let i = 0; i < Math.min(10, samples); i++) {
    const sample = view.getInt16(i * 2, true); // true = little-endian
    console.log(`Sample ${i}: ${sample}`);
  }
}