SoundPool概述

一、SoundPool简介

    SoundPool可以用来给应用程序管理或者播放音频资源。一个SoundPool是指从应用程序或者文件系统中加载进内存的音频样本的集合。SoundPool利用MediaCodec服务来将音频资源解码成16-bit的PCM数据流,这允许应用程序发送压缩流,而不必在播放期间承受 CPU 加载和解压缩延迟。
    SoundPool加载的音频资源由于是加载进内存,所以要求尽可能的短。每个音频资源的大小被限制在1M,差不多相当于时长5.6s,采样率在44.1kHz的双声道音频资源。如果超过这个限制大小就会被裁剪。
    为了低延迟播放,SoundPool在创建的时候就设置了同一时间播放音频流的数目,如果超过了这个限制,SoundPool将会根据优先级(同优先级根据年龄)自动停止先前播放的音频流。

二、SoundPool API概述

2.1、SoundPool Java层API

InterfaceDescription
release()释放SoundPool使用的资源
release()释放SoundPool使用的资源
load()加载音频资源,并返回一个SoundId,一共重载了四种load方法
unload()利用load得到的SoundId来卸载音频资源,与load对应
play()播放SoundId对应的音频资源,并返回一个用于控制的StreamId
pause()暂停StreamId对应的音频资源
resume()恢复StreamId对应的音频资源,与pause对应
autoPause()暂停所有当前SoundPool播放的音频流
autoResume()恢复所有AutoPause暂停的音频流,与autoPause对应
stop()停止StreamId对应的音频流,并且释放对应的Native层资源
setVolume()设置声音大小,分为左右声道,对应的大小从0~1,还有一个重载方法setVolume,不区分左右声道,一起设置同一增益
setPriority()设置当前streamId对应流的优先级
setLoop()设置循环,0:不循环,-1:一直循环,others:循环的次数
setRate()设置播放速度
setOnLoadCompleteListener()设置加载完成后的回调函数,需要实现OnLoadCompleteListener接口

三、SoundPool用例

fun testSoundPool() {
	soundPool = SoundPool.Builder()
		.setAudioAttributes(
        	AudioAttributes.Builder()
        	.setUsage(AudioAttributes.USAGE_MEDIA)
        	.build())
        .build()
        sourceId = soundPool.load(context, R.raw.testaudio, 1)
        soundPool.setOnLoadCompleteListener{ _, _, status ->
            if (status == 0) {
                //non-zero streamID if successful, zero if failed
                soundPool.play(sourceId, 1F, 1F, 0, -1, 1F);
                isPlaying = true
            } else {
                Log.e(TAG, "LoadSound Failed")
            }
}

四、SoundPool实例的构建

4.1、Java层SoundPool的创建

    	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, getCurrentOpPackageName()) != 0) {
            	throw new RuntimeException("Native setup failed");
        	}
        	mAttributes = attributes;

        	// FIXME: b/174876164 implement session id for soundpool
        	baseRegisterPlayer(AudioSystem.AUDIO_SESSION_ALLOCATE);
    	}

        /**
         * Sets the {@link AudioAttributes}. For examples, game applications will use attributes
         * built with usage information set to {@link AudioAttributes#USAGE_GAME}.
         * @param attributes a non-null
         * @return
         */
        public Builder setAudioAttributes(AudioAttributes attributes)
                throws IllegalArgumentException {
            if (attributes == null) {
                throw new IllegalArgumentException("Invalid null AudioAttributes");
            }
            mAudioAttributes = attributes;
            return this;
        }

        public SoundPool build() {
            if (mAudioAttributes == null) {
                mAudioAttributes = new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA).build();
            }
            return new SoundPool(mMaxStreams, mAudioAttributes);
        }
    }

创建SundPool主要需要两个参数,mMaxStream和mAudioAttributes,mMaxStream可以通过setMaxStream来设置,不设置的话默认是1,mAudioAttribute主要是音频流的属性,最后还是通过JNI调用Native层的方法创建SoundPool对象。

4.2、JNI层SoundPool的创建

frameworks/base/media/jni/soundpool/android_media_SoundPool.cpp

static jint
android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
        jint maxChannels, jobject jaa, jstring opPackageName)
{
    if (jaa == nullptr) {
        ALOGE("Error creating SoundPool: invalid audio attributes");
        return -1;
    }

    audio_attributes_t *paa = nullptr;
    // read the AudioAttributes values
    // 将Java层的AudioAttribute转换到Native层对应的结构体中
    paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
    const auto jtags =
            (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
    const char* tags = env->GetStringUTFChars(jtags, nullptr);
    // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
    strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
    env->ReleaseStringUTFChars(jtags, tags);
    paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
    paa->content_type =
            (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
    paa->flags = (audio_flags_mask_t) env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);

    ALOGV("android_media_SoundPool_native_setup");
    ScopedUtfChars opPackageNameStr(env, opPackageName);
    auto *ap = new SoundPool(maxChannels, paa, opPackageNameStr.c_str());
    if (ap == nullptr) {
        return -1;
    }

    // save pointer to SoundPool C++ object in opaque field in Java object
    env->SetLongField(thiz, fields.mNativeContext, (jlong) ap);

    // set callback with weak reference
    jobject globalWeakRef = env->NewGlobalRef(weakRef);
    ap->setCallback(android_media_callback, globalWeakRef);

    // audio attributes were copied in SoundPool creation
    free(paa);

    return 0;
}

这里的mMaxStream到了Native层成立对应的音频通道,构建Native层的SoundPool实例。

4.3、Native层SoundPool的构建

frameworks/base/media/jni/soundpool/SoundPool.cpp

// kManagerThreads = 1 historically.
// Not really necessary to have more than one, but it does speed things up by about
// 25% having 2 threads instead of 1 when playing many sounds.  Having many threads
// could starve other AudioFlinger clients with SoundPool activity. It may also cause
// issues with app loading, e.g. Camera.
static const size_t kStreamManagerThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;

SoundPool::SoundPool(
        int32_t maxStreams, const audio_attributes_t* attributes, const std::string& opPackageName)
    : mStreamManager(maxStreams, kStreamManagerThreads, attributes, opPackageName)
{
    ALOGV("%s(maxStreams=%d, attr={ content_type=%d, usage=%d, flags=0x%x, tags=%s })",
            __func__, maxStreams,
            attributes->content_type, attributes->usage, attributes->flags, attributes->tags);
}

这里主要是将对应的参数传入mStreamManager初始化,StreamManagerSoundPool作用域中用来管理流的工具,可以通过StreamId在Java层访问。

4.4、StreamManager的初始化

StreamManager::StreamManager(
        int32_t streams, size_t threads, const audio_attributes_t* attributes,
        std::string opPackageName)
    : StreamMap(streams)
    , mAttributes(*attributes)
    , mOpPackageName(std::move(opPackageName))
    , mLockStreamManagerStop(streams == 1 || kForceLockStreamManagerStop)
{
    ALOGV("%s(%d, %zu, ...)", __func__, streams, threads);
    forEach([this](Stream *stream) {
        stream->setStreamManager(this);
        if ((streamPosition(stream) & 1) == 0) { // put the first stream of pair as available.
            mAvailableStreams.insert(stream);
        }
    });

    mThreadPool = std::make_unique<ThreadPool>(
            std::min((size_t)streams,  // do not make more threads than streams to play
                    std::min(threads, (size_t)std::thread::hardware_concurrency())),
            "SoundPool_");
}

设置可用音频流mAvailableStreams是将每一对音频流的第一个音频流加入这个Set中,即(0,2,4,…),然后创建一个线程池,实际上由于kStreamManagerThreads的影响,线程池大小不会超过2。

创建完成SoundPool实例之后,将对应的指针传回到Java层,并将Java层刚开始传入的的弱引用升级成JNI层的全局引用以备后续使用,同时提供了对应的回调函数接口,至此结束了SoundPool Native层对象的创建。

五、SoundPool资源的加载

5.1、Java层SoundPool资源的加载

    /**
     * Load the sound from the specified APK resource.
     *
     * Note that the extension is dropped. For example, if you want to load
     * a sound from the raw resource file "explosion.mp3", you would specify
     * "R.raw.explosion" as the resource ID. Note that this means you cannot
     * have both an "explosion.wav" and an "explosion.mp3" in the res/raw
     * directory.
     *
     * @param context the application context
     * @param resId the resource ID
     * @param priority the priority of the sound. Currently has no effect. Use
     *                 a value of 1 for future compatibility.
     * @return a sound ID. This value can be used to play or unload the sound.
     */
    public int load(Context context, int resId, int priority) {
        AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
        int id = 0;
        if (afd != null) {
            id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
            try {
                afd.close();
            } catch (java.io.IOException ex) {
                //Log.d(TAG, "close failed:", ex);
            }
        }
        return id;
    }

    /**
     * Load the sound from an asset file descriptor.
     *
     * @param afd an asset file descriptor
     * @param priority the priority of the sound. Currently has no effect. Use
     *                 a value of 1 for future compatibility.
     * @return a sound ID. This value can be used to play or unload the sound.
     */
    public int load(AssetFileDescriptor afd, int priority) {
        if (afd != null) {
            long len = afd.getLength();
            if (len < 0) {
                throw new AndroidRuntimeException("no length for fd");
            }
            return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
        } else {
            return 0;
        }
    }

    /**
     * Load the sound from a FileDescriptor.
     *
     * This version is useful if you store multiple sounds in a single
     * binary. The offset specifies the offset from the start of the file
     * and the length specifies the length of the sound within the file.
     *
     * @param fd a FileDescriptor object
     * @param offset offset to the start of the sound
     * @param length length of the sound
     * @param priority the priority of the sound. Currently has no effect. Use
     *                 a value of 1 for future compatibility.
     * @return a sound ID. This value can be used to play or unload the sound.
     */
    public int load(FileDescriptor fd, long offset, long length, int priority) {
        return _load(fd, offset, length, priority);
    }

SoundPool资源的加载方式主要有三种,分别是

  1. 加载特定的APK资源:这里注意不区分文件扩展名,示例中所使用的方式
  2. 通过资源文件描述符加载
  3. 通过文件描述符加载

实际上三种方法最终都要获取对应的文件描述符来调用对应的方法。

5.2、JNI层资源的加载

static jint
android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
        jlong offset, jlong length, jint priority)
{
    ALOGV("android_media_SoundPool_load_FD");
    //将Java层的实例转换成Native层的实例
    SoundPool *ap = MusterSoundPool(env, thiz);
    if (ap == nullptr) return 0;
    return (jint) ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor),
            int64_t(offset), int64_t(length), int(priority));
}

5.3、Native层资源的加载

int32_t SoundPool::load(int fd, int64_t offset, int64_t length, int32_t priority)
{
    ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
            __func__, fd, (long long)offset, (long long)length, priority);
    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
    return mSoundManager.load(fd, offset, length, priority);
}

frameworks/base/media/jni/soundpool/SoundManager.cpp

int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority)
{
    ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
            __func__, fd, (long long)offset, (long long)length, priority);
    int32_t soundID;
    {
        std::lock_guard lock(mSoundManagerLock);
        // mNextSoundID is always positive and does not "integer overflow"
        do {
            mNextSoundID = mNextSoundID == INT32_MAX ? 1 : mNextSoundID + 1;
        } while (findSound_l(mNextSoundID) != nullptr);
        soundID = mNextSoundID;
        auto sound = std::make_shared<Sound>(soundID, fd, offset, length);
        mSounds.emplace(soundID, sound);
    }
    // mDecoder->loadSound() must be called outside of mSoundManagerLock.
    // mDecoder->loadSound() may block on mDecoder message queue space;
    // the message queue emptying may block on SoundManager::findSound().
    //
    // It is theoretically possible that sound loads might decode out-of-order.
    mDecoder->loadSound(soundID);
    return soundID;
}

frameworks/base/media/jni/soundpool/SoundDecoder.cpp

void SoundDecoder::loadSound(int32_t soundID)
{
    ALOGV("%s(%d)", __func__, soundID);
    size_t pendingSounds;
    {
        std::unique_lock lock(mLock);
        while (mSoundIDs.size() == kMaxQueueSize) {
            if (mQuit) return;
            ALOGV("%s: waiting soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
            mQueueSpaceAvailable.wait(lock);
        }
        if (mQuit) return;
        mSoundIDs.push_back(soundID);
        mQueueDataAvailable.notify_one();
        ALOGV("%s: adding soundID: %d  size: %zu", __func__, soundID, mSoundIDs.size());
        pendingSounds = mSoundIDs.size();
    }
    // Launch threads as needed.  The "as needed" is weakly consistent as we release mLock.
    if (pendingSounds > mThreadPool->getActiveThreadCount()) {
        const int32_t id = mThreadPool->launch([this](int32_t id) { run(id); });
        (void)id; // avoid clang warning -Wunused-variable -Wused-but-marked-unused
        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
    }
}

开始解码

void SoundDecoder::run(int32_t id)
{
    ALOGV("%s(%d): entering", __func__, id);
    std::unique_lock lock(mLock);
    while (!mQuit) {
        if (mSoundIDs.size() == 0) {
            ALOGV("%s(%d): waiting", __func__, id);
            mQueueDataAvailable.wait_for(
                    lock, std::chrono::duration<int32_t, std::milli>(kWaitTimeBeforeCloseMs));
            if (mSoundIDs.size() == 0) {
                break; // no new sound, exit this thread.
            }
            continue;
        }
        const int32_t soundID = mSoundIDs.front();
        mSoundIDs.pop_front();
        mQueueSpaceAvailable.notify_one();
        ALOGV("%s(%d): processing soundID: %d  size: %zu", __func__, id, soundID, mSoundIDs.size());
        lock.unlock();
        std::shared_ptr<Sound> sound = mSoundManager->findSound(soundID);
        status_t status = NO_INIT;
        if (sound.get() != nullptr) {
            status = sound->doLoad();
        }
        ALOGV("%s(%d): notifying loaded soundID:%d  status:%d", __func__, id, soundID, status);
        mSoundManager->notify(SoundPoolEvent(SoundPoolEvent::SOUND_LOADED, soundID, status));
        lock.lock();
    }
    ALOGV("%s(%d): exiting", __func__, id);
}

无论成功或失败,向上层发送SOUND_LOADED消息事件,并伴随status,主要通过status来判断加载是否成功。

六、加载完成的回调函数

6.1、Java层回调函数

Java层主要有一个OnLoadCompleteListener接口,如果用户有需要再加载完成后所作的操作(例如播放音频),可以实现这个接口。

6.1.1、回调函数接口

    public interface OnLoadCompleteListener {
        /**
         * Called when a sound has completed loading.
         *
         * @param soundPool SoundPool object from the load() method
         * @param sampleId the sample ID of the sound loaded.
         * @param status the status of the load operation (0 = success)
         */
        public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
    }

6.1.2、回调函数的注册

    /**
     * Sets the callback hook for the OnLoadCompleteListener.
     */
    public void setOnLoadCompleteListener(OnLoadCompleteListener listener) {
        if (listener == null) {
            mEventHandler.set(null);
            return;
        }

        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler.set(new EventHandler(looper, listener));
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler.set(new EventHandler(looper, listener));
        } else {
            mEventHandler.set(null);
        }
    }

完成回调函数的创建之后调用SoundPool.setOnLoadCompleteListener方法,将SoundPool中的mEventHandler对象置为新的事件对象。

    // post event from native code to message handler
    @SuppressWarnings("unchecked")
    private static void postEventFromNative(Object ref, int msg, int arg1, int arg2, Object obj) {
        SoundPool soundPool = ((WeakReference<SoundPool>) ref).get();
        if (soundPool == null) {
            return;
        }

        Handler eventHandler = soundPool.mEventHandler.get();
        if (eventHandler == null) {
            return;
        }

        Message message = eventHandler.obtainMessage(msg, arg1, arg2, obj);
        eventHandler.sendMessage(message);
    }

    private final class EventHandler extends Handler {
        private final OnLoadCompleteListener mOnLoadCompleteListener;

        EventHandler(Looper looper, @NonNull OnLoadCompleteListener onLoadCompleteListener) {
            super(looper);
            mOnLoadCompleteListener = onLoadCompleteListener;
        }

        @Override
        public void handleMessage(Message msg) {
            if (msg.what != SAMPLE_LOADED) {
                Log.e(TAG, "Unknown message type " + msg.what);
                return;
            }

            if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
            mOnLoadCompleteListener.onLoadComplete(SoundPool.this, msg.arg1, msg.arg2);
        }
    }

postEventFromNative是Native层需要调用的方法,当Native完成加载之后会调用该方法,该方法会发送SAMPLE_LOADED的Message给上层,EventHandler中处理该消息调用对应接口实现类中的onLoadComplete方法。

6.2、JNI层的回调函数

JNI层初始化时postEventFromNative的函数指针也被传入了JNI层用于后面加载完成后回调函数的调用

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
	fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.mPostEvent == nullptr) {
        ALOGE("Can't find android/media/SoundPool.postEventFromNative");
        return result;
    }
}

JNI层也有对应回调函数的指针,在SoundPool初始化时传入

//调用对应的Java层通知函数
static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user)
{
    ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    env->CallStaticVoidMethod(
            fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2,
            nullptr /* object */);
}

static jint
android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
        jint maxChannels, jobject jaa, jstring opPackageName)
{
	...
	// set callback with weak reference
    jobject globalWeakRef = env->NewGlobalRef(weakRef);
	ap->setCallback(android_media_callback, globalWeakRef);//设置回调函数
	...
}

这里主要传入对应的调用Java层回调函数的函数指针,这个指针需要传入三个参数,SoundPoolEvent事件,SoundPool实例,与用户指针(Java层传入的SoundPool对象指针的引用)。

6.3、回调函数的调用

第四章最后我们可以知道加载完成之后,会构建新的SoundPoolEvent消息实体,注入到SoundManager中。

void SoundManager::notify(SoundPoolEvent event)
{
    mCallbackHandler.notify(event);
}

frameworks/base/media/jni/soundpool/SoundManager.h

    // CallbackHandler is used to manage notifications back to the app when a sound
    // has been loaded.  It uses a recursive lock to allow setting the callback
    // during the callback.
    class CallbackHandler {
    public:
        void setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* userData)
        {
            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
            mSoundPool = soundPool;
            mCallback = callback;
            mUserData = userData;
        }
        void notify(SoundPoolEvent event) const
        {
            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
            if (mCallback != nullptr) {
                mCallback(event, mSoundPool, mUserData);
                // Note: mCallback may call setCallback().
                // so mCallback, mUserData may have changed.
            }
        }
 }

这里我们知道mCallback实际上对应的就是JNI层的函数android_media_callback的函数指针,这里通过对应的事件消息开始调用这个函数。android_media_callback实际上通过JNI层的CallStaticVoidMethod调用Java层的postEventFromNative方法,最终我们回到了postEventFromNative中调用onLoadComplete方法。

七、SoundPool播放音频

7.1、Java层播放音频

    /**
     * Play a sound from a sound ID.
     *
     * Play the sound specified by the soundID. This is the value
     * returned by the load() function. Returns a non-zero streamID
     * if successful, zero if it fails. The streamID can be used to
     * further control playback. Note that calling play() may cause
     * another sound to stop playing if the maximum number of active
     * streams is exceeded. A loop value of -1 means loop forever,
     * a value of 0 means don't loop, other values indicate the
     * number of repeats, e.g. a value of 1 plays the audio twice.
     * The playback rate allows the application to vary the playback
     * rate (pitch) of the sound. A value of 1.0 means play back at
     * the original frequency. A value of 2.0 means play back twice
     * as fast, and a value of 0.5 means playback at half speed.
     *
     * @param soundID a soundID returned by the load() function
     * @param leftVolume left volume value (range = 0.0 to 1.0)
     * @param rightVolume right volume value (range = 0.0 to 1.0)
     * @param priority stream priority (0 = lowest priority)
     * @param loop loop mode (0 = no loop, -1 = loop forever)
     * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
     * @return non-zero streamID if successful, zero if failed
     */
    public final int play(int soundID, float leftVolume, float rightVolume,
            int priority, int loop, float rate) {
        // FIXME: b/174876164 implement device id for soundpool
        baseStart(0);
        return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
    }

7.2、JNI层播放音频

static jint
android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID,
        jfloat leftVolume, jfloat rightVolume, jint priority, jint loop,
        jfloat rate)
{
    ALOGV("android_media_SoundPool_play\n");
    SoundPool *ap = MusterSoundPool(env, thiz);
    if (ap == nullptr) return 0;
    return (jint) ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate);
}

7.3、Native层播放音频

int32_t SoundPool::play(int32_t soundID, float leftVolume, float rightVolume,
        int32_t priority, int32_t loop, float rate)
{
    ALOGV("%s(soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
            __func__, soundID, leftVolume, rightVolume, priority, loop, rate);

    // New for R: check arguments to ensure track can be created.
    // If SoundPool defers the creation of the AudioTrack to the StreamManager thread,
    // the failure to create may not be visible to the caller, so this precheck is needed.
    if (checkVolume(&leftVolume, &rightVolume)
            || checkPriority(&priority)
            || checkLoop(&loop)
            || checkRate(&rate)) return 0;

    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
    const std::shared_ptr<soundpool::Sound> sound = mSoundManager.findSound(soundID);
    if (sound == nullptr || sound->getState() != soundpool::Sound::READY) {
        ALOGW("%s soundID %d not READY", __func__, soundID);
        return 0;
    }

    const int32_t streamID = mStreamManager.queueForPlay(
            sound, soundID, leftVolume, rightVolume, priority, loop, rate);
    ALOGV("%s returned %d", __func__, streamID);
    return streamID;
}

frameworks/base/media/jni/soundpool/StreamManager.cpp

int32_t StreamManager::queueForPlay(const std::shared_ptr<Sound> &sound,
        int32_t soundID, float leftVolume, float rightVolume,
        int32_t priority, int32_t loop, float rate)
{
    ALOGV("%s(sound=%p, soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
            __func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
    bool launchThread = false;
    int32_t streamID = 0;
    std::vector<std::any> garbage;

    { // for lock
        std::unique_lock lock(mStreamManagerLock);
        Stream *newStream = nullptr;
        bool fromAvailableQueue = false;
        ALOGV("%s: mStreamManagerLock lock acquired", __func__);

        sanityCheckQueue_l();
        // find an available stream, prefer one that has matching sound id.
        if (mAvailableStreams.size() > 0) {
            for (auto stream : mAvailableStreams) {
                if (stream->getSoundID() == soundID) {
                    newStream = stream;
                    ALOGV("%s: found soundID %d in available queue", __func__, soundID);
                    break;
                }
            }
            if (newStream == nullptr) {
                ALOGV("%s: found stream in available queue", __func__);
                newStream = *mAvailableStreams.begin();
            }
            newStream->setStopTimeNs(systemTime());
            fromAvailableQueue = true;
        }

        // also look in the streams restarting (if the paired stream doesn't have a pending play)
        if (newStream == nullptr || newStream->getSoundID() != soundID) {
            for (auto [unused , stream] : mRestartStreams) {
                if (!stream->getPairStream()->hasSound()) {
                    if (stream->getSoundID() == soundID) {
                        ALOGV("%s: found soundID %d in restart queue", __func__, soundID);
                        newStream = stream;
                        fromAvailableQueue = false;
                        break;
                    } else if (newStream == nullptr) {
                        ALOGV("%s: found stream in restart queue", __func__);
                        newStream = stream;
                    }
                }
            }
        }

        // no available streams, look for one to steal from the active list
        if (newStream == nullptr) {
            for (auto stream : mActiveStreams) {
                if (stream->getPriority() <= priority) {
                    if (newStream == nullptr
                            || newStream->getPriority() > stream->getPriority()) {
                        newStream = stream;
                        ALOGV("%s: found stream in active queue", __func__);
                    }
                }
            }
            if (newStream != nullptr) { // we need to mute as it is still playing.
                (void)newStream->requestStop(newStream->getStreamID());
            }
        }

        // none found, look for a stream that is restarting, evict one.
        if (newStream == nullptr) {
            for (auto [unused, stream] : mRestartStreams) {
                if (stream->getPairPriority() <= priority) {
                    ALOGV("%s: evict stream from restart queue", __func__);
                    newStream = stream;
                    break;
                }
            }
        }

        // DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads.

        if (newStream == nullptr) {
            ALOGD("%s: unable to find stream, returning 0", __func__);
            return 0; // unable to find available stream
        }

        Stream *pairStream = newStream->getPairStream();
        streamID = getNextIdForStream(pairStream);
        ALOGV("%s: newStream:%p  pairStream:%p, streamID:%d",
                __func__, newStream, pairStream, streamID);
        //设置播放属性
        pairStream->setPlay(
                streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate);
        if (fromAvailableQueue && kPlayOnCallingThread) {
            removeFromQueues_l(newStream);
            mProcessingStreams.emplace(newStream);
            lock.unlock();
            //播放
            if (Stream* nextStream = newStream->playPairStream(garbage)) {
                lock.lock();
                ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
                addToActiveQueue_l(nextStream);
            } else {
                lock.lock();
                mAvailableStreams.insert(newStream);
                streamID = 0;
            }
            mProcessingStreams.erase(newStream);
        } else {
            launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l();
        }
        sanityCheckQueue_l();
        ALOGV("%s: mStreamManagerLock released", __func__);
    } // lock

    if (launchThread) {
        const int32_t id = mThreadPool->launch([this](int32_t id) { run(id); });
        (void)id; // avoid clang warning -Wunused-variable -Wused-but-marked-unused
        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
    }
    ALOGV("%s: returning %d", __func__, streamID);
    // garbage is cleared here outside mStreamManagerLock.
    return streamID;
}

frameworks/base/media/jni/soundpool/Stream.cpp

Stream* Stream::playPairStream(std::vector<std::any>& garbage) {
    Stream* pairStream = getPairStream();
    LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
    {
        ALOGV("%s: track streamID: %d", __func__, (int)getStreamID());
        // TODO: Do we really want to force a simultaneous synchronization between
        // the stream and its pair?

        // note locking order - the paired stream is obtained before the queued stream.
        // we can invert the locking order, but it is slightly more optimal to do it this way.
        std::lock_guard lockp(pairStream->mLock);
        if (pairStream->mSound == nullptr) {
            return nullptr; // no pair sound
        }
        {
            std::lock_guard lock(mLock);
            LOG_ALWAYS_FATAL_IF(mState != IDLE, "State: %d must be IDLE", mState);
            // TODO: do we want a specific set() here?
            pairStream->mAudioTrack = mAudioTrack;
            pairStream->mSoundID = mSoundID; // optimization to reuse AudioTrack.
            pairStream->mToggle = mToggle;
            pairStream->mAutoPaused = mAutoPaused; // save autopause state
            pairStream->mMuted = mMuted;
            mAudioTrack.clear();  // the pair owns the audiotrack.
            mSound.reset();
            mSoundID = 0;
        }
        // TODO: do we need a specific play_l() anymore?
        const int pairState = pairStream->mState;
        // 下一步
        pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
                pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
                pairStream->mLoop, pairStream->mRate, garbage);
        if (pairStream->mState == IDLE) {
            return nullptr; // AudioTrack error
        }
        if (pairState == PAUSED) {  // reestablish pause
            pairStream->mState = PAUSED;
            pairStream->mAudioTrack->pause();
        }
    }
    return pairStream;
}
void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
        float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
        std::vector<std::any>& garbage)
{
    ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
            " priority=%d, loop=%d, rate=%f)",
            __func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
            priority, loop, rate);

    // initialize track
    const audio_stream_type_t streamType =
            AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
    const int32_t channelCount = sound->getChannelCount();
    const auto sampleRate = (uint32_t)lround(double(sound->getSampleRate()) * rate);
    size_t frameCount = 0;

    if (loop) {
        const audio_format_t format = sound->getFormat();
        const size_t frameSize = audio_is_linear_pcm(format)
                ? channelCount * audio_bytes_per_sample(format) : 1;
        frameCount = sound->getSizeInBytes() / frameSize;
    }

    if (mAudioTrack != nullptr) {
        if (mSoundID == sound->getSoundID()
                && mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
            // Reuse the old track if the soundID matches.
            // the sample rate may fail to change if the audio track is a fast track.
            ALOGV("%s: reusing track %p for sound %d",
                    __func__, mAudioTrack.get(), sound->getSoundID());
        } else {
            // If reuse not possible, move mAudioTrack to garbage, set to nullptr.
            garbage.emplace_back(std::move(mAudioTrack));
            mAudioTrack.clear(); // move should have cleared the sp<>, but we clear just in case.
        }
    }
    if (mAudioTrack == nullptr) {
        // mToggle toggles each time a track is started on a given stream.
        // The toggle is concatenated with the Stream 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)
        auto toggle = mToggle ^ 1;
        // NOLINTNEXTLINE(performance-no-int-to-ptr)
        void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle);
        audio_channel_mask_t soundChannelMask = sound->getChannelMask();
        // When sound contains a valid channel mask, use it as is.
        // Otherwise, use stream count to calculate channel mask.
        audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
                ? soundChannelMask : audio_channel_out_mask_from_count(channelCount);

        // do not create a new audio track if current track is compatible with sound parameters

        android::content::AttributionSourceState attributionSource;
        attributionSource.packageName = mStreamManager->getOpPackageName();
        attributionSource.token = sp<BBinder>::make();
        // TODO b/182469354 make consistent with AudioRecord, add util for native source
        mAudioTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
                channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
                staticCallback, userData,
                0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
                AudioTrack::TRANSFER_DEFAULT,
                nullptr /*offloadInfo*/, attributionSource,
                mStreamManager->getAttributes(),
                false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/);
        // Set caller name so it can be logged in destructor.
        // MediaMetricsConstants.h: AMEDIAMETRICS_PROP_CALLERNAME_VALUE_SOUNDPOOL
        mAudioTrack->setCallerName("soundpool");

        if (status_t status = mAudioTrack->initCheck();
            status != NO_ERROR) {
            ALOGE("%s: error %d creating AudioTrack", __func__, status);
            // TODO: should we consider keeping the soundID and reusing the old track?
            mState = IDLE;
            mSoundID = 0;
            mSound.reset();
            garbage.emplace_back(std::move(mAudioTrack)); // remove mAudioTrack.
            mAudioTrack.clear(); // move should have cleared the sp<>, but we clear just in case.
            return;
        }
        // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
        mToggle = toggle;
        ALOGV("%s: using new track %p for sound %d",
                __func__, mAudioTrack.get(), sound->getSoundID());
    }
    if (mMuted) {
        mAudioTrack->setVolume(0.f, 0.f);
    } else {
        mAudioTrack->setVolume(leftVolume, rightVolume);
    }
    mAudioTrack->setLoop(0, frameCount, loop);
    mAudioTrack->start();
    mSound = sound;
    mSoundID = sound->getSoundID();
    mPriority = priority;
    mLoop = loop;
    mLeftVolume = leftVolume;
    mRightVolume = rightVolume;
    mRate = rate;
    mState = PLAYING;
    mStopTimeNs = 0;
    mStreamID = nextStreamID;  // prefer this to be the last, as it is an atomic sync point
}

这里主要是操作AudioTrack

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值