Files
ccextractor/src/lib_ccx/dvb_subtitle_decoder.c
Rahul-2k4 77f3fd35f4 Fix #447: Resolve DVB split mode crash and routing logic
- Fixed NULL pointer dereference in dvb_subtitle_decoder.c (sub->prev check).
- Corrected logic in dvbsub_handle_display_segment to prevent dropped subtitles.
- Implemented robust encoder context swapping in general_loop.c for DVB streams.
- Added regression test: tests/regression/dvb_split.txt.
- Verified 100% completion in split mode and correct Teletext/DVB routing.
2025-12-27 00:11:09 +05:30

2076 lines
49 KiB
C

/*
* DVB subtitle decoding
* Copyright (c) 2005 Ian Caulfield
* Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
* Copyright (c) 2014 Anshul Maheshwari <er.anshul.maheshwari@gmail.com>
* License: GPL 2.0
*
* You should have received a copy of the GNU General Public
* License along with CCExtractor; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
/**
* @file dvbsub.c
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <errno.h>
#include "dvb_subtitle_decoder.h"
#include "utility.h"
#include "ccx_decoders_common.h"
#include "ocr.h"
#define DVBSUB_PAGE_SEGMENT 0x10
#define DVBSUB_REGION_SEGMENT 0x11
#define DVBSUB_CLUT_SEGMENT 0x12
#define DVBSUB_OBJECT_SEGMENT 0x13
#define DVBSUB_DISPLAYDEFINITION_SEGMENT 0x14
#define DVBSUB_DISPLAY_SEGMENT 0x80
#define SCALEBITS 10
#define ONE_HALF (1 << (SCALEBITS - 1))
#define FIX(x) ((int)((x) * (1 << SCALEBITS) + 0.5))
#define YUV_TO_RGB1_CCIR(cb1, cr1) \
{ \
cb = (cb1) - 128; \
cr = (cr1) - 128; \
r_add = FIX(1.40200 * 255.0 / 224.0) * cr + ONE_HALF; \
g_add = -FIX(0.34414 * 255.0 / 224.0) * cb - FIX(0.71414 * 255.0 / 224.0) * cr + \
ONE_HALF; \
b_add = FIX(1.77200 * 255.0 / 224.0) * cb + ONE_HALF; \
}
#define YUV_TO_RGB2_CCIR(r, g, b, y1) \
{ \
y = ((y1) - 16) * FIX(255.0 / 219.0); \
r = cm[(y + r_add) >> SCALEBITS]; \
g = cm[(y + g_add) >> SCALEBITS]; \
b = cm[(y + b_add) >> SCALEBITS]; \
}
#define times4(x) x, x, x, x
#define times256(x) times4(times4(times4(times4(times4(x)))))
#define MAX_NEG_CROP 1024
const uint8_t crop_tab[256 + 2 * MAX_NEG_CROP] = {times256(0x00), 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31,
0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D,
0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D,
0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85,
0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9,
0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5,
0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1,
0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD,
0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9,
0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5,
0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1,
0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
0xFE, 0xFF, times256(0xFF)};
#define cm (crop_tab + MAX_NEG_CROP)
#define RGBA(r, g, b, a) (((unsigned)(a) << 24) | ((r) << 16) | ((g) << 8) | (b))
typedef struct DVBSubCLUT
{
int id;
int version;
uint32_t clut4[4];
uint32_t clut16[16];
uint32_t clut256[256];
uint8_t ilut4[4];
uint8_t ilut16[16];
uint8_t ilut256[256];
struct DVBSubCLUT *next;
} DVBSubCLUT;
static DVBSubCLUT default_clut;
typedef struct DVBSubObjectDisplay
{
int object_id;
int region_id;
int x_pos;
int y_pos;
int fgcolor;
int bgcolor;
struct DVBSubObjectDisplay *region_list_next;
struct DVBSubObjectDisplay *object_list_next;
} DVBSubObjectDisplay;
typedef struct DVBSubObject
{
int id;
int version;
int type;
DVBSubObjectDisplay *display_list;
struct DVBSubObject *next;
} DVBSubObject;
typedef struct DVBSubRegionDisplay
{
int region_id;
int x_pos;
int y_pos;
struct DVBSubRegionDisplay *next;
} DVBSubRegionDisplay;
typedef struct DVBSubRegion
{
int id;
int version;
int width;
int height;
int depth;
int clut;
int bgcolor;
uint8_t *pbuf;
int buf_size;
int dirty;
DVBSubObjectDisplay *display_list;
struct DVBSubRegion *next;
} DVBSubRegion;
typedef struct DVBSubDisplayDefinition
{
int version;
int x;
int y;
int width;
int height;
} DVBSubDisplayDefinition;
typedef struct DVBSubContext
{
int composition_id;
int ancillary_id;
int lang_index;
int version;
/* Store time in ms */
LLONG time_out;
#ifdef ENABLE_OCR
void *ocr_ctx;
#endif
DVBSubRegion *region_list;
DVBSubCLUT *clut_list;
DVBSubObject *object_list;
DVBSubRegionDisplay *display_list;
DVBSubDisplayDefinition *display_definition;
} DVBSubContext;
static __inline unsigned int bytestream_get_byte(const uint8_t **b)
{
(*b) += 1;
return ((const uint8_t *)(*b - 1))[0];
}
static __inline unsigned int bytestream_get_be16(const uint8_t **b)
{
(*b) += 2;
return RB16(*b - 2);
}
typedef struct GetBitContext
{
const uint8_t *buffer, *buffer_end;
int index;
int size_in_bits;
int size_in_bits_plus8;
} GetBitContext;
/**
* Initialize GetBitContext.
* @param buffer bitstream buffer, must be FF_INPUT_BUFFER_PADDING_SIZE bytes
* larger than the actual read bits because some optimized bitstream
* readers read 32 or 64 bit at once and could read over the end
* @param bit_size the size of the buffer in bits
* @return 0 on success, AVERROR_INVALIDDATA if the buffer_size would overflow.
*/
static __inline int init_get_bits(GetBitContext *s, const uint8_t *buffer,
int bit_size)
{
int buffer_size;
int ret = 0;
if (bit_size >= INT_MAX - 7 || bit_size < 0 || !buffer)
{
buffer_size = bit_size = 0;
buffer = NULL;
ret = -1;
errno = EINVAL;
}
buffer_size = (bit_size + 7) >> 3;
s->buffer = buffer;
s->size_in_bits = bit_size;
s->size_in_bits_plus8 = bit_size + 8;
s->buffer_end = buffer + buffer_size;
s->index = 0;
return ret;
}
static __inline int get_bits_count(const GetBitContext *s)
{
return s->index;
}
static __inline unsigned int get_bits(GetBitContext *s, int n)
{
register int tmp;
unsigned int re_index = s->index;
unsigned int re_cache = 0;
unsigned int re_size_plus8 = s->size_in_bits_plus8;
if (n <= 0 || n > 25)
return -1;
re_cache = RB32(s->buffer + (re_index >> 3)) << (re_index & 7);
tmp = ((uint32_t)re_cache) >> (32 - n);
re_index = ((re_size_plus8 < re_index + (n)) ? (re_size_plus8) : (re_index + (n)));
s->index = re_index;
return tmp;
}
static __inline unsigned int get_bits1(GetBitContext *s)
{
unsigned int index = s->index;
uint8_t result = s->buffer[index >> 3];
result <<= index & 7;
result >>= 8 - 1;
if (s->index < s->size_in_bits_plus8)
index++;
s->index = index;
return result;
}
static DVBSubObject *get_object(DVBSubContext *ctx, int object_id)
{
DVBSubObject *ptr = ctx->object_list;
while (ptr && ptr->id != object_id)
ptr = ptr->next;
return ptr;
}
static DVBSubCLUT *get_clut(DVBSubContext *ctx, int clut_id)
{
DVBSubCLUT *ptr = ctx->clut_list;
while (ptr && ptr->id != clut_id)
ptr = ptr->next;
return ptr;
}
static DVBSubRegion *get_region(DVBSubContext *ctx, int region_id)
{
DVBSubRegion *ptr = ctx->region_list;
while (ptr && ptr->id != region_id)
ptr = ptr->next;
return ptr;
}
static void delete_region_display_list(DVBSubContext *ctx, DVBSubRegion *region)
{
DVBSubObject *object, *obj2, **obj2_ptr;
DVBSubObjectDisplay *display, *obj_disp, **obj_disp_ptr;
while (region->display_list)
{
display = region->display_list;
object = get_object(ctx, display->object_id);
if (object)
{
obj_disp_ptr = &object->display_list;
obj_disp = *obj_disp_ptr;
while (obj_disp && obj_disp != display)
{
obj_disp_ptr = &obj_disp->object_list_next;
obj_disp = *obj_disp_ptr;
}
if (obj_disp)
{
*obj_disp_ptr = obj_disp->object_list_next;
if (!object->display_list)
{
obj2_ptr = &ctx->object_list;
obj2 = *obj2_ptr;
while (obj2 != object)
{
assert(obj2);
obj2_ptr = &obj2->next;
obj2 = *obj2_ptr;
}
*obj2_ptr = obj2->next;
free(obj2);
}
}
}
region->display_list = display->region_list_next;
free(display);
}
}
static void delete_cluts(DVBSubContext *ctx)
{
DVBSubCLUT *clut;
while (ctx->clut_list)
{
clut = ctx->clut_list;
ctx->clut_list = clut->next;
free(clut);
}
}
static void delete_objects(DVBSubContext *ctx)
{
DVBSubObject *object;
while (ctx->object_list)
{
object = ctx->object_list;
ctx->object_list = object->next;
free(object);
}
}
static void delete_regions(DVBSubContext *ctx)
{
DVBSubRegion *region;
while (ctx->region_list)
{
region = ctx->region_list;
ctx->region_list = region->next;
delete_region_display_list(ctx, region);
free(region->pbuf);
free(region);
}
}
/**
* @param composition_id composition-page_id found in Subtitle descriptors
* associated with subtitle stream in the PMT
* it could be -1 if not found in PMT.
* @param ancillary_id ancillary-page_id found in Subtitle descriptors
* associated with subtitle stream in the PMT.
* it could be -1 if not found in PMT.
*
* @return DVB context kept as void* for abstraction
*
*/
void *dvbsub_init_decoder(struct dvb_config *cfg, int initialized_ocr)
{
int i, r, g, b, a = 0;
DVBSubContext *ctx = (DVBSubContext *)malloc(sizeof(DVBSubContext));
if (!ctx)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_init_decoder: Out of memory.");
}
memset(ctx, 0, sizeof(DVBSubContext));
if (cfg)
{
ctx->composition_id = cfg->composition_id[0];
ctx->ancillary_id = cfg->ancillary_id[0];
ctx->lang_index = cfg->lang_index[0];
}
else
{
ctx->composition_id = 1;
ctx->ancillary_id = 1;
ctx->lang_index = 1;
}
#ifdef ENABLE_OCR
if (!initialized_ocr)
ctx->ocr_ctx = init_ocr(ctx->lang_index);
#endif
ctx->version = -1;
default_clut.id = -1;
default_clut.next = NULL;
default_clut.clut4[0] = RGBA(0, 0, 0, 0);
default_clut.clut4[1] = RGBA(255, 255, 255, 255);
default_clut.clut4[2] = RGBA(0, 0, 0, 255);
default_clut.clut4[3] = RGBA(127, 127, 127, 255);
default_clut.ilut4[0] = 0;
default_clut.ilut4[1] = 255;
default_clut.ilut4[2] = 0;
default_clut.ilut4[3] = 127;
default_clut.clut16[0] = RGBA(0, 0, 0, 0);
default_clut.ilut16[0] = 0;
for (i = 1; i < 16; i++)
{
if (i < 8)
{
r = (i & 1) ? 255 : 0;
g = (i & 2) ? 255 : 0;
b = (i & 4) ? 255 : 0;
}
else
{
r = (i & 1) ? 127 : 0;
g = (i & 2) ? 127 : 0;
b = (i & 4) ? 127 : 0;
}
default_clut.clut16[i] = RGBA(r, g, b, 255);
default_clut.ilut16[i] = 0;
}
default_clut.clut256[0] = RGBA(0, 0, 0, 0);
default_clut.ilut256[0] = 0;
for (i = 1; i < 256; i++)
{
if (i < 8)
{
r = (i & 1) ? 255 : 0;
g = (i & 2) ? 255 : 0;
b = (i & 4) ? 255 : 0;
a = 63;
}
else
{
switch (i & 0x88)
{
case 0x00:
r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
a = 255;
break;
case 0x08:
r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
a = 127;
break;
case 0x80:
r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
a = 255;
break;
case 0x88:
r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
a = 255;
break;
}
}
default_clut.ilut256[i] = 0;
default_clut.clut256[i] = RGBA(r, g, b, a);
}
return (void *)ctx;
}
int dvbsub_close_decoder(void **dvb_ctx)
{
DVBSubContext *ctx;
DVBSubRegionDisplay *display;
if (!dvb_ctx || !*dvb_ctx)
return 0;
ctx = (DVBSubContext *)*dvb_ctx;
delete_regions(ctx);
delete_objects(ctx);
delete_cluts(ctx);
freep(&ctx->display_definition);
while (ctx->display_list)
{
display = ctx->display_list;
ctx->display_list = display->next;
free(display);
}
#ifdef ENABLE_OCR
if (ctx->ocr_ctx)
delete_ocr(&ctx->ocr_ctx);
#endif
freep(dvb_ctx);
return 0;
}
static int dvbsub_read_2bit_string(uint8_t *destbuf, int dbuf_len,
const uint8_t **srcbuf, int buf_size, int non_mod, uint8_t *map_table,
int x_pos)
{
GetBitContext gb;
int bits;
int run_length;
int pixels_read = x_pos;
init_get_bits(&gb, *srcbuf, buf_size << 3);
destbuf += x_pos;
while (get_bits_count(&gb) < buf_size << 3 && pixels_read < dbuf_len)
{
bits = get_bits(&gb, 2);
if (bits)
{
if (non_mod != 1 || bits != 1)
{
if (map_table)
*destbuf++ = map_table[bits];
else
*destbuf++ = bits;
}
pixels_read++;
}
else
{
bits = get_bits1(&gb);
if (bits == 1)
{
run_length = get_bits(&gb, 3) + 3;
bits = get_bits(&gb, 2);
if (non_mod == 1 && bits == 1)
pixels_read += run_length;
else
{
if (map_table)
bits = map_table[bits];
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
}
else
{
bits = get_bits1(&gb);
if (bits == 0)
{
bits = get_bits(&gb, 2);
if (bits == 2)
{
run_length = get_bits(&gb, 4) + 12;
bits = get_bits(&gb, 2);
if (non_mod == 1 && bits == 1)
pixels_read += run_length;
else
{
if (map_table)
bits = map_table[bits];
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
}
else if (bits == 3)
{
run_length = get_bits(&gb, 8) + 29;
bits = get_bits(&gb, 2);
if (non_mod == 1 && bits == 1)
pixels_read += run_length;
else
{
if (map_table)
bits = map_table[bits];
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
}
else if (bits == 1)
{
if (map_table)
bits = map_table[0];
else
bits = 0;
run_length = 2;
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
else
{
(*srcbuf) += (get_bits_count(&gb) + 7) >> 3;
return pixels_read;
}
}
else
{
if (map_table)
bits = map_table[0];
else
bits = 0;
*destbuf++ = bits;
pixels_read++;
}
}
}
}
if (get_bits(&gb, 6))
{
mprint("DVBSub error: line overflow at dvbsub_read_2bit_string()\n");
return -1;
}
(*srcbuf) += (get_bits_count(&gb) + 7) >> 3;
return pixels_read;
}
static int dvbsub_read_4bit_string(uint8_t *destbuf, int dbuf_len,
const uint8_t **srcbuf, int buf_size, int non_mod, uint8_t *map_table,
int x_pos)
{
GetBitContext gb;
int bits;
int run_length;
int pixels_read = x_pos;
init_get_bits(&gb, *srcbuf, buf_size << 3);
destbuf += x_pos;
while (get_bits_count(&gb) < buf_size << 3 && pixels_read < dbuf_len)
{
bits = get_bits(&gb, 4);
if (bits)
{
if (non_mod != 1 || bits != 1)
{
if (map_table)
*destbuf++ = map_table[bits];
else
*destbuf++ = bits;
}
pixels_read++;
}
else
{
bits = get_bits1(&gb);
if (bits == 0)
{
run_length = get_bits(&gb, 3);
if (run_length == 0)
{
(*srcbuf) += (get_bits_count(&gb) + 7) >> 3;
return pixels_read;
}
run_length += 2;
if (map_table)
bits = map_table[0];
else
bits = 0;
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
else
{
bits = get_bits1(&gb);
if (bits == 0)
{
run_length = get_bits(&gb, 2) + 4;
bits = get_bits(&gb, 4);
if (non_mod == 1 && bits == 1)
pixels_read += run_length;
else
{
if (map_table)
bits = map_table[bits];
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
}
else
{
bits = get_bits(&gb, 2);
if (bits == 2)
{
run_length = get_bits(&gb, 4) + 9;
bits = get_bits(&gb, 4);
if (non_mod == 1 && bits == 1)
pixels_read += run_length;
else
{
if (map_table)
bits = map_table[bits];
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
}
else if (bits == 3)
{
run_length = get_bits(&gb, 8) + 25;
bits = get_bits(&gb, 4);
if (non_mod == 1 && bits == 1)
pixels_read += run_length;
else
{
if (map_table)
bits = map_table[bits];
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
}
else if (bits == 1)
{
if (map_table)
bits = map_table[0];
else
bits = 0;
run_length = 2;
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
else
{
if (map_table)
bits = map_table[0];
else
bits = 0;
*destbuf++ = bits;
pixels_read++;
}
}
}
}
}
if (get_bits(&gb, 8))
{
mprint("DVBSub error: line overflow at dvbsub_read_4bit_string()\n");
return -1;
}
(*srcbuf) += (get_bits_count(&gb) + 7) >> 3;
return pixels_read;
}
static int dvbsub_read_8bit_string(uint8_t *destbuf, int dbuf_len,
const uint8_t **srcbuf, int buf_size, int non_mod, uint8_t *map_table,
int x_pos)
{
const uint8_t *sbuf_end = (*srcbuf) + buf_size;
int bits;
int run_length;
int pixels_read = x_pos;
destbuf += x_pos;
while (*srcbuf < sbuf_end && pixels_read < dbuf_len)
{
bits = *(*srcbuf)++;
if (bits)
{
if (non_mod != 1 || bits != 1)
{
if (map_table)
*destbuf++ = map_table[bits];
else
*destbuf++ = bits;
}
pixels_read++;
}
else
{
bits = *(*srcbuf)++;
run_length = bits & 0x7f;
if ((bits & 0x80) == 0)
{
if (run_length == 0)
{
return pixels_read;
}
if (map_table)
bits = map_table[0];
else
bits = 0;
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
else
{
bits = *(*srcbuf)++;
if (non_mod == 1 && bits == 1)
pixels_read += run_length;
if (map_table)
bits = map_table[bits];
else
while (run_length-- > 0 && pixels_read < dbuf_len)
{
*destbuf++ = bits;
pixels_read++;
}
}
}
}
if (*(*srcbuf)++)
{
mprint("DVBSub error: line overflow at dvbsub_read_8bit_string()\n");
return -1;
}
return pixels_read;
}
static int dvbsub_parse_pixel_data_block(void *dvb_ctx,
DVBSubObjectDisplay *display, const uint8_t *buf, int buf_size,
int top_bottom, int non_mod)
{
DVBSubContext *ctx = (DVBSubContext *)dvb_ctx;
DVBSubRegion *region = get_region(ctx, display->region_id);
const uint8_t *buf_end = buf + buf_size;
uint8_t *pbuf;
int x_pos, y_pos;
int i;
int parseerror = 0;
uint8_t map2to4[] = {0x0, 0x7, 0x8, 0xf};
uint8_t map2to8[] = {0x00, 0x77, 0x88, 0xff};
uint8_t map4to8[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
uint8_t *map_table;
if (region == 0)
return 0;
pbuf = region->pbuf;
region->dirty = 1;
x_pos = display->x_pos;
y_pos = display->y_pos;
y_pos += top_bottom;
while (!parseerror && buf < buf_end)
{
if ((*buf != 0xf0 && x_pos >= region->width) || y_pos >= region->height)
{
mprint("In dvbsub_parse_pixel_data_block(): Invalid object location. %d-%d %d-%d %02x\n", x_pos,
region->width, y_pos, region->height, *buf);
parseerror = 1;
break;
}
switch (*buf++)
{
case 0x10:
if (region->depth == 8)
map_table = map2to8;
else if (region->depth == 4)
map_table = map2to4;
else
map_table = NULL;
x_pos = dvbsub_read_2bit_string(pbuf + (y_pos * region->width),
region->width, &buf, buf_end - buf, non_mod, map_table,
x_pos);
if (x_pos < 0)
{
mprint("In dvbsub_parse_pixel_data_block(): dvbsub_read_2bit_string() returned error.\n");
parseerror = 1;
goto exitfunc;
}
break;
case 0x11:
if (region->depth < 4)
{
mprint("In dvbsub_parse_pixel_data_block(): 4-bit pixel string in %d-bit region.\n", region->depth);
parseerror = 1;
goto exitfunc;
}
if (region->depth == 8)
map_table = map4to8;
else
map_table = NULL;
x_pos = dvbsub_read_4bit_string(pbuf + (y_pos * region->width),
region->width, &buf, buf_end - buf, non_mod, map_table,
x_pos);
if (x_pos < 0)
{
mprint("In dvbsub_parse_pixel_data_block(): dvbsub_read_4bit_string() returned error.\n");
parseerror = 1;
goto exitfunc;
}
break;
case 0x12:
if (region->depth < 8)
{
mprint("In dvbsub_parse_pixel_data_block(): 8-bit pixel string in %d-bit region.\n", region->depth);
return -1;
}
x_pos = dvbsub_read_8bit_string(pbuf + (y_pos * region->width),
region->width, &buf, buf_end - buf, non_mod, NULL, x_pos);
if (x_pos < 0)
{
mprint("In dvbsub_parse_pixel_data_block(): dvbsub_read_8bit_string() returned error.\n");
parseerror = 1;
goto exitfunc;
}
break;
case 0x20:
map2to4[0] = (*buf) >> 4;
map2to4[1] = (*buf++) & 0xf;
map2to4[2] = (*buf) >> 4;
map2to4[3] = (*buf++) & 0xf;
break;
case 0x21:
for (i = 0; i < 4; i++)
map2to8[i] = *buf++;
break;
case 0x22:
for (i = 0; i < 16; i++)
map4to8[i] = *buf++;
break;
case 0xf0:
x_pos = display->x_pos;
y_pos += 2;
break;
default:
mprint("In dvbsub_parse_pixel_data_block(): Unknown/unsupported pixel block 0x%x\n", *(buf - 1));
/* no break */
}
}
exitfunc:
return parseerror ? -1 : 0;
}
static int dvbsub_parse_object_segment(void *dvb_ctx, const uint8_t *buf,
int buf_size)
{
DVBSubContext *ctx = (DVBSubContext *)dvb_ctx;
const uint8_t *buf_end = buf + buf_size;
int object_id;
DVBSubObject *object;
DVBSubObjectDisplay *display;
int top_field_len, bottom_field_len;
int coding_method, non_modifying_color;
object_id = RB16(buf);
buf += 2;
object = get_object(ctx, object_id);
if (!object)
return 0; // Unsure if we should return error
coding_method = ((*buf) >> 2) & 3;
non_modifying_color = ((*buf++) >> 1) & 1;
if (coding_method == 0)
{
top_field_len = RB16(buf);
buf += 2;
bottom_field_len = RB16(buf);
buf += 2;
if (buf + top_field_len + bottom_field_len > buf_end)
{
mprint("dvbsub_parse_object_segment(): Field data size too large\n");
return -1;
}
for (display = object->display_list; display;
display = display->object_list_next)
{
const uint8_t *block = buf;
int bfl = bottom_field_len;
if (dvbsub_parse_pixel_data_block(dvb_ctx, display, block,
top_field_len, 0, non_modifying_color))
{
mprint("dvbsub_parse_object_segment(): Something went wrong. Giving up on block (1).\n");
// Something went wrong, get out and hope we can
// recover
return -1;
}
if (bottom_field_len > 0)
block = buf + top_field_len;
else
bfl = top_field_len;
if (dvbsub_parse_pixel_data_block(dvb_ctx, display, block, bfl, 1,
non_modifying_color))
{
// Problems. Hope for the best.
mprint("dvbsub_parse_object_segment(): Something went wrong. Giving up on block (2).\n");
return -1;
}
}
}
else if (coding_method == 1)
{
mprint("FIXME support for string coding standard\n");
}
else
{
mprint("Unknown object coding %d\n", coding_method);
}
return 0;
}
static int dvbsub_parse_clut_segment(void *dvb_ctx, const uint8_t *buf,
int buf_size)
{
DVBSubContext *ctx = (DVBSubContext *)dvb_ctx;
const uint8_t *buf_end = buf + buf_size;
int clut_id;
int version;
DVBSubCLUT *clut;
int entry_id, depth, full_range;
int y, cr, cb, alpha;
int r, g, b, r_add, g_add, b_add;
clut_id = *buf++;
version = ((*buf) >> 4) & 15;
buf += 1;
clut = get_clut(ctx, clut_id);
if (!clut)
{
clut = (DVBSubCLUT *)malloc(sizeof(DVBSubCLUT));
if (!clut)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_parse_clut_segment: Out of memory.");
}
memcpy(clut, &default_clut, sizeof(DVBSubCLUT));
clut->id = clut_id;
clut->version = -1;
clut->next = ctx->clut_list;
ctx->clut_list = clut;
}
if (clut->version != version)
{
clut->version = version;
while (buf + 4 < buf_end)
{
entry_id = *buf++;
depth = (*buf) & 0xe0;
if (depth == 0)
{
mprint("dvbsub_parse_clut_segment(): Invalid clut depth 0x%x.\n", *buf);
return -1;
}
full_range = (*buf++) & 1;
if (full_range)
{
y = *buf++;
cr = *buf++;
cb = *buf++;
alpha = *buf++;
}
else
{
y = buf[0] & 0xfc;
cr = (((buf[0] & 3) << 2) | ((buf[1] >> 6) & 3)) << 4;
cb = (buf[1] << 2) & 0xf0;
alpha = (buf[1] << 6) & 0xc0;
buf += 2;
}
if (y == 0)
alpha = 0xff;
YUV_TO_RGB1_CCIR(cb, cr);
YUV_TO_RGB2_CCIR(r, g, b, y);
if (!!(depth & 0x80) + !!(depth & 0x40) + !!(depth & 0x20) > 1)
{
mprint("dvbsub_parse_clut_segment(): More than one bit level marked: %x\n", depth);
}
if (depth & 0x80)
{
clut->clut4[entry_id] = RGBA(r, g, b, 255 - alpha);
clut->ilut4[entry_id] = y;
}
else if (depth & 0x40)
{
clut->clut16[entry_id] = RGBA(r, g, b, 255 - alpha);
clut->ilut16[entry_id] = y;
}
else if (depth & 0x20)
{
clut->clut256[entry_id] = RGBA(r, g, b, 255 - alpha);
clut->ilut256[entry_id] = y;
}
}
}
return 0;
}
static void dvbsub_parse_region_segment(void *dvb_ctx, const uint8_t *buf,
int buf_size)
{
DVBSubContext *ctx = (DVBSubContext *)dvb_ctx;
const uint8_t *buf_end = buf + buf_size;
int region_id, object_id;
int version;
DVBSubRegion *region;
DVBSubObject *object;
DVBSubObjectDisplay *display;
int fill;
if (buf_size < 10)
{
mprint(" dvbsub_parse_region_segment(): [buf_size < 10, leaving region] ");
return;
}
region_id = *buf++;
version = ((*buf) >> 4) & 15;
region = get_region(ctx, region_id);
if (!region)
{
dbg_print(CCX_DMT_DVB, " [new region allocated] ");
region = (struct DVBSubRegion *)malloc(sizeof(struct DVBSubRegion));
if (!region)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_parse_region_segment: Out of memory allocating region.");
}
memset(region, 0, sizeof(struct DVBSubRegion));
region->id = region_id;
region->version = version;
region->next = ctx->region_list;
ctx->region_list = region;
}
else if (version == region->version)
{
dbg_print(CCX_DMT_DVB, " [already had this region and version] ");
return;
}
fill = ((*buf++) >> 3) & 1;
region->width = RB16(buf);
buf += 2;
region->height = RB16(buf);
buf += 2;
dbg_print(CCX_DMT_DVB, ", REGION %d WIDTH: %d, ", region_id, region->width);
dbg_print(CCX_DMT_DVB, "REGION %d HEIGHT: %d", region_id, region->height);
if (region->width * region->height != region->buf_size)
{
freep(&region->pbuf);
region->buf_size = region->width * region->height;
region->pbuf = (uint8_t *)malloc(region->buf_size);
if (!region->pbuf)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_parse_region_segment: Out of memory allocating pbuf.");
}
fill = 1;
region->dirty = 0;
}
region->depth = 1 << (((*buf++) >> 2) & 7);
if (region->depth < 2 || region->depth > 8)
{
mprint("dvbsub_parse_region_segment(): region depth %d is invalid\n", region->depth);
region->depth = 4;
}
region->clut = *buf++;
if (region->depth == 8)
{
region->bgcolor = *buf++;
buf += 1;
}
else
{
buf += 1;
if (region->depth == 4)
region->bgcolor = (((*buf++) >> 4) & 15);
else
region->bgcolor = (((*buf++) >> 2) & 3);
}
if (fill)
memset(region->pbuf, region->bgcolor, region->buf_size);
delete_region_display_list(ctx, region);
while (buf + 5 < buf_end)
{
object_id = RB16(buf);
buf += 2;
object = get_object(ctx, object_id);
if (!object)
{
object = (struct DVBSubObject *)malloc(sizeof(struct DVBSubObject));
if (!object)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_parse_region_segment: Out of memory allocating object.");
}
memset(object, 0, sizeof(struct DVBSubObject));
object->id = object_id;
object->next = ctx->object_list;
ctx->object_list = object;
}
object->type = (*buf) >> 6;
display = (struct DVBSubObjectDisplay *)malloc(
sizeof(struct DVBSubObjectDisplay));
if (!display)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_parse_region_segment: Out of memory allocating display.");
}
memset(display, 0, sizeof(struct DVBSubObjectDisplay));
display->object_id = object_id;
display->region_id = region_id;
display->x_pos = RB16(buf) & 0xfff;
buf += 2;
display->y_pos = RB16(buf) & 0xfff;
buf += 2;
if ((object->type == 1 || object->type == 2) && buf + 1 < buf_end)
{
display->fgcolor = *buf++;
display->bgcolor = *buf++;
}
display->region_list_next = region->display_list;
region->display_list = display;
display->object_list_next = object->display_list;
object->display_list = display;
}
assert(buf <= buf_end);
}
/*
* xxx loose last frame
*/
static void dvbsub_parse_page_segment(void *dvb_ctx, const uint8_t *buf,
int buf_size)
{
DVBSubContext *ctx = (DVBSubContext *)dvb_ctx;
DVBSubRegionDisplay *display;
DVBSubRegionDisplay *tmp_display_list, **tmp_ptr;
const uint8_t *buf_end = buf + buf_size;
int region_id;
int page_state;
int timeout;
int version;
if (buf_size < 2)
return;
timeout = *buf++;
version = ((*buf) >> 4) & 15;
page_state = ((*buf++) >> 2) & 3;
// if version same mean we are already updated
if (ctx->version == version)
{
return;
}
/* Convert time from second to ms */
ctx->time_out = timeout * 1000;
ctx->version = version;
if (page_state == 1 || page_state == 2)
{
dbg_print(CCX_DMT_DVB, ", PAGE STATE %d", page_state);
delete_regions(ctx);
delete_objects(ctx);
delete_cluts(ctx);
}
tmp_display_list = ctx->display_list;
ctx->display_list = NULL;
while (buf + 5 < buf_end)
{
region_id = *buf++;
buf += 1;
dbg_print(CCX_DMT_DVB, ", REGION %d ADDED", region_id);
display = tmp_display_list;
tmp_ptr = &tmp_display_list;
while (display && display->region_id != region_id)
{
tmp_ptr = &display->next;
display = display->next;
}
if (!display)
{
display = (struct DVBSubRegionDisplay *)malloc(
sizeof(struct DVBSubRegionDisplay));
if (!display)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_parse_page_segment: Out of memory allocating display.");
}
memset(display, 0, sizeof(struct DVBSubRegionDisplay));
}
display->region_id = region_id;
display->x_pos = RB16(buf);
buf += 2;
display->y_pos = RB16(buf);
buf += 2;
*tmp_ptr = display->next;
display->next = ctx->display_list;
ctx->display_list = display;
}
while (tmp_display_list)
{
display = tmp_display_list;
tmp_display_list = display->next;
free(display);
}
assert(buf <= buf_end);
}
static void dvbsub_parse_display_definition_segment(void *dvb_ctx,
const uint8_t *buf, int buf_size)
{
DVBSubContext *ctx = (DVBSubContext *)dvb_ctx;
DVBSubDisplayDefinition *display_def = ctx->display_definition;
int dds_version, info_byte;
if (buf_size < 5)
return;
info_byte = bytestream_get_byte(&buf);
dds_version = info_byte >> 4;
if (display_def && display_def->version == dds_version)
return; // already have this display definition version
if (!display_def)
{
display_def = (struct DVBSubDisplayDefinition *)malloc(
sizeof(*display_def));
if (!display_def)
return;
memset(display_def, 0, sizeof(*display_def));
ctx->display_definition = display_def;
}
display_def->version = dds_version;
display_def->x = 0;
display_def->y = 0;
display_def->width = bytestream_get_be16(&buf) + 1;
display_def->height = bytestream_get_be16(&buf) + 1;
if (buf_size < 13)
return;
if (info_byte & 1 << 3)
{ // display_window_flag
display_def->x = bytestream_get_be16(&buf);
display_def->width = bytestream_get_be16(&buf) - display_def->x + 1;
display_def->y = bytestream_get_be16(&buf);
display_def->height = bytestream_get_be16(&buf) - display_def->y + 1;
}
}
/**
* Write Subtitle in cc_subtitle structure in CC_BITMAP format
* when OCR subsystem is present then it also write recognised text in
* cc_bitmap ocr_text variable.
*/
static int write_dvb_sub(struct lib_cc_decode *dec_ctx, struct cc_subtitle *sub)
{
DVBSubContext *ctx;
DVBSubRegion *region;
DVBSubRegionDisplay *display;
DVBSubCLUT *clut;
DVBSubDisplayDefinition *display_def;
struct cc_bitmap *rect = NULL;
uint32_t *clut_table;
int offset_x = 0, offset_y = 0;
int x_pos = -1, y_pos = -1, width = 0, height = 0;
ctx = (DVBSubContext *)dec_ctx->private_data;
display_def = ctx->display_definition;
sub->type = CC_BITMAP;
sub->lang_index = ctx->lang_index;
if (display_def)
{
offset_x = display_def->x;
offset_y = display_def->y;
}
for (display = ctx->display_list; display; display = display->next)
{
region = get_region(ctx, display->region_id);
if (region && region->dirty)
sub->nb_data++;
}
if (sub->nb_data <= 0)
{
return 0;
}
rect = malloc(sizeof(struct cc_bitmap));
if (!rect)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In write_dvb_sub: Out of memory allocating rect.");
}
rect->data0 = NULL;
rect->data1 = NULL;
sub->flags |= SUB_EOD_MARKER;
sub->got_output = 1;
sub->data = rect;
sub->datatype = CC_DATATYPE_DVB;
// TODO: if different regions have different cluts, only the last one will be saved.
// Don't know if it will affect anything.
// The first loop, to determine the size of the whole subtitle (made up of different display/regions)
for (display = ctx->display_list; display; display = display->next)
{
region = get_region(ctx, display->region_id);
if (!region)
continue;
if (!region->dirty)
continue;
if (x_pos == -1)
{
x_pos = display->x_pos;
y_pos = display->y_pos;
width = region->width;
height = region->height;
}
else
{
if (x_pos > display->x_pos)
{
width += (x_pos - display->x_pos);
x_pos = display->x_pos;
}
if (display->y_pos < y_pos)
{
height += (y_pos - display->y_pos);
y_pos = display->y_pos;
}
if (display->x_pos + region->width > x_pos + width)
{
width = display->x_pos + region->width - x_pos;
}
if (display->y_pos + region->height > y_pos + height)
{
height = display->y_pos + region->height - y_pos;
}
}
rect->nb_colors = (1 << region->depth);
// Process CLUT
clut = get_clut(ctx, region->clut);
if (!clut)
clut = &default_clut;
switch (region->depth)
{
case 2:
clut_table = clut->clut4;
break;
case 8:
clut_table = clut->clut256;
break;
case 4:
default:
clut_table = clut->clut16;
break;
}
if (rect->data1 == NULL)
{
rect->data1 = malloc(1024);
if (!rect->data1)
{
free(rect);
fatal(EXIT_NOT_ENOUGH_MEMORY, "In write_dvb_sub: Out of memory allocating data1.");
}
}
memset(rect->data1, 0, 1024);
memcpy(rect->data1, clut_table, (1 << region->depth) * sizeof(uint32_t));
assert(((1 << region->depth) * sizeof(uint32_t)) <= 1024);
}
rect->x = x_pos + offset_x;
rect->y = y_pos + offset_y;
rect->w = width;
rect->h = height;
rect->linesize0 = width;
// The second loop, to generate the merged image
dbg_print(CCX_DMT_DVB, "\nCreating a data[0] of %d bytes (%d x %d)\n", width * height, width, height);
rect->data0 = (uint8_t *)malloc(width * height);
if (!rect->data0)
{
free(rect->data1);
free(rect);
fatal(EXIT_NOT_ENOUGH_MEMORY, "In write_dvb_sub: Out of memory allocating data0 (%d * %d = %d bytes).", width, height, width * height);
}
memset(rect->data0, 0x0, width * height);
for (display = ctx->display_list; display; display = display->next)
{
region = get_region(ctx, display->region_id);
if (!region)
continue;
int x_off = display->x_pos - x_pos;
int y_off = display->y_pos - y_pos;
for (int y = 0; y < region->height; y++)
{
for (int x = 0; x < region->width; x++)
{
int offset = ((y + y_off) * width) + x_off + x;
if (offset >= (width * height) || offset < 0)
{
mprint("write_dvb_sub(): Offset %d (out of bounds!) ignored.\n",
offset);
mprint(" Formula: offset=((y + y_off) * width) + x_off + x\n");
mprint(" y=%d, y_off=%d, width=%d, x_off=%d, x=%d\n",
y, y_off, width, x_off, x);
}
else
{
uint8_t c = (uint8_t)region->pbuf[y * region->width + x];
rect->data0[offset] = c;
}
}
}
}
sub->nb_data = 1; // Set nb_data to 1 since we have merged the images into one image.
// Perform OCR
#ifdef ENABLE_OCR
char *ocr_str = NULL;
if (ctx->ocr_ctx)
{
int ret = ocr_rect(ctx->ocr_ctx, rect, &ocr_str, region->bgcolor, dec_ctx->ocr_quantmode);
if (ret >= 0)
rect->ocr_text = ocr_str;
else
rect->ocr_text = NULL;
dbg_print(CCX_DMT_DVB, "\nOCR Result: %s\n", rect->ocr_text ? rect->ocr_text : "NULL");
}
else
{
rect->ocr_text = NULL;
}
#endif
return 0;
}
void dvbsub_handle_display_segment(struct encoder_ctx *enc_ctx,
struct lib_cc_decode *dec_ctx,
struct cc_subtitle *sub,
LLONG pre_fts_max)
{
DVBSubContext *ctx = (DVBSubContext *)dec_ctx->private_data;
LLONG current_pts = dec_ctx->timing->current_pts;
if (!enc_ctx)
return;
if (enc_ctx->write_previous) // this condition is used for the first subtitle - write_previous will be 0 first so we don't encode a non-existing previous sub
{
enc_ctx->prev->last_string = NULL; // Reset last recognized sub text
// Get the current FTS, which will be the start_time of the new subtitle
LLONG next_start_time = get_fts(dec_ctx->timing, dec_ctx->current_field);
if (!sub->prev)
{
// Previous subtitle is missing or invalid, skipping write_previous
enc_ctx->write_previous = 0;
if (enc_ctx->prev)
{
free_encoder_context(enc_ctx->prev);
enc_ctx->prev = NULL;
enc_ctx->prev = copy_encoder_context(enc_ctx);
}
}
else
{
// For DVB subtitles, a subtitle is displayed until the next one appears.
// For DVB subtitles, a subtitle is displayed until the next one appears.
// Use next_start_time as the end_time to ensure subtitle N ends when N+1 starts.
// This prevents any overlap between consecutive subtitles.
if (next_start_time > sub->prev->start_time)
{
sub->prev->end_time = next_start_time;
}
else
{
// PTS jump or timeline reset - next_start is at or before our start.
// Calculate duration from raw PTS, but cap to reasonable maximum (5 seconds)
// to avoid creating subtitles that overlap excessively with subsequent ones.
LLONG duration_ms = 0;
if (sub->prev->start_pts > 0 && current_pts > sub->prev->start_pts)
{
duration_ms = (current_pts - sub->prev->start_pts) / (MPEG_CLOCK_FREQ / 1000);
}
// Cap duration to 4 seconds or timeout if smaller
LLONG max_duration = 4000; // 4 seconds
if (sub->prev->time_out > 0 && sub->prev->time_out < max_duration)
{
max_duration = sub->prev->time_out;
}
if (duration_ms > max_duration)
{
duration_ms = max_duration;
}
sub->prev->end_time = sub->prev->start_time + duration_ms;
}
// Sanity check: if end_time still <= start_time, use minimal duration
if (sub->prev->end_time <= sub->prev->start_time)
{
dbg_print(CCX_DMT_DVB, "DVB timing: end <= start, using start+1\n");
sub->prev->end_time = sub->prev->start_time + 1;
}
// Apply timeout limit if specified
if (sub->prev->time_out > 0 && sub->prev->time_out < sub->prev->end_time - sub->prev->start_time)
{
sub->prev->end_time = sub->prev->start_time + sub->prev->time_out;
}
int timeok = 1;
if (dec_ctx->extraction_start.set &&
sub->prev->start_time < dec_ctx->extraction_start.time_in_ms)
timeok = 0;
if (dec_ctx->extraction_end.set &&
sub->prev->end_time > dec_ctx->extraction_end.time_in_ms)
{
timeok = 0;
dec_ctx->processed_enough = 1;
}
if (timeok)
{
encode_sub(enc_ctx->prev, sub->prev); // we encode it
enc_ctx->last_string = enc_ctx->prev->last_string; // Update last recognized string (used in Matroska)
enc_ctx->prev->last_string = NULL;
enc_ctx->srt_counter = enc_ctx->prev->srt_counter; // for dvb subs we need to update the current srt counter because we always encode the previous subtitle (and the counter is increased for the previous context)
enc_ctx->prev_start = enc_ctx->prev->prev_start;
sub->prev->got_output = 0;
if (enc_ctx->write_format == CCX_OF_WEBVTT)
{ // we already wrote header, but since we encoded last sub, we must prevent multiple headers in future
enc_ctx->wrote_webvtt_header = 1;
}
}
}
}
/* copy previous encoder context*/
free_encoder_context(enc_ctx->prev);
enc_ctx->prev = NULL;
enc_ctx->prev = copy_encoder_context(enc_ctx);
/* copy previous decoder context */
free_decoder_context(dec_ctx->prev);
dec_ctx->prev = NULL;
dec_ctx->prev = copy_decoder_context(dec_ctx);
freep(&dec_ctx->prev->private_data);
dec_ctx->prev->private_data = malloc(sizeof(struct DVBSubContext));
if (!dec_ctx->prev->private_data)
{
fatal(EXIT_NOT_ENOUGH_MEMORY, "In dvbsub_handle_display_segment: Out of memory allocating private_data.");
}
memcpy(dec_ctx->prev->private_data, dec_ctx->private_data, sizeof(struct DVBSubContext));
/* copy previous subtitle */
free_subtitle(sub->prev);
sub->time_out = ctx->time_out;
sub->prev = NULL;
sub->prev = copy_subtitle(sub);
// Use get_fts() which properly handles PTS jumps and maintains monotonic timing
sub->prev->start_time = get_fts(dec_ctx->timing, dec_ctx->current_field);
// Store the raw PTS for accurate duration calculation (not affected by PTS jump handling)
sub->prev->start_pts = current_pts;
write_dvb_sub(dec_ctx->prev, sub->prev); // we write the current dvb sub to update decoder context
enc_ctx->write_previous = 1; // we update our boolean value so next time the program reaches this block of code, it encodes the previous sub
#ifdef ENABLE_OCR
if (sub->prev)
{
struct cc_bitmap *content_prev = sub->prev->data;
dbg_print(CCX_DMT_DVB, "\nPrevious subtitle %x (%s)\nStart time: %lld; End time: %lld",
sub->prev, content_prev ? (content_prev->ocr_text ? content_prev->ocr_text : "NULL OCR") : "NULL DATA",
sub->prev->start_time, sub->prev->end_time);
}
struct cc_bitmap *content = sub->data;
dbg_print(CCX_DMT_DVB, "\nCurrent subtitle %x (%s)\nStart time: %lld; End time: %lld\n",
sub, content ? (content->ocr_text ? content->ocr_text : "NULL OCR") : "NULL DATA",
sub->start_time, sub->end_time);
#endif
}
/**
* @param dvb_ctx PreInitialized DVB context using DVB
* @param buf buffer containing segment data, first sync byte need to 0x0f.
* does not include data_identifier and subtitle_stream_id.
* @param buf_size size of buf buffer
* @param sub output subtitle data
*
* @return -1 on error
*/
int dvbsub_decode(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, const unsigned char *buf, int buf_size, struct cc_subtitle *sub)
{
DVBSubContext *ctx = (DVBSubContext *)dec_ctx->private_data;
const uint8_t *p, *p_end;
int segment_type;
int page_id;
int segment_length;
int ret = 0;
int got_segment = 0;
if (buf_size <= 6 || *buf != 0x0f)
{
mprint("dvbsub_decode: incomplete, broken or empty packet (size = %d, first byte=%02X)\n",
buf_size, *buf);
return -1;
}
p = buf;
p_end = buf + buf_size;
// Capture the max FTS before set_fts() processes any PTS jumps.
// This will be used as the end time for the previous subtitle.
LLONG pre_fts_max = get_fts_max(dec_ctx->timing);
dec_ctx->timing->current_tref = 0;
set_fts(dec_ctx->timing);
while (p_end - p >= 6 && *p == 0x0f)
{
p += 1;
segment_type = *p++;
page_id = RB16(p);
p += 2;
segment_length = RB16(p);
p += 2;
if (p_end - p < segment_length)
{
mprint("dvbsub_decode: incomplete, broken or empty packet, remaining bytes=%d, segment_length=%d\n",
p_end - p, segment_length);
return -1;
}
if (page_id == ctx->composition_id || page_id == ctx->ancillary_id || ctx->composition_id == -1 || ctx->ancillary_id == -1)
{
// debug traces
dbg_print(CCX_DMT_DVB, "DVBSUB - PTS: %" PRId64 ", ", dec_ctx->timing->current_pts);
dbg_print(CCX_DMT_DVB, "FTS: %d, ", dec_ctx->timing->fts_now);
dbg_print(CCX_DMT_DVB, "SEGMENT TYPE: %2X, ", segment_type);
switch (segment_type)
{
case DVBSUB_PAGE_SEGMENT:
dbg_print(CCX_DMT_DVB, "(DVBSUB_PAGE_SEGMENT), SEGMENT LENGTH: %d", segment_length);
dvbsub_parse_page_segment(ctx, p, segment_length);
got_segment |= 1;
break;
case DVBSUB_REGION_SEGMENT:
dbg_print(CCX_DMT_DVB, "(DVBSUB_REGION_SEGMENT), SEGMENT LENGTH: %d", segment_length);
dvbsub_parse_region_segment(ctx, p, segment_length);
got_segment |= 2;
break;
case DVBSUB_CLUT_SEGMENT:
dbg_print(CCX_DMT_DVB, "(DVBSUB_CLUT_SEGMENT), SEGMENT LENGTH: %d", segment_length);
ret = dvbsub_parse_clut_segment(ctx, p, segment_length);
if (ret < 0)
goto end;
got_segment |= 4;
break;
case DVBSUB_OBJECT_SEGMENT:
dbg_print(CCX_DMT_DVB, "(DVBSUB_OBJECT_SEGMENT), SEGMENT LENGTH: %d", segment_length);
ret = dvbsub_parse_object_segment(ctx, p, segment_length);
if (ret < 0)
goto end;
got_segment |= 8;
break;
case DVBSUB_DISPLAYDEFINITION_SEGMENT:
dbg_print(CCX_DMT_DVB, "(DVBSUB_DISPLAYDEFINITION_SEGMENT), SEGMENT LENGTH: %d", segment_length);
dvbsub_parse_display_definition_segment(ctx, p,
segment_length);
break;
case DVBSUB_DISPLAY_SEGMENT: // when we get a display segment, we save the current page
dbg_print(CCX_DMT_DVB, "(DVBSUB_DISPLAY_SEGMENT), SEGMENT LENGTH: %d", segment_length);
dvbsub_handle_display_segment(enc_ctx, dec_ctx, sub, pre_fts_max);
got_segment |= 16;
break;
default:
dbg_print(CCX_DMT_DVB, "Subtitling segment type 0x%x, page id %d, length %d\n",
segment_type, page_id, segment_length);
break;
}
}
dbg_print(CCX_DMT_DVB, "\n");
p += segment_length;
}
// Some streams do not send a display segment but if we have all the other
// segments then we need no further data.
if (got_segment == 15)
{
dvbsub_handle_display_segment(enc_ctx, dec_ctx, sub, pre_fts_max);
got_segment |= 16;
}
end:
if (ret >= 0)
ret = p - buf;
return ret;
}
/**
* @func parse_dvb_description
*
* data pointer to this function should be after description length and description tag is parsed
*
* @param cfg preallocated dvb_config structure where parsed description will be stored,Not to be NULL
*
* @return return -1 if invalid data found other wise 0 if everything goes well
* errno is set is to EINVAL if invalid data is found
*/
int parse_dvb_description(struct dvb_config *cfg, unsigned char *data,
unsigned int len)
{
/* 8 bytes per DVB subtitle substream d2:
* ISO_639_language_code (3 bytes),
* subtitling_type (1 byte),
* composition_page_id (2 bytes),
* ancillary_page_id (2 bytes)
*/
cfg->n_language = len / 8;
if (len > 0 && len % 8 != 0)
{
errno = EINVAL;
return -1;
}
if (cfg->n_language > 1)
{
mprint("DVB subtitles with multiple languages");
}
if (cfg->n_language > MAX_LANGUAGE_PER_DESC)
{
mprint("not supported more then %d language", MAX_LANGUAGE_PER_DESC);
}
unsigned char *data_ptr = data;
for (int i = 0; i < cfg->n_language; i++, data_ptr += i * 8)
{
/* setting language to undefined if not found in language lkup table */
char lang_name[4];
dbg_print(CCX_DMT_DVB, "DVBSUB - LANGUAGE \"");
for (int char_index = 0; char_index < 3; char_index++)
{
lang_name[char_index] = cctolower(data_ptr[char_index]);
dbg_print(CCX_DMT_DVB, "%c", lang_name[char_index]);
}
dbg_print(CCX_DMT_DVB, "\" FOUND\n");
int j = 0;
for (j = 0, cfg->lang_index[i] = 0; language[j] != NULL; j++)
{
if (!strncmp(lang_name, language[j], 3))
cfg->lang_index[i] = j;
}
cfg->sub_type[i] = data_ptr[3];
cfg->composition_id[i] = RB16(data_ptr + 4);
cfg->ancillary_id[i] = RB16(data_ptr + 6);
}
/*
Abhinav95: The way this function is called right now, only cfg->lang_index[0]
gets populated. E.g. for 3 stream languages, it will be called 3 times, and
set the language index in only the first element each time. This works with the
current state of the DVB code.
*/
if (ccx_options.dvblang)
{
if (strcmp(ccx_options.dvblang, language[cfg->lang_index[0]]) && strncmp(ccx_options.dvblang, data, 3))
{
mprint("Ignoring stream language '%s' not equal to dvblang '%s'\n",
data, ccx_options.dvblang);
return -1;
}
}
return 0;
}