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 
android_media_MediaMuxer_addTrack(JNIEnv * env,jclass,jlong nativeObject,jobjectArray keys,jobjectArray values)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 
android_media_MediaMuxer_writeSampleData(JNIEnv * env,jclass,jlong nativeObject,jint trackIndex,jobject byteBuf,jint offset,jint size,jlong timeUs,jint flags)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.
android_media_MediaMuxer_native_setup(JNIEnv * env,jclass clazz,jobject fileDescriptor,jint format)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 
android_media_MediaMuxer_setOrientationHint(JNIEnv * env,jclass,jlong nativeObject,jint degrees)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 
android_media_MediaMuxer_setLocation(JNIEnv * env,jclass,jlong nativeObject,jint latitude,jint longitude)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 
android_media_MediaMuxer_start(JNIEnv * env,jclass,jlong nativeObject)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 
android_media_MediaMuxer_stop(JNIEnv * env,jclass,jlong nativeObject)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 
android_media_MediaMuxer_native_release(JNIEnv *,jclass clazz,jlong nativeObject)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
register_android_media_MediaMuxer(JNIEnv * env)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