/*
 * Copyright (C) 2009 The Android Open Source Project
 * Copyright (C) 2011 Sony Ericsson Mobile Communications AB. All rights reserved.
 * This file contains confidential and proprietary information of Sony Ericsson
 * Mobile Communications AB.
 *
 * Original code 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.
 *
 * NOTE: This file has been modified by Sony Ericsson Mobile Communications AB.
 * Modifications are licensed under the Sony Ericsson Mobile Communications AB's
 * End User License Agreement ("EULA"). Any use of the modifications is subject
 * to the terms of the EULA.
 */

#define LOG_TAG "AMRWBDecoder"
#include <utils/Log.h>

#include "AMRWBDecoder.h"

#include "pvamrwbdecoder.h"

#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>

namespace android {

static const int32_t kNumSamplesPerFrame = 320;
static const int32_t kSampleRate = 16000;

AMRWBDecoder::AMRWBDecoder(const sp<MediaSource> &source)
    : mSource(source),
      mStarted(false),
      mBufferGroup(NULL),
      mState(NULL),
      mDecoderBuf(NULL),
      mDecoderCookie(NULL),
      mAnchorTimeUs(0),
      mNumSamplesOutput(0),
      mInputBuffer(NULL) {
}

AMRWBDecoder::~AMRWBDecoder() {
    if (mStarted) {
        stop();
    }
}

status_t AMRWBDecoder::start(MetaData *params) {
    CHECK(!mStarted);

    mBufferGroup = new MediaBufferGroup;
    mBufferGroup->add_buffer(
            new MediaBuffer(kNumSamplesPerFrame * sizeof(int16_t)));

    int32_t memReq = pvDecoder_AmrWbMemRequirements();
    mDecoderBuf = malloc(memReq);

    pvDecoder_AmrWb_Init(&mState, mDecoderBuf, &mDecoderCookie);

    mSource->start();

    mAnchorTimeUs = 0;
    mNumSamplesOutput = 0;
    mStarted = true;

    return OK;
}

status_t AMRWBDecoder::stop() {
    CHECK(mStarted);

    if (mInputBuffer) {
        mInputBuffer->release();
        mInputBuffer = NULL;
    }

    delete mBufferGroup;
    mBufferGroup = NULL;

    free(mDecoderBuf);
    mDecoderBuf = NULL;

    mSource->stop();

    mStarted = false;

    return OK;
}

sp<MetaData> AMRWBDecoder::getFormat() {
    sp<MetaData> srcFormat = mSource->getFormat();

    int32_t numChannels;
    int32_t sampleRate;

    CHECK(srcFormat->findInt32(kKeyChannelCount, &numChannels));
    CHECK_EQ(numChannels, 1);

    CHECK(srcFormat->findInt32(kKeySampleRate, &sampleRate));
    CHECK_EQ(sampleRate, kSampleRate);

    sp<MetaData> meta = new MetaData;
    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
    meta->setInt32(kKeyChannelCount, numChannels);
    meta->setInt32(kKeySampleRate, sampleRate);

    int64_t durationUs;
    if (srcFormat->findInt64(kKeyDuration, &durationUs)) {
        meta->setInt64(kKeyDuration, durationUs);
    }

    meta->setCString(kKeyDecoderComponent, "AMRWBDecoder");

    return meta;
}

static size_t getFrameSize(unsigned FT) {
    static const size_t kFrameSizeWB[9] = {
        132, 177, 253, 285, 317, 365, 397, 461, 477
    };

    size_t frameSize = kFrameSizeWB[FT];

    // Round up bits to bytes and add 1 for the header byte.
    frameSize = (frameSize + 7) / 8 + 1;

    return frameSize;
}

status_t AMRWBDecoder::read(
        MediaBuffer **out, const ReadOptions *options) {
    status_t err;

    *out = NULL;

    int64_t seekTimeUs;
    ReadOptions::SeekMode seekMode;
    if (options && options->getSeekTo(&seekTimeUs, &seekMode)) {
        CHECK(seekTimeUs >= 0);

        mNumSamplesOutput = 0;

        if (mInputBuffer) {
            mInputBuffer->release();
            mInputBuffer = NULL;
        }
    } else {
        seekTimeUs = -1;
    }

    if (mInputBuffer == NULL) {
        err = mSource->read(&mInputBuffer, options);

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

        int64_t timeUs;
        if (mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
            mAnchorTimeUs = timeUs;
            mNumSamplesOutput = 0;
        } else {
            // We must have a new timestamp after seeking.
            CHECK(seekTimeUs < 0);
        }
    }

    const uint8_t *inputPtr =
        (const uint8_t *)mInputBuffer->data() + mInputBuffer->range_offset();

    int16 mode = ((inputPtr[0] >> 3) & 0x0f);

    if (mode > 8 ) {
        LOGE("illegal AMR frame type %d", mode);

        return ERROR_MALFORMED;
    }

    MediaBuffer *buffer;
    CHECK_EQ(mBufferGroup->acquire_buffer(&buffer), OK);

    size_t frameSize = getFrameSize(mode);
    CHECK(mInputBuffer->range_length() >= frameSize);

    int16 frameType;
    RX_State rx_state;
    mime_unsorting(
            const_cast<uint8_t *>(&inputPtr[1]),
            mInputSampleBuffer,
            &frameType, &mode, 1, &rx_state);

    int16_t *outPtr = (int16_t *)buffer->data();

    int16_t numSamplesOutput;
    pvDecoder_AmrWb(
            mode, mInputSampleBuffer,
            outPtr,
            &numSamplesOutput,
            mDecoderBuf, frameType, mDecoderCookie);

    CHECK_EQ(numSamplesOutput, kNumSamplesPerFrame);

    for (int i = 0; i < kNumSamplesPerFrame; ++i) {
        /* Delete the 2 LSBs (14-bit output) */
        outPtr[i] &= 0xfffC;
    }

    buffer->set_range(0, numSamplesOutput * sizeof(int16_t));

    mInputBuffer->set_range(
            mInputBuffer->range_offset() + frameSize,
            mInputBuffer->range_length() - frameSize);

    if (mInputBuffer->range_length() == 0) {
        mInputBuffer->release();
        mInputBuffer = NULL;
    }

    buffer->meta_data()->setInt64(
            kKeyTime,
            mAnchorTimeUs
                + (mNumSamplesOutput * 1000000) / kSampleRate);

    mNumSamplesOutput += kNumSamplesPerFrame;

    *out = buffer;

    return OK;
}

}  // namespace android
