diff options
Diffstat (limited to 'video/out/vo_rpi.c')
-rw-r--r-- | video/out/vo_rpi.c | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/video/out/vo_rpi.c b/video/out/vo_rpi.c new file mode 100644 index 0000000..55f1a68 --- /dev/null +++ b/video/out/vo_rpi.c @@ -0,0 +1,938 @@ +/* + * This file is part of mpv. + * + * mpv 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. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdbool.h> +#include <assert.h> + +#include <bcm_host.h> +#include <interface/mmal/mmal.h> +#include <interface/mmal/util/mmal_util.h> +#include <interface/mmal/util/mmal_default_components.h> +#include <interface/mmal/vc/mmal_vc_api.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <libavutil/rational.h> + +#include "common/common.h" +#include "common/msg.h" +#include "opengl/common.h" +#include "options/m_config.h" +#include "osdep/timer.h" +#include "vo.h" +#include "win_state.h" +#include "video/mp_image.h" +#include "sub/osd.h" + +#include "opengl/ra_gl.h" +#include "gpu/video.h" + +struct mp_egl_rpi { + struct mp_log *log; + struct GL *gl; + struct ra *ra; + EGLDisplay egl_display; + EGLConfig egl_config; + EGLContext egl_context; + EGLSurface egl_surface; + // yep, the API keeps a pointer to it + EGL_DISPMANX_WINDOW_T egl_window; +}; + +struct priv { + DISPMANX_DISPLAY_HANDLE_T display; + DISPMANX_ELEMENT_HANDLE_T window; + DISPMANX_ELEMENT_HANDLE_T osd_overlay; + DISPMANX_UPDATE_HANDLE_T update; + uint32_t w, h; + uint32_t x, y; + double display_fps; + + double osd_pts; + struct mp_osd_res osd_res; + struct m_config_cache *opts_cache; + + struct mp_egl_rpi egl; + struct gl_video *gl_video; + struct mpgl_osd *osd; + + MMAL_COMPONENT_T *renderer; + bool renderer_enabled; + + bool display_synced, skip_osd; + struct mp_image *next_image; + + // for RAM input + MMAL_POOL_T *swpool; + + mp_mutex display_mutex; + mp_cond display_cond; + int64_t vsync_counter; + bool reload_display; + + int background_layer; + int video_layer; + int osd_layer; + + int display_nr; + int layer; + bool background; + bool enable_osd; +}; + +// Magic alignments (in pixels) expected by the MMAL internals. +#define ALIGN_W 32 +#define ALIGN_H 16 + +static void recreate_renderer(struct vo *vo); + +static void *get_proc_address(const GLubyte *name) +{ + void *p = eglGetProcAddress(name); + // EGL 1.4 (supported by the RPI firmware) does not necessarily return + // function pointers for core functions. + if (!p) { + void *h = dlopen("/opt/vc/lib/libbrcmGLESv2.so", RTLD_LAZY); + if (h) { + p = dlsym(h, name); + dlclose(h); + } + } + return p; +} + +static EGLConfig select_fb_config_egl(struct mp_egl_rpi *p) +{ + EGLint attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLint config_count; + EGLConfig config; + + eglChooseConfig(p->egl_display, attributes, &config, 1, &config_count); + + if (!config_count) { + MP_FATAL(p, "Could find EGL configuration!\n"); + return NULL; + } + + return config; +} + +static void mp_egl_rpi_destroy(struct mp_egl_rpi *p) +{ + if (p->egl_display) { + eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + } + if (p->egl_surface) + eglDestroySurface(p->egl_display, p->egl_surface); + if (p->egl_context) + eglDestroyContext(p->egl_display, p->egl_context); + p->egl_context = EGL_NO_CONTEXT; + eglReleaseThread(); + p->egl_display = EGL_NO_DISPLAY; + talloc_free(p->gl); + p->gl = NULL; +} + +static int mp_egl_rpi_init(struct mp_egl_rpi *p, DISPMANX_ELEMENT_HANDLE_T window, + int w, int h) +{ + p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!eglInitialize(p->egl_display, NULL, NULL)) { + MP_FATAL(p, "EGL failed to initialize.\n"); + goto fail; + } + + eglBindAPI(EGL_OPENGL_ES_API); + + EGLConfig config = select_fb_config_egl(p); + if (!config) + goto fail; + + p->egl_window = (EGL_DISPMANX_WINDOW_T){ + .element = window, + .width = w, + .height = h, + }; + p->egl_surface = eglCreateWindowSurface(p->egl_display, config, + &p->egl_window, NULL); + + if (p->egl_surface == EGL_NO_SURFACE) { + MP_FATAL(p, "Could not create EGL surface!\n"); + goto fail; + } + + EGLint context_attributes[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + p->egl_context = eglCreateContext(p->egl_display, config, + EGL_NO_CONTEXT, context_attributes); + + if (p->egl_context == EGL_NO_CONTEXT) { + MP_FATAL(p, "Could not create EGL context!\n"); + goto fail; + } + + eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, + p->egl_context); + + p->gl = talloc_zero(NULL, struct GL); + + const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); + mpgl_load_functions(p->gl, get_proc_address, exts, p->log); + + if (!p->gl->version && !p->gl->es) + goto fail; + + p->ra = ra_create_gl(p->gl, p->log); + if (!p->ra) + goto fail; + + return 0; + +fail: + mp_egl_rpi_destroy(p); + return -1; +} + +// Make mpi point to buffer, assuming MMAL_ENCODING_I420. +// buffer can be NULL. +// Return the required buffer space. +static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer, + struct mp_image_params *params) +{ + assert(params->imgfmt == IMGFMT_420P); + mp_image_set_params(mpi, params); + int w = MP_ALIGN_UP(params->w, ALIGN_W); + int h = MP_ALIGN_UP(params->h, ALIGN_H); + uint8_t *cur = buffer ? buffer->data : NULL; + size_t size = 0; + for (int i = 0; i < 3; i++) { + int div = i ? 2 : 1; + mpi->planes[i] = cur; + mpi->stride[i] = w / div; + size_t plane_size = h / div * mpi->stride[i]; + if (cur) + cur += plane_size; + size += plane_size; + } + return size; +} + +static void update_osd(struct vo *vo) +{ + struct priv *p = vo->priv; + if (!p->enable_osd) + return; + + if (!gl_video_check_osd_change(p->gl_video, &p->osd_res, p->osd_pts)) { + p->skip_osd = true; + return; + } + + MP_STATS(vo, "start rpi_osd"); + + struct vo_frame frame = {0}; + struct ra_fbo target = { + .tex = ra_create_wrapped_fb(p->egl.ra, 0, p->osd_res.w, p->osd_res.h), + .flip = true, + }; + gl_video_set_osd_pts(p->gl_video, p->osd_pts); + gl_video_render_frame(p->gl_video, &frame, target, RENDER_FRAME_DEF); + ra_tex_free(p->egl.ra, &target.tex); + + MP_STATS(vo, "stop rpi_osd"); +} + +static void resize(struct vo *vo) +{ + struct priv *p = vo->priv; + MMAL_PORT_T *input = p->renderer->input[0]; + + struct mp_rect src, dst; + + vo_get_src_dst_rects(vo, &src, &dst, &p->osd_res); + + int rotate[] = {MMAL_DISPLAY_ROT0, + MMAL_DISPLAY_ROT90, + MMAL_DISPLAY_ROT180, + MMAL_DISPLAY_ROT270}; + + + int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0, + dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0; + int p_x, p_y; + av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000); + MMAL_DISPLAYREGION_T dr = { + .hdr = { .id = MMAL_PARAMETER_DISPLAYREGION, + .size = sizeof(MMAL_DISPLAYREGION_T), }, + .src_rect = { .x = src.x0, .y = src.y0, .width = src_w, .height = src_h }, + .dest_rect = { .x = dst.x0 + p->x, .y = dst.y0 + p->y, + .width = dst_w, .height = dst_h }, + .layer = p->video_layer, + .display_num = p->display_nr, + .pixel_x = p_x, + .pixel_y = p_y, + .transform = rotate[vo->params ? vo->params->rotate / 90 : 0], + .fullscreen = vo->opts->fullscreen, + .set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT | + MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM | + MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM | + MMAL_DISPLAY_SET_FULLSCREEN, + }; + + if (vo->params && (vo->params->rotate % 180) == 90) { + MPSWAP(int, dr.src_rect.x, dr.src_rect.y); + MPSWAP(int, dr.src_rect.width, dr.src_rect.height); + } + + if (mmal_port_parameter_set(input, &dr.hdr)) + MP_WARN(vo, "could not set video rectangle\n"); + + if (p->gl_video) + gl_video_resize(p->gl_video, &src, &dst, &p->osd_res); +} + +static void destroy_overlays(struct vo *vo) +{ + struct priv *p = vo->priv; + + if (p->window) + vc_dispmanx_element_remove(p->update, p->window); + p->window = 0; + + gl_video_uninit(p->gl_video); + p->gl_video = NULL; + ra_free(&p->egl.ra); + mp_egl_rpi_destroy(&p->egl); + + if (p->osd_overlay) + vc_dispmanx_element_remove(p->update, p->osd_overlay); + p->osd_overlay = 0; +} + +static int update_display_size(struct vo *vo) +{ + struct priv *p = vo->priv; + + uint32_t n_w = 0, n_h = 0; + if (graphics_get_display_size(0, &n_w, &n_h) < 0) { + MP_FATAL(vo, "Could not get display size.\n"); + return -1; + } + + if (p->w == n_w && p->h == n_h) + return 0; + + p->w = n_w; + p->h = n_h; + + MP_VERBOSE(vo, "Display size: %dx%d\n", p->w, p->h); + + return 0; +} + +static int create_overlays(struct vo *vo) +{ + struct priv *p = vo->priv; + destroy_overlays(vo); + + if (!p->display) + return -1; + + if (vo->opts->fullscreen && p->background) { + // Use the whole screen. + VC_RECT_T dst = {.width = p->w, .height = p->h}; + VC_RECT_T src = {.width = 1 << 16, .height = 1 << 16}; + VC_DISPMANX_ALPHA_T alpha = { + .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, + .opacity = 0xFF, + }; + + p->window = vc_dispmanx_element_add(p->update, p->display, + p->background_layer, + &dst, 0, &src, + DISPMANX_PROTECTION_NONE, + &alpha, 0, 0); + if (!p->window) { + MP_FATAL(vo, "Could not add DISPMANX element.\n"); + return -1; + } + } + + if (p->enable_osd) { + VC_RECT_T dst = {.x = p->x, .y = p->y, + .width = p->osd_res.w, .height = p->osd_res.h}; + VC_RECT_T src = {.width = p->osd_res.w << 16, .height = p->osd_res.h << 16}; + VC_DISPMANX_ALPHA_T alpha = { + .flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE, + .opacity = 0xFF, + }; + p->osd_overlay = vc_dispmanx_element_add(p->update, p->display, + p->osd_layer, + &dst, 0, &src, + DISPMANX_PROTECTION_NONE, + &alpha, 0, 0); + if (!p->osd_overlay) { + MP_FATAL(vo, "Could not add DISPMANX element.\n"); + return -1; + } + + if (mp_egl_rpi_init(&p->egl, p->osd_overlay, + p->osd_res.w, p->osd_res.h) < 0) + { + MP_FATAL(vo, "EGL/GLES initialization for OSD renderer failed.\n"); + return -1; + } + p->gl_video = gl_video_init(p->egl.ra, vo->log, vo->global); + gl_video_set_clear_color(p->gl_video, (struct m_color){.a = 0}); + gl_video_set_osd_source(p->gl_video, vo->osd); + } + + p->display_fps = 0; + TV_GET_STATE_RESP_T tvstate; + TV_DISPLAY_STATE_T tvstate_disp; + if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) { + if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) { + p->display_fps = tvstate_disp.display.hdmi.frame_rate; + + HDMI_PROPERTY_PARAM_T param = { + .property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE, + }; + if (!vc_tv_hdmi_get_property(¶m) && + param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC) + p->display_fps = p->display_fps / 1.001; + } else { + p->display_fps = tvstate_disp.display.sdtv.frame_rate; + } + } + + resize(vo); + + vo_event(vo, VO_EVENT_WIN_STATE); + + vc_dispmanx_update_submit_sync(p->update); + p->update = vc_dispmanx_update_start(10); + + return 0; +} + +static int set_geometry(struct vo *vo) +{ + struct priv *p = vo->priv; + + if (vo->opts->fullscreen) { + vo->dwidth = p->w; + vo->dheight = p->h; + p->x = p->y = 0; + } else { + struct vo_win_geometry geo; + struct mp_rect screenrc = {0, 0, p->w, p->h}; + + vo_calc_window_geometry(vo, &screenrc, &geo); + vo_apply_window_geometry(vo, &geo); + + p->x = geo.win.x0; + p->y = geo.win.y0; + } + + resize(vo); + + if (create_overlays(vo) < 0) + return -1; + + return 0; +} + +static void wait_next_vsync(struct vo *vo) +{ + struct priv *p = vo->priv; + mp_mutex_lock(&p->display_mutex); + int64_t end = mp_time_ns() + MP_TIME_MS_TO_NS(50); + int64_t old = p->vsync_counter; + while (old == p->vsync_counter && !p->reload_display) { + if (mp_cond_timedwait_until(&p->display_cond, &p->display_mutex, end)) + break; + } + mp_mutex_unlock(&p->display_mutex); +} + +static void flip_page(struct vo *vo) +{ + struct priv *p = vo->priv; + + if (!p->renderer_enabled) + return; + + struct mp_image *mpi = p->next_image; + p->next_image = NULL; + + // For OSD + if (!p->skip_osd && p->egl.gl) + eglSwapBuffers(p->egl.egl_display, p->egl.egl_surface); + p->skip_osd = false; + + if (mpi) { + MMAL_PORT_T *input = p->renderer->input[0]; + MMAL_BUFFER_HEADER_T *ref = (void *)mpi->planes[3]; + + // Assume this field is free for use by us. + ref->user_data = mpi; + + if (mmal_port_send_buffer(input, ref)) { + MP_ERR(vo, "could not queue picture!\n"); + talloc_free(mpi); + } + } + + if (p->display_synced) + wait_next_vsync(vo); +} + +static void free_mmal_buffer(void *arg) +{ + MMAL_BUFFER_HEADER_T *buffer = arg; + mmal_buffer_header_release(buffer); +} + +static void draw_frame(struct vo *vo, struct vo_frame *frame) +{ + struct priv *p = vo->priv; + + if (!p->renderer_enabled) + return; + + mp_image_t *mpi = NULL; + if (!frame->redraw && !frame->repeat) + mpi = mp_image_new_ref(frame->current); + + talloc_free(p->next_image); + p->next_image = NULL; + + if (mpi) + p->osd_pts = mpi->pts; + + // Redraw only if the OSD has meaningfully changed, which we assume it + // hasn't when a frame is merely repeated for display sync. + p->skip_osd = !frame->redraw && frame->repeat; + + if (!p->skip_osd && p->egl.gl) + update_osd(vo); + + p->display_synced = frame->display_synced; + + if (mpi && mpi->imgfmt != IMGFMT_MMAL) { + MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue); + if (!buffer) { + talloc_free(mpi); + MP_ERR(vo, "Can't allocate buffer.\n"); + return; + } + mmal_buffer_header_reset(buffer); + + struct mp_image *new_ref = mp_image_new_custom_ref(NULL, buffer, + free_mmal_buffer); + if (!new_ref) { + mmal_buffer_header_release(buffer); + talloc_free(mpi); + MP_ERR(vo, "Out of memory.\n"); + return; + } + + mp_image_setfmt(new_ref, IMGFMT_MMAL); + new_ref->planes[3] = (void *)buffer; + + struct mp_image dmpi = {0}; + buffer->length = layout_buffer(&dmpi, buffer, vo->params); + mp_image_copy(&dmpi, mpi); + + talloc_free(mpi); + mpi = new_ref; + } + + p->next_image = mpi; +} + +static int query_format(struct vo *vo, int format) +{ + return format == IMGFMT_MMAL || format == IMGFMT_420P; +} + +static MMAL_FOURCC_T map_csp(enum mp_csp csp) +{ + switch (csp) { + case MP_CSP_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601; + case MP_CSP_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709; + case MP_CSP_SMPTE_240M: return MMAL_COLOR_SPACE_SMPTE240M; + default: return MMAL_COLOR_SPACE_UNKNOWN; + } +} + +static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + mmal_buffer_header_release(buffer); +} + +static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + struct mp_image *mpi = buffer->user_data; + talloc_free(mpi); +} + +static void disable_renderer(struct vo *vo) +{ + struct priv *p = vo->priv; + + if (p->renderer_enabled) { + mmal_port_disable(p->renderer->control); + mmal_port_disable(p->renderer->input[0]); + + mmal_port_flush(p->renderer->control); + mmal_port_flush(p->renderer->input[0]); + + mmal_component_disable(p->renderer); + } + mmal_pool_destroy(p->swpool); + p->swpool = NULL; + p->renderer_enabled = false; +} + +static int reconfig(struct vo *vo, struct mp_image_params *params) +{ + struct priv *p = vo->priv; + MMAL_PORT_T *input = p->renderer->input[0]; + bool opaque = params->imgfmt == IMGFMT_MMAL; + + if (!p->display) + return -1; + + disable_renderer(vo); + + input->format->encoding = opaque ? MMAL_ENCODING_OPAQUE : MMAL_ENCODING_I420; + input->format->es->video.width = MP_ALIGN_UP(params->w, ALIGN_W); + input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H); + input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h}; + input->format->es->video.par = (MMAL_RATIONAL_T){params->p_w, params->p_h}; + input->format->es->video.color_space = map_csp(params->color.space); + + if (mmal_port_format_commit(input)) + return -1; + + input->buffer_num = MPMAX(input->buffer_num_min, + input->buffer_num_recommended) + 3; + input->buffer_size = MPMAX(input->buffer_size_min, + input->buffer_size_recommended); + + if (!opaque) { + size_t size = layout_buffer(&(struct mp_image){0}, NULL, params); + if (input->buffer_size != size) { + MP_FATAL(vo, "We disagree with MMAL about buffer sizes.\n"); + return -1; + } + + p->swpool = mmal_pool_create(input->buffer_num, input->buffer_size); + if (!p->swpool) { + MP_FATAL(vo, "Could not allocate buffer pool.\n"); + return -1; + } + } + + if (set_geometry(vo) < 0) + return -1; + + p->renderer_enabled = true; + + if (mmal_port_enable(p->renderer->control, control_port_cb)) + return -1; + + if (mmal_port_enable(input, input_port_cb)) + return -1; + + if (mmal_component_enable(p->renderer)) { + MP_FATAL(vo, "Failed to enable video renderer.\n"); + return -1; + } + + resize(vo); + + return 0; +} + +static struct mp_image *take_screenshot(struct vo *vo) +{ + struct priv *p = vo->priv; + + if (!p->display) + return NULL; + + struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, p->w, p->h); + if (!img) + return NULL; + + DISPMANX_RESOURCE_HANDLE_T resource = + vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, + img->w | ((img->w * 4) << 16), img->h, + &(int32_t){0}); + if (!resource) + goto fail; + + if (vc_dispmanx_snapshot(p->display, resource, 0)) + goto fail; + + VC_RECT_T rc = {.width = img->w, .height = img->h}; + if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0])) + goto fail; + + vc_dispmanx_resource_delete(resource); + return img; + +fail: + vc_dispmanx_resource_delete(resource); + talloc_free(img); + return NULL; +} + +static void set_fullscreen(struct vo *vo) { + struct priv *p = vo->priv; + + if (p->renderer_enabled) + set_geometry(vo); + vo->want_redraw = true; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *p = vo->priv; + + switch (request) { + case VOCTRL_VO_OPTS_CHANGED: { + void *opt; + while (m_config_cache_get_next_changed(p->opts_cache, &opt)) { + struct mp_vo_opts *opts = p->opts_cache->opts; + if (&opts->fullscreen == opt) + set_fullscreen(vo); + } + return VO_TRUE; + } + case VOCTRL_SET_PANSCAN: + if (p->renderer_enabled) + resize(vo); + vo->want_redraw = true; + return VO_TRUE; + case VOCTRL_REDRAW_FRAME: + update_osd(vo); + return VO_TRUE; + case VOCTRL_SCREENSHOT_WIN: + *(struct mp_image **)data = take_screenshot(vo); + return VO_TRUE; + case VOCTRL_CHECK_EVENTS: { + mp_mutex_lock(&p->display_mutex); + bool reload_required = p->reload_display; + p->reload_display = false; + mp_mutex_unlock(&p->display_mutex); + if (reload_required) + recreate_renderer(vo); + return VO_TRUE; + } + case VOCTRL_GET_DISPLAY_FPS: + *(double *)data = p->display_fps; + return VO_TRUE; + case VOCTRL_GET_DISPLAY_RES: + ((int *)data)[0] = p->w; + ((int *)data)[1] = p->h; + return VO_TRUE; + } + + return VO_NOTIMPL; +} + +static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1, + uint32_t param2) +{ + struct vo *vo = callback_data; + struct priv *p = vo->priv; + mp_mutex_lock(&p->display_mutex); + p->reload_display = true; + mp_cond_signal(&p->display_cond); + mp_mutex_unlock(&p->display_mutex); + vo_wakeup(vo); +} + +static void vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *arg) +{ + struct vo *vo = arg; + struct priv *p = vo->priv; + mp_mutex_lock(&p->display_mutex); + p->vsync_counter += 1; + mp_cond_signal(&p->display_cond); + mp_mutex_unlock(&p->display_mutex); +} + +static void destroy_dispmanx(struct vo *vo) +{ + struct priv *p = vo->priv; + + disable_renderer(vo); + destroy_overlays(vo); + + if (p->update) + vc_dispmanx_update_submit_sync(p->update); + p->update = 0; + + if (p->display) { + vc_dispmanx_vsync_callback(p->display, NULL, NULL); + vc_dispmanx_display_close(p->display); + } + p->display = 0; +} + +static int recreate_dispmanx(struct vo *vo) +{ + struct priv *p = vo->priv; + + p->display = vc_dispmanx_display_open(p->display_nr); + p->update = vc_dispmanx_update_start(0); + if (!p->display || !p->update) { + MP_FATAL(vo, "Could not get DISPMANX objects.\n"); + if (p->display) + vc_dispmanx_display_close(p->display); + p->display = 0; + p->update = 0; + return -1; + } + + update_display_size(vo); + + vc_dispmanx_vsync_callback(p->display, vsync_callback, vo); + + return 0; +} + +static void recreate_renderer(struct vo *vo) +{ + MP_WARN(vo, "Recreating renderer after display change.\n"); + + destroy_dispmanx(vo); + recreate_dispmanx(vo); + + if (vo->params) { + if (reconfig(vo, vo->params) < 0) + MP_FATAL(vo, "Recreation failed.\n"); + } +} + +static void uninit(struct vo *vo) +{ + struct priv *p = vo->priv; + + vc_tv_unregister_callback_full(tv_callback, vo); + + talloc_free(p->next_image); + + destroy_dispmanx(vo); + + if (p->renderer) + mmal_component_release(p->renderer); + + mmal_vc_deinit(); + + mp_cond_destroy(&p->display_cond); + mp_mutex_destroy(&p->display_mutex); +} + +static int preinit(struct vo *vo) +{ + struct priv *p = vo->priv; + + p->background_layer = p->layer; + p->video_layer = p->layer + 1; + p->osd_layer = p->layer + 2; + + p->egl.log = vo->log; + + bcm_host_init(); + + if (mmal_vc_init()) { + MP_FATAL(vo, "Could not initialize MMAL.\n"); + return -1; + } + + mp_mutex_init(&p->display_mutex); + mp_cond_init(&p->display_cond); + + p->opts_cache = m_config_cache_alloc(p, vo->global, &vo_sub_opts); + + if (recreate_dispmanx(vo) < 0) + goto fail; + + if (update_display_size(vo) < 0) + goto fail; + + if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer)) + { + MP_FATAL(vo, "Could not create MMAL renderer.\n"); + goto fail; + } + + vc_tv_register_callback(tv_callback, vo); + + return 0; + +fail: + uninit(vo); + return -1; +} + +#define OPT_BASE_STRUCT struct priv +static const struct m_option options[] = { + {"display", OPT_INT(display_nr)}, + {"layer", OPT_INT(layer), OPTDEF_INT(-10)}, + {"background", OPT_BOOL(background)}, + {"osd", OPT_BOOL(enable_osd), OPTDEF_INT(1)}, + {0}, +}; + +const struct vo_driver video_out_rpi = { + .description = "Raspberry Pi (MMAL)", + .name = "rpi", + .caps = VO_CAP_ROTATE90, + .preinit = preinit, + .query_format = query_format, + .reconfig = reconfig, + .control = control, + .draw_frame = draw_frame, + .flip_page = flip_page, + .uninit = uninit, + .priv_size = sizeof(struct priv), + .options = options, + .options_prefix = "rpi", +}; |