mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-11 10:56:38 +00:00
60226124ec
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>
807 lines
20 KiB
C
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;
|
|
}
|