/* * Copyright (C) 2002-2024 D2X Project * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . */ #define AUDIO #include #include #include #include #include #include "byteswap.h" #include "chrono_timer.h" #include "decoders.h" #include "mvelib.h" #include "mve_audio.h" #ifdef AUDIO #include "movie_sound.h" #endif #define MVE_OPCODE_ENDOFSTREAM 0x00 // mcmd_end #define MVE_OPCODE_ENDOFCHUNK 0x01 // mcmd_next #define MVE_OPCODE_CREATETIMER 0x02 // mcmd_syncInit #define MVE_OPCODE_INITAUDIOBUFFERS 0x03 // mcmd_sndConfigure #define MVE_OPCODE_STARTSTOPAUDIO 0x04 // mcmd_sndSync #define MVE_OPCODE_INITVIDEOBUFFERS 0x05 // mcmd_nfConfig #define MVE_OPCODE_UNKNOWN_06 0x06 // mcmd_nfDecomp #define MVE_OPCODE_DISPLAYVIDEO 0x07 // mcmd_sfShowFrame #define MVE_OPCODE_AUDIOFRAMEDATA 0x08 // mcmd_sndAdd #define MVE_OPCODE_AUDIOFRAMESILENCE 0x09 // mcmd_sndSilence #define MVE_OPCODE_INITVIDEOMODE 0x0A // mcmd_gfxMode #define MVE_OPCODE_UNKNOWN_0B 0x0B // mcmd_palMakeSynthPalette #define MVE_OPCODE_SETPALETTE 0x0C // mcmd_palLoadPalette #define MVE_OPCODE_SETPALETTECOMPRESSED 0x0D // mcmd_palLoadCompPalette #define MVE_OPCODE_UNKNOWN_0E 0x0E // mcmd_nfChanges #define MVE_OPCODE_SETDECODINGMAP 0x0F // mcmd_nfParms #define MVE_OPCODE_UNKNOWN_10 0x10 // mcmd_nfDecompChg #define MVE_OPCODE_VIDEODATA 0x11 // mcmd_nfPkDecomp #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 short get_short(const unsigned char *data) { return D3::convert_le(data[0] | (data[1] << 8)); } static unsigned short get_ushort(const unsigned char *data) { return D3::convert_le(data[0] | (data[1] << 8)); } static int get_int(const unsigned char *data) { return D3::convert_le(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); } static unsigned int unhandled_chunks[32 * 256]; static int default_seg_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { unhandled_chunks[major << 8 | minor]++; return 1; } /************************* * general handlers *************************/ static int end_movie_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { return 0; } /************************* * timer handlers *************************/ /* * timer variables */ static int timer_created = 0; static int micro_frame_delay = 0; static int timer_started = 0; static unsigned long int timer_expire = 0; unsigned long int timer_getmicroseconds() { return D3::ChronoTimer::GetTimeUS(); } void timer_sleepmicroseconds(unsigned long int usec) { std::this_thread::sleep_for(std::chrono::microseconds(usec)); } static int create_timer_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { long temp; if (timer_created) return 1; else timer_created = 1; micro_frame_delay = get_int(data) * (int)get_short(data + 4); if (g_spdFactorNum != 0) { temp = micro_frame_delay; temp *= g_spdFactorNum; temp /= g_spdFactorDenom; micro_frame_delay = (int)temp; } return 1; } static void timer_stop() { timer_expire = 0; timer_started = 0; } static void timer_start() { timer_expire = timer_getmicroseconds(); timer_expire += micro_frame_delay; timer_started = 1; } #ifdef AUDIO static std::unique_ptr snd_ds; static int mve_audio_enabled = 1; #else static int mve_audio_enabled = 0; #endif static void do_timer_wait() { uint64_t ts; uint64_t tv; if (!timer_started) return; tv = timer_getmicroseconds(); if (tv > timer_expire) goto end; ts = timer_expire - tv; timer_sleepmicroseconds(ts); end: timer_expire += micro_frame_delay; } /************************* * audio handlers *************************/ static int create_audiobuf_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { #ifdef AUDIO if (!mve_audio_enabled) return 1; int flags = get_ushort(data + 2); int sample_rate = get_ushort(data + 4); // int desired_buffer = get_int(data + 6); int channels = (flags & MVE_AUDIO_FLAGS_STEREO) ? 2 : 1; int sample_size = (flags & MVE_AUDIO_FLAGS_16BIT) ? 2 : 1; bool is_compressed = false; if (minor > 0 && (flags & MVE_AUDIO_FLAGS_COMPRESSED)) { is_compressed = true; } snd_ds = std::make_unique(sample_rate, sample_size, channels, is_compressed); #endif return 1; } static int play_audio_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { #ifdef AUDIO if (snd_ds && snd_ds->IsInitialized()) { snd_ds->Play(); } #endif return 1; } static int audio_data_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { #ifdef AUDIO static const int selected_chan = 1; if (snd_ds && snd_ds->IsInitialized()) { int chan = get_ushort(data + 2); int size = get_ushort(data + 4); if (chan & selected_chan) { void *buf = malloc(size); if (major == MVE_OPCODE_AUDIOFRAMEDATA) { mveaudio_process((char *)buf, data, snd_ds->GetSampleSize(), snd_ds->IsCompressed()); } else { // SILENCE, MORTALS! memset(data, 0, size); } snd_ds->FillBuffer((char *)buf, size); free(buf); } } #endif return 1; } /************************* * video handlers *************************/ static int videobuf_created = 0; static int video_initialized = 0; int g_width, g_height; void *g_vBuffers = nullptr, *g_vBackBuf1, *g_vBackBuf2; static int g_destX, g_destY; static int g_screenWidth, g_screenHeight; static unsigned char *g_pCurMap = nullptr; static int g_nMapLength = 0; static int g_truecolor; static int create_videobuf_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { short w, h; short count, truecolor; if (videobuf_created) return 1; else videobuf_created = 1; w = get_short(data); h = get_short(data + 2); if (minor > 0) { count = get_short(data + 4); } else { count = 1; } 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 */ if (g_vBuffers == nullptr) g_vBackBuf1 = g_vBuffers = mve_alloc(g_width * g_height * 8); if (truecolor) { g_vBackBuf2 = (unsigned short *)g_vBackBuf1 + (g_width * g_height); } else { g_vBackBuf2 = (unsigned char *)g_vBackBuf1 + (g_width * g_height); } memset(g_vBackBuf1, 0, g_width * g_height * 4); g_truecolor = truecolor; return 1; } static int display_video_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { if (g_destX == -1) // center it g_destX = (g_screenWidth - g_width) >> 1; if (g_destY == -1) // center it g_destY = (g_screenHeight - g_height) >> 1; mve_showframe((unsigned char *)g_vBackBuf1, g_width, g_height, 0, 0, g_width, g_height, g_destX, g_destY, g_truecolor); g_frameUpdated = 1; return 1; } static int init_video_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { 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 major, unsigned char minor, unsigned char *data, int len, void *context) { short start, count; unsigned char *p; start = get_short(data); count = get_short(data + 2); p = data + 4; mve_setpalette(p - 3 * start, start, count); return 1; } static int video_codemap_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { g_pCurMap = data; g_nMapLength = len; return 1; } static int video_data_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { unsigned char *temp; // short nFrameHot = get_short(data); // short nFrameCold = get_short(data + 2); // short nXoffset = get_short(data + 4); // short nYoffset = get_short(data + 6); // short nXsize = get_short(data + 8); // short nYsize = get_short(data + 10); unsigned short nFlags = get_ushort(data + 12); if (nFlags & 1) { temp = (unsigned char *)g_vBackBuf1; g_vBackBuf1 = g_vBackBuf2; g_vBackBuf2 = temp; } /* convert the frame */ if (g_truecolor) { decodeFrame16((unsigned char *)g_vBackBuf1, g_pCurMap, g_nMapLength, data + 14, len - 14); } else { decodeFrame8((unsigned char *)g_vBackBuf1, g_pCurMap, g_nMapLength, data + 14, len - 14); } return 1; } static int end_chunk_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { g_pCurMap = nullptr; 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; } MVESTREAM* MVE_rmPrepMovie(void *src, int x, int y, int track) { MVESTREAM *mve; mve = mve_open(src); if (!mve) return nullptr; g_destX = x; g_destY = y; for (int i = 0; i < 32; i++) mve_set_handler(mve, i, default_seg_handler); 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_SETPALETTECOMPRESSED, default_seg_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 */ if (mve_audio_enabled) mve_play_next_chunk(mve); /* audio initialization chunk */ return mve; } 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; } int 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_ERR_EOF; if (micro_frame_delay && !init_timer) { timer_start(); init_timer = 1; } do_timer_wait(); return 0; } void MVE_rmEndMovie(MVESTREAM *mve) { timer_stop(); timer_created = 0; #ifdef AUDIO snd_ds.reset(); #endif mve_free(g_vBuffers); g_vBuffers = nullptr; g_pCurMap = nullptr; g_nMapLength = 0; videobuf_created = 0; video_initialized = 0; mve_close(mve); mve = nullptr; } void MVE_rmHoldMovie() { timer_started = 0; } void MVE_sndInit(bool enable) { #ifdef AUDIO if (enable) { mve_audio_enabled = 1; } else { mve_audio_enabled = 0; } #endif }