/*
* Skeleton of function spudec_process_controll() is from xine sources.
* Further works:
* LGB,... (yeah, try to improve it and insert your name here! ;-)
*
* Kim Minh Kaplan
* implement fragments reassembly, RLE decoding.
* read brightness from the IFO.
*
* For information on SPU format see
* and
*
* This file is part of MPlayer.
*
* MPlayer is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* MPlayer 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "config.h"
#include "core/mp_msg.h"
#include "spudec.h"
#include "vobsub.h"
#include "sub.h"
#include "core/mp_common.h"
#include "video/csputils.h"
typedef struct spu_packet_t packet_t;
struct spu_packet_t {
int is_decoded;
unsigned char *packet;
int data_len;
unsigned int palette[4];
unsigned int alpha[4];
unsigned int control_start; /* index of start of control data */
unsigned int current_nibble[2]; /* next data nibble (4 bits) to be
processed (for RLE decoding) for
even and odd lines */
int deinterlace_oddness; /* 0 or 1, index into current_nibble */
unsigned int start_col;
unsigned int start_row;
unsigned int width, height, stride;
unsigned int start_pts, end_pts;
packet_t *next;
};
typedef struct {
packet_t *queue_head;
packet_t *queue_tail;
unsigned int global_palette[16];
unsigned int orig_frame_width, orig_frame_height;
unsigned char* packet;
size_t packet_reserve; /* size of the memory pointed to by packet */
unsigned int packet_offset; /* end of the currently assembled fragment */
unsigned int packet_size; /* size of the packet once all fragments are assembled */
int packet_pts; /* PTS for this packet */
unsigned int palette[4];
unsigned int alpha[4];
unsigned int cuspal[4];
unsigned int custom;
unsigned int now_pts;
unsigned int start_pts, end_pts;
unsigned int start_col;
unsigned int start_row;
unsigned int width, height, stride;
size_t image_size; /* Size of the image buffer */
unsigned char *image; /* Grayscale value */
unsigned int pal_start_col, pal_start_row;
unsigned int pal_width, pal_height;
unsigned char *pal_image; /* palette entry value */
int auto_palette; /* 1 if we lack a palette and must use an heuristic. */
int font_start_level; /* Darkest value used for the computed font */
int spu_changed;
unsigned int forced_subs_only; /* flag: 0=display all subtitle, !0 display only forced subtitles */
unsigned int is_forced_sub; /* true if current subtitle is a forced subtitle */
struct sub_bitmap sub_part, borrowed_sub_part;
struct osd_bmp_indexed borrowed_bmp;
} spudec_handle_t;
static void spudec_queue_packet(spudec_handle_t *this, packet_t *packet)
{
if (this->queue_head == NULL)
this->queue_head = packet;
else
this->queue_tail->next = packet;
this->queue_tail = packet;
}
static packet_t *spudec_dequeue_packet(spudec_handle_t *this)
{
packet_t *retval = this->queue_head;
this->queue_head = retval->next;
if (this->queue_head == NULL)
this->queue_tail = NULL;
return retval;
}
static void spudec_free_packet(packet_t *packet)
{
free(packet->packet);
free(packet);
}
static inline unsigned int get_be16(const unsigned char *p)
{
return (p[0] << 8) + p[1];
}
static inline unsigned int get_be24(const unsigned char *p)
{
return (get_be16(p) << 8) + p[2];
}
static void next_line(packet_t *packet)
{
if (packet->current_nibble[packet->deinterlace_oddness] % 2)
packet->current_nibble[packet->deinterlace_oddness]++;
packet->deinterlace_oddness = (packet->deinterlace_oddness + 1) % 2;
}
static inline unsigned char get_nibble(packet_t *packet)
{
unsigned char nib;
unsigned int *nibblep = packet->current_nibble + packet->deinterlace_oddness;
if (*nibblep / 2 >= packet->control_start) {
mp_msg(MSGT_SPUDEC,MSGL_WARN, "SPUdec: ERROR: get_nibble past end of packet\n");
return 0;
}
nib = packet->packet[*nibblep / 2];
if (*nibblep % 2)
nib &= 0xf;
else
nib >>= 4;
++*nibblep;
return nib;
}
static int spudec_alloc_image(spudec_handle_t *this, int stride, int height)
{
if (this->width > stride) // just a safeguard
this->width = stride;
this->stride = stride;
this->height = height;
if (this->image_size < this->stride * this->height) {
if (this->image != NULL) {
free(this->image);
this->image = NULL;
free(this->pal_image);
this->pal_image = NULL;
this->image_size = 0;
this->pal_width = this->pal_height = 0;
}
this->image = malloc(2 * this->stride * this->height);
if (this->image) {
this->image_size = this->stride * this->height;
// use stride here as well to simplify reallocation checks
this->pal_image = malloc(this->stride * this->height);
}
}
return this->image != NULL;
}
static void setup_palette(spudec_handle_t *spu, uint32_t palette[256])
{
memset(palette, 0, sizeof(*palette) * 256);
struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
csp.int_bits_in = 8;
csp.int_bits_out = 8;
float cmatrix[3][4];
mp_get_yuv2rgb_coeffs(&csp, cmatrix);
for (int i = 0; i < 4; ++i) {
int alpha = spu->alpha[i];
// extend 4 -> 8 bit
alpha |= alpha << 4;
if (spu->custom && (spu->cuspal[i] >> 31) != 0)
alpha = 0;
int color = spu->custom ? spu->cuspal[i] :
spu->global_palette[spu->palette[i]];
int c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
mp_map_int_color(cmatrix, 8, c);
// R and G swapped, possibly due to vobsub_palette_to_yuv()
palette[i] = (alpha << 24u) | (c[2] << 16) | (c[1] << 8) | c[0];
}
}
static void crop_image(struct sub_bitmap *part)
{
if (part->w < 1 || part->h < 1)
return;
struct osd_bmp_indexed *bmp = part->bitmap;
bool invisible[256];
for (int n = 0; n < 256; n++)
invisible[n] = !(bmp->palette[n] >> 24);
int y0 = 0, y1 = part->h, x0 = part->w, x1 = 0;
bool y_all_invisible = true;
for (int y = 0; y < part->h; y++) {
uint8_t *pixels = bmp->bitmap + part->stride * y;
int cur = 0;
while (cur < part->w && invisible[pixels[cur]])
cur++;
int start_visible = cur;
int last_visible = -1;
while (cur < part->w) {
if (!invisible[pixels[cur]])
last_visible = cur;
cur++;
}
x0 = FFMIN(x0, start_visible);
x1 = FFMAX(x1, last_visible);
bool all_invisible = last_visible == -1;
if (all_invisible) {
if (y_all_invisible)
y0 = y;
} else {
y_all_invisible = false;
y1 = y + 1;
}
}
bmp->bitmap += x0 + y0 * part->stride;
part->w = FFMAX(x1 - x0, 0);
part->h = FFMAX(y1 - y0, 0);
part->x += x0;
part->y += y0;
}
static void spudec_process_data(spudec_handle_t *this, packet_t *packet)
{
unsigned int i, x, y;
uint8_t *dst;
if (!spudec_alloc_image(this, packet->stride, packet->height))
return;
this->pal_start_col = packet->start_col;
this->pal_start_row = packet->start_row;
this->pal_height = packet->height;
this->pal_width = packet->width;
this->stride = packet->stride;
memcpy(this->palette, packet->palette, sizeof(this->palette));
memcpy(this->alpha, packet->alpha, sizeof(this->alpha));
i = packet->current_nibble[1];
x = 0;
y = 0;
dst = this->pal_image;
while (packet->current_nibble[0] < i
&& packet->current_nibble[1] / 2 < packet->control_start
&& y < this->pal_height) {
unsigned int len, color;
unsigned int rle = 0;
rle = get_nibble(packet);
if (rle < 0x04) {
if (rle == 0) {
rle = (rle << 4) | get_nibble(packet);
if (rle < 0x04)
rle = (rle << 4) | get_nibble(packet);
}
rle = (rle << 4) | get_nibble(packet);
}
color = 3 - (rle & 0x3);
len = rle >> 2;
x += len;
if (len == 0 || x >= this->pal_width) {
len += this->pal_width - x;
next_line(packet);
x = 0;
++y;
}
memset(dst, color, len);
dst += len;
}
struct sub_bitmap *sub_part = &this->sub_part;
struct osd_bmp_indexed *bmp = &this->borrowed_bmp;
bmp->bitmap = this->pal_image;
setup_palette(this, bmp->palette);
sub_part->bitmap = bmp;
sub_part->stride = this->pal_width;
sub_part->w = this->pal_width;
sub_part->h = this->pal_height;
sub_part->x = this->pal_start_col;
sub_part->y = this->pal_start_row;
crop_image(sub_part);
}
/*
This function tries to create a usable palette.
It determines how many non-transparent colors are used, and assigns different
gray scale values to each color.
I tested it with four streams and even got something readable. Half of the
times I got black characters with white around and half the reverse.
*/
static void compute_palette(spudec_handle_t *this, packet_t *packet)
{
int used[16],i,cused,start,step,color;
memset(used, 0, sizeof(used));
for (i=0; i<4; i++)
if (packet->alpha[i]) /* !Transparent? */
used[packet->palette[i]] = 1;
for (cused=0, i=0; i<16; i++)
if (used[i]) cused++;
if (!cused) return;
if (cused == 1) {
start = 0x80;
step = 0;
} else {
start = 72;
step = (0xF0-start)/(cused-1);
}
memset(used, 0, sizeof(used));
for (i=0; i<4; i++) {
color = packet->palette[i];
if (packet->alpha[i] && !used[color]) { /* not assigned? */
used[color] = 1;
this->global_palette[color] = start<<16;
start += step;
}
}
}
static void spudec_process_control(spudec_handle_t *this, int pts100)
{
int a,b,c,d; /* Temporary vars */
unsigned int date, type;
unsigned int off;
unsigned int start_off = 0;
unsigned int next_off;
unsigned int start_pts = 0;
unsigned int end_pts = 0;
unsigned int current_nibble[2] = {0, 0};
unsigned int control_start;
unsigned int display = 0;
unsigned int start_col = 0;
unsigned int end_col = 0;
unsigned int start_row = 0;
unsigned int end_row = 0;
unsigned int width = 0;
unsigned int height = 0;
unsigned int stride = 0;
control_start = get_be16(this->packet + 2);
next_off = control_start;
while (start_off != next_off) {
start_off = next_off;
date = get_be16(this->packet + start_off) * 1024;
next_off = get_be16(this->packet + start_off + 2);
mp_msg(MSGT_SPUDEC,MSGL_DBG2, "date=%d\n", date);
off = start_off + 4;
for (type = this->packet[off++]; type != 0xff; type = this->packet[off++]) {
mp_msg(MSGT_SPUDEC,MSGL_DBG2, "cmd=%d ",type);
switch(type) {
case 0x00:
/* Menu ID, 1 byte */
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Menu ID\n");
/* shouldn't a Menu ID type force display start? */
start_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date;
end_pts = UINT_MAX;
display = 1;
this->is_forced_sub=~0; // current subtitle is forced
break;
case 0x01:
/* Start display */
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Start display!\n");
start_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date;
end_pts = UINT_MAX;
display = 1;
this->is_forced_sub=0;
break;
case 0x02:
/* Stop display */
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Stop display!\n");
end_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date;
break;
case 0x03:
/* Palette */
this->palette[0] = this->packet[off] >> 4;
this->palette[1] = this->packet[off] & 0xf;
this->palette[2] = this->packet[off + 1] >> 4;
this->palette[3] = this->packet[off + 1] & 0xf;
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Palette %d, %d, %d, %d\n",
this->palette[0], this->palette[1], this->palette[2], this->palette[3]);
off+=2;
break;
case 0x04:
/* Alpha */
a = this->packet[off] >> 4;
b = this->packet[off] & 0xf;
c = this->packet[off + 1] >> 4;
d = this->packet[off + 1] & 0xf;
// Note: some DVDs change these values to create a fade-in/fade-out effect
// We can not handle this, so just keep the highest value during the display time.
if (display) {
a = FFMAX(a, this->alpha[0]);
b = FFMAX(b, this->alpha[1]);
c = FFMAX(c, this->alpha[2]);
d = FFMAX(d, this->alpha[3]);
}
this->alpha[0] = a;
this->alpha[1] = b;
this->alpha[2] = c;
this->alpha[3] = d;
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Alpha %d, %d, %d, %d\n",
this->alpha[0], this->alpha[1], this->alpha[2], this->alpha[3]);
off+=2;
break;
case 0x05:
/* Co-ords */
a = get_be24(this->packet + off);
b = get_be24(this->packet + off + 3);
start_col = a >> 12;
end_col = a & 0xfff;
width = (end_col < start_col) ? 0 : end_col - start_col + 1;
stride = (width + 7) & ~7; /* Kludge: draw_alpha needs width multiple of 8 */
start_row = b >> 12;
end_row = b & 0xfff;
height = (end_row < start_row) ? 0 : end_row - start_row /* + 1 */;
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Coords col: %d - %d row: %d - %d (%dx%d)\n",
start_col, end_col, start_row, end_row,
width, height);
off+=6;
break;
case 0x06:
/* Graphic lines */
current_nibble[0] = 2 * get_be16(this->packet + off);
current_nibble[1] = 2 * get_be16(this->packet + off + 2);
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Graphic offset 1: %d offset 2: %d\n",
current_nibble[0] / 2, current_nibble[1] / 2);
off+=4;
break;
default:
mp_msg(MSGT_SPUDEC,MSGL_WARN,"spudec: Error determining control type 0x%02x. Skipping %d bytes.\n",
type, next_off - off);
goto next_control;
}
}
next_control:
if (!display)
continue;
if (end_pts == UINT_MAX && start_off != next_off) {
end_pts = get_be16(this->packet + next_off) * 1024;
end_pts = 1 - pts100 >= end_pts ? 0 : pts100 + end_pts - 1;
}
if (end_pts > 0) {
packet_t *packet = calloc(1, sizeof(packet_t));
int i;
packet->start_pts = start_pts;
packet->end_pts = end_pts;
packet->current_nibble[0] = current_nibble[0];
packet->current_nibble[1] = current_nibble[1];
packet->start_row = start_row;
packet->start_col = start_col;
packet->width = width;
packet->height = height;
packet->stride = stride;
packet->control_start = control_start;
for (i=0; i<4; i++) {
packet->alpha[i] = this->alpha[i];
packet->palette[i] = this->palette[i];
}
packet->packet = malloc(this->packet_size);
memcpy(packet->packet, this->packet, this->packet_size);
spudec_queue_packet(this, packet);
}
}
}
static void spudec_decode(spudec_handle_t *this, int pts100)
{
spudec_process_control(this, pts100);
}
int spudec_changed(void * this)
{
spudec_handle_t * spu = this;
return spu->spu_changed || spu->now_pts > spu->end_pts;
}
void spudec_assemble(void *this, unsigned char *packet, unsigned int len, int pts100)
{
spudec_handle_t *spu = this;
// spudec_heartbeat(this, pts100);
if (len < 2) {
mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: packet too short\n");
return;
}
spu->packet_pts = pts100;
if (spu->packet_offset == 0) {
unsigned int len2 = get_be16(packet);
// Start new fragment
if (spu->packet_reserve < len2) {
free(spu->packet);
spu->packet = malloc(len2);
spu->packet_reserve = spu->packet != NULL ? len2 : 0;
}
if (spu->packet != NULL) {
spu->packet_size = len2;
if (len > len2) {
mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid frag len / len2: %d / %d \n", len, len2);
return;
}
memcpy(spu->packet, packet, len);
spu->packet_offset = len;
spu->packet_pts = pts100;
}
} else {
// Continue current fragment
if (spu->packet_size < spu->packet_offset + len){
mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid fragment\n");
spu->packet_size = spu->packet_offset = 0;
return;
} else {
memcpy(spu->packet + spu->packet_offset, packet, len);
spu->packet_offset += len;
}
}
#if 1
// check if we have a complete packet (unfortunatelly packet_size is bad
// for some disks)
// [cb] packet_size is padded to be even -> may be one byte too long
if ((spu->packet_offset == spu->packet_size) ||
((spu->packet_offset + 1) == spu->packet_size)){
unsigned int x=0,y;
while(x+4<=spu->packet_offset){
y=get_be16(spu->packet+x+2); // next control pointer
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUtest: x=%d y=%d off=%d size=%d\n",x,y,spu->packet_offset,spu->packet_size);
if(x>=4 && x==y){ // if it points to self - we're done!
// we got it!
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUgot: off=%d size=%d \n",spu->packet_offset,spu->packet_size);
spudec_decode(spu, pts100);
spu->packet_offset = 0;
break;
}
if(y<=x || y>=spu->packet_size){ // invalid?
mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUtest: broken packet!!!!! y=%d < x=%d\n",y,x);
spu->packet_size = spu->packet_offset = 0;
break;
}
x=y;
}
// [cb] packet is done; start new packet
spu->packet_offset = 0;
}
#else
if (spu->packet_offset == spu->packet_size) {
spudec_decode(spu, pts100);
spu->packet_offset = 0;
}
#endif
}
void spudec_reset(void *this) // called after seek
{
spudec_handle_t *spu = this;
while (spu->queue_head)
spudec_free_packet(spudec_dequeue_packet(spu));
spu->now_pts = 0;
spu->end_pts = 0;
spu->packet_size = spu->packet_offset = 0;
}
void spudec_heartbeat(void *this, unsigned int pts100)
{
spudec_handle_t *spu = this;
spu->now_pts = pts100;
// TODO: detect and handle broken timestamps (e.g. due to wrapping)
while (spu->queue_head != NULL && pts100 >= spu->queue_head->start_pts) {
packet_t *packet = spudec_dequeue_packet(spu);
spu->start_pts = packet->start_pts;
spu->end_pts = packet->end_pts;
if (packet->is_decoded) {
free(spu->image);
spu->image_size = packet->data_len;
spu->image = packet->packet;
packet->packet = NULL;
spu->width = packet->width;
spu->height = packet->height;
spu->stride = packet->stride;
spu->start_col = packet->start_col;
spu->start_row = packet->start_row;
} else {
if (spu->auto_palette)
compute_palette(spu, packet);
spudec_process_data(spu, packet);
}
spudec_free_packet(packet);
spu->spu_changed = 1;
}
}
int spudec_visible(void *this){
spudec_handle_t *spu = this;
int ret=(spu->start_pts <= spu->now_pts &&
spu->now_pts < spu->end_pts &&
spu->height > 0);
// printf("spu visible: %d \n",ret);
return ret;
}
void spudec_set_forced_subs_only(void * const this, const unsigned int flag)
{
if(this){
((spudec_handle_t *)this)->forced_subs_only=flag;
mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPU: Display only forced subs now %s\n", flag ? "enabled": "disabled");
}
}
void spudec_get_indexed(void *this, struct mp_osd_res *dim,
struct sub_bitmaps *res)
{
spudec_handle_t *spu = this;
*res = (struct sub_bitmaps) { .format = SUBBITMAP_INDEXED };
struct sub_bitmap *part = &spu->borrowed_sub_part;
res->parts = part;
*part = spu->sub_part;
// Empty subs do happen when cropping
bool empty = part->w < 1 || part->h < 1;
if (spudec_visible(spu) && !empty) {
double xscale = (double) (dim->w - dim->ml - dim->mr) / spu->orig_frame_width;
double yscale = (double) (dim->h - dim->mt - dim->mb) / spu->orig_frame_height;
part->x = part->x * xscale + dim->ml;
part->y = part->y * yscale + dim->mt;
part->dw = part->w * xscale;
part->dh = part->h * yscale;
res->num_parts = 1;
res->scaled = true;
}
if (spu->spu_changed) {
res->bitmap_id = res->bitmap_pos_id = 1;
spu->spu_changed = 0;
}
}
static void spudec_parse_extradata(spudec_handle_t *this,
uint8_t *extradata, int extradata_len)
{
uint8_t *buffer, *ptr;
unsigned int *pal = this->global_palette, *cuspal = this->cuspal;
unsigned int tridx;
int i;
if (extradata_len == 16*4) {
for (i=0; i<16; i++)
pal[i] = AV_RB32(extradata + i*4);
this->auto_palette = 0;
return;
}
if (!(ptr = buffer = malloc(extradata_len+1)))
return;
memcpy(buffer, extradata, extradata_len);
buffer[extradata_len] = 0;
do {
if (*ptr == '#')
continue;
if (!strncmp(ptr, "size: ", 6))
sscanf(ptr + 6, "%dx%d", &this->orig_frame_width, &this->orig_frame_height);
if (!strncmp(ptr, "palette: ", 9) &&
sscanf(ptr + 9, "%x, %x, %x, %x, %x, %x, %x, %x, "
"%x, %x, %x, %x, %x, %x, %x, %x",
&pal[ 0], &pal[ 1], &pal[ 2], &pal[ 3],
&pal[ 4], &pal[ 5], &pal[ 6], &pal[ 7],
&pal[ 8], &pal[ 9], &pal[10], &pal[11],
&pal[12], &pal[13], &pal[14], &pal[15]) == 16) {
for (i=0; i<16; i++)
pal[i] = vobsub_palette_to_yuv(pal[i]);
this->auto_palette = 0;
}
if (!strncasecmp(ptr, "forced subs: on", 15))
this->forced_subs_only = 1;
if (!strncmp(ptr, "custom colors: ON, tridx: ", 26) &&
sscanf(ptr + 26, "%x, colors: %x, %x, %x, %x",
&tridx, cuspal+0, cuspal+1, cuspal+2, cuspal+3) == 5) {
for (i=0; i<4; i++) {
cuspal[i] = vobsub_rgb_to_yuv(cuspal[i]);
if (tridx & (1 << (12-4*i)))
cuspal[i] |= 1 << 31;
}
this->custom = 1;
}
} while ((ptr=strchr(ptr,'\n')) && *++ptr);
free(buffer);
}
void *spudec_new_scaled(unsigned int *palette, unsigned int frame_width, unsigned int frame_height, uint8_t *extradata, int extradata_len)
{
spudec_handle_t *this = calloc(1, sizeof(spudec_handle_t));
if (this){
this->orig_frame_height = frame_height;
this->orig_frame_width = frame_width;
// set up palette:
if (palette)
memcpy(this->global_palette, palette, sizeof(this->global_palette));
else
this->auto_palette = 1;
if (extradata)
spudec_parse_extradata(this, extradata, extradata_len);
/* XXX Although the video frame is some size, the SPU frame is
always maximum size i.e. 720 wide and 576 or 480 high */
// For HD files in MKV the VobSub resolution can be higher though,
// see largeres_vobsub.mkv
if (this->orig_frame_width <= 720 && this->orig_frame_height <= 576) {
this->orig_frame_width = 720;
if (this->orig_frame_height == 480 || this->orig_frame_height == 240)
this->orig_frame_height = 480;
else
this->orig_frame_height = 576;
}
}
else
mp_msg(MSGT_SPUDEC,MSGL_FATAL, "FATAL: spudec_init: calloc");
return this;
}
void *spudec_new(unsigned int *palette)
{
return spudec_new_scaled(palette, 0, 0, NULL, 0);
}
void spudec_free(void *this)
{
spudec_handle_t *spu = this;
if (spu) {
while (spu->queue_head)
spudec_free_packet(spudec_dequeue_packet(spu));
free(spu->packet);
spu->packet = NULL;
free(spu->image);
spu->image = NULL;
free(spu->pal_image);
spu->pal_image = NULL;
spu->image_size = 0;
spu->pal_width = spu->pal_height = 0;
free(spu);
}
}