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