Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e7937b98 authored by Andy Hung's avatar Andy Hung
Browse files

SoundPool: Refactor class

Make class names fit the Java API and documentation.

Rename Channel -> Stream.
Rename Sample -> Sound.
Rename SoundPoolThread -> SoundDecoder.

Move track start and stop to worker thread.

Fix up types to ensure future compatibility with Java ints
which are 32 bits always. Upgrade 16 bit types to
32 bits (e.g. sample id, sample rate, etc.)

Move sound related code into the SoundManager class.
Move stream related code into the StreamManager class.

Clean up locking, split SoundPool lock into the following locks
1) mApiLock
2) mStreamManagerLock
3) mSoundManagerLock
4) per Stream mLock.
5) mCallbackLock

Group locked data and make private in associated classes to ensure
restricted access, yet maximum concurrency.

Fix race conditions waiting for next event to play in stream.

Ensure track commands are handled consistently when stream is stolen.

Test: SoundPoolOggTest
Bug: 140807581
Change-Id: I8fcb374ee6329eb5474b973624584ca5080da862
parent aeda16fd
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -3,11 +3,16 @@ cc_library_shared {

    srcs: [
        "android_media_SoundPool.cpp",
        "Sound.cpp",
        "SoundDecoder.cpp",
        "SoundManager.cpp",
        "SoundPool.cpp",
        "SoundPoolThread.cpp",
        "Stream.cpp",
        "StreamManager.cpp",
    ],

    shared_libs: [
        "libaudioutils",
        "liblog",
        "libcutils",
        "libutils",
+241 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "SoundPool::Sound"
#include <utils/Log.h>

#include "Sound.h"

#include <media/NdkMediaCodec.h>
#include <media/NdkMediaExtractor.h>
#include <media/NdkMediaFormat.h>

namespace android::soundpool {

constexpr uint32_t kMaxSampleRate = 192000;
constexpr size_t   kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low mem devices)

Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length)
    : mSoundID(soundID)
    , mFd(dup(fd))
    , mOffset(offset)
    , mLength(length)
{
    ALOGV("%s(soundID=%d, fd=%d, offset=%lld, length=%lld)",
            __func__, soundID, fd, (long long)offset, (long long)length);
    ALOGW_IF(mFd == -1, "Unable to dup descriptor %d", fd);
}

Sound::~Sound()
{
    ALOGV("%s(soundID=%d, fd=%d)", __func__, mSoundID, mFd.get());
}

static status_t decode(int fd, int64_t offset, int64_t length,
        uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat,
        audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
        size_t *sizeInBytes) {
    ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)",
            __func__, fd, (long long)offset, (long long)length);
    std::unique_ptr<AMediaExtractor, decltype(&AMediaExtractor_delete)> ex{
            AMediaExtractor_new(), &AMediaExtractor_delete};
    status_t err = AMediaExtractor_setDataSourceFd(ex.get(), fd, offset, length);

    if (err != AMEDIA_OK) {
        return err;
    }

    *audioFormat = AUDIO_FORMAT_PCM_16_BIT;  // default format for audio codecs.
    const size_t numTracks = AMediaExtractor_getTrackCount(ex.get());
    for (size_t i = 0; i < numTracks; i++) {
        std::unique_ptr<AMediaFormat, decltype(&AMediaFormat_delete)> format{
                AMediaExtractor_getTrackFormat(ex.get(), i), &AMediaFormat_delete};
        const char *mime;
        if (!AMediaFormat_getString(format.get(),  AMEDIAFORMAT_KEY_MIME, &mime)) {
            return UNKNOWN_ERROR;
        }
        if (strncmp(mime, "audio/", 6) == 0) {
            std::unique_ptr<AMediaCodec, decltype(&AMediaCodec_delete)> codec{
                    AMediaCodec_createDecoderByType(mime), &AMediaCodec_delete};
            if (codec == nullptr
                    || AMediaCodec_configure(codec.get(), format.get(),
                            nullptr /* window */, nullptr /* drm */, 0 /* flags */) != AMEDIA_OK
                    || AMediaCodec_start(codec.get()) != AMEDIA_OK
                    || AMediaExtractor_selectTrack(ex.get(), i) != AMEDIA_OK) {
                return UNKNOWN_ERROR;
            }

            bool sawInputEOS = false;
            bool sawOutputEOS = false;
            uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
            size_t available = heap->getSize();
            size_t written = 0;
            format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format.

            while (!sawOutputEOS) {
                if (!sawInputEOS) {
                    ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec.get(), 5000);
                    ALOGV("%s: input buffer %zd", __func__, bufidx);
                    if (bufidx >= 0) {
                        size_t bufsize;
                        uint8_t * const buf = AMediaCodec_getInputBuffer(
                                codec.get(), bufidx, &bufsize);
                        if (buf == nullptr) {
                            ALOGE("%s: AMediaCodec_getInputBuffer returned nullptr, short decode",
                                    __func__);
                            break;
                        }
                        int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
                        ALOGV("%s: read %d", __func__, sampleSize);
                        if (sampleSize < 0) {
                            sampleSize = 0;
                            sawInputEOS = true;
                            ALOGV("%s: EOS", __func__);
                        }
                        const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex.get());

                        const media_status_t mstatus = AMediaCodec_queueInputBuffer(
                                codec.get(), bufidx,
                                0 /* offset */, sampleSize, presentationTimeUs,
                                sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
                        if (mstatus != AMEDIA_OK) {
                            // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
                            ALOGE("%s: AMediaCodec_queueInputBuffer returned status %d,"
                                    "short decode",
                                    __func__, (int)mstatus);
                            break;
                        }
                        (void)AMediaExtractor_advance(ex.get());
                    }
                }

                AMediaCodecBufferInfo info;
                const int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
                ALOGV("%s: dequeueoutput returned: %d", __func__, status);
                if (status >= 0) {
                    if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
                        ALOGV("%s: output EOS", __func__);
                        sawOutputEOS = true;
                    }
                    ALOGV("%s: got decoded buffer size %d", __func__, info.size);

                    const uint8_t * const buf = AMediaCodec_getOutputBuffer(
                            codec.get(), status, nullptr /* out_size */);
                    if (buf == nullptr) {
                        ALOGE("%s: AMediaCodec_getOutputBuffer returned nullptr, short decode",
                                __func__);
                        break;
                    }
                    const size_t dataSize = std::min((size_t)info.size, available);
                    memcpy(writePos, buf + info.offset, dataSize);
                    writePos += dataSize;
                    written += dataSize;
                    available -= dataSize;
                    const media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
                            codec.get(), status, false /* render */);
                    if (mstatus != AMEDIA_OK) {
                        // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
                        ALOGE("%s: AMediaCodec_releaseOutputBuffer"
                                " returned status %d, short decode",
                                __func__, (int)mstatus);
                        break;
                    }
                    if (available == 0) {
                        // there might be more data, but there's no space for it
                        sawOutputEOS = true;
                    }
                } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
                    ALOGV("%s: output buffers changed", __func__);
                } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
                    format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format
                    ALOGV("%s: format changed to: %s",
                           __func__, AMediaFormat_toString(format.get()));
                } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
                    ALOGV("%s: no output buffer right now", __func__);
                } else if (status <= AMEDIA_ERROR_BASE) {
                    ALOGE("%s: decode error: %d", __func__, status);
                    break;
                } else {
                    ALOGV("%s: unexpected info code: %d", __func__, status);
                }
            }

            (void)AMediaCodec_stop(codec.get());
            if (!AMediaFormat_getInt32(
                    format.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
                !AMediaFormat_getInt32(
                    format.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount)) {
                return UNKNOWN_ERROR;
            }
            if (!AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK,
                    (int32_t*) channelMask)) {
                *channelMask = AUDIO_CHANNEL_NONE;
            }
            *sizeInBytes = written;
            return OK;
        }
    }
    return UNKNOWN_ERROR;
}

status_t Sound::doLoad()
{
    ALOGV("%s()", __func__);
    status_t status = NO_INIT;
    if (mFd.get() != -1) {
        mHeap = new MemoryHeapBase(kDefaultHeapSize);

        ALOGV("%s: start decode", __func__);
        uint32_t sampleRate;
        int32_t channelCount;
        audio_format_t format;
        audio_channel_mask_t channelMask;
        status_t status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
                        &channelMask, mHeap, &mSizeInBytes);
        ALOGV("%s: close(%d)", __func__, mFd.get());
        mFd.reset();  // close

        if (status != NO_ERROR) {
            ALOGE("%s: unable to load sound", __func__);
        } else if (sampleRate > kMaxSampleRate) {
            ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate);
            status = BAD_VALUE;
        } else if (channelCount < 1 || channelCount > FCC_8) {
            ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount);
            status = BAD_VALUE;
        } else {
            // Correctly loaded, proper parameters
            ALOGV("%s: pointer = %p, sizeInBytes = %zu, sampleRate = %u, channelCount = %d",
                  __func__, mHeap->getBase(), mSizeInBytes, sampleRate, channelCount);
            mData = new MemoryBase(mHeap, 0, mSizeInBytes);
            mSampleRate = sampleRate;
            mChannelCount = channelCount;
            mFormat = format;
            mChannelMask = channelMask;
            mState = READY;  // this should be last, as it is an atomic sync point
            return NO_ERROR;
        }
    } else {
        ALOGE("%s: uninitialized fd, dup failed", __func__);
    }
    // ERROR handling
    mHeap.clear();
    mState = DECODE_ERROR; // this should be last, as it is an atomic sync point
    return status;
}

} // namespace android::soundpool
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <android-base/unique_fd.h>
#include <binder/MemoryBase.h>
#include <binder/MemoryHeapBase.h>
#include <system/audio.h>

namespace android::soundpool {

class SoundDecoder;

/**
 * Sound is a resource used by SoundPool, referenced by soundID.
 *
 * After loading, it is effectively const so no locking required.
 * However, in order to guarantee that all the values have been
 * written properly and read properly, we use the mState as an atomic synchronization
 * point.  So if getState() shows READY, then all the other getters may
 * be safely read.
 *
 * Technical details:
 * We access the mState atomic value through memory_order_seq_cst
 *
 * https://en.cppreference.com/w/cpp/atomic/memory_order
 *
 * which provides memory barriers.  So if the last value written by the SoundDecoder
 * is mState, then the compiler ensures no other prior writes by SoundDecoder will be
 * reordered afterwards, and memory barrier is placed (as necessary) to ensure the
 * cache is visible to other processors.
 *
 * Likewise, if the first value read by SoundPool is mState,
 * the compiler ensures no reads for that thread will be reordered before mState is read,
 * and a memory barrier is placed (as necessary) to ensure that the cache is properly
 * updated with other processor's writes before reading.
 *
 * See https://developer.android.com/training/articles/smp for discussions about
 * the variant load-acquire, store-release semantics.
 */
class Sound {
    friend SoundDecoder;  // calls doLoad().

public:
    enum sound_state : int32_t { LOADING, READY, DECODE_ERROR };
    // A sound starts in the LOADING state and transitions only once
    // to either READY or DECODE_ERROR when doLoad() is called.

    Sound(int soundID, int fd, int64_t offset, int64_t length);
    ~Sound();

    int32_t getSoundID() const { return mSoundID; }
    int32_t getChannelCount() const { return mChannelCount; }
    uint32_t getSampleRate() const { return mSampleRate; }
    audio_format_t getFormat() const { return mFormat; }
    audio_channel_mask_t getChannelMask() const { return mChannelMask; }
    size_t getSizeInBytes() const { return mSizeInBytes; }
    sound_state getState() const { return mState; }
    uint8_t* getData() const { return static_cast<uint8_t*>(mData->unsecurePointer()); }
    sp<IMemory> getIMemory() const { return mData; }

private:
    status_t doLoad();  // only SoundDecoder accesses this.

    size_t               mSizeInBytes = 0;
    const int32_t        mSoundID;
    uint32_t             mSampleRate = 0;
    std::atomic<sound_state> mState = LOADING; // used as synchronization point
    int32_t              mChannelCount = 0;
    audio_format_t       mFormat = AUDIO_FORMAT_INVALID;
    audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_NONE;
    base::unique_fd      mFd;     // initialized in constructor, reset to -1 after loading
    const int64_t        mOffset; // int64_t to match java long, see off64_t
    const int64_t        mLength; // int64_t to match java long, see off64_t
    sp<IMemory>          mData;
    sp<MemoryHeapBase>   mHeap;
};

} // namespace android::soundpool
+115 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "SoundPool::SoundDecoder"
#include "utils/Log.h"

#include "SoundDecoder.h"

namespace android::soundpool {

// Maximum Samples that can be background decoded before we block the caller.
static constexpr size_t kMaxQueueSize = 128;

// The amount of time we wait for a new Sound decode request
// before the SoundDecoder thread closes.
static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;

SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
    : mSoundManager(soundManager)
{
    ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
    // ThreadPool is created, but we don't launch any threads.
    mThreadPool = std::make_unique<ThreadPool>(
            std::min(threads, (size_t)std::thread::hardware_concurrency()),
            "SoundDecoder_");
}

SoundDecoder::~SoundDecoder()
{
    ALOGV("%s()", __func__);
    quit();
}

void SoundDecoder::quit()
{
    ALOGV("%s()", __func__);
    {
        std::lock_guard lock(mLock);
        mQuit = true;
        mQueueSpaceAvailable.notify_all(); // notify all load waiters
        mQueueDataAvailable.notify_all();  // notify all worker threads
    }
    mThreadPool->quit();
}

void SoundDecoder::run(int32_t id __unused /* ALOGV only */)
{
    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);
}

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 __unused = mThreadPool->launch([this](int32_t id) { run(id); });
        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
    }
}

} // end namespace android::soundpool
+51 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2007 The Android Open Source Project
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -14,53 +14,38 @@
 * limitations under the License.
 */

#ifndef SOUNDPOOLTHREAD_H_
#define SOUNDPOOLTHREAD_H_

#include <utils/threads.h>
#include <utils/Vector.h>
#include <media/AudioTrack.h>
#pragma once

#include "SoundPool.h"

namespace android {
#include <deque>
#include <mutex>

class SoundPoolMsg {
public:
    enum MessageType { INVALID, KILL, LOAD_SAMPLE };
    SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
    SoundPoolMsg(MessageType MessageType, int data) :
        mMessageType(MessageType), mData(data) {}
    uint16_t         mMessageType;
    uint16_t         mData;
};
namespace android::soundpool {

/*
 * This class handles background requests from the SoundPool
/**
 * SoundDecoder handles background decoding tasks.
 */
class SoundPoolThread {
class SoundDecoder {
public:
    explicit SoundPoolThread(SoundPool* SoundPool);
    ~SoundPoolThread();
    void loadSample(int sampleID);
    SoundDecoder(SoundManager* soundManager, size_t threads);
    ~SoundDecoder();
    void loadSound(int32_t soundID);
    void quit();
    void write(SoundPoolMsg msg);

private:
    static const size_t maxMessages = 128;
    void run(int32_t id);                       // The decode thread function.

    SoundManager* const     mSoundManager;      // set in constructor, has own lock
    std::unique_ptr<ThreadPool> mThreadPool;    // set in constructor, has own lock

    static int beginThread(void* arg);
    int run();
    void doLoadSample(int sampleID);
    const SoundPoolMsg read();
    std::mutex              mLock;
    std::condition_variable mQueueSpaceAvailable;
    std::condition_variable mQueueDataAvailable;

    Mutex                   mLock;
    Condition               mCondition;
    Vector<SoundPoolMsg>    mMsgQueue;
    SoundPool*              mSoundPool;
    bool                    mRunning;
    std::deque<int32_t>     mSoundIDs;            // GUARDED_BY(mLock);
    bool                    mQuit = false;        // GUARDED_BY(mLock);
};

} // end namespace android
} // end namespace android::soundpool
#endif /*SOUNDPOOLTHREAD_H_*/
Loading