Android音量调节参考一

基于android 9平台分析。
在Android系统中,默认的设备(phone等)音量都是分开控制的,这些包括媒体、铃声、闹铃、蓝牙、通话通过音频流来区别不同的音量类型。每种流类型都定义最大音量、最小音量及默认音量,Android 9定了了11中音频流类型:

流类型

//frameworks/base/media/java/android/media/AudioSystem.java
    public static final String[] STREAM_NAMES = new String[] {
        "STREAM_VOICE_CALL",
        "STREAM_SYSTEM",
        "STREAM_RING",
        "STREAM_MUSIC",
        "STREAM_ALARM",
        "STREAM_NOTIFICATION",
        "STREAM_BLUETOOTH_SCO",
        "STREAM_SYSTEM_ENFORCED",
        "STREAM_DTMF",
        "STREAM_TTS",
        "STREAM_ACCESSIBILITY"
    };

最大音量(音量等级)

//frameworks/base/services/core/java/com/android/server/audio/AudioService.java
   /** Maximum volume index values for audio streams */
    protected static int[] MAX_STREAM_VOLUME = new int[] {
        5,  // STREAM_VOICE_CALL
        7,  // STREAM_SYSTEM
        7,  // STREAM_RING
        15, // STREAM_MUSIC
        7,  // STREAM_ALARM
        7,  // STREAM_NOTIFICATION
        15, // STREAM_BLUETOOTH_SCO
        7,  // STREAM_SYSTEM_ENFORCED
        15, // STREAM_DTMF
        15, // STREAM_TTS
        15  // STREAM_ACCESSIBILITY
    };

根据音量曲线表,一般情况音量等级最大可以设置为100。但是,有些音频音量调节并不经过音箱曲线表,而是直接调用HAL层的set_volume,而HAL层对音量又做了类似“音量曲线”的转换。所有修改音量级别,可能会有以下问题:

1、调至15时音量已经最大,15级以上的音量等级无效。

比如amlogic T972将MAX_STREAM_VOLUME 调整为30等级,HAL层audio_hw.c对音量调节:out_set_volume()–>volume2Ms12DBGain()–>AmplToDb(),因15等级时DB值已经“够大”,再往上调音量变化不明显,修改如下:

Android音量调节参考一_第1张图片

 2、调节音量时音量过大导致输出波形失真。

因喇叭性能或功放电路的原因(最好改电路,否则产品声音小),CPU音量输出增益不能太大,否则引起波形失真。比如Mst358在HAL层audio_hw.c对音量调节:

Android音量调节参考一_第2张图片

 最小音量

//frameworks/base/services/core/java/com/android/server/audio/AudioService.java
    /** Minimum volume index values for audio streams */
    protected static int[] MIN_STREAM_VOLUME = new int[] {
        1,  // STREAM_VOICE_CALL
        0,  // STREAM_SYSTEM
        0,  // STREAM_RING
        0,  // STREAM_MUSIC
        1,  // STREAM_ALARM
        0,  // STREAM_NOTIFICATION
        0,  // STREAM_BLUETOOTH_SCO
        0,  // STREAM_SYSTEM_ENFORCED
        0,  // STREAM_DTMF
        0,  // STREAM_TTS
        1   // STREAM_ACCESSIBILITY
    };

设置最小音量的目的是有些音频不能单独设置为静音。

默认音量

//frameworks/base/media/java/android/media/AudioSystem.java
    public static int[] DEFAULT_STREAM_VOLUME = new int[] {
        4,  // STREAM_VOICE_CALL
        7,  // STREAM_SYSTEM
        0,  // STREAM_RING
        5, // STREAM_MUSIC
        0,  // STREAM_ALARM
        5,  // STREAM_NOTIFICATION
        7,  // STREAM_BLUETOOTH_SCO
        7,  // STREAM_SYSTEM_ENFORCED
        5, // STREAM_DTMF
        5, // STREAM_TTS
        5, // STREAM_ACCESSIBILITY
    };

音频流映射StreamAlias

不同设备的映射定义不同,系统中一共定义三种设备的音频流的映射,分别是STREAM_VOLUME_ALIAS_VOICE,STREAM_VOLUME_ALIAS_TELEVISION,STREAM_VOLUME_ALIAS_DEFAULT

StreamAlias存在的意义:流类型别名,某音频流的行为等同于另外一种音频流,可以将它映射为另一种音频流,比如AudioSystem.STREAM_RING用作AudioSystem.STREAM_MUSIC来处理。

StreamAlias实际使用的意义是,Android为了兼容各种设备,定义了尽可能多的音频流。但是,在有些简单的设备中,可能仅有一个喇叭,所以对音频操作没有必要区分音频流。所以通过StreamAlias,在手机和平板上实际上能调节的就是五种音量,在TV和就机顶盒之类设备能调节的仅仅一种音量即music,故如有需求需要统一音量的可以将当前的音频流改为TV类型。
调用AudioSsytem::getPlatformType()可知道系统是手机、平板或TV类型。

//frameworks/base/services/core/java/com/android/server/audio/AudioService.java
1.voice--具有语音功能的设备,电话等
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
};
2. television--电视机顶盒或投影设备
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
};
3. default--平板之类的设备
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
};

音量按键处理流程

Android音量调节参考一_第3张图片

 在Android平台上,音量键,主页键(home),都是全局按键,但是主页键是个例外不能被应用所捕获。

下面分析一下音量按键的流程,主要从framework层处理开始,至于EventHub 从驱动的/dev/input/event0获取按键信息到上抛属于Android input 系统方面的流程。

framework层接收音量按键

ViewRootImpl.processKeyEvent 处理Activity 上面收到的按键


        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            if (mUnhandledKeyManager.preViewDispatch(event)) {
                return FINISH_HANDLED;
            }

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }
			。。。。。
		}

从中可以看到mView.dispatchKeyEvent(event),完成将按键发送给Activity处理,由于每个Activity都是view的子类,所有这些按键将dispatchKeyEvent传递给onKeyDown:

   /**
     * Called to process key events.  You can override this to intercept all
     * key events before they are dispatched to the window.  Be sure to call
     * this implementation for key events that should be handled normally.
     *
     * @param event The key event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        final int keyCode = event.getKeyCode();
        if (keyCode == KeyEvent.KEYCODE_MENU &&
                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
            return true;
        }

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }

注意这个方法可以被子类覆盖。
win.superDispatchKeyEvent()不处理音量键,调用根View的dispatchKeyEvent,进而调用ViewGroup的dispatchKeyEvent,如果都没处理,则调用View的dispatchKeyEvent:

    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }

        // Give any attached key listener a first crack at the event.
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }

        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

通过event.dispatch进一步分发

//framework/base/core/java/android/view/KeyEvent.java
    public final boolean dispatch(Callback receiver, DispatcherState state,
            Object target) {
        switch (mAction) {
            case ACTION_DOWN: {
                mFlags &= ~FLAG_START_TRACKING;
                if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
                        + ": " + this);
                boolean res = receiver.onKeyDown(mKeyCode, this);
                if (state != null) {
                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
                        if (DEBUG) Log.v(TAG, "  Start tracking!");
                        state.startTracking(this, target);
                    } else if (isLongPress() && state.isTracking(this)) {
                        try {
                            if (receiver.onKeyLongPress(mKeyCode, this)) {
                                if (DEBUG) Log.v(TAG, "  Clear from long press!");
                                state.performedLongPress(this);
                                res = true;
                            }
                        } catch (AbstractMethodError e) {
                        }
                    }
                }
                return res;
            }
		......
    }

上面只关注了ACTION_DOWN的处理。KeyEvent.dispatch通过receiver.onKeyDown将最终的按键消息发送给当前的Activity,而receiver即为KeyEvent.Callback的实现类(View的子类等等),至此如果上面上传应用处理完了就会返回,如果没有处理就会流向mFallbackEventHandler.dispatchKeyEvent(event),其实mFallbackEventHandler就是PhoneFallbackEventHandler,接着看 PhoneFallbackEventHandler.dispatchKeyEvent的处理流程
 

//framework/base/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
    public boolean dispatchKeyEvent(KeyEvent event) {

        final int action = event.getAction();
        final int keyCode = event.getKeyCode();

        if (action == KeyEvent.ACTION_DOWN) {
            return onKeyDown(keyCode, event);
        } else {
            return onKeyUp(keyCode, event);
        }
    }

进入onKeyDown

    boolean onKeyDown(int keyCode, KeyEvent event) {
        /* ****************************************************************************
         * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
         * See the comment in PhoneWindow.onKeyDown
         * ****************************************************************************/
        final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();

        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                handleVolumeKeyEvent(event);
                return true;
            }
		。。。。。
    }

    private void handleVolumeKeyEvent(KeyEvent keyEvent) {
        getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
                AudioManager.USE_DEFAULT_STREAM_TYPE);
    }

调用MeidaSessionManager::dispatchVolumeKeyEventAsSystemService()–>MediaSessionService::dispatchVolumeKeyEvent()

		/*将音量按钮事件分派给其中一个已注册的侦听器。
		如果有一个音量键长按侦听器,并且没有活动的全局优先级会话,长按将被发送到长按侦听器,而不是调整音量。
		如果没有注册长按监听器、没有活动的全局优先级会话,则进行音量调节*/
        @Override
        public void dispatchVolumeKeyEvent(String packageName, boolean asSystemService,
                KeyEvent keyEvent, int stream, boolean musicOnly) {
            ......
            try {
                synchronized (mLock) {
                	//如果没有注册长按监听器,则调用dispatchVolumeKeyEventLocked进行音量调节。
                    if (isGlobalPriorityActiveLocked()
                            || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
                        dispatchVolumeKeyEventLocked(packageName, pid, uid, asSystemService,
                                keyEvent, stream, musicOnly);
                    } else {
                        // TODO: Consider the case when both volume up and down keys are pressed
                        //       at the same time.
                        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
                            if (keyEvent.getRepeatCount() == 0) {
                                // Keeps the copy of the KeyEvent because it can be reused.
                                mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
                                        KeyEvent.obtain(keyEvent);
                                mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
                                mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
                                mHandler.sendMessageDelayed(
                                        mHandler.obtainMessage(
                                                MessageHandler.MSG_VOLUME_INITIAL_DOWN,
                                                mCurrentFullUserRecord.mFullUserId, 0),
                                        mLongPressTimeout);
                            }
                            if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
                                mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
                                if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
                                    dispatchVolumeKeyLongPressLocked(
                                            mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
                                    // Mark that the key is already handled.
                                    mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
                                }
                                dispatchVolumeKeyLongPressLocked(keyEvent);
                            }
                        } else { // if up
				......
        }

继续dispatchVolumeKeyEventLocked()–>dispatchAdjustVolumeLocked()


        private void dispatchAdjustVolumeLocked(String packageName, int pid, int uid,
                boolean asSystemService, int suggestedStream, int direction, int flags) {
            MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
                    : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();

            boolean preferSuggestedStream = false;
            if (isValidLocalStreamType(suggestedStream)
                    && AudioSystem.isStreamActive(suggestedStream, 0)) {
                preferSuggestedStream = true;
            }

            if (session == null || preferSuggestedStream) {
                // Execute mAudioService.adjustSuggestedStreamVolume() on
                // handler thread of MediaSessionService.
                // This will release the MediaSessionService.mLock sooner and avoid
                // a potential deadlock between MediaSessionService.mLock and
                // ActivityManagerService lock.
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            String packageName = getContext().getOpPackageName();
                            mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
                                    flags, packageName, TAG);
                        } 
                    }
                });
            } else {
                session.adjustVolume(packageName, pid, uid, null, asSystemService,
                        direction, flags, true);
            }
        }

两种情况,一种调用mAudioService.adjustSuggestedStreamVolume(),一种调用session.adjustVolume()。这里以adjustSuggestedStreamVolume()为例。

AudioService音量控制流程

adjustSuggestedStreamVolume 过渡到adjustStreamVolume,进入音量设置的主要流程,主要对流类型,设备,声音设备状态,步进大小进行判断处理,另外蓝牙设备音量和主设备音量进行了控制,最后通过mVolumePanel刷新界面音量显示,并且广播通过上层应用。
 


    protected void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, String caller, int uid) {
		。。。。。
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
            mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);

            if (isMuteAdjust) {
                boolean state;
                if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
                    state = !streamState.mIsMuted;
                } else {
                    state = direction == AudioManager.ADJUST_MUTE;
                }
                if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
                    setSystemAudioMute(state);
                }
                for (int stream = 0; stream < mStreamStates.length; stream++) {
                    if (streamTypeAlias == mStreamVolumeAlias[stream]) {
                        if (!(readCameraSoundForced()
                                    && (mStreamStates[stream].getStreamType()
                                        == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
                            mStreamStates[stream].mute(state);
                        }
                    }
                }
            } else if ((direction == AudioManager.ADJUST_RAISE) &&
                    !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
                Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
                mVolumeController.postDisplaySafeVolumeWarning(flags);
            } else if (streamState.adjustIndex(direction * step, device, caller)
                    || streamState.mIsMuted) {
                // Post message to set system volume (it in turn will post a
                // message to persist).
                if (streamState.mIsMuted) {
                    // Unmute the stream if it was previously muted
                    if (direction == AudioManager.ADJUST_RAISE) {
                        // unmute immediately for volume up
                        streamState.mute(false);
                    } else if (direction == AudioManager.ADJUST_LOWER) {
                        if (mIsSingleVolume) {
                            sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
                                    streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
                        }
                    }
                }
                //发送MSG_SET_DEVICE_VOLUME消息去设置系统音量,在handleMessage()被处理
                sendMsg(mAudioHandler,
                        MSG_SET_DEVICE_VOLUME,
                        SENDMSG_QUEUE,
                        device,
                        0,
                        streamState,
                        0);
            }

            int newIndex = mStreamStates[streamType].getIndex(device);
            // Check if volume update should be send to AVRCP
            //蓝牙音量的控制
            if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
                (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                synchronized (mA2dpAvrcpLock) {
                    if (mA2dp != null && mAvrcpAbsVolSupported) {
                        mA2dp.setAvrcpAbsoluteVolume(newIndex / 10);
                    }
                }
            }

            // Check if volume update should be send to Hearing Aid
            if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
                setHearingAidVolume(newIndex, streamType);
            }

            // Check if volume update should be sent to Hdmi system audio.
            //与HDMI输出相关
            if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
                setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
            }
            if (mHdmiManager != null) {
                ......
            }
        }
        int index = mStreamStates[streamType].getIndex(device);
        //UI更新系统音量
        sendVolumeUpdate(streamType, oldIndex, index, flags);
    }

蓝牙音量的控制

由上可知,如果当前连接了蓝牙也将对音量进行控制,mA2dp.adjustAvrcpAbsoluteVolume,暂不分析。

音频处理设置

音频处理由AudioHandler来进行,adjustStreamVolume做完相关处理后,通过sendMsg发送音量变化消息MSG_SET_DEVICE_VOLUME进入AudioHandler.handleMessage调用AudioHandler.setDeviceVolume


    private void setDeviceVolume(VolumeStreamState streamState, int device) {

        synchronized (VolumeStreamState.class) {
            // Apply volume
            streamState.applyDeviceVolume_syncVSS(device);

            // Apply change to all streams using this one as alias
            int numStreamTypes = AudioSystem.getNumStreamTypes();
            for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                if (streamType != streamState.mStreamType &&
                        mStreamVolumeAlias[streamType] == streamState.mStreamType) {
                    // Make sure volume is also maxed out on A2DP device for aliased stream
                    // that may have a different device selected
                    int streamDevice = getDeviceForStream(streamType);
                    if ((device != streamDevice) && mAvrcpAbsVolSupported &&
                            ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
                    }
                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
                }
            }
        }
        // Post a persist volume msg
        sendMsg(mAudioHandler,
                MSG_PERSIST_VOLUME,
                SENDMSG_QUEUE,
                device,
                0,
                streamState,
                PERSIST_DELAY);

    }

VolumeStreamState.applyDeviceVolume_syncVSS设置设备音量


        // must be called while synchronized VolumeStreamState.class
        public void applyDeviceVolume_syncVSS(int device) {
            int index;
            if (mIsMuted) {
                index = 0;
            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
                index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
            } else if ((device & mFullVolumeDevices) != 0) {
                index = (mIndexMax + 5)/10;
            } else if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
                index = (mIndexMax + 5)/10;
            } else {
                index = (getIndex(device) + 5)/10;
            }
            AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
        }

接着发送MSG_PERSIST_VOLUME消息通过handleMessage进入persistVolume,最终调用System.putIntForUser将用户设置的内容设置到Settings.system中。

AudioSystem调节音量

applyDeviceVolume处理完,AudioSystem就开始接着往下设置setStreamVolumeIndex,通过JNI调用到AudioSystem.cpp中setStreamVolumeIndex()。


status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream,
                                           int index,
                                           audio_devices_t device)
{
    const sp& aps = AudioSystem::get_audio_policy_service();
    if (aps == 0) return PERMISSION_DENIED;
    return aps->setStreamVolumeIndex(stream, index, device);
}

获取去音频策略服务(AudioPolicyService.cpp),进行设置
AudioPolicyService::setStreamVolumeIndex()–>AudioPolicyManager::setStreamVolumeIndex()。

你可能感兴趣的:(Android,audio,android,audio)