androidsurface.c 14.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
/*****************************************************************************
 * androidsurface.c: android video output using Surface Flinger
 *****************************************************************************
 * Copyright © 2011 VideoLAN
 *
 * Authors: Ming Hu <tewilove@gmail.com>
 *          Ludovic Fauvet <etix@l0cal.com>
 *          Sébastien Toque <xilasz@gmail.com>
 *
10
11
12
13
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (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
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Lesser General Public License for more details.
19
 *
20
21
22
23
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
24
25
26
27
28
29
30
31
32
33
34

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_vout_display.h>
#include <vlc_picture_pool.h>

#include <dlfcn.h>
35
36
37
#include <android/native_window.h>
#include <jni.h>
#include <android/native_window_jni.h>
38
39
40
41

#ifndef ANDROID_SYM_S_LOCK
# define ANDROID_SYM_S_LOCK "_ZN7android7Surface4lockEPNS0_11SurfaceInfoEb"
#endif
42
43
44
#ifndef ANDROID_SYM_S_LOCK2
# define ANDROID_SYM_S_LOCK2 "_ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionE"
#endif
45
46
47
48
49
50
51
#ifndef ANDROID_SYM_S_UNLOCK
# define ANDROID_SYM_S_UNLOCK "_ZN7android7Surface13unlockAndPostEv"
#endif

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
52
53
54
55
56
#define CHROMA_TEXT N_("Chroma used")
#define CHROMA_LONGTEXT N_(\
    "Force use of a specific chroma for output. Default is RGB32.")

#define CFG_PREFIX "androidsurface-"
57
58
59
60
61
62
63
64
65
66
67

static int  Open (vlc_object_t *);
static void Close(vlc_object_t *);

vlc_module_begin()
    set_category(CAT_VIDEO)
    set_subcategory(SUBCAT_VIDEO_VOUT)
    set_shortname("AndroidSurface")
    set_description(N_("Android Surface video output"))
    set_capability("vout display", 155)
    add_shortcut("androidsurface", "android")
68
    add_string(CFG_PREFIX "chroma", NULL, CHROMA_TEXT, CHROMA_LONGTEXT, true)
69
70
71
72
73
74
75
    set_callbacks(Open, Close)
vlc_module_end()

/*****************************************************************************
 * JNI prototypes
 *****************************************************************************/

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 sar_num, int sar_den);
81
82
83

// _ZN7android7Surface4lockEPNS0_11SurfaceInfoEb
typedef void (*Surface_lock)(void *, void *, int);
84
85
// _ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionE
typedef void (*Surface_lock2)(void *, void *, void *);
86
87
88
// _ZN7android7Surface13unlockAndPostEv
typedef void (*Surface_unlockAndPost)(void *);

89
90
91
92
93
typedef ANativeWindow* (*ptr_ANativeWindow_fromSurface)(JNIEnv*, jobject);
typedef void (*ptr_ANativeWindow_release)(ANativeWindow*);
typedef int32_t (*ptr_ANativeWindow_lock)(ANativeWindow*, ANativeWindow_Buffer*, ARect*);
// Just using the normal Surface_unlockAndPost as prototype for ANativeWindow_unlockAndPost

94
95
96
97
98
99
100
101
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/

static picture_pool_t   *Pool  (vout_display_t *, unsigned);
static void             Display(vout_display_t *, picture_t *, subpicture_t *);
static int              Control(vout_display_t *, int, va_list);

102
103
104
105
106
107
108
109
110
111
112
/* */
typedef struct _SurfaceInfo {
    uint32_t    w;
    uint32_t    h;
    uint32_t    s;
    uint32_t    usage;
    uint32_t    format;
    uint32_t*   bits;
    uint32_t    reserved[2];
} SurfaceInfo;

113
114
115
116
117
/* */
struct vout_display_sys_t {
    picture_pool_t *pool;
    void *p_library;
    Surface_lock s_lock;
118
    Surface_lock2 s_lock2;
119
    Surface_unlockAndPost s_unlockAndPost;
120
121
122
123
124
125
    ptr_ANativeWindow_fromSurface s_winFromSurface;
    ptr_ANativeWindow_release s_winRelease;
    ptr_ANativeWindow_lock s_winLock;

    jobject jsurf;
    ANativeWindow *window;
126

127
128
129
    /* density */
    int i_sar_num;
    int i_sar_den;
130
131
};

Rafaël Carré's avatar
Rafaël Carré committed
132
struct picture_sys_t {
133
134
135
136
137
138
139
140
141
142
    void *surf;
    SurfaceInfo info;
    vout_display_sys_t *sys;
};

static int  AndroidLockSurface(picture_t *);
static void AndroidUnlockSurface(picture_t *);

static vlc_mutex_t single_instance = VLC_STATIC_MUTEX;

Rafaël Carré's avatar
Rafaël Carré committed
143
144
static inline void *LoadSurface(const char *psz_lib, vout_display_sys_t *sys)
{
145
    void *p_library = dlopen(psz_lib, RTLD_NOW);
146
147
148
149
150
151
152
153
154
155
156
157
    if (!p_library)
        return NULL;

    sys->s_lock = (Surface_lock)(dlsym(p_library, ANDROID_SYM_S_LOCK));
    sys->s_lock2 = (Surface_lock2)(dlsym(p_library, ANDROID_SYM_S_LOCK2));
    sys->s_unlockAndPost =
        (Surface_unlockAndPost)(dlsym(p_library, ANDROID_SYM_S_UNLOCK));

    if ((sys->s_lock || sys->s_lock2) && sys->s_unlockAndPost)
        return p_library;

    dlclose(p_library);
158
159
160
    return NULL;
}

Rafaël Carré's avatar
Rafaël Carré committed
161
162
static void *InitLibrary(vout_display_sys_t *sys)
{
163
164
165
166
167
168
169
170
171
172
173
174
    static const char *libs[] = {
        "libsurfaceflinger_client.so",
        "libgui.so",
        "libui.so"
    };

    for (size_t i = 0; i < sizeof(libs) / sizeof(*libs); i++) {
        void *lib = LoadSurface(libs[i], sys);
        if (lib)
            return lib;
    }
    return NULL;
175
176
}

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
static void *InitLibrary2(vout_display_sys_t *sys)
{
    void *p_library = dlopen("libandroid.so", RTLD_NOW);
    if (!p_library)
        return NULL;

    sys->s_winFromSurface =
        (ptr_ANativeWindow_fromSurface)(dlsym(p_library, "ANativeWindow_fromSurface"));
    sys->s_winRelease =
        (ptr_ANativeWindow_release)(dlsym(p_library, "ANativeWindow_release"));
    sys->s_winLock =
        (ptr_ANativeWindow_lock)(dlsym(p_library, "ANativeWindow_lock"));
    sys->s_unlockAndPost =
        (Surface_unlockAndPost)(dlsym(p_library, "ANativeWindow_unlockAndPost"));

    if (sys->s_winFromSurface && sys->s_winRelease && sys->s_winLock && sys->s_unlockAndPost)
        return p_library;

    sys->s_winFromSurface = NULL;
    sys->s_winRelease = NULL;
    sys->s_winLock = NULL;
    sys->s_unlockAndPost = NULL;

    dlclose(p_library);
    return NULL;
}

Rafaël Carré's avatar
Rafaël Carré committed
204
205
static int Open(vlc_object_t *p_this)
{
206
207
208
209
210
211
212
213
214
    vout_display_t *vd = (vout_display_t *)p_this;

    /* */
    if (vlc_mutex_trylock(&single_instance) != 0) {
        msg_Err(vd, "Can't start more than one instance at a time");
        return VLC_EGENERIC;
    }

    /* Allocate structure */
215
    vout_display_sys_t *sys = (struct vout_display_sys_t*) calloc(1, sizeof(*sys));
216
217
218
219
220
221
    if (!sys) {
        vlc_mutex_unlock(&single_instance);
        return VLC_ENOMEM;
    }

    /* */
222
    sys->p_library = InitLibrary(sys);
223
    if (!sys->p_library)
224
        sys->p_library = InitLibrary2(sys);
225
    if (!sys->p_library) {
226
        free(sys);
227
        msg_Err(vd, "Could not initialize libandroid.so/libui.so/libgui.so/libsurfaceflinger_client.so!");
228
229
230
231
232
233
        vlc_mutex_unlock(&single_instance);
        return VLC_EGENERIC;
    }

    /* Setup chroma */
    video_format_t fmt = vd->fmt;
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

    char *psz_fcc = var_InheritString(vd, CFG_PREFIX "chroma");
    if( psz_fcc ) {
        fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, psz_fcc);
        free(psz_fcc);
    } else
        fmt.i_chroma = VLC_CODEC_RGB32;

    switch(fmt.i_chroma) {
        case VLC_CODEC_YV12:
            /* avoid swscale usage by asking for I420 instead since the
             * vout already has code to swap the buffers */
            fmt.i_chroma = VLC_CODEC_I420;
        case VLC_CODEC_I420:
            break;

        case VLC_CODEC_RGB16:
            fmt.i_bmask = 0x0000001f;
            fmt.i_gmask = 0x000007e0;
            fmt.i_rmask = 0x0000f800;
            break;

        case VLC_CODEC_RGB32:
            fmt.i_rmask  = 0x000000ff;
            fmt.i_gmask  = 0x0000ff00;
            fmt.i_bmask  = 0x00ff0000;
            break;

        default:
            return VLC_EGENERIC;
    }
265
266
    video_format_FixRgb(&fmt);

267
    msg_Dbg(vd, "Pixel format %4.4s", (char*)&fmt.i_chroma);
268

269
    /* Create the associated picture */
270
    picture_sys_t *picsys = malloc(sizeof(*picsys));
271
    if (unlikely(picsys == NULL))
272
        goto enomem;
273
274
    picsys->sys = sys;

275
276
    picture_resource_t resource = { .p_sys = picsys };
    picture_t *picture = picture_NewFromResource(&fmt, &resource);
277
278
    if (!picture) {
        free(picsys);
279
        goto enomem;
280
    }
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307

    /* Wrap it into a picture pool */
    picture_pool_configuration_t pool_cfg;
    memset(&pool_cfg, 0, sizeof(pool_cfg));
    pool_cfg.picture_count = 1;
    pool_cfg.picture       = &picture;
    pool_cfg.lock          = AndroidLockSurface;
    pool_cfg.unlock        = AndroidUnlockSurface;

    sys->pool = picture_pool_NewExtended(&pool_cfg);
    if (!sys->pool) {
        picture_Release(picture);
        goto enomem;
    }

    /* Setup vout_display */
    vd->sys     = sys;
    vd->fmt     = fmt;
    vd->pool    = Pool;
    vd->display = Display;
    vd->control = Control;
    vd->prepare = NULL;
    vd->manage  = NULL;

    /* Fix initial state */
    vout_display_SendEventFullscreen(vd, false);

308
309
310
    sys->i_sar_num = vd->source.i_sar_num;
    sys->i_sar_den = vd->source.i_sar_den;

311
312
313
    return VLC_SUCCESS;

enomem:
314
    dlclose(sys->p_library);
315
316
317
318
319
    free(sys);
    vlc_mutex_unlock(&single_instance);
    return VLC_ENOMEM;
}

Rafaël Carré's avatar
Rafaël Carré committed
320
321
static void Close(vlc_object_t *p_this)
{
322
323
324
325
326
    vout_display_t *vd = (vout_display_t *)p_this;
    vout_display_sys_t *sys = vd->sys;

    picture_pool_Delete(sys->pool);
    dlclose(sys->p_library);
327
328
    if (sys->window)
        sys->s_winRelease(sys->window);
329
330
331
332
    free(sys);
    vlc_mutex_unlock(&single_instance);
}

Rafaël Carré's avatar
Rafaël Carré committed
333
334
static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
{
335
    VLC_UNUSED(count);
336
337

    return vd->sys->pool;
338
339
}

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
#define ALIGN_16_PIXELS( x ) ( ( ( x ) + 15 ) / 16 * 16 )
static void SetupPictureYV12( SurfaceInfo* p_surfaceInfo, picture_t *p_picture )
{
    /* according to document of android.graphics.ImageFormat.YV12 */
    int i_stride = ALIGN_16_PIXELS( p_surfaceInfo->s );
    int i_c_stride = ALIGN_16_PIXELS( i_stride / 2 );

    p_picture->p->i_pitch = i_stride;

    /* Fill chroma planes for planar YUV */
    for( int n = 1; n < p_picture->i_planes; n++ )
    {
        const plane_t *o = &p_picture->p[n-1];
        plane_t *p = &p_picture->p[n];

        p->p_pixels = o->p_pixels + o->i_lines * o->i_pitch;
        p->i_pitch  = i_c_stride;
        p->i_lines  = p_picture->format.i_height / 2;
    }

    if( vlc_fourcc_AreUVPlanesSwapped( p_picture->format.i_chroma,
                                       VLC_CODEC_YV12 ) ) {
        uint8_t *p_tmp = p_picture->p[1].p_pixels;
        p_picture->p[1].p_pixels = p_picture->p[2].p_pixels;
        p_picture->p[2].p_pixels = p_tmp;
    }
}

Rafaël Carré's avatar
Rafaël Carré committed
368
369
static int  AndroidLockSurface(picture_t *picture)
{
370
371
372
373
374
375
376
377
378
    picture_sys_t *picsys = picture->p_sys;
    vout_display_sys_t *sys = picsys->sys;
    SurfaceInfo *info;
    uint32_t sw, sh;
    void *surf;

    sw = picture->p[0].i_visible_pitch / picture->p[0].i_pixel_pitch;
    sh = picture->p[0].i_visible_lines;

379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
    if (sys->s_winFromSurface) {
        jobject jsurf = jni_LockAndGetAndroidJavaSurface();
        if (unlikely(!jsurf)) {
            jni_UnlockAndroidSurface();
            return VLC_EGENERIC;
        }
        if (sys->window && jsurf != sys->jsurf) {
            sys->s_winRelease(sys->window);
            sys->window = NULL;
        }
        sys->jsurf = jsurf;
        if (!sys->window) {
            JNIEnv *p_env;
            (*myVm)->AttachCurrentThread(myVm, &p_env, NULL);
            sys->window = sys->s_winFromSurface(p_env, jsurf);
            (*myVm)->DetachCurrentThread(myVm);
        }
        // Using sys->window instead of the native surface object
        // as parameter to the unlock function
        picsys->surf = surf = sys->window;
    } else {
        picsys->surf = surf = jni_LockAndGetAndroidSurface();
        if (unlikely(!surf)) {
            jni_UnlockAndroidSurface();
            return VLC_EGENERIC;
        }
405
    }
406
407
408
409
410
411
412
413
414
415
416
    info = &picsys->info;

    if (sys->s_winLock) {
        ANativeWindow_Buffer buf = { 0 };
        sys->s_winLock(sys->window, &buf, NULL);
        info->w      = buf.width;
        info->h      = buf.height;
        info->bits   = buf.bits;
        info->s      = buf.stride;
        info->format = buf.format;
    } else if (sys->s_lock)
417
418
419
        sys->s_lock(surf, info, 1);
    else
        sys->s_lock2(surf, info, NULL);
420

421
422
423
424
425
426
    // For RGB (32 or 16) we need to align on 8 or 4 pixels, 16 pixels for YUV
    int align_pixels = (16 / picture->p[0].i_pixel_pitch) - 1;
    uint32_t aligned_width = (sw + align_pixels) & ~align_pixels;

    if (info->w != aligned_width || info->h != sh) {
        // input size doesn't match the surface size -> request a resize
427
        jni_SetAndroidSurfaceSize(sw, sh, sys->i_sar_num, sys->i_sar_den);
428
429
430
        // When using ANativeWindow, one should use ANativeWindow_setBuffersGeometry
        // to set the size and format. In our case, these are set via the SurfaceHolder
        // in Java, so we seem to manage without calling this ANativeWindow function.
431
432
433
434
435
        sys->s_unlockAndPost(surf);
        jni_UnlockAndroidSurface();
        return VLC_EGENERIC;
    }

436
437
438
    picture->p[0].p_pixels = (uint8_t*)info->bits;
    picture->p[0].i_lines = info->h;
    picture->p[0].i_pitch = picture->p[0].i_pixel_pitch * info->s;
439
440
441

    if (info->format == 0x32315659 /*ANDROID_IMAGE_FORMAT_YV12*/)
        SetupPictureYV12(info, picture);
442
443
444
445

    return VLC_SUCCESS;
}

Rafaël Carré's avatar
Rafaël Carré committed
446
447
static void AndroidUnlockSurface(picture_t *picture)
{
448
449
450
451
452
453
454
455
    picture_sys_t *picsys = picture->p_sys;
    vout_display_sys_t *sys = picsys->sys;

    if (likely(picsys->surf))
        sys->s_unlockAndPost(picsys->surf);
    jni_UnlockAndroidSurface();
}

Rafaël Carré's avatar
Rafaël Carré committed
456
457
static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
{
458
459
    VLC_UNUSED(vd);
    VLC_UNUSED(subpicture);
460
461
462

    /* refcount lowers to 0, and pool_cfg.unlock is called */

463
464
465
    picture_Release(picture);
}

Rafaël Carré's avatar
Rafaël Carré committed
466
467
static int Control(vout_display_t *vd, int query, va_list args)
{
468
469
470
    VLC_UNUSED(args);

    switch (query) {
Rafaël Carré's avatar
Rafaël Carré committed
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
    case VOUT_DISPLAY_HIDE_MOUSE:
        return VLC_SUCCESS;

    default:
        msg_Err(vd, "Unknown request in android vout display");

    case VOUT_DISPLAY_CHANGE_FULLSCREEN:
    case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
    case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
    case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
    case VOUT_DISPLAY_CHANGE_ZOOM:
    case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
    case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
    case VOUT_DISPLAY_GET_OPENGL:
        return VLC_EGENERIC;
486
487
    }
}