18、Logcat 与多媒体:MediaPlayer、Camera、Audio 日志追踪
多媒体这块,说实话,是 Android 系统里最容易出幺蛾子的地方。你想想看,MediaPlayer 播放卡顿、Camera 打开黑屏、Audio 无声或者爆音——这些线上反馈,十有八九都得靠 Logcat 来定位。
我个人习惯,遇到多媒体问题,第一件事不是翻代码,而是先拉日志。因为多媒体框架的调用链特别长,从 App 到 Framework,再到 HAL 层,甚至到硬件驱动,任何一个环节断了,表现都是「不好使」。日志就是我们的探针。
18.1 MediaPlayer 日志追踪
MediaPlayer 的状态机比较复杂。我见过不少开发同学,一看到播放失败就怀疑解码器,其实很多时候是状态没切对。
先看一个典型的播放流程日志:
D/MediaPlayer: constructor
D/MediaPlayer: setDataSource: path = /sdcard/test.mp4
D/MediaPlayer: prepareAsync
D/MediaPlayer: start
I/MediaPlayer: MediaPlayer::notify, msg=1 (MEDIA_PREPARED)
I/MediaPlayer: MediaPlayer::notify, msg=2 (MEDIA_STARTED)
这里的关键消息号,我列个表方便你对照:
| 消息号 | 常量名 | 含义 |
|---|---|---|
| 1 | MEDIA_PREPARED | 准备完成,可以开始播放 |
| 2 | MEDIA_STARTED | 播放已开始 |
| 100 | MEDIA_ERROR | 播放出错,后面会跟 error code |
| 200 | MEDIA_INFO | 信息提示,比如缓冲开始/结束 |
避坑指南: 我曾经遇到一个 case,MediaPlayer 一直报 MEDIA_ERROR (1, -2147483648)。这个错误码看着吓人,其实是因为网络视频的 URL 里带了中文,底层解析失败。所以遇到错误,先检查数据源本身。
如果你看到日志里反复出现 MEDIA_INFO_BUFFERING_START 和 MEDIA_INFO_BUFFERING_END,那说明网络带宽不够,或者视频码率太高。这时候别纠结代码,先让 CDN 同学看看。
18.2 Camera 日志追踪
Camera 的问题,多半出在权限、预览尺寸和 HAL 这三块。我习惯先搜 CameraService 和 Camera2 这两个 tag。
一个正常的 Camera 打开流程,日志大概是这样的:
I/CameraService: CameraService::connect call (PID 1234)
D/Camera2Client: initialize: mCameraId=0
I/CameraDeviceClient: createSurface: 1920x1080, format=0x22
D/CameraHal: open: sensor_id=0
如果卡在 CameraService::connect 之后没下文,多半是权限被拒。看看有没有:
E/CameraService: Permission Denial: can't use camera
嗯,这个日志很直白,不用我多解释。
还有一种情况,预览黑屏但没报错。这时候我建议你搜 configureStreams 和 dequeueBuffer:
E/Camera3-Device: configureStreams: output stream 0: 1920x1080, format 0x22
E/Camera3-Stream: dequeueBuffer: Buffer queue is empty!
Buffer queue 为空,说明 Surface 没有及时消费数据。说白了,就是预览的 SurfaceView 或者 TextureView 没准备好。我曾经调试过一个项目,就是因为 Activity 的 onResume 里直接打开 Camera,但 SurfaceView 还没创建完成,导致一直黑屏。解决方案很简单,等 SurfaceHolder.Callback 回调后再打开。
小技巧: 用 logcat -s CameraService Camera2Client CameraHal 过滤,能省掉 90% 的噪音。
18.3 Audio 日志追踪
Audio 的问题,最头疼的是无声和杂音。无声还好,杂音往往跟采样率、通道数不匹配有关。
先看 AudioTrack 的创建日志:
D/AudioTrack: createTrack: streamType=3, sampleRate=44100, channelMask=0x3
I/AudioFlinger: createTrack: sessionId=10, output=2
如果 createTrack 返回了错误,比如:
E/AudioTrack: createTrack: AudioFlinger could not create track, status: -12
这个 -12 是 NO_MEMORY。说白了就是 AudioFlinger 那边的 buffer 分配失败了。我遇到过一种情况,App 里同时创建了 20 多个 AudioTrack 实例,直接把系统内存吃光了。解决方案是复用 AudioTrack,或者用 SoundPool 管理短音效。
再来看一个常见的无声问题日志:
D/AudioPolicyManager: getOutputForDevice: device=0x2, samplingRate=48000
W/AudioFlinger: mix thread: write to HAL returned -1
write to HAL returned -1 说明 HAL 层写数据失败了。这时候要检查一下,是不是音频通路被其他 App 占用了?比如有些手机在录屏时,会独占音频输出通道。
注意: 如果你看到 AUDIO_STREAM_MUSIC 和 AUDIO_STREAM_VOICE_CALL 混在一起,那多半是通话场景下的路由问题。我曾经在调试一个 VoIP 应用时,发现声音从听筒出来而不是扬声器,就是因为没设置 setSpeakerphoneOn(true)。
18.4 多媒体日志的通用排查思路
说了这么多,其实多媒体日志的排查,有个通用套路。我画了张图,你一看就明白:
说白了,就是先看有没有错误日志。有,就顺着错误码查。没有,就搜关键 TAG 看状态机走到哪一步了。大部分多媒体问题,都能用这个思路搞定。
最后说一句,多媒体日志往往量很大。我建议你多用 -s 过滤,或者用 grep 配合 logcat -b main -b system 一起看。别一股脑全拉出来,那跟大海捞针没区别。