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

Unity iOS使用Firebase PutStreamAsync上传大文件内存崩溃求助

解决方案与分析

一、解决PutStreamAsync内存飙升崩溃问题

你的内存暴涨问题核心是旧版Firebase Unity SDK的PutStreamAsync方法在iOS上会将整个文件流加载到内存中,而非真正流式分块上传,导致大文件直接撑爆内存。针对这个问题,可以尝试以下方案:

1. 使用带缓冲的分块流包装FileStream

通过BufferedStream限制单次读取的内存占用,强制分块处理:

public async Task<string> UploadTestVideo(TestExecution testExecution)
{
    string videoPath = TestPathHelper.GetLocalVideoPath(testExecution);
    StorageReference folderRef = storageReference.Child($"{StoragePathTestExecutions}/{testExecution.UID}/{StoragePathVideoRecordings}");
    StorageReference fileRef = folderRef.Child("recording.mp4");

    TVLog.Debug(Tag, $"Upload video <{videoPath}> to CloudStorage at <{fileRef.Path}>");
    try
    {
        MetadataChange newMetadata = new MetadataChange();
        newMetadata.ContentType = "video/mp4";

        // 用BufferedStream设置128KB缓冲,控制单次读取内存
        using (FileStream fileStream = File.OpenRead(videoPath))
        using (BufferedStream bufferedStream = new BufferedStream(fileStream, 1024 * 128))
        {
            TVLog.Debug(Tag, "Opened buffered file stream for uploading...");
            StorageMetadata metadata = await fileRef.PutStreamAsync(bufferedStream, newMetadata);
            TVLog.Debug(Tag, "Finished uploading video...");
            
            string md5Hash = metadata.Md5Hash;
            TVLog.Debug(Tag, "video md5 hash = " + md5Hash);
            return md5Hash;
        }
    }
    catch(StorageException e){
        TVLog.Exception(Tag, $"Exception uploading video {e.HttpResultCode} - {e.ErrorCode} - {e.Message}");
        return null;
    }
}

注:改用using语句自动释放流,比手动Close/Dispose更可靠,避免资源泄漏。

2. 手动实现分块上传

如果缓冲流效果不佳,可利用Firebase Storage的分块上传API,将文件分割为5MB左右的小块逐一上传,严格控制单块内存占用:

private const int ChunkSize = 5 * 1024 * 1024; // 5MB分块

public async Task<string> UploadLargeVideoInChunks(TestExecution testExecution)
{
    string videoPath = TestPathHelper.GetLocalVideoPath(testExecution);
    StorageReference fileRef = storageReference.Child($"{StoragePathTestExecutions}/{testExecution.UID}/{StoragePathVideoRecordings}/recording.mp4");
    string sessionUri = null;
    long totalBytes = new FileInfo(videoPath).Length;
    long uploadedBytes = 0;

    try
    {
        MetadataChange newMetadata = new MetadataChange();
        newMetadata.ContentType = "video/mp4";

        using (FileStream stream = File.OpenRead(videoPath))
        {
            byte[] buffer = new byte[ChunkSize];
            int bytesRead;
            while ((bytesRead = await stream.ReadAsync(buffer, 0, ChunkSize)) > 0)
            {
                // 处理最后一块可能不足ChunkSize的情况
                byte[] chunk = bytesRead == ChunkSize ? buffer : buffer.Take(bytesRead).ToArray();
                
                if (string.IsNullOrEmpty(sessionUri))
                {
                    // 第一块上传,获取会话URI用于续传
                    var snapshot = await fileRef.PutBytesAsync(chunk, newMetadata);
                    sessionUri = snapshot.SessionUri;
                }
                else
                {
                    // 后续分块上传,传入会话URI续传
                    await fileRef.PutBytesAsync(chunk, newMetadata, sessionUri);
                }

                uploadedBytes += bytesRead;
                TVLog.Debug(Tag, $"Uploaded {uploadedBytes}/{totalBytes} bytes");
            }
        }

        // 上传完成后获取最终元数据
        var finalMetadata = await fileRef.GetMetadataAsync();
        return finalMetadata.Md5Hash;
    }
    catch(StorageException e)
    {
        TVLog.Exception(Tag, $"Chunk upload failed: {e.HttpResultCode} - {e.ErrorCode} - {e.Message}");
        return null;
    }
}

二、解决PutFileAsync的ErrorObjectNotFound错误

该错误通常是文件路径格式不兼容导致,旧版Firebase SDK的PutFileAsync无法直接识别Unity的沙箱路径,需转换为iOS原生URL格式:

1. 转换Unity路径为iOS原生URL

修改路径处理逻辑,将Unity路径转换为file://开头的URL字符串:

public async Task<string> UploadViaPutFile(TestExecution testExecution)
{
    string videoPath = TestPathHelper.GetLocalVideoPath(testExecution);
    // 转换为iOS兼容的file URL,若路径含特殊字符需编码
    string iosFileUrl = $"file://{Uri.EscapeDataString(videoPath)}";
    StorageReference fileRef = storageReference.Child($"{StoragePathTestExecutions}/{testExecution.UID}/{StoragePathVideoRecordings}/recording.mp4");

    try
    {
        MetadataChange newMetadata = new MetadataChange();
        newMetadata.ContentType = "video/mp4";
        
        StorageMetadata metadata = await fileRef.PutFileAsync(iosFileUrl, newMetadata);
        return metadata.Md5Hash;
    }
    catch(StorageException e)
    {
        TVLog.Exception(Tag, $"PutFile failed: {e.HttpResultCode} - {e.ErrorCode} - {e.Message}");
        return null;
    }
}

2. 验证文件权限与存在性

调用PutFileAsync前,先确认文件存在且有读取权限:

if (!File.Exists(videoPath))
{
    TVLog.Error(Tag, $"File not found at path: {videoPath}");
    return null;
}

// 尝试读取文件头验证权限
try
{
    using (var stream = File.OpenRead(videoPath))
    {
        byte[] testBuffer = new byte[10];
        stream.Read(testBuffer, 0, 10);
    }
}
catch (UnauthorizedAccessException)
{
    TVLog.Error(Tag, "No permission to read the file");
    return null;
}

三、其他优化建议

  • 降低内存基线:上传前调用Resources.UnloadUnusedAssets()和GC.Collect(),清理Unity缓存,减少初始内存占用。
  • 监听内存警告:在iOS端监听系统内存警告,收到警告时暂停上传,释放非必要资源后再继续。
  • 尝试兼容SDK升级:若Unity Cloud Build允许调整Xcode版本,可尝试升级Firebase SDK到9.x以上版本,原生修复流式上传的内存问题。

内容的提问来源于stack exchange,提问作者gonza89

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 12:27:25