Firebase Function用ffmpeg转码模拟器正常,部署后内存溢出
问题
Firebase Storage 中有一个时长25秒、大小106 MB的.mov文件,我编写了一个Firebase可调用函数,通过ffmpeg将其转换为.mp4格式后存回Storage。本地使用Functions模拟器测试时完全正常,转换后的6MB视频可正常播放,但部署到生产环境后,处理同一文件就触发内存溢出错误:
Memory limit of 256 MiB exceeded with 407 MiB used. Consider increasing the memory limit...
将函数内存配额调整到1GiB后,依然报错:
Memory limit of 1024 MiB exceeded with 1029 MiB used.
函数代码如下:
const {initializeApp} = require("firebase-admin/app"); const {onCall} = require("firebase-functions/v2/https"); const { getStorage, getDownloadURL } = require('firebase-admin/storage'); initializeApp(); exports.convertVideo = onCall((request) => { const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path; const ffmpeg = require('fluent-ffmpeg'); ffmpeg.setFfmpegPath(ffmpegPath); const originalLocation = request.data.originalLocation; const convertedLocation = request.data.convertedLocation; const originalVideoFile = getStorage().bucket().file(originalLocation); const newVideoFile = getStorage().bucket().file(convertedLocation); return new Promise(async (resolve, reject) => { await originalVideoFile.download({destination: '/tmp/original'}).catch(console.error); ffmpeg('/tmp/original') .addOutputOptions('-movflags +frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov') .format('mp4') .on('error', (err) => { console.log(err); }) .pipe(newVideoFile.createWriteStream()) .on('error', (err) => { console.log(err); }) .on('close', async () => { fs.unlink('/tmp/original', (err) => { if (err) throw err; }); const convertedUrl = await getDownloadURL(newVideoFile); resolve([convertedLocation, convertedUrl]); }); }); });
本地测试使用的curl请求:
curl -d '{"data": {"originalLocation": "customer_videos/original_video.mov", "convertedLocation": "customer_videos/converted/original_video.mp4"}}' -H "Content-Type: application/json" http://127.0.0.1:5001/foo/bar/convertVideo
该请求在本地正常运行,但部署到生产环境后执行相同请求就会触发内存溢出,想了解问题根源。
分析与解决办法
1. 本地与生产环境的核心差异
本地模拟器不受Firebase Functions的内存配额限制,ffmpeg可以自由使用本机系统内存;但生产环境中函数的内存是严格受控的配额资源。ffmpeg处理高码率.mov文件时,解码过程会占用大量内存用于帧缓存、解码缓冲区,生产环境的配额不足以支撑这个开销。
2. 当前代码的内存优化点
- 避免完整下载文件:目前代码将106MB的文件完整下载到/tmp目录,下载过程中数据会先进入内存再写入磁盘,额外占用内存。可以直接用Storage文件的可读流传给ffmpeg,跳过本地下载步骤。
- ffmpeg参数优化:添加内存控制参数,比如
-threads 1(限制线程数,减少并发内存占用)、-preset ultrafast(使用快速编码预设,降低计算和内存开销)、-bufsize 1M(限制码率缓冲区大小)。 - 修复代码缺陷:代码未引入
fs模块,fs.unlink会触发错误导致临时文件无法删除;同时下载失败仅打印错误未终止流程,可能引发无效内存消耗。
优化后的代码示例
const {initializeApp} = require("firebase-admin/app"); const {onCall} = require("firebase-functions/v2/https"); const { getStorage, getDownloadURL } = require('firebase-admin/storage'); const fs = require('fs'); // 补充引入fs模块 initializeApp(); exports.convertVideo = onCall(async (request) => { const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path; const ffmpeg = require('fluent-ffmpeg'); ffmpeg.setFfmpegPath(ffmpegPath); const originalLocation = request.data.originalLocation; const convertedLocation = request.data.convertedLocation; const originalVideoFile = getStorage().bucket().file(originalLocation); const newVideoFile = getStorage().bucket().file(convertedLocation); return new Promise((resolve, reject) => { // 使用可读流直接传入ffmpeg,无需完整下载文件 const readStream = originalVideoFile.createReadStream(); ffmpeg(readStream) .addOutputOptions('-movflags +frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov') .addOutputOptions('-threads 1') // 限制线程数减少内存占用 .addOutputOptions('-preset ultrafast') // 快速编码预设 .addOutputOptions('-bufsize 1M') // 限制码率缓冲区 .format('mp4') .on('error', (err) => { console.error('FFmpeg转码错误:', err); readStream.destroy(); // 销毁可读流释放资源 reject(err); }) .pipe(newVideoFile.createWriteStream()) .on('error', (err) => { console.error('Storage写入错误:', err); readStream.destroy(); reject(err); }) .on('close', async () => { const convertedUrl = await getDownloadURL(newVideoFile); resolve([convertedLocation, convertedUrl]); }); }); });
3. 其他建议
- 如果优化后仍内存不足,可以尝试将函数内存调整到2GiB(Firebase Functions最高支持8GiB),但需注意更高内存配额会带来更高的运行成本。
- 考虑使用Google Cloud Transcoder API替代自定义ffmpeg函数,专业转码服务的资源管理更高效,无需手动配置内存和ffmpeg参数。
内容的提问来源于stack exchange,提问作者flyingL123
相关产品推荐
相关产品推荐

