CXR-L(EN)
cxr-l-sdk · v1.0.1 · snapshot 2026-05-13· ↗ source

Audio Recording Guide

Recording on Rokid Glasses

Rokid Glasses provides an 8-channel recording configuration. You can obtain these 8 channels of audio by setting the Audio Channel.

  • Channels 0/1: Post-algorithm audio
  • Channels 2/3/4/5: Raw audio from the 4 microphones on Rokid Glasses
  • Channels 6/7: Hardware echo reference

Reference implementation:

package com.rokid.cxrssdksamples.activities.audioRecord
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.IntentFilter
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.os.Environment
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.rokid.cxrssdksamples.activities.keys.KeyReceiver
import com.rokid.cxrssdksamples.activities.keys.KeyReceiverListener
import com.rokid.cxrssdksamples.activities.keys.KeyType
import com.rokid.cxrssdksamples.default.CONSTANT
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
interface PermissionNeed{
fun needPermission()
}
@SuppressLint(“MissingPermission”)
class AudioRecordViewModel : ViewModel() {
private val _isRecording = MutableStateFlow(false)
val isRecording = _isRecording.asStateFlow()
private val _isPreparing = MutableStateFlow(false)
val isPreparing = _isPreparing.asStateFlow()
var permissionNeed: PermissionNeed? = null
private var keyReceiver: KeyReceiver = KeyReceiver().apply {
listener = object : KeyReceiverListener {
override fun onReceive(keyType: KeyType) {
// Update state to show latest key type in UI
when (keyType) {
KeyType.CLICK -> {
Log.e(“AudioRecordActivity”, “Click”)
if (!permissionGranted.value) {
permissionNeed?.needPermission()
}else{
// Stop recording
if (_isRecording.value) {
_isRecording.value = false
stopRecording()
}else{
_isRecording.value = true
startAudioRecord()
}
}
}
KeyType.BUTTON_DOWN -> {
Log.e(“AudioRecordActivity”, “Button down”)
}
KeyType.BUTTON_UP -> {
Log.e(“AudioRecordActivity”, “Button up”)
}
KeyType.DOUBLE_CLICK -> {
Log.e(“AudioRecordActivity”, “Double click”)
}
KeyType.AI_START -> {
Log.e(“AudioRecordActivity”, “AI start”)
}
KeyType.LONG_PRESS -> {
Log.e(“AudioRecordActivity”, “Long press”)
}
else -> {
Log.e(“AudioRecordActivity”, “Other key”)
}
}
}
}
}
private var recorder: AudioRecord? = null
private var recordingThread: Thread? = null
private var isRecordingActive = false
private val _permissionGranted = MutableStateFlow(false)
val permissionGranted = _permissionGranted.asStateFlow()
companion object {
private const val SAMPLE_RATE = 16000 // 16kHz
private const val CHANNEL_CONFIG = CONSTANT.AUDIO_CHANNEL
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT // 16-bit
private const val BUFFER_SIZE = 1024
}
@SuppressLint(“UnspecifiedRegisterReceiverFlag”)
fun registerReceiver(activity: Activity) {
activity.registerReceiver(keyReceiver, IntentFilter().apply {
addAction(KeyType.CLICK.action)
addAction(KeyType.BUTTON_DOWN.action)
addAction(KeyType.BUTTON_UP.action)
addAction(KeyType.DOUBLE_CLICK.action)
addAction(KeyType.AI_START.action)
addAction(KeyType.LONG_PRESS.action)
priority = 100
})
}
fun unregisterReceiver(activity: Activity) {
keyReceiver?.let {
activity.unregisterReceiver(it)
}
}
fun permissionGranted(granted: Boolean) {
_permissionGranted.value = granted
}
fun stopRecording() {
isRecordingActive = false
recordingThread?.join()
recorder?.stop()
recorder?.release()
recorder = null
}
fun startAudioRecord() {
if (recorder == null) {
// val bufferSize = AudioRecord.getMinBufferSize(
// SAMPLE_RATE,
// CHANNEL_CONFIG,
// AUDIO_FORMAT
// )
recorder = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.MIC)
.setAudioFormat(
AudioFormat.Builder()
.setSampleRate(SAMPLE_RATE)
.setChannelMask(CHANNEL_CONFIG)
.setEncoding(AUDIO_FORMAT)
.build()
)
.build()
}
recorder?.startRecording()
isRecordingActive = true
recordingThread = Thread {
writeAudioDataToFile()
}
recordingThread?.start()
}
private fun writeAudioDataToFile() {
val audioDir = File(/sdcard/Audio/)
if (!audioDir.exists()) {
audioDir.mkdirs()
}
val timeStamp = SimpleDateFormat(“yyyyMMdd_HHmmss”, Locale.getDefault()).format(Date())
val fileName = “$timeStamp.pcm”
val file = File(audioDir, fileName)
Log.d(“AudioRecordViewModel”, “Saving audio to: ${file.absolutePath})
try {
FileOutputStream(file).use { outputStream ->
val buffer = ByteArray(BUFFER_SIZE)
while (isRecordingActive) {
val read = recorder?.read(buffer, 0, BUFFER_SIZE) ?: 0
if (read > 0) {
outputStream.write(buffer, 0, read)
}
}
}
} catch (e: Exception) {
Log.e(“AudioRecordViewModel”, “Error writing audio data to file”, e)
}
}
}
Marcin Miazga