From bf933b3904faa532af704db51fdb6872080beaf1 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 6 Oct 2022 10:37:07 -0700 Subject: [PATCH] Decode audio and video separately in InterplayDecoder --- src/common/cutscenes/playmve.cpp | 961 +++++++++++++++++-------------- src/common/cutscenes/playmve.h | 45 +- 2 files changed, 557 insertions(+), 449 deletions(-) diff --git a/src/common/cutscenes/playmve.cpp b/src/common/cutscenes/playmve.cpp index feeeea0530..a3ce67bfce 100644 --- a/src/common/cutscenes/playmve.cpp +++ b/src/common/cutscenes/playmve.cpp @@ -94,28 +94,97 @@ static const int16_t delta_table[] = { #define LE_32(x) (LE_16(x) | ((uint32_t)LE_16(x+2) << 16)) #define LE_64(x) (LE_32(x) | ((uint64_t)LE_32(x+4) << 32)) -static bool StreamCallbackFunc(SoundStream* stream, void* buff, int len, void* userdata) + +bool InterplayDecoder::StreamCallback(SoundStream *stream, void *buff, int len) { - InterplayDecoder* pId = (InterplayDecoder*)userdata; - memcpy(buff, &pId->audio.samples[pId->audio.nRead], len); - pId->audio.nRead += len / 2; - if (pId->audio.nRead >= (int)countof(pId->audio.samples)) pId->audio.nRead = 0; + for (int i = 0; i < len;) + { + if(audio.nRead < audio.nWrite) + { + int todo = std::min(audio.nWrite-audio.nRead, (len-i) / 2); + memcpy((char*)buff+i, &audio.samples[audio.nRead], todo*2); + audio.nRead += todo; + if (audio.nRead == audio.nWrite) + audio.nRead = audio.nWrite = 0; + i += todo*2; + continue; + } + + std::unique_lock plock(PacketMutex); + while (audio.Packets.empty()) + { + if (!bIsPlaying || ProcessNextChunk() >= CHUNK_SHUTDOWN) + { + bIsPlaying = false; + if (i == 0) + return false; + memset((char*)buff+i, 0, len-i); + return true; + } + } + AudioPacket pkt = std::move(audio.Packets.front()); + audio.Packets.pop_front(); + plock.unlock(); + + int nSamples = pkt.nSize; + const uint8_t *samplePtr = pkt.pData.get(); + if (audio.bCompressed) + { + int predictor[2]; + + for (int ch = 0; ch < audio.nChannels; ch++) + { + predictor[ch] = (int16_t)LE_16(samplePtr); + samplePtr += 2; + + audio.samples[audio.nWrite++] = predictor[ch]; + } + + bool stereo = audio.nChannels == 2; + nSamples -= 2*audio.nChannels; + nSamples &= ~(int)stereo; + + int ch = 0; + for (int j = 0; j < nSamples; ++j) + { + predictor[ch] += delta_table[*samplePtr++]; + predictor[ch] = clamp(predictor[ch], -32768, 32767); + + audio.samples[audio.nWrite++] = predictor[ch]; + + // toggle channel + ch ^= stereo; + } + } + else if (audio.nBitDepth == 8) + { + for (int j = 0; j < nSamples; ++j) + audio.samples[audio.nWrite++] = ((*samplePtr++)-128) << 8; + } + else + { + nSamples /= 2; + for (int j = 0; j < nSamples; ++j) + { + audio.samples[audio.nWrite++] = (int16_t)LE_16(samplePtr); + samplePtr += 2; + } + } + } return true; } InterplayDecoder::InterplayDecoder(bool soundenabled) { bIsPlaying = false; - bAudioStarted = !soundenabled; // This prevents the stream from getting created + bAudioStarted = false; + bAudioEnabled = soundenabled; nWidth = 0; nHeight = 0; nFrame = 0; memset(palette, 0, sizeof(palette)); - memset(&audio, 0, sizeof(audio)); - audio.nRead = 18000; // skip the initial silence. This is needed to sync audio and video because OpenAL's lag is a bit on the high side. - nFps = 0.0; nFrameDuration = 0; @@ -146,13 +215,280 @@ void InterplayDecoder::SwapFrames() nCurrentVideoBuffer = t; } +int InterplayDecoder::ProcessNextChunk() +{ + uint8_t chunkPreamble[CHUNK_PREAMBLE_SIZE]; + if (fr.Read(chunkPreamble, CHUNK_PREAMBLE_SIZE) != CHUNK_PREAMBLE_SIZE) { + Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); + return CHUNK_EOF; + } + + int chunkSize = LE_16(&chunkPreamble[0]); + int chunkType = LE_16(&chunkPreamble[2]); + + ChunkData.resize(chunkSize); + if (fr.Read(ChunkData.data(), chunkSize) != chunkSize) { + Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); + return CHUNK_BAD; + } + + const uint8_t *palPtr = nullptr; + const uint8_t *mapPtr = nullptr; + const uint8_t *vidPtr = nullptr; + int palStart = 0, palCount = 0; + int mapSize = 0, vidSize = 0; + + // iterate through individual opcodes + const uint8_t *chunkPtr = ChunkData.data(); + while (chunkSize > 0 && chunkType != CHUNK_BAD) + { + if (chunkSize < OPCODE_PREAMBLE_SIZE) + { + Printf(TEXTCOLOR_RED "InterplayDecoder: opcode size too small\n"); + return CHUNK_BAD; + } + int opcodeSize = LE_16(chunkPtr); + int opcodeType = chunkPtr[2]; + int opcodeVersion = chunkPtr[3]; + + chunkPtr += OPCODE_PREAMBLE_SIZE; + chunkSize -= OPCODE_PREAMBLE_SIZE; + if (chunkSize < opcodeSize) + { + Printf(TEXTCOLOR_RED "InterplayDecoder: opcode size too large for chunk\n"); + return CHUNK_BAD; + } + chunkSize -= opcodeSize; + + switch (opcodeType) + { + case OPCODE_END_OF_STREAM: + chunkPtr += opcodeSize; + break; + + case OPCODE_END_OF_CHUNK: + chunkPtr += opcodeSize; + break; + + case OPCODE_CREATE_TIMER: + nTimerRate = LE_32(chunkPtr); + nTimerDiv = LE_16(chunkPtr+4); + chunkPtr += 6; + nFrameDuration = ((uint64_t)nTimerRate * nTimerDiv) * 1000; + break; + + case OPCODE_INIT_AUDIO_BUFFERS: + { + // Skip 2 bytes + uint16_t flags = LE_16(chunkPtr+2); + audio.nSampleRate = LE_16(chunkPtr+4); + chunkPtr += 6; + + uint32_t nBufferBytes = (opcodeVersion == 0) ? LE_16(chunkPtr) : LE_32(chunkPtr); + chunkPtr += (opcodeVersion == 0) ? 2 : 4; + + audio.nChannels = (flags & 0x1) ? 2 : 1; + audio.nBitDepth = (flags & 0x2) ? 16 : 8; + audio.bCompressed = (opcodeVersion > 0 && (flags & 0x4)); + audio.samples = std::make_unique(nBufferBytes / (audio.nBitDepth/8)); + audio.nRead = audio.nWrite = 0; + break; + } + + case OPCODE_START_STOP_AUDIO: + chunkPtr += opcodeSize; + break; + + case OPCODE_INIT_VIDEO_BUFFERS: + { + assert(((opcodeVersion == 0 && opcodeSize >= 4) || + (opcodeVersion == 1 && opcodeSize >= 6) || + (opcodeVersion == 2 && opcodeSize >= 8)) && + opcodeSize <= 8 && ! videoStride); + + nWidth = LE_16(chunkPtr) * 8; + nHeight = LE_16(chunkPtr+2) * 8; + + int count, truecolour; + if (opcodeVersion > 0) + { + count = LE_16(chunkPtr+4); + if (opcodeVersion > 1) + { + truecolour = LE_16(chunkPtr+6); + assert(truecolour == 0); + } + } + chunkPtr += opcodeSize; + + pVideoBuffers[0] = new uint8_t[nWidth * nHeight]; + pVideoBuffers[1] = new uint8_t[nWidth * nHeight]; + + videoStride = nWidth; + + animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight); + break; + } + + case OPCODE_UNKNOWN_06: + case OPCODE_UNKNOWN_0E: + case OPCODE_UNKNOWN_10: + case OPCODE_UNKNOWN_12: + case OPCODE_UNKNOWN_13: + case OPCODE_UNKNOWN_14: + case OPCODE_UNKNOWN_15: + chunkPtr += opcodeSize; + break; + + case OPCODE_SEND_BUFFER: + //int nPalStart = LE_16(chunkPtr); + //int nPalCount = LE_16(chunkPtr+2); + + { + VideoPacket pkt; + pkt.pData = std::make_unique(palCount*3 + mapSize + vidSize); + pkt.nPalStart = palStart; + pkt.nPalCount = palCount; + pkt.nDecodeMapSize = mapSize; + pkt.nVideoDataSize = vidSize; + + if (palPtr) + memcpy(pkt.pData.get(), palPtr, palCount*3); + if (mapPtr) + memcpy(pkt.pData.get() + palCount*3, mapPtr, mapSize); + if (vidPtr) + memcpy(pkt.pData.get() + palCount*3 + mapSize, vidPtr, vidSize); + pkt.bSendFlag = true; + VideoPackets.emplace_back(std::move(pkt)); + } + + palPtr = nullptr; + palStart = palCount = 0; + mapPtr = nullptr; + mapSize = 0; + vidPtr = nullptr; + vidSize = 0; + + chunkPtr += opcodeSize; + break; + + case OPCODE_AUDIO_FRAME: + { + uint16_t seqIndex = LE_16(chunkPtr); + uint16_t streamMask = LE_16(chunkPtr+2); + uint16_t nSamples = LE_16(chunkPtr+4); // number of samples this chunk(?) + chunkPtr += 6; + + // We only bother with stream 0 + if (!(streamMask & 1) || !bAudioEnabled) + { + chunkPtr += opcodeSize - 6; + break; + } + + AudioPacket pkt; + pkt.nSize = opcodeSize - 6; + pkt.pData = std::make_unique(pkt.nSize); + memcpy(pkt.pData.get(), chunkPtr, pkt.nSize); + audio.Packets.emplace_back(std::move(pkt)); + + chunkPtr += opcodeSize - 6; + break; + } + + case OPCODE_SILENCE_FRAME: + chunkPtr += opcodeSize; + break; + + case OPCODE_INIT_VIDEO_MODE: + chunkPtr += opcodeSize; + break; + + case OPCODE_CREATE_GRADIENT: + chunkPtr += opcodeSize; + Printf("InterplayDecoder: Create gradient not supported.\n"); + break; + + case OPCODE_SET_PALETTE: + if (opcodeSize > 0x304 || opcodeSize < 4) { + Printf("set_palette opcode with invalid size\n"); + chunkType = CHUNK_BAD; + break; + } + + palStart = LE_16(chunkPtr); + palCount = LE_16(chunkPtr+2); + palPtr = chunkPtr + 4; + if (palStart > 255 || palStart+palCount > 256) { + Printf("set_palette indices out of range (%d -> %d)\n", palStart, palStart+palCount-1); + chunkType = CHUNK_BAD; + break; + } + if (opcodeSize-4 < palCount*3) { + Printf("set_palette opcode too small (%d < %d)\n", opcodeSize-4, palCount*3); + chunkType = CHUNK_BAD; + break; + } + + chunkPtr += opcodeSize; + break; + + case OPCODE_SET_PALETTE_COMPRESSED: + chunkPtr += opcodeSize; + Printf("InterplayDecoder: Set palette compressed not supported.\n"); + break; + + case OPCODE_SET_DECODING_MAP: + mapPtr = chunkPtr; + mapSize = opcodeSize; + + chunkPtr += opcodeSize; + break; + + case OPCODE_VIDEO_DATA: + vidPtr = chunkPtr; + vidSize = opcodeSize; + + chunkPtr += opcodeSize; + break; + + default: + Printf("InterplayDecoder: Unknown opcode (0x%x v%d, %d bytes).\n", opcodeType, opcodeVersion, opcodeSize); + chunkPtr += opcodeSize; + break; + } + } + + if (chunkType < CHUNK_SHUTDOWN && (palPtr || mapPtr || vidPtr)) + { + VideoPacket pkt; + pkt.pData = std::make_unique(palCount*3 + mapSize + vidSize); + pkt.nPalStart = palStart; + pkt.nPalCount = palCount; + pkt.nDecodeMapSize = mapSize; + pkt.nVideoDataSize = vidSize; + + if (palPtr) + memcpy(pkt.pData.get(), palPtr, palCount*3); + if(mapPtr) + memcpy(pkt.pData.get() + palCount*3, mapPtr, mapSize); + if(vidPtr) + memcpy(pkt.pData.get() + palCount*3 + mapSize, vidPtr, vidSize); + pkt.bSendFlag = false; + VideoPackets.emplace_back(std::move(pkt)); + } + + return chunkType; +} + + void InterplayDecoder::Close() { - fr.Close(); bIsPlaying = false; if (stream) S_StopCustomStream(stream); stream = nullptr; + fr.Close(); if (decodeMap.pData) { delete[] decodeMap.pData; @@ -187,454 +523,199 @@ bool InterplayDecoder::Open(FileReader &fr_) fr_.Seek(6, FileReader::SeekCur); fr = std::move(fr_); - //Run(); + if (ProcessNextChunk() != CHUNK_INIT_VIDEO) + { + Printf(TEXTCOLOR_RED "InterplayDecoder: First chunk not CHUNK_INIT_VIDEO\n"); + return false; + } + + uint8_t chunkPreamble[CHUNK_PREAMBLE_SIZE]; + if (fr.Read(chunkPreamble, CHUNK_PREAMBLE_SIZE) != CHUNK_PREAMBLE_SIZE) { + Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); + return false; + } + fr.Seek(-CHUNK_PREAMBLE_SIZE, FileReader::SeekCur); + + int chunkType = LE_16(&chunkPreamble[2]); + if (chunkType == CHUNK_VIDEO) + bAudioEnabled = false; + else + { + if (ProcessNextChunk() != CHUNK_INIT_AUDIO) + { + Printf(TEXTCOLOR_RED "InterplayDecoder: Second non-video chunk not CHUNK_INIT_AUDIO\n"); + return false; + } + bAudioEnabled = audio.nSampleRate > 0; + } + + bIsPlaying = true; return true; } bool InterplayDecoder::RunFrame(uint64_t clock) { - uint8_t chunkPreamble[CHUNK_PREAMBLE_SIZE]; - uint8_t opcodePreamble[OPCODE_PREAMBLE_SIZE]; - uint8_t opcodeType; - uint8_t opcodeVersion; - int opcodeSize, chunkSize; - int chunkType = 0; + // handle timing - wait until we're ready to process the next frame. + if (nNextFrameTime > clock) { + return true; + } + nNextFrameTime += nFrameDuration; - // iterate through the chunks in the file + if (!bAudioStarted && bAudioEnabled) + { + S_StopMusic(true); + // start audio playback + stream = S_CreateCustomStream(6000, audio.nSampleRate, audio.nChannels, MusicSamples16bit, StreamCallbackC, this); + bAudioStarted = true; + } + + bool doFrame = false; do { - // handle timing - wait until we're ready to process the next frame. - if (nNextFrameTime > clock) { - return true; - } - else { - nNextFrameTime += nFrameDuration; - } - - if (fr.Read(chunkPreamble, CHUNK_PREAMBLE_SIZE) != CHUNK_PREAMBLE_SIZE) { - Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); - return false; - } - - chunkSize = LE_16(&chunkPreamble[0]); - chunkType = LE_16(&chunkPreamble[2]); - - ChunkData.resize(chunkSize); - if (fr.Read(ChunkData.data(), chunkSize) != chunkSize) { - Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); - return false; - } - - // iterate through individual opcodes - ChunkPtr = ChunkData.data(); - while (chunkSize > 0 && chunkType != CHUNK_BAD) + std::unique_lock plock(PacketMutex); + while (VideoPackets.empty()) { - if (chunkSize < OPCODE_PREAMBLE_SIZE) + if (!bIsPlaying || ProcessNextChunk() >= CHUNK_SHUTDOWN) { - Printf(TEXTCOLOR_RED "InterplayDecoder: opcode size too small\n"); + bIsPlaying = false; return false; } - opcodeSize = LE_16(ChunkPtr); - opcodeType = ChunkPtr[2]; - opcodeVersion = ChunkPtr[3]; + } + VideoPacket pkt = std::move(VideoPackets.front()); + VideoPackets.pop_front(); + plock.unlock(); - ChunkPtr += OPCODE_PREAMBLE_SIZE; - chunkSize -= OPCODE_PREAMBLE_SIZE; - if (chunkSize < opcodeSize) + const uint8_t *palData = pkt.pData.get(); + const uint8_t *mapData = palData + pkt.nPalCount*3; + const uint8_t *vidData = mapData + pkt.nDecodeMapSize; + + if (pkt.nPalCount > 0) + { + int nPalEnd = pkt.nPalStart + pkt.nPalCount; + for (int i = pkt.nPalStart; i < nPalEnd; i++) { - Printf(TEXTCOLOR_RED "InterplayDecoder: opcode size too large for chunk\n"); - return false; - } - chunkSize -= opcodeSize; - - switch (opcodeType) - { - case OPCODE_END_OF_STREAM: - { - ChunkPtr += opcodeSize; - break; - } - - case OPCODE_END_OF_CHUNK: - { - ChunkPtr += opcodeSize; - break; - } - - case OPCODE_CREATE_TIMER: - { - nTimerRate = LE_32(ChunkPtr); - nTimerDiv = LE_16(ChunkPtr+4); - ChunkPtr += 6; - nFrameDuration = ((uint64_t)nTimerRate * nTimerDiv) * 1000; - break; - } - - case OPCODE_INIT_AUDIO_BUFFERS: - { - // Skip 2 bytes - uint16_t flags = LE_16(ChunkPtr+2); - audio.nSampleRate = LE_16(ChunkPtr+4); - ChunkPtr += 6; - - uint32_t nBufferBytes = (opcodeVersion == 0) ? LE_16(ChunkPtr) : LE_32(ChunkPtr); - ChunkPtr += (opcodeVersion == 0) ? 2 : 4; - - audio.nChannels = (flags & 0x1) ? 2 : 1; - audio.nBitDepth = (flags & 0x2) ? 16 : 8; - audio.bCompressed = (opcodeVersion > 0 && (flags & 0x4)); - break; - } - - case OPCODE_START_STOP_AUDIO: - { - if (!bAudioStarted) - { - S_StopMusic(true); - // start audio playback - stream = S_CreateCustomStream(6000, audio.nSampleRate, audio.nChannels, MusicSamples16bit, StreamCallbackFunc, this); - bAudioStarted = true; - } - - ChunkPtr += opcodeSize; - break; - } - - case OPCODE_INIT_VIDEO_BUFFERS: - { - assert(((opcodeVersion == 0 && opcodeSize >= 4) || - (opcodeVersion == 1 && opcodeSize >= 6) || - (opcodeVersion == 2 && opcodeSize >= 8)) && - opcodeSize <= 8); - - nWidth = LE_16(ChunkPtr) * 8; - nHeight = LE_16(ChunkPtr+2) * 8; - - int count, truecolour; - if (opcodeVersion > 0) - { - count = LE_16(ChunkPtr+4); - if (opcodeVersion > 1) - { - truecolour = LE_16(ChunkPtr+6); - assert(truecolour == 0); - } - } - ChunkPtr += opcodeSize; - - pVideoBuffers[0] = new uint8_t[nWidth * nHeight]; - pVideoBuffers[1] = new uint8_t[nWidth * nHeight]; - - videoStride = nWidth; - - animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight); - break; - } - - case OPCODE_UNKNOWN_06: - case OPCODE_UNKNOWN_0E: - case OPCODE_UNKNOWN_10: - case OPCODE_UNKNOWN_12: - case OPCODE_UNKNOWN_13: - case OPCODE_UNKNOWN_14: - case OPCODE_UNKNOWN_15: - { - ChunkPtr += opcodeSize; - break; - } - - case OPCODE_SEND_BUFFER: - { - int nPalStart = LE_16(ChunkPtr); - int nPalCount = LE_16(ChunkPtr+2); - ChunkPtr += opcodeSize; - - animtex.SetFrame(&palette[0].r , GetCurrentFrame()); - - nFrame++; - SwapFrames(); - break; - } - - case OPCODE_AUDIO_FRAME: - { - auto pStart = ChunkPtr; - uint16_t seqIndex = LE_16(ChunkPtr); - uint16_t streamMask = LE_16(ChunkPtr+2); - uint16_t nSamples = LE_16(ChunkPtr+4); // number of samples this chunk(?) - ChunkPtr += 6; - - // We only bother with stream 0 - if (!(streamMask & 1)) - { - ChunkPtr += opcodeSize - 6; - break; - } - - nSamples = opcodeSize - 6; - if (audio.bCompressed) - { - int predictor[2]; - - for (int ch = 0; ch < audio.nChannels; ch++) - { - predictor[ch] = (int16_t)LE_16(ChunkPtr); - ChunkPtr += 2; - - audio.samples[audio.nWrite++] = predictor[ch]; - if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; - } - - bool stereo = audio.nChannels == 2; - nSamples -= 2*audio.nChannels; - nSamples &= ~(int)stereo; - - int ch = 0; - for (int i = 0; i < nSamples;) - { - int todo = std::min(nSamples-i, (int)std::size(audio.samples)-audio.nWrite); - auto end = ChunkPtr + todo; - while(ChunkPtr != end) - { - predictor[ch] += delta_table[*ChunkPtr++]; - predictor[ch] = clamp(predictor[ch], -32768, 32767); - - audio.samples[audio.nWrite++] = predictor[ch]; - - // toggle channel - ch ^= stereo; - } - if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; - i += todo; - } - } - else if (audio.nBitDepth == 8) - { - for (int i = 0; i < nSamples;) - { - int todo = std::min(nSamples-i, (int)std::size(audio.samples)-audio.nWrite); - auto end = ChunkPtr + todo; - while(ChunkPtr != end) - audio.samples[audio.nWrite++] = ((*ChunkPtr++)-128) << 8; - if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; - i += todo; - } - } - else - { - nSamples /= 2; - for (int i = 0; i < nSamples;) - { - int todo = std::min(nSamples-i, (int)std::size(audio.samples)-audio.nWrite); - auto end = ChunkPtr + todo*2; - while(ChunkPtr != end) - { - audio.samples[audio.nWrite++] = (int16_t)LE_16(ChunkPtr); - ChunkPtr += 2; - } - if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; - i += todo; - } - } - - auto pEnd = ChunkPtr; - int nRead = (int)(pEnd - pStart); - assert(opcodeSize == nRead); - break; - } - - case OPCODE_SILENCE_FRAME: - { - uint16_t seqIndex = LE_16(ChunkPtr); - uint16_t streamMask = LE_16(ChunkPtr+2); - uint16_t nStreamLen = LE_16(ChunkPtr+4); - ChunkPtr += 6; - - if (streamMask & 1) - { - nStreamLen = (opcodeSize-6) * 8 / audio.nBitDepth; - - for (int i = 0; i < nStreamLen;) - { - int todo = std::min(nStreamLen-i, (int)std::size(audio.samples)-audio.nWrite); - memset(&audio.samples[audio.nWrite], 0, todo*2); - audio.nWrite += todo; - if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; - i += todo; - } - } - - break; - } - - case OPCODE_INIT_VIDEO_MODE: - { - ChunkPtr += opcodeSize; - break; - } - - case OPCODE_CREATE_GRADIENT: - { - ChunkPtr += opcodeSize; - Printf("InterplayDecoder: Create gradient not supported.\n"); - break; - } - - case OPCODE_SET_PALETTE: - { - if (opcodeSize > 0x304 || opcodeSize < 4) { - Printf("set_palette opcode with invalid size\n"); - chunkType = CHUNK_BAD; - break; - } - - int nPalStart = LE_16(ChunkPtr); - int nPalEnd = nPalStart + LE_16(ChunkPtr+2) - 1; - ChunkPtr += 4; - if (nPalStart > 255 || nPalEnd > 255) { - Printf("set_palette indices out of range (%d -> %d)\n", nPalStart, nPalEnd); - chunkType = CHUNK_BAD; - break; - } - for (int i = nPalStart; i <= nPalEnd; i++) - { - palette[i].r = (*ChunkPtr++) << 2; - palette[i].g = (*ChunkPtr++) << 2; - palette[i].b = (*ChunkPtr++) << 2; - palette[i].r |= palette[i].r >> 6; - palette[i].g |= palette[i].g >> 6; - palette[i].b |= palette[i].b >> 6; - } - break; - } - - case OPCODE_SET_PALETTE_COMPRESSED: - { - ChunkPtr += opcodeSize; - Printf("InterplayDecoder: Set palette compressed not supported.\n"); - break; - } - - case OPCODE_SET_DECODING_MAP: - { - if (!decodeMap.pData) - { - decodeMap.pData = new uint8_t[opcodeSize]; - decodeMap.nSize = opcodeSize; - } - else - { - if (opcodeSize != (int)decodeMap.nSize) { - delete[] decodeMap.pData; - decodeMap.pData = new uint8_t[opcodeSize]; - decodeMap.nSize = opcodeSize; - } - } - - memcpy(decodeMap.pData, ChunkPtr, opcodeSize); - ChunkPtr += opcodeSize; - break; - } - - case OPCODE_VIDEO_DATA: - { - auto pStart = ChunkPtr; - - // need to skip 14 bytes - ChunkPtr += 14; - - if (decodeMap.nSize) - { - int i = 0; - - for (uint32_t y = 0; y < nHeight; y += 8) - { - for (uint32_t x = 0; x < nWidth; x += 8) - { - uint32_t opcode; - - // alternate between getting low and high 4 bits - if (i & 1) { - opcode = decodeMap.pData[i >> 1] >> 4; - } - else { - opcode = decodeMap.pData[i >> 1] & 0x0F; - } - i++; - - int32_t offset = x + (y * videoStride); - - switch (opcode) - { - default: - break; - case 0: - DecodeBlock0(offset); - break; - case 1: - DecodeBlock1(offset); - break; - case 2: - DecodeBlock2(offset); - break; - case 3: - DecodeBlock3(offset); - break; - case 4: - DecodeBlock4(offset); - break; - case 5: - DecodeBlock5(offset); - break; - case 7: - DecodeBlock7(offset); - break; - case 8: - DecodeBlock8(offset); - break; - case 9: - DecodeBlock9(offset); - break; - case 10: - DecodeBlock10(offset); - break; - case 11: - DecodeBlock11(offset); - break; - case 12: - DecodeBlock12(offset); - break; - case 13: - DecodeBlock13(offset); - break; - case 14: - DecodeBlock14(offset); - break; - case 15: - DecodeBlock15(offset); - break; - } - } - } - } - - auto pEnd = ChunkPtr; - int nSkipBytes = opcodeSize - (int)(pEnd - pStart); // we can end up with 1 byte left we need to skip - assert(nSkipBytes <= 1); - - ChunkPtr += nSkipBytes; - break; - } - - default: - break; + palette[i].r = (*palData++) << 2; + palette[i].g = (*palData++) << 2; + palette[i].b = (*palData++) << 2; + palette[i].r |= palette[i].r >> 6; + palette[i].g |= palette[i].g >> 6; + palette[i].b |= palette[i].b >> 6; } } - } - while (chunkType < CHUNK_VIDEO && bIsPlaying); - return chunkType != CHUNK_END; + if (pkt.nDecodeMapSize > 0) + { + if (!decodeMap.pData) + { + decodeMap.pData = new uint8_t[pkt.nDecodeMapSize]; + decodeMap.nSize = pkt.nDecodeMapSize; + } + else + { + if (pkt.nDecodeMapSize != decodeMap.nSize) { + delete[] decodeMap.pData; + decodeMap.pData = new uint8_t[pkt.nDecodeMapSize]; + decodeMap.nSize = pkt.nDecodeMapSize; + } + } + + memcpy(decodeMap.pData, mapData, pkt.nDecodeMapSize); + } + + if (pkt.nVideoDataSize > 0 && decodeMap.nSize > 0) + { + auto pStart = vidData; + + // need to skip 14 bytes + ChunkPtr = pStart + 14; + + int i = 0; + for (uint32_t y = 0; y < nHeight; y += 8) + { + for (uint32_t x = 0; x < nWidth; x += 8) + { + uint32_t opcode; + + // alternate between getting low and high 4 bits + if (i & 1) { + opcode = decodeMap.pData[i >> 1] >> 4; + } + else { + opcode = decodeMap.pData[i >> 1] & 0x0F; + } + i++; + + int32_t offset = x + (y * videoStride); + + switch (opcode) + { + default: + break; + case 0: + DecodeBlock0(offset); + break; + case 1: + DecodeBlock1(offset); + break; + case 2: + DecodeBlock2(offset); + break; + case 3: + DecodeBlock3(offset); + break; + case 4: + DecodeBlock4(offset); + break; + case 5: + DecodeBlock5(offset); + break; + case 7: + DecodeBlock7(offset); + break; + case 8: + DecodeBlock8(offset); + break; + case 9: + DecodeBlock9(offset); + break; + case 10: + DecodeBlock10(offset); + break; + case 11: + DecodeBlock11(offset); + break; + case 12: + DecodeBlock12(offset); + break; + case 13: + DecodeBlock13(offset); + break; + case 14: + DecodeBlock14(offset); + break; + case 15: + DecodeBlock15(offset); + break; + } + } + } + + auto pEnd = ChunkPtr; + // we can end up with 1 byte left we need to skip + int nSkipBytes = pkt.nVideoDataSize - (int)(pEnd - pStart); + assert(nSkipBytes <= 1); + } + + doFrame = pkt.bSendFlag; + } while(!doFrame); + + animtex.SetFrame(&palette[0].r , GetCurrentFrame()); + + nFrame++; + SwapFrames(); + + return true; } void InterplayDecoder::CopyBlock(uint8_t* pDest, uint8_t* pSrc) diff --git a/src/common/cutscenes/playmve.h b/src/common/cutscenes/playmve.h index 6ed75184a5..974dc34ba3 100644 --- a/src/common/cutscenes/playmve.h +++ b/src/common/cutscenes/playmve.h @@ -44,6 +44,9 @@ #pragma once +#include +#include +#include #include #include "files.h" @@ -55,6 +58,21 @@ class InterplayDecoder { + struct AudioPacket + { + size_t nSize = 0; + std::unique_ptr pData; + }; + + struct VideoPacket + { + uint16_t nPalStart=0, nPalCount=0; + uint32_t nDecodeMapSize = 0; + uint32_t nVideoDataSize = 0; + bool bSendFlag = false; + std::unique_ptr pData; + }; + public: enum { @@ -109,15 +127,16 @@ public: struct AudioData { - int hFx; - int nChannels; - uint16_t nSampleRate; - uint8_t nBitDepth; - bool bCompressed; + int nChannels = 0; + uint16_t nSampleRate = 0; + uint8_t nBitDepth = 0; + bool bCompressed = false; - int16_t samples[6000 * kAudioBlocks]; // must be a multiple of the stream buffer size - int nWrite; - int nRead; + std::unique_ptr samples; + int nWrite = 0; + int nRead = 0; + + std::deque Packets; }; AudioData audio; @@ -126,6 +145,10 @@ public: AnimTextures& animTex() { return animtex; } private: + bool StreamCallback(SoundStream *stream, void *buff, int len); + static bool StreamCallbackC(SoundStream *stream, void *buff, int len, void *userdata) + { return static_cast(userdata)->StreamCallback(stream, buff, len); } + struct DecodeMap { uint8_t* pData; @@ -159,9 +182,11 @@ private: void DecodeBlock14(int32_t offset); void DecodeBlock15(int32_t offset); + std::mutex PacketMutex; FileReader fr; bool bIsPlaying, bAudioStarted; + bool bAudioEnabled; uint32_t nTimerRate, nTimerDiv; uint32_t nWidth, nHeight, nFrame; @@ -169,12 +194,14 @@ private: uint64_t nFrameDuration; std::vector ChunkData; - const uint8_t *ChunkPtr = nullptr; + int ProcessNextChunk(); + std::deque VideoPackets; uint8_t* pVideoBuffers[2]; uint32_t nCurrentVideoBuffer, nPreviousVideoBuffer; int32_t videoStride; + const uint8_t *ChunkPtr = nullptr; DecodeMap decodeMap; Palette palette[256];