From de19681d9a193d86b940a2f20d3263940273d725 Mon Sep 17 00:00:00 2001 From: 董国庆 <364620639@qq.com> Date: 星期二, 20 五月 2025 19:17:58 +0800 Subject: [PATCH] 录音 --- H5/components/voiceInputPopup.vue | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 168 insertions(+), 11 deletions(-) diff --git a/H5/components/voiceInputPopup.vue b/H5/components/voiceInputPopup.vue index 18e50be..b0393ee 100644 --- a/H5/components/voiceInputPopup.vue +++ b/H5/components/voiceInputPopup.vue @@ -44,9 +44,9 @@ minutes: 0, hours: 0, recordSegments: [], // 存储录音片段 - currentSegment: null, // 当前录音片段 status: false, - recorder: null + recorder: null, + currentSegment: null // 当前录音片段 } }, computed: { @@ -79,32 +79,48 @@ } }, startNewRecording() { + console.log('开始新的录音') this.isRecording = true this.isPaused = false this.startTimer() this.$refs.mumuRecorder.start() }, pauseRecording() { + console.log('暂停录音') this.isRecording = false this.isPaused = true this.stopTimer() this.$refs.mumuRecorder.stop() }, resumeRecording() { + console.log('继续录音') this.isRecording = true this.isPaused = false this.startTimer() this.$refs.mumuRecorder.start() }, handlerSuccess(res) { - console.log('录音成功:', res) + console.log('录音成功回调:', res) this.recorder = res // 保存当前录音片段 if (res.localUrl) { - this.recordSegments.push({ - url: res.localUrl, - duration: this.seconds + this.minutes * 60 + this.hours * 3600 - }) + console.log('当前录音片段URL:', res.localUrl) + console.log('当前已存储的片段数:', this.recordSegments.length) + console.log('当前片段:', this.currentSegment) + + // 只有当当前片段与上一个不同时才添加 + if (!this.currentSegment || this.currentSegment.url !== res.localUrl) { + console.log('添加新的录音片段') + this.currentSegment = { + url: res.localUrl, + data: res.data, + duration: this.seconds + this.minutes * 60 + this.hours * 3600 + } + this.recordSegments.push(this.currentSegment) + console.log('更新后的片段列表:', this.recordSegments) + } else { + console.log('跳过重复的录音片段') + } } }, handlerError(code) { @@ -129,6 +145,7 @@ closePopup() { // 清空录音片段 this.recordSegments = [] + this.currentSegment = null // 重置计时器 this.seconds = 0 this.minutes = 0 @@ -161,7 +178,108 @@ updateTimeDisplay() { this.time = `${String(this.hours).padStart(2, '0')}:${String(this.minutes).padStart(2, '0')}:${String(this.seconds).padStart(2, '0')}`; }, - onStop() { + // 合并音频文件 + async mergeAudioFiles(audioUrls) { + try { + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const audioBuffers = []; + + // 加载所有音频文件 + for (const url of audioUrls) { + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + audioBuffers.push(audioBuffer); + } + + // 计算总时长 + const totalLength = audioBuffers.reduce((acc, buffer) => acc + buffer.length, 0); + + // 创建新的音频缓冲区 + const mergedBuffer = audioContext.createBuffer( + audioBuffers[0].numberOfChannels, + totalLength, + audioBuffers[0].sampleRate + ); + + // 合并音频数据 + let offset = 0; + for (const buffer of audioBuffers) { + for (let channel = 0; channel < buffer.numberOfChannels; channel++) { + const channelData = buffer.getChannelData(channel); + mergedBuffer.copyToChannel(channelData, channel, offset); + } + offset += buffer.length; + } + + // 将合并后的音频转换为 Blob + const wavBlob = await this.audioBufferToWav(mergedBuffer); + const mergedUrl = URL.createObjectURL(wavBlob); + + return mergedUrl; + } catch (error) { + console.error('合并音频失败:', error); + throw error; + } + }, + + // 将 AudioBuffer 转换为 WAV 格式 + audioBufferToWav(buffer) { + const numOfChan = buffer.numberOfChannels; + const length = buffer.length * numOfChan * 2; + const buffer2 = new ArrayBuffer(44 + length); + const view = new DataView(buffer2); + const channels = []; + let sample; + let offset = 0; + let pos = 0; + + // 写入 WAV 文件头 + setUint32(0x46464952); // "RIFF" + setUint32(36 + length); // 文件长度 + setUint32(0x45564157); // "WAVE" + setUint32(0x20746d66); // "fmt " chunk + setUint32(16); // 长度 = 16 + setUint16(1); // PCM (uncompressed) + setUint16(numOfChan); + setUint32(buffer.sampleRate); + setUint32(buffer.sampleRate * 2 * numOfChan); // avg. bytes/sec + setUint16(numOfChan * 2); // block-align + setUint16(16); // 16-bit + setUint32(0x61746164); // "data" - chunk + setUint32(length); // chunk length + + // 写入音频数据 + for (let i = 0; i < buffer.numberOfChannels; i++) { + channels.push(buffer.getChannelData(i)); + } + + while (pos < buffer.length) { + for (let i = 0; i < numOfChan; i++) { + sample = Math.max(-1, Math.min(1, channels[i][pos])); + sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; + view.setInt16(44 + offset, sample, true); + offset += 2; + } + pos++; + } + + return new Blob([buffer2], { type: 'audio/wav' }); + + function setUint16(data) { + view.setUint16(pos, data, true); + pos += 2; + } + + function setUint32(data) { + view.setUint32(pos, data, true); + pos += 4; + } + }, + + // 修改 onStop 方法 + async onStop() { + console.log('停止录音') // 如果正在录音,先停止当前录音 if (this.isRecording) { this.$refs.mumuRecorder.stop() @@ -173,12 +291,51 @@ // 重置状态 this.isRecording = false this.isPaused = false + this.currentSegment = null // 处理录音片段 if (this.recordSegments.length > 0) { - // 发送第一个录音片段的URL - this.$emit('submit', this.recordSegments[0].url) - this.closePopup() + try { + uni.showLoading({ + title: '正在处理音频...' + }) + + // 获取所有录音片段的URL + const audioUrls = this.recordSegments.map(segment => segment.url) + console.log('准备合并的音频片段列表:', audioUrls) + console.log('音频片段数量:', audioUrls.length) + + // 合并音频文件 + const mergedUrl = await this.mergeAudioFiles(audioUrls) + + // 创建合并后的音频数据对象 + const mergedAudioData = { + url: this.recordSegments[this.recordSegments.length - 1].url, + data: this.recordSegments[this.recordSegments.length - 1].data, + duration: this.seconds + this.minutes * 60 + this.hours * 3600 + } + + uni.showToast({ + title: '录制成功', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + this.$emit('submit', mergedAudioData) + }, 2000) + + uni.hideLoading() + this.closePopup() + } catch (error) { + console.error('音频合并失败:', error) + uni.hideLoading() + uni.showToast({ + title: '音频合并失败,请重试', + icon: 'none', + duration: 2000 + }) + } } else { uni.showToast({ title: '未检测到录音文件', -- Gitblit v1.7.1