Documentation Index
Fetch the complete documentation index at: https://cloud-docs.mentra.glass/llms.txt
Use this file to discover all available pages before exploring further.
Overview
AudioManager handles all audio-related functionality within a user session. It processes incoming audio data from smart glasses, manages buffering, handles LC3 codec decoding (when enabled), and distributes audio to transcription services and subscribed apps.
File: packages/cloud/src/services/session/AudioManager.ts
Key Responsibilities
- Audio Processing: Receives and processes raw audio data
- Codec Handling: LC3 audio decoding (optional)
- Buffer Management: Maintains recent audio buffer and ordered processing
- Service Integration: Feeds audio to transcription and translation services
- App Distribution: Relays audio to subscribed apps
- Debug Support: Optional audio file writing for debugging
Architecture
Audio Processing Pipeline
Main Processing Method
async processAudioData(
audioData: ArrayBuffer | any,
isLC3 = this.IS_LC3
): Promise<ArrayBuffer | void> {
// Update timestamp
this.userSession.lastAudioTimestamp = Date.now();
// Notify microphone manager
this.userSession.microphoneManager.onAudioReceived();
// Process the audio
const processedAudioData = audioData; // Or LC3 decode if needed
if (processedAudioData) {
// Feed to services
this.userSession.transcriptionManager.feedAudio(processedAudioData);
this.userSession.translationManager.feedAudio(processedAudioData);
// Relay to apps
this.relayAudioToApps(processedAudioData);
}
return processedAudioData;
}
LC3 Codec Support
When LC3 is enabled:
private async processAudioInternal(
audioData: ArrayBuffer,
isLC3: boolean
): Promise<ArrayBuffer | void> {
if (isLC3 && this.lc3Service) {
try {
// Decode LC3 to PCM
const decodedData = await this.lc3Service.decodeAudioChunk(audioData);
if (!decodedData) {
this.logger.warn(`⚠️ LC3 decode returned null`);
return undefined;
}
return decodedData;
} catch (error) {
this.logger.error(`❌ Error decoding LC3 audio:`, error);
await this.reinitializeLc3Service();
return undefined;
}
}
return audioData; // Return as-is for PCM
}
Buffer Management
Recent Audio Buffer
Maintains last 10 seconds of audio:
private addToRecentBuffer(audioData: ArrayBufferLike): void {
const now = Date.now();
// Add to buffer
this.recentAudioBuffer.push({
data: audioData,
timestamp: now
});
// Prune old data (keep only last 10 seconds)
const tenSecondsAgo = now - 10_000;
this.recentAudioBuffer = this.recentAudioBuffer.filter(
chunk => chunk.timestamp >= tenSecondsAgo
);
}
Ordered Audio Buffer
For handling out-of-order audio chunks:
interface OrderedAudioBuffer {
chunks: SequencedAudioChunk[];
lastProcessedSequence: number;
processingInProgress: boolean;
expectedNextSequence: number;
bufferSizeLimit: number; // Max 100 chunks
bufferTimeWindowMs: number; // 500ms window
bufferProcessingInterval: NodeJS.Timeout | null;
}
Adding to Ordered Buffer
addToOrderedBuffer(chunk: SequencedAudioChunk): void {
// Add to buffer
this.orderedBuffer.chunks.push(chunk);
// Sort by sequence number
this.orderedBuffer.chunks.sort(
(a, b) => a.sequenceNumber - b.sequenceNumber
);
// Enforce size limit
if (this.orderedBuffer.chunks.length > this.orderedBuffer.bufferSizeLimit) {
this.orderedBuffer.chunks = this.orderedBuffer.chunks.slice(
this.orderedBuffer.chunks.length - this.orderedBuffer.bufferSizeLimit
);
}
}
Processing Ordered Buffer
async processOrderedBuffer(): Promise<void> {
if (this.orderedBuffer.processingInProgress) return;
try {
this.orderedBuffer.processingInProgress = true;
for (const chunk of this.orderedBuffer.chunks) {
// Skip already processed
if (chunk.sequenceNumber <= this.orderedBuffer.lastProcessedSequence) {
continue;
}
// Process the chunk
await this.processAudioData(chunk.data, chunk.isLC3);
// Update tracking
this.orderedBuffer.lastProcessedSequence = chunk.sequenceNumber;
this.orderedBuffer.expectedNextSequence = chunk.sequenceNumber + 1;
}
// Remove processed chunks
this.orderedBuffer.chunks = this.orderedBuffer.chunks.filter(
chunk => chunk.sequenceNumber > this.orderedBuffer.lastProcessedSequence
);
} finally {
this.orderedBuffer.processingInProgress = false;
}
}
App Distribution
Relay to Subscribed Apps
private relayAudioToApps(audioData: ArrayBuffer): void {
// Get subscribers
const subscribedPackageNames = subscriptionService.getSubscribedApps(
this.userSession,
StreamType.AUDIO_CHUNK
);
if (subscribedPackageNames.length === 0) return;
// Send to each subscriber
for (const packageName of subscribedPackageNames) {
const connection = this.userSession.appWebsockets.get(packageName);
if (connection?.readyState === WebSocket.OPEN) {
try {
connection.send(audioData);
} catch (sendError) {
this.logger.error(
`Error sending audio to ${packageName}:`,
sendError
);
}
}
}
}
Debug Features
Audio Writer
For debugging audio issues:
private initializeAudioWriterIfNeeded(): void {
if (this.DEBUG_AUDIO && !this.audioWriter) {
this.audioWriter = new AudioWriter(this.userSession.userId);
}
}
// Write audio data
if (this.DEBUG_AUDIO) {
await this.audioWriter?.writePCM(processedAudioData);
await this.audioWriter?.writeLC3(rawLC3Data);
}
Service Recovery
LC3 Service Reinitialization
Handles LC3 decoder failures:
private async reinitializeLc3Service(): Promise<void> {
try {
if (this.lc3Service) {
this.logger.warn(`⚠️ Attempting to reinitialize LC3 service`);
// Clean up existing
this.lc3Service.cleanup();
this.lc3Service = undefined;
// Create new service
const newLc3Service = createLC3Service(this.userSession.sessionId);
await newLc3Service.initialize();
this.lc3Service = newLc3Service;
this.logger.info(`✅ Successfully reinitialized LC3 service`);
}
} catch (reinitError) {
this.logger.error(`❌ Failed to reinitialize LC3 service:`, reinitError);
}
}
Configuration
Debug Flags
private readonly LOG_AUDIO = false; // Enable audio logging
private readonly DEBUG_AUDIO = false; // Enable audio file writing
private readonly IS_LC3 = false; // Enable LC3 processing
Buffer Configuration
- Recent Buffer: Last 10 seconds of audio
- Ordered Buffer Size: Maximum 100 chunks
- Buffer Time Window: 500ms for ordering
- Processing Interval: 100ms default
Public Methods
Get Recent Audio Buffer
getRecentAudioBuffer(): { data: ArrayBufferLike; timestamp: number }[] {
return [...this.recentAudioBuffer];
}
Lifecycle Management
Initialization
constructor(userSession: UserSession) {
this.userSession = userSession;
this.logger = userSession.logger.child({ service: "AudioManager" });
// Initialize ordered buffer
this.orderedBuffer = {
chunks: [],
lastProcessedSequence: -1,
processingInProgress: false,
expectedNextSequence: 0,
bufferSizeLimit: 100,
bufferTimeWindowMs: 500,
bufferProcessingInterval: null
};
// Initialize LC3 if needed
this.initializeLc3Service();
}
Disposal
dispose(): void {
// Stop buffer processing
this.stopOrderedBufferProcessing();
// Clean up LC3 service
if (this.lc3Service) {
this.lc3Service.cleanup();
this.lc3Service = undefined;
}
// Clean up audio writer
if (this.audioWriter) {
this.audioWriter = undefined;
}
// Clear buffers
this.recentAudioBuffer = [];
this.orderedBuffer.chunks = [];
this.logger.info("AudioManager disposed");
}
Integration Points
- MicrophoneManager: Notified when audio is received
- TranscriptionManager: Receives processed audio for speech-to-text
- TranslationManager: Receives audio for translation services
- SubscriptionService: Determines which apps receive audio
- LC3Service: Handles audio codec operations
- Buffer Pruning: Automatically removes old audio data
- Ordered Processing: Handles out-of-order packets gracefully
- Error Recovery: Reinitializes services on failure
- Selective Distribution: Only sends to subscribed apps
Best Practices
- Monitor Buffer Sizes to prevent memory issues
- Handle LC3 Failures with automatic recovery
- Use Debug Flags sparingly in production
- Process Audio Efficiently to maintain real-time performance
- Clean Up Resources properly on disposal