• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 = MediaMuxer::create(fd, fileFormat);
169     if (muxer == nullptr) {
170         jniThrowException(env, "java/lang/IllegalArgumentException", "Muxer creation failed");
171         return 0;
172     }
173     muxer->incStrong(clazz);
174     return reinterpret_cast<jlong>(muxer.get());
175 }
176 
android_media_MediaMuxer_setOrientationHint(JNIEnv * env,jclass,jlong nativeObject,jint degrees)177 static void android_media_MediaMuxer_setOrientationHint(
178         JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint degrees) {
179     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
180     if (muxer == NULL) {
181         jniThrowException(env, "java/lang/IllegalStateException",
182                           "Muxer was not set up correctly");
183         return;
184     }
185     status_t err = muxer->setOrientationHint(degrees);
186 
187     if (err != OK) {
188         jniThrowException(env, "java/lang/IllegalStateException",
189                           "Failed to set orientation hint");
190         return;
191     }
192 
193 }
194 
android_media_MediaMuxer_setLocation(JNIEnv * env,jclass,jlong nativeObject,jint latitude,jint longitude)195 static void android_media_MediaMuxer_setLocation(
196         JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint latitude, jint longitude) {
197     MediaMuxer* muxer = reinterpret_cast<MediaMuxer *>(nativeObject);
198 
199     status_t res = muxer->setLocation(latitude, longitude);
200     if (res != OK) {
201         jniThrowException(env, "java/lang/IllegalStateException",
202                           "Failed to set location");
203         return;
204     }
205 }
206 
android_media_MediaMuxer_start(JNIEnv * env,jclass,jlong nativeObject)207 static void android_media_MediaMuxer_start(JNIEnv *env, jclass /* clazz */,
208                                            jlong nativeObject) {
209     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
210     if (muxer == NULL) {
211         jniThrowException(env, "java/lang/IllegalStateException",
212                           "Muxer was not set up correctly");
213         return;
214     }
215     status_t err = muxer->start();
216 
217     if (err != OK) {
218         jniThrowException(env, "java/lang/IllegalStateException",
219                           "Failed to start the muxer");
220         return;
221     }
222 
223 }
224 
android_media_MediaMuxer_stop(JNIEnv * env,jclass,jlong nativeObject)225 static void android_media_MediaMuxer_stop(JNIEnv *env, jclass /* clazz */,
226                                           jlong nativeObject) {
227     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
228     if (muxer == NULL) {
229         jniThrowException(env, "java/lang/IllegalStateException",
230                           "Muxer was not set up correctly");
231         return;
232     }
233 
234     status_t err = muxer->stop();
235 
236     if (err != OK) {
237         ALOGE("Error during stop:%d", err);
238         jniThrowException(env, "java/lang/IllegalStateException",
239                     "Error during stop(), muxer would have stopped already");
240         return;
241     }
242 }
243 
android_media_MediaMuxer_native_release(JNIEnv *,jclass clazz,jlong nativeObject)244 static void android_media_MediaMuxer_native_release(
245         JNIEnv* /* env */, jclass clazz, jlong nativeObject) {
246     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
247     if (muxer != NULL) {
248         muxer->decStrong(clazz);
249     }
250 }
251 
252 static const JNINativeMethod gMethods[] = {
253 
254     { "nativeAddTrack", "(J[Ljava/lang/String;[Ljava/lang/Object;)I",
255         (void *)android_media_MediaMuxer_addTrack },
256 
257     { "nativeSetOrientationHint", "(JI)V",
258         (void *)android_media_MediaMuxer_setOrientationHint},
259 
260     { "nativeSetLocation", "(JII)V",
261         (void *)android_media_MediaMuxer_setLocation},
262 
263     { "nativeStart", "(J)V", (void *)android_media_MediaMuxer_start},
264 
265     { "nativeWriteSampleData", "(JILjava/nio/ByteBuffer;IIJI)V",
266         (void *)android_media_MediaMuxer_writeSampleData },
267 
268     { "nativeStop", "(J)V", (void *)android_media_MediaMuxer_stop},
269 
270     { "nativeSetup", "(Ljava/io/FileDescriptor;I)J",
271         (void *)android_media_MediaMuxer_native_setup },
272 
273     { "nativeRelease", "(J)V",
274         (void *)android_media_MediaMuxer_native_release },
275 
276 };
277 
278 // This function only registers the native methods, and is called from
279 // JNI_OnLoad in android_media_MediaPlayer.cpp
register_android_media_MediaMuxer(JNIEnv * env)280 int register_android_media_MediaMuxer(JNIEnv *env) {
281     int err = AndroidRuntime::registerNativeMethods(env,
282                 "android/media/MediaMuxer", gMethods, NELEM(gMethods));
283 
284     jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
285     CHECK(byteBufClass != NULL);
286 
287     gFields.arrayID =
288         env->GetMethodID(byteBufClass, "array", "()[B");
289     CHECK(gFields.arrayID != NULL);
290 
291     return err;
292 }
293