1 /*
2 * Copyright 2019, 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 "AndroidMediaStreams"
19
20 #include <utils/Log.h>
21 #include "android_media_Streams.h"
22
23 #include <media/stagefright/foundation/ADebug.h>
24 #include <media/stagefright/foundation/ABuffer.h>
25 #include <media/stagefright/foundation/AMessage.h>
26
27 #include <nativehelper/ScopedLocalRef.h>
28
29 namespace android {
30
FileStream(const int fd)31 FileStream::FileStream(const int fd)
32 : mPosition(0) {
33 mFile = fdopen(fd, "r");
34 if (mFile == NULL) {
35 return;
36 }
37 }
38
FileStream(const String8 filename)39 FileStream::FileStream(const String8 filename)
40 : mPosition(0) {
41 mFile = fopen(filename.string(), "r");
42 if (mFile == NULL) {
43 return;
44 }
45 }
46
~FileStream()47 FileStream::~FileStream() {
48 if (mFile != NULL) {
49 fclose(mFile);
50 mFile = NULL;
51 }
52 }
53
GetData(const size_t offset,const size_t length,std::uint8_t * data)54 piex::Error FileStream::GetData(
55 const size_t offset, const size_t length, std::uint8_t* data) {
56 if (mFile == NULL) {
57 return piex::Error::kFail;
58 }
59
60 // Seek first.
61 if (mPosition != offset) {
62 fseek(mFile, offset, SEEK_SET);
63 }
64
65 // Read bytes.
66 size_t size = fread((void*)data, sizeof(std::uint8_t), length, mFile);
67 mPosition += size;
68
69 // Handle errors and verify the size.
70 if (ferror(mFile) || size != length) {
71 ALOGV("GetData read failed: (offset: %zu, length: %zu)", offset, length);
72 return piex::Error::kFail;
73 }
74 return piex::Error::kOk;
75 }
76
exists() const77 bool FileStream::exists() const {
78 return mFile != NULL;
79 }
80
GetExifFromRawImage(piex::StreamInterface * stream,const String8 & filename,piex::PreviewImageData & image_data)81 bool GetExifFromRawImage(
82 piex::StreamInterface* stream, const String8& filename,
83 piex::PreviewImageData& image_data) {
84 // Reset the PreviewImageData to its default.
85 image_data = piex::PreviewImageData();
86
87 if (!piex::IsRaw(stream)) {
88 // Format not supported.
89 ALOGV("Format not supported: %s", filename.string());
90 return false;
91 }
92
93 piex::Error err = piex::GetPreviewImageData(stream, &image_data);
94
95 if (err != piex::Error::kOk) {
96 // The input data seems to be broken.
97 ALOGV("Raw image not detected: %s (piex error code: %d)", filename.string(), (int32_t)err);
98 return false;
99 }
100
101 return true;
102 }
103
ConvertKeyValueArraysToKeyedVector(JNIEnv * env,jobjectArray keys,jobjectArray values,KeyedVector<String8,String8> * keyedVector)104 bool ConvertKeyValueArraysToKeyedVector(
105 JNIEnv *env, jobjectArray keys, jobjectArray values,
106 KeyedVector<String8, String8>* keyedVector) {
107
108 int nKeyValuePairs = 0;
109 bool failed = false;
110 if (keys != NULL && values != NULL) {
111 nKeyValuePairs = env->GetArrayLength(keys);
112 failed = (nKeyValuePairs != env->GetArrayLength(values));
113 }
114
115 if (!failed) {
116 failed = ((keys != NULL && values == NULL) ||
117 (keys == NULL && values != NULL));
118 }
119
120 if (failed) {
121 ALOGE("keys and values arrays have different length");
122 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
123 return false;
124 }
125
126 for (int i = 0; i < nKeyValuePairs; ++i) {
127 // No need to check on the ArrayIndexOutOfBoundsException, since
128 // it won't happen here.
129 jstring key = (jstring) env->GetObjectArrayElement(keys, i);
130 jstring value = (jstring) env->GetObjectArrayElement(values, i);
131
132 const char* keyStr = env->GetStringUTFChars(key, NULL);
133 if (!keyStr) { // OutOfMemoryError
134 return false;
135 }
136
137 const char* valueStr = env->GetStringUTFChars(value, NULL);
138 if (!valueStr) { // OutOfMemoryError
139 env->ReleaseStringUTFChars(key, keyStr);
140 return false;
141 }
142
143 keyedVector->add(String8(keyStr), String8(valueStr));
144
145 env->ReleaseStringUTFChars(key, keyStr);
146 env->ReleaseStringUTFChars(value, valueStr);
147 env->DeleteLocalRef(key);
148 env->DeleteLocalRef(value);
149 }
150 return true;
151 }
152
makeIntegerObject(JNIEnv * env,int32_t value)153 static jobject makeIntegerObject(JNIEnv *env, int32_t value) {
154 ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Integer"));
155 CHECK(clazz.get() != NULL);
156
157 jmethodID integerConstructID =
158 env->GetMethodID(clazz.get(), "<init>", "(I)V");
159 CHECK(integerConstructID != NULL);
160
161 return env->NewObject(clazz.get(), integerConstructID, value);
162 }
163
makeLongObject(JNIEnv * env,int64_t value)164 static jobject makeLongObject(JNIEnv *env, int64_t value) {
165 ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Long"));
166 CHECK(clazz.get() != NULL);
167
168 jmethodID longConstructID = env->GetMethodID(clazz.get(), "<init>", "(J)V");
169 CHECK(longConstructID != NULL);
170
171 return env->NewObject(clazz.get(), longConstructID, value);
172 }
173
makeFloatObject(JNIEnv * env,float value)174 static jobject makeFloatObject(JNIEnv *env, float value) {
175 ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Float"));
176 CHECK(clazz.get() != NULL);
177
178 jmethodID floatConstructID =
179 env->GetMethodID(clazz.get(), "<init>", "(F)V");
180 CHECK(floatConstructID != NULL);
181
182 return env->NewObject(clazz.get(), floatConstructID, value);
183 }
184
makeByteBufferObject(JNIEnv * env,const void * data,size_t size)185 static jobject makeByteBufferObject(
186 JNIEnv *env, const void *data, size_t size) {
187 jbyteArray byteArrayObj = env->NewByteArray(size);
188 env->SetByteArrayRegion(byteArrayObj, 0, size, (const jbyte *)data);
189
190 ScopedLocalRef<jclass> clazz(env, env->FindClass("java/nio/ByteBuffer"));
191 CHECK(clazz.get() != NULL);
192
193 jmethodID byteBufWrapID =
194 env->GetStaticMethodID(
195 clazz.get(), "wrap", "([B)Ljava/nio/ByteBuffer;");
196 CHECK(byteBufWrapID != NULL);
197
198 jobject byteBufObj = env->CallStaticObjectMethod(
199 clazz.get(), byteBufWrapID, byteArrayObj);
200
201 env->DeleteLocalRef(byteArrayObj); byteArrayObj = NULL;
202
203 return byteBufObj;
204 }
205
SetMapInt32(JNIEnv * env,jobject hashMapObj,jmethodID hashMapPutID,const char * key,int32_t value)206 static void SetMapInt32(
207 JNIEnv *env, jobject hashMapObj, jmethodID hashMapPutID,
208 const char *key, int32_t value) {
209 jstring keyObj = env->NewStringUTF(key);
210 jobject valueObj = makeIntegerObject(env, value);
211
212 env->CallObjectMethod(hashMapObj, hashMapPutID, keyObj, valueObj);
213
214 env->DeleteLocalRef(valueObj); valueObj = NULL;
215 env->DeleteLocalRef(keyObj); keyObj = NULL;
216 }
217
ConvertMessageToMap(JNIEnv * env,const sp<AMessage> & msg,jobject * map)218 status_t ConvertMessageToMap(
219 JNIEnv *env, const sp<AMessage> &msg, jobject *map) {
220 ScopedLocalRef<jclass> hashMapClazz(
221 env, env->FindClass("java/util/HashMap"));
222
223 if (hashMapClazz.get() == NULL) {
224 return -EINVAL;
225 }
226
227 jmethodID hashMapConstructID =
228 env->GetMethodID(hashMapClazz.get(), "<init>", "()V");
229
230 if (hashMapConstructID == NULL) {
231 return -EINVAL;
232 }
233
234 jmethodID hashMapPutID =
235 env->GetMethodID(
236 hashMapClazz.get(),
237 "put",
238 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
239
240 if (hashMapPutID == NULL) {
241 return -EINVAL;
242 }
243
244 jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID);
245
246 for (size_t i = 0; i < msg->countEntries(); ++i) {
247 AMessage::Type valueType;
248 const char *key = msg->getEntryNameAt(i, &valueType);
249
250 if (!strncmp(key, "android._", 9)) {
251 // don't expose private keys (starting with android._)
252 continue;
253 }
254
255 jobject valueObj = NULL;
256
257 switch (valueType) {
258 case AMessage::kTypeInt32:
259 {
260 int32_t val;
261 CHECK(msg->findInt32(key, &val));
262
263 valueObj = makeIntegerObject(env, val);
264 break;
265 }
266
267 case AMessage::kTypeInt64:
268 {
269 int64_t val;
270 CHECK(msg->findInt64(key, &val));
271
272 valueObj = makeLongObject(env, val);
273 break;
274 }
275
276 case AMessage::kTypeFloat:
277 {
278 float val;
279 CHECK(msg->findFloat(key, &val));
280
281 valueObj = makeFloatObject(env, val);
282 break;
283 }
284
285 case AMessage::kTypeString:
286 {
287 AString val;
288 CHECK(msg->findString(key, &val));
289
290 valueObj = env->NewStringUTF(val.c_str());
291 break;
292 }
293
294 case AMessage::kTypeBuffer:
295 {
296 sp<ABuffer> buffer;
297 CHECK(msg->findBuffer(key, &buffer));
298
299 valueObj = makeByteBufferObject(
300 env, buffer->data(), buffer->size());
301 break;
302 }
303
304 case AMessage::kTypeRect:
305 {
306 int32_t left, top, right, bottom;
307 CHECK(msg->findRect(key, &left, &top, &right, &bottom));
308
309 SetMapInt32(
310 env,
311 hashMap,
312 hashMapPutID,
313 AStringPrintf("%s-left", key).c_str(),
314 left);
315
316 SetMapInt32(
317 env,
318 hashMap,
319 hashMapPutID,
320 AStringPrintf("%s-top", key).c_str(),
321 top);
322
323 SetMapInt32(
324 env,
325 hashMap,
326 hashMapPutID,
327 AStringPrintf("%s-right", key).c_str(),
328 right);
329
330 SetMapInt32(
331 env,
332 hashMap,
333 hashMapPutID,
334 AStringPrintf("%s-bottom", key).c_str(),
335 bottom);
336 break;
337 }
338
339 default:
340 break;
341 }
342
343 if (valueObj != NULL) {
344 jstring keyObj = env->NewStringUTF(key);
345
346 env->CallObjectMethod(hashMap, hashMapPutID, keyObj, valueObj);
347
348 env->DeleteLocalRef(keyObj); keyObj = NULL;
349 env->DeleteLocalRef(valueObj); valueObj = NULL;
350 }
351 }
352
353 *map = hashMap;
354
355 return OK;
356 }
357
ConvertKeyValueArraysToMessage(JNIEnv * env,jobjectArray keys,jobjectArray values,sp<AMessage> * out)358 status_t ConvertKeyValueArraysToMessage(
359 JNIEnv *env, jobjectArray keys, jobjectArray values,
360 sp<AMessage> *out) {
361 ScopedLocalRef<jclass> stringClass(env, env->FindClass("java/lang/String"));
362 CHECK(stringClass.get() != NULL);
363 ScopedLocalRef<jclass> integerClass(env, env->FindClass("java/lang/Integer"));
364 CHECK(integerClass.get() != NULL);
365 ScopedLocalRef<jclass> longClass(env, env->FindClass("java/lang/Long"));
366 CHECK(longClass.get() != NULL);
367 ScopedLocalRef<jclass> floatClass(env, env->FindClass("java/lang/Float"));
368 CHECK(floatClass.get() != NULL);
369 ScopedLocalRef<jclass> byteBufClass(env, env->FindClass("java/nio/ByteBuffer"));
370 CHECK(byteBufClass.get() != NULL);
371
372 sp<AMessage> msg = new AMessage;
373
374 jsize numEntries = 0;
375
376 if (keys != NULL) {
377 if (values == NULL) {
378 return -EINVAL;
379 }
380
381 numEntries = env->GetArrayLength(keys);
382
383 if (numEntries != env->GetArrayLength(values)) {
384 return -EINVAL;
385 }
386 } else if (values != NULL) {
387 return -EINVAL;
388 }
389
390 for (jsize i = 0; i < numEntries; ++i) {
391 jobject keyObj = env->GetObjectArrayElement(keys, i);
392
393 if (!env->IsInstanceOf(keyObj, stringClass.get())) {
394 return -EINVAL;
395 }
396
397 const char *tmp = env->GetStringUTFChars((jstring)keyObj, NULL);
398
399 if (tmp == NULL) {
400 return -ENOMEM;
401 }
402
403 AString key = tmp;
404
405 env->ReleaseStringUTFChars((jstring)keyObj, tmp);
406 tmp = NULL;
407
408 if (key.startsWith("android._")) {
409 // don't propagate private keys (starting with android._)
410 continue;
411 }
412
413 jobject valueObj = env->GetObjectArrayElement(values, i);
414
415 if (env->IsInstanceOf(valueObj, stringClass.get())) {
416 const char *value = env->GetStringUTFChars((jstring)valueObj, NULL);
417
418 if (value == NULL) {
419 return -ENOMEM;
420 }
421
422 msg->setString(key.c_str(), value);
423
424 env->ReleaseStringUTFChars((jstring)valueObj, value);
425 value = NULL;
426 } else if (env->IsInstanceOf(valueObj, integerClass.get())) {
427 jmethodID intValueID =
428 env->GetMethodID(integerClass.get(), "intValue", "()I");
429 CHECK(intValueID != NULL);
430
431 jint value = env->CallIntMethod(valueObj, intValueID);
432
433 msg->setInt32(key.c_str(), value);
434 } else if (env->IsInstanceOf(valueObj, longClass.get())) {
435 jmethodID longValueID =
436 env->GetMethodID(longClass.get(), "longValue", "()J");
437 CHECK(longValueID != NULL);
438
439 jlong value = env->CallLongMethod(valueObj, longValueID);
440
441 msg->setInt64(key.c_str(), value);
442 } else if (env->IsInstanceOf(valueObj, floatClass.get())) {
443 jmethodID floatValueID =
444 env->GetMethodID(floatClass.get(), "floatValue", "()F");
445 CHECK(floatValueID != NULL);
446
447 jfloat value = env->CallFloatMethod(valueObj, floatValueID);
448
449 msg->setFloat(key.c_str(), value);
450 } else if (env->IsInstanceOf(valueObj, byteBufClass.get())) {
451 jmethodID positionID =
452 env->GetMethodID(byteBufClass.get(), "position", "()I");
453 CHECK(positionID != NULL);
454
455 jmethodID limitID =
456 env->GetMethodID(byteBufClass.get(), "limit", "()I");
457 CHECK(limitID != NULL);
458
459 jint position = env->CallIntMethod(valueObj, positionID);
460 jint limit = env->CallIntMethod(valueObj, limitID);
461
462 sp<ABuffer> buffer = new ABuffer(limit - position);
463
464 void *data = env->GetDirectBufferAddress(valueObj);
465
466 if (data != NULL) {
467 memcpy(buffer->data(),
468 (const uint8_t *)data + position,
469 buffer->size());
470 } else {
471 jmethodID arrayID =
472 env->GetMethodID(byteBufClass.get(), "array", "()[B");
473 CHECK(arrayID != NULL);
474
475 jbyteArray byteArray =
476 (jbyteArray)env->CallObjectMethod(valueObj, arrayID);
477 CHECK(byteArray != NULL);
478
479 env->GetByteArrayRegion(
480 byteArray,
481 position,
482 buffer->size(),
483 (jbyte *)buffer->data());
484
485 env->DeleteLocalRef(byteArray); byteArray = NULL;
486 }
487
488 msg->setBuffer(key.c_str(), buffer);
489 }
490 }
491
492 *out = msg;
493
494 return OK;
495 }
496
497 } // namespace android
498
499