[FFmpeg-devel] [PATCH 2/2] Adding closed caption decoder

anshul.ffmpeg at gmail.com anshul.ffmpeg at gmail.com
Wed Dec 3 13:28:58 CET 2014


From: Anshul Maheshwari <anshul.ffmpeg at gmail.com>

---
I did implement lots of other thing, with respect to older patch.
like multi screen and rollup feature.

This implementation is working well with srt output format.
As of now ffplay is not able to show the decoded frames on screen.
I dont know why?

May be someone look at output and help, that what is missing.

 libavcodec/Makefile       |   1 +
 libavcodec/allcodecs.c    |   1 +
 libavcodec/ccaption_dec.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 378 insertions(+)
 create mode 100644 libavcodec/ccaption_dec.c

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index fa0f53d..bbc516d 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -173,6 +173,7 @@ OBJS-$(CONFIG_BRENDER_PIX_DECODER)     += brenderpix.o
 OBJS-$(CONFIG_C93_DECODER)             += c93.o
 OBJS-$(CONFIG_CAVS_DECODER)            += cavs.o cavsdec.o cavsdsp.o \
                                           cavsdata.o mpeg12data.o
+OBJS-$(CONFIG_CCAPTION_DECODER)        += ccaption_dec.o
 OBJS-$(CONFIG_CDGRAPHICS_DECODER)      += cdgraphics.o
 OBJS-$(CONFIG_CDXL_DECODER)            += cdxl.o
 OBJS-$(CONFIG_CINEPAK_DECODER)         += cinepak.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 0d39d33..8c07388 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -480,6 +480,7 @@ void avcodec_register_all(void)
     /* subtitles */
     REGISTER_ENCDEC (SSA,               ssa);
     REGISTER_ENCDEC (ASS,               ass);
+    REGISTER_DECODER(CCAPTION,          ccaption);
     REGISTER_ENCDEC (DVBSUB,            dvbsub);
     REGISTER_ENCDEC (DVDSUB,            dvdsub);
     REGISTER_DECODER(JACOSUB,           jacosub);
diff --git a/libavcodec/ccaption_dec.c b/libavcodec/ccaption_dec.c
new file mode 100644
index 0000000..57443e4
--- /dev/null
+++ b/libavcodec/ccaption_dec.c
@@ -0,0 +1,376 @@
+/*
+ * Closed Caption Decoding
+ * Copyright (c) 2014 Anshul Maheshwari
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "avcodec.h"
+#include "libavcodec/ass.h"
+
+#define SCREEN_ROWS 15
+#define SCREEN_COLUMNS 32
+
+#define SET_FLAG(var, val) ( var |= ( 1 << (val) ) )
+#define UNSET_FLAG(var, val) ( var &=  ~( 1 << (val)) )
+#define CHECK_FLAG(var, val) ( (var) & (1 << (val) ) )
+
+enum cc_mode {
+    CCMODE_POPON,
+    CCMODE_PAINTON,
+    CCMODE_ROLLUP_2,
+    CCMODE_ROLLUP_3,
+    CCMODE_ROLLUP_4,
+    CCMODE_TEXT,
+};
+typedef struct {
+    int row;
+    int column;
+} cursor_t;
+
+// buffer for CC
+typedef struct {
+    uint8_t characters[SCREEN_ROWS][SCREEN_COLUMNS+1];
+    /*
+     * row used flag will be 0 when none in use other wise it will have its
+     * corrosponding bit high.
+     * for setting row 1  use row | (1 >> 1)
+     * for setting row 15 use row | (1 >> 15)
+     */
+    int16_t  row_used;
+} screen_t;
+
+
+typedef struct CCaptionSubContext {
+    int parity_table[256];
+    int row_cnt;
+    screen_t screen[2];
+    int active_screen;
+    cursor_t cursor;
+    char *buffer;
+    int index;
+    int data_len;
+    int buf_len;
+    /* erase display memory */
+    int edm;
+    int rollup;
+    enum  cc_mode mode;
+    int64_t start_time;
+    /* visible screen time */
+    int64_t startv_time;
+    int64_t end_time;
+    char prev_cmd[2];
+}CCaptionSubContext;
+
+static unsigned int av_always_inline is_oddparity1p7(unsigned int val)
+{
+//#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
+#if 0
+    asm goto (
+    "and  $0x7F, %0\n\t"
+    "jnp %l1 \/n\t"
+    :/* no output */
+    :"r"(val)
+    :"cc"
+    :odd
+    );
+    val = !(val&0x8);
+odd:
+    val = !!(val&0x80);
+#else
+    int ones = 0;
+    int i = 0;
+
+    for (i = 0; i < 7; i++) {
+        if (val & (1 << i))
+            ones++;
+    }
+    val = ones & 1;
+#endif
+    return val;
+}
+
+static void build_parity_table(int *parity_table)
+{
+        unsigned int byte;
+        int parity_v;
+        for (byte = 0; byte <= 127; byte++) {
+                parity_v = is_oddparity1p7(byte);
+                parity_table[byte] = parity_v;
+                parity_table[byte | 0x80] = !parity_v;
+        }
+}
+
+static av_cold int init_decoder(AVCodecContext *avctx)
+{
+
+    CCaptionSubContext *ctx = avctx->priv_data;
+
+    build_parity_table(ctx->parity_table);
+    ctx->row_cnt = 0;
+    ctx->buf_len = 512;
+    ctx->buffer = av_realloc(NULL, ctx->buf_len);
+    *ctx->buffer = '\0';
+    ctx->index  = 0;
+    ctx->edm = 0;
+    /* taking by default roll up to 2 */
+    ctx->rollup = 2;
+    ctx->data_len = 0;
+    /* set active screen as 0 */
+    ctx->active_screen = 0;
+    memset(ctx->prev_cmd,0,2);
+    ff_ass_subtitle_header_default(avctx);
+    return 0;
+}
+
+static av_cold int close_decoder(AVCodecContext *avctx)
+{
+    CCaptionSubContext *ctx = avctx->priv_data;
+    av_free(ctx->buffer);
+    return 0;
+}
+
+/**
+ * This function after validating parity bit, also remove it from data pair.
+ */
+static int validate_cc_data_pair (unsigned char *cc_data_pair, int *parity_table)
+{
+    unsigned char cc_valid = (*cc_data_pair & 4) >>2;
+    unsigned char cc_type = *cc_data_pair & 3;
+
+    if (!cc_valid)
+        return -1;
+
+    // if EIA-608 data then verify parity.
+    if (cc_type==0 || cc_type==1) {
+        if (!parity_table[cc_data_pair[2]]) {
+        // If the second byte doesn't pass parity, ignore pair
+            return -1;
+        }
+        if (!parity_table[cc_data_pair[1]]) {
+        // The first byte doesn't pass parity, we replace it with a solid blank
+        // and process the pair.
+            cc_data_pair[1]=0x7F;
+        }
+    }
+
+    //Skip non-data
+    if( (cc_data_pair[0] == 0xFA || cc_data_pair[0] == 0xFC || cc_data_pair[0] == 0xFD )
+         && (cc_data_pair[1] & 0x7F) == 0 && (cc_data_pair[2] & 0x7F) == 0)
+        return -1;
+
+    //skip 708 data
+    if(cc_type == 3 || cc_type == 2 )
+        return -1;
+
+    /* remove parity bit */
+    cc_data_pair[1] &= 0x7F;
+    cc_data_pair[2] &= 0x7F;
+
+
+    return 0;
+
+}
+static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
+{
+    static const int row_map[] = {
+        11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
+    };
+    const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
+
+    if( row_map[index] <= 0 )
+        return;
+
+    ctx->cursor.row = row_map[index] - 1;
+    ctx->cursor.column = 0;
+
+}
+
+/**
+ * @param pts it is required to set end time
+ */
+static void av_always_inline handle_edm(CCaptionSubContext *ctx,int64_t pts)
+{
+    int i,index = 0;
+    screen_t *screen = ctx->screen + ctx->active_screen;
+
+    ctx->start_time = ctx->startv_time;
+    for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
+    {
+        if(CHECK_FLAG(screen->row_used,i)) {
+            index += av_strlcpy(ctx->buffer + index, screen->characters[i], ctx->buf_len);
+            if (index > ctx->buf_len) {
+                index = ctx->buf_len;
+                ctx->buf_len *= 2;
+                ctx->buffer = av_realloc(ctx->buffer, ctx->buf_len);
+                index += av_strlcpy(ctx->buffer + index, screen->characters[i], ctx->buf_len);
+            }
+            index += av_strlcpy(ctx->buffer + index, "\\N", ctx->buf_len);
+            UNSET_FLAG(screen->row_used, i);
+        }
+    }
+    ctx->startv_time = pts;
+    ctx->edm = 1;
+    ctx->data_len = index;
+    ctx->end_time = pts;
+}
+static void handle_eoc(CCaptionSubContext *ctx, int64_t pts)
+{
+    ctx->active_screen = !ctx->active_screen;
+    ctx->startv_time = pts;
+}
+static screen_t *get_writing_screen(CCaptionSubContext *ctx)
+{
+    switch (ctx->mode) {
+    case CCMODE_POPON:
+        // use Inactive screen
+        return ctx->screen + !ctx->active_screen;
+    case CCMODE_PAINTON:
+    case CCMODE_ROLLUP_2:
+    case CCMODE_ROLLUP_3:
+    case CCMODE_ROLLUP_4:
+    case CCMODE_TEXT:
+        // use active screen
+        return ctx->screen + !ctx->active_screen;
+    }
+    /* It was never an option */
+    return NULL;
+}
+static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
+{
+    screen_t *screen = get_writing_screen(ctx);
+    char *row = screen->characters[ctx->cursor.row] + ctx->cursor.column;
+
+    SET_FLAG(screen->row_used,ctx->cursor.row);
+
+    *row++ = hi;
+    ctx->cursor.column++;
+    if(lo) {
+        *row++ = lo;
+        ctx->cursor.column++;
+    }
+    *row = 0;
+    /* reset prev command since character can repeat */
+    ctx->prev_cmd[0] = 0;
+    ctx->prev_cmd[1] = 0;
+}
+static int process_cc608(CCaptionSubContext *ctx, int64_t pts, unsigned char hi, unsigned char lo)
+{
+
+#define COR3(var, with1, with2, with3)  ( (var) == (with1) ||  (var) == (with2) || (var) == (with3) )
+    if ( hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) {
+    /* ignore redundant command */
+    } else if ( (hi == 0x10 && (lo >= 0x40 || lo <= 0x5f)) ||
+              ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) {
+        handle_pac(ctx, hi, lo);
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x20 ) {
+    /* resume caption loading */
+        ctx->mode = CCMODE_POPON;
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x25 ) {
+        ctx->rollup = 2;
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
+        ctx->rollup = 3;
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
+        ctx->rollup = 4;
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x29 ) {
+    /* resume direct captioning */
+        ctx->mode = CCMODE_PAINTON;
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2C ) {
+    /* erase display memory */
+        handle_edm(ctx, pts);
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2D ) {
+    /* carriage return */
+        if(ctx->buf_len < ctx->index + 3 ) {
+            ctx->buf_len *= 2;
+            ctx->buffer = av_realloc(ctx->buffer, ctx->buf_len);
+        }
+        ctx->row_cnt++;
+        if(ctx->row_cnt == ctx->rollup) {
+            ctx->row_cnt = 0;
+            ctx->buffer[ctx->index ] = 0;
+            handle_edm(ctx, pts);
+        }
+        /* If there is no data or end of data then no point using cr */
+        if(ctx->index) {
+            ctx->buffer[ctx->index] = '\\';
+            ctx->index++;
+            ctx->buffer[ctx->index] = 'N';
+            ctx->index++;
+        }
+    } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
+    /* end of caption */
+        handle_eoc(ctx, pts);
+    } else if (hi>=0x20) {
+    /* Standard characters (always in pairs) */
+        handle_char(ctx, hi, lo, pts);
+    } else {
+    /* Ignoring all other non data code */
+    }
+
+    /* set prev command */
+     ctx->prev_cmd[0] = hi;
+     ctx->prev_cmd[1] = lo;
+
+#undef COR3
+    return 0;
+
+}
+static int decode(AVCodecContext *avctx,
+                         void *data, int *got_sub,
+                         AVPacket *avpkt) {
+    CCaptionSubContext *ctx = avctx->priv_data;
+    AVSubtitle *sub = data;
+    unsigned char *bptr = avpkt->data;
+    int len = avpkt->size;
+    int ret = 0;
+    int i;
+
+    for (i  = 0; i < len; i += 3) {
+        unsigned char cc_type = *(bptr + i) & 3;
+        if (validate_cc_data_pair( bptr + i, ctx->parity_table ) )
+            continue;
+        /* ignoring data field 1 */
+        if(cc_type == 1)
+            continue;
+        else
+            process_cc608(ctx, avpkt->pts, *(bptr + i + 1), *(bptr + i + 2));
+    }
+    if(ctx->edm && *ctx->buffer)
+    {
+        int start_time = av_rescale_q(ctx->start_time, avctx->time_base, (AVRational){ 1, 100 });
+        int end_time = av_rescale_q(ctx->end_time, avctx->time_base, (AVRational){ 1, 100 });
+        ret = ff_ass_add_rect(sub, ctx->buffer, start_time, end_time - start_time , 0);
+        if (ret < 0)
+            return ret;
+        sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
+        ctx->edm = 0;
+    }
+
+    *got_sub = sub->num_rects > 0;
+    return 0;
+}
+
+AVCodec ff_ccaption_decoder = {
+    .name           = "cc_dec",
+    .long_name      = NULL_IF_CONFIG_SMALL("Closed Caption Decoder"),
+    .type           = AVMEDIA_TYPE_SUBTITLE,
+    .id             = AV_CODEC_ID_EIA_608,
+    .priv_data_size = sizeof(CCaptionSubContext),
+    .init           = init_decoder,
+    .close          = close_decoder,
+    .decode         = decode,
+};
-- 
1.8.1.4



More information about the ffmpeg-devel mailing list