blob: a4807843d7d8eab45e9852e019e9eff44f02ca57 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2**
3** Copyright 2008, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18//#define LOG_NDEBUG 0
19#define LOG_TAG "MediaMetadataRetrieverJNI"
20
Jooyung Hancb1e8962019-02-21 14:18:11 +090021#include <cmath>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022#include <assert.h>
23#include <utils/Log.h>
24#include <utils/threads.h>
Derek Sollenbergereece0dd2014-02-27 14:31:29 -050025#include <SkBitmap.h>
Andreas Huberd2506a52014-01-29 10:32:46 -080026#include <media/IMediaHTTPService.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027#include <media/mediametadataretriever.h>
Elliott Hughes95d3f862014-06-10 16:53:31 -070028#include <media/mediascanner.h>
Chong Zhanga89f6e12018-03-07 16:22:18 -080029#include <nativehelper/ScopedLocalRef.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030#include <private/media/VideoFrame.h>
31
32#include "jni.h"
Steven Moreland2279b252017-07-19 09:50:45 -070033#include <nativehelper/JNIHelp.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034#include "android_runtime/AndroidRuntime.h"
Chris Watkins4eaa2932015-03-20 10:31:42 -070035#include "android_media_MediaDataSource.h"
Jooyung Hancb1e8962019-02-21 14:18:11 +090036#include "android_media_Streams.h"
Andreas Huberd2506a52014-01-29 10:32:46 -080037#include "android_util_Binder.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
Mike Reed021a3442014-08-07 17:03:17 -040039#include "android/graphics/GraphicsJNI.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
41using namespace android;
42
43struct fields_t {
44 jfieldID context;
James Dongc371a022011-04-06 12:16:07 -070045 jclass bitmapClazz; // Must be a global ref
James Dong53ebc722010-11-08 16:04:27 -080046 jmethodID createBitmapMethod;
James Dong9f2cde32011-03-18 17:55:06 -070047 jmethodID createScaledBitmapMethod;
James Dongc371a022011-04-06 12:16:07 -070048 jclass configClazz; // Must be a global ref
James Dong0e4b5352010-12-19 13:05:33 -080049 jmethodID createConfigMethod;
Chong Zhanga89f6e12018-03-07 16:22:18 -080050 jclass bitmapParamsClazz; // Must be a global ref
51 jfieldID inPreferredConfig;
52 jfieldID outActualConfig;
53 jclass arrayListClazz; // Must be a global ref
54 jmethodID arrayListInit;
55 jmethodID arrayListAdd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056};
57
58static fields_t fields;
59static Mutex sLock;
Marco Nelissen4935d052009-08-03 11:12:58 -070060static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061
62static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
63{
64 if (opStatus == (status_t) INVALID_OPERATION) {
65 jniThrowException(env, "java/lang/IllegalStateException", NULL);
66 } else if (opStatus != (status_t) OK) {
67 if (strlen(message) > 230) {
68 // If the message is too long, don't bother displaying the status code.
69 jniThrowException( env, exception, message);
70 } else {
71 char msg[256];
72 // Append the status code to the message.
73 sprintf(msg, "%s: status = 0x%X", message, opStatus);
74 jniThrowException( env, exception, msg);
75 }
76 }
77}
78
Marco Nelissen463ec6b2018-01-10 14:00:29 -080079static sp<MediaMetadataRetriever> getRetriever(JNIEnv* env, jobject thiz)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080{
81 // No lock is needed, since it is called internally by other methods that are protected
Ashok Bhat075e9a12014-01-06 13:45:09 +000082 MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetLongField(thiz, fields.context);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 return retriever;
84}
85
Marco Nelissen463ec6b2018-01-10 14:00:29 -080086static void setRetriever(JNIEnv* env, jobject thiz, const sp<MediaMetadataRetriever> &retriever)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087{
88 // No lock is needed, since it is called internally by other methods that are protected
Marco Nelissen463ec6b2018-01-10 14:00:29 -080089
90 if (retriever != NULL) {
91 retriever->incStrong(thiz);
92 }
93 sp<MediaMetadataRetriever> old = getRetriever(env, thiz);
94 if (old != NULL) {
95 old->decStrong(thiz);
96 }
97
98 env->SetLongField(thiz, fields.context, (jlong) retriever.get());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099}
100
Andreas Huber5b7ced62011-03-21 10:25:44 -0700101static void
102android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
Andreas Huberd2506a52014-01-29 10:32:46 -0800103 JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
James Dong17524dc2011-05-04 13:41:58 -0700104 jobjectArray keys, jobjectArray values) {
105
Steve Block71f2cf12011-10-20 11:56:00 +0100106 ALOGV("setDataSource");
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800107 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 if (retriever == 0) {
Andreas Huber5b7ced62011-03-21 10:25:44 -0700109 jniThrowException(
110 env,
111 "java/lang/IllegalStateException", "No retriever available");
112
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 return;
114 }
115
Andreas Huber5b7ced62011-03-21 10:25:44 -0700116 if (!path) {
117 jniThrowException(
118 env, "java/lang/IllegalArgumentException", "Null pointer");
119
120 return;
121 }
122
123 const char *tmp = env->GetStringUTFChars(path, NULL);
Andreas Huber5bb357f2011-03-21 11:55:01 -0700124 if (!tmp) { // OutOfMemoryError exception already thrown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 return;
126 }
127
Andreas Huber5bb357f2011-03-21 11:55:01 -0700128 String8 pathStr(tmp);
Andreas Huber5b7ced62011-03-21 10:25:44 -0700129 env->ReleaseStringUTFChars(path, tmp);
130 tmp = NULL;
131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 // Don't let somebody trick us in to reading some random block of memory
Andreas Huber5b7ced62011-03-21 10:25:44 -0700133 if (strncmp("mem://", pathStr.string(), 6) == 0) {
134 jniThrowException(
135 env, "java/lang/IllegalArgumentException", "Invalid pathname");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 return;
137 }
138
Andreas Huber5b7ced62011-03-21 10:25:44 -0700139 // We build a similar KeyedVector out of it.
140 KeyedVector<String8, String8> headersVector;
James Dong79f407c2011-05-05 12:50:04 -0700141 if (!ConvertKeyValueArraysToKeyedVector(
142 env, keys, values, &headersVector)) {
143 return;
Andreas Huber5b7ced62011-03-21 10:25:44 -0700144 }
Andreas Huberd2506a52014-01-29 10:32:46 -0800145
146 sp<IMediaHTTPService> httpService;
147 if (httpServiceBinderObj != NULL) {
148 sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
149 httpService = interface_cast<IMediaHTTPService>(binder);
150 }
151
Andreas Huber5b7ced62011-03-21 10:25:44 -0700152 process_media_retriever_call(
153 env,
154 retriever->setDataSource(
Andreas Huberd2506a52014-01-29 10:32:46 -0800155 httpService,
156 pathStr.string(),
157 headersVector.size() > 0 ? &headersVector : NULL),
Andreas Huber5b7ced62011-03-21 10:25:44 -0700158
159 "java/lang/RuntimeException",
160 "setDataSource failed");
161}
162
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
164{
Steve Block71f2cf12011-10-20 11:56:00 +0100165 ALOGV("setDataSource");
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800166 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 if (retriever == 0) {
168 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
169 return;
170 }
171 if (!fileDescriptor) {
172 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
173 return;
174 }
Elliott Hughesa3804cf2011-04-11 16:50:19 -0700175 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176 if (offset < 0 || length < 0 || fd < 0) {
177 if (offset < 0) {
Ashok Bhat075e9a12014-01-06 13:45:09 +0000178 ALOGE("negative offset (%lld)", (long long)offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 }
180 if (length < 0) {
Ashok Bhat075e9a12014-01-06 13:45:09 +0000181 ALOGE("negative length (%lld)", (long long)length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 }
183 if (fd < 0) {
Steve Block3762c312012-01-06 19:20:56 +0000184 ALOGE("invalid file descriptor");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 }
186 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
187 return;
188 }
189 process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
190}
191
Chris Watkins4eaa2932015-03-20 10:31:42 -0700192static void android_media_MediaMetadataRetriever_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource)
193{
194 ALOGV("setDataSourceCallback");
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800195 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
Chris Watkins4eaa2932015-03-20 10:31:42 -0700196 if (retriever == 0) {
197 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
198 return;
199 }
200 if (dataSource == NULL) {
201 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
202 return;
203 }
204
205 sp<IDataSource> callbackDataSource = new JMediaDataSource(env, dataSource);
206 process_media_retriever_call(env, retriever->setDataSource(callbackDataSource), "java/lang/RuntimeException", "setDataSourceCallback failed");
207}
208
Carl Shapiroae12a502011-01-20 23:13:09 -0800209template<typename T>
210static void rotate0(T* dst, const T* src, size_t width, size_t height)
211{
212 memcpy(dst, src, width * height * sizeof(T));
213}
214
215template<typename T>
216static void rotate90(T* dst, const T* src, size_t width, size_t height)
217{
218 for (size_t i = 0; i < height; ++i) {
219 for (size_t j = 0; j < width; ++j) {
220 dst[j * height + height - 1 - i] = src[i * width + j];
221 }
222 }
223}
224
225template<typename T>
226static void rotate180(T* dst, const T* src, size_t width, size_t height)
227{
228 for (size_t i = 0; i < height; ++i) {
229 for (size_t j = 0; j < width; ++j) {
230 dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
231 }
232 }
233}
234
235template<typename T>
236static void rotate270(T* dst, const T* src, size_t width, size_t height)
237{
238 for (size_t i = 0; i < height; ++i) {
239 for (size_t j = 0; j < width; ++j) {
240 dst[(width - 1 - j) * height + i] = src[i * width + j];
241 }
242 }
243}
244
245template<typename T>
246static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
247{
248 switch (angle) {
249 case 0:
250 rotate0(dst, src, width, height);
251 break;
252 case 90:
253 rotate90(dst, src, width, height);
254 break;
255 case 180:
256 rotate180(dst, src, width, height);
257 break;
258 case 270:
259 rotate270(dst, src, width, height);
260 break;
261 }
262}
263
Chong Zhang4342f082017-10-05 14:27:23 -0700264static jobject getBitmapFromVideoFrame(
Chong Zhanga89f6e12018-03-07 16:22:18 -0800265 JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height,
266 SkColorType outColorType) {
Chong Zhang7d127142018-04-09 14:43:01 -0700267 ALOGV("getBitmapFromVideoFrame: dimension = %dx%d, displaySize = %dx%d, bytes = %d",
268 videoFrame->mWidth,
269 videoFrame->mHeight,
James Dong0e4b5352010-12-19 13:05:33 -0800270 videoFrame->mDisplayWidth,
271 videoFrame->mDisplayHeight,
272 videoFrame->mSize);
273
Chong Zhanga89f6e12018-03-07 16:22:18 -0800274 ScopedLocalRef<jobject> config(env,
275 env->CallStaticObjectMethod(
276 fields.configClazz,
277 fields.createConfigMethod,
278 GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType)));
James Dong0e4b5352010-12-19 13:05:33 -0800279
Hangyu Kuang05520362017-07-21 15:01:23 -0700280 uint32_t width, height, displayWidth, displayHeight;
James Dong9f2cde32011-03-18 17:55:06 -0700281 bool swapWidthAndHeight = false;
Carl Shapiroae12a502011-01-20 23:13:09 -0800282 if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
James Dong9f2cde32011-03-18 17:55:06 -0700283 width = videoFrame->mHeight;
284 height = videoFrame->mWidth;
285 swapWidthAndHeight = true;
Hangyu Kuang05520362017-07-21 15:01:23 -0700286 displayWidth = videoFrame->mDisplayHeight;
287 displayHeight = videoFrame->mDisplayWidth;
Carl Shapiroae12a502011-01-20 23:13:09 -0800288 } else {
James Dong9f2cde32011-03-18 17:55:06 -0700289 width = videoFrame->mWidth;
290 height = videoFrame->mHeight;
Hangyu Kuang05520362017-07-21 15:01:23 -0700291 displayWidth = videoFrame->mDisplayWidth;
292 displayHeight = videoFrame->mDisplayHeight;
Carl Shapiroae12a502011-01-20 23:13:09 -0800293 }
294
James Dong0e4b5352010-12-19 13:05:33 -0800295 jobject jBitmap = env->CallStaticObjectMethod(
296 fields.bitmapClazz,
297 fields.createBitmapMethod,
Carl Shapiroae12a502011-01-20 23:13:09 -0800298 width,
299 height,
Chong Zhanga89f6e12018-03-07 16:22:18 -0800300 config.get());
wang, biaoc847a482012-10-30 15:35:01 +0800301 if (jBitmap == NULL) {
302 if (env->ExceptionCheck()) {
303 env->ExceptionClear();
304 }
Chong Zhang4342f082017-10-05 14:27:23 -0700305 ALOGE("getBitmapFromVideoFrame: create Bitmap failed!");
wang, biaoc847a482012-10-30 15:35:01 +0800306 return NULL;
307 }
Carl Shapiroae12a502011-01-20 23:13:09 -0800308
John Recked207b92015-04-10 13:52:57 -0700309 SkBitmap bitmap;
310 GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap);
James Dong0e4b5352010-12-19 13:05:33 -0800311
Chong Zhanga89f6e12018-03-07 16:22:18 -0800312 if (outColorType == kRGB_565_SkColorType) {
313 rotate((uint16_t*)bitmap.getPixels(),
314 (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
315 videoFrame->mWidth,
316 videoFrame->mHeight,
317 videoFrame->mRotationAngle);
318 } else {
319 rotate((uint32_t*)bitmap.getPixels(),
320 (uint32_t*)((char*)videoFrame + sizeof(VideoFrame)),
321 videoFrame->mWidth,
322 videoFrame->mHeight,
323 videoFrame->mRotationAngle);
324 }
James Dong0e4b5352010-12-19 13:05:33 -0800325
Hangyu Kuang05520362017-07-21 15:01:23 -0700326 if (dst_width <= 0 || dst_height <= 0) {
327 dst_width = displayWidth;
328 dst_height = displayHeight;
329 } else {
330 float factor = std::min((float)dst_width / (float)displayWidth,
331 (float)dst_height / (float)displayHeight);
332 dst_width = std::round(displayWidth * factor);
333 dst_height = std::round(displayHeight * factor);
334 }
335
Chong Zhang7d127142018-04-09 14:43:01 -0700336 if ((uint32_t)dst_width != width || (uint32_t)dst_height != height) {
Steve Block71f2cf12011-10-20 11:56:00 +0100337 ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
Hangyu Kuang05520362017-07-21 15:01:23 -0700338 width, height, dst_width, dst_height);
James Dong9f2cde32011-03-18 17:55:06 -0700339 jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
Hangyu Kuang05520362017-07-21 15:01:23 -0700340 fields.createScaledBitmapMethod,
341 jBitmap,
342 dst_width,
343 dst_height,
344 true);
Chong Zhanga89f6e12018-03-07 16:22:18 -0800345
346 env->DeleteLocalRef(jBitmap);
James Dong9f2cde32011-03-18 17:55:06 -0700347 return scaledBitmap;
348 }
349
James Dong0e4b5352010-12-19 13:05:33 -0800350 return jBitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351}
352
Chong Zhanga89f6e12018-03-07 16:22:18 -0800353static int getColorFormat(JNIEnv *env, jobject options) {
354 if (options == NULL) {
355 return HAL_PIXEL_FORMAT_RGBA_8888;
356 }
357
358 ScopedLocalRef<jobject> inConfig(env, env->GetObjectField(options, fields.inPreferredConfig));
359 SkColorType prefColorType = GraphicsJNI::getNativeBitmapColorType(env, inConfig.get());
360
361 if (prefColorType == kRGB_565_SkColorType) {
362 return HAL_PIXEL_FORMAT_RGB_565;
363 }
364 return HAL_PIXEL_FORMAT_RGBA_8888;
365}
366
367static SkColorType setOutColorType(JNIEnv *env, int colorFormat, jobject options) {
368 SkColorType outColorType = kN32_SkColorType;
369 if (colorFormat == HAL_PIXEL_FORMAT_RGB_565) {
370 outColorType = kRGB_565_SkColorType;
371 }
372
373 if (options != NULL) {
374 ScopedLocalRef<jobject> config(env,
375 env->CallStaticObjectMethod(
376 fields.configClazz,
377 fields.createConfigMethod,
378 GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType)));
379
380 env->SetObjectField(options, fields.outActualConfig, config.get());
381 }
382 return outColorType;
383}
384
Chong Zhang4342f082017-10-05 14:27:23 -0700385static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
386 JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
387{
388 ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
389 (long long)timeUs, option, dst_width, dst_height);
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800390 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
Chong Zhang4342f082017-10-05 14:27:23 -0700391 if (retriever == 0) {
392 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
393 return NULL;
394 }
395
396 // Call native method to retrieve a video frame
397 VideoFrame *videoFrame = NULL;
398 sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
399 if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
400 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
401 }
402 if (videoFrame == NULL) {
403 ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
404 return NULL;
405 }
406
Chong Zhanga89f6e12018-03-07 16:22:18 -0800407 return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, kRGB_565_SkColorType);
Chong Zhang4342f082017-10-05 14:27:23 -0700408}
409
410static jobject android_media_MediaMetadataRetriever_getImageAtIndex(
Chong Zhanga89f6e12018-03-07 16:22:18 -0800411 JNIEnv *env, jobject thiz, jint index, jobject params)
Chong Zhang4342f082017-10-05 14:27:23 -0700412{
413 ALOGV("getImageAtIndex: index %d", index);
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800414 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
Chong Zhang4342f082017-10-05 14:27:23 -0700415 if (retriever == 0) {
416 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
417 return NULL;
418 }
419
Chong Zhanga89f6e12018-03-07 16:22:18 -0800420 int colorFormat = getColorFormat(env, params);
421
Chong Zhang4342f082017-10-05 14:27:23 -0700422 // Call native method to retrieve an image
423 VideoFrame *videoFrame = NULL;
Chong Zhanga89f6e12018-03-07 16:22:18 -0800424 sp<IMemory> frameMemory = retriever->getImageAtIndex(index, colorFormat);
Chong Zhang4342f082017-10-05 14:27:23 -0700425 if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
426 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
427 }
428 if (videoFrame == NULL) {
429 ALOGE("getImageAtIndex: videoFrame is a NULL pointer");
430 return NULL;
431 }
432
Chong Zhanga89f6e12018-03-07 16:22:18 -0800433 SkColorType outColorType = setOutColorType(env, colorFormat, params);
434
435 return getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
Chong Zhang4342f082017-10-05 14:27:23 -0700436}
437
Chong Zhang7d127142018-04-09 14:43:01 -0700438static jobject android_media_MediaMetadataRetriever_getThumbnailImageAtIndex(
439 JNIEnv *env, jobject thiz, jint index, jobject params, jint targetSize, jint maxPixels)
440{
441 ALOGV("getThumbnailImageAtIndex: index %d", index);
442
443 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
444 if (retriever == 0) {
445 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
446 return NULL;
447 }
448
449 int colorFormat = getColorFormat(env, params);
450 jint dst_width = -1, dst_height = -1;
451
452 // Call native method to retrieve an image
453 VideoFrame *videoFrame = NULL;
454 sp<IMemory> frameMemory = retriever->getImageAtIndex(
455 index, colorFormat, true /*metaOnly*/, true /*thumbnail*/);
456 if (frameMemory != 0) {
457 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
458 int32_t thumbWidth = videoFrame->mWidth;
459 int32_t thumbHeight = videoFrame->mHeight;
460 videoFrame = NULL;
461 int64_t thumbPixels = thumbWidth * thumbHeight;
462
463 // Here we try to use the included thumbnail if it's not too shabby.
464 // If this fails ThumbnailUtils would have to decode the full image and
465 // downscale which could take long.
466 if (thumbWidth >= targetSize || thumbHeight >= targetSize
467 || thumbPixels * 6 >= maxPixels) {
468 frameMemory = retriever->getImageAtIndex(
469 index, colorFormat, false /*metaOnly*/, true /*thumbnail*/);
470 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
471
472 if (thumbPixels > maxPixels) {
473 int downscale = ceil(sqrt(thumbPixels / (float)maxPixels));
474 dst_width = thumbWidth / downscale;
475 dst_height = thumbHeight /downscale;
476 }
477 }
478 }
479 if (videoFrame == NULL) {
480 ALOGV("getThumbnailImageAtIndex: no suitable thumbnails available");
481 return NULL;
482 }
483
484 // Ignore rotation for thumbnail extraction to be consistent with
485 // thumbnails extracted by BitmapFactory APIs.
486 videoFrame->mRotationAngle = 0;
487
488 SkColorType outColorType = setOutColorType(env, colorFormat, params);
489
490 return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, outColorType);
491}
492
Chong Zhanga89f6e12018-03-07 16:22:18 -0800493static jobject android_media_MediaMetadataRetriever_getFrameAtIndex(
494 JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames, jobject params)
Chong Zhang4342f082017-10-05 14:27:23 -0700495{
496 ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames);
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800497 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
Chong Zhang4342f082017-10-05 14:27:23 -0700498 if (retriever == 0) {
499 jniThrowException(env,
500 "java/lang/IllegalStateException", "No retriever available");
501 return NULL;
502 }
503
Chong Zhanga89f6e12018-03-07 16:22:18 -0800504 int colorFormat = getColorFormat(env, params);
505
Chong Zhang4342f082017-10-05 14:27:23 -0700506 std::vector<sp<IMemory> > frames;
Chong Zhanga89f6e12018-03-07 16:22:18 -0800507 status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames, colorFormat);
Chong Zhang4342f082017-10-05 14:27:23 -0700508 if (err != OK || frames.size() == 0) {
Chong Zhang6c851292018-03-14 18:56:56 -0700509 jniThrowException(env,
510 "java/lang/IllegalStateException", "No frames from retriever");
Chong Zhang4342f082017-10-05 14:27:23 -0700511 return NULL;
512 }
Chong Zhanga89f6e12018-03-07 16:22:18 -0800513 jobject arrayList = env->NewObject(fields.arrayListClazz, fields.arrayListInit);
514 if (arrayList == NULL) {
Chong Zhang6c851292018-03-14 18:56:56 -0700515 jniThrowException(env,
516 "java/lang/IllegalStateException", "Can't create bitmap array");
Chong Zhang4342f082017-10-05 14:27:23 -0700517 return NULL;
518 }
519
Chong Zhanga89f6e12018-03-07 16:22:18 -0800520 SkColorType outColorType = setOutColorType(env, colorFormat, params);
521
Chong Zhang4342f082017-10-05 14:27:23 -0700522 for (size_t i = 0; i < frames.size(); i++) {
523 if (frames[i] == NULL || frames[i]->pointer() == NULL) {
524 ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i);
525 continue;
526 }
527 VideoFrame *videoFrame = static_cast<VideoFrame *>(frames[i]->pointer());
Chong Zhanga89f6e12018-03-07 16:22:18 -0800528 jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
529 env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj);
530 env->DeleteLocalRef(bitmapObj);
Chong Zhang4342f082017-10-05 14:27:23 -0700531 }
Chong Zhanga89f6e12018-03-07 16:22:18 -0800532 return arrayList;
Chong Zhang4342f082017-10-05 14:27:23 -0700533}
534
James Dongdf9b3492011-01-04 15:03:48 -0800535static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
536 JNIEnv *env, jobject thiz, jint pictureType)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537{
Steve Block71f2cf12011-10-20 11:56:00 +0100538 ALOGV("getEmbeddedPicture: %d", pictureType);
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800539 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800540 if (retriever == 0) {
541 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
542 return NULL;
543 }
544 MediaAlbumArt* mediaAlbumArt = NULL;
James Dongdf9b3492011-01-04 15:03:48 -0800545
546 // FIXME:
547 // Use pictureType to retrieve the intended embedded picture and also change
548 // the method name to getEmbeddedPicture().
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
550 if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object
551 mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
552 }
553 if (mediaAlbumArt == NULL) {
Steve Block3762c312012-01-06 19:20:56 +0000554 ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 return NULL;
556 }
557
Elliott Hughes95d3f862014-06-10 16:53:31 -0700558 jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 if (!array) { // OutOfMemoryError exception has already been thrown.
Steve Block3762c312012-01-06 19:20:56 +0000560 ALOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 } else {
Elliott Hughes95d3f862014-06-10 16:53:31 -0700562 const jbyte* data =
563 reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
564 env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 }
566
567 // No need to delete mediaAlbumArt here
568 return array;
569}
570
571static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
572{
Steve Block71f2cf12011-10-20 11:56:00 +0100573 ALOGV("extractMetadata");
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800574 sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 if (retriever == 0) {
576 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
577 return NULL;
578 }
579 const char* value = retriever->extractMetadata(keyCode);
580 if (!value) {
Steve Block71f2cf12011-10-20 11:56:00 +0100581 ALOGV("extractMetadata: Metadata is not found");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 return NULL;
583 }
Steve Block71f2cf12011-10-20 11:56:00 +0100584 ALOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 return env->NewStringUTF(value);
586}
587
588static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
589{
Steve Block71f2cf12011-10-20 11:56:00 +0100590 ALOGV("release");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 Mutex::Autolock lock(sLock);
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800592 setRetriever(env, thiz, NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593}
594
595static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
596{
Steve Block71f2cf12011-10-20 11:56:00 +0100597 ALOGV("native_finalize");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800598 // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
599 android_media_MediaMetadataRetriever_release(env, thiz);
600}
601
Marco Nelissen4935d052009-08-03 11:12:58 -0700602// This function gets a field ID, which in turn causes class initialization.
603// It is called from a static block in MediaMetadataRetriever, which won't run until the
604// first time an instance of this class is used.
605static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
606{
Chong Zhanga89f6e12018-03-07 16:22:18 -0800607 ScopedLocalRef<jclass> clazz(env, env->FindClass(kClassPathName));
608 if (clazz.get() == NULL) {
Marco Nelissen4935d052009-08-03 11:12:58 -0700609 return;
610 }
611
Chong Zhanga89f6e12018-03-07 16:22:18 -0800612 fields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
Marco Nelissen4935d052009-08-03 11:12:58 -0700613 if (fields.context == NULL) {
Marco Nelissen4935d052009-08-03 11:12:58 -0700614 return;
615 }
616
Chong Zhanga89f6e12018-03-07 16:22:18 -0800617 clazz.reset(env->FindClass("android/graphics/Bitmap"));
618 if (clazz.get() == NULL) {
Brian Carlstrom46e18c112011-04-05 22:44:45 -0700619 return;
620 }
Chong Zhanga89f6e12018-03-07 16:22:18 -0800621 fields.bitmapClazz = (jclass) env->NewGlobalRef(clazz.get());
Marco Nelissen4935d052009-08-03 11:12:58 -0700622 if (fields.bitmapClazz == NULL) {
Marco Nelissen4935d052009-08-03 11:12:58 -0700623 return;
624 }
James Dong0e4b5352010-12-19 13:05:33 -0800625 fields.createBitmapMethod =
626 env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
627 "(IILandroid/graphics/Bitmap$Config;)"
628 "Landroid/graphics/Bitmap;");
629 if (fields.createBitmapMethod == NULL) {
James Dong0e4b5352010-12-19 13:05:33 -0800630 return;
631 }
James Dong9f2cde32011-03-18 17:55:06 -0700632 fields.createScaledBitmapMethod =
633 env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap",
634 "(Landroid/graphics/Bitmap;IIZ)"
635 "Landroid/graphics/Bitmap;");
636 if (fields.createScaledBitmapMethod == NULL) {
James Dong9f2cde32011-03-18 17:55:06 -0700637 return;
638 }
James Dong0e4b5352010-12-19 13:05:33 -0800639
Chong Zhanga89f6e12018-03-07 16:22:18 -0800640 clazz.reset(env->FindClass("android/graphics/Bitmap$Config"));
641 if (clazz.get() == NULL) {
Brian Carlstrom46e18c112011-04-05 22:44:45 -0700642 return;
643 }
Chong Zhanga89f6e12018-03-07 16:22:18 -0800644 fields.configClazz = (jclass) env->NewGlobalRef(clazz.get());
James Dong0e4b5352010-12-19 13:05:33 -0800645 if (fields.configClazz == NULL) {
James Dong0e4b5352010-12-19 13:05:33 -0800646 return;
647 }
648 fields.createConfigMethod =
649 env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
650 "(I)Landroid/graphics/Bitmap$Config;");
651 if (fields.createConfigMethod == NULL) {
James Dong0e4b5352010-12-19 13:05:33 -0800652 return;
653 }
Chong Zhanga89f6e12018-03-07 16:22:18 -0800654
655 clazz.reset(env->FindClass("android/media/MediaMetadataRetriever$BitmapParams"));
656 if (clazz.get() == NULL) {
657 return;
658 }
659 fields.bitmapParamsClazz = (jclass) env->NewGlobalRef(clazz.get());
660 if (fields.bitmapParamsClazz == NULL) {
661 return;
662 }
663 fields.inPreferredConfig = env->GetFieldID(fields.bitmapParamsClazz,
664 "inPreferredConfig", "Landroid/graphics/Bitmap$Config;");
665 if (fields.inPreferredConfig == NULL) {
666 return;
667 }
668 fields.outActualConfig = env->GetFieldID(fields.bitmapParamsClazz,
669 "outActualConfig", "Landroid/graphics/Bitmap$Config;");
670 if (fields.outActualConfig == NULL) {
671 return;
672 }
673
674 clazz.reset(env->FindClass("java/util/ArrayList"));
675 if (clazz.get() == NULL) {
676 return;
677 }
678 fields.arrayListClazz = (jclass) env->NewGlobalRef(clazz.get());
679 if (fields.arrayListClazz == NULL) {
680 return;
681 }
682 fields.arrayListInit = env->GetMethodID(clazz.get(), "<init>", "()V");
683 if (fields.arrayListInit == NULL) {
684 return;
685 }
686 fields.arrayListAdd = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z");
687 if (fields.arrayListAdd == NULL) {
688 return;
689 }
Marco Nelissen4935d052009-08-03 11:12:58 -0700690}
691
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
693{
Steve Block71f2cf12011-10-20 11:56:00 +0100694 ALOGV("native_setup");
Marco Nelissen463ec6b2018-01-10 14:00:29 -0800695 sp<MediaMetadataRetriever> retriever = new MediaMetadataRetriever();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 if (retriever == 0) {
697 jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
698 return;
699 }
Ashok Bhat075e9a12014-01-06 13:45:09 +0000700 setRetriever(env, thiz, retriever);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701}
702
703// JNI mapping between Java methods and native methods
Daniel Micay76f6a862015-09-19 17:31:01 -0400704static const JNINativeMethod nativeMethods[] = {
James Dong17524dc2011-05-04 13:41:58 -0700705 {
706 "_setDataSource",
Andreas Huberd2506a52014-01-29 10:32:46 -0800707 "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
James Dong17524dc2011-05-04 13:41:58 -0700708 (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
709 },
710
Chong Zhanga89f6e12018-03-07 16:22:18 -0800711 {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
712 (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
713 {"_setDataSource", "(Landroid/media/MediaDataSource;)V",
714 (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
715 {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;",
716 (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
717 {
718 "_getImageAtIndex",
719 "(ILandroid/media/MediaMetadataRetriever$BitmapParams;)Landroid/graphics/Bitmap;",
720 (void *)android_media_MediaMetadataRetriever_getImageAtIndex
721 },
722
723 {
Chong Zhang7d127142018-04-09 14:43:01 -0700724 "getThumbnailImageAtIndex",
725 "(ILandroid/media/MediaMetadataRetriever$BitmapParams;II)Landroid/graphics/Bitmap;",
726 (void *)android_media_MediaMetadataRetriever_getThumbnailImageAtIndex
727 },
728
729 {
Chong Zhanga89f6e12018-03-07 16:22:18 -0800730 "_getFrameAtIndex",
731 "(IILandroid/media/MediaMetadataRetriever$BitmapParams;)Ljava/util/List;",
732 (void *)android_media_MediaMetadataRetriever_getFrameAtIndex
733 },
734
735 {"extractMetadata", "(I)Ljava/lang/String;",
736 (void *)android_media_MediaMetadataRetriever_extractMetadata},
737 {"getEmbeddedPicture", "(I)[B",
738 (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
739 {"release", "()V",
740 (void *)android_media_MediaMetadataRetriever_release},
741 {"native_finalize", "()V",
742 (void *)android_media_MediaMetadataRetriever_native_finalize},
743 {"native_setup", "()V",
744 (void *)android_media_MediaMetadataRetriever_native_setup},
745 {"native_init", "()V",
746 (void *)android_media_MediaMetadataRetriever_native_init},
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747};
748
Marco Nelissen4935d052009-08-03 11:12:58 -0700749// This function only registers the native methods, and is called from
750// JNI_OnLoad in android_media_MediaPlayer.cpp
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751int register_android_media_MediaMetadataRetriever(JNIEnv *env)
752{
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 return AndroidRuntime::registerNativeMethods
Marco Nelissen4935d052009-08-03 11:12:58 -0700754 (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755}