/src/vlc/modules/codec/stl.c
Line | Count | Source |
1 | | /***************************************************************************** |
2 | | * stl.c: EBU STL decoder |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2010 Laurent Aimar |
5 | | * |
6 | | * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org> |
7 | | * |
8 | | * This program is free software; you can redistribute it and/or modify it |
9 | | * under the terms of the GNU Lesser General Public License as published by |
10 | | * the Free Software Foundation; either version 2.1 of the License, or |
11 | | * (at your option) any later version. |
12 | | * |
13 | | * This program is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU Lesser General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public License |
19 | | * along with this program; if not, write to the Free Software Foundation, |
20 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
21 | | *****************************************************************************/ |
22 | | |
23 | | /***************************************************************************** |
24 | | * Preamble |
25 | | *****************************************************************************/ |
26 | | #ifdef HAVE_CONFIG_H |
27 | | # include "config.h" |
28 | | #endif |
29 | | #include <assert.h> |
30 | | |
31 | | #include <vlc_common.h> |
32 | | #include <vlc_plugin.h> |
33 | | #include <vlc_codec.h> |
34 | | #include <vlc_charset.h> |
35 | | |
36 | | #include "substext.h" /* required for font scaling / updater */ |
37 | | |
38 | | /***************************************************************************** |
39 | | * Module descriptor |
40 | | *****************************************************************************/ |
41 | | static int Open (vlc_object_t *); |
42 | | static void Close(vlc_object_t *); |
43 | | |
44 | 108 | vlc_module_begin() |
45 | 54 | set_description(N_("EBU STL subtitles decoder")) |
46 | 54 | set_subcategory(SUBCAT_INPUT_SCODEC) |
47 | 54 | set_capability("spu decoder", 10) |
48 | 108 | set_callbacks(Open, Close) |
49 | 54 | vlc_module_end() |
50 | | |
51 | | /***************************************************************************** |
52 | | * Local definitions/prototypes |
53 | | *****************************************************************************/ |
54 | 0 | #define GSI_BLOCK_SIZE 1024 |
55 | | |
56 | 0 | #define STL_GROUPS_MAX 255 |
57 | | |
58 | 0 | #define STL_TEXTFIELD_SIZE 112 |
59 | 0 | #define STL_TTI_HEADER_SIZE 16 |
60 | 0 | #define STL_TTI_SIZE (STL_TTI_HEADER_SIZE + STL_TEXTFIELD_SIZE) |
61 | | |
62 | | #define STL_TF_TELETEXT_FIRST 0x00 |
63 | 0 | #define STL_TF_TELETEXT_LAST 0x1f |
64 | 0 | #define STL_TF_CHARCODE1_FIRST 0x20 |
65 | 0 | #define STL_TF_CHARCODE1_LAST 0x7f |
66 | 0 | #define STL_TF_ITALICS_ON 0x80 |
67 | 0 | #define STL_TF_ITALICS_OFF 0x81 |
68 | 0 | #define STL_TF_UNDERLINE_ON 0x82 |
69 | 0 | #define STL_TF_UNDERLINE_OFF 0x83 |
70 | 0 | #define STL_TF_BOXING_ON 0x84 |
71 | 0 | #define STL_TF_BOXING_OFF 0x85 |
72 | 0 | #define STL_TF_LINEBREAK 0x8a |
73 | 0 | #define STL_TF_END_FILL 0x8f |
74 | 0 | #define STL_TF_CHARCODE2_FIRST 0xa1 |
75 | | |
76 | | typedef enum { |
77 | | CCT_ISO_6937_2 = 0x3030, CCT_BEGIN = CCT_ISO_6937_2, |
78 | | CCT_ISO_8859_5 = 0x3031, |
79 | | CCT_ISO_8859_6 = 0x3032, |
80 | | CCT_ISO_8859_7 = 0x3033, |
81 | | CCT_ISO_8859_8 = 0x3034, CCT_END = CCT_ISO_8859_8 |
82 | | } cct_number_value_t; |
83 | | |
84 | | typedef struct |
85 | | { |
86 | | uint8_t i_accumulating; |
87 | | uint8_t i_justify; |
88 | | vlc_tick_t i_start; |
89 | | vlc_tick_t i_end; |
90 | | text_style_t *p_style; |
91 | | text_segment_t *p_segment; |
92 | | text_segment_t **pp_segment_last; |
93 | | } stl_sg_t; |
94 | | |
95 | | typedef struct { |
96 | | cct_number_value_t value; |
97 | | const char *str; |
98 | | } cct_number_t; |
99 | | |
100 | | typedef struct |
101 | | { |
102 | | stl_sg_t groups[STL_GROUPS_MAX + 1]; |
103 | | cct_number_value_t cct; |
104 | | uint8_t i_fps; |
105 | | } decoder_sys_t; |
106 | | |
107 | | static cct_number_t cct_nums[] = { {CCT_ISO_6937_2, "ISO_6937-2"}, |
108 | | {CCT_ISO_8859_5, "ISO_8859-5"}, |
109 | | {CCT_ISO_8859_6, "ISO_8859-6"}, |
110 | | {CCT_ISO_8859_7, "ISO_8859-7"}, |
111 | | {CCT_ISO_8859_8, "ISO_8859-8"} }; |
112 | | |
113 | | static text_style_t * CreateGroupStyle(void) |
114 | 0 | { |
115 | 0 | text_style_t *p_style = text_style_Create(STYLE_NO_DEFAULTS); |
116 | 0 | if(p_style) |
117 | 0 | { |
118 | 0 | p_style->i_features = STYLE_HAS_FLAGS|STYLE_HAS_BACKGROUND_ALPHA|STYLE_HAS_BACKGROUND_COLOR; |
119 | | /* Teletext needs default background to black */ |
120 | 0 | p_style->i_background_alpha = STYLE_ALPHA_OPAQUE; |
121 | 0 | p_style->i_background_color = 0x000000; |
122 | 0 | p_style->i_font_size = 0; |
123 | 0 | p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE; |
124 | 0 | } |
125 | 0 | return p_style; |
126 | 0 | } |
127 | | |
128 | | static void TextBufferFlush(stl_sg_t *p_group, uint8_t *p_buf, uint8_t *pi_buf, |
129 | | const char *psz_charset) |
130 | 0 | { |
131 | 0 | if(*pi_buf == 0) |
132 | 0 | return; |
133 | | |
134 | 0 | char *psz_utf8 = FromCharset(psz_charset, p_buf, *pi_buf); |
135 | 0 | if(psz_utf8) |
136 | 0 | { |
137 | 0 | *p_group->pp_segment_last = text_segment_New(psz_utf8); |
138 | 0 | if(*p_group->pp_segment_last) |
139 | 0 | { |
140 | 0 | if(p_group->p_style) |
141 | 0 | (*p_group->pp_segment_last)->style = text_style_Duplicate(p_group->p_style); |
142 | 0 | p_group->pp_segment_last = &((*p_group->pp_segment_last)->p_next); |
143 | 0 | } |
144 | 0 | free(psz_utf8); |
145 | 0 | } |
146 | |
|
147 | 0 | *pi_buf = 0; |
148 | 0 | } |
149 | | |
150 | | static void GroupParseTeletext(stl_sg_t *p_group, uint8_t code) |
151 | 0 | { |
152 | 0 | if(p_group->p_style == NULL && |
153 | 0 | !(p_group->p_style = CreateGroupStyle())) |
154 | 0 | return; |
155 | | |
156 | | /* See ETS 300 706 Table 26 as EBU 3264 does only name values |
157 | | and does not explain at all */ |
158 | | |
159 | 0 | static const uint32_t colors[] = |
160 | 0 | { |
161 | 0 | 0x000000, |
162 | 0 | 0xFF0000, |
163 | 0 | 0x00FF00, |
164 | 0 | 0xFFFF00, |
165 | 0 | 0x0000FF, |
166 | 0 | 0xFF00FF, |
167 | 0 | 0x00FFFF, |
168 | 0 | 0xFFFFFF, |
169 | 0 | }; |
170 | | |
171 | | /* Teletext data received, so we need to enable background */ |
172 | 0 | p_group->p_style->i_style_flags |= STYLE_BACKGROUND; |
173 | |
|
174 | 0 | switch(code) |
175 | 0 | { |
176 | 0 | case 0x0c: |
177 | 0 | p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE; |
178 | 0 | p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH); |
179 | 0 | break; |
180 | | |
181 | 0 | case 0x0d: /* double height */ |
182 | 0 | p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE * 2; |
183 | 0 | p_group->p_style->i_style_flags &= ~STYLE_DOUBLEWIDTH; |
184 | 0 | p_group->p_style->i_style_flags |= STYLE_HALFWIDTH; |
185 | 0 | break; |
186 | | |
187 | 0 | case 0x0e: /* double width */ |
188 | 0 | p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE; |
189 | 0 | p_group->p_style->i_style_flags &= ~STYLE_HALFWIDTH; |
190 | 0 | p_group->p_style->i_style_flags |= STYLE_DOUBLEWIDTH; |
191 | 0 | break; |
192 | | |
193 | 0 | case 0x0f: /* double size */ |
194 | 0 | p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE * 2; |
195 | 0 | p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH); |
196 | 0 | break; |
197 | | |
198 | 0 | case 0x1d: |
199 | 0 | p_group->p_style->i_background_color = p_group->p_style->i_font_color; |
200 | 0 | p_group->p_style->i_features &= ~STYLE_HAS_FONT_COLOR; |
201 | 0 | break; |
202 | | |
203 | 0 | case 0x1c: |
204 | 0 | p_group->p_style->i_background_color = colors[0]; |
205 | 0 | break; |
206 | | |
207 | 0 | default: |
208 | 0 | if(code < 8) |
209 | 0 | { |
210 | 0 | p_group->p_style->i_font_color = colors[code]; |
211 | 0 | p_group->p_style->i_features |= STYLE_HAS_FONT_COLOR; |
212 | 0 | } |
213 | | |
214 | | /* Need to handle Mosaic ? Really ? */ |
215 | 0 | break; |
216 | 0 | } |
217 | |
|
218 | 0 | } |
219 | | |
220 | | static void GroupApplyStyle(stl_sg_t *p_group, uint8_t code) |
221 | 0 | { |
222 | 0 | if(p_group->p_style == NULL && |
223 | 0 | !(p_group->p_style = CreateGroupStyle())) |
224 | 0 | return; |
225 | | |
226 | 0 | switch(code) |
227 | 0 | { |
228 | 0 | case STL_TF_ITALICS_ON: |
229 | 0 | p_group->p_style->i_style_flags |= STYLE_ITALIC; |
230 | 0 | break; |
231 | 0 | case STL_TF_ITALICS_OFF: |
232 | 0 | p_group->p_style->i_style_flags &= ~STYLE_ITALIC; |
233 | 0 | break; |
234 | 0 | case STL_TF_UNDERLINE_ON: |
235 | 0 | p_group->p_style->i_style_flags |= STYLE_UNDERLINE; |
236 | 0 | break; |
237 | 0 | case STL_TF_UNDERLINE_OFF: |
238 | 0 | p_group->p_style->i_style_flags &= ~STYLE_UNDERLINE; |
239 | 0 | break; |
240 | 0 | case STL_TF_BOXING_ON: |
241 | 0 | case STL_TF_BOXING_OFF: |
242 | 0 | default: |
243 | 0 | break; |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | static vlc_tick_t ParseTimeCode(const uint8_t *data, double fps) |
248 | 0 | { |
249 | 0 | return vlc_tick_from_sec( data[0] * 3600 + |
250 | 0 | data[1] * 60 + |
251 | 0 | data[2] * 1 + |
252 | 0 | data[3] / fps); |
253 | 0 | } |
254 | | |
255 | | static void ClearTeletextStyles(stl_sg_t *p_group) |
256 | 0 | { |
257 | 0 | if(p_group->p_style) |
258 | 0 | { |
259 | 0 | p_group->p_style->i_features &= ~STYLE_HAS_FONT_COLOR; |
260 | 0 | p_group->p_style->i_background_color = 0x000000; |
261 | 0 | p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE; |
262 | 0 | p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH); |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | | /* Returns true if group is we need to output group */ |
267 | | static bool ParseTTI(stl_sg_t *p_group, const uint8_t *p_data, const char *psz_charset, double fps) |
268 | 0 | { |
269 | 0 | uint8_t p_buffer[STL_TEXTFIELD_SIZE]; |
270 | 0 | uint8_t i_buffer = 0; |
271 | | |
272 | | /* Header */ |
273 | 0 | uint8_t ebn = p_data[3]; |
274 | 0 | if(ebn > 0xef && ebn != 0xff) |
275 | 0 | return false; |
276 | | |
277 | 0 | if(p_data[15] != 0x00) /* comment flag */ |
278 | 0 | return false; |
279 | | |
280 | 0 | if(p_data[14] > 0x00) |
281 | 0 | p_group->i_justify = p_data[14]; |
282 | | |
283 | | /* Accumulating started or continuing. |
284 | | * We must not flush current segments on output and continue on next block */ |
285 | 0 | p_group->i_accumulating = (p_data[4] == 0x01 || p_data[4] == 0x02); |
286 | |
|
287 | 0 | p_group->i_start = ParseTimeCode( &p_data[5], fps ); |
288 | 0 | p_group->i_end = ParseTimeCode( &p_data[9], fps ); |
289 | | |
290 | | /* Text Field */ |
291 | 0 | for (size_t i = STL_TTI_HEADER_SIZE; i < STL_TTI_SIZE; i++) |
292 | 0 | { |
293 | 0 | const uint8_t code = p_data[i]; |
294 | 0 | switch(code) |
295 | 0 | { |
296 | 0 | case STL_TF_LINEBREAK: |
297 | 0 | p_buffer[i_buffer++] = '\n'; |
298 | 0 | TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset); |
299 | | /* Clear teletext styles on each new row */ |
300 | 0 | ClearTeletextStyles(p_group); |
301 | 0 | break; |
302 | | |
303 | 0 | case STL_TF_END_FILL: |
304 | 0 | TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset); |
305 | 0 | ClearTeletextStyles(p_group); |
306 | 0 | return true; |
307 | | |
308 | 0 | default: |
309 | 0 | if(code <= STL_TF_TELETEXT_LAST) |
310 | 0 | { |
311 | 0 | TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset); |
312 | 0 | GroupParseTeletext(p_group, code); |
313 | 0 | } |
314 | 0 | else if((code >= STL_TF_CHARCODE1_FIRST && code <= STL_TF_CHARCODE1_LAST) || |
315 | 0 | code >= STL_TF_CHARCODE2_FIRST) |
316 | 0 | { |
317 | 0 | p_buffer[i_buffer++] = code; |
318 | 0 | } |
319 | 0 | else if(code >= STL_TF_ITALICS_ON && code <= STL_TF_BOXING_OFF) |
320 | 0 | { |
321 | 0 | TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset); |
322 | 0 | GroupApplyStyle(p_group, code); |
323 | 0 | } |
324 | 0 | break; |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | 0 | TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset); |
329 | |
|
330 | 0 | return false; |
331 | 0 | } |
332 | | |
333 | | static void FillSubpictureUpdater(stl_sg_t *p_group, subtext_updater_sys_t *p_spu_sys) |
334 | 0 | { |
335 | 0 | if(p_group->i_accumulating) |
336 | 0 | { |
337 | 0 | p_spu_sys->region.p_segments = text_segment_Copy(p_group->p_segment); |
338 | 0 | } |
339 | 0 | else |
340 | 0 | { |
341 | 0 | p_spu_sys->region.p_segments = p_group->p_segment; |
342 | 0 | p_group->p_segment = NULL; |
343 | 0 | p_group->pp_segment_last = &p_group->p_segment; |
344 | 0 | } |
345 | |
|
346 | 0 | p_spu_sys->region.b_absolute = false; |
347 | 0 | p_spu_sys->region.b_in_window = false; |
348 | 0 | p_spu_sys->region.align = SUBPICTURE_ALIGN_BOTTOM; |
349 | 0 | if(p_group->i_justify == 0x01) |
350 | 0 | p_spu_sys->region.inner_align = SUBPICTURE_ALIGN_LEFT; |
351 | 0 | else if(p_group->i_justify == 0x03) |
352 | 0 | p_spu_sys->region.inner_align = SUBPICTURE_ALIGN_RIGHT; |
353 | 0 | } |
354 | | |
355 | | static void ResetGroups(decoder_sys_t *p_sys) |
356 | 0 | { |
357 | 0 | for(size_t i=0; i<=STL_GROUPS_MAX; i++) |
358 | 0 | { |
359 | 0 | stl_sg_t *p_group = &p_sys->groups[i]; |
360 | 0 | if(p_group->p_segment) |
361 | 0 | { |
362 | 0 | text_segment_ChainDelete(p_group->p_segment); |
363 | 0 | p_group->p_segment = NULL; |
364 | 0 | p_group->pp_segment_last = &p_group->p_segment; |
365 | 0 | } |
366 | |
|
367 | 0 | if(p_group->p_style) |
368 | 0 | { |
369 | 0 | text_style_Delete(p_group->p_style); |
370 | 0 | p_group->p_style = NULL; |
371 | 0 | } |
372 | |
|
373 | 0 | p_group->i_accumulating = false; |
374 | 0 | p_group->i_end = VLC_TICK_INVALID; |
375 | 0 | p_group->i_start = VLC_TICK_INVALID; |
376 | 0 | p_group->i_justify = 0; |
377 | 0 | } |
378 | 0 | } |
379 | | |
380 | | static int Decode(decoder_t *p_dec, block_t *p_block) |
381 | 0 | { |
382 | 0 | if (p_block == NULL) /* No Drain */ |
383 | 0 | return VLCDEC_SUCCESS; |
384 | | |
385 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
386 | |
|
387 | 0 | if(p_block->i_buffer < STL_TTI_SIZE) |
388 | 0 | p_block->i_flags |= BLOCK_FLAG_CORRUPTED; |
389 | |
|
390 | 0 | if(p_block->i_flags & (BLOCK_FLAG_CORRUPTED|BLOCK_FLAG_DISCONTINUITY)) |
391 | 0 | { |
392 | 0 | ResetGroups(p_dec->p_sys); |
393 | |
|
394 | 0 | if(p_block->i_flags & BLOCK_FLAG_CORRUPTED) |
395 | 0 | { |
396 | 0 | block_Release(p_block); |
397 | 0 | return VLCDEC_SUCCESS; |
398 | 0 | } |
399 | 0 | } |
400 | | |
401 | 0 | const char *psz_charset = cct_nums[p_sys->cct - CCT_BEGIN].str; |
402 | 0 | for (size_t i = 0; i < p_block->i_buffer / STL_TTI_SIZE; i++) |
403 | 0 | { |
404 | 0 | stl_sg_t *p_group = &p_sys->groups[p_block->p_buffer[0]]; |
405 | 0 | if(ParseTTI(p_group, &p_block->p_buffer[i * STL_TTI_SIZE], psz_charset, p_sys->i_fps) && |
406 | 0 | p_group->p_segment != NULL ) |
407 | 0 | { |
408 | | /* output */ |
409 | 0 | subpicture_t *p_sub = decoder_NewSubpictureText(p_dec); |
410 | 0 | if( p_sub ) |
411 | 0 | { |
412 | 0 | FillSubpictureUpdater(p_group, p_sub->updater.sys ); |
413 | |
|
414 | 0 | if(p_group->i_end != VLC_TICK_INVALID && p_group->i_start >= p_block->i_dts) |
415 | 0 | { |
416 | 0 | p_sub->i_start = VLC_TICK_0 + p_group->i_start; |
417 | 0 | p_sub->i_stop = VLC_TICK_0 + p_group->i_end; |
418 | 0 | } |
419 | 0 | else |
420 | 0 | { |
421 | 0 | p_sub->i_start = p_block->i_pts; |
422 | 0 | p_sub->i_stop = p_block->i_pts + p_block->i_length; |
423 | 0 | p_sub->b_ephemer = (p_block->i_length == 0); |
424 | 0 | } |
425 | 0 | decoder_QueueSub(p_dec, p_sub); |
426 | 0 | } |
427 | 0 | } |
428 | 0 | } |
429 | |
|
430 | 0 | ResetGroups(p_sys); |
431 | |
|
432 | 0 | block_Release(p_block); |
433 | 0 | return VLCDEC_SUCCESS; |
434 | 0 | } |
435 | | |
436 | | static int ParseGSI(decoder_t *dec, decoder_sys_t *p_sys) |
437 | 0 | { |
438 | 0 | uint8_t *header = dec->fmt_in->p_extra; |
439 | 0 | if (!header) { |
440 | 0 | msg_Err(dec, "NULL EBU header (GSI block)\n"); |
441 | 0 | return VLC_EGENERIC; |
442 | 0 | } |
443 | | |
444 | 0 | if (GSI_BLOCK_SIZE != dec->fmt_in->i_extra) { |
445 | 0 | msg_Err(dec, "EBU header is not in expected size (%zu)\n", dec->fmt_in->i_extra); |
446 | 0 | return VLC_EGENERIC; |
447 | 0 | } |
448 | | |
449 | 0 | char dfc_fps_str[] = { header[6], header[7], '\0' }; |
450 | 0 | int fps = strtol(dfc_fps_str, NULL, 10); |
451 | 0 | if (1 > fps || 60 < fps) { |
452 | 0 | msg_Warn(dec, "EBU header contains unsupported DFC fps ('%s'); falling back to 25\n", dfc_fps_str); |
453 | 0 | fps = 25; |
454 | 0 | } |
455 | |
|
456 | 0 | int cct = (header[12] << 8) | header[13]; |
457 | 0 | if (CCT_BEGIN > cct || CCT_END < cct) { |
458 | 0 | msg_Err(dec, "EBU header contains illegal CCT (0x%x)\n", cct); |
459 | 0 | return VLC_EGENERIC; |
460 | 0 | } |
461 | | |
462 | 0 | msg_Dbg(dec, "DFC fps=%d, CCT=0x%x", fps, cct); |
463 | 0 | p_sys->i_fps = fps; |
464 | 0 | p_sys->cct = cct; |
465 | |
|
466 | 0 | return VLC_SUCCESS; |
467 | 0 | } |
468 | | |
469 | | static int Open(vlc_object_t *object) |
470 | 390 | { |
471 | 390 | decoder_t *dec = (decoder_t*)object; |
472 | | |
473 | 390 | if (dec->fmt_in->i_codec != VLC_CODEC_EBU_STL) |
474 | 390 | return VLC_EGENERIC; |
475 | | |
476 | 0 | decoder_sys_t *sys = calloc(1, sizeof(*sys)); |
477 | 0 | if (!sys) |
478 | 0 | return VLC_ENOMEM; |
479 | | |
480 | 0 | int rc = ParseGSI(dec, sys); |
481 | 0 | if (VLC_SUCCESS != rc) { |
482 | 0 | free(sys); |
483 | 0 | return rc; |
484 | 0 | } |
485 | | |
486 | 0 | for(size_t i=0; i<=STL_GROUPS_MAX; i++) |
487 | 0 | sys->groups[i].pp_segment_last = &sys->groups[i].p_segment; |
488 | |
|
489 | 0 | dec->p_sys = sys; |
490 | 0 | dec->pf_decode = Decode; |
491 | 0 | dec->fmt_out.i_codec = 0; |
492 | 0 | return VLC_SUCCESS; |
493 | 0 | } |
494 | | |
495 | | static void Close(vlc_object_t *object) |
496 | 0 | { |
497 | 0 | decoder_t *dec = (decoder_t*)object; |
498 | 0 | decoder_sys_t *p_sys = dec->p_sys; |
499 | |
|
500 | 0 | ResetGroups(p_sys); |
501 | 0 | free(p_sys); |
502 | 0 | } |