/*
**
** Copyright 2007, 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_TAG "AmrInputStream"
#include "utils/Log.h"

#include <media/mediarecorder.h>
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <utils/threads.h>

#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "gsmamr_encoder_wrapper.h"


// ----------------------------------------------------------------------------

using namespace android;

// Corresponds to max bit rate of 12.2 kbps.
static const int aMaxOutputBufferSize = 32;

static const int SAMPLES_PER_FRAME = 8000 * 20 / 1000;


//
// helper function to throw an exception
//
static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) {
    if (jclass cls = env->FindClass(ex)) {
        char msg[1000];
        sprintf(msg, fmt, data);
        env->ThrowNew(cls, msg);
        env->DeleteLocalRef(cls);
    }
}

static jint android_media_AmrInputStream_GsmAmrEncoderNew
        (JNIEnv *env, jclass clazz) {
    CPvGsmAmrEncoder* gae = new CPvGsmAmrEncoder();
    if (gae == NULL) {
        throwException(env, "java/lang/IllegalStateException",
                "new CPvGsmAmrEncoder() failed", 0);
    }
    return (jint)gae;
}

static void android_media_AmrInputStream_GsmAmrEncoderInitialize
        (JNIEnv *env, jclass clazz, jint gae) {
    // set input parameters
    TEncodeProperties encodeProps;
    encodeProps.iInBitsPerSample = 16;
    encodeProps.iInSamplingRate = 8000;
    encodeProps.iInClockRate = 1000;
    encodeProps.iInNumChannels = 1;
    encodeProps.iInInterleaveMode = TEncodeProperties::EINTERLEAVE_LR;
    encodeProps.iMode = CPvGsmAmrEncoder::GSM_AMR_12_2;
    encodeProps.iBitStreamFormat = false;
    encodeProps.iAudioObjectType = 0;
    encodeProps.iOutSamplingRate = encodeProps.iInSamplingRate;
    encodeProps.iOutNumChannels = encodeProps.iInNumChannels;
    encodeProps.iOutClockRate = encodeProps.iInClockRate;

    if (int rtn = ((CPvGsmAmrEncoder*)gae)->
            InitializeEncoder(aMaxOutputBufferSize, &encodeProps)) {
        throwException(env, "java/lang/IllegalArgumentException",
                "CPvGsmAmrEncoder::InitializeEncoder failed %d", rtn);
    }
}

static jint android_media_AmrInputStream_GsmAmrEncoderEncode
        (JNIEnv *env, jclass clazz,
         jint gae, jbyteArray pcm, jint pcmOffset, jbyteArray amr, jint amrOffset) {

    // set up input stream
    jbyte inBuf[SAMPLES_PER_FRAME*2];
    TInputAudioStream in;
    in.iSampleBuffer = (uint8*)inBuf;
    env->GetByteArrayRegion(pcm, pcmOffset, sizeof(inBuf), inBuf);
    in.iSampleLength = sizeof(inBuf);
    in.iMode = CPvGsmAmrEncoder::GSM_AMR_12_2;
    in.iStartTime = 0;
    in.iStopTime = 0;

    // set up output stream
    jbyte outBuf[aMaxOutputBufferSize];
    int32 sampleFrameSize[1] = { 0 };
    TOutputAudioStream out;
    out.iBitStreamBuffer = (uint8*)outBuf;
    out.iNumSampleFrames = 0;
    out.iSampleFrameSize = sampleFrameSize;
    out.iStartTime = 0;
    out.iStopTime = 0;

    // encode
    if (int rtn = ((CPvGsmAmrEncoder*)gae)->Encode(in, out)) {
        throwException(env, "java/io/IOException", "CPvGsmAmrEncoder::Encode failed %d", rtn);
        return -1;
    }

    // validate one-frame assumption
    if (out.iNumSampleFrames != 1) {
        throwException(env, "java/io/IOException",
                "CPvGsmAmrEncoder::Encode more than one frame returned %d", out.iNumSampleFrames);
        return 0;
    }

    // copy result
    int length = out.iSampleFrameSize[0];

    // The 1st byte of PV AMR frames are WMF (Wireless Multimedia Forum)
    // bitpacked, i.e.;
    //    [P(4) + FT(4)]. Q=1 for good frame, P=padding bit, 0
    // Here we are converting the header to be as specified in Section 5.3 of
    // RFC 3267 (AMR storage format) i.e.
    //    [P(1) + FT(4) + Q(1) + P(2)].
    if (length > 0) {
      outBuf[0] = (outBuf[0] << 3) | 0x4;
    }

    env->SetByteArrayRegion(amr, amrOffset, length, outBuf);

    return length;
}

static void android_media_AmrInputStream_GsmAmrEncoderCleanup
        (JNIEnv *env, jclass clazz, jint gae) {
    if (int rtn = ((CPvGsmAmrEncoder*)gae)->CleanupEncoder()) {
        throwException(env, "java/lang/IllegalStateException",
                "CPvGsmAmrEncoder::CleanupEncoder failed %d", rtn);
    }
}

static void android_media_AmrInputStream_GsmAmrEncoderDelete
        (JNIEnv *env, jclass clazz, jint gae) {
    delete (CPvGsmAmrEncoder*)gae;
}

// ----------------------------------------------------------------------------

static JNINativeMethod gMethods[] = {
    {"GsmAmrEncoderNew",        "()I",        (void*)android_media_AmrInputStream_GsmAmrEncoderNew},
    {"GsmAmrEncoderInitialize", "(I)V",       (void*)android_media_AmrInputStream_GsmAmrEncoderInitialize},
    {"GsmAmrEncoderEncode",     "(I[BI[BI)I", (void*)android_media_AmrInputStream_GsmAmrEncoderEncode},
    {"GsmAmrEncoderCleanup",    "(I)V",       (void*)android_media_AmrInputStream_GsmAmrEncoderCleanup},
    {"GsmAmrEncoderDelete",     "(I)V",       (void*)android_media_AmrInputStream_GsmAmrEncoderDelete},
};


int register_android_media_AmrInputStream(JNIEnv *env)
{
    const char* const kClassPathName = "android/media/AmrInputStream";

    return AndroidRuntime::registerNativeMethods(env,
            kClassPathName, gMethods, NELEM(gMethods));
}