Home | History | Annotate | Download | only in jni
      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/JNIHelp.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         jniThrowException(env, "java/lang/IllegalStateException",
    234                           "Failed to stop the muxer");
    235         return;
    236     }
    237 }
    238 
    239 static void android_media_MediaMuxer_native_release(
    240         JNIEnv* /* env */, jclass clazz, jlong nativeObject) {
    241     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
    242     if (muxer != NULL) {
    243         muxer->decStrong(clazz);
    244     }
    245 }
    246 
    247 static const JNINativeMethod gMethods[] = {
    248 
    249     { "nativeAddTrack", "(J[Ljava/lang/String;[Ljava/lang/Object;)I",
    250         (void *)android_media_MediaMuxer_addTrack },
    251 
    252     { "nativeSetOrientationHint", "(JI)V",
    253         (void *)android_media_MediaMuxer_setOrientationHint},
    254 
    255     { "nativeSetLocation", "(JII)V",
    256         (void *)android_media_MediaMuxer_setLocation},
    257 
    258     { "nativeStart", "(J)V", (void *)android_media_MediaMuxer_start},
    259 
    260     { "nativeWriteSampleData", "(JILjava/nio/ByteBuffer;IIJI)V",
    261         (void *)android_media_MediaMuxer_writeSampleData },
    262 
    263     { "nativeStop", "(J)V", (void *)android_media_MediaMuxer_stop},
    264 
    265     { "nativeSetup", "(Ljava/io/FileDescriptor;I)J",
    266         (void *)android_media_MediaMuxer_native_setup },
    267 
    268     { "nativeRelease", "(J)V",
    269         (void *)android_media_MediaMuxer_native_release },
    270 
    271 };
    272 
    273 // This function only registers the native methods, and is called from
    274 // JNI_OnLoad in android_media_MediaPlayer.cpp
    275 int register_android_media_MediaMuxer(JNIEnv *env) {
    276     int err = AndroidRuntime::registerNativeMethods(env,
    277                 "android/media/MediaMuxer", gMethods, NELEM(gMethods));
    278 
    279     jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
    280     CHECK(byteBufClass != NULL);
    281 
    282     gFields.arrayID =
    283         env->GetMethodID(byteBufClass, "array", "()[B");
    284     CHECK(gFields.arrayID != NULL);
    285 
    286     return err;
    287 }
    288