]> git.sesse.net Git - vlc/blob - modules/video_output/android/opaque.c
android/opaque: fix crash on Close with subtitles
[vlc] / modules / video_output / android / opaque.c
1 /*****************************************************************************
2  * opaque.c: Android video output module using direct rendering with
3  * opaque buffers
4  *****************************************************************************
5  * Copyright (C) 2013 Felix Abecassis
6  *
7  * Authors: Felix Abecassis <felix.abecassis@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_vout_display.h>
31 #include <vlc_picture_pool.h>
32 #include <vlc_filter.h>
33 #include <vlc_md5.h>
34
35 #include <dlfcn.h>
36
37 #include "../codec/omxil/android_opaque.h"
38 #include "utils.h"
39
40 static int  Open (vlc_object_t *);
41 static void Close(vlc_object_t *);
42
43 vlc_module_begin()
44     set_category(CAT_VIDEO)
45     set_subcategory(SUBCAT_VIDEO_VOUT)
46     set_shortname("vout_mediacodec")
47     set_description(N_("Android MediaCodec direct rendering video output"))
48     set_capability("vout display", 200)
49     add_shortcut("androidsurface", "android")
50     set_callbacks(Open, Close)
51 vlc_module_end()
52
53 #define THREAD_NAME "vout_mediacodec"
54 extern int jni_attach_thread(JNIEnv **env, const char *thread_name);
55 extern void jni_detach_thread();
56 extern jobject jni_LockAndGetSubtitlesSurface();
57 extern void  jni_UnlockAndroidSurface();
58
59 static const vlc_fourcc_t subpicture_chromas[] =
60 {
61     VLC_CODEC_RGBA,
62     0
63 };
64
65 /*****************************************************************************
66  * Local prototypes
67  *****************************************************************************/
68
69 static picture_pool_t   *Pool  (vout_display_t *, unsigned);
70 static void             Display(vout_display_t *, picture_t *, subpicture_t *);
71 static int              Control(vout_display_t *, int, va_list);
72
73 struct vout_display_sys_t
74 {
75     picture_pool_t *pool;
76
77     void *p_library;
78     native_window_api_t native_window;
79
80     jobject jsurf;
81     ANativeWindow *window;
82
83     video_format_t fmt;
84
85     filter_t *p_spu_blend;
86     picture_t *subtitles_picture;
87
88     bool b_has_subpictures;
89
90     uint8_t hash[16];
91 };
92
93 static void DisplaySubpicture(vout_display_t *vd, subpicture_t *subpicture)
94 {
95     vout_display_sys_t *sys = vd->sys;
96
97     struct md5_s hash;
98     InitMD5(&hash);
99     if (subpicture) {
100         for (subpicture_region_t *r = subpicture->p_region; r != NULL; r = r->p_next) {
101             AddMD5(&hash, &r->i_x, sizeof(r->i_x));
102             AddMD5(&hash, &r->i_y, sizeof(r->i_y));
103             AddMD5(&hash, &r->fmt.i_visible_width, sizeof(r->fmt.i_visible_width));
104             AddMD5(&hash, &r->fmt.i_visible_height, sizeof(r->fmt.i_visible_height));
105             AddMD5(&hash, &r->fmt.i_x_offset, sizeof(r->fmt.i_x_offset));
106             AddMD5(&hash, &r->fmt.i_y_offset, sizeof(r->fmt.i_y_offset));
107             const int pixels_offset = r->fmt.i_y_offset * r->p_picture->p->i_pitch +
108                                       r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch;
109
110             for (int y = 0; y < r->fmt.i_visible_height; y++)
111                 AddMD5(&hash, &r->p_picture->p->p_pixels[pixels_offset + y*r->p_picture->p->i_pitch], r->fmt.i_visible_width);
112         }
113     }
114     EndMD5(&hash);
115     if (!memcmp(hash.buf, sys->hash, 16))
116         return;
117     memcpy(sys->hash, hash.buf, 16);
118
119     jobject jsurf = jni_LockAndGetSubtitlesSurface();
120     if (sys->window && jsurf != sys->jsurf)
121     {
122         sys->native_window.winRelease(sys->window);
123         sys->window = NULL;
124     }
125     sys->jsurf = jsurf;
126     if (!sys->window)
127     {
128         JNIEnv *p_env;
129         jni_attach_thread(&p_env, THREAD_NAME);
130         sys->window = sys->native_window.winFromSurface(p_env, jsurf);
131         jni_detach_thread();
132     }
133
134     ANativeWindow_Buffer buf = { 0 };
135     int32_t err = sys->native_window.winLock(sys->window, &buf, NULL);
136     if (err) {
137         jni_UnlockAndroidSurface();
138         return;
139     }
140
141     if (buf.width >= sys->fmt.i_width && buf.height >= sys->fmt.i_height)
142     {
143         /* Wrap the NativeWindow corresponding to the subtitles surface in a picture_t */
144         picture_t *picture = sys->subtitles_picture;
145         picture->p[0].p_pixels = (uint8_t*)buf.bits;
146         picture->p[0].i_lines = buf.height;
147         picture->p[0].i_pitch = picture->p[0].i_pixel_pitch * buf.stride;
148         /* Clear the subtitles surface. */
149         memset(picture->p[0].p_pixels, 0, picture->p[0].i_pitch * picture->p[0].i_lines);
150         if (subpicture)
151         {
152             /* Allocate a blending filter if needed. */
153             if (unlikely(!sys->p_spu_blend))
154                 sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd), &picture->format);
155             picture_BlendSubpicture(picture, sys->p_spu_blend, subpicture);
156         }
157     }
158
159     sys->native_window.unlockAndPost(sys->window);
160     jni_UnlockAndroidSurface();
161 }
162
163 static int  LockSurface(picture_t *);
164 static void UnlockSurface(picture_t *);
165
166 /* We need to allocate a picture pool of more than 30 buffers in order
167  * to be connected directly to the decoder without any intermediate
168  * buffer pool. */
169 #define POOL_SIZE 31
170
171 static int Open(vlc_object_t *p_this)
172 {
173     vout_display_t *vd = (vout_display_t*)p_this;
174
175     video_format_t fmt = vd->fmt;
176
177     if (fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE)
178         return VLC_EGENERIC;
179     if (vout_display_IsWindowed(vd))
180         return VLC_EGENERIC;
181
182     /* Allocate structure */
183     vout_display_sys_t *sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys));
184     if (!sys)
185         return VLC_ENOMEM;
186
187     sys->p_library = LoadNativeWindowAPI(&sys->native_window);
188     if (!sys->p_library)
189     {
190         free(sys);
191         msg_Err(vd, "Could not initialize NativeWindow API.");
192         return VLC_EGENERIC;
193     }
194     sys->fmt = fmt;
195     video_format_t subpicture_format = sys->fmt;
196     subpicture_format.i_chroma = VLC_CODEC_RGBA;
197     /* Create a RGBA picture for rendering subtitles. */
198     picture_resource_t rsc;
199     memset(&rsc, 0, sizeof(rsc));
200     sys->subtitles_picture = picture_NewFromResource(&subpicture_format, &rsc);
201
202     /* Export the subpicture capability of this vout. */
203     vd->info.subpicture_chromas = subpicture_chromas;
204
205     int i_pictures = POOL_SIZE;
206     picture_t** pictures = calloc(sizeof(*pictures), i_pictures);
207     if (!pictures)
208         goto error;
209     for (int i = 0; i < i_pictures; i++)
210     {
211         picture_sys_t *p_picsys = calloc(1, sizeof(*p_picsys));
212         if (unlikely(p_picsys == NULL))
213             goto error;
214
215         picture_resource_t resource = { .p_sys = p_picsys };
216         picture_t *picture = picture_NewFromResource(&fmt, &resource);
217         if (!picture)
218         {
219             free(p_picsys);
220             goto error;
221         }
222         pictures[i] = picture;
223     }
224
225     /* Wrap it into a picture pool */
226     picture_pool_configuration_t pool_cfg;
227     memset(&pool_cfg, 0, sizeof(pool_cfg));
228     pool_cfg.picture_count = i_pictures;
229     pool_cfg.picture       = pictures;
230     pool_cfg.lock          = LockSurface;
231     pool_cfg.unlock        = UnlockSurface;
232
233     sys->pool = picture_pool_NewExtended(&pool_cfg);
234     if (!sys->pool)
235     {
236         for (int i = 0; i < i_pictures; i++)
237             picture_Release(pictures[i]);
238         goto error;
239     }
240
241     /* Setup vout_display */
242     vd->sys     = sys;
243     vd->fmt     = fmt;
244     vd->pool    = Pool;
245     vd->display = Display;
246     vd->control = Control;
247     vd->prepare = NULL;
248     vd->manage  = Manage;
249
250     /* Fix initial state */
251     vout_display_SendEventFullscreen(vd, false);
252
253     return VLC_SUCCESS;
254
255 error:
256     free(pictures);
257     Close(p_this);
258     return VLC_ENOMEM;
259 }
260
261 static void Close(vlc_object_t *p_this)
262 {
263     vout_display_t *vd = (vout_display_t *)p_this;
264     vout_display_sys_t *sys = vd->sys;
265
266     picture_pool_Delete(sys->pool);
267     if (sys->window)
268         sys->native_window.winRelease(sys->window);
269     dlclose(sys->p_library);
270     if (sys->subtitles_picture)
271         picture_Release(sys->subtitles_picture);
272     if (sys->p_spu_blend)
273         filter_DeleteBlend(sys->p_spu_blend);
274     free(sys);
275 }
276
277 static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
278 {
279     VLC_UNUSED(count);
280
281     return vd->sys->pool;
282 }
283
284 static int LockSurface(picture_t *picture)
285 {
286     VLC_UNUSED(picture);
287
288     return VLC_SUCCESS;
289 }
290
291 static void UnlockSurface(picture_t *picture)
292 {
293     picture_sys_t *p_picsys = picture->p_sys;
294     void (*unlock_callback)(picture_sys_t*) = p_picsys->pf_unlock_callback;
295     if (unlock_callback)
296         unlock_callback(p_picsys);
297 }
298
299 static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
300 {
301     VLC_UNUSED(vd);
302     VLC_UNUSED(subpicture);
303
304     picture_sys_t *p_picsys = picture->p_sys;
305     vout_display_sys_t *sys = vd->sys;
306     void (*display_callback)(picture_sys_t*) = p_picsys->pf_display_callback;
307     if (display_callback)
308         display_callback(p_picsys);
309
310     if (subpicture && sys->subtitles_picture)
311         sys->b_has_subpictures = true;
312     /* As long as no subpicture was received, do not call
313        DisplaySubpicture since JNI calls and clearing the subtitles
314        surface are expensive operations. */
315     if (sys->b_has_subpictures)
316     {
317         DisplaySubpicture(vd, subpicture);
318         if (!subpicture)
319         {
320             /* The surface has been cleared and there is no new
321                subpicture to upload, do not clear again until a new
322                subpicture is received. */
323             sys->b_has_subpictures = false;
324         }
325     }
326
327     /* refcount lowers to 0, and pool_cfg.unlock is called */
328     picture_Release(picture);
329     if (subpicture)
330         subpicture_Delete(subpicture);
331 }
332
333 static int Control(vout_display_t *vd, int query, va_list args)
334 {
335     VLC_UNUSED(args);
336
337     switch (query) {
338     case VOUT_DISPLAY_HIDE_MOUSE:
339         return VLC_SUCCESS;
340
341     default:
342         msg_Err(vd, "Unknown request in vout mediacodec display");
343
344     case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
345     case VOUT_DISPLAY_CHANGE_FULLSCREEN:
346     case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
347     case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
348     case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
349     case VOUT_DISPLAY_CHANGE_ZOOM:
350     case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
351         return VLC_EGENERIC;
352     }
353 }