@94ai/softphone 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
8
+ <title>Document</title>
9
+ <style>
10
+ .btn {
11
+ padding: 12px 16px;
12
+ background-color: #3498db;
13
+ color: white;
14
+ border-radius: 2px;
15
+ border: none;
16
+ box-shadow: 0 1px 4px 1px black;
17
+ }
18
+
19
+ a {
20
+ margin-top: 20px;
21
+ text-decoration: none;
22
+ }
23
+ </style>
24
+ </head>
25
+
26
+ <body>
27
+ <button id="btn" class="btn">点击录制3秒语音</button>
28
+ <p>设备采样率: <span id="tips"></span></p>
29
+ <script>
30
+ isRecording = false;
31
+ let constraints = { audio: true };
32
+ let duration = 3;
33
+ let sampleRate = 16000;
34
+ let chunks = [];
35
+
36
+ //采集媒体流
37
+ navigator.mediaDevices.getUserMedia(constraints)
38
+ .then((stream) => {
39
+ ac = new AudioContext({ sampleRate: 16000 });
40
+ document.getElementById('tips').innerHTML = ac.sampleRate;
41
+
42
+ //创建音频处理的源节点
43
+ let source = ac.createMediaStreamSource(stream);
44
+ //创建自定义处理节点
45
+ let scriptNode = ac.createScriptProcessor(4096, 1, 1);
46
+ //创建音频处理的输出节点
47
+ let dest = ac.createMediaStreamDestination();
48
+
49
+ //串联连接
50
+ source.connect(scriptNode);
51
+ scriptNode.connect(dest);
52
+
53
+ //定义stream chunk的事件处理
54
+ scriptNode.onaudioprocess = function (audioProcessingEvent) {
55
+ //获取输入流位置
56
+ var inputBuffer = audioProcessingEvent.inputBuffer;
57
+ //获取输出流位置
58
+ var outputBuffer = audioProcessingEvent.outputBuffer;
59
+ for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
60
+ var inputData = inputBuffer.getChannelData(channel);
61
+ var outputData = outputBuffer.getChannelData(channel);
62
+ if (isRecording) {
63
+ console.log(inputData)
64
+ console.log('获取到片段!');
65
+ for (let i = 0; i < inputData.length; i = i + 1) {
66
+ chunks.push(inputData[i]);
67
+ }
68
+ // console.log(chunks)
69
+ }
70
+ };
71
+ }
72
+ })
73
+ .catch(err => {
74
+ console.log(err);
75
+ });
76
+
77
+ //按钮控制录音与否
78
+ document.getElementById('btn').addEventListener('click', function (event) {
79
+ isRecording = true;
80
+ console.log('正在录音...');
81
+ setTimeout(() => {
82
+ isRecording = false;
83
+ console.log('录音结束');
84
+ //结束录音并重新处理数据
85
+ processPCMData();
86
+ }, duration * 1000);
87
+ })
88
+
89
+ //处理原始数据
90
+ function processPCMData() {
91
+ //添加前置字节包装为wav格式,然后将float转换为16位深形式
92
+ let depth16bitArrayBuffer = encodeWAV(chunks);
93
+
94
+ //生成一个audio标签听录音结果
95
+ let blob = new Blob([depth16bitArrayBuffer]);
96
+ let url = URL.createObjectURL(blob);
97
+ const downloadEl = document.createElement('audio');
98
+ downloadEl.style = 'display: block';
99
+ downloadEl.controls = true;
100
+ downloadEl.src = url;
101
+ document.body.appendChild(downloadEl);
102
+ }
103
+
104
+ /***************************以下为研究过程中涉及到的功能函数******************************************/
105
+
106
+ //重采样方法
107
+ function Resample() {
108
+ let reSampleRate = 16000;
109
+ let r = interleave(chunks, reSampleRate);
110
+ sourceResample = ac.createBufferSource();
111
+ myArrayBuffer = ac.createBuffer(1, r.length, reSampleRate);
112
+ nowBuffering = myArrayBuffer.getChannelData(0);
113
+
114
+ for (let i = 0; i < nowBuffering.length; i++) {
115
+ nowBuffering[i] = r[i];
116
+ }
117
+ sourceResample.buffer = myArrayBuffer;
118
+ sourceResample.connect(ac.destination);
119
+ sourceResample.start();
120
+ }
121
+
122
+ //重采样辅助方法,简易的间隔丢弃策略
123
+ function interleave(e, outputSampleRate) {
124
+ var t = e.length;
125
+ sampleRate = 44100;
126
+ var s = 0,
127
+ o = sampleRate / outputSampleRate, // 2
128
+ u = Math.ceil(t * outputSampleRate / sampleRate), //
129
+ a = new Float32Array(u);
130
+ for (i = 0; i < u; i++) {
131
+ a[i] = e[Math.floor(s)];
132
+ s += o;
133
+ }
134
+ return a;
135
+ }
136
+
137
+ //输出wav文件,wav是在pcm前增加44个字节的辅助信息
138
+ function encodeWAV(samples) {
139
+ let buffer = new ArrayBuffer(44 + samples.length * 2);
140
+ let view = new DataView(buffer);
141
+ /* RIFF identifier */
142
+ writeString(view, 0, 'RIFF');
143
+ /* RIFF chunk length */
144
+ view.setUint32(4, 36 + samples.length * 2, true);
145
+ /* RIFF type */
146
+ writeString(view, 8, 'WAVE');
147
+ /* format chunk identifier */
148
+ writeString(view, 12, 'fmt ');
149
+ /* format chunk length */
150
+ view.setUint32(16, 16, true);
151
+ /* sample format (raw) */
152
+ view.setUint16(20, 1, true);
153
+ /* channel count */
154
+ view.setUint16(22, 1, true);
155
+ /* sample rate */
156
+ view.setUint32(24, sampleRate, true);
157
+ /* byte rate (sample rate * block align) */
158
+ view.setUint32(28, sampleRate * 4, true);
159
+ /* block align (channel count * bytes per sample) */
160
+ view.setUint16(32, 1 * 2, true);
161
+ /* bits per sample */
162
+ view.setUint16(34, 16, true);
163
+ /* data chunk identifier */
164
+ writeString(view, 36, 'data');
165
+ /* data chunk length */
166
+ view.setUint32(40, samples.length * 2, true);
167
+
168
+ floatTo16BitPCM(view, 44, samples);
169
+
170
+ return view;
171
+ }
172
+
173
+ //辅助方法
174
+ function writeString(view, offset, string) {
175
+ for (let i = 0; i < string.length; i++) {
176
+ view.setUint8(offset + i, string.charCodeAt(i));
177
+ }
178
+ }
179
+
180
+ //float32转换16bit位深pcm
181
+ function floatTo16BitPCM(output, offset, input) {
182
+ for (let i = 0; i < input.length; i++ , offset += 2) {
183
+ let s = Math.max(-1, Math.min(1, input[i]));
184
+ output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
185
+ }
186
+ }
187
+ </script>
188
+ </body>
189
+
190
+ </html>
Binary file