/*
**
** 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));
}