Next.js浏览器端用FFmpeg合成音视频时遇WebAssembly链接错误求助
浏览器端Next.js项目中FFmpeg WebAssembly加载错误解决
问题场景
在Next.js开发的网页应用中,需在浏览器端将三张图片与一段音频合成为视频,要求每张图片展示时长均等且总时长与音频长度匹配。实现代码如下:
import {FFmpeg} from '@ffmpeg/ffmpeg'; import { fetchFile, toBlobURL } from '@ffmpeg/util'; export async function createVideo(ImageFiles, audioFile) { try { const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.4/dist/umd'; const ffmpeg = new FFmpeg({ log: true}); console.log('Loading ffmpeg core'); await ffmpeg.load({ corePath: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), wasmPath: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), }); await ffmpeg.load(); console.log('Finished loading ffmpeg core'); for (let i = 0; i < ImageFiles.length; i++) { ffmpeg.writeFile( `image${i+1}.jpg`, await fetchFile(ImageFiles[i].imageUrl) ); } ffmpeg.FS('writeFile', 'audio.mp3', await fetchFile(audioFile)); const durationPerImage = (await getAudioDuration(ffmpeg, 'audio.mp3')) / ImageFiles.length; let filterComplex = ''; for (let i = 0; i < ImageFiles.length - 1; i++) {filterComplex += `[${i}:v]trim=duration=${durationPerImage},setpts=PTS-STARTPTS[v${i}]; `; } filterComplex += `${ImageFiles.slice(0, -1).map((_, i) => `[v${i}]`).join('')}concat=n=${ImageFiles.length - 1}:v=1:a=0,format=yuv420p[v];`; await ffmpeg.run( '-framerate', '1', '-loop', '1', '-t', durationPerImage, '-i', 'image%d.jpg', '-i', 'audio.mp3', '-filter_complex', filterComplex, '-map', '[v]', '-map', '1:a', '-c:v', 'libx264', '-tune', 'stillimage', '-c:a', 'aac', '-b:a', '192k', 'output.mp4' ); const data = ffmpeg.FS('readFile', 'output.mp4'); const videoURL = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })); return videoURL; } catch (error) { console.error('Error creating video:', error); throw new Error('Failed to create video'); } } async function getAudioDuration(ffmpeg, audioFilename) { await ffmpeg.run('-i', audioFilename, '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', 'duration.txt'); const data = ffmpeg.FS('readFile', 'duration.txt'); const durationString = new TextDecoder().decode(data); const duration = Math.floor(parseFloat(durationString.trim())); return duration; }
运行时出现如下错误:
CreateVideo.js:65 Error creating video: RuntimeError: Aborted(LinkError: WebAssembly.instantiate(): Import #70 module="a" function="qa": function import requires a callable). Build with -sASSERTIONS for more info.
解决方法
1. 移除重复的FFmpeg加载调用
代码中连续执行了两次await ffmpeg.load(),第一次调用已传入核心文件路径完成初始化,第二次重复调用会导致WebAssembly实例冲突,引发导入错误。只需保留第一次加载调用即可。
2. 统一FFmpeg核心版本
确保@ffmpeg/ffmpeg与@ffmpeg/core版本完全匹配,版本不兼容会导致WebAssembly导入失败。建议升级到兼容的稳定版本(例如@ffmpeg/core@0.12.7,需对应@ffmpeg/ffmpeg@0.12.7)。
3. 优化音频时长获取逻辑
原代码通过生成临时文件读取时长的方式效率低且易引发文件冲突,改用ffmpeg.probe方法直接获取音频元数据,无需额外生成文件。
4. 修正滤镜链逻辑
原滤镜链仅处理前n-1张图片,导致最后一张图片未被加入视频,修正后需处理所有图片并拼接完整视频流。
5. 新增时长对齐参数
添加-shortest参数确保视频时长严格匹配音频时长,避免计算误差导致的时长不一致。
修正后的完整代码
import { FFmpeg } from '@ffmpeg/ffmpeg'; import { fetchFile, toBlobURL } from '@ffmpeg/util'; export async function createVideo(ImageFiles, audioFile) { try { // 确保core版本与@ffmpeg/ffmpeg版本一致,此处以0.12.7为例 const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.7/dist/umd'; const ffmpeg = new FFmpeg({ log: true }); console.log('Loading ffmpeg core'); // 仅调用一次load完成初始化 await ffmpeg.load({ corePath: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), wasmPath: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), }); console.log('Finished loading ffmpeg core'); // 写入所有图片文件 for (let i = 0; i < ImageFiles.length; i++) { await ffmpeg.writeFile( `image${i + 1}.jpg`, await fetchFile(ImageFiles[i].imageUrl) ); } // 写入音频文件 await ffmpeg.writeFile('audio.mp3', await fetchFile(audioFile)); // 通过probe直接获取音频时长,无需临时文件 const probeResult = await ffmpeg.probe('audio.mp3'); const audioDuration = parseFloat(probeResult.format.duration); const durationPerImage = audioDuration / ImageFiles.length; // 构建包含所有图片的滤镜链 let filterComplex = ''; for (let i = 0; i < ImageFiles.length; i++) { filterComplex += `[${i}:v]trim=duration=${durationPerImage},setpts=PTS-STARTPTS[v${i}]; `; } // 拼接所有处理后的视频流 const videoStreams = ImageFiles.map((_, i) => `[v${i}]`).join(''); filterComplex += `${videoStreams}concat=n=${ImageFiles.length}:v=1:a=0,format=yuv420p[v];`; // 执行FFmpeg命令,添加-shortest确保时长对齐 await ffmpeg.run( '-framerate', '1', '-loop', '1', '-t', durationPerImage.toString(), '-i', 'image%d.jpg', '-i', 'audio.mp3', '-filter_complex', filterComplex, '-map', '[v]', '-map', '1:a', '-c:v', 'libx264', '-tune', 'stillimage', '-c:a', 'aac', '-b:a', '192k', '-shortest', 'output.mp4' ); // 生成视频URL const data = ffmpeg.FS('readFile', 'output.mp4'); const videoURL = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })); // 清理临时文件,释放内存 await ffmpeg.FS('unlink', 'audio.mp3'); ImageFiles.forEach((_, i) => ffmpeg.FS('unlink', `image${i + 1}.jpg`)); ffmpeg.FS('unlink', 'output.mp4'); return videoURL; } catch (error) { console.error('Error creating video:', error); throw new Error('Failed to create video'); } }
内容的提问来源于stack exchange,提问作者Anil
相关产品推荐
相关产品推荐

