Mercurial > mplayer.hg
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libvo/vo_mng.c Sat May 21 15:04:50 2011 +0000 @@ -0,0 +1,620 @@ +/* + * MPlayer output to MNG file + * + * Copyright (C) 2011 Stefan Schuermans <stefan blinkenarea org> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <zlib.h> + +#define MNG_INCLUDE_WRITE_PROCS +#define MNG_ACCESS_CHUNKS +#define MNG_SUPPORT_READ +#define MNG_SUPPORT_DISPLAY +#define MNG_SUPPORT_WRITE +#include <libmng.h> + +#include "video_out.h" +#include "video_out_internal.h" +#include "mp_msg.h" + +#define VOMNG_DEFAULT_DELAY_MS (100) /* default delay of a frame */ + +static vo_info_t info = { + .name = "MNG file", + .short_name = "mng", + .author = "Stefan Schuermans <stefan blinkenarea org>" +}; + +LIBVO_EXTERN(mng) + +/* a frame to be written to the MNG file */ +struct vomng_frame { + mng_ptr data; /**< deflate compressed data, malloc-ed */ + mng_uint32 len; /**< length of compressed data */ + unsigned int time_ms; /**< timestamp of frame (in ms) */ + struct vomng_frame *next; /**< next frame */ +}; + +/* properties of MNG output */ +struct vomng_properties { + char *out_file_name; /**< name of output file, malloc-ed */ + unsigned int width, height; /**< dimensions */ + unsigned char *canvas; /**< canvas for frame, + canvas := row ... row, + row := filter_id pix ... pix, + pix := red green blue */ + struct vomng_frame *frame_first; /**< list of frames */ + struct vomng_frame *frame_last; + int is_init; /**< if initialized */ +}; + +/* private data of MNG vo module */ +static struct vomng_properties vomng; + +/** + * @brief libmng callback: allocate memory + * @param[in] size size of requested memory block + * @return pointer to memory block, which is initialized to zero + */ +static mng_ptr vomng_alloc(mng_size_t size) +{ + return calloc(1, size); +} + +/** + * @brief libmng callback: free memory + * @param[in] pointer to memory block + * @param[in] size size of requested memory block + */ +static void vomng_free(mng_ptr ptr, mng_size_t size) +{ + free(ptr); +} + +/** + * @brief libmng callback: open stream + * @param[in] mng libmng handle + * @return if stream could be opened + */ +static mng_bool vomng_openstream(mng_handle mng) +{ + return MNG_TRUE; /* stream is always open wen we get here, + tell libmng that everything is okay */ +} + +/** + * @brief libmng callback: stream should be closed + * @param[in] mng libmng handle + * @return if stream could be closed + */ +static mng_bool vomng_closestream(mng_handle mng) +{ + return MNG_TRUE; /* stream will be closed later, + tell libmng that everything is okay */ +} + +/** + * @brief libmng callback: write libmng data to the open stream + * @param[in] mng libmng handle + * @param[in] *buf pointer to data to write + * @param[in] size size of data to write + * @param[out] *written size of data written + * @return if data was written successfully + */ +static mng_bool vomng_writedata(mng_handle mng, mng_ptr buf, + mng_uint32 size, mng_uint32 *written) +{ + FILE *file = mng_get_userdata(mng); + *written = fwrite(buf, 1, size, file); + /* according to the example in libmng documentation, true is always + returned here, short writes can be detected by libmng via *written */ + return MNG_TRUE; +} + +/** + * @brief compress frame data + * @param[in] width width of canvas + * @param[in] height height of canvas + * @param[in] *canvas data on canvas (including MNG filter IDs) + * @param[out] *out_ptr pointer to compressed data, malloc-ed + * @param[out] *out_len length of compressed data + */ +static void vomng_canvas_to_compressed(unsigned int width, unsigned int height, + const unsigned char *canvas, + mng_ptr *out_ptr, mng_uint32 *out_len) +{ + mng_uint32 raw_len; + unsigned char *ptr; + unsigned long len; + + /* default: no output */ + *out_ptr = NULL; + *out_len = 0; + + /* raw_len := length of input data + - it will be significantly shorter than 32 bit + - the "1 +" is needed because each row starts with the filter ID */ + raw_len = height * (1 + width * 3); + + /* compress data + - compress2 output size will be smaller than raw_len * 1.001 + 12 (see + man page), so calculate the next larger integer value in len and + allocate abuffer of this size + - len will still contain a value shorter than 32 bit */ + len = raw_len + (raw_len + 999) / 1000 + 12; + ptr = malloc(len); + if (!ptr) + return; + compress2(ptr, &len, canvas, raw_len, Z_BEST_COMPRESSION); + + /* return allocated compressed data + - we have to convert the output length to a shorter data type as + libmng does not accept an unsigned long as length + - convert here, because we can see here that the conversion is safe + - see comments about raw_len and len above + - compress2 never increases value of len */ + *out_ptr = ptr; + *out_len = len; +} + +/** + * @brief write frame to MNG file + * @param[in] *frame the frame to write to MNG file + * @param[in] mng libmng handle + * @param[in] width width of canvas + * @param[in] height height of canvas + * @param[in] first_frame if the frame is the first one in the file + * @return 0 on success, 1 on error + */ +static int vomng_write_frame(struct vomng_frame *frame, mng_handle mng, + unsigned int width, unsigned int height, + int first_frame) +{ + unsigned int delay_ms; + + /* determine delay */ + if (frame->next) + delay_ms = frame->next->time_ms - frame->time_ms; + else + delay_ms = VOMNG_DEFAULT_DELAY_MS; /* default delay for last frame */ + + /* write frame headers to MNG file */ + if (mng_putchunk_seek(mng, 0, MNG_NULL)) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing SEEK chunk failed\n"); + return 1; + } + if (mng_putchunk_fram(mng, MNG_FALSE, + /* keep canvas if not 1st frame */ + first_frame ? MNG_FRAMINGMODE_1 + : MNG_FRAMINGMODE_NOCHANGE, + 0, MNG_NULL, /* no frame name */ + MNG_CHANGEDELAY_DEFAULT, /* change only delay */ + MNG_CHANGETIMOUT_NO, + MNG_CHANGECLIPPING_NO, + MNG_CHANGESYNCID_NO, + delay_ms, /* new delay */ + 0, /* no new timeout */ + 0, 0, 0, 0, 0, /* no new boundary */ + 0, 0)) { /* no count, no IDs */ + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing FRAM chunk failed\n"); + return 1; + } + if (mng_putchunk_defi(mng, 0, /* no ID */ + MNG_DONOTSHOW_VISIBLE, + MNG_ABSTRACT, + MNG_TRUE, 0, 0, /* top left location */ + MNG_FALSE, 0, 0, 0, 0)) { /* no clipping */ + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing DEFI chunk failed\n"); + return 1; + } + if (mng_putchunk_ihdr(mng, width, height, /* dimensions */ + 8, MNG_COLORTYPE_RGB, /* RBG */ + MNG_COMPRESSION_DEFLATE, + MNG_FILTER_ADAPTIVE, + MNG_INTERLACE_NONE)) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IHDR chunk failed\n"); + return 1; + } + + /* write frame data */ + if (mng_putchunk_idat(mng, frame->len, frame->data)) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IDAT chunk failed\n"); + return 1; + } + + /* write frame footers to MNG file */ + if (mng_putchunk_iend(mng)) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IEND chunk failed\n"); + return 1; + } + + return 0; +} + +/** + * @brief write buffered frames to MNG file + * @return 0 on success, 1 on error + */ +static int vomng_write_file(void) +{ + FILE *file; + mng_handle mng; + struct vomng_frame *frame; + unsigned int frames, duration_ms; + int first; + + /* refuse to create empty MNG file */ + if (!vomng.frame_first || !vomng.frame_last) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: not creating empty file\n"); + return 1; + } + + /* create output file */ + file = fopen(vomng.out_file_name, "wb"); + if (!file) { + mp_msg(MSGT_VO, MSGL_ERR, + "vomng: could not open output file \"%s\": %s\n", + vomng.out_file_name, strerror(errno)); + return 1; + } + + /* inititalize MNG library */ + mng = mng_initialize(file, vomng_alloc, vomng_free, MNG_NULL); + if (!mng) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: could not initialize libmng\n"); + fclose(file); + return 1; + } + if (mng_setcb_openstream (mng, vomng_openstream ) || + mng_setcb_closestream(mng, vomng_closestream) || + mng_setcb_writedata (mng, vomng_writedata )) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot set callbacks for libmng\n"); + mng_cleanup(&mng); + fclose(file); + return 1; + } + + /* create new MNG image in memory */ + if (mng_create(mng)) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot create MNG image in memory\n"); + mng_cleanup(&mng); + fclose(file); + return 1; + } + + /* determine number of frames and total duration */ + frames = 0; + for (frame = vomng.frame_first; frame; frame = frame->next) + frames++; + duration_ms = vomng.frame_last->time_ms - vomng.frame_first->time_ms; + + /* write MNG header chunks */ + if (mng_putchunk_mhdr(mng, + vomng.width, /* dimensions */ + vomng.height, + 1000, 0, /* ticks per second, layer */ + frames, /* number of frames */ + duration_ms, /* total duration */ + MNG_SIMPLICITY_VALID | + MNG_SIMPLICITY_SIMPLEFEATURES | + MNG_SIMPLICITY_COMPLEXFEATURES) || + mng_putchunk_save(mng, + MNG_TRUE, 0, 0) || /* empty save chunk */ + mng_putchunk_term(mng, + MNG_TERMACTION_CLEAR, /* show last frame forever */ + MNG_ITERACTION_CLEAR, + 0, 0)) { + mp_msg(MSGT_VO, MSGL_ERR, + "vomng: writing MHDR/SAVE/TERM chunks failed\n"); + mng_write(mng); /* write out buffered chunks before cleanup */ + mng_cleanup(&mng); + fclose(file); + return 1; + } + + /* write frames */ + first = 1; + for (frame = vomng.frame_first; frame; frame = frame->next) { + if (vomng_write_frame(frame, mng, vomng.width, vomng.height, first)) + break; + first = 0; + } + if (frame) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing frames failed\n"); + mng_write(mng); /* write out buffered chunks before cleanup */ + mng_cleanup(&mng); + fclose(file); + return 1; + } + + /* write MNG end chunk */ + if (mng_putchunk_mend(mng)) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing end chunk failed\n"); + mng_write(mng); /* write out buffered chunks before cleanup */ + mng_cleanup(&mng); + fclose(file); + return 1; + } + + /* finish and cleanup */ + mng_write(mng); /* write out buffered chunks before cleanup */ + mng_cleanup(&mng); + fclose(file); + + return 0; +} + +/** + * @brief close all files and free all memory of MNG vo module + */ +static void vomng_prop_reset(void) +{ + struct vomng_frame *frame, *next; + + /* we are initialized properly */ + if (vomng.is_init) { + /* write buffered frames to MNG file */ + if (vomng_write_file()) + mp_msg(MSGT_VO, MSGL_ERR, + "vomng: writing output file failed\n"); + } + + /* reset state */ + vomng.is_init = 0; + if (vomng.frame_first) { + frame = vomng.frame_first; + while (frame) { + next = frame->next; + free(frame->data); + free(frame); + frame = next; + } + vomng.frame_first = NULL; + vomng.frame_last = NULL; + } + free(vomng.canvas); + vomng.canvas = NULL; + vomng.width = 0; + vomng.height = 0; +} + +/** + * @brief close files, free memory and delete private data of MNG von module + */ +static void vomng_prop_cleanup(void) +{ + vomng_prop_reset(); + free(vomng.out_file_name); +} + +/** + * @brief configure MNG vo module + * @param[in] width video width + * @param[in] height video height + * @param[in] d_width (unused) + * @param[in] d_height (unused) + * @param[in] flags (unused) + * @param[in] title (unused) + * @param[in] format video frame format + * @return 0 on success, 1 on error + */ +static int config(uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, + uint32_t flags, char *title, uint32_t format) +{ + uint32_t row_stride, y; + + /* reset state */ + vomng_prop_reset(); + + /* check format */ + if (format != IMGFMT_RGB24) { + mp_msg(MSGT_VO, MSGL_ERR, + "vomng: config with invalid format (!= IMGFMT_RGB24)\n"); + return 1; + } + + /* allocate canvas */ + vomng.width = width; + vomng.height = height; + row_stride = 1 + width * 3; /* rows contain filter IDs */ + vomng.canvas = calloc(height * row_stride, 1); + if (!vomng.canvas) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n"); + return 1; + } + /* fill in filter IDs for rows */ + for (y = 0; y < height; y++) + *(vomng.canvas + row_stride * y) = MNG_FILTER_NONE; + + /* we are initialized */ + vomng.is_init = 1; + + return 0; +} + +/** + * @brief draw on screen display (unsupported for MNG vo module) + */ +static void draw_osd(void) +{ +} + +/** + * @brief display data currently on canvas + */ +static void flip_page(void) +{ + unsigned int last_ms; + struct vomng_frame *frame; + + /* get time of last frame in ms + (intensive testing showed that the time obtained from vo_pts + is the time of the previous frame) */ + last_ms = (unsigned int)(vo_pts / 90.0 + 0.5); + + /* set time of last frame */ + if (vomng.frame_last) + vomng.frame_last->time_ms = last_ms; + + /* create new frame */ + frame = calloc(1, sizeof(*frame)); + if (!frame) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n"); + return; + } + /* time of frame is not yet known (see comment about vo_pts about 20 + lines above), approximate time using time of last frame and the + default frame delay */ + frame->time_ms = last_ms + VOMNG_DEFAULT_DELAY_MS; + frame->next = NULL; + + /* compress canvas data */ + vomng_canvas_to_compressed(vomng.width, vomng.height, vomng.canvas, + &frame->data, &frame->len); + if (!frame->data) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: compressing frame failed\n"); + free(frame); + return; + } + + /* add frame to list */ + if (!vomng.frame_first || !vomng.frame_last) { + vomng.frame_first = frame; + vomng.frame_last = frame; + } else { + vomng.frame_last->next = frame; + vomng.frame_last = frame; + } +} + +/** + * @brief put frame data onto canvas (not supported) + * @return always 1 to indicate error + */ +static int draw_frame(uint8_t *src[]) +{ + /* draw_frame() not supported + * VFCAP_ACCEPT_STRIDE is set for format + * so draw_slice() will be called instead of this function */ + return 1; +} + +/** + * @brief deinitialize MNG vo module + */ +static void uninit(void) +{ + vomng_prop_cleanup(); +} + +/** + * @brief deal with events (not supported) + */ +static void check_events(void) +{ +} + +/** + * @brief put a slice of frame data onto canvas + * @param[in] srcimg pointer to data + * @param[in] stride line stride in data + * @param[in] wf frame slice width + * @param[in] hf frame slice height + * @param[in] xf leftmost x coordinate of frame slice + * @param[in] yf topmost y coordinate of frame slice + * @return always 0 to indicate success + */ +static int draw_slice(uint8_t *srcimg[], int stride[], + int wf, int hf, int xf, int yf) +{ + uint8_t *line_ptr; + int line_len, row_stride, y; + + /* put pixel data from slice to canvas */ + line_ptr = srcimg[0]; + line_len = stride[0]; + row_stride = 1 + vomng.width * 3; /* rows contain filter IDs */ + for (y = 0; y < hf; y++) + memcpy(vomng.canvas + (yf + y) * row_stride + 1 + xf * 3, + line_ptr + y * line_len, wf * 3); + + return 0; +} + +/** + * @brief pre-initialize MNG vo module + * @param[in] *arg arguments passed to MNG vo module (output file name) + * @return 0 on success, 1 on error + */ +static int preinit(const char *arg) +{ + /* get name of output file */ + if (!arg || !*arg) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: MNG output file must be given," + " example: -vo mng:output.mng\n"); + vomng_prop_cleanup(); + return 1; + } + vomng.out_file_name = strdup(arg); + if (!vomng.out_file_name) { + mp_msg(MSGT_VO, MSGL_ERR, "vomng: out of memory\n"); + vomng_prop_cleanup(); + return 1; + } + + return 0; +} + +/** + * @brief get supported formats + * @param[in] format format to check support for + * @return acceptance flags + */ +static int query_format(uint32_t format) +{ + if (format == IMGFMT_RGB24) + return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | + VFCAP_ACCEPT_STRIDE; + return 0; +} + +/** + * @brief handle control stuff + * @param[in] request control request + * @param[in] *data data (dependent on control request) + * @return response to control request + */ +static int control(uint32_t request, void *data) +{ + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(*((uint32_t *)data)); + } + return VO_NOTIMPL; +} +