【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音开关

2025博客之星年度评选已开启 10w+人浏览 1.6k人参与

微信小程序实现智能设备语音控制(百度语音识别API)——新手踩坑全记录与解决方案

作为编程新手,最近尝试开发一款“智能设备语音控制”微信小程序(核心功能:语音指令→百度API转文字→控制客厅灯/卧室空调),过程中踩了无数坑:从音频参数不匹配到Token获取失败,从API请求格式错误到音频解析异常,最终一步步解决所有问题实现功能。本文将以新手视角,递进式梳理整个开发流程、遇到的所有问题及排查解决方案,既是开发总结,也希望能帮到同阶段的小伙伴。
在这里插入图片描述

手机界面
在这里插入图片描述

一、开发前准备(新手必看:环境+配置不能少)

1.1 开发环境搭建

首先确保基础开发环境到位,新手别跳过这一步,否则后续会出现各种“莫名报错”:

  • 微信开发者工具:下载最新稳定版(本文使用v2.0.12510260),安装后登录微信账号,创建“小程序”项目(选择“不使用云服务”,模板选“空模板”)。
  • 基础库版本:开发者工具→详情→本地设置→调试基础库,选择2.30.0(稳定版,兼容录音、文件读取等API)。
  • 真机调试准备:小程序录音功能不支持模拟器,全程需用“真机调试”(工具右上角→真机调试,手机扫码即可)。

1.2 百度语音识别API申请(核心!密钥别错)

小程序的语音转文字依赖百度短语音识别API,需先申请开发者密钥:

  1. 登录百度智能云官网,注册/登录账号;
  2. 进入“产品服务→人工智能→语音技术”,点击“立即使用”;
  3. 创建应用:填写应用名称(如“小程序语音控制”),勾选“短语音识别”服务,提交后即可看到API KeySecret Key(后续代码要用到,复制保存好,别漏字符/多空格);
  4. 领取免费资源:进入“资源管理”,领取“短语音识别”免费资源包(5万次调用,足够测试)。
    使用到的核心API
    在这里插入图片描述
    在这里插入图片描述

1.3 小程序域名白名单配置(新手最易漏!)

微信小程序限制网络请求域名,需提前配置合法域名,否则会报“请求未找到合法域名”:

  1. 登录微信公众平台,进入自己的小程序项目;
  2. 开发→开发设置→服务器域名:
    • request合法域名添加:https://aip.baidubce.com(获取Token)、https://vop.baidu.com(语音识别);
    • 无需配置uploadFile域名(后续改用JSON方式上传音频,不用wx.uploadFile);
  3. 保存后重启微信开发者工具,配置才会生效。

二、核心功能开发(从页面到逻辑,递进式实现)

2.1 页面结构(index.wxml):新手友好的简单布局

先搭建基础界面,包含“语音按钮、识别结果、手动输入框、设备列表”,代码注释清晰,新手能看懂:

<!-- 语音控制区 -->
<view class="voice-section">
  <button class="mic-btn {{isRecording ? 'recording' : ''}}" bindtap="startVoiceControl">
    <text class="mic-icon">🎤</text>
    <text class="mic-text">{{isRecording ? '停止录音' : '点击说话控制设备'}}</text>
  </button>
  <view class="voice-result">识别结果:{{voiceText || '未识别到语音'}}</view>
  <input 
    class="voice-input" 
    placeholder="手动输入指令(如:打开客厅灯)" 
    bindinput="inputVoiceText"
    value="{{voiceText}}"
  />
  <button class="send-btn" bindtap="executeVoiceCommand">执行指令</button>
</view>

<!-- 智能设备列表 -->
<view class="device-section">
  <text class="section-title">我的智能设备</text>
  <!-- 客厅灯 -->
  <view class="device-card">
    <view class="device-info">
      <text class="device-name">客厅灯</text>
      <text class="device-status {{deviceList.livingRoomLight.status ? 'online' : 'offline'}}">
        {{deviceList.livingRoomLight.status ? '在线' : '离线'}}
      </text>
    </view>
    <view class="device-control">
      <text class="switch-status">{{deviceList.livingRoomLight.switch ? '开' : '关'}}</text>
      <button 
        class="switch-btn {{deviceList.livingRoomLight.switch ? 'on' : 'off'}}"
        bindtap="toggleDeviceSwitch"
        data-device="livingRoomLight"
      >
        {{deviceList.livingRoomLight.switch ? '关闭' : '打开'}}
      </button>
    </view>
  </view>

  <!-- 卧室空调 -->
  <view class="device-card">
    <view class="device-info">
      <text class="device-name">卧室空调</text>
      <text class="device-status {{deviceList.bedroomAir.status ? 'online' : 'offline'}}">
        {{deviceList.bedroomAir.status ? '在线' : '离线'}}
      </text>
    </view>
    <view class="device-control">
      <text class="switch-status">{{deviceList.bedroomAir.switch ? '开' : '关'}}</text>
      <button 
        class="switch-btn {{deviceList.bedroomAir.switch ? 'on' : 'off'}}"
        bindtap="toggleDeviceSwitch"
        data-device="bedroomAir"
      >
        {{deviceList.bedroomAir.switch ? '关闭' : '打开'}}
      </button>
    </view>
  </view>
</view>

<!-- 操作提示弹窗 -->
<view class="result-toast" wx:if="{{showResult}}">{{resultText}}</view>

2.2 样式美化(index.wxss):简单易上手

不用复杂样式,重点是界面清晰,新手可直接复制:

page { background-color: #f5f5f5; padding-bottom: 20px; }

/* 语音控制区 */
.voice-section {
  background: white;
  padding: 20px;
  margin-bottom: 10px;
  text-align: center;
}
.mic-btn {
  background: #1677ff;
  color: white;
  border: none;
  border-radius: 50px;
  padding: 15px 30px;
  font-size: 16px;
  margin-bottom: 15px;
}
.mic-btn.recording { background: #ff4d4f; }
.mic-icon { font-size: 20px; margin-right: 8px; }
.voice-result {
  font-size: 14px;
  color: #666;
  margin: 10px 0;
  text-align: left;
  padding: 0 10%;
}
.voice-input {
  border: 1px solid #e5e5e5;
  border-radius: 8px;
  padding: 10px 15px;
  font-size: 14px;
  margin-bottom: 10px;
  width: 90%;
}
.send-btn {
  background: #00b578;
  color: white;
  border: none;
  border-radius: 8px;
  padding: 8px 20px;
  font-size: 14px;
}

/* 设备列表 */
.device-section { padding: 0 10px; }
.section-title {
  font-size: 16px;
  font-weight: bold;
  margin: 15px 0;
  display: block;
}
.device-card {
  background: white;
  border-radius: 10px;
  padding: 15px;
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.device-status.online { color: #00b578; }
.device-status.offline { color: #ff4d4f; }
.switch-btn.on { background: #ff4d4f; color: white; border: none; }
.switch-btn.off { background: #1677ff; color: white; border: none; }

/* 提示弹窗 */
.result-toast {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(0,0,0,0.7);
  color: white;
  padding: 10px 20px;
  border-radius: 8px;
  font-size: 14px;
  z-index: 999;
}

2.3 核心逻辑开发(index.js):分模块实现

新手建议分模块写逻辑,避免代码混乱,核心模块包括:录音初始化、Token获取、语音转文字、设备控制。

第一步:初始化数据+录音管理器
Page({
  data: {
    voiceText: '', // 语音识别结果
    // 设备初始状态(模拟)
    deviceList: {
      livingRoomLight: { status: true, switch: false }, // 客厅灯:在线、关闭
      bedroomAir: { status: true, switch: true }        // 卧室空调:在线、打开
    },
    showResult: false, // 提示弹窗显示状态
    resultText: '',    // 提示文本
    isRecording: false,// 录音状态
    recorderManager: null, // 录音管理器
    tempFilePath: ''   // 录音文件路径
  },

  // 页面加载时初始化录音管理器
  onLoad: function() {
    const recorderManager = wx.getRecorderManager();
    this.setData({ recorderManager });
    this.initRecorder(); // 初始化录音回调
  },

  // 初始化录音回调(开始/停止/错误)
  initRecorder: function() {
    const that = this;
    const recorderManager = this.data.recorderManager;

    // 录音开始
    recorderManager.onStart(() => {
      that.setData({ isRecording: true });
      that.showResultToast('正在录音,请说话');
    });

    // 录音停止(获取文件路径,调用识别接口)
    recorderManager.onStop((res) => {
      that.setData({
        isRecording: false,
        tempFilePath: res.tempFilePath
      });
      that.voiceToText(res.tempFilePath); // 核心:语音转文字
    });

    // 录音错误
    recorderManager.onError((err) => {
      that.setData({ isRecording: false });
      that.showResultToast('录音失败:' + err.errMsg);
    });
  },
})
第二步:录音控制+手动输入
// 开始/停止录音(含权限申请)
startVoiceControl: function() {
  const { isRecording, recorderManager } = this.data;
  if (isRecording) {
    recorderManager.stop();
  } else {
    // 动态申请录音权限(新手别忘!否则真机录音失败)
    wx.authorize({
      scope: 'scope.record',
      success: () => {
        // 录音参数:严格匹配百度API要求(新手重点!参数错必报错)
        recorderManager.start({
          format: 'wav',        // 百度支持的格式
          sampleRate: 16000,    // 百度强制采样率
          numberOfChannels: 1,  // 单声道
          encodeBitRate: 64000, // 微信允许范围(24000-96000)
          duration: 6000        // 最长录音6秒
        });
      },
      fail: () => {
        wx.showModal({
          title: '权限申请失败',
          content: '需要麦克风权限才能语音控制,请前往设置开启',
          confirmText: '去设置',
          success: (res) => {
            if (res.confirm) wx.openSetting();
          }
        });
      }
    });
  }
},

// 手动输入指令
inputVoiceText: function(e) {
  this.setData({ voiceText: e.detail.value });
},

// 显示提示弹窗(3秒后隐藏)
showResultToast: function(text) {
  this.setData({ showResult: true, resultText: text });
  setTimeout(() => {
    this.setData({ showResult: false });
  }, 3000);
},
第三步:百度Token获取+语音转文字(核心!按官方规范)

新手最容易在这里出错,重点是“按百度API官方要求传参”,放弃wx.uploadFile,改用JSON方式上传(base64编码音频):

// 核心:百度语音转文字(按官方JSON方式)
voiceToText: function(tempFilePath) {
  const that = this;
  // ========== 新手替换这里!填自己的百度API密钥 ==========
  const apiKey = '你的API Key';
  const secretKey = '你的Secret Key';
  // ======================================================

  // 步骤1:获取百度Access Token(官方POST表单方式)
  wx.request({
    url: 'https://aip.baidubce.com/oauth/2.0/token',
    method: 'POST',
    header: { 'Content-Type': 'application/x-www-form-urlencoded' },
    data: {
      grant_type: 'client_credentials', // 官方固定值
      client_id: apiKey,
      client_secret: secretKey
    },
    success: (tokenRes) => {
      // 检查Token是否获取成功
      if (tokenRes.statusCode !== 200 || tokenRes.data.error) {
        let errMsg = tokenRes.data.error_description || 'Token获取失败';
        if (tokenRes.data.error === 'invalid_client') errMsg = 'API Key/Secret Key错误,请核对';
        that.showResultToast(errMsg);
        return;
      }
      const accessToken = tokenRes.data.access_token;

      // 步骤2:读取录音文件(二进制→base64,获取原始字节长度)
      const fs = wx.getFileSystemManager();
      fs.readFile({
        filePath: tempFilePath,
        encoding: 'binary', // 先读二进制,获取原始长度
        success: (binaryRes) => {
          const fileBinary = binaryRes.data;
          const fileLen = fileBinary.length; // 原始字节长度(百度要求的len参数)
          // 二进制转base64(百度要求的speech参数)
          const speechBase64 = wx.arrayBufferToBase64(new Uint8Array(
            fileBinary.split('').map(char => char.charCodeAt(0))
          ));

          // 步骤3:构造百度API要求的JSON请求体
          const requestData = {
            "format": "wav",
            "rate": 16000,
            "channel": 1,
            "cuid": "wx-miniprogram-123456789", // 固定值,避免为空
            "token": accessToken,
            "dev_pid": 1537, // 普通话近场识别模型
            "len": fileLen,  // 原始字节长度(关键!不是base64长度)
            "speech": speechBase64 // base64编码的音频
          };

          // 步骤4:发送POST请求(手动序列化JSON,避免字段丢失)
          wx.request({
            url: 'https://vop.baidu.com/server_api',
            method: 'POST',
            header: { 'Content-Type': 'application/json; charset=utf-8' },
            data: JSON.stringify(requestData),
            success: (transRes) => {
              console.log('百度API返回:', transRes.data); // 新手调试用:看返回结果
              if (transRes.data.err_no === 0 && transRes.data.result) {
                const text = transRes.data.result[0];
                that.setData({ voiceText: text });
                that.showResultToast('识别成功:' + text);
                that.executeVoiceCommand(); // 自动执行设备控制
              } else {
                const errMsg = transRes.data.err_msg || '识别失败';
                that.showResultToast(`识别失败:${errMsg}(错误码:${transRes.data.err_no}`);
              }
            },
            fail: (err) => {
              that.showResultToast('请求百度API失败:' + err.errMsg);
            }
          });
        },
        fail: (fileErr) => {
          that.showResultToast('读取录音文件失败:' + fileErr.errMsg);
        }
      });
    },
    fail: (tokenErr) => {
      that.showResultToast('获取Token失败:' + tokenErr.errMsg);
    }
  });
},
第四步:设备控制逻辑(指令解析+手动切换)
// 执行语音/手动指令(解析关键词控制设备)
executeVoiceCommand: function() {
  const { voiceText, deviceList } = this.data;
  if (!voiceText.trim()) {
    this.showResultToast('请输入/说出控制指令');
    return;
  }

  // 解析指令(新手可扩展更多设备)
  let targetDevice = '';
  let targetAction = '';
  let deviceName = '';

  if (voiceText.includes('客厅灯')) {
    targetDevice = 'livingRoomLight';
    deviceName = '客厅灯';
    targetAction = voiceText.includes('打开') || voiceText.includes('开') ? 'on' : 'off';
  } else if (voiceText.includes('卧室空调')) {
    targetDevice = 'bedroomAir';
    deviceName = '卧室空调';
    targetAction = voiceText.includes('打开') || voiceText.includes('开') ? 'on' : 'off';
  } else {
    this.showResultToast('无法识别指令,请说“打开客厅灯”或“关闭卧室空调”');
    return;
  }

  // 检查设备是否在线
  if (!deviceList[targetDevice].status) {
    this.showResultToast(`${deviceName}已离线,无法控制`);
    return;
  }

  // 更新设备状态
  const newDeviceList = JSON.parse(JSON.stringify(deviceList));
  newDeviceList[targetDevice].switch = targetAction === 'on';
  this.setData({ deviceList: newDeviceList });
  this.showResultToast(`${deviceName}${targetAction === 'on' ? '打开' : '关闭'}`);
},

// 手动切换设备开关
toggleDeviceSwitch: function(e) {
  const deviceKey = e.currentTarget.dataset.device;
  const { deviceList } = this.data;
  const deviceName = deviceKey === 'livingRoomLight' ? '客厅灯' : '卧室空调';

  if (!deviceList[deviceKey].status) {
    this.showResultToast(`${deviceName}已离线,无法操作`);
    return;
  }

  const newDeviceList = JSON.parse(JSON.stringify(deviceList));
  newDeviceList[deviceKey].switch = !newDeviceList[deviceKey].switch;
  this.setData({ deviceList: newDeviceList });
  this.showResultToast(`${deviceName}${newDeviceList[deviceKey].switch ? '打开' : '关闭'}`);
}
});

2.4 全局配置(app.json)

简单配置页面和导航栏,新手直接复制:

{
  "pages": ["pages/index/index"],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#1677ff",
    "navigationBarTitleText": "智能开关语音控制",
    "navigationBarTextStyle": "white"
  },
  "sitemapLocation": "sitemap.json",
  "lazyCodeLoading": "requiredComponents"
}

三、新手踩坑排错全流程(递进式,按报错顺序)

作为新手,开发中遇到的每一个报错都让人头大,以下是我遇到的所有问题+排查思路+解决方案,新手可对照自查:

坑1:param rate invalid(音频参数不匹配)

  • 报错现象:百度API返回“param rate invalid”,语音识别失败;
  • 原因分析:录音参数(格式/采样率/码率)和百度API要求不匹配,比如用了mp3格式、采样率8000、码率超出范围;
  • 解决方案
    1. 录音格式改为wav(百度推荐);
    2. 采样率固定为16000(百度强制);
    3. 声道数改为1(单声道,百度要求)。

坑2:encodeBitRate超出范围

  • 报错现象:微信控制台提示“encodeBitRate 256000 不在允许的范围内(24000-96000)”;
  • 原因分析:一开始把码率设为256000,超出微信录音API的限制;
  • 解决方案:将encodeBitRate改为64000(既符合微信要求,也兼容百度API)。

坑3:invalid_client(Token获取失败)

  • 报错现象:获取Token时返回“invalid_client”,提示“unknown client id”或“Client authentication failed”;
  • 原因分析
    1. API Key/Secret Key复制错误(多空格、少字符、大小写错);
    2. Token请求方式不对(用了GET/URL拼接参数,而非官方要求的POST表单);
  • 解决方案
    1. 重新登录百度智能云,复制正确的API Key/Secret Key;
    2. 按官方要求用POST方式,参数放在data里(而非URL拼接)。

坑4:json read error(3300)(音频上传方式不规范)

  • 报错现象:百度API返回“json read error(错误码:3300)”;
  • 原因分析:一开始用wx.uploadFile(form-data方式)上传音频,不符合百度API的JSON/RAW上传规范;
  • 解决方案:放弃wx.uploadFile,改用wx.request按官方JSON方式上传(音频转base64,构造JSON请求体)。

坑5:json speech not found(speech/len参数异常)

  • 报错现象:百度API返回“json speech not found”(err_no=3300),控制台显示“音频原始字节长度:undefined”;
  • 原因分析
    1. len参数为undefined(小程序readFileencoding: 'base64'时无length字段);
    2. speech字段在请求序列化时丢失;
  • 解决方案
    1. 先以binary编码读取文件,手动计算原始字节长度;
    2. 手动序列化JSON请求体(JSON.stringify),确保speech字段不丢失;
    3. 兜底cuid参数为非空值,避免参数为空导致解析异常。

通用排错技巧(新手必学)

  1. 看控制台日志:开发者工具→调试器→控制台,打印关键参数(如Token、音频长度、请求体),定位哪一步出错;
  2. 真机调试:模拟器不支持录音/文件读取,所有测试必须用真机;
  3. 核对官方文档:百度语音识别API的参数、请求方式一定要对照官方文档,新手别凭“感觉”写;
  4. 简化测试:先手动传固定的base64音频测试API是否正常,再联调录音功能。

四、最终效果与总结

4.1 实现效果

  1. 点击“🎤 点击说话控制设备”,授权麦克风后开始录音;
  2. 说“打开客厅灯”,录音停止后自动调用百度API识别文字;
  3. 识别成功后,客厅灯状态变为“开”,并弹出提示;
  4. 也可手动输入指令,点击“执行指令”控制设备;
  5. 设备离线时,操作会提示“离线无法控制”。

4.2 新手学习总结

作为编程新手,这次开发让我收获很多:

  1. API调用要严格按官方规范:尤其是第三方API(如百度语音),参数、请求方式、格式错一个就会报错;
  2. 排错要“递进式”:先定位报错环节(Token→音频读取→API请求),再查参数/格式,别盲目改代码;
  3. 小程序权限/域名配置别漏:新手容易忽略域名白名单、录音权限,这些是基础;
  4. 日志打印是神器:新手要学会用console.log打印关键参数,快速定位问题。

五、完整代码获取

本文所有代码已整理成可直接运行的项目,新手只需替换百度API密钥,按步骤配置即可运行。核心注意点:

https://gitee.com/JavaBigDataStudy/smart-device-control-center.git

  • 替换index.js中的apiKeysecretKey
  • 配置微信小程序域名白名单;
  • 用真机调试(模拟器不支持录音)。

希望这篇新手向的踩坑总结能帮到大家,也欢迎各位小伙伴交流补充~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder_Boy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值