diff --git a/configure.ac b/configure.ac index 985c891657c76399ef9d8b359bf5278f1ddd1a07..7babe954dc66f9067471db879ce64439e4126387 100644 --- a/configure.ac +++ b/configure.ac @@ -3364,7 +3364,7 @@ AC_ARG_ENABLE(android-surface, [ --enable-android-surface Android Surface video output module (default disabled)]) if test "${enable_android_surface}" = "yes"; then VLC_ADD_PLUGIN([android_surface]) - VLC_ADD_PLUGIN([android_opaque]) + VLC_ADD_PLUGIN([android_window]) fi dnl diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST index 2cd3f61955f401134080e80c295c0e08f042d0d5..6d93386e7445122a2060faa8e2453fea986d2b72 100644 --- a/modules/MODULES_LIST +++ b/modules/MODULES_LIST @@ -33,8 +33,8 @@ $Id$ * anaglyph: anaglyph 3d video filter * android_audiotrack: audio output for Android, based on AudioTrack * android_native_window: Android native window provider module - * android_opaque: Android direct GPU rendering video output * android_surface: video output for Android, based on Surface + * android_window: Android direct/undirect rendering video output * antiflicker: anti-flicker video filter * araw: Pseudo audio decoder for raw PCM * aribcam: ARIB STD-B25 decoder/virtual CAM diff --git a/modules/codec/omxil/android_mediacodec.c b/modules/codec/omxil/android_mediacodec.c index 1eeee694f0fb8f6a5d308f8bc4802239a1faee85..d248c0836beee4da2886c88810e8ef1d86bb66a9 100644 --- a/modules/codec/omxil/android_mediacodec.c +++ b/modules/codec/omxil/android_mediacodec.c @@ -42,6 +42,7 @@ #include <OMX_Component.h> #include "omxil_utils.h" #include "android_opaque.h" +#include "../../video_output/android/android_window.h" #define INFO_OUTPUT_BUFFERS_CHANGED -3 #define INFO_OUTPUT_FORMAT_CHANGED -2 @@ -617,23 +618,25 @@ static void CloseDecoder(vlc_object_t *p_this) /***************************************************************************** * vout callbacks *****************************************************************************/ -static void DisplayBuffer(picture_sys_t* p_picsys, bool b_render) +static void UnlockPicture(picture_t* p_pic) { - decoder_t *p_dec = p_picsys->p_dec; + picture_sys_t *p_picsys = p_pic->p_sys; + decoder_t *p_dec = p_picsys->priv.hw.p_dec; decoder_sys_t *p_sys = p_dec->p_sys; - if (!p_picsys->b_valid) + if (!p_picsys->priv.hw.b_valid) return; vlc_mutex_lock(get_android_opaque_mutex()); /* Picture might have been invalidated while waiting on the mutex. */ - if (!p_picsys->b_valid) { + if (!p_picsys->priv.hw.b_valid) { vlc_mutex_unlock(get_android_opaque_mutex()); return; } - uint32_t i_index = p_picsys->i_index; + uint32_t i_index = p_picsys->priv.hw.i_index; + bool b_render = p_picsys->b_render; p_sys->inflight_picture[i_index] = NULL; /* Release the MediaCodec buffer. */ @@ -646,21 +649,11 @@ static void DisplayBuffer(picture_sys_t* p_picsys, bool b_render) } jni_detach_thread(); - p_picsys->b_valid = false; + p_picsys->priv.hw.b_valid = false; vlc_mutex_unlock(get_android_opaque_mutex()); } -static void UnlockCallback(picture_sys_t* p_picsys) -{ - DisplayBuffer(p_picsys, false); -} - -static void DisplayCallback(picture_sys_t* p_picsys) -{ - DisplayBuffer(p_picsys, true); -} - static void InvalidateAllPictures(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; @@ -669,7 +662,7 @@ static void InvalidateAllPictures(decoder_t *p_dec) for (int i = 0; i < p_sys->i_output_buffers; ++i) { picture_t *p_pic = p_sys->inflight_picture[i]; if (p_pic) { - p_pic->p_sys->b_valid = false; + p_pic->p_sys->priv.hw.b_valid = false; p_sys->inflight_picture[i] = NULL; } } @@ -707,7 +700,7 @@ static void GetOutput(decoder_t *p_dec, JNIEnv *env, picture_t **pp_pic, jlong t } else if (p_sys->direct_rendering) { picture_t *p_pic = *pp_pic; picture_sys_t *p_picsys = p_pic->p_sys; - int i_prev_index = p_picsys->i_index; + int i_prev_index = p_picsys->priv.hw.i_index; (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, i_prev_index, false); if ((*env)->ExceptionOccurred(env)) { msg_Err(p_dec, "Exception in MediaCodec.releaseOutputBuffer " \ @@ -738,11 +731,11 @@ static void GetOutput(decoder_t *p_dec, JNIEnv *env, picture_t **pp_pic, jlong t if (p_sys->direct_rendering) { picture_sys_t *p_picsys = p_pic->p_sys; - p_picsys->pf_display_callback = DisplayCallback; - p_picsys->pf_unlock_callback = UnlockCallback; - p_picsys->p_dec = p_dec; - p_picsys->i_index = index; - p_picsys->b_valid = true; + p_picsys->pf_lock_pic = NULL; + p_picsys->pf_unlock_pic = UnlockPicture; + p_picsys->priv.hw.p_dec = p_dec; + p_picsys->priv.hw.i_index = index; + p_picsys->priv.hw.b_valid = true; p_sys->inflight_picture[index] = p_pic; } else { @@ -974,11 +967,11 @@ static picture_t *DecodeVideo(decoder_t *p_dec, block_t **pp_block) if (invalid_picture) { invalid_picture->date = VLC_TS_INVALID; picture_sys_t *p_picsys = invalid_picture->p_sys; - p_picsys->pf_display_callback = NULL; - p_picsys->pf_unlock_callback = NULL; - p_picsys->p_dec = NULL; - p_picsys->i_index = -1; - p_picsys->b_valid = false; + p_picsys->pf_lock_pic = NULL; + p_picsys->pf_unlock_pic = NULL; + p_picsys->priv.hw.p_dec = NULL; + p_picsys->priv.hw.i_index = -1; + p_picsys->priv.hw.b_valid = false; } else { /* If we cannot return a picture we must free the diff --git a/modules/codec/omxil/android_opaque.h b/modules/codec/omxil/android_opaque.h index 30b145fa3438bc48090326d6a8dc1b145769e4fe..5d8b1e66e58d8fa449c87aef4a14b05d5f0c0f70 100644 --- a/modules/codec/omxil/android_opaque.h +++ b/modules/codec/omxil/android_opaque.h @@ -30,15 +30,6 @@ #include <vlc_common.h> -struct picture_sys_t -{ - void (*pf_display_callback)(picture_sys_t*); - void (*pf_unlock_callback)(picture_sys_t*); - decoder_t *p_dec; - uint32_t i_index; - int b_valid; -}; - vlc_mutex_t* get_android_opaque_mutex(void); #endif diff --git a/modules/codec/omxil/omxil.c b/modules/codec/omxil/omxil.c index 19bf3f78940a95e6576968b93996f92a032ac5da..95e5d0ae8628b78e9f9afe5066d3939e6e90bd83 100644 --- a/modules/codec/omxil/omxil.c +++ b/modules/codec/omxil/omxil.c @@ -46,6 +46,7 @@ #include <dlfcn.h> #include <jni.h> #include "android_opaque.h" +#include "../../video_output/android/android_window.h" #endif #ifndef NDEBUG @@ -96,8 +97,7 @@ static OMX_ERRORTYPE OmxFillBufferDone( OMX_HANDLETYPE, OMX_PTR, #if defined(USE_IOMX) static void *DequeueThread( void *data ); -static void DisplayCallback( picture_sys_t* p_picsys ); -static void UnlockCallback( picture_sys_t* p_picsys ); +static void UnlockPicture( picture_t* p_pic ); static void HwBuffer_Init( decoder_t *p_dec, OmxPort *p_port ); static void HwBuffer_Destroy( decoder_t *p_dec, OmxPort *p_port ); static int HwBuffer_AllocateBuffers( decoder_t *p_dec, OmxPort *p_port ); @@ -1635,11 +1635,11 @@ static picture_t *DecodeVideo( decoder_t *p_dec, block_t **pp_block ) if (invalid_picture) { invalid_picture->date = VLC_TS_INVALID; picture_sys_t *p_picsys = invalid_picture->p_sys; - p_picsys->pf_display_callback = NULL; - p_picsys->pf_unlock_callback = NULL; - p_picsys->p_dec = NULL; - p_picsys->i_index = -1; - p_picsys->b_valid = false; + p_picsys->pf_lock_pic = NULL; + p_picsys->pf_unlock_pic = NULL; + p_picsys->priv.hw.p_dec = NULL; + p_picsys->priv.hw.i_index = -1; + p_picsys->priv.hw.b_valid = false; } else { /* If we cannot return a picture we must free the block since the decoder will proceed with the @@ -2418,14 +2418,14 @@ static int HwBuffer_Stop( decoder_t *p_dec, OmxPort *p_port ) if( p_pic ) { picture_sys_t *p_picsys = p_pic->p_sys; if( p_picsys ) { - void *p_handle = p_port->pp_buffers[p_picsys->i_index]->pBuffer; + void *p_handle = p_port->pp_buffers[p_picsys->priv.hw.i_index]->pBuffer; if( p_handle ) { p_port->p_hwbuf->anwpriv.cancel( p_port->p_hwbuf->window_priv, p_handle ); - HwBuffer_ChangeState( p_dec, p_port, p_picsys->i_index, + HwBuffer_ChangeState( p_dec, p_port, p_picsys->priv.hw.i_index, BUF_STATE_NOT_OWNED ); } - p_picsys->b_valid = false; + p_picsys->priv.hw.b_valid = false; } p_port->p_hwbuf->inflight_picture[i] = NULL; } @@ -2493,11 +2493,11 @@ static int HwBuffer_GetPic( decoder_t *p_dec, OmxPort *p_port, p_pic->date = FromOmxTicks( p_header->nTimeStamp ); p_picsys = p_pic->p_sys; - p_picsys->pf_display_callback = DisplayCallback; - p_picsys->pf_unlock_callback = UnlockCallback; - p_picsys->p_dec = p_dec; - p_picsys->i_index = i_index; - p_picsys->b_valid = true; + p_picsys->pf_lock_pic = NULL; + p_picsys->pf_unlock_pic = UnlockPicture; + p_picsys->priv.hw.p_dec = p_dec; + p_picsys->priv.hw.i_index = i_index; + p_picsys->priv.hw.b_valid = true; HWBUFFER_LOCK(); p_port->p_hwbuf->inflight_picture[i_index] = p_pic; @@ -2600,27 +2600,28 @@ static void *DequeueThread( void *data ) /***************************************************************************** * vout callbacks *****************************************************************************/ -static void DisplayBuffer( picture_sys_t* p_picsys, bool b_render ) +static void UnlockPicture( picture_t* p_pic ) { - decoder_t *p_dec = p_picsys->p_dec; + picture_sys_t *p_picsys = p_pic->p_sys; + decoder_t *p_dec = p_picsys->priv.hw.p_dec; decoder_sys_t *p_sys = p_dec->p_sys; OmxPort *p_port = &p_sys->out; void *p_handle; - if( !p_picsys->b_valid ) return; + if( !p_picsys->priv.hw.b_valid ) return; HWBUFFER_LOCK(); /* Picture might have been invalidated while waiting on the mutex. */ - if (!p_picsys->b_valid) { + if (!p_picsys->priv.hw.b_valid) { HWBUFFER_UNLOCK(); return; } - p_handle = p_port->pp_buffers[p_picsys->i_index]->pBuffer; + p_handle = p_port->pp_buffers[p_picsys->priv.hw.i_index]->pBuffer; OMX_DBG( "DisplayBuffer: %s %p", - b_render ? "render" : "cancel", p_handle ); + p_picsys->b_render ? "render" : "cancel", p_handle ); if( !p_handle ) { @@ -2628,31 +2629,21 @@ static void DisplayBuffer( picture_sys_t* p_picsys, bool b_render ) goto end; } - if( b_render ) + if( p_picsys->b_render ) p_port->p_hwbuf->anwpriv.queue( p_port->p_hwbuf->window_priv, p_handle ); else p_port->p_hwbuf->anwpriv.cancel( p_port->p_hwbuf->window_priv, p_handle ); - HwBuffer_ChangeState( p_dec, p_port, p_picsys->i_index, BUF_STATE_NOT_OWNED ); + HwBuffer_ChangeState( p_dec, p_port, p_picsys->priv.hw.i_index, BUF_STATE_NOT_OWNED ); HWBUFFER_BROADCAST( p_port ); - p_port->p_hwbuf->inflight_picture[p_picsys->i_index] = NULL; + p_port->p_hwbuf->inflight_picture[p_picsys->priv.hw.i_index] = NULL; end: - p_picsys->b_valid = false; - p_picsys->i_index = -1; + p_picsys->priv.hw.b_valid = false; + p_picsys->priv.hw.i_index = -1; HWBUFFER_UNLOCK(); } -static void UnlockCallback( picture_sys_t* p_picsys ) -{ - DisplayBuffer( p_picsys, false ); -} - -static void DisplayCallback( picture_sys_t* p_picsys ) -{ - DisplayBuffer( p_picsys, true ); -} - #endif // USE_IOMX diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am index dccc3fa6b9cc10a567275a87a5a4e016ada119a6..b8586db1a198db1ba782d8ba85c33fceaf60cf64 100644 --- a/modules/video_output/Makefile.am +++ b/modules/video_output/Makefile.am @@ -250,11 +250,11 @@ libandroid_native_window_plugin_la_SOURCES = video_output/android/nativewindow.c libandroid_native_window_plugin_la_CFLAGS = $(AM_CFLAGS) libandroid_native_window_plugin_la_LIBADD = $(LIBDL) -libandroid_opaque_plugin_la_SOURCES = video_output/android/opaque.c video_output/android/utils.c video_output/android/utils.h -libandroid_opaque_plugin_la_CFLAGS = $(AM_CFLAGS) -libandroid_opaque_plugin_la_LIBADD = $(LIBDL) -libandroid_opaque_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' -EXTRA_LTLIBRARIES += libandroid_opaque_plugin.la +libandroid_window_plugin_la_SOURCES = video_output/android/android_window.c video_output/android/android_window.h video_output/android/utils.c video_output/android/utils.h +libandroid_window_plugin_la_CFLAGS = $(AM_CFLAGS) +libandroid_window_plugin_la_LIBADD = $(LIBDL) +libandroid_window_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' +EXTRA_LTLIBRARIES += libandroid_window_plugin.la libandroid_surface_plugin_la_SOURCES = video_output/android/surface.c video_output/android/utils.c video_output/android/utils.h libandroid_surface_plugin_la_CFLAGS = $(AM_CFLAGS) @@ -264,7 +264,7 @@ EXTRA_LTLIBRARIES += libandroid_surface_plugin.la if HAVE_ANDROID vout_LTLIBRARIES += libandroid_native_window_plugin.la -vout_LTLIBRARIES += $(LTLIBandroid_opaque) +vout_LTLIBRARIES += $(LTLIBandroid_window) vout_LTLIBRARIES += $(LTLIBandroid_surface) if HAVE_EGL vout_LTLIBRARIES += libegl_android_plugin.la diff --git a/modules/video_output/android/android_window.c b/modules/video_output/android/android_window.c new file mode 100644 index 0000000000000000000000000000000000000000..8dc8c185d8ceaf34f5023a01dde071b02d18bc4e --- /dev/null +++ b/modules/video_output/android/android_window.c @@ -0,0 +1,925 @@ +/***************************************************************************** + * android_window.c: Android video output module + ***************************************************************************** + * Copyright (C) 2014 VLC authors and VideoLAN + * + * Authors: Thomas Guillem <thomas@gllm.fr> + * Felix Abecassis <felix.abecassis@gmail.com> + * Ming Hu <tewilove@gmail.com> + * Ludovic Fauvet <etix@l0cal.com> + * Sébastien Toque <xilasz@gmail.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * 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. + *****************************************************************************/ + +#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 <vlc_filter.h> +#include <vlc_md5.h> + +#include <dlfcn.h> + +#include "android_window.h" +#include "utils.h" + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +#define PRIV_WINDOW_MAX_BUFFER_COUNT 32 +#define PRIV_WINDOW_MIN_BUFFER_COUNT 2 + +#define USE_ANWP +#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-" +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("android_window") + set_description(N_("Android video output")) + set_capability("vout display", 200) + add_shortcut("androidwindow", "android") + set_callbacks(Open, Close) +vlc_module_end() + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ + +#define THREAD_NAME "android_window" +extern int jni_attach_thread(JNIEnv **env, const char *thread_name); +extern void jni_detach_thread(); + +extern jobject jni_LockAndGetAndroidJavaSurface(); +extern jobject jni_LockAndGetSubtitlesSurface(); +extern void jni_UnlockAndroidSurface(); + +extern void jni_SetSurfaceLayout(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den); +extern int jni_ConfigureSurface(jobject jsurf, int width, int height, int hal, bool *configured); + +static const vlc_fourcc_t subpicture_chromas[] = +{ + VLC_CODEC_RGBA, + 0 +}; + +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); + +typedef struct android_window android_window; +struct android_window +{ + video_format_t fmt; + int i_android_hal; + unsigned int i_angle; + unsigned int i_pic_count; + unsigned int i_min_undequeued; + bool b_use_priv; + + jobject jsurf; + ANativeWindow *p_handle; + native_window_priv *p_handle_priv; +}; + +struct vout_display_sys_t +{ + picture_pool_t *pool; + + void *p_library; + native_window_api_t anw; + native_window_priv_api_t anwp; + bool b_has_anwp; + + android_window *p_window; + android_window *p_sub_window; + + filter_t *p_spu_blend; + picture_t *p_sub_pic; + + bool b_has_subpictures; + + uint8_t hash[16]; +}; + +static int UpdateWindowSize(video_format_t *p_fmt, bool b_cropped) +{ + unsigned int i_width, i_height; + unsigned int i_sar_num = 1, i_sar_den = 1; + video_format_t rot_fmt; + + video_format_ApplyRotation(&rot_fmt, p_fmt); + + if (rot_fmt.i_sar_num != 0 && rot_fmt.i_sar_den != 0) { + i_sar_num = rot_fmt.i_sar_num; + i_sar_den = rot_fmt.i_sar_den; + } + if (b_cropped) { + i_width = rot_fmt.i_visible_width; + i_height = rot_fmt.i_visible_height; + } else { + i_width = rot_fmt.i_width; + i_height = rot_fmt.i_height; + } + + jni_SetSurfaceLayout(i_width, i_height, + rot_fmt.i_visible_width, + rot_fmt.i_visible_height, + i_sar_num, + i_sar_den); + return 0; +} + +static picture_t *PictureAlloc(vout_display_sys_t *sys, video_format_t *fmt) +{ + picture_t *p_pic; + picture_resource_t rsc; + picture_sys_t *p_picsys = calloc(1, sizeof(*p_picsys)); + + if (unlikely(p_picsys == NULL)) + return NULL; + + p_picsys->p_vd_sys = sys; + + memset(&rsc, 0, sizeof(picture_resource_t)); + rsc.p_sys = p_picsys, + + p_pic = picture_NewFromResource(fmt, &rsc); + if (!p_pic) + { + free(p_picsys); + return NULL; + } + return p_pic; +} + +static void FixSubtitleFormat(vout_display_sys_t *sys) +{ + video_format_t *p_fmt = &sys->p_sub_window->fmt; + + if (p_fmt->i_visible_width == 0 || p_fmt->i_visible_height == 0) { + p_fmt->i_visible_width = p_fmt->i_width; + p_fmt->i_visible_height = p_fmt->i_height; + } + if (p_fmt->i_sar_num > 0 && p_fmt->i_sar_den > 0) { + if (p_fmt->i_sar_num >= p_fmt->i_sar_den) + p_fmt->i_width = (int64_t)p_fmt->i_visible_width * p_fmt->i_sar_num / p_fmt->i_sar_den; + else + p_fmt->i_height = (int64_t)p_fmt->i_visible_height * p_fmt->i_sar_den / p_fmt->i_sar_num; + p_fmt->i_sar_num = 1; + p_fmt->i_sar_den = 1; + } else { + p_fmt->i_width = p_fmt->i_visible_width; + p_fmt->i_height = p_fmt->i_visible_height; + } + p_fmt->i_x_offset = 0; + p_fmt->i_y_offset = 0; +} + +#define ALIGN_16_PIXELS( x ) ( ( ( x ) + 15 ) / 16 * 16 ) +static void SetupPictureYV12(picture_t *p_picture, uint32_t i_in_stride) +{ + /* according to document of android.graphics.ImageFormat.YV12 */ + int i_stride = ALIGN_16_PIXELS(i_in_stride); + 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; + /* + Explicitly set the padding lines of the picture to black (127 for YUV) + since they might be used by Android during rescaling. + */ + int visible_lines = p_picture->format.i_visible_height / 2; + if (visible_lines < p->i_lines) + memset(&p->p_pixels[visible_lines * p->i_pitch], 127, (p->i_lines - visible_lines) * p->i_pitch); + } + + 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; + } +} + +static android_window *AndroidWindow_New(vout_display_sys_t *sys, + video_format_t *p_fmt, + bool b_use_priv) +{ + android_window *p_window = calloc(1, sizeof(android_window)); + + if (!p_window) + return NULL; + + if (p_fmt->i_chroma != VLC_CODEC_ANDROID_OPAQUE) { + p_window->b_use_priv = sys->b_has_anwp && b_use_priv; + + p_window->i_android_hal = ChromaToAndroidHal(p_fmt->i_chroma); + if (p_window->i_android_hal == -1) { + free(p_window); + return NULL; + } + } + + if (p_window->b_use_priv) { + switch (p_fmt->orientation) + { + case ORIENT_ROTATED_90: + p_window->i_angle = 90; + break; + case ORIENT_ROTATED_180: + p_window->i_angle = 180; + break; + case ORIENT_ROTATED_270: + p_window->i_angle = 270; + break; + default: + p_window->i_angle = 0; + } + p_window->fmt = *p_fmt; + } else { + video_format_ApplyRotation(&p_window->fmt, p_fmt); + } + p_window->i_pic_count = 1; + return p_window; +} + +static void AndroidWindow_Destroy(vout_display_sys_t *sys, + android_window *p_window) +{ + if (p_window->p_handle_priv) + sys->anwp.disconnect(p_window->p_handle_priv); + if (p_window->p_handle) + sys->anw.winRelease(p_window->p_handle); + free(p_window); +} + +static int AndroidWindow_UpdateCrop(vout_display_sys_t *sys, + android_window *p_window) +{ + if (!p_window->p_handle_priv) + return -1; + + return sys->anwp.setCrop(p_window->p_handle_priv, + p_window->fmt.i_x_offset, + p_window->fmt.i_y_offset, + p_window->fmt.i_visible_width, + p_window->fmt.i_visible_height); +} + +static unsigned int AndroidWindow_GetPicCount(vout_display_sys_t *sys, + android_window *p_window) +{ + VLC_UNUSED(sys); + return p_window->i_min_undequeued + p_window->i_pic_count; +} + +static int AndroidWindow_SetSurface(vout_display_sys_t *sys, + android_window *p_window, + jobject jsurf) +{ + if (p_window->p_handle && jsurf != p_window->jsurf) { + if (p_window->p_handle_priv) { + sys->anwp.disconnect(p_window->p_handle_priv); + p_window->p_handle_priv = NULL; + } + sys->anw.winRelease(p_window->p_handle); + p_window->p_handle = NULL; + } + + p_window->jsurf = jsurf; + if (!p_window->p_handle) { + JNIEnv *p_env; + + jni_attach_thread(&p_env, THREAD_NAME); + if (!p_env) + return -1; + p_window->p_handle = sys->anw.winFromSurface(p_env, p_window->jsurf); + jni_detach_thread(); + if (!p_window->p_handle) + return -1; + } + + return 0; +} + +static int AndroidWindow_SetupANWP(vout_display_sys_t *sys, + android_window *p_window) +{ + if (!p_window->p_handle_priv) + p_window->p_handle_priv = sys->anwp.connect(p_window->p_handle); + + if (!p_window->p_handle_priv) + goto error; + + if (sys->anwp.setup(p_window->p_handle_priv, + p_window->fmt.i_width, p_window->fmt.i_height, + p_window->i_android_hal, + false, 0) != 0) + goto error; + + sys->anwp.getMinUndequeued(p_window->p_handle_priv, + &p_window->i_min_undequeued); + + if ((p_window->i_min_undequeued + p_window->i_pic_count) > + PRIV_WINDOW_MAX_BUFFER_COUNT) + p_window->i_pic_count = PRIV_WINDOW_MAX_BUFFER_COUNT + - p_window->i_min_undequeued; + + if (sys->anwp.setBufferCount(p_window->p_handle_priv, + AndroidWindow_GetPicCount(sys, p_window)) != 0) + goto error; + + if (sys->anwp.setOrientation(p_window->p_handle_priv, + p_window->i_angle) != 0) + goto error; + + AndroidWindow_UpdateCrop(sys, p_window); + + return 0; +error: + if (p_window->p_handle_priv) { + sys->anwp.disconnect(p_window->p_handle_priv); + p_window->p_handle_priv = NULL; + } + p_window->b_use_priv = false; + if (p_window->i_angle != 0) + video_format_ApplyRotation(&p_window->fmt, &p_window->fmt); + p_window->i_angle = 0; + return -1; +} + +static int AndroidWindow_SetupANW(vout_display_sys_t *sys, + android_window *p_window) +{ + int err; + bool configured; + + p_window->i_pic_count = 1; + p_window->i_min_undequeued = 0; + + /* + * anw.setBuffersGeometry is broken in gingerbread. + * use jni_ConfigureSurface to configure the surface on the java side + * synchronsouly. + * jni_ConfigureSurface return -1 when you don't need to call it (ie, after + * gingerbread). + * if jni_ConfigureSurface succeed, you need to get a new surface handle. + * That's why AndroidWindow_SetSurface is called again here. + */ + err = jni_ConfigureSurface(p_window->jsurf, + p_window->fmt.i_width, + p_window->fmt.i_height, + p_window->i_android_hal, + &configured); + if (err == 0) { + if (configured) { + jobject jsurf = p_window->jsurf; + p_window->jsurf = NULL; + if (AndroidWindow_SetSurface(sys, p_window, jsurf) != 0) + return -1; + } else + return -1; + } else { + err = sys->anw.setBuffersGeometry(p_window->p_handle, + p_window->fmt.i_width, + p_window->fmt.i_height, + p_window->i_android_hal); + } + return err; +} + +static int AndroidWindow_Setup(vout_display_sys_t *sys, + android_window *p_window, + unsigned int i_pic_count) +{ + int align_pixels; + picture_t *p_pic; + + if (i_pic_count != 0) + p_window->i_pic_count = i_pic_count; + + p_pic = PictureAlloc(sys, &p_window->fmt); + // For RGB (32 or 16) we need to align on 8 or 4 pixels, 16 pixels for YUV + align_pixels = (16 / p_pic->p[0].i_pixel_pitch) - 1; + p_window->fmt.i_height = p_pic->format.i_height; + p_window->fmt.i_width = (p_pic->format.i_width + align_pixels) & ~align_pixels; + picture_Release(p_pic); + + if (!p_window->b_use_priv + || AndroidWindow_SetupANWP(sys, p_window) != 0) { + if (AndroidWindow_SetupANW(sys, p_window) != 0) + return -1; + } + + return 0; +} + +static void AndroidWindow_UnlockPicture(vout_display_sys_t *sys, + android_window *p_window, + picture_t *p_pic) +{ + picture_sys_t *p_picsys = p_pic->p_sys; + + if (p_window->b_use_priv) { + int err = 0; + void *p_handle = p_picsys->priv.sw.p_handle; + + if (p_handle == NULL) + return; + + err = sys->anwp.unlockData(p_window->p_handle_priv, p_handle); + + if (err == 0) { + if (p_picsys->b_render) + err = sys->anwp.queue(p_window->p_handle_priv, p_handle); + else + err = sys->anwp.cancel(p_window->p_handle_priv, p_handle); + } + } else + sys->anw.unlockAndPost(p_window->p_handle); +} + +static int AndroidWindow_LockPicture(vout_display_sys_t *sys, + android_window *p_window, + picture_t *p_pic) +{ + picture_sys_t *p_picsys = p_pic->p_sys; + + if (p_window->b_use_priv) { + void *p_handle; + int err; + + err = sys->anwp.dequeue(p_window->p_handle_priv, &p_handle); + err = err == 0 ? sys->anwp.lock(p_window->p_handle_priv, p_handle) : err; + err = err == 0 ? sys->anwp.lockData(p_window->p_handle_priv, + p_handle, + &p_picsys->priv.sw.buf) : err; + if (err != 0) + return -1; + p_picsys->priv.sw.p_handle = p_handle; + } else { + if (sys->anw.winLock(p_window->p_handle, + &p_picsys->priv.sw.buf, NULL) != 0) + return -1; + } + if (p_picsys->priv.sw.buf.width < 0 || + p_picsys->priv.sw.buf.height < 0 || + (unsigned)p_picsys->priv.sw.buf.width < p_window->fmt.i_width || + (unsigned)p_picsys->priv.sw.buf.height < p_window->fmt.i_height) { + AndroidWindow_UnlockPicture(sys, p_window, p_pic); + return -1; + } + + p_pic->p[0].p_pixels = p_picsys->priv.sw.buf.bits; + p_pic->p[0].i_lines = p_picsys->priv.sw.buf.height; + p_pic->p[0].i_pitch = p_pic->p[0].i_pixel_pitch * p_picsys->priv.sw.buf.stride; + + if (p_picsys->priv.sw.buf.format == PRIV_WINDOW_FORMAT_YV12) + SetupPictureYV12(p_pic, p_picsys->priv.sw.buf.stride); + + return 0; +} + +static int SetupWindowSurface(vout_display_sys_t *sys, unsigned i_pic_count) +{ + int err; + jobject jsurf = jni_LockAndGetAndroidJavaSurface(); + err = AndroidWindow_SetSurface(sys, sys->p_window, jsurf); + jni_UnlockAndroidSurface(); + err = err == 0 ? AndroidWindow_Setup(sys, sys->p_window, i_pic_count) : err; + return err; +} + +static int SetupWindowSubtitleSurface(vout_display_sys_t *sys) +{ + int err; + jobject jsurf = jni_LockAndGetSubtitlesSurface(); + err = AndroidWindow_SetSurface(sys, sys->p_sub_window, jsurf); + jni_UnlockAndroidSurface(); + err = err == 0 ? AndroidWindow_Setup(sys, sys->p_sub_window, 1) : err; + return err; +} + +static void SetRGBMask(video_format_t *p_fmt) +{ + switch(p_fmt->i_chroma) { + case VLC_CODEC_RGB16: + p_fmt->i_bmask = 0x0000001f; + p_fmt->i_gmask = 0x000007e0; + p_fmt->i_rmask = 0x0000f800; + break; + + case VLC_CODEC_RGB32: + case VLC_CODEC_RGBA: + p_fmt->i_rmask = 0x000000ff; + p_fmt->i_gmask = 0x0000ff00; + p_fmt->i_bmask = 0x00ff0000; + break; + } +} + +static int Open(vlc_object_t *p_this) +{ + vout_display_t *vd = (vout_display_t*)p_this; + vout_display_sys_t *sys; + video_format_t sub_fmt; + + if (vout_display_IsWindowed(vd)) + return VLC_EGENERIC; + + /* Allocate structure */ + vd->sys = sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys)); + if (!sys) + return VLC_ENOMEM; + + sys->p_library = LoadNativeWindowAPI(&sys->anw); + if (!sys->p_library) { + msg_Err(vd, "Could not initialize NativeWindow API."); + goto error; + } + +#ifdef USE_ANWP + if (LoadNativeWindowPrivAPI(&sys->anwp) == 0) + sys->b_has_anwp = true; + else + msg_Warn(vd, "Could not initialize NativeWindow Priv API."); +#endif + + if (vd->fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) { + /* Setup chroma */ + char *psz_fcc = var_InheritString(vd, CFG_PREFIX "chroma"); + if (psz_fcc) { + vd->fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, psz_fcc); + free(psz_fcc); + } else + vd->fmt.i_chroma = VLC_CODEC_RGB32; + + switch(vd->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 */ + vd->fmt.i_chroma = VLC_CODEC_I420; + case VLC_CODEC_I420: + break; + case VLC_CODEC_RGB16: + case VLC_CODEC_RGB32: + case VLC_CODEC_RGBA: + SetRGBMask(&vd->fmt); + video_format_FixRgb(&vd->fmt); + break; + default: + goto error; + } + + sys->p_window = AndroidWindow_New(sys, &vd->fmt, true); + if (!sys->p_window) + goto error; + + if (SetupWindowSurface(sys, 0) != 0) + goto error; + + /* use software rotation if we don't use private anw */ + if (!sys->p_window->b_use_priv) + video_format_ApplyRotation(&vd->fmt, &vd->fmt); + + msg_Dbg(vd, "using %s", sys->p_window->b_use_priv ? "ANWP" : "ANW"); + } else { + /* vd->fmt.i_chroma == VLC_CODEC_ANDROID_OPAQUE */ + sys->p_window = AndroidWindow_New(sys, &vd->fmt, false); + if (!sys->p_window) + goto error; + msg_Dbg(vd, "using opaque"); + } + + video_format_ApplyRotation(&sub_fmt, &vd->fmt); + sub_fmt.i_chroma = subpicture_chromas[0]; + SetRGBMask(&sub_fmt); + video_format_FixRgb(&sub_fmt); + sys->p_sub_window = AndroidWindow_New(sys, &sub_fmt, false); + if (!sys->p_sub_window) + goto error; + FixSubtitleFormat(sys); + + /* Export the subpicture capability of this vout. */ + vd->info.subpicture_chromas = subpicture_chromas; + + /* Setup vout_display */ + vd->pool = Pool; + vd->prepare = NULL; + vd->display = Display; + vd->control = Control; + vd->manage = Manage; + + /* Fix initial state */ + vout_display_SendEventFullscreen(vd, false); + + return VLC_SUCCESS; + +error: + Close(p_this); + return VLC_ENOMEM; +} + +static void Close(vlc_object_t *p_this) +{ + vout_display_t *vd = (vout_display_t *)p_this; + vout_display_sys_t *sys = vd->sys; + + if (!sys) + return; + + if (sys->pool) + picture_pool_Release(sys->pool); + if (sys->p_window) + AndroidWindow_Destroy(sys, sys->p_window); + + if (sys->p_sub_pic) + picture_Release(sys->p_sub_pic); + if (sys->p_spu_blend) + filter_DeleteBlend(sys->p_spu_blend); + if (sys->p_sub_window) + AndroidWindow_Destroy(sys, sys->p_sub_window); + + if (sys->p_library) + dlclose(sys->p_library); + + free(sys); +} + +static int DefaultLockPicture(picture_t *p_pic) +{ + picture_sys_t *p_picsys = p_pic->p_sys; + vout_display_sys_t *sys = p_picsys->p_vd_sys; + + return AndroidWindow_LockPicture(sys, sys->p_window, p_pic); +} + +static void DefaultUnlockPicture(picture_t *p_pic) +{ + picture_sys_t *p_picsys = p_pic->p_sys; + vout_display_sys_t *sys = p_picsys->p_vd_sys; + + AndroidWindow_UnlockPicture(sys, sys->p_window, p_pic); +} + +static int LockPicture(picture_t *p_pic) +{ + picture_sys_t *p_picsys = p_pic->p_sys; + + p_picsys->b_render = false; + if (p_picsys->pf_lock_pic) + return p_picsys->pf_lock_pic(p_pic); + return 0; +} + +static void UnlockPicture(picture_t *p_pic) +{ + picture_sys_t *p_picsys = p_pic->p_sys; + + if (p_picsys->pf_unlock_pic) + p_picsys->pf_unlock_pic(p_pic); +} + +static picture_pool_t *PoolAlloc(vout_display_t *vd, unsigned requested_count) +{ + vout_display_sys_t *sys = vd->sys; + picture_pool_t *pool = NULL; + picture_t **pp_pics = NULL; + unsigned int i = 0; + + if (vd->fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) { + if (SetupWindowSurface(sys, requested_count) != 0) + goto error; + + requested_count = AndroidWindow_GetPicCount(sys, sys->p_window); + msg_Dbg(vd, "PoolAlloc: request %d frames", requested_count); + } else { + requested_count = 31; // TODO: + } + UpdateWindowSize(&sys->p_window->fmt, sys->p_window->b_use_priv); + + pp_pics = calloc(requested_count, sizeof(picture_t)); + + for (i = 0; i < requested_count; i++) + { + picture_t *p_pic = PictureAlloc(sys, &sys->p_window->fmt); + if (!p_pic) + goto error; + if (vd->fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) { + p_pic->p_sys->pf_lock_pic = DefaultLockPicture; + p_pic->p_sys->pf_unlock_pic = DefaultUnlockPicture; + } + + pp_pics[i] = p_pic; + } + + picture_pool_configuration_t pool_cfg; + memset(&pool_cfg, 0, sizeof(pool_cfg)); + pool_cfg.picture_count = requested_count; + pool_cfg.picture = pp_pics; + pool_cfg.lock = LockPicture; + pool_cfg.unlock = UnlockPicture; + pool = picture_pool_NewExtended(&pool_cfg); + +error: + if (!pool && pp_pics) { + for (unsigned j = 0; j < i; j++) + picture_Release(pp_pics[j]); + } + free(pp_pics); + return pool; +} + +static void SubpictureDisplay(vout_display_t *vd, subpicture_t *subpicture) +{ + vout_display_sys_t *sys = vd->sys; + + struct md5_s hash; + InitMD5(&hash); + if (subpicture) { + for (subpicture_region_t *r = subpicture->p_region; r != NULL; r = r->p_next) { + AddMD5(&hash, &r->i_x, sizeof(r->i_x)); + AddMD5(&hash, &r->i_y, sizeof(r->i_y)); + AddMD5(&hash, &r->fmt.i_visible_width, sizeof(r->fmt.i_visible_width)); + AddMD5(&hash, &r->fmt.i_visible_height, sizeof(r->fmt.i_visible_height)); + AddMD5(&hash, &r->fmt.i_x_offset, sizeof(r->fmt.i_x_offset)); + AddMD5(&hash, &r->fmt.i_y_offset, sizeof(r->fmt.i_y_offset)); + const int pixels_offset = r->fmt.i_y_offset * r->p_picture->p->i_pitch + + r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch; + + for (unsigned int y = 0; y < r->fmt.i_visible_height; y++) + AddMD5(&hash, &r->p_picture->p->p_pixels[pixels_offset + y*r->p_picture->p->i_pitch], r->fmt.i_visible_width); + } + } + EndMD5(&hash); + if (!memcmp(hash.buf, sys->hash, 16)) + return; + memcpy(sys->hash, hash.buf, 16); + + if (AndroidWindow_LockPicture(sys, sys->p_sub_window, sys->p_sub_pic) != 0) + return; + + /* Clear the subtitles surface. */ + memset(sys->p_sub_pic->p[0].p_pixels, 0, + sys->p_sub_pic->p[0].i_pitch * sys->p_sub_pic->p[0].i_lines); + + if (subpicture) + { + /* Allocate a blending filter if needed. */ + if (unlikely(!sys->p_spu_blend)) + sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd), + &sys->p_sub_pic->format); + picture_BlendSubpicture(sys->p_sub_pic, sys->p_spu_blend, subpicture); + } + AndroidWindow_UnlockPicture(sys, sys->p_sub_window, sys->p_sub_pic); +} + +static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count) +{ + vout_display_sys_t *sys = vd->sys; + + if (sys->pool == NULL) + sys->pool = PoolAlloc(vd, requested_count); + return sys->pool; +} + +static void Display(vout_display_t *vd, picture_t *picture, + subpicture_t *subpicture) +{ + vout_display_sys_t *sys = vd->sys; + picture_sys_t *p_picsys = picture->p_sys; + + /* refcount lowers to 0, and pool_cfg.unlock is called */ + p_picsys->b_render = true; + picture_Release(picture); + + if (subpicture) { + if (!sys->p_sub_pic && SetupWindowSubtitleSurface(sys) == 0) + sys->p_sub_pic = PictureAlloc(sys, &sys->p_sub_window->fmt); + + if (sys->p_sub_pic) + sys->b_has_subpictures = true; + } + /* As long as no subpicture was received, do not call + SubpictureDisplay since JNI calls and clearing the subtitles + surface are expensive operations. */ + if (sys->b_has_subpictures) + { + SubpictureDisplay(vd, subpicture); + if (!subpicture) + { + /* The surface has been cleared and there is no new + subpicture to upload, do not clear again until a new + subpicture is received. */ + sys->b_has_subpictures = false; + } + } + + if (subpicture) + subpicture_Delete(subpicture); +} + +static void CopySourceAspect(video_format_t *p_dest, + const video_format_t *p_src) +{ + p_dest->i_sar_num = p_src->i_sar_num; + p_dest->i_sar_den = p_src->i_sar_den; +} + +static int Control(vout_display_t *vd, int query, va_list args) +{ + vout_display_sys_t *sys = vd->sys; + + switch (query) { + case VOUT_DISPLAY_HIDE_MOUSE: + return VLC_SUCCESS; + case VOUT_DISPLAY_RESET_PICTURES: + { + if (sys->p_window->fmt.i_chroma == VLC_CODEC_ANDROID_OPAQUE) + return VLC_EGENERIC; + + msg_Dbg(vd, "resetting pictures"); + + if (sys->pool != NULL) + { + picture_pool_Release(sys->pool); + sys->pool = NULL; + } + return VLC_SUCCESS; + } + case VOUT_DISPLAY_CHANGE_SOURCE_CROP: + case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: + { + const video_format_t *source; + video_format_t sub_fmt; + + msg_Dbg(vd, "change source crop/aspect"); + + source = va_arg(args, const video_format_t *); + video_format_ApplyRotation(&sub_fmt, source); + + if (query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) { + video_format_CopyCrop(&sys->p_window->fmt, source); + AndroidWindow_UpdateCrop(sys, sys->p_window); + + video_format_CopyCrop(&sys->p_sub_window->fmt, &sub_fmt); + } else { + CopySourceAspect(&sys->p_window->fmt, source); + + CopySourceAspect(&sys->p_sub_window->fmt, &sub_fmt); + } + UpdateWindowSize(&sys->p_window->fmt, sys->p_window->b_use_priv); + FixSubtitleFormat(sys); + + if (sys->p_sub_pic) { + picture_Release(sys->p_sub_pic); + sys->p_sub_pic = NULL; + } + if (sys->p_spu_blend) { + filter_DeleteBlend(sys->p_spu_blend); + sys->p_spu_blend = NULL; + } + + return VLC_SUCCESS; + } + default: + msg_Warn(vd, "Unknown request in android_window"); + case VOUT_DISPLAY_CHANGE_ZOOM: + case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: + case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: + return VLC_EGENERIC; + } +} diff --git a/modules/video_output/android/android_window.h b/modules/video_output/android/android_window.h new file mode 100644 index 0000000000000000000000000000000000000000..68101c8f9fdfeeabd674f5db18ab9d650bf17f9f --- /dev/null +++ b/modules/video_output/android/android_window.h @@ -0,0 +1,59 @@ +/***************************************************************************** + * android_window.c: Android video output module + ***************************************************************************** + * Copyright (C) 2014 VLC authors and VideoLAN + * + * Authors: Thomas Guillem <thomas@gllm.fr> + * Felix Abecassis <felix.abecassis@gmail.com> + * Ming Hu <tewilove@gmail.com> + * Ludovic Fauvet <etix@l0cal.com> + * Sébastien Toque <xilasz@gmail.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * 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. + *****************************************************************************/ + +#ifndef ANDROID_WINDOW_H_ +#define ANDROID_WINDOW_H_ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <vlc_common.h> +#include <vlc_vout_display.h> +#include <android/native_window.h> + +struct picture_sys_t +{ + vout_display_sys_t *p_vd_sys; + + int (*pf_lock_pic)(picture_t *); + void (*pf_unlock_pic)(picture_t *); + + union { + struct { + decoder_t *p_dec; + uint32_t i_index; + bool b_valid; + } hw; + struct { + void *p_handle; + ANativeWindow_Buffer buf; + } sw; + } priv; + bool b_render; +}; + +#endif diff --git a/modules/video_output/android/opaque.c b/modules/video_output/android/opaque.c deleted file mode 100644 index b06feb4af4a8e6e6a00579f3ea72333259c50988..0000000000000000000000000000000000000000 --- a/modules/video_output/android/opaque.c +++ /dev/null @@ -1,353 +0,0 @@ -/***************************************************************************** - * opaque.c: Android video output module using direct rendering with - * opaque buffers - ***************************************************************************** - * Copyright (C) 2013 Felix Abecassis - * - * Authors: Felix Abecassis <felix.abecassis@gmail.com> - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * 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. - *****************************************************************************/ - -#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 <vlc_filter.h> -#include <vlc_md5.h> - -#include <dlfcn.h> - -#include "../codec/omxil/android_opaque.h" -#include "utils.h" - -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("vout_mediacodec") - set_description(N_("Android MediaCodec direct rendering video output")) - set_capability("vout display", 200) - add_shortcut("androidsurface", "android") - set_callbacks(Open, Close) -vlc_module_end() - -#define THREAD_NAME "vout_mediacodec" -extern int jni_attach_thread(JNIEnv **env, const char *thread_name); -extern void jni_detach_thread(); -extern jobject jni_LockAndGetSubtitlesSurface(); -extern void jni_UnlockAndroidSurface(); - -static const vlc_fourcc_t subpicture_chromas[] = -{ - VLC_CODEC_RGBA, - 0 -}; - -/***************************************************************************** - * 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); - -struct vout_display_sys_t -{ - picture_pool_t *pool; - - void *p_library; - native_window_api_t native_window; - - jobject jsurf; - ANativeWindow *window; - - video_format_t fmt; - - filter_t *p_spu_blend; - picture_t *subtitles_picture; - - bool b_has_subpictures; - - uint8_t hash[16]; -}; - -static void DisplaySubpicture(vout_display_t *vd, subpicture_t *subpicture) -{ - vout_display_sys_t *sys = vd->sys; - - struct md5_s hash; - InitMD5(&hash); - if (subpicture) { - for (subpicture_region_t *r = subpicture->p_region; r != NULL; r = r->p_next) { - AddMD5(&hash, &r->i_x, sizeof(r->i_x)); - AddMD5(&hash, &r->i_y, sizeof(r->i_y)); - AddMD5(&hash, &r->fmt.i_visible_width, sizeof(r->fmt.i_visible_width)); - AddMD5(&hash, &r->fmt.i_visible_height, sizeof(r->fmt.i_visible_height)); - AddMD5(&hash, &r->fmt.i_x_offset, sizeof(r->fmt.i_x_offset)); - AddMD5(&hash, &r->fmt.i_y_offset, sizeof(r->fmt.i_y_offset)); - const int pixels_offset = r->fmt.i_y_offset * r->p_picture->p->i_pitch + - r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch; - - for (int y = 0; y < r->fmt.i_visible_height; y++) - AddMD5(&hash, &r->p_picture->p->p_pixels[pixels_offset + y*r->p_picture->p->i_pitch], r->fmt.i_visible_width); - } - } - EndMD5(&hash); - if (!memcmp(hash.buf, sys->hash, 16)) - return; - memcpy(sys->hash, hash.buf, 16); - - jobject jsurf = jni_LockAndGetSubtitlesSurface(); - if (sys->window && jsurf != sys->jsurf) - { - sys->native_window.winRelease(sys->window); - sys->window = NULL; - } - sys->jsurf = jsurf; - if (!sys->window) - { - JNIEnv *p_env; - jni_attach_thread(&p_env, THREAD_NAME); - sys->window = sys->native_window.winFromSurface(p_env, jsurf); - jni_detach_thread(); - } - - ANativeWindow_Buffer buf = { 0 }; - int32_t err = sys->native_window.winLock(sys->window, &buf, NULL); - if (err) { - jni_UnlockAndroidSurface(); - return; - } - - if (buf.width >= sys->fmt.i_width && buf.height >= sys->fmt.i_height) - { - /* Wrap the NativeWindow corresponding to the subtitles surface in a picture_t */ - picture_t *picture = sys->subtitles_picture; - picture->p[0].p_pixels = (uint8_t*)buf.bits; - picture->p[0].i_lines = buf.height; - picture->p[0].i_pitch = picture->p[0].i_pixel_pitch * buf.stride; - /* Clear the subtitles surface. */ - memset(picture->p[0].p_pixels, 0, picture->p[0].i_pitch * picture->p[0].i_lines); - if (subpicture) - { - /* Allocate a blending filter if needed. */ - if (unlikely(!sys->p_spu_blend)) - sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd), &picture->format); - picture_BlendSubpicture(picture, sys->p_spu_blend, subpicture); - } - } - - sys->native_window.unlockAndPost(sys->window); - jni_UnlockAndroidSurface(); -} - -static int LockSurface(picture_t *); -static void UnlockSurface(picture_t *); - -/* We need to allocate a picture pool of more than 30 buffers in order - * to be connected directly to the decoder without any intermediate - * buffer pool. */ -#define POOL_SIZE 31 - -static int Open(vlc_object_t *p_this) -{ - vout_display_t *vd = (vout_display_t*)p_this; - - video_format_t fmt = vd->fmt; - - if (fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) - return VLC_EGENERIC; - if (vout_display_IsWindowed(vd)) - return VLC_EGENERIC; - - /* Allocate structure */ - vout_display_sys_t *sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys)); - if (!sys) - return VLC_ENOMEM; - - sys->p_library = LoadNativeWindowAPI(&sys->native_window); - if (!sys->p_library) - { - free(sys); - msg_Err(vd, "Could not initialize NativeWindow API."); - return VLC_EGENERIC; - } - sys->fmt = fmt; - video_format_t subpicture_format = sys->fmt; - subpicture_format.i_chroma = VLC_CODEC_RGBA; - /* Create a RGBA picture for rendering subtitles. */ - picture_resource_t rsc; - memset(&rsc, 0, sizeof(rsc)); - sys->subtitles_picture = picture_NewFromResource(&subpicture_format, &rsc); - - /* Export the subpicture capability of this vout. */ - vd->info.subpicture_chromas = subpicture_chromas; - - int i_pictures = POOL_SIZE; - picture_t** pictures = calloc(sizeof(*pictures), i_pictures); - if (!pictures) - goto error; - for (int i = 0; i < i_pictures; i++) - { - picture_sys_t *p_picsys = calloc(1, sizeof(*p_picsys)); - if (unlikely(p_picsys == NULL)) - goto error; - - picture_resource_t resource = { .p_sys = p_picsys }; - picture_t *picture = picture_NewFromResource(&fmt, &resource); - if (!picture) - { - free(p_picsys); - goto error; - } - pictures[i] = picture; - } - - /* Wrap it into a picture pool */ - picture_pool_configuration_t pool_cfg; - memset(&pool_cfg, 0, sizeof(pool_cfg)); - pool_cfg.picture_count = i_pictures; - pool_cfg.picture = pictures; - pool_cfg.lock = LockSurface; - pool_cfg.unlock = UnlockSurface; - - sys->pool = picture_pool_NewExtended(&pool_cfg); - if (!sys->pool) - { - for (int i = 0; i < i_pictures; i++) - picture_Release(pictures[i]); - goto error; - } - - /* Setup vout_display */ - vd->sys = sys; - vd->fmt = fmt; - vd->pool = Pool; - vd->display = Display; - vd->control = Control; - vd->prepare = NULL; - vd->manage = Manage; - - /* Fix initial state */ - vout_display_SendEventFullscreen(vd, false); - - return VLC_SUCCESS; - -error: - free(pictures); - Close(p_this); - return VLC_ENOMEM; -} - -static void Close(vlc_object_t *p_this) -{ - vout_display_t *vd = (vout_display_t *)p_this; - vout_display_sys_t *sys = vd->sys; - - picture_pool_Release(sys->pool); - if (sys->window) - sys->native_window.winRelease(sys->window); - dlclose(sys->p_library); - if (sys->subtitles_picture) - picture_Release(sys->subtitles_picture); - if (sys->p_spu_blend) - filter_DeleteBlend(sys->p_spu_blend); - free(sys); -} - -static picture_pool_t *Pool(vout_display_t *vd, unsigned count) -{ - VLC_UNUSED(count); - - return vd->sys->pool; -} - -static int LockSurface(picture_t *picture) -{ - VLC_UNUSED(picture); - - return VLC_SUCCESS; -} - -static void UnlockSurface(picture_t *picture) -{ - picture_sys_t *p_picsys = picture->p_sys; - void (*unlock_callback)(picture_sys_t*) = p_picsys->pf_unlock_callback; - if (unlock_callback) - unlock_callback(p_picsys); -} - -static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture) -{ - VLC_UNUSED(vd); - VLC_UNUSED(subpicture); - - picture_sys_t *p_picsys = picture->p_sys; - vout_display_sys_t *sys = vd->sys; - void (*display_callback)(picture_sys_t*) = p_picsys->pf_display_callback; - if (display_callback) - display_callback(p_picsys); - - if (subpicture && sys->subtitles_picture) - sys->b_has_subpictures = true; - /* As long as no subpicture was received, do not call - DisplaySubpicture since JNI calls and clearing the subtitles - surface are expensive operations. */ - if (sys->b_has_subpictures) - { - DisplaySubpicture(vd, subpicture); - if (!subpicture) - { - /* The surface has been cleared and there is no new - subpicture to upload, do not clear again until a new - subpicture is received. */ - sys->b_has_subpictures = false; - } - } - - /* refcount lowers to 0, and pool_cfg.unlock is called */ - picture_Release(picture); - if (subpicture) - subpicture_Delete(subpicture); -} - -static int Control(vout_display_t *vd, int query, va_list args) -{ - VLC_UNUSED(args); - - switch (query) { - case VOUT_DISPLAY_HIDE_MOUSE: - return VLC_SUCCESS; - - default: - msg_Err(vd, "Unknown request in vout mediacodec display"); - - case VOUT_DISPLAY_CHANGE_SOURCE_CROP: - 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: - return VLC_EGENERIC; - } -}