如何在wavesurfer.js中加载Python后端生成的Numpy音频(无文件写入)
问题原因
decodeAudioData API只能解码包含文件格式头的音频数据(如WAV、MP3),但你的后端返回的是裸PCM字节流(没有WAV文件头),浏览器无法识别并解码,因此抛出错误。
解决方案
提供两种可行方案,任选其一即可:
方案1:后端返回完整的WAV格式数据
修改后端代码,将PCM数据封装成标准WAV格式(包含文件头)后返回,前端可直接用decodeAudioData解码。
修改后的后端代码:
from flask import Flask, request, Response from flask_cors import CORS, cross_origin from scipy.io.wavfile import read import numpy as np import wave from io import BytesIO app = Flask(__name__) cors = CORS(app) app.config['CORS_HEADERS'] = 'Content-Type' DUMMY_AUDIO_PATH = "public/test.wav" # 移除重复的Flask实例创建代码 def audio_to_wav_bytes(sample_rate, audio_data): # 统一转换为int16格式 if audio_data.dtype != np.int16: if audio_data.dtype in ('float32', 'float64'): audio_data = (audio_data * np.iinfo(np.int16).max).astype(np.int16) else: audio_data = audio_data.astype(np.int16) # 在内存中生成WAV格式字节流 bytes_io = BytesIO() with wave.open(bytes_io, 'wb') as wav_file: # 根据音频数据设置WAV参数:声道数、采样宽度、采样率 wav_file.setnchannels(1 if len(audio_data.shape) == 1 else audio_data.shape[1]) wav_file.setsampwidth(2) wav_file.setframerate(sample_rate) wav_file.writeframes(audio_data.tobytes()) bytes_io.seek(0) return bytes_io.read() @app.route('/generate', methods=['POST']) @cross_origin() def generate(): duration = int(request.form.get('duration')) sample_rate, audio_data = read(DUMMY_AUDIO_PATH) # 移除不必要的int64转换 wav_bytes = audio_to_wav_bytes(sample_rate, audio_data) return Response(wav_bytes, content_type='audio/wav')
前端代码无需修改,保持原generateSample和to_ws函数逻辑即可。
方案2:前端直接处理裸PCM数据
跳过decodeAudioData,直接用已知的音频参数(采样率、声道数)创建AudioBuffer并填充裸PCM数据。
修改前端to_ws函数:
async function to_ws(pcmBuffer, instance) { const offlineAudioContext = instance.backend.ac; // 替换为你实际的音频参数,建议从后端额外返回该信息 const sampleRate = 44100; const channels = 1; // 立体声则设为2 // 创建对应参数的AudioBuffer const audioBuffer = offlineAudioContext.createBuffer(channels, pcmBuffer.byteLength / 2, sampleRate); // 将int16格式PCM转换为AudioBuffer要求的float32格式 const view = new Int16Array(pcmBuffer); for (let channel = 0; channel < channels; channel++) { const channelData = audioBuffer.getChannelData(channel); for (let i = 0; i < view.length / channels; i++) { // 把int16范围[-32768, 32767]映射到float32的[-1, 1] channelData[i] = view[i * channels + channel] / 32768; } } return audioBuffer; }
额外优化
- 建议后端在响应头或返回的JSON中携带采样率、声道数等参数,避免前端硬编码导致不匹配。
- 修复前端
insert_audio函数的数组越界问题:
// 原错误代码 var after_data = original_channel_data.subarray(Math.floor(position * originalAudioBuffer.sampleRate), (originalAudioBuffer.length * originalAudioBuffer.sampleRate)); // 修改为 var after_data = original_channel_data.subarray(Math.floor(position * originalAudioBuffer.sampleRate), originalAudioBuffer.length);
内容的提问来源于stack exchange,提问作者ysig
相关产品推荐
相关产品推荐

