1.AudioService主要在java层主要有三个方面的作用:
(1) 音量管理
(2) 音频设备管理
(3) AudioFocus(音频焦点)机制

2.源码路径:
./frameworks/base/media/java/android/media/AudioManager.java
./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
./frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
./frameworks/base/services/core/java/com/android/server/audio/FocusRequester.java
./frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
./frameworks/base/media/java/android/media/AudioSystem.java
./frameworks/base/services/java/com/android/server/SystemServer.java
./frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.java

3.类图关系如下

由上图可知:
AudioService继承由IAudioService.aidl自动生成的IAudioService.Stub类,并实现IAudioService.Stub对应的相关方法,此处AudioService位于Bn端,即服务端。

AudioManager是AudioService在客户端的一个代理,位于Bp端。对AudioManager的一些方法调用最终几乎都会调用到AudioManager端。

AudioService的功能实现依赖AudioSystem类,AudioSystem.java没有实例化,它是java层到native层的代理。AudioService将通过它与AudioPolicyService以及AudioFlinger进行交互.

上面的图以及描述主要是参考了博主阿拉神农的博客,博客链接:https://blog.csdn.net/innost/article/details/47419025

3.1 音量管理
在android9.0 系统,音量按键的的音量管理主要由PhoneWindosManager服务来拦截按键的按下以及释放。当我们按下声音按键的时候UML流程图大概如下:

从上面的图中:
AudioService.java中:主要通过adjustStreamVolume来实现音量的控制

代码路径:./frameworks/base/services/core/java/com/android/server/audio/AudioService.javaprotected void adjustStreamVolume(int streamType, int direction, int flags,String callingPackage, String caller, int uid) {//(1)设置音量,通过Hanlder的方式.......sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);.......//(2)更新uisendVolumeUpdate(stream, index, index, flags);}

(1)设置音量

代码路径:./frameworks/base/services/core/java/com/android/server/audio/AudioService.javapublic void handleMessage(Message msg) {switch (msg.what) {//(1)上面的sendMsg,这里handler处理MSG_SET_DEVICE_VOLUME//setDeviceVolume最终调用AudioSystem的setStreamVolumeIndex进行音量控制case MSG_SET_DEVICE_VOLUME:setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);break;case MSG_SET_ALL_VOLUMES:setAllVolumes((VolumeStreamState) msg.obj);break;//(2)将音量值设置保留到SettingProvider中,方便开机进行读取设置音量case MSG_PERSIST_VOLUME:persistVolume((VolumeStreamState) msg.obj, msg.arg1);break;
......}

(2)UI更新
最终会调用 mController.volumeChanged(streamType, flags);来更新ui,mController是IVolumeController类,其他应用可以通过AudioManager的setVolumeController提供的api来赋值mController,并且重写volumeChanged方法,然后当音量变化的的时候便可以进行回调通知Ui更新。

代码路径:
./frameworks/base/services/core/java/com/android/server/audio/AudioService.java// UI update and Broadcast Intent
protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {streamType = mStreamVolumeAlias[streamType];if (streamType == AudioSystem.STREAM_MUSIC) {flags =  (flags);}//调用postVolumeChanged来通知UI更新mVolumeController.postVolumeChanged(streamType, flags);
}public void postVolumeChanged(int streamType, int flags) {if (mController == null)return;try {//调用volumeChanged来通知已经注册VolumeController回调的apk来更新UI。mController.volumeChanged(streamType, flags);} catch (RemoteException e) {Log.w(TAG, "Error calling volumeChanged", e);}
}

相关代码路径:

3.2 音频设备管理
AudioService除了设置音量,还管理音频的一些插入以及移除,以耳机为例检测耳机的插入过程。

(1)耳机的监听服务:
android 9.0 系统中,在SystemServer的startOtherServices方法中启动了有线设备的接入监听服务WiredAccessoryManager。

代码路径:
./frameworks/base/services/java/com/android/server/SystemServer.javaprivate void startOtherServices() {traceBeginAndSlog("StartWiredAccessoryManager");...try {// Listen for wired headset changesinputManager.setWiredAccessoryCallbacks(new WiredAccessoryManager(context, inputManager));} catch (Throwable e) {reportWtf("starting WiredAccessoryManager", e);}traceEnd();...
}

WiredAccessoryManager中通过WiredAccessoryObserver来监听有线设备的插入以及移除。

代码路径:
./frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.javapublic WiredAccessoryManager(Context context, InputManagerService inputManager) {PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager");mWakeLock.setReferenceCounted(false);mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);mInputManager = inputManager;mUseDevInputEventForAudioJack =context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);//启动有线的接入监听mObserver = new WiredAccessoryObserver();
}

WiredAccessoryObserver的实现

代码路径:
./frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.javaclass WiredAccessoryObserver extends UEventObserver {private final List<UEventInfo> mUEventInfo;
...
...// Monitor h2wif (!mUseDevInputEventForAudioJack) {uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC, BIT_LINEOUT);if (uei.checkSwitchExists()) {retVal.add(uei);} else {Slog.w(TAG, "This kernel does not have wired headset support");}}// Monitor USBuei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL, 0);if (uei.checkSwitchExists()) {retVal.add(uei);} else {Slog.w(TAG, "This kernel does not have usb audio support");}// Monitor HDMI//// If the kernel has support for the "hdmi_audio" switch, use that.  It will be// signalled only when the HDMI driver has a video mode configured, and the downstream// sink indicates support for audio in its EDID.//// If the kernel does not have an "hdmi_audio" switch, just fall back on the older// "hdmi" switch instead.uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0, 0);if (uei.checkSwitchExists()) {retVal.add(uei);} else {uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0, 0);if (uei.checkSwitchExists()) {retVal.add(uei);} else {Slog.w(TAG, "This kernel does not have HDMI audio support");}}// Monitor DPuei = new UEventInfo(NAME_DP, BIT_HDMI_AUDIO, 0, 0);if (uei.checkSwitchExists()) {retVal.add(uei);} else {Slog.w(TAG, "This kernel does not have dp audio support");}return retVal;}//当有kernel有onEvent事件的时候会调用onUEvent来进行设备插入的处理//例如耳机的插入主要是/devices/virtual/switch/的节点变化@Overridepublic void onUEvent(UEventObserver.UEvent event) {if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());try {String devPath = event.get("DEVPATH");String name = event.get("SWITCH_NAME");int state = Integer.parseInt(event.get("SWITCH_STATE"));synchronized (mLock) {updateStateLocked(devPath, name, state);//更新耳机便哈}} catch (NumberFormatException e) {Slog.e(TAG, "Could not parse switch state from event " + event);}}private void updateStateLocked(String devPath, String name, int state) {for (int i = 0; i < mUEventInfo.size(); ++i) {UEventInfo uei = mUEventInfo.get(i);if (devPath.equals(uei.getDevPath())) {updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state));return;}}}......}

上面WiredAccessoryObserver会通过监听/devices/virtual/switch/的节点变化,当节点有变化的时候,onUEvent会通过以下调用过程:
updateStateLocked---->updateLocked
---->mHandler.sendMessage(MSG_NEW_DEVICE_STATE)
----> setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
---->setDeviceStateLocked
----> mAudioManager.setWiredDeviceConnectionState(inDevice, state, “”, headsetName);

最终调用了AudioService的setWiredDeviceConnectionState来进行上层耳机插入的处理:

在AudioService中调用流程:
---->setWiredDeviceConnectionState------(通过handler处理)
----->onSetWiredDeviceConnectionState
----->sendDeviceConnectionIntent

代码路径:
./frameworks/base/services/core/java/com/android/server/audio/AudioService.javaprivate void onSetWiredDeviceConnectionState(int device, int state, String address,String deviceName, String caller) {............synchronized (mConnectedDevices) {if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");}if (!handleDeviceConnection(state == 1, device, address, deviceName)) {// change of connection state failed, bailoutreturn;}if (state != 0) {if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");}//added by custom begin.定制化部分开始//这里添加了自己当时需求的客制化部分,主要就是像苹果手机那样,//插入调低音量为50if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET ||device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)) {VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];//50% of music volumeint halfIndex = streamState.getMaxIndex()/2;streamState.setIndex(halfIndex, device, caller);sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);//open safe media volume state mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;Slog.i(TAG,"vsoonbsp add,set 50% of music volume after headset insert,halfIndex="+halfIndex+" mSafeMediaVolumeState="+mSafeMediaVolumeState); }//added by custom end.定制化部分结束if ((device & mSafeMediaVolumeDevices) != 0) {sendMsg(mAudioHandler,MSG_CHECK_MUSIC_ACTIVE,SENDMSG_REPLACE,0,0,caller,MUSIC_ACTIVE_POLL_PERIOD_MS);}// Television devices without CEC service apply software volume on HDMI outputif (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {if(isAtv() || isTablet()){mFixedVolumeDevices = 0;} else {mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;}checkAllFixedVolumeDevices();if (mHdmiManager != null) {synchronized (mHdmiManager) {if (mHdmiPlaybackClient != null) {mHdmiCecSink = false;mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);}}}}if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {sendEnabledSurroundFormats(mContentResolver, true);}if(isAtv() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {if(mBitstreamSetting != null) {mBitstreamSetting.hdmiAutoUpdate();}}} else {if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {if (mHdmiManager != null) {synchronized (mHdmiManager) {mHdmiCecSink = false;}}}}//发送耳机插入广播,通知其他感兴趣的系统模块以及apk,Intent.ACTION_HEADSET_PLUGsendDeviceConnectionIntent(device, state, address, deviceName);updateAudioRoutes(device, state);}}

3.3 音频焦点管理策略

(1)什么是音频焦点管理策略

场景1:手机正在播放音乐,突然电话来电,这时候音乐播放声音会被静音,而只留电话声音。

场景2:手机正在播放音乐,这时候如果导航应用播报,音乐播放音量会减小,等待导航播报结束后,音乐播放音量会恢复。

上面两个场景便用到了android的音频焦点管理,所以音频焦点策略就是在一个时间内只能允许一个音频播放实例拥有焦点,每个音频实例播放之前都应该向AudioService申请焦点,申请成功才开始播放;当一个音频实例正在播放的过程中,此时焦点被其他音频播放实例抢占,这时候正在播放的的音频实例会丢失焦点,失去焦点的音频播放实例应该根据实际情况来进行静音,暂停播放或者适当减小音量等操作,等被抢占的焦点被归还的时候再把之前的音频播放状态恢复。

这里注意两点:
①音频焦点策略只是android提供的一个机制,并且建议我们去使用的一种机制,并不是系统本身会强制采取的机制,如果各个应用都没有采用音频焦点策略管理机制,那么所有应用一起混合播放出来的音频声音,那将会可能是乱七八糟的声音。

②通话的时候,通话相关的音频模块也会申请音频焦点,其音频焦点的优先级是最高的,所以其可以从任何拥有音频焦点的音频播放实例中抢走音频焦点。

(2)音频焦点使用的Demo
demo实例来自于网上博主:renshuguo123723
博客链接:https://blog.csdn.net/renshuguo123723/article/details/85207284

public void play(){//在开始播放前,先申请AudioFocus,注意传入的参数 int result = mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN); //只有成功申请到AudioFocus之后才能开始播放 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) mMediaPlayer.start(); else//申请失败,如果系统没有问题,这一定是正在通话过程中,所以,还是不要播放了showMessageCannotStartPlaybackDuringACall();
} public void stop() { //停止播放时,需要释放AudioFocusmAudioManager.abandonAudioFocus(mAudioFocusListener);
}private onAudioFocusChangeListener mAudioFocusListener = newOnAudioFocusChangeListener(){ //当AudioFocus发生变化时,这个函数将会被调用。其中参数focusChange指示发生了什么变化public void onAudioFocusChange(int focusChange){switch( focusChange) { /*AudioFocus被长期夺走,需要中止播放,并释放AudioFocus,这种情况对应于
抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN*/case AUDIOFOCUS_LOSS: stop();break; /*AudioFocus被临时夺走,不久就会被归还,只需要暂停,AudioFocus被归还后
再恢复播放 ;这对应于抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN_TRANSIENT*/case AUDIOFOCUS_LOSS_TRANSIENT: saveCurrentPlayingState(); pause();break; /*AudioFocus被临时夺走,允许不暂停,所以降低音量 ,这对应于抢走AudioFocus的
回放实例使用了AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK*/case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:saveCurrentPlayingState();setVolume(getVolume()/2); break; /*AudioFocus被归还,这时需要恢复被夺走前的播放状态*/case AUDIOFOCUS_GAIN:restorePlayingState();break; }}
};

(3)焦点回调实现分析
从上面的demo实例中可知,想要知道焦点的变化,
①实现焦点变化的回调方法
②调用AudioManager中requestAudioFocus的申请音频焦点。

那么音频焦点是如何被调用的呢?下面通过一张图来解析描述:

如图
①一些应用调用AudioManager中requestAudioFocus来申请焦点,requestAudioFocus而最终会调用实现如下代码,该方法中会将回调OnAudioFocusChangeListener l对像以及其他参数封装在AudioFocusRequest类型对象afr里面。

代码路径:
./frameworks/base/media/java/android/media/AudioManager.javapublic int requestAudioFocus(OnAudioFocusChangeListener l,@NonNull AudioAttributes requestAttributes,int durationHint,int flags,AudioPolicy ap) throws IllegalArgumentException {............/**该步骤(1)主要是将所有音频申请焦点的信息通过builder的封装到一个*AudioFocusRequest类型的对象afr*afr最重要一点包含了焦点变化时的OnAudioFocusChangeListener类型回调对象l*/final AudioFocusRequest afr = new AudioFocusRequest.Builder(durationHint).setOnAudioFocusChangeListenerInt(l, null /* no Handler for this legacy API */).setAudioAttributes(requestAttributes).setAcceptsDelayedFocusGain((flags & AUDIOFOCUS_FLAG_DELAY_OK)== AUDIOFOCUS_FLAG_DELAY_OK).setWillPauseWhenDucked((flags & AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)== AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS).setLocksFocus((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK).build();//利用封装好的对象afr申请焦点       return requestAudioFocus(afr, ap);}
代码路径:
./frameworks/base/media/java/android/media/AudioManager.javapublic int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {.......
......./**注册上面封装好的AudioFocusRequest类型对象afr,注意里包含了            *OnAudioFocusChangeListener类型的回调对象l*/registerAudioFocusRequest(afr);final IAudioService service = getService();
.......final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());final BlockingFocusResultReceiver focusReceiver;synchronized (mFocusRequestsLock) {try {/***调用AudioService的方法调用*/// TODO status contains result and generation counter for ext policystatus = service.requestAudioFocus(afr.getAudioAttributes(),afr.getFocusGain(), mICallBack,mAudioFocusDispatcher,clientId,getContext().getOpPackageName() /* package name */, afr.getFlags(),ap != null ? ap.cb() : null,sdk);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {// default path with no external focus policyreturn status;}
......}

②③是将上面封装的AudioFocusRequest对象afr
与hanlder h又封装在FocusRequestInfo类型fri对象里面,然后将该对象fri储存在key-value的Map里面。

代码路径:
./frameworks/base/media/java/android/media/AudioManager.javapublic void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {final Handler h = afr.getOnAudioFocusChangeListenerHandler();/**步骤(2):afr与hanlder h封装成FocusRequestInfo类型fri对象里面*/final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :new ServiceEventHandlerDelegate(h).getHandler());final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());/**步骤(3):将fri放到Map表里面*/mAudioFocusIdListenerMap.put(key, fri);
}

④⑤从上面看出,最终是将应用注册的onAudioFocusChangeListener类型的回调对象l(这里用参数l来表示)包含在FocusRequestInfo类型的fri对象里面,并且将fri放到一个Map里面。到这里可能有疑惑,放到Map里面,AudioManager如何调用回调对像l来通知应用呢?其实AudioManager中有一个mAudioFocusDispatcher对象,该对象的回调方法dispatchAudioFocusChange会回调onAudioFocusChangeListener l的回调方法onAudioFocusChange。如下:
代码路径:
./frameworks/base/media/java/android/media/AudioManager.java

代码路径:private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {@Overridepublic void dispatchAudioFocusChange(int focusChange, String id) {/***步骤(4):该函数主要获取上面(3)步骤放置到Map里面的FocusRequestInfo对象*/final FocusRequestInfo fri = findFocusRequestInfo(id);if (fri != null)  {/*步骤(5):*这一步便是上面步骤(1)有把回调对象OnAudioFocusChangeListener封装在了*AudioFocusRequest类型的对象里面,*fri.mRequest.getOnAudioFocusChangeListener返回的是注册进去的    * OnAudioFocusChangeListener类型的回调对象。              */final OnAudioFocusChangeListener listener =fri.mRequest.getOnAudioFocusChangeListener();if (listener != null) {final Handler h = (fri.mHandler == null) ?mServiceEventHandlerDelegate.getHandler() : fri.mHandler;/**通过handler发送MSSG_FOCUS_CHANGE信息*/final Message m = h.obtainMessage(MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/,id/*obj*/);h.sendMessage(m);}}}

接收MSSG_FOCUS_CHANGE信息,并进行调用onAudioFocusChangeListener l的回调方法onAudioFocusChange来通知应用。

代码路径:
./frameworks/base/media/java/android/media/AudioManager.javaprivate class ServiceEventHandlerDelegate {private final Handler mHandler;ServiceEventHandlerDelegate(Handler handler) {Looper looper;if (handler == null) {if ((looper = Looper.myLooper()) == null) {looper = Looper.getMainLooper();}} else {looper = handler.getLooper();}if (looper != null) {// implement the event handler delegate to receive events from audio servicemHandler = new Handler(looper) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {/**接收MSSG_FOCUS_CHANGE信息,并进行回调onAudioFocusChangeListener l通知应用。*/case MSSG_FOCUS_CHANGE: {final FocusRequestInfo fri = findFocusRequestInfo((String)msg.obj);if (fri != null)  {final OnAudioFocusChangeListener listener =fri.mRequest.getOnAudioFocusChangeListener();if (listener != null) {Log.d(TAG, "dispatching onAudioFocusChange("+ msg.arg1 + ") to " + msg.obj);/**这里调用onAudioFocusChangeListener里面的onAudioFocusChange*/listener.onAudioFocusChange(msg.arg1);}}} break;

那么AudioService是如何可以回调 mAudioFocusDispatcher的呢?
(1)mAudioFocusDispatcher是在AudioManager调用AudioService在AudioManager客户端的代理方法requestAudioFocus的时候通过参数形式将mAudioFocusDispatcher传入。

代码路径:
./frameworks/base/media/java/android/media/AudioManager.javasynchronized (mFocusRequestsLock) {try {// TODO status contains result and generation counter for ext policy/**r调用AudioService在AudioManager客户端的代理方法requestAudioFocus*此处传入mAudioFocusDispatcher对像*/status = service.requestAudioFocus(afr.getAudioAttributes(),afr.getFocusGain(), mICallBack,mAudioFocusDispatcher,clientId,getContext().getOpPackageName() /* package name */, afr.getFlags(),ap != null ? ap.cb() : null,sdk);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}

(2)然后在AudioService端流程如下:最终会调用mAudioFocusDispatcher中的回调方法dispatchAudioFocusChange。
源码路径:
./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
./frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
./frameworks/base/services/core/java/com/android/server/audio/FocusRequester.java

AudioService相关推荐

  1. 《深入理解Android:卷III A》一一第3章 深入理解AudioService

    第3章 深入理解AudioService 本章主要内容: 探讨AudioService如何进行音量管理 了解音频外设的管理机制 探讨AudioFocus的工作原理 本章涉及的源代码文件名及位置: Au ...

  2. android音频系统(4):AudioService之音量管理

    前言:AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,AudioService是音频系统在java层的大本营: android音频系统,分为两个部分:数据流和策略: 数据流 ...

  3. android音频系统(5):AudioService之音频焦点

    前言:上一节我们分析了AudioService对音量的管理,这一节来看下AudioService对音频焦点的处理,也就是音频系统中的AudioFocus机制,它用来处理多个音频不合理的同时播放的糟糕后 ...

  4. Audioservice、Audiomanager和Audiosystem

    JAVA: Audioservice.Audiomanager和Audiosystem AudioService: 继承自IAudioService.Stub,IAudioService.Stub类很 ...

  5. Android AudioService安全音量设置逻辑

    问题点描述: 还原出厂设置后,第一次启动安卓系统会自动降音量设成10,导致开机时音量不一致问题. 原因:安全音量逻辑将音量设置为10 安全音量配置和音量值 \frameworks\base\core\ ...

  6. 对AudioService 的认识(1)

    AudioService 是一个 Java 服务,主要用于控制音频输入输出的过程,如调节音量大小,设置音量模式,设置蓝牙音频的模式,我现在具体分析和AudioService 相关的软件层.  它主要是 ...

  7. Android音量控制曲线

    摘要:本文介绍了android音量的控制曲线的计算方法. 由于人耳对声音的听感具指数曲线型,也就是对小音量时比较敏感,随着声音的加大其听感随之变的不敏感,其变化近似指数函数曲线的形式.为了使听感变的近 ...

  8. 从 Android 静音看正确的查bug的姿势?

    0.写在前面 没抢到小马哥的红包,无心回家了,回公司写篇文章安慰下自己TT..话说年关难过,bug多多,时间久了难免头昏脑热,不辨朝暮,难识乾坤...艾玛,扯远了,话说谁没踩过坑,可视大家都是如何从坑 ...

  9. Android 解读Event和Main Log

    1 Android P EventLogTags文件 Android P 9.0.0 所有EventLogTags文件List: system/bt/EventLogTags.logtags syst ...

最新文章

  1. css中调整高度充满_CSS(十三).高度如何铺满全屏
  2. container_of(ptr, type, member)宏定义解析
  3. MySQL extract()函数
  4. [react] 给组件设置很多属性时不想一个个去设置有什么办法可以解决这问题呢?
  5. 如何将exe文件在linux下执行,如何在Linux系统下查找可执行文件
  6. Jetty9 源码初解(2)——IO之Connection
  7. 在Eclipse新建菜单中添加菜单项,其他地方添加菜单项类似
  8. BAD packet signature 18245
  9. C# 创建、部署和调用WebService的简单示例
  10. 在后台增加一个查询条件
  11. html+css响应式布局
  12. C++ msdn 离线版下载地址
  13. windows10下破解开机密码
  14. FAT文件系统存储原理
  15. 插入排序一块说说-很合适~~~二分查找和折半
  16. Android 9.0系统源码_SystemUI(四)通知图标控制器
  17. python爬虫豆瓣评论论文_Python爬虫(三)——对豆瓣图书各模块评论数与评分图形化分析...
  18. matlab输入数据作方程,用MATLAB函数编写并求解微分方程
  19. 2022-06-23 JVM学习
  20. 财富自由的声音:蚂蚁上市前,取消了周报

热门文章

  1. 我男的,做电话销售,月入6000+,今年25了,感觉做不了几年,要不要转行软件测试,或者换其他工作?
  2. 北航团队四年研究成果登上《科学·机器人》长文封面,仿生学科研又上一层楼...
  3. CSDN日报20170515 ——《 聊聊我对 WannaCry 产生的感慨》
  4. 办公本推荐计算机专业,2021年办公本/全能笔记本电脑选购指南(附良心机型推荐)...
  5. 小度智能音箱2红外版测评和拆机
  6. 【自用】2.git应用
  7. 计算机二级ppt学什么软件,计算机二级办公软件机考试参考资料.ppt
  8. ue4 联机烘焙出现问题和解决方式
  9. Yii2默认界面增加多级菜单
  10. K210--运行NOMMU linux