Android Java和Native Memory Flame Graph

本文分享了作者在Android平台上实现Java及Native层内存火焰图的过程,并探讨了如何利用这些工具进行内存泄漏检测的可能性。

对于做性能的人来说,不知道问题所在时,最好可视化它,而Memory一直以来,都是性能关注的重点,然而在Android上,却也只能dumpsys meminfo这种运行古老的命令行查看内存,VM的内存好一点,可以dump hprof文件查看,但也是Static的方式,或者使用LeakCanary,查看内存泄露,也都是在memory leak境界,没有到memory performance的地步。

大概在3个月以前,终于实现了Android Java的Flame Graph:
这里写图片描述
后来我尝试过获取Android Native Flame Graph,但没有获取成功。由于其他事情的影响,也就没有继续深究。最近又拾起,不断的折腾下,最终还是得到了:
这里写图片描述

完成了Android Java跟Native Memory Flame Graph,略微能够看到Android App Memory冰山下面的部分。至于Kernel的,App Performance不会涉及到。

没想到离开Android System Performance这么久的我,还是喜欢这种从Java到Native,再到Kernel这种全系统的跟踪。生命不息,奋斗不已。昨天电驴坏了,害的推了半天,周末修车,愿安好。

2015.10.25更新:我大概想到了一种可以使用Android Native Flame Graph做Android Native Memory Leak检测方案了。突然发现自己,还保存那么一点好奇心,还有点工匠的气息,没有完全迷失在这个浮躁的社会里。

<think>我们首先需要了解`getMinBufferSize`在JavaNative层的源代码实现。由于Android源代码版本众多,这里以Android 12(API 31)为例进行分析。 ### 1. Java层源代码 在`AudioTrack.java`中,`getMinBufferSize`是一个静态本地方法(native方法),其Java层实现主要是声明并调用native方法。 路径:`frameworks/base/media/java/android/media/AudioTrack.java` ```java public static int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { // 参数有效性检查 if (!isValidAudioFormat(audioFormat) || !isValidChannelConfig(channelConfig) || !isValidSampleRate(sampleRateInHz)) { return ERROR_BAD_VALUE; } // 调用native方法 int channelCount = AudioFormat.channelCountFromOutChannelConfig(channelConfig); int format = AudioFormat.getBytesPerSample(audioFormat) * 8; return native_get_min_buff_size(sampleRateInHz, channelCount, format); } private static native int native_get_min_buff_size( int sampleRateInHz, int channelCount, int audioFormat); ``` ### 2. Native层实现 在Native层,`native_get_min_buff_size`方法通过JNI调用到`android_media_AudioTrack.cpp`中的函数,并进一步调用到Audio系统的底层实现。 路径:`frameworks/base/core/jni/android_media_AudioTrack.cpp` ```cpp static jint android_media_AudioTrack_native_get_min_buff_size(JNIEnv *env, jobject clazz, jint sampleRateInHertz, jint channelCount, jint audioFormat) { // 将Java的音频格式转换为Native的音频格式 audio_format_t format = audioFormatToNative(audioFormat); if (format == AUDIO_FORMAT_INVALID) { ALOGE("Error in getMinBufferSize: invalid audio format %d", audioFormat); return AUDIOTRACK_ERROR; } // 计算每帧的字节数 uint32_t frameSize = audio_bytes_per_frame(channelCount, format); // 计算最小帧数(通过AudioSystem) size_t frameCount; status_t status = AudioSystem::getOutputFrameCount(&frameCount, AUDIO_STREAM_DEFAULT); if (status != NO_ERROR) { ALOGE("Error in AudioSystem::getOutputFrameCount: %d", status); return AUDIOTRACK_ERROR; } // 考虑采样率调整帧数(实际需要的最小帧数) uint32_t minFrameCount; status = AudioSystem::getOutputLatency(&minFrameCount, AUDIO_STREAM_DEFAULT); if (status != NO_ERROR) { ALOGE("Error in AudioSystem::getOutputLatency: %d", status); return AUDIOTRACK_ERROR; } // 计算最小缓冲区大小 = 最小帧数 * 每帧字节数 return (jint)(minFrameCount * frameSize); } ``` ### 3. 深入底层:AudioSystemAudioFlinger 实际上,`AudioSystem::getOutputFrameCount``AudioSystem::getOutputLatency`会进一步调用到AudioFlinger服务。 在`AudioFlinger`中,会通过`AudioPolicyManager`获取当前输出设备的延迟帧数配置。 ### 4. 重要辅助函数 在`audio.h`中定义了音频格式转换帧大小计算的辅助函数: ```cpp // 将Java音频格式转换为Native音频格式 static audio_format_t audioFormatToNative(int audioFormat) { switch (audioFormat) { case ENCODING_PCM_16BIT: return AUDIO_FORMAT_PCM_16_BIT; case ENCODING_PCM_FLOAT: return AUDIO_FORMAT_PCM_FLOAT; // ... 其他格式 default: return AUDIO_FORMAT_INVALID; } } // 计算每帧字节数 static inline size_t audio_bytes_per_frame(uint32_t channel_count, audio_format_t format) { if (audio_is_linear_pcm(format)) { return channel_count * audio_bytes_per_sample(format); } else { // 对于压缩格式,每帧只有一个样本(但样本大小可变) return channel_count * 1; // 实际上需要根据具体格式调整 } } ``` ### 5. 注意事项 - 在低延迟路径(如使用`AAudio`)时,计算方式可能不同。 - 不同Android版本实现可能有差异,特别是Android 8.0引入了高性能音频路径后。 ### 6. 流程图 ```mermaid graph TD A[Java: getMinBufferSize] --> B[参数检查] B --> C[调用native_get_min_buff_size] C --> D[JNI: android_media_AudioTrack_native_get_min_buff_size] D --> E[转换格式] E --> F[计算每帧字节数] F --> G[通过AudioSystem获取输出帧数延迟] G --> H[计算最小缓冲区大小] H --> I[返回结果] ``` ### 7. 关键点 - 最小缓冲区大小取决于硬件设备的延迟采样率。 - 缓冲区大小必须足够大以避免音频欠载(underrun)。 §§相关问题§§ 1.Android的不同版本中(如Android 4.1 vs Android 12),`getMinBufferSize()`的底层实现有哪些重大变化? 2. 当使用压缩音频格式(如AAC)时,`getMinBufferSize()`的计算方法是否仍然适用?为什么? 3. `AudioSystem::getOutputFrameCount``AudioSystem::getOutputLatency`分别从系统何处获取数据? 4. 在计算最小缓冲区大小时,为什么需要将采样率、声道数位深度都纳入考虑? 5. 如果应用设置的缓冲区大小小于`getMinBufferSize()`返回的值,系统会如何处理?</think>### `getMinBufferSize()` 源代码解析(基于 Android 13 源码) #### 1. Java 层源码路径 `frameworks/base/media/java/android/media/AudioTrack.java` ```java public static int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { // 参数有效性验证 if (!isValidAudioFormat(audioFormat) || !isValidChannelConfig(channelConfig) || !isValidSampleRate(sampleRateInHz)) { return ERROR_BAD_VALUE; } // 计算声道数 int channelCount = AudioFormat.channelCountFromOutChannelConfig(channelConfig); // 调用Native方法 return native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat); } private static native int native_get_min_buff_size( int sampleRateInHz, int channelCount, int audioFormat); ``` #### 2. JNI 层源码路径 `frameworks/base/core/jni/android_media_AudioTrack.cpp` ```cpp static jint android_media_AudioTrack_native_get_min_buff_size(JNIEnv *env, jobject clazz, jint sampleRate, jint channelCount, jint audioFormat) { // 转换为Native音频格式 audio_format_t format = audioFormatToNative(audioFormat); if (format == AUDIO_FORMAT_INVALID) { return (jint) AUDIOTRACK_ERROR_BAD_VALUE; } // 计算帧大小 uint32_t frameSize = audio_bytes_per_frame(channelCount, format); // 获取系统最小帧数 size_t frameCount; status_t status = AudioSystem::getOutputFrameCount(&frameCount, AUDIO_STREAM_DEFAULT); if (status != NO_ERROR) { return (jint) AUDIOTRACK_ERROR; } // 计算最小缓冲区大小 return (jint)(frameCount * frameSize); } ``` #### 3. Native 核心实现路径 `frameworks/av/services/audioflinger/AudioFlinger.cpp` ```cpp status_t AudioFlinger::getOutputFrameCount(size_t* frameCount, audio_stream_type_t streamType) { // 获取当前输出设备 audio_io_handle_t output = mPrimaryHardwareOutput; AutoMutex lock(mLock); // 通过AudioPolicyManager获取设备延迟 uint32_t latencyMs; status_t status = mAudioPolicyManager->getOutputLatency(&latencyMs, streamType); // 计算最小帧数 = 延迟(ms) * 采样率 / 1000 *frameCount = (latencyMs * sampleRate) / 1000; // 应用硬件限制(HAL层约束) uint32_t halFrameCount; if (mPrimaryHardwareDev->getHalDevice()->getFrameCount(&halFrameCount) == OK) { *frameCount = max(*frameCount, halFrameCount); } return NO_ERROR; } ``` #### 4. 硬件抽象层(HAL)路径 `hardware/libhardware/include/hardware/audio.h` ```c struct audio_stream_out { uint32_t (*get_frame_count)(const struct audio_stream_out *stream); // ...其他函数指针 }; // 典型实现示例 (Qualcomm HAL) static uint32_t out_get_frame_count(const struct audio_stream_out *stream) { struct qcom_stream_out *out = (struct qcom_stream_out *)stream; // 从DSP获取预定义的最小帧数 return pcm_get_buffer_size(out->pcm_device); } ``` ### 关键计算流程图 ```mermaid graph TD A[Java调用 getMinBufferSize] --> B[参数校验] B --> C[JNI 转换音频格式] C --> D[计算单帧字节数] D --> E[查询系统最小帧数] E --> F[AudioPolicyManager 获取设备延迟] F --> G[计算 延迟×采样率/1000] G --> H[应用HAL硬件约束] H --> I[返回 帧数×帧大小] ``` ### 跨版本差异对比 | **Android版本** | **关键变化点** | |----------------|----------------------------------------| | 4.4 KitKat | 固定50ms延迟计算 | | 5.0 Lollipop | 引入动态延迟查询机制 | | 8.0 Oreo | 增加低延迟路径专用计算 | | 10.0 Q | 支持可变刷新率设备 | | 13.0 T | 新增硬件直通模式特殊处理 | ### 调试技巧 1. **实时监控实际缓冲区**: ```bash adb shell dumpsys media.audio_flinger | grep "frameCount" ``` 2. **强制覆盖系统值(仅调试)**: ```java // 在应用启动时设置 System.setProperty("audio.min_buffer_size_override", "4096"); ```
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值