summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt14
-rw-r--r--media/java/android/media/MediaMuxer.java288
-rw-r--r--media/jni/Android.mk1
-rw-r--r--media/jni/android_media_MediaMuxer.cpp233
-rw-r--r--media/jni/android_media_MediaPlayer.cpp6
5 files changed, 542 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt
index 385d9ac37957..b3dde6b8ffe6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -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;
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
new file mode 100644
index 000000000000..dfd0e9476354
--- /dev/null
+++ b/media/java/android/media/MediaMuxer.java
@@ -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;
+ }
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 6aaab2203869..ac8fb743ca76 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -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 \
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
new file mode 100644
index 000000000000..30ebb00a0ad5
--- /dev/null
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -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;
+}
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 8768e96d2ef3..e9f6a1ee8859 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -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;