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