#include <vlc_demux.h> /* demux_t */
#include <vlc_input.h> /* Seekpoints, chapters */
#include <vlc_dialog.h> /* BD+/AACS warnings */
+#include <vlc_vout.h> /* vout_PutSubpicture / subpicture_t */
#include <libbluray/bluray.h>
#include <libbluray/meta_data.h>
+#include <libbluray/overlay.h>
/*****************************************************************************
* Module descriptor
set_callbacks( blurayOpen, blurayClose )
vlc_module_end ()
+/* libbluray's overlay.h defines 2 types of overlay (bd_overlay_plane_e). */
+#define MAX_OVERLAY 2
-struct demux_sys_t
+typedef enum OverlayStatus {
+ Closed = 0,
+ ToDisplay, //Used to mark the overlay to be displayed the first time.
+ Displayed,
+ Outdated //used to update the overlay after it has been sent to the vout
+} OverlayStatus;
+
+typedef struct bluray_overlay_t
{
- BLURAY *bluray;
+ VLC_GC_MEMBERS
+
+ vlc_mutex_t lock;
+ subpicture_t *p_pic;
+ OverlayStatus status;
+ subpicture_region_t *p_regions;
+} bluray_overlay_t;
+
+struct demux_sys_t
+{
+ BLURAY *bluray;
/* Titles */
- unsigned int i_title;
- unsigned int i_longest_title;
- input_title_t **pp_title;
+ unsigned int i_title;
+ unsigned int i_longest_title;
+ input_title_t **pp_title;
/* Menus */
- bool b_menu;
+ bluray_overlay_t *p_overlays[MAX_OVERLAY];
+ int current_overlay; // -1 if no current overlay;
+ bool b_menu;
+
+ /* */
+ input_thread_t *p_input;
+ vout_thread_t *p_vout;
/* TS stream */
- stream_t *p_parser;
+ stream_t *p_parser;
+};
+
+struct subpicture_updater_sys_t
+{
+ bluray_overlay_t *p_overlay;
};
/*****************************************************************************
static int blurayInitTitles(demux_t *p_demux );
static int bluraySetTitle(demux_t *p_demux, int i_title);
+static void blurayOverlayProc(void *ptr, const BD_OVERLAY * const overlay);
+
#define FROM_TICKS(a) (a*CLOCK_FREQ / INT64_C(90000))
#define TO_TICKS(a) (a*INT64_C(90000)/CLOCK_FREQ)
#define CUR_LENGTH p_sys->pp_title[p_demux->info.i_title]->i_length
}
/* */
- p_demux->p_sys = p_sys = malloc(sizeof(*p_sys));
+ p_demux->p_sys = p_sys = calloc(1, sizeof(*p_sys));
if (unlikely(!p_sys)) {
return VLC_ENOMEM;
}
- p_sys->p_parser = NULL;
+ p_sys->current_overlay = -1;
/* init demux info fields */
p_demux->info.i_update = 0;
p_sys->b_menu = var_InheritBool( p_demux, "bluray-menu" );
if ( p_sys->b_menu )
{
- /*
- * libbluray will start playback from "First-Title" title
- * Therefore, We don't have to select any title.
- */
- bd_play( p_sys->bluray );
+ p_sys->p_input = demux_GetParentInput(p_demux);
+ if (unlikely(!p_sys->p_input))
+ goto error;
+
+ /* libbluray will start playback from "First-Title" title */
+ bd_play(p_sys->bluray);
+
+ /* Registering overlay event handler */
+ bd_register_overlay_proc(p_sys->bluray, p_demux, blurayOverlayProc);
}
else
{
demux_t *p_demux = (demux_t*)object;
demux_sys_t *p_sys = p_demux->p_sys;
+ /*
+ * Close libbluray first.
+ * This will close all the overlays before we release p_vout
+ * bd_close( NULL ) can crash
+ */
+ assert(p_sys->bluray);
+ bd_close(p_sys->bluray);
+
+ if (p_sys->p_vout != NULL)
+ vlc_object_release(p_sys->p_vout);
+ if (p_sys->p_input != NULL)
+ vlc_object_release(p_sys->p_input);
if (p_sys->p_parser)
stream_Delete(p_sys->p_parser);
vlc_input_title_Delete(p_sys->pp_title[i]);
TAB_CLEAN( p_sys->i_title, p_sys->pp_title );
- /* bd_close( NULL ) can crash */
- assert(p_sys->bluray);
- bd_close(p_sys->bluray);
free(p_sys);
}
+/*****************************************************************************
+ * subpicture_updater_t functions:
+ *****************************************************************************/
+static int subpictureUpdaterValidate( subpicture_t *p_subpic,
+ bool b_fmt_src, const video_format_t *p_fmt_src,
+ bool b_fmt_dst, const video_format_t *p_fmt_dst,
+ mtime_t i_ts )
+{
+ VLC_UNUSED( b_fmt_src );
+ VLC_UNUSED( b_fmt_dst );
+ VLC_UNUSED( p_fmt_src );
+ VLC_UNUSED( p_fmt_dst );
+ VLC_UNUSED( i_ts );
+
+ subpicture_updater_sys_t *p_upd_sys = p_subpic->updater.p_sys;
+ bluray_overlay_t *p_overlay = p_upd_sys->p_overlay;
+
+ vlc_mutex_lock(&p_overlay->lock);
+ int res = p_overlay->status == Outdated;
+ vlc_mutex_unlock(&p_overlay->lock);
+ return res;
+}
+
+/* This should probably be moved to subpictures.c afterward */
+static subpicture_region_t* subpicture_region_Clone(subpicture_region_t *p_region_src)
+{
+ if (!p_region_src)
+ return NULL;
+ subpicture_region_t *p_region_dst = subpicture_region_New(&p_region_src->fmt);
+ if (unlikely(!p_region_dst))
+ return NULL;
+
+ p_region_dst->i_x = p_region_src->i_x;
+ p_region_dst->i_y = p_region_src->i_y;
+ p_region_dst->i_align = p_region_src->i_align;
+ p_region_dst->i_alpha = p_region_src->i_alpha;
+
+ p_region_dst->psz_text = p_region_src->psz_text ? strdup(p_region_src->psz_text) : NULL;
+ p_region_dst->psz_html = p_region_src->psz_html ? strdup(p_region_src->psz_html) : NULL;
+ if (p_region_src->p_style != NULL) {
+ p_region_dst->p_style = malloc(sizeof(*p_region_dst->p_style));
+ p_region_dst->p_style = text_style_Copy(p_region_dst->p_style,
+ p_region_src->p_style);
+ }
+
+ //Palette is already copied by subpicture_region_New, we just have to duplicate p_pixels
+ for (int i = 0; i < p_region_src->p_picture->i_planes; i++)
+ memcpy(p_region_dst->p_picture->p[i].p_pixels,
+ p_region_src->p_picture->p[i].p_pixels,
+ p_region_src->p_picture->p[i].i_lines * p_region_src->p_picture->p[i].i_pitch);
+ return p_region_dst;
+}
+
+static void subpictureUpdaterUpdate(subpicture_t *p_subpic,
+ const video_format_t *p_fmt_src,
+ const video_format_t *p_fmt_dst,
+ mtime_t i_ts)
+{
+ VLC_UNUSED(p_fmt_src);
+ VLC_UNUSED(p_fmt_dst);
+ VLC_UNUSED(i_ts);
+ subpicture_updater_sys_t *p_upd_sys = p_subpic->updater.p_sys;
+ bluray_overlay_t *p_overlay = p_upd_sys->p_overlay;
+
+ /*
+ * When this function is called, all p_subpic regions are gone.
+ * We need to duplicate our regions (stored internaly) to this subpic.
+ */
+ vlc_mutex_lock(&p_overlay->lock);
+
+ subpicture_region_t *p_src = p_overlay->p_regions;
+ if (!p_src)
+ return;
+
+ subpicture_region_t **p_dst = &(p_subpic->p_region);
+ while (p_src != NULL) {
+ *p_dst = subpicture_region_Clone(p_src);
+ if (*p_dst == NULL)
+ break ;
+ p_dst = &((*p_dst)->p_next);
+ p_src = p_src->p_next;
+ }
+ if (*p_dst != NULL)
+ (*p_dst)->p_next = NULL;
+ p_overlay->status = Displayed;
+ vlc_mutex_unlock(&p_overlay->lock);
+}
+
+static void subpictureUpdaterDestroy(subpicture_t *p_subpic)
+{
+ vlc_gc_decref(p_subpic->updater.p_sys->p_overlay);
+}
+
+/*****************************************************************************
+ * libbluray overlay handling:
+ *****************************************************************************/
+static void blurayCleanOverayStruct(gc_object_t *p_gc)
+{
+ bluray_overlay_t *p_overlay = vlc_priv(p_gc, bluray_overlay_t);
+
+ /*
+ * This will be called when destroying the picture.
+ * Don't delete it again from here!
+ */
+ vlc_mutex_destroy(&p_overlay->lock);
+ subpicture_region_Delete(p_overlay->p_regions);
+ free(p_overlay);
+}
+
+static void blurayCloseAllOverlays(demux_t *p_demux)
+{
+ demux_sys_t *p_sys = p_demux->p_sys;
+
+ p_demux->p_sys->current_overlay = -1;
+ if (p_sys->p_vout != NULL) {
+ for (int i = 0; i < 0; i++) {
+ if (p_sys->p_overlays[i] != NULL) {
+ vout_FlushSubpictureChannel(p_sys->p_vout,
+ p_sys->p_overlays[i]->p_pic->i_channel);
+ vlc_gc_decref(p_sys->p_overlays[i]);
+ p_sys->p_overlays[i] = NULL;
+ }
+ }
+ vlc_object_release(p_sys->p_vout);
+ p_sys->p_vout = NULL;
+ }
+}
+
+/*
+ * Mark the overlay as "ToDisplay" status.
+ * This will not send the overlay to the vout instantly, as the vout
+ * may not be acquired (not acquirable) yet.
+ * If is has already been acquired, the overlay has already been sent to it,
+ * therefore, we only flag the overlay as "Outdated"
+ */
+static void blurayActivateOverlay(demux_t *p_demux, const BD_OVERLAY* const ov)
+{
+ demux_sys_t *p_sys = p_demux->p_sys;
+
+ /*
+ * If the overlay is already displayed, mark the picture as outdated.
+ * We must NOT use vout_PutSubpicture if a picture is already displayed.
+ */
+ vlc_mutex_lock(&p_sys->p_overlays[ov->plane]->lock);
+ if ((p_sys->p_overlays[ov->plane]->status == Displayed ||
+ p_sys->p_overlays[ov->plane]->status == Outdated)
+ && p_sys->p_vout) {
+ p_sys->p_overlays[ov->plane]->status = Outdated;
+ vlc_mutex_unlock(&p_sys->p_overlays[ov->plane]->lock);
+ return ;
+ }
+ /*
+ * Mark the overlay as available, but don't display it right now.
+ * the blurayDemuxMenu will send it to vout, as it may be unavailable when
+ * the overlay is computed
+ */
+ p_sys->current_overlay = ov->plane;
+ p_sys->p_overlays[ov->plane]->status = ToDisplay;
+ vlc_mutex_unlock(&p_sys->p_overlays[ov->plane]->lock);
+}
+
+static void blurayInitOverlay(demux_t *p_demux, const BD_OVERLAY* const ov)
+{
+ demux_sys_t *p_sys = p_demux->p_sys;
+
+ assert(p_sys->p_overlays[ov->plane] == NULL);
+
+ p_sys->p_overlays[ov->plane] = calloc(1, sizeof(**p_sys->p_overlays));
+ if (unlikely(!p_sys->p_overlays[ov->plane]))
+ return;
+
+ subpicture_updater_sys_t *p_upd_sys = malloc(sizeof(*p_upd_sys));
+ if (unlikely(!p_upd_sys)) {
+ free(p_sys->p_overlays[ov->plane]);
+ p_sys->p_overlays[ov->plane] = NULL;
+ return;
+ }
+ vlc_gc_init(p_sys->p_overlays[ov->plane], blurayCleanOverayStruct);
+ /* Incrementing refcounter: vout + demux */
+ vlc_gc_incref(p_sys->p_overlays[ov->plane]);
+
+ p_upd_sys->p_overlay = p_sys->p_overlays[ov->plane];
+ subpicture_updater_t updater = {
+ .pf_validate = subpictureUpdaterValidate,
+ .pf_update = subpictureUpdaterUpdate,
+ .pf_destroy = subpictureUpdaterDestroy,
+ .p_sys = p_upd_sys,
+ };
+ p_sys->p_overlays[ov->plane]->p_pic = subpicture_New(&updater);
+ p_sys->p_overlays[ov->plane]->p_pic->i_original_picture_width = ov->w;
+ p_sys->p_overlays[ov->plane]->p_pic->i_original_picture_height = ov->h;
+ p_sys->p_overlays[ov->plane]->p_pic->b_ephemer = true;
+ p_sys->p_overlays[ov->plane]->p_pic->b_absolute = true;
+}
+
+/**
+ * Destroy every regions in the subpicture.
+ * This is done in two steps:
+ * - Wiping our private regions list
+ * - Flagging the overlay as outdated, so the changes are replicated from
+ * the subpicture_updater_t::pf_update
+ * This doesn't destroy the subpicture, as the overlay may be used again by libbluray.
+ */
+static void blurayClearOverlay(demux_t *p_demux, const BD_OVERLAY* const ov)
+{
+ demux_sys_t *p_sys = p_demux->p_sys;
+
+ vlc_mutex_lock(&p_sys->p_overlays[ov->plane]->lock);
+
+ subpicture_region_ChainDelete(p_sys->p_overlays[ov->plane]->p_regions);
+ p_sys->p_overlays[ov->plane]->p_regions = NULL;
+ p_sys->p_overlays[ov->plane]->status = Outdated;
+ vlc_mutex_unlock(&p_sys->p_overlays[ov->plane]->lock);
+}
+
+/*
+ * This will draw to the overlay by adding a region to our region list
+ * This will have to be copied to the subpicture used to render the overlay.
+ */
+static void blurayDrawOverlay(demux_t *p_demux, const BD_OVERLAY* const ov)
+{
+ demux_sys_t *p_sys = p_demux->p_sys;
+
+ /*
+ * Compute a subpicture_region_t.
+ * It will be copied and sent to the vout later.
+ */
+ if (!ov->img)
+ return;
+
+ vlc_mutex_lock(&p_sys->p_overlays[ov->plane]->lock);
+
+ /* Find a region to update */
+ subpicture_region_t *p_reg = p_sys->p_overlays[ov->plane]->p_regions;
+ subpicture_region_t *p_last = NULL;
+ while (p_reg != NULL) {
+ p_last = p_reg;
+ if (p_reg->i_x == ov->x && p_reg->i_y == ov->y &&
+ p_reg->fmt.i_width == ov->w && p_reg->fmt.i_height == ov->h)
+ break;
+ p_reg = p_reg->p_next;
+ }
+
+ /* If there is no region to update, create a new one. */
+ if (!p_reg) {
+ video_format_t fmt;
+ video_format_Init(&fmt, 0);
+ video_format_Setup(&fmt, VLC_CODEC_YUVP, ov->w, ov->h, 1, 1);
+
+ p_reg = subpicture_region_New(&fmt);
+ p_reg->i_x = ov->x;
+ p_reg->i_y = ov->y;
+ /* Append it to our list. */
+ if (p_last != NULL)
+ p_last->p_next = p_reg;
+ else /* If we don't have a last region, then our list empty */
+ p_sys->p_overlays[ov->plane]->p_regions = p_reg;
+ }
+
+ /* Now we can update the region, regardless it's an update or an insert */
+ const BD_PG_RLE_ELEM *img = ov->img;
+ for (int y = 0; y < ov->h; y++) {
+ for (int x = 0; x < ov->w;) {
+ memset(p_reg->p_picture->p[0].p_pixels +
+ y * p_reg->p_picture->p[0].i_pitch + x,
+ img->color, img->len);
+ x += img->len;
+ img++;
+ }
+ }
+ if (ov->palette) {
+ p_reg->fmt.p_palette->i_entries = 256;
+ for (int i = 0; i < 256; ++i) {
+ p_reg->fmt.p_palette->palette[i][0] = ov->palette[i].Y;
+ p_reg->fmt.p_palette->palette[i][1] = ov->palette[i].Cb;
+ p_reg->fmt.p_palette->palette[i][2] = ov->palette[i].Cr;
+ p_reg->fmt.p_palette->palette[i][3] = ov->palette[i].T;
+ }
+ }
+ vlc_mutex_unlock(&p_sys->p_overlays[ov->plane]->lock);
+ /*
+ * /!\ The region is now stored in our internal list, but not in the subpicture /!\
+ */
+}
+
+static void blurayOverlayProc(void *ptr, const BD_OVERLAY *const overlay)
+{
+ demux_t *p_demux = (demux_t*)ptr;
+
+ if (!overlay) {
+ msg_Info(p_demux, "Closing overlay.");
+ blurayCloseAllOverlays(p_demux);
+ return;
+ }
+ switch (overlay->cmd) {
+ case BD_OVERLAY_INIT:
+ msg_Info(p_demux, "Initializing overlay");
+ blurayInitOverlay(p_demux, overlay);
+ break;
+ case BD_OVERLAY_CLEAR:
+ blurayClearOverlay(p_demux, overlay);
+ break;
+ case BD_OVERLAY_FLUSH:
+ blurayActivateOverlay(p_demux, overlay);
+ break;
+ case BD_OVERLAY_DRAW:
+ blurayDrawOverlay(p_demux, overlay);
+ break;
+ default:
+ msg_Warn(p_demux, "Unknown BD overlay command: %u", overlay->cmd);
+ break;
+ }
+}
+
+static void bluraySendOverlayToVout(demux_t *p_demux)
+{
+ demux_sys_t *p_sys = p_demux->p_sys;
+
+ assert(p_sys->current_overlay >= 0 &&
+ p_sys->p_overlays[p_sys->current_overlay] != NULL &&
+ p_sys->p_overlays[p_sys->current_overlay]->p_pic != NULL);
+
+ p_sys->p_overlays[p_sys->current_overlay]->p_pic->i_start =
+ p_sys->p_overlays[p_sys->current_overlay]->p_pic->i_stop = mdate();
+ p_sys->p_overlays[p_sys->current_overlay]->p_pic->i_channel =
+ vout_RegisterSubpictureChannel(p_sys->p_vout);
+ /*
+ * After this point, the picture should not be accessed from the demux thread,
+ * as it's hold by the vout thread.
+ * This must be done only once per subpicture, ie. only once between each
+ * blurayInitOverlay & blurayCloseOverlay call.
+ */
+ vout_PutSubpicture(p_sys->p_vout, p_sys->p_overlays[p_sys->current_overlay]->p_pic);
+ /*
+ * Mark the picture as Outdated, as it contains no region for now.
+ * This will make the subpicture_updater_t call pf_update
+ */
+ p_sys->p_overlays[p_sys->current_overlay]->status = Outdated;
+}
static int blurayInitTitles(demux_t *p_demux )
{
block_Release(p_block);
return 1;
}
+ if (p_sys->current_overlay != -1)
+ {
+ vlc_mutex_lock(&p_sys->p_overlays[p_sys->current_overlay]->lock);
+ if (p_sys->p_overlays[p_sys->current_overlay]->status == ToDisplay) {
+ vlc_mutex_unlock(&p_sys->p_overlays[p_sys->current_overlay]->lock);
+ if (p_sys->p_vout == NULL)
+ p_sys->p_vout = input_GetVout(p_sys->p_input);
+ if (p_sys->p_vout != NULL) {
+ bluraySendOverlayToVout(p_demux);
+ }
+ } else
+ vlc_mutex_unlock(&p_sys->p_overlays[p_sys->current_overlay]->lock);
+ }
}
p_block->i_buffer = nread;