天天看點

android 按鍵聲音

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

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;  

}  

從這裡可以看到與使用者接口onclicklistener結合起來了,當使用者注冊了clicklistener,則調用發出按鍵音函數playsoundeffect ()和響應使用者寫好的clicklistener的onclick()方法。這裡playsoundeffect函數傳的參數soundeffectcontants.click為多少呢,從soundeffectconstants.java可知soundeffectconstants.click

= 0:

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);  

                case soundeffectconstants.navigation_left:  

                    audiomanager.playsoundeffect(audiomanager.fx_focus_navigation_left);  

                case soundeffectconstants.navigation_right:  

                    audiomanager.playsoundeffect(audiomanager.fx_focus_navigation_right);  

                case soundeffectconstants.navigation_up:  

                    audiomanager.playsoundeffect(audiomanager.fx_focus_navigation_up);  

                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) {  

    if (!querysoundeffectsenabled()) {  

    iaudioservice service = getservice();  

    try {  

        service.playsoundeffect(effecttype);  

    } catch (remoteexception e) {  

        log.e(tag, "dead object in playsoundeffect"+e);  

調用了iaudioservice的playsoundeffect() 方法,iaudioservice方法是使用aidl生成的接口,aidl源檔案:frameworks/base/media/java/android/media/iaudioservice.aidl,真正響應的地方在audioservice.java中:

/** @see audiomanager#playsoundeffect(int) */  

public void playsoundeffect(int effecttype) {  

    sendmsg(maudiohandler, msg_play_sound_effect, shared_msg, sendmsg_noop,  

            effecttype, -1, null, 0);  

該方法調用sendmsg()方法,傳入一下參數:(maudiohandler,  7, -1, 1, 0, -1, null, 0),sendmsg()方法如下:

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!");  

    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);//調用該函數播放按鍵音  

            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) {  

                            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)綁定了起來。

繼續閱讀