From c612907d1ca9f9151c245c16348690da0f0f16d4 Mon Sep 17 00:00:00 2001 From: diego Date: Sun, 30 Nov 2008 13:22:34 +0000 Subject: MNG demuxer by Stefan Schuermans, stefan blinkenarea org git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@28052 b3059339-0415-0410-9bf9-f77b7e298cf2 --- libmpdemux/demux_mng.c | 625 +++++++++++++++++++++++++++++++++++++++++++++++++ libmpdemux/demuxer.c | 4 + libmpdemux/demuxer.h | 3 +- libmpdemux/video.c | 1 + 4 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 libmpdemux/demux_mng.c (limited to 'libmpdemux') diff --git a/libmpdemux/demux_mng.c b/libmpdemux/demux_mng.c new file mode 100644 index 0000000000..9d6005f44e --- /dev/null +++ b/libmpdemux/demux_mng.c @@ -0,0 +1,625 @@ +/* + * MNG file demuxer for MPlayer + * + * Copyright (C) 2008 Stefan Schuermans + * + * 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 +#include +#include + +#include "config.h" + +#include "mp_msg.h" +#include "help_mp.h" + +#include "stream/stream.h" +#include "demuxer.h" +#include "stheader.h" + +#define MNG_SUPPORT_READ +#define MNG_SUPPORT_DISPLAY +#include + +/** + * \brief some small fixed start time > 0 + * + * Start time must be > 0 for the variable frame time mechanism + * (GIF, MATROSKA, MNG) in video.c to work for the first frame. + */ +#define MNG_START_PTS 0.01f + +/** + * \brief private context structure + * + * This structure is used as private data for MPlayer demuxer + * and also as private data for the MNG library. + * + * All members ending in \p _ms are in milliseconds + */ +typedef struct { + stream_t * stream; ///< pointer to MNG data input stream + mng_handle h_mng; ///< MNG library image handle + int header_processed; ///< if MNG image header is processed + mng_uint32 width; ///< MNG image width + mng_uint32 height; ///< MNG image height + int total_time_ms; ///< total MNG animation time + unsigned char * canvas; /**< \brief canvas to draw the image onto + * \details + * \li lines top-down + * \li pixels left-to-right + * \li channels RGB + * \li no padding + * \li NULL if no canvas yet + */ + int displaying; /**< \brief if displaying already, + * i.e. if mng_display has + * already been called + */ + int finished; ///< if animation is finished + int global_time_ms; ///< current global time for MNG library + int anim_cur_time_ms; ///< current frame time in MNG animation + int anim_frame_duration_ms; ///< current frame duration in MNG animation + int show_cur_time_ms; /**< \brief current time in the show process, + * i.e. time of last demux packet + */ + int show_next_time_ms; /**< \brief next time in the show process, + * i.e. time of next demux packet + */ + int timer_ms; /**< \brief number of milliseconds after which + * libmng wants to be called again + */ +} mng_priv_t; + +/** + * \brief MNG library callback: Allocate a new zero-filled memory block. + * \param[in] size memory block size + * \return pointer to new memory block + */ +static mng_ptr demux_mng_alloc(mng_size_t size) +{ + return calloc(1, size); +} + +/** + * \brief MNG library callback: Free memory block. + * \param[in] ptr pointer to memory block + * \param[in] size memory block size + */ +static void demux_mng_free(mng_ptr ptr, mng_size_t size) +{ + free(ptr); +} + +/** + * \brief MNG library callback: Open MNG stream. + * \param[in] h_mng MNG library image handle + * \return \p MNG_TRUE on success, \p MNG_FALSE on error (never happens) + */ +static mng_bool demux_mng_openstream(mng_handle h_mng) +{ + mng_priv_t * mng_priv = mng_get_userdata(h_mng); + stream_t * stream = mng_priv->stream; + + // rewind stream to the beginning + stream_seek(stream, stream->start_pos); + + return MNG_TRUE; +} + +/** + * \brief MNG library callback: Close MNG stream. + * \param[in] h_mng MNG library image handle + * \return \p MNG_TRUE on success, \p MNG_FALSE on error (never happens) + */ +static mng_bool demux_mng_closestream(mng_handle h_mng) +{ + return MNG_TRUE; +} + +/** + * \brief MNG library callback: Read data from stream. + * \param[in] h_mng MNG library image handle + * \param[in] buf pointer to buffer to fill with data + * \param[in] size size of buffer + * \param[out] read number of bytes read from stream + * \return \p MNG_TRUE on success, \p MNG_FALSE on error (never happens) + */ +static mng_bool demux_mng_readdata(mng_handle h_mng, mng_ptr buf, + mng_uint32 size, mng_uint32 * read) +{ + mng_priv_t * mng_priv = mng_get_userdata(h_mng); + stream_t * stream = mng_priv->stream; + + // simply read data from stream and return number of bytes or error + *read = stream_read(stream, buf, size); + + return MNG_TRUE; +} + +/** + * \brief MNG library callback: Header information is processed now. + * \param[in] h_mng MNG library image handle + * \param[in] width image width + * \param[in] height image height + * \return \p MNG_TRUE on success, \p MNG_FALSE on error + */ +static mng_bool demux_mng_processheader(mng_handle h_mng, mng_uint32 width, + mng_uint32 height) +{ + mng_priv_t * mng_priv = mng_get_userdata(h_mng); + + // remember size in private data + mng_priv->header_processed = 1; + mng_priv->width = width; + mng_priv->height = height; + + // get total animation time + mng_priv->total_time_ms = mng_get_playtime(h_mng); + + // allocate canvas + mng_priv->canvas = malloc(height * width * 4); + if (!mng_priv->canvas) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "demux_mng: could not allocate canvas of size %dx%d\n", + width, height); + return MNG_FALSE; + } + + return MNG_TRUE; +} + +/** + * \brief MNG library callback: Get access to a canvas line. + * \param[in] h_mng MNG library image handle + * \param[in] line y coordinate of line to access + * \return pointer to line on success, \p MNG_NULL on error + */ +static mng_ptr demux_mng_getcanvasline(mng_handle h_mng, mng_uint32 line) +{ + mng_priv_t * mng_priv = mng_get_userdata(h_mng); + + // return pointer to canvas line + if (line < mng_priv->height && mng_priv->canvas) + return (mng_ptr)(mng_priv->canvas + line * mng_priv->width * 4); + else + return (mng_ptr)MNG_NULL; +} + +/** + * \brief MNG library callback: A part of the canvas should be shown. + * + * This function is called by libmng whenever it thinks a + * rectangular part of the display should be updated. This + * can happen multiple times for a frame and/or a single time + * for a frame. Only the the part of the display occupied by + * the rectangle defined by x, y, width, height is to be updated. + * It is possible that some parts of the display are not updated + * for many frames. There is no chance here to find out if the + * current frame is completed with this update or not. + * + * This mechanism does not match MPlayer's demuxer architecture, + * so it will not be used exactly as intended by libmng. + * A new frame is generated in the demux_mng_fill_buffer() function + * whenever libmng tells us to wait for some time. + * + * \param[in] h_mng MNG library image handle + * \param[in] x rectangle's left edge + * \param[in] y rectangle's top edge + * \param[in] width rectangle's width + * \param[in] height rectangle's heigt + * \return \p MNG_TRUE on success, \p MNG_FALSE on error (never happens) + */ +static mng_bool demux_mng_refresh(mng_handle h_mng, mng_uint32 x, mng_uint32 y, + mng_uint32 width, mng_uint32 height) +{ + // nothing to do here, the image data is already on the canvas + return MNG_TRUE; +} + +/** + * \brief MNG library callback: Get how many milliseconds have passed. + * \param[in] h_mng MNG library image handle + * \return global time in milliseconds + */ +static mng_uint32 demux_mng_gettickcount(mng_handle h_mng) +{ + mng_priv_t * mng_priv = mng_get_userdata(h_mng); + + // return current global time + return mng_priv->global_time_ms; +} + +/** + * \brief MNG library callback: Please call again after some milliseconds. + * \param[in] h_mng MNG library image handle + * \param[in] msecs number of milliseconds after which to call again + * \return \p MNG_TRUE on success, \p MNG_FALSE on error (never happens) + */ +static mng_bool demux_mng_settimer(mng_handle h_mng, mng_uint32 msecs) +{ + mng_priv_t * mng_priv = mng_get_userdata(h_mng); + + // Save number of milliseconds after which to call the MNG library again + // in private data. + mng_priv->timer_ms = msecs; + return MNG_TRUE; +} + +/** + * \brief MPlayer callback: Check if stream contains MNG data. + * \param[in] demuxer demuxer structure + * \return demuxer type constant, \p 0 if unknown + */ +static int demux_mng_check_file(demuxer_t *demuxer) +{ + char buf[4]; + if (stream_read(demuxer->stream, buf, 4) != 4) + return 0; + if (memcmp(buf, "\x8AMNG", 4)) + return 0; + return DEMUXER_TYPE_MNG; +} + +/** + * \brief MPlayer callback: Fill buffer from MNG stream. + * \param[in] demuxer demuxer structure + * \param[in] ds demuxer stream + * \return \p 1 on success, \p 0 on error + */ +static int demux_mng_fill_buffer(demuxer_t * demuxer, + demux_stream_t * ds) +{ + mng_priv_t * mng_priv = demuxer->priv; + mng_handle h_mng = mng_priv->h_mng; + mng_retcode mng_ret; + demux_packet_t * dp; + + // exit if animation is finished + if (mng_priv->finished) + return 0; + + // advance animation to requested next show time + while (mng_priv->anim_cur_time_ms + mng_priv->anim_frame_duration_ms + <= mng_priv->show_next_time_ms && !mng_priv->finished) { + + // advance global and animation time + mng_priv->global_time_ms += mng_priv->anim_frame_duration_ms; + mng_priv->anim_cur_time_ms += mng_priv->anim_frame_duration_ms; + + // Clear variable MNG library will write number of milliseconds to + // (via settimer callback). + mng_priv->timer_ms = 0; + + // get next image from MNG library + if (mng_priv->displaying) + mng_ret = mng_display_resume(h_mng); // resume displaying MNG data + // to canvas + else + mng_ret = mng_display(h_mng); // start displaying MNG data to canvas + if (mng_ret && mng_ret != MNG_NEEDTIMERWAIT) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "demux_mng: could not display MNG data to canvas: " + "mng_retcode %d\n", mng_ret); + return 0; + } + mng_priv->displaying = 1; // mng_display() has been called now + mng_priv->finished = mng_ret == 0; // animation is finished iff + // mng_display() returned 0 + + // save current frame duration + mng_priv->anim_frame_duration_ms = mng_priv->timer_ms < 1 + ? 1 : mng_priv->timer_ms; + + } // while (mng_priv->anim_cur_time_ms + ... + + // create a new demuxer packet + dp = new_demux_packet(mng_priv->height * mng_priv->width * 4); + + // copy image data into demuxer packet + memcpy(dp->buffer, mng_priv->canvas, + mng_priv->height * mng_priv->width * 4); + + // set current show time to requested show time + mng_priv->show_cur_time_ms = mng_priv->show_next_time_ms; + + // get time of next frame to show + mng_priv->show_next_time_ms = mng_priv->anim_cur_time_ms + + mng_priv->anim_frame_duration_ms; + + // Set position and timing information in demuxer video and demuxer packet. + // - Time must be time of next frame and always be > 0 for the variable + // frame time mechanism (GIF, MATROSKA, MNG) in video.c to work. + demuxer->video->dpos++; + dp->pts = (float)mng_priv->show_next_time_ms / 1000.0f + MNG_START_PTS; + dp->pos = stream_tell(demuxer->stream); + ds_add_packet(demuxer->video, dp); + + return 1; +} + +/** + * \brief MPlayer callback: Open MNG stream. + * \param[in] demuxer demuxer structure + * \return demuxer structure on success, \p NULL on error + */ +static demuxer_t * demux_mng_open(demuxer_t * demuxer) +{ + mng_priv_t * mng_priv; + mng_handle h_mng; + mng_retcode mng_ret; + sh_video_t * sh_video; + + // create private data structure + mng_priv = calloc(1, sizeof(mng_priv_t)); + + //stream pointer into private data + mng_priv->stream = demuxer->stream; + + // initialize MNG image instance + h_mng = mng_initialize((mng_ptr)mng_priv, demux_mng_alloc, + demux_mng_free, MNG_NULL); + if (!h_mng) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "demux_mng: could not initialize MNG image instance\n"); + free(mng_priv); + return NULL; + } + + // MNG image handle into private data + mng_priv->h_mng = h_mng; + + // set required MNG callbacks + if (mng_setcb_openstream(h_mng, demux_mng_openstream) || + mng_setcb_closestream(h_mng, demux_mng_closestream) || + mng_setcb_readdata(h_mng, demux_mng_readdata) || + mng_setcb_processheader(h_mng, demux_mng_processheader) || + mng_setcb_getcanvasline(h_mng, demux_mng_getcanvasline) || + mng_setcb_refresh(h_mng, demux_mng_refresh) || + mng_setcb_gettickcount(h_mng, demux_mng_gettickcount) || + mng_setcb_settimer(h_mng, demux_mng_settimer) || + mng_set_canvasstyle(h_mng, MNG_CANVAS_RGBA8)) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "demux_mng: could not set MNG callbacks\n"); + mng_cleanup(&h_mng); + free(mng_priv); + return NULL; + } + + // start reading MNG data + mng_ret = mng_read(h_mng); + if (mng_ret) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "demux_mng: could not start reading MNG data: " + "mng_retcode %d\n", mng_ret); + mng_cleanup(&h_mng); + free(mng_priv); + return NULL; + } + + // check that MNG header is processed now + if (!mng_priv->header_processed) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "demux_mng: internal error: header not processed\n"); + mng_cleanup(&h_mng); + free(mng_priv); + return NULL; + } + + // create a new video stream header + sh_video = new_sh_video(demuxer, 0); + + // Make sure the demuxer knows about the new video stream header + // (even though new_sh_video() ought to take care of it). + // (Thanks to demux_gif.c for this.) + demuxer->video->sh = sh_video; + + // Make sure that the video demuxer stream header knows about its + // parent video demuxer stream (this is getting wacky), or else + // video_read_properties() will choke. + // (Thanks to demux_gif.c for this.) + sh_video->ds = demuxer->video; + + // set format of pixels in video packets + sh_video->format = mmioFOURCC(32, 'B', 'G', 'R'); + + // set framerate to some value (MNG does not have a fixed framerate) + sh_video->fps = 5.0f; + sh_video->frametime = 1.0f / sh_video->fps; + + // set video frame parameters + sh_video->bih = malloc(sizeof(BITMAPINFOHEADER)); + sh_video->bih->biCompression = sh_video->format; + sh_video->bih->biWidth = mng_priv->width; + sh_video->bih->biHeight = mng_priv->height; + sh_video->bih->biBitCount = 32; + sh_video->bih->biPlanes = 1; + + // Set start time to something > 0. + // - This is required for the variable frame time mechanism + // (GIF, MATROSKA, MNG) in video.c to work for the first frame. + sh_video->ds->pts = MNG_START_PTS; + + // set private data in demuxer and return demuxer + demuxer->priv = mng_priv; + return demuxer; +} + +/** + * \brief MPlayer callback: Close MNG stream. + * \param[in] demuxer demuxer structure + */ +static void demux_mng_close(demuxer_t* demuxer) +{ + mng_priv_t * mng_priv = demuxer->priv; + + if (mng_priv) { + + // shutdown MNG image instance + if (mng_priv->h_mng) + mng_cleanup(&mng_priv->h_mng); + + // free private data + if (mng_priv->canvas) + free(mng_priv->canvas); + + free(mng_priv); + } +} + +/** + * \brief MPlayer callback: Seek in MNG stream. + * \param[in] demuxer demuxer structure + * \param[in] rel_seek_secs relative seek time in seconds + * \param[in] audio_delay unused, MNG does not contain audio + * \param[in] flags bit flags, \p 1: absolute, \p 2: fractional position + */ +static void demux_mng_seek(demuxer_t * demuxer, float rel_seek_secs, + float audio_delay, int flags) +{ + mng_priv_t * mng_priv = demuxer->priv; + mng_handle h_mng = mng_priv->h_mng; + mng_retcode mng_ret; + int seek_ms, pos_ms; + + // exit if not ready to seek (header not yet read or not yet displaying) + if (!mng_priv->header_processed || !mng_priv->displaying) + return; + + // get number of milliseconds to seek to + if (flags & 2) // seek by fractional position (0.0 ... 1.0) + seek_ms = (int)(rel_seek_secs * (float)mng_priv->total_time_ms); + else // seek by time in seconds + seek_ms = (int)(rel_seek_secs * 1000.0f + 0.5f); + + // get new position in milliseconds + if (flags & 1) // absolute + pos_ms = seek_ms; + else // relative + pos_ms = mng_priv->show_cur_time_ms + seek_ms; + + // fix position + if (pos_ms < 0) + pos_ms = 0; + if (pos_ms > mng_priv->total_time_ms) + pos_ms = mng_priv->total_time_ms; + + // FIXME + // In principle there is a function to seek in MNG: mng_display_gotime(). + // - Using it did not work out (documentation is very brief, + // example code does not exist?). + // - The following code works, but its performance is quite bad. + + // seeking forward + if (pos_ms >= mng_priv->show_cur_time_ms) { + + // Simply advance show time to seek position. + // - Everything else will be handled in demux_mng_fill_buffer(). + mng_priv->show_next_time_ms = pos_ms; + + } // if (pos_ms > mng_priv->show_time_ms) + + // seeking backward + else { // if (pos_ms > mng_priv->show_time_ms) + + // Clear variable MNG library will write number of milliseconds to + // (via settimer callback). + mng_priv->timer_ms = 0; + + // Restart displaying and advance show time to seek position. + // - Everything else will be handled in demux_mng_fill_buffer(). + mng_ret = mng_display_reset(h_mng); + // If a timer wait is needed, fool libmng that requested time + // passed and try again. + if (mng_ret == MNG_NEEDTIMERWAIT) { + mng_priv->global_time_ms += mng_priv->timer_ms; + mng_ret = mng_display_reset(h_mng); + } + if (mng_ret) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "demux_mng: could not reset MNG display state: " + "mng_retcode %d\n", mng_ret); + return; + } + mng_priv->displaying = 0; + mng_priv->finished = 0; + mng_priv->anim_cur_time_ms = 0; + mng_priv->anim_frame_duration_ms = 0; + mng_priv->show_next_time_ms = pos_ms; + + } // if (pos_ms > mng_priv->show_time_ms) ... else +} + +/** + * \brief MPlayer callback: Control MNG stream. + * \param[in] demuxer demuxer structure + * \param[in] cmd code of control command to perform + * \param[in,out] arg command argument + * \return demuxer control response code + */ +static int demux_mng_control(demuxer_t * demuxer, int cmd, void * arg) +{ + mng_priv_t * mng_priv = demuxer->priv; + + switch(cmd) { + + // get total movie length + case DEMUXER_CTRL_GET_TIME_LENGTH: + if (mng_priv->header_processed) { + *(double *)arg = (double)mng_priv->total_time_ms / 1000.0; + return DEMUXER_CTRL_OK; + } else { + return DEMUXER_CTRL_DONTKNOW; + } + break; + + // get position in movie + case DEMUXER_CTRL_GET_PERCENT_POS: + if (mng_priv->header_processed && mng_priv->total_time_ms > 0) { + *(int *)arg = (100 * mng_priv->show_cur_time_ms + + mng_priv->total_time_ms / 2) + / mng_priv->total_time_ms; + return DEMUXER_CTRL_OK; + } else { + return DEMUXER_CTRL_DONTKNOW; + } + break; + + default: + return DEMUXER_CTRL_NOTIMPL; + + } // switch (cmd) +} + +const demuxer_desc_t demuxer_desc_mng = { + "MNG demuxer", + "mng", + "MNG", + "Stefan Schuermans ", + "MNG files, using libmng", + DEMUXER_TYPE_MNG, + 0, // unsafe autodetect (only checking magic at beginning of stream) + demux_mng_check_file, + demux_mng_fill_buffer, + demux_mng_open, + demux_mng_close, + demux_mng_seek, + demux_mng_control +}; + diff --git a/libmpdemux/demuxer.c b/libmpdemux/demuxer.c index 20130332fd..7d9ec52efc 100644 --- a/libmpdemux/demuxer.c +++ b/libmpdemux/demuxer.c @@ -80,6 +80,7 @@ extern const demuxer_desc_t demuxer_desc_lavf; extern const demuxer_desc_t demuxer_desc_lavf_preferred; extern const demuxer_desc_t demuxer_desc_aac; extern const demuxer_desc_t demuxer_desc_nut; +extern const demuxer_desc_t demuxer_desc_mng; /* Please do not add any new demuxers here. If you want to implement a new * demuxer, add it to libavformat, except for wrappers around external @@ -151,6 +152,9 @@ const demuxer_desc_t *const demuxer_list[] = { #endif #ifdef CONFIG_XMMS &demuxer_desc_xmms, +#endif +#ifdef CONFIG_MNG + &demuxer_desc_mng, #endif /* Please do not add any new demuxers here. If you want to implement a new * demuxer, add it to libavformat, except for wrappers around external diff --git a/libmpdemux/demuxer.h b/libmpdemux/demuxer.h index a97f3746db..e8893fc402 100644 --- a/libmpdemux/demuxer.h +++ b/libmpdemux/demuxer.h @@ -71,11 +71,12 @@ #define DEMUXER_TYPE_NUT 43 #define DEMUXER_TYPE_LAVF_PREFERRED 44 #define DEMUXER_TYPE_RTP_NEMESI 45 +#define DEMUXER_TYPE_MNG 46 // This should always match the higest demuxer type number. // Unless you want to disallow users to force the demuxer to some types #define DEMUXER_TYPE_MIN 0 -#define DEMUXER_TYPE_MAX 45 +#define DEMUXER_TYPE_MAX 46 #define DEMUXER_TYPE_DEMUXERS (1<<16) // A virtual demuxer type for the network code diff --git a/libmpdemux/video.c b/libmpdemux/video.c index 1faf077840..41a3d7d006 100644 --- a/libmpdemux/video.c +++ b/libmpdemux/video.c @@ -561,6 +561,7 @@ int video_read_frame(sh_video_t* sh_video,float* frame_time_ptr,unsigned char** if(!force_fps) switch(demuxer->file_format){ case DEMUXER_TYPE_GIF: case DEMUXER_TYPE_MATROSKA: + case DEMUXER_TYPE_MNG: if(d_video->pts>0 && pts1>0 && d_video->pts>pts1) frame_time=d_video->pts-pts1; break; -- cgit v1.2.3