| The Android Open Source Project | f013e1a | 2008-12-17 18:05:43 -0800 | [diff] [blame] | 1 | /* |
| 2 | ** |
| 3 | ** Copyright 2007, The Android Open Source Project |
| 4 | ** |
| 5 | ** Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | ** you may not use this file except in compliance with the License. |
| 7 | ** You may obtain a copy of the License at |
| 8 | ** |
| 9 | ** http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | ** |
| 11 | ** Unless required by applicable law or agreed to in writing, software |
| 12 | ** distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | ** See the License for the specific language governing permissions and |
| 15 | ** limitations under the License. |
| 16 | */ |
| 17 | |
| 18 | #define LOG_TAG "AmrInputStream" |
| 19 | #include "utils/Log.h" |
| 20 | |
| 21 | #include <media/mediarecorder.h> |
| 22 | #include <stdio.h> |
| 23 | #include <assert.h> |
| 24 | #include <limits.h> |
| 25 | #include <unistd.h> |
| 26 | #include <fcntl.h> |
| 27 | #include <utils/threads.h> |
| 28 | |
| 29 | #include "jni.h" |
| 30 | #include "JNIHelp.h" |
| 31 | #include "android_runtime/AndroidRuntime.h" |
| 32 | #include "gsmamr_encoder_wrapper.h" |
| 33 | |
| 34 | |
| 35 | // ---------------------------------------------------------------------------- |
| 36 | |
| 37 | using namespace android; |
| 38 | |
| 39 | // Corresponds to max bit rate of 12.2 kbps. |
| 40 | static const int aMaxOutputBufferSize = 32; |
| 41 | |
| 42 | static const int SAMPLES_PER_FRAME = 8000 * 20 / 1000; |
| 43 | |
| 44 | |
| 45 | // |
| 46 | // helper function to throw an exception |
| 47 | // |
| 48 | static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) { |
| 49 | if (jclass cls = env->FindClass(ex)) { |
| 50 | char msg[1000]; |
| 51 | sprintf(msg, fmt, data); |
| 52 | env->ThrowNew(cls, msg); |
| 53 | env->DeleteLocalRef(cls); |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | static jint android_media_AmrInputStream_GsmAmrEncoderNew |
| 58 | (JNIEnv *env, jclass clazz) { |
| 59 | CPvGsmAmrEncoder* gae = new CPvGsmAmrEncoder(); |
| 60 | if (gae == NULL) { |
| 61 | throwException(env, "java/lang/IllegalStateException", |
| 62 | "new CPvGsmAmrEncoder() failed", 0); |
| 63 | } |
| 64 | return (jint)gae; |
| 65 | } |
| 66 | |
| 67 | static void android_media_AmrInputStream_GsmAmrEncoderInitialize |
| 68 | (JNIEnv *env, jclass clazz, jint gae) { |
| 69 | // set input parameters |
| 70 | TEncodeProperties encodeProps; |
| 71 | encodeProps.iInBitsPerSample = 16; |
| 72 | encodeProps.iInSamplingRate = 8000; |
| 73 | encodeProps.iInClockRate = 1000; |
| 74 | encodeProps.iInNumChannels = 1; |
| 75 | encodeProps.iInInterleaveMode = TEncodeProperties::EINTERLEAVE_LR; |
| 76 | encodeProps.iMode = CPvGsmAmrEncoder::GSM_AMR_12_2; |
| Jianhong Jiang | 3ca47d1 | 2009-04-13 19:01:51 -0700 | [diff] [blame] | 77 | encodeProps.iBitStreamFormat = false; |
| The Android Open Source Project | f013e1a | 2008-12-17 18:05:43 -0800 | [diff] [blame] | 78 | encodeProps.iAudioObjectType = 0; |
| 79 | encodeProps.iOutSamplingRate = encodeProps.iInSamplingRate; |
| 80 | encodeProps.iOutNumChannels = encodeProps.iInNumChannels; |
| 81 | encodeProps.iOutClockRate = encodeProps.iInClockRate; |
| 82 | |
| 83 | if (int rtn = ((CPvGsmAmrEncoder*)gae)-> |
| 84 | InitializeEncoder(aMaxOutputBufferSize, &encodeProps)) { |
| 85 | throwException(env, "java/lang/IllegalArgumentException", |
| 86 | "CPvGsmAmrEncoder::InitializeEncoder failed %d", rtn); |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | static jint android_media_AmrInputStream_GsmAmrEncoderEncode |
| 91 | (JNIEnv *env, jclass clazz, |
| 92 | jint gae, jbyteArray pcm, jint pcmOffset, jbyteArray amr, jint amrOffset) { |
| 93 | |
| 94 | // set up input stream |
| 95 | jbyte inBuf[SAMPLES_PER_FRAME*2]; |
| 96 | TInputAudioStream in; |
| 97 | in.iSampleBuffer = (uint8*)inBuf; |
| 98 | env->GetByteArrayRegion(pcm, pcmOffset, sizeof(inBuf), inBuf); |
| 99 | in.iSampleLength = sizeof(inBuf); |
| 100 | in.iMode = CPvGsmAmrEncoder::GSM_AMR_12_2; |
| 101 | in.iStartTime = 0; |
| 102 | in.iStopTime = 0; |
| 103 | |
| 104 | // set up output stream |
| 105 | jbyte outBuf[aMaxOutputBufferSize]; |
| 106 | int32 sampleFrameSize[1] = { 0 }; |
| 107 | TOutputAudioStream out; |
| 108 | out.iBitStreamBuffer = (uint8*)outBuf; |
| 109 | out.iNumSampleFrames = 0; |
| 110 | out.iSampleFrameSize = sampleFrameSize; |
| 111 | out.iStartTime = 0; |
| 112 | out.iStopTime = 0; |
| 113 | |
| 114 | // encode |
| 115 | if (int rtn = ((CPvGsmAmrEncoder*)gae)->Encode(in, out)) { |
| 116 | throwException(env, "java/io/IOException", "CPvGsmAmrEncoder::Encode failed %d", rtn); |
| 117 | return -1; |
| 118 | } |
| 119 | |
| 120 | // validate one-frame assumption |
| 121 | if (out.iNumSampleFrames != 1) { |
| 122 | throwException(env, "java/io/IOException", |
| 123 | "CPvGsmAmrEncoder::Encode more than one frame returned %d", out.iNumSampleFrames); |
| 124 | return 0; |
| 125 | } |
| 126 | |
| 127 | // copy result |
| 128 | int length = out.iSampleFrameSize[0]; |
| 129 | |
| 130 | // The 1st byte of PV AMR frames are WMF (Wireless Multimedia Forum) |
| 131 | // bitpacked, i.e.; |
| 132 | // [P(4) + FT(4)]. Q=1 for good frame, P=padding bit, 0 |
| 133 | // Here we are converting the header to be as specified in Section 5.3 of |
| 134 | // RFC 3267 (AMR storage format) i.e. |
| 135 | // [P(1) + FT(4) + Q(1) + P(2)]. |
| 136 | if (length > 0) { |
| 137 | outBuf[0] = (outBuf[0] << 3) | 0x4; |
| 138 | } |
| 139 | |
| 140 | env->SetByteArrayRegion(amr, amrOffset, length, outBuf); |
| 141 | |
| 142 | return length; |
| 143 | } |
| 144 | |
| 145 | static void android_media_AmrInputStream_GsmAmrEncoderCleanup |
| 146 | (JNIEnv *env, jclass clazz, jint gae) { |
| 147 | if (int rtn = ((CPvGsmAmrEncoder*)gae)->CleanupEncoder()) { |
| 148 | throwException(env, "java/lang/IllegalStateException", |
| 149 | "CPvGsmAmrEncoder::CleanupEncoder failed %d", rtn); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | static void android_media_AmrInputStream_GsmAmrEncoderDelete |
| 154 | (JNIEnv *env, jclass clazz, jint gae) { |
| 155 | delete (CPvGsmAmrEncoder*)gae; |
| 156 | } |
| 157 | |
| 158 | // ---------------------------------------------------------------------------- |
| 159 | |
| 160 | static JNINativeMethod gMethods[] = { |
| 161 | {"GsmAmrEncoderNew", "()I", (void*)android_media_AmrInputStream_GsmAmrEncoderNew}, |
| 162 | {"GsmAmrEncoderInitialize", "(I)V", (void*)android_media_AmrInputStream_GsmAmrEncoderInitialize}, |
| 163 | {"GsmAmrEncoderEncode", "(I[BI[BI)I", (void*)android_media_AmrInputStream_GsmAmrEncoderEncode}, |
| 164 | {"GsmAmrEncoderCleanup", "(I)V", (void*)android_media_AmrInputStream_GsmAmrEncoderCleanup}, |
| 165 | {"GsmAmrEncoderDelete", "(I)V", (void*)android_media_AmrInputStream_GsmAmrEncoderDelete}, |
| 166 | }; |
| 167 | |
| 168 | |
| 169 | int register_android_media_AmrInputStream(JNIEnv *env) |
| 170 | { |
| 171 | const char* const kClassPathName = "android/media/AmrInputStream"; |
| The Android Open Source Project | f013e1a | 2008-12-17 18:05:43 -0800 | [diff] [blame] | 172 | |
| 173 | return AndroidRuntime::registerNativeMethods(env, |
| 174 | kClassPathName, gMethods, NELEM(gMethods)); |
| 175 | } |
| 176 | |
| 177 | |