• 【Unity性能优化】静态资源优化——Audio优化


    写在前面

    性能优化示例Git仓库https://github.com/WanKcn/Suntail_Village_Optimization
    详细说明见仓库内README。
    示例项目《Suntail_Village_Optimization》(以下简称项目SV

    学习地址B站Metaverse大衍神君老师《Unity性能优化》系列课程
    CSDN上的性能优化相关文章与学习笔记均存放在仓库/Documents目录下


    1. 前言

    Unity中的资源有哪些?
    在Unity中,资产可以是项目中用于创建游戏或者应用程序的任何对象。这些资源大致可以分为两类

    • 一类是游戏内部创建的资源:比如常见的预制体,脚本,动画控制器,渲染器,材质,动画编辑器,音频混合器,Pro Builder网,一些粒子特效等;
    • 另一类为外部导入的文件,比如美术的一些模型网格,视屏,音乐,音效,动画动效,字体等。

    asset工作流程
    上面的截图是Unity官方文档资产工作流程图。每一列代表一个步骤,使用资源的工作流程分为五步:

    1. 把资源导入到unity编辑器
    2. 使用带有这些资源的的Unity编辑器创建内容
    3. 构建成应用或者游戏
    4. 分发构建好的文件,方便用户们可以通过发布者或应用商店访问这些文件
    5. 在运行游戏时,可以根据需要加载进一步的更新

    Unity创建的资源为什么要涉及到导入问题呢?
    不管是Unity内部创建还是外部导入的资源,都会涉及到Unity导入问题。这是由于创建资源在不同平台下可能会涉及到导入设置的改变。或创建的资源在不同开发平台的机器上打开,或使用到了其他三方开发包,他们都需要进行资源的导入。在不同平台,合理的资源导入设置与资源规格可以为应用程序带来较高效率,反之运行效率会变差。 性能优化关于对资源的优化从这五个步骤入手。

    结合案例SV项目,在《【Unity性能优化】项目优化前需要进行哪些准备工作》一文中,对SV项目的资源分布做了大致的分析。
    在这里插入图片描述
    SV项目中,预制件,音效,材质,模型,纹理资源使用情况较重。可能会存在导入设置不合理,从而带来性能瓶颈,则需要对这些资源进行检查并优化。

    2. 使用Asset Checker进行资源检测

    上面的截图也看到了,大量的资源。如果对每一个资源进行检查分析,工程量巨大且不太现实。这里借助Unity提供的Asset Checker来完成资源检查工作。
    Asset Checker下载地址:https://upr.unity.cn/instructions/assetchecker
    在这里插入图片描述

    下载好了以后根据操作手册进行配置。官方的操作手册是以Win为示例,并没有Mac配置方法,基本都差不多,仔细阅读文档即可。
    另外我在B站找到了一个Up主分享的关于Asset Checker for Mac的教程视频作为辅助参考:Unity资源检测工具Asset Checher for Mac使用分享uinty资产检查工具

    需要注意的是,Mac下很可能出现非法开发者无法访问的情况,则需要先在终端运行命令sudo spctl --master-disable之后再跑配置命令assetcheck.exe --project= --projectId=

    项目id的申请,需要登录本人UnityID,创建项目,我这里创建为SV_Test。
    在这里插入图片描述
    打开创建的项目,可以在左侧基本信息拿到ProjectID。
    在这里插入图片描述
    在Mac下示例:

    mac下解压后的assetcheck路径 --项目路径 --申请的项目id
    
    • 1

    在这里插入图片描述
    等待一段时间后(网络较差时,需要多等待一些时间),直到进度为100%。此时刷新UPR首页来到我的项目/SV_Test/资源监测可以看到检查结果报告。
    在这里插入图片描述

    3. Audio优化

    打开使用Asset Checker获得的资源检查报告在这里插入图片描述
    通过上图可以看到,工具检查了84个资源,对其中的75个提出了改进建议。

    • 音频应该启用forceMono,以节省存储和内存
    • default 的常规音效应使用CompressedInMemory
    • default 的音乐应该使用Streaming

    3.1 启用Force to Mono

    其中有73份文件建议启用forceMono,以节省内存和包体大小。可以在SV工程中看到建议修改的音频资源Force To Mono选项并没有开启。
    在这里插入图片描述
    为什么建议通过勾选Force To Mono来优化音频呢?
    被建议的音频是双声道音频,且左右两声道的音乐完全相同,可以用勾选ForceToMono的方式强制将此音频修改为单声道,内容不丢失的情况下可以减少它的使用内存和大小。特别是在移动平台下几乎听不出任何区别。如果左右声道内容不同,开启ForceToMono会导致听到的声音错误。

    3.2 压缩格式与采样率

    另外,需要注意这里的一下音频详细信息,如下图:
    在这里插入图片描述
    红色框出的部分显示的是音频 原始资源大小导入压缩后的大小 、和整体压缩比

    音频在不同平台下的压缩格式
    在这里插入图片描述

    一般情况下,应该尽可能使用未压缩的wav文件作为音频源文件,通过不同平台支持的压缩格式控制压缩比。一般移动平台下,unity下大多数音频文件采用Vorbis压缩方法。如果音乐不循环可以使用MP3格式。一些操作系统对特定的压缩格式有额外的优化,比如在iOS系统上可以使用MP3格式。此外一些简短常用的音效可以使用ADPCM格式。虽然这种格式的压缩比可能不是最好的,但在播放过程中解码速度很快。总之,音频压缩策略需要考虑不同压缩格式在不同平台下的特点,以及音乐音效文件在不同用途下使用不同的压缩格式。

    关注音频源文件的采样率
    在这里插入图片描述
    通常在移动平台都会选择对音质影响最小的最低设置,一般移动平台音频采样率经验数据建议设置为22050Hz。可以看到该音频源文件的音频采样率是4.8w赫兹,在移动平台上完全没有必要,只会徒增文件大小和内存占用。
    如果要修改默认的音频采样频率,可以通过Sample Rate Setting下拉菜单选择复写采样频率,并修改采样频率为22050Hz,记得点击Apply。与上面的音频信息对比可以发现导入后的大小从原来的16.3kb缩减为8.7kb,越低采样频率生产的音频文件会越小。
    在这里插入图片描述

    3.3 音乐加载类型

    回到检测报告,如下图,报告对不同的音频类型,音效和音乐使用不同的加载类型。
    在这里插入图片描述
    对应unity音频inspector窗口的Load Type设置。它影响的是unity的Asset工作流程图中的第五步(加载)。
    在这里插入图片描述
    如上图,加载类型有三种,默认是Decompress On Load,该选项一般对应音频压缩后大小<200kb的音效文件,如果音效文件大于200kb,则推荐第二种类型Compressed In Memory。如果是背景音乐文件,或较大较长的音效文件。则推荐使用Streaming流加载,可以避免载入时卡顿。
    此外,当游戏需要静音时,不要将音量设置为0,应该销毁音频audiosource组件把它从内存中完全卸载。 音乐音效一般不会成为性能瓶颈,但优化音乐音效可以减少对内存的使用与包体大小,使用了第三方带有复杂逻辑的库除外。

    3.4 编写Audio性能优化工具

    这里我用python写了一个批处理工具。使用需要注意几个目录的更换和具体优化方法的调用
    TextPath:把资源报告中的文件列表复制到文本中。
    ProjectDir:工程目录
    BackUpDir:修改文件的备份目录

    留意代码中的opt_file方法,数据修改部分根据需求调用不同的方法。

    # Author: 文若 
    # CreateDate: 2022/9/15
    
    """
    Audio_Optimize 优化工具
    """
    
    import os
    import os.path
    from shutil import copyfile
    
    # Unity工程目录,注意后面有个"/"
    ProjectDir = "/Users/wankcn/Desktop/EditorTest/"
    # 文本文件目录 需要拷贝性能报告到文本里 我这里命名为audio.txt
    TextPath = "/Users/wankcn/Desktop/audio_txt"
    # 源文件备份目录
    BackUpDir = ProjectDir + "BackUp/"
    
    # 测试文件
    TestFile = "Assets/Suntail Village/Audio/Enviroment/Items/Item_Food_Large_2.wav"
    
    
    def read_file(src_path):
        """全文读取"""
        # if os.path.isfile(src_path) and os.path.splitext(src_path)[-1].lower() == '.txt':
        if os.path.isfile(src_path):
            with open(src_path, 'r', encoding='utf-8') as f:
                lines = f.readlines()
                # 需要去掉"\n"
                for i in range(len(lines)):
                    lines[i] = lines[i].rstrip()
            return lines
        else:
            return None
    
    
    def read_file2(src_path):
        """行读取"""
        lines = []
        if os.path.isfile(src_path):
            with open(src_path, 'r', encoding='utf-8') as f:
                while True:
                    line = f.readline()
                    if not line:
                        break
                    lines.append(line.strip())  # 必要步骤
            return lines
        else:
            return None
    
    
    def get_audiofile_data(full_src_path):
        """获取的文件内容"""
        # 内容存入字典 {name:[索引,前半段,后半段]} 用:分割
        dic = {}
        files = read_file(full_src_path)
        for i in range(len(files)):
            strs = files[i].split(':')
            key = strs[0].strip()
            dic[key] = [i, strs[0], strs[1]]
        return dic
    
    
    def copy_file(source_path):
        if not os.path.exists(BackUpDir):
            os.makedirs(BackUpDir)
            print(">> 指定的备份目录不存在,创建完成 >>")
    
        # 拷贝一份源meta文件到新目录中
        new_file_name = source_path.split('/')[-1]
        new_path = BackUpDir + new_file_name
    
        if not os.path.exists(new_path):
            print(">>【备份完成】 {0}".format(new_path))
            copyfile(source_path, new_path)
        else:
            print(">>【文件已存在】终止操作!!地址为: {1}".format(new_file_name, new_path))
    
    
    def write_file(file_name, data):
        with open(file_name, "w", encoding="utf-8") as f:
            for k, v in data.items():
                f.write(v[1] + ':' + v[2] + "\n")
        print(">>【写入完成】{0}".format(file_name))
    
    
    def opt_force_to_mono(data):
        """对文本内容进行处理 代码还可以再优化"""
        rate = data['sampleRateOverride'][2]
        # 修改音频采样率
        if int(rate) >= 22050:
            data['sampleRateOverride'][2] = " 22050"
            data['sampleRateSetting'][2] = " 2"
    
        # 勾选ForeToMono
        data['forceToMono'][2] = " 1"
        return data
    
    
    def opt_compressed_in_memory(data):
        data['loadType'][2] = " 1"
        return data
    
    
    def opt_streaming(data):
        data['loadType'][2] = " 2"
        return data
    
    
    def opt_file(file_name):
        # 拼接目录,修改AudioClip.meta
        full_src_path = os.path.join(ProjectDir, file_name + ".meta")
    
        # 文件备份
        copy_file(full_src_path)
    
        # 数据结构
        dic = get_audiofile_data(full_src_path)
    
        # 修改数据
        data = opt_force_to_mono(dic)
        # data = opt_compressed_in_memory(dic)
        # data = opt_streaming(dic)
    
        # 写入数据
        write_file(full_src_path, data)
    
    
    def opt(path):
        files = read_file(path)
        for f in files:
            opt_file(f)
    
    
    if __name__ == '__main__':
        opt(TextPath)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137

    3.5 Android与iPhone11优化前后对比

    因为我没有安卓手机,所以我这里只对比安卓包体优化前后的大小。
    在这里插入图片描述
    安卓打包后的对比,左边是优化后,右边是优化前,虽然只缩减了0.1MB哈哈,但是包体是有明显减小的。这是因为我在asset下又相比之前加了优化工具和一些编辑器脚本等文件如下图是22个较大的脚本改动。不过结果很直观,优化是有效果的。
    在这里插入图片描述
    如果安卓包不够明显,接下来来看iPhone11的实际效果
    1.先来看包体大小,下面第一张是我从上一遍文章中截的图,当时留有包大小的截图。第二张是我刚才新打包后的包体大小截图。非常明显,包体减小了25MB以上。
    在这里插入图片描述
    在这里插入图片描述

    2.再来看游戏运行时的profiler情况。
    在这里插入图片描述
    在这里插入图片描述
    第一张图是优化前,第二张图是优化后。真的是看到这个结果我非常的开心!瞬间写了一天的优化工具值了!

    优化前优化后
    TotalAudioCpu0.6%3.0%
    DPSCpu0.5%0.6%
    StreamingCpu0%2.3%
    TotalAudioMemory76.8M7.1M
    SampleSoundMemory74.3M1.6M

    可以通过图表非常直观看到,开启相同音源情况下,优化后内存的占用比之前要少了近70兆,而Cpu的负载仅提高了2%。这是因为一部分音频loadtype改成了streaming,额外产生了一些cpu开销。与内存方面相比,这个优化是非常值得的,尤其在正式的工程中含有大量的音频文件,这一优化变得非常有价值。

  • 相关阅读:
    跟我学Python图像处理丨带你掌握傅里叶变换原理及实现
    vue的使用
    【React】React全家桶(十)Redux
    力扣Hot100-994腐烂的橘子
    Kafka与Spring Boot等应用框架的集成及消息驱动模型
    八大排序(一)冒泡排序,选择排序,插入排序,希尔排序
    JAVA电商平台免费搭建 B2B2C商城系统 多用户商城系统 直播带货 新零售商城 o2o商城 电子商务 拼团商城 分销商城
    459. 重复的子字符串
    【解决】VSCode编写C++自定义头文件undefined reference异常问题
    leetCode 309.买卖股票的最佳时机含冷冻期 动态规划 + 滚动数组
  • 原文地址:https://blog.csdn.net/wankcn/article/details/126857109