2 * ARIB STD-B24 caption decoder using the libaribcaption library
3 * Copyright (c) 2022 TADANO Tokumei
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "codec_internal.h"
25 #include "libavcodec/ass.h"
26 #include "libavutil/avstring.h"
27 #include "libavutil/avutil.h"
28 #include "libavutil/thread.h"
29 #include "libavutil/log.h"
30 #include "libavutil/opt.h"
32 #include <aribcaption/aribcaption.h>
34 #if !defined(DEFAULT_FONT_ASS)
35 # define DEFAULT_FONT_ASS "sans-serif"
38 #define ARIBC_BPRINT_SIZE_INIT 64
39 #define ARIBC_BPRINT_SIZE_MAX (8 * 1024)
40 #define ARIBC_ALPHA_MAX_NUM 4
41 #define ARIBC_ALPHA_DEFAULT_FRONT 0xFF
42 #define ARIBC_ALPHA_DEFAULT_BACK 0x80
44 #define ARIBCC_COLOR_RGB(c) ((c) & 0xFFFFFF)
45 #define ARIBCC_COLOR_DIFF_RGB(c1,c2) (((c1) ^ (c2)) & 0x00FFFFFF)
46 #define ARIBCC_COLOR_DIFF_A(c1,c2) (((c1) ^ (c2)) & 0xFF000000)
48 #define CLUT_RGBA(r,g,b,a) (((unsigned)(a) << 24) | ((r) << 16) | ((g) << 8) | (b))
49 #define CLUT_A(c) (((c) >> 24) & 0xFF)
50 #define CLUT_R(c) (((c) >> 16) & 0xFF)
51 #define CLUT_G(c) (((c) >> 8) & 0xFF)
52 #define CLUT_B(c) ( (c) & 0xFF)
54 #define ARIBCC_COLOR_TO_CLUT_RGBA(c,a) (((ARIBCC_COLOR_A(c) ? ARIBCC_COLOR_A(c) : (a)) << 24) | \
55 (ARIBCC_COLOR_R(c) << 16) | \
56 (ARIBCC_COLOR_G(c) << 8) | \
59 typedef struct ARIBCaptionContext {
61 AVCodecContext *avctx;
62 const AVPacket *avpkt;
65 aribcc_context_t *context;
66 aribcc_decoder_t *decoder;
67 aribcc_renderer_t *renderer;
73 int force_stroke_text;
74 int ignore_background;
78 int replace_msz_ascii;
79 int replace_msz_japanese;
80 int replace_msz_glyph;
90 int bitmap_plane_width;
91 int bitmap_plane_height;
97 aribcc_caption_t caption;
98 aribcc_render_result_t render_result;
102 uint8_t clut_alpha[ARIBC_ALPHA_MAX_NUM];
103 } ARIBCaptionContext;
105 static void hex_dump_debug(void *ctx, const char *buf, int buf_size)
109 for (i = 0; i < buf_size; i++) {
110 ff_dlog(ctx, "%02hhx ", buf[i]);
118 static void logcat_callback(aribcc_loglevel_t level, const char* message, void* userdata)
120 ARIBCaptionContext *ctx = userdata;
123 if (ctx->decoder != NULL) {
125 case ARIBCC_LOGLEVEL_ERROR:
128 case ARIBCC_LOGLEVEL_WARNING:
129 lvl = AV_LOG_WARNING;
135 av_log(ctx, lvl, "%s\n", message);
139 static void estimate_video_frame_size(ARIBCaptionContext *ctx)
141 if (ctx->avctx->width > 0 && ctx->avctx->height > 0) {
142 /* input video size specified by -canvas_size option */
143 ctx->bitmap_plane_width = ctx->avctx->width;
144 ctx->bitmap_plane_height = ctx->avctx->height;
145 } else if (ctx->plane_width == 960) {
146 /* ARIB TR-B14 Fascicle 2 Volume 3 [Section 2] 4.3.1 */
147 /* ARIB TR-B14 Fascicle 2 Volume 3 [Section 2] Appendix-4 */
148 ctx->bitmap_plane_width = 1440;
149 ctx->bitmap_plane_height = 1080;
151 ctx->bitmap_plane_width = ctx->plane_width;
152 ctx->bitmap_plane_height = ctx->plane_height;
154 /* Expand either width or height */
155 if (ctx->bitmap_plane_height * ctx->plane_width > ctx->bitmap_plane_width * ctx->plane_height) {
156 ctx->frame_height = ctx->bitmap_plane_height;
157 ctx->frame_width = ctx->frame_height * ctx->plane_width / ctx->plane_height;
159 ctx->frame_width = ctx->bitmap_plane_width;
160 ctx->frame_height = ctx->frame_width * ctx->plane_height / ctx->plane_width;
164 static void clut_set_alpha(ARIBCaptionContext *ctx, uint8_t a)
168 for (i = 0; i < ARIBC_ALPHA_MAX_NUM; i++) {
169 if (ctx->clut_alpha[i] == 0) {
170 ctx->clut_alpha[i] = a;
173 if (ctx->clut_alpha[i] == a)
179 static uint8_t clut_find_nearlest_alpha(ARIBCaptionContext *ctx, uint8_t a)
187 for (i = 0; i < ARIBC_ALPHA_MAX_NUM; i++) {
188 if (ctx->clut_alpha[i] == a)
190 if (ctx->clut_alpha[i] == 0)
192 if (abs((int)a - (int)ctx->clut_alpha[i]) < d) {
193 d = abs((int)a - (int)ctx->clut_alpha[i]);
197 return ctx->clut_alpha[j];
200 static int clut_find(ARIBCaptionContext *ctx, uint32_t rgba)
204 for (i = 0; i < ctx->clut_idx; i++) {
205 if (ctx->clut[i] == rgba)
211 static inline int clut_color_distance(uint32_t rgba1, uint32_t rgba2)
213 return abs((int)CLUT_R(rgba1) - (int)CLUT_R(rgba2)) +
214 abs((int)CLUT_G(rgba1) - (int)CLUT_G(rgba2)) +
215 abs((int)CLUT_B(rgba1) - (int)CLUT_B(rgba2));
218 static uint8_t clut_pick_or_set(ARIBCaptionContext *ctx, int r, int g, int b, int a)
223 a = clut_find_nearlest_alpha(ctx, a);
225 return 0; /* transparent */
226 rgba = CLUT_RGBA(r,g,b,a);
230 for (i = 0; i < ctx->clut_idx; i++) {
231 if (ctx->clut[i] == rgba)
233 if (CLUT_A(ctx->clut[i]) != a)
235 d = clut_color_distance(ctx->clut[i], rgba);
242 if (ctx->clut_idx >= AVPALETTE_COUNT)
243 ctx->clut_overflow++;
246 ctx->clut[ctx->clut_idx++] = rgba;
252 /* initialiaze CLUT with each character colors */
253 static void clut_init(ARIBCaptionContext *ctx, aribcc_caption_region_t *region)
255 aribcc_color_t text_color, back_color, stroke_color;
258 ctx->clut[0] = CLUT_RGBA(0,0,0,0); /* transparent */
259 ctx->clut_alpha[0] = 0xFF;
261 ctx->clut_overflow = 0;
262 text_color = region->chars[0].text_color;
263 back_color = region->chars[0].back_color;
264 stroke_color = region->chars[0].stroke_color;
265 rgba = ARIBCC_COLOR_TO_CLUT_RGBA(text_color, ARIBC_ALPHA_DEFAULT_FRONT);
266 ctx->clut[ctx->clut_idx++] = rgba;
267 clut_set_alpha(ctx, CLUT_A(rgba));
268 rgba = ARIBCC_COLOR_TO_CLUT_RGBA(back_color, ARIBC_ALPHA_DEFAULT_BACK);
269 ctx->clut[ctx->clut_idx++] = rgba;
270 clut_set_alpha(ctx, CLUT_A(rgba));
271 rgba = ARIBCC_COLOR_TO_CLUT_RGBA(stroke_color, ARIBC_ALPHA_DEFAULT_FRONT);
272 if (clut_find(ctx, rgba) < 0) {
273 ctx->clut[ctx->clut_idx++] = rgba;
274 clut_set_alpha(ctx, CLUT_A(rgba));
277 for (int i = 1; i < region->char_count; i++) {
278 if (region->chars[i].text_color != text_color) {
279 rgba = ARIBCC_COLOR_TO_CLUT_RGBA(region->chars[i].text_color,
280 ARIBC_ALPHA_DEFAULT_FRONT);
281 if (clut_find(ctx, rgba) < 0) {
282 ctx->clut[ctx->clut_idx++] = rgba;
283 clut_set_alpha(ctx, CLUT_A(rgba));
286 if (region->chars[i].back_color != back_color) {
287 rgba = ARIBCC_COLOR_TO_CLUT_RGBA(region->chars[i].back_color,
288 ARIBC_ALPHA_DEFAULT_BACK);
289 if (clut_find(ctx, rgba) < 0) {
290 ctx->clut[ctx->clut_idx++] = rgba;
291 clut_set_alpha(ctx, CLUT_A(rgba));
294 if (region->chars[i].stroke_color != stroke_color) {
295 rgba = ARIBCC_COLOR_TO_CLUT_RGBA(region->chars[i].stroke_color,
296 ARIBC_ALPHA_DEFAULT_FRONT);
297 if (clut_find(ctx, rgba) < 0) {
298 if (ctx->clut_idx < AVPALETTE_COUNT)
299 ctx->clut[ctx->clut_idx++] = rgba;
300 clut_set_alpha(ctx, CLUT_A(rgba));
307 * aribcaption_trans_{bitmap|ass|text}_subtitle()
309 * Transfer decoded subtitle to AVSubtitle with corresponding subtitle type.
311 * @param ctx pointer to the ARIBCaptionContext
312 * @return > 0 number of rectangles to be displayed
316 static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
319 AVSubtitle *sub = ctx->sub;
320 int status, rect_idx;
321 int old_width = ctx->frame_width;
322 int old_height = ctx->frame_height;
324 if (ctx->caption.plane_width > 0 && ctx->caption.plane_height > 0) {
325 ctx->plane_width = ctx->caption.plane_width;
326 ctx->plane_height = ctx->caption.plane_height;
328 estimate_video_frame_size(ctx);
329 if (ctx->frame_width != old_width || ctx->frame_height != old_height) {
330 ff_dlog(ctx, "canvas: %dx%d plane: %dx%d bitmap: %dx%d frame: %dx%d\n",
331 ctx->avctx->width, ctx->avctx->height,
332 ctx->plane_width, ctx->plane_height,
333 ctx->bitmap_plane_width, ctx->bitmap_plane_height,
334 ctx->frame_width, ctx->frame_height);
335 if (!aribcc_renderer_set_frame_size(ctx->renderer,
336 ctx->frame_width, ctx->frame_height)) {
337 av_log(ctx, AV_LOG_ERROR,
338 "aribcc_renderer_set_frame_size() returned with error.\n");
339 return AVERROR_EXTERNAL;
343 status = aribcc_renderer_append_caption(ctx->renderer, &ctx->caption);
345 av_log(ctx, AV_LOG_ERROR,
346 "aribcc_renderer_append_caption() returned with error.\n");
347 return AVERROR_EXTERNAL;
350 status = aribcc_renderer_render(ctx->renderer, ctx->pts, &ctx->render_result);
352 case ARIBCC_RENDER_STATUS_GOT_IMAGE:
355 case ARIBCC_RENDER_STATUS_GOT_IMAGE_UNCHANGED:
356 aribcc_render_result_cleanup(&ctx->render_result);
357 ff_dlog(ctx, "got image unchanged\n");
360 case ARIBCC_RENDER_STATUS_NO_IMAGE:
361 ff_dlog(ctx, "no image\n");
364 case ARIBCC_RENDER_STATUS_ERROR:
365 av_log(ctx, AV_LOG_ERROR,
366 "aribcc_renderer_render() returned with error.\n");
367 return AVERROR_EXTERNAL;
370 aribcc_render_result_cleanup(&ctx->render_result);
371 av_log(ctx, AV_LOG_ERROR,
372 "aribcc_renderer_render() returned unknown status: %d\n", status);
373 return AVERROR_EXTERNAL;
376 if (!ctx->render_result.image_count || ctx->render_result.images == NULL) {
377 aribcc_render_result_cleanup(&ctx->render_result);
378 ff_dlog(ctx, "no image (%d)\n", ctx->render_result.image_count);
382 sub->format = 0; /* graphic */
383 sub->rects = av_calloc(ctx->render_result.image_count, sizeof(*sub->rects));
385 ret = AVERROR(ENOMEM);
388 for (int i = 0; i < ctx->render_result.image_count; i++) {
389 sub->rects[i] = av_mallocz(sizeof(*sub->rects[i]));
390 if (!sub->rects[i]) {
391 ret = AVERROR(ENOMEM);
396 for (rect_idx = 0; rect_idx < ctx->caption.region_count; rect_idx++) {
397 AVSubtitleRect *rect = sub->rects[rect_idx];
398 aribcc_image_t *image = &ctx->render_result.images[rect_idx];
399 int w, h, shrink_height, dst_idx;
401 clut_init(ctx, &ctx->caption.regions[rect_idx]);
403 rect->w = image->width * ctx->bitmap_plane_width / ctx->frame_width;
404 rect->h = image->height * ctx->bitmap_plane_height / ctx->frame_height;
405 rect->data[0] = av_mallocz(rect->w * rect->h);
406 if (!rect->data[0]) {
407 ret = AVERROR(ENOMEM);
410 if ((image->height != rect->h && image->width != rect->w) ||
411 image->stride < image->width * 4 ||
412 image->stride * image->height > image->bitmap_size) {
413 av_log(ctx, AV_LOG_ERROR, "Bug: unexpected rendered image: %d(%d)x%d -> %dx%d\n",
414 image->width, image->stride / 4, image->height, rect->w, rect->h);
415 ret = AVERROR_EXTERNAL;
419 shrink_height = image->height != rect->h;
421 for (h = 0; h < rect->h; h++) {
422 for (w = 0; w < rect->w; w++) {
423 /* Bi-linear interpolation */
424 int n, m, idx0, idx1, r, g, b, a;
427 div_a = h * ctx->frame_height;
428 n = ctx->bitmap_plane_height;
430 y1 = FFMIN(y0 + 1, image->height - 1);
432 idx0 = image->stride * y0 + w * 4;
433 idx1 = image->stride * y1 + w * 4;
436 div_a = w * ctx->frame_width;
437 n = ctx->bitmap_plane_width;
439 x1 = FFMIN(x0 + 1, image->width - 1);
441 idx0 = image->stride * h + x0 * 4;
442 idx1 = image->stride * h + x1 * 4;
444 r = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
445 g = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
446 b = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
447 a = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
448 rect->data[0][dst_idx++] = clut_pick_or_set(ctx, r, g, b, a);
451 rect->data[1] = av_memdup(ctx->clut, AVPALETTE_SIZE);
452 if (!rect->data[1]) {
453 ret = AVERROR(ENOMEM);
457 if (ctx->avctx->profile == AV_PROFILE_ARIB_PROFILE_C) {
458 /* ARIB TR-B14 version 3.8 Fascicle 1-(2/2) Volume 3 [Section 4] */
459 /* No position information is provided for profile C */
460 rect->x = (ctx->frame_width - rect->w) / 2;
461 rect->y = ctx->frame_height - rect->h * (ctx->caption.region_count - rect_idx);
463 rect->x = image->dst_x * ctx->bitmap_plane_width / ctx->frame_width;
464 rect->y = image->dst_y * ctx->bitmap_plane_height / ctx->frame_height;
466 rect->type = SUBTITLE_BITMAP;
467 rect->linesize[0] = rect->w;
468 rect->nb_colors = 256;
470 ff_dlog(ctx, "BITMAP subtitle%s (%d,%d) %dx%d -> (%d,%d) %dx%d [%d]: %d colors\n",
471 (ctx->caption.regions[rect_idx].is_ruby) ? " (ruby)" : "",
472 image->dst_x, image->dst_y, image->width, image->height,
473 rect->x, rect->y, rect->w, rect->h,
474 rect_idx, ctx->clut_idx);
475 if (ctx->clut_overflow)
476 av_log(ctx, AV_LOG_WARNING, "CLUT overflow (%d).\n", ctx->clut_overflow);
478 sub->num_rects = rect_idx;
484 for (int i = 0; i < ctx->caption.region_count; i++) {
486 av_freep(&sub->rects[i]->data[0]);
487 av_freep(&sub->rects[i]->data[1]);
488 av_freep(&sub->rects[i]);
491 av_freep(&sub->rects);
498 static int set_ass_header(ARIBCaptionContext *ctx)
500 AVCodecContext *avctx = ctx->avctx;
502 const char *font_name;
503 const char *fonts = ctx->font;
505 if (ctx->border_style == 4) {
512 if (ctx->force_stroke_text)
513 outline = (int)(ctx->stroke_width * 4.0 / 3.0);
516 font_name = av_get_token(&fonts, ",");
518 font_name = av_strdup(DEFAULT_FONT_ASS);
520 return AVERROR(ENOMEM);
522 av_freep(&avctx->subtitle_header);
523 avctx->subtitle_header = av_asprintf(
525 "ScriptType: v4.00+\r\n"
528 "WrapStyle: 2\r\n" /* 2: no word wrapping */
533 "Fontname, Fontsize, "
534 "PrimaryColour, SecondaryColour, OutlineColour, BackColour, "
535 "Bold, Italic, Underline, StrikeOut, "
538 "BorderStyle, Outline, Shadow, "
539 "Alignment, MarginL, MarginR, MarginV, "
543 "Default," /* Name */
544 "%s,%d," /* Font{name,size} */
545 "&H%x,&H%x,&H%x,&H%x," /* {Primary,Secondary,Outline,Back}Colour */
546 "%d,%d,%d,0," /* Bold, Italic, Underline, StrikeOut */
547 "100,100," /* Scale{X,Y} */
548 "0,0," /* Spacing, Angle */
549 "%d,%d,%d," /* BorderStyle, Outline, Shadow */
550 "%d,10,10,10," /* Alignment, Margin[LRV] */
551 "0\r\n" /* Encoding */
555 "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n",
556 ctx->plane_width, ctx->plane_height,
557 font_name, ctx->font_size,
558 ASS_DEFAULT_COLOR, ASS_DEFAULT_COLOR,
559 ASS_DEFAULT_BACK_COLOR, ASS_DEFAULT_BACK_COLOR,
560 -ASS_DEFAULT_BOLD, -ASS_DEFAULT_ITALIC, -ASS_DEFAULT_UNDERLINE,
561 ctx->border_style, outline, shadow, ASS_DEFAULT_ALIGNMENT);
563 av_freep(&font_name);
564 if (!avctx->subtitle_header)
565 return AVERROR(ENOMEM);
566 avctx->subtitle_header_size = strlen(avctx->subtitle_header);
570 static void set_ass_color(AVBPrint *buf, int color_num,
571 aribcc_color_t new_color, aribcc_color_t old_color)
573 if (ARIBCC_COLOR_DIFF_RGB(new_color, old_color))
574 av_bprintf(buf, "{\\%dc&H%06x&}", color_num,
575 ARIBCC_COLOR_RGB(new_color));
576 if (ARIBCC_COLOR_DIFF_A(new_color, old_color))
577 av_bprintf(buf, "{\\%da&H%02x&}", color_num,
578 0xFF - ARIBCC_COLOR_A(new_color));
581 static int aribcaption_trans_ass_subtitle(ARIBCaptionContext *ctx)
583 AVSubtitle *sub = ctx->sub;
585 bool single_rect = ctx->ass_single_rect;
586 int ret = 0, rect_idx;
588 if (ctx->caption.plane_width > 0 && ctx->caption.plane_height > 0 &&
589 (ctx->caption.plane_width != ctx->plane_width ||
590 ctx->caption.plane_height != ctx->plane_height)) {
591 ctx->plane_width = ctx->caption.plane_width;
592 ctx->plane_height = ctx->caption.plane_height;
593 if ((ret = set_ass_header(ctx)) < 0)
597 /* ARIB TR-B14 version 3.8 Fascicle 1-(2/2) Volume 3 [Section 4] */
598 /* No position information is provided for profile C */
599 if (ctx->avctx->profile == AV_PROFILE_ARIB_PROFILE_C)
602 sub->format = 1; /* text */
603 if (ctx->caption.region_count == 0) {
604 /* clear previous caption for indefinite duration */
605 ff_ass_add_rect(sub, "", ctx->readorder++, 0, NULL, NULL);
609 av_bprint_init(&buf, ARIBC_BPRINT_SIZE_INIT, ARIBC_BPRINT_SIZE_MAX);
611 if (single_rect && ctx->avctx->profile != AV_PROFILE_ARIB_PROFILE_C) {
613 x = ctx->plane_width;
614 y = ctx->plane_height;
615 for (int i = 0; i < ctx->caption.region_count; i++) {
616 rx = ctx->caption.regions[i].x;
617 ry = ctx->caption.regions[i].y;
623 av_bprintf(&buf, "{\\an7}");
625 y += ctx->plane_height;
627 av_bprintf(&buf, "{\\pos(%d,%d)}", x, y);
631 for (int i = 0; i < ctx->caption.region_count; i++) {
632 aribcc_caption_region_t *region = &ctx->caption.regions[i];
633 aribcc_color_t text_color = ARIBCC_MAKE_RGBA(0xFF, 0xFF, 0xFF,
634 ARIBC_ALPHA_DEFAULT_FRONT);
635 aribcc_color_t stroke_color = ARIBCC_MAKE_RGBA(0, 0, 0,
636 ARIBC_ALPHA_DEFAULT_FRONT);
637 aribcc_color_t back_color = ARIBCC_MAKE_RGBA(0, 0, 0,
638 ARIBC_ALPHA_DEFAULT_BACK);
639 aribcc_charstyle_t charstyle = ctx->charstyle;
640 int char_width = ctx->font_size;
641 int char_height = ctx->font_size;
642 int char_horizontal_spacing = 0;
644 if (region->is_ruby && ctx->ignore_ruby)
651 x += ctx->plane_width;
653 y += ctx->plane_height;
654 av_bprint_clear(&buf);
655 av_bprintf(&buf, "{\\an7}");
657 av_bprintf(&buf, "{\\pos(%d,%d)}", x, y);
660 av_bprintf(&buf, "{\\fs%d}", char_height / 2);
662 for (int j = 0; j < region->char_count; j++) {
663 aribcc_caption_char_t *ch = ®ion->chars[j];
665 if (ctx->avctx->profile != AV_PROFILE_ARIB_PROFILE_C) {
666 if (ch->char_horizontal_spacing != char_horizontal_spacing) {
667 av_bprintf(&buf, "{\\fsp%d}", (region->is_ruby) ?
668 ch->char_horizontal_spacing / 2 :
669 ch->char_horizontal_spacing);
670 char_horizontal_spacing = ch->char_horizontal_spacing;
672 if (ch->char_width != char_width) {
673 av_bprintf(&buf, "{\\fscx%"PRId64"}",
674 av_rescale(ch->char_width, 100, ctx->font_size));
675 char_width = ch->char_width;
677 if (ch->char_height != char_height) {
678 av_bprintf(&buf, "{\\fscy%"PRId64"}",
679 av_rescale(ch->char_height, 100, ctx->font_size));
680 char_height = ch->char_height;
683 if (ch->style != charstyle) {
684 aribcc_charstyle_t diff = ch->style ^ charstyle;
685 if (diff & ARIBCC_CHARSTYLE_STROKE) {
686 if (charstyle & ARIBCC_CHARSTYLE_STROKE) {
687 if (ctx->force_stroke_text)
688 av_bprintf(&buf, "{\\bord%d}",
689 (int)(ctx->stroke_width * 4.0 / 3.0));
691 av_bprintf(&buf, "{\\bord0}");
693 av_bprintf(&buf, "{\\bord3}");
695 if (diff & ARIBCC_CHARSTYLE_BOLD) {
696 if (charstyle & ARIBCC_CHARSTYLE_BOLD)
697 av_bprintf(&buf, "{\\b0}");
699 av_bprintf(&buf, "{\\b1}");
701 if (diff & ARIBCC_CHARSTYLE_ITALIC) {
702 if (charstyle & ARIBCC_CHARSTYLE_ITALIC)
703 av_bprintf(&buf, "{\\i0}");
705 av_bprintf(&buf, "{\\i1}");
707 if (diff & ARIBCC_CHARSTYLE_UNDERLINE) {
708 if (charstyle & ARIBCC_CHARSTYLE_UNDERLINE)
709 av_bprintf(&buf, "{\\u0}");
711 av_bprintf(&buf, "{\\u1}");
713 charstyle = ch->style;
715 if (ch->text_color != text_color) {
716 set_ass_color(&buf, 1, ch->text_color, text_color);
717 text_color = ch->text_color;
719 if (ch->stroke_color != stroke_color) {
720 set_ass_color(&buf, 3, ch->stroke_color, stroke_color);
721 stroke_color = ch->stroke_color;
723 if (ch->back_color != back_color) {
724 if (ctx->border_style == 4)
725 set_ass_color(&buf, 4, ch->back_color, back_color);
727 set_ass_color(&buf, 3, ch->back_color, back_color);
728 back_color = ch->back_color;
730 if (region->chars[j].type == ARIBCC_CHARTYPE_DRCS)
731 av_bprintf(&buf, "\xe3\x80\x93"); /* Geta Mark */
733 ff_ass_bprint_text_event(&buf, ch->u8str, strlen(ch->u8str), "", 0);
737 if (i + 1 < ctx->caption.region_count)
738 av_bprintf(&buf, "{\\r}\\N");
739 ff_dlog(ctx, "ASS subtitle%s (%d,%d) %dx%d [%d]\n",
740 (region->is_ruby) ? " (ruby)" : "",
741 region->x, region->y, region->width, region->height,
744 if (!av_bprint_is_complete(&buf)) {
745 ret = AVERROR(ENOMEM);
748 ff_dlog(ctx, "ASS subtitle%s (%d,%d) %dx%d [%d]: %s\n",
749 (region->is_ruby) ? " (ruby)" : "",
750 region->x, region->y, region->width, region->height,
753 ret = ff_ass_add_rect(sub, buf.str, ctx->readorder++, 0 , NULL, NULL);
760 if (!av_bprint_is_complete(&buf)) {
761 ret = AVERROR(ENOMEM);
764 ff_dlog(ctx, "ASS subtitle: %s\n", buf.str);
766 ret = ff_ass_add_rect(sub, buf.str, ctx->readorder++, 0 , NULL, NULL);
772 av_bprint_finalize(&buf, NULL);
777 for (int i = 0; i < ctx->caption.region_count; i++) {
779 av_freep(&sub->rects[i]->ass);
780 av_freep(&sub->rects[i]);
783 av_freep(&sub->rects);
786 av_bprint_finalize(&buf, NULL);
791 static int aribcaption_trans_text_subtitle(ARIBCaptionContext *ctx)
793 AVSubtitle *sub = ctx->sub;
794 AVSubtitleRect *rect;
798 sub->rects = av_calloc(ctx->caption.region_count, sizeof(*sub->rects));
800 ret = AVERROR(ENOMEM);
805 sub->rects[0] = av_mallocz(sizeof(*sub->rects[0]));
806 if (!sub->rects[0]) {
807 ret = AVERROR(ENOMEM);
810 rect = sub->rects[0];
812 if (ctx->caption.region_count == 0)
813 text = ""; /* clear previous caption */
815 text = ctx->caption.text;
816 ff_dlog(ctx, "TEXT subtitle: %s\n", text);
818 rect->text = av_strdup(text);
820 ret = AVERROR(ENOMEM);
824 sub->format = 1; /* text */
825 rect->type = SUBTITLE_TEXT;
831 rect = sub->rects[0];
833 av_freep(&rect->text);
836 av_freep(&sub->rects);
843 static int aribcaption_decode(AVCodecContext *avctx, AVSubtitle *sub,
844 int *got_sub_ptr, const AVPacket *avpkt)
846 ARIBCaptionContext *ctx = avctx->priv_data;
849 ff_dlog(ctx, "ARIB caption packet pts=%"PRIx64":\n", avpkt->pts);
850 if (sub->num_rects) {
851 avpriv_request_sample(ctx, "Different Version of Segment asked Twice");
852 return AVERROR_PATCHWELCOME;
854 hex_dump_debug(ctx, avpkt->data, avpkt->size);
858 ctx->time_base = avctx->pkt_timebase;
859 if (ctx->time_base.num <= 0 || ctx->time_base.den <= 0) {
860 av_log(ctx, AV_LOG_VERBOSE, "No timebase set. assuming 90kHz.\n");
861 ctx->time_base = av_make_q(1, 90000);
863 if (avpkt->pts == AV_NOPTS_VALUE)
864 ctx->pts = ARIBCC_PTS_NOPTS;
866 ctx->pts = av_rescale_q(avpkt->pts, ctx->time_base, (AVRational){1, 1000});
868 status = aribcc_decoder_decode(ctx->decoder, avpkt->data, avpkt->size,
869 ctx->pts, &ctx->caption);
870 if (status == ARIBCC_DECODE_STATUS_ERROR) {
871 av_log(ctx, AV_LOG_ERROR,
872 "aribcc_decoder_decode() returned with error.\n");
873 return AVERROR(EAGAIN);
875 if (status == ARIBCC_DECODE_STATUS_NO_CAPTION) {
876 ff_dlog(ctx, "No caption.\n");
879 ff_dlog(ctx, "type=%02x, flags=%x, lang=%03x\n",
880 ctx->caption.type, ctx->caption.flags, ctx->caption.iso6392_language_code);
881 ff_dlog(ctx, "region count = %d, start=%d.%d, duration=%d.%d\n",
882 ctx->caption.region_count,
883 (int)(ctx->caption.pts / 1000), (int)(ctx->caption.pts % 1000),
884 (int)((ctx->caption.wait_duration == ARIBCC_DURATION_INDEFINITE) ?
885 -1 : ctx->caption.wait_duration / 1000),
886 (int)((ctx->caption.wait_duration == ARIBCC_DURATION_INDEFINITE) ?
887 0 : ctx->caption.wait_duration % 1000));
890 switch ((enum AVSubtitleType) ctx->subtitle_type) {
892 status = aribcaption_trans_text_subtitle(ctx);
896 status = aribcaption_trans_ass_subtitle(ctx);
899 case SUBTITLE_BITMAP:
900 status = aribcaption_trans_bitmap_subtitle(ctx);
909 av_log(ctx, AV_LOG_ERROR, "Failed to set Subtitle: %s\n",
911 aribcc_caption_cleanup(&ctx->caption);
916 if (ctx->avpkt->pts != AV_NOPTS_VALUE)
917 sub->pts = av_rescale_q(ctx->avpkt->pts,
918 ctx->time_base, AV_TIME_BASE_Q);
919 if (ctx->caption.wait_duration == ARIBCC_DURATION_INDEFINITE)
920 sub->end_display_time = UINT32_MAX;
922 sub->end_display_time = (uint32_t)ctx->caption.wait_duration;
925 aribcc_caption_cleanup(&ctx->caption);
929 static void aribcaption_flush(AVCodecContext *avctx)
931 ARIBCaptionContext *ctx = avctx->priv_data;
934 aribcc_decoder_flush(ctx->decoder);
936 aribcc_renderer_flush(ctx->renderer);
937 if (!(avctx->flags2 & AV_CODEC_FLAG2_RO_FLUSH_NOOP))
941 static int aribcaption_close(AVCodecContext *avctx)
943 ARIBCaptionContext *ctx = avctx->priv_data;
945 av_freep(&ctx->clut);
947 aribcc_renderer_free(ctx->renderer);
949 aribcc_decoder_free(ctx->decoder);
951 aribcc_context_free(ctx->context);
956 static int aribcaption_init(AVCodecContext *avctx)
958 ARIBCaptionContext *ctx = avctx->priv_data;
959 aribcc_profile_t profile;
964 switch (avctx->profile) {
965 case AV_PROFILE_ARIB_PROFILE_A:
966 profile = ARIBCC_PROFILE_A;
967 /* assume 960x540 at initial state */
968 ctx->plane_width = 960;
969 ctx->plane_height = 540;
972 case AV_PROFILE_ARIB_PROFILE_C:
973 profile = ARIBCC_PROFILE_C;
974 ctx->plane_width = 320;
975 ctx->plane_height = 180;
979 av_log(avctx, AV_LOG_ERROR, "Unknown or unsupported profile set.\n");
980 return AVERROR(EINVAL);
982 /* determine BorderStyle of ASS header */
983 if (ctx->ignore_background)
984 ctx->border_style = 1;
986 ctx->border_style = 4;
987 ctx->charstyle = ARIBCC_CHARSTYLE_DEFAULT;
988 if (ctx->force_stroke_text || ctx->ignore_background)
989 ctx->charstyle |= ARIBCC_CHARSTYLE_STROKE;
991 if (!(ctx->context = aribcc_context_alloc())) {
992 av_log(avctx, AV_LOG_ERROR, "Failed to alloc libaribcaption context.\n");
993 return AVERROR_EXTERNAL;
995 aribcc_context_set_logcat_callback(ctx->context, logcat_callback, avctx);
996 if (!(ctx->decoder = aribcc_decoder_alloc(ctx->context))) {
997 av_log(avctx, AV_LOG_ERROR, "Failed to alloc libaribcaption decoder.\n");
998 return AVERROR_EXTERNAL;
1000 if (!aribcc_decoder_initialize(ctx->decoder,
1001 (enum aribcc_encoding_scheme_t) ctx->encoding_scheme,
1002 ARIBCC_CAPTIONTYPE_CAPTION,
1004 ARIBCC_LANGUAGEID_FIRST)) {
1005 av_log(avctx, AV_LOG_ERROR, "Failed to initialize libaribcaption decoder.\n");
1006 return AVERROR_EXTERNAL;
1008 aribcc_decoder_set_replace_msz_fullwidth_ascii(ctx->decoder,
1009 ctx->replace_msz_ascii);
1010 aribcc_decoder_set_replace_msz_fullwidth_japanese(ctx->decoder,
1011 ctx->replace_msz_japanese);
1013 /* Similar behavior as ffmpeg tool to set canvas size */
1014 if (ctx->canvas_width > 0 && ctx->canvas_height > 0 &&
1015 (ctx->avctx->width == 0 || ctx->avctx->height == 0)) {
1016 ctx->avctx->width = ctx->canvas_width;
1017 ctx->avctx->height = ctx->canvas_height;
1020 switch ((enum AVSubtitleType) ctx->subtitle_type) {
1022 ret = set_ass_header(ctx);
1024 av_log(avctx, AV_LOG_ERROR, "Failed to set ASS header: %s\n",
1030 case SUBTITLE_BITMAP:
1031 if(!(ctx->renderer = aribcc_renderer_alloc(ctx->context))) {
1032 av_log(avctx, AV_LOG_ERROR, "Failed to alloc libaribcaption renderer.\n");
1033 return AVERROR_EXTERNAL;
1035 if(!aribcc_renderer_initialize(ctx->renderer,
1036 ARIBCC_CAPTIONTYPE_CAPTION,
1037 ARIBCC_FONTPROVIDER_TYPE_AUTO,
1038 ARIBCC_TEXTRENDERER_TYPE_AUTO)) {
1039 av_log(avctx, AV_LOG_ERROR, "Failed to initialize libaribcaption renderer.\n");
1040 return AVERROR_EXTERNAL;
1042 estimate_video_frame_size(ctx);
1043 ff_dlog(ctx, "canvas: %dx%d plane: %dx%d bitmap: %dx%d frame: %dx%d\n",
1044 ctx->avctx->width, ctx->avctx->height,
1045 ctx->plane_width, ctx->plane_height,
1046 ctx->bitmap_plane_width, ctx->bitmap_plane_height,
1047 ctx->frame_width, ctx->frame_height);
1048 if (!aribcc_renderer_set_frame_size(ctx->renderer,
1049 ctx->frame_width, ctx->frame_height)) {
1050 av_log(ctx, AV_LOG_ERROR,
1051 "aribcc_renderer_set_frame_size() returned with error.\n");
1052 return AVERROR_EXTERNAL;
1055 if (!(ctx->clut = av_mallocz(AVPALETTE_SIZE)))
1056 return AVERROR(ENOMEM);
1058 aribcc_renderer_set_storage_policy(ctx->renderer, ARIBCC_CAPTION_STORAGE_POLICY_MINIMUM, 0);
1059 aribcc_renderer_set_replace_drcs(ctx->renderer, ctx->replace_drcs);
1060 aribcc_renderer_set_force_stroke_text(ctx->renderer, ctx->force_stroke_text);
1061 aribcc_renderer_set_force_no_background(ctx->renderer, ctx->ignore_background);
1062 aribcc_renderer_set_force_no_ruby(ctx->renderer, ctx->ignore_ruby);
1063 aribcc_renderer_set_stroke_width(ctx->renderer, ctx->stroke_width);
1064 aribcc_renderer_set_replace_msz_halfwidth_glyph(ctx->renderer,
1065 ctx->replace_msz_glyph);
1069 const char **font_families = NULL;
1070 const char *fonts = ctx->font;
1073 const char **ff = av_realloc_array(font_families, count + 1, sizeof(*font_families));
1079 ff[count++] = av_get_token(&fonts, ",");
1080 if (!ff[count - 1]) {
1087 if (!is_nomem && count)
1088 aribcc_renderer_set_default_font_family(ctx->renderer, font_families, count, true);
1090 av_freep(&font_families[--count]);
1091 av_freep(&font_families);
1093 return AVERROR(ENOMEM);
1108 #if !defined(ASS_SINGLE_RECT)
1109 # define ASS_SINGLE_RECT 0
1112 #define OFFSET(x) offsetof(ARIBCaptionContext, x)
1113 #define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM
1114 static const AVOption options[] = {
1115 { "sub_type", "subtitle rendering type",
1116 OFFSET(subtitle_type), AV_OPT_TYPE_INT,
1117 { .i64 = SUBTITLE_ASS }, SUBTITLE_NONE, SUBTITLE_ASS, SD, "type" },
1118 { "none", "do nothing", 0, AV_OPT_TYPE_CONST,
1119 { .i64 = SUBTITLE_NONE }, .flags = SD, .unit = "type" },
1120 { "bitmap", "bitmap rendering", 0, AV_OPT_TYPE_CONST,
1121 { .i64 = SUBTITLE_BITMAP }, .flags = SD, .unit = "type" },
1122 { "text", "plain text", 0, AV_OPT_TYPE_CONST,
1123 { .i64 = SUBTITLE_TEXT }, .flags = SD, .unit = "type" },
1124 { "ass", "formatted text", 0, AV_OPT_TYPE_CONST,
1125 { .i64 = SUBTITLE_ASS }, .flags = SD, .unit = "type" },
1126 { "caption_encoding", "encoding scheme of subtitle text",
1127 OFFSET(encoding_scheme), AV_OPT_TYPE_INT, { .i64 = ARIBCC_ENCODING_SCHEME_AUTO },
1128 ARIBCC_ENCODING_SCHEME_AUTO, ARIBCC_ENCODING_SCHEME_ABNT_NBR_15606_1_LATIN, SD, "encoding" },
1129 { "auto", "automatically detect encoding scheme", 0, AV_OPT_TYPE_CONST,
1130 { .i64 = ARIBCC_ENCODING_SCHEME_AUTO }, .flags = SD, .unit = "encoding" },
1131 { "jis", "8bit-char JIS encoding (Japanese ISDB captions)", 0, AV_OPT_TYPE_CONST,
1132 { .i64 = ARIBCC_ENCODING_SCHEME_ARIB_STD_B24_JIS }, .flags = SD, .unit = "encoding" },
1133 { "utf8", "UTF-8 encoding (Philippines ISDB-T captions)", 0, AV_OPT_TYPE_CONST,
1134 { .i64 = ARIBCC_ENCODING_SCHEME_ARIB_STD_B24_UTF8 }, .flags = SD, .unit = "encoding" },
1135 { "latin", "latin characters (SBTVD / ISDB-Tb captions used in South America)", 0, AV_OPT_TYPE_CONST,
1136 { .i64 = ARIBCC_ENCODING_SCHEME_ABNT_NBR_15606_1_LATIN }, .flags = SD, .unit = "encoding" },
1137 { "ass_single_rect", "workaround of ASS subtitle for players which can't handle multi-rectangle [ass]",
1138 OFFSET(ass_single_rect), AV_OPT_TYPE_BOOL, { .i64 = ASS_SINGLE_RECT }, 0, 1, SD },
1139 { "font", "comma-separated font family [ass, bitmap]",
1140 OFFSET(font), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, SD },
1141 { "force_outline_text", "always render characters with outline [(ass), bitmap]",
1142 OFFSET(force_stroke_text), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, SD },
1143 { "ignore_background", "ignore rendering caption background [(ass), bitmap]",
1144 OFFSET(ignore_background), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, SD },
1145 { "ignore_ruby", "ignore ruby-like characters [ass, bitmap]",
1146 OFFSET(ignore_ruby), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, SD },
1147 { "outline_width", "outline width of text [(ass), bitmap]",
1148 OFFSET(stroke_width), AV_OPT_TYPE_FLOAT, { .dbl = 1.5 }, 0.0, 3.0, SD },
1149 { "replace_drcs", "replace known DRCS [bitmap]",
1150 OFFSET(replace_drcs), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
1151 { "replace_msz_ascii", "replace MSZ fullwidth alphanumerics with halfwidth alphanumerics [ass, bitmap]",
1152 OFFSET(replace_msz_ascii), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
1153 { "replace_msz_japanese", "replace MSZ fullwidth Japanese with halfwidth [ass, bitmap]",
1154 OFFSET(replace_msz_japanese), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
1155 { "replace_msz_glyph", "replace MSZ characters with halfwidth glyphs [bitmap]",
1156 OFFSET(replace_msz_glyph), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
1157 {"canvas_size", "set input video size (WxH or abbreviation) [bitmap]",
1158 OFFSET(canvas_width), AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, INT_MAX, SD },
1162 static const AVClass aribcaption_class = {
1163 .class_name = "aribcaption decoder",
1164 .item_name = av_default_item_name,
1166 .version = LIBAVUTIL_VERSION_INT,
1169 const FFCodec ff_libaribcaption_decoder = {
1170 .p.name = "libaribcaption",
1171 .p.long_name = NULL_IF_CONFIG_SMALL("ARIB STD-B24 caption decoder"),
1172 .p.type = AVMEDIA_TYPE_SUBTITLE,
1173 .p.id = AV_CODEC_ID_ARIB_CAPTION,
1174 .priv_data_size = sizeof(ARIBCaptionContext),
1175 .init = aribcaption_init,
1176 .close = aribcaption_close,
1177 FF_CODEC_DECODE_SUB_CB(aribcaption_decode),
1178 .flush = aribcaption_flush,
1179 .p.priv_class = &aribcaption_class,
1180 .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,