快速背景:该应用程序是一个音频播放器,ffmpeg 编译为原生共享对象并用于解码,单独的原生库编译为共享对象并用于音频处理,AudioTrack 用于输出处理后的音频。所有音频功能都封装在一个类中,该类使用静态类变量来确保只有一个实例。在这个类中:一个java线程用于从ffmpeg获取数据,通过原生处理库完成音频处理。本机处理调用需要 2 到 3.5 毫秒,具体取决于配置。处理后的音频位于由读取和写入计数信号量管理的 ByteBuffer 数组中。一个单独的 java 线程 ByteBuffer.get 一个音频块,减少 sem 计数并将 byte[] 数据发送到 AudioTrack。AudioTrack 配置为流模式,缓冲区大小为 getMinBufferSize 返回的大小的 2 倍。音频数据以每通道 512 个样本块的形式通过系统,对于立体声,2048 字节。
问题:一切都很好,随机的时间,然后一切都停止了,没有崩溃,没有 SIGSEG,应用程序只是消耗了大部分 CPU 并且什么都不做。GUI 没有响应,ADT 中的调试器失去了与应用程序的连接。使用 shell 和 top -m 10 -t,我可以看到应用程序 GC 线程正在消耗 49%,我假设是单核。有时,AudioTrack 会以低利用率弹出。与应用程序相关的其他线程从未出现在前 10 名中。这在 4.2.2 上发生得非常快,在 4.0.1 和 4.1.1 上不太确定。
为了解决:我使用 libc.debug.malloc 10 来验证本机内存使用情况。发现并修复了一些泄漏,但根据 libc,没有任何内容超出缓冲区的末端。安装 ByteBuffers 作为解决方法的尝试,之前使用了 byte[] 并表现出相同的问题。我删除了 AudioTrack 并替换为基于 OpenSL ES 的本机代码,结果相同。毫不奇怪,因为 OpenSL ES 使用了 AudioTrack。Log.i 样式的调试消息显示写入 AudioTrack 线程停止消耗数据,ffmpeg 读取和处理线程继续,直到 ByteBuffer 数组填满。我已将 System.gc() 调用放在战略位置,有时需要更长时间,但仍会出现挂起。我在 THREAD_PRIORITY_URGENT_AUDIO 设置了“已处理的音频数据到 AudioTrack”线程的优先级,没有观察到任何变化。
我已经广泛搜索了类似问题的实例,但发现的信息很少。我为 Eclipse/ADT 安装了 ARM 的 DS-5 调试功能。该工具能够保持与旋转应用程序的连接。暂停表明 GC 线程处于 dlmalloc_inspect_all 内部的无限循环中,可能试图回收或合并本机堆。AudioTrack 线程在暂停时处于 nanosleep,从 usleep 调用,AudioTrack.cpp 中只有两个 this 实例,一个在 processAudiobuffer 中,另一个在 tryLock 中。tryLock 从 stepServer 和 framesReady 调用。我无法从挂起的应用程序中获取堆栈转储,kill -3 在挂起 #1 threadid=2 (pcf=0) 时产生自旋,该应用程序从未被声明为 ANR,因此没有 /data/ANR/traces.txt。
我的概要 - GC 正在做它的事情并检查本机堆。根据文档,GC 不会在 JNI 调用中间暂停。但是它必须暂停 AudioTrack 线程,并且当本机堆检查与 AudioTrack 的 processAudioBuffer 的执行同时发生时,就会发生死锁。
问题: 1) 我肯定会受益于原生组件的堆栈跟踪,缺少开发平台和 JTAG 调试器,还有其他方法可以尝试吗?2) 有没有人看到 GC 和 AudioTrack 进入死锁路线的任何问题?3) GC dlmalloc_inspect_all 调用是否有可能被抑制或以其他方式同步以避免此问题?4)对解决这个问题有什么建议吗?
如果有帮助,我很乐意发布一些代码