Skip to content

Instantly share code, notes, and snippets.

@xqq
Last active February 5, 2022 12:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xqq/890fcbb508bcfd950324bdfdeb27e778 to your computer and use it in GitHub Desktop.
Save xqq/890fcbb508bcfd950324bdfdeb27e778 to your computer and use it in GitHub Desktop.
/*****************************************************************************
* 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 = &region->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