/* ** ** 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 "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "gsmamr_enc.h" // ---------------------------------------------------------------------------- using namespace android; // Corresponds to max bit rate of 12.2 kbps. static const int MAX_OUTPUT_BUFFER_SIZE = 32; static const int FRAME_DURATION_MS = 20; static const int SAMPLING_RATE_HZ = 8000; static const int SAMPLES_PER_FRAME = ((SAMPLING_RATE_HZ * FRAME_DURATION_MS) / 1000); static const int BYTES_PER_SAMPLE = 2; // Assume 16-bit PCM samples static const int BYTES_PER_FRAME = (SAMPLES_PER_FRAME * BYTES_PER_SAMPLE); struct GsmAmrEncoderState { GsmAmrEncoderState() : mEncState(NULL), mSidState(NULL), mLastModeUsed(0) { } ~GsmAmrEncoderState() {} void* mEncState; void* mSidState; int32_t mLastModeUsed; }; static jlong android_media_AmrInputStream_GsmAmrEncoderNew (JNIEnv *env, jclass /* clazz */) { GsmAmrEncoderState* gae = new GsmAmrEncoderState(); if (gae == NULL) { jniThrowRuntimeException(env, "Out of memory"); } return (jlong)gae; } static void android_media_AmrInputStream_GsmAmrEncoderInitialize (JNIEnv *env, jclass /* clazz */, jlong gae) { GsmAmrEncoderState *state = (GsmAmrEncoderState *) gae; int32_t nResult = AMREncodeInit(&state->mEncState, &state->mSidState, false); if (nResult != OK) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "GsmAmrEncoder initialization failed %d", nResult); } } static jint android_media_AmrInputStream_GsmAmrEncoderEncode (JNIEnv *env, jclass /* clazz */, jlong gae, jbyteArray pcm, jint pcmOffset, jbyteArray amr, jint amrOffset) { jbyte inBuf[BYTES_PER_FRAME]; jbyte outBuf[MAX_OUTPUT_BUFFER_SIZE]; env->GetByteArrayRegion(pcm, pcmOffset, sizeof(inBuf), inBuf); GsmAmrEncoderState *state = (GsmAmrEncoderState *) gae; int32_t length = AMREncode(state->mEncState, state->mSidState, (Mode) MR122, (int16_t *) inBuf, (unsigned char *) outBuf, (Frame_Type_3GPP*) &state->mLastModeUsed, AMR_TX_WMF); if (length < 0) { jniThrowExceptionFmt(env, "java/io/IOException", "Failed to encode a frame with error code: %d", length); return (jint)-1; } // 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 (jint)length; } static void android_media_AmrInputStream_GsmAmrEncoderCleanup (JNIEnv* /* env */, jclass /* clazz */, jlong gae) { GsmAmrEncoderState *state = (GsmAmrEncoderState *) gae; AMREncodeExit(&state->mEncState, &state->mSidState); state->mEncState = NULL; state->mSidState = NULL; } static void android_media_AmrInputStream_GsmAmrEncoderDelete (JNIEnv* /* env */, jclass /* clazz */, jlong gae) { delete (GsmAmrEncoderState*)gae; } // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { {"GsmAmrEncoderNew", "()J", (void*)android_media_AmrInputStream_GsmAmrEncoderNew}, {"GsmAmrEncoderInitialize", "(J)V", (void*)android_media_AmrInputStream_GsmAmrEncoderInitialize}, {"GsmAmrEncoderEncode", "(J[BI[BI)I", (void*)android_media_AmrInputStream_GsmAmrEncoderEncode}, {"GsmAmrEncoderCleanup", "(J)V", (void*)android_media_AmrInputStream_GsmAmrEncoderCleanup}, {"GsmAmrEncoderDelete", "(J)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)); }