Last active
February 5, 2022 12:44
-
-
Save xqq/890fcbb508bcfd950324bdfdeb27e778 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***************************************************************************** | |
* 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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment