android 按鍵聲音
1.Android的audio流的型別有以下12種:
- /* The audio stream for phone calls */
- public static final int STREAM_VOICE_CALL = 0;//通話連線時的音訊流(通話聲)
- /* The audio stream for system sounds */
- public static final int STREAM_SYSTEM = 1;//系統音訊流
- /* The audio stream for the phone ring and message alerts */
- public static final int STREAM_RING = 2;//來電鈴聲
- /* The audio stream for music playback */
- public static final int STREAM_MUSIC = 3;//媒體音訊流
- /* The audio stream for alarms */
- public static final int STREAM_ALARM = 4;//鬧鐘音訊流
- /* The audio stream for notifications */
- public static final int STREAM_NOTIFICATION = 5;//通知音訊流
- /* @hide The audio stream for phone calls when connected on bluetooth */
- public static final int STREAM_BLUETOOTH_SCO = 6;//從註釋上看時使用藍芽耳機通話的音訊流
- /* @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */
- public static final int STREAM_SYSTEM_ENFORCED = 7;//一些國家強制使用的音訊流??不太明白
- /* @hide The audio stream for DTMF tones */
- public static final int STREAM_DTMF = 8;//DTMF音訊流
- /* @hide The audio stream for text to speech (TTS) */
- public static final int STREAM_TTS = 9;//TTS: Text to Speech:檔案到語言的音訊流,即機器說話
- /* @hide The audio stream for Fm */
- public static final int STREAM_FM = 10;//FM的音訊流
- /* @hide The audio stream for MATV */
- public static final int STREAM_MATV = 11;//TV的音訊流
每種音訊流所規定的最大值:
- /** @hide Maximum volume index values for audio streams */
- private int[] MAX_STREAM_VOLUME = new int[] {
- 6, // STREAM_VOICE_CALL
- 7, // STREAM_SYSTEM
- 7, // STREAM_RING
- 12, // STREAM_MUSIC
- 7, // STREAM_ALARM
- 7, // STREAM_NOTIFICATION
- 15, // STREAM_BLUETOOTH_SCO
- 7, // STREAM_SYSTEM_ENFORCED
- 15, // STREAM_DTMF
- 15, // STREAM_TTS
- 13, //STREAM_FM
- 13 //stream_MATV
- };
2.所有的按鍵事件都是touch事件,這部分我會另外開篇博文介紹。
開始本文正文,Anndroid系統中所有View帶有按鍵音,使用者可以通過Settings>Sound>勾選Audible Selection即可開啟按鍵音。但是有個奇怪的地方:此按鍵音是與媒體音量(即STREAM_MUSIC)繫結的,難道按鍵音的STREAM TYPE就是STREAM_MUSIC嗎?我們從程式碼中尋找一下。
首先所有的View點選的時候都有按鍵音,我們從View.java的點選事件找起,在view的響應的onTouchEvent()方法中有如下程式碼:
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
- if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
- // take focus if we don't have it already and we should in
- // touch mode.
- boolean focusTaken = false;
- if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
- focusTaken = requestFocus();
- }
- if (!mHasPerformedLongPress) {
- // This is a tap, so remove the longpress check
- removeLongPressCallback();
- // Only perform take click actions if we were in the pressed state
- if (!focusTaken) {
- // Use a Runnable and post this rather than calling
- // performClick directly. This lets other visual state
- // of the view update before click actions start.
- if (mPerformClick == null) {
- mPerformClick = new PerformClick();
- }
- if (!post(mPerformClick)) {
- performClick();//這裡響應click事件
- }
- }
- }
- if (mUnsetPressedState == null) {
- mUnsetPressedState = new UnsetPressedState();
- }
- if (prepressed) {
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- } else if (!post(mUnsetPressedState)) {
- // If the post failed, unpress right now
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- break;
- public boolean performClick() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
- if (mOnClickListener != null) {
- playSoundEffect(SoundEffectConstants.CLICK);
- mOnClickListener.onClick(this);
- return true;
- }
- return false;
- }
- public class SoundEffectConstants
- {
- SoundEffectConstants() { throw new RuntimeException("Stub!"); }
- public static int getContantForFocusDirection(int direction) { throw new RuntimeException("Stub!"); }
- public static final int CLICK = 0;
- public static final int NAVIGATION_LEFT = 1;
- public static final int NAVIGATION_UP = 2;
- public static final int NAVIGATION_RIGHT = 3;
- public static final int NAVIGATION_DOWN = 4;
- }
playSoundEffect ()的具體內容如下:
- public void playSoundEffect(int soundConstant) {
- if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
- return;
- }
- mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
- }
真正呼叫的是AttachInfo Callbacks介面的playSoundEffect()函式:
- /**
- * A set of information given to a view when it is attached to its parent
- * window.
- */
- static class AttachInfo {
- interface Callbacks {
- void playSoundEffect(int effectId);
- boolean performHapticFeedback(int effectId, boolean always);
- }
看註釋可知其真正的方法寫在parent window中,那parent window是哪個呢?ViewRoot的實現該回撥介面:
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
具體的playSoundEffect()函式內容:
- public void playSoundEffect(int effectId) {
- checkThread();
- try {
- final AudioManager audioManager = getAudioManager();
- switch (effectId) {
- case SoundEffectConstants.CLICK:
- audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
- return;
- case SoundEffectConstants.NAVIGATION_DOWN:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
- return;
- case SoundEffectConstants.NAVIGATION_LEFT:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
- return;
- case SoundEffectConstants.NAVIGATION_RIGHT:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
- return;
- case SoundEffectConstants.NAVIGATION_UP:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
- return;
- default:
- throw new IllegalArgumentException("unknown effect id " + effectId +
- " not defined in " + SoundEffectConstants.class.getCanonicalName());
- }
- } catch (IllegalStateException e) {
- // Exception thrown by getAudioManager() when mView is null
- Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);
- e.printStackTrace();
- }
- }
我們傳入的引數為SoundEffectContants.CLICK,呼叫AudioManager的playSoundEffect()方法,引數為AudioManger.FX_KEY_CLICK,繼續往下看,在AudioManager.java中playSoundEffect()方法:
- public void playSoundEffect(int effectType) {
- if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
- return;
- }
- if (!querySoundEffectsEnabled()) {
- return;
- }
- IAudioService service = getService();
- try {
- service.playSoundEffect(effectType);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in playSoundEffect"+e);
- }
- }
- /** @see AudioManager#playSoundEffect(int) */
- public void playSoundEffect(int effectType) {
- sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP,
- effectType, -1, null, 0);
- }
- private static void sendMsg(Handler handler, int baseMsg, int streamType,
- int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
- int msg = (streamType == SHARED_MSG) ? baseMsg : getMsg(baseMsg, streamType);
- if (existingMsgPolicy == SENDMSG_REPLACE) {
- handler.removeMessages(msg);
- } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
- Log.d(TAG, "sendMsg: Msg " + msg + " existed!");
- return;
- }
- handler
- .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
- }
該方法就是將傳入的引數經過計算,obtain一個message,message的what = 7 arg1 = 0 arg2 = -1 object = null; 處理該訊息的地方handleMessage ():
- @Override
- public void handleMessage(Message msg) {
- int baseMsgWhat = getMsgBase(msg.what);
- switch (baseMsgWhat) {
- ...
- case MSG_PLAY_SOUND_EFFECT:
- playSoundEffect(msg.arg1, msg.arg2);
- break;
- ...
- }
呼叫了帶兩個引數的playSoundEffect()函式,傳入引數 0,-1:
- private void playSoundEffect(int effectType, int volume) {
- synchronized (mSoundEffectsLock) {
- if (mSoundPool == null) {
- return;
- }
- float volFloat;
- // use STREAM_MUSIC volume attenuated by 3 dB if volume is not specified by caller
- if (volume < 0) {
- //以下計算播放的音量大小:
- // Same linear to log conversion as in native AudioSystem::linearToLog() (AudioSystem.cpp)
- float dBPerStep = (float)((0.5 * 100) / MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
- int musicVolIndex = (mStreamStates[AudioSystem.STREAM_MUSIC].mIndex + 5) / 10;
- float musicVoldB = dBPerStep * (musicVolIndex - MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
- volFloat = (float)Math.pow(10, (musicVoldB - 3)/20);
- } else {
- volFloat = (float) volume / 1000.0f;
- }
- if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
- mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f);//呼叫該函式播放按鍵音
- } else {
- MediaPlayer mediaPlayer = new MediaPlayer();
- if (mediaPlayer != null) {
- try {
- String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];
- mediaPlayer.setDataSource(filePath);
- mediaPlayer.setAudioStreamType(AudioSystem.STREAM_RING);
- mediaPlayer.prepare();
- mediaPlayer.setVolume(volFloat, volFloat);
- mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
- public void onCompletion(MediaPlayer mp) {
- cleanupPlayer(mp);
- }
- });
- mediaPlayer.setOnErrorListener(new OnErrorListener() {
- public boolean onError(MediaPlayer mp, int what, int extra) {
- cleanupPlayer(mp);
- return true;
- }
- });
- mediaPlayer.start();
- } catch (IOException ex) {
- Log.w(TAG, "MediaPlayer IOException: "+ex);
- } catch (IllegalArgumentException ex) {
- Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
- } catch (IllegalStateException ex) {
- Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
- }
- }
- }
- }
- }
因為傳入的引數volume為-1,按鍵音大小的值走if(volume < 0)內,函式中此部分計算的是按鍵音的大小volFloat,可以看出,整個計算過程都跟媒體音量STREAM_MUSIC有關,這裡就看出,按鍵音的音量大小是與STREAM_MUSIC繫結的,那按鍵音的型別呢?繼續看下去,函式 mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f);中使用了SoundPool來播放按鍵音,我們看看該SoundPool初始化步驟,在AudioService初始化時,呼叫了loadSoundEffects()函式:
- public AudioService(Context context) {
- ......
- loadSoundEffects();
- .......
- }
loadSoundEffects()函式的具體實現如下:
- public boolean loadSoundEffects() {
- synchronized (mSoundEffectsLock) {
- if (mSoundPool != null) {
- return true;
- }
- mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0);
- ......
- return true;
- }
在此函式中,初始化了該SoundPool,型別為STREAM_SYSTEM。
到這裡,結果出來了,android中,view的按鍵音型別為系統音訊(STREAM_SYSTEM),而音量的大小與媒體音量(STREAM_MUSIC)繫結了起來。
相關文章
- Android聲音相關總結Android
- 語音的關鍵聲學特徵(語音情感特徵提取)特徵
- android 處理按鍵動作Android
- android按鍵模擬測試Android
- 【android】android使用命令模擬按鍵Android
- win10沒有聲音怎麼辦_win10電腦沒有聲音一鍵恢復Win10
- android 獲得後退鍵按事件Android事件
- Android中實現錄製內建聲音Android
- Android多媒體之認識聲音、錄音與播放(PCM)Android
- Windows 7系統聲音正常酷狗音樂無聲音Windows
- 聲音魔法課
- 電腦沒聲音但是揚聲器顯示有聲音怎麼辦?電腦沒聲音的解決辦法
- Android為撥號盤dialer定製聲音DTMF TonesAndroid
- android音視訊指南-響應媒體按鈕Android
- 實現android按下Enter鍵便隱藏輸入鍵盤Android
- 調節聲音 工具
- Mac按鍵Mac
- 三行程式碼按鍵消抖 獨立按鍵 矩陣按鍵 長按 短按 雙擊行程矩陣
- Android按下返回鍵,將應用退到後臺Android
- 【梟·音訊】聲隨意動——淺談《暗影火炬城》聲音設計音訊
- 微信聲音解鎖使用教程 微信聲音解鎖怎麼設定?
- golang聲音播放的初探Golang
- Windows 的各種聲音Windows
- Win10系統下聲音出現破音爆音聲音延遲卡頓的完美解決方法Win10
- Android 聲網音視訊體驗記錄|掘金技術徵文Android
- tmux常用按鍵UX
- 剪映裡怎麼用Siri的聲音?剪映新增Siri聲音教程
- BAFTA遊戲最佳聲音獎《對馬之魂》的聲音設計幕後遊戲
- 電腦沒聲音怎麼辦? 電腦沒聲音的原因總結
- 為什麼電腦沒聲音? 電腦沒聲音的原因總結!
- 鍵盤失靈按什麼鍵恢復 win10鍵盤恢復按鍵Win10
- 電腦沒聲音是什麼原因 電腦沒聲音怎麼處理
- 電腦沒聲音怎麼辦?電腦沒聲音的解決辦法
- E50拍照時候會出聲音,聲音檔案位於……
- 如何基於實時聲紋變聲實現對聲音的“克隆”
- 雜談:遊戲中的“聲音”遊戲
- 讓你的App有聲音APP
- 聲音的響度PythonPython