[实战笔记]使用TeeSink进行音频调试

本文详细记录了在Android设备上使用TeeSink工具进行音频调试过程中遇到的问题,包括系统启动失败、配置af.tee后录音生成问题,以及不同平台声音问题的排查实例。

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

最近几个平台都会遇到一些声音的问题,包括投影的蓝牙传输播放声音小、TV煲机中逐步无声等等。刚好试着使用一下TeeSink工具进行调试,顺便记录一下。

TeeSink的官方使用文档如下:

音频调试  |  Android 开源项目  |  Android Open Source Project

准备步骤

按照操作步骤,需要执行的工作如下:

1.编译出了userdebug/eng的版本软件烧录,以确保 ro.debuggable 为 [1]。

2.将Configuration.h中 TEE_SINK 配置的注释放开(有些厂家是默认放开的)

3.make libaudioflinger,然后替换板端的system/lib下同名so,确认权限后重启生效。

因为使用的平台版本是Android 9 、Android 11、Android 12,因此默认teesink抓取的录音会放至/data/misc/audioserver目录下。如果低于Android 7,目录为/data/misc/media。

4.配置af.tee=#。

TeeSink 保存录音名称解析

从 Android 11 上扒出对应的规则说明:

 Tee filenames generated as follows:

 "aftee_Date_ThreadId_C_reason.wav" RecordThread
 "aftee_Date_ThreadId_M_reason.wav" MixerThread (Normal)
 "aftee_Date_ThreadId_F_reason.wav" MixerThread (Fast)
 "aftee_Date_ThreadId_TrackId_R_reason.wav" RecordTrack
 "aftee_Date_ThreadId_TrackId_TrackName_T_reason.wav" PlaybackTrack

 where Date = YYYYmmdd_HHMMSS_MSEC

 where Reason = [ DTOR | DUMP | REMOVE ]

 Examples:
  aftee_20180424_153811_038_13_57_2_T_REMOVE.wav
  aftee_20180424_153811_218_13_57_2_T_REMOVE.wav
  aftee_20180424_153811_378_13_57_2_T_REMOVE.wav
  aftee_20180424_153825_147_62_C_DUMP.wav
  aftee_20180424_153825_148_62_59_R_DUMP.wav
  aftee_20180424_153825_149_13_F_DUMP.wav
  aftee_20180424_153842_125_62_59_R_REMOVE.wav
  aftee_20180424_153842_168_62_C_DTOR.wav

Teesink使用问题记录

1.替换so后系统启动失败

确保了板端的软件和当前编译代码是一致的,在代码中放开了TEE_SINK的配置后,make出so替换进系统中,重启发现系统启动失败。

报错定位在AudioFlinger或者是SurfaceFlinger(?),出现了crash。

解决方法:

未知谜团,采用最简单粗暴的整机软件烧录编译解决。

2.配置af.tee=# 后,dump af没有teesink记录和录音生成

这里遇到三种不同的情况,需要一一确认:

2.1 读取不到 af.tee 的prop

按照官方文档给出的做法:

echo af.tee=# > /data/local.prop

chmod 644 /data/local.prop

reboot

重启后串口读取af.tee的取值,发现没有af.tee这个prop。这个情况在部分芯片上出现,估计是一些配置限定,令系统启动时没有读取local.prop这个文件。

解决方法:

新建的路走不通,直接在已有的prop文件上新增一条。直接mount了vendor分区,在build.prop / default.prop上加入af.tee的配置。

为啥需要在prop文件内配置呢?因为af.tee是不带persist前缀的,默认不带持久化处理,重启后会失效。想要启动之后自动生成这个prop配置,就需要在prop文件里配置,系统启动时会读取所有prop文件,一一配置里面的内容。

2.2 af.tee 的读取时机问题

在 2.1 出现重启后读取不到 af.tee 的情况下,正常进入系统后,手动配置了 af.tee=#,再进行dump发现没有效果。查阅源码,发现 AF 是在构造方法中解析 af.tee,并保存到mTeeSinkInputEnabled、mTeeSinkOutputEnabled、mTeeSinkTrackEnabled三个变量中。也就是说,af.tee 需要在audioserver 启动前配置完成才有效果。

解决方法:

手动配置完成后 kill audioserver重新触发一次流程。或者简单点,采用2.1的解决方式,在已有prop文件上加入af.tee的配置。

2.3 播放音视频时,dump没有teesink记录和录音生成

按照官方说明的参数配置,基本上可以顺利dump出录音记录了。

这里需要提一下af.tee的配置说明:

  • 1 = 输入线程。即 RecordThread。dump后在data/misc/audioserver中记录的后缀为‘C’(capture)
  • 2 = MixerThread 输出线程。官方指的是FastMixer 输出,但我查看了NBAIO_Tee.cpp的说明发现这里只要是混音线程都可以正常 dump 出来。这里的混音线程包括 Fast MixerThread 或者是Normal MixerThread。dump后在data/misc/audioserver中记录的后缀为‘M’(normal mixer)或者是 ‘F’ (fast mixer)。
  • 4 = Track。Track包括输入的 RecordTrack 和输出的 AudioTrack 。dump后在data/misc/audioserver中记录的后缀为‘T’(track)或者‘R’(record track)。
#ifdef TEE_SINK
    char value[PROPERTY_VALUE_MAX];
    (void) property_get("ro.debuggable", value, "0");
    int debuggable = atoi(value);
    int teeEnabled = 0;
    if (debuggable) {
        (void) property_get("af.tee", value, "0");
        teeEnabled = atoi(value);
    }
    // FIXME symbolic constants here
    if (teeEnabled & 1) {
        mTeeSinkInputEnabled = true;
    }
    if (teeEnabled & 2) {
        mTeeSinkOutputEnabled = true;
    }
    if (teeEnabled & 4) {
        mTeeSinkTrackEnabled = true;
    }
#endif

注:以上代码是基于 Android 9 扒出来的,在 Android 11 上已经换了一种方式配置了。

根据上面的配置,能够确认TeeSink能够抓取的数据包括:MixerThread、RecordThread以及两个Thread内部的Tracks。反推一下,就能得出TeeSink无法使能的场景:

1.使用非MixerThread播放的数据。

本地媒体播放视频,dump不出数据;播放音频,顺利dump出来。对比了一下dump af 的内容,在播放音频时开启的PlaybackThread类型为MixerThread,播放视频时开启的PlaybackTrhead类型为DirectThread。DirectThread默认不会处理这些数据,一般会直接送到hal层由芯片去解析数据,因此在 fw 层其实还是非pcm数据,因此Google不会对DirectThread存流。内置的TeeSink只能获取MixerThread和下面AudioTrack的内容,有这个区别。

2.不通过AF传输的数据。

这里给两个场景:

(1)信源播放

TV的本质业务是信源播放。尽管Google后续版本是想要规范TV产品业务进AOSP里的,但是目前芯片和TV厂家都是使用的自己支持的一套。

TV业务上层应用OSD层根据用户选择进入某个通道,通过和芯片厂商的封装协议open对应通道的audio和video信号。板端根据输入信号状况,回调状态码给上层应用。如果成功打开,关闭OSD层,通过VIDEO层进行显示。

整体的交互完全靠封装私有接口进行对接,没有Framework层的参与。此时去dump af,会发现没有任何PlaybackThread的记录。因此这部分是没有办法通过TeeSink去抓取数据的。

(2)厂商的播放器优化

各个厂商或多或少都会对MediaPlayer进行优化,以提供更强大、更优化的能力和支持更多的格式。一部分厂商会保持MediaPlayer作为对外封装的播放器,只把内部实现替换成NuPlayer或者其他实现。这种做法TeeSink一般来说还是可以抓取到的。

另一些厂商替换程度更加彻底,换成内部实现的player,直接绕开Framework的一系列流程,把整体工作全部自己完成,一般对接的时候会被叫做私有通路。这种状态下,Framework不参与其中,dump af也会没有任何PlaybackThread的记录,自然就没有办法通过TeeSink抓取了。

针对上述这两种场景,其实芯片厂商会内置了一些dump命令去抓取驱动的音频数据。可以合理利用这些命令去抓取全场景的出声数据。

3.dump出文件打开为空数据

我常常会拿来排查这个情况:播放某个音频数据,有正常的MixerThread和active track记录,但是无声。

查看MixerThread和RecordThread的记录,正常dump出 pcm文件后,通过Audacity打开pcm,发现完全没有波形。这个可能是因为对应的Track被设置成静音了(没错,track其实也是有自己对应的音量的,往上层可以简单理解为某个Mediaplayer通过setVolume设置的音量)。

这种情况下,查看dump af 中对应的track中的数据,队友对应的L和R,其中最大的音量值(track默认状态就是最大,可以简单理解为UI值中的100)对应的是0,最小音量值(可以简单理解为UI值中的0)对应的是-inf。

别问这有什么场景,当年自己移植patch踩过坑;也排查过上层应用奇怪需求设置播放器音量的问题。

TeeSink排查案例

回到最开始我遇到的问题。

案例一:客户使用手机蓝牙连接机器,播放歌曲发现声音很小,需要调至很大才能听清。

拿到这个问题,先是拿了不同的手机进行对比,幸运的是三台手机表现不一致,已经能初步断定和手机有关系。

取其中两台手机连接同一机器,dump af查看track音量值参数,均为默认最大值。播放同一软件的同一首歌(为了保持音源一致),抓取teesink数据做对比,发现系统端收到的数据幅度差异很大。因此确认问题是在AudioTrack接受的数据的问题,与客户澄清。

案例二:一些TV平台出现的播放无声问题。

比如遇到A平台生产机器发现音量调大后突然出现的整机无声问题;B平台生产机器发现煲机中途突然整机无声问题。

在无声状态下先排查fw层的mute状态、音量设置通路、底层寄存器mute脚和db值状态之后,就可以开始用teesink和厂商自带的dump工具抓取播放数据排查了。

TeeSink可以作为Fw数据排查,厂商自带的是整机输出数据排查,可以确认软件端输出流程是否正常。排查完软件端后,就会交由amp功放和硬件同事继续排查其他问题。

### 关于 `libwds.so.1` 共享库缺失的解决方案 当遇到错误提示 `error while loading shared libraries: libwds.so.1: cannot open shared object file: No such file or directory` 时,这通常意味着系统缺少所需的共享库文件 `libwds.so.1` 或者该文件未正确链接到系统的动态链接器路径中。以下是详细的解决方法: --- #### 方法一:安装缺失的共享库 如果系统确实缺少 `libwds.so.1` 文件,则需要找到并安装提供该库的软件包。具体步骤如下: 1. **查找所需软件包** 使用包管理工具(如 `apt`, `yum`, 或 `dnf`)搜索包含 `libwds.so.1` 的软件包名称。例如,在基于 Debian 的系统上可以运行以下命令: ```bash apt search libwds ``` 如果是在 Red Hat/CentOS/Fedora 系统上,可以运行: ```bash yum provides */libwds.so.1 ``` 2. **安装相应的软件包** 找到提供 `libwds.so.1` 的软件包后,使用包管理工具进行安装。例如: ```bash sudo apt install libwds1 # 对应 Debian/Ubuntu 系统 ``` 或者: ```bash sudo yum install wds-libs # 对应 CentOS/RHEL 系统 ``` 3. **验证安装结果** 安装完成后,确认 `/usr/lib` 或 `/usr/lib64` 目录下是否存在 `libwds.so.1` 文件: ```bash ls /usr/lib*/libwds.so.1 ``` --- #### 方法二:创建软链接(适用于已有更高版本库) 如果系统已经存在较高版本的 `libwds.so.x` 文件(其中 `x > 1`),可以通过创建软链接的方式解决问题。例如: 1. 查找现有版本的 `libwds.so` 文件位置: ```bash find / -name "libwds*.so*" 2>/dev/null ``` 2. 假设找到了 `/usr/lib/libwds.so.2`,则可以为其创建名为 `libwds.so.1` 的软链接: ```bash sudo ln -s /usr/lib/libwds.so.2 /usr/lib/libwds.so.1 ``` 3. 验证软链接是否成功: ```bash ls -l /usr/lib/libwds.so.1 ``` 注意:这种方法的前提是高版本的库向后兼容低版本的功能需求[^1]。 --- #### 方法三:更新动态链接器缓存 即使安装了必要的库文件,仍需确保动态链接器能够找到它们。为此,需要更新动态链接器缓存: 1. 编辑 `/etc/ld.so.conf.d/` 下的相关配置文件,添加库所在的目录路径。例如: ```bash echo "/usr/local/lib" | sudo tee -a /etc/ld.so.conf.d/usr_local_lib.conf ``` 2. 更新动态链接器缓存: ```bash sudo ldconfig ``` 3. 验证动态链接器是否能找到新库: ```bash ldd $(which your_executable) | grep libwds ``` --- #### 方法四:手动编译或下载缺失库 如果官方仓库中不存在提供 `libwds.so.1` 的软件包,或者当前系统不支持自动安装,可以选择手动获取源码并编译: 1. 访问项目的官方网站或其他可信资源站点,下载 `libwds` 库的源代码压缩包。 2. 解压后按照说明文档编译并安装: ```bash ./configure --prefix=/usr make sudo make install ``` 3. 完成后再次检查是否有 `libwds.so.1` 文件生成,并将其加入动态链接器路径。 --- #### 注意事项 - **依赖关系冲突**:某些情况下,不同版本的库可能会引发应用程序崩溃或行为异常。因此建议优先采用官方推荐的方法解决此类问题。 - **权限不足**:在执行涉及修改系统文件的操作前,请确保拥有足够的管理员权限。 - **备份原始数据**:对于任何更改原生库的行为都应当谨慎对待,必要时做好相应记录以便回滚操作。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值