1 /*
2 mediastreamer2 library - modular sound and video processing and streaming
3 This is the video capture filter for Android.
4 It uses one of the JNI wrappers to access Android video capture API.
5 See:
6 	org.linphone.mediastream.video.capture.AndroidVideoApi9JniWrapper
7 	org.linphone.mediastream.video.capture.AndroidVideoApi8JniWrapper
8 	org.linphone.mediastream.video.capture.AndroidVideoApi5JniWrapper
9 
10  * Copyright (C) 2010  Belledonne Communications, Grenoble, France
11 
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 as published by the Free Software Foundation; either version 2
15 of the License, or (at your option) any later version.
16 
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 GNU General Public License for more details.
21 
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
25  */
26 
27 extern "C" {
28 #include "mediastreamer2/msvideo.h"
29 #include "mediastreamer2/msfilter.h"
30 #include "mediastreamer2/mswebcam.h"
31 #include "mediastreamer2/msjava.h"
32 #include "mediastreamer2/msticker.h"
33 }
34 
35 #include <jni.h>
36 #include <math.h>
37 
38 static int android_sdk_version = 5;
39 
40 static const char* AndroidApi9WrapperPath = "org/linphone/mediastream/video/capture/AndroidVideoApi9JniWrapper";
41 static const char* AndroidApi8WrapperPath = "org/linphone/mediastream/video/capture/AndroidVideoApi8JniWrapper";
42 static const char* AndroidApi5WrapperPath = "org/linphone/mediastream/video/capture/AndroidVideoApi5JniWrapper";
43 static const char* VersionPath 			  = "org/linphone/mediastream/Version";
44 
45 #define UNDEFINED_ROTATION -1
46 
47 /************************ Data structures              ************************/
48 // Struct holding Android's cameras properties
49 struct AndroidWebcamConfig {
50 	int id;
51 	int frontFacing;
52 	int orientation;
53 };
54 
55 struct AndroidReaderContext {
AndroidReaderContextAndroidReaderContext56 	AndroidReaderContext(MSFilter *f, MSWebCam *cam):filter(f), webcam(cam),frame(0),fps(5){
57 		ms_message("Creating AndroidReaderContext for Android VIDEO capture filter");
58 		ms_mutex_init(&mutex,NULL);
59 		androidCamera = 0;
60 		previewWindow = 0;
61 		rotation = rotationSavedDuringVSize = UNDEFINED_ROTATION;
62 		allocator = ms_yuv_buf_allocator_new();
63 	};
64 
~AndroidReaderContextAndroidReaderContext65 	~AndroidReaderContext(){
66 		if (frame != 0) {
67 			freeb(frame);
68 		}
69 		ms_yuv_buf_allocator_free(allocator);
70 		ms_mutex_destroy(&mutex);
71 	};
72 
73 	MSFrameRateController fpsControl;
74 	MSAverageFPS averageFps;
75 
76 	MSFilter *filter;
77 	MSWebCam *webcam;
78 
79 	mblk_t *frame;
80 	float fps;
81 	MSVideoSize requestedSize, hwCapableSize, usedSize;
82 	ms_mutex_t mutex;
83 	int rotation, rotationSavedDuringVSize;
84 	int useDownscaling;
85 	char fps_context[64];
86 	MSYuvBufAllocator *allocator;
87 
88 	jobject androidCamera;
89 	jobject previewWindow;
90 	jclass helperClass;
91 };
92 
93 /************************ Private helper methods       ************************/
94 static jclass getHelperClassGlobalRef(JNIEnv *env);
95 static int compute_image_rotation_correction(AndroidReaderContext* d, int rotation);
96 static void compute_cropping_offsets(MSVideoSize hwSize, MSVideoSize outputSize, int* yoff, int* cbcroff);
97 static AndroidReaderContext *getContext(MSFilter *f);
98 
99 
100 /************************ MS2 filter methods           ************************/
video_capture_set_fps(MSFilter * f,void * arg)101 static int video_capture_set_fps(MSFilter *f, void *arg){
102 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
103 	d->fps=*((float*)arg);
104 	return 0;
105 }
106 
video_capture_set_autofocus(MSFilter * f,void * data)107 static int video_capture_set_autofocus(MSFilter *f, void* data){
108 	JNIEnv *env = ms_get_jni_env();
109 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
110 	jmethodID method = env->GetStaticMethodID(d->helperClass,"activateAutoFocus", "(Ljava/lang/Object;)V");
111 	env->CallStaticObjectMethod(d->helperClass, method, d->androidCamera);
112 
113 	return 0;
114 }
115 
video_capture_get_fps(MSFilter * f,void * arg)116 static int video_capture_get_fps(MSFilter *f, void *arg){
117 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
118 	*((float*)arg) = ms_average_fps_get(&d->averageFps);
119 	return 0;
120 }
121 
video_capture_set_vsize(MSFilter * f,void * data)122 static int video_capture_set_vsize(MSFilter *f, void* data){
123 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
124 	ms_mutex_lock(&d->mutex);
125 
126 	d->requestedSize=*(MSVideoSize*)data;
127 
128 	// always request landscape mode, orientation is handled later
129 	if (d->requestedSize.height > d->requestedSize.width) {
130 		int tmp = d->requestedSize.height;
131 		d->requestedSize.height = d->requestedSize.width;
132 		d->requestedSize.width = tmp;
133 	}
134 
135 	JNIEnv *env = ms_get_jni_env();
136 
137 	jmethodID method = env->GetStaticMethodID(d->helperClass,"selectNearestResolutionAvailable", "(III)[I");
138 
139 	// find neareast hw-available resolution (using jni call);
140 	jobject resArray = env->CallStaticObjectMethod(d->helperClass, method, ((AndroidWebcamConfig*)d->webcam->data)->id, d->requestedSize.width, d->requestedSize.height);
141 
142 	if (!resArray) {
143 		ms_mutex_unlock(&d->mutex);
144 		ms_error("Failed to retrieve camera '%d' supported resolutions\n", ((AndroidWebcamConfig*)d->webcam->data)->id);
145 		return -1;
146 	}
147 
148 	// handle result :
149 	//   - 0 : width
150     //   - 1 : height
151     //   - 2 : useDownscaling
152 	jint res[3];
153 	env->GetIntArrayRegion((jintArray)resArray, 0, 3, res);
154 	ms_message("Camera selected resolution is: %dx%d (requested: %dx%d) with downscaling?%d\n", res[0], res[1], d->requestedSize.width, d->requestedSize.height, res[2]);
155 	d->hwCapableSize.width =  res[0];
156 	d->hwCapableSize.height = res[1];
157 	d->useDownscaling = res[2];
158 
159 	int rqSize = d->requestedSize.width * d->requestedSize.height;
160 	int hwSize = d->hwCapableSize.width * d->hwCapableSize.height;
161 	double downscale = d->useDownscaling ? 0.5 : 1;
162 
163 	// if hw supplies a smaller resolution, modify requested size accordingly
164 	if ((hwSize * downscale * downscale) < rqSize) {
165 		ms_message("Camera cannot produce requested resolution %dx%d, will supply smaller one: %dx%d\n",
166 			d->requestedSize.width, d->requestedSize.height, (int) (res[0] * downscale), (int) (res[1]*downscale));
167 		d->usedSize.width = (int) (d->hwCapableSize.width * downscale);
168 		d->usedSize.height = (int) (d->hwCapableSize.height * downscale);
169 	} else if ((hwSize * downscale * downscale) > rqSize) {
170 		ms_message("Camera cannot produce requested resolution %dx%d, will capture a bigger one (%dx%d) and crop it to match encoder requested resolution\n",
171 			d->requestedSize.width, d->requestedSize.height, (int)(res[0] * downscale), (int)(res[1] * downscale));
172 		d->usedSize.width = d->requestedSize.width;
173 		d->usedSize.height = d->requestedSize.height;
174 	} else {
175 		d->usedSize.width = d->requestedSize.width;
176 		d->usedSize.height = d->requestedSize.height;
177 	}
178 
179 	// is phone held |_ to cam orientation ?
180 	if (d->rotation == UNDEFINED_ROTATION || compute_image_rotation_correction(d, d->rotation) % 180 != 0) {
181 		if (d->rotation == UNDEFINED_ROTATION) {
182 			ms_error("To produce a correct image, Mediastreamer MUST be aware of device's orientation BEFORE calling 'configure_video_source'\n");
183 			ms_warning("Capture filter do not know yet about device's orientation.\n"
184 				"Current assumption: device is held perpendicular to its webcam (ie: portrait mode for a phone)\n");
185 			d->rotationSavedDuringVSize = 0;
186 		} else {
187 			d->rotationSavedDuringVSize = d->rotation;
188 		}
189 		bool camIsLandscape = d->hwCapableSize.width > d->hwCapableSize.height;
190 		bool useIsLandscape = d->usedSize.width > d->usedSize.height;
191 
192 		// if both are landscape or both portrait, swap
193 		if (camIsLandscape == useIsLandscape) {
194 			int t = d->usedSize.width;
195 			d->usedSize.width = d->usedSize.height;
196 			d->usedSize.height = t;
197 			ms_message("Swapped resolution width and height to : %dx%d\n", d->usedSize.width, d->usedSize.height);
198 		}
199 	} else {
200 		d->rotationSavedDuringVSize = d->rotation;
201 	}
202 
203 	ms_mutex_unlock(&d->mutex);
204 	return 0;
205 }
206 
video_capture_get_vsize(MSFilter * f,void * data)207 static int video_capture_get_vsize(MSFilter *f, void* data){
208 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
209 	*(MSVideoSize*)data=d->usedSize;
210 	return 0;
211 }
212 
video_capture_get_pix_fmt(MSFilter * f,void * data)213 static int video_capture_get_pix_fmt(MSFilter *f, void *data){
214 	*(MSPixFmt*)data=MS_YUV420P;
215 	return 0;
216 }
217 
218 // Java will give us a pointer to capture preview surface.
video_set_native_preview_window(MSFilter * f,void * arg)219 static int video_set_native_preview_window(MSFilter *f, void *arg) {
220 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
221 
222 	ms_mutex_lock(&d->mutex);
223 
224 	jobject w = (jobject)*((unsigned long*)arg);
225 
226 	if (w == d->previewWindow) {
227 		ms_mutex_unlock(&d->mutex);
228 		return 0;
229 	}
230 
231 	JNIEnv *env = ms_get_jni_env();
232 
233 	jmethodID method = env->GetStaticMethodID(d->helperClass,"setPreviewDisplaySurface", "(Ljava/lang/Object;Ljava/lang/Object;)V");
234 
235 	if (d->androidCamera) {
236 		if (d->previewWindow == 0) {
237 			ms_message("Preview capture window set for the 1st time (win: %p rotation:%d)\n", w, d->rotation);
238 		} else {
239 			ms_message("Preview capture window changed (oldwin: %p newwin: %p rotation:%d)\n", d->previewWindow, w, d->rotation);
240 
241 			env->CallStaticVoidMethod(d->helperClass,
242 						env->GetStaticMethodID(d->helperClass,"stopRecording", "(Ljava/lang/Object;)V"),
243 						d->androidCamera);
244 			env->DeleteGlobalRef(d->androidCamera);
245 			d->androidCamera = env->NewGlobalRef(
246 			env->CallStaticObjectMethod(d->helperClass,
247 						env->GetStaticMethodID(d->helperClass,"startRecording", "(IIIIIJ)Ljava/lang/Object;"),
248 						((AndroidWebcamConfig*)d->webcam->data)->id,
249 						d->hwCapableSize.width,
250 						d->hwCapableSize.height,
251 						(jint)d->fps,
252 						(d->rotation != UNDEFINED_ROTATION) ? d->rotation:0,
253 						(jlong)d));
254 		}
255 		// if previewWindow AND camera are valid => set preview window
256 		if (w && d->androidCamera)
257 			env->CallStaticVoidMethod(d->helperClass, method, d->androidCamera, w);
258 	} else {
259 		ms_message("Preview capture window set but camera not created yet; remembering it for later use\n");
260 	}
261 	d->previewWindow = w;
262 
263 	ms_mutex_unlock(&d->mutex);
264 	return 0;
265 }
266 
video_get_native_preview_window(MSFilter * f,void * arg)267 static int video_get_native_preview_window(MSFilter *f, void *arg) {
268 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
269 	*((unsigned long *)arg) = (unsigned long)d->previewWindow;
270 	return 0;
271 }
272 
video_set_device_rotation(MSFilter * f,void * arg)273 static int video_set_device_rotation(MSFilter* f, void* arg) {
274 	AndroidReaderContext* d = (AndroidReaderContext*) f->data;
275 	d->rotation=*((int*)arg);
276 	ms_message("%s : %d\n", __FUNCTION__, d->rotation);
277 	return 0;
278 }
279 
video_capture_preprocess(MSFilter * f)280 void video_capture_preprocess(MSFilter *f){
281 	ms_message("Preprocessing of Android VIDEO capture filter");
282 
283 	AndroidReaderContext *d = getContext(f);
284 	ms_mutex_lock(&d->mutex);
285 
286 	snprintf(d->fps_context, sizeof(d->fps_context), "Captured mean fps=%%f, expected=%f", d->fps);
287 	ms_video_init_framerate_controller(&d->fpsControl, d->fps);
288 	ms_video_init_average_fps(&d->averageFps, d->fps_context);
289 
290 	JNIEnv *env = ms_get_jni_env();
291 
292 	jmethodID method = env->GetStaticMethodID(d->helperClass,"startRecording", "(IIIIIJ)Ljava/lang/Object;");
293 
294 	ms_message("Starting Android camera '%d' (rotation:%d)", ((AndroidWebcamConfig*)d->webcam->data)->id, d->rotation);
295 	jobject cam = env->CallStaticObjectMethod(d->helperClass, method,
296 			((AndroidWebcamConfig*)d->webcam->data)->id,
297 			d->hwCapableSize.width,
298 			d->hwCapableSize.height,
299 			(jint)d->fps,
300 			d->rotationSavedDuringVSize,
301 			(jlong)d);
302 	d->androidCamera = env->NewGlobalRef(cam);
303 
304 	if (d->previewWindow) {
305 		method = env->GetStaticMethodID(d->helperClass,"setPreviewDisplaySurface", "(Ljava/lang/Object;Ljava/lang/Object;)V");
306 		env->CallStaticVoidMethod(d->helperClass, method, d->androidCamera, d->previewWindow);
307 	}
308 	ms_message("Preprocessing of Android VIDEO capture filter done");
309 	ms_mutex_unlock(&d->mutex);
310 }
311 
video_capture_process(MSFilter * f)312 static void video_capture_process(MSFilter *f){
313 	AndroidReaderContext* d = getContext(f);
314 
315 	ms_mutex_lock(&d->mutex);
316 
317 	// If frame not ready, return
318 	if (d->frame == 0) {
319 		ms_mutex_unlock(&d->mutex);
320 		return;
321 	}
322 
323 	ms_video_update_average_fps(&d->averageFps, f->ticker->time);
324 
325 	ms_queue_put(f->outputs[0],d->frame);
326 	d->frame = 0;
327 	ms_mutex_unlock(&d->mutex);
328 }
329 
video_capture_postprocess(MSFilter * f)330 static void video_capture_postprocess(MSFilter *f){
331 	ms_message("Postprocessing of Android VIDEO capture filter");
332 	AndroidReaderContext* d = getContext(f);
333 	JNIEnv *env = ms_get_jni_env();
334 
335 	ms_mutex_lock(&d->mutex);
336 
337 	if (d->androidCamera) {
338 		jmethodID method = env->GetStaticMethodID(d->helperClass,"stopRecording", "(Ljava/lang/Object;)V");
339 
340 		env->CallStaticVoidMethod(d->helperClass, method, d->androidCamera);
341 		env->DeleteGlobalRef(d->androidCamera);
342 	}
343 	d->androidCamera = 0;
344 	d->previewWindow = 0;
345 	if (d->frame){
346 		freemsg(d->frame);
347 		d->frame=NULL;
348 	}
349 	ms_mutex_unlock(&d->mutex);
350 }
351 
video_capture_init(MSFilter * f)352 static void video_capture_init(MSFilter *f) {
353 	AndroidReaderContext* d = new AndroidReaderContext(f, 0);
354 	ms_message("Init of Android VIDEO capture filter (%p)", d);
355 	JNIEnv *env = ms_get_jni_env();
356 	d->helperClass = getHelperClassGlobalRef(env);
357 	f->data = d;
358 }
359 
video_capture_uninit(MSFilter * f)360 static void video_capture_uninit(MSFilter *f) {
361 	ms_message("Uninit of Android VIDEO capture filter");
362 	AndroidReaderContext* d = getContext(f);
363 	JNIEnv *env = ms_get_jni_env();
364 	env->DeleteGlobalRef(d->helperClass);
365 	delete d;
366 }
367 
368 static MSFilterMethod video_capture_methods[]={
369 		{	MS_FILTER_SET_FPS,	&video_capture_set_fps},
370 		{	MS_FILTER_GET_FPS,	&video_capture_get_fps},
371 		{	MS_FILTER_SET_VIDEO_SIZE, &video_capture_set_vsize},
372 		{	MS_FILTER_GET_VIDEO_SIZE, &video_capture_get_vsize},
373 		{	MS_FILTER_GET_PIX_FMT, &video_capture_get_pix_fmt},
374 		{	MS_VIDEO_DISPLAY_SET_NATIVE_WINDOW_ID , &video_set_native_preview_window },//preview is managed by capture filter
375 		{	MS_VIDEO_DISPLAY_GET_NATIVE_WINDOW_ID , &video_get_native_preview_window },
376 		{   MS_VIDEO_CAPTURE_SET_DEVICE_ORIENTATION, &video_set_device_rotation },
377 		{   MS_VIDEO_CAPTURE_SET_AUTOFOCUS, &video_capture_set_autofocus },
378 		{	0,0 }
379 };
380 
381 MSFilterDesc ms_video_capture_desc={
382 		MS_ANDROID_VIDEO_READ_ID,
383 		"MSAndroidVideoCapture",
384 		N_("A filter that captures Android video."),
385 		MS_FILTER_OTHER,
386 		NULL,
387 		0,
388 		1,
389 		video_capture_init,
390 		video_capture_preprocess,
391 		video_capture_process,
392 		video_capture_postprocess,
393 		video_capture_uninit,
394 		video_capture_methods
395 };
396 
397 MS_FILTER_DESC_EXPORT(ms_video_capture_desc)
398 
399 /* Webcam methods */
400 static void video_capture_detect(MSWebCamManager *obj);
video_capture_cam_init(MSWebCam * cam)401 static void video_capture_cam_init(MSWebCam *cam){
402 	ms_message("Android VIDEO capture filter cam init");
403 }
404 
video_capture_create_reader(MSWebCam * obj)405 static MSFilter *video_capture_create_reader(MSWebCam *obj){
406 	ms_message("Instanciating Android VIDEO capture MS filter");
407 
408 	MSFilter* lFilter = ms_factory_create_filter_from_desc(ms_web_cam_get_factory(obj), &ms_video_capture_desc);
409 	getContext(lFilter)->webcam = obj;
410 
411 	return lFilter;
412 }
413 
414 MSWebCamDesc ms_android_video_capture_desc={
415 		"AndroidVideoCapture",
416 		&video_capture_detect,
417 		&video_capture_cam_init,
418 		&video_capture_create_reader,
419 		NULL
420 };
421 
video_capture_detect(MSWebCamManager * obj)422 static void video_capture_detect(MSWebCamManager *obj){
423 	ms_message("Detecting Android VIDEO cards");
424 	JNIEnv *env = ms_get_jni_env();
425 	jclass helperClass = getHelperClassGlobalRef(env);
426 
427 	if (helperClass==NULL) return;
428 
429 	// create 3 int arrays - assuming 2 webcams at most
430 	jintArray indexes = (jintArray)env->NewIntArray(2);
431 	jintArray frontFacing = (jintArray)env->NewIntArray(2);
432 	jintArray orientation = (jintArray)env->NewIntArray(2);
433 
434 	jmethodID method = env->GetStaticMethodID(helperClass,"detectCameras", "([I[I[I)I");
435 
436 	int count = env->CallStaticIntMethod(helperClass, method, indexes, frontFacing, orientation);
437 
438 	ms_message("%d cards detected", count);
439 	for(int i=0; i<count; i++) {
440 		MSWebCam *cam = ms_web_cam_new(&ms_android_video_capture_desc);
441 		AndroidWebcamConfig* c = new AndroidWebcamConfig();
442 		env->GetIntArrayRegion(indexes, i, 1, &c->id);
443 		env->GetIntArrayRegion(frontFacing, i, 1, &c->frontFacing);
444 		env->GetIntArrayRegion(orientation, i, 1, &c->orientation);
445 		cam->data = c;
446 		cam->name = ms_strdup("Android video name");
447 		char* idstring = (char*) ms_malloc(15);
448 		snprintf(idstring, 15, "Android%d", c->id);
449 		cam->id = idstring;
450 		ms_web_cam_manager_add_cam(obj,cam);
451 		ms_message("camera created: id=%d frontFacing=%d orientation=%d [msid:%s]\n", c->id, c->frontFacing, c->orientation, idstring);
452 	}
453 	env->DeleteLocalRef(indexes);
454 	env->DeleteLocalRef(frontFacing);
455 	env->DeleteLocalRef(orientation);
456 
457 	env->DeleteGlobalRef(helperClass);
458 	ms_message("Detection of Android VIDEO cards done");
459 }
460 
461 
462 
463 /************************ JNI methods                  ************************/
464 #ifdef __cplusplus
465 extern "C" {
466 #endif
467 
Java_org_linphone_mediastream_video_capture_AndroidVideoApi5JniWrapper_putImage(JNIEnv * env,jclass thiz,jlong nativePtr,jbyteArray frame)468 JNIEXPORT void JNICALL Java_org_linphone_mediastream_video_capture_AndroidVideoApi5JniWrapper_putImage(JNIEnv*  env,
469 		jclass  thiz,jlong nativePtr,jbyteArray frame) {
470 	AndroidReaderContext* d = (AndroidReaderContext*) nativePtr;
471 
472 	ms_mutex_lock(&d->mutex);
473 
474 	if (!d->androidCamera){
475 		ms_mutex_unlock(&d->mutex);
476 		return;
477 	}
478 
479 	if (!ms_video_capture_new_frame(&d->fpsControl,d->filter->ticker->time)) {
480 		ms_mutex_unlock(&d->mutex);
481 		return;
482 	}
483 
484 	if (d->rotation != UNDEFINED_ROTATION && d->rotationSavedDuringVSize != d->rotation) {
485 		ms_warning("Rotation has changed (new value: %d) since vsize was run (old value: %d)."
486 					"Will produce inverted images. Use set_device_orientation() then update call.\n",
487 			d->rotation, d->rotationSavedDuringVSize);
488 	}
489 
490 	int image_rotation_correction = compute_image_rotation_correction(d, d->rotationSavedDuringVSize);
491 
492 	jboolean isCopied;
493 	jbyte* jinternal_buff = env->GetByteArrayElements(frame, &isCopied);
494 	if (isCopied) {
495 		ms_warning("The video frame received from Java has been copied");
496 	}
497 
498 	int y_cropping_offset=0, cbcr_cropping_offset=0;
499 	MSVideoSize targetSize;
500 	d->useDownscaling?targetSize.width=d->requestedSize.width*2:targetSize.width=d->requestedSize.width;
501 	d->useDownscaling?targetSize.height=d->requestedSize.height*2:targetSize.height=d->requestedSize.height;
502 
503 	compute_cropping_offsets(d->hwCapableSize, targetSize, &y_cropping_offset, &cbcr_cropping_offset);
504 
505 	int width = d->hwCapableSize.width;
506 	int height = d->hwCapableSize.height;
507 
508 	uint8_t* y_src = (uint8_t*)(jinternal_buff + y_cropping_offset);
509 	uint8_t* cbcr_src = (uint8_t*) (jinternal_buff + width * height + cbcr_cropping_offset);
510 
511 
512 	/* Warning note: image_rotation_correction == 90 does not imply portrait mode !
513 	   (incorrect function naming).
514 	   It only implies one thing: image needs to rotated by that amount to be correctly
515 	   displayed.
516 	*/
517  	mblk_t* yuv_block = copy_ycbcrbiplanar_to_true_yuv_with_rotation_and_down_scale_by_2(d->allocator, y_src
518 														, cbcr_src
519 														, image_rotation_correction
520 														, d->usedSize.width
521 														, d->usedSize.height
522 														, d->hwCapableSize.width
523 														, d->hwCapableSize.width
524 														, false
525 														, d->useDownscaling);
526 	if (yuv_block) {
527 		if (d->frame)
528 			freemsg(d->frame);
529 		d->frame = yuv_block;
530 	}
531 	ms_mutex_unlock(&d->mutex);
532 
533 	// JNI_ABORT free the buffer without copying back the possible changes
534 	env->ReleaseByteArrayElements(frame, jinternal_buff, JNI_ABORT);
535 }
536 
537 #ifdef __cplusplus
538 }
539 #endif
540 
compute_image_rotation_correction(AndroidReaderContext * d,int rotation)541 static int compute_image_rotation_correction(AndroidReaderContext* d, int rotation) {
542 	AndroidWebcamConfig* conf = (AndroidWebcamConfig*)(AndroidWebcamConfig*)d->webcam->data;
543 
544 	int result;
545 	if (conf->frontFacing) {
546 		ms_debug("%s: %d + %d\n", __FUNCTION__, ((AndroidWebcamConfig*)d->webcam->data)->orientation, rotation);
547 	 	result = ((AndroidWebcamConfig*)d->webcam->data)->orientation + rotation;
548 	} else {
549 		ms_debug("%s: %d - %d\n", __FUNCTION__, ((AndroidWebcamConfig*)d->webcam->data)->orientation, rotation);
550 	 	result = ((AndroidWebcamConfig*)d->webcam->data)->orientation - rotation;
551 	}
552 	while(result < 0)
553 		result += 360;
554 	return result % 360;
555 }
556 
557 
compute_cropping_offsets(MSVideoSize hwSize,MSVideoSize outputSize,int * yoff,int * cbcroff)558 static void compute_cropping_offsets(MSVideoSize hwSize, MSVideoSize outputSize, int* yoff, int* cbcroff) {
559 	// if hw <= out -> return
560 	if (hwSize.width * hwSize.height <= outputSize.width * outputSize.height) {
561 		*yoff = 0;
562 		*cbcroff = 0;
563 		return;
564 	}
565 
566 	int halfDiffW = (hwSize.width - ((outputSize.width>outputSize.height)?outputSize.width:outputSize.height)) / 2;
567 	int halfDiffH = (hwSize.height - ((outputSize.width<outputSize.height)?outputSize.width:outputSize.height)) / 2;
568 
569 	*yoff = hwSize.width * halfDiffH + halfDiffW;
570 	*cbcroff = hwSize.width * halfDiffH * 0.5 + halfDiffW;
571 }
572 
573 
getHelperClassGlobalRef(JNIEnv * env)574 static jclass getHelperClassGlobalRef(JNIEnv *env) {
575 	ms_message("getHelperClassGlobalRef (env: %p)", env);
576 	const char* className;
577 	// FindClass only returns local references.
578 
579 	// Find the current Android SDK version
580 	jclass version = env->FindClass(VersionPath);
581 	jmethodID method = env->GetStaticMethodID(version,"sdk", "()I");
582 	android_sdk_version = env->CallStaticIntMethod(version, method);
583 	ms_message("Android SDK version found is %i", android_sdk_version);
584 	env->DeleteLocalRef(version);
585 
586 	if (android_sdk_version >= 9) {
587 		className = AndroidApi9WrapperPath;
588 	} else if (android_sdk_version >= 8) {
589 		className = AndroidApi8WrapperPath;
590 	} else {
591 		className = AndroidApi5WrapperPath;
592 	}
593 	jclass c = env->FindClass(className);
594 	if (c == 0) {
595 		ms_error("Could not load class '%s' (%d)", className, android_sdk_version);
596 		return NULL;
597 	} else {
598 		jclass globalRef = reinterpret_cast<jclass>(env->NewGlobalRef(c));
599 		env->DeleteLocalRef(c);
600 		return globalRef;
601 	}
602 }
603 
getContext(MSFilter * f)604 static AndroidReaderContext *getContext(MSFilter *f) {
605 	return (AndroidReaderContext*) f->data;
606 }
607