SoundPool

作者: DarcyZhou | 来源:发表于2022-09-26 08:49 被阅读0次

1.SoundPool和MediaPlayer

  • SoundPool适合短且对反应速度比较高的情况(游戏音效或按键声等),文件大小一般控制在几十K到几百K,最好不超过1M;
  • SoundPool 可以与MediaPlayer同时播放,SoundPool也可以同时播放多个声音;
  • SoundPool 最终编解码实现与MediaPlayer相同;
  • MediaPlayer只能同时播放一个声音,加载文件有一定的时间,适合文件比较大,响应时间要是那种不是非常高的场景。

2.按键音实例

2.1 按键响应流程

播放代码位于framework/base/media/java/android/media/AudioManager.java

public void  playSoundEffect(int effectType) {
    if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
        return;
    }

      if (!querySoundEffectsEnabled()) {
          return;
      }

    IAudioService service = getService();
    try {
        service.playSoundEffect(effectType);
    } catch (RemoteException e) {
        Log.e(TAG, "Dead object in playSoundEffect"+e);
    }
}
  • 播放流程图
播放流程.png
  • /frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void onPlaySoundEffect(int effectType, int volume) {
    synchronized (mSoundEffectsLock) {

        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);
            volFloat = 1.0f;

        } else {
            volFloat = 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);
           Log.d(TAG, "mSoundPool play ----- ");
        } else {
           // 省略MediaPlayer播放流程
        }
    }
}

2.2 SoundPool初始化

AudioService.java#onLoadSoundEffects()

loadTouchSoundAssets(); //加载音频资源

mSoundPool = new SoundPool.Builder()
        .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
        .setAudioAttributes(new AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
            .build())
        .build();

3.SoundPool源码分析

3.1 构造实例

soundpool一般可以通过Builder()构造实例,并通过setMaxStreams()和setAudioAttributes()分别设置同时播放的最大流数,以及音频参数AudioAttributes,其中若不设置参数则使用默认参数。

  • /frameworks/base/media/java/android/media/SoundPool.java
public SoundPool build() {
    if (mAudioAttributes == null) {
        mAudioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA).build();
    }
    return new SoundPool(mMaxStreams, mAudioAttributes);
}
-------------
private SoundPool(int maxStreams, AudioAttributes attributes) {
    super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL);

    // do native setup
    if (native_setup(new WeakReference<SoundPool>(this), maxStreams, attributes) != 0) {
        throw new RuntimeException("Native setup failed");
    }
    mLock = new Object();
    mAttributes = attributes;

    baseRegisterPlayer();
}
  • /frameworks/base/media/jni/soundpool/android_media_SoundPool.cpp

JNI调用,会创建C++的SoundPool实例:

180  static jint
181  android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
182          jint maxChannels, jobject jaa)
183  {
...
203      ALOGV("android_media_SoundPool_native_setup");
204      SoundPool *ap = new SoundPool(maxChannels, paa);
...
  • /frameworks/base/media/jni/soundpool/SoundPool.cpp
SoundPool::SoundPool(int maxChannels, const audio_attributes_t* pAttributes)
{
    ALOGV("SoundPool constructor: maxChannels=%d, attr.usage=%d, attr.flags=0x%x, attr.tags=%s",
            maxChannels, pAttributes->usage, pAttributes->flags, pAttributes->tags);

    // check limits
    mMaxChannels = maxChannels;
    if (mMaxChannels < 1) {
        mMaxChannels = 1;
    }
    else if (mMaxChannels > 32) {
        mMaxChannels = 32;
    }
    ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);

    mQuit = false;
    mMuted = false;
    mDecodeThread = 0;
    memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
    mAllocated = 0;
    mNextSampleID = 0;
    mNextChannelID = 0;

    mCallback = 0;
    mUserData = 0;

    mChannelPool = new SoundChannel[mMaxChannels];
    for (int i = 0; i < mMaxChannels; ++i) {
        mChannelPool[i].init(this);
        mChannels.push_back(&mChannelPool[i]);
    }

    // start decode thread
    startThreads();
}

这里的频道数必须在1到32个之间,即最大支持32路播放。然后又初始化了mChannelPool数组,即多个SoundChannel类,并且对每一个调用其init函数,放入mChannels指针列表。我们这里继续看下SoundChannel类的init函数:

void SoundChannel::init(SoundPool* soundPool)
{
    mSoundPool = soundPool;
    mPrevSampleID = -1;
}

这个函数只是简单的保存了调用类SoundPool的指针到mSoundPool变量里。回到构造函数,我们继续看startThreads函数。

bool SoundPool::startThreads()
{
    createThreadEtc(beginThread, this, "SoundPool");
    if (mDecodeThread == NULL)
        mDecodeThread = new SoundPoolThread(this);
    return mDecodeThread != NULL;
}

从这个函数可以看出每个SoundPool都有一个decode线程(只能有一个),好了,这里只是新建的一个SoundPoolThread,然后就返回了。这样SoundPool在native的初始化就完成了,我们有了一些SoundChannel,还有一个decode线程。

3.2 加载资源

调用mSoundPool.load方法加载资源,在SoundPool中这个load函数有多个重载对应不同的资源类型,比如内置资源就是用资源id,而外置资源就是用文件路径,或者asset文件路径,或者文件打开后的FD标识,最终还是调用到native层_load()。

49  static jint
50  android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
51          jlong offset, jlong length, jint priority)
52  {
53      ALOGV("android_media_SoundPool_load_FD");
54      SoundPool *ap = MusterSoundPool(env, thiz);
55      if (ap == NULL) return 0;
56      return (jint) ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor),
57              int64_t(offset), int64_t(length), int(priority));
58  }

函数MusterSoundPool用来获取之前新建的SoundPool类,并且调用其load函数

  • /frameworks/base/media/jni/soundpool/SoundPool.cpp
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();
    }
    mDecodeThread->loadSample(sampleID);
    return sampleID;
}

在load函数中,首先会新建一个名叫sample的类且跟上四个参数,sample的id号,加载文件路径,偏移位置和长度。并且将这个新建的类保存在mapping列表mSamples中,即每一个需加载的文件有一个Sample类对应,索引号即为其id号。

  • /frameworks/base/media/jni/soundpool/SoundPoolThread.cpp
//------ 1
void startLoad() { mState = LOADING; }
//------ 2
void SoundPoolThread::loadSample(int sampleID) {
   write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
}
//------ 3
void SoundPoolThread::write(SoundPoolMsg msg) {
    Mutex::Autolock lock(&mLock);
    while (mMsgQueue.size() >= maxMessages) {
        mCondition.wait(mLock);
    }

    // if thread is quitting, don't add to queue
    if (mRunning) {
        mMsgQueue.push(msg);
        mCondition.signal();
    }
}
//------ 4
int SoundPoolThread::run() {
    ALOGV("run");
    for (;;) {
        SoundPoolMsg msg = read();
        ALOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
        switch (msg.mMessageType) {
        case SoundPoolMsg::KILL:
            ALOGV("goodbye");
            return NO_ERROR;
        case SoundPoolMsg::LOAD_SAMPLE:
            doLoadSample(msg.mData);
            break;
        default:
            ALOGW("run: Unrecognized message %d\n",
                    msg.mMessageType);
            break;
        }
    }
}

首先调用Sample类中的startLoad函数来设置当前sample的状态,这里即LOADING状态。在loadSample函数中会将当前的sampleid号打包成一个消息并调用write函数写到消息队列中,如果消息队列满了会稍微等等,如果还没满则会加入队列并通知取消息的线程(mDecodeThread)。在这个线程中会读取消息的类型,这里为LOAD_SAMPLE,并调用doLoadSample函数,参数即为sampleid号。我们看下这个函数。

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

函数会先根据id号找到相对应的Sample类并调用doLoad函数(说觉得这里转了一大圈才开始加载数据只是为了调度来平衡磁盘操作)

  • /frameworks/base/media/jni/soundpool/SoundPool.cpp
status_t Sample::doLoad()
{
    uint32_t sampleRate;
    int numChannels;
    audio_format_t format;
    status_t status;
    mHeap = new MemoryHeapBase(kDefaultHeapSize);

    ALOGV("Start decode");
    status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
                                 mHeap, &mSize);
    ALOGV("close(%d)", mFd);
    ::close(mFd);
    mFd = -1;
    if (status != NO_ERROR) {
        ALOGE("Unable to load sample");
        goto error;
    }
    ALOGV("pointer = %p, size = %zu, sampleRate = %u, numChannels = %d",
          mHeap->getBase(), mSize, sampleRate, numChannels);

    if (sampleRate > kMaxSampleRate) {
       ALOGE("Sample rate (%u) out of range", sampleRate);
       status = BAD_VALUE;
       goto error;
    }

    if ((numChannels < 1) || (numChannels > FCC_8)) {
        ALOGE("Sample channel count (%d) out of range", numChannels);
        status = BAD_VALUE;
        goto error;
    }

    mData = new MemoryBase(mHeap, 0, mSize);
    mSampleRate = sampleRate;
    mNumChannels = numChannels;
    mFormat = format;
    mState = READY;
    return NO_ERROR;

error:
    mHeap.clear();
    return status;
}

首先他会进行decode即解码文件成波形文件,可以理解为wav文件纯数据。(预加载以备播放,SoundPool的好处之一)。这里还做了些判断,比如采样率不能大于48000,声道数不能小于1或者大于FCC_8(=8),然后就保存了这个加载数据。

3.3 播放过程

3.3.1 正常流程

再回到按键音播放的例子:

mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
                    volFloat, volFloat, 0, 0, 1.0f);

其实播放也很简单,只需要知道Sampleid号,然后直接调用SoundPool的play函数即可。

  • /frameworks/base/media/java/android/media/SoundPool.java
    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);
    }

通过JNI调用到native层:

  • /frameworks/base/media/jni/soundpool/SoundPool.cpp
int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
        int priority, int loop, float rate)
{
    ALOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
            sampleID, leftVolume, rightVolume, priority, loop, rate);
    SoundChannel* channel;
    int channelID;

    Mutex::Autolock lock(&mLock);

    if (mQuit) {
        return 0;
    }
    // is sample ready?
    sp<Sample> sample(findSample_l(sampleID)); // 1
    if ((sample == 0) || (sample->state() != Sample::READY)) {
        ALOGW("  sample %d not READY", sampleID);
        return 0;
    }

    dump();

    // allocate a channel
    channel = allocateChannel_l(priority, sampleID); // 2

    // no channel allocated - return 0
    if (!channel) {
        ALOGV("No channel allocated");
        return 0;
    }

    channelID = ++mNextChannelID;

    ALOGV("play channel %p state = %d", channel, channel->state());
    channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate); // 3
    return channelID;
}
  • 第一步:根据sampleID找到对应的音频
  • 第二步:分配用于播放用的channel
  • 第三步:使用channel播放音频
SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
{
    List<SoundChannel*>::iterator iter;
    SoundChannel* channel = NULL;

    // check if channel for given sampleID still available
    if (!mChannels.empty()) {
        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
            if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
                channel = *iter;
                mChannels.erase(iter);
                ALOGV("Allocated recycled channel for same sampleID");
                break;
            }
        }
    }

    // allocate any channel
    if (!channel && !mChannels.empty()) {
        iter = mChannels.begin();
        if (priority >= (*iter)->priority()) {
            channel = *iter;
            mChannels.erase(iter);
            ALOGV("Allocated active channel");
        }
    }

    // update priority and put it back in the list
    if (channel) {
        channel->setPriority(priority);
        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
            if (priority < (*iter)->priority()) {
                break;
            }
        }
        mChannels.insert(iter, channel);
    }
    return channel;
}

这个函数其实就是一个算法用来更新channel的,它首先会判断我们传进来的权限值,并找到最接近的那个channel(小于等于)作为返回值,然后再找到最接近的另一个channel(大于等于),并插在它后面。这里的mChannels是我们创建SoundPool时一并初始化的列表。找到了适合弹药口径的打炮,让我们看下channel的play函数。

  • /frameworks/base/media/jni/soundpool/SoundPool.cpp
void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
        float rightVolume, int priority, int loop, float rate)
{
    sp<AudioTrack> oldTrack;
    sp<AudioTrack> newTrack;
    status_t status = NO_ERROR;

    { // scope for the lock
        Mutex::Autolock lock(&mLock);

        ALOGV("SoundChannel::play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f,"
                " priority=%d, loop=%d, rate=%f",
                this, sample->sampleID(), nextChannelID, leftVolume, rightVolume,
                priority, loop, rate);

        // if not idle, this voice is being stolen
        if (mState != IDLE) { // 3.3.1中分析
            ALOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID);
            mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
            stop_l();
            return;
        }

        // initialize track
        size_t afFrameCount;
        uint32_t afSampleRate;
        audio_stream_type_t streamType = audio_attributes_to_stream_type(mSoundPool->attributes());
        if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
            afFrameCount = kDefaultFrameCount;
        }
        if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
            afSampleRate = kDefaultSampleRate;
        }
        int numChannels = sample->numChannels();
        uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5);
        size_t frameCount = 0;

        if (loop) {
            const audio_format_t format = sample->format();
            const size_t frameSize = audio_is_linear_pcm(format)
                    ? numChannels * audio_bytes_per_sample(format) : 1;
            frameCount = sample->size() / frameSize;
        }

#ifndef USE_SHARED_MEM_BUFFER
        uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate;
        // Ensure minimum audio buffer size in case of short looped sample
        if(frameCount < totalFrames) {
            frameCount = totalFrames;
        }
#endif

        // check if the existing track has the same sample id.
        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());
            }
        }
        if (newTrack == 0) {
            // mToggle toggles each time a track is started on a given channel.
            // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
            // as callback user data. This enables the detection of callbacks received from the old
            // audio track while the new one is being started and avoids processing them with
            // wrong audio audio buffer size  (mAudioBufferSize)
            unsigned long toggle = mToggle ^ 1;
            void *userData = (void *)((unsigned long)this | toggle);
            audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);

            // do not create a new audio track if current track is compatible with sample parameters
    #ifdef USE_SHARED_MEM_BUFFER
            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
                    channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData,
                    0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
                    AudioTrack::TRANSFER_DEFAULT,
                    NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
    #else
            uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
            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());
        }
        newTrack->setVolume(leftVolume, rightVolume);
        newTrack->setLoop(0, frameCount, loop);
        mPos = 0;
        mSample = sample;
        mChannelID = nextChannelID;
        mPriority = priority;
        mLoop = loop;
        mLeftVolume = leftVolume;
        mRightVolume = rightVolume;
        mNumChannels = numChannels;
        mRate = rate;
        clearNextEvent();
        mState = PLAYING;
        mAudioTrack->start();
        mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
    }

exit:
    ALOGV("delete oldTrack %p", oldTrack.get());
    if (status != NO_ERROR) {
        mAudioTrack.clear();
    }
}

首先会判断这个channel是不是正在播放,假设这时候channel还没工作,然后我们就会新建一个AudioTrack。这里我们可以看到两端初始化代码并且使用宏来隔开,区别在于一个是共享内存的AudioTrack,另一个使用FastTrack,即低延时的机制。这里还给了一个回调函数参数用来从文件搬数据到AudioTrack,这个回调函数具体我们就不分析的,简单来说就是AudioTrack定时调用回调函数申请数据,回调函数只管读取数据返回给它。在初始化完AudioTrack之后就调用start函数让它工作了,这个时候声音也就出来了。最后这个play函数还不忘把老的AudioTrack给清空防止内存泄漏。

3.3.2 特殊流程

上面SoundPool基本播放声音的分析就结束了,但是还有个特殊情况我们得分析下。即如果所有的channel都在播放,但应用程序又想播放新的数据会怎么处理呢?这时候就会用到之前play函数里那个判断,即是否当前状态不为空闲状态。

  • /frameworks/base/media/jni/soundpool/SoundPool.cpp
if (mState != IDLE) {
    ALOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID);
    mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
    stop_l();
    return;
}

如果当前channel已经在播放,又有一个新的sample想被播放,则会进入这个条件语句。这里会先设置一个新的类SoundEvent,即mNextEvent,这个类会保存想要播放的sample以及channel的id号等一些参数。然后停止当前播放的channel,这一过程称之为偷窃。这里channel的id号不是当前播放的channel的id号,正确的讲每次播放新sample时的channel的id号都不一样。然后我们看下stop_l函数。

void SoundChannel::stop_l()
{
    if (doStop_l()) {
        mSoundPool->done_l(this);
    }
}
------
bool SoundChannel::doStop_l()
{
    if (mState != IDLE) {
        setVolume_l(0, 0);
        ALOGV("stop");
        mAudioTrack->stop();
        mPrevSampleID = mSample->sampleID();
        mSample.clear();
        mState = IDLE;
        mPriority = IDLE_PRIORITY;
        return true;
    }
    return false;
}
------
void SoundPool::done_l(SoundChannel* channel)
{
    ALOGV("done_l(%d)", channel->channelID());
    // if "stolen", play next event
    if (channel->nextChannelID() != 0) {
        ALOGV("add to restart list");
        addToRestartList(channel);
    }

    // return to idle state
    else {
        ALOGV("move to front");
        moveToFront_l(channel);
    }
}

这里会先调用doStop_l来停止当前的播放。然后继续调用SoundPool的done_l函数。在这个函数中,会先判断是否这个channel是否被偷窃了,即调用nextChannelID函数来判断。

  • /frameworks/base/media/jni/soundpool/SoundPool.h
int nextChannelID() { return mNextEvent.channelID(); }

我们可以看到其实就是判断是否mNextEvent是否有id值保存,之前我们已经保存了一份,所以这里的判断就满足了。然后会把当前的channel放到restart列表中

void SoundPool::addToRestartList(SoundChannel* channel)
{
    Mutex::Autolock lock(&mRestartLock);
    if (!mQuit) {
        mRestart.push_back(channel);
        mCondition.signal();
    }
}

添加完channel到mRestart之后,又会向mCondition发出信号,另一个线程肯定已经等了很久了。这个线程之前没有提到,当新建SoundPool的时候,会调用createThreadEtc函数来创建一条SoundPool自己的线程并执行,这时候它就知道要开干了。

int SoundPool::run()
{
    mRestartLock.lock();
...
        while (!mRestart.empty()) {
            SoundChannel* channel;
            ALOGV("Getting channel from list");
            List<SoundChannel*>::iterator iter = mRestart.begin();
            channel = *iter;
            mRestart.erase(iter);
            mRestartLock.unlock();
            if (channel != 0) {
                Mutex::Autolock lock(&mLock);
                channel->nextEvent();
            }
            mRestartLock.lock();
            if (mQuit) break;
        }
    }
...
}

这个线程里会从mRestart列表中一个个拿出channel并调用nextEvent函数。

void SoundChannel::nextEvent()
{
    sp<Sample> sample;
    int nextChannelID;
    float leftVolume;
    float rightVolume;
    int priority;
    int loop;
    float rate;

    // check for valid event
    {
        Mutex::Autolock lock(&mLock);
        nextChannelID = mNextEvent.channelID();
        if (nextChannelID  == 0) {
            ALOGV("stolen channel has no event");
            return;
        }

        sample = mNextEvent.sample();
        leftVolume = mNextEvent.leftVolume();
        rightVolume = mNextEvent.rightVolume();
        priority = mNextEvent.priority();
        loop = mNextEvent.loop();
        rate = mNextEvent.rate();
    }

    ALOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID);
    play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
}

这个函数首先会读取channel的mNextEvent,我们在之前已经设置过一些参数了,包括了sample已经channel的id号等。然后直接调用了之前我们分析的play函数,这样作为play函数来说这就算是一个新的sample需要播放了,然后么,声音就又出来了。

3.3.3 暂停/停止

当然什么事都有始有终,有播放就有停止暂停神马的,这些功能实现比较简单,这里就简单说下,所有sample在播放时都有个channel会返回给应用层,所以停止暂时基本上就是调用那个channel的stop和pause函数,接着对AudioTrack进行函数调用操作。但是如果是被偷窃的channel呢?这个也不必担心,在停止暂停的时候如果当前的channel的id不存在,它会继续找它的mNextEvent里channel的id号,最终还是能达到目的的。

  • /frameworks/base/media/java/android/media/SoundPool.java
public native final void pause(int streamID);
public native final void resume(int streamID);
public native final void autoPause(); // Pause all active streams
public native final void autoResume(); // Resume all previously active streams
public native final void stop(int streamID);
  • /frameworks/base/media/jni/soundpool/SoundPool.cpp
void SoundChannel::pause()
{
    Mutex::Autolock lock(&mLock);
    if (mState == PLAYING) {
        ALOGV("pause track");
        mState = PAUSED;
        mAudioTrack->pause();
    }
}
------影响
void SoundChannel::resume()
{
    Mutex::Autolock lock(&mLock);
    if (mState == PAUSED) {
        ALOGV("resume track");
        mState = PLAYING;
        mAutoPaused = false;
        mAudioTrack->start();
    }
}
------
void SoundChannel::autoPause()
{
    Mutex::Autolock lock(&mLock);
    if (mState == PLAYING) {
        ALOGV("pause track");
        mState = PAUSED;
        mAutoPaused = true;
        mAudioTrack->pause();
    }
}
------
void SoundChannel::autoResume()
{
    Mutex::Autolock lock(&mLock);
    if (mAutoPaused && (mState == PAUSED)) {
        ALOGV("resume track");
        mState = PLAYING;
        mAutoPaused = false;
        mAudioTrack->start();
    }
}
------
void SoundChannel::stop()
{
    bool stopped;
    {
        Mutex::Autolock lock(&mLock);
        stopped = doStop_l();
    }

    if (stopped) {
        mSoundPool->done_l(this);
    }
}

3.4 流程图

整体流程图仅供参考,部分地方由于版本不一致不一样。比如解码的地方。

流程图.png

相关文章

网友评论

      本文标题:SoundPool

      本文链接:https://www.haomeiwen.com/subject/jdynortx.html