FFmpeg在AWS Lambda压缩大视频时输出截断、0字节问题求助
问题根因
- 大文件截断核心原因:第一版代码通过
subprocess.PIPE把FFmpeg的输出全存在内存里,3008MB的Lambda内存扣除运行时、依赖、FFmpeg进程本身占用后,留给管道缓存的空间不足,输出视频流超过剩余内存阈值就会被强制截断,加-nostdin参数解决不了内存上限问题。 - 移除
-f mpegts就出0字节文件的原因:MP4容器编码完成后需要回写moov元数据头到文件开头,流式管道不支持随机写入,FFmpeg没法完成回写操作,最终输出的就是无有效数据的损坏文件;mpegts是面向流式传输设计的容器,不需要回写文件头,所以管道输出能拿到可读内容,但依然受内存缓存限制。 - 第二版代码提示
output.mp4不存在的原因:直接把S3对象key拼接到shell命令字符串里用os.system调用,key里带的空格、特殊符号会导致shell解析命令失败,FFmpeg根本没有正常启动;同时你没有打印FFmpeg的运行日志,看不到命令执行失败的报错。 - 日志无报错的原因:代码没有捕获、打印FFmpeg执行时的stderr输出,所有转码过程的错误信息都存在进程内存里没输出到CloudWatch,自然只能看到函数启动、结束的基础日志。
- 第一版代码的FFmpeg命令还存在参数错误:漏写了视频编码器指定参数
-c:v,直接写libx264会被FFmpeg识别为输入路径,导致编码逻辑异常。
修复步骤
- 放弃用内存管道接FFmpeg输出的方案,直接读写Lambda的
/tmp临时目录存储输入、输出文件;当前2048MB临时存储可支持处理单文件总大小(输入+输出)不超过2GB的场景,处理更大文件时直接在Lambda配置中将临时存储上限调整到10240MB即可。 - 所有外部命令调用统一用
subprocess.run传参数数组,不要用字符串拼接+os.system的方式,避免特殊字符转义失败。 - 必须在代码中打印FFmpeg的stderr输出到CloudWatch,转码后检查进程返回码,非0返回码直接抛错,避免静默失败。
- 将Lambda函数超时时间调整到15分钟(Lambda单实例最长执行时长),内存配置拉到10240MB时会分配满额vCPU,转码速度比3008MB快2-3倍,可大幅降低超时概率。
- 输出文件上传S3成功后再删除源文件,避免转码失败导致源文件丢失;每次执行结束清理
/tmp下的临时文件,避免残留文件占满磁盘空间。
修正后可直接运行的代码
import json import os import subprocess import boto3 S3_DESTINATION_BUCKET = "rw-video-out" os.chdir('/tmp') def lambda_handler(event, context): s3_source_bucket = event['Records'][0]['s3']['bucket']['name'] s3_source_key = event['Records'][0]['s3']['object']['key'] s3_source_basename = os.path.splitext(os.path.basename(s3_source_key))[0] local_input_path = f"/tmp/input_{context.aws_request_id}" local_output_path = f"/tmp/output_{context.aws_request_id}.mp4" s3_destination_key = f"{s3_source_basename}-comp.mp4" s3_client = boto3.client('s3') # 下载源文件到本地临时存储 s3_client.download_file(s3_source_bucket, s3_source_key, local_input_path) # 构造FFmpeg参数数组,避免shell转义问题 ffmpeg_cmd = [ "/opt/bin/ffmpeg", "-y", "-i", local_input_path, "-c:v", "libx264", "-preset", "fast", "-crf", "28", "-c:a", "copy", "-movflags", "+faststart", local_output_path ] # 执行转码,捕获输出 proc = subprocess.run( ffmpeg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False ) # 打印FFmpeg日志到CloudWatch print("FFmpeg run log:", proc.stderr.decode('utf-8', errors='ignore')) # 校验转码结果 if proc.returncode != 0: raise RuntimeError(f"FFmpeg transcode failed, exit code: {proc.returncode}") if not os.path.exists(local_output_path) or os.path.getsize(local_output_path) == 0: raise RuntimeError("Transcode output file is invalid") # 上传结果到目标桶 s3_client.upload_file(local_output_path, S3_DESTINATION_BUCKET, s3_destination_key) # 清理临时文件 for f in [local_input_path, local_output_path]: if os.path.exists(f): os.remove(f) # 确认上传成功后删除源文件 s3_client.delete_object(Bucket=s3_source_bucket, Key=s3_source_key) return { 'statusCode': 200, 'body': json.dumps('Processing complete successfully') }
额外优化建议
- 如果需要处理单文件超过10GB的场景,给Lambda挂载EFS文件系统作为存储目录,不受临时存储10GB上限限制。
- 可以给FFmpeg命令添加
-progress pipe:1参数输出转码进度,避免长时间无日志输出被Lambda判定为僵死进程终止。 - 配置S3事件触发时增加后缀过滤,避免处理已经压缩过的输出文件导致循环触发。
内容的提问来源于stack exchange,提问作者Joesph Stah Lynn
相关产品推荐
相关产品推荐

