/* * 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 #include "android_media_Streams.h" #include "android_runtime/AndroidRuntime.h" #include "jni.h" #include #include #include #include #include #include #include namespace android { struct fields_t { jmethodID arrayID; }; static fields_t gFields; } using namespace android; static jint android_media_MediaMuxer_addTrack( JNIEnv *env, jclass /* clazz */, jlong nativeObject, jobjectArray keys, jobjectArray values) { sp muxer(reinterpret_cast(nativeObject)); if (muxer == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Muxer was not set up correctly"); return -1; } sp 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 */, jlong nativeObject, jint trackIndex, jobject byteBuf, jint offset, jint size, jlong timeUs, jint flags) { sp muxer(reinterpret_cast(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", (long long)dstSize, size, offset); if (byteArray != NULL) { env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0); } jniThrowException(env, "java/lang/IllegalArgumentException", "sample has a wrong size"); return; } sp 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 jlong android_media_MediaMuxer_native_setup( JNIEnv *env, jclass clazz, jobject fileDescriptor, jint format) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); ALOGV("native_setup: fd %d", fd); // It appears that if an invalid file descriptor is passed through // binder calls, the server-side of the inter-process function call // is skipped. As a result, the check at the server-side to catch // the invalid file descritpor never gets invoked. This is to workaround // this issue by checking the file descriptor first before passing // it through binder call. int flags = fcntl(fd, F_GETFL); if (flags == -1) { ALOGE("Fail to get File Status Flags err: %s", strerror(errno)); jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid file descriptor"); return 0; } // fd must be in read-write mode or write-only mode. if ((flags & (O_RDWR | O_WRONLY)) == 0) { ALOGE("File descriptor is not in read-write mode or write-only mode"); jniThrowException(env, "java/io/IOException", "File descriptor is not in read-write mode or write-only mode"); return 0; } MediaMuxer::OutputFormat fileFormat = static_cast(format); sp muxer = new MediaMuxer(fd, fileFormat); muxer->incStrong(clazz); return reinterpret_cast(muxer.get()); } static void android_media_MediaMuxer_setOrientationHint( JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint degrees) { sp muxer(reinterpret_cast(nativeObject)); if (muxer == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Muxer was not set up correctly"); return; } status_t err = muxer->setOrientationHint(degrees); if (err != OK) { jniThrowException(env, "java/lang/IllegalStateException", "Failed to set orientation hint"); return; } } static void android_media_MediaMuxer_setLocation( JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint latitude, jint longitude) { MediaMuxer* muxer = reinterpret_cast(nativeObject); status_t res = muxer->setLocation(latitude, longitude); if (res != OK) { jniThrowException(env, "java/lang/IllegalStateException", "Failed to set location"); return; } } static void android_media_MediaMuxer_start(JNIEnv *env, jclass /* clazz */, jlong nativeObject) { sp muxer(reinterpret_cast(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 */, jlong nativeObject) { sp muxer(reinterpret_cast(nativeObject)); if (muxer == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Muxer was not set up correctly"); return; } status_t err = muxer->stop(); if (err != OK) { ALOGE("Error during stop:%d", err); jniThrowException(env, "java/lang/IllegalStateException", "Error during stop(), muxer would have stopped already"); return; } } static void android_media_MediaMuxer_native_release( JNIEnv* /* env */, jclass clazz, jlong nativeObject) { sp muxer(reinterpret_cast(nativeObject)); if (muxer != NULL) { muxer->decStrong(clazz); } } static const JNINativeMethod gMethods[] = { { "nativeAddTrack", "(J[Ljava/lang/String;[Ljava/lang/Object;)I", (void *)android_media_MediaMuxer_addTrack }, { "nativeSetOrientationHint", "(JI)V", (void *)android_media_MediaMuxer_setOrientationHint}, { "nativeSetLocation", "(JII)V", (void *)android_media_MediaMuxer_setLocation}, { "nativeStart", "(J)V", (void *)android_media_MediaMuxer_start}, { "nativeWriteSampleData", "(JILjava/nio/ByteBuffer;IIJI)V", (void *)android_media_MediaMuxer_writeSampleData }, { "nativeStop", "(J)V", (void *)android_media_MediaMuxer_stop}, { "nativeSetup", "(Ljava/io/FileDescriptor;I)J", (void *)android_media_MediaMuxer_native_setup }, { "nativeRelease", "(J)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 byteBufClass = env->FindClass("java/nio/ByteBuffer"); CHECK(byteBufClass != NULL); gFields.arrayID = env->GetMethodID(byteBufClass, "array", "()[B"); CHECK(gFields.arrayID != NULL); return err; }