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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 01:37:34