带着问题分析Framework层源码(一):按键音声音太小,我们该如何增大?

作为一名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

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值