android 按键声音

http://www.360doc.com/content/13/1217/19/1698092_337948357.shtml

1.Android的audio流的类型有以下12种:

  1. /* The audio stream for phone calls */  
  2.     public static final int STREAM_VOICE_CALL = 0;//通话连接时的音频流(通话声)  
  3.     /* The audio stream for system sounds */  
  4.     public static final int STREAM_SYSTEM = 1;//系统音频流  
  5.     /* The audio stream for the phone ring and message alerts */  
  6.     public static final int STREAM_RING = 2;//来电铃声  
  7.     /* The audio stream for music playback */  
  8.     public static final int STREAM_MUSIC = 3;//媒体音频流  
  9.     /* The audio stream for alarms */  
  10.     public static final int STREAM_ALARM = 4;//闹钟音频流  
  11.     /* The audio stream for notifications */  
  12.     public static final int STREAM_NOTIFICATION = 5;//通知音频流  
  13.     /* @hide The audio stream for phone calls when connected on bluetooth */  
  14.     public static final int STREAM_BLUETOOTH_SCO = 6;//从注释上看时使用蓝牙耳机通话的音频流  
  15.     /* @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */  
  16.     public static final int STREAM_SYSTEM_ENFORCED = 7;//一些国家强制使用的音频流??不太明白  
  17.     /* @hide The audio stream for DTMF tones */  
  18.     public static final int STREAM_DTMF = 8;//DTMF音频流  
  19.     /* @hide The audio stream for text to speech (TTS) */  
  20.     public static final int STREAM_TTS = 9;//TTS: Text to Speech:文件到语言的音频流,即机器说话  
  21.     /* @hide The audio stream for Fm */  
  22.     public static final int STREAM_FM = 10;//FM的音频流  
  23.     /* @hide The audio stream for MATV */  
  24.     public static final int STREAM_MATV = 11;//TV的音频流 

每种音频流所规定的最大值:

  1. /** @hide Maximum volume index values for audio streams */  
  2.  private int[] MAX_STREAM_VOLUME = new int[] {  
  3.      6,  // STREAM_VOICE_CALL  
  4.      7,  // STREAM_SYSTEM  
  5.      7,  // STREAM_RING  
  6.      12// STREAM_MUSIC  
  7.      7,  // STREAM_ALARM  
  8.      7,  // STREAM_NOTIFICATION  
  9.      15// STREAM_BLUETOOTH_SCO  
  10.      7,  // STREAM_SYSTEM_ENFORCED  
  11.      15// STREAM_DTMF  
  12.      15// STREAM_TTS  
  13.      13//STREAM_FM  
  14.      13  //stream_MATV  
  15.  };  

2.所有的按键事件都是touch事件,这部分我会另外开篇博文介绍。


开始本文正文,Anndroid系统中所有View带有按键音,用户可以通过Settings>Sound>勾选Audible Selection即可开启按键音。但是有个奇怪的地方:此按键音是与媒体音量(即STREAM_MUSIC)绑定的,难道按键音的STREAM TYPE就是STREAM_MUSIC吗?我们从代码中寻找一下。


首先所有的View点击的时候都有按键音,我们从View.java的点击事件找起,在view的响应的onTouchEvent()方法中有如下代码:


  1. switch (event.getAction()) {  
  2.                case MotionEvent.ACTION_UP:  
  3.                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  4.                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  5.                        // take focus if we don't have it already and we should in  
  6.                        // touch mode.  
  7.                        boolean focusTaken = false;  
  8.                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  9.                            focusTaken = requestFocus();  
  10.                        }  
  11.   
  12.                        if (!mHasPerformedLongPress) {  
  13.                            // This is a tap, so remove the longpress check  
  14.                            removeLongPressCallback();  
  15.   
  16.                            // Only perform take click actions if we were in the pressed state  
  17.                            if (!focusTaken) {  
  18.                                // Use a Runnable and post this rather than calling  
  19.                                // performClick directly. This lets other visual state  
  20.                                // of the view update before click actions start.  
  21.                                if (mPerformClick == null) {  
  22.                                    mPerformClick = new PerformClick();  
  23.                                }  
  24.                                if (!post(mPerformClick)) {  
  25.                                    performClick();//这里响应click事件  
  26.                                }  
  27.                            }  
  28.                        }  
  29.   
  30.                        if (mUnsetPressedState == null) {  
  31.                            mUnsetPressedState = new UnsetPressedState();  
  32.                        }  
  33.   
  34.                        if (prepressed) {  
  35.                            mPrivateFlags |= PRESSED;  
  36.                            refreshDrawableState();  
  37.                            postDelayed(mUnsetPressedState,  
  38.                                    ViewConfiguration.getPressedStateDuration());  
  39.                        } else if (!post(mUnsetPressedState)) {  
  40.                            // If the post failed, unpress right now  
  41.                            mUnsetPressedState.run();  
  42.                        }  
  43.                        removeTapCallback();  
  44.                    }  
  45.                    break;  


  1. public boolean performClick() {  
  2.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  3.   
  4.     if (mOnClickListener != null) {  
  5.         playSoundEffect(SoundEffectConstants.CLICK);  
  6.         mOnClickListener.onClick(this);  
  7.         return true;  
  8.     }  
  9.   
  10.     return false;  
  11. }  
从这里可以看到与用户接口onClickListener结合起来了,当用户注册了clickListener,则调用发出按键音函数playSoundEffect ()和响应用户写好的clickListener的onClick()方法。这里playSoundEffect函数传的参数SoundEffectContants.CLICK为多少呢,从SoundEffectConstants.java可知SoundEffectConstants.CLICK = 0:


  1. public class SoundEffectConstants  
  2. {  
  3. SoundEffectConstants() { throw new RuntimeException("Stub!"); }  
  4. public static  int getContantForFocusDirection(int direction) { throw new RuntimeException("Stub!"); }  
  5. public static final int CLICK = 0;  
  6. public static final int NAVIGATION_LEFT = 1;  
  7. public static final int NAVIGATION_UP = 2;  
  8. public static final int NAVIGATION_RIGHT = 3;  
  9. public static final int NAVIGATION_DOWN = 4;  
  10. }  


playSoundEffect ()的具体内容如下:

  1. public void playSoundEffect(int soundConstant) {  
  2.     if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {  
  3.         return;  
  4.     }  
  5.     mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);  
  6. }  

真正调用的是AttachInfo Callbacks接口的playSoundEffect()函数:

  1. /** 
  2.     * A set of information given to a view when it is attached to its parent 
  3.     * window. 
  4.     */  
  5.    static class AttachInfo {  
  6.        interface Callbacks {  
  7.            void playSoundEffect(int effectId);  
  8.            boolean performHapticFeedback(int effectId, boolean always);  
  9.        }  

看注释可知其真正的方法写在parent window中,那parent window是哪个呢?ViewRoot的实现该回调接口:

  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  

具体的playSoundEffect()函数内容:

  1. public void playSoundEffect(int effectId) {  
  2.         checkThread();  
  3.   
  4.         try {  
  5.             final AudioManager audioManager = getAudioManager();  
  6.   
  7.             switch (effectId) {  
  8.                 case SoundEffectConstants.CLICK:  
  9.                     audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);  
  10.                     return;  
  11.                 case SoundEffectConstants.NAVIGATION_DOWN:  
  12.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);  
  13.                     return;  
  14.                 case SoundEffectConstants.NAVIGATION_LEFT:  
  15.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);  
  16.                     return;  
  17.                 case SoundEffectConstants.NAVIGATION_RIGHT:  
  18.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);  
  19.                     return;  
  20.                 case SoundEffectConstants.NAVIGATION_UP:  
  21.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);  
  22.                     return;  
  23.                 default:  
  24.                     throw new IllegalArgumentException("unknown effect id " + effectId +  
  25.                             " not defined in " + SoundEffectConstants.class.getCanonicalName());  
  26.             }  
  27.         } catch (IllegalStateException e) {  
  28.             // Exception thrown by getAudioManager() when mView is null  
  29.             Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);  
  30.             e.printStackTrace();  
  31.         }  
  32.     }  

我们传入的参数为SoundEffectContants.CLICK,调用AudioManager的playSoundEffect()方法,参数为AudioManger.FX_KEY_CLICK,继续往下看,在AudioManager.java中playSoundEffect()方法:

  1. public void  playSoundEffect(int effectType) {  
  2.     if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {  
  3.         return;  
  4.     }  
  5.   
  6.     if (!querySoundEffectsEnabled()) {  
  7.         return;  
  8.     }  
  9.   
  10.     IAudioService service = getService();  
  11.     try {  
  12.         service.playSoundEffect(effectType);  
  13.     } catch (RemoteException e) {  
  14.         Log.e(TAG, "Dead object in playSoundEffect"+e);  
  15.     }  
  16. }  
调用了IAudioService的playSoundEffect() 方法,IAudioService方法是使用aidl生成的接口,aidl源文件:frameworks/base/media/java/android/media/IAudioService.aidl,真正响应的地方在AudioService.java中:

  1. /** @see AudioManager#playSoundEffect(int) */  
  2. public void playSoundEffect(int effectType) {  
  3.     sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP,  
  4.             effectType, -1null0);  
  5. }  
该方法调用sendMsg()方法,传入一下参数:(mAudioHandler,  7, -1, 1, 0, -1, null, 0),sendMsg()方法如下:

  1. private static void sendMsg(Handler handler, int baseMsg, int streamType,  
  2.         int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {  
  3.     int msg = (streamType == SHARED_MSG) ? baseMsg : getMsg(baseMsg, streamType);  
  4.   
  5.     if (existingMsgPolicy == SENDMSG_REPLACE) {  
  6.         handler.removeMessages(msg);  
  7.     } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {  
  8.         Log.d(TAG, "sendMsg: Msg " + msg + " existed!");  
  9.         return;  
  10.     }  
  11.   
  12.     handler  
  13.             .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);  
  14. }  

该方法就是将传入的参数经过计算,obtain一个message,message的what = 7 arg1 = 0 arg2 = -1 object = null; 处理该消息的地方handleMessage ():

  1. @Override  
  2.         public void handleMessage(Message msg) {  
  3.             int baseMsgWhat = getMsgBase(msg.what);  
  4.   
  5.             switch (baseMsgWhat) {  
  6.   
  7. ...  
  8.   
  9.                 case MSG_PLAY_SOUND_EFFECT:  
  10.                     playSoundEffect(msg.arg1, msg.arg2);  
  11.                     break;  
  12. ...  
  13.   
  14. }  

调用了带两个参数的playSoundEffect()函数,传入参数 0,-1:

  1. private void playSoundEffect(int effectType, int volume) {  
  2.     synchronized (mSoundEffectsLock) {  
  3.   
  4.         if (mSoundPool == null) {  
  5.             return;  
  6.         }  
  7.         float volFloat;  
  8.         // use STREAM_MUSIC volume attenuated by 3 dB if volume is not specified by caller  
  9.         if (volume < 0) {  
  10.             //以下计算播放的音量大小:  
  11.                  // Same linear to log conversion as in native AudioSystem::linearToLog() (AudioSystem.cpp)  
  12.             float dBPerStep = (float)((0.5 * 100) / MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);  
  13.             int musicVolIndex = (mStreamStates[AudioSystem.STREAM_MUSIC].mIndex + 5) / 10;  
  14.             float musicVoldB = dBPerStep * (musicVolIndex - MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);  
  15.             volFloat = (float)Math.pow(10, (musicVoldB - 3)/20);  
  16.         } else {  
  17.             volFloat = (float) volume / 1000.0f;  
  18.         }  
  19.         if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {  
  20.   
  21.             mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 001.0f);//调用该函数播放按键音  
  22.         } else {  
  23.           
  24.             MediaPlayer mediaPlayer = new MediaPlayer();  
  25.             if (mediaPlayer != null) {  
  26.                 try {  
  27.                     String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];  
  28.                     mediaPlayer.setDataSource(filePath);  
  29.                     mediaPlayer.setAudioStreamType(AudioSystem.STREAM_RING);  
  30.                     mediaPlayer.prepare();  
  31.                     mediaPlayer.setVolume(volFloat, volFloat);  
  32.                     mediaPlayer.setOnCompletionListener(new OnCompletionListener() {  
  33.                         public void onCompletion(MediaPlayer mp) {  
  34.                             cleanupPlayer(mp);  
  35.                         }  
  36.                     });  
  37.                     mediaPlayer.setOnErrorListener(new OnErrorListener() {  
  38.                         public boolean onError(MediaPlayer mp, int what, int extra) {  
  39.                             cleanupPlayer(mp);  
  40.                             return true;  
  41.                         }  
  42.                     });  
  43.                     mediaPlayer.start();  
  44.                 } catch (IOException ex) {  
  45.                     Log.w(TAG, "MediaPlayer IOException: "+ex);  
  46.                 } catch (IllegalArgumentException ex) {  
  47.                     Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);  
  48.                 } catch (IllegalStateException ex) {  
  49.                     Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);  
  50.                 }  
  51.             }  
  52.         }  
  53.     }  
  54. }  

因为传入的参数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()函数:

  1. public AudioService(Context context) {  
  2. ......  
  3.         loadSoundEffects();  
  4. .......  
  5.     }  

loadSoundEffects()函数的具体实现如下:

  1.  public boolean loadSoundEffects() {  
  2.        synchronized (mSoundEffectsLock) {  
  3.            if (mSoundPool != null) {  
  4.                return true;  
  5.            }  
  6.            mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0);  
  7.   
  8.           ......  
  9.   
  10.        return true;  
  11. }  

在此函数中,初始化了该SoundPool,类型为STREAM_SYSTEM。


到这里,结果出来了,android中,view的按键音类型为系统音频(STREAM_SYSTEM),而音量的大小与媒体音量(STREAM_MUSIC)绑定了起来。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值