x
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/*每个页面公共css */
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
import App from './App'
|
||||
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import './uni.promisify.adaptor'
|
||||
Vue.config.productionTip = false
|
||||
App.mpType = 'app'
|
||||
const app = new Vue({
|
||||
...App
|
||||
})
|
||||
app.$mount()
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
import { createSSRApp } from 'vue'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "RecorderManager-onFrameRecorded",
|
||||
"appid": "",
|
||||
"description": "",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyleCompiler": "uni-app",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
},
|
||||
"modules": {
|
||||
"Record": {}
|
||||
},
|
||||
"distribute": {
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
"ios": {},
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
"quickapp": {},
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "2"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "录音实时帧回调"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="start-record" @click="onStartRecord">开始录音</view>
|
||||
<view class="stop-record" @click="onStopRecord">停止录音</view>
|
||||
|
||||
<view class="frame">
|
||||
<view>实时回调:是否是最后一帧:{{isLastFrame}}</view>
|
||||
<view class="frameBuffer">{{frameBuffer}}</view>
|
||||
</view>
|
||||
|
||||
<yao-RecordFrame
|
||||
ref="recordFrame"
|
||||
@onFrameRecorded="frameRecorded"
|
||||
@currentDecibels="onCurrentDecibels"
|
||||
@onStop="stopIt"></yao-RecordFrame>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
frameBuffer:'',//实时帧的frameBuffer值
|
||||
isLastFrame:false,//是否是最后一帧
|
||||
vuLevel:0
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
},
|
||||
methods: {
|
||||
onStartRecord(){
|
||||
//开启录音(仅支持两种参数)
|
||||
this.$refs.recordFrame.start({
|
||||
sampleRate:16000,
|
||||
frameSize:1024,
|
||||
gain:1.0 //增益值,数字越高音频声音越大 1.0~20.0
|
||||
});
|
||||
this.isLastFrame='false';
|
||||
},
|
||||
onStopRecord(){
|
||||
//停止录音
|
||||
this.$refs.recordFrame.stop();
|
||||
},
|
||||
//停止录音
|
||||
stopIt(base64){
|
||||
//base64音频只能在浏览器播放
|
||||
//也可以base64音频转成文件音频可app播放
|
||||
console.log(base64);
|
||||
},
|
||||
//帧回调
|
||||
frameRecorded({isLastFrame,frameBuffer}){
|
||||
//console.log(isLastFrame,frameBuffer);
|
||||
if(!isLastFrame){
|
||||
this.frameBuffer=frameBuffer;
|
||||
|
||||
}
|
||||
|
||||
if(this.isLastFrame!='true'){
|
||||
this.isLastFrame=isLastFrame?'true':'false';
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
onCurrentDecibels(decibels){
|
||||
//当前分贝值 最小可测分贝(-80) 最大可测分贝(-30)
|
||||
console.log("当前分贝:" + decibels)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.start-record{
|
||||
background:#007aff;
|
||||
width:90%;
|
||||
margin:20rpx auto;
|
||||
color:#fff;
|
||||
padding:20rpx 0;
|
||||
text-align:center;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
.stop-record{
|
||||
background:#e64340;
|
||||
width:90%;
|
||||
margin:20rpx auto;
|
||||
color:#fff;
|
||||
padding:20rpx 0;
|
||||
text-align:center;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
.frame{
|
||||
width:90%;
|
||||
margin:0 auto;
|
||||
}
|
||||
.frameBuffer{
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,72 @@
|
||||
# yao-RecordFrame
|
||||
|
||||
##配置
|
||||
需要将模块下uni_modules/yao-RecordFrame的dist复制到static目录下面
|
||||
或者
|
||||
将uni_modules/yao-RecordFrame/dist目录的配置文件放到static/dist目录下面
|
||||
|
||||
###说明
|
||||
插件只适用于接实时语音识别的模型,不适合录音上传
|
||||
|
||||
### 示例代码
|
||||
|
||||
```javascript
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="start-record" @click="onStartRecord">开始录音</view>
|
||||
<view class="stop-record" @click="onStopRecord">停止录音</view>
|
||||
|
||||
<view class="frame">
|
||||
<view>实时回调:是否是最后一帧:{{isLastFrame}}</view>
|
||||
<view class="frameBuffer">{{frameBuffer}}</view>
|
||||
</view>
|
||||
|
||||
<yao-RecordFrame
|
||||
ref="recordFrame"
|
||||
@onFrameRecorded="frameRecorded"
|
||||
@onStop="stopIt"></yao-RecordFrame>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
frameBuffer:'',//实时帧的frameBuffer值
|
||||
isLastFrame:false,//是否是最后一帧
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
onStartRecord(){
|
||||
//开启录音(仅支持两种参数)
|
||||
this.$refs.recordFrame.start({
|
||||
sampleRate:16000,
|
||||
frameSize:1024
|
||||
})
|
||||
},
|
||||
onStopRecord(){
|
||||
//停止录音
|
||||
this.$refs.recordFrame.stop();
|
||||
},
|
||||
//停止录音
|
||||
stopIt(base64){
|
||||
//base64音频只能在浏览器播放
|
||||
//也可以base64音频转成文件音频可app播放
|
||||
console.log(base64);
|
||||
},
|
||||
frameRecorded({isLastFrame,frameBuffer}){
|
||||
console.log(isLastFrame,frameBuffer);
|
||||
if(!isLastFrame){
|
||||
this.frameBuffer=frameBuffer;
|
||||
}
|
||||
|
||||
this.isLastFrame=isLastFrame?'true':'false';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```
|
||||
@@ -0,0 +1,300 @@
|
||||
! function() {
|
||||
"use strict";
|
||||
|
||||
function t(t, e) {
|
||||
for (var r = 0; r < e.length; r++) {
|
||||
var n = e[r];
|
||||
n.enumerable = n.enumerable || !1, n.configurable = !0, "value" in n && (n.writable = !0), Object
|
||||
.defineProperty(t, (i = n.key, o = void 0, "symbol" == typeof(o = function(t, e) {
|
||||
if ("object" != typeof t || null === t) return t;
|
||||
var r = t[Symbol.toPrimitive];
|
||||
if (void 0 !== r) {
|
||||
var n = r.call(t, e || "default");
|
||||
if ("object" != typeof n) return n;
|
||||
throw new TypeError("@@toPrimitive must return a primitive value.")
|
||||
}
|
||||
return ("string" === e ? String : Number)(t)
|
||||
}(i, "string")) ? o : String(o)), n)
|
||||
}
|
||||
var i, o
|
||||
}
|
||||
|
||||
function e(t) {
|
||||
return e = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function(t) {
|
||||
return t.__proto__ || Object.getPrototypeOf(t)
|
||||
}, e(t)
|
||||
}
|
||||
|
||||
function r(t, e) {
|
||||
return r = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function(t, e) {
|
||||
return t.__proto__ = e, t
|
||||
}, r(t, e)
|
||||
}
|
||||
|
||||
function n() {
|
||||
if ("undefined" == typeof Reflect || !Reflect.construct) return !1;
|
||||
if (Reflect.construct.sham) return !1;
|
||||
if ("function" == typeof Proxy) return !0;
|
||||
try {
|
||||
return Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function() {}))), !0
|
||||
} catch (t) {
|
||||
return !1
|
||||
}
|
||||
}
|
||||
|
||||
function i(t, e, o) {
|
||||
return i = n() ? Reflect.construct.bind() : function(t, e, n) {
|
||||
var i = [null];
|
||||
i.push.apply(i, e);
|
||||
var o = new(Function.bind.apply(t, i));
|
||||
return n && r(o, n.prototype), o
|
||||
}, i.apply(null, arguments)
|
||||
}
|
||||
|
||||
function o(t) {
|
||||
var n = "function" == typeof Map ? new Map : void 0;
|
||||
return o = function(t) {
|
||||
if (null === t || (o = t, -1 === Function.toString.call(o).indexOf("[native code]"))) return t;
|
||||
var o;
|
||||
if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function");
|
||||
if (void 0 !== n) {
|
||||
if (n.has(t)) return n.get(t);
|
||||
n.set(t, a)
|
||||
}
|
||||
|
||||
function a() {
|
||||
return i(t, arguments, e(this).constructor)
|
||||
}
|
||||
return a.prototype = Object.create(t.prototype, {
|
||||
constructor: {
|
||||
value: a,
|
||||
enumerable: !1,
|
||||
writable: !0,
|
||||
configurable: !0
|
||||
}
|
||||
}), r(a, t)
|
||||
}, o(t)
|
||||
}
|
||||
|
||||
function a(t) {
|
||||
if (void 0 === t) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||
return t
|
||||
}
|
||||
|
||||
function s(t) {
|
||||
var r = n();
|
||||
return function() {
|
||||
var n, i = e(t);
|
||||
if (r) {
|
||||
var o = e(this).constructor;
|
||||
n = Reflect.construct(i, arguments, o)
|
||||
} else n = i.apply(this, arguments);
|
||||
return function(t, e) {
|
||||
if (e && ("object" == typeof e || "function" == typeof e)) return e;
|
||||
if (void 0 !== e) throw new TypeError(
|
||||
"Derived constructors may only return object or undefined");
|
||||
return a(t)
|
||||
}(this, n)
|
||||
}
|
||||
}
|
||||
|
||||
function f(t) {
|
||||
return function(t) {
|
||||
if (Array.isArray(t)) return u(t)
|
||||
}(t) || function(t) {
|
||||
if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"]) return Array
|
||||
.from(t)
|
||||
}(t) || function(t, e) {
|
||||
if (!t) return;
|
||||
if ("string" == typeof t) return u(t, e);
|
||||
var r = Object.prototype.toString.call(t).slice(8, -1);
|
||||
"Object" === r && t.constructor && (r = t.constructor.name);
|
||||
if ("Map" === r || "Set" === r) return Array.from(t);
|
||||
if ("Arguments" === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)) return u(t, e)
|
||||
}(t) || function() {
|
||||
throw new TypeError(
|
||||
"Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
function u(t, e) {
|
||||
(null == e || e > t.length) && (e = t.length);
|
||||
for (var r = 0, n = new Array(e); r < e; r++) n[r] = t[r];
|
||||
return n
|
||||
}
|
||||
|
||||
function l(t, e, r, n) {
|
||||
this.fromSampleRate = t, this.toSampleRate = e, this.channels = 0 | r, this.noReturn = !!n, this.initialize()
|
||||
}
|
||||
l.prototype.initialize = function() {
|
||||
if (!(this.fromSampleRate > 0 && this.toSampleRate > 0 && this.channels > 0)) throw new Error(
|
||||
"Invalid settings specified for the resampler.");
|
||||
this.fromSampleRate == this.toSampleRate ? (this.resampler = this.bypassResampler, this.ratioWeight = 1) : (
|
||||
this.fromSampleRate < this.toSampleRate ? (this.lastWeight = 1, this.resampler = this
|
||||
.compileLinearInterpolation) : (this.tailExists = !1, this.lastWeight = 0, this.resampler = this
|
||||
.compileMultiTap), this.ratioWeight = this.fromSampleRate / this.toSampleRate)
|
||||
}, l.prototype.compileLinearInterpolation = function(t) {
|
||||
var e = t.length;
|
||||
this.initializeBuffers(e);
|
||||
var r, n, i = this.outputBufferSize,
|
||||
o = this.ratioWeight,
|
||||
a = this.lastWeight,
|
||||
s = 0,
|
||||
f = 0,
|
||||
u = 0,
|
||||
l = this.outputBuffer;
|
||||
if (e % this.channels == 0) {
|
||||
if (e > 0) {
|
||||
for (; a < 1; a += o)
|
||||
for (s = 1 - (f = a % 1), r = 0; r < this.channels; ++r) l[u++] = this.lastOutput[r] * s + t[
|
||||
r] * f;
|
||||
for (a--, e -= this.channels, n = Math.floor(a) * this.channels; u < i && n < e;) {
|
||||
for (s = 1 - (f = a % 1), r = 0; r < this.channels; ++r) l[u++] = t[n + r] * s + t[n + this
|
||||
.channels + r] * f;
|
||||
a += o, n = Math.floor(a) * this.channels
|
||||
}
|
||||
for (r = 0; r < this.channels; ++r) this.lastOutput[r] = t[n++];
|
||||
return this.lastWeight = a % 1, this.bufferSlice(u)
|
||||
}
|
||||
return this.noReturn ? 0 : []
|
||||
}
|
||||
throw new Error("Buffer was of incorrect sample length.")
|
||||
}, l.prototype.compileMultiTap = function(t) {
|
||||
var e = [],
|
||||
r = t.length;
|
||||
this.initializeBuffers(r);
|
||||
var n = this.outputBufferSize;
|
||||
if (r % this.channels == 0) {
|
||||
if (r > 0) {
|
||||
for (var i = this.ratioWeight, o = 0, a = 0; a < this.channels; ++a) e[a] = 0;
|
||||
var s = 0,
|
||||
f = 0,
|
||||
u = !this.tailExists;
|
||||
this.tailExists = !1;
|
||||
var l = this.outputBuffer,
|
||||
h = 0,
|
||||
c = 0;
|
||||
do {
|
||||
if (u)
|
||||
for (o = i, a = 0; a < this.channels; ++a) e[a] = 0;
|
||||
else {
|
||||
for (o = this.lastWeight, a = 0; a < this.channels; ++a) e[a] += this.lastOutput[a];
|
||||
u = !0
|
||||
}
|
||||
for (; o > 0 && s < r;) {
|
||||
if (!(o >= (f = 1 + s - c))) {
|
||||
for (a = 0; a < this.channels; ++a) e[a] += t[s + a] * o;
|
||||
c += o, o = 0;
|
||||
break
|
||||
}
|
||||
for (a = 0; a < this.channels; ++a) e[a] += t[s++] * f;
|
||||
c = s, o -= f
|
||||
}
|
||||
if (0 != o) {
|
||||
for (this.lastWeight = o, a = 0; a < this.channels; ++a) this.lastOutput[a] = e[a];
|
||||
this.tailExists = !0;
|
||||
break
|
||||
}
|
||||
for (a = 0; a < this.channels; ++a) l[h++] = e[a] / i
|
||||
} while (s < r && h < n);
|
||||
return this.bufferSlice(h)
|
||||
}
|
||||
return this.noReturn ? 0 : []
|
||||
}
|
||||
throw new Error("Buffer was of incorrect sample length.")
|
||||
}, l.prototype.bypassResampler = function(t) {
|
||||
return this.noReturn ? (this.outputBuffer = t, t.length) : t
|
||||
}, l.prototype.bufferSlice = function(t) {
|
||||
if (this.noReturn) return t;
|
||||
try {
|
||||
return this.outputBuffer.subarray(0, t)
|
||||
} catch (e) {
|
||||
try {
|
||||
return this.outputBuffer.length = t, this.outputBuffer
|
||||
} catch (e) {
|
||||
return this.outputBuffer.slice(0, t)
|
||||
}
|
||||
}
|
||||
}, l.prototype.initializeBuffers = function(t) {
|
||||
this.outputBufferSize = Math.ceil(t * this.toSampleRate / this.fromSampleRate);
|
||||
try {
|
||||
this.outputBuffer = new Float32Array(this.outputBufferSize), this.lastOutput = new Float32Array(this
|
||||
.channels)
|
||||
} catch (t) {
|
||||
this.outputBuffer = [], this.lastOutput = []
|
||||
}
|
||||
};
|
||||
var h = function(e) {
|
||||
! function(t, e) {
|
||||
if ("function" != typeof e && null !== e) throw new TypeError(
|
||||
"Super expression must either be null or a function");
|
||||
t.prototype = Object.create(e && e.prototype, {
|
||||
constructor: {
|
||||
value: t,
|
||||
writable: !0,
|
||||
configurable: !0
|
||||
}
|
||||
}), Object.defineProperty(t, "prototype", {
|
||||
writable: !1
|
||||
}), e && r(t, e)
|
||||
}(h, e);
|
||||
var n, i, o, u = s(h);
|
||||
|
||||
function h() {
|
||||
var t;
|
||||
! function(t, e) {
|
||||
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
|
||||
}(this, h);
|
||||
var e = a(t = u.call(this));
|
||||
return t.port.onmessage = function(t) {
|
||||
var r = t.data,
|
||||
n = r.type,
|
||||
i = r.data;
|
||||
if (console.log("type", n), "init" === n) {
|
||||
var o = i.frameSize,
|
||||
a = i.toSampleRate,
|
||||
s = i.arrayBufferType,
|
||||
f = i.fromSampleRate;
|
||||
return e.frameSize = o * Math.floor(f / a), e.resampler = new l(f, a, 1), e
|
||||
.frameBuffer = [], void(e.arrayBufferType = s),e.gain = i.gain || 1.0;
|
||||
}
|
||||
"stop" === n && (e.port.postMessage({
|
||||
frameBuffer: e.transData(e.frameBuffer),
|
||||
isLastFrame: !0
|
||||
}), e.frameBuffer = [])
|
||||
}, t
|
||||
}
|
||||
return n = h, (i = [{
|
||||
key: "process",
|
||||
value: function(t) {
|
||||
var e, r = t[0][0];
|
||||
return this.frameSize ? ((e = this.frameBuffer).push.apply(e, f(r)), this
|
||||
.frameBuffer.length >= this.frameSize && (this.port.postMessage({
|
||||
frameBuffer: this.transData(this.frameBuffer),
|
||||
isLastFrame: !1
|
||||
}), this.frameBuffer = []), !0) : (r && this.port.postMessage({
|
||||
frameBuffer: this.transData(r),
|
||||
isLastFrame: !1
|
||||
}), !0)
|
||||
}
|
||||
}, {
|
||||
key: "transData",
|
||||
value: function(t) {
|
||||
const gain = this.gain; // 增益系数,可根据实际需求修改
|
||||
t = t.map(sample => sample * gain); // 对每个样本应用增益
|
||||
return "short16" === this.arrayBufferType && (t = function(t) {
|
||||
for (var e = new ArrayBuffer(2 * t.length), r = new DataView(e), n = 0,
|
||||
i = 0; i < t.length; i += 1, n += 2) {
|
||||
var o = Math.max(-1, Math.min(1, t[i]));
|
||||
r.setInt16(n, o < 0 ? 32768 * o : 32767 * o, !0)
|
||||
}
|
||||
return r.buffer
|
||||
}(t = this.resampler.resampler(t))), t
|
||||
}
|
||||
}]) && t(n.prototype, i), o && t(n, o), Object.defineProperty(n, "prototype", {
|
||||
writable: !1
|
||||
}), h
|
||||
}(o(AudioWorkletProcessor));
|
||||
registerProcessor("processor-worklet", h)
|
||||
}();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,13 @@
|
||||
uni.addInterceptor({
|
||||
returnValue (res) {
|
||||
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
|
||||
return res;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
res.then((res) => {
|
||||
if (!res) return resolve(res)
|
||||
return res[0] ? reject(res[0]) : resolve(res[1])
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
|
||||
/* 颜色变量 */
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-font-size-lg:16px;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:20px;
|
||||
$uni-img-size-base:26px;
|
||||
$uni-img-size-lg:40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
$uni-border-radius-base: 3px;
|
||||
$uni-border-radius-lg: 6px;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 5px;
|
||||
$uni-spacing-row-base: 10px;
|
||||
$uni-spacing-row-lg: 15px;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 4px;
|
||||
$uni-spacing-col-base: 8px;
|
||||
$uni-spacing-col-lg: 12px;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
||||
@@ -0,0 +1,11 @@
|
||||
## 1.0.5(2025-09-03)
|
||||
新增分贝值
|
||||
## 1.0.4(2025-08-30)
|
||||
加大增益值范围
|
||||
## 1.0.3(2025-08-29)
|
||||
增益
|
||||
## 1.0.2(2025-08-29)
|
||||
添加新属性:增益
|
||||
## 1.0.0(2025-08-02)
|
||||
# yao-RecordFrame
|
||||
web audio api录音
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<view
|
||||
:options="options"
|
||||
:change:options="record.startRecord"
|
||||
:status="status"
|
||||
:change:status="record.onStop"
|
||||
>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default{
|
||||
data(){
|
||||
return{
|
||||
options:null,
|
||||
status:null
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
start(option){
|
||||
this.options=option;
|
||||
this.status='start';
|
||||
},
|
||||
stop(){
|
||||
this.status='stop';
|
||||
this.options=null;
|
||||
},
|
||||
frameRecorded({isLastFrame,frameBuffer}){
|
||||
this.$emit('onFrameRecorded',{isLastFrame,frameBuffer:this.base64ToUint8Array(frameBuffer)})
|
||||
},
|
||||
decibels(value){
|
||||
this.$emit('currentDecibels',value);
|
||||
},
|
||||
base64ToUint8Array(base64) {
|
||||
const binaryString = atob(base64.split(',')[1] || base64);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
},
|
||||
toShowToast(){
|
||||
uni.showToast({
|
||||
title:'发生错误,请检查是否有麦克风权限',
|
||||
icon:'none'
|
||||
});
|
||||
this.stop();
|
||||
},
|
||||
recordedChunks(base64){
|
||||
this.$emit('onStop',base64)
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script module="record" lang="renderjs">
|
||||
// 保存需要关闭的引用
|
||||
let mediaStream;
|
||||
let audioContext;
|
||||
let processor;
|
||||
// 全局变量存储录音数据
|
||||
let recordedChunks = [];
|
||||
let decibelHistory = []; // 存储分贝历史数据用于可视化
|
||||
|
||||
// 分贝计算相关配置
|
||||
const DB_CONFIG = {
|
||||
minDecibels: -80, // 最小可测分贝
|
||||
maxDecibels: -30, // 最大可测分贝
|
||||
smoothingTimeConstant: 0.8 // 平滑系数,使分贝变化更平缓
|
||||
};
|
||||
|
||||
export default{
|
||||
data(){
|
||||
return{
|
||||
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
async startRecord(options){
|
||||
if(options==null) return;
|
||||
|
||||
if (audioContext) return;
|
||||
|
||||
try{
|
||||
// 配置参数
|
||||
var a, i = options.sampleRate,
|
||||
s = options.frameSize;
|
||||
if(options.gain<1.0){
|
||||
options.gain=1.0;
|
||||
}
|
||||
if(options.gain>20.0){
|
||||
options.gain=20.0;
|
||||
}
|
||||
|
||||
audioContext = new AudioContext();
|
||||
|
||||
// 加载并初始化AudioWorklet
|
||||
await audioContext.audioWorklet.addModule('static/dist/processor.worklet.js');
|
||||
|
||||
//const mediaStream = new MediaStream();
|
||||
mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
sampleRate: options.sampleRate,
|
||||
channelCount: 1
|
||||
}
|
||||
});
|
||||
|
||||
const source = audioContext.createMediaStreamSource(mediaStream);
|
||||
processor = new AudioWorkletNode(audioContext, 'processor-worklet');
|
||||
|
||||
// 初始化处理器
|
||||
processor.port.postMessage({
|
||||
type: 'init',
|
||||
data: {
|
||||
frameSize: options.frameSize, // 样本数 (1280字节 / 2字节每样本)
|
||||
fromSampleRate: 48000, // 输入采样率
|
||||
toSampleRate: options.sampleRate, // 输出采样率 (1/3)
|
||||
arrayBufferType: 'short16',
|
||||
gain:options.gain
|
||||
}
|
||||
});
|
||||
|
||||
// 接收40ms间隔的音频数据
|
||||
processor.port.onmessage = (t) => {
|
||||
|
||||
var r = t.data,
|
||||
o = r.frameBuffer,
|
||||
n = r.isLastFrame;
|
||||
|
||||
// 计算当前帧的分贝值
|
||||
if (o && o.byteLength > 0) {
|
||||
const decibels = this.calculateDecibels(o);
|
||||
this.onDecibelsCalculated(decibels); // 触发分贝回调
|
||||
|
||||
// 存储历史数据,限制长度
|
||||
decibelHistory.push(decibels);
|
||||
if (decibelHistory.length > 100) {
|
||||
decibelHistory.shift();
|
||||
}
|
||||
}
|
||||
|
||||
if (null == o ? void 0 : o.byteLength)
|
||||
for (var a = 0; a < o.byteLength;) {
|
||||
const frameData = {
|
||||
isLastFrame: n && a + s >= o.byteLength,
|
||||
frameBuffer: t.data.frameBuffer.slice(a, a + s)
|
||||
};
|
||||
this.onFrameRecorded(frameData);
|
||||
|
||||
// 存储录音数据(仅在非暂停状态)
|
||||
recordedChunks.push(frameData.frameBuffer);
|
||||
|
||||
a += s;
|
||||
}
|
||||
else this.onFrameRecorded(t.data);
|
||||
|
||||
};
|
||||
|
||||
source.connect(processor);
|
||||
processor.connect(audioContext.destination);
|
||||
}catch(err){
|
||||
this.$ownerInstance.callMethod('toShowToast');
|
||||
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
onFrameRecorded({isLastFrame,frameBuffer}){
|
||||
this.$ownerInstance.callMethod('frameRecorded', {isLastFrame,frameBuffer:this.toBase64(frameBuffer)});
|
||||
},
|
||||
calculateDecibels(frameBuffer) {
|
||||
try {
|
||||
// 将帧数据转换为16位整数数组
|
||||
const samples = new Int16Array(frameBuffer);
|
||||
|
||||
// 计算均方根(RMS)
|
||||
let sum = 0;
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
const value = samples[i] / 32768; // 归一化到[-1, 1]范围
|
||||
sum += value * value; // 平方和
|
||||
}
|
||||
|
||||
const rms = Math.sqrt(sum / samples.length);
|
||||
|
||||
// 防止log(0)错误
|
||||
if (rms < 0.00001) {
|
||||
return DB_CONFIG.minDecibels;
|
||||
}
|
||||
|
||||
// 转换为分贝 (20 * log10(rms))
|
||||
let db = 20 * Math.log10(rms);
|
||||
|
||||
// 应用平滑处理
|
||||
if (decibelHistory.length > 0) {
|
||||
const lastDb = decibelHistory[decibelHistory.length - 1];
|
||||
db = lastDb * DB_CONFIG.smoothingTimeConstant + db * (1 - DB_CONFIG.smoothingTimeConstant);
|
||||
}
|
||||
|
||||
// 限制分贝范围
|
||||
return Math.max(DB_CONFIG.minDecibels, Math.min(DB_CONFIG.maxDecibels, db));
|
||||
} catch (e) {
|
||||
console.error('计算分贝时出错:', e);
|
||||
return DB_CONFIG.minDecibels;
|
||||
}
|
||||
},
|
||||
onDecibelsCalculated(decibels) {
|
||||
//console.log(`当前分贝: ${decibels.toFixed(1)} dB`);
|
||||
this.$ownerInstance.callMethod('decibels', decibels.toFixed(1));
|
||||
// 可以在这里添加分贝可视化逻辑
|
||||
// 例如更新UI显示当前音量
|
||||
},
|
||||
toBase64(buffer){
|
||||
let binary = "";
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
},
|
||||
async onRecordedChunks(chunks){
|
||||
|
||||
var mergedBuffer=this.mergeAudioBuffers(chunks);
|
||||
// 2. 将合并的二进制流转换为WAV格式(需要添加WAV文件头)
|
||||
const wavBlob = this.createWavBlob(mergedBuffer, 1, 16000); // 单声道,44100Hz
|
||||
// 3. 将WAV转为Base64(可选,若需传输)
|
||||
const base64 = await this.blobToBase64(wavBlob);
|
||||
this.$ownerInstance.callMethod('recordedChunks',base64)
|
||||
},
|
||||
onStop(value){
|
||||
if(value!=='stop') return;
|
||||
|
||||
|
||||
this.onFrameRecorded({isLastFrame:true,frameBuffer:''});
|
||||
|
||||
this.onRecordedChunks(recordedChunks);
|
||||
|
||||
recordedChunks = [];
|
||||
|
||||
if (mediaStream) {
|
||||
// 停止所有媒体轨道
|
||||
mediaStream.getTracks().forEach(track => track.stop());
|
||||
mediaStream = null;
|
||||
}
|
||||
|
||||
if (processor) {
|
||||
// 断开音频节点连接
|
||||
processor.disconnect();
|
||||
processor = null;
|
||||
}
|
||||
|
||||
if (audioContext) {
|
||||
// 关闭音频上下文
|
||||
audioContext.close().then(() => {
|
||||
audioContext = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
//合并所有buffer
|
||||
mergeAudioBuffers(buffers) {
|
||||
let totalLength = buffers.reduce((acc, buf) => acc + buf.byteLength, 0);
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
buffers.forEach(buffer => {
|
||||
result.set(new Uint8Array(buffer), offset);
|
||||
offset += buffer.byteLength;
|
||||
});
|
||||
// 验证合并后的长度是否正确
|
||||
if (offset !== totalLength) console.error("合并后的长度不符!");
|
||||
return result.buffer;
|
||||
},
|
||||
// 新增WAV封装函数(基于前序回答的createWavBlob)
|
||||
createWavBlob(pcmData, numChannels, sampleRate) {
|
||||
const bytesPerSample = 2; // 16-bit PCM
|
||||
const blockAlign = numChannels * bytesPerSample;
|
||||
const byteRate = sampleRate * blockAlign;
|
||||
const bufferLength = pcmData.byteLength;
|
||||
const totalLength = 44 + bufferLength; // WAV头(44字节) + 音频数据
|
||||
|
||||
const buffer = new ArrayBuffer(totalLength);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
// 写入WAV文件头(RIFF、fmt、data区块)
|
||||
this.writeString(view, 0, 'RIFF');
|
||||
view.setUint32(4, totalLength - 8, true);
|
||||
this.writeString(view, 8, 'WAVE');
|
||||
this.writeString(view, 12, 'fmt ');
|
||||
view.setUint32(16, 16, true); // fmt区块大小
|
||||
view.setUint16(20, 1, true); // PCM格式
|
||||
view.setUint16(22, numChannels, true);
|
||||
view.setUint32(24, sampleRate, true);
|
||||
view.setUint32(28, byteRate, true);
|
||||
view.setUint16(32, blockAlign, true);
|
||||
view.setUint16(34, 16, true); // 16位采样
|
||||
this.writeString(view, 36, 'data');
|
||||
view.setUint32(40, bufferLength, true);
|
||||
|
||||
// 写入音频数据(假设pcmData是Uint8Array,需转换为16位PCM)
|
||||
const pcm16 = new Int16Array(pcmData);
|
||||
for (let i = 0; i < pcm16.length; i++) {
|
||||
view.setInt16(44 + i * 2, pcm16[i], true);
|
||||
}
|
||||
|
||||
return new Blob([buffer], { type: 'audio/wav' });
|
||||
},
|
||||
// 辅助函数:Blob转Base64
|
||||
blobToBase64(blob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
},
|
||||
// 辅助函数:字符串写入DataView
|
||||
writeString(view, offset, string) {
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
! function() {
|
||||
"use strict";
|
||||
|
||||
function t(t, e) {
|
||||
for (var r = 0; r < e.length; r++) {
|
||||
var n = e[r];
|
||||
n.enumerable = n.enumerable || !1, n.configurable = !0, "value" in n && (n.writable = !0), Object
|
||||
.defineProperty(t, (i = n.key, o = void 0, "symbol" == typeof(o = function(t, e) {
|
||||
if ("object" != typeof t || null === t) return t;
|
||||
var r = t[Symbol.toPrimitive];
|
||||
if (void 0 !== r) {
|
||||
var n = r.call(t, e || "default");
|
||||
if ("object" != typeof n) return n;
|
||||
throw new TypeError("@@toPrimitive must return a primitive value.")
|
||||
}
|
||||
return ("string" === e ? String : Number)(t)
|
||||
}(i, "string")) ? o : String(o)), n)
|
||||
}
|
||||
var i, o
|
||||
}
|
||||
|
||||
function e(t) {
|
||||
return e = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function(t) {
|
||||
return t.__proto__ || Object.getPrototypeOf(t)
|
||||
}, e(t)
|
||||
}
|
||||
|
||||
function r(t, e) {
|
||||
return r = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function(t, e) {
|
||||
return t.__proto__ = e, t
|
||||
}, r(t, e)
|
||||
}
|
||||
|
||||
function n() {
|
||||
if ("undefined" == typeof Reflect || !Reflect.construct) return !1;
|
||||
if (Reflect.construct.sham) return !1;
|
||||
if ("function" == typeof Proxy) return !0;
|
||||
try {
|
||||
return Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function() {}))), !0
|
||||
} catch (t) {
|
||||
return !1
|
||||
}
|
||||
}
|
||||
|
||||
function i(t, e, o) {
|
||||
return i = n() ? Reflect.construct.bind() : function(t, e, n) {
|
||||
var i = [null];
|
||||
i.push.apply(i, e);
|
||||
var o = new(Function.bind.apply(t, i));
|
||||
return n && r(o, n.prototype), o
|
||||
}, i.apply(null, arguments)
|
||||
}
|
||||
|
||||
function o(t) {
|
||||
var n = "function" == typeof Map ? new Map : void 0;
|
||||
return o = function(t) {
|
||||
if (null === t || (o = t, -1 === Function.toString.call(o).indexOf("[native code]"))) return t;
|
||||
var o;
|
||||
if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function");
|
||||
if (void 0 !== n) {
|
||||
if (n.has(t)) return n.get(t);
|
||||
n.set(t, a)
|
||||
}
|
||||
|
||||
function a() {
|
||||
return i(t, arguments, e(this).constructor)
|
||||
}
|
||||
return a.prototype = Object.create(t.prototype, {
|
||||
constructor: {
|
||||
value: a,
|
||||
enumerable: !1,
|
||||
writable: !0,
|
||||
configurable: !0
|
||||
}
|
||||
}), r(a, t)
|
||||
}, o(t)
|
||||
}
|
||||
|
||||
function a(t) {
|
||||
if (void 0 === t) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||
return t
|
||||
}
|
||||
|
||||
function s(t) {
|
||||
var r = n();
|
||||
return function() {
|
||||
var n, i = e(t);
|
||||
if (r) {
|
||||
var o = e(this).constructor;
|
||||
n = Reflect.construct(i, arguments, o)
|
||||
} else n = i.apply(this, arguments);
|
||||
return function(t, e) {
|
||||
if (e && ("object" == typeof e || "function" == typeof e)) return e;
|
||||
if (void 0 !== e) throw new TypeError(
|
||||
"Derived constructors may only return object or undefined");
|
||||
return a(t)
|
||||
}(this, n)
|
||||
}
|
||||
}
|
||||
|
||||
function f(t) {
|
||||
return function(t) {
|
||||
if (Array.isArray(t)) return u(t)
|
||||
}(t) || function(t) {
|
||||
if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"]) return Array
|
||||
.from(t)
|
||||
}(t) || function(t, e) {
|
||||
if (!t) return;
|
||||
if ("string" == typeof t) return u(t, e);
|
||||
var r = Object.prototype.toString.call(t).slice(8, -1);
|
||||
"Object" === r && t.constructor && (r = t.constructor.name);
|
||||
if ("Map" === r || "Set" === r) return Array.from(t);
|
||||
if ("Arguments" === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)) return u(t, e)
|
||||
}(t) || function() {
|
||||
throw new TypeError(
|
||||
"Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
function u(t, e) {
|
||||
(null == e || e > t.length) && (e = t.length);
|
||||
for (var r = 0, n = new Array(e); r < e; r++) n[r] = t[r];
|
||||
return n
|
||||
}
|
||||
|
||||
function l(t, e, r, n) {
|
||||
this.fromSampleRate = t, this.toSampleRate = e, this.channels = 0 | r, this.noReturn = !!n, this.initialize()
|
||||
}
|
||||
l.prototype.initialize = function() {
|
||||
if (!(this.fromSampleRate > 0 && this.toSampleRate > 0 && this.channels > 0)) throw new Error(
|
||||
"Invalid settings specified for the resampler.");
|
||||
this.fromSampleRate == this.toSampleRate ? (this.resampler = this.bypassResampler, this.ratioWeight = 1) : (
|
||||
this.fromSampleRate < this.toSampleRate ? (this.lastWeight = 1, this.resampler = this
|
||||
.compileLinearInterpolation) : (this.tailExists = !1, this.lastWeight = 0, this.resampler = this
|
||||
.compileMultiTap), this.ratioWeight = this.fromSampleRate / this.toSampleRate)
|
||||
}, l.prototype.compileLinearInterpolation = function(t) {
|
||||
var e = t.length;
|
||||
this.initializeBuffers(e);
|
||||
var r, n, i = this.outputBufferSize,
|
||||
o = this.ratioWeight,
|
||||
a = this.lastWeight,
|
||||
s = 0,
|
||||
f = 0,
|
||||
u = 0,
|
||||
l = this.outputBuffer;
|
||||
if (e % this.channels == 0) {
|
||||
if (e > 0) {
|
||||
for (; a < 1; a += o)
|
||||
for (s = 1 - (f = a % 1), r = 0; r < this.channels; ++r) l[u++] = this.lastOutput[r] * s + t[
|
||||
r] * f;
|
||||
for (a--, e -= this.channels, n = Math.floor(a) * this.channels; u < i && n < e;) {
|
||||
for (s = 1 - (f = a % 1), r = 0; r < this.channels; ++r) l[u++] = t[n + r] * s + t[n + this
|
||||
.channels + r] * f;
|
||||
a += o, n = Math.floor(a) * this.channels
|
||||
}
|
||||
for (r = 0; r < this.channels; ++r) this.lastOutput[r] = t[n++];
|
||||
return this.lastWeight = a % 1, this.bufferSlice(u)
|
||||
}
|
||||
return this.noReturn ? 0 : []
|
||||
}
|
||||
throw new Error("Buffer was of incorrect sample length.")
|
||||
}, l.prototype.compileMultiTap = function(t) {
|
||||
var e = [],
|
||||
r = t.length;
|
||||
this.initializeBuffers(r);
|
||||
var n = this.outputBufferSize;
|
||||
if (r % this.channels == 0) {
|
||||
if (r > 0) {
|
||||
for (var i = this.ratioWeight, o = 0, a = 0; a < this.channels; ++a) e[a] = 0;
|
||||
var s = 0,
|
||||
f = 0,
|
||||
u = !this.tailExists;
|
||||
this.tailExists = !1;
|
||||
var l = this.outputBuffer,
|
||||
h = 0,
|
||||
c = 0;
|
||||
do {
|
||||
if (u)
|
||||
for (o = i, a = 0; a < this.channels; ++a) e[a] = 0;
|
||||
else {
|
||||
for (o = this.lastWeight, a = 0; a < this.channels; ++a) e[a] += this.lastOutput[a];
|
||||
u = !0
|
||||
}
|
||||
for (; o > 0 && s < r;) {
|
||||
if (!(o >= (f = 1 + s - c))) {
|
||||
for (a = 0; a < this.channels; ++a) e[a] += t[s + a] * o;
|
||||
c += o, o = 0;
|
||||
break
|
||||
}
|
||||
for (a = 0; a < this.channels; ++a) e[a] += t[s++] * f;
|
||||
c = s, o -= f
|
||||
}
|
||||
if (0 != o) {
|
||||
for (this.lastWeight = o, a = 0; a < this.channels; ++a) this.lastOutput[a] = e[a];
|
||||
this.tailExists = !0;
|
||||
break
|
||||
}
|
||||
for (a = 0; a < this.channels; ++a) l[h++] = e[a] / i
|
||||
} while (s < r && h < n);
|
||||
return this.bufferSlice(h)
|
||||
}
|
||||
return this.noReturn ? 0 : []
|
||||
}
|
||||
throw new Error("Buffer was of incorrect sample length.")
|
||||
}, l.prototype.bypassResampler = function(t) {
|
||||
return this.noReturn ? (this.outputBuffer = t, t.length) : t
|
||||
}, l.prototype.bufferSlice = function(t) {
|
||||
if (this.noReturn) return t;
|
||||
try {
|
||||
return this.outputBuffer.subarray(0, t)
|
||||
} catch (e) {
|
||||
try {
|
||||
return this.outputBuffer.length = t, this.outputBuffer
|
||||
} catch (e) {
|
||||
return this.outputBuffer.slice(0, t)
|
||||
}
|
||||
}
|
||||
}, l.prototype.initializeBuffers = function(t) {
|
||||
this.outputBufferSize = Math.ceil(t * this.toSampleRate / this.fromSampleRate);
|
||||
try {
|
||||
this.outputBuffer = new Float32Array(this.outputBufferSize), this.lastOutput = new Float32Array(this
|
||||
.channels)
|
||||
} catch (t) {
|
||||
this.outputBuffer = [], this.lastOutput = []
|
||||
}
|
||||
};
|
||||
var h = function(e) {
|
||||
! function(t, e) {
|
||||
if ("function" != typeof e && null !== e) throw new TypeError(
|
||||
"Super expression must either be null or a function");
|
||||
t.prototype = Object.create(e && e.prototype, {
|
||||
constructor: {
|
||||
value: t,
|
||||
writable: !0,
|
||||
configurable: !0
|
||||
}
|
||||
}), Object.defineProperty(t, "prototype", {
|
||||
writable: !1
|
||||
}), e && r(t, e)
|
||||
}(h, e);
|
||||
var n, i, o, u = s(h);
|
||||
|
||||
function h() {
|
||||
var t;
|
||||
! function(t, e) {
|
||||
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
|
||||
}(this, h);
|
||||
var e = a(t = u.call(this));
|
||||
return t.port.onmessage = function(t) {
|
||||
var r = t.data,
|
||||
n = r.type,
|
||||
i = r.data;
|
||||
if (console.log("type", n), "init" === n) {
|
||||
var o = i.frameSize,
|
||||
a = i.toSampleRate,
|
||||
s = i.arrayBufferType,
|
||||
f = i.fromSampleRate;
|
||||
return e.frameSize = o * Math.floor(f / a), e.resampler = new l(f, a, 1), e
|
||||
.frameBuffer = [], void(e.arrayBufferType = s),e.gain = i.gain || 1.0;
|
||||
}
|
||||
"stop" === n && (e.port.postMessage({
|
||||
frameBuffer: e.transData(e.frameBuffer),
|
||||
isLastFrame: !0
|
||||
}), e.frameBuffer = [])
|
||||
}, t
|
||||
}
|
||||
return n = h, (i = [{
|
||||
key: "process",
|
||||
value: function(t) {
|
||||
var e, r = t[0][0];
|
||||
return this.frameSize ? ((e = this.frameBuffer).push.apply(e, f(r)), this
|
||||
.frameBuffer.length >= this.frameSize && (this.port.postMessage({
|
||||
frameBuffer: this.transData(this.frameBuffer),
|
||||
isLastFrame: !1
|
||||
}), this.frameBuffer = []), !0) : (r && this.port.postMessage({
|
||||
frameBuffer: this.transData(r),
|
||||
isLastFrame: !1
|
||||
}), !0)
|
||||
}
|
||||
}, {
|
||||
key: "transData",
|
||||
value: function(t) {
|
||||
const gain = this.gain; // 增益系数,可根据实际需求修改
|
||||
t = t.map(sample => sample * gain); // 对每个样本应用增益
|
||||
return "short16" === this.arrayBufferType && (t = function(t) {
|
||||
for (var e = new ArrayBuffer(2 * t.length), r = new DataView(e), n = 0,
|
||||
i = 0; i < t.length; i += 1, n += 2) {
|
||||
var o = Math.max(-1, Math.min(1, t[i]));
|
||||
r.setInt16(n, o < 0 ? 32768 * o : 32767 * o, !0)
|
||||
}
|
||||
return r.buffer
|
||||
}(t = this.resampler.resampler(t))), t
|
||||
}
|
||||
}]) && t(n.prototype, i), o && t(n, o), Object.defineProperty(n, "prototype", {
|
||||
writable: !1
|
||||
}), h
|
||||
}(o(AudioWorkletProcessor));
|
||||
registerProcessor("processor-worklet", h)
|
||||
}();
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"id": "yao-RecordFrame",
|
||||
"displayName": "web audio api录音+实时帧回调 支持 ios Android h5",
|
||||
"version": "1.0.5",
|
||||
"description": "web audio api录音、实时帧回调、分贝值",
|
||||
"keywords": [
|
||||
"ios",
|
||||
"Android",
|
||||
"h5",
|
||||
"实时帧回调",
|
||||
"分贝"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"uni-app": "^4.07",
|
||||
"uni-app-x": ""
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": "3371387322"
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "插件不采集任何数据",
|
||||
"permissions": "需要麦克风权限"
|
||||
},
|
||||
"npmurl": "",
|
||||
"darkmode": "x",
|
||||
"i18n": "x",
|
||||
"widescreen": "x"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "x",
|
||||
"aliyun": "x",
|
||||
"alipay": "x"
|
||||
},
|
||||
"client": {
|
||||
"uni-app": {
|
||||
"vue": {
|
||||
"vue2": "√",
|
||||
"vue3": "√"
|
||||
},
|
||||
"web": {
|
||||
"safari": "√",
|
||||
"chrome": "√"
|
||||
},
|
||||
"app": {
|
||||
"vue": "√",
|
||||
"nvue": "-",
|
||||
"android": "√",
|
||||
"ios": "√",
|
||||
"harmony": "x"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "x",
|
||||
"alipay": "x",
|
||||
"toutiao": "x",
|
||||
"baidu": "x",
|
||||
"kuaishou": "x",
|
||||
"jd": "x",
|
||||
"harmony": "x",
|
||||
"qq": "x",
|
||||
"lark": "x"
|
||||
},
|
||||
"quickapp": {
|
||||
"huawei": "x",
|
||||
"union": "x"
|
||||
}
|
||||
},
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"app": {
|
||||
"android": "-",
|
||||
"ios": "-",
|
||||
"harmony": "-"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
# yao-RecordFrame
|
||||
|
||||
##配置
|
||||
需要将模块下uni_modules/yao-RecordFrame的dist复制到static目录下面
|
||||
或者
|
||||
将uni_modules/yao-RecordFrame/dist目录的配置文件放到static/dist目录下面
|
||||
|
||||
###说明
|
||||
插件只适用于接实时语音识别的模型,不适合录音上传
|
||||
|
||||
### 示例代码
|
||||
|
||||
```javascript
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="start-record" @click="onStartRecord">开始录音</view>
|
||||
<view class="stop-record" @click="onStopRecord">停止录音</view>
|
||||
|
||||
<view class="frame">
|
||||
<view>实时回调:是否是最后一帧:{{isLastFrame}}</view>
|
||||
<view class="frameBuffer">{{frameBuffer}}</view>
|
||||
</view>
|
||||
|
||||
<yao-RecordFrame
|
||||
ref="recordFrame"
|
||||
@onFrameRecorded="frameRecorded"
|
||||
@currentDecibels="onCurrentDecibels"
|
||||
@onStop="stopIt"></yao-RecordFrame>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
frameBuffer:'',//实时帧的frameBuffer值
|
||||
isLastFrame:false,//是否是最后一帧
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
onStartRecord(){
|
||||
//开启录音(仅支持两种参数)
|
||||
this.$refs.recordFrame.start({
|
||||
sampleRate:16000,
|
||||
frameSize:1024,
|
||||
gain:1.0 //增益值,数字越高音频声音越大 1.0~20.0
|
||||
})
|
||||
},
|
||||
onStopRecord(){
|
||||
//停止录音
|
||||
this.$refs.recordFrame.stop();
|
||||
},
|
||||
//停止录音
|
||||
stopIt(base64){
|
||||
//base64音频只能在浏览器播放
|
||||
//也可以base64音频转成文件音频可app播放
|
||||
console.log(base64);
|
||||
},
|
||||
frameRecorded({isLastFrame,frameBuffer}){
|
||||
console.log(isLastFrame,frameBuffer);
|
||||
if(!isLastFrame){
|
||||
this.frameBuffer=frameBuffer;
|
||||
}
|
||||
|
||||
this.isLastFrame=isLastFrame?'true':'false';
|
||||
},
|
||||
onCurrentDecibels(decibels){
|
||||
//当前分贝值 最小可测分贝(-80) 最大可测分贝(-30)
|
||||
console.log("当前分贝:" + decibels)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user