You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 13:09:35