记录音画控制、平台打包过程中遇到的一些坑点,有一部分是 unity 的文档描述不够完善造成的使用错误;一部分是属于神坑级BUG,比较难分析出来,只有网上查经验或者肝unity源码能解决的。
在此记录,不断更新吧,先简单记一下能回忆起来的部分。
动画
Animator.CrossFade 切换时进度变化的坑点
做一个逻辑,在上一个动画快播放完毕时,淡入下一个动画。1
this.animator.CrossFade(name, 0.1f, 0, 0f);
切换动画,紧接着立即或者在接下来几帧1
2AnimatorStateInfo 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。
快速切换对话音频流时偶现播放前次
之前的处理时,开始新的对话时,情况音频缓存 buffer1
this._pcmArray.Clear();
但是光清空可能会有隐患,因为下一次对话开启时,可能正好有一个 PcmReaderCallback
正在执行中。
所以最好的方法是在开始新会话时重建buffer。1
this._pcmArray = new ArrayList();
偶尔出现卡顿、音节重复
最恶心的问题之一,很难排查,听个一两分钟的音频也不一定能复现,可能很多次才复现一次,但是肯定是能复现的。unity的文档写的跟开玩笑一样,最终还是得肉身趟坑攒经验。排查历程:
- 是不是服务端数据错误?
将音频保存到本地播放多次,没有出现 - 是不是audio read 时,因数据不足,中间填0,打印,没有填0
这里发现有的卡顿是有填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堆栈有好几种:
偶现启动时 crash
引擎侧 crash
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 中点类名进去看头部声明查找到。
本文链接:https://www.zoucz.com/blog/2022/11/30/c1ffbdd0-70c3-11ed-9fa0-5dbc93f9d3ee/