【发布时间】:2013-04-17 17:30:29
【问题描述】:
快速背景: app是一个音频播放器,ffmpeg编译为native共享对象用于解码,单独的native library编译为共享对象用于音频处理,AudioTrack用于输出处理后的音频。所有音频功能都封装在一个类中,该类使用静态类变量来确保只有一个实例。在这个类中:一个java线程用于从ffmpeg获取数据,通过原生处理库完成音频处理。本机处理调用需要 2 到 3.5 毫秒,具体取决于配置。处理后的音频位于由读取和写入计数信号量管理的 ByteBuffer 数组中。一个单独的 java 线程 ByteBuffer.获取一个音频块,减少 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) 对解决此问题有何建议?
如果有帮助,我很乐意发布一些代码
【问题讨论】:
-
解决方案:虽然在 onDestroy() 中调用,但我发现我必须手动调用本机代码“清理”函数才能在 libc.debug.malloc 10 模式下从调试 libc 获取日志消息。在手写的霓虹灯汇编函数中存在“离开缓冲区”错误。堆上损坏的数据会导致未定义的行为,想象一下。在查找本机堆问题时,请确保调用了清理代码,在这种情况下,android 杀死应用程序会限制分配检查/报告。
标签: android garbage-collection audiotrack