Android系统内录方案

系统内录、录音、AudioSource.REMOTE_SUBMIX、AudioPlaybackCapture API、AOSP、Android源码、Android11

0. 背景介绍

01. 内录两种技术方案

音乐屏保其中一个音波律动样式,需要获取设备当前播放的Media类型音频流实现律动效果。结合Android官方API有两种方式可以实现:

1.系统应用使用AudioSource.REMOTE_SUBMIX方式,无需用户介入(下文统称为系统内录)

2.三方应用使用AudioPlaybackCapture API,需要用户授权(下文统称为Capture API)

两种方式在实际落地过程中都遇到阻塞问题:

1.系统内录,会截断蓝牙设备和扬声器的声音,在我们设备上具体现象是除了内置喇叭,其他通道的声音都会被截断。相关文章介绍很多,比如https://www.51cto.com/article/716095.html

2.Capture API,使用海外主流音乐应用播放时无法获取音频流,但使用国内QQ音乐、网易音乐测试可以正常录音

既然两种方式都有问题,综合用户体验,解决系统内录的优先级更高,本文后续也围绕系统内录方案展开。

02. 系统内录网上解决方案

方案1:网上搜索解决系统内录时截断音频流的问题,主要都是一种修改方式,比如https://juejin.cn/post/6844903705561530382,最早来源应该某个AOSP版本的改动,后续被各种引用,实际在Android11上不生效

方案2:https://mp.weixin.qq.com/s/PCaBiOmyoyCY1lhdo8ueDg 这篇公众号文章分析原理时也提出解决方案,比较靠谱,是最终解决方案中的一环

综合网上可搜索到的资料,以及LLM给出的建议都无法最终解决,只能深入源码debug。

1. 源码Debug主要过程

1.1 从播放入手解决

通过网上相关原理介绍,方案2的改动是一个关键点,按照建议在Engine中增加Devices的内容,允许remoteSubmix设备和其他设备共存,但是录音时蓝牙设备还是不出声。

/services/audiopolicy/enginedefault/src/Engine.cpp

diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 2edecf8e4e..72ea3f6bab 100755
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -371,16 +371,17 @@ DeviceVector Engine::getDevicesForStrategyInt(legacy_strategy strategy,
     // FIXME: STRATEGY_REROUTING follow STRATEGY_MEDIA for now
     case STRATEGY_REROUTING:
     case STRATEGY_MEDIA: {
-        DeviceVector devices2;
+        DeviceVector devices8;
         if (strategy != STRATEGY_SONIFICATION) {
             // no sonification on remote submix (e.g. WFD)
             sp<DeviceDescriptor> remoteSubmix;
             if ((remoteSubmix = availableOutputDevices.getDevice(
                     AUDIO_DEVICE_OUT_REMOTE_SUBMIX, String8("0"),
                     AUDIO_FORMAT_DEFAULT)) != nullptr) {
-                devices2.add(remoteSubmix);
+                devices8.add(remoteSubmix);
             }
         }
+        DeviceVector devices2;
         if (isInCall() && (strategy == STRATEGY_MEDIA)) {
             devices = getDevicesForStrategyInt(
                     STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
@@ -426,6 +427,10 @@ DeviceVector Engine::getDevicesForStrategyInt(legacy_strategy strategy,
                     AUDIO_DEVICE_OUT_HDMI_ARC, AUDIO_DEVICE_OUT_SPDIF, AUDIO_DEVICE_OUT_AUX_LINE});
         }

+        if (!devices8.isEmpty()) {
+            devices.add(devices8);
+            ALOGI("Thing devices.add(devices8) and size: %zd", devices.size());
+        }
         devices2.add(devices3);
         // device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION or
         // STRATEGY_ENFORCED_AUDIBLE, AUDIO_DEVICE_NONE otherwise

查看logcat日志,发现有E级别日志,audioserver以及三方应用都有报错,异常信息如下

2025-02-06 15:06:53.292   292-3245  AudioFlinger            audioserver                          E  createTrack() getOutputForAttr() return error -38 or invalid output handle
2025-02-06 15:06:53.293   292-6220  FMQ                     audioserver                          E  grantorIdx must be less than 3
2025-02-06 15:06:53.293  1200-6030  IAudioFlinger           com.demo.music                    E  createTrack returned error -38
2025-02-06 15:06:53.293  1200-6030  AudioTrack              com.demo.music                    E  createTrack_l(60): AudioFlinger could not create track, status: -38 output 0
2025-02-06 15:06:53.293  1200-6030  AudioTrack              com.demo.music                    W  restoreTrack_l(60): failed status -38, retries 3

createTrack()出现异常,说明是output播放阶段出现的问题,按照createTrack的调用栈一路加日志调试,发现关键原因:当****remoteSubmix与其他设备共存时,****AudioPolicyManager::getOutputsForDevices 返回结果为空,导致后续createTrack异常,原因参考注释:

/services/audiopolicy/managerdefault/AudioPolicyManager.cpp

SortedVector<audio_io_handle_t> AudioPolicyManager::getOutputsForDevices(
            const DeviceVector &devices,
            const SwAudioOutputCollection& openOutputs)
{
    SortedVector<audio_io_handle_t> outputs;

    ALOGVV("%s() devices %s", __func__, devices.toString().c_str());
    for (size_t i = 0; i < openOutputs.size(); i++) {
        ALOGVV("output %zu isDuplicated=%d device=%s",
                i, openOutputs.valueAt(i)->isDuplicated(),
                openOutputs.valueAt(i)->supportedDevices().toString().c_str());
      //此处supportsAllDevices(devices)全部都返回false,outputs没有add设备,空集合返回
        if (openOutputs.valueAt(i)->supportsAllDevices(devices)
                && openOutputs.valueAt(i)->devicesSupportEncodedFormats(devices.types())) {
            ALOGVV("%s() found output %d", __func__, openOutputs.keyAt(i));
            outputs.add(openOutputs.keyAt(i));
        }
    }
      //返回了空集合
    return outputs;
}

日志打印到此处,播放链路的关键问题已经定位,期间尝试了几种修改supportsAllDevices的方式,比如:

●方式1:修改audio_policy_configuration.xml,让output设备可以支持remotesubmix

●方式2:修改supportsAllDevices对应逻辑,不需要supportsAll,只要supportsAny就行

过程中使用了方式2进行解决,出现新的情况:录音时蓝牙耳机可以正常播放,但是录音出来的数据为空!由于修改的代码最终不需要,这里就不贴了。

1.2 从录音入手解决

既然录音时蓝牙耳机已经可以正常播放,问题需要调整方向从录音链路入手。录音链路问题排查比播放链路更棘手,因为从logcat日志并没有找到任何E级别或者error相关的信息。唯一比较明显的突破口是“原先系统内录时如果通过设备自带喇叭是可以正常播放的,也能正常录制,但是兼容remoteSubmix设备之后自带喇叭虽然能播放,但也无法录制”,于是一步步增加调试日志一行行对比“可以正常录音”与“无法正常录音”日志区别,最终在AudioPolicyManager::checkOutputsForDevice找到关键线索,相关改动如下:

services/audiopolicy/managerdefault/AudioPolicyManager.cpp

diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManage
r.cpp
index a592dea65c..3ece410e46 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -4881,11 +4881,19 @@ status_t AudioPolicyManager::checkOutputsForDevice(const sp<DeviceDescriptor>& d
                         audio_io_handle_t duplicatedOutput = AUDIO_IO_HANDLE_NONE;

                         //TODO: configure audio effect output stage here
+                        DeviceVector devices = mEngine->getOutputDevicesForAttributes(attributes_initializer(AUDIO_USAGE_MEDIA
), nullptr, false /*fromCache*/);
+                        sp<SwAudioOutputDescriptor> bestOutput = getOutputsForDevicesAnyNoDuplicate(devices, mOutputs);
+                        if (bestOutput != nullptr) {
+                            ALOGI("AR# bestOutput!!:%s", bestOutput->devices().toString().c_str());
+                        } else {
+                            bestOutput = mPrimaryOutput;
+                            ALOGI("AR# hasPrimaryOutput!");
+                        }

                         // open a duplicating output thread for the new output and the primary output
                         sp<SwAudioOutputDescriptor> dupOutputDesc =
                                 new SwAudioOutputDescriptor(NULL, mpClientInterface);
-                        status_t status = dupOutputDesc->openDuplicating(mPrimaryOutput, desc,
+                        status_t status = dupOutputDesc->openDuplicating(bestOutput, desc,
                                                                          &duplicatedOutput);

主要改动逻辑是:修改替换mPrimaryOutput成当前最佳输出output设备进行duplicate操作。至此,播放链路和录音链路两处最关键的改动都已经完成。

1.3 改动总结

综上,Android11系统内录支持蓝牙/功能等通道的关键改动涉及播放和录音两个链路:

1.播放链路,Engine::getDevicesForStrategyInt返回当前所有设备时,允许remoteSubmix设备与蓝牙/功能等设备共存

2.录音链路,AudioPolicyManager::checkOutputsForDevice进行output通道duplicate操作时,替换为当前主输出通道,而不是固定的PrimaryOutput。当进行duplicate操作时,复制出来的虚拟设备会增加AUDIO_DEVICE_OUT_REMOTE_SUBMIX类型的支持,所以前面提到的supportsAllDevices问题也一并解决了。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容