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

Commit 7ca4cd6d authored by Andy Hung's avatar Andy Hung Committed by Android (Google) Code Review
Browse files

Merge "SoundPool: Refactor class"

parents 4dd77f0b e7937b98
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