]> git.sesse.net Git - vlc/blob - modules/video_output/androidsurface.c
avcodec: give right buffer size not the smallest one
[vlc] / modules / video_output / androidsurface.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 <android/native_window.h>
36 #include <jni.h>
37 #include <android/native_window_jni.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 typedef ANativeWindow* (*ptr_ANativeWindow_fromSurface)(JNIEnv*, jobject);
90 typedef void (*ptr_ANativeWindow_release)(ANativeWindow*);
91 typedef int32_t (*ptr_ANativeWindow_lock)(ANativeWindow*, ANativeWindow_Buffer*, ARect*);
92 // Just using the normal Surface_unlockAndPost as prototype for ANativeWindow_unlockAndPost
93
94 /*****************************************************************************
95  * Local prototypes
96  *****************************************************************************/
97
98 static picture_pool_t   *Pool  (vout_display_t *, unsigned);
99 static void             Display(vout_display_t *, picture_t *, subpicture_t *);
100 static int              Control(vout_display_t *, int, va_list);
101
102 /* */
103 typedef struct _SurfaceInfo {
104     uint32_t    w;
105     uint32_t    h;
106     uint32_t    s;
107     uint32_t    usage;
108     uint32_t    format;
109     uint32_t*   bits;
110     uint32_t    reserved[2];
111 } SurfaceInfo;
112
113 /* */
114 struct vout_display_sys_t {
115     picture_pool_t *pool;
116     void *p_library;
117     Surface_lock s_lock;
118     Surface_lock2 s_lock2;
119     Surface_unlockAndPost s_unlockAndPost;
120     ptr_ANativeWindow_fromSurface s_winFromSurface;
121     ptr_ANativeWindow_release s_winRelease;
122     ptr_ANativeWindow_lock s_winLock;
123
124     jobject jsurf;
125     ANativeWindow *window;
126
127     /* density */
128     int i_sar_num;
129     int i_sar_den;
130
131     video_format_t fmt;
132     bool b_changed_crop;
133 };
134
135 struct picture_sys_t {
136     void *surf;
137     SurfaceInfo info;
138     vout_display_sys_t *sys;
139 };
140
141 static int  AndroidLockSurface(picture_t *);
142 static void AndroidUnlockSurface(picture_t *);
143
144 static vlc_mutex_t single_instance = VLC_STATIC_MUTEX;
145
146 static inline void *LoadSurface(const char *psz_lib, vout_display_sys_t *sys)
147 {
148     void *p_library = dlopen(psz_lib, RTLD_NOW);
149     if (!p_library)
150         return NULL;
151
152     sys->s_lock = (Surface_lock)(dlsym(p_library, ANDROID_SYM_S_LOCK));
153     sys->s_lock2 = (Surface_lock2)(dlsym(p_library, ANDROID_SYM_S_LOCK2));
154     sys->s_unlockAndPost =
155         (Surface_unlockAndPost)(dlsym(p_library, ANDROID_SYM_S_UNLOCK));
156
157     if ((sys->s_lock || sys->s_lock2) && sys->s_unlockAndPost)
158         return p_library;
159
160     dlclose(p_library);
161     return NULL;
162 }
163
164 static void *InitLibrary(vout_display_sys_t *sys)
165 {
166     static const char *libs[] = {
167         "libsurfaceflinger_client.so",
168         "libgui.so",
169         "libui.so"
170     };
171
172     for (size_t i = 0; i < sizeof(libs) / sizeof(*libs); i++) {
173         void *lib = LoadSurface(libs[i], sys);
174         if (lib)
175             return lib;
176     }
177     return NULL;
178 }
179
180 static void *InitLibrary2(vout_display_sys_t *sys)
181 {
182     void *p_library = dlopen("libandroid.so", RTLD_NOW);
183     if (!p_library)
184         return NULL;
185
186     sys->s_winFromSurface =
187         (ptr_ANativeWindow_fromSurface)(dlsym(p_library, "ANativeWindow_fromSurface"));
188     sys->s_winRelease =
189         (ptr_ANativeWindow_release)(dlsym(p_library, "ANativeWindow_release"));
190     sys->s_winLock =
191         (ptr_ANativeWindow_lock)(dlsym(p_library, "ANativeWindow_lock"));
192     sys->s_unlockAndPost =
193         (Surface_unlockAndPost)(dlsym(p_library, "ANativeWindow_unlockAndPost"));
194
195     if (sys->s_winFromSurface && sys->s_winRelease && sys->s_winLock && sys->s_unlockAndPost)
196         return p_library;
197
198     sys->s_winFromSurface = NULL;
199     sys->s_winRelease = NULL;
200     sys->s_winLock = NULL;
201     sys->s_unlockAndPost = NULL;
202
203     dlclose(p_library);
204     return NULL;
205 }
206
207 static int Open(vlc_object_t *p_this)
208 {
209     vout_display_t *vd = (vout_display_t *)p_this;
210
211     /* */
212     if (vlc_mutex_trylock(&single_instance) != 0) {
213         msg_Err(vd, "Can't start more than one instance at a time");
214         return VLC_EGENERIC;
215     }
216
217     /* Allocate structure */
218     vout_display_sys_t *sys = (struct vout_display_sys_t*) calloc(1, sizeof(*sys));
219     if (!sys) {
220         vlc_mutex_unlock(&single_instance);
221         return VLC_ENOMEM;
222     }
223
224     /* */
225     sys->p_library = InitLibrary2(sys);
226     if (!sys->p_library)
227         sys->p_library = InitLibrary(sys);
228     if (!sys->p_library) {
229         free(sys);
230         msg_Err(vd, "Could not initialize libandroid.so/libui.so/libgui.so/libsurfaceflinger_client.so!");
231         vlc_mutex_unlock(&single_instance);
232         return VLC_EGENERIC;
233     }
234
235     /* Setup chroma */
236     video_format_t fmt = vd->fmt;
237
238     char *psz_fcc = var_InheritString(vd, CFG_PREFIX "chroma");
239     if( psz_fcc ) {
240         fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, psz_fcc);
241         free(psz_fcc);
242     } else
243         fmt.i_chroma = VLC_CODEC_RGB32;
244
245     switch(fmt.i_chroma) {
246         case VLC_CODEC_YV12:
247             /* avoid swscale usage by asking for I420 instead since the
248              * vout already has code to swap the buffers */
249             fmt.i_chroma = VLC_CODEC_I420;
250         case VLC_CODEC_I420:
251             break;
252
253         case VLC_CODEC_RGB16:
254             fmt.i_bmask = 0x0000001f;
255             fmt.i_gmask = 0x000007e0;
256             fmt.i_rmask = 0x0000f800;
257             break;
258
259         case VLC_CODEC_RGB32:
260             fmt.i_rmask  = 0x000000ff;
261             fmt.i_gmask  = 0x0000ff00;
262             fmt.i_bmask  = 0x00ff0000;
263             break;
264
265         default:
266             return VLC_EGENERIC;
267     }
268     video_format_FixRgb(&fmt);
269
270     msg_Dbg(vd, "Pixel format %4.4s", (char*)&fmt.i_chroma);
271     sys->fmt = fmt;
272
273     /* Create the associated picture */
274     picture_sys_t *picsys = malloc(sizeof(*picsys));
275     if (unlikely(picsys == NULL))
276         goto enomem;
277     picsys->sys = sys;
278
279     picture_resource_t resource = { .p_sys = picsys };
280     picture_t *picture = picture_NewFromResource(&fmt, &resource);
281     if (!picture) {
282         free(picsys);
283         goto enomem;
284     }
285
286     /* Wrap it into a picture pool */
287     picture_pool_configuration_t pool_cfg;
288     memset(&pool_cfg, 0, sizeof(pool_cfg));
289     pool_cfg.picture_count = 1;
290     pool_cfg.picture       = &picture;
291     pool_cfg.lock          = AndroidLockSurface;
292     pool_cfg.unlock        = AndroidUnlockSurface;
293
294     sys->pool = picture_pool_NewExtended(&pool_cfg);
295     if (!sys->pool) {
296         picture_Release(picture);
297         goto enomem;
298     }
299
300     /* Setup vout_display */
301     vd->sys     = sys;
302     vd->fmt     = fmt;
303     vd->pool    = Pool;
304     vd->display = Display;
305     vd->control = Control;
306     vd->prepare = NULL;
307     vd->manage  = NULL;
308
309     /* Fix initial state */
310     vout_display_SendEventFullscreen(vd, false);
311
312     sys->i_sar_num = vd->source.i_sar_num;
313     sys->i_sar_den = vd->source.i_sar_den;
314
315     return VLC_SUCCESS;
316
317 enomem:
318     dlclose(sys->p_library);
319     free(sys);
320     vlc_mutex_unlock(&single_instance);
321     return VLC_ENOMEM;
322 }
323
324 static void Close(vlc_object_t *p_this)
325 {
326     vout_display_t *vd = (vout_display_t *)p_this;
327     vout_display_sys_t *sys = vd->sys;
328
329     picture_pool_Delete(sys->pool);
330     if (sys->window)
331         sys->s_winRelease(sys->window);
332     dlclose(sys->p_library);
333     free(sys);
334     vlc_mutex_unlock(&single_instance);
335 }
336
337 static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
338 {
339     VLC_UNUSED(count);
340
341     return vd->sys->pool;
342 }
343
344 #define ALIGN_16_PIXELS( x ) ( ( ( x ) + 15 ) / 16 * 16 )
345 static void SetupPictureYV12( SurfaceInfo* p_surfaceInfo, picture_t *p_picture )
346 {
347     /* according to document of android.graphics.ImageFormat.YV12 */
348     int i_stride = ALIGN_16_PIXELS( p_surfaceInfo->s );
349     int i_c_stride = ALIGN_16_PIXELS( i_stride / 2 );
350
351     p_picture->p->i_pitch = i_stride;
352
353     /* Fill chroma planes for planar YUV */
354     for( int n = 1; n < p_picture->i_planes; n++ )
355     {
356         const plane_t *o = &p_picture->p[n-1];
357         plane_t *p = &p_picture->p[n];
358
359         p->p_pixels = o->p_pixels + o->i_lines * o->i_pitch;
360         p->i_pitch  = i_c_stride;
361         p->i_lines  = p_picture->format.i_height / 2;
362     }
363
364     if( vlc_fourcc_AreUVPlanesSwapped( p_picture->format.i_chroma,
365                                        VLC_CODEC_YV12 ) ) {
366         uint8_t *p_tmp = p_picture->p[1].p_pixels;
367         p_picture->p[1].p_pixels = p_picture->p[2].p_pixels;
368         p_picture->p[2].p_pixels = p_tmp;
369     }
370 }
371
372 static int  AndroidLockSurface(picture_t *picture)
373 {
374     picture_sys_t *picsys = picture->p_sys;
375     vout_display_sys_t *sys = picsys->sys;
376     SurfaceInfo *info;
377     uint32_t sw, sh;
378     void *surf;
379
380     sw = sys->fmt.i_width;
381     sh = sys->fmt.i_height;
382
383     if (sys->s_winFromSurface) {
384         jobject jsurf = jni_LockAndGetAndroidJavaSurface();
385         if (unlikely(!jsurf)) {
386             jni_UnlockAndroidSurface();
387             return VLC_EGENERIC;
388         }
389         if (sys->window && jsurf != sys->jsurf) {
390             sys->s_winRelease(sys->window);
391             sys->window = NULL;
392         }
393         sys->jsurf = jsurf;
394         if (!sys->window) {
395             JNIEnv *p_env;
396             (*myVm)->AttachCurrentThread(myVm, &p_env, NULL);
397             sys->window = sys->s_winFromSurface(p_env, jsurf);
398             (*myVm)->DetachCurrentThread(myVm);
399         }
400         // Using sys->window instead of the native surface object
401         // as parameter to the unlock function
402         picsys->surf = surf = sys->window;
403     } else {
404         picsys->surf = surf = jni_LockAndGetAndroidSurface();
405         if (unlikely(!surf)) {
406             jni_UnlockAndroidSurface();
407             return VLC_EGENERIC;
408         }
409     }
410     info = &picsys->info;
411
412     if (sys->s_winLock) {
413         ANativeWindow_Buffer buf = { 0 };
414         sys->s_winLock(sys->window, &buf, NULL);
415         info->w      = buf.width;
416         info->h      = buf.height;
417         info->bits   = buf.bits;
418         info->s      = buf.stride;
419         info->format = buf.format;
420     } else if (sys->s_lock)
421         sys->s_lock(surf, info, 1);
422     else
423         sys->s_lock2(surf, info, NULL);
424
425     // For RGB (32 or 16) we need to align on 8 or 4 pixels, 16 pixels for YUV
426     int align_pixels = (16 / picture->p[0].i_pixel_pitch) - 1;
427     uint32_t aligned_width = (sw + align_pixels) & ~align_pixels;
428
429     if (info->w != aligned_width || info->h != sh || sys->b_changed_crop) {
430         // input size doesn't match the surface size -> request a resize
431         jni_SetAndroidSurfaceSize(aligned_width, sh, sys->fmt.i_visible_width, sys->fmt.i_visible_height, sys->i_sar_num, sys->i_sar_den);
432         // When using ANativeWindow, one should use ANativeWindow_setBuffersGeometry
433         // to set the size and format. In our case, these are set via the SurfaceHolder
434         // in Java, so we seem to manage without calling this ANativeWindow function.
435         sys->s_unlockAndPost(surf);
436         jni_UnlockAndroidSurface();
437         sys->b_changed_crop = false;
438         return VLC_EGENERIC;
439     }
440
441     picture->p[0].p_pixels = (uint8_t*)info->bits;
442     picture->p[0].i_lines = info->h;
443     picture->p[0].i_pitch = picture->p[0].i_pixel_pitch * info->s;
444
445     if (info->format == 0x32315659 /*ANDROID_IMAGE_FORMAT_YV12*/)
446         SetupPictureYV12(info, picture);
447
448     return VLC_SUCCESS;
449 }
450
451 static void AndroidUnlockSurface(picture_t *picture)
452 {
453     picture_sys_t *picsys = picture->p_sys;
454     vout_display_sys_t *sys = picsys->sys;
455
456     if (likely(picsys->surf))
457         sys->s_unlockAndPost(picsys->surf);
458     jni_UnlockAndroidSurface();
459 }
460
461 static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
462 {
463     VLC_UNUSED(vd);
464     VLC_UNUSED(subpicture);
465
466     /* refcount lowers to 0, and pool_cfg.unlock is called */
467
468     picture_Release(picture);
469 }
470
471 static int Control(vout_display_t *vd, int query, va_list args)
472 {
473     VLC_UNUSED(args);
474
475     switch (query) {
476     case VOUT_DISPLAY_HIDE_MOUSE:
477         return VLC_SUCCESS;
478
479     case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
480     {
481         if (!vd->sys)
482             return VLC_EGENERIC;
483         vout_display_sys_t *sys = vd->sys;
484
485         const video_format_t *source = (const video_format_t *)va_arg(args, const video_format_t *);
486         sys->fmt = *source;
487         sys->b_changed_crop = true;
488         return VLC_SUCCESS;
489     }
490
491     default:
492         msg_Err(vd, "Unknown request in android vout display");
493
494     case VOUT_DISPLAY_CHANGE_FULLSCREEN:
495     case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
496     case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
497     case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
498     case VOUT_DISPLAY_CHANGE_ZOOM:
499     case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
500     case VOUT_DISPLAY_GET_OPENGL:
501         return VLC_EGENERIC;
502     }
503 }