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

Next.js集成ffmpeg-wasm时随机出现「内存访问越界」运行时错误的问题求助

Next.js集成ffmpeg-wasm时随机出现「内存访问越界」运行时错误的问题求助

各位开发者好,我最近在Next.js项目里用ffmpeg-wasm做图片格式转换,遇到了一个特别诡异的随机错误,卡壳好几天了,想请教下有没有人碰到过类似情况或者有排查思路。

问题详情

我做了个简单的页面:通过文件选择框上传图片,点击按钮后用ffmpeg把每张图转成JPEG格式,核心执行命令是:

await ffmpeg.exec(['-i', originalFileName, '-q:v', '5', convertedFileName]);

但每次操作都有大概50%的概率成功,50%的概率直接抛出**「memory access out of bounds」**的运行时错误,完全没规律可言。

我已经做了各种测试,结果都一样:

  • 换过不同格式、大小的图片,甚至用纯色小图测试
  • 重启应用、重启电脑,换Chrome/Firefox/Edge,连移动端浏览器也试过
  • 换输出格式(比如转PNG、WebP),甚至换ffmpeg命令做其他操作(比如查询媒体信息)
  • 确认过虚拟文件系统(VFS)的文件写入是正常的,原始图片能正确写入VFS

环境信息

  • 系统:Windows 10
  • Next.js版本:15.3.5(客户端组件,带'use client'指令)
  • ffmpeg相关依赖:@ffmpeg/ffmpeg@0.12.10,@ffmpeg/util对应版本

完整组件代码

'use client';
import * as React from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';

export default function Page() {
  const [files, setFiles] = React.useState<File[]>([]);
  const [error, setError] = React.useState<string | null>(null);

  // FFmpeg 相关状态与引用
  const ffmpegRef = React.useRef<FFmpeg | null>(null);
  const [ffmpegLoaded, setFFmpegLoaded] = React.useState(false);
  const [isConvertingImages, setIsConvertingImages] = React.useState(false);
  const [videoGenerationProgress, setVideoGenerationProgress] = React.useState<string>('');

  // 组件挂载时加载FFmpeg
  React.useEffect(() => {
    const loadFFmpeg = async () => {
      const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.10/dist/umd";
      try {
        const ffmpeg = new FFmpeg();
        // 监听FFmpeg日志
        ffmpeg.on('log', ({ message }) => {
          if (message.includes('frame=')) {
            setVideoGenerationProgress(message);
          }
        });
        await ffmpeg.load({
          coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
          wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
        });
        ffmpegRef.current = ffmpeg;
        setFFmpegLoaded(true);
        console.log('FFmpeg loaded successfully!');
      } catch (err) {
        console.error('Failed to load FFmpeg:', err);
        setError('Failed to load video processing tools.');
      }
    };
    loadFFmpeg();
  }, []);

  // 文件选择处理
  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files && e.target.files.length > 0) {
      const newFiles = Array.from(e.target.files).filter(file => file.type.startsWith('image/'));
      setFiles(newFiles);
      setError(null);
    }
  };

  // 图片转换逻辑
  const handleConvertImages = async () => {
    if (!ffmpegLoaded || !ffmpegRef.current) {
      setError('FFmpeg is not loaded yet. Please wait.');
      return;
    }
    if (files.length === 0) {
      setError('Please select images first to generate a video.');
      return;
    }

    setIsConvertingImages(true);
    setError(null);
    setVideoGenerationProgress('');

    try {
      const ffmpeg = ffmpegRef.current;
      const targetExtension = 'jpeg';
      const convertedImageNames: string[] = [];

      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const originalFileName = `original_image_${String(i).padStart(3, '0')}.${file.name.split('.').pop()}`;
        const convertedFileName = `converted_image_${String(i).padStart(3, '0')}.${targetExtension}`;
        convertedImageNames.push(convertedFileName);

        // 写入文件到VFS
        await ffmpeg.writeFile(`${originalFileName}`, await fetchFile(file));
        console.log(`Wrote original ${originalFileName} to FFmpeg FS`);
        setVideoGenerationProgress(`Converting ${file.name} to ${targetExtension.toUpperCase()}...`);

        // 执行转换命令(随机出错的核心代码)
        await ffmpeg.exec(['-i', originalFileName, '-q:v', '5', convertedFileName]);
        console.log(`Converted ${originalFileName} to ${convertedFileName}`);

        // 删除原始文件释放内存
        await ffmpeg.deleteFile(originalFileName);
        console.log(`Deleted original ${originalFileName} from FFmpeg FS`);
      }

      setFiles([]);
      setVideoGenerationProgress(`All images converted to ${targetExtension.toUpperCase()}.`);
    } catch (err) {
      console.error('Error converting images:', err);
      setError(`Failed to convert images: ${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setIsConvertingImages(false);
    }
  };

  return (
    <div className="min-h-screen bg-gray-100 flex items-center justify-center p-4 relative overflow-hidden">
      <div className="bg-white p-8 rounded-lg shadow-xl w-full max-w-2xl">
        <h1 className="text-2xl font-bold mb-6">Image Converter</h1>
        <input
          type="file"
          multiple
          accept="image/*"
          onChange={handleFileChange}
          disabled={isConvertingImages}
          className="mb-4 block w-full p-2 border rounded"
        />
        {error && <p className="text-red-500 mb-4">{error}</p>}
        <button
          onClick={handleConvertImages}
          disabled={!ffmpegLoaded || isConvertingImages || files.length === 0}
          className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
        >
          {isConvertingImages ? 'Converting...' : 'Convert Images'}
        </button>
        {videoGenerationProgress && <p className="mt-4 text-gray-600">{videoGenerationProgress}</p>}
      </div>
    </div>
  );
}

我现在怀疑是不是WebAssembly的内存管理和Next.js客户端组件的生命周期有冲突?或者ffmpeg-wasm在Windows环境下有什么没被发现的随机内存问题?有没有人有类似的排查经验,或者能给点方向参考?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:04:33