1 /*
2 * Copyright 2012, 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 "MediaCodec-JNI"
19 #include <utils/Log.h>
20
21 #include <media/stagefright/foundation/ADebug.h>
22 #include <media/stagefright/foundation/AMessage.h>
23 #include <media/stagefright/MediaCodecList.h>
24 #include <media/IMediaCodecList.h>
25 #include <media/MediaCodecInfo.h>
26
27 #include <utils/Vector.h>
28
29 #include <vector>
30
31 #include "android_runtime/AndroidRuntime.h"
32 #include "jni.h"
33 #include <nativehelper/JNIHelp.h>
34 #include "android_media_Streams.h"
35
36 using namespace android;
37
38 /**
39 * This object unwraps codec aliases into individual codec infos as the Java interface handles
40 * aliases in this way.
41 */
42 class JavaMediaCodecListWrapper {
43 public:
44 struct Info {
45 sp<MediaCodecInfo> info;
46 AString alias;
47 };
48
getCodecInfo(size_t index) const49 const Info getCodecInfo(size_t index) const {
50 if (index < mInfoList.size()) {
51 return mInfoList[index];
52 }
53 // return
54 return Info { nullptr /* info */, "(none)" /* alias */ };
55 }
56
countCodecs() const57 size_t countCodecs() const {
58 return mInfoList.size();
59 }
60
getCodecList() const61 sp<IMediaCodecList> getCodecList() const {
62 return mCodecList;
63 }
64
findCodecByName(AString name) const65 size_t findCodecByName(AString name) const {
66 auto it = mInfoIndex.find(name);
67 return it == mInfoIndex.end() ? -ENOENT : it->second;
68 }
69
JavaMediaCodecListWrapper(sp<IMediaCodecList> mcl)70 JavaMediaCodecListWrapper(sp<IMediaCodecList> mcl)
71 : mCodecList(mcl) {
72 size_t numCodecs = mcl->countCodecs();
73 for (size_t ix = 0; ix < numCodecs; ++ix) {
74 sp<MediaCodecInfo> info = mcl->getCodecInfo(ix);
75 Vector<AString> namesAndAliases;
76 info->getAliases(&namesAndAliases);
77 namesAndAliases.insertAt(0);
78 namesAndAliases.editItemAt(0) = info->getCodecName();
79 for (const AString &nameOrAlias : namesAndAliases) {
80 if (mInfoIndex.count(nameOrAlias) > 0) {
81 // skip duplicate names or aliases
82 continue;
83 }
84 mInfoIndex.emplace(nameOrAlias, mInfoList.size());
85 mInfoList.emplace_back(Info { info, nameOrAlias });
86 }
87 }
88 }
89
90 private:
91 sp<IMediaCodecList> mCodecList;
92 std::vector<Info> mInfoList;
93 std::map<AString, size_t> mInfoIndex;
94 };
95
96 static std::mutex sMutex;
97 static std::unique_ptr<JavaMediaCodecListWrapper> sListWrapper;
98
getCodecList(JNIEnv * env)99 static const JavaMediaCodecListWrapper *getCodecList(JNIEnv *env) {
100 std::lock_guard<std::mutex> lock(sMutex);
101 if (sListWrapper == nullptr) {
102 sp<IMediaCodecList> mcl = MediaCodecList::getInstance();
103 if (mcl == NULL) {
104 // This should never happen unless something is really wrong
105 jniThrowException(
106 env, "java/lang/RuntimeException", "cannot get MediaCodecList");
107 }
108
109 sListWrapper.reset(new JavaMediaCodecListWrapper(mcl));
110 }
111 return sListWrapper.get();
112 }
113
getCodecInfo(JNIEnv * env,jint index)114 static JavaMediaCodecListWrapper::Info getCodecInfo(JNIEnv *env, jint index) {
115 const JavaMediaCodecListWrapper *mcl = getCodecList(env);
116 if (mcl == nullptr) {
117 // Runtime exception already pending.
118 return JavaMediaCodecListWrapper::Info { nullptr /* info */, "(none)" /* alias */ };
119 }
120
121 JavaMediaCodecListWrapper::Info info = mcl->getCodecInfo(index);
122 if (info.info == NULL) {
123 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
124 }
125
126 return info;
127 }
128
android_media_MediaCodecList_getCodecCount(JNIEnv * env,jobject)129 static jint android_media_MediaCodecList_getCodecCount(
130 JNIEnv *env, jobject /* thiz */) {
131 const JavaMediaCodecListWrapper *mcl = getCodecList(env);
132 if (mcl == NULL) {
133 // Runtime exception already pending.
134 return 0;
135 }
136
137 return mcl->countCodecs();
138 }
139
android_media_MediaCodecList_getCodecName(JNIEnv * env,jobject,jint index)140 static jstring android_media_MediaCodecList_getCodecName(
141 JNIEnv *env, jobject /* thiz */, jint index) {
142 JavaMediaCodecListWrapper::Info info = getCodecInfo(env, index);
143 if (info.info == NULL) {
144 // Runtime exception already pending.
145 return NULL;
146 }
147
148 const char *name = info.alias.c_str();
149 return env->NewStringUTF(name);
150 }
151
android_media_MediaCodecList_getCanonicalName(JNIEnv * env,jobject,jint index)152 static jstring android_media_MediaCodecList_getCanonicalName(
153 JNIEnv *env, jobject /* thiz */, jint index) {
154 JavaMediaCodecListWrapper::Info info = getCodecInfo(env, index);
155 if (info.info == NULL) {
156 // Runtime exception already pending.
157 return NULL;
158 }
159
160 const char *name = info.info->getCodecName();
161 return env->NewStringUTF(name);
162 }
163
android_media_MediaCodecList_findCodecByName(JNIEnv * env,jobject,jstring name)164 static jint android_media_MediaCodecList_findCodecByName(
165 JNIEnv *env, jobject /* thiz */, jstring name) {
166 if (name == NULL) {
167 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
168 return -ENOENT;
169 }
170
171 const char *nameStr = env->GetStringUTFChars(name, NULL);
172 if (nameStr == NULL) {
173 // Out of memory exception already pending.
174 return -ENOENT;
175 }
176
177 const JavaMediaCodecListWrapper *mcl = getCodecList(env);
178 if (mcl == NULL) {
179 // Runtime exception already pending.
180 env->ReleaseStringUTFChars(name, nameStr);
181 return -ENOENT;
182 }
183
184 jint ret = mcl->findCodecByName(nameStr);
185 env->ReleaseStringUTFChars(name, nameStr);
186 return ret;
187 }
188
android_media_MediaCodecList_getAttributes(JNIEnv * env,jobject,jint index)189 static jboolean android_media_MediaCodecList_getAttributes(
190 JNIEnv *env, jobject /* thiz */, jint index) {
191 JavaMediaCodecListWrapper::Info info = getCodecInfo(env, index);
192 if (info.info == NULL) {
193 // Runtime exception already pending.
194 return 0;
195 }
196
197 return info.info->getAttributes();
198 }
199
android_media_MediaCodecList_getSupportedTypes(JNIEnv * env,jobject,jint index)200 static jarray android_media_MediaCodecList_getSupportedTypes(
201 JNIEnv *env, jobject /* thiz */, jint index) {
202 JavaMediaCodecListWrapper::Info info = getCodecInfo(env, index);
203 if (info.info == NULL) {
204 // Runtime exception already pending.
205 return NULL;
206 }
207
208 Vector<AString> types;
209 info.info->getSupportedMediaTypes(&types);
210
211 jclass clazz = env->FindClass("java/lang/String");
212 CHECK(clazz != NULL);
213
214 jobjectArray array = env->NewObjectArray(types.size(), clazz, NULL);
215
216 for (size_t i = 0; i < types.size(); ++i) {
217 jstring obj = env->NewStringUTF(types.itemAt(i).c_str());
218 env->SetObjectArrayElement(array, i, obj);
219 env->DeleteLocalRef(obj);
220 obj = NULL;
221 }
222
223 return array;
224 }
225
android_media_MediaCodecList_getCodecCapabilities(JNIEnv * env,jobject,jint index,jstring type)226 static jobject android_media_MediaCodecList_getCodecCapabilities(
227 JNIEnv *env, jobject /* thiz */, jint index, jstring type) {
228 if (type == NULL) {
229 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
230 return NULL;
231 }
232
233 JavaMediaCodecListWrapper::Info info = getCodecInfo(env, index);
234 if (info.info == NULL) {
235 // Runtime exception already pending.
236 return NULL;
237 }
238
239
240 const char *typeStr = env->GetStringUTFChars(type, NULL);
241 if (typeStr == NULL) {
242 // Out of memory exception already pending.
243 return NULL;
244 }
245
246 Vector<MediaCodecInfo::ProfileLevel> profileLevels;
247 Vector<uint32_t> colorFormats;
248
249 sp<AMessage> defaultFormat = new AMessage();
250 defaultFormat->setString("mime", typeStr);
251
252 // TODO query default-format also from codec/codec list
253 const sp<MediaCodecInfo::Capabilities> &capabilities =
254 info.info->getCapabilitiesFor(typeStr);
255 env->ReleaseStringUTFChars(type, typeStr);
256 typeStr = NULL;
257 if (capabilities == NULL) {
258 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
259 return NULL;
260 }
261
262 capabilities->getSupportedColorFormats(&colorFormats);
263 capabilities->getSupportedProfileLevels(&profileLevels);
264 sp<AMessage> details = capabilities->getDetails();
265 bool isEncoder = info.info->isEncoder();
266
267 jobject defaultFormatObj = NULL;
268 if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
269 return NULL;
270 }
271
272 jobject infoObj = NULL;
273 if (ConvertMessageToMap(env, details, &infoObj)) {
274 env->DeleteLocalRef(defaultFormatObj);
275 return NULL;
276 }
277
278 jclass capsClazz =
279 env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
280 CHECK(capsClazz != NULL);
281
282 jclass profileLevelClazz =
283 env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
284 CHECK(profileLevelClazz != NULL);
285
286 jobjectArray profileLevelArray =
287 env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
288
289 jfieldID profileField =
290 env->GetFieldID(profileLevelClazz, "profile", "I");
291
292 jfieldID levelField =
293 env->GetFieldID(profileLevelClazz, "level", "I");
294
295 for (size_t i = 0; i < profileLevels.size(); ++i) {
296 const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
297
298 jobject profileLevelObj = env->AllocObject(profileLevelClazz);
299
300 env->SetIntField(profileLevelObj, profileField, src.mProfile);
301 env->SetIntField(profileLevelObj, levelField, src.mLevel);
302
303 env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
304
305 env->DeleteLocalRef(profileLevelObj);
306 profileLevelObj = NULL;
307 }
308
309 jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
310
311 for (size_t i = 0; i < colorFormats.size(); ++i) {
312 jint val = colorFormats.itemAt(i);
313 env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
314 }
315
316 jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
317 "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
318 "Ljava/util/Map;Ljava/util/Map;)V");
319
320 jobject caps = env->NewObject(capsClazz, capsConstructID,
321 profileLevelArray, colorFormatsArray, isEncoder,
322 defaultFormatObj, infoObj);
323
324 env->DeleteLocalRef(profileLevelArray);
325 profileLevelArray = NULL;
326
327 env->DeleteLocalRef(colorFormatsArray);
328 colorFormatsArray = NULL;
329
330 env->DeleteLocalRef(defaultFormatObj);
331 defaultFormatObj = NULL;
332
333 env->DeleteLocalRef(infoObj);
334 infoObj = NULL;
335
336 return caps;
337 }
338
android_media_MediaCodecList_getGlobalSettings(JNIEnv * env,jobject)339 static jobject android_media_MediaCodecList_getGlobalSettings(JNIEnv *env, jobject /* thiz */) {
340 const JavaMediaCodecListWrapper *mcl = getCodecList(env);
341 if (mcl == NULL) {
342 // Runtime exception already pending.
343 return NULL;
344 }
345
346 const sp<AMessage> settings = mcl->getCodecList()->getGlobalSettings();
347 if (settings == NULL) {
348 jniThrowException(env, "java/lang/RuntimeException", "cannot get global settings");
349 return NULL;
350 }
351
352 jobject settingsObj = NULL;
353 if (ConvertMessageToMap(env, settings, &settingsObj)) {
354 return NULL;
355 }
356
357 return settingsObj;
358 }
359
android_media_MediaCodecList_native_init(JNIEnv *)360 static void android_media_MediaCodecList_native_init(JNIEnv* /* env */) {
361 }
362
363 static const JNINativeMethod gMethods[] = {
364 { "native_getCodecCount", "()I", (void *)android_media_MediaCodecList_getCodecCount },
365
366 { "getCanonicalName", "(I)Ljava/lang/String;",
367 (void *)android_media_MediaCodecList_getCanonicalName },
368
369 { "getCodecName", "(I)Ljava/lang/String;",
370 (void *)android_media_MediaCodecList_getCodecName },
371
372 { "getAttributes", "(I)I", (void *)android_media_MediaCodecList_getAttributes },
373
374 { "getSupportedTypes", "(I)[Ljava/lang/String;",
375 (void *)android_media_MediaCodecList_getSupportedTypes },
376
377 { "getCodecCapabilities",
378 "(ILjava/lang/String;)Landroid/media/MediaCodecInfo$CodecCapabilities;",
379 (void *)android_media_MediaCodecList_getCodecCapabilities },
380
381 { "native_getGlobalSettings",
382 "()Ljava/util/Map;",
383 (void *)android_media_MediaCodecList_getGlobalSettings },
384
385 { "findCodecByName", "(Ljava/lang/String;)I",
386 (void *)android_media_MediaCodecList_findCodecByName },
387
388 { "native_init", "()V", (void *)android_media_MediaCodecList_native_init },
389 };
390
register_android_media_MediaCodecList(JNIEnv * env)391 int register_android_media_MediaCodecList(JNIEnv *env) {
392 return AndroidRuntime::registerNativeMethods(env,
393 "android/media/MediaCodecList", gMethods, NELEM(gMethods));
394 }
395
396