Как работает setMicrophoneMute ()? - PullRequest
30 голосов
/ 29 июля 2011

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

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

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

РЕДАКТИРОВАТЬ: Я заметил, что документация для этого метода говорит:

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

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

РЕДАКТИРОВАТЬ: Ответ ниже велик, но я все еще не понимаю:

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

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

Ответы [ 3 ]

23 голосов
/ 18 августа 2011

Чтобы уточнить ответ 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 с вызовом extern C для созданияAudioHardware, который подключает правильный 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. Основываясь на текущей реализации, кажется, что беззвучный режим работает, когда нет вызова, но могут быть специфические проблемы с оборудованием на платформах, которые мы здесь не изучали.

EDIT:

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

12 голосов
/ 29 июля 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();
}
8 голосов
/ 22 ноября 2015

Я обнаружил те же проблемы в 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);
...