|
/***************************************************************************** |
|
* 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_codec.h> |
|
|
|
#include <aribcaption/aribcaption.h> |
|
|
|
|
|
/***************************************************************************** |
|
* Module descriptor |
|
*****************************************************************************/ |
|
static int Open(vlc_object_t *); |
|
static void Close(vlc_object_t *); |
|
|
|
#define ARIBCAPTION_CFG_PREFIX "aribcaption-" |
|
|
|
#define CFG_TEXT_REPLACE_DRCS N_("Replace known DRCS") |
|
|
|
#define CFG_TEXT_FORCE_STROKE_TEXT N_("Force stroke text") |
|
|
|
#define CFG_TEXT_IGNORE_BACKGROUND N_("Ignore background") |
|
|
|
#define CFG_TEXT_IGNORE_RUBY N_("Ignore ruby (furigana)") |
|
|
|
#define CFG_TEXT_FADEOUT N_("Fadeout") |
|
|
|
vlc_module_begin () |
|
set_shortname(N_("ARIB caption")) |
|
set_description(N_("ARIB caption renderer using libaribcaption")) |
|
set_capability("spu decoder", 60) |
|
set_subcategory(SUBCAT_INPUT_SCODEC) |
|
set_callbacks(Open, Close) |
|
|
|
add_bool(ARIBCAPTION_CFG_PREFIX "replace-drcs", true, CFG_TEXT_REPLACE_DRCS, CFG_TEXT_REPLACE_DRCS) |
|
add_bool(ARIBCAPTION_CFG_PREFIX "force-stroke-text", false, CFG_TEXT_FORCE_STROKE_TEXT, CFG_TEXT_FORCE_STROKE_TEXT) |
|
add_bool(ARIBCAPTION_CFG_PREFIX "ignore-background", false, CFG_TEXT_IGNORE_BACKGROUND, CFG_TEXT_IGNORE_BACKGROUND) |
|
add_bool(ARIBCAPTION_CFG_PREFIX "ignore-ruby", false, CFG_TEXT_IGNORE_RUBY, CFG_TEXT_IGNORE_RUBY) |
|
add_bool(ARIBCAPTION_CFG_PREFIX "fadeout", false, CFG_TEXT_FADEOUT, CFG_TEXT_FADEOUT) |
|
vlc_module_end () |
|
|
|
/***************************************************************************** |
|
* Local prototypes |
|
*****************************************************************************/ |
|
static int Decode(decoder_t *, block_t *); |
|
static void Flush(decoder_t *); |
|
|
|
/* */ |
|
typedef struct |
|
{ |
|
/* The following fields of decoder_sys_t are shared between decoder and spu units */ |
|
vlc_mutex_t lock; |
|
int i_refcount; |
|
|
|
bool b_cfg_replace_drcs; |
|
bool b_cfg_force_stroke_text; |
|
bool b_cfg_ignore_background; |
|
bool b_cfg_ignore_ruby; |
|
bool b_cfg_fadeout; |
|
|
|
decoder_t *p_dec; |
|
|
|
aribcc_context_t *p_context; |
|
aribcc_decoder_t *p_decoder; |
|
aribcc_renderer_t *p_renderer; |
|
video_format_t fmt; |
|
} decoder_sys_t; |
|
|
|
static void DecSysRetain(decoder_sys_t *p_sys); |
|
|
|
static void DecSysRelease(decoder_sys_t *p_sys); |
|
|
|
|
|
static void LogcatCallback(aribcc_loglevel_t level, const char *message, void *userdata); |
|
|
|
|
|
/* */ |
|
static int SubpictureValidate(subpicture_t *, |
|
bool, const video_format_t *, |
|
bool, const video_format_t *, |
|
vlc_tick_t); |
|
static void SubpictureUpdate(subpicture_t *, |
|
const video_format_t *, |
|
const video_format_t *, |
|
vlc_tick_t); |
|
static void SubpictureDestroy(subpicture_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 CopyImageToRegion(subpicture_region_t *p_region, const aribcc_image_t* image); |
|
|
|
|
|
|
|
/***************************************************************************** |
|
* 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_EGENERIC; |
|
} |
|
|
|
p_sys = (decoder_sys_t*)calloc(1, sizeof(decoder_sys_t)); |
|
if (!p_sys) |
|
return VLC_ENOMEM; |
|
|
|
vlc_mutex_init(&p_sys->lock); |
|
p_sys->i_refcount = 1; |
|
video_format_Init(&p_sys->fmt, 0); |
|
p_sys->p_dec = p_dec; |
|
|
|
p_sys->b_cfg_replace_drcs = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "replace-drcs"); |
|
p_sys->b_cfg_force_stroke_text = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "force-stroke-text"); |
|
p_sys->b_cfg_ignore_background = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "ignore-background"); |
|
p_sys->b_cfg_ignore_ruby = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "ignore-ruby"); |
|
p_sys->b_cfg_fadeout = var_InheritBool(p_this, ARIBCAPTION_CFG_PREFIX "fadeout"); |
|
|
|
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; |
|
|
|
|
|
/* 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, (void*)p_dec); |
|
|
|
|
|
/* 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; |
|
} |
|
|
|
b_succ = aribcc_renderer_initialize(p_renderer, |
|
ARIBCC_CAPTIONTYPE_CAPTION, |
|
ARIBCC_FONTPROVIDER_TYPE_AUTO, |
|
ARIBCC_TEXTRENDERER_TYPE_AUTO); |
|
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); |
|
aribcc_renderer_set_replace_drcs(p_renderer, p_sys->b_cfg_replace_drcs); |
|
aribcc_renderer_set_force_stroke_text(p_renderer, p_sys->b_cfg_force_stroke_text); |
|
aribcc_renderer_set_force_no_background(p_renderer, p_sys->b_cfg_ignore_background); |
|
aribcc_renderer_set_force_no_ruby(p_renderer, p_sys->b_cfg_ignore_ruby); |
|
|
|
return VLC_SUCCESS; |
|
} |
|
|
|
/***************************************************************************** |
|
* Close: finish |
|
*****************************************************************************/ |
|
static void Close(vlc_object_t *p_this) |
|
{ |
|
decoder_t *p_dec = (decoder_t *)p_this; |
|
|
|
var_Destroy(p_this, ARIBCAPTION_CFG_PREFIX "replace-drcs"); |
|
var_Destroy(p_this, ARIBCAPTION_CFG_PREFIX "force-stroke-text"); |
|
var_Destroy(p_this, ARIBCAPTION_CFG_PREFIX "ignore-background"); |
|
var_Destroy(p_this, ARIBCAPTION_CFG_PREFIX "ignore-ruby"); |
|
var_Destroy(p_this, ARIBCAPTION_CFG_PREFIX "fadeout"); |
|
|
|
DecSysRelease(p_dec->p_sys); |
|
} |
|
|
|
|
|
static void DecSysRetain(decoder_sys_t *p_sys) |
|
{ |
|
vlc_mutex_lock(&p_sys->lock); |
|
p_sys->i_refcount++; |
|
vlc_mutex_unlock(&p_sys->lock); |
|
} |
|
|
|
static void DecSysRelease(decoder_sys_t *p_sys) |
|
{ |
|
vlc_mutex_lock(&p_sys->lock); |
|
p_sys->i_refcount--; |
|
if (p_sys->i_refcount > 0) { |
|
vlc_mutex_unlock(&p_sys->lock); |
|
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); |
|
|
|
vlc_mutex_unlock(&p_sys->lock); |
|
free(p_sys); |
|
} |
|
|
|
static void LogcatCallback(aribcc_loglevel_t level, const char *message, void *userdata) |
|
{ |
|
decoder_t *p_dec = (decoder_t *)userdata; |
|
|
|
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); |
|
} |
|
} |
|
|
|
/***************************************************************************** |
|
* Flush: |
|
*****************************************************************************/ |
|
static void Flush(decoder_t *p_dec) |
|
{ |
|
decoder_sys_t *p_sys = p_dec->p_sys; |
|
vlc_mutex_lock(&p_sys->lock); |
|
aribcc_decoder_flush(p_sys->p_decoder); |
|
aribcc_renderer_flush(p_sys->p_renderer); |
|
vlc_mutex_unlock(&p_sys->lock); |
|
} |
|
|
|
/**************************************************************************** |
|
* 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; |
|
} |
|
|
|
if (p_block->i_buffer == 0 || p_block->p_buffer[0] == '\0') { |
|
block_Release(p_block); |
|
return VLCDEC_SUCCESS; |
|
} |
|
|
|
vlc_mutex_lock(&p_sys->lock); |
|
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_NO_CAPTION) { |
|
vlc_mutex_unlock(&p_sys->lock); |
|
block_Release(p_block); |
|
return VLCDEC_SUCCESS; |
|
} else if (status == ARIBCC_DECODE_STATUS_ERROR) { |
|
msg_Err(p_dec, "aribcc_decoder_decode() returned with error"); |
|
vlc_mutex_unlock(&p_sys->lock); |
|
block_Release(p_block); |
|
return VLCDEC_SUCCESS; |
|
} else if (status == ARIBCC_DECODE_STATUS_GOT_CAPTION) { |
|
aribcc_renderer_append_caption(p_sys->p_renderer, &caption); |
|
aribcc_caption_cleanup(&caption); |
|
} |
|
vlc_mutex_unlock(&p_sys->lock); |
|
|
|
libaribcaption_spu_updater_sys_t *p_spusys = calloc(1, sizeof(*p_spusys)); |
|
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; |
|
|
|
subpicture_updater_t updater = { |
|
.pf_validate = SubpictureValidate, |
|
.pf_update = SubpictureUpdate, |
|
.pf_destroy = SubpictureDestroy, |
|
.p_sys = p_spusys, |
|
}; |
|
|
|
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_absolute = true; |
|
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); |
|
|
|
if (p_spu) |
|
decoder_QueueSub(p_dec, p_spu); |
|
|
|
return VLCDEC_SUCCESS; |
|
} |
|
|
|
/**************************************************************************** |
|
* |
|
****************************************************************************/ |
|
static int SubpictureValidate(subpicture_t *p_subpic, |
|
bool b_src_changed, const video_format_t *p_src_format, |
|
bool b_dst_changed, const video_format_t *p_dst_format, |
|
vlc_tick_t i_ts) |
|
{ |
|
VLC_UNUSED(p_src_format); |
|
|
|
libaribcaption_spu_updater_sys_t *p_spusys = p_subpic->updater.p_sys; |
|
decoder_sys_t *p_sys = p_spusys->p_dec_sys; |
|
|
|
vlc_mutex_lock(&p_sys->lock); |
|
|
|
video_format_t fmt = *p_dst_format; |
|
fmt.i_chroma = VLC_CODEC_RGBA; |
|
fmt.i_bits_per_pixel = 0; |
|
fmt.i_x_offset = 0; |
|
fmt.i_y_offset = 0; |
|
|
|
if (b_src_changed || b_dst_changed) { |
|
aribcc_renderer_set_frame_size(p_sys->p_renderer, fmt.i_visible_width, fmt.i_visible_height); |
|
p_sys->fmt = fmt; |
|
} |
|
|
|
const vlc_tick_t i_stream_date = p_spusys->i_pts + (i_ts - p_subpic->i_start); |
|
bool b_changed; |
|
|
|
aribcc_render_status_t 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) { |
|
msg_Err(p_sys->p_dec, "aribcc_renderer_render() returned with error"); |
|
vlc_mutex_unlock(&p_sys->lock); |
|
return VLC_SUCCESS; |
|
} else if (status == ARIBCC_RENDER_STATUS_GOT_IMAGE_UNCHANGED) { |
|
b_changed = false; |
|
} else { |
|
b_changed = true; |
|
} |
|
|
|
if (!b_changed && !b_src_changed && !b_dst_changed && |
|
(p_spusys->render_result.images != NULL) == (p_subpic->p_region != NULL)) { |
|
aribcc_render_result_cleanup(&p_spusys->render_result); |
|
vlc_mutex_unlock(&p_sys->lock); |
|
return VLC_SUCCESS; |
|
} |
|
|
|
/* The lock is released by SubpictureUpdate */ |
|
return VLC_EGENERIC; |
|
} |
|
|
|
static void SubpictureUpdate(subpicture_t *p_subpic, |
|
const video_format_t *p_src_format, |
|
const video_format_t *p_dst_format, |
|
vlc_tick_t i_ts) |
|
{ |
|
VLC_UNUSED(p_src_format); VLC_UNUSED(p_dst_format); VLC_UNUSED(i_ts); |
|
|
|
libaribcaption_spu_updater_sys_t *p_spusys = p_subpic->updater.p_sys; |
|
decoder_sys_t *p_sys = p_spusys->p_dec_sys; |
|
|
|
video_format_t fmt = p_sys->fmt; |
|
aribcc_image_t *p_images = p_spusys->render_result.images; |
|
uint32_t i_image_count = p_spusys->render_result.image_count; |
|
|
|
p_subpic->i_original_picture_width = fmt.i_visible_width; |
|
p_subpic->i_original_picture_height = fmt.i_visible_height; |
|
|
|
if (!p_images || i_image_count == 0) { |
|
vlc_mutex_unlock(&p_sys->lock); |
|
return; |
|
} |
|
|
|
/* Allocate the regions and draw them */ |
|
subpicture_region_t **pp_region_last = &p_subpic->p_region; |
|
|
|
for (uint32_t i = 0; i < i_image_count; i++) { |
|
aribcc_image_t *image = &p_images[i]; |
|
video_format_t fmt_region = fmt; |
|
|
|
fmt_region.i_width = |
|
fmt_region.i_visible_width = image->width; |
|
fmt_region.i_height = |
|
fmt_region.i_visible_height = image->height; |
|
|
|
subpicture_region_t *region = subpicture_region_New(&fmt_region); |
|
if (!region) |
|
break; |
|
|
|
region->i_x = image->dst_x; |
|
region->i_y = image->dst_y; |
|
region->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT; |
|
|
|
CopyImageToRegion(region, image); |
|
|
|
*pp_region_last = region; |
|
pp_region_last = ®ion->p_next; |
|
} |
|
|
|
aribcc_render_result_cleanup(&p_spusys->render_result); |
|
vlc_mutex_unlock(&p_sys->lock); |
|
} |
|
|
|
static void SubpictureDestroy(subpicture_t *p_subpic) |
|
{ |
|
libaribcaption_spu_updater_sys_t *p_spusys = p_subpic->updater.p_sys; |
|
DecSysRelease(p_spusys->p_dec_sys); |
|
free(p_spusys); |
|
} |
|
|
|
static void CopyImageToRegion(subpicture_region_t *p_region, const aribcc_image_t *image) |
|
{ |
|
const plane_t *p = &p_region->p_picture->p[0]; |
|
const int i_height = p_region->fmt.i_height; |
|
const uint32_t copy_line_size = __MIN(image->stride, p->i_pitch); |
|
|
|
memset(p->p_pixels, 0, p->i_pitch * p->i_visible_lines); |
|
|
|
for (int y = 0; y < i_height; y++) { |
|
const uint8_t *src_line_begin = image->bitmap + y * image->stride; |
|
uint8_t *dst_line_begin = p->p_pixels + y * p->i_pitch; |
|
memcpy(dst_line_begin, src_line_begin, copy_line_size); |
|
} |
|
} |