From 20d1a3738fc0eac0badde0f6a2df59473cd98303 Mon Sep 17 00:00:00 2001 From: Ilya Shurumov Date: Mon, 21 Sep 2020 00:21:36 +0600 Subject: [PATCH] - AVI (MJPEG) video player prototype for FMV --- src_rebuild/EMULATOR/LIBSPU.C | 5 + src_rebuild/GAME/C/FMVPLAY.C | 11 +- src_rebuild/premake5.lua | 15 +- src_rebuild/premake_libjpeg.lua | 58 ++ src_rebuild/utils/video_source/ReadAVI.cpp | 706 ++++++++++++++++++ src_rebuild/utils/video_source/ReadAVI.h | 181 +++++ .../utils/video_source/VideoPlayer.cpp | 268 +++++++ 7 files changed, 1234 insertions(+), 10 deletions(-) create mode 100644 src_rebuild/premake_libjpeg.lua create mode 100644 src_rebuild/utils/video_source/ReadAVI.cpp create mode 100644 src_rebuild/utils/video_source/ReadAVI.h create mode 100644 src_rebuild/utils/video_source/VideoPlayer.cpp diff --git a/src_rebuild/EMULATOR/LIBSPU.C b/src_rebuild/EMULATOR/LIBSPU.C index 415b1c8a..37623217 100644 --- a/src_rebuild/EMULATOR/LIBSPU.C +++ b/src_rebuild/EMULATOR/LIBSPU.C @@ -380,6 +380,11 @@ void SpuInit(void) _SpuInit(0); } +void SpuQuit(void) +{ + // do nothing! +} + void UpdateVoiceSample(SPUVoice& voice) { //if (!voice.sampledirty) diff --git a/src_rebuild/GAME/C/FMVPLAY.C b/src_rebuild/GAME/C/FMVPLAY.C index 00af78fb..3c930a32 100644 --- a/src_rebuild/GAME/C/FMVPLAY.C +++ b/src_rebuild/GAME/C/FMVPLAY.C @@ -155,10 +155,11 @@ void PlayFMV(unsigned char render) /* end block 3 */ // End Line: 223 +extern int FMV_main(RENDER_ARGS* args); + // [D] [A] void PlayRender(RENDER_ARGS *args) { -#ifdef PSX static unsigned long oldsp; StopAllChannels(); @@ -169,7 +170,7 @@ void PlayRender(RENDER_ARGS *args) args->screenx = draw_mode_pal.framex; args->screeny = draw_mode_pal.framey; args->subtitle = gSubtitles; - +#ifdef PSX if (Loadfile("FMV\\FMV.EXE", &DAT_800ff800) != 0) { oldsp = GetSp(); @@ -179,11 +180,11 @@ void PlayRender(RENDER_ARGS *args) Exec(&DAT_800ff810, 1, args); SetSp(oldsp); } - - ReInitSystem(); #else - // TODO: use jpsx? + FMV_main(args); #endif + ReInitSystem(); + } diff --git a/src_rebuild/premake5.lua b/src_rebuild/premake5.lua index 9eca2a1a..80a55223 100644 --- a/src_rebuild/premake5.lua +++ b/src_rebuild/premake5.lua @@ -1,10 +1,12 @@ -- premake5.lua -- you can redefine dependencies -local SDL2_DIR = os.getenv("SDL2_DIR") or "dependencies/SDL2" -local GLEW_DIR = os.getenv("GLEW_DIR") or "dependencies/glew" -local OPENAL_DIR = os.getenv("OPENAL_DIR") or "dependencies/openal-soft" -local GAME_REGION = os.getenv("GAME_REGION") or "NTSC_VERSION" -- or PAL_VERSION +SDL2_DIR = os.getenv("SDL2_DIR") or "dependencies/SDL2" +GLEW_DIR = os.getenv("GLEW_DIR") or "dependencies/glew" +OPENAL_DIR = os.getenv("OPENAL_DIR") or "dependencies/openal-soft" +JPEG_DIR = os.getenv("JPEG_DIR") or "dependencies/jpeg" + +GAME_REGION = os.getenv("GAME_REGION") or "NTSC_VERSION" -- or PAL_VERSION if not (GAME_REGION == "NTSC_VERSION" or GAME_REGION == "PAL_VERSION") then error("'GAME_REGION' should be 'NTSC_VERSION' or 'PAL_VERSION'") @@ -28,6 +30,8 @@ workspace "REDRIVER2" defines { "NDEBUG", } + + dofile("premake_libjpeg.lua") -- EMULATOR layer project "PSX" @@ -110,9 +114,10 @@ project "REDRIVER2" SDL2_DIR.."/include", GLEW_DIR.."/include", OPENAL_DIR.."/include", + JPEG_DIR.."/", } - links { "PSX" } -- only need to link emulator + links { "PSX", "jpeg" } -- only need to link emulator linkoptions { "/SAFESEH:NO", -- Image Has Safe Exception Handers: No. Because of openal-soft diff --git a/src_rebuild/premake_libjpeg.lua b/src_rebuild/premake_libjpeg.lua new file mode 100644 index 00000000..a433c5f9 --- /dev/null +++ b/src_rebuild/premake_libjpeg.lua @@ -0,0 +1,58 @@ +project "jpeg" + kind "StaticLib" + language "C" + targetdir "lib/%{cfg.buildcfg}" + + location(JPEG_DIR) + + files { + JPEG_DIR.."/jaricom.c", + JPEG_DIR.."/jcapimin.c", + JPEG_DIR.."/jcapistd.c", + JPEG_DIR.."/jcarith.c", + JPEG_DIR.."/jccoefct.c", + JPEG_DIR.."/jccolor.c", + JPEG_DIR.."/jcdctmgr.c", + JPEG_DIR.."/jchuff.c", + JPEG_DIR.."/jcinit.c", + JPEG_DIR.."/jcmainct.c", + JPEG_DIR.."/jcmarker.c", + JPEG_DIR.."/jcmaster.c", + JPEG_DIR.."/jcomapi.c", + JPEG_DIR.."/jcparam.c", + JPEG_DIR.."/jcprepct.c", + JPEG_DIR.."/jcsample.c", + JPEG_DIR.."/jctrans.c", + JPEG_DIR.."/jdapimin.c", + JPEG_DIR.."/jdapistd.c", + JPEG_DIR.."/jdarith.c", + JPEG_DIR.."/jdatadst.c", + JPEG_DIR.."/jdatasrc.c", + JPEG_DIR.."/jdcoefct.c", + JPEG_DIR.."/jdcolor.c", + JPEG_DIR.."/jddctmgr.c", + JPEG_DIR.."/jdhuff.c", + JPEG_DIR.."/jdinput.c", + JPEG_DIR.."/jdmainct.c", + JPEG_DIR.."/jdmarker.c", + JPEG_DIR.."/jdmaster.c", + JPEG_DIR.."/jdmerge.c", + JPEG_DIR.."/jdpostct.c", + JPEG_DIR.."/jdsample.c", + JPEG_DIR.."/jdtrans.c", + JPEG_DIR.."/jerror.c", + JPEG_DIR.."/jfdctflt.c", + JPEG_DIR.."/jfdctfst.c", + JPEG_DIR.."/jfdctint.c", + JPEG_DIR.."/jidctflt.c", + JPEG_DIR.."/jidctfst.c", + JPEG_DIR.."/jidctint.c", + JPEG_DIR.."/jmemmgr.c", + JPEG_DIR.."/jmemnobs.c", + JPEG_DIR.."/jquant1.c", + JPEG_DIR.."/jquant2.c", + JPEG_DIR.."/jutils.c" + } + + filter "configurations:Release" + optimize "Full" diff --git a/src_rebuild/utils/video_source/ReadAVI.cpp b/src_rebuild/utils/video_source/ReadAVI.cpp new file mode 100644 index 00000000..1fa64628 --- /dev/null +++ b/src_rebuild/utils/video_source/ReadAVI.cpp @@ -0,0 +1,706 @@ +/* + * ReadAVI.cpp + * + * + * Copyright (c) 2004-2013, Michael Kohn http://www.mikekohn.net/ + * + * Copyright (c) 2018, olegvedi@gmail.com (C++ implementation and adds) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "ReadAVI.h" + +#include +#include + +#define ZEROIZE(x) {memset(&x, 0, sizeof(x));} + +using namespace std; + +#define VERBOSE +#define DBG_LVL_DEF 2 + +#ifdef VERBOSE +#define MSG_DBG(dl, format, ...) {if(dl <= DBG_LVL_DEF ) fprintf(stdout, format ,##__VA_ARGS__);} +#else +#define MSG_DBG(dl, format, ...) +#endif + +ReadAVI::chunk_type_int_t ReadAVI::chunk_types[ChunkTypesCnt] = { + {"db", ctype_uncompressed_video_frame }, + {"dc", ctype_compressed_video_frame }, + {"pc", ctype_palette_change }, + {"wb", ctype_audio_data } }; + +ReadAVI::ReadAVI(const char* filename) +{ + data_buf = NULL; + stream_format_vid.palette = NULL; + data_buf_size = 0; + inFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + inFile.open(filename, ios_base::ate | ios_base::in | ios_base::binary); + fileSize = inFile.tellg(); + inFile.close(); + inFile.open(filename, ios_base::in | ios_base::binary); + + try { + parse_riff(); + } + catch (std::system_error& e) { //file corrupted? + std::cerr << e.code().message() << "\n"; + if (index_entries.size() == 0) { + free(); + throw; + } + } +} + +ReadAVI::~ReadAVI() +{ + free(); +} + +void ReadAVI::free() +{ + if (inFile.is_open()) { + inFile.close(); + } + + delete[] stream_format_vid.palette; + delete[] data_buf; +} + +void ReadAVI::check_data_buf(unsigned size) +{ + if (data_buf_size < size) { + delete[] data_buf; + data_buf = new unsigned char[size + 1]; + data_buf_size = size; + } +} + +int ReadAVI::GetFrameFromIndex(frame_entry_t* frame_entry) +{ + if (frame_entry->pointer >= index_entries.size()) { + MSG_DBG(2, "Pointer out of range\n"); + return -5; + } + for (unsigned i = frame_entry->pointer; i < index_entries.size(); i++) { + if (index_entries[i].type & frame_entry->type) { + check_data_buf(index_entries[i].dwChunkLength); + try { + inFile.seekg(index_entries[i].dwChunkOffset, ios_base::beg); + read_chars_bin(data_buf, index_entries[i].dwChunkLength); + } + catch (...) { + return -1; + } + frame_entry->buf = data_buf; + frame_entry->pointer = i + 1; + frame_entry->stream_num = index_entries[i].stream_num; + frame_entry->type = index_entries[i].type; + return index_entries[i].dwChunkLength; + } + } + MSG_DBG(2, "No more frames\n"); + return -1; +} + +#ifdef VERBOSE +int ReadAVI::hex_dump_chunk(int chunk_len) +{ + char chars[17]; + unsigned char c; + int ch, n; + + chars[16] = 0; + + for (n = 0; n < chunk_len; n++) { + if ((n % 16) == 0) { + if (n != 0) + MSG_DBG(2, "%s\n", chars); + MSG_DBG(2, " "); + memset(chars, ' ', 16); + } + read_chars_bin(&c, 1); + ch = c; + if (ch == EOF) + break; + MSG_DBG(2, "%02x ", ch); + if (ch >= ' ' && ch <= 126) { + chars[n % 16] = ch; + } + else { + chars[n % 16] = '.'; + } + } + + if ((n % 16) != 0) { + for (ch = n % 16; ch < 16; ch++) { + MSG_DBG(2, " "); + } + } + MSG_DBG(2, "%s\n", chars); + + return 0; +} +#endif + +int ReadAVI::decodeCkid(char* ckid, chunk_type_t* chunk_type) +{ + int stream = (ckid[0] - '0') * 10 + (ckid[1] - '0'); + for (int i = 0; i < ChunkTypesCnt; i++) { + if (chunk_types[i].type_id[0] == ckid[2] && chunk_types[i].type_id[1] == ckid[3]) { + *chunk_type = chunk_types[i].type; + return stream; + } + } + return -1; +} + +int ReadAVI::parse_idx1(int chunk_len) +{ + index_entry_t index_entry; + index_entry.type = ctype_none; + int t; + + MSG_DBG(2, " IDX1\n"); + MSG_DBG(2, " -------------------------------\n"); + MSG_DBG(2, " stream type dwFlags dwChunkOffset dwChunkLength\n"); + + for (t = 0; t < chunk_len / 16; t++) { + char buf[5]; + read_chars(buf, 4); + index_entry.stream_num = decodeCkid(buf, &index_entry.type); + index_entry.dwFlags = read_int(); + index_entry.dwChunkOffset = read_int() + movi_offset + 0x10; + index_entry.dwChunkLength = read_int(); + + MSG_DBG(2, " %d %d 0x%08x 0x%08x 0x%08x\n", index_entry.stream_num, index_entry.type, + index_entry.dwFlags, index_entry.dwChunkOffset, index_entry.dwChunkLength); + index_entries.push_back(index_entry); + } + + MSG_DBG(2, "\n"); + + return 0; +} + +int ReadAVI::read_avi_header() +{ +#ifdef VERBOSE + long offset = inFile.tellg(); +#endif + avi_header.TimeBetweenFrames = read_int(); + avi_header.MaximumDataRate = read_int(); + avi_header.PaddingGranularity = read_int(); + avi_header.Flags = read_int(); + avi_header.TotalNumberOfFrames = read_int(); + avi_header.NumberOfInitialFrames = read_int(); + avi_header.NumberOfStreams = read_int(); + avi_header.SuggestedBufferSize = read_int(); + avi_header.Width = read_int(); + avi_header.Height = read_int(); + avi_header.TimeScale = read_int(); + avi_header.DataRate = read_int(); + avi_header.StartTime = read_int(); + avi_header.DataLength = read_int(); + + MSG_DBG(2, " offset=0x%lx\n", offset); + MSG_DBG(2, " TimeBetweenFrames: %d\n", avi_header.TimeBetweenFrames); + MSG_DBG(2, " MaximumDataRate: %d\n", avi_header.MaximumDataRate); + MSG_DBG(2, " PaddingGranularity: %d\n", avi_header.PaddingGranularity); + MSG_DBG(2, " Flags: %d\n", avi_header.Flags); + MSG_DBG(2, " TotalNumberOfFrames: %d\n", avi_header.TotalNumberOfFrames); + MSG_DBG(2, " NumberOfInitialFrames: %d\n", avi_header.NumberOfInitialFrames); + MSG_DBG(2, " NumberOfStreams: %d\n", avi_header.NumberOfStreams); + MSG_DBG(2, " SuggestedBufferSize: %d\n", avi_header.SuggestedBufferSize); + MSG_DBG(2, " Width: %d\n", avi_header.Width); + MSG_DBG(2, " Height: %d\n", avi_header.Height); + MSG_DBG(2, " TimeScale: %d\n", avi_header.TimeScale); + MSG_DBG(2, " DataRate: %d\n", avi_header.DataRate); + MSG_DBG(2, " StartTime: %d\n", avi_header.StartTime); + MSG_DBG(2, " DataLength: %d\n", avi_header.DataLength); + + return 0; +} + +static void print_data_handler(unsigned char* handler) +{ + int t; + + for (t = 0; t < 4; t++) { + if ((handler[t] >= 'a' && handler[t] <= 'z') || (handler[t] >= 'A' && handler[t] <= 'Z') + || (handler[t] >= '0' && handler[t] <= '9')) + { + MSG_DBG(2, "%c", handler[t]); + } + else { + MSG_DBG(2, "[0x%02x]", handler[t]); + } + } +} + +int ReadAVI::read_stream_header(stream_header_t* sheader) +{ +#ifdef VERBOSE + long offset = inFile.tellg(); +#endif + read_chars(sheader->DataType, 4); + read_chars(sheader->DataHandler, 4); + sheader->Flags = read_int(); + sheader->Priority = read_int(); + sheader->InitialFrames = read_int(); + sheader->TimeScale = read_int(); + sheader->DataRate = read_int(); + sheader->StartTime = read_int(); + sheader->DataLength = read_int(); + sheader->SuggestedBufferSize = read_int(); + sheader->Quality = read_int(); + sheader->SampleSize = read_int(); + + MSG_DBG(2, " offset=0x%lx\n", offset); + MSG_DBG(2, " DataType: %s\n", sheader->DataType); + MSG_DBG(2, " DataHandler: "); + print_data_handler((unsigned char*)sheader->DataHandler); + MSG_DBG(2, "\n"); + MSG_DBG(2, " Flags: %d\n", sheader->Flags); + MSG_DBG(2, " Priority: %d\n", sheader->Priority); + MSG_DBG(2, " InitialFrames: %d\n", sheader->InitialFrames); + MSG_DBG(2, " TimeScale: %d\n", sheader->TimeScale); + MSG_DBG(2, " DataRate: %d\n", sheader->DataRate); + MSG_DBG(2, " StartTime: %d\n", sheader->StartTime); + MSG_DBG(2, " DataLength: %d\n", sheader->DataLength); + MSG_DBG(2, " SuggestedBufferSize: %d\n", sheader->SuggestedBufferSize); + MSG_DBG(2, " Quality: %d\n", sheader->Quality); + MSG_DBG(2, " SampleSize: %d\n", sheader->SampleSize); + + return 0; +} + +int ReadAVI::read_stream_format() +{ + int t, r, g, b; +#ifdef VERBOSE + long offset = inFile.tellg(); +#endif + stream_format_vid.header_size = read_int(); + stream_format_vid.image_width = read_int(); + stream_format_vid.image_height = read_int(); + stream_format_vid.number_of_planes = read_word(); + stream_format_vid.bits_per_pixel = read_word(); + read_chars(stream_format_vid.compression_type, 4); + //stream_format_vid.compression_type = read_int(); + stream_format_vid.image_size_in_bytes = read_int(); + stream_format_vid.x_pels_per_meter = read_int(); + stream_format_vid.y_pels_per_meter = read_int(); + stream_format_vid.colors_used = read_int(); + stream_format_vid.colors_important = read_int(); + stream_format_vid.palette = 0; + + if (stream_format_vid.colors_important != 0) { + stream_format_vid.palette = new int[stream_format_vid.colors_important]; + for (t = 0; t < stream_format_vid.colors_important; t++) { + unsigned char buf[3]; + read_chars_bin(buf, 3); + b = buf[0]; + g = buf[1]; + r = buf[2]; + stream_format_vid.palette[t] = (r << 16) + (g << 8) + b; + } + } + + MSG_DBG(2, " offset=0x%lx\n", offset); + MSG_DBG(2, " header_size: %d\n", stream_format_vid.header_size); + MSG_DBG(2, " image_width: %d\n", stream_format_vid.image_width); + MSG_DBG(2, " image_height: %d\n", stream_format_vid.image_height); + MSG_DBG(2, " number_of_planes: %d\n", stream_format_vid.number_of_planes); + MSG_DBG(2, " bits_per_pixel: %d\n", stream_format_vid.bits_per_pixel); + MSG_DBG(2, " compression_type: %s\n", stream_format_vid.compression_type); + /* MSG_DBG(2, " compression_type: %04x (%c%c%c%c)\n", stream_format_vid.compression_type, + ((stream_format_vid.compression_type) & 255), ((stream_format_vid.compression_type >> 8) & 255), + ((stream_format_vid.compression_type >> 16) & 255), ((stream_format_vid.compression_type >> 24) & 255)); + */ + MSG_DBG(2, " image_size_in_bytes: %d\n", stream_format_vid.image_size_in_bytes); + MSG_DBG(2, " x_pels_per_meter: %d\n", stream_format_vid.x_pels_per_meter); + MSG_DBG(2, " y_pels_per_meter: %d\n", stream_format_vid.y_pels_per_meter); + MSG_DBG(2, " colors_used: %d\n", stream_format_vid.colors_used); + MSG_DBG(2, " colors_important: %d\n", stream_format_vid.colors_important); + + return 0; +} + +int ReadAVI::read_stream_format_auds() +{ +#ifdef VERBOSE + long offset = inFile.tellg(); +#endif + stream_format_auds.format = read_word(); + stream_format_auds.channels = read_word(); + stream_format_auds.samples_per_second = read_int(); + stream_format_auds.bytes_per_second = read_int(); + int block_align = read_word(); + stream_format_auds.block_size_of_data = read_word(); + stream_format_auds.bits_per_sample = read_word(); + //stream_format_auds.extended_size=read_word(); + + MSG_DBG(2, " offset=0x%lx\n", offset); + MSG_DBG(2, " format: %d\n", stream_format_auds.format); + MSG_DBG(2, " channels: %d\n", stream_format_auds.channels); + MSG_DBG(2, " samples_per_second: %d\n", stream_format_auds.samples_per_second); + MSG_DBG(2, " bytes_per_second: %d\n", stream_format_auds.bytes_per_second); + MSG_DBG(2, " block_align: %d\n", block_align); + MSG_DBG(2, " block_size_of_data: %d\n", stream_format_auds.block_size_of_data); + MSG_DBG(2, " bits_per_sample: %d\n", stream_format_auds.bits_per_sample); + + return 0; +} + +int ReadAVI::parse_hdrl_list() +{ + char chunk_id[5]; + int chunk_size; + char chunk_type[5]; + int end_of_chunk; + int next_chunk; +#ifdef VERBOSE + long offset = inFile.tellg(); +#endif + int stream_type = 0; // 0=video 1=sound + + read_chars(chunk_id, 4); + chunk_size = read_int(); + read_chars(chunk_type, 4); + + MSG_DBG(2, " AVI Header LIST (id=%s size=%d type=%s offset=0x%lx)\n", chunk_id, chunk_size, chunk_type, offset); + MSG_DBG(2, " {\n"); + + end_of_chunk = chunk_size - 4 + inFile.tellg(); + if ((end_of_chunk % 4) != 0) { + //MSG_DBG(2, "Adjusting end of chunk %d\n", end_of_chunk); + //end_of_chunk=end_of_chunk+(4-(end_of_chunk%4)); + //MSG_DBG(2, "Adjusting end of chunk %d\n", end_of_chunk); + } + + if (strcmp(chunk_id, "JUNK") == 0) { + inFile.seekg(end_of_chunk, ios_base::beg); + MSG_DBG(2, " }\n"); + return 0; + } + + while (inFile.tellg() < end_of_chunk) { +#ifdef VERBOSE + long offset = inFile.tellg(); +#endif + read_chars(chunk_type, 4); + chunk_size = read_int(); + next_chunk = chunk_size + inFile.tellg(); + if ((chunk_size % 4) != 0) { + //MSG_DBG(2, "Chunk size not a multiple of 4?\n"); + //chunk_size=chunk_size+(4-(chunk_size%4)); + } + + MSG_DBG(2, " %.4s (size=%d offset=0x%lx)\n", chunk_type, chunk_size, offset); + MSG_DBG(2, " {\n"); + + if (strcmp("strh", chunk_type) == 0) + { + long marker = inFile.tellg(); + char buffer[5]; + read_chars(buffer, 4); + inFile.seekg(marker, ios_base::beg); + + if (strcmp(buffer, "vids") == 0) + { + stream_type = 0; + read_stream_header(&stream_header_vid); + } + else if (strcmp(buffer, "auds") == 0) + { + stream_type = 1; + read_stream_header(&stream_header_auds); + } + else + { + MSG_DBG(2, "Unknown stream type %s\n", buffer); + return -1; + } + } + else if (strcmp("strf", chunk_type) == 0) + { + if (stream_type == 0) { + read_stream_format(); + } + else { + read_stream_format_auds(); + } + } + else if (strcmp("strd", chunk_type) == 0) + { + + } + else + { + MSG_DBG(2, " Unknown chunk type: %s\n", chunk_type); + // skip_chunk(); + } + + MSG_DBG(2, " }\n"); + + inFile.seekg(next_chunk, ios_base::beg); + } + + MSG_DBG(2, " }\n"); + inFile.seekg(end_of_chunk, ios_base::beg); + + return 0; +} + +int ReadAVI::parse_movi(int size) +{ + char chunk_id[5]; + long end_of_chunk; + index_entry_t index_entry; + int blk_size; + + do { + long offset = inFile.tellg(); + /* + if (fileSize - offset < 8) + break; + */ + read_chars(chunk_id, 4); + index_entry.stream_num = decodeCkid(chunk_id, &index_entry.type); + + if (index_entry.stream_num < 0) { + inFile.seekg(offset - 4, ios_base::beg); + break; + } + + index_entry.dwChunkLength = read_int(); + + if (!(avi_header.Flags & AVIF_HASINDEX)) { + index_entry.dwChunkOffset = offset + 8; + index_entries.push_back(index_entry); + } + + MSG_DBG(3, " AVI Movi Chunk ( id=%s(%d-%d) chunk_size=%d offset=0x%lx)\n", chunk_id, index_entry.type, + index_entry.stream_num, index_entry.dwChunkLength, offset); + MSG_DBG(3, " {\n"); + + end_of_chunk = index_entry.dwChunkLength + inFile.tellg(); + end_of_chunk = (end_of_chunk + 1) & ~1; + /* if ((end_of_chunk % 4) != 0) { + end_of_chunk = end_of_chunk + (4 - (end_of_chunk % 4)); + } + */ + // MSG_DBG(2, "end_of_chunk:%d %X\n", end_of_chunk, end_of_chunk); + inFile.seekg(end_of_chunk, ios_base::beg); + MSG_DBG(3, " }\n"); + + blk_size = end_of_chunk - offset; + size -= blk_size; + MSG_DBG(3, "size:%d \n", size); + + } while (size > 7); + + return 0; +} + +int ReadAVI::parse_hdrl(unsigned int size) +{ + char chunk_id[5]; + int chunk_size; + int end_of_chunk; + long offset = inFile.tellg(); + + read_chars(chunk_id, 4); + chunk_size = read_int(); + + MSG_DBG(2, " AVI Header Chunk (id=%s size=%d offset=0x%lx)\n", chunk_id, chunk_size, offset); + MSG_DBG(2, " {\n"); + + end_of_chunk = chunk_size + inFile.tellg(); + if ((end_of_chunk % 4) != 0) { + end_of_chunk = end_of_chunk + (4 - (end_of_chunk % 4)); + } + + read_avi_header(); + MSG_DBG(2, " }\n"); + + while (inFile.tellg() < offset + size - 4) { + //MSG_DBG(2, "Should end at 0x%lx 0x%lx\n",offset+size,inFile.tellg()); + parse_hdrl_list(); + } + + return 0; +} + +int ReadAVI::parse_riff() +{ + char chunk_id[5]; + int chunk_size; + char chunk_type[5]; + int end_of_chunk, end_of_subchunk; + + ZEROIZE(avi_header); + ZEROIZE(stream_header_vid); + ZEROIZE(stream_format_vid); + ZEROIZE(stream_header_auds); + ZEROIZE(stream_format_auds); +#ifdef VERBOSE + long offset = inFile.tellg(); +#endif + read_chars(chunk_id, 4); + chunk_size = read_int(); + read_chars(chunk_type, 4); + + MSG_DBG(2, "RIFF Chunk (id=%s size=%d type=%s offset=0x%lx)\n", chunk_id, chunk_size, chunk_type, offset); + MSG_DBG(2, "{\n"); + + if (strcmp("RIFF", chunk_id) != 0) + { + MSG_DBG(2, "Not a RIFF file.\n"); + return 1; + } + else if (strcmp("AVI ", chunk_type) != 0) + { + MSG_DBG(2, "Not an AVI file.\n"); + return 1; + } + + end_of_chunk = chunk_size - 4 + inFile.tellg(); + + while (inFile.tellg() < end_of_chunk) { + long offset = inFile.tellg(); + read_chars(chunk_id, 4); + chunk_size = read_int(); + end_of_subchunk = chunk_size + inFile.tellg(); + + if (strcmp("JUNK", chunk_id) == 0 || strcmp("PAD ", chunk_id) == 0) { + chunk_type[0] = 0; + } + else { + read_chars(chunk_type, 4); + } + + MSG_DBG(2, " New Chunk (id=%s size=%d type=%s offset=0x%lx)\n", chunk_id, chunk_size, chunk_type, offset); + MSG_DBG(2, " {\n"); + + fflush(stdout); + + if (strcmp("JUNK", chunk_id) == 0 || strcmp("PAD ", chunk_id) == 0) { + if ((chunk_size % 4) != 0) { + chunk_size = chunk_size + (4 - (chunk_size % 4)); + } +#ifdef VERBOSE + hex_dump_chunk(chunk_size); +#endif + } + else if (strcmp("INFO", chunk_type) == 0) { + if ((chunk_size % 4) != 0) { + chunk_size = chunk_size + (4 - (chunk_size % 4)); + } +#ifdef VERBOSE + hex_dump_chunk(chunk_size); +#endif + } + else if (strcmp("hdrl", chunk_type) == 0) { + parse_hdrl(chunk_size); + /* skip_chunk(); */ + } + else if (strcmp("movi", chunk_type) == 0) { + movi_offset = offset; + parse_movi(chunk_size); + } + else if (strcmp("idx1", chunk_id) == 0) { + inFile.seekg(inFile.tellg() - std::streamoff(4), ios_base::beg); + parse_idx1(chunk_size); + }/* else if (strcmp("indx", chunk_id) == 0) { + inFile.seekg(inFile.tellg() - 4L, ios_base::beg); + parse_indx(chunk_size); + }*/else { + MSG_DBG(2, " Unknown chunk at %d (%4s)\n", (int)inFile.tellg() - 8, chunk_type); + if (chunk_size == 0) + break; + } + + inFile.seekg(end_of_subchunk, ios_base::beg); + MSG_DBG(2, " }\n"); + + } + + if (stream_format_vid.palette) { + delete[] stream_format_vid.palette; + stream_format_vid.palette = NULL; + } + + MSG_DBG(2, "}\n"); + + return 0; +} + +int ReadAVI::read_int() +{ + int c; + unsigned char buf[4]; + + inFile.read((char*)buf, 4); + c = buf[0]; + c = c + (buf[1] << 8); + c = c + (buf[2] << 16); + c = c + (buf[3] << 24); + + return c; +} + +int ReadAVI::read_word() +{ + int c; + unsigned char buf[2]; + + inFile.read((char*)buf, 2); + c = buf[0]; + c = c + (buf[1] << 8); + + return c; +} + +void ReadAVI::read_chars(char* s, int count) +{ + inFile.read(s, count); + s[count] = 0; +} + +void ReadAVI::read_chars_bin(unsigned char* s, int count) +{ + inFile.read((char*)s, count); +} diff --git a/src_rebuild/utils/video_source/ReadAVI.h b/src_rebuild/utils/video_source/ReadAVI.h new file mode 100644 index 00000000..77bb3f26 --- /dev/null +++ b/src_rebuild/utils/video_source/ReadAVI.h @@ -0,0 +1,181 @@ +/* + * ReadAVI.h + * + * Created on: 8 мая 2018 г. + * Author: olegvedi@gmail.com + */ + +#ifndef READAVI_H_ +#define READAVI_H_ + +#include +#include + +class ReadAVI { + enum { + AVIF_HASINDEX = 0x10, + AVIF_MUSTUSEINDEX = 0x20, + AVIF_ISINTERLEAVED = 0x100, + AVIF_WASCAPTUREFILE = 0x10000, + AVIF_COPYRIGHTED = 0x20000, + }; + +public: + typedef struct { + int TimeBetweenFrames; + int MaximumDataRate; + int PaddingGranularity; + int Flags; + int TotalNumberOfFrames; + int NumberOfInitialFrames; + int NumberOfStreams; + int SuggestedBufferSize; + int Width; + int Height; + int TimeScale; + int DataRate; + int StartTime; + int DataLength; + } avi_header_t; + + typedef struct { + char DataType[5]; + char DataHandler[5]; + int Flags; + int Priority; + int InitialFrames; + int TimeScale; + int DataRate; + int StartTime; + int DataLength; + int SuggestedBufferSize; + int Quality; + int SampleSize; + } stream_header_t; + + typedef struct { + int header_size; + int image_width; + int image_height; + int number_of_planes; + int bits_per_pixel; + char compression_type[5]; + int image_size_in_bytes; + int x_pels_per_meter; + int y_pels_per_meter; + int colors_used; + int colors_important; + int* palette; + } stream_format_t; + + typedef struct { + int header_size; + int format; + int channels; + int samples_per_second; + int bytes_per_second; + int block_size_of_data; + int bits_per_sample; + int extended_size; + } stream_format_auds_t; + +#define ChunkTypesCnt 4 + typedef enum { + ctype_none = 0, + ctype_uncompressed_video_frame = 1, + ctype_compressed_video_frame = 2, + ctype_palette_change = 4, + ctype_audio_data = 8, + ctype_video_data = 1 + 2, + } chunk_type_t; + + ReadAVI(const char* filename); + virtual ~ReadAVI(); + + avi_header_t GetAviHeader() + { + return avi_header; + } + stream_header_t GetVideoStreamHeader() + { + return stream_header_vid; + } + stream_format_t GetVideoFormat() + { + return stream_format_vid; + } + stream_header_t GetAudioStreamHeader() + { + return stream_header_auds; + } + stream_format_auds_t GetAudioFormat() + { + return stream_format_auds; + } + + unsigned GetIndexCnt() + { + return index_entries.size(); + } + + typedef struct { + chunk_type_t type; + unsigned stream_num; + unsigned char* buf; + // unsigned frame_size; + unsigned pointer; + } frame_entry_t; + + int GetFrameFromIndex(frame_entry_t* frame_entry); + +private: + typedef struct { + chunk_type_t type; + int stream_num; + int dwFlags; + int dwChunkOffset; + int dwChunkLength; + } index_entry_t; + + typedef struct { + char type_id[3]; + chunk_type_t type; + } chunk_type_int_t; + + static chunk_type_int_t chunk_types[ChunkTypesCnt]; + + std::vector index_entries; + std::ifstream inFile; + avi_header_t avi_header; + stream_header_t stream_header_vid; + stream_format_t stream_format_vid; + stream_header_t stream_header_auds; + stream_format_auds_t stream_format_auds; + long fileSize; + long movi_offset; + unsigned char* data_buf; + unsigned data_buf_size; + + int hex_dump_chunk(int chunk_len); + + int parse_riff(); + int parse_hdrl_list(); + int parse_idx1(int chunk_len); + //int parse_indx(int chunk_len); + int read_avi_header(); + int read_stream_header(stream_header_t* sheader); + int read_stream_format(); + int read_stream_format_auds(); + int parse_hdrl(unsigned int size); + int parse_movi(int size); + + int decodeCkid(char* ckid, chunk_type_t* chunk_type); + int read_int(); + int read_word(); + void read_chars(char* s, int count); + void read_chars_bin(unsigned char* s, int count); + void check_data_buf(unsigned size); + void free(); +}; + +#endif /* READAVI_H_ */ diff --git a/src_rebuild/utils/video_source/VideoPlayer.cpp b/src_rebuild/utils/video_source/VideoPlayer.cpp new file mode 100644 index 00000000..6f750a5a --- /dev/null +++ b/src_rebuild/utils/video_source/VideoPlayer.cpp @@ -0,0 +1,268 @@ +/* + * player.h + * + * Created on: 8 мая 2018 г. + * Author: olegvedi@gmail.com + */ + +#include +#include +#include +#include + +extern "C" +{ +#include +} + + +#include "EMULATOR_PRIVATE.H" + +#include "ReadAVI.h" + +#include "DRIVER2.H" +#include "C\PAD.H" + +int UnpackJPEG(unsigned char* src_buf, unsigned src_length, unsigned bpp, unsigned char* dst_buf) +{ + // it's rough but it works... + jpeg_decompress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + + jpeg_mem_src(&cinfo, src_buf, src_length); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + for (u_char* curr_scanline = dst_buf; cinfo.output_scanline < cinfo.output_height; curr_scanline += cinfo.output_width * cinfo.num_components) + { + jpeg_read_scanlines(&cinfo, &curr_scanline, 1); + + for (int s = 0; s < cinfo.output_width * cinfo.num_components; s += cinfo.num_components) + { + // flip RGB + u_char tmp = curr_scanline[s]; + curr_scanline[s] = curr_scanline[s+2]; + curr_scanline[s+2] = tmp; + } + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return 0; +} + +// emulator window TODO: interface +extern SDL_Window* g_window; + +void GetMovieRectangle(SDL_Rect& rect, ReadAVI::stream_format_t& strFmt) +{ + int windowWidth, windowHeight; + Emulator_GetScreenSize(windowWidth, windowHeight); + + float psxScreenW = 320.0f; + float psxScreenH = 240.0f; + + rect.x = 0; + rect.y = (psxScreenH - strFmt.image_height) / 2; + rect.w = strFmt.image_width; + rect.h = strFmt.image_height; + + const float video_aspect = float(strFmt.image_width) / float(strFmt.image_height + 48); + float emuScreenAspect = float(windowHeight) / float(windowWidth); + + { + // first map to 0..1 + float clipRectX = (float)(rect.x - activeDispEnv.disp.x) / psxScreenW; + float clipRectY = (float)(rect.y - activeDispEnv.disp.y) / psxScreenH; + float clipRectW = (float)(rect.w) / psxScreenW; + float clipRectH = (float)(rect.h) / psxScreenH; + + // then map to screen + clipRectY -= 0.5f; + + clipRectY /= video_aspect * emuScreenAspect; + clipRectH /= emuScreenAspect * video_aspect; + + clipRectY += 0.5f; + + rect.x = clipRectX * windowWidth; + rect.y = clipRectY * windowHeight; + rect.w = clipRectW * windowWidth; + rect.h = clipRectH * windowHeight; + } +} + +// send audio buffer +void QueueAudioBuffer(ALuint buffer, ALuint source, ReadAVI::frame_entry_t& frame_entry, ReadAVI::stream_format_auds_t& audio_format, int frame_offset, int frame_size) +{ + ALenum alFormat; + + if (audio_format.block_size_of_data == 8) + alFormat = audio_format.channels == 2 ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8; + else if (audio_format.block_size_of_data == 16) + alFormat = audio_format.channels == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + else + alFormat = AL_FORMAT_MONO16; + + // upload to specific buffer + alBufferData(buffer, alFormat, frame_entry.buf + frame_offset, frame_size, audio_format.samples_per_second); + + // queue after uploading + alSourceQueueBuffers(source, 1, &buffer); +} + +void DoPlayFMV(RENDER_ARG* arg, int subtitles) +{ + int fd = arg->render - (arg->render % 10); + if (fd > 0) + fd /= 10; + + char filename[250]; + sprintf(filename, "DRIVER2\\FMV\\%d\\RENDER%d.STR[0].AVI", fd, arg->render); + + ReadAVI readAVI(filename); + + int indexes = readAVI.GetIndexCnt(); + printf("indexes:%d\n", indexes); + + ReadAVI::avi_header_t avi_header = readAVI.GetAviHeader(); + + ReadAVI::stream_format_t stream_format = readAVI.GetVideoFormat(); + ReadAVI::stream_format_auds_t audio_format = readAVI.GetAudioFormat(); + + printf("video stream compression: %s\n", stream_format.compression_type); + printf("audio stream format: %d\n", audio_format.format); + + if (strcmp(stream_format.compression_type, "MJPG")) { + printf("Only MJPG supported\n"); + return; + } + + SDL_Surface* BufSurface = SDL_CreateRGBSurface(0, stream_format.image_width, stream_format.image_height, stream_format.bits_per_pixel, 0, 0, 0, 0); + + ReadAVI::frame_entry_t frame_entry; + frame_entry.type = (ReadAVI::chunk_type_t)(ReadAVI::ctype_video_data | ReadAVI::ctype_audio_data); + frame_entry.pointer = 0; + + ALuint audioStreamSource; + ALuint audioStreamBuffers[4]; + + alGenSources(1, &audioStreamSource); + alGenBuffers(4, audioStreamBuffers); + alSourcei(audioStreamSource, AL_LOOPING, AL_FALSE); + + int nextTime = SDL_GetTicks(); + + int frame_size; + int queue_counter = 0; + + while (true) + { + if (SDL_GetTicks() < nextTime) // wait for frame + { + Emulator_EndScene(); + continue; + } + + frame_entry.type = (ReadAVI::chunk_type_t)(ReadAVI::ctype_video_data | ReadAVI::ctype_audio_data); + frame_size = readAVI.GetFrameFromIndex(&frame_entry); + + // done, no frames + if (frame_size < 0) + break; + + if (frame_size > 0) + { + if (frame_entry.type == ReadAVI::ctype_compressed_video_frame) + { + // Update video frame + SDL_Surface* MainSurface = SDL_GetWindowSurface(g_window); + + SDL_LockSurface(BufSurface); + + int ret = UnpackJPEG(frame_entry.buf, frame_size, stream_format.bits_per_pixel, (unsigned char*)BufSurface->pixels); + SDL_UnlockSurface(BufSurface); + + if (ret == 0) + { + SDL_Rect rect; + GetMovieRectangle(rect, stream_format); + + SDL_UpperBlitScaled(BufSurface, NULL, MainSurface, &rect); + SDL_UpdateWindowSurface(g_window); + } + + SDL_FreeSurface(MainSurface); + + // set next step time + nextTime = SDL_GetTicks() + (avi_header.TimeBetweenFrames - 9000) / 1000; + } + else if (frame_entry.type == ReadAVI::ctype_audio_data) + { + // Update audio buffer + ALint state; + alGetSourcei(audioStreamSource, AL_SOURCE_STATE, &state); + + int numProcessed = 0; + alGetSourcei(audioStreamSource, AL_BUFFERS_PROCESSED, &numProcessed); + + if (state == AL_STOPPED || numProcessed > 0) + { + ALuint qbuffer; + + // stop queued + if (state == AL_STOPPED) + { + alGetSourcei(audioStreamSource, AL_BUFFERS_QUEUED, &numProcessed); + + // dequeue all buffers for restarting + while (numProcessed--) + alSourceUnqueueBuffers(audioStreamSource, 1, &qbuffer); + + // restart + queue_counter = 0; + } + else if(numProcessed && queue_counter >= 4) + { + // dequeue one buffer + alSourceUnqueueBuffers(audioStreamSource, 1, &qbuffer); + QueueAudioBuffer(qbuffer, audioStreamSource, frame_entry, audio_format, 0, frame_size); + } + } + + // for starting only + if (queue_counter < 4) + QueueAudioBuffer(audioStreamBuffers[queue_counter++], audioStreamSource, frame_entry, audio_format, 0, frame_size); + + if(queue_counter > 0 && state != AL_PLAYING) + alSourcePlay(audioStreamSource); + } + } + + ReadControllers(); + + if (Pads[0].mapnew > 0) + break; + + Emulator_EndScene(); + } + + alDeleteSources(1, &audioStreamSource); + alDeleteBuffers(4, audioStreamBuffers); +} + +// FMV main function +int FMV_main(RENDER_ARGS* args) +{ + for (int i = 0; i < args->nRenders; i++) + { + DoPlayFMV(&args->Args[i], args->subtitle); + } + + return 0; +} +