概述
作为一名Android开发人员,对源码的阅读是必不可少的。但是Android源码那么庞大,从何开始阅读,如何开始阅读,很多人都会感觉无从下手,今天我来带着问题,去带大家分析一下Android源码,并解决问题。源码并不可怕,耐着性子看就好了。
今天我们来看一下如何增大按键音量呢,下面贴一下调用播放按键音方法的代码:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.playSoundEffect(3);
了解Audio服务和Binder机制的朋友肯定都知道,这个AudioManager是什么。我们这边获取的audioManager其实就是Android系统在系统启动时,注册的系统服务AudioService的代理对象的包装,我们可以这么理解。如果有朋友对Binder机制不了解的,可以去网上搜搜其他文章。
在这里,我们调用audioManager的playSoundEffect进行按键音的播放。
下面我们一步步跟下去,看看这个playSoundEffect方法到底做了什么事。
1.AudioManager.playSoundEffect
public void playSoundEffect(int effectType) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
if (!querySoundEffectsEnabled(Process.myUserHandle().getIdentifier())) {
return;
}
final IAudioService service = getService();
try {
//调用AudioService的playSoundEffect方法,跨进程调用
//这个service其实就是AudioService在java端的代理对象
service.playSoundEffect(effectType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
2.AudioService.playSoundEffect
/** @see AudioManager#playSoundEffect(int) */
public void playSoundEffect(int effectType) {
//第二个参数代表软件音量,可以由调用者传入,我们今天说的调节音量的大小说的不是这个
playSoundEffectVolume(effectType, -1.0f);
}
/** @see AudioManager#playSoundEffect(int, float) */
public void playSoundEffectVolume(int effectType, float volume) {
// do not try to play the sound effect if the system stream is muted
if (isStreamMutedByRingerOrZenMode(STREAM_SYSTEM)) {
return;
}
if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) {
Log.w(TAG, "AudioService effectType value " + effectType + " out of range");
return;
}
sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
effectType, (int) (volume * 1000), null, 0);
}
playSoundEffect方法最终走到了playSoundEffectVolume,并且调用sendMsg发送了一个消息。
3. onPlaySoundEffect(msg.arg1, msg.arg2);
mAudioHandler在处理MSG_PLAY_SOUND_EFFECT这个消息时,调用了onPlaySoundEffect这个方法,这个方法主要做了什么事呢,我们继续往下看。
private void onPlaySoundEffect(int effectType, int volume) {
synchronized (mSoundEffectsLock) {
//1.初始化资源
onLoadSoundEffects();
if (mSoundPool == null) {
return;
}
float volFloat;
// use default if volume is not specified by caller
if (volume < 0) {
volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
} else {
volFloat = volume / 1000.0f;
}
if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
//使用soundpool播放
mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
volFloat, volFloat, 0, 0, 1.0f);
} else {
//MediaPlayer播放
MediaPlayer mediaPlayer = new MediaPlayer();
try {
String filePath = getSoundEffectFilePath(effectType);
mediaPlayer.setDataSource(filePath);
mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
mediaPlayer.prepare();
mediaPlayer.setVolume(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);
}
}
}
}
呀,我们发现,我们的已经到了我们的播放代码了,当SOUND_EFFECT_FILES_MAP这个二维数组里面的值大于0的时候,使用SoundPool播放按键音量,否则使用MediaPlayer播放。
那么这个SOUND_EFFECT_FILES_MAP这个数组里面的值是怎么来的呢?
我们看下我们注释一的地方,onLoadSoundEffects方法做了一些资源的初始化。
private boolean onLoadSoundEffects() {
int status;
synchronized (mSoundEffectsLock) {
//判断系统是否启动成功
if (!mSystemReady) {
Log.w(TAG, "onLoadSoundEffects() called before boot complete");
return false;
}
.......
loadTouchSoundAssets();
//构造SoundPool对象
mSoundPool = new SoundPool.Builder()
.setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build())
.build();
mSoundPoolCallBack = null;
//内部构造了一个消息循环
//内部创建了mSoundPoolCallBack并且为SoundPool设置了OnLoadCompleteListener回调
mSoundPoolListenerThread = new SoundPoolListenerThread();
mSoundPoolListenerThread.start();
.........
.........
int numSamples = 0;
//for循环为SOUND_EFFECT_FILES_MAP赋值
for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
.......
if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
//获取文件路径
String filePath = getSoundEffectFilePath(effect);
//注意这里的load是个异步操作,如果load成功
//回调mSoundPoolListenerThread注册回调的回调方法即mSoundPoolCallBack方法
int sampleId = mSoundPool.load(filePath, 0);
if (sampleId <= 0) {
Log.w(TAG, "Soundpool could not load file: "+filePath);
} else {
//数组内部的值赋值为sampleId
SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
numSamples++;
}
} else {
SOUND_EFFECT_FILES_MAP[effect][1] =
poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
}
}
// 这里注意了上面我门说了load方法是个异步的方法
if (numSamples > 0) {
mSoundPoolCallBack.setSamples(poolId);
attempts = 3;
status = 1;
while ((status == 1) && (attempts-- > 0)) {
try {
//调用wait方法开始等待
mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
//mSoundPoolListenerThread内部注册的setOnLoadCompleteListener内部调用notify释放了 锁之后,表示load成功了
status = mSoundPoolCallBack.status();
} catch (InterruptedException e) {
Log.w(TAG, "Interrupted while waiting sound pool callback.");
}
}
} else {
status = -1;
}
.......
.......
}
return (status == 0);
}
我们来看下mSoundPoolListenerThread内部注册的OnLoadCompleteListener回调。
private final class SoundPoolCallback implements
android.media.SoundPool.OnLoadCompleteListener {
..........
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
synchronized (mSoundEffectsLock) {
//等待所有的资源全部加载成功
//调用notify释放锁 与上面的wait相对应
int i = mSamples.indexOf(sampleId);
if (i >= 0) {
mSamples.remove(i);
}
if ((status != 0) || mSamples. isEmpty()) {
mStatus = status;
mSoundEffectsLock.notify();
}
}
好了,我们知道当我们资源初始化成功后,SOUND_EFFECT_FILES_MAP的值是大于0 的,为sampleId。所以我们的onPlaySoundEffect正常情况下是使用SoundPool播放按键音的。
4.SoundPool.play
public final int play(int soundID, float leftVolume, float rightVolume,
int priority, int loop, float rate) {
baseStart();
return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
}
调用_play方法,这个方法是native方法。
5.从android_media_SoundPool.cpp中的_play方法到SoundPool.cpp的play方法
.......
........
ALOGV("play channel %p state = %d", channel, channel->state());
//调用SoundChannel的play方法
channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);
return channelID;
6. SoundChannel::play
void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
float rightVolume, int priority, int loop, float rate)
{
........
........
// 如果存在有相同sampleId的AudioTrack,就使用这个AudioTrack
if (mAudioTrack != 0 && mPrevSampleID == sample->sampleID()) {
// the sample rate may fail to change if the audio track is a fast track.
if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
newTrack = mAudioTrack;
ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
}
}
........
........
uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
//如果不存在就创建一个新的AudioTrack
newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
bufferFrames, AUDIO_SESSION_ALLOCATE, AudioTrack::TRANSFER_DEFAULT,
NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
#endif
oldTrack = mAudioTrack;
status = newTrack->initCheck();
if (status != NO_ERROR) {
ALOGE("Error creating AudioTrack");
// newTrack goes out of scope, so reference count drops to zero
goto exit;
}
// From now on, AudioTrack callbacks received with previous toggle value will be ignored.
mToggle = toggle;
mAudioTrack = newTrack;
ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
}
//设置是否静音
if (mMuted) {
newTrack->setVolume(0.0f, 0.0f);
} else {
newTrack->setVolume(leftVolume, rightVolume);
}
newTrack->setLoop(0, frameCount, loop);
.........
//开始播放
mAudioTrack->start();
mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
}
exit:
ALOGV("delete oldTrack %p", oldTrack.get());
if (status != NO_ERROR) {
mAudioTrack.clear();
}
}
经过上面源码分析,我们可以知道,SounPool底层是使用AudioTrack进行播放的,这个AudioTrack是什么呢,不知道的朋友可以去了解一下Android Audio系统。我们这里只要只要知道AudioTrack处理的数据是用来播放PCM数据的。整个play流程分析下来,好像没有地方能够调整音量大小的啊。其实不然,上面我们已经说了,AudioTrack是用来播放PCM数据的,那么我们去处理下PCM数据的大小,不就可以放大音量了么?(参考http://blog.jianchihu.net/pcm-vol-control-advance.html) 仔细想想,我们前面分析SoundPool的时候,传入了音频文件的路径,而底层使用了AudioTrack进行播放,其中必定涉及解码的操作,那么这个解码的操作在哪呢?上面我们提到一个SoundPool使用十分需要注意的地方,它的load方法是异步的,为啥设计成异步呢?是不是执行了什么耗时操作呢?比如解码操作?
分析到这里,那我们就回头看看我们的load方法咯!
7.SoundPool.load
public int load(AssetFileDescriptor afd, int priority) {
if (afd != null) {
long len = afd.getLength();
if (len < 0) {
throw new AndroidRuntimeException("no length for fd");
}
//调用了native方法
return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
} else {
return 0;
}
}
8.SoundPool.cpp的load方法
int SoundPool::load(int fd, int64_t offset, int64_t length, int priority __unused)
{
ALOGV("load: fd=%d, offset=%" PRId64 ", length=%" PRId64 ", priority=%d",
fd, offset, length, priority);
int sampleID;
{
Mutex::Autolock lock(&mLock);
sampleID = ++mNextSampleID;
sp<Sample> sample = new Sample(sampleID, fd, offset, length);
mSamples.add(sampleID, sample);
sample->startLoad();
}
//调用SoundPoolThread的loadSample方法
mDecodeThread->loadSample(sampleID);
return sampleID;
}
9.SoundPoolThread.cpp的loadSample方法
void SoundPoolThread::loadSample(int sampleID) {
//往消息队列发送了一个消息
write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
}
10.SoundPoolThread::doLoadSample
void SoundPoolThread::doLoadSample(int sampleID) {
sp <Sample> sample = mSoundPool->findSample(sampleID);
status_t status = -1;
if (sample != 0) {
status = sample->doLoad();
}
mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
}
11.Sample::doLoad
status_t Sample::doLoad()
{
........
........
//这里很重要创建了一个MemoryHeapBase用来描述匿名共享内存的服务
mHeap = new MemoryHeapBase(kDefaultHeapSize);
ALOGV("Start decode");
//进行解码操作
status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
mHeap, &mSize);
........
........
return NO_ERROR;
error:
mHeap.clear();
return status;
}
走到Sample的doLoad方法,里面做了两个很重要的操作,1.创建了MemoryHeapBase描述匿名共享内存的服务。2.对音频文件进行解码操作。匿名共享内存的知识,我们这里不做关注,有兴趣的朋友可以自行了解,这里我们只要知道,它一般被用来进程间通信数据的传递就可以了。我们主要关注decode方法。
12.decode
static status_t decode(int fd, int64_t offset, int64_t length,
uint32_t *rate, int *numChannels, audio_format_t *audioFormat,
sp<MemoryHeapBase> heap, size_t *memsize) {
.......
......
//调用getBase方法获取匿名共享内存的映射地址
uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
//获取匿名共享内存的大小
size_t available = heap->getSize();
size_t written = 0;
while (!sawOutputEOS) {
if (!sawInputEOS) {
......
......
//调用memcpy方法向开辟的匿名共享内存中写入数据
memcpy(writePos, buf + info.offset, dataSize);
........
........
}
}
return UNKNOWN_ERROR;
}
decode方法中我们看到,它调用了memcpy方法向匿名共享内存写入了解码完成的数据。这个数据其实就是我们Audiotrack播放的PCM数据。
好了分析到这里我们总结一下,播放按键音流程从AudioManager的playSoundEffect方法到AudioService的playSoundEffectVolume方法,然后向mAudioHandler发送一个消息,处理消息时调用onPlaySoundEffect方法。这个方法里一些资源加载的判断和区分,重要的是这里的构建完成SoundPool对象后的load方法**(异步方法**),这个方法最后会创建一块匿名共享内存,并且执行解码操作,然后将解码完成的数据写入匿名共享内存,供底层的AudioTrack播放时使用。
如果了解如何增大PCM数据音量方法的朋友看到这里,肯定已经有些解决方案了。我们只要在decode方法解码是,将写入匿名共享内存的数据做一下处理就好了,下面我们直接贴代码。
//在SoundPool.cpp文件中增加数据处理方法
static void increasePCMData(uint8_t * buf, int64_t length, int factor) {
signed short MIN = -0x8000;
signed short MAX = 0x7FFF;
signed short low = 0, high = 0, data = 0, maxData = 0, minData = 0;
//获取一个音频帧中的最大值`max`和最小值`min`
for (int i = 0; i < length; i += 2) {
low = buf[i];
high = buf[i + 1];
data = low + (high << 8);
maxData = maxData > data ? maxData : data;
minData = minData < data ? minData : data;
}
//根据获取到的最大值和最小值分别计算出在不失真的情况下,允许的放大倍数`maxfactor`和`minfactor`
signed short maxfactor = maxData != 0 ? MAX / maxData : 1;
signed short minfactor = minData != 0 ? MIN / minData : 1;
if (minfactor == 1 || maxfactor == 1) {
}
//取其最小值为允许的放大倍数`allowfactor`
signed short allowfactor = 0;
if (maxfactor >= minfactor) {
allowfactor = minfactor;
} else {
allowfactor = maxfactor;
}
//根据经验系数`factor`,选择合适的系数
factor = factor > allowfactor ? allowfactor : factor;
if (factor <= 1) {
}
//对PCM数据放大
signed long newData = 0;
for (int i = 0; i < length; i += 2) {
low = buf[i];
high = buf[i + 1];
data = low + (high << 8);
newData = data * factor;
//边界值溢出处理
if (newData < MIN) {
newData = MIN;
} else if (newData > MAX) {
newData = MAX;
}
data = newData & 0xffff;
buf[i] = data & 0x00ff;
buf[i + 1] = (data & 0xff00) >> 8;
}
}
//decode方法做如下修改
//调用我们新增的方法,对数据进行处理,然后将处理完的数据写入匿名共享内存。
increasePCMData(buf,length,6);
size_t dataSize = info.size;
if (dataSize > available) {
dataSize = available;
}
memcpy(writePos, buf + info.offset, dataSize);
修改做完了之后,重新编译验证一下,发现音量的确增大了许多。好了,关于Framework层增大SoundPool音量的方法,到这里就介绍结束了。这里提一下MediaPlayer底层还是使用的AudioTrack进行音频的播放,有兴趣的朋友可以自己看源码分析一下,我们如何去增大MediaPlayer播放音频的声音大小。
关于声音的一些知识大家可以参考:http://blog.jianchihu.net/pcm-vol-control-advance.html
最后
以上就是野性鼠标为你收集整理的带着问题分析Framework层源码(一):按键音声音太小,我们该如何增大?的全部内容,希望文章能够帮你解决带着问题分析Framework层源码(一):按键音声音太小,我们该如何增大?所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复