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