Skip to content

Speaker

Overview

Maverick AI glasses have a built-in speaker. The SDK handles it via the M2AudioService (accessed from Evs.audioService).

There are two ways to play audio on the glasses:

Way Use it for API
Stream Live audio: AI / text-to-speech replies, media, internet radio openStreamwriteendStream / closeStream
Sound resource Short sounds you play again and again: chimes, alerts, UI feedback M2AudioResource + playSound

The SDK converts your audio to the glasses' own format (LC3) and sends it over BLE at real-time pace.

Streaming Audio

Open a stream with the format of the bytes you will pass to write:

Input Description
M2AudioStreamInput.Pcm16(sampleRate, channels) Raw signed 16-bit little-endian PCM, mono or stereo, any sample rate
M2AudioStreamInput.Encoded(format) Encoded audio - MP3, AAC, M4A or WAV. The SDK decodes it.
M2AudioStreamInput.Lc3Frames Audio already in the glasses' LC3 format, forwarded as is
M2AudioStreamInput.Url(url) An internet radio stream (ICY / HTTP MP3 or AAC). The SDK reads it itself - do not call write.

Only one stream can be open at a time.

// Text-to-speech output: 24 kHz mono PCM
Evs.audioService.openStream(M2AudioStreamInput.Pcm16(sampleRate = 24000))

Evs.audioService.write(pcmChunk)      // call as audio arrives
// ...
Evs.audioService.endStream()          // after the last chunk: let the tail finish playing
// Text-to-speech output: 24 kHz mono PCM
let audio = Evs.shared.audioService
audio.openStream(input: M2AudioStreamInput.Pcm16(sampleRate: 24000, channels: .mono))

audio.write(data: pcmChunk, offset: 0, size: pcmChunk.size)   // call as audio arrives
// ...
audio.endStream()                                             // let the tail finish playing

Ending a Stream

Call What it does
endStream() Graceful end: plays the audio still buffered, then closes. Use after the last write.
closeStream() Stops now and drops anything still buffered. Use to cancel.
flush() Drops the buffered audio but keeps the stream open - for when the user interrupts the AI mid-sentence and a new reply starts right away.

For Encoded streams you do not need endStream(): the SDK reports onAudioStreamFinished by itself when the decoder reaches the end of the input and the speaker has played it.

Stream Events

Evs.audioService.registerStreamListener(object : M2AudioService.IOnAudioStreamEvents {
    override fun onAudioStreamChanged(input: M2AudioStreamInput?) { /* opened / closed */ }
    override fun onAudioStreamStarted(input: M2AudioStreamInput) { /* first audio reached the glasses */ }
    override fun onAudioStreamFinished(input: M2AudioStreamInput) { /* Encoded stream fully played */ }
    override fun onAudioStreamError(error: M2AudioStreamError) { /* error.code, error.message */ }
    override fun onStreamMetadata(title: String) { /* internet radio: now playing */ }
})

write returns false when the SDK rejects the input straight away (no stream open, wrong data for the format). getLastStreamError() returns the reason.

Sound Resources

A sound is uploaded to the glasses once and played from there, so replaying it costs no BLE traffic. Supported source formats: WAV, MP3, AAC, M4A, and LC3.

// From a file bundled with the app - the format comes from the extension
val chime = M2AudioResource("sounds/chime.wav")

Evs.audioService.playSound(chime)                 // uploads on first use, then plays
Evs.audioService.playSound(chime, repeat = true)  // loop
Evs.audioService.stopSound(chime)
let chime = M2AudioResource(nameWithExtension: "sounds/chime.wav", persistent: false, tag: nil)

Evs.shared.audioService.playSound(audio: chime, repeat: false)
Evs.shared.audioService.stopSound(audio: chime)

In-memory audio works too: M2AudioResource(bytes, M2AudioFormat.Mp3).

Call What it does
playSound(audio, repeat) Plays the sound, uploading it first if needed
playSoundAdvanced(audio, repeat, offsetMillis, durationMillis) Plays part of the sound
stopSound(audio) Stops it
releaseSound(audio) Frees its slot on the glasses. A persistent sound stays cached for next time.
deleteSound(audio) Removes it from the glasses, cache included

Pass persistent = true when creating the resource to keep the sound in the glasses' cache across sessions.

Volume

Call Description
setVolume(volume) / getVolume() Speaker volume, 0-100. Default 80.
setDigitalGain(gain) Extra software boost above the hardware maximum. 1.0 = off; up to about 4.0 (+12 dB). Costs CPU - use only as much as needed.

Notes

  • Close the stream when you are done - an open stream keeps the speaker on.
  • With the Glasses Simulator, the audio plays on the PC speaker.

See Also

  • Microphone - Capture audio from the glasses microphones
  • Resources - Upload and cache resources on the glasses