Merge "Java API for MediaMuxer" into jb-mr2-dev

This commit is contained in:
ztenghui
2013-03-11 21:45:19 +00:00
committed by Android (Google) Code Review
5 changed files with 542 additions and 0 deletions

View File

@@ -11584,6 +11584,20 @@ package android.media {
field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0
}
public final class MediaMuxer {
ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException;
method public int addTrack(android.media.MediaFormat);
method public void release();
method public void start();
method public void stop();
method public void writeSampleData(int, java.nio.ByteBuffer, android.media.MediaCodec.BufferInfo);
field public static final int SAMPLE_FLAG_SYNC = 1; // 0x1
}
public static final class MediaMuxer.OutputFormat {
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
}
public class MediaPlayer {
ctor public MediaPlayer();
method public void addTimedTextSource(java.lang.String, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;

View File

@@ -0,0 +1,288 @@
/*
* Copyright (C) 2013 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.
*/
package android.media;
import android.media.MediaCodec.BufferInfo;
import dalvik.system.CloseGuard;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
/**
* MediaMuxer facilitates muxing elementary streams. Currently only supports an
* mp4 file as the output and at most one audio and/or one video elementary
* stream.
* <p>
* It is generally used like this:
*
* <pre>
* MediaMuxer muxer = new MediaMuxer(...);
* MediaFormat audioFormat = new MediaFormat(...);
* MediaFormat videoFormat = new MediaFormat(...);
* int audioTrackIndex = muxer.addTrack(audioFormat);
* int videoTrackIndex = muxer.addTrack(videoFormat);
* ByteBuffer inputBuffer = ByteBuffer.allocate(...);
* muxer.start();
* while(inputBuffer has new data) {
* if (new data is audio sample) {
* muxer.writeSampleData(audioTrackIndex, inputBuffer, ...);
* } else if (new data is video sample) {
* muxer.writeSampleData(videoTrackIndex, inputBuffer, ...);
* }
* }
* muxer.stop();
* muxer.release();
* </pre>
*/
final public class MediaMuxer {
private int mNativeContext;
static {
System.loadLibrary("media_jni");
}
/**
* Defines the output format. These constants are used with constructor.
*/
public static final class OutputFormat {
/* Do not change these values without updating their counterparts
* in include/media/stagefright/MediaMuxer.h!
*/
private OutputFormat() {}
/** MPEG4 media file format*/
public static final int MUXER_OUTPUT_MPEG_4 = 0;
};
/**
* The sample is a sync sample, which does not require other video samples
* to decode. This flag is used in {@link #writeSampleData} to indicate
* which sample is a sync sample.
*/
/* Keep this flag in sync with its equivalent in
* include/media/stagefright/MediaMuxer.h.
*/
public static final int SAMPLE_FLAG_SYNC = 1;
// All the native functions are listed here.
private static native int nativeSetup(FileDescriptor fd, int format);
private static native void nativeRelease(int nativeObject);
private static native void nativeStart(int nativeObject);
private static native void nativeStop(int nativeObject);
private static native int nativeAddTrack(int nativeObject, String[] keys,
Object[] values);
private static native void nativeWriteSampleData(int nativeObject,
int trackIndex, ByteBuffer byteBuf,
int offset, int size, long presentationTimeUs, int flags);
// Muxer internal states.
private static final int MUXER_STATE_UNINITIALIZED = -1;
private static final int MUXER_STATE_INITIALIZED = 0;
private static final int MUXER_STATE_STARTED = 1;
private static final int MUXER_STATE_STOPPED = 2;
private int mState = MUXER_STATE_UNINITIALIZED;
private final CloseGuard mCloseGuard = CloseGuard.get();
private int mLastTrackIndex = -1;
private int mNativeObject;
/**
* Constructor
* Creates a media muxer that writes to the specified path.
* @param path The path of the output media file.
* @param format The format of the output media file.
* @see android.media.MediaMuxer.OutputFormat
* @throws IOException if failed to open the file for write
*/
public MediaMuxer(String path, int format) throws IOException {
if (path == null) {
throw new IllegalArgumentException("path must not be null");
}
if (format != OutputFormat.MUXER_OUTPUT_MPEG_4) {
throw new IllegalArgumentException("format is invalid");
}
FileOutputStream fos = null;
try {
File file = new File(path);
fos = new FileOutputStream(file);
FileDescriptor fd = fos.getFD();
mNativeObject = nativeSetup(fd, format);
mState = MUXER_STATE_INITIALIZED;
mCloseGuard.open("release");
} finally {
if (fos != null) {
fos.close();
}
}
}
/**
* Starts the muxer.
* <p>Make sure this is called after {@link #addTrack} and before
* {@link #writeSampleData}.</p>
*/
public void start() {
if (mNativeObject == 0) {
throw new IllegalStateException("Muxer has been released!");
}
if (mState == MUXER_STATE_INITIALIZED) {
nativeStart(mNativeObject);
mState = MUXER_STATE_STARTED;
} else {
throw new IllegalStateException("Can't start due to wrong state.");
}
}
/**
* Stops the muxer.
* <p>Once the muxer stops, it can not be restarted.</p>
*/
public void stop() {
if (mState == MUXER_STATE_STARTED) {
nativeStop(mNativeObject);
mState = MUXER_STATE_STOPPED;
} else {
throw new IllegalStateException("Can't stop due to wrong state.");
}
}
@Override
protected void finalize() throws Throwable {
try {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
mNativeObject = 0;
}
} finally {
super.finalize();
}
}
/**
* Adds a track with the specified format.
* @param format The media format for the track.
* @return The track index for this newly added track, and it should be used
* in the {@link #writeSampleData}.
*/
public int addTrack(MediaFormat format) {
if (format == null) {
throw new IllegalArgumentException("format must not be null.");
}
if (mState != MUXER_STATE_INITIALIZED) {
throw new IllegalStateException("Muxer is not initialized.");
}
if (mNativeObject == 0) {
throw new IllegalStateException("Muxer has been released!");
}
int trackIndex = -1;
// Convert the MediaFormat into key-value pairs and send to the native.
Map<String, Object> formatMap = format.getMap();
String[] keys = null;
Object[] values = null;
int mapSize = formatMap.size();
if (mapSize > 0) {
keys = new String[mapSize];
values = new Object[mapSize];
int i = 0;
for (Map.Entry<String, Object> entry : formatMap.entrySet()) {
keys[i] = entry.getKey();
values[i] = entry.getValue();
++i;
}
trackIndex = nativeAddTrack(mNativeObject, keys, values);
} else {
throw new IllegalArgumentException("format must not be empty.");
}
// Track index number is expected to incremented as addTrack succeed.
// However, if format is invalid, it will get a negative trackIndex.
if (mLastTrackIndex >= trackIndex) {
throw new IllegalArgumentException("Invalid format.");
}
mLastTrackIndex = trackIndex;
return trackIndex;
}
/**
* Writes an encoded sample into the muxer. The application needs to make
* sure that the samples are written into the right tracks.
* @param byteBuf The encoded sample.
* @param trackIndex The track index for this sample.
* @param bufferInfo The buffer information related to this sample.
*/
public void writeSampleData(int trackIndex, ByteBuffer byteBuf,
BufferInfo bufferInfo) {
if (trackIndex < 0 || trackIndex > mLastTrackIndex) {
throw new IllegalArgumentException("trackIndex is invalid");
}
if (byteBuf == null) {
throw new IllegalArgumentException("byteBuffer must not be null");
}
if (bufferInfo == null) {
throw new IllegalArgumentException("bufferInfo must not be null");
}
if (bufferInfo.size < 0 || bufferInfo.offset < 0
|| (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity()
|| bufferInfo.presentationTimeUs < 0) {
throw new IllegalArgumentException("bufferInfo must specify a" +
" valid buffer offset, size and presentation time");
}
if (mNativeObject == 0) {
throw new IllegalStateException("Muxer has been released!");
}
if (mState != MUXER_STATE_STARTED) {
throw new IllegalStateException("Can't write, muxer is not started");
}
nativeWriteSampleData(mNativeObject, trackIndex, byteBuf,
bufferInfo.offset, bufferInfo.size,
bufferInfo.presentationTimeUs, bufferInfo.flags);
}
/**
* Make sure you call this when you're done to free up any resources
* instead of relying on the garbage collector to do this for you at
* some point in the future.
*/
public void release() {
if (mState == MUXER_STATE_STARTED) {
throw new IllegalStateException("Can't release when muxer is started");
}
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
mNativeObject = 0;
mCloseGuard.close();
}
mState = MUXER_STATE_UNINITIALIZED;
}
}

View File

@@ -6,6 +6,7 @@ LOCAL_SRC_FILES:= \
android_media_MediaCodec.cpp \
android_media_MediaCodecList.cpp \
android_media_MediaExtractor.cpp \
android_media_MediaMuxer.cpp \
android_media_MediaPlayer.cpp \
android_media_MediaRecorder.cpp \
android_media_MediaScanner.cpp \

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2013, 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_NDEBUG 0
#define LOG_TAG "MediaMuxer-JNI"
#include <utils/Log.h>
#include "android_media_Utils.h"
#include "android_runtime/AndroidRuntime.h"
#include "jni.h"
#include "JNIHelp.h"
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaMuxer.h>
namespace android {
struct fields_t {
jfieldID context;
jmethodID arrayID;
};
static fields_t gFields;
}
using namespace android;
static jint android_media_MediaMuxer_addTrack(
JNIEnv *env, jclass clazz, jint nativeObject, jobjectArray keys,
jobjectArray values) {
sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
if (muxer == NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"Muxer was not set up correctly");
return -1;
}
sp<AMessage> trackformat;
status_t err = ConvertKeyValueArraysToMessage(env, keys, values,
&trackformat);
if (err != OK) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"ConvertKeyValueArraysToMessage got an error");
return err;
}
// Return negative value when errors happen in addTrack.
jint trackIndex = muxer->addTrack(trackformat);
if (trackIndex < 0) {
jniThrowException(env, "java/lang/IllegalStateException",
"Failed to add the track to the muxer");
return -1;
}
return trackIndex;
}
static void android_media_MediaMuxer_writeSampleData(
JNIEnv *env, jclass clazz, jint nativeObject, jint trackIndex,
jobject byteBuf, jint offset, jint size, jlong timeUs, jint flags) {
sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
if (muxer == NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"Muxer was not set up correctly");
return;
}
// Try to convert the incoming byteBuffer into ABuffer
void *dst = env->GetDirectBufferAddress(byteBuf);
jlong dstSize;
jbyteArray byteArray = NULL;
if (dst == NULL) {
byteArray =
(jbyteArray)env->CallObjectMethod(byteBuf, gFields.arrayID);
if (byteArray == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"byteArray is null");
return;
}
jboolean isCopy;
dst = env->GetByteArrayElements(byteArray, &isCopy);
dstSize = env->GetArrayLength(byteArray);
} else {
dstSize = env->GetDirectBufferCapacity(byteBuf);
}
if (dstSize < (offset + size)) {
ALOGE("writeSampleData saw wrong dstSize %lld, size %d, offset %d",
dstSize, size, offset);
if (byteArray != NULL) {
env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
}
jniThrowException(env, "java/lang/IllegalArgumentException",
"sample has a wrong size");
return;
}
sp<ABuffer> buffer = new ABuffer((char *)dst + offset, size);
status_t err = muxer->writeSampleData(buffer, trackIndex, timeUs, flags);
if (byteArray != NULL) {
env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
}
if (err != OK) {
jniThrowException(env, "java/lang/IllegalStateException",
"writeSampleData returned an error");
}
return;
}
// Constructor counterpart.
static jint android_media_MediaMuxer_native_setup(
JNIEnv *env, jclass clazz, jobject fileDescriptor,
jint format) {
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
ALOGV("native_setup: fd %d", fd);
MediaMuxer::OutputFormat fileFormat =
static_cast<MediaMuxer::OutputFormat>(format);
sp<MediaMuxer> muxer = new MediaMuxer(fd, fileFormat);
muxer->incStrong(clazz);
return int(muxer.get());
}
static void android_media_MediaMuxer_start(JNIEnv *env, jclass clazz,
jint nativeObject) {
sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
if (muxer == NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"Muxer was not set up correctly");
return;
}
status_t err = muxer->start();
if (err != OK) {
jniThrowException(env, "java/lang/IllegalStateException",
"Failed to start the muxer");
return;
}
}
static void android_media_MediaMuxer_stop(JNIEnv *env, jclass clazz,
jint nativeObject) {
sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
if (muxer == NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"Muxer was not set up correctly");
return;
}
status_t err = muxer->stop();
if (err != OK) {
jniThrowException(env, "java/lang/IllegalStateException",
"Failed to stop the muxer");
return;
}
}
static void android_media_MediaMuxer_native_release(
JNIEnv *env, jclass clazz, jint nativeObject) {
sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
if (muxer != NULL) {
muxer->decStrong(clazz);
}
}
static JNINativeMethod gMethods[] = {
{ "nativeAddTrack", "(I[Ljava/lang/String;[Ljava/lang/Object;)I",
(void *)android_media_MediaMuxer_addTrack },
{ "nativeStart", "(I)V", (void *)android_media_MediaMuxer_start},
{ "nativeWriteSampleData", "(IILjava/nio/ByteBuffer;IIJI)V",
(void *)android_media_MediaMuxer_writeSampleData },
{ "nativeStop", "(I)V", (void *)android_media_MediaMuxer_stop},
{ "nativeSetup", "(Ljava/io/FileDescriptor;I)I",
(void *)android_media_MediaMuxer_native_setup },
{ "nativeRelease", "(I)V",
(void *)android_media_MediaMuxer_native_release },
};
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaMuxer(JNIEnv *env) {
int err = AndroidRuntime::registerNativeMethods(env,
"android/media/MediaMuxer", gMethods, NELEM(gMethods));
jclass clazz = env->FindClass("android/media/MediaMuxer");
CHECK(clazz != NULL);
gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
CHECK(gFields.context != NULL);
jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
CHECK(byteBufClass != NULL);
gFields.arrayID =
env->GetMethodID(byteBufClass, "array", "()[B");
CHECK(gFields.arrayID != NULL);
return err;
}

View File

@@ -883,6 +883,7 @@ extern int register_android_media_MediaCodec(JNIEnv *env);
extern int register_android_media_MediaExtractor(JNIEnv *env);
extern int register_android_media_MediaCodecList(JNIEnv *env);
extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
extern int register_android_media_MediaMuxer(JNIEnv *env);
extern int register_android_media_MediaRecorder(JNIEnv *env);
extern int register_android_media_MediaScanner(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
@@ -963,6 +964,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
goto bail;
}
if (register_android_media_MediaMuxer(env) < 0) {
ALOGE("ERROR: MediaMuxer native registration failed");
goto bail;
}
if (register_android_media_MediaCodecList(env) < 0) {
ALOGE("ERROR: MediaCodec native registration failed");
goto bail;