]> git.sesse.net Git - vlc/blob - modules/video_output/android/surface.c
android: request rotation if video is oriented
[vlc] / modules / video_output / android / surface.c
1 /*****************************************************************************
2  * androidsurface.c: android video output using Surface Flinger
3  *****************************************************************************
4  * Copyright © 2011 VideoLAN
5  *
6  * Authors: Ming Hu <tewilove@gmail.com>
7  *          Ludovic Fauvet <etix@l0cal.com>
8  *          Sébastien Toque <xilasz@gmail.com>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_vout_display.h>
32 #include <vlc_picture_pool.h>
33
34 #include <dlfcn.h>
35 #include <jni.h>
36
37 #include "utils.h"
38
39 #ifndef ANDROID_SYM_S_LOCK
40 # define ANDROID_SYM_S_LOCK "_ZN7android7Surface4lockEPNS0_11SurfaceInfoEb"
41 #endif
42 #ifndef ANDROID_SYM_S_LOCK2
43 # define ANDROID_SYM_S_LOCK2 "_ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionE"
44 #endif
45 #ifndef ANDROID_SYM_S_UNLOCK
46 # define ANDROID_SYM_S_UNLOCK "_ZN7android7Surface13unlockAndPostEv"
47 #endif
48
49 /*****************************************************************************
50  * Module descriptor
51  *****************************************************************************/
52 #define CHROMA_TEXT N_("Chroma used")
53 #define CHROMA_LONGTEXT N_(\
54     "Force use of a specific chroma for output. Default is RGB32.")
55
56 #define CFG_PREFIX "androidsurface-"
57
58 static int  Open (vlc_object_t *);
59 static void Close(vlc_object_t *);
60
61 vlc_module_begin()
62     set_category(CAT_VIDEO)
63     set_subcategory(SUBCAT_VIDEO_VOUT)
64     set_shortname("AndroidSurface")
65     set_description(N_("Android Surface video output"))
66     set_capability("vout display", 155)
67     add_shortcut("androidsurface", "android")
68     add_string(CFG_PREFIX "chroma", NULL, CHROMA_TEXT, CHROMA_LONGTEXT, true)
69     set_callbacks(Open, Close)
70 vlc_module_end()
71
72 /*****************************************************************************
73  * JNI prototypes
74  *****************************************************************************/
75
76 extern JavaVM *myVm;
77 extern void *jni_LockAndGetAndroidSurface();
78 extern jobject jni_LockAndGetAndroidJavaSurface();
79 extern void  jni_UnlockAndroidSurface();
80 extern void  jni_SetAndroidSurfaceSize(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den);
81
82 // _ZN7android7Surface4lockEPNS0_11SurfaceInfoEb
83 typedef void (*Surface_lock)(void *, void *, int);
84 // _ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionE
85 typedef void (*Surface_lock2)(void *, void *, void *);
86 // _ZN7android7Surface13unlockAndPostEv
87 typedef void (*Surface_unlockAndPost)(void *);
88
89 /*****************************************************************************
90  * Local prototypes
91  *****************************************************************************/
92
93 static picture_pool_t   *Pool  (vout_display_t *, unsigned);
94 static void             Display(vout_display_t *, picture_t *, subpicture_t *);
95 static int              Control(vout_display_t *, int, va_list);
96
97 /* */
98 typedef struct _SurfaceInfo {
99     uint32_t    w;
100     uint32_t    h;
101     uint32_t    s;
102     uint32_t    usage;
103     uint32_t    format;
104     uint32_t*   bits;
105     uint32_t    reserved[2];
106 } SurfaceInfo;
107
108 /* */
109 struct vout_display_sys_t {
110     picture_pool_t *pool;
111     void *p_library;
112     Surface_lock s_lock;
113     Surface_lock2 s_lock2;
114     Surface_unlockAndPost s_unlockAndPost;
115     native_window_api_t native_window;
116
117     jobject jsurf;
118     ANativeWindow *window;
119
120     /* density */
121     int i_sar_num;
122     int i_sar_den;
123
124     video_format_t fmt;
125     bool b_changed_crop;
126 };
127
128 struct picture_sys_t {
129     void *surf;
130     SurfaceInfo info;
131     vout_display_sys_t *sys;
132 };
133
134 static int  AndroidLockSurface(picture_t *);
135 static void AndroidUnlockSurface(picture_t *);
136
137 static vlc_mutex_t single_instance = VLC_STATIC_MUTEX;
138
139 static inline void *LoadSurface(const char *psz_lib, vout_display_sys_t *sys)
140 {
141     void *p_library = dlopen(psz_lib, RTLD_NOW);
142     if (!p_library)
143         return NULL;
144
145     sys->s_lock = (Surface_lock)(dlsym(p_library, ANDROID_SYM_S_LOCK));
146     sys->s_lock2 = (Surface_lock2)(dlsym(p_library, ANDROID_SYM_S_LOCK2));
147     sys->s_unlockAndPost =
148         (Surface_unlockAndPost)(dlsym(p_library, ANDROID_SYM_S_UNLOCK));
149
150     if ((sys->s_lock || sys->s_lock2) && sys->s_unlockAndPost)
151         return p_library;
152
153     dlclose(p_library);
154     return NULL;
155 }
156
157 static void *InitLibrary(vout_display_sys_t *sys)
158 {
159     static const char *libs[] = {
160         "libsurfaceflinger_client.so",
161         "libgui.so",
162         "libui.so"
163     };
164
165     for (size_t i = 0; i < sizeof(libs) / sizeof(*libs); i++) {
166         void *lib = LoadSurface(libs[i], sys);
167         if (lib)
168             return lib;
169     }
170     return NULL;
171 }
172
173 static int Open(vlc_object_t *p_this)
174 {
175     vout_display_t *vd = (vout_display_t *)p_this;
176     video_format_t fmt;
177     video_format_ApplyRotation(&fmt, &vd->fmt);
178
179     if (fmt.i_chroma == VLC_CODEC_ANDROID_OPAQUE)
180         return VLC_EGENERIC;
181
182     /* */
183     if (vlc_mutex_trylock(&single_instance) != 0) {
184         msg_Err(vd, "Can't start more than one instance at a time");
185         return VLC_EGENERIC;
186     }
187
188     /* Allocate structure */
189     vout_display_sys_t *sys = (struct vout_display_sys_t*) calloc(1, sizeof(*sys));
190     if (!sys) {
191         vlc_mutex_unlock(&single_instance);
192         return VLC_ENOMEM;
193     }
194
195     /* */
196     sys->p_library = LoadNativeWindowAPI(&sys->native_window);
197     sys->s_unlockAndPost = (Surface_unlockAndPost)sys->native_window.unlockAndPost;
198     if (!sys->p_library)
199         sys->p_library = InitLibrary(sys);
200     if (!sys->p_library) {
201         free(sys);
202         msg_Err(vd, "Could not initialize libandroid.so/libui.so/libgui.so/libsurfaceflinger_client.so!");
203         vlc_mutex_unlock(&single_instance);
204         return VLC_EGENERIC;
205     }
206
207     /* Setup chroma */
208     char *psz_fcc = var_InheritString(vd, CFG_PREFIX "chroma");
209     if( psz_fcc ) {
210         fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, psz_fcc);
211         free(psz_fcc);
212     } else
213         fmt.i_chroma = VLC_CODEC_RGB32;
214
215     switch(fmt.i_chroma) {
216         case VLC_CODEC_YV12:
217             /* avoid swscale usage by asking for I420 instead since the
218              * vout already has code to swap the buffers */
219             fmt.i_chroma = VLC_CODEC_I420;
220         case VLC_CODEC_I420:
221             break;
222
223         case VLC_CODEC_RGB16:
224             fmt.i_bmask = 0x0000001f;
225             fmt.i_gmask = 0x000007e0;
226             fmt.i_rmask = 0x0000f800;
227             break;
228
229         case VLC_CODEC_RGB32:
230             fmt.i_rmask  = 0x000000ff;
231             fmt.i_gmask  = 0x0000ff00;
232             fmt.i_bmask  = 0x00ff0000;
233             break;
234
235         default:
236             return VLC_EGENERIC;
237     }
238     video_format_FixRgb(&fmt);
239
240     msg_Dbg(vd, "Pixel format %4.4s", (char*)&fmt.i_chroma);
241     sys->fmt = fmt;
242
243     /* Create the associated picture */
244     picture_sys_t *picsys = malloc(sizeof(*picsys));
245     if (unlikely(picsys == NULL))
246         goto enomem;
247     picsys->sys = sys;
248
249     picture_resource_t resource = { .p_sys = picsys };
250     picture_t *picture = picture_NewFromResource(&fmt, &resource);
251     if (!picture) {
252         free(picsys);
253         goto enomem;
254     }
255
256     /* Wrap it into a picture pool */
257     picture_pool_configuration_t pool_cfg;
258     memset(&pool_cfg, 0, sizeof(pool_cfg));
259     pool_cfg.picture_count = 1;
260     pool_cfg.picture       = &picture;
261     pool_cfg.lock          = AndroidLockSurface;
262     pool_cfg.unlock        = AndroidUnlockSurface;
263
264     sys->pool = picture_pool_NewExtended(&pool_cfg);
265     if (!sys->pool) {
266         picture_Release(picture);
267         goto enomem;
268     }
269
270     /* Setup vout_display */
271     vd->sys     = sys;
272     vd->fmt     = fmt;
273     vd->pool    = Pool;
274     vd->display = Display;
275     vd->control = Control;
276     vd->prepare = NULL;
277     vd->manage  = Manage;
278
279     /* Fix initial state */
280     vout_display_SendEventFullscreen(vd, false);
281
282     sys->i_sar_num = vd->source.i_sar_num;
283     sys->i_sar_den = vd->source.i_sar_den;
284
285     return VLC_SUCCESS;
286
287 enomem:
288     dlclose(sys->p_library);
289     free(sys);
290     vlc_mutex_unlock(&single_instance);
291     return VLC_ENOMEM;
292 }
293
294 static void Close(vlc_object_t *p_this)
295 {
296     vout_display_t *vd = (vout_display_t *)p_this;
297     vout_display_sys_t *sys = vd->sys;
298
299     picture_pool_Delete(sys->pool);
300     if (sys->window)
301         sys->native_window.winRelease(sys->window);
302     dlclose(sys->p_library);
303     free(sys);
304     vlc_mutex_unlock(&single_instance);
305 }
306
307 static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
308 {
309     VLC_UNUSED(count);
310
311     return vd->sys->pool;
312 }
313
314 #define ALIGN_16_PIXELS( x ) ( ( ( x ) + 15 ) / 16 * 16 )
315 static void SetupPictureYV12( SurfaceInfo* p_surfaceInfo, picture_t *p_picture )
316 {
317     /* according to document of android.graphics.ImageFormat.YV12 */
318     int i_stride = ALIGN_16_PIXELS( p_surfaceInfo->s );
319     int i_c_stride = ALIGN_16_PIXELS( i_stride / 2 );
320
321     p_picture->p->i_pitch = i_stride;
322
323     /* Fill chroma planes for planar YUV */
324     for( int n = 1; n < p_picture->i_planes; n++ )
325     {
326         const plane_t *o = &p_picture->p[n-1];
327         plane_t *p = &p_picture->p[n];
328
329         p->p_pixels = o->p_pixels + o->i_lines * o->i_pitch;
330         p->i_pitch  = i_c_stride;
331         p->i_lines  = p_picture->format.i_height / 2;
332         /*
333           Explicitly set the padding lines of the picture to black (127 for YUV)
334           since they might be used by Android during rescaling.
335         */
336         int visible_lines = p_picture->format.i_visible_height / 2;
337         if (visible_lines < p->i_lines)
338             memset(&p->p_pixels[visible_lines * p->i_pitch], 127, (p->i_lines - visible_lines) * p->i_pitch);
339     }
340
341     if( vlc_fourcc_AreUVPlanesSwapped( p_picture->format.i_chroma,
342                                        VLC_CODEC_YV12 ) ) {
343         uint8_t *p_tmp = p_picture->p[1].p_pixels;
344         p_picture->p[1].p_pixels = p_picture->p[2].p_pixels;
345         p_picture->p[2].p_pixels = p_tmp;
346     }
347 }
348
349 static int  AndroidLockSurface(picture_t *picture)
350 {
351     picture_sys_t *picsys = picture->p_sys;
352     vout_display_sys_t *sys = picsys->sys;
353     SurfaceInfo *info;
354     uint32_t sw, sh;
355     void *surf;
356
357     sw = sys->fmt.i_width;
358     sh = sys->fmt.i_height;
359
360     if (sys->native_window.winFromSurface) {
361         jobject jsurf = jni_LockAndGetAndroidJavaSurface();
362         if (unlikely(!jsurf)) {
363             jni_UnlockAndroidSurface();
364             return VLC_EGENERIC;
365         }
366         if (sys->window && jsurf != sys->jsurf) {
367             sys->native_window.winRelease(sys->window);
368             sys->window = NULL;
369         }
370         sys->jsurf = jsurf;
371         if (!sys->window) {
372             JNIEnv *p_env;
373             (*myVm)->AttachCurrentThread(myVm, &p_env, NULL);
374             sys->window = sys->native_window.winFromSurface(p_env, jsurf);
375             (*myVm)->DetachCurrentThread(myVm);
376         }
377         // Using sys->window instead of the native surface object
378         // as parameter to the unlock function
379         picsys->surf = surf = sys->window;
380     } else {
381         picsys->surf = surf = jni_LockAndGetAndroidSurface();
382         if (unlikely(!surf)) {
383             jni_UnlockAndroidSurface();
384             return VLC_EGENERIC;
385         }
386     }
387     info = &picsys->info;
388
389     if (sys->native_window.winLock) {
390         ANativeWindow_Buffer buf = { 0 };
391         int32_t err = sys->native_window.winLock(sys->window, &buf, NULL);
392         if (err) {
393             jni_UnlockAndroidSurface();
394             return VLC_EGENERIC;
395         }
396         info->w      = buf.width;
397         info->h      = buf.height;
398         info->bits   = buf.bits;
399         info->s      = buf.stride;
400         info->format = buf.format;
401     } else if (sys->s_lock)
402         sys->s_lock(surf, info, 1);
403     else
404         sys->s_lock2(surf, info, NULL);
405
406     // For RGB (32 or 16) we need to align on 8 or 4 pixels, 16 pixels for YUV
407     int align_pixels = (16 / picture->p[0].i_pixel_pitch) - 1;
408     uint32_t aligned_width = (sw + align_pixels) & ~align_pixels;
409
410     if (info->w != aligned_width || info->h != sh || sys->b_changed_crop) {
411         // input size doesn't match the surface size -> request a resize
412         jni_SetAndroidSurfaceSize(aligned_width, sh, sys->fmt.i_visible_width, sys->fmt.i_visible_height, sys->i_sar_num, sys->i_sar_den);
413         // When using ANativeWindow, one should use ANativeWindow_setBuffersGeometry
414         // to set the size and format. In our case, these are set via the SurfaceHolder
415         // in Java, so we seem to manage without calling this ANativeWindow function.
416         sys->s_unlockAndPost(surf);
417         jni_UnlockAndroidSurface();
418         sys->b_changed_crop = false;
419         return VLC_EGENERIC;
420     }
421
422     picture->p[0].p_pixels = (uint8_t*)info->bits;
423     picture->p[0].i_lines = info->h;
424     picture->p[0].i_pitch = picture->p[0].i_pixel_pitch * info->s;
425
426     if (info->format == 0x32315659 /*ANDROID_IMAGE_FORMAT_YV12*/)
427         SetupPictureYV12(info, picture);
428
429     return VLC_SUCCESS;
430 }
431
432 static void AndroidUnlockSurface(picture_t *picture)
433 {
434     picture_sys_t *picsys = picture->p_sys;
435     vout_display_sys_t *sys = picsys->sys;
436
437     if (likely(picsys->surf))
438         sys->s_unlockAndPost(picsys->surf);
439     jni_UnlockAndroidSurface();
440 }
441
442 static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
443 {
444     VLC_UNUSED(vd);
445     VLC_UNUSED(subpicture);
446
447     /* refcount lowers to 0, and pool_cfg.unlock is called */
448
449     picture_Release(picture);
450 }
451
452 static int Control(vout_display_t *vd, int query, va_list args)
453 {
454     VLC_UNUSED(args);
455
456     switch (query) {
457     case VOUT_DISPLAY_HIDE_MOUSE:
458         return VLC_SUCCESS;
459
460     case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
461     {
462         if (!vd->sys)
463             return VLC_EGENERIC;
464         vout_display_sys_t *sys = vd->sys;
465
466         const video_format_t *source = (const video_format_t *)va_arg(args, const video_format_t *);
467         sys->fmt = *source;
468         sys->b_changed_crop = true;
469         return VLC_SUCCESS;
470     }
471
472     default:
473         msg_Err(vd, "Unknown request in android vout display");
474
475     case VOUT_DISPLAY_CHANGE_FULLSCREEN:
476     case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
477     case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
478     case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
479     case VOUT_DISPLAY_CHANGE_ZOOM:
480     case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
481     case VOUT_DISPLAY_GET_OPENGL:
482         return VLC_EGENERIC;
483     }
484 }