/*
**
** Copyright 2018, 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 "MediaPlayer2AudioOutput"
#include <mediaplayer2/MediaPlayer2AudioOutput.h>

#include <cutils/properties.h> // for property_get
#include <utils/Log.h>

#include <media/stagefright/foundation/ADebug.h>

namespace {

const float kMaxRequiredSpeed = 8.0f; // for PCM tracks allow up to 8x speedup.

} // anonymous namespace

namespace android {

// TODO: Find real cause of Audio/Video delay in PV framework and remove this workaround
/* static */ int MediaPlayer2AudioOutput::mMinBufferCount = 4;
/* static */ bool MediaPlayer2AudioOutput::mIsOnEmulator = false;

status_t MediaPlayer2AudioOutput::dump(int fd, const Vector<String16>& args) const {
    const size_t SIZE = 256;
    char buffer[SIZE];
    String8 result;

    result.append(" MediaPlayer2AudioOutput\n");
    snprintf(buffer, 255, "  volume(%f)\n", mVolume);
    result.append(buffer);
    snprintf(buffer, 255, "  msec per frame(%f), latency (%d)\n",
            mMsecsPerFrame, (mJAudioTrack != nullptr) ? mJAudioTrack->latency() : -1);
    result.append(buffer);
    snprintf(buffer, 255, "  aux effect id(%d), send level (%f)\n",
            mAuxEffectId, mSendLevel);
    result.append(buffer);

    ::write(fd, result.string(), result.size());
    if (mJAudioTrack != nullptr) {
        mJAudioTrack->dump(fd, args);
    }
    return NO_ERROR;
}

MediaPlayer2AudioOutput::MediaPlayer2AudioOutput(int32_t sessionId, uid_t uid, int pid,
        const jobject attributes)
    : mCallback(nullptr),
      mCallbackCookie(nullptr),
      mCallbackData(nullptr),
      mVolume(1.0),
      mPlaybackRate(AUDIO_PLAYBACK_RATE_DEFAULT),
      mSampleRateHz(0),
      mMsecsPerFrame(0),
      mFrameSize(0),
      mSessionId(sessionId),
      mUid(uid),
      mPid(pid),
      mSendLevel(0.0),
      mAuxEffectId(0),
      mFlags(AUDIO_OUTPUT_FLAG_NONE) {
    ALOGV("MediaPlayer2AudioOutput(%d)", sessionId);

    if (attributes != nullptr) {
        mAttributes = new JObjectHolder(attributes);
    }

    setMinBufferCount();
    mRoutingDelegates.clear();
}

MediaPlayer2AudioOutput::~MediaPlayer2AudioOutput() {
    for (auto routingDelegate : mRoutingDelegates) {
        JAudioTrack::removeGlobalRef(routingDelegate.second);
    }
    close();
    delete mCallbackData;
}

//static
void MediaPlayer2AudioOutput::setMinBufferCount() {
    char value[PROPERTY_VALUE_MAX];
    if (property_get("ro.kernel.qemu", value, 0)) {
        mIsOnEmulator = true;
        mMinBufferCount = 12;  // to prevent systematic buffer underrun for emulator
    }
}

// static
bool MediaPlayer2AudioOutput::isOnEmulator() {
    setMinBufferCount();  // benign race wrt other threads
    return mIsOnEmulator;
}

// static
int MediaPlayer2AudioOutput::getMinBufferCount() {
    setMinBufferCount();  // benign race wrt other threads
    return mMinBufferCount;
}

ssize_t MediaPlayer2AudioOutput::bufferSize() const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return NO_INIT;
    }
    return mJAudioTrack->frameCount() * mFrameSize;
}

ssize_t MediaPlayer2AudioOutput::frameCount() const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return NO_INIT;
    }
    return mJAudioTrack->frameCount();
}

ssize_t MediaPlayer2AudioOutput::channelCount() const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return NO_INIT;
    }
    return mJAudioTrack->channelCount();
}

ssize_t MediaPlayer2AudioOutput::frameSize() const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return NO_INIT;
    }
    return mFrameSize;
}

uint32_t MediaPlayer2AudioOutput::latency () const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return 0;
    }
    return mJAudioTrack->latency();
}

float MediaPlayer2AudioOutput::msecsPerFrame() const {
    Mutex::Autolock lock(mLock);
    return mMsecsPerFrame;
}

status_t MediaPlayer2AudioOutput::getPosition(uint32_t *position) const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return NO_INIT;
    }
    return mJAudioTrack->getPosition(position);
}

status_t MediaPlayer2AudioOutput::getTimestamp(AudioTimestamp &ts) const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return NO_INIT;
    }
    return mJAudioTrack->getTimestamp(ts);
}

// TODO: Remove unnecessary calls to getPlayedOutDurationUs()
// as it acquires locks and may query the audio driver.
//
// Some calls could conceivably retrieve extrapolated data instead of
// accessing getTimestamp() or getPosition() every time a data buffer with
// a media time is received.
//
// Calculate duration of played samples if played at normal rate (i.e., 1.0).
int64_t MediaPlayer2AudioOutput::getPlayedOutDurationUs(int64_t nowUs) const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr || mSampleRateHz == 0) {
        return 0;
    }

    uint32_t numFramesPlayed;
    int64_t numFramesPlayedAtUs;
    AudioTimestamp ts;

    status_t res = mJAudioTrack->getTimestamp(ts);

    if (res == OK) {                 // case 1: mixing audio tracks and offloaded tracks.
        numFramesPlayed = ts.mPosition;
        numFramesPlayedAtUs = ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000;
        //ALOGD("getTimestamp: OK %d %lld", numFramesPlayed, (long long)numFramesPlayedAtUs);
    } else {                         // case 2: transitory state on start of a new track
                                     // case 3: transitory at new track or audio fast tracks.
        numFramesPlayed = 0;
        numFramesPlayedAtUs = nowUs;
        //ALOGD("getTimestamp: WOULD_BLOCK %d %lld",
        //        numFramesPlayed, (long long)numFramesPlayedAtUs);
    }

    // CHECK_EQ(numFramesPlayed & (1 << 31), 0);  // can't be negative until 12.4 hrs, test
    // TODO: remove the (int32_t) casting below as it may overflow at 12.4 hours.
    int64_t durationUs = (int64_t)((int32_t)numFramesPlayed * 1000000LL / mSampleRateHz)
            + nowUs - numFramesPlayedAtUs;
    if (durationUs < 0) {
        // Occurs when numFramesPlayed position is very small and the following:
        // (1) In case 1, the time nowUs is computed before getTimestamp() is called and
        //     numFramesPlayedAtUs is greater than nowUs by time more than numFramesPlayed.
        // (2) In case 3, using getPosition and adding mAudioSink->latency() to
        //     numFramesPlayedAtUs, by a time amount greater than numFramesPlayed.
        //
        // Both of these are transitory conditions.
        ALOGV("getPlayedOutDurationUs: negative duration %lld set to zero", (long long)durationUs);
        durationUs = 0;
    }
    ALOGV("getPlayedOutDurationUs(%lld) nowUs(%lld) frames(%u) framesAt(%lld)",
            (long long)durationUs, (long long)nowUs,
            numFramesPlayed, (long long)numFramesPlayedAtUs);
    return durationUs;
}

status_t MediaPlayer2AudioOutput::getFramesWritten(uint32_t *frameswritten) const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return NO_INIT;
    }
    ExtendedTimestamp ets;
    status_t status = mJAudioTrack->getTimestamp(&ets);
    if (status == OK || status == WOULD_BLOCK) {
        *frameswritten = (uint32_t)ets.mPosition[ExtendedTimestamp::LOCATION_CLIENT];
    }
    return status;
}

void MediaPlayer2AudioOutput::setAudioAttributes(const jobject attributes) {
    Mutex::Autolock lock(mLock);
    mAttributes = (attributes == nullptr) ? nullptr : new JObjectHolder(attributes);
}

audio_stream_type_t MediaPlayer2AudioOutput::getAudioStreamType() const {
    ALOGV("getAudioStreamType");
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == nullptr) {
        return AUDIO_STREAM_DEFAULT;
    }
    return mJAudioTrack->getAudioStreamType();
}

void MediaPlayer2AudioOutput::close_l() {
    mJAudioTrack.clear();
}

status_t MediaPlayer2AudioOutput::open(
        uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask,
        audio_format_t format,
        AudioCallback cb, void *cookie,
        audio_output_flags_t flags,
        const audio_offload_info_t *offloadInfo,
        uint32_t suggestedFrameCount) {
    ALOGV("open(%u, %d, 0x%x, 0x%x, %d 0x%x)", sampleRate, channelCount, channelMask,
                format, mSessionId, flags);

    // offloading is only supported in callback mode for now.
    // offloadInfo must be present if offload flag is set
    if (((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) &&
            ((cb == nullptr) || (offloadInfo == nullptr))) {
        return BAD_VALUE;
    }

    // compute frame count for the AudioTrack internal buffer
    const size_t frameCount =
           ((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) ? 0 : suggestedFrameCount;

    if (channelMask == CHANNEL_MASK_USE_CHANNEL_ORDER) {
        channelMask = audio_channel_out_mask_from_count(channelCount);
        if (0 == channelMask) {
            ALOGE("open() error, can\'t derive mask for %d audio channels", channelCount);
            return NO_INIT;
        }
    }

    Mutex::Autolock lock(mLock);
    mCallback = cb;
    mCallbackCookie = cookie;

    sp<JAudioTrack> jT;
    CallbackData *newcbd = nullptr;

    ALOGV("creating new JAudioTrack");

    if (mCallback != nullptr) {
        newcbd = new CallbackData(this);
        jT = new JAudioTrack(
                 sampleRate,
                 format,
                 channelMask,
                 CallbackWrapper,
                 newcbd,
                 frameCount,
                 mSessionId,
                 mAttributes != nullptr ? mAttributes->getJObject() : nullptr,
                 1.0f);  // default value for maxRequiredSpeed
    } else {
        // TODO: Due to buffer memory concerns, we use a max target playback speed
        // based on mPlaybackRate at the time of open (instead of kMaxRequiredSpeed),
        // also clamping the target speed to 1.0 <= targetSpeed <= kMaxRequiredSpeed.
        const float targetSpeed =
                std::min(std::max(mPlaybackRate.mSpeed, 1.0f), kMaxRequiredSpeed);
        ALOGW_IF(targetSpeed != mPlaybackRate.mSpeed,
                "track target speed:%f clamped from playback speed:%f",
                targetSpeed, mPlaybackRate.mSpeed);
        jT = new JAudioTrack(
                 sampleRate,
                 format,
                 channelMask,
                 nullptr,
                 nullptr,
                 frameCount,
                 mSessionId,
                 mAttributes != nullptr ? mAttributes->getJObject() : nullptr,
                 targetSpeed);
    }

    if (jT == 0) {
        ALOGE("Unable to create audio track");
        delete newcbd;
        // t goes out of scope, so reference count drops to zero
        return NO_INIT;
    }

    CHECK((jT != nullptr) && ((mCallback == nullptr) || (newcbd != nullptr)));

    mCallbackData = newcbd;
    ALOGV("setVolume");
    jT->setVolume(mVolume);

    mSampleRateHz = sampleRate;
    mFlags = flags;
    mMsecsPerFrame = 1E3f / (mPlaybackRate.mSpeed * sampleRate);
    mFrameSize = jT->frameSize();
    mJAudioTrack = jT;

    return updateTrack_l();
}

status_t MediaPlayer2AudioOutput::updateTrack_l() {
    if (mJAudioTrack == nullptr) {
        return NO_ERROR;
    }

    status_t res = NO_ERROR;
    // Note some output devices may give us a direct track even though we don't specify it.
    // Example: Line application b/17459982.
    if ((mJAudioTrack->getFlags()
            & (AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD | AUDIO_OUTPUT_FLAG_DIRECT)) == 0) {
        res = mJAudioTrack->setPlaybackRate(mPlaybackRate);
        if (res == NO_ERROR) {
            mJAudioTrack->setAuxEffectSendLevel(mSendLevel);
            res = mJAudioTrack->attachAuxEffect(mAuxEffectId);
        }
    }
    if (mPreferredDevice != nullptr) {
        mJAudioTrack->setPreferredDevice(mPreferredDevice->getJObject());
    }

    mJAudioTrack->registerRoutingDelegates(mRoutingDelegates);

    ALOGV("updateTrack_l() DONE status %d", res);
    return res;
}

status_t MediaPlayer2AudioOutput::start() {
    ALOGV("start");
    Mutex::Autolock lock(mLock);
    if (mCallbackData != nullptr) {
        mCallbackData->endTrackSwitch();
    }
    if (mJAudioTrack != nullptr) {
        mJAudioTrack->setVolume(mVolume);
        mJAudioTrack->setAuxEffectSendLevel(mSendLevel);
        status_t status = mJAudioTrack->start();
        return status;
    }
    return NO_INIT;
}

ssize_t MediaPlayer2AudioOutput::write(const void* buffer, size_t size, bool blocking) {
    Mutex::Autolock lock(mLock);
    LOG_ALWAYS_FATAL_IF(mCallback != nullptr, "Don't call write if supplying a callback.");

    //ALOGV("write(%p, %u)", buffer, size);
    if (mJAudioTrack != nullptr) {
        return mJAudioTrack->write(buffer, size, blocking);
    }
    return NO_INIT;
}

void MediaPlayer2AudioOutput::stop() {
    ALOGV("stop");
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack != nullptr) {
        mJAudioTrack->stop();
    }
}

void MediaPlayer2AudioOutput::flush() {
    ALOGV("flush");
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack != nullptr) {
        mJAudioTrack->flush();
    }
}

void MediaPlayer2AudioOutput::pause() {
    ALOGV("pause");
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack != nullptr) {
        mJAudioTrack->pause();
    }
}

void MediaPlayer2AudioOutput::close() {
    ALOGV("close");
    sp<JAudioTrack> track;
    {
        Mutex::Autolock lock(mLock);
        track = mJAudioTrack;
        close_l(); // clears mJAudioTrack
    }
    // destruction of the track occurs outside of mutex.
}

void MediaPlayer2AudioOutput::setVolume(float volume) {
    ALOGV("setVolume(%f)", volume);
    Mutex::Autolock lock(mLock);
    mVolume = volume;
    if (mJAudioTrack != nullptr) {
        mJAudioTrack->setVolume(volume);
    }
}

status_t MediaPlayer2AudioOutput::setPlaybackRate(const AudioPlaybackRate &rate) {
    ALOGV("setPlaybackRate(%f %f %d %d)",
                rate.mSpeed, rate.mPitch, rate.mFallbackMode, rate.mStretchMode);
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == 0) {
        // remember rate so that we can set it when the track is opened
        mPlaybackRate = rate;
        return OK;
    }
    status_t res = mJAudioTrack->setPlaybackRate(rate);
    if (res != NO_ERROR) {
        return res;
    }
    // rate.mSpeed is always greater than 0 if setPlaybackRate succeeded
    CHECK_GT(rate.mSpeed, 0.f);
    mPlaybackRate = rate;
    if (mSampleRateHz != 0) {
        mMsecsPerFrame = 1E3f / (rate.mSpeed * mSampleRateHz);
    }
    return res;
}

status_t MediaPlayer2AudioOutput::getPlaybackRate(AudioPlaybackRate *rate) {
    ALOGV("getPlaybackRate");
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == 0) {
        return NO_INIT;
    }
    *rate = mJAudioTrack->getPlaybackRate();
    return NO_ERROR;
}

status_t MediaPlayer2AudioOutput::setAuxEffectSendLevel(float level) {
    ALOGV("setAuxEffectSendLevel(%f)", level);
    Mutex::Autolock lock(mLock);
    mSendLevel = level;
    if (mJAudioTrack != nullptr) {
        return mJAudioTrack->setAuxEffectSendLevel(level);
    }
    return NO_ERROR;
}

status_t MediaPlayer2AudioOutput::attachAuxEffect(int effectId) {
    ALOGV("attachAuxEffect(%d)", effectId);
    Mutex::Autolock lock(mLock);
    mAuxEffectId = effectId;
    if (mJAudioTrack != nullptr) {
        return mJAudioTrack->attachAuxEffect(effectId);
    }
    return NO_ERROR;
}

status_t MediaPlayer2AudioOutput::setPreferredDevice(jobject device) {
    ALOGV("setPreferredDevice");
    Mutex::Autolock lock(mLock);
    status_t ret = NO_ERROR;
    if (mJAudioTrack != nullptr) {
        ret = mJAudioTrack->setPreferredDevice(device);
    }
    if (ret == NO_ERROR) {
        mPreferredDevice = new JObjectHolder(device);
    }
    return ret;
}

jobject MediaPlayer2AudioOutput::getRoutedDevice() {
    ALOGV("getRoutedDevice");
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack != nullptr) {
        return mJAudioTrack->getRoutedDevice();
    }
    return nullptr;
}

status_t MediaPlayer2AudioOutput::addAudioDeviceCallback(jobject jRoutingDelegate) {
    ALOGV("addAudioDeviceCallback");
    Mutex::Autolock lock(mLock);
    jobject listener = JAudioTrack::getListener(jRoutingDelegate);
    if (mJAudioTrack != nullptr &&
        JAudioTrack::findByKey(mRoutingDelegates, listener) == nullptr) {
        jobject handler = JAudioTrack::getHandler(jRoutingDelegate);
        jobject routingDelegate = JAudioTrack::addGlobalRef(jRoutingDelegate);
        mRoutingDelegates.push_back(std::pair<jobject, jobject>(listener, routingDelegate));
        return mJAudioTrack->addAudioDeviceCallback(routingDelegate, handler);
    }
    return NO_ERROR;
}

status_t MediaPlayer2AudioOutput::removeAudioDeviceCallback(jobject listener) {
    ALOGV("removeAudioDeviceCallback");
    Mutex::Autolock lock(mLock);
    jobject routingDelegate = nullptr;
    if (mJAudioTrack != nullptr &&
        (routingDelegate = JAudioTrack::findByKey(mRoutingDelegates, listener)) != nullptr) {
        mJAudioTrack->removeAudioDeviceCallback(routingDelegate);
        JAudioTrack::eraseByKey(mRoutingDelegates, listener);
        if (JAudioTrack::removeGlobalRef(routingDelegate) != NO_ERROR) {
            return BAD_VALUE;
        }
    }
    return NO_ERROR;
}

// static
void MediaPlayer2AudioOutput::CallbackWrapper(
        int event, void *cookie, void *info) {
    //ALOGV("callbackwrapper");
    CallbackData *data = (CallbackData*)cookie;
    // lock to ensure we aren't caught in the middle of a track switch.
    data->lock();
    MediaPlayer2AudioOutput *me = data->getOutput();
    JAudioTrack::Buffer *buffer = (JAudioTrack::Buffer *)info;
    if (me == nullptr) {
        // no output set, likely because the track was scheduled to be reused
        // by another player, but the format turned out to be incompatible.
        data->unlock();
        if (buffer != nullptr) {
            buffer->mSize = 0;
        }
        return;
    }

    switch(event) {
    case JAudioTrack::EVENT_MORE_DATA: {
        size_t actualSize = (*me->mCallback)(
                me, buffer->mData, buffer->mSize, me->mCallbackCookie,
                CB_EVENT_FILL_BUFFER);

        // Log when no data is returned from the callback.
        // (1) We may have no data (especially with network streaming sources).
        // (2) We may have reached the EOS and the audio track is not stopped yet.
        // Note that AwesomePlayer/AudioPlayer will only return zero size when it reaches the EOS.
        // NuPlayer2Renderer will return zero when it doesn't have data (it doesn't block to fill).
        //
        // This is a benign busy-wait, with the next data request generated 10 ms or more later;
        // nevertheless for power reasons, we don't want to see too many of these.

        ALOGV_IF(actualSize == 0 && buffer->mSize > 0, "callbackwrapper: empty buffer returned");

        buffer->mSize = actualSize;
        } break;

    case JAudioTrack::EVENT_STREAM_END:
        // currently only occurs for offloaded callbacks
        ALOGV("callbackwrapper: deliver EVENT_STREAM_END");
        (*me->mCallback)(me, nullptr /* buffer */, 0 /* size */,
                me->mCallbackCookie, CB_EVENT_STREAM_END);
        break;

    case JAudioTrack::EVENT_NEW_IAUDIOTRACK :
        ALOGV("callbackwrapper: deliver EVENT_TEAR_DOWN");
        (*me->mCallback)(me,  nullptr /* buffer */, 0 /* size */,
                me->mCallbackCookie, CB_EVENT_TEAR_DOWN);
        break;

    case JAudioTrack::EVENT_UNDERRUN:
        // This occurs when there is no data available, typically
        // when there is a failure to supply data to the AudioTrack.  It can also
        // occur in non-offloaded mode when the audio device comes out of standby.
        //
        // If an AudioTrack underruns it outputs silence. Since this happens suddenly
        // it may sound like an audible pop or glitch.
        //
        // The underrun event is sent once per track underrun; the condition is reset
        // when more data is sent to the AudioTrack.
        ALOGD("callbackwrapper: EVENT_UNDERRUN (discarded)");
        break;

    default:
        ALOGE("received unknown event type: %d inside CallbackWrapper !", event);
    }

    data->unlock();
}

int32_t MediaPlayer2AudioOutput::getSessionId() const {
    Mutex::Autolock lock(mLock);
    return mSessionId;
}

void MediaPlayer2AudioOutput::setSessionId(const int32_t sessionId) {
    Mutex::Autolock lock(mLock);
    mSessionId = sessionId;
}

uint32_t MediaPlayer2AudioOutput::getSampleRate() const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == 0) {
        return 0;
    }
    return mJAudioTrack->getSampleRate();
}

int64_t MediaPlayer2AudioOutput::getBufferDurationInUs() const {
    Mutex::Autolock lock(mLock);
    if (mJAudioTrack == 0) {
        return 0;
    }
    int64_t duration;
    if (mJAudioTrack->getBufferDurationInUs(&duration) != OK) {
        return 0;
    }
    return duration;
}

} // namespace android
