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