在Flask Python中实现实时语音可视化与语音转文本的技术求助
在Flask Python中实现实时语音可视化与语音转文本的技术求助
Hey there! 看起来你已经搞定了语音转文本和准确率计算的核心功能,现在要加实时语音可视化对吧?这个功能得靠前端Web Audio API和后端配合来实现,因为实时的音频数据处理得在浏览器端做,后端负责后续的文本识别。我给你梳理下修改方案,一步步来:
一、核心思路说明
你当前的实现是点击按钮后后端一次性录音并识别,这没法做实时可视化。我们要改成:
- 前端用Web Audio API获取麦克风的实时音频流,同时绘制可视化波形/频谱
- 用Recorder.js录制完整音频,结束后把音频文件异步发送给后端
- 后端接收音频文件后,进行语音识别和准确率计算,再返回结果给前端展示
二、修改前端(record.html)
更新你的record.html,添加Canvas用于可视化,并用JavaScript处理录音、可视化和异步提交:
<!doctype html> {% extends 'index.html' %} {% block title %} Rhyme {% endblock %} {% block content %} <div> <!-- 可视化画布 --> <canvas id="audioCanvas" width="600" height="200" style="border:1px solid #ccc;"></canvas> <!-- 控制按钮 --> <button id="startBtn">开始录音</button> <button id="stopBtn" disabled>停止录音</button> <!-- 结果展示区域 --> <div id="resultArea"></div> </div> <script> // 获取DOM元素 const canvas = document.getElementById('audioCanvas'); const ctx = canvas.getContext('2d'); const startBtn = document.getElementById('startBtn'); const stopBtn = document.getElementById('stopBtn'); const resultArea = document.getElementById('resultArea'); // 音频相关变量 let audioContext; let analyser; let mediaStream; let recorder; let animationId; // 初始化音频上下文和分析器 async function initAudio() { audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); analyser.fftSize = 256; // 控制频谱精度,数值越小波形越宽 const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); // 实时绘制波形函数 function drawWave() { animationId = requestAnimationFrame(drawWave); analyser.getByteTimeDomainData(dataArray); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 2; ctx.strokeStyle = '#007bff'; ctx.beginPath(); const sliceWidth = canvas.width / bufferLength; let x = 0; for(let i = 0; i < bufferLength; i++) { const v = dataArray[i] / 128.0; const y = v * canvas.height / 2; if(i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } x += sliceWidth; } ctx.lineTo(canvas.width, canvas.height / 2); ctx.stroke(); } // 获取麦克风流 mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); const source = audioContext.createMediaStreamSource(mediaStream); source.connect(analyser); // 开始绘制 drawWave(); } // 开始录音 startBtn.addEventListener('click', async () => { await initAudio(); // 初始化Recorder.js(确保你已经把recorder.js放在static/js下) recorder = new Recorder(audioContext.createMediaStreamSource(mediaStream), { numChannels: 1 }); recorder.record(); startBtn.disabled = true; stopBtn.disabled = false; }); // 停止录音并提交到后端 stopBtn.addEventListener('click', async () => { recorder.stop(); cancelAnimationFrame(animationId); // 停止绘制 mediaStream.getTracks().forEach(track => track.stop()); // 关闭麦克风 audioContext.close(); // 转换为Blob并发送 recorder.exportWAV(async (blob) => { const formData = new FormData(); formData.append('audio', blob, 'recording.wav'); formData.append('csrf_token', '{{ csrf_token() }}'); // 带上CSRF令牌 try { const response = await fetch('/record', { method: 'POST', body: formData }); const result = await response.json(); // 展示结果 if(result.success) { resultArea.innerHTML = ` <p>This is what you said: ${result.sung}</p> <p>Accuracy: ${result.accuracy.toFixed(2)}</p> `; } else { resultArea.innerHTML = `<p>${result.error}</p>`; } } catch(err) { resultArea.innerHTML = `<p>提交失败,请重试</p>`; console.error(err); } finally { startBtn.disabled = false; stopBtn.disabled = true; } }); }); </script> {% endblock %}
三、修改后端(app.py)
更新你的/record路由,支持接收音频文件,并返回JSON格式的结果:
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user, current_user from datetime import timedelta from flask_wtf import CSRFProtect import speech_recognition as sr from difflib import SequenceMatcher import io app = Flask(__name__) csrf = CSRFProtect(app) app.config.update( DEBUG = True, SECRET_KEY = 'SECRETKEY' ) login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = "login" login_manager.login_message = u"Login to access this page." class User(UserMixin): def __init__(self, userId): self.userId = userId def get_id(self): return self.userId def similar(a, b): return SequenceMatcher(None, a, b).ratio() # 假设你原来的format_rhyme函数是这样的(用户代码里没实现,这里补个示例) def format_rhyme(text): return text.strip().lower() @app.route('/record', methods=['GET', 'POST']) @login_required def record(): if request.method == 'POST': # 检查是否有音频文件上传 if 'audio' not in request.files: return jsonify({'success': False, 'error': 'No audio file provided'}) audio_file = request.files['audio'] if audio_file.filename == '': return jsonify({'success': False, 'error': 'No selected file'}) # 用SpeechRecognition处理音频文件 r = sr.Recognizer() try: # 把文件转成BytesIO给recognizer处理 with sr.AudioFile(io.BytesIO(audio_file.read())) as source: audio = r.record(source) text = r.recognize_google(audio) original = format_rhyme("This is an apple") sung = format_rhyme(text) accuracy = similar(original, sung) return jsonify({'success': True, 'sung': sung, 'accuracy': accuracy}) except sr.UnknownValueError: return jsonify({'success': False, 'error': 'Sorry could not recognize what you said'}) except Exception as e: return jsonify({'success': False, 'error': f'Error: {str(e)}'}) # GET请求返回页面 return render_template("record.html") # 这里要补上你的login、logout等路由,用户原来的代码里没贴全,记得保留 @login_manager.user_loader def load_user(user_id): return User(user_id) if __name__ == '__main__': app.run(debug=True)
四、注意事项
- 确保你已经把
recorder.js放在static/js目录下(它是基于Web Audio API的录音工具,你可以用标准的开源实现) - 浏览器需要HTTPS或者localhost才能访问麦克风(开发环境用localhost没问题)
- 可以调整
analyser.fftSize的值来改变可视化的精度,比如512、1024,数值越大波形越细腻 - 如果想要更炫酷的频谱图可视化,可以把
getByteTimeDomainData换成getByteFrequencyData,然后修改绘制逻辑
这样修改后,用户点击开始录音就能看到实时的波形可视化,停止录音后后端会处理语音转文本并返回准确率,完美结合你要的两个功能!
备注:内容来源于stack exchange,提问作者J Prem
相关产品推荐
相关产品推荐

