USB声卡解码器连接Android手机时问题的出现和分析[三] 从Android源码找出采样率锁定的原因
农步祥 于 2017.09.28 02:05:36 | 源自:www.soomal.com | 版权:原创
平均/总评分:09.92/129

在前两篇文章中,我们通过偶然机会发现了Android智能手机在搭配外接USB声卡、解码器等音频设备时存在的一些兼容性问题。除了OTG模式不稳定、供电不佳、设置无效、经常需要重启外,多数Android设备的音乐播放器会将外接USB声卡的播放采样率锁定在一个较高值上[通常是192kHz],而主流的44.1kHz音乐源均会SRC至192kHz播放,对音质体验带来了负面影响。

Android设备连接USB声卡时的工作流程分析

虽然从Android 5.0版本以来,许多Android设备即使存在SRC现象,也比更早时期的Android SRC对音质的负面影响改善不少,处于可以接受的程度。但对于对音质要求更高的使用场合来说,尤其是欣赏无损和高清音乐时,其声音相对应有的正常音质水平还是明显粗糙。那么这个问题从何而来?那么首先应该简单了解一下Android的USB音频架构的大致工作流程。

在《Qualcomm 高通芯片组与Android音频系统缺陷测评分析 》[作者:赵宇为 ] 一文中,我们已经简单介绍了Android音频架构的工作流程,虽然这个流程图是用于解释高通手机在Android系统下的音频架构,而在连接USB设备时,其工作原理是完全一致的,只不过硬件驱动由手机内置音频CODEC芯片或SoC厂商的驱动改成了通用的UAC[USB Audio Class]驱动。另外在我们翻译自海外网站的一篇文章《Android的10毫秒问题 解读Android系统音频通道延迟缺陷》[作者:Gabor、Patrick ] 中,里面有一张动态gif图也直观地体现了Android音频系统的工作流程,将两者结合,我们可以得出一个新的图表。

和当前多数Android设备的内置音频系统SRC一样,在连接USB2.0声卡时,系统的采样率会锁定在192kHz采样率上,在使用系统内置的音乐播放器或网易云等应用时,也会统一SRC至192kHz播放。

Android系统外接USB音频设备时采样率锁定深度分析

虽然明白了Android设备SRC的工作流程,但这并不能解释外接USB声卡时会SRC至192而不是Android手机更常见的48kHz,一开始我个人初步判断为Android系统去倾向于设置USB声卡或UAC驱动所支持的最高采样率值。然而蛋爷表示,许多USB声卡所支持的最高采样率已经达到384kHz,为何却偏偏是锁定192呢?这里便涉及到一个有关Android系统底层的问题:Android系统是如何获得和设置USB声卡的采样率的?

为了获得答案,我们首先要确认Android的UAC驱动能支持多高的采样率,和桌面Linux系统一样,Android的内核声卡驱动主要来源自开源的ALSA。其声卡信息会在系统的/proc/asound/目录下显示,我们在接上乐之邦或XMOS声卡后,可以从/proc/asound/card1目录中获得外接USB声卡的信息。

从显示信息来看,数字时代2在Android下最高支持至384kHz,看来锁定192并非来自硬件驱动的限制。看来这个问题是源自Android系统自身了,很大概率问题是出现在Android的硬件抽象管理层HAL驱动上,那么我们所获得的信息是否就到此为止了呢?答案当然是否定的,由于Android的绝大部分代码是开源的,任何人都能在网上通过网页浏览器和git等开发工具软件随意查看。本着还残留一些码农的编程基础和Linux系统知识,在花费了两小时后,找到了锁定采样率的真正原因,由于涉及编程知识,对这方面缺乏了解的读者如果觉得内容实在太硬,可以直接跳过看本文结尾的总结。

Android的HAL部分源代码基本公开,其USB音频管理部分源码位于/hal/audio_extn/usb.c中。从代码片段中,我们可以轻易找到涉及采样率的几个关键点:

这两段代码片段包含的内容是:定义了一个数组,里面包含了USB音频设备支持的采样率集合supported_sample_rates{44100, 48000, 64000, 88200, 96000, 176400, 192000, 384000}。还定义了一个整数为MAX_SAMPLE_RATE_SIZE,也就是这个集合的最高值,根据算式可以得出结果为八[即384000,384kHz,在编程语言中数组集合从0开始,实际结果是7]。而在初始化usb_card_config中,系统设定的采样率正是最高的MAX_SAMPLE_RATE_SIZE。按照这段源代码的逻辑,在正常的情况下,Android系统的USB外接音频设备初始化采样率应该是设备所支持的最大值[数字时代2或XMOS都支持至384kHz]而非192kHz。

根据这三段代码线索,我们在同一个代码文件中找到了HAL获取USB设备采样率的方式函数usb_get_sample_rates。毫不意外的是,HAL正是读取/proc/asound目录中的声卡信息获得了采样率列表,然而在判断声卡最大采样率上,源代码是这么写的:

由于在这段遍历循环代码中,由于需要判断声卡实际支持的采样率和supported_sample_rates中的数字对应,也就是合集,由于剔除了许多USB声卡不支持的64000采样率,以及supported_sample_rates中没有的352800,HAL自己应获得的设备采样率应该是合集{44100, 48000, 88200, 96000, 176400, 192000, 384000},然而系统日志中最终输出显示,系统最终获得的采样率合集是{44100, 48000, 88200, 96000, 176400, 192000},384000则无故“消失”了。

这也正是初始化USB声卡usb_card_config时所获得的最大值,如果是SA9027或PCM270X等采样率低于192kHz的USB音频方案,则采用声卡硬件驱动所支持的采样率最大值,例如48kHz、96kHz等,这也就是Android系统设备在连接乐之邦、XMOS等USB2.0高性能异步音频方案时锁定192kHz的“罪魁祸首”。

总结

造成如此奇妙的SRC问题,原因却如此的简单:只是由于Android HAL以及AudioFlinger源代码开发者的错误,就造成了USB音频设备在Android系统下采样率锁定在192kHz的问题。为何至今却没有被谷歌旗下任何高级攻城狮和码农们发现和解决?最直接的原因自然是Android源代码的拥有者谷歌对于这部分应用从来没有真正上心过,Android手机是目前普及度最高的个人终端设备,音频爱好者自然会对音质有更高的要求,那谷歌在Android 6.0开始就着力在PDF上宣传的所谓专业Android音频系统,到底是对谁说的?

尽管外接USB音频设备时,Android用户的确还有海贝音乐应用这一选择,也有类似OPPO R11等系统自带USB音频采样率自动识别切换的手机个例,但海贝毕竟无法支持云音乐应用,而OPPO则是由于自身也是高端音频设备的厂商,自然需要让HA-2等便携式或台式解码器在OPPO手机下正常工作。对于广大Android手机用户群体以及忠实的云音乐用户来说,是否就只能等待谷歌或者手机厂商自己去良心发现或利益需求后才能解决呢?

转发到新浪微博 转发到腾讯微博 RSS订阅 收藏本文 本文代码
请您评分 1 2 3 4 5 6 7 8 9 10
123.121.005.***
123.121.005.***
发表于2017.10.07 17:31:29
62
049.130.131.***
049.130.131.***
发表于2017.10.06 13:18:43
61
国庆节前升级了Flyme7.0结果外接设备各种bug都出现了。一会无声,一会卡顿破音。真是走倒退路了!
发表于2017.10.06 13:06:44
60
222.188.148.***
222.188.148.***
发表于2017.10.05 01:04:17
59
从14年开始用这个组合一直到现在,云音乐什么的压根不理会,装一个QQ音乐有时候搜需要的歌就行
此帖使用Win10提交
发表于2017.10.04 00:15:00
58
122.059.077.***
122.059.077.***
发表于2017.10.03 12:42:24
57
117.136.070.***
117.136.070.***
发表于2017.10.02 11:53:31
56
112.193.093.***
112.193.093.***
发表于2017.10.01 07:45:09
55
125.068.011.***
125.068.011.***
发表于2017.09.30 07:21:10
54
发表于2017.09.29 18:12:07
53
384000确实丢失了,但看样子问题不在这段代码。编辑亲自编译源码验证,赞
发表于2017.09.29 18:06:15
52
111.076.***.***
111.076.***.***
一看就知道了啊,'i<MAX_SAMPLE_RATE_SIZE',MAX_SAMPLE_RATE_SIZE=7,length(support_sample_rates)=8
所以384000漏掉了
此帖使用Win10提交
发表于2017.09.29 17:16:54
51
next_sr_string是用strtok获取的,strtok 并不是线程安全的函数,会不会是这个问题?

next_sr_string是用strtok获取的,strtok 并不是线程安全的函数,会不会是这个问题?
如果能改代码,把next_sr_string和sr打印一下,就很清楚了。
发表于2017.09.29 16:02:30
50
看不懂
此帖使用Lumia 950提交
发表于2017.09.29 13:32:14
49
八成ALOGI_IF这个函数里有上限。
此帖使用iPhone提交
发表于2017.09.29 10:53:05
48
036.023.***.***
036.023.***.***
就是找声卡的采样率和系统支持的采样率的交集,至于为什么漏了384000,应该有别的原因。
此帖使用HTC U-3W提交
发表于2017.09.29 09:25:02
47
就这么简单
此帖使用ZUK Z2131提交
发表于2017.09.29 06:20:01
46
122.226.185.***
122.226.185.***
发表于2017.09.28 22:46:43
45
125.073.***.***
125.073.***.***
发表于2017.09.28 20:07:48
44
123.015.***.***
123.015.***.***
(手动滑稽)
此帖使用Win10提交
发表于2017.09.28 19:26:20
43
提示本贴可以匿名回复 ,您现在正处在潜水状态
回复
验证码
0040 为防止广告机贴垃圾,不得已而为之
表情
正文