/* * This file is part of the DXX-Rebirth project . * 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 #include #include #include #ifdef _WIN32 # include #else # include # include # ifdef macintosh # include # include # else # include # include # include # endif // macintosh #endif // _WIN32 #include #if DXX_USE_SDLMIXER #include #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 #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(get_short(data+4)); if (g_spdFactorNum != 0) { temp = micro_frame_delay; temp *= g_spdFactorNum; temp /= g_spdFactorDenom; micro_frame_delay = static_cast(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 { MovieMemoryFree(p); } }; template struct MVE_audio_clamp { const unsigned scale; MVE_audio_clamp(const unsigned DigiVolume) : scale(DigiVolume) { } T operator()(const T &i) const { return (static_cast(i) * scale) / 8; } }; } static int audiobuf_created = 0; static void mve_audio_callback(void *userdata, unsigned char *stream, int len); static std::array, TOTAL_AUDIO_BUFFERS> mve_audio_buffers; static std::array 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 mve_audio_spec; static void mve_audio_callback(void *, unsigned char *stream, int len) { #ifdef DXX_REPORT_TOTAL_LENGTH int total=0; #endif 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(mve_audio_buffers[mve_audio_bufhead].get()))+mve_audio_curbuf_curpos, /* cur input position */ length); /* cur input length */ #ifdef DXX_REPORT_TOTAL_LENGTH total += length; #endif 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; } #ifdef DXX_REPORT_TOTAL_LENGTH //con_printf(CON_CRITICAL, "= <%d (%d), %d, %d>: %d", mve_audio_bufhead, mve_audio_curbuf_curpos, mve_audio_buftail, len, total); #endif /* return total; */ if (len != 0 /* ospace remaining */ && mve_audio_bufhead != mve_audio_buftail) /* buffers remaining */ { memcpy(stream, /* dest */ (reinterpret_cast(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 = std::make_unique(); 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 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(MovieMemoryAllocate(nsamp))); mveaudio_uncompress(p.get(), data); /* XXX */ } else { nsamp -= 8; data += 8; p.reset(reinterpret_cast(MovieMemoryAllocate(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(reinterpret_cast(p16) + nsamp), p16, MVE_audio_clamp(DigiVolume)); } else { int8_t *const p8 = reinterpret_cast(p.get()); std::transform(p8, p8 + nsamp, p8, MVE_audio_clamp(DigiVolume)); } } } else { p.reset(reinterpret_cast(MovieMemoryAllocate(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); const auto cvtbuf = std::make_unique(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(MovieMemoryAllocate(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 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(reinterpret_cast(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 *) { MovieShowFrame(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; MovieSetPalette(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; } int MVE_rmPrepMovie(MVESTREAM_ptr_t &pMovie, MVEFILE::stream_type *const 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) { 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); }