/src/vlc/modules/codec/spudec/parse.c
Line | Count | Source (jump to first uncovered line) |
1 | | /***************************************************************************** |
2 | | * parse.c: SPU parser |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2000-2001, 2005, 2006 VLC authors and VideoLAN |
5 | | * |
6 | | * Authors: Sam Hocevar <sam@zoy.org> |
7 | | * Laurent Aimar <fenrir@via.ecp.fr> |
8 | | * Gildas Bazin <gbazin@videolan.org> |
9 | | * |
10 | | * This program is free software; you can redistribute it and/or modify it |
11 | | * under the terms of the GNU Lesser General Public License as published by |
12 | | * the Free Software Foundation; either version 2.1 of the License, or |
13 | | * (at your option) any later version. |
14 | | * |
15 | | * This program is distributed in the hope that it will be useful, |
16 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | | * GNU Lesser General Public License for more details. |
19 | | * |
20 | | * You should have received a copy of the GNU Lesser General Public License |
21 | | * along with this program; if not, write to the Free Software Foundation, |
22 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
23 | | *****************************************************************************/ |
24 | | |
25 | | /***************************************************************************** |
26 | | * Preamble |
27 | | *****************************************************************************/ |
28 | | #ifdef HAVE_CONFIG_H |
29 | | # include "config.h" |
30 | | #endif |
31 | | |
32 | | #include <vlc_common.h> |
33 | | #include <vlc_codec.h> |
34 | | |
35 | | #include "spudec.h" |
36 | | |
37 | | /***************************************************************************** |
38 | | * Local prototypes. |
39 | | *****************************************************************************/ |
40 | | typedef struct |
41 | | { |
42 | | int i_width; |
43 | | int i_height; |
44 | | int i_x; |
45 | | int i_y; |
46 | | vlc_tick_t i_start; |
47 | | vlc_tick_t i_stop; |
48 | | bool b_ephemer; |
49 | | bool b_subtitle; |
50 | | } spu_properties_t; |
51 | | |
52 | | typedef struct |
53 | | { |
54 | | int pi_offset[2]; /* byte offsets to data */ |
55 | | |
56 | | /* Color information */ |
57 | | bool b_palette; |
58 | | uint8_t pi_alpha[4]; |
59 | | uint8_t pi_yuv[4][3]; |
60 | | /* PXCTLI commands storage */ |
61 | | const uint8_t *p_pxctli; |
62 | | size_t i_pxclti; |
63 | | |
64 | | /* Auto crop fullscreen subtitles */ |
65 | | bool b_auto_crop; |
66 | | int i_y_top_offset; |
67 | | int i_y_bottom_offset; |
68 | | |
69 | | } subpicture_data_t; |
70 | | |
71 | | static int ParseControlSeq( decoder_t *, vlc_tick_t i_pts, |
72 | | void(*pf_queue)(decoder_t *, subpicture_t *) ); |
73 | | static int ParseRLE ( decoder_t *, subpicture_data_t *, |
74 | | const spu_properties_t *, uint16_t * ); |
75 | | static int Render ( decoder_t *, subpicture_t *, const uint16_t *, |
76 | | const subpicture_data_t *, const spu_properties_t * ); |
77 | | |
78 | | /***************************************************************************** |
79 | | * AddNibble: read a nibble from a source packet and add it to our integer. |
80 | | *****************************************************************************/ |
81 | | static inline unsigned int AddNibble( unsigned int i_code, |
82 | | const uint8_t *p_src, unsigned int *pi_index ) |
83 | 0 | { |
84 | 0 | if( *pi_index & 0x1 ) |
85 | 0 | { |
86 | 0 | return( i_code << 4 | ( p_src[(*pi_index)++ >> 1] & 0xf ) ); |
87 | 0 | } |
88 | 0 | else |
89 | 0 | { |
90 | 0 | return( i_code << 4 | p_src[(*pi_index)++ >> 1] >> 4 ); |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | | static void CLUTIdxToYUV(const struct subs_format_t *subs, |
95 | | const uint8_t idx[4], uint8_t yuv[4][3]) |
96 | 0 | { |
97 | 0 | for( int i = 0; i < 4 ; i++ ) |
98 | 0 | { |
99 | 0 | uint32_t i_ayvu = subs->spu.palette[1+idx[i]]; |
100 | | /* FIXME: this job should be done sooner */ |
101 | 0 | yuv[3-i][0] = i_ayvu>>16; |
102 | 0 | yuv[3-i][1] = i_ayvu; |
103 | 0 | yuv[3-i][2] = i_ayvu>>8; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | static void ParsePXCTLI( decoder_t *p_dec, const subpicture_data_t *p_spu_data, |
108 | | subpicture_t *p_spu ) |
109 | 0 | { |
110 | 0 | plane_t *p_plane = &p_spu->p_region->p_picture->p[0]; |
111 | 0 | video_palette_t *p_palette = p_spu->p_region->fmt.p_palette; |
112 | |
|
113 | 0 | for( size_t i=0;i<p_spu_data->i_pxclti; i++ ) |
114 | 0 | { |
115 | 0 | uint16_t i_col = GetWBE(&p_spu_data->p_pxctli[i*6 + 0]); |
116 | 0 | uint16_t i_color = GetWBE(&p_spu_data->p_pxctli[i*6 + 2]); |
117 | 0 | uint16_t i_contrast = GetWBE(&p_spu_data->p_pxctli[i*6 + 4]); |
118 | |
|
119 | 0 | if(p_palette->i_entries +4 >= VIDEO_PALETTE_COLORS_MAX) |
120 | 0 | break; |
121 | | |
122 | 0 | if( p_dec->fmt_in->subs.spu.palette[0] == SPU_PALETTE_DEFINED ) |
123 | 0 | { |
124 | | /* Lookup the CLUT palette for the YUV values */ |
125 | 0 | uint8_t idx[4]; |
126 | 0 | uint8_t yuv[4][3]; |
127 | 0 | uint8_t alpha[4]; |
128 | 0 | idx[0] = (i_color >> 12)&0x0f; |
129 | 0 | idx[1] = (i_color >> 8)&0x0f; |
130 | 0 | idx[2] = (i_color >> 4)&0x0f; |
131 | 0 | idx[3] = i_color&0x0f; |
132 | 0 | CLUTIdxToYUV( &p_dec->fmt_in->subs, idx, yuv ); |
133 | | |
134 | | /* Process the contrast */ |
135 | 0 | alpha[3] = (i_contrast >> 12)&0x0f; |
136 | 0 | alpha[2] = (i_contrast >> 8)&0x0f; |
137 | 0 | alpha[1] = (i_contrast >> 4)&0x0f; |
138 | 0 | alpha[0] = i_contrast&0x0f; |
139 | | |
140 | | /* Create a new YUVA palette entries for the picture */ |
141 | 0 | int index_map[4]; |
142 | 0 | for( int j=0; j<4; j++ ) |
143 | 0 | { |
144 | 0 | uint8_t yuvaentry[4]; |
145 | 0 | yuvaentry[0] = yuv[j][0]; |
146 | 0 | yuvaentry[1] = yuv[j][1]; |
147 | 0 | yuvaentry[2] = yuv[j][2]; |
148 | 0 | yuvaentry[3] = alpha[j] * 0x11; |
149 | 0 | int i_index = VIDEO_PALETTE_COLORS_MAX; |
150 | 0 | for( int k = p_palette->i_entries; k > 0; k-- ) |
151 | 0 | { |
152 | 0 | if( !memcmp( &p_palette->palette[k], yuvaentry, sizeof(uint8_t [4]) ) ) |
153 | 0 | { |
154 | 0 | i_index = VIDEO_PALETTE_COLORS_MAX; |
155 | 0 | break; |
156 | 0 | } |
157 | 0 | } |
158 | | |
159 | | /* Add an entry in out palette */ |
160 | 0 | if( i_index == VIDEO_PALETTE_COLORS_MAX ) |
161 | 0 | { |
162 | 0 | if(p_palette->i_entries == VIDEO_PALETTE_COLORS_MAX) |
163 | 0 | { |
164 | 0 | msg_Warn( p_dec, "Cannot create new color, skipping PXCTLI" ); |
165 | 0 | return; |
166 | 0 | } |
167 | 0 | i_index = p_palette->i_entries++; |
168 | 0 | memcpy( p_palette->palette[ i_index ], yuvaentry, sizeof(uint8_t [4]) ); |
169 | 0 | } |
170 | 0 | index_map[j] = i_index; |
171 | 0 | } |
172 | | |
173 | 0 | if( p_spu->p_region->i_x >= i_col ) |
174 | 0 | i_col -= p_spu->p_region->i_x; |
175 | |
|
176 | 0 | for( int j=0; j<p_plane->i_visible_lines; j++ ) |
177 | 0 | { |
178 | 0 | uint8_t *p_line = &p_plane->p_pixels[j * p_plane->i_pitch]; |
179 | | /* Extends to end of the line */ |
180 | 0 | for( int k=i_col; k<p_plane->i_visible_pitch; k++ ) |
181 | 0 | { |
182 | 0 | if( p_line[k] < 4 ) /* can forge write-again */ |
183 | 0 | p_line[k] = index_map[ p_line[k] ]; |
184 | 0 | } |
185 | 0 | } |
186 | 0 | } |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | | /***************************************************************************** |
191 | | * OutputPicture: |
192 | | ***************************************************************************** |
193 | | *****************************************************************************/ |
194 | | static void OutputPicture( decoder_t *p_dec, |
195 | | const subpicture_data_t *p_spu_data, |
196 | | const spu_properties_t *p_spu_properties, |
197 | | void(*pf_queue)(decoder_t *, subpicture_t *) ) |
198 | 0 | { |
199 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
200 | 0 | subpicture_t *p_spu; |
201 | 0 | uint16_t *p_pixeldata; |
202 | | |
203 | | /* Allocate the subpicture internal data. */ |
204 | 0 | p_spu = decoder_NewSubpicture( p_dec, NULL ); |
205 | 0 | if( !p_spu ) return; |
206 | | |
207 | 0 | p_spu->i_original_picture_width = |
208 | 0 | p_dec->fmt_in->subs.spu.i_original_frame_width; |
209 | 0 | p_spu->i_original_picture_height = |
210 | 0 | p_dec->fmt_in->subs.spu.i_original_frame_height; |
211 | 0 | p_spu->i_start = p_spu_properties->i_start; |
212 | 0 | p_spu->i_stop = p_spu_properties->i_stop; |
213 | 0 | p_spu->b_ephemer = p_spu_properties->b_ephemer; |
214 | 0 | p_spu->b_subtitle = p_spu_properties->b_subtitle; |
215 | |
|
216 | 0 | if( p_spu->i_stop <= p_spu->i_start && !p_spu->b_ephemer ) |
217 | 0 | { |
218 | | /* This subtitle will live for 5 seconds or until the next subtitle */ |
219 | 0 | p_spu->i_stop = p_spu->i_start + VLC_TICK_FROM_MS(500 * 11); |
220 | 0 | p_spu->b_ephemer = true; |
221 | 0 | } |
222 | | |
223 | | /* we are going to expand the RLE stuff so that we won't need to read |
224 | | * nibbles later on. This will speed things up a lot. Plus, we'll only |
225 | | * need to do this stupid interlacing stuff once. |
226 | | * |
227 | | * Rationale for the "p_spudec->i_rle_size * 4*sizeof(*spu_data.p_data)": |
228 | | * one byte gaves two nibbles and may be used twice (once per field) |
229 | | * generating 4 codes. |
230 | | */ |
231 | 0 | p_pixeldata = vlc_alloc( p_sys->i_rle_size, sizeof(*p_pixeldata) * 2 * 2 ); |
232 | | |
233 | | /* We try to display it */ |
234 | 0 | subpicture_data_t render_spu_data = *p_spu_data; /* Need a copy */ |
235 | 0 | if( ParseRLE( p_dec, &render_spu_data, p_spu_properties, p_pixeldata ) ) |
236 | 0 | { |
237 | | /* There was a parse error, delete the subpicture */ |
238 | 0 | subpicture_Delete( p_spu ); |
239 | 0 | free( p_pixeldata ); |
240 | 0 | return; |
241 | 0 | } |
242 | | |
243 | | #ifdef DEBUG_SPUDEC |
244 | | msg_Dbg( p_dec, "total size: 0x%x, RLE offsets: 0x%x 0x%x", |
245 | | p_sys->i_spu_size, |
246 | | render_spu_data.pi_offset[0], render_spu_data.pi_offset[1] ); |
247 | | #endif |
248 | | |
249 | 0 | if( Render( p_dec, p_spu, p_pixeldata, &render_spu_data, p_spu_properties ) ) |
250 | 0 | { |
251 | 0 | subpicture_Delete( p_spu ); |
252 | 0 | free( p_pixeldata ); |
253 | 0 | return; |
254 | 0 | } |
255 | | |
256 | 0 | free( p_pixeldata ); |
257 | |
|
258 | 0 | if( p_spu_data->p_pxctli && p_spu ) |
259 | 0 | ParsePXCTLI( p_dec, p_spu_data, p_spu ); |
260 | |
|
261 | 0 | pf_queue( p_dec, p_spu ); |
262 | 0 | } |
263 | | |
264 | | static int Validate( decoder_t *p_dec, unsigned i_index, |
265 | | unsigned i_cur_seq, unsigned i_next_seq, |
266 | | const subpicture_data_t *p_spu_data, |
267 | | const spu_properties_t *p_spu_properties ) |
268 | 0 | { |
269 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
270 | | |
271 | | /* Check that the next sequence index matches the current one */ |
272 | 0 | if( i_next_seq < i_cur_seq ) |
273 | 0 | { |
274 | 0 | msg_Err( p_dec, "index mismatch (0x%.4x < 0x%.4x)", |
275 | 0 | i_next_seq, i_cur_seq ); |
276 | 0 | return VLC_EGENERIC; |
277 | 0 | } |
278 | | |
279 | 0 | if( i_index > p_sys->i_spu_size ) |
280 | 0 | { |
281 | 0 | msg_Err( p_dec, "uh-oh, we went too far (0x%.4x > 0x%.4x)", |
282 | 0 | i_index, p_sys->i_spu_size ); |
283 | 0 | return VLC_EGENERIC; |
284 | 0 | } |
285 | | |
286 | 0 | const int i_spu_size = p_sys->i_spu - 4; |
287 | 0 | if( p_spu_data->pi_offset[0] < 0 || p_spu_data->pi_offset[0] >= i_spu_size || |
288 | 0 | p_spu_data->pi_offset[1] < 0 || p_spu_data->pi_offset[1] >= i_spu_size ) |
289 | 0 | { |
290 | 0 | msg_Err( p_dec, "invalid offset values" ); |
291 | 0 | return VLC_EGENERIC; |
292 | 0 | } |
293 | | |
294 | 0 | if( p_spu_properties->i_start == VLC_TICK_INVALID ) |
295 | 0 | { |
296 | 0 | msg_Err( p_dec, "no `start display' command" ); |
297 | 0 | return VLC_EGENERIC; |
298 | 0 | } |
299 | | |
300 | | /* Get rid of padding bytes */ |
301 | 0 | if( i_index > i_next_seq && p_sys->i_spu_size > i_index + 1 ) |
302 | 0 | { |
303 | | /* Zero or one padding byte are quite usual |
304 | | * More than one padding byte - this is very strange, but |
305 | | * we can ignore them. */ |
306 | 0 | msg_Warn( p_dec, "%i padding bytes, we usually get 0 or 1 of them", |
307 | 0 | p_sys->i_spu_size - i_index ); |
308 | 0 | } |
309 | |
|
310 | 0 | return VLC_SUCCESS; |
311 | 0 | } |
312 | | |
313 | | /***************************************************************************** |
314 | | * ParsePacket: parse an SPU packet and send it to the video output |
315 | | ***************************************************************************** |
316 | | * This function parses the SPU packet and, if valid, sends it to the |
317 | | * video output. |
318 | | *****************************************************************************/ |
319 | | void ParsePacket( decoder_t *p_dec, void(*pf_queue)(decoder_t *, subpicture_t *) ) |
320 | 0 | { |
321 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
322 | | |
323 | | /* Getting the control part */ |
324 | 0 | if( ParseControlSeq( p_dec, p_sys->i_pts, pf_queue ) ) |
325 | 0 | return; |
326 | 0 | } |
327 | | |
328 | | /***************************************************************************** |
329 | | * ParseControlSeq: parse all SPU control sequences |
330 | | ***************************************************************************** |
331 | | * This is the most important part in SPU decoding. We get dates, palette |
332 | | * information, coordinates, and so on. For more information on the |
333 | | * subtitles format, see http://sam.zoy.org/doc/dvd/subtitles/index.html |
334 | | *****************************************************************************/ |
335 | | static int ParseControlSeq( decoder_t *p_dec, vlc_tick_t i_pts, |
336 | | void(*pf_queue)(decoder_t *, subpicture_t *) ) |
337 | 0 | { |
338 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
339 | | |
340 | | /* Our current index in the SPU packet */ |
341 | 0 | unsigned int i_index; |
342 | | |
343 | | /* The next start-of-control-sequence index and the previous one */ |
344 | 0 | unsigned int i_next_seq = 0, i_cur_seq = 0; |
345 | | |
346 | | /* Command and date */ |
347 | 0 | uint8_t i_command = SPU_CMD_END; |
348 | 0 | vlc_tick_t date = 0; |
349 | 0 | bool b_cmd_offset = false; |
350 | 0 | bool b_cmd_alpha = false; |
351 | | |
352 | | /* Create working space for spu data */ |
353 | 0 | subpicture_data_t spu_data_cmd; |
354 | 0 | memset( &spu_data_cmd, 0, sizeof(spu_data_cmd) ); |
355 | 0 | spu_data_cmd.pi_offset[0] = -1; |
356 | 0 | spu_data_cmd.pi_offset[1] = -1; |
357 | 0 | spu_data_cmd.b_palette = false; |
358 | 0 | spu_data_cmd.b_auto_crop = false; |
359 | 0 | spu_data_cmd.i_y_top_offset = 0; |
360 | 0 | spu_data_cmd.i_y_bottom_offset = 0; |
361 | 0 | spu_data_cmd.pi_alpha[0] = 0x00; |
362 | 0 | spu_data_cmd.pi_alpha[1] = 0x0f; |
363 | 0 | spu_data_cmd.pi_alpha[2] = 0x0f; |
364 | 0 | spu_data_cmd.pi_alpha[3] = 0x0f; |
365 | |
|
366 | 0 | subpicture_data_t spu_data = spu_data_cmd; |
367 | | |
368 | | /* Initialize the structure */ |
369 | 0 | spu_properties_t spu_properties; |
370 | 0 | memset( &spu_properties, 0, sizeof(spu_properties) ); |
371 | 0 | spu_properties.i_start = VLC_TICK_INVALID; |
372 | 0 | spu_properties.i_stop = VLC_TICK_INVALID; |
373 | 0 | spu_properties.b_subtitle = true; |
374 | |
|
375 | 0 | for( i_index = 4 + p_sys->i_rle_size; i_index < p_sys->i_spu_size ; ) |
376 | 0 | { |
377 | | /* If we just read a command sequence, read the next one; |
378 | | * otherwise, go on with the commands of the current sequence. */ |
379 | 0 | if( i_command == SPU_CMD_END ) |
380 | 0 | { |
381 | 0 | if( i_index + 4 > p_sys->i_spu_size ) |
382 | 0 | { |
383 | 0 | msg_Err( p_dec, "overflow in SPU command sequence" ); |
384 | 0 | return VLC_EGENERIC; |
385 | 0 | } |
386 | | |
387 | | /* */ |
388 | 0 | b_cmd_offset = false; |
389 | 0 | b_cmd_alpha = false; |
390 | | /* Get the control sequence date */ |
391 | 0 | date = VLC_TICK_FROM_MS(GetWBE( &p_sys->buffer[i_index] ) * 11); |
392 | | |
393 | | /* Next offset */ |
394 | 0 | i_cur_seq = i_index; |
395 | 0 | i_next_seq = GetWBE( &p_sys->buffer[i_index+2] ); |
396 | |
|
397 | 0 | if( i_next_seq > p_sys->i_spu_size ) |
398 | 0 | { |
399 | 0 | msg_Err( p_dec, "overflow in SPU next command sequence" ); |
400 | 0 | return VLC_EGENERIC; |
401 | 0 | } |
402 | | |
403 | | /* Skip what we just read */ |
404 | 0 | i_index += 4; |
405 | 0 | } |
406 | | |
407 | 0 | i_command = p_sys->buffer[i_index]; |
408 | |
|
409 | 0 | switch( i_command ) |
410 | 0 | { |
411 | 0 | case SPU_CMD_FORCE_DISPLAY: /* 00 (force displaying) */ |
412 | 0 | spu_properties.i_start = i_pts + date; |
413 | 0 | spu_properties.b_ephemer = true; |
414 | | /* ignores picture date as display start time |
415 | | * works around non displayable (offset by few ms) |
416 | | * spu menu over still frame in SPU_Select */ |
417 | 0 | spu_properties.b_subtitle = false; |
418 | 0 | i_index += 1; |
419 | 0 | break; |
420 | | |
421 | | /* Convert the dates in seconds to PTS values */ |
422 | 0 | case SPU_CMD_START_DISPLAY: /* 01 (start displaying) */ |
423 | 0 | spu_properties.i_start = i_pts + date; |
424 | 0 | i_index += 1; |
425 | 0 | break; |
426 | | |
427 | 0 | case SPU_CMD_STOP_DISPLAY: /* 02 (stop displaying) */ |
428 | 0 | spu_properties.i_stop = i_pts + date; |
429 | 0 | i_index += 1; |
430 | 0 | break; |
431 | | |
432 | 0 | case SPU_CMD_SET_PALETTE: |
433 | | /* 03xxxx (palette) */ |
434 | 0 | if( i_index + 3 > p_sys->i_spu_size ) |
435 | 0 | { |
436 | 0 | msg_Err( p_dec, "overflow in SPU command" ); |
437 | 0 | return VLC_EGENERIC; |
438 | 0 | } |
439 | | |
440 | 0 | if( p_dec->fmt_in->subs.spu.palette[0] == SPU_PALETTE_DEFINED ) |
441 | 0 | { |
442 | 0 | uint8_t idx[4]; |
443 | |
|
444 | 0 | spu_data_cmd.b_palette = true; |
445 | |
|
446 | 0 | idx[0] = (p_sys->buffer[i_index+1]>>4)&0x0f; |
447 | 0 | idx[1] = (p_sys->buffer[i_index+1])&0x0f; |
448 | 0 | idx[2] = (p_sys->buffer[i_index+2]>>4)&0x0f; |
449 | 0 | idx[3] = (p_sys->buffer[i_index+2])&0x0f; |
450 | |
|
451 | 0 | CLUTIdxToYUV( &p_dec->fmt_in->subs, idx, spu_data_cmd.pi_yuv ); |
452 | 0 | } |
453 | |
|
454 | 0 | i_index += 3; |
455 | 0 | break; |
456 | | |
457 | 0 | case SPU_CMD_SET_ALPHACHANNEL: /* 04xxxx (alpha channel) */ |
458 | 0 | if( i_index + 3 > p_sys->i_spu_size ) |
459 | 0 | { |
460 | 0 | msg_Err( p_dec, "overflow in SPU command" ); |
461 | 0 | return VLC_EGENERIC; |
462 | 0 | } |
463 | | |
464 | 0 | if(!p_sys->b_disabletrans) |
465 | 0 | { /* If we want to use original transparency values */ |
466 | 0 | b_cmd_alpha = true; |
467 | 0 | spu_data_cmd.pi_alpha[3] = (p_sys->buffer[i_index+1]>>4)&0x0f; |
468 | 0 | spu_data_cmd.pi_alpha[2] = (p_sys->buffer[i_index+1])&0x0f; |
469 | 0 | spu_data_cmd.pi_alpha[1] = (p_sys->buffer[i_index+2]>>4)&0x0f; |
470 | 0 | spu_data_cmd.pi_alpha[0] = (p_sys->buffer[i_index+2])&0x0f; |
471 | 0 | } |
472 | |
|
473 | 0 | i_index += 3; |
474 | 0 | break; |
475 | | |
476 | 0 | case SPU_CMD_SET_COORDINATES: /* 05xxxyyyxxxyyy (coordinates) */ |
477 | 0 | if( i_index + 7 > p_sys->i_spu_size ) |
478 | 0 | { |
479 | 0 | msg_Err( p_dec, "overflow in SPU command" ); |
480 | 0 | return VLC_EGENERIC; |
481 | 0 | } |
482 | | |
483 | 0 | spu_properties.i_x = (p_sys->buffer[i_index+1]<<4)| |
484 | 0 | ((p_sys->buffer[i_index+2]>>4)&0x0f); |
485 | 0 | spu_properties.i_width = (((p_sys->buffer[i_index+2]&0x0f)<<8)| |
486 | 0 | p_sys->buffer[i_index+3]) - spu_properties.i_x + 1; |
487 | |
|
488 | 0 | spu_properties.i_y = (p_sys->buffer[i_index+4]<<4)| |
489 | 0 | ((p_sys->buffer[i_index+5]>>4)&0x0f); |
490 | 0 | spu_properties.i_height = (((p_sys->buffer[i_index+5]&0x0f)<<8)| |
491 | 0 | p_sys->buffer[i_index+6]) - spu_properties.i_y + 1; |
492 | 0 | if (spu_properties.i_width < 0 || spu_properties.i_height < 0) { |
493 | 0 | msg_Err( p_dec, "integer overflow in SPU command" ); |
494 | 0 | return VLC_EGENERIC; |
495 | 0 | } |
496 | | |
497 | | /* Auto crop fullscreen subtitles */ |
498 | 0 | if( spu_properties.i_height > 250 ) |
499 | 0 | spu_data.b_auto_crop = true; |
500 | |
|
501 | 0 | i_index += 7; |
502 | 0 | break; |
503 | | |
504 | 0 | case SPU_CMD_SET_OFFSETS: /* 06xxxxyyyy (byte offsets) */ |
505 | 0 | if( i_index + 5 > p_sys->i_spu_size ) |
506 | 0 | { |
507 | 0 | msg_Err( p_dec, "overflow in SPU command" ); |
508 | 0 | return VLC_EGENERIC; |
509 | 0 | } |
510 | | |
511 | 0 | b_cmd_offset = true; |
512 | 0 | spu_data.pi_offset[0] = GetWBE(&p_sys->buffer[i_index+1]) - 4; |
513 | 0 | spu_data.pi_offset[1] = GetWBE(&p_sys->buffer[i_index+3]) - 4; |
514 | 0 | i_index += 5; |
515 | 0 | break; |
516 | | |
517 | 0 | case SPU_CMD_SET_COLCON: |
518 | 0 | if( i_index + 7 > p_sys->i_spu_size ) |
519 | 0 | { |
520 | 0 | msg_Err( p_dec, "overflow in SPU command" ); |
521 | 0 | return VLC_EGENERIC; |
522 | 0 | } |
523 | | |
524 | 0 | spu_properties.i_start = i_pts + date; |
525 | |
|
526 | 0 | if( p_sys->i_spu_size > |
527 | 0 | i_index + 3 + 4 + (p_sys->buffer[i_index+5] >> 4) ) |
528 | 0 | { |
529 | 0 | spu_data.p_pxctli = &p_sys->buffer[i_index+3 + 4]; |
530 | 0 | spu_data.i_pxclti = p_sys->buffer[i_index+5] >> 4; |
531 | 0 | } |
532 | |
|
533 | 0 | i_index += 1 + GetWBE(&p_sys->buffer[i_index+1]); |
534 | 0 | break; |
535 | | |
536 | 0 | case SPU_CMD_END: /* ff (end) */ |
537 | |
|
538 | 0 | if( b_cmd_offset ) |
539 | 0 | { |
540 | | /* It seems that palette and alpha from the block having |
541 | | * the cmd offset have to be used |
542 | | * XXX is it all ? */ |
543 | 0 | spu_data.b_palette = spu_data_cmd.b_palette; |
544 | 0 | if( spu_data_cmd.b_palette ) |
545 | 0 | memcpy( spu_data.pi_yuv, spu_data_cmd.pi_yuv, sizeof(spu_data_cmd.pi_yuv) ); |
546 | 0 | if( b_cmd_alpha ) |
547 | 0 | memcpy( spu_data.pi_alpha, spu_data_cmd.pi_alpha, sizeof(spu_data_cmd.pi_alpha) ); |
548 | 0 | } |
549 | |
|
550 | 0 | i_index += 1; |
551 | |
|
552 | 0 | if( Validate( p_dec, i_index, i_cur_seq, i_next_seq, |
553 | 0 | &spu_data, &spu_properties ) == VLC_SUCCESS ) |
554 | 0 | OutputPicture( p_dec, &spu_data, &spu_properties, pf_queue ); |
555 | |
|
556 | 0 | break; |
557 | | |
558 | 0 | default: /* xx (unknown command) */ |
559 | 0 | msg_Warn( p_dec, "unknown SPU command 0x%.2x", i_command ); |
560 | 0 | if( i_index + 1 < i_next_seq ) |
561 | 0 | { |
562 | | /* There is at least one other command sequence */ |
563 | 0 | if( p_sys->buffer[i_next_seq - 1] == SPU_CMD_END ) |
564 | 0 | { |
565 | | /* This is consistent. Skip to that command sequence. */ |
566 | 0 | i_index = i_next_seq; |
567 | 0 | } |
568 | 0 | else |
569 | 0 | { |
570 | | /* There were other commands. */ |
571 | 0 | msg_Warn( p_dec, "cannot recover, dropping subtitle" ); |
572 | 0 | return VLC_EGENERIC; |
573 | 0 | } |
574 | 0 | } |
575 | 0 | else |
576 | 0 | { |
577 | | /* We were in the last command sequence. Stop parsing by |
578 | | * pretending we met an SPU_CMD_END command. */ |
579 | 0 | i_command = SPU_CMD_END; |
580 | 0 | i_index++; |
581 | 0 | } |
582 | 0 | } |
583 | | |
584 | | /* */ |
585 | 0 | if( i_command == SPU_CMD_END && i_index != i_next_seq ) |
586 | 0 | break; |
587 | 0 | } |
588 | | |
589 | | /* Successfully parsed ! */ |
590 | | |
591 | 0 | return VLC_SUCCESS; |
592 | 0 | } |
593 | | |
594 | | /***************************************************************************** |
595 | | * ParseRLE: parse the RLE part of the subtitle |
596 | | ***************************************************************************** |
597 | | * This part parses the subtitle graphical data and stores it in a more |
598 | | * convenient structure for later decoding. For more information on the |
599 | | * subtitles format, see http://sam.zoy.org/doc/dvd/subtitles/index.html |
600 | | *****************************************************************************/ |
601 | | static int ParseRLE( decoder_t *p_dec, |
602 | | subpicture_data_t *p_spu_data, |
603 | | const spu_properties_t *p_spu_properties, |
604 | | uint16_t *p_pixeldata ) |
605 | 0 | { |
606 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
607 | |
|
608 | 0 | const unsigned int i_width = p_spu_properties->i_width; |
609 | 0 | const unsigned int i_height = p_spu_properties->i_height; |
610 | 0 | unsigned int i_x, i_y; |
611 | |
|
612 | 0 | uint16_t *p_dest = p_pixeldata; |
613 | | |
614 | | /* The subtitles are interlaced, we need two offsets */ |
615 | 0 | unsigned int i_id = 0; /* Start on the even SPU layer */ |
616 | 0 | unsigned int pi_table[ 2 ]; |
617 | 0 | unsigned int *pi_offset; |
618 | | |
619 | | /* Cropping */ |
620 | 0 | bool b_empty_top = true; |
621 | 0 | unsigned int i_skipped_top = 0, i_skipped_bottom = 0; |
622 | 0 | unsigned int i_transparent_code = 0; |
623 | | |
624 | | /* Colormap statistics */ |
625 | 0 | int i_border = -1; |
626 | 0 | int stats[4]; stats[0] = stats[1] = stats[2] = stats[3] = 0; |
627 | |
|
628 | 0 | pi_table[ 0 ] = p_spu_data->pi_offset[ 0 ] << 1; |
629 | 0 | pi_table[ 1 ] = p_spu_data->pi_offset[ 1 ] << 1; |
630 | |
|
631 | 0 | for( i_y = 0 ; i_y < i_height ; i_y++ ) |
632 | 0 | { |
633 | 0 | unsigned int i_code; |
634 | 0 | pi_offset = pi_table + i_id; |
635 | |
|
636 | 0 | for( i_x = 0 ; i_x < i_width ; i_x += i_code >> 2 ) |
637 | 0 | { |
638 | 0 | i_code = 0; |
639 | 0 | for( unsigned int i_min = 1; i_min <= 0x40 && i_code < i_min; i_min <<= 2 ) |
640 | 0 | { |
641 | 0 | if( (*pi_offset >> 1) >= p_sys->i_spu_size ) |
642 | 0 | { |
643 | 0 | msg_Err( p_dec, "out of bounds while reading rle" ); |
644 | 0 | return VLC_EGENERIC; |
645 | 0 | } |
646 | 0 | i_code = AddNibble( i_code, &p_sys->buffer[4], pi_offset ); |
647 | 0 | } |
648 | 0 | if( i_code < 0x0004 ) |
649 | 0 | { |
650 | | /* If the 14 first bits are set to 0, then it's a |
651 | | * new line. We emulate it. */ |
652 | 0 | i_code |= ( i_width - i_x ) << 2; |
653 | 0 | } |
654 | |
|
655 | 0 | if( ( (i_code >> 2) + i_x + i_y * i_width ) > i_height * i_width ) |
656 | 0 | { |
657 | 0 | msg_Err( p_dec, "out of bounds, %i at (%i,%i) is out of %ix%i", |
658 | 0 | i_code >> 2, i_x, i_y, i_width, i_height ); |
659 | 0 | return VLC_EGENERIC; |
660 | 0 | } |
661 | | |
662 | | /* Try to find the border color */ |
663 | 0 | if( p_spu_data->pi_alpha[ i_code & 0x3 ] != 0x00 ) |
664 | 0 | { |
665 | 0 | i_border = i_code & 0x3; |
666 | 0 | stats[i_border] += i_code >> 2; |
667 | 0 | } |
668 | | |
669 | | /* Auto crop subtitles (a lot more optimized) */ |
670 | 0 | if( p_spu_data->b_auto_crop ) |
671 | 0 | { |
672 | 0 | if( !i_y ) |
673 | 0 | { |
674 | | /* We assume that if the first line is transparent, then |
675 | | * it is using the palette index for the |
676 | | * (background) transparent color */ |
677 | 0 | if( (i_code >> 2) == i_width && |
678 | 0 | p_spu_data->pi_alpha[ i_code & 0x3 ] == 0x00 ) |
679 | 0 | { |
680 | 0 | i_transparent_code = i_code; |
681 | 0 | } |
682 | 0 | else |
683 | 0 | { |
684 | 0 | p_spu_data->b_auto_crop = false; |
685 | 0 | } |
686 | 0 | } |
687 | |
|
688 | 0 | if( i_code == i_transparent_code ) |
689 | 0 | { |
690 | 0 | if( b_empty_top ) |
691 | 0 | { |
692 | | /* This is a blank top line, we skip it */ |
693 | 0 | i_skipped_top++; |
694 | 0 | } |
695 | 0 | else |
696 | 0 | { |
697 | | /* We can't be sure the current lines will be skipped, |
698 | | * so we store the code just in case. */ |
699 | 0 | *p_dest++ = i_code; |
700 | 0 | i_skipped_bottom++; |
701 | 0 | } |
702 | 0 | } |
703 | 0 | else |
704 | 0 | { |
705 | | /* We got a valid code, store it */ |
706 | 0 | *p_dest++ = i_code; |
707 | | |
708 | | /* Valid code means no blank line */ |
709 | 0 | b_empty_top = false; |
710 | 0 | i_skipped_bottom = 0; |
711 | 0 | } |
712 | 0 | } |
713 | 0 | else |
714 | 0 | { |
715 | 0 | *p_dest++ = i_code; |
716 | 0 | } |
717 | 0 | } |
718 | | |
719 | | /* Check that we didn't go too far */ |
720 | 0 | if( i_x > i_width ) |
721 | 0 | { |
722 | 0 | msg_Err( p_dec, "i_x overflowed, %i > %i", i_x, i_width ); |
723 | 0 | return VLC_EGENERIC; |
724 | 0 | } |
725 | | |
726 | | /* Byte-align the stream */ |
727 | 0 | if( *pi_offset & 0x1 ) |
728 | 0 | { |
729 | 0 | (*pi_offset)++; |
730 | 0 | } |
731 | | |
732 | | /* Swap fields */ |
733 | 0 | i_id = ~i_id & 0x1; |
734 | 0 | } |
735 | | |
736 | | /* We shouldn't get any padding bytes */ |
737 | 0 | if( i_y < i_height ) |
738 | 0 | { |
739 | 0 | msg_Err( p_dec, "padding bytes found in RLE sequence" ); |
740 | 0 | msg_Err( p_dec, "send mail to <sam@zoy.org> if you " |
741 | 0 | "want to help debugging this" ); |
742 | | |
743 | | /* Skip them just in case */ |
744 | 0 | while( i_y < i_height ) |
745 | 0 | { |
746 | 0 | *p_dest++ = i_width << 2; |
747 | 0 | i_y++; |
748 | 0 | } |
749 | |
|
750 | 0 | return VLC_EGENERIC; |
751 | 0 | } |
752 | | |
753 | | #ifdef DEBUG_SPUDEC |
754 | | msg_Dbg( p_dec, "valid subtitle, size: %ix%i, position: %i,%i", |
755 | | p_spu_properties->i_width, p_spu_properties->i_height, |
756 | | p_spu_properties->i_x, p_spu_properties->i_y ); |
757 | | #endif |
758 | | |
759 | | /* Crop if necessary */ |
760 | 0 | if( i_skipped_top || i_skipped_bottom ) |
761 | 0 | { |
762 | 0 | p_spu_data->i_y_top_offset = i_skipped_top; |
763 | 0 | p_spu_data->i_y_bottom_offset = i_skipped_bottom; |
764 | | #ifdef DEBUG_SPUDEC |
765 | | msg_Dbg( p_dec, "cropped to: %ix%i, position: %i,%i", |
766 | | p_spu_properties->i_width, |
767 | | p_spu_properties->i_height - (i_skipped_top + i_skipped_bottom), |
768 | | p_spu_properties->i_x, p_spu_properties->i_y + i_skipped_top ); |
769 | | #endif |
770 | 0 | } |
771 | | |
772 | | /* Handle color if no palette was found */ |
773 | 0 | if( !p_spu_data->b_palette ) |
774 | 0 | { |
775 | 0 | int i, i_inner = -1, i_shade = -1; |
776 | | |
777 | | /* Set the border color */ |
778 | 0 | if( i_border != -1 ) |
779 | 0 | { |
780 | 0 | p_spu_data->pi_yuv[i_border][0] = 0x00; |
781 | 0 | p_spu_data->pi_yuv[i_border][1] = 0x80; |
782 | 0 | p_spu_data->pi_yuv[i_border][2] = 0x80; |
783 | 0 | stats[i_border] = 0; |
784 | 0 | } |
785 | | |
786 | | /* Find the inner colors */ |
787 | 0 | for( i = 0 ; i < 4 && i_inner == -1 ; i++ ) |
788 | 0 | { |
789 | 0 | if( stats[i] ) |
790 | 0 | { |
791 | 0 | i_inner = i; |
792 | 0 | } |
793 | 0 | } |
794 | |
|
795 | 0 | for( ; i < 4 && i_shade == -1 ; i++ ) |
796 | 0 | { |
797 | 0 | if( stats[i] ) |
798 | 0 | { |
799 | 0 | if( stats[i] > stats[i_inner] ) |
800 | 0 | { |
801 | 0 | i_shade = i_inner; |
802 | 0 | i_inner = i; |
803 | 0 | } |
804 | 0 | else |
805 | 0 | { |
806 | 0 | i_shade = i; |
807 | 0 | } |
808 | 0 | } |
809 | 0 | } |
810 | | |
811 | | /* Set the inner color */ |
812 | 0 | if( i_inner != -1 ) |
813 | 0 | { |
814 | 0 | p_spu_data->pi_yuv[i_inner][0] = 0xff; |
815 | 0 | p_spu_data->pi_yuv[i_inner][1] = 0x80; |
816 | 0 | p_spu_data->pi_yuv[i_inner][2] = 0x80; |
817 | 0 | } |
818 | | |
819 | | /* Set the anti-aliasing color */ |
820 | 0 | if( i_shade != -1 ) |
821 | 0 | { |
822 | 0 | p_spu_data->pi_yuv[i_shade][0] = 0x80; |
823 | 0 | p_spu_data->pi_yuv[i_shade][1] = 0x80; |
824 | 0 | p_spu_data->pi_yuv[i_shade][2] = 0x80; |
825 | 0 | } |
826 | |
|
827 | | #ifdef DEBUG_SPUDEC |
828 | | msg_Dbg( p_dec, "using custom palette (border %i, inner %i, shade %i)", |
829 | | i_border, i_inner, i_shade ); |
830 | | #endif |
831 | 0 | } |
832 | |
|
833 | 0 | return VLC_SUCCESS; |
834 | 0 | } |
835 | | |
836 | | static int Render( decoder_t *p_dec, subpicture_t *p_spu, |
837 | | const uint16_t *p_pixeldata, |
838 | | const subpicture_data_t *p_spu_data, |
839 | | const spu_properties_t *p_spu_properties ) |
840 | 0 | { |
841 | 0 | uint8_t *p_p; |
842 | 0 | int i_x, i_y, i_len, i_color, i_pitch; |
843 | 0 | const uint16_t *p_source = p_pixeldata; |
844 | 0 | video_format_t fmt; |
845 | 0 | video_palette_t palette; |
846 | | |
847 | | /* Create a new subpicture region */ |
848 | 0 | video_format_Init( &fmt, VLC_CODEC_YUVP ); |
849 | 0 | fmt.i_sar_num = 0; /* 0 means use aspect ratio of background video */ |
850 | 0 | fmt.i_sar_den = 1; |
851 | 0 | fmt.i_width = fmt.i_visible_width = p_spu_properties->i_width; |
852 | 0 | fmt.i_height = fmt.i_visible_height = p_spu_properties->i_height - |
853 | 0 | p_spu_data->i_y_top_offset - p_spu_data->i_y_bottom_offset; |
854 | 0 | fmt.i_x_offset = fmt.i_y_offset = 0; |
855 | 0 | fmt.p_palette = &palette; |
856 | 0 | fmt.p_palette->i_entries = 4; |
857 | 0 | for( i_x = 0; i_x < fmt.p_palette->i_entries; i_x++ ) |
858 | 0 | { |
859 | 0 | fmt.p_palette->palette[i_x][0] = p_spu_data->pi_yuv[i_x][0]; |
860 | 0 | fmt.p_palette->palette[i_x][1] = p_spu_data->pi_yuv[i_x][1]; |
861 | 0 | fmt.p_palette->palette[i_x][2] = p_spu_data->pi_yuv[i_x][2]; |
862 | 0 | fmt.p_palette->palette[i_x][3] = p_spu_data->pi_alpha[i_x] * 0x11; |
863 | 0 | } |
864 | |
|
865 | 0 | p_spu->p_region = subpicture_region_New( &fmt ); |
866 | 0 | if( !p_spu->p_region ) |
867 | 0 | { |
868 | 0 | fmt.p_palette = NULL; |
869 | 0 | video_format_Clean( &fmt ); |
870 | 0 | msg_Err( p_dec, "cannot allocate SPU region" ); |
871 | 0 | return VLC_EGENERIC; |
872 | 0 | } |
873 | | |
874 | 0 | p_spu->p_region->i_x = p_spu_properties->i_x; |
875 | 0 | p_spu->p_region->i_y = p_spu_properties->i_y + p_spu_data->i_y_top_offset; |
876 | 0 | p_p = p_spu->p_region->p_picture->p->p_pixels; |
877 | 0 | i_pitch = p_spu->p_region->p_picture->p->i_pitch; |
878 | | |
879 | | /* Draw until we reach the bottom of the subtitle */ |
880 | 0 | for( i_y = 0; i_y < (int)fmt.i_height * i_pitch; i_y += i_pitch ) |
881 | 0 | { |
882 | | /* Draw until we reach the end of the line */ |
883 | 0 | for( i_x = 0 ; i_x < (int)fmt.i_width; i_x += i_len ) |
884 | 0 | { |
885 | | /* Get the RLE part, then draw the line */ |
886 | 0 | i_color = *p_source & 0x3; |
887 | 0 | i_len = *p_source++ >> 2; |
888 | 0 | memset( p_p + i_x + i_y, i_color, i_len ); |
889 | 0 | } |
890 | 0 | } |
891 | |
|
892 | 0 | fmt.p_palette = NULL; |
893 | 0 | video_format_Clean( &fmt ); |
894 | |
|
895 | 0 | return VLC_SUCCESS; |
896 | 0 | } |