summaryrefslogtreecommitdiff
path: root/src/libspudec/spu.c
blob: bb8717142ed46a213f7aaa54edd58461a7f05137 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/*****
*
 * Copyright (C) James Courtier-Dutton James@superbug.demon.co.uk - July 2001
 *
 * This file is part of xine
 * This file was originally part of the OMS program.
*
* This program 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, or (at your option)
* any later version.
*
* This program 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 this program; see the file COPYING.  If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*****/

/*
 * spu.c - converts DVD subtitles to an XPM image
 *
 * Mostly based on hard work by:
 *
 * Copyright (C) 2000   Samuel Hocevar <sam@via.ecp.fr>
 *                       and Michel Lespinasse <walken@via.ecp.fr>
 *
 * Lots of rearranging by:
 *	Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 *	Thomas Mirlacher <dent@cosy.sbg.ac.at>
 *		implemented reassembling
 *		cleaner implementation of SPU are saving
 *		overlaying (proof of concept for now)
 *		... and yes, it works now with oms
 *		added tranparency (provided by the SPU hdr) 
 *		changed structures for easy porting to MGAs DVD mode
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *                                                     
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <malloc.h>
#include <sys/stat.h>
#include <fcntl.h>

//#include <oms/plugin/output_video.h>	// for clut_t
#include "spu.h"

static u_int field;		// which field we are currently decoding

#define DISPLAY_INIT

#define REASSEMBLY_START	0
#define REASSEMBLY_MID		1
#define REASSEMBLY_UNNEEDED	2

static u_int reassembly_flag = REASSEMBLY_START;

struct reassembly_s {
  uint8_t *buf;
  uint8_t *buf_ptr;	// actual pointer to still empty buffer
  u_int buf_len;
  u_int cmd_offset;
} reassembly;
	
#define LOG_DEBUG 1

#ifdef DEBUG
#define LOG(lvl, fmt...)	fprintf (stderr, fmt);
#else
#define LOG(lvl, fmt...)
#endif


static u_int _get_bits (u_int bits, vo_overlay_t *spu)
{
  static u_int data;
  static u_int bits_left;
  u_int ret = 0;

  if (!bits) {	/* for realignment to next byte */
    bits_left = 0;
  }

  while (bits) {
    if (bits > bits_left) {
      ret |= data << (bits - bits_left);
      bits -= bits_left;

      data = reassembly.buf[spu->offset[field]++];
      bits_left = 8;
    } else {
      bits_left -= bits;
      ret |= data >> (bits_left);
      data &= (1 << bits_left) - 1;
      bits = 0;
    }
  }

  return ret;	
}


void spuInit (void)
{
}	


static inline void _spu_put_pixel (vo_overlay_t *spu, u_int len, uint8_t colorid)
{
  uint8_t *spu_data_ptr = &spu->data[spu->_x + spu->_y * spu->width];
	
  spu->_x += len;

  memset (spu_data_ptr, spu->trans[colorid]<<4 | colorid, len);
}


static int _spu_next_line (vo_overlay_t *spu)
{
  _get_bits (0, spu); // byte align rle data
	
			   spu->_x = 0;
  spu->_y++;
  field = (field+1) & 0x01; // Toggle fields
	
				 if (spu->_y >= spu->height) {
				   LOG (LOG_DEBUG, ".");
				   return -1;
				 }
  return 0;
}


// DENT: we need a mechanism here, when having non-linearities (like jumps, ff)
     //	like pass NULL pkt_data to reset reassembly

static struct reassembly_s *_reassembly (uint8_t *pkt_data, u_int pkt_len)
{
  LOG (LOG_DEBUG, "pkt_len: %d", pkt_len);

  if (reassembly_flag == REASSEMBLY_UNNEEDED)
    reassembly_flag = REASSEMBLY_START;

  if (reassembly_flag == REASSEMBLY_START) {
    reassembly.buf_len = (((u_int)pkt_data[0])<<8) | pkt_data[1];
    reassembly.cmd_offset = (((u_int)pkt_data[2])<<8) | pkt_data[3];

    LOG (LOG_DEBUG, "buf_len: %d", reassembly.buf_len);
    LOG (LOG_DEBUG, "cmd_off: %d", reassembly.cmd_offset);

    // the whole spu fits into the supplied packet
	 if (pkt_len >= reassembly.buf_len) {
	   LOG (LOG_DEBUG, "0)");
	   reassembly.buf = pkt_data;
	   reassembly_flag = REASSEMBLY_UNNEEDED;
	   return &reassembly;
	 } else {
	   LOG (LOG_DEBUG, "1)");
	   if (!(reassembly.buf = malloc (reassembly.buf_len + 1))) {
	     LOG (LOG_DEBUG, "unable to alloc buffer");
	     return NULL;
	   }
	   reassembly.buf_ptr = reassembly.buf;

	   memcpy (reassembly.buf_ptr, pkt_data, pkt_len);
	   reassembly.buf_ptr += pkt_len;
	   reassembly_flag = REASSEMBLY_MID;
	 }
  } else {
    LOG (LOG_DEBUG, "2)");
    if ((reassembly.buf_ptr+pkt_len) > (reassembly.buf+reassembly.buf_len))
      pkt_len = reassembly.buf_len - (reassembly.buf_ptr - reassembly.buf);


    memcpy (reassembly.buf_ptr, pkt_data, pkt_len);
    reassembly.buf_ptr += pkt_len;

    if (reassembly.buf_ptr >= (reassembly.buf+reassembly.buf_len)) {
      reassembly_flag = REASSEMBLY_START;
      return &reassembly;
    }
  }
	
  LOG (LOG_DEBUG, "3)");
  return NULL;	
}


#define CMD_SPU_MENU		0x00
#define CMD_SPU_SHOW		0x01
#define CMD_SPU_HIDE		0x02
#define CMD_SPU_SET_PALETTE	0x03
#define CMD_SPU_SET_ALPHA	0x04
#define CMD_SPU_SET_SIZE	0x05
#define CMD_SPU_SET_PXD_OFFSET	0x06
#define CMD_SPU_EOF		0xff

/* The time is given as an offset from the presentation time stamp
   and it is measured in number of fields. If we play a NTSC movie
   the time for each field is 1/(2*29.97) seconds. */
#define TIME_UNIT 1000*1.0/(2*29.97)

int spuParseHdr (vo_overlay_t *spu, uint8_t *pkt_data, u_int pkt_len)
{
  struct reassembly_s *reassembly;
  uint8_t *buf; 
  u_int DCSQ_offset, prev_DCSQ_offset = -1;

  if (!(reassembly = _reassembly (pkt_data, pkt_len)))
    return -1;

  buf = reassembly->buf;
  DCSQ_offset = reassembly->cmd_offset;
	
  while (DCSQ_offset != prev_DCSQ_offset) { /* Display Control Sequences */
    u_int i = DCSQ_offset;
		
    spu->duration = /* PTS + */ ((buf[i] << 8) + buf[i+1]) * TIME_UNIT;
    LOG (LOG_DEBUG, "time = %d ms", spu->duration);
    i += 2;
		
    prev_DCSQ_offset = DCSQ_offset;
    DCSQ_offset = (buf[i] << 8) + buf[i+1];
    i += 2;
		
    while (buf[i] != CMD_SPU_EOF) {		/* Command Sequence */
      switch (buf[i]) {
      case CMD_SPU_SHOW:		/* show subpicture */
	LOG (LOG_DEBUG, "\tshow subpicture");
	i++;
	break;
	
      case CMD_SPU_HIDE:		/* hide subpicture */
	LOG (LOG_DEBUG, "\thide subpicture");
	i++;
	break;
	
      case CMD_SPU_SET_PALETTE: {	/* CLUT */
	spu_clut_t *clut = (spu_clut_t *) &buf[i+1];
	
	spu->clut[0] = clut->entry0;
	spu->clut[1] = clut->entry1;
	spu->clut[2] = clut->entry2;
	spu->clut[3] = clut->entry3;
	LOG (LOG_DEBUG, "\tclut [%d %d %d %d]",
	     spu->clut[0], spu->clut[1], spu->clut[2], spu->clut[3]);
	i += 3;
	break;
      }	
      case CMD_SPU_SET_ALPHA:	{	/* transparency palette */
#ifndef DENT_TEST
	spu_clut_t *trans = (spu_clut_t *) &buf[i+1];
	
	spu->trans[3] = trans->entry0;
	spu->trans[2] = trans->entry1;
	spu->trans[1] = trans->entry2;
	spu->trans[0] = trans->entry3;
#else
	spu->trans[0] = 0;
	spu->trans[1] = spu->trans[2] = spu->trans[3] = 15;
#endif
	LOG (LOG_DEBUG, "\ttrans [%d %d %d %d]\n",
	     spu->trans[0], spu->trans[1], spu->trans[2], spu->trans[3]);
	i += 3;
	break;
      }
      
      case CMD_SPU_SET_SIZE:		/* image coordinates */
	spu->x = (buf[i+1] << 4) |
	  (buf[i+2] >> 4);
	spu->width = (((buf[i+2] & 0x0f) << 8) |
		      buf[i+3]) - spu->x + 1; /* 1-720 */
						   
	spu->y = (buf[i+4]  << 4) |
	  (buf[i+5] >> 4);
	spu->height = (((buf[i+5] & 0x0f) << 8)
		       | buf[i+6]) - spu->y + 1; /* 1-576 */
						      
	spu->data = (uint8_t *) malloc (spu->width * spu->height * sizeof (uint8_t));
				/* Private stuff */
	spu->_x = spu->_y = 0;
	LOG (LOG_DEBUG, "\tx = %d y = %d width = %d height = %d",
	     spu->x, spu->y, spu->width, spu->height);
	i += 7;
	break;
	
      case CMD_SPU_SET_PXD_OFFSET:	/* image 1 / image 2 offsets */
	spu->offset[0] = (((u_int)buf[i+1]) << 8) | buf[i+2];
	spu->offset[1] = (((u_int)buf[i+3]) << 8) | buf[i+4];
	LOG (LOG_DEBUG, "\toffset[0] = %d offset[1] = %d",
	     spu->offset[0], spu->offset[1]);
	i += 5;
	break;
	
      case CMD_SPU_MENU:
	/*
	 * hardcoded menu clut, uncomment this and comment CMD_SPU_SET_PALETTE and
	 * CMD_SPU_SET_ALPHA to see the menu buttons
	 */
#ifdef DENT_TEST
	spu->clut[0] = 0;
	spu->clut[1] = 9;
	spu->clut[2] = 8;
	spu->clut[3] = 12;
	spu->trans[0] = 0;
	spu->trans[1] = spu->trans[2] = spu->trans[3] = 15;
#endif
	i++;
	break;
	
      default:
	LOG (LOG_DEBUG, "invalid sequence in control header (%.2x)", buf[i]);
	i++;
	break;
      }
    }
    i++; /* lose the CMD_SPU_EOF code (no need to, really) */
	      
    /* Until we change the interface we parse all 'Command Sequence's 
       but just overwrite the data in spu. Should be a list instead. */
  }
  
  /* Here we should have a linked list of display commands ready to 
     be decoded/executed by later calling some spu???() */

  return 0;
}


void spuParseData (vo_overlay_t *spu)
{
  field = 0;
  _get_bits (0, spu);	/* Reset/init bit code */
	
  while ((spu->offset[1] < reassembly.cmd_offset)) {
    u_int len;
    u_int color;
    u_int vlc;
    
    vlc = _get_bits (4, spu);
    if (vlc < 0x0004) {
      vlc = (vlc << 4) | _get_bits (4, spu);
      if (vlc < 0x0010) {
	vlc = (vlc << 4) | _get_bits (4, spu);
	if (vlc < 0x0040) {
	  vlc = (vlc << 4) | _get_bits (4, spu);
	}
      }
    }
    
    color = vlc & 0x03;
    len = vlc>>2;
    
    /* if len == 0 -> end sequence - fill to end of line */
    len = len ? : spu->width - spu->_x;
    
    _spu_put_pixel (spu, len, color);
    
    if (spu->_x >= spu->width)
      if (_spu_next_line (spu) < 0)
	goto clean_up;
  }
  
  /* Like the eof-line escape, fill the rest of the sp. with background */
  _spu_put_pixel (spu, spu->width - spu->_x, 0);
  while (!_spu_next_line (spu)) {
    _spu_put_pixel (spu, spu->width - spu->_x, 0);
  }

 clean_up:
  if (reassembly_flag != REASSEMBLY_UNNEEDED) {
    LOG (LOG_DEBUG, "freeing reassembly.buf");
    free (reassembly.buf);
  }

  reassembly_flag = REASSEMBLY_START;
}