Android音量调节参考一

news2024/10/2 22:12:39

基于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值已经“够大”,再往上调音量变化不明显,修改如下:

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

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

 最小音量

//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平台上,音量键,主页键(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<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
    if (aps == 0) return PERMISSION_DENIED;
    return aps->setStreamVolumeIndex(stream, index, device);
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1293418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MATLAB】MVMD信号分解+FFT+HHT组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 MVMD信号分解FFTHHT组合算法是一种强大的分析方法&#xff0c;结合了变分模态分解(MVMD)、快速傅里叶变换(FFT)和希尔伯特-黄变换(HHT)。 首先&#xff0c;使用MVMD将原始信号分解成多个…

几分钟在Ubuntu搭建本地Emlog博客网站并发布至公网无需购买域名服务器

文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总结 前言 博客作为使…

Python-封装配置文件

Code [url] baidu http://www.baidu.com[value] send_value 百度[server] ip 220.181.111.188封装的格式可以套用 # 封装,类似函数调用 import configparserclass ReadConfigIni():def __init__(self,filename):self.cf configparser.ConfigParser()self.cf.read(filenam…

分布式搜索引擎03

1.数据聚合 聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如: 什么品牌的手机最受欢迎? 这些手机的平均价格、最高价格、最低价格? 这些手机每月的销售情况如何? 实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近…

[Linux] nginx配置的主配置文件

一、六个模块的作用 全局块&#xff1a;全局配置&#xff0c;对全局生效&#xff1b; events块&#xff1a;配置影响 Nginx 服务器与用户的网络连接&#xff1b; http块&#xff1a;配置代理&#xff0c;缓存&#xff0c;日志定义等绝大多数功能和第三方模块的配置&#xff1b;…

STM32 cubeMX 呼吸灯实验

文章代码使用 HAL 库。 文章目录 一、1.PWM原理二、LED 原理图三、使用cubemx 配置 led四、PWM 相关函数五、PWM占空比占空比计算六、PWM 呼吸灯重要代码总结 呼吸灯 一、1.PWM原理 PWM全称为脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09;&#xff0c;是一种常…

【Linux】进程通信之命名管道mkfifo

1.认识命名管道 匿名管道应用的一个限制就是只能在具有共同祖先&#xff08;具有亲缘关系&#xff09;的进程间通信。如果我们想在不相关的进程之间交换数据&#xff0c;可以使用FIFO文件来做这项工作&#xff0c;它经常被称为命名管道。命名管道是一种特殊类型的文件 2.在命…

华清远见嵌入式学习——QT——作业1

作业要求&#xff1a; 代码&#xff1a; ①&#xff1a;头文件 #ifndef LOGIN_H #define LOGIN_H#include <QWidget> #include <QLineEdit> //行编辑器类 #include <QPushButton> //按钮类 #include <QLabel> //标签类 #include <QM…

(五) Python 代理模式

文章目录 5.1 代理模式概述5.1.1 代理介绍5.1.2 代理模式的作用 5.2 代理模式的UML类图5.3 了解不同类型的代理5.3.1虚拟代理5.3.2 远程代理5.3.3 保护代理5.3.4 智能代理 5.4 现实世界中的代理模式5.5 代理模式的优点5.6 门面模式和代理模式之间的比较 5.1 代理模式概述 5.1.…

华为鸿蒙开发——开发及引用静态共享包(HAR)、应用配置文件

文章目录 简述一、创建HAR模块二、编译HAR模块三、应用配置文件&#xff08;Stage模型&#xff09;四、应用配置文件&#xff08;FA模型&#xff09;1、配置文件的内部结构&#xff08;1&#xff09;app&#xff08;2&#xff09;deviceConfig&#xff08;3&#xff09;module …

GDPU 数据结构 天码行空13

文章目录 一、【实验目的】二、【实验内容】三、实验源代码四、实验结果五、实验总结 一、【实验目的】 (1) 理解插入排序算法的实现过程&#xff1b; &#xff08;2&#xff09;理解不同排序算法的时间复杂度及适用环境&#xff1b; &#xff08;3&#xff09;了解算法性能…

高精度时钟芯片SD2405

概要 SD2405是一款非常优秀的RTC解决方案&#xff0c;为了能让用户在Arduino上有一款方便易用的时钟模块。该模块是一款内置晶振&#xff0c;支持IIC串行接口的高精度时钟模块&#xff1b;内置一次性工业级电池&#xff0c;可保证外部掉电的情况下&#xff0c;可以继续工作5~8…

华为数通方向HCIP-DataCom H12-831题库(多选题:161-180)

第161题 以下关于IS-IS路由渗透的描述,正确的有哪些项? A、若要配置Level-2区域的路由向Level-1区域渗透,则需要在Level-1设备上配置命令import-routeisis level_-2 into level_-1 B、缺省情况下,Level-2区域无Level-1区域的路由信息,需要通过在Level-1-2设备上配置impor…

3.PyTorch——常用神经网络层

import numpy as np import pandas as pd import torch as t from PIL import Image from torchvision.transforms import ToTensor, ToPILImaget.__version__2.1.13.1 图像相关层 图像相关层主要包括卷积层&#xff08;Conv&#xff09;、池化层&#xff08;Pool&#xff09;…

翻译: 生成式人工智能的经济潜力 第2部分行业影响 The economic potential of generative AI

麦肯锡报告 翻译: 生成式人工智能的经济潜力 第一部分商业价值 The economic potential of generative AI 1. 行业影响 在我们分析的63个使用案例中&#xff0c;生成式人工智能有潜力在各行各业创造2.6万亿至4.4万亿美元的价值。其确切影响将取决于各种因素&#xff0c;比如…

SpringBoot框架+原生HTML开发,基于云端SaaS服务方式的电子病历编辑器源码

一体化电子病历编辑器源码&#xff0c;电子病历系统 一体化电子病历系统基于云端SaaS服务的方式&#xff0c;采用B/S&#xff08;Browser/Server&#xff09;架构提供&#xff0c;覆盖了医疗机构电子病历模板制作到管理使用的整个流程。除实现在线制作内容丰富、图文并茂、功能…

MySQL主从复制(一主两从)架构搭建(阿里云服务器)

建立主机master 1.建立数据库master docker run --name master --restart always -p 3308:3306 -v /root/docker/volumes/etc/master:/etc/mysql -v /root/docker/volumes/var/lib/master:/var/lib/mysql -e MYSQL_ROOT_PASSWORDriCXT8zM -d mysql:latest 2.复制master的配置文…

讲一下maven的生命周期

Maven是一种强大的项目管理工具&#xff0c;它可以帮助开发者组织和管理项目的构建过程。Maven的生命周期指的是一系列的活动&#xff0c;包括如何创建、准备、构建和测试项目的过程。以下是对Maven生命周期的主要阶段的简要概述&#xff1a; 获取项目&#xff1a;在这个阶段&…

【开源】基于Vue+SpringBoot的智慧家政系统

项目编号&#xff1a; S 063 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S063&#xff0c;文末获取源码。} 项目编号&#xff1a;S063&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询家政服…

扩展卡尔曼滤波技术(Extended Kalman Filter,EKF)

一、概念介绍 卡尔曼滤波是一种高效率的递归滤波器(自回归滤波器), 它能够从一系列的不完全包含噪声的测量中&#xff0c;估计动态系统的状态&#xff0c;然而简单的卡尔曼滤波必须应用在符合高斯分布的系统中。 扩展卡尔曼滤波就是为了解决非线性问题&#xff0c;普通卡尔曼…