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_Utils.h"
22 #include "android_runtime/AndroidRuntime.h"
23 #include "jni.h"
24 #include "JNIHelp.h"
25 
26 #include <media/stagefright/foundation/ABuffer.h>
27 #include <media/stagefright/foundation/ADebug.h>
28 #include <media/stagefright/foundation/AMessage.h>
29 #include <media/stagefright/MediaMuxer.h>
30 
31 namespace android {
32 
33 struct fields_t {
34     jmethodID arrayID;
35 };
36 
37 static fields_t gFields;
38 
39 }
40 
41 using namespace android;
42 
android_media_MediaMuxer_addTrack(JNIEnv * env,jclass,jlong nativeObject,jobjectArray keys,jobjectArray values)43 static jint android_media_MediaMuxer_addTrack(
44         JNIEnv *env, jclass /* clazz */, jlong nativeObject, jobjectArray keys,
45         jobjectArray values) {
46     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
47     if (muxer == NULL) {
48         jniThrowException(env, "java/lang/IllegalStateException",
49                           "Muxer was not set up correctly");
50         return -1;
51     }
52 
53     sp<AMessage> trackformat;
54     status_t err = ConvertKeyValueArraysToMessage(env, keys, values,
55                                                   &trackformat);
56     if (err != OK) {
57         jniThrowException(env, "java/lang/IllegalArgumentException",
58                           "ConvertKeyValueArraysToMessage got an error");
59         return err;
60     }
61 
62     // Return negative value when errors happen in addTrack.
63     jint trackIndex = muxer->addTrack(trackformat);
64 
65     if (trackIndex < 0) {
66         jniThrowException(env, "java/lang/IllegalStateException",
67                           "Failed to add the track to the muxer");
68         return -1;
69     }
70     return trackIndex;
71 }
72 
android_media_MediaMuxer_writeSampleData(JNIEnv * env,jclass,jlong nativeObject,jint trackIndex,jobject byteBuf,jint offset,jint size,jlong timeUs,jint flags)73 static void android_media_MediaMuxer_writeSampleData(
74         JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint trackIndex,
75         jobject byteBuf, jint offset, jint size, jlong timeUs, jint flags) {
76     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
77     if (muxer == NULL) {
78         jniThrowException(env, "java/lang/IllegalStateException",
79                           "Muxer was not set up correctly");
80         return;
81     }
82 
83     // Try to convert the incoming byteBuffer into ABuffer
84     void *dst = env->GetDirectBufferAddress(byteBuf);
85 
86     jlong dstSize;
87     jbyteArray byteArray = NULL;
88 
89     if (dst == NULL) {
90 
91         byteArray =
92             (jbyteArray)env->CallObjectMethod(byteBuf, gFields.arrayID);
93 
94         if (byteArray == NULL) {
95             jniThrowException(env, "java/lang/IllegalArgumentException",
96                               "byteArray is null");
97             return;
98         }
99 
100         jboolean isCopy;
101         dst = env->GetByteArrayElements(byteArray, &isCopy);
102 
103         dstSize = env->GetArrayLength(byteArray);
104     } else {
105         dstSize = env->GetDirectBufferCapacity(byteBuf);
106     }
107 
108     if (dstSize < (offset + size)) {
109         ALOGE("writeSampleData saw wrong dstSize %lld, size  %d, offset %d",
110               (long long)dstSize, size, offset);
111         if (byteArray != NULL) {
112             env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
113         }
114         jniThrowException(env, "java/lang/IllegalArgumentException",
115                           "sample has a wrong size");
116         return;
117     }
118 
119     sp<ABuffer> buffer = new ABuffer((char *)dst + offset, size);
120 
121     status_t err = muxer->writeSampleData(buffer, trackIndex, timeUs, flags);
122 
123     if (byteArray != NULL) {
124         env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
125     }
126 
127     if (err != OK) {
128         jniThrowException(env, "java/lang/IllegalStateException",
129                           "writeSampleData returned an error");
130     }
131     return;
132 }
133 
134 // Constructor counterpart.
android_media_MediaMuxer_native_setup(JNIEnv * env,jclass clazz,jobject fileDescriptor,jint format)135 static jlong android_media_MediaMuxer_native_setup(
136         JNIEnv *env, jclass clazz, jobject fileDescriptor,
137         jint format) {
138     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
139     ALOGV("native_setup: fd %d", fd);
140 
141     MediaMuxer::OutputFormat fileFormat =
142         static_cast<MediaMuxer::OutputFormat>(format);
143     sp<MediaMuxer> muxer = new MediaMuxer(fd, fileFormat);
144     muxer->incStrong(clazz);
145     return reinterpret_cast<jlong>(muxer.get());
146 }
147 
android_media_MediaMuxer_setOrientationHint(JNIEnv * env,jclass,jlong nativeObject,jint degrees)148 static void android_media_MediaMuxer_setOrientationHint(
149         JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint degrees) {
150     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
151     if (muxer == NULL) {
152         jniThrowException(env, "java/lang/IllegalStateException",
153                           "Muxer was not set up correctly");
154         return;
155     }
156     status_t err = muxer->setOrientationHint(degrees);
157 
158     if (err != OK) {
159         jniThrowException(env, "java/lang/IllegalStateException",
160                           "Failed to set orientation hint");
161         return;
162     }
163 
164 }
165 
android_media_MediaMuxer_setLocation(JNIEnv * env,jclass,jlong nativeObject,jint latitude,jint longitude)166 static void android_media_MediaMuxer_setLocation(
167         JNIEnv *env, jclass /* clazz */, jlong nativeObject, jint latitude, jint longitude) {
168     MediaMuxer* muxer = reinterpret_cast<MediaMuxer *>(nativeObject);
169 
170     status_t res = muxer->setLocation(latitude, longitude);
171     if (res != OK) {
172         jniThrowException(env, "java/lang/IllegalStateException",
173                           "Failed to set location");
174         return;
175     }
176 }
177 
android_media_MediaMuxer_start(JNIEnv * env,jclass,jlong nativeObject)178 static void android_media_MediaMuxer_start(JNIEnv *env, jclass /* clazz */,
179                                            jlong nativeObject) {
180     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
181     if (muxer == NULL) {
182         jniThrowException(env, "java/lang/IllegalStateException",
183                           "Muxer was not set up correctly");
184         return;
185     }
186     status_t err = muxer->start();
187 
188     if (err != OK) {
189         jniThrowException(env, "java/lang/IllegalStateException",
190                           "Failed to start the muxer");
191         return;
192     }
193 
194 }
195 
android_media_MediaMuxer_stop(JNIEnv * env,jclass,jlong nativeObject)196 static void android_media_MediaMuxer_stop(JNIEnv *env, jclass /* clazz */,
197                                           jlong nativeObject) {
198     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
199     if (muxer == NULL) {
200         jniThrowException(env, "java/lang/IllegalStateException",
201                           "Muxer was not set up correctly");
202         return;
203     }
204 
205     status_t err = muxer->stop();
206 
207     if (err != OK) {
208         jniThrowException(env, "java/lang/IllegalStateException",
209                           "Failed to stop the muxer");
210         return;
211     }
212 }
213 
android_media_MediaMuxer_native_release(JNIEnv *,jclass clazz,jlong nativeObject)214 static void android_media_MediaMuxer_native_release(
215         JNIEnv* /* env */, jclass clazz, jlong nativeObject) {
216     sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject));
217     if (muxer != NULL) {
218         muxer->decStrong(clazz);
219     }
220 }
221 
222 static JNINativeMethod gMethods[] = {
223 
224     { "nativeAddTrack", "(J[Ljava/lang/String;[Ljava/lang/Object;)I",
225         (void *)android_media_MediaMuxer_addTrack },
226 
227     { "nativeSetOrientationHint", "(JI)V",
228         (void *)android_media_MediaMuxer_setOrientationHint},
229 
230     { "nativeSetLocation", "(JII)V",
231         (void *)android_media_MediaMuxer_setLocation},
232 
233     { "nativeStart", "(J)V", (void *)android_media_MediaMuxer_start},
234 
235     { "nativeWriteSampleData", "(JILjava/nio/ByteBuffer;IIJI)V",
236         (void *)android_media_MediaMuxer_writeSampleData },
237 
238     { "nativeStop", "(J)V", (void *)android_media_MediaMuxer_stop},
239 
240     { "nativeSetup", "(Ljava/io/FileDescriptor;I)J",
241         (void *)android_media_MediaMuxer_native_setup },
242 
243     { "nativeRelease", "(J)V",
244         (void *)android_media_MediaMuxer_native_release },
245 
246 };
247 
248 // This function only registers the native methods, and is called from
249 // JNI_OnLoad in android_media_MediaPlayer.cpp
register_android_media_MediaMuxer(JNIEnv * env)250 int register_android_media_MediaMuxer(JNIEnv *env) {
251     int err = AndroidRuntime::registerNativeMethods(env,
252                 "android/media/MediaMuxer", gMethods, NELEM(gMethods));
253 
254     jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
255     CHECK(byteBufClass != NULL);
256 
257     gFields.arrayID =
258         env->GetMethodID(byteBufClass, "array", "()[B");
259     CHECK(gFields.arrayID != NULL);
260 
261     return err;
262 }
263