audio.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. // @ts-ignore
  2. import lamejs from 'lamejs'
  3. // @ts-ignore
  4. import MPEGMode from 'lamejs/src/js/MPEGMode'
  5. // @ts-ignore
  6. import Lame from 'lamejs/src/js/Lame'
  7. // @ts-ignore
  8. import BitStream from 'lamejs/src/js/BitStream'
  9. if (globalThis) {
  10. (globalThis as any).MPEGMode = MPEGMode
  11. ; (globalThis as any).Lame = Lame
  12. ; (globalThis as any).BitStream = BitStream
  13. }
  14. export const convertToMp3 = (recorder: any) => {
  15. const wav = lamejs.WavHeader.readHeader(recorder.getWAV())
  16. const { channels, sampleRate } = wav
  17. const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128)
  18. const result = recorder.getChannelData()
  19. const buffer = []
  20. const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2)
  21. const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2)
  22. const remaining = leftData.length + (rightData ? rightData.length : 0)
  23. const maxSamples = 1152
  24. for (let i = 0; i < remaining; i += maxSamples) {
  25. const left = leftData.subarray(i, i + maxSamples)
  26. let right = null
  27. let mp3buf = null
  28. if (channels === 2) {
  29. right = rightData.subarray(i, i + maxSamples)
  30. mp3buf = mp3enc.encodeBuffer(left, right)
  31. }
  32. else {
  33. mp3buf = mp3enc.encodeBuffer(left)
  34. }
  35. if (mp3buf.length > 0)
  36. buffer.push(mp3buf)
  37. }
  38. const enc = mp3enc.flush()
  39. if (enc.length > 0)
  40. buffer.push(enc)
  41. return new Blob(buffer, { type: 'audio/mp3' })
  42. }
  43. export const convertFloat32ArrayToMp3 = (audio: Float32Array) => {
  44. // Float32Array of audio samples at sample rate 16000
  45. const floatArray2Int16 = (floatBuffer: Float32Array) => {
  46. const int16Buffer = new Int16Array(floatBuffer.length);
  47. for (let i = 0, len = floatBuffer.length; i < len; i++) {
  48. if (floatBuffer[i] < 0) {
  49. int16Buffer[i] = 0x8000 * floatBuffer[i];
  50. } else {
  51. int16Buffer[i] = 0x7fff * floatBuffer[i];
  52. }
  53. }
  54. return int16Buffer;
  55. }
  56. const mergeArray = (list: Float32Array[]) => {
  57. const length = list.length * list[0].length;
  58. let data = new Float32Array(length);
  59. let offset = 0;
  60. for (let i = 0; i < list.length; i++) {
  61. data.set(list[i], offset);
  62. offset += list[i].length;
  63. }
  64. return data;
  65. }
  66. const encodeMono = (
  67. channels: number,
  68. sampleRate: number,
  69. samples: Int16Array
  70. ) => {
  71. const buffer: ArrayBuffer[] = [];
  72. const mp3enc: lamejs.Mp3Encoder = new lamejs.Mp3Encoder(
  73. channels,
  74. sampleRate,
  75. 128
  76. );
  77. let remaining = samples.length;
  78. const maxSamples = 1152;
  79. for (let i = 0; remaining >= maxSamples; i += maxSamples) {
  80. const mono = samples.subarray(i, i + maxSamples);
  81. const mp3buf = mp3enc.encodeBuffer(mono);
  82. if (mp3buf.length > 0) {
  83. buffer.push(mp3buf);
  84. }
  85. remaining -= maxSamples;
  86. }
  87. const d = mp3enc.flush();
  88. if (d.length > 0) {
  89. buffer.push(d);
  90. }
  91. return new Blob(buffer, { type: 'audio/mp3' });
  92. }
  93. const int16Array = floatArray2Int16(audio);
  94. const mp3Blob = encodeMono(1, 16000, int16Array);
  95. return mp3Blob
  96. }
  97. function writeString(view: DataView, offset: number, string: string) {
  98. for (let i = 0; i < string.length; i++) {
  99. view.setUint8(offset + i, string.charCodeAt(i));
  100. }
  101. }
  102. export const convertMp3ArrayBufferToWavArrayBuffer = async (mp3ArrayBuffer: ArrayBuffer): Promise<ArrayBuffer> => {
  103. const mp3Blob = new Blob([mp3ArrayBuffer], { type: 'audio/mp3' })
  104. const wavBlob = await convertMp3BlobToWavBlob(mp3Blob)
  105. return wavBlob.arrayBuffer()
  106. }
  107. export const convertMp3BlobToWavBlob = async (mp3Blob: Blob): Promise<Blob> => {
  108. // 将Blob对象转换为ArrayBuffer对象
  109. const arrayBuffer = await mp3Blob.arrayBuffer();
  110. // 创建AudioContext对象并解码音频数据
  111. const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
  112. const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
  113. // 获取音频数据的声道和采样率
  114. const numberOfChannels = audioBuffer.numberOfChannels;
  115. const sampleRate = audioBuffer.sampleRate;
  116. // 获取音频数据的每个声道的数据
  117. const channelData = [];
  118. for (let i = 0; i < numberOfChannels; i++) {
  119. const data = audioBuffer.getChannelData(i);
  120. // 将Float32Array转换为Int16Array
  121. const int16Data = new Int16Array(data.length);
  122. for (let j = 0; j < data.length; j++) {
  123. int16Data[j] = Math.round(data[j] * 32767);
  124. }
  125. channelData.push(int16Data);
  126. }
  127. // 构造WAV文件头
  128. const wavHeader = new ArrayBuffer(44);
  129. const view = new DataView(wavHeader);
  130. // RIFF头
  131. writeString(view, 0, 'RIFF'); // RIFF标记
  132. view.setUint32(4, 36 + channelData[0].length * 2 * numberOfChannels, true); // 文件大小(不包括RIFF头)
  133. writeString(view, 8, 'WAVE'); // WAVE标记
  134. // 格式块
  135. writeString(view, 12, 'fmt '); // 格式块标记
  136. view.setUint32(16, 16, true); // 格式块大小
  137. view.setUint16(20, 1, true); // 格式类型(1表示PCM)
  138. view.setUint16(22, numberOfChannels, true); // 声道数
  139. view.setUint32(24, sampleRate, true); // 采样率
  140. view.setUint32(28, sampleRate * 2 * numberOfChannels, true); // 字节率
  141. view.setUint16(32, 2 * numberOfChannels, true); // 块对齐
  142. view.setUint16(34, 16, true); // 位深度
  143. // 数据块
  144. writeString(view, 36, 'data'); // 数据块标记
  145. view.setUint32(40, channelData[0].length * 2 * numberOfChannels, true); // 数据大小
  146. // 合并所有音频数据
  147. const wavData = new Uint8Array(wavHeader.byteLength + channelData[0].length * 2 * numberOfChannels);
  148. new Uint8Array(wavHeader).forEach((byte, index) => {
  149. wavData[index] = byte;
  150. });
  151. let offset = wavHeader.byteLength;
  152. for (let i = 0; i < channelData[0].length; i++) {
  153. for (let j = 0; j < numberOfChannels; j++) {
  154. wavData[offset++] = channelData[j][i] & 0xFF;
  155. wavData[offset++] = (channelData[j][i] >> 8) & 0xFF;
  156. }
  157. }
  158. // 创建wav格式的Blob对象
  159. const wavBlob = new Blob([wavData], { type: 'audio/wav' });
  160. return wavBlob;
  161. }
  162. export class AudioRecoder {
  163. private _sampleRate: number;
  164. private _channleCount: number;
  165. private _chunkSize: number;
  166. private _audioContext: AudioContext | null = null;
  167. private _mediaStream: MediaStream | null = null;
  168. private _audioWorkletNode: AudioWorkletNode | null = null;
  169. private _audioBuffer: number[] = [];
  170. private _onFloat32AudioChunk?: (chunk: Float32Array) => void;
  171. private _onUint8AudioChunk?: (chunk: Uint8Array) => void;
  172. private _isPaused: boolean = false;
  173. constructor(
  174. sampleRate = 16000,
  175. channleCount = 1,
  176. chunkSize = 16000 / 1000 * 60 * 2, // 60ms数据(字节数, 一个frame 16位, 2个byte)
  177. onUint8AudioChunk?: (chunk: Uint8Array) => void,
  178. onFloat32AudioChunk?: (chunk: Float32Array) => void
  179. ) {
  180. this._sampleRate = sampleRate;
  181. this._channleCount = channleCount;
  182. this._chunkSize = chunkSize;
  183. this._onFloat32AudioChunk = onFloat32AudioChunk;
  184. this._onUint8AudioChunk = onUint8AudioChunk;
  185. }
  186. pause(): void {
  187. this._isPaused = true;
  188. // 清空音频缓冲区,避免恢复时播放旧数据
  189. this._audioBuffer = [];
  190. }
  191. resume(): void {
  192. this._isPaused = false;
  193. }
  194. isPaused(): boolean {
  195. return this._isPaused;
  196. }
  197. async start() {
  198. // 获取麦克风权限,优化音频质量设置
  199. this._mediaStream = await navigator.mediaDevices.getUserMedia({
  200. audio: {
  201. sampleRate: this._sampleRate,
  202. channelCount: this._channleCount,
  203. // 回声消除
  204. echoCancellation: true,
  205. // 噪声抑制
  206. noiseSuppression: true,
  207. // 自动增益控制
  208. autoGainControl: true
  209. }
  210. });
  211. // 创建AudioContext对象
  212. this._audioContext = new (window.AudioContext || (window as any).webkitAudioContext)({
  213. sampleRate: this._sampleRate
  214. });
  215. // 创建AudioWorkletNode对象
  216. await this._audioContext.audioWorklet.addModule(
  217. // URL.createObjectURL(new Blob([
  218. // `
  219. // class AudioProcessor extends AudioWorkletProcessor {
  220. // process(inputs, outputs, parameters) {
  221. // const input = inputs[0];
  222. // if (input && input[0]) {
  223. // // 将Float32Array转换为Int16Array
  224. // const float32Data = input[0];
  225. // const int16Data = new Int16Array(float32Data.length);
  226. // for (let i = 0; i < float32Data.length; i++) {
  227. // const sample = Math.max(-1, Math.min(1, float32Data[i]));
  228. // int16Data[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
  229. // }
  230. // this.port.postMessage(int16Data);
  231. // }
  232. // return true;
  233. // }
  234. // }
  235. // registerProcessor('audio-processor', AudioProcessor);
  236. // `
  237. // ], { type: 'application/javascript' }))
  238. URL.createObjectURL(new Blob([
  239. `
  240. class AudioProcessor extends AudioWorkletProcessor {
  241. process(inputs, outputs, parameters) {
  242. const input = inputs[0];
  243. if (input && input[0]) {
  244. const float32Data = input[0];
  245. this.port.postMessage(float32Data);
  246. }
  247. return true;
  248. }
  249. }
  250. registerProcessor('audio-processor', AudioProcessor);
  251. `
  252. ], { type: 'application/javascript' }))
  253. );
  254. const source = this._audioContext.createMediaStreamSource(this._mediaStream);
  255. this._audioWorkletNode = new AudioWorkletNode(this._audioContext, 'audio-processor');
  256. this._audioWorkletNode.port.onmessage = (event) => {
  257. this.processAudioData(event.data);
  258. }
  259. source.connect(this._audioWorkletNode);
  260. }
  261. stop(): void {
  262. if (this._audioWorkletNode) {
  263. this._audioWorkletNode.disconnect();
  264. this._audioWorkletNode = null;
  265. }
  266. if (this._mediaStream) {
  267. this._mediaStream.getTracks().forEach(track => track.stop());
  268. this._mediaStream = null;
  269. }
  270. if (this._audioContext) {
  271. this._audioContext.close();
  272. this._audioContext = null;
  273. }
  274. }
  275. private processAudioData(audioData: Float32Array): void {
  276. // 如果暂停,不处理音频数据
  277. if (this._isPaused) {
  278. return;
  279. }
  280. // 音频预处理:降噪和增强
  281. const processedAudio = this.preprocessAudioData(audioData);
  282. if (this._onFloat32AudioChunk) {
  283. this._onFloat32AudioChunk(processedAudio);
  284. }
  285. // float32转int16,使用更精确的转换
  286. const int16Data = new Int16Array(processedAudio.length);
  287. for (let i = 0; i < processedAudio.length; i++) {
  288. const sample = Math.max(-1, Math.min(1, processedAudio[i]));
  289. int16Data[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
  290. }
  291. // 将Int16Array转换为字节数组
  292. const bytes = new Uint8Array(int16Data.length * 2);
  293. for (let i = 0; i < int16Data.length; i++) {
  294. const sample = int16Data[i];
  295. bytes[i * 2] = sample & 0xFF; // 低字节
  296. bytes[i * 2 + 1] = (sample >> 8) & 0xFF; // 高字节
  297. }
  298. // 添加到缓冲区
  299. for (let i = 0; i < bytes.length; i++) {
  300. this._audioBuffer.push(bytes[i]);
  301. }
  302. // 如果缓冲区达到目标大小,发送音频块
  303. while (this._audioBuffer.length >= this._chunkSize) {
  304. const chunk = new Uint8Array(this._audioBuffer.splice(0, this._chunkSize));
  305. if (this._onUint8AudioChunk) {
  306. this._onUint8AudioChunk(chunk);
  307. }
  308. }
  309. }
  310. // 音频预处理方法
  311. private preprocessAudioData(audioData: Float32Array): Float32Array {
  312. const processed = new Float32Array(audioData.length);
  313. // 1. 音量标准化
  314. let maxAmplitude = 0;
  315. for (let i = 0; i < audioData.length; i++) {
  316. maxAmplitude = Math.max(maxAmplitude, Math.abs(audioData[i]));
  317. }
  318. if (maxAmplitude > 0) {
  319. const normalizationFactor = 0.8 / maxAmplitude;
  320. for (let i = 0; i < audioData.length; i++) {
  321. processed[i] = audioData[i] * normalizationFactor;
  322. }
  323. }
  324. // 2. 简单的高通滤波(去除低频噪音)
  325. const alpha = 0.95;
  326. for (let i = 1; i < processed.length; i++) {
  327. processed[i] = alpha * (processed[i] - processed[i - 1]) + processed[i - 1];
  328. }
  329. // 3. 动态范围压缩
  330. const threshold = 0.1;
  331. const ratio = 0.3;
  332. for (let i = 0; i < processed.length; i++) {
  333. const absValue = Math.abs(processed[i]);
  334. if (absValue > threshold) {
  335. const compressed = threshold + (absValue - threshold) * ratio;
  336. processed[i] = processed[i] > 0 ? compressed : -compressed;
  337. }
  338. }
  339. return processed;
  340. }
  341. }