You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

535 lines
19 KiB

/*****************************************************************************
* ARIB STD-B24 caption decoder/renderer using libaribcaption.
*****************************************************************************
* Copyright (C) 2022 magicxqq
*
* Authors: magicxqq <xqq@xqq.im>
*
* 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.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_atomic.h>
#include <vlc_codec.h>
#include <aribcaption/aribcaption.h>
/*****************************************************************************
* Local prototypes
*****************************************************************************/
typedef struct
{
/* The following fields of decoder_sys_t are shared between decoder and spu units */
vlc_atomic_rc_t rc;
bool b_cfg_fadeout;
aribcc_context_t *p_context;
aribcc_decoder_t *p_decoder;
aribcc_renderer_t *p_renderer;
vlc_mutex_t dec_lock; // protect p_dec pointer
decoder_t* p_dec; // pointer to decoder_t for logcat callback, NULL if decoder closed
} decoder_sys_t;
typedef struct
{
decoder_sys_t *p_dec_sys;
vlc_tick_t i_pts;
aribcc_render_result_t render_result;
} libaribcaption_spu_updater_sys_t;
static void DecSysRetain(decoder_sys_t *p_sys)
{
vlc_atomic_rc_inc(&p_sys->rc);
}
static void DecSysRelease(decoder_sys_t *p_sys)
{
if (!vlc_atomic_rc_dec(&p_sys->rc))
return;
if (p_sys->p_renderer)
aribcc_renderer_free(p_sys->p_renderer);
if (p_sys->p_decoder)
aribcc_decoder_free(p_sys->p_decoder);
if (p_sys->p_context)
aribcc_context_free(p_sys->p_context);
free(p_sys);
}
/****************************************************************************
*
****************************************************************************/
static void CopyImageToRegion(picture_t *dst_pic, const aribcc_image_t *image)
{
if(image->pixel_format != ARIBCC_PIXELFORMAT_RGBA8888)
return;
plane_t *p_dstplane = &dst_pic->p[0];
plane_t srcplane;
srcplane.i_lines = image->height;
srcplane.i_pitch = image->stride;
srcplane.i_pixel_pitch = p_dstplane->i_pixel_pitch;
srcplane.i_visible_lines = image->height;
srcplane.i_visible_pitch = image->width /* in pixels */ * p_dstplane->i_pixel_pitch;
srcplane.p_pixels = image->bitmap;
plane_CopyPixels( p_dstplane, &srcplane );
}
static void SubpictureUpdate(subpicture_t *p_subpic,
const struct vlc_spu_updater_configuration *cfg)
{
libaribcaption_spu_updater_sys_t *p_spusys = p_subpic->updater.sys;
decoder_sys_t *p_sys = p_spusys->p_dec_sys;
const video_format_t *p_src_format = cfg->video_src;
const video_format_t *p_dst_format = cfg->video_dst;
bool b_src_changed = p_src_format->i_visible_width != cfg->prev_src->i_visible_width ||
p_src_format->i_visible_height != cfg->prev_src->i_visible_height;
bool b_dst_changed = !video_format_IsSimilar(cfg->prev_dst, p_dst_format);
unsigned i_render_area_width = p_dst_format->i_visible_width;
unsigned i_render_area_height = p_src_format->i_visible_height * p_dst_format->i_visible_width /
p_src_format->i_visible_width;
if (b_src_changed || b_dst_changed) {
/* don't let library freely scale using either the min of width or height ratio */
aribcc_renderer_set_frame_size(p_sys->p_renderer, i_render_area_width,
i_render_area_height);
}
const vlc_tick_t i_stream_date = p_spusys->i_pts + (cfg->pts - p_subpic->i_start);
/* Retrieve the expected render status for detecting whether the subtitle image changed */
aribcc_render_status_t status = aribcc_renderer_try_render(p_sys->p_renderer,
MS_FROM_VLC_TICK(i_stream_date));
if (status == ARIBCC_RENDER_STATUS_GOT_IMAGE_UNCHANGED) {
/* Skip rendering since images were not changed */
if (!b_src_changed && !b_dst_changed) {
return;
}
}
status = aribcc_renderer_render(p_sys->p_renderer,
MS_FROM_VLC_TICK(i_stream_date),
&p_spusys->render_result);
if (status == ARIBCC_RENDER_STATUS_ERROR) {
return;
}
vlc_spu_regions_Clear( &p_subpic->regions );
aribcc_image_t *p_images = p_spusys->render_result.images;
uint32_t i_image_count = p_spusys->render_result.image_count;
if (!p_images || i_image_count == 0) {
return;
}
p_subpic->i_original_picture_width = i_render_area_width;
p_subpic->i_original_picture_height = i_render_area_height;
video_format_t fmt_region = *p_dst_format;
fmt_region.i_chroma = VLC_CODEC_RGBA;
fmt_region.i_x_offset = 0;
fmt_region.i_y_offset = 0;
fmt_region.transfer = TRANSFER_FUNC_SRGB;
fmt_region.primaries = COLOR_PRIMARIES_SRGB;
fmt_region.space = COLOR_SPACE_SRGB;
fmt_region.color_range = COLOR_RANGE_FULL;
/* Allocate the regions and draw them */
for (uint32_t i = 0; i < i_image_count; i++) {
aribcc_image_t *image = &p_images[i];
fmt_region.i_width =
fmt_region.i_visible_width = image->width;
fmt_region.i_height =
fmt_region.i_visible_height = image->height;
fmt_region.i_sar_num = 1;
fmt_region.i_sar_den = 1;
subpicture_region_t *region = subpicture_region_New(&fmt_region);
if (!region)
break;
region->b_absolute = true; region->b_in_window = false;
region->i_x = image->dst_x;
region->i_y = image->dst_y;
region->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
CopyImageToRegion(region->p_picture, image);
vlc_spu_regions_push(&p_subpic->regions, region);
}
aribcc_render_result_cleanup(&p_spusys->render_result);
}
static void SubpictureDestroy(subpicture_t *p_subpic)
{
libaribcaption_spu_updater_sys_t *p_spusys = p_subpic->updater.sys;
DecSysRelease(p_spusys->p_dec_sys);
free(p_spusys);
}
/****************************************************************************
* Decode:
****************************************************************************/
static int Decode(decoder_t *p_dec, block_t *p_block)
{
decoder_sys_t *p_sys = p_dec->p_sys;
if (p_block == NULL) /* No Drain */
return VLCDEC_SUCCESS;
if (p_block->i_flags & BLOCK_FLAG_CORRUPTED) {
block_Release(p_block);
return VLCDEC_SUCCESS;
}
aribcc_caption_t caption;
aribcc_decode_status_t status = aribcc_decoder_decode(p_sys->p_decoder,
p_block->p_buffer,
p_block->i_buffer,
MS_FROM_VLC_TICK(p_block->i_pts),
&caption);
if (status == ARIBCC_DECODE_STATUS_ERROR) {
msg_Err(p_dec, "aribcc_decoder_decode() returned with error");
}
if (status == ARIBCC_DECODE_STATUS_ERROR || status == ARIBCC_DECODE_STATUS_NO_CAPTION) {
block_Release(p_block);
return VLCDEC_SUCCESS;
}
aribcc_renderer_append_caption(p_sys->p_renderer, &caption);
aribcc_caption_cleanup(&caption);
libaribcaption_spu_updater_sys_t *p_spusys = malloc(sizeof(libaribcaption_spu_updater_sys_t));
if (!p_spusys) {
block_Release(p_block);
return VLCDEC_SUCCESS;
}
p_spusys->p_dec_sys = p_sys;
p_spusys->i_pts = p_block->i_pts;
memset(&p_spusys->render_result, 0, sizeof(p_spusys->render_result));
static const struct vlc_spu_updater_ops spu_ops =
{
.update = SubpictureUpdate,
.destroy = SubpictureDestroy,
};
subpicture_updater_t updater = {
.sys = p_spusys,
.ops = &spu_ops,
};
subpicture_t *p_spu = decoder_NewSubpicture(p_dec, &updater);
if (!p_spu) {
msg_Warn(p_dec, "can't get spu buffer");
free(p_spusys);
block_Release(p_block);
return VLCDEC_SUCCESS;
}
p_spu->i_start = p_block->i_pts;
p_spu->i_stop = p_block->i_pts;
p_spu->b_fade = p_sys->b_cfg_fadeout;
if (caption.wait_duration == ARIBCC_DURATION_INDEFINITE) {
p_spu->b_ephemer = true;
} else {
p_spu->i_stop = p_block->i_pts + VLC_TICK_FROM_MS(caption.wait_duration);
}
DecSysRetain(p_sys); /* Keep a reference for the returned subpicture */
block_Release(p_block);
decoder_QueueSub(p_dec, p_spu);
return VLCDEC_SUCCESS;
}
/*****************************************************************************
* Flush:
*****************************************************************************/
static void Flush(decoder_t *p_dec)
{
decoder_sys_t *p_sys = p_dec->p_sys;
aribcc_decoder_flush(p_sys->p_decoder);
aribcc_renderer_flush(p_sys->p_renderer);
}
/*****************************************************************************
* libaribcaption logcat callback function
*****************************************************************************/
static void LogcatCallback(aribcc_loglevel_t level, const char *message, void *userdata)
{
decoder_sys_t *p_sys = userdata;
if (p_sys->p_dec != NULL) {
vlc_mutex_lock(&p_sys->dec_lock);
if (p_sys->p_dec != NULL) {
decoder_t* p_dec = p_sys->p_dec;
if (level == ARIBCC_LOGLEVEL_ERROR) {
msg_Err(p_dec, "%s", message);
} else if (level == ARIBCC_LOGLEVEL_WARNING) {
msg_Warn(p_dec, "%s", message);
} else {
msg_Dbg(p_dec, "%s", message);
}
}
vlc_mutex_unlock(&p_sys->dec_lock);
}
}
#define ARIBCAPTION_CFG_PREFIX "aribcaption-"
/*****************************************************************************
* Open: Create libaribcaption context/decoder/renderer.
*****************************************************************************/
static int Open(vlc_object_t *p_this)
{
decoder_t *p_dec = (decoder_t *)p_this;
decoder_sys_t *p_sys;
if (p_dec->fmt_in->i_codec != VLC_CODEC_ARIB_A &&
p_dec->fmt_in->i_codec != VLC_CODEC_ARIB_C) {
return VLC_ENOTSUP;
}
p_sys = malloc(sizeof(decoder_sys_t));
if (unlikely(p_sys == NULL))
return VLC_ENOMEM;
vlc_atomic_rc_init(&p_sys->rc);
p_sys->b_cfg_fadeout = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "fadeout");
vlc_mutex_init(&p_sys->dec_lock);
p_sys->p_dec = p_dec;
/* Create libaribcaption context */
aribcc_context_t *p_ctx = p_sys->p_context = aribcc_context_alloc();
if (!p_ctx) {
msg_Err(p_dec, "libaribcaption context allocation failed");
DecSysRelease(p_sys);
return VLC_EGENERIC;
}
aribcc_context_set_logcat_callback(p_ctx, LogcatCallback, p_sys);
/* Create the decoder */
aribcc_decoder_t* p_decoder = p_sys->p_decoder = aribcc_decoder_alloc(p_ctx);
if (!p_decoder) {
msg_Err(p_dec, "libaribcaption decoder creation failed");
DecSysRelease(p_sys);
return VLC_EGENERIC;
}
aribcc_profile_t i_profile = ARIBCC_PROFILE_A;
if (p_dec->fmt_in->i_codec == VLC_CODEC_ARIB_C) {
i_profile = ARIBCC_PROFILE_C;
}
bool b_succ = aribcc_decoder_initialize(p_decoder,
ARIBCC_ENCODING_SCHEME_AUTO,
ARIBCC_CAPTIONTYPE_CAPTION,
i_profile,
ARIBCC_LANGUAGEID_FIRST);
if (!b_succ) {
msg_Err(p_dec, "libaribcaption decoder initialization failed");
DecSysRelease(p_sys);
return VLC_EGENERIC;
}
/* Create the renderer */
aribcc_renderer_t* p_renderer = p_sys->p_renderer = aribcc_renderer_alloc(p_ctx);
if (!p_renderer) {
msg_Err(p_dec, "libaribcaption renderer creation failed");
DecSysRelease(p_sys);
return VLC_EGENERIC;
}
int i_cfg_rendering_backend =
var_InheritInteger(p_this, ARIBCAPTION_CFG_PREFIX "rendering-backend");
b_succ = aribcc_renderer_initialize(p_renderer,
ARIBCC_CAPTIONTYPE_CAPTION,
ARIBCC_FONTPROVIDER_TYPE_AUTO,
(aribcc_textrenderer_type_t)i_cfg_rendering_backend);
if (!b_succ) {
msg_Err(p_dec, "libaribcaption renderer initialization failed");
DecSysRelease(p_sys);
return VLC_EGENERIC;
}
aribcc_renderer_set_storage_policy(p_renderer, ARIBCC_CAPTION_STORAGE_POLICY_MINIMUM, 0);
bool b_cfg_replace_drcs = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "replace-drcs");
bool b_cfg_force_stroke_text = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "force-stroke-text");
bool b_cfg_ignore_background = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "ignore-background");
bool b_cfg_ignore_ruby = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "ignore-ruby");
float f_cfg_stroke_width = var_InheritFloat(p_this, ARIBCAPTION_CFG_PREFIX "stroke-width");
aribcc_renderer_set_replace_drcs(p_renderer, b_cfg_replace_drcs);
aribcc_renderer_set_force_stroke_text(p_renderer, b_cfg_force_stroke_text);
aribcc_renderer_set_force_no_background(p_renderer, b_cfg_ignore_background);
aribcc_renderer_set_force_no_ruby(p_renderer, b_cfg_ignore_ruby);
aribcc_renderer_set_stroke_width(p_renderer, f_cfg_stroke_width);
char *psz_cfg_font_name = var_InheritString(p_this, ARIBCAPTION_CFG_PREFIX "font");
if (psz_cfg_font_name) {
const char* font_families[] = { psz_cfg_font_name };
aribcc_renderer_set_default_font_family(p_renderer,
font_families,
ARRAY_SIZE(font_families),
true);
free(psz_cfg_font_name);
}
p_dec->p_sys = p_sys;
p_dec->pf_decode = Decode;
p_dec->pf_flush = Flush;
p_dec->fmt_out.i_codec = VLC_CODEC_RGBA;
return VLC_SUCCESS;
}
/*****************************************************************************
* Close: finish
*****************************************************************************/
static void Close(vlc_object_t *p_this)
{
decoder_t *p_dec = (decoder_t *)p_this;
decoder_sys_t *p_sys = p_dec->p_sys;
vlc_mutex_lock(&p_sys->dec_lock);
p_sys->p_dec = NULL;
vlc_mutex_unlock(&p_sys->dec_lock);
DecSysRelease(p_sys);
}
#define CFG_TEXT_RENDERING_BACKEND N_("Text rendering backend")
#define CFG_LONGTEXT_RENDERING_BACKEND N_("Select text rendering backend")
#define CFG_TEXT_FONT N_("Font")
#define CFG_LONGTEXT_FONT N_("Select font")
#define CFG_TEXT_REPLACE_DRCS N_("Replace known DRCS")
#define CFG_LONGTEXT_REPLACE_DRCS N_("Replace known DRCS in the caption")
#define CFG_TEXT_FORCE_STROKE_TEXT N_("Force stroke text")
#define CFG_LONGTEXT_FORCE_STROKE_TEXT N_("Always render characters with stroke")
#define CFG_TEXT_IGNORE_BACKGROUND N_("Ignore background")
#define CFG_LONGTEXT_IGNORE_BACKGROUND N_("Ignore rendering caption background")
#define CFG_TEXT_IGNORE_RUBY N_("Ignore ruby (furigana)")
#define CFG_LONGTEXT_IGNORE_RUBY N_("Ignore ruby-like characters, like furigana")
#define CFG_TEXT_FADEOUT N_("Fadeout")
#define CFG_LONGTEXT_FADEOUT N_("Enable Fadeout")
#define CFG_TEXT_STROKE_WIDTH N_("Stroke width")
#define CFG_LONGTEXT_STROKE_WIDTH N_("Indicate stroke width for stroke text")
static const int rendering_backend_values[] = {
ARIBCC_TEXTRENDERER_TYPE_AUTO,
#if defined(ARIBCC_USE_CORETEXT)
ARIBCC_TEXTRENDERER_TYPE_CORETEXT,
#endif
#if defined(ARIBCC_USE_DIRECTWRITE)
ARIBCC_TEXTRENDERER_TYPE_DIRECTWRITE,
#endif
#if defined(ARIBCC_USE_FREETYPE)
ARIBCC_TEXTRENDERER_TYPE_FREETYPE
#endif
};
static const char* const ppsz_rendering_backend_descriptions[] = {
N_("Auto"),
#if defined(ARIBCC_USE_CORETEXT)
N_("CoreText"),
#endif
#if defined(ARIBCC_USE_DIRECTWRITE)
N_("DirectWrite"),
#endif
#if defined(ARIBCC_USE_FREETYPE)
N_("FreeType")
#endif
};
#ifdef __APPLE__
# define DEFAULT_FAMILY "Hiragino Maru Gothic ProN"
#elif defined(_WIN32)
# define DEFAULT_FAMILY "MS Gothic"
#else
# define DEFAULT_FAMILY "sans-serif"
#endif
/*****************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin ()
set_shortname(N_("ARIB caption"))
set_description(N_("ARIB caption renderer"))
set_capability("spu decoder", 60)
set_subcategory(SUBCAT_INPUT_SCODEC)
set_callbacks(Open, Close)
add_integer(ARIBCAPTION_CFG_PREFIX "rendering-backend", 0, CFG_TEXT_RENDERING_BACKEND, CFG_LONGTEXT_RENDERING_BACKEND)
change_integer_list(rendering_backend_values, ppsz_rendering_backend_descriptions)
add_font(ARIBCAPTION_CFG_PREFIX "font", DEFAULT_FAMILY, CFG_TEXT_FONT, CFG_LONGTEXT_FONT)
add_bool(ARIBCAPTION_CFG_PREFIX "replace-drcs", true, CFG_TEXT_REPLACE_DRCS, CFG_LONGTEXT_REPLACE_DRCS)
add_bool(ARIBCAPTION_CFG_PREFIX "force-stroke-text", false, CFG_TEXT_FORCE_STROKE_TEXT, CFG_LONGTEXT_FORCE_STROKE_TEXT)
add_bool(ARIBCAPTION_CFG_PREFIX "ignore-background", false, CFG_TEXT_IGNORE_BACKGROUND, CFG_LONGTEXT_IGNORE_BACKGROUND)
add_bool(ARIBCAPTION_CFG_PREFIX "ignore-ruby", false, CFG_TEXT_IGNORE_RUBY, CFG_LONGTEXT_IGNORE_RUBY)
add_bool(ARIBCAPTION_CFG_PREFIX "fadeout", false, CFG_TEXT_FADEOUT, CFG_LONGTEXT_FADEOUT)
add_float_with_range(ARIBCAPTION_CFG_PREFIX "stroke-width", 1.5f, 0.0f, 3.0f, CFG_TEXT_STROKE_WIDTH, CFG_LONGTEXT_STROKE_WIDTH)
vlc_module_end ()