comparison libvo/vo_mng.c @ 33389:2672587086ad

Add MNG output support. Patch by Stefan Schuermans, stefan blinkenarea dot org.
author cboesch
date Sat, 21 May 2011 15:04:50 +0000
parents
children cc3071a7c057
comparison
equal deleted inserted replaced
33388:25880d57740d 33389:2672587086ad
1 /*
2 * MPlayer output to MNG file
3 *
4 * Copyright (C) 2011 Stefan Schuermans <stefan blinkenarea org>
5 *
6 * This file is part of MPlayer.
7 *
8 * MPlayer is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * MPlayer 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 General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30
31 #include <zlib.h>
32
33 #define MNG_INCLUDE_WRITE_PROCS
34 #define MNG_ACCESS_CHUNKS
35 #define MNG_SUPPORT_READ
36 #define MNG_SUPPORT_DISPLAY
37 #define MNG_SUPPORT_WRITE
38 #include <libmng.h>
39
40 #include "video_out.h"
41 #include "video_out_internal.h"
42 #include "mp_msg.h"
43
44 #define VOMNG_DEFAULT_DELAY_MS (100) /* default delay of a frame */
45
46 static vo_info_t info = {
47 .name = "MNG file",
48 .short_name = "mng",
49 .author = "Stefan Schuermans <stefan blinkenarea org>"
50 };
51
52 LIBVO_EXTERN(mng)
53
54 /* a frame to be written to the MNG file */
55 struct vomng_frame {
56 mng_ptr data; /**< deflate compressed data, malloc-ed */
57 mng_uint32 len; /**< length of compressed data */
58 unsigned int time_ms; /**< timestamp of frame (in ms) */
59 struct vomng_frame *next; /**< next frame */
60 };
61
62 /* properties of MNG output */
63 struct vomng_properties {
64 char *out_file_name; /**< name of output file, malloc-ed */
65 unsigned int width, height; /**< dimensions */
66 unsigned char *canvas; /**< canvas for frame,
67 canvas := row ... row,
68 row := filter_id pix ... pix,
69 pix := red green blue */
70 struct vomng_frame *frame_first; /**< list of frames */
71 struct vomng_frame *frame_last;
72 int is_init; /**< if initialized */
73 };
74
75 /* private data of MNG vo module */
76 static struct vomng_properties vomng;
77
78 /**
79 * @brief libmng callback: allocate memory
80 * @param[in] size size of requested memory block
81 * @return pointer to memory block, which is initialized to zero
82 */
83 static mng_ptr vomng_alloc(mng_size_t size)
84 {
85 return calloc(1, size);
86 }
87
88 /**
89 * @brief libmng callback: free memory
90 * @param[in] pointer to memory block
91 * @param[in] size size of requested memory block
92 */
93 static void vomng_free(mng_ptr ptr, mng_size_t size)
94 {
95 free(ptr);
96 }
97
98 /**
99 * @brief libmng callback: open stream
100 * @param[in] mng libmng handle
101 * @return if stream could be opened
102 */
103 static mng_bool vomng_openstream(mng_handle mng)
104 {
105 return MNG_TRUE; /* stream is always open wen we get here,
106 tell libmng that everything is okay */
107 }
108
109 /**
110 * @brief libmng callback: stream should be closed
111 * @param[in] mng libmng handle
112 * @return if stream could be closed
113 */
114 static mng_bool vomng_closestream(mng_handle mng)
115 {
116 return MNG_TRUE; /* stream will be closed later,
117 tell libmng that everything is okay */
118 }
119
120 /**
121 * @brief libmng callback: write libmng data to the open stream
122 * @param[in] mng libmng handle
123 * @param[in] *buf pointer to data to write
124 * @param[in] size size of data to write
125 * @param[out] *written size of data written
126 * @return if data was written successfully
127 */
128 static mng_bool vomng_writedata(mng_handle mng, mng_ptr buf,
129 mng_uint32 size, mng_uint32 *written)
130 {
131 FILE *file = mng_get_userdata(mng);
132 *written = fwrite(buf, 1, size, file);
133 /* according to the example in libmng documentation, true is always
134 returned here, short writes can be detected by libmng via *written */
135 return MNG_TRUE;
136 }
137
138 /**
139 * @brief compress frame data
140 * @param[in] width width of canvas
141 * @param[in] height height of canvas
142 * @param[in] *canvas data on canvas (including MNG filter IDs)
143 * @param[out] *out_ptr pointer to compressed data, malloc-ed
144 * @param[out] *out_len length of compressed data
145 */
146 static void vomng_canvas_to_compressed(unsigned int width, unsigned int height,
147 const unsigned char *canvas,
148 mng_ptr *out_ptr, mng_uint32 *out_len)
149 {
150 mng_uint32 raw_len;
151 unsigned char *ptr;
152 unsigned long len;
153
154 /* default: no output */
155 *out_ptr = NULL;
156 *out_len = 0;
157
158 /* raw_len := length of input data
159 - it will be significantly shorter than 32 bit
160 - the "1 +" is needed because each row starts with the filter ID */
161 raw_len = height * (1 + width * 3);
162
163 /* compress data
164 - compress2 output size will be smaller than raw_len * 1.001 + 12 (see
165 man page), so calculate the next larger integer value in len and
166 allocate abuffer of this size
167 - len will still contain a value shorter than 32 bit */
168 len = raw_len + (raw_len + 999) / 1000 + 12;
169 ptr = malloc(len);
170 if (!ptr)
171 return;
172 compress2(ptr, &len, canvas, raw_len, Z_BEST_COMPRESSION);
173
174 /* return allocated compressed data
175 - we have to convert the output length to a shorter data type as
176 libmng does not accept an unsigned long as length
177 - convert here, because we can see here that the conversion is safe
178 - see comments about raw_len and len above
179 - compress2 never increases value of len */
180 *out_ptr = ptr;
181 *out_len = len;
182 }
183
184 /**
185 * @brief write frame to MNG file
186 * @param[in] *frame the frame to write to MNG file
187 * @param[in] mng libmng handle
188 * @param[in] width width of canvas
189 * @param[in] height height of canvas
190 * @param[in] first_frame if the frame is the first one in the file
191 * @return 0 on success, 1 on error
192 */
193 static int vomng_write_frame(struct vomng_frame *frame, mng_handle mng,
194 unsigned int width, unsigned int height,
195 int first_frame)
196 {
197 unsigned int delay_ms;
198
199 /* determine delay */
200 if (frame->next)
201 delay_ms = frame->next->time_ms - frame->time_ms;
202 else
203 delay_ms = VOMNG_DEFAULT_DELAY_MS; /* default delay for last frame */
204
205 /* write frame headers to MNG file */
206 if (mng_putchunk_seek(mng, 0, MNG_NULL)) {
207 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing SEEK chunk failed\n");
208 return 1;
209 }
210 if (mng_putchunk_fram(mng, MNG_FALSE,
211 /* keep canvas if not 1st frame */
212 first_frame ? MNG_FRAMINGMODE_1
213 : MNG_FRAMINGMODE_NOCHANGE,
214 0, MNG_NULL, /* no frame name */
215 MNG_CHANGEDELAY_DEFAULT, /* change only delay */
216 MNG_CHANGETIMOUT_NO,
217 MNG_CHANGECLIPPING_NO,
218 MNG_CHANGESYNCID_NO,
219 delay_ms, /* new delay */
220 0, /* no new timeout */
221 0, 0, 0, 0, 0, /* no new boundary */
222 0, 0)) { /* no count, no IDs */
223 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing FRAM chunk failed\n");
224 return 1;
225 }
226 if (mng_putchunk_defi(mng, 0, /* no ID */
227 MNG_DONOTSHOW_VISIBLE,
228 MNG_ABSTRACT,
229 MNG_TRUE, 0, 0, /* top left location */
230 MNG_FALSE, 0, 0, 0, 0)) { /* no clipping */
231 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing DEFI chunk failed\n");
232 return 1;
233 }
234 if (mng_putchunk_ihdr(mng, width, height, /* dimensions */
235 8, MNG_COLORTYPE_RGB, /* RBG */
236 MNG_COMPRESSION_DEFLATE,
237 MNG_FILTER_ADAPTIVE,
238 MNG_INTERLACE_NONE)) {
239 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IHDR chunk failed\n");
240 return 1;
241 }
242
243 /* write frame data */
244 if (mng_putchunk_idat(mng, frame->len, frame->data)) {
245 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IDAT chunk failed\n");
246 return 1;
247 }
248
249 /* write frame footers to MNG file */
250 if (mng_putchunk_iend(mng)) {
251 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IEND chunk failed\n");
252 return 1;
253 }
254
255 return 0;
256 }
257
258 /**
259 * @brief write buffered frames to MNG file
260 * @return 0 on success, 1 on error
261 */
262 static int vomng_write_file(void)
263 {
264 FILE *file;
265 mng_handle mng;
266 struct vomng_frame *frame;
267 unsigned int frames, duration_ms;
268 int first;
269
270 /* refuse to create empty MNG file */
271 if (!vomng.frame_first || !vomng.frame_last) {
272 mp_msg(MSGT_VO, MSGL_ERR, "vomng: not creating empty file\n");
273 return 1;
274 }
275
276 /* create output file */
277 file = fopen(vomng.out_file_name, "wb");
278 if (!file) {
279 mp_msg(MSGT_VO, MSGL_ERR,
280 "vomng: could not open output file \"%s\": %s\n",
281 vomng.out_file_name, strerror(errno));
282 return 1;
283 }
284
285 /* inititalize MNG library */
286 mng = mng_initialize(file, vomng_alloc, vomng_free, MNG_NULL);
287 if (!mng) {
288 mp_msg(MSGT_VO, MSGL_ERR, "vomng: could not initialize libmng\n");
289 fclose(file);
290 return 1;
291 }
292 if (mng_setcb_openstream (mng, vomng_openstream ) ||
293 mng_setcb_closestream(mng, vomng_closestream) ||
294 mng_setcb_writedata (mng, vomng_writedata )) {
295 mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot set callbacks for libmng\n");
296 mng_cleanup(&mng);
297 fclose(file);
298 return 1;
299 }
300
301 /* create new MNG image in memory */
302 if (mng_create(mng)) {
303 mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot create MNG image in memory\n");
304 mng_cleanup(&mng);
305 fclose(file);
306 return 1;
307 }
308
309 /* determine number of frames and total duration */
310 frames = 0;
311 for (frame = vomng.frame_first; frame; frame = frame->next)
312 frames++;
313 duration_ms = vomng.frame_last->time_ms - vomng.frame_first->time_ms;
314
315 /* write MNG header chunks */
316 if (mng_putchunk_mhdr(mng,
317 vomng.width, /* dimensions */
318 vomng.height,
319 1000, 0, /* ticks per second, layer */
320 frames, /* number of frames */
321 duration_ms, /* total duration */
322 MNG_SIMPLICITY_VALID |
323 MNG_SIMPLICITY_SIMPLEFEATURES |
324 MNG_SIMPLICITY_COMPLEXFEATURES) ||
325 mng_putchunk_save(mng,
326 MNG_TRUE, 0, 0) || /* empty save chunk */
327 mng_putchunk_term(mng,
328 MNG_TERMACTION_CLEAR, /* show last frame forever */
329 MNG_ITERACTION_CLEAR,
330 0, 0)) {
331 mp_msg(MSGT_VO, MSGL_ERR,
332 "vomng: writing MHDR/SAVE/TERM chunks failed\n");
333 mng_write(mng); /* write out buffered chunks before cleanup */
334 mng_cleanup(&mng);
335 fclose(file);
336 return 1;
337 }
338
339 /* write frames */
340 first = 1;
341 for (frame = vomng.frame_first; frame; frame = frame->next) {
342 if (vomng_write_frame(frame, mng, vomng.width, vomng.height, first))
343 break;
344 first = 0;
345 }
346 if (frame) {
347 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing frames failed\n");
348 mng_write(mng); /* write out buffered chunks before cleanup */
349 mng_cleanup(&mng);
350 fclose(file);
351 return 1;
352 }
353
354 /* write MNG end chunk */
355 if (mng_putchunk_mend(mng)) {
356 mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing end chunk failed\n");
357 mng_write(mng); /* write out buffered chunks before cleanup */
358 mng_cleanup(&mng);
359 fclose(file);
360 return 1;
361 }
362
363 /* finish and cleanup */
364 mng_write(mng); /* write out buffered chunks before cleanup */
365 mng_cleanup(&mng);
366 fclose(file);
367
368 return 0;
369 }
370
371 /**
372 * @brief close all files and free all memory of MNG vo module
373 */
374 static void vomng_prop_reset(void)
375 {
376 struct vomng_frame *frame, *next;
377
378 /* we are initialized properly */
379 if (vomng.is_init) {
380 /* write buffered frames to MNG file */
381 if (vomng_write_file())
382 mp_msg(MSGT_VO, MSGL_ERR,
383 "vomng: writing output file failed\n");
384 }
385
386 /* reset state */
387 vomng.is_init = 0;
388 if (vomng.frame_first) {
389 frame = vomng.frame_first;
390 while (frame) {
391 next = frame->next;
392 free(frame->data);
393 free(frame);
394 frame = next;
395 }
396 vomng.frame_first = NULL;
397 vomng.frame_last = NULL;
398 }
399 free(vomng.canvas);
400 vomng.canvas = NULL;
401 vomng.width = 0;
402 vomng.height = 0;
403 }
404
405 /**
406 * @brief close files, free memory and delete private data of MNG von module
407 */
408 static void vomng_prop_cleanup(void)
409 {
410 vomng_prop_reset();
411 free(vomng.out_file_name);
412 }
413
414 /**
415 * @brief configure MNG vo module
416 * @param[in] width video width
417 * @param[in] height video height
418 * @param[in] d_width (unused)
419 * @param[in] d_height (unused)
420 * @param[in] flags (unused)
421 * @param[in] title (unused)
422 * @param[in] format video frame format
423 * @return 0 on success, 1 on error
424 */
425 static int config(uint32_t width, uint32_t height,
426 uint32_t d_width, uint32_t d_height,
427 uint32_t flags, char *title, uint32_t format)
428 {
429 uint32_t row_stride, y;
430
431 /* reset state */
432 vomng_prop_reset();
433
434 /* check format */
435 if (format != IMGFMT_RGB24) {
436 mp_msg(MSGT_VO, MSGL_ERR,
437 "vomng: config with invalid format (!= IMGFMT_RGB24)\n");
438 return 1;
439 }
440
441 /* allocate canvas */
442 vomng.width = width;
443 vomng.height = height;
444 row_stride = 1 + width * 3; /* rows contain filter IDs */
445 vomng.canvas = calloc(height * row_stride, 1);
446 if (!vomng.canvas) {
447 mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n");
448 return 1;
449 }
450 /* fill in filter IDs for rows */
451 for (y = 0; y < height; y++)
452 *(vomng.canvas + row_stride * y) = MNG_FILTER_NONE;
453
454 /* we are initialized */
455 vomng.is_init = 1;
456
457 return 0;
458 }
459
460 /**
461 * @brief draw on screen display (unsupported for MNG vo module)
462 */
463 static void draw_osd(void)
464 {
465 }
466
467 /**
468 * @brief display data currently on canvas
469 */
470 static void flip_page(void)
471 {
472 unsigned int last_ms;
473 struct vomng_frame *frame;
474
475 /* get time of last frame in ms
476 (intensive testing showed that the time obtained from vo_pts
477 is the time of the previous frame) */
478 last_ms = (unsigned int)(vo_pts / 90.0 + 0.5);
479
480 /* set time of last frame */
481 if (vomng.frame_last)
482 vomng.frame_last->time_ms = last_ms;
483
484 /* create new frame */
485 frame = calloc(1, sizeof(*frame));
486 if (!frame) {
487 mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n");
488 return;
489 }
490 /* time of frame is not yet known (see comment about vo_pts about 20
491 lines above), approximate time using time of last frame and the
492 default frame delay */
493 frame->time_ms = last_ms + VOMNG_DEFAULT_DELAY_MS;
494 frame->next = NULL;
495
496 /* compress canvas data */
497 vomng_canvas_to_compressed(vomng.width, vomng.height, vomng.canvas,
498 &frame->data, &frame->len);
499 if (!frame->data) {
500 mp_msg(MSGT_VO, MSGL_ERR, "vomng: compressing frame failed\n");
501 free(frame);
502 return;
503 }
504
505 /* add frame to list */
506 if (!vomng.frame_first || !vomng.frame_last) {
507 vomng.frame_first = frame;
508 vomng.frame_last = frame;
509 } else {
510 vomng.frame_last->next = frame;
511 vomng.frame_last = frame;
512 }
513 }
514
515 /**
516 * @brief put frame data onto canvas (not supported)
517 * @return always 1 to indicate error
518 */
519 static int draw_frame(uint8_t *src[])
520 {
521 /* draw_frame() not supported
522 * VFCAP_ACCEPT_STRIDE is set for format
523 * so draw_slice() will be called instead of this function */
524 return 1;
525 }
526
527 /**
528 * @brief deinitialize MNG vo module
529 */
530 static void uninit(void)
531 {
532 vomng_prop_cleanup();
533 }
534
535 /**
536 * @brief deal with events (not supported)
537 */
538 static void check_events(void)
539 {
540 }
541
542 /**
543 * @brief put a slice of frame data onto canvas
544 * @param[in] srcimg pointer to data
545 * @param[in] stride line stride in data
546 * @param[in] wf frame slice width
547 * @param[in] hf frame slice height
548 * @param[in] xf leftmost x coordinate of frame slice
549 * @param[in] yf topmost y coordinate of frame slice
550 * @return always 0 to indicate success
551 */
552 static int draw_slice(uint8_t *srcimg[], int stride[],
553 int wf, int hf, int xf, int yf)
554 {
555 uint8_t *line_ptr;
556 int line_len, row_stride, y;
557
558 /* put pixel data from slice to canvas */
559 line_ptr = srcimg[0];
560 line_len = stride[0];
561 row_stride = 1 + vomng.width * 3; /* rows contain filter IDs */
562 for (y = 0; y < hf; y++)
563 memcpy(vomng.canvas + (yf + y) * row_stride + 1 + xf * 3,
564 line_ptr + y * line_len, wf * 3);
565
566 return 0;
567 }
568
569 /**
570 * @brief pre-initialize MNG vo module
571 * @param[in] *arg arguments passed to MNG vo module (output file name)
572 * @return 0 on success, 1 on error
573 */
574 static int preinit(const char *arg)
575 {
576 /* get name of output file */
577 if (!arg || !*arg) {
578 mp_msg(MSGT_VO, MSGL_ERR, "vomng: MNG output file must be given,"
579 " example: -vo mng:output.mng\n");
580 vomng_prop_cleanup();
581 return 1;
582 }
583 vomng.out_file_name = strdup(arg);
584 if (!vomng.out_file_name) {
585 mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n");
586 vomng_prop_cleanup();
587 return 1;
588 }
589
590 return 0;
591 }
592
593 /**
594 * @brief get supported formats
595 * @param[in] format format to check support for
596 * @return acceptance flags
597 */
598 static int query_format(uint32_t format)
599 {
600 if (format == IMGFMT_RGB24)
601 return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW |
602 VFCAP_ACCEPT_STRIDE;
603 return 0;
604 }
605
606 /**
607 * @brief handle control stuff
608 * @param[in] request control request
609 * @param[in] *data data (dependent on control request)
610 * @return response to control request
611 */
612 static int control(uint32_t request, void *data)
613 {
614 switch (request) {
615 case VOCTRL_QUERY_FORMAT:
616 return query_format(*((uint32_t *)data));
617 }
618 return VO_NOTIMPL;
619 }
620