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