【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=<project_path> --projectId=<project_id>

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

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

在这里插入图片描述
等待一段时间后(网络较差时,需要多等待一些时间),直到进度为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)

3.5 Android与iPhone11优化前后对比

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

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

优化前 优化后
TotalAudioCpu 0.6% 3.0%
DPSCpu 0.5% 0.6%
StreamingCpu 0% 2.3%
TotalAudioMemory 76.8M 7.1M
SampleSoundMemory 74.3M 1.6M

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

猜你喜欢

转载自blog.csdn.net/wankcn/article/details/126857109