音频采集(VUE3+JAVA)
- 互联网
- 2025-09-01 08:30:01

vue部分代码
xx.vue
import Recorder from './Recorder.js'; export default { data() { return { mediaStream: null, recorder: null, isRecording: false, audioChunks: [], vadInterval: null // 新增:用于存储声音活动检测的间隔 ID }; }, async mounted() { this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); this.startVAD(); }, beforeDestroy() { // 新增:组件销毁前清理声音活动检测的间隔 if (this.vadInterval) { cancelAnimationFrame(this.vadInterval); } }, created() { this.defaultLogin(); }, methods: { startVAD() { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const source = audioContext.createMediaStreamSource(this.mediaStream); const analyser = audioContext.createAnalyser(); source.connect(analyser); analyser.fftSize = 2048; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); const checkVoiceActivity = () => { analyser.getByteFrequencyData(dataArray); let sum = 0; for (let i = 0; i < bufferLength; i++) { sum += dataArray[i]; } const average = sum / bufferLength; if (average > 30 && !this.isRecording) { this.startRecording(); } else if (average < 10 && this.isRecording) { setTimeout(() => { analyser.getByteFrequencyData(dataArray); let newSum = 0; for (let i = 0; i < bufferLength; i++) { newSum += dataArray[i]; } const newAverage = newSum / bufferLength; if (newAverage < 10) { this.stopRecording(); } }, 500); } this.vadInterval = requestAnimationFrame(checkVoiceActivity); // 存储间隔 ID }; requestAnimationFrame(checkVoiceActivity); }, startRecording() { this.recorder = new Recorder(this.mediaStream); this.recorder.record(); this.isRecording = true; console.log('开始录制'); }, stopRecording() { if (this.recorder && this.isRecording) { this.recorder.stopAndExport((blob) => { const formData = new FormData(); formData.append('audioFile', blob, 'recorded-audio.opus'); }); this.isRecording = false; console.log('停止录制'); } } } };Recorder.js
class Recorder { constructor(stream) { const AudioContext = window.AudioContext || window.webkitAudioContext; try { this.audioContext = new AudioContext(); } catch (error) { console.error('创建 AudioContext 失败:', error); throw new Error('无法创建音频上下文,录音功能无法使用'); } this.stream = stream; this.mediaRecorder = new MediaRecorder(stream); this.audioChunks = []; this.mediaRecorder.addEventListener('dataavailable', (event) => { if (event.data.size > 0) { this.audioChunks.push(event.data); } }); this.mediaRecorder.addEventListener('stop', () => { console.log('录音停止,开始导出音频'); }); } record() { try { this.mediaRecorder.start(); } catch (error) { console.error('开始录音失败:', error); throw new Error('无法开始录音'); } } stop() { try { this.mediaRecorder.stop(); } catch (error) { console.error('停止录音失败:', error); throw new Error('无法停止录音'); } } exportWAV(callback) { try { const blob = new Blob(this.audioChunks, { type: 'audio/wav' }); console.log('生成的 Blob 的 MIME 类型:', blob.type); const reader = new FileReader(); reader.readAsArrayBuffer(blob); reader.onloadend = () => { const arrayBuffer = reader.result; }; callback(blob); this.audioChunks = []; } catch (error) { console.error('导出 WAV 格式失败:', error); throw new Error('无法导出 WAV 格式的音频'); } } stopAndExport(callback) { this.mediaRecorder.addEventListener('stop', () => { this.exportWAV(callback); }); this.stop(); } } export default Recorder; JAVA部分VoiceInputServiceImpl.java
package com.medical.asr.service.impl; import com.medical.asr.service.VoiceInputService; import com.medical mon.props.FileProps; import lombok.extern.slf4j.Slf4j; import org.springblade.core.tool.utils.StringPool; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @Service @Transactional(rollbackFor = Exception.class) @Slf4j public class VoiceInputServiceImpl implements VoiceInputService { @Autowired private FileProps fileProps; /** * 接收音频文件,并保存在目录下 * @param audioFile 音频文件 * @return 文件路径 */ private String receiveAudio(MultipartFile audioFile) { if (audioFile == null || audioFile.isEmpty()) { log.info("未收到音频文件"); return StringPool.EMPTY; } try { //文件存放的地址 String uploadDir = fileProps.getUploadPath(); System.out.println(uploadDir); File dir = new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } String fileName = System.currentTimeMillis() + "-" + audioFile.getOriginalFilename(); Path filePath = Paths.get(uploadDir, fileName); Files.write(filePath, audioFile.getBytes()); log.info("Received audio file: " + fileName); log.info("音频接收成功"); return filePath.toString(); } catch (IOException e) { log.error("保存音频文件时出错: " + e.getMessage()); return StringPool.EMPTY; } } }但是出了一个问题,就是这样生成的音频文件,通过ffmpeg查看发现是存在问题的,用来听没问题,但是要做加工,就不是合适的WAV文件。
人家需要满足这样的条件:
而我们这边出来的音频文件是这样的:
sample_rate(采样率), bits_per_sample(每个采样点所使用的位数 / 位深度), codec_name(编解码器名称), codec_long_name(编解码器完整名称 / 详细描述)这几项都不满足。于是查找opus转pcm的方案,修改之前的代码,新代码为:
private String receiveAudio(MultipartFile audioFile) { if (audioFile == null || audioFile.isEmpty()) { log.info("未收到音频文件"); return StringPool.EMPTY; } try { String uploadDir = fileProps.getUploadPath(); System.out.println(uploadDir); File dir = new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } String fileName = System.currentTimeMillis() + "-" + audioFile.getOriginalFilename(); Path filePath = Paths.get(uploadDir, fileName); Files.write(filePath, audioFile.getBytes()); log.info("Received audio file: " + fileName); log.info("音频接收成功"); // 转换音频格式和采样率 int dotIndex = fileName.lastIndexOf('.'); if (dotIndex!= -1) { fileName = fileName.substring(0, dotIndex); } String outputPath = Paths.get(uploadDir, "converted_" + fileName + ".wav").toString(); //新增的部分 convertAudio(filePath.toString(), outputPath); return outputPath; } catch (IOException e) { log.error("保存音频文件时出错: " + e.getMessage()); return StringPool.EMPTY; } } public static void convertAudio(String inputPath, String outputPath) { String ffmpegCommand = "ffmpeg -i " + inputPath + " -ar 16000 -ac 1 -acodec pcm_s16le " + outputPath; try { Process process = Runtime.getRuntime().exec(ffmpegCommand); // 读取进程的输出和错误流,以便及时发现问题 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = reader.readLine())!= null) { System.out.println(line); } reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); while ((line = reader.readLine())!= null) { System.out.println(line); } process.waitFor(); } catch (IOException | InterruptedException e) { log.error("转换音频时出错: " + e.getMessage()); e.printStackTrace(); } }这里面使用了ffmpeg的命令,如果当前环境没有ffmpeg,要记得先去下载安装ffmpeg,然后配置环境变量后再使用。
音频采集(VUE3+JAVA)由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“音频采集(VUE3+JAVA)”
下一篇
【二分搜索题目】