记录音画控制、平台打包过程中遇到的一些坑点,有一部分是 unity 的文档描述不够完善造成的使用错误;一部分是属于神坑级BUG,比较难分析出来,只有网上查经验或者肝unity源码能解决的。

在此记录,不断更新吧,先简单记一下能回忆起来的部分。

动画

Animator.CrossFade 切换时进度变化的坑点

做一个逻辑,在上一个动画快播放完毕时,淡入下一个动画。

1
this.animator.CrossFade(name, 0.1f, 0, 0f);

切换动画,紧接着立即或者在接下来几帧

1
2
AnimatorStateInfo animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
animatorStateInfo.normalizedTime; // do somethine with this progress

获取动画播放进度并执行相关逻辑,发现播放进度并没有切换到新的动画的进度,而是会在旧动画的进度停留一段时间。

音频

播放延时问题

Unity Editor中打开Edit菜单
选择Project Settings
选择Audio,您会在Inspector中看到DSP Buffer Size,其中有数个选项可供选择:Default, Best latency, Good latency, Best Performance.
选择 Best latency,检查延迟问题是否得到了解决

播放杂音问题

1
this._currentAudioClip = AudioClip.Create("tts", lengthSamples, channels, sampleRate, true, _PcmReaderCallback);

神坑!!! 流式音频数据读取回调,在每次读取数据时会默认分配一段 unsafe 的内存,如果读取数据时,tts的数据尚未返回,就会导致播放杂音或者之前内存块中残留的音频。

1
private void _PcmReaderCallback(float[] data)
{
    int copyCount = Math.Min(this._pcmArray.Count, data.Length);
    if (copyCount == 0){
        // !!! 重要:PcmReaderCallback 为 data 分配了 unSafe 的内存空间,这里如果不给 fill 0,就会导致播放杂音或者残留之前内存块内的音频!
        Array.Fill(data, 0);
        return;
    };
    // ...
}

需要判断,若没有数据时,手动填0。

部分音节被拉长

其实和播放杂音问题比较类似,其实上面的修复方案并不完整,还有一点要注意的:

1
try {
    //拷贝数据到 AudioClip 的 buffer 中
    this._pcmArray.CopyTo(0, data, 0, copyCount);
    //若有不够的部分,填充0,避免播放未知数据
    if(copyCount < data.Length) {
        Array.Fill(data, 0, copyCount, data.Length - copyCount);
    }
} catch(Exception e){
    Debug.Log("PcmReaderCallback error:" + e.Message);
}

有数据,但是数据不够时,也需要填0。

快速切换对话音频流时偶现播放前次

之前的处理时,开始新的对话时,情况音频缓存 buffer

1
this._pcmArray.Clear();

但是光清空可能会有隐患,因为下一次对话开启时,可能正好有一个 PcmReaderCallback 正在执行中。
所以最好的方法是在开始新会话时重建buffer。

1
this._pcmArray = new ArrayList();

偶尔出现卡顿、音节重复

最恶心的问题之一,很难排查,听个一两分钟的音频也不一定能复现,可能很多次才复现一次,但是肯定是能复现的。unity的文档写的跟开玩笑一样,最终还是得肉身趟坑攒经验。排查历程:

  • 是不是服务端数据错误?
    将音频保存到本地播放多次,没有出现
  • 是不是audio read 时,因数据不足,中间填0,打印,没有填0

image.png

这里发现有的卡顿是有填0的。

但是后来发现,并不是所有的卡顿都会填0日志。

  • 数据片段处理错误,是否有非法数据?
    后来又看到写入音频流式数据过程中的 cast 报错 At least one element in the source array could not be cast down to the destination array type.
1
this._pcmArray.CopyTo(0, data, 0, copyCount);

cast 报错时有个逻辑,会丢掉当前报错的片段,也会造成卡顿。_pcmArray 是一个 ArrayList。

这里提示数据源中有部分数据不是正常的 float。

audioclip是要求数据范围在 -1~1 之间的,尝试写死数据为 -1.1,发现并未报错。

  • 是不是偶尔有数据范围是奇数位,导致 pasre to int16 失败?
    打印流式片包中字节长度,发现都是偶数。

  • 是不是线程安全问题?
    audioclip的数据读取回调是在音频线程中调用的,数据源是在unity的主线程中,打印调用时间戳发现经常会同一个时间戳调用多次,是否存在线程安全问题?会导致 data race,造成 cast 报错,或者多次读取同一个片段,导致音频重复(确实是出现过)。

加锁,仍然出现卡顿的问题。考虑到加锁对性能的影响,去掉锁。

  • 尝试修改数据源类型,避免cast工作
    思考,都是 float,为啥还要 cast 呢? 数据源类型从 ArrayList 修改为泛型数组 List

又听了十几分钟,好似没有复现卡顿了。

真机调试 —— 你妹! 听了几分钟出现了重复音节!

真机倒是没有卡顿的问题了,也没有出现 cast 报错了,说明这个改动多少可能是有效果的。

  • 继续加锁
    性能没有正确性重要,加回锁试试。

听了10分钟,重复读没有出现了, 没有任何异常log打出来,但是仍然出现了极轻微的一次卡顿。

暂时放弃,等以后有新的思路再继续排查,或者封装平台原生的流式播放器解决。

平台打包

android 上偶现动人物卡死

伴随 Timeout while trying to pause the Unity Engine 报错。

原因是 android 端代码在某些情况下调用了 unityPlayer 的resume,但是没有调用 onWindowFocusChange,导致 unity 的 view 一直未获取焦点,处于暂停状态。

横竖屏切换时 crash

原因是 activity 重建,再加上 activity 的生命周期未绑定 unityplayer 的 destroy,导致crash。见上篇文章。

抓到的引擎crash堆栈有好几种:
image.png
image.png
image.png

偶现启动时 crash

引擎侧 crash
image.png
debug中,估计还是和 destory 未绑定有关

启动成功但是动画失效

playerSettings —— stripEngineCode 代码优化开启后会导致classID找不到等问题,参考 https://www.jianshu.com/p/e7120f025281 解决,或者关闭。

比如我遇到的是 Could not produce class with ID 74,去 unity 的 ClassIDReference文档 里边一查,确实就是 AnimationClip 类找不到了。
在Assets目录下创建 link.xml,内容写

1
2
3
4
5
6
<linker>
<!--Preserve types and members in an assembly-->
<assembly fullname="UnityEngine.AnimationModule">
<type fullname="UnityEngine.AnimationClip" preserve="all"/>
</assembly>
</linker>

重新导出打包,问题解决。 注意,assembly 名称,可以在 IDE 中点类名进去看头部声明查找到。

☞ 参与评论