setMicrophoneMute() 是如何工作的?

32

我一直在尝试使用Android的 AudioManager.setMicrophoneMute(),但并没有太大的成功。也就是说,无论我做什么,它都拒绝将麦克风静音。

我在网上搜索了一些线索,发现有几个参考文献报告了类似的经验:

这引出了一个问题: AudioManager.setMicrophoneMute()真的起作用吗?它只是一个存根方法,等待在某个未来的Android版本中实现吗?如果不是,它是如何工作的?我需要什么才能使它工作?使它按其名称暗示的方式工作的条件是什么?

编辑:我注意到此方法的文档说:

此方法应仅由替换音频设置的全平台管理或主要电话应用程序的应用程序使用。

这是什么意思?为什么我要替换全平台管理?我真的需要这样做吗?如果是这样,我该怎么做?

编辑:下面的答案很好,但我仍然不明白:

  1. 该标志(数据库中的SET_MIC_MUTE)如何使用?
  2. 这个标志什么时候会断开手机内部的前置放大电路与麦克风信号之间的连接?
  3. 如果它不这样做,谁会这样做?
  4. 如果没有什么可以做到这一点,这个“静音”如何工作?

请解释。谢谢。


1
我相信你一定已经这样做了,但是你是否启用了checkAudioSettingsPermission测试所需的权限呢? - YetAnotherUser
2
@YetAnotherUser 我检查了checkAudioSettingsPermission的“文档”,除了返回布尔值之外,没有任何线索说明它是做什么的,以及它期望在字符串参数中接收什么。我真的不想把问题从setMicrophoneMute()改为checkAudioSettingsPermission,但你知道checkAudioSettingsPermission做什么以及那个String参数的含义吗?+1 - ateiob
1
您可能希望概述最初想要实现的内容。听起来您有点偏离了轨道。您可能不希望替换电话应用程序,也不应该这样做(顺便说一下,如果没有平台证书,您是无法这样做的)。 - marsbear
2
@marsbear,我真的不想替换任何东西。 :) 我只是想静音麦克风。Android有一个功能(实际上有4个,如下所示,只有一个人勇敢地回答了),声称可以做到这一点,但它不起作用。我该怎么办?+1尝试帮助。 - ateiob
请确认您的清单文件中是否包含 MODIFY_AUDIO_SETTINGS 或 RECORD_AUDIO 权限。 - Smugrik
1
@Smugrik 当然我在清单文件中有 MODIFY_AUDIO_SETTINGS 和 RECORD_AUDIO 权限。我认为第一个链接提到了这些设置,这是显而易见的。 - ateiob
3个回答

23

要详细说明an00b上面的答案和编辑过的问题,我们需要深入挖掘源代码。IAudioflinger是与AudioFlinger服务交互的接口,调用

virtual status_t setMicMute(bool state)
{
    Parcel data, reply;
    data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
    data.writeInt32(state);
    remote()->transact(SET_MIC_MUTE, data, &reply);
    return reply.readInt32();
}

实际上,它是用于静音麦克风的Binder事务。Binder调用的接收端看起来像这样:

status_t BnAudioFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)  { 
    switch(code) {
        ...
        case SET_MIC_MUTE: {
            CHECK_INTERFACE(IAudioFlinger, data, reply);
            int state = data.readInt32();
            reply->writeInt32( setMicMute(state) );
            return NO_ERROR;
        } break;
    ...
    }
}

接下来需要查看这个函数:AudioFlinger.cpp中setMicMute的实际实现。

status_t AudioFlinger::setMicMute(bool state) {
    // check calling permissions
    if (!settingsAllowed()) {
        return PERMISSION_DENIED;
    }

    AutoMutex lock(mHardwareLock);
    mHardwareStatus = AUDIO_HW_SET_MIC_MUTE;
    status_t ret = mAudioHardware->setMicMute(state);
    mHardwareStatus = AUDIO_HW_IDLE;
    return ret;
}

这里有两个需要注意的地方。第一个是要能够静音麦克风,需要进行权限检查。在settingsAllowed中检查的权限是android.permission.MODIFY_AUDIO_SETTINGS,因此如上面的评论之一所提到的,静音麦克风的第一个要求是您的应用程序已声明需要此权限。另一个需要注意的是,我们现在调用硬件特定版本的setMicMute函数,使用mAudioHardware->setMicMute(state)。

更多关于硬件连接方式的信息,请研究文件AudioHardwareInterface.cpp。基本上,它最终会进入一个名为libhardware的外部C调用,以创建适合平台的正确AudioHardWare。还有针对使用基于A2DP的硬件、模拟器的通用硬件和stubbing audio的开关。假设您正在实际设备上工作,则实现非常依赖于硬件。为了对此有所感觉,我们可以使用Crespo(Nexus S)的可用音频硬件作为示例。

status_t AudioHardware::setMicMute(bool state) {
    LOGV("setMicMute(%d) mMicMute %d", state, mMicMute);
    sp<AudioStreamInALSA> spIn;
    {
        AutoMutex lock(mLock);
        if (mMicMute != state) {
            mMicMute = state;
            // in call mute is handled by RIL
            if (mMode != AudioSystem::MODE_IN_CALL) {
                spIn = getActiveInput_l();
            }
        }
    }

    if (spIn != 0) {
        spIn->standby();
    }

    return NO_ERROR;
}
根据这个例子,我们可以讨论智能手机中音频路由的实现。正如您在Crespo实现中所看到的,如果您正在通话中,则只有在没有通话时才会响应麦克风静音调用。原因是音频通过模拟基带路由,该基带处理功率调节、放大和其他事项。在通话中,声音通常由模拟基带和调制解调器CPU共同处理,而不是通过应用CPU进行路由。在这种情况下,您可能需要通过RIL通过调制解调器CPU来静音麦克风。但由于这种行为取决于硬件,因此没有通用解决方案。
回答您的四个附加问题:
1. 标志沿着几层代码传递,直到最终到达特定于硬件的静音麦克风。 2. 在特定于硬件的代码运行后,除了某些设备在通话中保持连接外,麦克风会被断开连接。 3. 当setMicrophoneMute无法静音麦克风时,例如在通话中,可能可使用其中一个电话API来完成。建议研究电话应用程序。 4. 基于当前实现,当没有通话时,静音似乎有效,但是在我们未研究的平台上可能存在特定于硬件的问题。
编辑:
经过进一步调查,向调制解调器CPU发送静音命令的方法是通过com.android.internal.telephony包中的内部Phone接口完成的,该接口不对SDK开发人员提供。基于您看到的评论,声音管理或原始电话应用程序的替代应用程序应使用此功能,我猜测AudioManager.setMicrophoneMute()应始终静音麦克风,而不管其他应用程序是否使用它们,因此在硬件实现中添加了检查,以避免破坏电话应用程序的状态,该应用程序跟踪静音连接以及麦克风。由于静音是一个比一开始考虑时更复杂的操作,并且涉及呼叫状态等,因此此函数现在可能无法按预期工作,这取决于硬件实现细节。

哇!我没想到会有比这更好的答案。an00b的回答是一个很好的起点,但你肯定深入到最深层次,这正是我需要理解为什么在RecognitionListener正在监听时它对我不起作用的原因。也就是说,如果我尝试在RecognitionListener.onReadyForSpeech()内调用mAudioManager.setMicrophoneMute(true),则什么也不会发生:麦克风继续接受语音,就好像从未调用过静音一样。而且没有通话正在进行中!这是否意味着Google的RecognitionListener设置了AudioSystem :: MODE_IN_CALL? - ateiob
我刚刚发现了另一个版本的AudioHardware.cpp,它以更加复杂的方式实现了它...而我想要的只是在onReadyForSpeech()期间简单地断开麦克风...有没有办法实现这一点?替换音频管理意味着什么? - ateiob
1
我需要再深入研究一下音频代码,才能回答你关于onReadyForSpeech()的问题。但目前为止,我会说似乎API函数AudioManager.setMicroPhoneMute()并不能真正反映现代智能手机中音频路由的复杂性。这可能意味着没有通用的方法来实现你想要的功能,因为每个硬件平台似乎都以不同的方式处理麦克风静音。如果我找到了方法,我会编辑这条评论。 - BMB
1
谢谢再次+1。如果您能帮我找到适用于Nexus One(特别是CyanogenMod 6)的正确的AudioHardware.cpp,我将不胜感激。 - ateiob

12

尝试查看AudioManager源代码:

public void setMicrophoneMute(boolean on){
    IAudioService service = getService();
    try {
        service.setMicrophoneMute(on);
    } catch (RemoteException e) {
        Log.e(TAG, "Dead object in setMicrophoneMute", e);
    }
}

将麦克风静音的任务委托给名为IAudioService的服务:

public void setMicrophoneMute(boolean on) {
    if (!checkAudioSettingsPermission("setMicrophoneMute()")) {
        return;
    }
    synchronized (mSettingsLock) {
        if (on != mMicMute) {
            AudioSystem.muteMicrophone(on);
            mMicMute = on;
        } 
    }
}

这将被委托给AudioSystem处理,而它似乎是在本地代码中实现的:

status_t AudioSystem::muteMicrophone(bool state) {
    const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
    if (af == 0) return PERMISSION_DENIED;
    return af->setMicMute(state);
}

而这将被委派给IAudioFlinger,如同在IAudioFlinger.cpp中所找到的:

virtual status_t setMicMute(bool state)
{
    Parcel data, reply;
    data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
    data.writeInt32(state);
    remote()->transact(SET_MIC_MUTE, data, &reply);
    return reply.readInt32();
}

2
谢谢,但我仍然不明白它是如何工作的。我只看到它通过许多层和包装器基本上在某个数据库中设置或清除标志。但是这个标志是如何被使用的?这个标志实际上什么时候会断开手机内部的前置放大电路中的麦克风信号?如果它没有这样做,那么谁会这样做?如果没有任何东西这样做,那么这个“静音”功能应该如何工作?请解释一下。+1。 - ateiob

9
我在三星Galaxy设备上发现了同样的问题,我通过使用MODE_IN_COMMUNICATION模式解决了它。
在AudioManager.java源代码中,它说:
  1. MODE_IN_CALL - 通话音频模式。建立电话通话。
  2. MODE_IN_COMMUNICATION - 通讯音频模式。建立音视频聊天或VoIP电话。
因为我使用第三方VOIP库,所以我使用了MODE_IN_COMMUNICATION并解决了这个问题。
AudioManager audioManager = (AudioManager)
context.getSystemService(Context.AUDIO_SERVICE);
// get original mode 
int originalMode = audioManager.getMode();
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
// change mute 
boolean state = !audioManager.isMicrophoneMute();
audioManager.setMicrophoneMute(state);
// set mode back 
audioManager.setMode(originalMode);

1
MODE_IN_COMMUNICATION解决了问题,我之前一直在尝试使用MODE_IN_CALL来静音/取消静音麦克风。感谢您的帮助。 - Adrian C.
不错的技巧,但在 Google Pixel 4a 上无法使用,因为它的 DeviceHAL 不断记录警告:"Device set_mic_mute: Function not implemented"。 - WebViewer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接