背景
本人最近在做数字人项目,用到科大讯飞的语音识别功能,遇到了许多坑,做个总结,给兄弟们铺铺路。科大讯飞语音识别主要通过识别声音然后转换成文字,具体展示如下图所示:
一、项目环境
vue3+ts+vite
二、注册科大讯飞
注册后新建个应用,拿到APPID、APISecret、APIkey,在项目中会用到这三个参数,新用户有500条免费的服务量。
三、下载语音识别demo
科大讯飞文档中心中示例demo,博主选择的是js语言,注意该demo项目环境为webpack+js
选择demo-js语言下载
四、新建vue3项目
yarn creat vite 'project-name'
或者
npm init vite@latest 'project-name'
五、项目目录
digitalPerson
├─ .gitignore
├─ README.md
├─ auto-imports.d.ts
├─ index.html
├─ package.json
├─ public
│ ├─ index.html
│ └─ vite.svg
├─ src
│ ├─ App.vue
│ ├─ assets
│ │ └─ vue.svg
│ ├─ components
│ │ └─ HelloWorld.vue
│ ├─ iat_ws.js
│ ├─ layout
│ │ └─ index.vue
│ ├─ main.ts
│ ├─ router
│ │ └─ index.ts
│ ├─ style.css
│ ├─ until
│ │ ├─ base64js.js
│ │ ├─ bootstrap.js
│ │ ├─ browser.min.js
│ │ ├─ jquery.js
│ │ └─ transcode.worker.js
│ ├─ views
│ │ └─ index.vue
│ └─ vite-env.d.ts
├─ tsconfig.json
├─ tsconfig.node.json
├─ vite.config.ts
├─ vite.config.ts.js
└─ yarn.lock
六、配置
package.json
配置项具体如下,我们需要安装一些依赖来支持语音识别demo适应vue3环境,不然会遇到很多问题.
"dependencies": {
"@originjs/vite-plugin-commonjs": "^1.0.3",
"@rollup/plugin-inject": "^5.0.3",
"ant-design-vue": "^3.2.15",
"jquery": "^3.6.2",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@types/node": "^18.11.17",
"@vitejs/plugin-vue": "^4.0.0",
"crypto-js": "4.0.0",
"typescript": "^4.9.3",
"unplugin-auto-import": "^0.12.1",
"vconsole": "^3.15.0",
"vite": "^4.0.0",
"vue-tsc": "^1.0.11"
}
依赖名称 | 描述 |
---|---|
@originjs/vite-plugin-commonjs | 项目中混用 require 和 import ,即存在混用 commonJS 和 ES6 模块的情况,需要用该插件的transformMixedEsModules 配置进行 hotfix |
@rollup/plugin-inject | 使用该插件注入全局 jQuery 环境 |
@types/node | 可以整体解决模块的声明文件问题 |
crypto-js | 加密、解密 |
unplugin-auto-import | 自动导入vue、vue-router等提供的API |
vconsole | 提供轻量、可拓展、针对手机网页的前端开发者调试面板 |
具体代码实现如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import { viteCommonjs } from '@originjs/vite-plugin-commonjs' // 让浏览器支持commonjs语法
import inject from '@rollup/plugin-inject'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router']//自动导入vue和vue-router相关函数
}),
inject({
$: "jquery", // 这里会自动载入 node_modules 中的 jquery
jQuery: "jquery",
"windows.jQuery": "jquery"
}),
viteCommonjs({
transformMixedEsModules: true//混用 commonJS 和 ES6 模块
})
],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'components': resolve(__dirname, 'src/components'),
},
extensions: ['.js', '.json', '.ts', '.vue'], // 使用路径别名时想要省略的后缀名,可以自己 增减
},
server: {
port: 8070,
host: true,
open: true,
proxy: {},
hmr: true, // 开启热更新
}
})
七、踩坑记录
在科大讯飞的js-demo中,并没有用到vue或者vite,我们实际开发时都会遇到let transWorker = new TransWorker()
代码报错,比如:
报错1:TypeError:TransWorker is not a constructor
报错2:Uncaught SyntaxError: The requested module '/src/until/transcode.worker.js?t=1671455993687' does not provide an export named 'default'
为什么会报错呢?
import TransWorker from ‘js/transcode.worker.js
引入了webWorker文件,webWorker相当于js中的线程,在主线程中启动一个子线程不影响ui,有关webworker内容如下:
web worker 是运行在后台的 JavaScript,不会影响页面的性能。 当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。 web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。
在iat_ws.js(本人自定义的文件)文件里面const transWorker = new TransWorker() 报错,原因是vue里面不能直接使用原生的new Worker,vite中有专门关于webworker的配置项,详细请前往vite中webworker配置
语法: const worker = new Worker(new URL('./worker.js', import.meta.url))
所以需要将以下代码
import TransWorker from './until/transcode.worker.js'
let transWorker = new TransWorker()
改为如下
const transWorker = new Worker(new URL('./until/transcode.worker.js', import.meta.url))
并且transcode.worker.js
文件需要修改为如下所示:
self.onmessage = function(e){
transAudioData.transcode(e.data)
}
let transAudioData = {
transcode(audioData) {
let output = transAudioData.to16kHz(audioData)
output = transAudioData.to16BitPCM(output)
output = Array.from(new Uint8Array(output.buffer))
self.postMessage(output)
},
to16kHz(audioData) {
var data = new Float32Array(audioData)
var fitCount = Math.round(data.length * (16000 / 44100))
var newData = new Float32Array(fitCount)
var springFactor = (data.length - 1) / (fitCount - 1)
newData[0] = data[0]
for (let i = 1; i < fitCount - 1; i++) {
var tmp = i * springFactor
var before = Math.floor(tmp).toFixed()
var after = Math.ceil(tmp).toFixed()
var atPoint = tmp - before
newData[i] = data[before] + (data[after] - data[before]) * atPoint
}
newData[fitCount - 1] = data[data.length - 1]
return newData
},
to16BitPCM(input) {
var dataLength = input.length * (16 / 8)
var dataBuffer = new ArrayBuffer(dataLength)
var dataView = new DataView(dataBuffer)
var offset = 0
for (var i = 0; i < input.length; i++, offset += 2) {
var s = Math.max(-1, Math.min(1, input[i]))
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
}
return dataView
},
}
八、实现效果
具体实现效果如下,样式忽略😄