Как работает setMicrophoneMute()?

Я пытался использовать Android AudioManager.setMicrophoneMute() без особого успеха. То есть он просто отказывается отключать микрофон, что бы я ни делал.

Я искал в Интернете некоторые подсказки и нашел несколько ссылок, сообщающих о подобном опыте:

Напрашивается вопрос: работает ли AudioManager.setMicrophoneMute() вообще? Это всего лишь метод-заглушка, ожидающий реализации в какой-то будущей версии Android? Если нет, то как это работает? Что мне нужно, чтобы заставить его работать? Каковы условия, которые заставляют его работать, как следует из его названия?

EDIT: я заметил, что в документации по этому методу говорится:

Этот метод следует использовать только приложениям, которые заменяют общеплатформенное управление настройками звука или основное приложение телефонии.

Что это значит? Зачем мне менять общеплатформенное управление? Мне действительно нужно это делать? Если да, то как мне это сделать?

EDIT: Ответ ниже отличный, но я все еще не понимаю:

  1. Как используется этот флаг (SET_MIC_MUTE в базе данных)?
  2. Когда этот флажок фактически отключает сигнал микрофона от схемы предварительного усилителя внутри телефона?
  3. Если он этого не делает, то кто это делает?
  4. Если ничего этого не делает, как ожидается, что этот «немой» будет работать?

Пожалуйста, объясни. Спасибо.


person ateiob    schedule 29.07.2011    source источник
comment
Я уверен, что вы должны иметь - но включили ли вы разрешения, проверенные checkAudioSettingsPermission?   -  person YetAnotherUser    schedule 03.08.2011
comment
@YetAnotherUser Я проверил документацию для checkAudioSettingsPermission и, за исключением возврата логического значения, нет никакой подсказки относительно того, что он делает и что он ожидает получить в параметре String. Я действительно не хочу менять вопрос с setMicrophoneMute() на checkAudioSettingsPermission, но знаете ли вы, что делает checkAudioSettingsPermission и что означает этот параметр String? +1   -  person ateiob    schedule 05.08.2011
comment
Вы можете указать, чего вы хотели достичь изначально. Похоже, вы немного сбились с курса. Вы, вероятно, не хотите заменять телефонное приложение, да и не должны (и, кстати, не можете без сертификата платформы).   -  person marsbear    schedule 09.08.2011
comment
@marsbear Я действительно не хочу ничего заменять. :) Все, что я хочу сделать, это отключить микрофон. Android имеет функцию (на самом деле 4 из них, как показано ниже единственным, кто был достаточно смел, чтобы ответить), которая утверждает, что делает это, но это не работает. Что мне делать? +1 за попытку помочь.   -  person ateiob    schedule 10.08.2011
comment
просто проверяю: есть ли у вас разрешение MODIFY_AUDIO_SETTINGS или RECORD_AUDIO в манифесте?   -  person Smugrik    schedule 17.08.2011
comment
@Smugrik Конечно, у меня есть разрешения MODIFY_AUDIO_SETTINGS и RECORD_AUDIO в манифесте. Я думал, что это подразумевается предоставлением первой ссылки, которая относится к этим настройкам.   -  person ateiob    schedule 17.08.2011


Ответы (3)


Чтобы уточнить ответ an00b:s выше и отредактированную версию вопроса, мы должны углубиться в исходный код. 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;
    ...
    }
}

и вызовы фактической реализации setMicMute в файле AudioFlinger. Следующий шаг — посмотреть на эту функцию:

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 для createAudioHardware, который подключает правильный AudioHardWare для платформы. Существуют также переключатели для использования оборудования на основе A2DP, общий для эмулятора и заглушки звука. Предполагается, что вы работаете на реальном устройстве, реализация которого во многом зависит от аппаратного обеспечения. Чтобы почувствовать это, мы можем использовать доступное аудиооборудование от 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, вызов отключения звука микрофона будет учитываться только в том случае, если вы не участвуете в вызове. Причина этого в том, что звук направляется через аналоговую полосу модулирующих сигналов, которая отвечает за регулировку мощности, усиление и другие функции. Во время вызова голосовой звук часто обрабатывается процессором аналоговой полосы частот и модема вместе и не направляется через процессор приложения. В этом случае вам может понадобиться пройти через процессор модема через RIL, чтобы отключить микрофон. Но поскольку это поведение зависит от оборудования, общего решения нет.

Чтобы дать краткую версию ваших 4 дополнительных вопросов:

  1. Флаг передается через несколько слоев кода, пока не окажется в аппаратно-зависимом микрофоне отключения звука.

  2. Микрофон отключается, когда выполняется аппаратный код, за исключением случаев, когда во время вызова по крайней мере на некоторых устройствах.

  3. Когда setMicrophoneMute не отключает звук микрофона, то есть во время разговора это можно сделать с помощью одного из API телефонии, я бы посоветовал изучить приложение для телефона.

  4. Судя по текущей реализации, отключение звука работает, когда не происходит вызов, но могут быть проблемы с аппаратным обеспечением на платформах, которые мы здесь не изучали.

ИЗМЕНИТЬ:

Еще немного покопался, и способ отправить команду отключения звука на процессор модема — через внутренний интерфейс телефона, который является частью пакета com.android.internal.telephony, который недоступен разработчикам SDK. Основываясь на комментарии, который вы видели, что эта функция должна использоваться только приложениями, которые заменяют управление звуком или исходное приложение телефонии, я бы предположил, что AudioManager.setMicrophoneMute() должен был всегда отключать микрофон независимо. Но поскольку другие приложения, вероятно, используют это, они добавили проверку в аппаратную реализацию, чтобы не испортить состояние телефонного приложения, которое отслеживает отключенные соединения, а также микрофон. Эта функция, вероятно, не работает так, как предполагалось, из-за деталей аппаратной реализации и того факта, что отключение звука — гораздо более сложная операция, чем можно было бы подумать при рассмотрении состояний вызовов.

person BMB    schedule 18.08.2011
comment
Вау! Я не мог ожидать лучшего ответа, чем этот. Ответ an00b был хорошей отправной точкой, но вы, безусловно, углубились в самый глубокий уровень, а это то, что мне нужно, чтобы понять, почему это не работает для меня, когда RecognitionListener слушает. То есть, если я пытаюсь позвонить mAudioManager.setMicrophoneMute(true) внутри RecognitionListener.onReadyForSpeech(), ничего не происходит: микрофон продолжает принимать речь, как будто Mute никогда не вызывался. И нет текущего вызова! Означает ли это, что RecognitionListener Google устанавливает AudioSystem::MODE_IN_CALL? - person ateiob; 18.08.2011
comment
Я только что нашел другую версию AudioHardware.cpp, которая реализует его еще более более сложный способ... И все, что я хотел, это просто ненадолго отключить микрофон, находясь в onReadyForSpeech()... Есть ли вообще способ сделать это? Что влечет за собой замена управления звуком? - person ateiob; 18.08.2011
comment
Мне нужно было бы снова немного покопаться в коде аудио, чтобы ответить на ваш вопрос о onReadyForSpeech(), но пока я бы сказал, что функция API AudioManager.setMicroPhoneMute() на самом деле не отражает сложность маршрутизации звука в современных смартфонах. . Это, вероятно, означает, что не существует универсального способа реализовать то, что вы хотите, поскольку каждая аппаратная платформа по-разному справляется с отключением микрофона. Я отредактирую этот комментарий, если найду способ. - person BMB; 19.08.2011
comment
Спасибо +1 еще раз. Если вы можете помочь мне найти правильный AudioHardware.cpp для Nexus One (особенно CyanogenMod 6) это было бы очень полезно. - person ateiob; 19.08.2011

Попробуйте просмотреть исходный код 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();
}
person an00b    schedule 29.07.2011
comment
Спасибо, но я все еще не понимаю, как это работает. Все, что я вижу, это то, что он проходит через множество слоев и оболочек, чтобы установить или снять флаг в какой-то базе данных. Но как этот флаг используется? Когда этот флажок фактически отключает сигнал микрофона от схемы предварительного усилителя внутри телефона? Если он этого не делает, то кто это делает? Если ничего этого не происходит, как ожидается, что этот режим будет работать? Пожалуйста, объясни. +1. - person ateiob; 01.08.2011

Я обнаружил те же проблемы на Samsung 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);
person alexwan02    schedule 22.11.2015
comment
MODE_IN_COMMUNICATION сделал свое дело, я пытался отключить/включить микрофон с помощью MODE_IN_CALL. Спасибо за помощь - person Adrian C.; 08.03.2016