/* GStreamer * Copyright (C) <2020> Jan Schmidt <jan@centricular.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdlib.h> //#define HACK_2BIT /* Force 2-bit output by discarding colours */ //#define HACK_4BIT /* Force 4-bit output by discarding colours */ #include "gstdvbsubenc.h" #include <gst/base/gstbytewriter.h> #include <gst/base/gstbitwriter.h> #include "libimagequant/libimagequant.h" #define DVB_SEGMENT_SYNC_BYTE 0xF enum DVBSegmentType { DVB_SEGMENT_TYPE_PAGE_COMPOSITION = 0x10, DVB_SEGMENT_TYPE_REGION_COMPOSITION = 0x11, DVB_SEGMENT_TYPE_CLUT_DEFINITION = 0x12, DVB_SEGMENT_TYPE_OBJECT_DATA = 0x13, DVB_SEGMENT_TYPE_DISPLAY_DEFINITION = 0x14, DVB_SEGMENT_TYPE_END_OF_DISPLAY = 0x80 }; enum DVBPixelDataType { DVB_PIXEL_DATA_TYPE_2BIT = 0x10, DVB_PIXEL_DATA_TYPE_4BIT = 0x11, DVB_PIXEL_DATA_TYPE_8BIT = 0x12, DVB_PIXEL_DATA_TYPE_END_OF_LINE = 0xF0 }; struct HistogramEntry { guint32 colour; guint32 count; guint32 substitution; }; struct ColourEntry { guint32 colour; guint32 pix_index; }; typedef struct HistogramEntry HistogramEntry; typedef struct ColourEntry ColourEntry; static gint compare_uint32 (gconstpointer a, gconstpointer b) { guint32 v1 = *(guint32 *) (a); guint32 v2 = *(guint32 *) (b); if (v1 < v2) return -1; if (v1 > v2) return 1; return 0; } static gint compare_colour_entry_colour (gconstpointer a, gconstpointer b) { const ColourEntry *c1 = (ColourEntry *) (a); const ColourEntry *c2 = (ColourEntry *) (b); /* Reverse order, so highest alpha comes first: */ return compare_uint32 (&c2->colour, &c1->colour); } static void image_get_rgba_row_callback (liq_color row_out[], int row_index, int width, void *user_info) { int column_index; GstVideoFrame *src = (GstVideoFrame *) (user_info); guint8 *src_pixels = (guint8 *) (src->data[0]); const guint32 src_stride = GST_VIDEO_INFO_PLANE_STRIDE (&src->info, 0); guint8 *src_row = src_pixels + (row_index * src_stride); gint offset = 0; for (column_index = 0; column_index < width; column_index++) { liq_color *col = row_out + column_index; guint8 *p = src_row + offset; /* FIXME: We pass AYUV into the ARGB colour values, * which works but probably makes suboptimal choices about * which colours to preserve. It would be better to convert to RGBA * and back again, or to modify libimagequant to handle ayuv */ col->a = p[0]; col->r = p[1]; col->g = p[2]; col->b = p[3]; offset += 4; } } /* * Utility function to unintelligently extract a * (max) 256 colour image from an AYUV input * Dumb for now, but could be improved if needed. If there's * more than 256 colours in the input, it will reduce it 256 * by taking the most common 255 colours + transparent and mapping all * remaining colours to the nearest neighbour. * * FIXME: Integrate a better palette selection algorithm. */ gboolean gst_dvbsubenc_ayuv_to_ayuv8p (GstVideoFrame * src, GstVideoFrame * dest, int max_colours, guint32 * out_num_colours) { /* Allocate a temporary array the size of the input frame, copy in * the source pixels, sort them by value and then count the first * up to 256 colours. */ gboolean ret = FALSE; GArray *colours, *histogram; gint i, num_pixels, dest_y_index, out_index; guint num_colours, cur_count; guint32 last; guint8 *s; HistogramEntry *h; ColourEntry *c; const guint32 src_stride = GST_VIDEO_INFO_PLANE_STRIDE (&src->info, 0); const guint32 dest_stride = GST_VIDEO_INFO_PLANE_STRIDE (&dest->info, 0); if (GST_VIDEO_INFO_FORMAT (&src->info) != GST_VIDEO_FORMAT_AYUV) return FALSE; if (GST_VIDEO_INFO_WIDTH (&src->info) != GST_VIDEO_INFO_WIDTH (&dest->info) || GST_VIDEO_INFO_HEIGHT (&src->info) != GST_VIDEO_INFO_HEIGHT (&dest->info)) return FALSE; num_pixels = GST_VIDEO_INFO_WIDTH (&src->info) * GST_VIDEO_INFO_HEIGHT (&src->info); s = (guint8 *) (src->data[0]); colours = g_array_sized_new (FALSE, FALSE, sizeof (ColourEntry), num_pixels); colours = g_array_set_size (colours, num_pixels); histogram = g_array_sized_new (FALSE, TRUE, sizeof (HistogramEntry), num_pixels); histogram = g_array_set_size (histogram, num_pixels); /* Copy the pixels to an array we can sort, dropping any stride padding, * and recording the output index into the destination bitmap in the * pix_index field */ dest_y_index = 0; out_index = 0; for (i = 0; i < GST_VIDEO_INFO_HEIGHT (&src->info); i++) { guint32 x_index; gint x; for (x = 0, x_index = 0; x < GST_VIDEO_INFO_WIDTH (&src->info); x++, x_index += 4) { guint8 *pix = s + x_index; c = &g_array_index (colours, ColourEntry, out_index); c->colour = GST_READ_UINT32_BE (pix); c->pix_index = dest_y_index + x; out_index++; } s += src_stride; dest_y_index += dest_stride; } /* Build a histogram of the highest colour counts: */ g_array_sort (colours, compare_colour_entry_colour); c = &g_array_index (colours, ColourEntry, 0); last = c->colour; num_colours = 0; cur_count = 1; for (i = 1; i < num_pixels; i++) { ColourEntry *c = &g_array_index (colours, ColourEntry, i); guint32 cur = c->colour; if (cur == last) { cur_count++; continue; } /* Colour changed - add an entry to the histogram */ h = &g_array_index (histogram, HistogramEntry, num_colours); h->colour = last; h->count = cur_count; num_colours++; cur_count = 1; last = cur; } h = &g_array_index (histogram, HistogramEntry, num_colours); h->colour = last; h->count = cur_count; num_colours++; GST_LOG ("image has %u colours", num_colours); histogram = g_array_set_size (histogram, num_colours); if (num_colours > max_colours) { liq_image *image; liq_result *res; const liq_palette *pal; int i; int height = GST_VIDEO_INFO_HEIGHT (&src->info); unsigned char **dest_rows = malloc (height * sizeof (void *)); guint8 *dest_palette = (guint8 *) (dest->data[1]); liq_attr *attr = liq_attr_create (); gint out_index = 0; for (i = 0; i < height; i++) { dest_rows[i] = (guint8 *) (dest->data[0]) + i * dest_stride; } liq_set_max_colors (attr, max_colours); image = liq_image_create_custom (attr, image_get_rgba_row_callback, src, GST_VIDEO_INFO_WIDTH (&src->info), GST_VIDEO_INFO_HEIGHT (&src->info), 0); res = liq_quantize_image (attr, image); liq_write_remapped_image_rows (res, image, dest_rows); pal = liq_get_palette (res); num_colours = pal->count; /* Write out the palette */ for (i = 0; i < num_colours; i++) { guint8 *c = dest_palette + out_index; const liq_color *col = pal->entries + i; c[0] = col->a; c[1] = col->r; c[2] = col->g; c[3] = col->b; out_index += 4; } free (dest_rows); liq_attr_destroy (attr); liq_image_destroy (image); liq_result_destroy (res); } else { guint8 *d = (guint8 *) (dest->data[0]); guint8 *palette = (guint8 *) (dest->data[1]); gint out_index = 0; /* Write out the palette */ for (i = 0; i < num_colours; i++) { h = &g_array_index (histogram, HistogramEntry, i); GST_WRITE_UINT32_BE (palette + out_index, h->colour); out_index += 4; } /* Write out the palette image. At this point, both the * colours and histogram arrays are sorted in descending AYUV value, * so walk them both and write out the current palette index */ out_index = 0; for (i = 0; i < num_pixels; i++) { c = &g_array_index (colours, ColourEntry, i); h = &g_array_index (histogram, HistogramEntry, out_index); if (c->colour != h->colour) { out_index++; h = &g_array_index (histogram, HistogramEntry, out_index); g_assert (h->colour == c->colour); /* We must be walking colours in the same order in both arrays */ } d[c->pix_index] = out_index; } } ret = TRUE; if (out_num_colours) *out_num_colours = num_colours; g_array_free (colours, TRUE); g_array_free (histogram, TRUE); return ret; } typedef void (*EncodeRLEFunc) (GstByteWriter * b, const guint8 * pixels, const gint stride, const gint w, const gint h); static void encode_rle2 (GstByteWriter * b, const guint8 * pixels, const gint stride, const gint w, const gint h) { GstBitWriter bits; int y; gst_bit_writer_init (&bits); for (y = 0; y < h; y++) { int x = 0; guint size; gst_byte_writer_put_uint8 (b, DVB_PIXEL_DATA_TYPE_2BIT); while (x < w) { int x_end = x; int run_length; guint8 pix; pix = pixels[x_end++]; while (x_end < w && pixels[x_end] == pix) x_end++; #ifdef HACK_2BIT pix >>= 6; /* HACK to convert 8 bit to 2 bit palette */ #endif /* 284 is the largest run length we can encode */ run_length = MIN (x_end - x, 284); if (run_length >= 29) { /* 000011LLLL = run 29 to 284 pixels */ if (run_length > 284) run_length = 284; gst_bit_writer_put_bits_uint8 (&bits, 0x03, 6); gst_bit_writer_put_bits_uint8 (&bits, run_length - 29, 8); gst_bit_writer_put_bits_uint8 (&bits, pix, 2); } else if (run_length >= 12 && run_length <= 27) { /* 000010LLLL = run 12 to 27 pixels */ gst_bit_writer_put_bits_uint8 (&bits, 0x02, 6); gst_bit_writer_put_bits_uint8 (&bits, run_length - 12, 4); gst_bit_writer_put_bits_uint8 (&bits, pix, 2); } else if (run_length >= 3 && run_length <= 10) { /* 001LL = run 3 to 10 pixels */ gst_bit_writer_put_bits_uint8 (&bits, 0, 2); gst_bit_writer_put_bits_uint8 (&bits, 0x8 + run_length - 3, 4); gst_bit_writer_put_bits_uint8 (&bits, pix, 2); } /* Missed cases - 11 pixels, 28 pixels or a short length 1 or 2 pixels * - write out a single pixel if != 0, or 1 or 2 pixels of black */ else if (pix != 0) { gst_bit_writer_put_bits_uint8 (&bits, pix, 2); run_length = 1; } else if (run_length == 2) { /* 0000 01 - 2 pixels colour 0 */ gst_bit_writer_put_bits_uint8 (&bits, 0x1, 6); run_length = 2; } else { /* 0001 - single pixel colour 0 */ gst_bit_writer_put_bits_uint8 (&bits, 0x1, 4); run_length = 1; } x += run_length; GST_LOG ("%u pixels = colour %u", run_length, pix); } /* End of line 0x00 */ gst_bit_writer_put_bits_uint8 (&bits, 0x00, 8); /* pad by 4 bits if needed to byte align, then * write bit string to output */ gst_bit_writer_align_bytes (&bits, 0); size = gst_bit_writer_get_size (&bits); gst_byte_writer_put_data (b, gst_bit_writer_get_data (&bits), size / 8); gst_bit_writer_reset (&bits); gst_bit_writer_init (&bits); GST_LOG ("y %u 2-bit RLE string = %u bits", y, size); gst_byte_writer_put_uint8 (b, DVB_PIXEL_DATA_TYPE_END_OF_LINE); pixels += stride; } } static void encode_rle4 (GstByteWriter * b, const guint8 * pixels, const gint stride, const gint w, const gint h) { GstBitWriter bits; int y; gst_bit_writer_init (&bits); for (y = 0; y < h; y++) { int x = 0; guint size; gst_byte_writer_put_uint8 (b, DVB_PIXEL_DATA_TYPE_4BIT); while (x < w) { int x_end = x; int run_length; guint8 pix; pix = pixels[x_end++]; while (x_end < w && pixels[x_end] == pix) x_end++; /* 280 is the largest run length we can encode */ run_length = MIN (x_end - x, 280); GST_LOG ("Encoding run %u pixels = colour %u", run_length, pix); #ifdef HACK_4BIT pix >>= 4; /* HACK to convert 8 bit to 4 palette */ #endif if (pix == 0 && run_length >= 3 && run_length <= 9) { gst_bit_writer_put_bits_uint8 (&bits, 0, 4); gst_bit_writer_put_bits_uint8 (&bits, run_length - 2, 4); } else if (run_length >= 4 && run_length < 25) { /* 4 to 7 pixels encoding */ if (run_length > 7) run_length = 7; gst_bit_writer_put_bits_uint8 (&bits, 0, 4); gst_bit_writer_put_bits_uint8 (&bits, 0x8 + run_length - 4, 4); gst_bit_writer_put_bits_uint8 (&bits, pix, 4); } else if (run_length >= 25) { /* Run length 25 to 280 pixels */ if (run_length > 280) run_length = 280; gst_bit_writer_put_bits_uint8 (&bits, 0x0f, 8); gst_bit_writer_put_bits_uint8 (&bits, run_length - 25, 8); gst_bit_writer_put_bits_uint8 (&bits, pix, 4); } /* Short length, 1, 2 or 3 pixels - write out a single pixel if != 0, * or 1 or 2 pixels of black */ else if (pix != 0) { gst_bit_writer_put_bits_uint8 (&bits, pix, 4); run_length = 1; } else if (run_length > 1) { /* 0000 1101 */ gst_bit_writer_put_bits_uint8 (&bits, 0xd, 8); run_length = 2; } else { /* 0000 1100 */ gst_bit_writer_put_bits_uint8 (&bits, 0xc, 8); run_length = 1; } x += run_length; GST_LOG ("Put %u pixels = colour %u", run_length, pix); } /* End of line 0x00 */ gst_bit_writer_put_bits_uint8 (&bits, 0x00, 8); /* pad by 4 bits if needed to byte align, then * write bit string to output */ gst_bit_writer_align_bytes (&bits, 0); size = gst_bit_writer_get_size (&bits); gst_byte_writer_put_data (b, gst_bit_writer_get_data (&bits), size / 8); gst_bit_writer_reset (&bits); gst_bit_writer_init (&bits); GST_LOG ("y %u 4-bit RLE string = %u bits", y, size); gst_byte_writer_put_uint8 (b, DVB_PIXEL_DATA_TYPE_END_OF_LINE); pixels += stride; } } static void encode_rle8 (GstByteWriter * b, const guint8 * pixels, const gint stride, const gint w, const gint h) { int y; for (y = 0; y < h; y++) { int x = 0; gst_byte_writer_put_uint8 (b, DVB_PIXEL_DATA_TYPE_8BIT); while (x < w) { int x_end = x; int run_length; guint8 pix; pix = pixels[x_end++]; while (x_end < w && pixels[x_end] == pix) x_end++; /* 127 is the largest run length we can encode */ run_length = MIN (x_end - x, 127); if (run_length == 1 && pix != 0) { /* a single non-zero pixel - encode directly */ gst_byte_writer_put_uint8 (b, pix); } else if (pix == 0) { /* Encode up to 1-127 pixels of colour 0 */ gst_byte_writer_put_uint8 (b, 0); gst_byte_writer_put_uint8 (b, run_length); } else if (run_length > 2) { /* Encode 3-127 pixels of colour 'pix' directly */ gst_byte_writer_put_uint8 (b, 0); gst_byte_writer_put_uint8 (b, 0x80 | run_length); gst_byte_writer_put_uint8 (b, pix); } else { /* Short 1-2 pixel run, encode it directly */ if (run_length == 2) gst_byte_writer_put_uint8 (b, pix); gst_byte_writer_put_uint8 (b, pix); g_assert (run_length == 1 || run_length == 2); } x += run_length; } /* End of line bytes */ gst_byte_writer_put_uint8 (b, 0x00); // This 2nd 0x00 byte is correct from the spec, but ffmpeg // as of 2020-04-24 does not like it gst_byte_writer_put_uint8 (b, 0x00); gst_byte_writer_put_uint8 (b, DVB_PIXEL_DATA_TYPE_END_OF_LINE); pixels += stride; } } static gboolean dvbenc_write_object_data (GstByteWriter * b, int object_version, int page_id, int object_id, SubpictureRect * s) { guint seg_size_pos, end_pos; guint pixel_fields_size_pos, top_start_pos, bottom_start_pos; EncodeRLEFunc encode_rle_func; const gint stride = GST_VIDEO_INFO_PLANE_STRIDE (&s->frame->info, 0); const gint w = GST_VIDEO_INFO_WIDTH (&s->frame->info); const gint h = GST_VIDEO_INFO_HEIGHT (&s->frame->info); const guint8 *pixels = (guint8 *) (s->frame->data[0]); if (s->nb_colours <= 4) encode_rle_func = encode_rle2; else if (s->nb_colours <= 16) encode_rle_func = encode_rle4; else encode_rle_func = encode_rle8; gst_byte_writer_put_uint8 (b, DVB_SEGMENT_SYNC_BYTE); gst_byte_writer_put_uint8 (b, DVB_SEGMENT_TYPE_OBJECT_DATA); gst_byte_writer_put_uint16_be (b, page_id); seg_size_pos = gst_byte_writer_get_pos (b); gst_byte_writer_put_uint16_be (b, 0); gst_byte_writer_put_uint16_be (b, object_id); /* version number, coding_method (0), non-modifying-flag (0), reserved bit */ gst_byte_writer_put_uint8 (b, (object_version << 4) | 0x01); pixel_fields_size_pos = gst_byte_writer_get_pos (b); gst_byte_writer_put_uint16_be (b, 0); gst_byte_writer_put_uint16_be (b, 0); /* Write the top field (even) lines (round up lines / 2) */ top_start_pos = gst_byte_writer_get_pos (b); encode_rle_func (b, pixels, stride * 2, w, (h + 1) / 2); /* Write the bottom field (odd) lines (round down lines / 2) */ bottom_start_pos = gst_byte_writer_get_pos (b); if (h > 1) encode_rle_func (b, pixels + stride, stride * 2, w, h >> 1); end_pos = gst_byte_writer_get_pos (b); /* If the encoded size of the top+bottom field data blocks is even, * add a stuffing byte */ if (((end_pos - top_start_pos) & 1) == 0) { gst_byte_writer_put_uint8 (b, 0); end_pos = gst_byte_writer_get_pos (b); } /* Re-write the size fields */ gst_byte_writer_set_pos (b, seg_size_pos); if (end_pos - (seg_size_pos + 2) > G_MAXUINT16) return FALSE; /* Data too big */ gst_byte_writer_put_uint16_be (b, end_pos - (seg_size_pos + 2)); if (bottom_start_pos - top_start_pos > G_MAXUINT16) return FALSE; /* Data too big */ if (end_pos - bottom_start_pos > G_MAXUINT16) return FALSE; /* Data too big */ gst_byte_writer_set_pos (b, pixel_fields_size_pos); gst_byte_writer_put_uint16_be (b, bottom_start_pos - top_start_pos); gst_byte_writer_put_uint16_be (b, end_pos - bottom_start_pos); gst_byte_writer_set_pos (b, end_pos); GST_LOG ("Object seg size %u top_size %u bottom_size %u", end_pos - (seg_size_pos + 2), bottom_start_pos - top_start_pos, end_pos - bottom_start_pos); return TRUE; } static void dvbenc_write_clut (GstByteWriter * b, int object_version, int page_id, int clut_id, SubpictureRect * s) { guint8 *palette; int clut_entry_flag; guint seg_size_pos, pos; int i; if (s->nb_colours <= 4) clut_entry_flag = 4; else if (s->nb_colours <= 16) clut_entry_flag = 2; else clut_entry_flag = 1; gst_byte_writer_put_uint8 (b, DVB_SEGMENT_SYNC_BYTE); gst_byte_writer_put_uint8 (b, DVB_SEGMENT_TYPE_CLUT_DEFINITION); gst_byte_writer_put_uint16_be (b, page_id); seg_size_pos = gst_byte_writer_get_pos (b); gst_byte_writer_put_uint16_be (b, 0); gst_byte_writer_put_uint8 (b, clut_id); /* version number, reserved bits */ gst_byte_writer_put_uint8 (b, (object_version << 4) | 0x0F); palette = (guint8 *) (s->frame->data[1]); for (i = 0; i < s->nb_colours; i++) { gst_byte_writer_put_uint8 (b, i); /* clut_entry_flag | 4-bits reserved | full_range_flag = 1 */ gst_byte_writer_put_uint8 (b, clut_entry_flag << 5 | 0x1F); /* Write YVUT value, where T (transparency) = 255 - A, Palette is AYUV */ gst_byte_writer_put_uint8 (b, palette[1]); /* Y */ gst_byte_writer_put_uint8 (b, palette[3]); /* V */ gst_byte_writer_put_uint8 (b, palette[2]); /* U */ gst_byte_writer_put_uint8 (b, 255 - palette[0]); /* A */ #if defined (HACK_2BIT) palette += 4 * 64; /* HACK to generate 4-colour palette */ #elif defined (HACK_4BIT) palette += 4 * 16; /* HACK to generate 16-colour palette */ #else palette += 4; #endif } /* Re-write the size field */ pos = gst_byte_writer_get_pos (b); gst_byte_writer_set_pos (b, seg_size_pos); gst_byte_writer_put_uint16_be (b, pos - (seg_size_pos + 2)); gst_byte_writer_set_pos (b, pos); } static void dvbenc_write_region_segment (GstByteWriter * b, int object_version, int page_id, int region_id, SubpictureRect * s) { int region_depth; guint seg_size_pos, pos; gint w = GST_VIDEO_INFO_WIDTH (&s->frame->info); gint h = GST_VIDEO_INFO_HEIGHT (&s->frame->info); if (s->nb_colours <= 4) region_depth = 1; else if (s->nb_colours <= 16) region_depth = 2; else region_depth = 3; gst_byte_writer_put_uint8 (b, DVB_SEGMENT_SYNC_BYTE); gst_byte_writer_put_uint8 (b, DVB_SEGMENT_TYPE_REGION_COMPOSITION); gst_byte_writer_put_uint16_be (b, page_id); /* Size placeholder */ seg_size_pos = gst_byte_writer_get_pos (b); gst_byte_writer_put_uint16_be (b, 0); gst_byte_writer_put_uint8 (b, region_id); /* version number, fill flag, reserved bits */ gst_byte_writer_put_uint8 (b, (object_version << 4) | (0 << 3) | 0x07); gst_byte_writer_put_uint16_be (b, w); gst_byte_writer_put_uint16_be (b, h); /* level_of_compatibility and depth */ gst_byte_writer_put_uint8 (b, region_depth << 5 | region_depth << 2 | 0x03); /* CLUT id */ gst_byte_writer_put_uint8 (b, region_id); /* Dummy flags for the fill colours */ gst_byte_writer_put_uint16_be (b, 0x0003); /* Object ID = region_id = CLUT id */ gst_byte_writer_put_uint16_be (b, region_id); /* object type = 0, x,y corner = 0 */ gst_byte_writer_put_uint16_be (b, 0x0000); gst_byte_writer_put_uint16_be (b, 0xf000); /* Re-write the size field */ pos = gst_byte_writer_get_pos (b); gst_byte_writer_set_pos (b, seg_size_pos); gst_byte_writer_put_uint16_be (b, pos - (seg_size_pos + 2)); gst_byte_writer_set_pos (b, pos); } GstBuffer * gst_dvbenc_encode (int object_version, int page_id, SubpictureRect * s, guint num_subpictures) { GstByteWriter b; guint seg_size_pos, pos; guint i; #ifdef HACK_2BIT /* HACK: Only output 4 colours (results may be garbage, but tests * the encoding */ s->nb_colours = 4; #elif defined (HACK_4BIT) /* HACK: Only output 16 colours */ s->nb_colours = 16; #endif gst_byte_writer_init (&b); /* GStreamer passes DVB subpictures as private PES packets with * 0x20 0x00 prefixed */ gst_byte_writer_put_uint16_be (&b, 0x2000); /* Page Composition Segment */ gst_byte_writer_put_uint8 (&b, DVB_SEGMENT_SYNC_BYTE); gst_byte_writer_put_uint8 (&b, DVB_SEGMENT_TYPE_PAGE_COMPOSITION); gst_byte_writer_put_uint16_be (&b, page_id); seg_size_pos = gst_byte_writer_get_pos (&b); gst_byte_writer_put_uint16_be (&b, 0); gst_byte_writer_put_uint8 (&b, 30); /* We always write complete overlay subregions, so use page_state = 2 (mode change) */ gst_byte_writer_put_uint8 (&b, (object_version << 4) | (2 << 2) | 0x3); for (i = 0; i < num_subpictures; i++) { gst_byte_writer_put_uint8 (&b, i); gst_byte_writer_put_uint8 (&b, 0xFF); gst_byte_writer_put_uint16_be (&b, s[i].x); gst_byte_writer_put_uint16_be (&b, s[i].y); } /* Rewrite the size field */ pos = gst_byte_writer_get_pos (&b); gst_byte_writer_set_pos (&b, seg_size_pos); gst_byte_writer_put_uint16_be (&b, pos - (seg_size_pos + 2)); gst_byte_writer_set_pos (&b, pos); /* Region Composition */ for (i = 0; i < num_subpictures; i++) { dvbenc_write_region_segment (&b, object_version, page_id, i, s + i); } /* CLUT definitions */ for (i = 0; i < num_subpictures; i++) { dvbenc_write_clut (&b, object_version, page_id, i, s + i); } /* object data */ for (i = 0; i < num_subpictures; i++) { /* FIXME: Any object data could potentially overflow the 64K field * size, in which case we should split it */ if (!dvbenc_write_object_data (&b, object_version, page_id, i, s + i)) { GST_WARNING ("Object data was too big to encode"); goto fail; } } /* End of Display Set segment */ gst_byte_writer_put_uint8 (&b, DVB_SEGMENT_SYNC_BYTE); gst_byte_writer_put_uint8 (&b, DVB_SEGMENT_TYPE_END_OF_DISPLAY); gst_byte_writer_put_uint16_be (&b, page_id); gst_byte_writer_put_uint16_be (&b, 0); /* End of PES data marker */ gst_byte_writer_put_uint8 (&b, 0xFF); return gst_byte_writer_reset_and_get_buffer (&b); fail: gst_byte_writer_reset (&b); return NULL; }