React中实时绘制音频波形时数据不连贯怎么办?

UE丶春萍 阅读 112

在用React和Web Audio API画音频波形时,上传文件后波形显示总是断断续续的,调整过bufferSize也不行。比如这段代码:


function Waveform({ file }) {
  const canvasRef = useRef();
  useEffect(() => {
    const ctx = canvasRef.current.getContext('2d');
    const audioCtx = new AudioContext();
    const source = audioCtx.createMediaElementSource(file);
    const analyser = audioCtx.createAnalyser();
    source.connect(analyser).connect(audioCtx.destination);
    analyser.fftSize = 2048;

    function draw() {
      const data = new Uint8Array(analyser.frequencyBinCount);
      analyser.getByteTimeDomainData(data);
      ctx.clearRect(0, 0, 512, 200);
      // 这里绘制逻辑可能有问题
      requestAnimationFrame(draw);
    }
    draw();
  }, [file]);
  return ;
}

试过把fftSize改小反而更卡,而且波形在播放时会突然跳帧。是不是时间戳没同步?或者canvas绘制的频率设置不对?

我来解答 赞 11 收藏
二维码
手机扫码查看
2 条解答
萌新.新艳
我之前踩过这个坑,波形不连贯的原因主要有两个:一个是绘制逻辑有问题,另一个是音频上下文的时间管理没做好。

你现在的代码里有几个地方需要调整。首先,requestAnimationFrame确实适合做动画更新,但你要确保每次绘制时把波形数据正确映射到canvas上。其次,fftSize设置为2048是可以的,不用改得太小,这样能保证频率分辨率。

关键是你的绘制逻辑。你可以试试下面的代码:

function Waveform({ file }) {
const canvasRef = useRef();
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
const audioCtx = new AudioContext();
const source = audioCtx.createMediaElementSource(new Audio(URL.createObjectURL(file)));
const analyser = audioCtx.createAnalyser();
source.connect(analyser).connect(audioCtx.destination);
analyser.fftSize = 2048;

const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

function draw() {
requestAnimationFrame(draw);
analyser.getByteTimeDomainData(dataArray);

ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(0, 0, 0)';
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();
}

draw();

return () => {
audioCtx.close();
};
}, [file]);

return <canvas ref={canvasRef} width={512} height={200} />;
}


注意几个点:
1. 绘制时要根据dataArray的值计算出正确的坐标。
2. 每次清空canvas后重新绘制整个波形。
3. 最后记得在组件卸载时关闭AudioContext,不然会有内存泄漏。

按照这个写法,波形应该就能平滑显示了。如果还有问题,可能是浏览器性能或者音频文件本身的问题,那就得另当别论了。
点赞 7
2026-02-02 11:07
设计师利娟
你这绘制逻辑确实有点问题,主要是没把波形数据正确映射到canvas上。试试下面这段改过的代码,应该能用:

function Waveform({ file }) {
const canvasRef = useRef();
useEffect(() => {
const ctx = canvasRef.current.getContext('2d');
const audioCtx = new AudioContext();
const source = audioCtx.createMediaElementSource(file);
const analyser = audioCtx.createAnalyser();
source.connect(analyser).connect(audioCtx.destination);
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

function draw() {
analyser.getByteTimeDomainData(dataArray);
ctx.clearRect(0, 0, 512, 200);
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(0, 0, 0)';
ctx.beginPath();

const sliceWidth = 512 / bufferLength;
let x = 0;

for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * 100 + 100;

if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);

x += sliceWidth;
}

ctx.stroke();
requestAnimationFrame(draw);
}
draw();
}, [file]);
return <canvas ref={canvasRef} width={512} height={200} />;
}
点赞 8
2026-01-29 17:16