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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 03:42:08