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

Commit 68ccf103 authored by ztenghui's avatar ztenghui
Browse files

Java API for MediaMuxer

Implement the Java API for MediaMuxer.

bug:7991013

Change-Id: Ibf3aa256cc1d89f1c7636b74c9fb53073d173636
parent a509b6b1
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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;
+288 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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 \
+233 −0
Original line number Diff line number Diff line
/*
 * 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;
}
+6 −0
Original line number Diff line number Diff line
@@ -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;