C++程序  |  580行  |  19.71 KB

/*
 * Copyright (C) 2015 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 "audio-track-native"

#include "Blob.h"
#include "Gate.h"
#include "sl-utils.h"

#include <deque>
#include <utils/Errors.h>

// Select whether to use STL shared pointer or to use Android strong pointer.
// We really don't promote any sharing of this object for its lifetime, but nevertheless could
// change the shared pointer value on the fly if desired.
#define USE_SHARED_POINTER

#ifdef USE_SHARED_POINTER
#include <memory>
template <typename T> using shared_pointer = std::shared_ptr<T>;
#else
#include <utils/RefBase.h>
template <typename T> using shared_pointer = android::sp<T>;
#endif

using namespace android;

// Must be kept in sync with Java android.media.cts.AudioTrackNative.WriteFlags
enum {
    WRITE_FLAG_BLOCKING = (1 << 0),
};

// TODO: Add a single buffer blocking write mode which does not require additional memory.
// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).

class AudioTrackNative
#ifndef USE_SHARED_POINTER
        : public RefBase // android strong pointers require RefBase
#endif
{
public:
    AudioTrackNative() :
        mEngineObj(NULL),
        mEngine(NULL),
        mOutputMixObj(NULL),
        mPlayerObj(NULL),
        mPlay(NULL),
        mBufferQueue(NULL),
        mPlayState(SL_PLAYSTATE_STOPPED),
        mNumBuffers(0)
    { }

    ~AudioTrackNative() {
        close();
    }

    typedef std::lock_guard<std::recursive_mutex> auto_lock;

    status_t open(uint32_t numChannels, uint32_t sampleRate, bool useFloat,
            uint32_t numBuffers) {
        close();
        auto_lock l(mLock);
        mEngineObj = OpenSLEngine();
        if (mEngineObj == NULL) {
            ALOGW("cannot create OpenSL ES engine");
            return INVALID_OPERATION;
        }

        SLresult res;
        for (;;) {
            /* Get the SL Engine Interface which is implicit */
            res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
            if (res != SL_RESULT_SUCCESS) break;

            // Create Output Mix object to be used by player
            res = (*mEngine)->CreateOutputMix(
                    mEngine, &mOutputMixObj, 0 /* numInterfaces */,
                    NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
            if (res != SL_RESULT_SUCCESS) break;

            // Realizing the Output Mix object in synchronous mode.
            res = (*mOutputMixObj)->Realize(mOutputMixObj, SL_BOOLEAN_FALSE /* async */);
            if (res != SL_RESULT_SUCCESS) break;

            /* Setup the data source structure for the buffer queue */
            SLDataLocator_BufferQueue bufferQueue;
            bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
            bufferQueue.numBuffers = numBuffers;
            mNumBuffers = numBuffers;

            /* Setup the format of the content in the buffer queue */

            SLAndroidDataFormat_PCM_EX pcm;
            pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
            pcm.numChannels = numChannels;
            pcm.sampleRate = sampleRate * 1000;
            pcm.bitsPerSample = useFloat ?
                    SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
            pcm.containerSize = pcm.bitsPerSample;
            pcm.channelMask = channelCountToMask(numChannels);
            pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
            // additional
            pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
                                    : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
            SLDataSource audioSource;
            audioSource.pFormat = (void *)&pcm;
            audioSource.pLocator = (void *)&bufferQueue;

            /* Setup the data sink structure */
            SLDataLocator_OutputMix locator_outputmix;
            locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
            locator_outputmix.outputMix = mOutputMixObj;

            SLDataSink audioSink;
            audioSink.pLocator = (void *)&locator_outputmix;
            audioSink.pFormat = NULL;

            SLboolean required[1];
            SLInterfaceID iidArray[1];
            required[0] = SL_BOOLEAN_TRUE;
            iidArray[0] = SL_IID_BUFFERQUEUE;

            res = (*mEngine)->CreateAudioPlayer(mEngine, &mPlayerObj,
                    &audioSource, &audioSink, 1 /* numInterfaces */, iidArray, required);
            if (res != SL_RESULT_SUCCESS) break;

            res = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE /* async */);
            if (res != SL_RESULT_SUCCESS) break;

            res = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY, (void*)&mPlay);
            if (res != SL_RESULT_SUCCESS) break;

            res = (*mPlayerObj)->GetInterface(
                    mPlayerObj, SL_IID_BUFFERQUEUE, (void*)&mBufferQueue);
            if (res != SL_RESULT_SUCCESS) break;

            /* Setup to receive buffer queue event callbacks */
            res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
            if (res != SL_RESULT_SUCCESS) break;

            // success
            break;
        }
        if (res != SL_RESULT_SUCCESS) {
            close(); // should be safe to close even with lock held
            ALOGW("open error %s", android::getSLErrStr(res));
            return INVALID_OPERATION;
        }
        return OK;
    }

    void close() {
        SLObjectItf engineObj;
        SLObjectItf outputMixObj;
        SLObjectItf playerObj;
        {
            auto_lock l(mLock);
            if (mPlay != NULL && mPlayState != SL_PLAYSTATE_STOPPED) {
                (void)stop();
            }
            // once stopped, we can unregister the callback
            if (mBufferQueue != NULL) {
                (void)(*mBufferQueue)->RegisterCallback(
                        mBufferQueue, NULL /* callback */, NULL /* *pContext */);
            }
            (void)flush();
            engineObj = mEngineObj;
            outputMixObj = mOutputMixObj;
            playerObj = mPlayerObj;
            // clear out interfaces and objects
            mPlay = NULL;
            mBufferQueue = NULL;
            mEngine = NULL;
            mPlayerObj = NULL;
            mOutputMixObj = NULL;
            mEngineObj = NULL;
            mPlayState = SL_PLAYSTATE_STOPPED;
        }
        // destroy without lock
        if (playerObj != NULL) {
            (*playerObj)->Destroy(playerObj);
        }
        if (outputMixObj != NULL) {
            (*outputMixObj)->Destroy(outputMixObj);
        }
        if (engineObj != NULL) {
            CloseSLEngine(engineObj);
        }
    }

    status_t setPlayState(SLuint32 playState) {
        auto_lock l(mLock);
        if (mPlay == NULL) {
            return INVALID_OPERATION;
        }
        SLresult res = (*mPlay)->SetPlayState(mPlay, playState);
        if (res != SL_RESULT_SUCCESS) {
            ALOGW("setPlayState %d error %s", playState, android::getSLErrStr(res));
            return INVALID_OPERATION;
        }
        mPlayState = playState;
        return OK;
    }

    SLuint32 getPlayState() {
        auto_lock l(mLock);
        if (mPlay == NULL) {
            return SL_PLAYSTATE_STOPPED;
        }
        SLuint32 playState;
        SLresult res = (*mPlay)->GetPlayState(mPlay, &playState);
        if (res != SL_RESULT_SUCCESS) {
            ALOGW("getPlayState error %s", android::getSLErrStr(res));
            return SL_PLAYSTATE_STOPPED;
        }
        return playState;
    }

    status_t getPositionInMsec(int64_t *position) {
        auto_lock l(mLock);
        if (mPlay == NULL) {
            return INVALID_OPERATION;
        }
        if (position == NULL) {
            return BAD_VALUE;
        }
        SLuint32 pos;
        SLresult res = (*mPlay)->GetPosition(mPlay, &pos);
        if (res != SL_RESULT_SUCCESS) {
            ALOGW("getPosition error %s", android::getSLErrStr(res));
            return INVALID_OPERATION;
        }
        // only lower 32 bits valid
        *position = pos;
        return OK;
    }

    status_t start() {
        return setPlayState(SL_PLAYSTATE_PLAYING);
    }

    status_t pause() {
        return setPlayState(SL_PLAYSTATE_PAUSED);
    }

    status_t stop() {
        return setPlayState(SL_PLAYSTATE_STOPPED);
    }

    status_t flush() {
        auto_lock l(mLock);
        status_t result = OK;
        if (mBufferQueue != NULL) {
            SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
            if (res != SL_RESULT_SUCCESS) {
                return INVALID_OPERATION;
            }
        }

        // possible race if the engine is in the callback
        // safety is only achieved if the player is paused or stopped.
        mDeliveredQueue.clear();
        return result;
    }

    status_t write(const void *buffer, size_t size, bool isBlocking = false) {
        std::lock_guard<std::mutex> rl(mWriteLock);
        // not needed if we assume that a single thread is doing the reading
        // or we always operate in non-blocking mode.

        {
            auto_lock l(mLock);
            if (mBufferQueue == NULL) {
                return INVALID_OPERATION;
            }
            if (mDeliveredQueue.size() < mNumBuffers) {
                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
                mDeliveredQueue.emplace_back(b);
                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
                return size;
            }
            if (!isBlocking) {
                return 0;
            }
            mWriteReady.closeGate(); // we're full.
        }
        if (mWriteReady.wait()) {
            auto_lock l(mLock);
            if (mDeliveredQueue.size() < mNumBuffers) {
                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
                mDeliveredQueue.emplace_back(b);
                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
                return size;
            }
        }
        ALOGW("unable to deliver write");
        return 0;
    }

    void logBufferState() {
        auto_lock l(mLock);
        SLBufferQueueState state;
        SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
        CheckErr(res);
        ALOGD("logBufferState state.count:%d  state.playIndex:%d", state.count, state.playIndex);
    }

    size_t getBuffersPending() {
        auto_lock l(mLock);
        return mDeliveredQueue.size();
    }

private:
    void bufferQueueCallback(SLBufferQueueItf queueItf) {
        auto_lock l(mLock);
        if (queueItf != mBufferQueue) {
            ALOGW("invalid buffer queue interface, ignoring");
            return;
        }
        // logBufferState();

        // remove from delivered queue
        if (mDeliveredQueue.size()) {
            mDeliveredQueue.pop_front();
        } else {
            ALOGW("no delivered data!");
        }
        if (!mWriteReady.isOpen()) {
            mWriteReady.openGate();
        }
    }

    static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
        SLresult res;
        // naked native track
        AudioTrackNative *track = (AudioTrackNative *)pContext;
        track->bufferQueueCallback(queueItf);
    }

    SLObjectItf          mEngineObj;
    SLEngineItf          mEngine;
    SLObjectItf          mOutputMixObj;
    SLObjectItf          mPlayerObj;
    SLPlayItf            mPlay;
    SLBufferQueueItf     mBufferQueue;
    SLuint32             mPlayState;
    SLuint32             mNumBuffers;
    std::recursive_mutex mLock;           // monitor lock - locks public API methods and callback.
                                          // recursive since it may call itself through API.
    std::mutex           mWriteLock;      // write lock - for blocking mode, prevents multiple
                                          // writer threads from overlapping writes.  this is
                                          // generally unnecessary as writes occur from
                                          // one thread only.  acquire this before mLock.
    Gate                 mWriteReady;
    std::deque<std::shared_ptr<BlobReadOnly>> mDeliveredQueue; // delivered to mBufferQueue
};

/* Java static methods.
 *
 * These are not directly exposed to the user, so we can assume a valid "jtrack" handle
 * to be passed in.
 */

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeTest(
    JNIEnv * /* env */, jclass /* clazz */,
    jint numChannels, jint sampleRate, jboolean useFloat,
    jint msecPerBuffer, jint numBuffers)
{
    AudioTrackNative track;
    const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
    const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;

    status_t res;
    void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
    for (;;) {
        res = track.open(numChannels, sampleRate, useFloat, numBuffers);
        if (res != OK) break;

        for (int i = 0; i < numBuffers; ++i) {
            track.write((char *)buffer + i * (framesPerBuffer * frameSize),
                    framesPerBuffer * frameSize);
        }

        track.logBufferState();
        res = track.start();
        if (res != OK) break;

        size_t buffers;
        while ((buffers = track.getBuffersPending()) > 0) {
            // ALOGD("outstanding buffers: %zu", buffers);
            usleep(5 * 1000 /* usec */);
        }
        res = track.stop();
        break;
    }
    track.close();
    free(buffer);
    return res;
}

extern "C" jlong Java_android_media_cts_AudioTrackNative_nativeCreateTrack(
    JNIEnv * /* env */, jclass /* clazz */)
{
    return (jlong)(new shared_pointer<AudioTrackNative>(new AudioTrackNative()));
}

extern "C" void Java_android_media_cts_AudioTrackNative_nativeDestroyTrack(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
    delete (shared_pointer<AudioTrackNative> *)jtrack;
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeOpen(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack,
    jint numChannels, jint sampleRate, jboolean useFloat, jint numBuffers)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)INVALID_OPERATION;
    }
    return (jint)track->open(numChannels, sampleRate, useFloat == JNI_TRUE,
            numBuffers);
}

extern "C" void Java_android_media_cts_AudioTrackNative_nativeClose(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() != NULL) {
        track->close();
    }
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStart(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)INVALID_OPERATION;
    }
    return (jint)track->start();
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStop(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)INVALID_OPERATION;
    }
    return (jint)track->stop();
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativePause(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)INVALID_OPERATION;
    }
    return (jint)track->pause();
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeFlush(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)INVALID_OPERATION;
    }
    return (jint)track->flush();
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetPositionInMsec(
    JNIEnv *env, jclass /* clazz */, jlong jtrack, jlongArray jPosition)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)INVALID_OPERATION;
    }
    int64_t pos;
    status_t res = track->getPositionInMsec(&pos);
    if (res != OK) {
        return res;
    }
    jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
    if (nPostition == NULL) {
        ALOGE("Unable to get array for nativeGetPositionInMsec()");
        return BAD_VALUE;
    }
    nPostition[0] = (jlong)pos;
    env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
    return OK;
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetBuffersPending(
    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)0;
    }
    return (jint)track->getBuffersPending();
}

template <typename T>
static inline jint writeToTrack(jlong jtrack, const T *data,
    jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
    if (track.get() == NULL) {
        return (jint)INVALID_OPERATION;
    }

    const bool isBlocking = writeFlags & WRITE_FLAG_BLOCKING;
    const size_t sizeInBytes = sizeInSamples * sizeof(T);
    ssize_t ret = track->write(data + offsetInSamples, sizeInBytes, isBlocking);
    return (jint)(ret > 0 ? ret / sizeof(T) : ret);
}

template <typename T>
static inline jint writeArray(JNIEnv *env, jclass /* clazz */, jlong jtrack,
        T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
    if (javaAudioData == NULL) {
        return (jint)INVALID_OPERATION;
    }

    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
    if (cAudioData == NULL) {
        ALOGE("Error retrieving source of audio data to play");
        return (jint)BAD_VALUE;
    }

    jint ret = writeToTrack(jtrack, cAudioData, offsetInSamples, sizeInSamples, writeFlags);
    envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
    return ret;
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteByteArray(
    JNIEnv *env, jclass clazz, jlong jtrack,
    jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
    ALOGV("nativeWriteByteArray(%p, %d, %d, %d)",
            byteArray, offsetInSamples, sizeInSamples, writeFlags);
    return writeArray(env, clazz, jtrack, byteArray, offsetInSamples, sizeInSamples, writeFlags);
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteShortArray(
    JNIEnv *env, jclass clazz, jlong jtrack,
    jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
    ALOGV("nativeWriteShortArray(%p, %d, %d, %d)",
            shortArray, offsetInSamples, sizeInSamples, writeFlags);
    return writeArray(env, clazz, jtrack, shortArray, offsetInSamples, sizeInSamples, writeFlags);
}

extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteFloatArray(
    JNIEnv *env, jclass clazz, jlong jtrack,
    jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
    ALOGV("nativeWriteFloatArray(%p, %d, %d, %d)",
            floatArray, offsetInSamples, sizeInSamples, writeFlags);
    return writeArray(env, clazz, jtrack, floatArray, offsetInSamples, sizeInSamples, writeFlags);
}