Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd91fcc9 authored by Marco Nelissen's avatar Marco Nelissen Committed by android-build-merger
Browse files

Merge "Make AmrInputStream use MediaCodec" am: 86665826

am: 618c1a9a

Change-Id: Ic6723d4e2ce7ab401908ca955a785463612509bb
parents d517ea75 618c1a9a
Loading
Loading
Loading
Loading
+115 −58
Original line number Original line Diff line number Diff line
@@ -18,29 +18,30 @@ package android.media;


import java.io.InputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.IOException;
import java.nio.ByteBuffer;

import android.media.MediaCodec.BufferInfo;
import android.util.Log;




/**
/**
 * AmrInputStream
 * AmrInputStream
 * @hide
 * @hide
 */
 */
public final class AmrInputStream extends InputStream
public final class AmrInputStream extends InputStream {
{    
    static {
        System.loadLibrary("media_jni");
    }
    
    private final static String TAG = "AmrInputStream";
    private final static String TAG = "AmrInputStream";
    
    
    // frame is 20 msec at 8.000 khz
    // frame is 20 msec at 8.000 khz
    private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
    private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;


    MediaCodec mCodec;
    BufferInfo mInfo;
    boolean mSawOutputEOS;
    boolean mSawInputEOS;

    // pcm input stream
    // pcm input stream
    private InputStream mInputStream;
    private InputStream mInputStream;


    // native handle
    private long mGae;
    
    // result amr stream
    // result amr stream
    private final byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
    private final byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
    private int mBufIn = 0;
    private int mBufIn = 0;
@@ -55,8 +56,31 @@ public final class AmrInputStream extends InputStream
     */
     */
    public AmrInputStream(InputStream inputStream) {
    public AmrInputStream(InputStream inputStream) {
        mInputStream = inputStream;
        mInputStream = inputStream;
        mGae = GsmAmrEncoderNew();

        GsmAmrEncoderInitialize(mGae);
        MediaFormat format  = new MediaFormat();
        format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
        format.setInteger(MediaFormat.KEY_BIT_RATE, 12200);

        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
        String name = mcl.findEncoderForFormat(format);
        if (name != null) {
            try {
                mCodec = MediaCodec.createByCodecName(name);
                mCodec.configure(format,
                        null /* surface */,
                        null /* crypto */,
                        MediaCodec.CONFIGURE_FLAG_ENCODE);
                mCodec.start();
            } catch (IOException e) {
                if (mCodec != null) {
                    mCodec.release();
                }
                mCodec = null;
            }
        }
        mInfo = new BufferInfo();
    }
    }


    @Override
    @Override
@@ -72,67 +96,100 @@ public final class AmrInputStream extends InputStream


    @Override
    @Override
    public int read(byte[] b, int offset, int length) throws IOException {
    public int read(byte[] b, int offset, int length) throws IOException {
        if (mGae == 0) throw new IllegalStateException("not open");
        if (mCodec == null) {
            throw new IllegalStateException("not open");
        }


        // local buffer of amr encoded audio empty
        if (mBufOut >= mBufIn && !mSawOutputEOS) {
        if (mBufOut >= mBufIn) {
            // no data left in buffer, refill it
            // reset the buffer
            mBufOut = 0;
            mBufOut = 0;
            mBufIn = 0;
            mBufIn = 0;


            // fetch a 20 msec frame of pcm
            // first push as much data into the encoder as possible
            for (int i = 0; i < SAMPLES_PER_FRAME * 2; ) {
            while (!mSawInputEOS) {
                int n = mInputStream.read(mBuf, i, SAMPLES_PER_FRAME * 2 - i);
                int index = mCodec.dequeueInputBuffer(0);
                if (n == -1) return -1;
                if (index < 0) {
                i += n;
                    // no input buffer currently available
                    break;
                } else {
                    int numRead;
                    for (numRead = 0; numRead < SAMPLES_PER_FRAME * 2; ) {
                        int n = mInputStream.read(mBuf, numRead, SAMPLES_PER_FRAME * 2 - numRead);
                        if (n == -1) {
                            mSawInputEOS = true;
                            break;
                        }
                        numRead += n;
                    }
                    ByteBuffer buf = mCodec.getInputBuffer(index);
                    buf.put(mBuf, 0, numRead);
                    mCodec.queueInputBuffer(index,
                            0 /* offset */,
                            numRead,
                            0 /* presentationTimeUs */,
                            mSawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0 /* flags */);
                }
            }
            }


            // encode it
            // now read encoded data from the encoder (blocking, since we just filled up the
            mBufIn = GsmAmrEncoderEncode(mGae, mBuf, 0, mBuf, 0);
            // encoder's input with data it should be able to output at least one buffer)
            while (true) {
                int index = mCodec.dequeueOutputBuffer(mInfo, -1);
                if (index >= 0) {
                    mBufIn = mInfo.size;
                    ByteBuffer out = mCodec.getOutputBuffer(index);
                    out.get(mBuf, 0 /* offset */, mBufIn /* length */);
                    mCodec.releaseOutputBuffer(index,  false /* render */);
                    if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        mSawOutputEOS = true;
                    }
                    break;
                }
            }
        }
        }


        // return encoded audio to user
        if (mBufOut < mBufIn) {
        if (length > mBufIn - mBufOut) length = mBufIn - mBufOut;
            // there is data in the buffer
            if (length > mBufIn - mBufOut) {
                length = mBufIn - mBufOut;
            }
            System.arraycopy(mBuf, mBufOut, b, offset, length);
            System.arraycopy(mBuf, mBufOut, b, offset, length);
            mBufOut += length;
            mBufOut += length;
        
            return length;
            return length;
        }
        }


        if (mSawInputEOS && mSawOutputEOS) {
            // no more data available in buffer, codec or input stream
            return -1;
        }

        // caller should try again
        return 0;
    }

    @Override
    @Override
    public void close() throws IOException {
    public void close() throws IOException {
        try {
        try {
            if (mInputStream != null) mInputStream.close();
            if (mInputStream != null) {
                mInputStream.close();
            }
        } finally {
        } finally {
            mInputStream = null;
            mInputStream = null;
            try {
            try {
                if (mGae != 0) GsmAmrEncoderCleanup(mGae);
                if (mCodec != null) {
            } finally {
                    mCodec.release();
                try {
                    if (mGae != 0) GsmAmrEncoderDelete(mGae);
                } finally {
                    mGae = 0;
                }
                }
            } finally {
                mCodec = null;
            }
            }
        }
        }
    }
    }


    @Override
    @Override
    protected void finalize() throws Throwable {
    protected void finalize() throws Throwable {
        if (mGae != 0) {
        if (mCodec != null) {
            close();
            Log.w(TAG, "AmrInputStream wasn't closed");
            throw new IllegalStateException("someone forgot to close AmrInputStream");
            mCodec.release();
        }
        }
    }
    }
    
    //
    // AudioRecord JNI interface
    //
    private static native long GsmAmrEncoderNew();
    private static native void GsmAmrEncoderInitialize(long gae);
    private static native int GsmAmrEncoderEncode(long gae,
            byte[] pcm, int pcmOffset, byte[] amr, int amrOffset) throws IOException;
    private static native void GsmAmrEncoderCleanup(long gae);
    private static native void GsmAmrEncoderDelete(long gae);

}
}
+0 −6
Original line number Original line Diff line number Diff line
@@ -2,7 +2,6 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
include $(CLEAR_VARS)


LOCAL_SRC_FILES:= \
LOCAL_SRC_FILES:= \
    android_media_AmrInputStream.cpp \
    android_media_ExifInterface.cpp \
    android_media_ExifInterface.cpp \
    android_media_ImageWriter.cpp \
    android_media_ImageWriter.cpp \
    android_media_ImageReader.cpp \
    android_media_ImageReader.cpp \
@@ -46,11 +45,9 @@ LOCAL_SHARED_LIBRARIES := \
    libusbhost \
    libusbhost \
    libexif \
    libexif \
    libpiex \
    libpiex \
    libstagefright_amrnb_common \
    libandroidfw
    libandroidfw


LOCAL_STATIC_LIBRARIES := \
LOCAL_STATIC_LIBRARIES := \
    libstagefright_amrnbenc


LOCAL_C_INCLUDES += \
LOCAL_C_INCLUDES += \
    external/libexif/ \
    external/libexif/ \
@@ -60,9 +57,6 @@ LOCAL_C_INCLUDES += \
    frameworks/base/libs/hwui \
    frameworks/base/libs/hwui \
    frameworks/av/media/libmedia \
    frameworks/av/media/libmedia \
    frameworks/av/media/libstagefright \
    frameworks/av/media/libstagefright \
    frameworks/av/media/libstagefright/codecs/amrnb/enc/src \
    frameworks/av/media/libstagefright/codecs/amrnb/common \
    frameworks/av/media/libstagefright/codecs/amrnb/common/include \
    frameworks/av/media/mtp \
    frameworks/av/media/mtp \
    frameworks/native/include/media/openmax \
    frameworks/native/include/media/openmax \
    $(call include-path-for, libhardware)/hardware \
    $(call include-path-for, libhardware)/hardware \
+0 −137
Original line number Original line Diff line number Diff line
/*
**
** 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 const 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));
}
+0 −6
Original line number Original line Diff line number Diff line
@@ -1106,7 +1106,6 @@ extern int register_android_media_MediaScanner(JNIEnv *env);
extern int register_android_media_MediaSync(JNIEnv *env);
extern int register_android_media_MediaSync(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
extern int register_android_media_AmrInputStream(JNIEnv *env);
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
@@ -1152,11 +1151,6 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
        goto bail;
        goto bail;
    }
    }


    if (register_android_media_AmrInputStream(env) < 0) {
        ALOGE("ERROR: AmrInputStream native registration failed\n");
        goto bail;
    }

    if (register_android_media_ResampleInputStream(env) < 0) {
    if (register_android_media_ResampleInputStream(env) < 0) {
        ALOGE("ERROR: ResampleInputStream native registration failed\n");
        ALOGE("ERROR: ResampleInputStream native registration failed\n");
        goto bail;
        goto bail;