一、Product Introduction

Product Overview

Intelligent Voice Development Kit

An intelligent voice development kit designed for efficient development, integrating high performance, low power consumption, and cross-platform compatibility. The kit features an integrated USB sound card and 4/6-microphone array, equipped with CAE noise reduction and AEC echo cancellation technologies. It has a built-in power amplifier to directly drive dual speakers, and comes with an AI SDK and AIUI cloud service.

With low-code integration, it enables quick implementation and shortens project cycles. Boasting a microphone sensitivity of -32dBA and a signal-to-noise ratio of 65dB, it supports stable wake-up and recognition within a 3-5 meter range. Its wide-temperature and low-power design makes it suitable for multiple scenarios, and it is widely applied in products and fields such as robots, industrial development boards, intelligent service terminals, smart mirrors, smart home panels, and commercial display devices.

_images/intelligent-voice-control-kit.png

Detailed Specifications

Microphone Array Board

Microphone Type Linear 4-Microphone Array Circular 6-Microphone Array
Module Array Shape Long Strip Circular
Number of Array Elements Supports 4 ECM Electret Microphones (6027 specification recommended) Supports 6 On-Board MEMS Analog Silicon Microphones
MIC Signal-to-Noise Ratio SNR > 70dB SNR > 65dB
MIC Sensitivity -32dBA, Consistency Error ≤ 2dB -32dBA, Consistency Error ≤ 1dB
Wake-up Distance 3 ~ 5m 3 ~ 5m
Recognition Distance 3 ~ 5m 3 ~ 5m
Sound Source Localization Horizontal 180° 360° Circular
Localization Accuracy ±15° ±15°
Other Features AEC Function Supported, 60° Pickup Beam (3 Beams Supported) AEC Function Supported, 60° Pickup Beam (6 Beams Supported)
Communication Interface 14Pin-0.5mm FPC Connector 14Pin-0.5mm FPC Connector
Dimensions 111.5mm × 12mm, Maximum Height 8mm Diameter 79.5mm, Maximum Height 8mm, Mounting Hole Diameter 3mm
Operating Temperature -10°C ~ 70°C -10°C ~ 70°C
Storage Temperature -40°C ~ 85°C -40°C ~ 85°C

USB Sound Card

Module Specifications
Interfaces 1 × 12V Power Amplifier Power Supply & UAC (6Pin-2mm), 1 × Type-C (5V Power Supply & Audio Transmission), 1 × 4&6-Microphone Board Interface (14Pin-0.5mm FPC Connector), 1 × 3.5mm Audio Input Interface, 1 × AEC Interface (5Pin-1.25mm), 1 × Left Channel Speaker (2Pin-2mm), 1 × Right Channel Speaker (2Pin-2mm)
Dimensions 88mm × 40mm, Maximum Height 8mm

二、Hardware Usage

Hardware Connection

_images/connection_en.png

DIP Switch

_images/toggle_switch.png

三、Android Development

For mass production authorization SN codes and the Android platform SDK, please contact the business department at (sales@t-firefly.com) to obtain them.

1. Noise Reduction Algorithm Authorization

Each device requires a unique SN code for authentication. The SN code included in the demo is configured in assets/auth.ini of the app module. The currently used authorization API for the noise reduction algorithm engine is as follows:

/**
** Authorization API
** sn: Authorization code, provided by our company; each device must use a unique SN code.
**/
public static native int CAEAuth(String sn);

Local Authorization: Burn the authorization code to the specified directory of the device, then read the authorization code through code for authentication. Users need to properly manage the “unique device code” and “SN authorization code” to prevent loss of the authorization code due to device flashing.

For official mass production, it is necessary to purchase official SN authorization codes in bulk before use. The algorithm library verifies the validity of the SN code each time it is initialized; therefore, the device must be connected to the network during initialization, and the system time must be accurate. After successful initialization, the algorithm can be used continuously regardless of subsequent network disconnections.

2. HLW Noise Reduction Algorithm Development

2.1 Processing Framework of the Noise Reduction Algorithm

_images/cae_algorithm_framework_en.png


Audio Format Conversion: Engines for different microphone types have different requirements for audio data formats. The specific formats are as follows:

Microphone Type Channels
4-Microphone 4 mics + 2 refs, 6 channels in total, 32-bit per channel
6-Microphone 6 mics + 2 refs, 8 channels in total, 32-bit per channel

2.4 Algorithm API Calling Process

_images/api_process_en.png

2.5 Interface Encapsulation Example

package com.voice.bothlent.caeLib.engine;

import android.content.Context;
import android.util.Log;

import com.iflytek.iflyos.cae.CAE;
import com.voice.bothlent.caeLib.bean.CheckResultBean;
import com.voice.bothlent.caeLib.bean.WakeInfo;
import com.voice.bothlent.caeLib.utils.FileUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Short-range HLW engine [approx. 1.5 meters]
 */
public class HlwNearEngine extends BaseEngine {
    private static final String configCAE_RES = "CAE_RES";
    private static final String configIVW_RES = "IVW_RES";
    private static final String configINI_RES = "INI_RES";
    private static final String configPARAM_RES = "PARAM_RES";
    private static final String configCAE_DIST = "CAE_DIST";
    private static final String iniHlwName = "hlw.ini";
    private static final String configMicNum = "nMicNum";
    private static final String assetParentPath = "hlw_near/";
    private static final String TAG = "HlwNearEngine";

    static {
        CAE.loadLib("hlw");
    }

    @Override
    protected WakeInfo parseWakeInfo(String s) {
        // {"cur_ms":288768, "start_ms":287290, "end_ms":288310, "beam":1, "physical":1, "similar":1498.000000, "similar_thresh":900.000000, "power":1210436681728.000000, "angle":75.000000, "keyword":"ni3 hao3 hua1 sheng1"}
        JSONObject jsonObject = null;
        WakeInfo wakeInfo = new WakeInfo();
        try {
            jsonObject = new JSONObject(s);
            int beam = jsonObject.optInt("physical");
            int angle = jsonObject.optInt("angle");
            int similar = jsonObject.optInt("similar");
            wakeInfo.setBeam(beam);
            wakeInfo.setAngle(angle);
            wakeInfo.setScore(similar);
        } catch (JSONException e) {
            e.printStackTrace();
            wakeInfo.setAngle(0);
            wakeInfo.setBeam(0);
        }
        return wakeInfo;
    }

    @Override
    public CheckResultBean init(Context context, String sn) {
        //res check
        CheckResultBean checkResult = new CheckResultBean();
        List<String> keyList = Arrays.asList(configCAE_RES, configIVW_RES,configINI_RES,configPARAM_RES,configCAE_DIST);
        Map<String, String> stringStringMap = FileUtils.readAssetValueByKey(context,
                assetParentPath + iniHlwName, keyList);
        if (stringStringMap != null && stringStringMap.size() == keyList.size()) {
            try {
                configParentPath = context.getFilesDir().getAbsolutePath();

                String cae_res_path = stringStringMap.get(configCAE_RES);
                checkResource(context, configParentPath+cae_res_path, assetParentPath);
                String ivw_res_path = stringStringMap.get(configIVW_RES);
                checkResource(context, configParentPath+ivw_res_path, assetParentPath);

                String ini_res_path = stringStringMap.get(configINI_RES);
                checkResource(context, configParentPath+ini_res_path, assetParentPath);
                String param_res_path = stringStringMap.get(configPARAM_RES);
                checkResource(context, configParentPath+param_res_path, assetParentPath);
                String cae_dist = stringStringMap.get(configCAE_DIST);

                File fileIni = null ;
                FileOutputStream fileOutputStream =null ;
                try {
                    fileIni = new File(configParentPath + new File(ini_res_path).getParent(), "hlw_real.ini");
                    fileIni.createNewFile();
                    fileOutputStream = new FileOutputStream(fileIni);
                    fileOutputStream.write(String.format("CAE_RES = %s\n",configParentPath+cae_res_path).getBytes());
                    fileOutputStream.write(String.format(Locale.getDefault(),"CAE_DIST = %d\n",Integer.parseInt(cae_dist)).getBytes());
                    fileOutputStream.write(String.format("IVW_RES = %s\n",configParentPath+ivw_res_path).getBytes());
                    fileOutputStream.close();
                }finally {
                    if (fileOutputStream != null){
                        fileOutputStream.close();
                    }
                }

                // Step 1: Initialize according to the configuration resources
                int isInit = CAE.CAENew(fileIni.getAbsolutePath(), configParentPath+param_res_path, mCAEListener);
                if (isInit == 0) {
                    // Step 2: Authenticate with the authorization code, and it can be used after successful authentication
                    String version = CAE.CAEGetVersion();
                    Log.e(TAG, "init: " + sn + "   " + version);
                    int auth = CAE.CAEAuth(sn);
                    if (auth >= 0) {
                        checkResult.setState(1);
                        List<String> keyListMicNum = Collections.singletonList(configMicNum);
                        Map<String, String> MicNumMap = FileUtils.readAssetValueByKey(context,
                                assetParentPath + new File(cae_res_path).getName(), keyListMicNum);
                        checkResult.setCheckResult(String.format(Locale.getDefault(), "%s,%s,micNum=%s",  version,cae_res_path, MicNumMap.get(configMicNum)));
                    } else {
                        checkResult.setState(0);
                        checkResult.setCheckResult("cae auth error, code  = " + auth);
                    }
                } else {
                    checkResult.setState(0);
                    checkResult.setCheckResult("cae init error,please check res file " + (configParentPath+ini_res_path) + ";" + (configParentPath+param_res_path));
                }

            } catch (IOException ignored) {
                checkResult.setState(0);
                checkResult.setCheckResult("copy res file error " + ignored);
            }
        } else {
            checkResult.setState(0);
            checkResult.setCheckResult("hlw.ini config error");
        }
        return checkResult;
    }

    @Override
    public void setBeam(int beam) {
        int i = CAE.CAESetRealBeam(beam);
        Log.e(TAG, "setBeam: " + i + "   " + beam);
    }

    @Override
    public void writeAudio(byte[] audio, int len) {
        CAE.CAEAudioWrite(audio, len);
    }

    @Override
    public void setShowLog(boolean show) {
        CAE.CAESetShowLog(show ? 0 : 1);
    }

    @Override
    public void onDestroy() {
        CAE.CAEDestory();
    }
}