/src/vlc/modules/codec/scte27.c
Line | Count | Source (jump to first uncovered line) |
1 | | /***************************************************************************** |
2 | | * scte27.c : SCTE-27 subtitles decoder |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 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 | | #ifdef HAVE_CONFIG_H |
24 | | # include "config.h" |
25 | | #endif |
26 | | |
27 | | #include <vlc_common.h> |
28 | | #include <vlc_plugin.h> |
29 | | #include <vlc_codec.h> |
30 | | #include <vlc_bits.h> |
31 | | |
32 | | #include <assert.h> |
33 | | |
34 | | /***************************************************************************** |
35 | | * Module descriptor. |
36 | | *****************************************************************************/ |
37 | | static int Open (vlc_object_t *); |
38 | | static void Close(vlc_object_t *); |
39 | | |
40 | 100 | vlc_module_begin () |
41 | 50 | set_description(N_("SCTE-27 decoder")) |
42 | 50 | set_shortname(N_("SCTE-27")) |
43 | 50 | set_capability( "spu decoder", 51) |
44 | 50 | set_subcategory(SUBCAT_INPUT_SCODEC) |
45 | 50 | set_callbacks(Open, Close) |
46 | 50 | vlc_module_end () |
47 | | |
48 | | /**************************************************************************** |
49 | | * Local prototypes |
50 | | ****************************************************************************/ |
51 | | typedef struct |
52 | | { |
53 | | int segment_id; |
54 | | int segment_size; |
55 | | uint8_t *segment_buffer; |
56 | | vlc_tick_t segment_date; |
57 | | } decoder_sys_t; |
58 | | |
59 | | typedef struct { |
60 | | uint8_t y, u, v; |
61 | | uint8_t alpha; |
62 | | } scte27_color_t; |
63 | | |
64 | | static const scte27_color_t scte27_color_transparent = { |
65 | | .y = 0x00, |
66 | | .u = 0x80, |
67 | | .v = 0x80, |
68 | | .alpha = 0x00, |
69 | | }; |
70 | | |
71 | | static scte27_color_t bs_read_color(bs_t *bs) |
72 | 0 | { |
73 | 0 | scte27_color_t color; |
74 | | |
75 | | /* XXX it's unclear if a value of 0 in Y/U/V means a transparent pixel */ |
76 | 0 | color.y = bs_read(bs, 5) << 3; |
77 | 0 | color.alpha = bs_read1(bs) ? 0xff : 0x80; |
78 | 0 | color.v = bs_read(bs, 5) << 3; |
79 | 0 | color.u = bs_read(bs, 5) << 3; |
80 | |
|
81 | 0 | return color; |
82 | 0 | } |
83 | | |
84 | | static inline void SetYUVPPixel(picture_t *picture, int x, int y, int value) |
85 | 0 | { |
86 | 0 | picture->p->p_pixels[y * picture->p->i_pitch + x] = value; |
87 | 0 | } |
88 | | |
89 | | static subpicture_region_t *DecodeSimpleBitmap(decoder_t *dec, |
90 | | const uint8_t *data, int size) |
91 | 0 | { |
92 | 0 | VLC_UNUSED(dec); |
93 | | /* Parse the bitmap and its properties */ |
94 | 0 | bs_t bs; |
95 | 0 | bs_init(&bs, data, size); |
96 | |
|
97 | 0 | bs_skip(&bs, 5); |
98 | 0 | int is_framed = bs_read(&bs, 1); |
99 | 0 | int outline_style = bs_read(&bs, 2); |
100 | 0 | scte27_color_t character_color = bs_read_color(&bs); |
101 | 0 | int top_h = bs_read(&bs, 12); |
102 | 0 | int top_v = bs_read(&bs, 12); |
103 | 0 | int bottom_h = bs_read(&bs, 12); |
104 | 0 | int bottom_v = bs_read(&bs, 12); |
105 | 0 | if (top_h >= bottom_h || top_v >= bottom_v) |
106 | 0 | return NULL; |
107 | 0 | int frame_top_h = top_h; |
108 | 0 | int frame_top_v = top_v; |
109 | 0 | int frame_bottom_h = bottom_h; |
110 | 0 | int frame_bottom_v = bottom_v; |
111 | 0 | scte27_color_t frame_color = scte27_color_transparent; |
112 | 0 | if (is_framed) { |
113 | 0 | frame_top_h = bs_read(&bs, 12); |
114 | 0 | frame_top_v = bs_read(&bs, 12); |
115 | 0 | frame_bottom_h = bs_read(&bs, 12); |
116 | 0 | frame_bottom_v = bs_read(&bs, 12); |
117 | 0 | frame_color = bs_read_color(&bs); |
118 | 0 | if (frame_top_h > top_h || |
119 | 0 | frame_top_v > top_v || |
120 | 0 | frame_bottom_h < bottom_h || |
121 | 0 | frame_bottom_v < bottom_v) |
122 | 0 | return NULL; |
123 | 0 | } |
124 | 0 | int outline_thickness = 0; |
125 | 0 | scte27_color_t outline_color = scte27_color_transparent; |
126 | 0 | int shadow_right = 0; |
127 | 0 | int shadow_bottom = 0; |
128 | 0 | scte27_color_t shadow_color = scte27_color_transparent; |
129 | 0 | if (outline_style == 1) { |
130 | 0 | bs_skip(&bs, 4); |
131 | 0 | outline_thickness = bs_read(&bs, 4); |
132 | 0 | outline_color = bs_read_color(&bs); |
133 | 0 | } else if (outline_style == 2) { |
134 | 0 | shadow_right = bs_read(&bs, 4); |
135 | 0 | shadow_bottom = bs_read(&bs, 4); |
136 | 0 | shadow_color = bs_read_color(&bs); |
137 | 0 | } else if (outline_style == 3) { |
138 | 0 | bs_skip(&bs, 24); |
139 | 0 | } |
140 | 0 | bs_skip(&bs, 16); // bitmap_compressed_length |
141 | 0 | int bitmap_h = bottom_h - top_h; |
142 | 0 | int bitmap_v = bottom_v - top_v; |
143 | 0 | int bitmap_size = bitmap_h * bitmap_v; |
144 | 0 | bool *bitmap = vlc_alloc(bitmap_size, sizeof(*bitmap)); |
145 | 0 | if (!bitmap) |
146 | 0 | return NULL; |
147 | 0 | for (int position = 0; position < bitmap_size;) { |
148 | 0 | if (bs_eof(&bs)) { |
149 | 0 | for (; position < bitmap_size; position++) |
150 | 0 | bitmap[position] = false; |
151 | 0 | break; |
152 | 0 | } |
153 | | |
154 | 0 | int run_on_length = 0; |
155 | 0 | int run_off_length = 0; |
156 | 0 | if (!bs_read1(&bs)) { |
157 | 0 | if (!bs_read1(&bs)) { |
158 | 0 | if (!bs_read1(&bs)) { |
159 | 0 | if (bs_read(&bs, 2) == 1) { |
160 | 0 | int next = __MIN((position / bitmap_h + 1) * bitmap_h, |
161 | 0 | bitmap_size); |
162 | 0 | for (; position < next; position++) |
163 | 0 | bitmap[position] = false; |
164 | 0 | } |
165 | 0 | } else { |
166 | 0 | run_on_length = 4; |
167 | 0 | } |
168 | 0 | } else { |
169 | 0 | run_off_length = 6; |
170 | 0 | } |
171 | 0 | } else { |
172 | 0 | run_on_length = 3; |
173 | 0 | run_off_length = 5; |
174 | 0 | } |
175 | |
|
176 | 0 | if (run_on_length > 0) { |
177 | 0 | int run = bs_read(&bs, run_on_length); |
178 | 0 | if (!run) |
179 | 0 | run = 1 << run_on_length; |
180 | 0 | for (; position < bitmap_size && run > 0; position++, run--) |
181 | 0 | bitmap[position] = true; |
182 | 0 | } |
183 | 0 | if (run_off_length > 0) { |
184 | 0 | int run = bs_read(&bs, run_off_length); |
185 | 0 | if (!run) |
186 | 0 | run = 1 << run_off_length; |
187 | 0 | for (; position < bitmap_size && run > 0; position++, run--) |
188 | 0 | bitmap[position] = false; |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | | /* Render the bitmap into a subpicture_region_t */ |
193 | | |
194 | | /* Reserve the place for the style |
195 | | * FIXME It's unclear if it is needed or if the bitmap should already include |
196 | | * the needed margin (I think the samples I have do both). */ |
197 | 0 | int margin_h = 0; |
198 | 0 | int margin_v = 0; |
199 | 0 | if (outline_style == 1) { |
200 | 0 | margin_h = |
201 | 0 | margin_v = outline_thickness; |
202 | 0 | } else if (outline_style == 2) { |
203 | 0 | margin_h = shadow_right; |
204 | 0 | margin_v = shadow_bottom; |
205 | 0 | } |
206 | 0 | frame_top_h -= margin_h; |
207 | 0 | frame_top_v -= margin_v; |
208 | 0 | frame_bottom_h += margin_h; |
209 | 0 | frame_bottom_v += margin_v; |
210 | |
|
211 | 0 | const int frame_h = frame_bottom_h - frame_top_h; |
212 | 0 | const int frame_v = frame_bottom_v - frame_top_v; |
213 | 0 | const int bitmap_oh = top_h - frame_top_h; |
214 | 0 | const int bitmap_ov = top_v - frame_top_v; |
215 | |
|
216 | 0 | enum { |
217 | 0 | COLOR_FRAME, |
218 | 0 | COLOR_CHARACTER, |
219 | 0 | COLOR_OUTLINE, |
220 | 0 | COLOR_SHADOW, |
221 | 0 | }; |
222 | 0 | video_palette_t palette = { |
223 | 0 | .i_entries = 4, |
224 | 0 | .palette = { |
225 | 0 | [COLOR_FRAME] = { |
226 | 0 | frame_color.y, |
227 | 0 | frame_color.u, |
228 | 0 | frame_color.v, |
229 | 0 | frame_color.alpha |
230 | 0 | }, |
231 | 0 | [COLOR_CHARACTER] = { |
232 | 0 | character_color.y, |
233 | 0 | character_color.u, |
234 | 0 | character_color.v, |
235 | 0 | character_color.alpha |
236 | 0 | }, |
237 | 0 | [COLOR_OUTLINE] = { |
238 | 0 | outline_color.y, |
239 | 0 | outline_color.u, |
240 | 0 | outline_color.v, |
241 | 0 | outline_color.alpha |
242 | 0 | }, |
243 | 0 | [COLOR_SHADOW] = { |
244 | 0 | shadow_color.y, |
245 | 0 | shadow_color.u, |
246 | 0 | shadow_color.v, |
247 | 0 | shadow_color.alpha |
248 | 0 | }, |
249 | 0 | }, |
250 | 0 | }; |
251 | 0 | video_format_t fmt = { |
252 | 0 | .i_chroma = VLC_CODEC_YUVP, |
253 | 0 | .i_width = frame_h, |
254 | 0 | .i_visible_width = frame_h, |
255 | 0 | .i_height = frame_v, |
256 | 0 | .i_visible_height = frame_v, |
257 | 0 | .i_sar_num = 0, /* Use video AR */ |
258 | 0 | .i_sar_den = 1, |
259 | 0 | .p_palette = &palette, |
260 | 0 | }; |
261 | 0 | subpicture_region_t *r = subpicture_region_New(&fmt); |
262 | 0 | if (!r) { |
263 | 0 | free(bitmap); |
264 | 0 | return NULL; |
265 | 0 | } |
266 | 0 | r->i_x = frame_top_h; |
267 | 0 | r->i_y = frame_top_v; |
268 | | |
269 | | /* Fill up with frame (background) color */ |
270 | 0 | for (int y = 0; y < frame_v; y++) |
271 | 0 | memset(&r->p_picture->p->p_pixels[y * r->p_picture->p->i_pitch], |
272 | 0 | COLOR_FRAME, |
273 | 0 | frame_h); |
274 | | |
275 | | /* Draw the outline/shadow if requested */ |
276 | 0 | if (outline_style == 1) { |
277 | | /* Draw an outline |
278 | | * XXX simple but slow and of low quality (no anti-aliasing) */ |
279 | 0 | bool circle[16][16]; |
280 | 0 | for (int dy = 0; dy <= 15; dy++) { |
281 | 0 | for (int dx = 0; dx <= 15; dx++) |
282 | 0 | circle[dy][dx] = (dx > 0 || dy > 0) && |
283 | 0 | dx * dx + dy * dy <= outline_thickness * outline_thickness; |
284 | 0 | } |
285 | 0 | for (int by = 0; by < bitmap_v; by++) { |
286 | 0 | for (int bx = 0; bx < bitmap_h; bx++) { |
287 | 0 | if (!bitmap[by * bitmap_h + bx]) |
288 | 0 | continue; |
289 | 0 | for (int dy = 0; dy <= outline_thickness; dy++) { |
290 | 0 | for (int dx = 0; dx <= outline_thickness; dx++) { |
291 | 0 | if (circle[dy][dx]) { |
292 | 0 | SetYUVPPixel(r->p_picture, |
293 | 0 | bx + bitmap_oh + dx, by + bitmap_ov + dy, COLOR_OUTLINE); |
294 | 0 | SetYUVPPixel(r->p_picture, |
295 | 0 | bx + bitmap_oh - dx, by + bitmap_ov + dy, COLOR_OUTLINE); |
296 | 0 | SetYUVPPixel(r->p_picture, |
297 | 0 | bx + bitmap_oh + dx, by + bitmap_ov - dy, COLOR_OUTLINE); |
298 | 0 | SetYUVPPixel(r->p_picture, |
299 | 0 | bx + bitmap_oh - dx, by + bitmap_ov - dy, COLOR_OUTLINE); |
300 | 0 | } |
301 | 0 | } |
302 | 0 | } |
303 | 0 | } |
304 | 0 | } |
305 | 0 | } else if (outline_style == 2) { |
306 | | /* Draw a shadow by drawing the character shifted by shadow right/bottom */ |
307 | 0 | for (int by = 0; by < bitmap_v; by++) { |
308 | 0 | for (int bx = 0; bx < bitmap_h; bx++) { |
309 | 0 | if (bitmap[by * bitmap_h + bx]) |
310 | 0 | SetYUVPPixel(r->p_picture, |
311 | 0 | bx + bitmap_oh + shadow_right, |
312 | 0 | by + bitmap_ov + shadow_bottom, |
313 | 0 | COLOR_SHADOW); |
314 | 0 | } |
315 | 0 | } |
316 | 0 | } |
317 | | |
318 | | /* Draw the character */ |
319 | 0 | for (int by = 0; by < bitmap_v; by++) { |
320 | 0 | for (int bx = 0; bx < bitmap_h; bx++) { |
321 | 0 | if (bitmap[by * bitmap_h + bx]) |
322 | 0 | SetYUVPPixel(r->p_picture, |
323 | 0 | bx + bitmap_oh, by + bitmap_ov, COLOR_CHARACTER); |
324 | 0 | } |
325 | 0 | } |
326 | 0 | free(bitmap); |
327 | 0 | return r; |
328 | 0 | } |
329 | | |
330 | | static subpicture_t *DecodeSubtitleMessage(decoder_t *dec, |
331 | | const uint8_t *data, int size, |
332 | | vlc_tick_t date) |
333 | 0 | { |
334 | 0 | if (size < 12) |
335 | 0 | goto error; |
336 | | |
337 | | /* Parse the header */ |
338 | 0 | bool pre_clear_display = data[3] & 0x80; |
339 | 0 | int display_standard = data[3] & 0x1f; |
340 | 0 | int subtitle_type = data[8] >> 4; |
341 | 0 | int display_duration = ((data[8] & 0x07) << 8) | data[9]; |
342 | 0 | int block_length = GetWBE(&data[10]); |
343 | |
|
344 | 0 | size -= 12; |
345 | 0 | data += 12; |
346 | |
|
347 | 0 | if (block_length > size) |
348 | 0 | goto error; |
349 | | |
350 | 0 | if (subtitle_type != 1) { |
351 | | /* Reserved */ |
352 | 0 | return NULL; |
353 | 0 | } |
354 | | |
355 | 0 | subpicture_region_t *region = DecodeSimpleBitmap(dec, data, block_length); |
356 | 0 | if (!region) |
357 | 0 | goto error; |
358 | 0 | subpicture_t *sub = decoder_NewSubpicture(dec, NULL); |
359 | 0 | if (!sub) { |
360 | 0 | subpicture_region_Delete(region); |
361 | 0 | return NULL; |
362 | 0 | } |
363 | 0 | vlc_tick_t frame_duration; |
364 | 0 | switch (display_standard) { |
365 | 0 | case 0: |
366 | 0 | sub->i_original_picture_width = 720; |
367 | 0 | sub->i_original_picture_height = 480; |
368 | 0 | frame_duration = VLC_TICK_FROM_US(33367); |
369 | 0 | break; |
370 | 0 | case 1: |
371 | 0 | sub->i_original_picture_width = 720; |
372 | 0 | sub->i_original_picture_height = 576; |
373 | 0 | frame_duration = VLC_TICK_FROM_MS(40); |
374 | 0 | break; |
375 | 0 | case 2: |
376 | 0 | sub->i_original_picture_width = 1280; |
377 | 0 | sub->i_original_picture_height = 720; |
378 | 0 | frame_duration = VLC_TICK_FROM_US(16683); |
379 | 0 | break; |
380 | 0 | case 3: |
381 | 0 | sub->i_original_picture_width = 1920; |
382 | 0 | sub->i_original_picture_height = 1080; |
383 | 0 | frame_duration = VLC_TICK_FROM_US(16683); |
384 | 0 | break; |
385 | 0 | default: |
386 | 0 | msg_Warn(dec, "Unknown display standard"); |
387 | 0 | sub->i_original_picture_width = 0; |
388 | 0 | sub->i_original_picture_height = 0; |
389 | 0 | frame_duration = VLC_TICK_FROM_MS(40); |
390 | 0 | break; |
391 | 0 | } |
392 | 0 | region->b_absolute = true; region->b_in_window = false; |
393 | 0 | if (!pre_clear_display) |
394 | 0 | msg_Warn(dec, "SCTE-27 subtitles without pre_clear_display flag are not well supported"); |
395 | 0 | sub->b_ephemer = true; |
396 | 0 | sub->i_start = date; |
397 | 0 | sub->i_stop = date + display_duration * frame_duration; |
398 | 0 | vlc_spu_regions_push(&sub->regions, region); |
399 | |
|
400 | 0 | return sub; |
401 | | |
402 | 0 | error: |
403 | 0 | msg_Err(dec, "corrupted subtitle_message"); |
404 | 0 | return NULL; |
405 | 0 | } |
406 | | |
407 | | static int Decode(decoder_t *dec, block_t *b) |
408 | 0 | { |
409 | 0 | decoder_sys_t *sys = dec->p_sys; |
410 | |
|
411 | 0 | if (b == NULL ) /* No Drain */ |
412 | 0 | return VLCDEC_SUCCESS; |
413 | | |
414 | 0 | if (b->i_flags & (BLOCK_FLAG_CORRUPTED)) |
415 | 0 | goto exit; |
416 | | |
417 | 0 | while (b->i_buffer > 3) { |
418 | 0 | const int table_id = b->p_buffer[0]; |
419 | 0 | if (table_id != 0xc6) { |
420 | | //if (table_id != 0xff) |
421 | | // msg_Err(dec, "Invalid SCTE-27 table id (0x%x)", table_id); |
422 | 0 | break; |
423 | 0 | } |
424 | 0 | const int section_length = ((b->p_buffer[1] & 0xf) << 8) | b->p_buffer[2]; |
425 | 0 | if (section_length <= 1 + 4 || b->i_buffer < 3 + (unsigned)section_length) { |
426 | 0 | msg_Err(dec, "Invalid SCTE-27 section length"); |
427 | 0 | break; |
428 | 0 | } |
429 | 0 | const int protocol_version = b->p_buffer[3] & 0x3f; |
430 | 0 | if (protocol_version != 0) { |
431 | 0 | msg_Err(dec, "Unsupported SCTE-27 protocol version (%d)", protocol_version); |
432 | 0 | break; |
433 | 0 | } |
434 | 0 | const bool segmentation_overlay = b->p_buffer[3] & 0x40; |
435 | |
|
436 | 0 | subpicture_t *sub = NULL; |
437 | 0 | if (segmentation_overlay) { |
438 | 0 | if (section_length < 1 + 5 + 4) |
439 | 0 | break; |
440 | 0 | int id = GetWBE(&b->p_buffer[4]); |
441 | 0 | int last = (b->p_buffer[6] << 4) | (b->p_buffer[7] >> 4); |
442 | 0 | int index = ((b->p_buffer[7] & 0x0f) << 8) | b->p_buffer[8]; |
443 | 0 | if (index > last) |
444 | 0 | break; |
445 | 0 | if (index == 0) { |
446 | 0 | sys->segment_id = id; |
447 | 0 | sys->segment_size = 0; |
448 | 0 | sys->segment_date = b->i_pts != VLC_TICK_INVALID ? b->i_pts : b->i_dts; |
449 | 0 | } else { |
450 | 0 | if (sys->segment_id != id || sys->segment_size <= 0) { |
451 | 0 | sys->segment_id = -1; |
452 | 0 | break; |
453 | 0 | } |
454 | 0 | } |
455 | | |
456 | 0 | int segment_size = section_length - 1 - 5 - 4; |
457 | |
|
458 | 0 | sys->segment_buffer = xrealloc(sys->segment_buffer, |
459 | 0 | sys->segment_size + segment_size); |
460 | 0 | memcpy(&sys->segment_buffer[sys->segment_size], |
461 | 0 | &b->p_buffer[9], segment_size); |
462 | 0 | sys->segment_size += segment_size; |
463 | |
|
464 | 0 | if (index == last) { |
465 | 0 | sub = DecodeSubtitleMessage(dec, |
466 | 0 | sys->segment_buffer, |
467 | 0 | sys->segment_size, |
468 | 0 | sys->segment_date); |
469 | 0 | sys->segment_size = 0; |
470 | 0 | } |
471 | 0 | } else { |
472 | 0 | sub = DecodeSubtitleMessage(dec, |
473 | 0 | &b->p_buffer[4], |
474 | 0 | section_length - 1 - 4, |
475 | 0 | b->i_pts != VLC_TICK_INVALID ? b->i_pts : b->i_dts); |
476 | 0 | } |
477 | 0 | if (sub != NULL) |
478 | 0 | decoder_QueueSub(dec, sub); |
479 | |
|
480 | 0 | b->i_buffer -= 3 + section_length; |
481 | 0 | b->p_buffer += 3 + section_length; |
482 | 0 | break; |
483 | 0 | } |
484 | |
|
485 | 0 | exit: |
486 | 0 | block_Release(b); |
487 | 0 | return VLCDEC_SUCCESS; |
488 | 0 | } |
489 | | |
490 | | static int Open(vlc_object_t *object) |
491 | 11.2k | { |
492 | 11.2k | decoder_t *dec = (decoder_t *)object; |
493 | | |
494 | 11.2k | if (dec->fmt_in->i_codec != VLC_CODEC_SCTE_27) |
495 | 11.2k | return VLC_EGENERIC; |
496 | | |
497 | 0 | decoder_sys_t *sys = dec->p_sys = malloc(sizeof(*sys)); |
498 | 0 | if (!sys) |
499 | 0 | return VLC_ENOMEM; |
500 | 0 | sys->segment_id = -1; |
501 | 0 | sys->segment_size = 0; |
502 | 0 | sys->segment_buffer = NULL; |
503 | |
|
504 | 0 | dec->pf_decode = Decode; |
505 | 0 | dec->fmt_out.i_codec = VLC_CODEC_YUVP; |
506 | |
|
507 | 0 | return VLC_SUCCESS; |
508 | 0 | } |
509 | | |
510 | | static void Close(vlc_object_t *object) |
511 | 0 | { |
512 | 0 | decoder_t *dec = (decoder_t *)object; |
513 | 0 | decoder_sys_t *sys = dec->p_sys; |
514 | |
|
515 | 0 | free(sys->segment_buffer); |
516 | 0 | free(sys); |
517 | 0 | } |