Android系统下解决音频underrun噪声问题的一种更优方法

本文针对复杂的使用场景,提出一种动态调整延时时间的方法来解决Android系统中的音频Underrun问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注意:本文基于 Android 7.0 进行分析

 

【问题概要】

    上一次我介绍了一种 Android 系统下发生音频 underrun 问题的解决方法(参见《记一次Android系统下解决音频UnderRun问题的过程》),这之后平静了一段时间,测试组同事也没有再报告相关的噪声问题。

    但就在前 2 天,测试组同事告诉我说她们又听见噪声了,并且这次的使用场景比上次复杂了许多——由于从 Android 6.0 开始已经支持应用多开以及多窗口的功能,所以她们先在后台运行了一个程序(比如 阴阳师、全民飞机大战 这样的游戏),再在前台播放视频,于是噪声大量出现了。上次问题的情况比较简单,出现噪声时 framesReady 的值与 framesDesired 的值始终相差 2。所以我通过在 Android 原有处理 underrun 问题的方法的延时基础上增加 3 毫秒,解决了问题。但这次的问题,我们从 Log 中可以看到差值分布范围广,使用固定的延时时间已经无法消除噪声

    在 Log 中搜索包含“underrun”关键字的内容可以看到如下记录:

 

03-07 18:44:04.290  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(407) < framesDesired(516)
03-07 18:44:04.470  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(31) < framesDesired(516)
03-07 18:44:04.570  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(47) < framesDesired(516)
03-07 18:44:04.730  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(79) < framesDesired(516)
03-07 18:44:04.950  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(119) < framesDesired(516)
03-07 18:44:05.007  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(127) < framesDesired(516)
03-07 18:44:05.139  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(151) < framesDesired(516)
03-07 18:44:05.231  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(167) < framesDesired(516)
03-07 18:44:05.323  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(183) < framesDesired(516)
03-07 18:44:05.415  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(199) < framesDesired(516)
03-07 18:44:05.507  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(215) < framesDesired(516)
03-07 18:44:05.563  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(223) < framesDesired(516)
03-07 18:44:05.614  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(231) < framesDesired(516)
03-07 18:44:05.669  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(239) < framesDesired(516)
03-07 18:44:05.783  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(263) < framesDesired(516)
03-07 18:44:05.840  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(271) < framesDesired(516)
03-07 18:44:05.886  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(279) < framesDesired(516)
03-07 18:44:05.937  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(287) < framesDesired(516)
03-07 18:44:05.996  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(295) < framesDesired(516)
03-07 18:44:06.130  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(319) < framesDesired(516)
03-07 18:44:06.187  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(327) < framesDesired(516)


【解决问题】

 

    既然 framesReady与 framesDesired 的差值不再固定,那么我们也应该根据差值大小来调节延时时间来解决问题只要我们检测到 framesReady 小于 framesDesired,我们就进行 1 毫秒延时,然后再获取延时后的 framesReady 值与 framesDesired 值进行比较,如果 framesReady 仍然小于 framesDesired,那么则继续延时 1 毫秒。如此循环,直到 framesReady 值大于等于 framesDesired 或者超时退出。这次的改动是在上次修复的代码基础上进行的(参见《记一次Android系统下解决音频UnderRun问题的过程》)。话不多说,直接把修复问题的 Patch 贴上来吧,如下:

 

diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3c941bc..614a5c2 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1454,6 +1454,8 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge
                                              type_t type,
                                              bool systemReady)
     :   ThreadBase(audioFlinger, id, device, AUDIO_DEVICE_NONE, type, systemReady),
+        mLackFrames(0),
+        mNeededFrames(0),
         mNormalFrameCount(0), mSinkBuffer(NULL),
         mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
         mMixerBuffer(NULL),
@@ -2729,6 +2731,7 @@ void AudioFlinger::PlaybackThread::detachAuxEffect_l(int effectId)
 bool AudioFlinger::PlaybackThread::threadLoop()
 {
     Vector< sp<Track> > tracksToRemove;
+    size_t minReadyFrames = 0;
 
     mStandbyTimeNs = systemTime();
 
@@ -3012,10 +3015,32 @@ bool AudioFlinger::PlaybackThread::threadLoop()
                         const int32_t deltaMs = delta / 1000000;
                         const int32_t throttleMs = mHalfBufferMs - deltaMs;
                         if ((signed)mHalfBufferMs >= throttleMs && throttleMs > 0) {
-                            //usleep(throttleMs * 1000);    // We can prolong this sleep time to prepare much data for threadLoop_write()
-                            usleep((throttleMs + 3) * 1000);     /* Delay more 3ms to prepare much data to 
-                                                                  * fix tencent video player underrun bug   20161216 
-                                                                  */
+                            //usleep(throttleMs * 1000);    // This is the original method to solve underrun problem by Android
+                            
+			     /* In short, the underrun problem occurs due to framesReady less than 
+			      * desiredFrames. So we can simply wait the upper level prepares enough 
+			      * data for writing when we detected framesReady < desiredFrames.
+			      * Use do...while loop to wait and judge if we have enough data.
+			      * Fix all underrun problem - 20170401
+			      */
+			     ALOGV("Qidi - real mLackFrames = %d", mLackFrames);
+			     if(mLackFrames > 0) {    // 只要检测到framesReady小于framesDesired则执行下方的延时等待
+				  size_t count = mActiveTracks.size();
+				  size_t tmpReadyFrames = 0;
+				  unsigned char waitTimeout = 0;
+                                 minReadyFrames = mNeededFrames;
+				  #define MAX_TIME 5
+				  do {
+					usleep(1000);    // 延时1毫米,然后判断准备好的数据是否足够,以及是否超时
+	        			for (size_t i = 0; i < count; i++) {
+	                			sp<Track> t = mActiveTracks[i].promote();
+						Track* const track = t.get();
+						tmpReadyFrames = track->framesReady();
+						minReadyFrames =  tmpReadyFrames > minReadyFrames ? minReadyFrames : tmpReadyFrames;
+						ALOGV("Qidi - minReadyFrames=%d, mNeededFrames=%d", minReadyFrames, mNeededFrames);
+	        			}
+	        		  }while((minReadyFrames < mNeededFrames) && (waitTimeout++ < MAX_TIME));    // 如果数据不足且没有超时,则继续等待
+			     }
+			     mLackFrames  = 0;
+			     minReadyFrames = 0;
+			
                             // notify of throttle start on verbose log
                             ALOGV_IF(mThreadThrottleEndMs == mThreadThrottleTimeMs,
                                     "mixer(%p) throttle begin:"
@@ -3915,7 +3940,8 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
         // add frames already consumed but not yet released by the resampler
         // because mAudioTrackServerProxy->framesReady() will include these frames
         desiredFrames += mAudioMixer->getUnreleasedFrames(track->name());
-
+        mNeededFrames = desiredFrames;
+		
         uint32_t minFrames = 1;
         if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&
                 (mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
@@ -4147,6 +4173,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
             if (framesReady < desiredFrames && !track->isStopped() && !track->isPaused()) {    // underrun occurs
                 ALOGV("track(%p) underrun,  framesReady(%zu) < framesDesired(%zd)",
                         track, framesReady, desiredFrames);
+	       mLackFrames = desiredFrames - framesReady;
                 track->mAudioTrackServerProxy->tallyUnderrunFrames(desiredFrames);
             }
             // clear effect chain input buffer if an active track underruns to avoid sending
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 46ac300..14d88c6 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -480,6 +480,12 @@ public:
 //FIXME may be more appropriate if expressed in time units. Need to revise how underrun is handled
     // for offloaded tracks
     static const int8_t kMaxTrackRetriesOffload = 20;
+    size_t    mLackFrames;
+	 // mLackFrames is used for adjusting underrun delay time dynamically.
+	 // such delay is performed in MixerThread::prepareTracks_l() when 
+	 // underrun occurrs.
+    size_t	mNeededFrames;
+	 // mNeededFrames is equal to desiredFrames in MixerThread::prepareTracks_l()
 
     PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
                    audio_io_handle_t id, audio_devices_t device, type_t type, bool systemReady);

    修改完成后,编译系统并烧写镜像文件到设备上。经测试,该方法对所有之前出现过的 underrun 问题均有效。
 

 

<think>我们正在讨论Android音频系统的内部调试。用户要求详述调试方法。 根据之前的对话,我们知道startOutput()是音频系统中的调试日志,可以通过logcat捕获。 现在需要详细说明如何进行Android音频系统内部的调试,包括日志捕获、工具使用、源码分析等。 回答后必须提出3-5个相关问题,以“§§相关问题§§”开头,然后列出问题。 步骤: 1. 使用logcat捕获日志(包括过滤、提高日志级别) 2. 使用dumpsys检查音频服务状态 3. 使用strace跟踪系统调用 4. 修改源码添加自定义日志(如果需要) 5. 使用其他工具(如amat, bugreport等) 注意:回答要详细,但不要超出范围。</think>### Android 音频系统内部调试详解 进行 Android 音频系统调试需结合 **日志分析**、**系统工具** 和 **硬件诊断** 三方面,以下是详细流程: --- #### **一、核心调试工具** | 工具 | 命令示例 | 作用 | |---------------------|-----------------------------------|----------------------------------------------------------------------| | **ADB logcat** | `adb logcat -v time -b main \| grep -iE "Audio|startOutput"` | 捕获实时音频日志(过滤关键函数) | | **dumpsys audio** | `adb shell dumpsys media.audio_policy` | 输出音频策略完整状态(设备路由/音量曲线/活动流) | | **strace** | `adb shell strace -p <audioserver_pid> -f -e trace=ioctl` | 跟踪音频服务系统调用(定位硬件交互故障) | | **amat** | `adb shell amat` | 原生音频诊断工具(Android 10+,输出编解码器状态/延迟数据) | | **tinymix** | `adb shell tinymix` | 查看/修改 ALSA 声卡配置(需 root) | --- #### **二、关键调试场景与步骤 ##### 场景1:音频路由异常(如耳机无法切换) 1. **捕获路由决策日志**: ```bash adb logcat -v raw | grep "Device selected" ``` 2. **检查音频策略**: ```bash adb shell dumpsys audio | grep -A 15 "Devices:" ``` 3. **验证硬件端口**: ```bash adb shell tinymix -D 0 # 查看声卡0的混音器设置 ``` ##### 场景2:播放卡顿/杂音 1. **跟踪音频延迟**: ```bash adb shell dumpsys media.audio_flinger | grep "underrun" # 检查欠载次数 ``` 2. **分析 CPU 负载**: ```bash adb shell top -H -p $(pidof audioserver) # 监控音频服务线程 ``` ##### 场景3:无声故障 1. **检查流状态**: ```bash adb shell dumpsys audio | grep "Stream:" -A 8 ``` 2. **强制重载音频策略**: ```bash adb shell killall audioserver # 重启音频服务(Android 9+) ``` --- #### **三、高级调试技巧** 1. **提升日志级别**: ```bash adb shell setprop log.tag.AudioPolicy VERBOSE # 开启详细日志 adb logcat -c && adb logcat | grep "APM" # 过滤策略管理器日志 ``` 2. **硬件寄存器诊断**(需 root): ```bash adb shell cat /sys/kernel/debug/asoc/*/codec_reg # 查看编解码器寄存器 ``` 3. **生成完整诊断报告**: ```bash adb bugreport # 包含所有音频服务状态快照 ``` --- #### **四、源码级调试准备** 1. **编译调试版系统**: ```mk # device.mk 添加调试标志 PRODUCT_PROPERTY_OVERRIDES += debug.audio.trace=1 ``` 2. **关键断点位置**: - `AudioPolicyManager::getOutputForDevice()`(设备选择逻辑) - `AudioFlinger::PlaybackThread::threadLoop()`(播放线程核心) ---
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值