Merge "Make AmrInputStream use MediaCodec"
This commit is contained in:
@@ -18,45 +18,69 @@ 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;
|
||||||
private int mBufOut = 0;
|
private int mBufOut = 0;
|
||||||
|
|
||||||
// helper for bytewise read()
|
// helper for bytewise read()
|
||||||
private byte[] mOneByte = new byte[1];
|
private byte[] mOneByte = new byte[1];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new AmrInputStream, which converts 16 bit PCM to AMR
|
* Create a new AmrInputStream, which converts 16 bit PCM to AMR
|
||||||
* @param inputStream InputStream containing 16 bit PCM.
|
* @param inputStream InputStream containing 16 bit PCM.
|
||||||
*/
|
*/
|
||||||
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
|
||||||
@@ -64,7 +88,7 @@ public final class AmrInputStream extends InputStream
|
|||||||
int rtn = read(mOneByte, 0, 1);
|
int rtn = read(mOneByte, 0, 1);
|
||||||
return rtn == 1 ? (0xff & mOneByte[0]) : -1;
|
return rtn == 1 ? (0xff & mOneByte[0]) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b) throws IOException {
|
public int read(byte[] b) throws IOException {
|
||||||
return read(b, 0, b.length);
|
return read(b, 0, b.length);
|
||||||
@@ -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) {
|
|
||||||
// reset the buffer
|
if (mBufOut >= mBufIn && !mSawOutputEOS) {
|
||||||
|
// no data left in buffer, refill it
|
||||||
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 */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now read encoded data from the encoder (blocking, since we just filled up the
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// encode it
|
|
||||||
mBufIn = GsmAmrEncoderEncode(mGae, mBuf, 0, mBuf, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return encoded audio to user
|
if (mBufOut < mBufIn) {
|
||||||
if (length > mBufIn - mBufOut) length = mBufIn - mBufOut;
|
// there is data in the buffer
|
||||||
System.arraycopy(mBuf, mBufOut, b, offset, length);
|
if (length > mBufIn - mBufOut) {
|
||||||
mBufOut += length;
|
length = mBufIn - mBufOut;
|
||||||
|
}
|
||||||
return length;
|
System.arraycopy(mBuf, mBufOut, b, offset, length);
|
||||||
|
mBufOut += 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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
/*
|
|
||||||
**
|
|
||||||
** 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));
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user