dxx-rebirth/d2x-rebirth/libmve/mveplay.cpp
Kp 787d95241c Fix crash after pausing movie
The movie code assumes that when the topmost window is closed, that
window must have been the movie window, and that the movie data can now
be freed.  However, if the movie is paused, a new topmost window is
created to tell the user that the movie is paused.  When that topmost
pause window is deleted, the movie code frees the movie data, even
though the movie is not done.  Corruption ensues, ultimately leading to
a crash.

Reported-by: Jayman2000 <https://github.com/dxx-rebirth/dxx-rebirth/issues/410>
2018-10-10 02:01:12 +00:00

802 lines
21 KiB
C++

/*
* This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
* It is copyright by its individual contributors, as recorded in the
* project's Git history. See COPYING.txt at the top level for license
* terms and a link to the Git history.
*/
//#define DEBUG
#include "dxxsconf.h"
#include <vector>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <errno.h>
# include <fcntl.h>
# ifdef macintosh
# include <types.h>
# include <OSUtils.h>
# else
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# endif // macintosh
#endif // _WIN32
#include <SDL.h>
#if DXX_USE_SDLMIXER
#include <SDL_mixer.h>
#endif
#include "config.h"
#include "mvelib.h"
#include "mve_audio.h"
#include "byteutil.h"
#include "decoders.h"
#include "libmve.h"
#include "args.h"
#include "console.h"
#include "u_mem.h"
#include "compiler-exchange.h"
#include "compiler-make_unique.h"
#define MVE_OPCODE_ENDOFSTREAM 0x00
#define MVE_OPCODE_ENDOFCHUNK 0x01
#define MVE_OPCODE_CREATETIMER 0x02
#define MVE_OPCODE_INITAUDIOBUFFERS 0x03
#define MVE_OPCODE_STARTSTOPAUDIO 0x04
#define MVE_OPCODE_INITVIDEOBUFFERS 0x05
#define MVE_OPCODE_DISPLAYVIDEO 0x07
#define MVE_OPCODE_AUDIOFRAMEDATA 0x08
#define MVE_OPCODE_AUDIOFRAMESILENCE 0x09
#define MVE_OPCODE_INITVIDEOMODE 0x0A
#define MVE_OPCODE_SETPALETTE 0x0C
#define MVE_OPCODE_SETPALETTECOMPRESSED 0x0D
#define MVE_OPCODE_SETDECODINGMAP 0x0F
#define MVE_OPCODE_VIDEODATA 0x11
#define MVE_AUDIO_FLAGS_STEREO 1
#define MVE_AUDIO_FLAGS_16BIT 2
#define MVE_AUDIO_FLAGS_COMPRESSED 4
int g_spdFactorNum=0;
static int g_spdFactorDenom=10;
static int g_frameUpdated = 0;
static int16_t get_short(const unsigned char *data)
{
short value;
value = data[0] | (data[1] << 8);
return value;
}
static uint16_t get_ushort(const unsigned char *data)
{
unsigned short value;
value = data[0] | (data[1] << 8);
return value;
}
static int32_t get_int(const unsigned char *data)
{
int value;
value = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
return value;
}
/*************************
* general handlers
*************************/
static int end_movie_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
return 0;
}
/*************************
* timer handlers
*************************/
/*
* timer variables
*/
static int timer_created = 0;
static int micro_frame_delay=0;
static int timer_started=0;
static struct timeval timer_expire = {0, 0};
#ifndef DXX_HAVE_STRUCT_TIMESPEC
struct timespec
{
long int tv_sec; /* Seconds. */
long int tv_nsec; /* Nanoseconds. */
};
#endif
#if defined(_WIN32) || defined(macintosh)
int gettimeofday(struct timeval *tv, void *)
{
static int counter = 0;
#ifdef _WIN32
DWORD now = GetTickCount();
#else
long now = TickCount();
#endif
counter++;
tv->tv_sec = now / 1000;
tv->tv_usec = (now % 1000) * 1000 + counter;
return 0;
}
#endif // defined(_WIN32) || defined(macintosh)
static int create_timer_handler(unsigned char, unsigned char, const unsigned char *data, int, void *)
{
#if !defined(_WIN32) && !defined(macintosh) // FIXME
__extension__ long long temp;
#else
long temp;
#endif
if (timer_created)
return 1;
else
timer_created = 1;
micro_frame_delay = get_int(data) * static_cast<int>(get_short(data+4));
if (g_spdFactorNum != 0)
{
temp = micro_frame_delay;
temp *= g_spdFactorNum;
temp /= g_spdFactorDenom;
micro_frame_delay = static_cast<int>(temp);
}
return 1;
}
static void timer_stop(void)
{
timer_expire.tv_sec = 0;
timer_expire.tv_usec = 0;
timer_started = 0;
}
static void timer_start(void)
{
int nsec=0;
gettimeofday(&timer_expire, NULL);
timer_expire.tv_usec += micro_frame_delay;
if (timer_expire.tv_usec > 1000000)
{
nsec = timer_expire.tv_usec / 1000000;
timer_expire.tv_sec += nsec;
timer_expire.tv_usec -= nsec*1000000;
}
timer_started=1;
}
static void do_timer_wait(void)
{
int nsec=0;
struct timespec ts;
struct timeval tv;
if (! timer_started)
return;
gettimeofday(&tv, NULL);
if (tv.tv_sec > timer_expire.tv_sec)
goto end;
else if (tv.tv_sec == timer_expire.tv_sec && tv.tv_usec >= timer_expire.tv_usec)
goto end;
ts.tv_sec = timer_expire.tv_sec - tv.tv_sec;
ts.tv_nsec = 1000 * (timer_expire.tv_usec - tv.tv_usec);
if (ts.tv_nsec < 0)
{
ts.tv_nsec += 1000000000UL;
--ts.tv_sec;
}
#ifdef _WIN32
Sleep(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
#elif defined(macintosh)
Delay(ts.tv_sec * 1000 + ts.tv_nsec / 1000000, NULL);
#else
if (nanosleep(&ts, NULL) == -1 && errno == EINTR)
exit(1);
#endif
end:
timer_expire.tv_usec += micro_frame_delay;
if (timer_expire.tv_usec > 1000000)
{
nsec = timer_expire.tv_usec / 1000000;
timer_expire.tv_sec += nsec;
timer_expire.tv_usec -= nsec*1000000;
}
}
/*************************
* audio handlers
*************************/
#define TOTAL_AUDIO_BUFFERS 64
namespace {
class MVE_audio_deleter
{
public:
void operator()(int16_t *p) const
{
mve_free(p);
}
};
template <typename T>
struct MVE_audio_clamp
{
const unsigned scale;
MVE_audio_clamp(const unsigned DigiVolume) :
scale(DigiVolume)
{
}
T operator()(const T &i) const
{
return (static_cast<int32_t>(i) * scale) / 8;
}
};
}
static int audiobuf_created = 0;
static void mve_audio_callback(void *userdata, unsigned char *stream, int len);
static array<std::unique_ptr<short[], MVE_audio_deleter>, TOTAL_AUDIO_BUFFERS> mve_audio_buffers;
static array<unsigned, TOTAL_AUDIO_BUFFERS> mve_audio_buflens;
static int mve_audio_curbuf_curpos=0;
static int mve_audio_bufhead=0;
static int mve_audio_buftail=0;
static int mve_audio_playing=0;
static int mve_audio_canplay=0;
static unsigned mve_audio_flags;
static int mve_audio_enabled = 1;
static std::unique_ptr<SDL_AudioSpec> mve_audio_spec;
static void mve_audio_callback(void *, unsigned char *stream, int len)
{
int total=0;
int length;
if (mve_audio_bufhead == mve_audio_buftail)
return /* 0 */;
//con_printf(CON_CRITICAL, "+ <%d (%d), %d, %d>", mve_audio_bufhead, mve_audio_curbuf_curpos, mve_audio_buftail, len);
while (mve_audio_bufhead != mve_audio_buftail /* while we have more buffers */
&& len > (mve_audio_buflens[mve_audio_bufhead]-mve_audio_curbuf_curpos)) /* and while we need more data */
{
length = mve_audio_buflens[mve_audio_bufhead]-mve_audio_curbuf_curpos;
memcpy(stream, /* cur output position */
(reinterpret_cast<uint8_t *>(mve_audio_buffers[mve_audio_bufhead].get()))+mve_audio_curbuf_curpos, /* cur input position */
length); /* cur input length */
total += length;
stream += length; /* advance output */
len -= length; /* decrement avail ospace */
mve_audio_buffers[mve_audio_bufhead].reset(); /* free the buffer */
mve_audio_buflens[mve_audio_bufhead]=0; /* free the buffer */
if (++mve_audio_bufhead == TOTAL_AUDIO_BUFFERS) /* next buffer */
mve_audio_bufhead = 0;
mve_audio_curbuf_curpos = 0;
}
//con_printf(CON_CRITICAL, "= <%d (%d), %d, %d>: %d", mve_audio_bufhead, mve_audio_curbuf_curpos, mve_audio_buftail, len, total);
/* return total; */
if (len != 0 /* ospace remaining */
&& mve_audio_bufhead != mve_audio_buftail) /* buffers remaining */
{
memcpy(stream, /* dest */
(reinterpret_cast<uint8_t *>(mve_audio_buffers[mve_audio_bufhead].get()))+mve_audio_curbuf_curpos, /* src */
len); /* length */
mve_audio_curbuf_curpos += len; /* advance input */
stream += len; /* advance output (unnecessary) */
len -= len; /* advance output (unnecessary) */
if (mve_audio_curbuf_curpos >= mve_audio_buflens[mve_audio_bufhead]) /* if this ends the current chunk */
{
mve_audio_buffers[mve_audio_bufhead].reset(); /* free buffer */
mve_audio_buflens[mve_audio_bufhead]=0;
if (++mve_audio_bufhead == TOTAL_AUDIO_BUFFERS) /* next buffer */
mve_audio_bufhead = 0;
mve_audio_curbuf_curpos = 0;
}
}
//con_printf(CON_CRITICAL, "- <%d (%d), %d, %d>", mve_audio_bufhead, mve_audio_curbuf_curpos, mve_audio_buftail, len);
}
static int create_audiobuf_handler(unsigned char, unsigned char minor, const unsigned char *data, int, void *)
{
int flags;
int sample_rate;
int desired_buffer;
if (!mve_audio_enabled)
return 1;
if (audiobuf_created)
return 1;
else
audiobuf_created = 1;
flags = get_ushort(data + 2);
sample_rate = get_ushort(data + 4);
desired_buffer = get_int(data + 6);
const unsigned stereo = (flags & MVE_AUDIO_FLAGS_STEREO);
const unsigned bitsize = (flags & MVE_AUDIO_FLAGS_16BIT);
if (!minor)
flags &= ~MVE_AUDIO_FLAGS_COMPRESSED;
const unsigned compressed = flags & MVE_AUDIO_FLAGS_COMPRESSED;
mve_audio_flags = flags;
const unsigned format = (bitsize)
? (words_bigendian ? AUDIO_S16MSB : AUDIO_S16LSB)
: AUDIO_U8;
if (CGameArg.SndDisableSdlMixer)
{
con_puts(CON_CRITICAL, "creating audio buffers:");
con_printf(CON_CRITICAL, "sample rate = %d, desired buffer = %d, stereo = %d, bitsize = %d, compressed = %d",
sample_rate, desired_buffer, stereo ? 1 : 0, bitsize ? 16 : 8, compressed ? 1 : 0);
}
mve_audio_spec = make_unique<SDL_AudioSpec>();
mve_audio_spec->freq = sample_rate;
mve_audio_spec->format = format;
mve_audio_spec->channels = (stereo) ? 2 : 1;
mve_audio_spec->samples = 4096;
mve_audio_spec->callback = mve_audio_callback;
mve_audio_spec->userdata = NULL;
// MD2211: if using SDL_Mixer, we never reinit the sound system
if (CGameArg.SndDisableSdlMixer)
{
if (SDL_OpenAudio(mve_audio_spec.get(), NULL) >= 0) {
con_puts(CON_CRITICAL, " success");
mve_audio_canplay = 1;
}
else {
con_printf(CON_CRITICAL, " failure : %s", SDL_GetError());
mve_audio_canplay = 0;
}
}
#if DXX_USE_SDLMIXER
else {
// MD2211: using the same old SDL audio callback as a postmixer in SDL_mixer
Mix_SetPostMix(mve_audio_spec->callback, mve_audio_spec->userdata);
mve_audio_canplay = 1;
}
#endif
mve_audio_buffers = {};
mve_audio_buflens = {};
return 1;
}
static int play_audio_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
if (mve_audio_canplay && !mve_audio_playing && mve_audio_bufhead != mve_audio_buftail)
{
if (CGameArg.SndDisableSdlMixer)
SDL_PauseAudio(0);
#if DXX_USE_SDLMIXER
else
Mix_Pause(0);
#endif
mve_audio_playing = 1;
}
return 1;
}
static int audio_data_handler(unsigned char major, unsigned char, const unsigned char *data, int, void *)
{
static const int selected_chan=1;
int chan;
int nsamp;
if (mve_audio_canplay)
{
if (mve_audio_playing)
SDL_LockAudio();
chan = get_ushort(data + 2);
nsamp = get_ushort(data + 4);
if (chan & selected_chan)
{
std::unique_ptr<int16_t[], MVE_audio_deleter> p;
const auto DigiVolume = GameCfg.DigiVolume;
/* HACK: +4 mveaudio_uncompress adds 4 more bytes */
/* At volume 0 (minimum), no sound is wanted. */
if (DigiVolume && major == MVE_OPCODE_AUDIOFRAMEDATA) {
const auto flags = mve_audio_flags;
if (flags & MVE_AUDIO_FLAGS_COMPRESSED) {
nsamp += 4;
p.reset(reinterpret_cast<int16_t *>(mve_alloc(nsamp)));
mveaudio_uncompress(p.get(), data); /* XXX */
} else {
nsamp -= 8;
data += 8;
p.reset(reinterpret_cast<int16_t *>(mve_alloc(nsamp)));
memcpy(p.get(), data, nsamp);
}
if (DigiVolume < 8)
{
/* At volume 8 (maximum), no scaling is needed. */
if (flags & MVE_AUDIO_FLAGS_16BIT)
{
int16_t *const p16 = p.get();
std::transform(p16, reinterpret_cast<int16_t *>(reinterpret_cast<uint8_t *>(p16) + nsamp), p16, MVE_audio_clamp<int16_t>(DigiVolume));
}
else
{
int8_t *const p8 = reinterpret_cast<int8_t *>(p.get());
std::transform(p8, p8 + nsamp, p8, MVE_audio_clamp<int8_t>(DigiVolume));
}
}
} else {
p.reset(reinterpret_cast<int16_t *>(mve_alloc(nsamp)));
memset(p.get(), 0, nsamp); /* XXX */
}
unsigned buflen = nsamp;
// MD2211: the following block does on-the-fly audio conversion for SDL_mixer
#if DXX_USE_SDLMIXER
if (!CGameArg.SndDisableSdlMixer) {
// build converter: in = MVE format, out = SDL_mixer output
int out_freq;
Uint16 out_format;
int out_channels;
Mix_QuerySpec(&out_freq, &out_format, &out_channels); // get current output settings
SDL_AudioCVT cvt{};
SDL_BuildAudioCVT(&cvt, mve_audio_spec->format, mve_audio_spec->channels, mve_audio_spec->freq,
out_format, out_channels, out_freq);
RAIIdmem<uint8_t[]> cvtbuf;
MALLOC(cvtbuf, uint8_t[], nsamp * cvt.len_mult);
cvt.buf = cvtbuf.get();
cvt.len = nsamp;
// read the audio buffer into the conversion buffer
memcpy(cvt.buf, p.get(), nsamp);
// do the conversion
if (SDL_ConvertAudio(&cvt))
con_printf(CON_URGENT, "%s:%u: SDL_ConvertAudio failed: nsamp=%u out_format=%i out_channels=%i out_freq=%i", __FILE__, __LINE__, nsamp, out_format, out_channels, out_freq);
else
{
// copy back to the audio buffer
const std::size_t converted_buffer_size = cvt.len_cvt;
p.reset(reinterpret_cast<int16_t *>(mve_alloc(converted_buffer_size))); // free the old audio buffer
buflen = converted_buffer_size;
memcpy(p.get(), cvt.buf, converted_buffer_size);
}
}
#endif
mve_audio_buffers[mve_audio_buftail] = std::move(p);
mve_audio_buflens[mve_audio_buftail] = buflen;
if (++mve_audio_buftail == TOTAL_AUDIO_BUFFERS)
mve_audio_buftail = 0;
if (mve_audio_buftail == mve_audio_bufhead)
con_printf(CON_CRITICAL, "d'oh! buffer ring overrun (%d)", mve_audio_bufhead);
}
if (mve_audio_playing)
SDL_UnlockAudio();
}
return 1;
}
/*************************
* video handlers
*************************/
static int videobuf_created = 0;
static int video_initialized = 0;
int g_width, g_height;
static std::vector<unsigned char> g_vBuffers;
unsigned char *g_vBackBuf1, *g_vBackBuf2;
static int g_destX, g_destY;
static int g_screenWidth, g_screenHeight;
static const unsigned char *g_pCurMap;
static int g_nMapLength=0;
static int g_truecolor;
static int create_videobuf_handler(unsigned char, unsigned char minor, const unsigned char *data, int, void *)
{
short w, h,
#ifdef DEBUG
count,
#endif
truecolor;
if (videobuf_created)
return 1;
else
videobuf_created = 1;
w = get_short(data);
h = get_short(data+2);
#ifdef DEBUG
if (minor > 0) {
count = get_short(data+4);
} else {
count = 1;
}
#endif
if (minor > 1) {
truecolor = get_short(data+6);
} else {
truecolor = 0;
}
g_width = w << 3;
g_height = h << 3;
/* TODO: * 4 causes crashes on some files */
/* only malloc once */
g_vBuffers.assign(g_width * g_height * 8, 0);
g_vBackBuf1 = &g_vBuffers[0];
if (truecolor) {
g_vBackBuf2 = reinterpret_cast<uint8_t *>(reinterpret_cast<uint16_t *>(g_vBackBuf1) + (g_width * g_height));
} else {
g_vBackBuf2 = (g_vBackBuf1 + (g_width * g_height));
}
#ifdef DEBUG
con_printf(CON_CRITICAL, "DEBUG: w,h=%d,%d count=%d, tc=%d", w, h, count, truecolor);
#endif
g_truecolor = truecolor;
return 1;
}
static int display_video_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
mve_showframe(g_vBackBuf1, g_destX, g_destY, g_width, g_height, g_screenWidth, g_screenHeight);
g_frameUpdated = 1;
return 1;
}
static int init_video_handler(unsigned char, unsigned char, const unsigned char *data, int, void *)
{
short width, height;
if (video_initialized)
return 1; /* maybe we actually need to change width/height here? */
else
video_initialized = 1;
width = get_short(data);
height = get_short(data+2);
g_screenWidth = width;
g_screenHeight = height;
return 1;
}
static int video_palette_handler(unsigned char, unsigned char, const unsigned char *data, int, void *)
{
short start, count;
start = get_short(data);
count = get_short(data+2);
auto p = data + 4;
mve_setpalette(p - 3*start, start, count);
return 1;
}
static int video_codemap_handler(unsigned char, unsigned char, const unsigned char *data, int len, void *)
{
g_pCurMap = data;
g_nMapLength = len;
return 1;
}
static int video_data_handler(unsigned char, unsigned char, const unsigned char *data, int len, void *)
{
unsigned short nFlags;
// don't need those but kept for further reference
// nFrameHot = get_short(data);
// nFrameCold = get_short(data+2);
// nXoffset = get_short(data+4);
// nYoffset = get_short(data+6);
// nXsize = get_short(data+8);
// nYsize = get_short(data+10);
nFlags = get_ushort(data+12);
if (nFlags & 1)
{
std::swap(g_vBackBuf1, g_vBackBuf2);
}
/* convert the frame */
if (g_truecolor) {
decodeFrame16(g_vBackBuf1, g_pCurMap, g_nMapLength, data+14, len-14);
} else {
decodeFrame8(g_vBackBuf1, g_pCurMap, g_nMapLength, data+14, len-14);
}
return 1;
}
static int end_chunk_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
g_pCurMap=NULL;
return 1;
}
void MVE_ioCallbacks(mve_cb_Read io_read)
{
mve_read = io_read;
}
void MVE_memCallbacks(mve_cb_Alloc mem_alloc, mve_cb_Free mem_free)
{
mve_alloc = mem_alloc;
mve_free = mem_free;
}
void MVE_sfCallbacks(mve_cb_ShowFrame showframe)
{
mve_showframe = showframe;
}
void MVE_palCallbacks(mve_cb_SetPalette setpalette)
{
mve_setpalette = setpalette;
}
int MVE_rmPrepMovie(MVESTREAM_ptr_t &pMovie, void *src, int x, int y, int)
{
if (pMovie) {
mve_reset(pMovie.get());
return 0;
}
pMovie = mve_open(src);
if (!pMovie)
return 1;
g_destX = x;
g_destY = y;
auto &mve = *pMovie.get();
mve_set_handler(mve, MVE_OPCODE_ENDOFSTREAM, end_movie_handler);
mve_set_handler(mve, MVE_OPCODE_ENDOFCHUNK, end_chunk_handler);
mve_set_handler(mve, MVE_OPCODE_CREATETIMER, create_timer_handler);
mve_set_handler(mve, MVE_OPCODE_INITAUDIOBUFFERS, create_audiobuf_handler);
mve_set_handler(mve, MVE_OPCODE_STARTSTOPAUDIO, play_audio_handler);
mve_set_handler(mve, MVE_OPCODE_INITVIDEOBUFFERS, create_videobuf_handler);
mve_set_handler(mve, MVE_OPCODE_DISPLAYVIDEO, display_video_handler);
mve_set_handler(mve, MVE_OPCODE_AUDIOFRAMEDATA, audio_data_handler);
mve_set_handler(mve, MVE_OPCODE_AUDIOFRAMESILENCE, audio_data_handler);
mve_set_handler(mve, MVE_OPCODE_INITVIDEOMODE, init_video_handler);
mve_set_handler(mve, MVE_OPCODE_SETPALETTE, video_palette_handler);
mve_set_handler(mve, MVE_OPCODE_SETDECODINGMAP, video_codemap_handler);
mve_set_handler(mve, MVE_OPCODE_VIDEODATA, video_data_handler);
mve_play_next_chunk(mve); /* video initialization chunk */
mve_play_next_chunk(mve); /* audio initialization chunk */
return 0;
}
void MVE_getVideoSpec(MVE_videoSpec *vSpec)
{
vSpec->screenWidth = g_screenWidth;
vSpec->screenHeight = g_screenHeight;
vSpec->width = g_width;
vSpec->height = g_height;
vSpec->truecolor = g_truecolor;
}
MVE_StepStatus MVE_rmStepMovie(MVESTREAM &mve)
{
static int init_timer=0;
int cont=1;
if (!timer_started)
timer_start();
while (cont && !g_frameUpdated) // make a "step" be a frame, not a chunk...
cont = mve_play_next_chunk(mve);
g_frameUpdated = 0;
if (!cont)
return MVE_StepStatus::EndOfFile;
if (micro_frame_delay && !init_timer) {
timer_start();
init_timer = 1;
}
do_timer_wait();
return MVE_StepStatus::Continue;
}
void MVE_rmEndMovie(std::unique_ptr<MVESTREAM>)
{
timer_stop();
timer_created = 0;
if (mve_audio_canplay) {
// MD2211: if using SDL_Mixer, we never reinit sound, hence never close it
if (CGameArg.SndDisableSdlMixer)
{
SDL_CloseAudio();
}
mve_audio_canplay = 0;
}
mve_audio_buffers = {};
mve_audio_buflens = {};
mve_audio_curbuf_curpos=0;
mve_audio_bufhead=0;
mve_audio_buftail=0;
mve_audio_playing=0;
mve_audio_canplay=0;
mve_audio_flags = 0;
mve_audio_spec.reset();
audiobuf_created = 0;
g_vBuffers.clear();
g_pCurMap=NULL;
g_nMapLength=0;
videobuf_created = 0;
video_initialized = 0;
}
void MVE_rmHoldMovie()
{
timer_started = 0;
}
void MVE_sndInit(int x)
{
mve_audio_enabled = (x == -1 ? 0 : 1);
}