soundpool代码分析

简述

SoundPool顾名思义,就是将很多声音资源都放入到池中,可以重复利用。SoundPool中有channel的概念,channel是audiotrack的载体,channel是SoundPool中的通道单元,sample是声音的资源单元,SoundPool将sample分配给channel去播放,通过对channel以及sample的重复利用来达到资源利用的目的。

正文

由于该部分代码逻辑以及实现相对简单,我们不打算写测试程序了,直接按照初始化,分配,播放,结束,暂停这样的逻辑来看。

初始化

先来看下SoundPool的初始化:

SoundPool::SoundPool(int maxChannels, const audio_attributes_t* pAttributes)
{
    // check limits 最大只能支持到32个channel,其实就是对应32个audiotrack
    mMaxChannels = maxChannels;
    if (mMaxChannels < 1) {
        mMaxChannels = 1;
    }
    else if (mMaxChannels > 32) {
        mMaxChannels = 32;
    }
    ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
    // 相关参数初始化
    mQuit = false;
    mDecodeThread = 0;
    memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
    mAllocated = 0;
    mNextSampleID = 0;
    mNextChannelID = 0;

    mCallback = 0;
    mUserData = 0;
    // channelpool空间分配、初始化,push到mChannels中
    mChannelPool = new SoundChannel[mMaxChannels];
    for (int i = 0; i < mMaxChannels; ++i) {
        // 对channel进行简单的初始化,将SoundPool传入,prevSampleId设置为-1
        mChannelPool[i].init(this);
        mChannels.push_back(&mChannelPool[i]);
    }

    // start decode thread
    startThreads();
}

接下来看下startThreads

bool SoundPool::startThreads()
{
    // 让beginThread函数称为一个线程在跑,后面有贴这部分的代码
    createThreadEtc(beginThread, this, "SoundPool");
    if (mDecodeThread == NULL)
        // 创建decode线程
        mDecodeThread = new SoundPoolThread(this);
    return mDecodeThread != NULL;
}

看下beginThread,只是简单的获取SoundPool对象,然后执行run方法。

int SoundPool::beginThread(void* arg)
{
    SoundPool* p = (SoundPool*)arg;
    return p->run();
}

线程的主体内容:主要是监控mStop和mRestart两个数组,如果里面都有成员,则进行相应的操作。

int SoundPool::run()
{
    mRestartLock.lock();
    while (!mQuit) {
        mCondition.wait(mRestartLock);
        ALOGV("awake");
        if (mQuit) break;
        // 需要结束的channels
        while (!mStop.empty()) {
            SoundChannel* channel;
            List<SoundChannel* >::iterator iter = mStop.begin();
            channel = *iter;
            mStop.erase(iter);
            mRestartLock.unlock();
            if (channel != 0) {
                Mutex::Autolock lock(&mLock);
                channel->stop();
            }
            mRestartLock.lock();
            if (mQuit) break;
        }
        // 需要重启的channels,这种场景是一个channel处在NO IDLE,却又被复用了,会先将其stop掉,
        // 然后进行restart的操作。这个需要结合channel中的nextEvent一起看。
        while (!mRestart.empty()) {
            SoundChannel* channel;
            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;
        }
    }

    mStop.clear();
    mRestart.clear();
    mCondition.signal();
    mRestartLock.unlock();
    return 0;
}

startThreads除了将SoundPool中的线程跑起来之外,还将decode线程的SoundPoolThread跑起来。我们来看下SoundPoolThread的构造函数:

SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
    mSoundPool(soundPool)
{
    // 消息队列设置容量,默认为5
    mMsgQueue.setCapacity(maxMessages);
    // 将该线程跑起来。同样直接看run函数就行了。
    if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
        mRunning = true;
    }
}

int SoundPoolThread::run() {
    ALOGV("run");
    for (;;) {
        // 获取信息,然后根据信息进行相应的处理,只处理了kill和load sample的操作。
        SoundPoolMsg msg = read();
        switch (msg.mMessageType) {
        case SoundPoolMsg::KILL:
            // 该循环就直接退出了。
            return NO_ERROR;
        case SoundPoolMsg::LOAD_SAMPLE:
            // 这个部分的代码,我们下面load的过程再引入。
            doLoadSample(msg.mData);
            break;
        default:
            break;
        }
    }

}

到这边SoundPool就创建出来了。

load sound

接下来我们来看下loadplay这两个关键性函数。

int SoundPool::load(int fd, int64_t offset, int64_t length, int priority __unused)
{
    Mutex::Autolock lock(&mLock);
    // 创造sample对象,这个对应到一个音源
    sp<Sample> sample = new Sample(++mNextSampleID, fd, offset, length);
    // 添加到数组中
    mSamples.add(sample->sampleID(), sample);
    // 开始导入
    doLoad(sample);
    return sample->sampleID();
}

void SoundPool::doLoad(sp<Sample>& sample)
{
    // 只是简单的将sample的mState这是为loading
    sample->startLoad();
    // 通知decode线程去load这个sample
    mDecodeThread->loadSample(sample->sampleID());
}
// 将请求push到消息队列中
void SoundPoolThread::loadSample(int sampleID) {
    write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
}
// 刚才我们看decode线程的时候看到,下面函数就是load sample的处理方法。
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));
}

接下来我们看下sample如何load文件的。

status_t Sample::doLoad()
{
    uint32_t sampleRate;
    int numChannels;
    audio_format_t format;
    status_t status;
    mHeap = new MemoryHeapBase(kDefaultHeapSize);
    // 进行解码,并且获取到对应的数据。
    status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
                                 mHeap, &mSize);
    // 关闭文件
    ::close(mFd);
    mFd = -1;
    if (status != NO_ERROR) {
        goto error;
    }

    if (sampleRate > kMaxSampleRate) {
       status = BAD_VALUE;
       goto error;
    }

    if ((numChannels < 1) || (numChannels > 8)) {
        status = BAD_VALUE;
        goto error;
    }
    // 让mData指向对应的存储空间。
    mData = new MemoryBase(mHeap, 0, mSize);
    mSampleRate = sampleRate;
    mNumChannels = numChannels;
    mFormat = format;
    // sample状态切换到READY
    mState = READY;
    return NO_ERROR;

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

上面函数就是去解析文件,然后获取对应的数据,并将sample的状态切到READY。

顺便看下unload流程:

bool SoundPool::unload(int sampleID)
{
    Mutex::Autolock lock(&mLock);
    return mSamples.removeItem(sampleID) >= 0; // removeItem() returns index or BAD_VALUE
}

只是简单的将sample从mSamples中移除了。

play流程

接下来我们看play的流程

int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
        int priority, int loop, float rate)
{
    sp<Sample> sample;
    SoundChannel* channel;
    int channelID;

    Mutex::Autolock lock(&mLock);

    if (mQuit) {
        return 0;
    }
    // 找到对应的sample,并且判断是否已经ready了。
    sample = findSample(sampleID);
    if ((sample == 0) || (sample->state() != Sample::READY)) {
        return 0;
    }
    // 将channelpool的信息打印出来。
    dump();

    // 分配channel
    channel = allocateChannel_l(priority, sampleID);

    // 如果没有空间了,返回0
    if (!channel) {
        return 0;
    }
    // channelID递增
    channelID = ++mNextChannelID;
    // 让channel进行paly
    channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);
    return channelID;
}

我们先来看下分配channel的策略。

SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
{
    List<SoundChannel*>::iterator iter;
    SoundChannel* channel = NULL;

    // 检查下是否有哪个channel曾经打开过指定sampleID的,并且处于可用状态。可用状态是通过state来判断
    if (!mChannels.empty()) {
        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
            if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
                channel = *iter;
                mChannels.erase(iter);
                break;
            }
        }
    }

    // 如果上面没有的话,并且channel不为空,取第一个进行匹配,如果priority大于第一个,也就是大于最小
    // 值。所以正常可以分配到。默认0是最低的了,所以上层必须给一个大于等于0的priority。
    if (!channel && !mChannels.empty()) {
        iter = mChannels.begin();
        if (priority >= (*iter)->priority()) {
            channel = *iter;
            mChannels.erase(iter);
        }
    }

    // 对所有channel按照优先级大小进行排序 从小到大排序
    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,但是却给了它一个全新的channelID,是一个递增的值。
接着我们看下channel中如何实现play方法的。

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

        // 如果当前的channel还没进入idle状态,需要将新的这个请求设置到nextEvent中,然后结束当前的
        // 该channel播放的track。
        if (mState != IDLE) {
            // 将相关参数设置到NextEvent中。stop结束完,就直接返回了。
            mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
            stop_l();
            return;
        }

        // initialize track
        size_t afFrameCount;
        uint32_t afSampleRate;
        // 获取一些常规信息。这边通过属性获取streamtype需要注意下,这边获取的跟audiopolicy中获取的可能会不一样
        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;
        // 如果是loop的话,计算framecount的方式比较特殊
        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;
        }
// 如果没有定义采用SHARED MEMORY
#ifndef USE_SHARED_MEM_BUFFER
        // kDefaultBufferCount这个是预设的值为4倍的audioflinger中的framecount
        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.
        // 检查下这个channel的track之前的sample id是否跟这次一样,如果一样就考虑复用。
        if (mAudioTrack != 0 && mPrevSampleID == sample->sampleID()) {
            // 设置采样率可能会失败,比如在fast track中。
            if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
                newTrack = mAudioTrack;
            }
        }
        // 如果无法复用track的话,需要重新new track
        if (newTrack == 0) {
            // mToggle这个是作为一个区分是否同一个track标志,由于它们公用一个callback,所以通过传递不一样的
            // userData可以进行区分。
            unsigned long toggle = mToggle ^ 1;
            void *userData = (void *)((unsigned long)this | toggle);
            audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);

    #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,
                    // 有callback,采用默认,在audiotrack那边默认采用callback的形式
                    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) {
                goto exit;
            }
            // 更新下toggle,这样就可以进行做区分了。
            mToggle = toggle;
            mAudioTrack = newTrack;
        }
        newTrack->setVolume(leftVolume, rightVolume);
        newTrack->setLoop(0, frameCount, loop);
        mPos = 0;
        mSample = sample;
        // 这边将nextChannelID给到ChannelID中,在下面给清除掉了。mChannelID这个是nextEvent的成员,所以也是
        // nextChannelID,如果有值存在的话,说明这个channel还有没完成的事情,需要再进行处理。
        mChannelID = nextChannelID;
        mPriority = priority;
        mLoop = loop;
        mLeftVolume = leftVolume;
        mRightVolume = rightVolume;
        mNumChannels = numChannels;
        mRate = rate;
        // 这个需要注意的地方,这边将清空channelId和sample的计数,说明后续没有事情要做了。
        clearNextEvent();
        // 状态切换到PLAYING
        mState = PLAYING;
        // track开始播放,这个时候就会问SoundPool要数据了。
        mAudioTrack->start();
        mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
    }

exit:
    if (status != NO_ERROR) {
        mAudioTrack.clear();
    }
}

接下来我们来看下callback的处理逻辑。

// 这边第二个参数 就是刚才我们构造的toggle,这个event是从audiotrack那边发过来的。
// 第三个参数,也是从audiotrack传递过来的,如果是需要更多的数据的话,就会传个缓冲区的buffer过来,否则基本上为NULL
void SoundChannel::callback(int event, void* user, void *info)
{
    SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1));

    channel->process(event, info, (unsigned long)user & 1);
}
// 真正的实现体在这边
void SoundChannel::process(int event, void *info, unsigned long toggle)
{
    Mutex::Autolock lock(&mLock);

    AudioTrack::Buffer* b = NULL;
    // 需要更多的数据,获取audiotrack中的buffer的地址,一会儿将数据拷贝到这边来。
    if (event == AudioTrack::EVENT_MORE_DATA) {
       b = static_cast<AudioTrack::Buffer *>(info);
    }
    // 发现audiotrack发生改变了,这个callback就可以直接退出来不需要再进行处理了。
    if (mToggle != toggle) {
        if (b != NULL) {
            b->size = 0;
        }
        return;
    }
    // 获取sample对象
    sp<Sample> sample = mSample;

    if (event == AudioTrack::EVENT_MORE_DATA) {

        // 发现缓冲区的size为0的话,表示已经stop了,直接返回
        if (b->size == 0) return;
        // 如果状态为IDLE,将audiotrack缓冲区的size设置为0
        if (mState == IDLE) {
            b->size = 0;
            return;
        }
        // 如果sample存在的话,则开始获取数据
        if (sample != 0) {
            // fill buffer
            uint8_t* q = (uint8_t*) b->i8;
            size_t count = 0;

            if (mPos < (int)sample->size()) {
                uint8_t* p = sample->data() + mPos;
                count = sample->size() - mPos;
                if (count > b->size) {
                    count = b->size;
                }
                // 进行数据拷贝
                memcpy(q, p, count);
            } else if (mPos < mAudioBufferSize) {
                count = mAudioBufferSize - mPos;
                if (count > b->size) {
                    count = b->size;
                }
                // 将0到count进行清空
                memset(q, 0, count);
            }

            mPos += count;
            b->size = count;
        }
    // 如果出现underrun或者是已经buffer end了,那就将该track加入到stop队列,让SoundPool去stop
    } else if (event == AudioTrack::EVENT_UNDERRUN || event == AudioTrack::EVENT_BUFFER_END) {
        mSoundPool->addToStopList(this);
    } else if (event == AudioTrack::EVENT_LOOP_END) {
        ALOGV("End loop %p channel %d", this, mChannelID);
    } else if (event == AudioTrack::EVENT_NEW_IAUDIOTRACK) {
        ALOGV("process %p channel %d NEW_IAUDIOTRACK", this, mChannelID);
    } else {
        ALOGW("SoundChannel::process unexpected event %d", event);
    }
}

上面的函数主要就是实现了audiotrack中需要callback,一个是获取数据,一个是实现stop。

我们先看下nextEvent的部分。
在上面我们看到play函数中,有判断当前channel的state是否不为IDLE,如果不为IDLE,那就会进行nextEvent的set操作,然后调用channel的stop_l函数。

set函数代码:

void SoundEvent::set(const sp<Sample>& sample, int channelID, float leftVolume,
            float rightVolume, int priority, int loop, float rate)
{
    mSample = sample;
    mChannelID = channelID;
    mLeftVolume = leftVolume;
    mRightVolume = rightVolume;
    mPriority = priority;
    mLoop = loop;
    mRate =rate;
}

只是简单的将参数进行保存,其实最主要的就是sample和channelID了。有了channelID,就表示有NextEvent了。

stop流程

接下来我们来看下stop_l的逻辑:

void SoundChannel::stop_l()
{
    if (doStop_l()) {
        mSoundPool->done_l(this);
    }
}

bool SoundChannel::doStop_l()
{
    if (mState != IDLE) {
        setVolume_l(0, 0);
        mAudioTrack->stop();
        // 保存当前sampleID到mPrevSampleID
        mPrevSampleID = mSample->sampleID();
        mSample.clear();
        // 状态重返IDLE
        mState = IDLE;
        // 优先级被设置为-1
        mPriority = IDLE_PRIORITY;
        return true;
    }
    return false;
}

void SoundPool::done_l(SoundChannel* channel)
{
    // 如果是被强制stop的话,那这个channel还有事情要继续处理,添加到restartlist中
    if (channel->nextChannelID() != 0) {
        addToRestartList(channel);
    }
    // 表示该channel可以再次被利用,会回到mChannels的头部
    else {
        moveToFront_l(channel);
    }
}

stop_l的逻辑大体就是停止audiotrack,然后保存sample id,更新mstate、priority,如果有nextevent,那就添加到restart,否则将该channel放到mChannel的队列头部,方便下次使用。

上面是属于强制stop的,我们再来看看因为underrun或者buffer end的stop。
只是将其添加到了SoundPool的stop list中,前面我们已经贴出来run函数的代码了,其中主要就是调用了channel的stop函数。

void SoundChannel::stop()
{
    bool stopped;
    {
        Mutex::Autolock lock(&mLock);
        // doStop_l我们上面看过了。
        stopped = doStop_l();
    }

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

逻辑上跟stop_l并没啥区别,只是在doStop_l的时候加锁了。这也是什么还要在实现另外一个stop的原因,一个因为在原来的函数中已经加锁了,所以不能也无需再加锁。

总结:

SoundPool的实现相对简单,没啥好总结的了。
它的好处就是重复利用声音文件,我们只需要告诉SoundPool我们需要播放某个sample id就可以完成播放的逻辑,免去维护audiotrack等操作。