Android適配音頻外設&音頻基礎知識

監(jiān)聽USB音頻外設的插拔/開關機

Android系統(tǒng)監(jiān)聽音頻設備變化的方法

對于Android常見的音頻設備,有下面這個回調,可以監(jiān)聽到音頻設備列表的變化。但是經(jīng)過測試,音頻外設插拔時是沒有這些回調事件發(fā)出來的。

image.png

如何監(jiān)聽USB音頻外設的插拔

比較容易想到通過監(jiān)聽USB設備連接狀態(tài)的廣播,實現(xiàn)感知USB音頻外設的插拔/開關機。代碼如下:

   fun initUsbAudioDeviceList(): ArrayList<AudioInfo> {
        val iFilter = IntentFilter()
//        iFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
        iFilter.addAction(AudioManager.ACTION_HEADSET_PLUG)
        iFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
        iFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)

        val mBroadcastReceiver = USBBroadCastReceiver()
        context.registerReceiver(
            mBroadcastReceiver, iFilter
        )
        return updateUsbAudioDevices()
    }

切換音頻設備

Android應用層如何切換音頻設備

Android應用層切換音頻路由的方法

Android系統(tǒng)內部有自己的音頻路由策略,對應用層開放的,能夠改變系統(tǒng)音頻路由設備的方法,有以下兩個,在AudioManager中:

/**
 * Sets the speakerphone on or off.
 * <p>
 * This method should only be used by applications that replace the platform-wide
 * management of audio settings or the main telephony application.
 *
 * @param on set <var>true</var> to turn on speakerphone;
 *           <var>false</var> to turn it off
 */
public void setSpeakerphoneOn(boolean on){
    final IAudioService service = getService();
    try {
        service.setSpeakerphoneOn(on);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
/*
 * @see #stopBluetoothSco()
 * @see #ACTION_SCO_AUDIO_STATE_UPDATED
 */
public void startBluetoothSco(){
    final IAudioService service = getService();
    try {
        service.startBluetoothSco(mICallBack,
                getContext().getApplicationInfo().targetSdkVersion);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

Android切換音頻路由方法使用:

切換到藍牙耳機

audioManager.setSpeakerphoneOn(false);
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);

切換到有線耳機

audioManager.setSpeakerphoneOn(false);
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);

切換到聽筒

audioManager.setSpeakerphoneOn(false);
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);

切換到揚聲器

audioManager.setSpeakerphoneOn(true);
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);

Android系統(tǒng)音頻路由介紹

系統(tǒng)會根據(jù)不同的音頻流類型選擇不同的音頻路由策略,然后根據(jù)音頻路由策略和當前的硬件狀態(tài)路由到合適的設備進行播放。

音頻流類型

AudioSystem.java

/** Used to identify the volume of audio streams for phone calls */
public static final int STREAM_VOICE_CALL = 0;
/** Used to identify the volume of audio streams for system sounds */
public static final int STREAM_SYSTEM = 1;
/** Used to identify the volume of audio streams for the phone ring and message alerts */
public static final int STREAM_RING = 2;
/** Used to identify the volume of audio streams for music playback */
public static final int STREAM_MUSIC = 3;
/** Used to identify the volume of audio streams for alarms */
public static final int STREAM_ALARM = 4;
/** Used to identify the volume of audio streams for notifications */
public static final int STREAM_NOTIFICATION = 5;
/** Used to identify the volume of audio streams for phone calls when connected on bluetooth */
public static final int STREAM_BLUETOOTH_SCO = 6;
/** Used to identify the volume of audio streams for enforced system sounds in certain
 * countries (e.g camera in Japan) */
@UnsupportedAppUsage
public static final int STREAM_SYSTEM_ENFORCED = 7;
/** Used to identify the volume of audio streams for DTMF tones */
public static final int STREAM_DTMF = 8;
/** Used to identify the volume of audio streams exclusively transmitted through the
 *  speaker (TTS) of the device */
public static final int STREAM_TTS = 9;
/** Used to identify the volume of audio streams for accessibility prompts */
public static final int STREAM_ACCESSIBILITY = 10;

一般通話音頻播放是STREAM_VOICE_CALL,鈴聲或者提示音播放是STREAM_MUSIC。

音頻流類型與路由策略的對應關系

AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy(
        AudioSystem::stream_type stream) {
    // stream to strategy mapping
    switch (stream) {
    case AudioSystem::VOICE_CALL:
    case AudioSystem::BLUETOOTH_SCO:
        return STRATEGY_PHONE;
    case AudioSystem::RING:
    case AudioSystem::ALARM:
        return STRATEGY_SONIFICATION;
    case AudioSystem::NOTIFICATION:
        return STRATEGY_SONIFICATION_RESPECTFUL;
    case AudioSystem::DTMF:
        return STRATEGY_DTMF;
    default:
        ALOGE("unknown stream type");
    case AudioSystem::SYSTEM:
        // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs
        // while key clicks are played produces a poor result
    case AudioSystem::TTS:
    case AudioSystem::MUSIC:
        return STRATEGY_MEDIA;
    case AudioSystem::ENFORCED_AUDIBLE:
        return STRATEGY_ENFORCED_AUDIBLE;
    }
}

根據(jù)音頻路由策略選擇設備

audio_devices_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy,
                                                             bool fromCache)
{
    uint32_t device = AUDIO_DEVICE_NONE;
    if (fromCache) {
        ALOGVV("getDeviceForStrategy() from cache strategy %d, device %x",
              strategy, mDeviceForStrategy[strategy]);
        return mDeviceForStrategy[strategy];
    }
    switch (strategy) {
    case STRATEGY_SONIFICATION_RESPECTFUL:
        ...
        break;
    case STRATEGY_DTMF:
       ...
        // when in call, DTMF and PHONE strategies follow the same rules
        // FALL THROUGH
    case STRATEGY_PHONE:
        // for phone strategy, we first consider the forced use and then the available devices by order
        // of priority
        ...

    case STRATEGY_SONIFICATION:
        ...
        // FALL THROUGH
    case STRATEGY_ENFORCED_AUDIBLE:
       ...
        // The second device used for sonification is the same as the device used by media strategy
        // FALL THROUGH
    case STRATEGY_MEDIA: {
        ...
         break;
    default:
        ALOGW("getDeviceForStrategy() unknown strategy: %d", strategy);
        break;
    }
    ALOGVV("getDeviceForStrategy() strategy %d, device %x", strategy, device);
    return device;
}

STRATEGY_MEDIA:

 case STRATEGY_MEDIA: {
        uint32_t device2 = AUDIO_DEVICE_NONE;
        if (strategy != STRATEGY_SONIFICATION) {
            // no sonification on remote submix (e.g. WFD)
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_REMOTE_SUBMIX;
        }
        if ((device2 == AUDIO_DEVICE_NONE) &&
                mHasA2dp && (mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
                (getA2dpOutput() != 0) && !mA2dpSuspended) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
            if (device2 == AUDIO_DEVICE_NONE) {
                device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
            }
            if (device2 == AUDIO_DEVICE_NONE) {
                device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
            }
        }
        if (device2 == AUDIO_DEVICE_NONE) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
        }
        if (device2 == AUDIO_DEVICE_NONE) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADSET;
        }
        if (device2 == AUDIO_DEVICE_NONE) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
        }
        if (device2 == AUDIO_DEVICE_NONE) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
        }
        if (device2 == AUDIO_DEVICE_NONE) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
        }
        if ((device2 == AUDIO_DEVICE_NONE) && (strategy != STRATEGY_SONIFICATION)) {
            // no sonification on aux digital (e.g. HDMI)
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
        }
        if ((device2 == AUDIO_DEVICE_NONE) &&
                (mForceUse[AudioSystem::FOR_DOCK] == AudioSystem::FORCE_ANALOG_DOCK)) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
        }
        if (device2 == AUDIO_DEVICE_NONE) {
            device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
        }
        // device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION or
        // STRATEGY_ENFORCED_AUDIBLE, AUDIO_DEVICE_NONE otherwise
        device |= device2;
        if (device) break;
        device = mDefaultOutputDevice;
        if (device == AUDIO_DEVICE_NONE) {
            ALOGE("getDeviceForStrategy() no device found for STRATEGY_MEDIA");
        }
        } break;

STRATEGY_PHONE:

 case STRATEGY_PHONE:
        // for phone strategy, we first consider the forced use and then the available devices by order
        // of priority
        switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) {
        case AudioSystem::FORCE_BT_SCO:
            if (!isInCall() || strategy != STRATEGY_DTMF) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
                if (device) break;
            }
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
            if (device) break;
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO;
            if (device) break;
            // if SCO device is requested but no SCO device is available, fall back to default case
            // FALL THROUGH
        default:    // FORCE_NONE
            // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
            if (mHasA2dp && !isInCall() &&
                    (mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
                    (getA2dpOutput() != 0) && !mA2dpSuspended) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
                if (device) break;
            }
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
            if (device) break;
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADSET;
            if (device) break;
            if (mPhoneState != AudioSystem::MODE_IN_CALL) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
                if (device) break;
            }
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_EARPIECE;
            if (device) break;
            device = mDefaultOutputDevice;
            if (device == AUDIO_DEVICE_NONE) {
                ALOGE("getDeviceForStrategy() no device found for STRATEGY_PHONE");
            }
            break;
        case AudioSystem::FORCE_SPEAKER:
            // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to
            // A2DP speaker when forcing to speaker output
            if (mHasA2dp && !isInCall() &&
                    (mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
                    (getA2dpOutput() != 0) && !mA2dpSuspended) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
                if (device) break;
            }
            if (mPhoneState != AudioSystem::MODE_IN_CALL) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
                if (device) break;
            }
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
            if (device) break;
            device = mDefaultOutputDevice;
            if (device == AUDIO_DEVICE_NONE) {
                ALOGE("getDeviceForStrategy() no device found for STRATEGY_PHONE, FORCE_SPEAKER");
            }
            break;
        }
    break;

可以看出,一般情況下(以上的 default: // FORCE_NONE邏輯),STRATEGY_MEDIA和STRATEGY_PHONE這兩種路由策略,一般情況下,都會按照優(yōu)先級順序 藍牙耳機 > 有線耳機 > USB外接音頻設備和其他外界設備 > 默認設置(聽筒/揚聲器)來選擇當前的音頻輸出設備。

而STRATEGY_PHONE路由策略下,選擇音頻設備邏輯,還會受mForceUse[AudioSystem::FOR_COMMUNICATION]的影響。且force use邏輯的優(yōu)先級高于默認的根據(jù)設備可用性按順序選擇的邏輯。在FORCE_BT_SCO和FORCE_SPEAKER情況下,會優(yōu)先選擇 藍牙a2dp揚聲器/揚聲器 播放。

那么mForceUse中的設置又是哪來的呢?AudioManager-->setSpeakerphoneOn這個方法,就是會影響mForceUse中數(shù)據(jù)的其中一個方法,下面以這個方法為例分析。

舉例setSpeakerphoneOn如何影響音頻路由

該方法調用鏈路:

image1.png

AudioPolicyManagerBase::setForceUse源碼:

void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config)
{
    ALOGV("setForceUse() usage %d, config %d, mPhoneState %d", usage, config, mPhoneState);
    bool forceVolumeReeval = false;
    switch(usage) {
    case AudioSystem::FOR_COMMUNICATION:
        if (config != AudioSystem::FORCE_SPEAKER && config != AudioSystem::FORCE_BT_SCO &&
            config != AudioSystem::FORCE_NONE) {
            ALOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config);
            return;
        }
        forceVolumeReeval = true;
        mForceUse[usage] = config;
        break;
    case AudioSystem::FOR_MEDIA:
        if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP &&
            config != AudioSystem::FORCE_WIRED_ACCESSORY &&
            config != AudioSystem::FORCE_ANALOG_DOCK &&
            config != AudioSystem::FORCE_DIGITAL_DOCK && config != AudioSystem::FORCE_NONE &&
            config != AudioSystem::FORCE_NO_BT_A2DP) {
            ALOGW("setForceUse() invalid config %d for FOR_MEDIA", config);
            return;
        }
        mForceUse[usage] = config;
        break;
    case AudioSystem::FOR_RECORD:
        if (config != AudioSystem::FORCE_BT_SCO && config != AudioSystem::FORCE_WIRED_ACCESSORY &&
            config != AudioSystem::FORCE_NONE) {
            ALOGW("setForceUse() invalid config %d for FOR_RECORD", config);
            return;
        }
        mForceUse[usage] = config;
        break;
    case AudioSystem::FOR_DOCK:
        if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK &&
            config != AudioSystem::FORCE_BT_DESK_DOCK &&
            config != AudioSystem::FORCE_WIRED_ACCESSORY &&
            config != AudioSystem::FORCE_ANALOG_DOCK &&
            config != AudioSystem::FORCE_DIGITAL_DOCK) {
            ALOGW("setForceUse() invalid config %d for FOR_DOCK", config);
        }
        forceVolumeReeval = true;
        mForceUse[usage] = config;
        break;
    case AudioSystem::FOR_SYSTEM:
        if (config != AudioSystem::FORCE_NONE &&
            config != AudioSystem::FORCE_SYSTEM_ENFORCED) {
            ALOGW("setForceUse() invalid config %d for FOR_SYSTEM", config);
        }
        forceVolumeReeval = true;
        mForceUse[usage] = config;
        break;
    default:
        ALOGW("setForceUse() invalid usage %d", usage);
        break;
    }
    // check for device and output changes triggered by new force usage
   ...
    }
}

這里將設置保存到mForceUse 后,在路由策略選擇音頻設備方法AudioPolicyManagerBase::getDeviceForStrategy中,會消費這個數(shù)據(jù),如果是強制切換到揚聲器,便會優(yōu)先路由到揚聲器或者外接音頻設備。

類似的,AudioManager的setBluetoothScoOn方法,也是通過設置mForceUse,實現(xiàn)對音頻設備的強制切換。

如何在系統(tǒng)內置音頻設備和外接的音頻設備之間實現(xiàn)切換?

根據(jù)Android系統(tǒng)默認的音頻路由策略,當Android設備外接了音頻設備時,外接音頻設備的優(yōu)先級大于默認的設備,因此會優(yōu)先路由到外接的設備;
如果在外接了音頻的情況下想切回到自帶的揚聲器播放,可以嘗試AudioManager的setSpeakerPhoneOn方法,如果這個方法無效,基本只能依賴廠商修改固件中的音頻路由規(guī)則來解決。

調節(jié)音量

Android調節(jié)音量方式

AudioManager->setStreamVolume


public void setStreamVolume(int streamType, int index, int flags) {
    final IAudioService service = getService();
    try {
        service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

AudioManager會調用到AudioService中去執(zhí)行音量的調節(jié),在AudioService中,有一個內部類VolumeStreamState,這個類包含了一個流類型的所有音量信息;在AudioService中維護了一個VolumeStreamState類型的數(shù)組mStreamStates,對不同流類型的音量分別管理。AudioService負責保存音量信息,并將音量傳遞給AudioFlinger,使得音量設置對硬件生效。

那么不同流類型的音量控制一定是分開的嗎?也不一定。Android系統(tǒng)中與音量控制別名的設計,比如

音量控制別名數(shù)組mStreamVolumeAlias,存儲了與AudioSystem類中11種流類型對應音量控制流的別名,在進行音量調節(jié)操作之前,會線通過這個別名數(shù)組拿到對應的StreamType,然后在進行調節(jié)。因此,對于不同的流類型,但是別名相同的,音量調節(jié)會同步影響,而不是完全隔離。那么會中通話音量和鈴聲播放音量是不是同步生效取決于固件中音量控制的邏輯。


/* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
 * of another stream: This avoids multiplying the volume settings for hidden
 * stream types that follow other stream behavior for volume settings
 * NOTE: do not create loops in aliases!
 * Some streams alias to different streams according to device category (phone or tablet) or
 * use case (in call vs off call...). See updateStreamVolumeAlias() for more details.
 *  mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
 *  (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and
 *  STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/
private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
    AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
    AudioSystem.STREAM_RING,            // STREAM_SYSTEM
    AudioSystem.STREAM_RING,            // STREAM_RING
    AudioSystem.STREAM_MUSIC,           // STREAM_MUSIC
    AudioSystem.STREAM_ALARM,           // STREAM_ALARM
    AudioSystem.STREAM_RING,            // STREAM_NOTIFICATION
    AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
    AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
    AudioSystem.STREAM_RING,            // STREAM_DTMF
    AudioSystem.STREAM_MUSIC,           // STREAM_TTS
    AudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
};
private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
    AudioSystem.STREAM_MUSIC,       // STREAM_VOICE_CALL
    AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM
    AudioSystem.STREAM_MUSIC,       // STREAM_RING
    AudioSystem.STREAM_MUSIC,       // STREAM_MUSIC
    AudioSystem.STREAM_MUSIC,       // STREAM_ALARM
    AudioSystem.STREAM_MUSIC,       // STREAM_NOTIFICATION
    AudioSystem.STREAM_MUSIC,       // STREAM_BLUETOOTH_SCO
    AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM_ENFORCED
    AudioSystem.STREAM_MUSIC,       // STREAM_DTMF
    AudioSystem.STREAM_MUSIC,       // STREAM_TTS
    AudioSystem.STREAM_MUSIC        // STREAM_ACCESSIBILITY
};
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
    AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
    AudioSystem.STREAM_RING,            // STREAM_SYSTEM
    AudioSystem.STREAM_RING,            // STREAM_RING
    AudioSystem.STREAM_MUSIC,           // STREAM_MUSIC
    AudioSystem.STREAM_ALARM,           // STREAM_ALARM
    AudioSystem.STREAM_RING,            // STREAM_NOTIFICATION
    AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
    AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
    AudioSystem.STREAM_RING,            // STREAM_DTMF
    AudioSystem.STREAM_MUSIC,           // STREAM_TTS
    AudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
};
protected static int[] mStreamVolumeAlias;
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
        String caller, int uid) {
    if (DEBUG_VOL) {
        Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
                + ", calling=" + callingPackage + ")");
    }
    if (mUseFixedVolume) {
        return;
    }

    ensureValidStreamType(streamType);
    int streamTypeAlias = mStreamVolumeAlias[streamType];
    VolumeStreamState streamState = mStreamStates[streamTypeAlias];
    ...
  }

VOLUME_CHANGED_ACTION廣播是如何觸發(fā)的?

調節(jié)音量到觸發(fā)VOLUME_CHANGED_ACTION廣播的流程如下:

image2.png

也就是說,只有通過AudioManager.setStreamVolume 這個方法的調用調節(jié)了設備內置的/外接的音頻設備的音量,才會觸發(fā)這個廣播的發(fā)送。

播放音頻

MediaPlayer

可以播放一些比較長的音樂;MediaPlayer啟動延遲高,不適合播放短小間隔小的音頻,比如字母與數(shù)字單音頻組成的提示音;有播放狀態(tài)的回調。

if (mMediaPlayer != null) {
    mMediaPlayer.stop();
    mMediaPlayer.reset();
} else {
    mMediaPlayer = new MediaPlayer();
}
try {
    AssetFileDescriptor afd =
        ContextUtil.getContext().getResources().openRawResourceFd(resId);
    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mMediaPlayer.setLooping(true);
    mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
        afd.getLength());
    mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
            mMediaPlayer.start();
        }
    });
    mMediaPlayer.prepareAsync();
} catch (Exception e) {
    Logger.e("CallingRingPlayer", "start MediaPlayer error:" + e.getMessage());
}

SoundPool

適合一些短促、比較小的音頻,比如通知鈴聲、字母或數(shù)字的單音頻等;可以批量預加載資源,得到資源id,根據(jù)資源id來播放音頻(啟動延遲短);播放完成后沒有回調。

//初始化
SoundPool.Builder builder = new SoundPool.Builder();
builder.setMaxStreams(maxStreams);
AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
attrBuilder.setLegacyStreamType(M_STREAM_TYPE);
builder.setAudioAttributes(attrBuilder.build());
mSound = builder.build();

//進程啟動時預先load音頻文件
mSound.setOnLoadCompleteListener((soundPool, sampleId, status) -> {
    Logger.i(TAG, "ring file load success");
});
mInMeetingToneId = mSound.load(context, R.raw.ring_in_meeting, 1);
mCountdownToneId = mSound.load(context, R.raw.countdown_audio, 1);
mRemindToneId = mSound.load(context, R.raw.remind_audio, 1);
mOutMeetingToneId =
    mSound.load(context, R.raw.ring_out_meeting, 1);

 //play
 mSound.play(mInMeetingToneId, 1, 1, Integer.MAX_VALUE, 0, 1);

AudioTrack

AudioTrack 有兩種數(shù)據(jù)加載模式(MODE_STREAM 和 MODE_STATIC), 對應著兩種完全不同的使用場景。

  • MODE_STREAM:在這種模式下,通過 write 一次次把音頻數(shù)據(jù)寫到 AudioTrack 中。這和平時通過 write 調用往文件中寫數(shù)據(jù)類似,但這種方式每次都需要把數(shù)據(jù)從用戶提供的 Buffer 中拷貝到 AudioTrack 內部的 Buffer 中,在一定程度上會使引起延時。為解決這一問題,AudioTrack 就引入了第二種模式。

  • MODE_STATIC:在這種模式下,只需要在 play 之前通過一次 write 調用,把所有數(shù)據(jù)傳遞到 AudioTrack 中的內部緩沖區(qū),后續(xù)就不必再傳遞數(shù)據(jù)了。這種模式適用于像鈴聲這種內存占用較小、延時要求較高的文件。但它也有一個缺點,就是一次 write 的數(shù)據(jù)不能太多,否則系統(tǒng)無法分配足夠的內存來存儲全部數(shù)據(jù)。

// 初始化AudioTrack 
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, // 指定在流的類型  // STREAM_ALARM:警告聲  // STREAM_MUSCI:音樂聲,例如music等  // STREAM_RING:鈴聲  // STREAM_SYSTEM:系統(tǒng)聲音  // STREAM_VOCIE_CALL:電話聲音  

                samplerate,// 設置音頻數(shù)據(jù)的采樣率  AudioFormat.CHANNEL_CONFIGURATION_STEREO,// 設置輸出聲道為雙聲道立體聲  AudioFormat.ENCODING_PCM_16BIT,// 設置音頻數(shù)據(jù)塊是8位還是16位  
                mAudioMinBufSize, AudioTrack.MODE_STREAM);// 設置模式類型,在這里設置為流類型  // AudioTrack中有MODE_STATIC和MODE_STREAM兩種分類。  // STREAM方式表示由用戶通過write方式把數(shù)據(jù)一次一次得寫到audiotrack中。  // 這種方式的缺點就是JAVA層和Native層不斷地交換數(shù)據(jù),效率損失較大。  // 而STATIC方式表示是一開始創(chuàng)建的時候,就把音頻數(shù)據(jù)放到一個固定的buffer,然后直接傳給audiotrack,  // 后續(xù)就不用一次次得write了。AudioTrack會自己播放這個buffer中的數(shù)據(jù)。  // 這種方法對于鈴聲等體積較小的文件比較合適。  

mAudioTrack.play();  // 啟動
mAudioTrack.write();//數(shù)據(jù)寫入audiotrack中// 停止與釋放資源
mAudioTrack.stop();
mAudioTrack.release();

AudioTrack只能播放已經(jīng)解碼的PCM流,不能多種格式的聲音文件,例如MP3,AAC,WAV,OGG,MIDI等。因為wav格式的音頻文件大部分都是PCM流,AudioTrack不創(chuàng)建解碼器,所以可播放不需要解碼的wav文件。

一般rtc的通話語音使用的這種方式。

Android音頻架構

AudioRcorder和AudioTrack是Audio系統(tǒng)對外提供API類,AudioRcorder主要用于完成音頻數(shù)據(jù)的采集,而AudioTrack則是負責音頻數(shù)據(jù)的輸出。AudioFlinger管理著系統(tǒng)中的輸入輸出音頻流,并承擔著音頻數(shù)據(jù)的混合,通過讀寫Audio硬件實現(xiàn)音頻數(shù)據(jù)的輸入輸出功能;AudioPolicyService是Audio系統(tǒng)的策略控制中心,掌管系統(tǒng)中聲音設備的選擇和切換、音量控制等。

Android音頻控制架構圖如下:

image3.png

在Android音頻框架中,主要以下面部分組成:

Application:音頻應用,如音樂播放器,錄音機,收音機等。

Framework java層:

  • AudioTrack:負責回放數(shù)據(jù)的輸出,屬于應用框架 API 類

  • AudioRecord:負責錄音數(shù)據(jù)的采集,屬于應用框架 API 類

  • AudioSystem: 負責音頻事務的綜合管理,屬于應用框架 API 類

Framework Libraries:

  • AudioTrack:負責回放數(shù)據(jù)的輸出,屬于本地框架 API 類

  • AudioRecord:負責錄音數(shù)據(jù)的采集,屬于本地框架 API 類

  • AudioSystem: 負責音頻事務的綜合管理,屬于本地框架 API 類

  • AudioPolicyService:音頻策略的制定者,負責音頻設備切換的策略抉擇、音量調節(jié)策略等

  • AudioFlinger:音頻策略的執(zhí)行者,負責輸入輸出流設備的管理及音頻流數(shù)據(jù)的處理傳輸

音頻播放數(shù)據(jù)流向圖如下:

image4.png

其他問題解決分享:

問題一:連接音頻外設時,偶現(xiàn)入會提示音播放不出來

原因:會中只有自己一個人或者會中其他人都mute的情況下,有新的人入會容易出現(xiàn), 因為這時候系統(tǒng)的音頻通道被關閉。被關閉的原因是Google 考慮手機低功耗設計了策略:如果3s沒有app播放音頻就會關閉硬件。下次有app播放才會打開硬件,重新打開的時候需要重新初始化音頻播放通道,包括USB驅動、音頻驅動、音頻應用軟件等的初始化,這個耗時較長,導致初始化期間的音頻播放不出來,而入會提示音時長總共就只有一兩百ms,便完全聽不到了。

如何修復:

預先播放一次音量為0的音頻,300ms后(一般音頻鏈路已經(jīng)初始化完成),再次正常播放需要播放的音頻。

    mSound.play(mInMeetingToneId, 0, 0, Integer.MAX_VALUE, 0, 1);
    mHandler.postDelayed(
        () -> mSound.play(mInMeetingToneId, 1, 1, Integer.MAX_VALUE, 0, 1), 300);

如果是系統(tǒng)應用的話,也可設置系統(tǒng)屬性,修改standby的時長解決 ro.audio.flinger_standbytime_ms。

總結下適配音頻外設坑比較多的原因:

(一)Android原生支持耳機這樣的音頻外設,可以調節(jié)音量,需要考慮Android主機和耳機之間的互操作性和一致性。在業(yè)務適配場景下,除了保證Android設備和外界設備之間的互操作性之外,還需要考慮外界設備狀態(tài)和業(yè)務一致性。

比如:

  • 設置頁面/會中頁面的音量顯示、音頻外設實際的音量是否一致

  • 開啟麥克風的時候,業(yè)務的UI展示和音頻外設實際的設置是否一致

  • 音頻設備切換的時候,業(yè)務展示選擇設備的與系統(tǒng)實際路由的是否一

(二)外接音頻設備之后音頻播放鏈路更長,可能產生延時導致體驗問題

(三)外接音頻設備(甚至可能多個),支持隨意切換,一般需要廠商修改固件支持

參考:

從上往下認識安卓音頻框架

深入剖析Android音頻之AudioTrack - mfmdaoyou - 博客園

Android P Audio系統(tǒng)筆記:AudioPolicy& AudioFlinger初始化 | 航行學園

Android 音頻系統(tǒng):從 AudioTrack 到 AudioFlinger_zhuyong006的博客-CSDN博客

Android音頻路由策略_zhuyong006的博客-CSDN博客_android 音頻路由

Android深入淺出之Audio第三部分Audio Policy[1] - innost - 博客園

Android音頻子系統(tǒng),Audiopolicyservice音頻策略的制定(五)_lin-0410的博客-CSDN博客

Android 音量系統(tǒng)分析 - 掘金

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容