gstreamer/subprojects/gst-plugins-bad/gst/dvdspu/gstspu-pgs.c
Sebastian Dröge 60226124ec dvdspu: Make sure enough data is allocated for the available data
If the size read from the stream is smaller than the currently available
data then the size is bogus and the data should simply be discarded.

Fixes ZDI-CAN-20994
Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2660

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4899>
2023-06-20 10:07:14 +00:00

807 lines
20 KiB
C

/* GStreamer Sub-Picture Unit - PGS handling
* Copyright (C) 2009 Jan Schmidt <thaytan@noraisin.net>
*
* 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 <string.h>
#include <gst/gst.h>
#include "gstdvdspu.h"
#include "gstspu-pgs.h"
const struct PgsFrameRateEntry
{
guint8 id;
guint fps_n;
guint fps_d;
} PgsFrameRates[] = {
{
64, 30000, 1001} /* 29.97 FPS */
};
typedef enum PgsCommandType PgsCommandType;
enum PgsCommandType
{
PGS_COMMAND_SET_PALETTE = 0x14,
PGS_COMMAND_SET_OBJECT_DATA = 0x15,
PGS_COMMAND_PRESENTATION_SEGMENT = 0x16,
PGS_COMMAND_SET_WINDOW = 0x17,
PGS_COMMAND_INTERACTIVE_SEGMENT = 0x18,
PGS_COMMAND_END_DISPLAY = 0x80,
PGS_COMMAND_INVALID = 0xFFFF
};
static gint gstspu_exec_pgs_buffer (GstDVDSpu * dvdspu, GstBuffer * buf);
#define DUMP_CMDS 0
#define DUMP_FULL_IMAGE 0
#define DUMP_FULL_PALETTE 0
#if DUMP_CMDS
#define PGS_DUMP(...) g_print(__VA_ARGS__)
#else
#define PGS_DUMP(...)
#endif
static void
dump_bytes (guint8 * data, guint16 len)
{
gint i;
/* Dump the numbers */
for (i = 0; i < len; i++) {
PGS_DUMP ("0x%02x ", data[i]);
if (!((i + 1) % 16))
PGS_DUMP ("\n");
}
if (len > 0 && (i % 16))
PGS_DUMP ("\n");
}
static void
dump_rle_data (GstDVDSpu * dvdspu, guint8 * data, guint32 len)
{
#if DUMP_FULL_IMAGE
guint16 obj_h;
guint16 obj_w;
guint8 *end = data + len;
guint x = 0;
if (data + 4 > end)
return;
/* RLE data: */
obj_w = GST_READ_UINT16_BE (data);
obj_h = GST_READ_UINT16_BE (data + 2);
data += 4;
PGS_DUMP ("RLE image is %ux%u\n", obj_w, obj_h);
while (data < end) {
guint8 pal_id;
guint16 run_len;
pal_id = *data++;
if (pal_id != 0) {
// PGS_DUMP ("data 0x%02x\n", data[0]);
run_len = 1;
} else {
if (data + 1 > end)
return;
switch (data[0] & 0xC0) {
case 0x00:
//PGS_DUMP ("data 0x%02x\n", data[0]);
run_len = (data[0] & 0x3f);
data++;
break;
case 0x40:
if (data + 2 > end)
return;
//PGS_DUMP ("data 0x%02x 0x%02x\n", data[0], data[1]);
run_len = ((data[0] << 8) | data[1]) & 0x3fff;
data += 2;
break;
case 0x80:
if (data + 2 > end)
return;
//PGS_DUMP ("data 0x%02x 0x%02x\n", data[0], data[1]);
run_len = (data[0] & 0x3f);
pal_id = data[1];
data += 2;
break;
case 0xC0:
if (data + 3 > end)
return;
//PGS_DUMP ("data 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2]);
run_len = ((data[0] << 8) | data[1]) & 0x3fff;
pal_id = data[2];
data += 3;
break;
default:
run_len = 0;
break;
}
}
{
gint i;
#if 1
if (dvdspu->spu_state.pgs.palette[pal_id].A) {
guint8 val = dvdspu->spu_state.pgs.palette[pal_id].A;
for (i = 0; i < run_len; i++)
PGS_DUMP ("%02x ", val);
} else {
for (i = 0; i < run_len; i++)
PGS_DUMP (" ");
}
if (!run_len || (x + run_len) > obj_w)
PGS_DUMP ("\n");
#else
PGS_DUMP ("Run x: %d pix: %d col: %d\n", x, run_len, pal_id);
#endif
}
x += run_len;
if (!run_len || x > obj_w)
x = 0;
};
PGS_DUMP ("\n");
#endif
}
static void
pgs_composition_object_render (PgsCompositionObject * obj, SpuState * state,
GstVideoFrame * frame)
{
SpuColour *colour;
guint8 *planes[3]; /* YUV frame pointers */
gint strides[3];
guint8 *data, *end;
guint16 obj_w;
guint16 obj_h G_GNUC_UNUSED;
guint x, y, i, min_x, max_x;
if (G_UNLIKELY (obj->rle_data == NULL || obj->rle_data_size == 0
|| obj->rle_data_used != obj->rle_data_size))
return;
data = obj->rle_data;
end = data + obj->rle_data_used;
if (data + 4 > end)
return;
/* FIXME: Calculate and use the cropping window for the output, as the
* intersection of the crop rectangle for this object (if any) and the
* window specified by the object's window_id */
/* Store the start of each plane */
planes[0] = GST_VIDEO_FRAME_COMP_DATA (frame, 0);
planes[1] = GST_VIDEO_FRAME_COMP_DATA (frame, 1);
planes[2] = GST_VIDEO_FRAME_COMP_DATA (frame, 2);
strides[0] = GST_VIDEO_FRAME_COMP_STRIDE (frame, 0);
strides[1] = GST_VIDEO_FRAME_COMP_STRIDE (frame, 1);
strides[2] = GST_VIDEO_FRAME_COMP_STRIDE (frame, 2);
y = MIN (obj->y, state->info.height);
planes[0] += strides[0] * y;
planes[1] += strides[1] * (y / 2);
planes[2] += strides[2] * (y / 2);
/* RLE data: */
obj_w = GST_READ_UINT16_BE (data);
obj_h = GST_READ_UINT16_BE (data + 2);
data += 4;
min_x = MIN (obj->x, strides[0]);
max_x = MIN (obj->x + obj_w, strides[0]);
state->comp_left = x = min_x;
state->comp_right = max_x;
gstspu_clear_comp_buffers (state);
while (data < end) {
guint8 pal_id;
guint16 run_len;
pal_id = *data++;
if (pal_id != 0) {
run_len = 1;
} else {
if (data + 1 > end)
return;
switch (data[0] & 0xC0) {
case 0x00:
run_len = (data[0] & 0x3f);
data++;
break;
case 0x40:
if (data + 2 > end)
return;
run_len = ((data[0] << 8) | data[1]) & 0x3fff;
data += 2;
break;
case 0x80:
if (data + 2 > end)
return;
run_len = (data[0] & 0x3f);
pal_id = data[1];
data += 2;
break;
case 0xC0:
if (data + 3 > end)
return;
run_len = ((data[0] << 8) | data[1]) & 0x3fff;
pal_id = data[2];
data += 3;
break;
default:
run_len = 0;
break;
}
}
colour = &state->pgs.palette[pal_id];
if (colour->A) {
guint32 inv_A = 0xff - colour->A;
if (G_UNLIKELY (x + run_len > max_x))
run_len = (max_x - x);
for (i = 0; i < run_len; i++) {
planes[0][x] = (inv_A * planes[0][x] + colour->Y) / 0xff;
state->comp_bufs[0][x / 2] += colour->U;
state->comp_bufs[1][x / 2] += colour->V;
state->comp_bufs[2][x / 2] += colour->A;
x++;
}
} else {
x += run_len;
}
if (!run_len || x > max_x) {
x = min_x;
planes[0] += strides[0];
if (y % 2) {
gstspu_blend_comp_buffers (state, planes);
gstspu_clear_comp_buffers (state);
planes[1] += strides[1];
planes[2] += strides[2];
}
y++;
if (y >= state->info.height)
return; /* Hit the bottom */
}
}
if (y % 2)
gstspu_blend_comp_buffers (state, planes);
}
static void
pgs_composition_object_clear (PgsCompositionObject * obj)
{
if (obj->rle_data) {
g_free (obj->rle_data);
obj->rle_data = NULL;
}
obj->rle_data_size = obj->rle_data_used = 0;
}
static void
pgs_presentation_segment_set_object_count (PgsPresentationSegment * ps,
guint8 n_objects)
{
if (ps->objects == NULL) {
if (n_objects == 0)
return;
ps->objects =
g_array_sized_new (FALSE, TRUE, sizeof (PgsCompositionObject),
n_objects);
g_array_set_size (ps->objects, n_objects);
return;
}
/* Clear memory in any extraneous objects */
if (ps->objects->len > n_objects) {
guint i;
for (i = n_objects; i < ps->objects->len; i++) {
PgsCompositionObject *cur =
&g_array_index (ps->objects, PgsCompositionObject, i);
pgs_composition_object_clear (cur);
}
}
g_array_set_size (ps->objects, n_objects);
if (n_objects == 0) {
g_array_free (ps->objects, TRUE);
ps->objects = NULL;
}
}
static PgsCompositionObject *
pgs_presentation_segment_find_object (PgsPresentationSegment * ps,
guint16 obj_id)
{
guint i;
if (ps->objects == NULL)
return NULL;
for (i = 0; i < ps->objects->len; i++) {
PgsCompositionObject *cur =
&g_array_index (ps->objects, PgsCompositionObject, i);
if (cur->id == obj_id)
return cur;
}
return NULL;
}
static int
parse_presentation_segment (GstDVDSpu * dvdspu, guint8 type, guint8 * payload,
guint16 len)
{
guint8 *end = payload + len;
PgsPresentationSegment *ps = &dvdspu->spu_state.pgs.pres_seg;
guint8 n_objects, palette_id;
gint i;
/* Parse video descriptor */
if (payload + 5 > end)
return 0;
ps->vid_w = GST_READ_UINT16_BE (payload);
ps->vid_h = GST_READ_UINT16_BE (payload + 2);
ps->vid_fps_code = payload[4];
payload += 5;
/* Parse composition descriptor */
if (payload + 3 > end)
return 0;
ps->composition_no = GST_READ_UINT16_BE (payload);
ps->composition_state = payload[2];
payload += 3;
/* Parse other bits */
if (payload + 3 > end)
return 0;
ps->flags = payload[0];
palette_id = payload[1];
n_objects = payload[2];
payload += 3;
if (ps->flags & PGS_PRES_SEGMENT_FLAG_UPDATE_PALETTE)
ps->palette_id = palette_id;
PGS_DUMP ("Video width %u height %u fps code %u\n", ps->vid_w, ps->vid_h,
ps->vid_fps_code);
PGS_DUMP
("Composition num %u state 0x%02x flags 0x%02x palette id %u n_objects %u\n",
ps->composition_no, ps->composition_state, ps->flags, ps->palette_id,
n_objects);
pgs_presentation_segment_set_object_count (ps, n_objects);
for (i = 0; i < (gint) n_objects; i++) {
PgsCompositionObject *obj =
&g_array_index (ps->objects, PgsCompositionObject, i);
if (payload + 8 > end)
break;
obj->id = GST_READ_UINT16_BE (payload);
obj->win_id = payload[2];
obj->flags = payload[3];
obj->x = GST_READ_UINT16_BE (payload + 4);
obj->y = GST_READ_UINT16_BE (payload + 6);
obj->rle_data_size = obj->rle_data_used = 0;
payload += 8;
PGS_DUMP ("Composition object %d Object ID %u Window ID %u flags 0x%02x "
"x %u y %u\n", i, obj->id, obj->win_id, obj->flags, obj->x, obj->y);
if (obj->flags & PGS_COMPOSITION_OBJECT_FLAG_CROPPED) {
if (payload + 8 > end)
break;
obj->crop_x = GST_READ_UINT16_BE (payload);
obj->crop_y = GST_READ_UINT16_BE (payload + 2);
obj->crop_w = GST_READ_UINT16_BE (payload + 4);
obj->crop_h = GST_READ_UINT16_BE (payload + 6);
payload += 8;
PGS_DUMP ("Cropping window x %u y %u w %u h %u\n",
obj->crop_x, obj->crop_y, obj->crop_w, obj->crop_h);
}
if (obj->flags & ~(PGS_COMPOSITION_OBJECT_FLAG_CROPPED |
PGS_COMPOSITION_OBJECT_FLAG_FORCED))
GST_ERROR ("PGS Composition Object has unknown flags: 0x%02x",
obj->flags);
}
if (payload != end) {
GST_ERROR ("PGS Composition Object: %" G_GSSIZE_FORMAT
" bytes not consumed", (gssize) (end - payload));
dump_bytes (payload, end - payload);
}
return 0;
}
static int
parse_set_palette (GstDVDSpu * dvdspu, guint8 type, guint8 * payload,
guint16 len)
{
SpuState *state = &dvdspu->spu_state;
const gint PGS_PALETTE_ENTRY_SIZE = 5;
guint8 *end = payload + len;
guint8 palette_id G_GNUC_UNUSED;
guint8 palette_version G_GNUC_UNUSED;
gint n_entries, i;
if (len < 2) /* Palette command too short */
return 0;
palette_id = payload[0];
palette_version = payload[1];
payload += 2;
n_entries = (len - 2) / PGS_PALETTE_ENTRY_SIZE;
PGS_DUMP ("Palette ID %u version %u. %d entries\n",
palette_id, palette_version, n_entries);
for (i = 0; i < 256; i++)
state->pgs.palette[i].A = 0;
for (i = 0; i < n_entries; i++) {
guint8 n, Y, U, V, A;
n = payload[0];
Y = payload[1];
V = payload[2];
U = payload[3];
A = payload[4];
#if DUMP_FULL_PALETTE
PGS_DUMP ("Entry %3d: Y %3d U %3d V %3d A %3d ", n, Y, U, V, A);
if (((i + 1) % 2) == 0)
PGS_DUMP ("\n");
#endif
/* Premultiply the palette entries by the alpha */
state->pgs.palette[n].Y = Y * A;
state->pgs.palette[n].U = U * A;
state->pgs.palette[n].V = V * A;
state->pgs.palette[n].A = A;
payload += PGS_PALETTE_ENTRY_SIZE;
}
#if DUMP_FULL_PALETTE
if (n_entries > 0 && (i % 2))
PGS_DUMP ("\n");
#endif
if (payload != end) {
GST_ERROR ("PGS Set Palette: %" G_GSSIZE_FORMAT " bytes not consumed",
(gssize) (end - payload));
dump_bytes (payload, end - payload);
}
return 0;
}
static int
parse_set_window (GstDVDSpu * dvdspu, guint8 type, guint8 * payload,
guint16 len)
{
SpuState *state = &dvdspu->spu_state;
guint8 *end = payload + len;
guint8 win_count, win_id G_GNUC_UNUSED;
gint i;
if (payload + 1 > end)
return 0;
dump_bytes (payload, len);
win_count = payload[0];
payload++;
for (i = 0; i < win_count; i++) {
if (payload + 9 > end)
return 0;
/* FIXME: Store each window ID separately into an array */
win_id = payload[0];
state->pgs.win_x = GST_READ_UINT16_BE (payload + 1);
state->pgs.win_y = GST_READ_UINT16_BE (payload + 3);
state->pgs.win_w = GST_READ_UINT16_BE (payload + 5);
state->pgs.win_h = GST_READ_UINT16_BE (payload + 7);
payload += 9;
PGS_DUMP ("Win ID %u x %d y %d w %d h %d\n",
win_id, state->pgs.win_x, state->pgs.win_y, state->pgs.win_w,
state->pgs.win_h);
}
if (payload != end) {
GST_ERROR ("PGS Set Window: %" G_GSSIZE_FORMAT " bytes not consumed",
(gssize) (end - payload));
dump_bytes (payload, end - payload);
}
return 0;
}
static int
parse_set_object_data (GstDVDSpu * dvdspu, guint8 type, guint8 * payload,
guint16 len)
{
SpuPgsState *pgs_state = &dvdspu->spu_state.pgs;
PgsCompositionObject *obj;
guint8 *end = payload + len;
guint16 obj_id;
guint8 obj_ver, flags;
if (payload + 4 > end)
return 0;
obj_id = GST_READ_UINT16_BE (payload);
obj_ver = payload[2];
flags = payload[3];
payload += 4;
obj = pgs_presentation_segment_find_object (&(pgs_state->pres_seg), obj_id);
PGS_DUMP ("Object ID %d ver %u flags 0x%02x\n", obj_id, obj_ver, flags);
if (flags & PGS_OBJECT_UPDATE_FLAG_START_RLE) {
obj->rle_data_ver = obj_ver;
if (payload + 3 > end)
return 0;
obj->rle_data_size = GST_READ_UINT24_BE (payload);
payload += 3;
if (end - payload > obj->rle_data_size)
return 0;
PGS_DUMP ("%d bytes of RLE data, of %d bytes total.\n",
(int) (end - payload), obj->rle_data_size);
obj->rle_data = g_realloc (obj->rle_data, obj->rle_data_size);
obj->rle_data_used = end - payload;
memcpy (obj->rle_data, payload, end - payload);
payload = end;
} else {
PGS_DUMP ("%d bytes of additional RLE data\n", (int) (end - payload));
/* Check that the data chunk is for this object version, and fits in the buffer */
if (obj->rle_data_ver == obj_ver &&
obj->rle_data_used + end - payload <= obj->rle_data_size) {
memcpy (obj->rle_data + obj->rle_data_used, payload, end - payload);
obj->rle_data_used += end - payload;
payload = end;
}
}
if (obj->rle_data_size == obj->rle_data_used)
dump_rle_data (dvdspu, obj->rle_data, obj->rle_data_size);
if (payload != end) {
GST_ERROR ("PGS Set Object Data: %" G_GSSIZE_FORMAT " bytes not consumed",
(gssize) (end - payload));
dump_bytes (payload, end - payload);
}
return 0;
}
static int
parse_pgs_packet (GstDVDSpu * dvdspu, guint8 type, guint8 * payload,
guint16 len)
{
SpuPgsState *pgs_state = &dvdspu->spu_state.pgs;
int ret = 0;
if (!pgs_state->in_presentation_segment
&& type != PGS_COMMAND_PRESENTATION_SEGMENT) {
PGS_DUMP ("Expected BEGIN PRESENTATION SEGMENT command. "
"Got command type 0x%02x len %u. Skipping\n", type, len);
return 0;
}
switch (type) {
case PGS_COMMAND_PRESENTATION_SEGMENT:
PGS_DUMP ("*******************************************\n"
"Begin PRESENTATION_SEGMENT (0x%02x) packet len %u\n", type, len);
pgs_state->in_presentation_segment =
pgs_state->have_presentation_segment = TRUE;
ret = parse_presentation_segment (dvdspu, type, payload, len);
break;
case PGS_COMMAND_SET_OBJECT_DATA:
PGS_DUMP ("*** Set Object Data (0x%02x) packet len %u\n", type, len);
ret = parse_set_object_data (dvdspu, type, payload, len);
break;
case PGS_COMMAND_SET_PALETTE:
PGS_DUMP ("*** Set Palette (0x%02x) packet len %u\n", type, len);
ret = parse_set_palette (dvdspu, type, payload, len);
break;
case PGS_COMMAND_SET_WINDOW:
PGS_DUMP ("*** Set Window command (0x%02x) packet len %u\n", type, len);
ret = parse_set_window (dvdspu, type, payload, len);
break;
case PGS_COMMAND_INTERACTIVE_SEGMENT:
PGS_DUMP ("*** Interactive Segment command(0x%02x) packet len %u\n",
type, len);
dump_bytes (payload, len);
break;
case PGS_COMMAND_END_DISPLAY:
PGS_DUMP ("*** End Display command (0x%02x) packet len %u\n", type,
len);
pgs_state->in_presentation_segment = FALSE;
break;
default:
GST_ERROR ("Unknown PGS command: type 0x%02x len %u", type, len);
dump_bytes (payload, len);
break;
}
PGS_DUMP ("\n");
return ret;
}
gint
gstspu_exec_pgs_buffer (GstDVDSpu * dvdspu, GstBuffer * buf)
{
GstMapInfo map;
guint8 *pos, *end;
guint8 type;
guint16 packet_len;
gint remaining;
gst_buffer_map (buf, &map, GST_MAP_READ);
pos = map.data;
end = pos + map.size;
/* Need at least 3 bytes */
if (pos + 3 > end) {
PGS_DUMP ("Not enough bytes to be a PGS packet\n");
goto error;
}
PGS_DUMP ("Begin dumping command buffer of size %u ts %" GST_TIME_FORMAT "\n",
(guint) (end - pos), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
do {
type = *pos++;
packet_len = GST_READ_UINT16_BE (pos);
pos += 2;
if (pos + packet_len > end) {
PGS_DUMP ("Invalid packet length %u (only have %u bytes)\n", packet_len,
(guint) (end - pos));
goto error;
}
if (parse_pgs_packet (dvdspu, type, pos, packet_len))
goto error;
pos += packet_len;
} while (pos + 3 <= end);
PGS_DUMP ("End dumping command buffer with %u bytes remaining\n",
(guint) (end - pos));
remaining = (gint) (pos - map.data);
gst_buffer_unmap (buf, &map);
return remaining;
/* ERRORS */
error:
{
gst_buffer_unmap (buf, &map);
return -1;
}
}
void
gstspu_pgs_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts,
GstBuffer * buf)
{
SpuState *state = &dvdspu->spu_state;
state->next_ts = event_ts;
state->pgs.pending_cmd = buf;
}
gboolean
gstspu_pgs_execute_event (GstDVDSpu * dvdspu)
{
SpuState *state = &dvdspu->spu_state;
if (state->pgs.pending_cmd) {
gstspu_exec_pgs_buffer (dvdspu, state->pgs.pending_cmd);
gst_buffer_unref (state->pgs.pending_cmd);
state->pgs.pending_cmd = NULL;
}
state->next_ts = GST_CLOCK_TIME_NONE;
state->flags &= ~SPU_STATE_DISPLAY;
if (state->pgs.have_presentation_segment) {
if (state->pgs.pres_seg.objects && state->pgs.pres_seg.objects->len > 0)
state->flags |= SPU_STATE_DISPLAY;
}
return FALSE;
}
void
gstspu_pgs_render (GstDVDSpu * dvdspu, GstVideoFrame * frame)
{
SpuState *state = &dvdspu->spu_state;
PgsPresentationSegment *ps = &state->pgs.pres_seg;
guint i;
if (ps->objects == NULL)
return;
for (i = 0; i < ps->objects->len; i++) {
PgsCompositionObject *cur =
&g_array_index (ps->objects, PgsCompositionObject, i);
pgs_composition_object_render (cur, state, frame);
}
}
gboolean
gstspu_pgs_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event)
{
gst_event_unref (event);
return FALSE;
}
void
gstspu_pgs_flush (GstDVDSpu * dvdspu)
{
SpuPgsState *pgs_state = &dvdspu->spu_state.pgs;
if (pgs_state->pending_cmd) {
gst_buffer_unref (pgs_state->pending_cmd);
pgs_state->pending_cmd = NULL;
}
pgs_state->have_presentation_segment = FALSE;
pgs_state->in_presentation_segment = FALSE;
pgs_presentation_segment_set_object_count (&pgs_state->pres_seg, 0);
pgs_state->win_x = pgs_state->win_y = pgs_state->win_w = pgs_state->win_h = 0;
}