Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 1 | /* |
2 | * PGS subtitle decoder |
||
3 | * Copyright (c) 2009 Stephen Backway |
||
4 | * |
||
5 | * This file is part of FFmpeg. |
||
6 | * |
||
7 | * FFmpeg is free software; you can redistribute it and/or |
||
8 | * modify it under the terms of the GNU Lesser General Public |
||
9 | * License as published by the Free Software Foundation; either |
||
10 | * version 2.1 of the License, or (at your option) any later version. |
||
11 | * |
||
12 | * FFmpeg is distributed in the hope that it will be useful, |
||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
15 | * Lesser General Public License for more details. |
||
16 | * |
||
17 | * You should have received a copy of the GNU Lesser General Public |
||
18 | * License along with FFmpeg; if not, write to the Free Software |
||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
20 | */ |
||
21 | |||
22 | /** |
||
23 | * @file |
||
24 | * PGS subtitle decoder |
||
25 | */ |
||
26 | |||
27 | #include "avcodec.h" |
||
28 | #include "dsputil.h" |
||
29 | #include "bytestream.h" |
||
30 | #include "libavutil/colorspace.h" |
||
31 | #include "libavutil/imgutils.h" |
||
32 | #include "libavutil/opt.h" |
||
33 | |||
34 | #define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) |
||
35 | |||
36 | enum SegmentType { |
||
37 | PALETTE_SEGMENT = 0x14, |
||
38 | PICTURE_SEGMENT = 0x15, |
||
39 | PRESENTATION_SEGMENT = 0x16, |
||
40 | WINDOW_SEGMENT = 0x17, |
||
41 | DISPLAY_SEGMENT = 0x80, |
||
42 | }; |
||
43 | |||
44 | typedef struct PGSSubPictureReference { |
||
45 | int x; |
||
46 | int y; |
||
47 | int picture_id; |
||
48 | int composition; |
||
49 | } PGSSubPictureReference; |
||
50 | |||
51 | typedef struct PGSSubPresentation { |
||
52 | int id_number; |
||
53 | int object_count; |
||
54 | PGSSubPictureReference *objects; |
||
55 | int64_t pts; |
||
56 | } PGSSubPresentation; |
||
57 | |||
58 | typedef struct PGSSubPicture { |
||
59 | int w; |
||
60 | int h; |
||
61 | uint8_t *rle; |
||
62 | unsigned int rle_buffer_size, rle_data_len; |
||
63 | unsigned int rle_remaining_len; |
||
64 | } PGSSubPicture; |
||
65 | |||
66 | typedef struct PGSSubContext { |
||
67 | AVClass *class; |
||
68 | PGSSubPresentation presentation; |
||
69 | uint32_t clut[256]; |
||
70 | PGSSubPicture pictures[UINT16_MAX]; |
||
71 | int forced_subs_only; |
||
72 | } PGSSubContext; |
||
73 | |||
74 | static av_cold int init_decoder(AVCodecContext *avctx) |
||
75 | { |
||
76 | avctx->pix_fmt = AV_PIX_FMT_PAL8; |
||
77 | |||
78 | return 0; |
||
79 | } |
||
80 | |||
81 | static av_cold int close_decoder(AVCodecContext *avctx) |
||
82 | { |
||
83 | uint16_t picture; |
||
84 | |||
85 | PGSSubContext *ctx = avctx->priv_data; |
||
86 | |||
87 | av_freep(&ctx->presentation.objects); |
||
88 | ctx->presentation.object_count = 0; |
||
89 | |||
90 | for (picture = 0; picture < UINT16_MAX; ++picture) { |
||
91 | av_freep(&ctx->pictures[picture].rle); |
||
92 | ctx->pictures[picture].rle_buffer_size = 0; |
||
93 | } |
||
94 | |||
95 | return 0; |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * Decode the RLE data. |
||
100 | * |
||
101 | * The subtitle is stored as a Run Length Encoded image. |
||
102 | * |
||
103 | * @param avctx contains the current codec context |
||
104 | * @param sub pointer to the processed subtitle data |
||
105 | * @param buf pointer to the RLE data to process |
||
106 | * @param buf_size size of the RLE data to process |
||
107 | */ |
||
108 | static int decode_rle(AVCodecContext *avctx, AVSubtitle *sub, int rect, |
||
109 | const uint8_t *buf, unsigned int buf_size) |
||
110 | { |
||
111 | const uint8_t *rle_bitmap_end; |
||
112 | int pixel_count, line_count; |
||
113 | |||
114 | rle_bitmap_end = buf + buf_size; |
||
115 | |||
116 | sub->rects[rect]->pict.data[0] = av_malloc(sub->rects[rect]->w * sub->rects[rect]->h); |
||
117 | |||
118 | if (!sub->rects[rect]->pict.data[0]) |
||
119 | return -1; |
||
120 | |||
121 | pixel_count = 0; |
||
122 | line_count = 0; |
||
123 | |||
124 | while (buf < rle_bitmap_end && line_count < sub->rects[rect]->h) { |
||
125 | uint8_t flags, color; |
||
126 | int run; |
||
127 | |||
128 | color = bytestream_get_byte(&buf); |
||
129 | run = 1; |
||
130 | |||
131 | if (color == 0x00) { |
||
132 | flags = bytestream_get_byte(&buf); |
||
133 | run = flags & 0x3f; |
||
134 | if (flags & 0x40) |
||
135 | run = (run << 8) + bytestream_get_byte(&buf); |
||
136 | color = flags & 0x80 ? bytestream_get_byte(&buf) : 0; |
||
137 | } |
||
138 | |||
139 | if (run > 0 && pixel_count + run <= sub->rects[rect]->w * sub->rects[rect]->h) { |
||
140 | memset(sub->rects[rect]->pict.data[0] + pixel_count, color, run); |
||
141 | pixel_count += run; |
||
142 | } else if (!run) { |
||
143 | /* |
||
144 | * New Line. Check if correct pixels decoded, if not display warning |
||
145 | * and adjust bitmap pointer to correct new line position. |
||
146 | */ |
||
147 | if (pixel_count % sub->rects[rect]->w > 0) |
||
148 | av_log(avctx, AV_LOG_ERROR, "Decoded %d pixels, when line should be %d pixels\n", |
||
149 | pixel_count % sub->rects[rect]->w, sub->rects[rect]->w); |
||
150 | line_count++; |
||
151 | } |
||
152 | } |
||
153 | |||
154 | if (pixel_count < sub->rects[rect]->w * sub->rects[rect]->h) { |
||
155 | av_log(avctx, AV_LOG_ERROR, "Insufficient RLE data for subtitle\n"); |
||
156 | return -1; |
||
157 | } |
||
158 | |||
159 | av_dlog(avctx, "Pixel Count = %d, Area = %d\n", pixel_count, sub->rects[rect]->w * sub->rects[rect]->h); |
||
160 | |||
161 | return 0; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Parse the picture segment packet. |
||
166 | * |
||
167 | * The picture segment contains details on the sequence id, |
||
168 | * width, height and Run Length Encoded (RLE) bitmap data. |
||
169 | * |
||
170 | * @param avctx contains the current codec context |
||
171 | * @param buf pointer to the packet to process |
||
172 | * @param buf_size size of packet to process |
||
173 | * @todo TODO: Enable support for RLE data over multiple packets |
||
174 | */ |
||
175 | static int parse_picture_segment(AVCodecContext *avctx, |
||
176 | const uint8_t *buf, int buf_size) |
||
177 | { |
||
178 | PGSSubContext *ctx = avctx->priv_data; |
||
179 | |||
180 | uint8_t sequence_desc; |
||
181 | unsigned int rle_bitmap_len, width, height; |
||
182 | uint16_t picture_id; |
||
183 | |||
184 | if (buf_size <= 4) |
||
185 | return -1; |
||
186 | buf_size -= 4; |
||
187 | |||
188 | picture_id = bytestream_get_be16(&buf); |
||
189 | |||
190 | /* skip 1 unknown byte: Version Number */ |
||
191 | buf++; |
||
192 | |||
193 | /* Read the Sequence Description to determine if start of RLE data or appended to previous RLE */ |
||
194 | sequence_desc = bytestream_get_byte(&buf); |
||
195 | |||
196 | if (!(sequence_desc & 0x80)) { |
||
197 | /* Additional RLE data */ |
||
198 | if (buf_size > ctx->pictures[picture_id].rle_remaining_len) |
||
199 | return -1; |
||
200 | |||
201 | memcpy(ctx->pictures[picture_id].rle + ctx->pictures[picture_id].rle_data_len, buf, buf_size); |
||
202 | ctx->pictures[picture_id].rle_data_len += buf_size; |
||
203 | ctx->pictures[picture_id].rle_remaining_len -= buf_size; |
||
204 | |||
205 | return 0; |
||
206 | } |
||
207 | |||
208 | if (buf_size <= 7) |
||
209 | return -1; |
||
210 | buf_size -= 7; |
||
211 | |||
212 | /* Decode rle bitmap length, stored size includes width/height data */ |
||
213 | rle_bitmap_len = bytestream_get_be24(&buf) - 2*2; |
||
214 | |||
215 | /* Get bitmap dimensions from data */ |
||
216 | width = bytestream_get_be16(&buf); |
||
217 | height = bytestream_get_be16(&buf); |
||
218 | |||
219 | /* Make sure the bitmap is not too large */ |
||
220 | if (avctx->width < width || avctx->height < height) { |
||
221 | av_log(avctx, AV_LOG_ERROR, "Bitmap dimensions larger than video.\n"); |
||
222 | return -1; |
||
223 | } |
||
224 | |||
225 | if (buf_size > rle_bitmap_len) { |
||
226 | av_log(avctx, AV_LOG_ERROR, "too much RLE data\n"); |
||
227 | return AVERROR_INVALIDDATA; |
||
228 | } |
||
229 | |||
230 | ctx->pictures[picture_id].w = width; |
||
231 | ctx->pictures[picture_id].h = height; |
||
232 | |||
233 | av_fast_malloc(&ctx->pictures[picture_id].rle, &ctx->pictures[picture_id].rle_buffer_size, rle_bitmap_len); |
||
234 | |||
235 | if (!ctx->pictures[picture_id].rle) |
||
236 | return -1; |
||
237 | |||
238 | memcpy(ctx->pictures[picture_id].rle, buf, buf_size); |
||
239 | ctx->pictures[picture_id].rle_data_len = buf_size; |
||
240 | ctx->pictures[picture_id].rle_remaining_len = rle_bitmap_len - buf_size; |
||
241 | |||
242 | return 0; |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Parse the palette segment packet. |
||
247 | * |
||
248 | * The palette segment contains details of the palette, |
||
249 | * a maximum of 256 colors can be defined. |
||
250 | * |
||
251 | * @param avctx contains the current codec context |
||
252 | * @param buf pointer to the packet to process |
||
253 | * @param buf_size size of packet to process |
||
254 | */ |
||
255 | static void parse_palette_segment(AVCodecContext *avctx, |
||
256 | const uint8_t *buf, int buf_size) |
||
257 | { |
||
258 | PGSSubContext *ctx = avctx->priv_data; |
||
259 | |||
260 | const uint8_t *buf_end = buf + buf_size; |
||
261 | const uint8_t *cm = ff_cropTbl + MAX_NEG_CROP; |
||
262 | int color_id; |
||
263 | int y, cb, cr, alpha; |
||
264 | int r, g, b, r_add, g_add, b_add; |
||
265 | |||
266 | /* Skip two null bytes */ |
||
267 | buf += 2; |
||
268 | |||
269 | while (buf < buf_end) { |
||
270 | color_id = bytestream_get_byte(&buf); |
||
271 | y = bytestream_get_byte(&buf); |
||
272 | cr = bytestream_get_byte(&buf); |
||
273 | cb = bytestream_get_byte(&buf); |
||
274 | alpha = bytestream_get_byte(&buf); |
||
275 | |||
276 | YUV_TO_RGB1(cb, cr); |
||
277 | YUV_TO_RGB2(r, g, b, y); |
||
278 | |||
279 | av_dlog(avctx, "Color %d := (%d,%d,%d,%d)\n", color_id, r, g, b, alpha); |
||
280 | |||
281 | /* Store color in palette */ |
||
282 | ctx->clut[color_id] = RGBA(r,g,b,alpha); |
||
283 | } |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Parse the presentation segment packet. |
||
288 | * |
||
289 | * The presentation segment contains details on the video |
||
290 | * width, video height, x & y subtitle position. |
||
291 | * |
||
292 | * @param avctx contains the current codec context |
||
293 | * @param buf pointer to the packet to process |
||
294 | * @param buf_size size of packet to process |
||
295 | * @todo TODO: Implement cropping |
||
296 | */ |
||
297 | static void parse_presentation_segment(AVCodecContext *avctx, |
||
298 | const uint8_t *buf, int buf_size, |
||
299 | int64_t pts) |
||
300 | { |
||
301 | PGSSubContext *ctx = avctx->priv_data; |
||
302 | |||
303 | int w = bytestream_get_be16(&buf); |
||
304 | int h = bytestream_get_be16(&buf); |
||
305 | |||
306 | uint16_t object_index; |
||
307 | |||
308 | ctx->presentation.pts = pts; |
||
309 | |||
310 | av_dlog(avctx, "Video Dimensions %dx%d\n", |
||
311 | w, h); |
||
312 | if (av_image_check_size(w, h, 0, avctx) >= 0) |
||
313 | avcodec_set_dimensions(avctx, w, h); |
||
314 | |||
315 | /* Skip 1 bytes of unknown, frame rate? */ |
||
316 | buf++; |
||
317 | |||
318 | ctx->presentation.id_number = bytestream_get_be16(&buf); |
||
319 | |||
320 | /* |
||
321 | * Skip 3 bytes of unknown: |
||
322 | * state |
||
323 | * palette_update_flag (0x80), |
||
324 | * palette_id_to_use, |
||
325 | */ |
||
326 | buf += 3; |
||
327 | |||
328 | ctx->presentation.object_count = bytestream_get_byte(&buf); |
||
329 | if (!ctx->presentation.object_count) |
||
330 | return; |
||
331 | |||
332 | /* Verify that enough bytes are remaining for all of the objects. */ |
||
333 | buf_size -= 11; |
||
334 | if (buf_size < ctx->presentation.object_count * 8) { |
||
335 | ctx->presentation.object_count = 0; |
||
336 | return; |
||
337 | } |
||
338 | |||
339 | av_freep(&ctx->presentation.objects); |
||
340 | ctx->presentation.objects = av_malloc(sizeof(PGSSubPictureReference) * ctx->presentation.object_count); |
||
341 | if (!ctx->presentation.objects) { |
||
342 | ctx->presentation.object_count = 0; |
||
343 | return; |
||
344 | } |
||
345 | |||
346 | for (object_index = 0; object_index < ctx->presentation.object_count; ++object_index) { |
||
347 | PGSSubPictureReference *reference = &ctx->presentation.objects[object_index]; |
||
348 | reference->picture_id = bytestream_get_be16(&buf); |
||
349 | |||
350 | /* Skip window_id_ref */ |
||
351 | buf++; |
||
352 | /* composition_flag (0x80 - object cropped, 0x40 - object forced) */ |
||
353 | reference->composition = bytestream_get_byte(&buf); |
||
354 | |||
355 | reference->x = bytestream_get_be16(&buf); |
||
356 | reference->y = bytestream_get_be16(&buf); |
||
357 | |||
358 | /* TODO If cropping, cropping_x, cropping_y, cropping_width, cropping_height (all 2 bytes).*/ |
||
359 | av_dlog(avctx, "Subtitle Placement ID=%d, x=%d, y=%d\n", reference->picture_id, reference->x, reference->y); |
||
360 | |||
361 | if (reference->x > avctx->width || reference->y > avctx->height) { |
||
362 | av_log(avctx, AV_LOG_ERROR, "Subtitle out of video bounds. x = %d, y = %d, video width = %d, video height = %d.\n", |
||
363 | reference->x, reference->y, avctx->width, avctx->height); |
||
364 | reference->x = 0; |
||
365 | reference->y = 0; |
||
366 | } |
||
367 | } |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * Parse the display segment packet. |
||
372 | * |
||
373 | * The display segment controls the updating of the display. |
||
374 | * |
||
375 | * @param avctx contains the current codec context |
||
376 | * @param data pointer to the data pertaining the subtitle to display |
||
377 | * @param buf pointer to the packet to process |
||
378 | * @param buf_size size of packet to process |
||
379 | * @todo TODO: Fix start time, relies on correct PTS, currently too late |
||
380 | * |
||
381 | * @todo TODO: Fix end time, normally cleared by a second display |
||
382 | * @todo segment, which is currently ignored as it clears |
||
383 | * @todo the subtitle too early. |
||
384 | */ |
||
385 | static int display_end_segment(AVCodecContext *avctx, void *data, |
||
386 | const uint8_t *buf, int buf_size) |
||
387 | { |
||
388 | AVSubtitle *sub = data; |
||
389 | PGSSubContext *ctx = avctx->priv_data; |
||
390 | int64_t pts; |
||
391 | |||
392 | uint16_t rect; |
||
393 | |||
394 | /* |
||
395 | * The end display time is a timeout value and is only reached |
||
396 | * if the next subtitle is later than timeout or subtitle has |
||
397 | * not been cleared by a subsequent empty display command. |
||
398 | */ |
||
399 | |||
400 | pts = ctx->presentation.pts != AV_NOPTS_VALUE ? ctx->presentation.pts : sub->pts; |
||
401 | memset(sub, 0, sizeof(*sub)); |
||
402 | sub->pts = pts; |
||
403 | ctx->presentation.pts = AV_NOPTS_VALUE; |
||
404 | |||
405 | // Blank if last object_count was 0. |
||
406 | if (!ctx->presentation.object_count) |
||
407 | return 1; |
||
408 | |||
409 | sub->start_display_time = 0; |
||
410 | sub->end_display_time = 20000; |
||
411 | sub->format = 0; |
||
412 | |||
413 | sub->num_rects = ctx->presentation.object_count; |
||
414 | sub->rects = av_mallocz(sizeof(*sub->rects) * sub->num_rects); |
||
415 | |||
416 | for (rect = 0; rect < sub->num_rects; ++rect) { |
||
417 | uint16_t picture_id = ctx->presentation.objects[rect].picture_id; |
||
418 | sub->rects[rect] = av_mallocz(sizeof(*sub->rects[rect])); |
||
419 | sub->rects[rect]->x = ctx->presentation.objects[rect].x; |
||
420 | sub->rects[rect]->y = ctx->presentation.objects[rect].y; |
||
421 | sub->rects[rect]->w = ctx->pictures[picture_id].w; |
||
422 | sub->rects[rect]->h = ctx->pictures[picture_id].h; |
||
423 | sub->rects[rect]->type = SUBTITLE_BITMAP; |
||
424 | |||
425 | /* Process bitmap */ |
||
426 | sub->rects[rect]->pict.linesize[0] = ctx->pictures[picture_id].w; |
||
427 | if (ctx->pictures[picture_id].rle) { |
||
428 | if (ctx->pictures[picture_id].rle_remaining_len) |
||
429 | av_log(avctx, AV_LOG_ERROR, "RLE data length %u is %u bytes shorter than expected\n", |
||
430 | ctx->pictures[picture_id].rle_data_len, ctx->pictures[picture_id].rle_remaining_len); |
||
431 | if (decode_rle(avctx, sub, rect, ctx->pictures[picture_id].rle, ctx->pictures[picture_id].rle_data_len) < 0) |
||
432 | return 0; |
||
433 | } |
||
434 | |||
435 | /* Allocate memory for colors */ |
||
436 | sub->rects[rect]->nb_colors = 256; |
||
437 | sub->rects[rect]->pict.data[1] = av_mallocz(AVPALETTE_SIZE); |
||
438 | |||
439 | /* Copy the forced flag */ |
||
440 | sub->rects[rect]->flags = (ctx->presentation.objects[rect].composition & 0x40) != 0 ? AV_SUBTITLE_FLAG_FORCED : 0; |
||
441 | |||
442 | if (!ctx->forced_subs_only || ctx->presentation.objects[rect].composition & 0x40) |
||
443 | memcpy(sub->rects[rect]->pict.data[1], ctx->clut, sub->rects[rect]->nb_colors * sizeof(uint32_t)); |
||
444 | } |
||
445 | |||
446 | return 1; |
||
447 | } |
||
448 | |||
449 | static int decode(AVCodecContext *avctx, void *data, int *data_size, |
||
450 | AVPacket *avpkt) |
||
451 | { |
||
452 | const uint8_t *buf = avpkt->data; |
||
453 | int buf_size = avpkt->size; |
||
454 | AVSubtitle *sub = data; |
||
455 | |||
456 | const uint8_t *buf_end; |
||
457 | uint8_t segment_type; |
||
458 | int segment_length; |
||
459 | int i; |
||
460 | |||
461 | av_dlog(avctx, "PGS sub packet:\n"); |
||
462 | |||
463 | for (i = 0; i < buf_size; i++) { |
||
464 | av_dlog(avctx, "%02x ", buf[i]); |
||
465 | if (i % 16 == 15) |
||
466 | av_dlog(avctx, "\n"); |
||
467 | } |
||
468 | |||
469 | if (i & 15) |
||
470 | av_dlog(avctx, "\n"); |
||
471 | |||
472 | *data_size = 0; |
||
473 | |||
474 | /* Ensure that we have received at a least a segment code and segment length */ |
||
475 | if (buf_size < 3) |
||
476 | return -1; |
||
477 | |||
478 | buf_end = buf + buf_size; |
||
479 | |||
480 | /* Step through buffer to identify segments */ |
||
481 | while (buf < buf_end) { |
||
482 | segment_type = bytestream_get_byte(&buf); |
||
483 | segment_length = bytestream_get_be16(&buf); |
||
484 | |||
485 | av_dlog(avctx, "Segment Length %d, Segment Type %x\n", segment_length, segment_type); |
||
486 | |||
487 | if (segment_type != DISPLAY_SEGMENT && segment_length > buf_end - buf) |
||
488 | break; |
||
489 | |||
490 | switch (segment_type) { |
||
491 | case PALETTE_SEGMENT: |
||
492 | parse_palette_segment(avctx, buf, segment_length); |
||
493 | break; |
||
494 | case PICTURE_SEGMENT: |
||
495 | parse_picture_segment(avctx, buf, segment_length); |
||
496 | break; |
||
497 | case PRESENTATION_SEGMENT: |
||
498 | parse_presentation_segment(avctx, buf, segment_length, sub->pts); |
||
499 | break; |
||
500 | case WINDOW_SEGMENT: |
||
501 | /* |
||
502 | * Window Segment Structure (No new information provided): |
||
503 | * 2 bytes: Unknown, |
||
504 | * 2 bytes: X position of subtitle, |
||
505 | * 2 bytes: Y position of subtitle, |
||
506 | * 2 bytes: Width of subtitle, |
||
507 | * 2 bytes: Height of subtitle. |
||
508 | */ |
||
509 | break; |
||
510 | case DISPLAY_SEGMENT: |
||
511 | *data_size = display_end_segment(avctx, data, buf, segment_length); |
||
512 | break; |
||
513 | default: |
||
514 | av_log(avctx, AV_LOG_ERROR, "Unknown subtitle segment type 0x%x, length %d\n", |
||
515 | segment_type, segment_length); |
||
516 | break; |
||
517 | } |
||
518 | |||
519 | buf += segment_length; |
||
520 | } |
||
521 | |||
522 | return buf_size; |
||
523 | } |
||
524 | |||
525 | #define OFFSET(x) offsetof(PGSSubContext, x) |
||
526 | #define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM |
||
527 | static const AVOption options[] = { |
||
528 | {"forced_subs_only", "Only show forced subtitles", OFFSET(forced_subs_only), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD}, |
||
529 | { NULL }, |
||
530 | }; |
||
531 | |||
532 | static const AVClass pgsdec_class = { |
||
533 | .class_name = "PGS subtitle decoder", |
||
534 | .item_name = av_default_item_name, |
||
535 | .option = options, |
||
536 | .version = LIBAVUTIL_VERSION_INT, |
||
537 | }; |
||
538 | |||
539 | AVCodec ff_pgssub_decoder = { |
||
540 | .name = "pgssub", |
||
541 | .long_name = NULL_IF_CONFIG_SMALL("HDMV Presentation Graphic Stream subtitles"), |
||
542 | .type = AVMEDIA_TYPE_SUBTITLE, |
||
543 | .id = AV_CODEC_ID_HDMV_PGS_SUBTITLE, |
||
544 | .priv_data_size = sizeof(PGSSubContext), |
||
545 | .init = init_decoder, |
||
546 | .close = close_decoder, |
||
547 | .decode = decode, |
||
548 | .priv_class = &pgsdec_class, |
||
549 | };>>>>>>>>>>=>=>>=>><>>>>><>><>><> |