1 /* 2 * Copyright 2013, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 //#define LOG_NDEBUG 0 18 #define LOG_TAG "MediaMuxer-JNI" 19 #include <utils/Log.h> 20 21 #include "android_media_Streams.h" 22 #include "android_runtime/AndroidRuntime.h" 23 #include "jni.h" 24 #include <nativehelper/JNIPlatformHelp.h> 25 26 #include <unistd.h> 27 #include <fcntl.h> 28 29 #include <media/stagefright/foundation/ABuffer.h> 30 #include <media/stagefright/foundation/ADebug.h> 31 #include <media/stagefright/foundation/AMessage.h> 32 #include <media/stagefright/MediaMuxer.h> 33 34 namespace android { 35 36 struct fields_t { 37 jmethodID arrayID; 38 }; 39 40 static fields_t gFields; 41 42 } 43 44 using namespace android; 45 46 static jint android_media_MediaMuxer_addTrack( 47 JNIEnv *env, jclass /* clazz */, jlong nativeObject, jobjectArray keys, 48 jobjectArray values) { 49 sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); 50 if (muxer == NULL) { 51 jniThrowException(env, "java/lang/IllegalStateException", 52 "Muxer was not set up correctly"); 53 return -1; 54 } 55 56 sp<AMessage> trackformat; 57 status_t err = ConvertKeyValueArraysToMessage(env, keys, values, 58 &trackformat); 59 if (err != OK) { 60 jniThrowException(env, "java/lang/IllegalArgumentException", 61 "ConvertKeyValueArraysToMessage got an error"); 62 return err; 63 } 64 65 // Return negative value when errors happen in addTrack. 66 jint trackIndex = muxer->addTrack(trackformat); 67 68 if (trackIndex < 0) { 69 jniThrowException(env, "java/lang/IllegalStateException", 70 "Failed to add the track to the muxer"); 71 return -1; 72 } 73 return trackIndex; 74 } 75 76 static void android_media_MediaMuxer_writeSampleData( 77 JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint trackIndex, 78 jobject byteBuf, jint offset, jint size, jlong timeUs, jint flags) { 79 sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); 80 if (muxer == NULL) { 81 jniThrowException(env, "java/lang/IllegalStateException", 82 "Muxer was not set up correctly"); 83 return; 84 } 85 86 // Try to convert the incoming byteBuffer into ABuffer 87 void *dst = env->GetDirectBufferAddress(byteBuf); 88 89 jlong dstSize; 90 jbyteArray byteArray = NULL; 91 92 if (dst == NULL) { 93 94 byteArray = 95 (jbyteArray)env->CallObjectMethod(byteBuf, gFields.arrayID); 96 97 if (byteArray == NULL) { 98 jniThrowException(env, "java/lang/IllegalArgumentException", 99 "byteArray is null"); 100 return; 101 } 102 103 jboolean isCopy; 104 dst = env->GetByteArrayElements(byteArray, &isCopy); 105 106 dstSize = env->GetArrayLength(byteArray); 107 } else { 108 dstSize = env->GetDirectBufferCapacity(byteBuf); 109 } 110 111 if (dstSize < (offset + size)) { 112 ALOGE("writeSampleData saw wrong dstSize %lld, size %d, offset %d", 113 (long long)dstSize, size, offset); 114 if (byteArray != NULL) { 115 env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0); 116 } 117 jniThrowException(env, "java/lang/IllegalArgumentException", 118 "sample has a wrong size"); 119 return; 120 } 121 122 sp<ABuffer> buffer = new ABuffer((char *)dst + offset, size); 123 124 status_t err = muxer->writeSampleData(buffer, trackIndex, timeUs, flags); 125 126 if (byteArray != NULL) { 127 env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0); 128 } 129 130 if (err != OK) { 131 jniThrowException(env, "java/lang/IllegalStateException", 132 "writeSampleData returned an error"); 133 } 134 return; 135 } 136 137 // Constructor counterpart. 138 static jlong android_media_MediaMuxer_native_setup( 139 JNIEnv *env, jclass clazz, jobject fileDescriptor, 140 jint format) { 141 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); 142 ALOGV("native_setup: fd %d", fd); 143 144 // It appears that if an invalid file descriptor is passed through 145 // binder calls, the server-side of the inter-process function call 146 // is skipped. As a result, the check at the server-side to catch 147 // the invalid file descritpor never gets invoked. This is to workaround 148 // this issue by checking the file descriptor first before passing 149 // it through binder call. 150 int flags = fcntl(fd, F_GETFL); 151 if (flags == -1) { 152 ALOGE("Fail to get File Status Flags err: %s", strerror(errno)); 153 jniThrowException(env, "java/lang/IllegalArgumentException", 154 "Invalid file descriptor"); 155 return 0; 156 } 157 158 // fd must be in read-write mode or write-only mode. 159 if ((flags & (O_RDWR | O_WRONLY)) == 0) { 160 ALOGE("File descriptor is not in read-write mode or write-only mode"); 161 jniThrowException(env, "java/io/IOException", 162 "File descriptor is not in read-write mode or write-only mode"); 163 return 0; 164 } 165 166 MediaMuxer::OutputFormat fileFormat = 167 static_cast<MediaMuxer::OutputFormat>(format); 168 sp<MediaMuxer> muxer = new MediaMuxer(fd, fileFormat); 169 muxer->incStrong(clazz); 170 return reinterpret_cast<jlong>(muxer.get()); 171 } 172 173 static void android_media_MediaMuxer_setOrientationHint( 174 JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint degrees) { 175 sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); 176 if (muxer == NULL) { 177 jniThrowException(env, "java/lang/IllegalStateException", 178 "Muxer was not set up correctly"); 179 return; 180 } 181 status_t err = muxer->setOrientationHint(degrees); 182 183 if (err != OK) { 184 jniThrowException(env, "java/lang/IllegalStateException", 185 "Failed to set orientation hint"); 186 return; 187 } 188 189 } 190 191 static void android_media_MediaMuxer_setLocation( 192 JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint latitude, jint longitude) { 193 MediaMuxer* muxer = reinterpret_cast<MediaMuxer *>(nativeObject); 194 195 status_t res = muxer->setLocation(latitude, longitude); 196 if (res != OK) { 197 jniThrowException(env, "java/lang/IllegalStateException", 198 "Failed to set location"); 199 return; 200 } 201 } 202 203 static void android_media_MediaMuxer_start(JNIEnv *env, jclass /* clazz */, 204 jlong nativeObject) { 205 sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); 206 if (muxer == NULL) { 207 jniThrowException(env, "java/lang/IllegalStateException", 208 "Muxer was not set up correctly"); 209 return; 210 } 211 status_t err = muxer->start(); 212 213 if (err != OK) { 214 jniThrowException(env, "java/lang/IllegalStateException", 215 "Failed to start the muxer"); 216 return; 217 } 218 219 } 220 221 static void android_media_MediaMuxer_stop(JNIEnv *env, jclass /* clazz */, 222 jlong nativeObject) { 223 sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); 224 if (muxer == NULL) { 225 jniThrowException(env, "java/lang/IllegalStateException", 226 "Muxer was not set up correctly"); 227 return; 228 } 229 230 status_t err = muxer->stop(); 231 232 if (err != OK) { 233 ALOGE("Error during stop:%d", err); 234 jniThrowException(env, "java/lang/IllegalStateException", 235 "Error during stop(), muxer would have stopped already"); 236 return; 237 } 238 } 239 240 static void android_media_MediaMuxer_native_release( 241 JNIEnv* /* env */, jclass clazz, jlong nativeObject) { 242 sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); 243 if (muxer != NULL) { 244 muxer->decStrong(clazz); 245 } 246 } 247 248 static const JNINativeMethod gMethods[] = { 249 250 { "nativeAddTrack", "(J[Ljava/lang/String;[Ljava/lang/Object;)I", 251 (void *)android_media_MediaMuxer_addTrack }, 252 253 { "nativeSetOrientationHint", "(JI)V", 254 (void *)android_media_MediaMuxer_setOrientationHint}, 255 256 { "nativeSetLocation", "(JII)V", 257 (void *)android_media_MediaMuxer_setLocation}, 258 259 { "nativeStart", "(J)V", (void *)android_media_MediaMuxer_start}, 260 261 { "nativeWriteSampleData", "(JILjava/nio/ByteBuffer;IIJI)V", 262 (void *)android_media_MediaMuxer_writeSampleData }, 263 264 { "nativeStop", "(J)V", (void *)android_media_MediaMuxer_stop}, 265 266 { "nativeSetup", "(Ljava/io/FileDescriptor;I)J", 267 (void *)android_media_MediaMuxer_native_setup }, 268 269 { "nativeRelease", "(J)V", 270 (void *)android_media_MediaMuxer_native_release }, 271 272 }; 273 274 // This function only registers the native methods, and is called from 275 // JNI_OnLoad in android_media_MediaPlayer.cpp 276 int register_android_media_MediaMuxer(JNIEnv *env) { 277 int err = AndroidRuntime::registerNativeMethods(env, 278 "android/media/MediaMuxer", gMethods, NELEM(gMethods)); 279 280 jclass byteBufClass = env->FindClass("java/nio/ByteBuffer"); 281 CHECK(byteBufClass != NULL); 282 283 gFields.arrayID = 284 env->GetMethodID(byteBufClass, "array", "()[B"); 285 CHECK(gFields.arrayID != NULL); 286 287 return err; 288 } 289