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

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

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

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

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

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

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

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

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

Пожалуйста, объясни. Благодарю.

3 ответа

Решение

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

РЕДАКТИРОВАТЬ:

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

Попробуйте посмотреть исходный код 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();
}

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