From 09b31afd40441fdcdef80ff98320d8a4aad2c0d2 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 14 Oct 2022 20:18:13 +0200 Subject: [PATCH] - movieplayer update from GZDoom. --- source/common/cutscenes/movieplayer.cpp | 285 +++-- source/common/cutscenes/playmve.cpp | 979 +++++++++++------- source/common/cutscenes/playmve.h | 64 +- .../libsmackerdec/include/BitReader.h | 17 +- .../libsmackerdec/include/SmackerDecoder.h | 23 +- .../libsmackerdec/src/BitReader.cpp | 21 +- .../libsmackerdec/src/SmackerDecoder.cpp | 260 +++-- 7 files changed, 1060 insertions(+), 589 deletions(-) diff --git a/source/common/cutscenes/movieplayer.cpp b/source/common/cutscenes/movieplayer.cpp index 6b8a050af..fbc1f9f64 100644 --- a/source/common/cutscenes/movieplayer.cpp +++ b/source/common/cutscenes/movieplayer.cpp @@ -47,6 +47,10 @@ #include "filesystem.h" #include "vm.h" #include "printf.h" +#include +#include +#include +#include "filereadermusicinterface.h" class MoviePlayer { @@ -67,6 +71,78 @@ public: virtual FTextureID GetTexture() = 0; }; +//--------------------------------------------------------------------------- +// +// +// +//--------------------------------------------------------------------------- + +// A simple filter is used to smooth out jittery timers +static const double AudioAvgFilterCoeff{std::pow(0.01, 1.0/10.0)}; +// A threshold is in place to avoid constantly skipping due to imprecise timers. +static constexpr double AudioSyncThreshold{0.03}; + +class MovieAudioTrack +{ + SoundStream *AudioStream = nullptr; + int SampleRate = 0; + int FrameSize = 0; + int64_t EndClockDiff = 0; + +public: + MovieAudioTrack() = default; + ~MovieAudioTrack() + { + if(AudioStream) + S_StopCustomStream(AudioStream); + } + + bool Start(int srate, int channels, MusicCustomStreamType sampletype, StreamCallback callback, void *ptr) + { + SampleRate = srate; + FrameSize = channels * ((sampletype == MusicSamples16bit) ? sizeof(int16_t) : sizeof(float)); + int bufsize = 40 * SampleRate / 1000 * FrameSize; + AudioStream = S_CreateCustomStream(bufsize, SampleRate, channels, sampletype, callback, ptr); + return !!AudioStream; + } + + void Finish() + { + if(AudioStream) + S_StopCustomStream(AudioStream); + AudioStream = nullptr; + } + + uint64_t GetClockTime(uint64_t clock) + { + // If there's no stream playing, report the frame clock adjusted by the audio + // end time. This ensures the returned clock keeps incrementing even after + // the audio stopped. + if(!AudioStream || EndClockDiff != 0) + return clock + EndClockDiff; + + auto pos = AudioStream->GetPlayPosition(); + int64_t postime = static_cast(pos.samplesplayed / double(SampleRate) * 1'000'000'000.0); + postime = std::max(0, postime - pos.latency.count()); + + if(AudioStream->IsEnded()) + { + // If the stream just ended, get the difference between the frame clock and + // the audio end time, so future calls keep incrementing the clock from this + // point. An alternative option may be to allow the AudioStream to hook into + // the audio device clock, which can keep incrementing at the same rate + // without the stream itself actually playing. + EndClockDiff = postime - clock; + } + + return static_cast(postime); + } + + SoundStream *GetAudioStream() const noexcept { return AudioStream; } + int GetSampleRate() const noexcept { return SampleRate; } + int GetFrameSize() const noexcept { return FrameSize; } +}; + //--------------------------------------------------------------------------- // // @@ -174,8 +250,16 @@ public: class MvePlayer : public MoviePlayer { InterplayDecoder decoder; + MovieAudioTrack audioTrack; bool failed = false; + bool StreamCallback(SoundStream*, void *buff, int len) + { + return decoder.FillSamples(buff, len); + } + static bool StreamCallbackC(SoundStream *stream, void *buff, int len, void *userdata) + { return static_cast(userdata)->StreamCallback(stream, buff, len); } + public: bool isvalid() { return !failed; } @@ -193,12 +277,23 @@ public: bool Frame(uint64_t clock) override { if (failed) return false; - bool playon = decoder.RunFrame(clock); + + if (!audioTrack.GetAudioStream() && decoder.HasAudio() && clock != 0) + { + S_StopMusic(true); + // start audio playback + if (!audioTrack.Start(decoder.GetSampleRate(), decoder.NumChannels(), MusicSamples16bit, StreamCallbackC, this)) + decoder.DisableAudio(); + } + + bool playon = decoder.RunFrame(audioTrack.GetClockTime(clock)); return playon; } ~MvePlayer() { + audioTrack.Finish(); + decoder.Close(); } @@ -221,6 +316,9 @@ class VpxPlayer : public MoviePlayer AnimTextures animtex; const TArray animSnd; + ZMusic_MusicStream MusicStream = nullptr; + MovieAudioTrack AudioTrack; + unsigned width, height; TArray Pic; TArray readBuf; @@ -240,6 +338,12 @@ class VpxPlayer : public MoviePlayer public: int soundtrack = -1; + bool StreamCallback(SoundStream*, void *buff, int len) + { + return ZMusic_FillStream(MusicStream, buff, len); + } + static bool StreamCallbackC(SoundStream *stream, void *buff, int len, void *userdata) + { return static_cast(userdata)->StreamCallback(stream, buff, len); } public: bool isvalid() { return !failed; } @@ -405,9 +509,21 @@ public: void Start() override { - if (soundtrack > 0) + if (SoundStream *stream = AudioTrack.GetAudioStream()) { - S_ChangeMusic(fileSystem.GetFileFullName(soundtrack, false), 0, false); + stream->SetPaused(false); + } + else if (soundtrack >= 0) + { + FileReader reader = fileSystem.OpenFileReader(soundtrack); + if (reader.isOpen()) + { + MusicStream = ZMusic_OpenSong(GetMusicReader(reader), MDEV_DEFAULT, nullptr); + } + if (!MusicStream) + { + Printf(PRINT_BOLD, "Failed to decode %s\n", fileSystem.GetFileFullName(soundtrack, false)); + } } animtex.SetSize(AnimTexture::YUV, width, height); } @@ -420,8 +536,31 @@ public: bool Frame(uint64_t clock) override { + if (!AudioTrack.GetAudioStream() && MusicStream && clock != 0) + { + S_StopMusic(true); + + bool ok = false; + SoundStreamInfo info{}; + ZMusic_GetStreamInfo(MusicStream, &info); + // if mBufferSize == 0, the music stream is played externally (e.g. + // Windows' MIDI synth), which we can't keep synced. Play anyway? + if (info.mBufferSize > 0 && ZMusic_Start(MusicStream, 0, false)) + { + ok = AudioTrack.Start(info.mSampleRate, abs(info.mNumChannels), + (info.mNumChannels < 0) ? MusicSamples16bit : MusicSamplesFloat, &StreamCallbackC, this); + } + if (!ok) + { + ZMusic_Close(MusicStream); + MusicStream = nullptr; + } + } + + clock = AudioTrack.GetClockTime(clock); + bool stop = false; - if (clock > nextframetime) + if (clock >= nextframetime) { nextframetime += nsecsperframe; @@ -461,15 +600,21 @@ public: return !stop; } - void Stop() + void Stop() override { - S_StopMusic(true); + if (SoundStream *stream = AudioTrack.GetAudioStream()) + stream->SetPaused(true); bool nostopsound = (flags & NOSOUNDCUTOFF); if (!nostopsound) soundEngine->StopAllChannels(); } ~VpxPlayer() { + if(MusicStream) + { + AudioTrack.Finish(); + ZMusic_Close(MusicStream); + } vpx_codec_destroy(&codec); animtex.Clean(); } @@ -488,13 +633,10 @@ public: struct AudioData { - int hFx; SmackerAudioInfo inf; - int16_t samples[6000 * 20]; // must be a multiple of the stream buffer size and larger than the initial chunk of audio - - int nWrite; - int nRead; + int nWrite = 0; + int nRead = 0; }; class SmkPlayer : public MoviePlayer @@ -506,46 +648,59 @@ class SmkPlayer : public MoviePlayer uint8_t palette[768]; AnimTextures animtex; TArray pFrame; - TArray audioBuffer; + TArray audioBuffer; int nFrames; bool fullscreenScale; uint64_t nFrameNs; int nFrame = 0; const TArray animSnd; FString filename; - SoundStream* stream = nullptr; + MovieAudioTrack AudioTrack; bool hassound = false; public: bool isvalid() { return hSMK.isValid; } - static bool StreamCallbackFunc(SoundStream* stream, void* buff, int len, void* userdata) + bool StreamCallback(SoundStream* stream, void* buff, int len) { - SmkPlayer* pId = (SmkPlayer*)userdata; - memcpy(buff, &pId->adata.samples[pId->adata.nRead], len); - pId->adata.nRead += len / 2; - if (pId->adata.nRead >= (int)countof(pId->adata.samples)) pId->adata.nRead = 0; + const int samplerate = AudioTrack.GetSampleRate(); + const int framesize = AudioTrack.GetFrameSize(); + + int avail = (adata.nWrite - adata.nRead) * 2; + + int wrote = 0; + while(wrote < len) + { + if (avail == 0) + { + auto read = Smacker_GetAudioData(hSMK, 0, audioBuffer.Data()); + if (read == 0) + { + if (wrote == 0) + return false; + break; + } + + adata.nWrite = read / 2; + avail = read; + } + + int todo = std::min(len-wrote, avail); + + memcpy((char*)buff+wrote, &audioBuffer[adata.nRead], todo); + adata.nRead += todo / 2; + if(adata.nRead == adata.nWrite) + adata.nRead = adata.nWrite = 0; + avail -= todo; + wrote += todo; + } + + if (wrote < len) + memset((char*)buff+wrote, 0, len-wrote); return true; } - - void copy8bitSamples(unsigned count) - { - for (unsigned i = 0; i < count; i++) - { - adata.samples[adata.nWrite] = (audioBuffer[i] - 128) << 8; - if (++adata.nWrite >= (int)countof(adata.samples)) adata.nWrite = 0; - } - } - - void copy16bitSamples(unsigned count) - { - auto ptr = (uint16_t*)audioBuffer.Data(); - for (unsigned i = 0; i < count/2; i++) - { - adata.samples[adata.nWrite] = *ptr++; - if (++adata.nWrite >= (int)countof(adata.samples)) adata.nWrite = 0; - } - } + static bool StreamCallbackC(SoundStream* stream, void* buff, int len, void* userdata) + { return static_cast(userdata)->StreamCallback(stream, buff, len); } SmkPlayer(const char *fn, TArray& ans, int flags_) : animSnd(std::move(ans)) @@ -564,25 +719,26 @@ public: Smacker_GetPalette(hSMK, palette); numAudioTracks = Smacker_GetNumAudioTracks(hSMK); - if (numAudioTracks) + if (numAudioTracks && SoundEnabled()) { adata.nWrite = 0; adata.nRead = 0; adata.inf = Smacker_GetAudioTrackDetails(hSMK, 0); if (adata.inf.idealBufferSize > 0) { - audioBuffer.Resize(adata.inf.idealBufferSize); - auto read = Smacker_GetAudioData(hSMK, 0, (int16_t*)audioBuffer.Data()); - if (adata.inf.bitsPerSample == 8) copy8bitSamples(read); - else copy16bitSamples(read); + audioBuffer.Resize(adata.inf.idealBufferSize / 2); hassound = true; } + for (int i = 1;i < numAudioTracks;++i) + Smacker_DisableAudioTrack(hSMK, i); + numAudioTracks = 1; } if (!hassound) { adata.inf = {}; + Smacker_DisableAudioTrack(hSMK, 0); + numAudioTracks = 0; } - } //--------------------------------------------------------------------------- @@ -594,6 +750,8 @@ public: void Start() override { animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight); + if (SoundStream *stream = AudioTrack.GetAudioStream()) + stream->SetPaused(false); } //--------------------------------------------------------------------------- @@ -604,30 +762,29 @@ public: bool Frame(uint64_t clock) override { + if (!AudioTrack.GetAudioStream() && numAudioTracks && clock != 0) + { + S_StopMusic(true); + + if (!AudioTrack.Start(adata.inf.sampleRate, adata.inf.nChannels, MusicSamples16bit, StreamCallbackC, this)) + { + Smacker_DisableAudioTrack(hSMK, 0); + numAudioTracks = 0; + } + } + + clock = AudioTrack.GetClockTime(clock); int frame = int(clock / nFrameNs); twod->ClearScreen(); - if (frame > nFrame) - { - Smacker_GetPalette(hSMK, palette); - Smacker_GetFrame(hSMK, pFrame.Data()); - animtex.SetFrame(palette, pFrame.Data()); - if (numAudioTracks && SoundEnabled()) - { - auto read = Smacker_GetAudioData(hSMK, 0, (int16_t*)audioBuffer.Data()); - if (adata.inf.bitsPerSample == 8) copy8bitSamples(read); - else copy16bitSamples(read); - if (!stream && read) // the sound may not start in the first frame, but the stream cannot start without any sound data present. - stream = S_CreateCustomStream(6000, adata.inf.sampleRate, adata.inf.nChannels, MusicSamples16bit, StreamCallbackFunc, this); - - } - - } - - if (frame > nFrame) + if (frame >= nFrame) { nFrame++; Smacker_GetNextFrame(hSMK); + Smacker_GetPalette(hSMK, palette); + Smacker_GetFrame(hSMK, pFrame.Data()); + animtex.SetFrame(palette, pFrame.Data()); + bool nostopsound = (flags & NOSOUNDCUTOFF); if (!hassound) for (unsigned i = 0; i < animSnd.Size(); i += 2) { @@ -647,13 +804,15 @@ public: void Stop() override { - if (stream) S_StopCustomStream(stream); + if (SoundStream *stream = AudioTrack.GetAudioStream()) + stream->SetPaused(true); bool nostopsound = (flags & NOSOUNDCUTOFF); if (!nostopsound && !hassound) soundEngine->StopAllChannels(); } ~SmkPlayer() { + AudioTrack.Finish(); Smacker_Close(hSMK); animtex.Clean(); } @@ -687,7 +846,7 @@ MoviePlayer* OpenMovie(const char* filename, TArray& ans, const int* framet { size_t nLen = strlen(filename); // Strip the drive letter and retry. - if (nLen >= 3 && isalpha((uint8_t)filename[0]) && filename[1] == ':' && filename[2] == '/') + if (nLen >= 3 && isalpha(filename[0]) && filename[1] == ':' && filename[2] == '/') { filename += 3; fr = fileSystem.OpenFileReader(filename); diff --git a/source/common/cutscenes/playmve.cpp b/source/common/cutscenes/playmve.cpp index ffd830d19..70b56b43a 100644 --- a/source/common/cutscenes/playmve.cpp +++ b/source/common/cutscenes/playmve.cpp @@ -89,31 +89,112 @@ static const int16_t delta_table[] = { }; -// macro to fetch 16-bit little-endian words from a bytestream -#define LE_16(x) ((*x) | ((*(x+1)) << 8)) +// macros to fetch little-endian words from a bytestream +#define LE_16(x) ((uint16_t)((*(x)) | ((*((x)+1)) << 8))) +#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::FillSamples(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 = (int)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 ? 1 : 0; + } + } + 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; } +void InterplayDecoder::DisableAudio() +{ + if (bAudioEnabled) + { + std::unique_lock plock(PacketMutex); + bAudioEnabled = false; + audio.Packets.clear(); + } +} + + InterplayDecoder::InterplayDecoder(bool soundenabled) { bIsPlaying = false; - bAudioStarted = !soundenabled; // This prevents the stream from getting created + 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; @@ -144,13 +225,277 @@ 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; @@ -185,373 +530,191 @@ 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 + 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]); - - // iterate through individual opcodes - while (chunkSize > 0) + std::unique_lock plock(PacketMutex); + while (VideoPackets.empty()) { - if (fr.Read(opcodePreamble, OPCODE_PREAMBLE_SIZE) != OPCODE_PREAMBLE_SIZE) + if (!bIsPlaying || ProcessNextChunk() >= CHUNK_SHUTDOWN) { - Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); + bIsPlaying = false; return false; } + } + VideoPacket pkt = std::move(VideoPackets.front()); + VideoPackets.pop_front(); + plock.unlock(); - opcodeSize = LE_16(&opcodePreamble[0]); - opcodeType = opcodePreamble[2]; - opcodeVersion = opcodePreamble[3]; + const uint8_t *palData = pkt.pData.get(); + const uint8_t *mapData = palData + pkt.nPalCount*3; + const uint8_t *vidData = mapData + pkt.nDecodeMapSize; - chunkSize -= OPCODE_PREAMBLE_SIZE; - chunkSize -= opcodeSize; - - switch (opcodeType) + if (pkt.nPalCount > 0) + { + int nPalEnd = pkt.nPalStart + pkt.nPalCount; + for (int i = pkt.nPalStart; i < nPalEnd; i++) { - case OPCODE_END_OF_STREAM: - { - fr.Seek(opcodeSize, FileReader::SeekCur); - break; - } - - case OPCODE_END_OF_CHUNK: - { - fr.Seek(opcodeSize, FileReader::SeekCur); - break; - } - - case OPCODE_CREATE_TIMER: - { - nTimerRate = fr.ReadUInt32(); - nTimerDiv = fr.ReadUInt16(); - nFrameDuration = ((uint64_t)nTimerRate * nTimerDiv) * 1000; - break; - } - - case OPCODE_INIT_AUDIO_BUFFERS: - { - fr.Seek(2, FileReader::SeekCur); - uint16_t flags = fr.ReadUInt16(); - audio.nSampleRate = fr.ReadUInt16(); - - uint32_t nBufferBytes; - - if (opcodeVersion == 0) { - nBufferBytes = fr.ReadUInt16(); - } - else { - nBufferBytes = fr.ReadUInt32(); - } - - if (flags & 0x1) { - audio.nChannels = 2; - } - else { - audio.nChannels = 1; - } - if (flags & 0x2) { - audio.nBitDepth = 16; - } - else { - audio.nBitDepth = 8; - } - break; - } - - case OPCODE_START_STOP_AUDIO: - { - if (!bAudioStarted) - { - // start audio playback - stream = S_CreateCustomStream(6000, audio.nSampleRate, audio.nChannels, MusicSamples16bit, StreamCallbackFunc, this); - bAudioStarted = true; - } - - fr.Seek(opcodeSize, FileReader::SeekCur); - break; - } - - case OPCODE_INIT_VIDEO_BUFFERS: - { - assert(opcodeSize == 8); - nWidth = fr.ReadUInt16() * 8; - nHeight = fr.ReadUInt16() * 8; - - int count = fr.ReadUInt16(); - int truecolour = fr.ReadUInt16(); - assert(truecolour == 0); - - 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: - { - fr.Seek(opcodeSize, FileReader::SeekCur); - break; - } - - case OPCODE_SEND_BUFFER: - { - int nPalStart = fr.ReadUInt16(); - int nPalCount = fr.ReadUInt16(); - - animtex.SetFrame(&palette[0].r , GetCurrentFrame()); - - nFrame++; - SwapFrames(); - - fr.Seek(opcodeSize-4, FileReader::SeekCur); - break; - } - - case OPCODE_AUDIO_FRAME: - { - int nStart = (int)fr.Tell(); - uint16_t seqIndex = fr.ReadUInt16(); - uint16_t streamMask = fr.ReadUInt16(); - uint16_t nSamples = fr.ReadUInt16(); // number of samples this chunk - - int predictor[2]; - int i = 0; - - for (int ch = 0; ch < audio.nChannels; ch++) - { - predictor[ch] = fr.ReadUInt16(); - i++; - - if (predictor[ch] & 0x8000) { - predictor[ch] |= 0xFFFF0000; // sign extend - } - - audio.samples[audio.nWrite++] = predictor[ch]; - if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; - } - - int ch = 0; - for (; i < (nSamples / 2); i++) - { - predictor[ch] += delta_table[fr.ReadUInt8()]; - predictor[ch] = clamp(predictor[ch], -32768, 32768); - - audio.samples[audio.nWrite++] = predictor[ch]; - if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; - - // toggle channel - ch ^= audio.nChannels - 1; - } - - int nEnd = (int)fr.Tell(); - int nRead = nEnd - nStart; - assert(opcodeSize == nRead); - break; - } - - case OPCODE_SILENCE_FRAME: - { - uint16_t seqIndex = fr.ReadUInt16(); - uint16_t streamMask = fr.ReadUInt16(); - uint16_t nStreamLen = fr.ReadUInt16(); - break; - } - - case OPCODE_INIT_VIDEO_MODE: - { - fr.Seek(opcodeSize, FileReader::SeekCur); - break; - } - - case OPCODE_CREATE_GRADIENT: - { - fr.Seek(opcodeSize, FileReader::SeekCur); - 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 = fr.ReadUInt16(); - int nPalCount = fr.ReadUInt16(); - for (int i = nPalStart; i <= nPalCount; i++) - { - palette[i].r = fr.ReadUInt8() << 2; - palette[i].g = fr.ReadUInt8() << 2; - palette[i].b = fr.ReadUInt8() << 2; - } - break; - } - - case OPCODE_SET_PALETTE_COMPRESSED: - { - fr.Seek(opcodeSize, FileReader::SeekCur); - 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; - } - } - - int nRead = (int)fr.Read(decodeMap.pData, opcodeSize); - assert(nRead == opcodeSize); - break; - } - - case OPCODE_VIDEO_DATA: - { - int nStart = (int)fr.Tell(); - - // need to skip 14 bytes - fr.Seek(14, FileReader::SeekCur); - - 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; - } - } - } - } - - int nEnd = (int)fr.Tell(); - int nSkipBytes = opcodeSize - (nEnd - nStart); // we can end up with 1 byte left we need to skip - assert(nSkipBytes <= 1); - - fr.Seek(nSkipBytes, FileReader::SeekCur); - 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) @@ -581,7 +744,7 @@ void InterplayDecoder::DecodeBlock1(int32_t offset) void InterplayDecoder::DecodeBlock2(int32_t offset) { // copy block from 2 frames ago using a motion vector; need 1 more byte - uint8_t B = fr.ReadUInt8(); + uint8_t B = *ChunkPtr++; int x, y; @@ -603,7 +766,7 @@ void InterplayDecoder::DecodeBlock2(int32_t offset) void InterplayDecoder::DecodeBlock3(int32_t offset) { // copy 8x8 block from current frame from an up/left block - uint8_t B = fr.ReadUInt8(); + uint8_t B = *ChunkPtr++; int x, y; @@ -629,7 +792,7 @@ void InterplayDecoder::DecodeBlock4(int32_t offset) int x, y; uint8_t B, BL, BH; - B = fr.ReadUInt8(); + B = *ChunkPtr++; BL = B & 0x0F; BH = (B >> 4) & 0x0F; @@ -645,8 +808,8 @@ void InterplayDecoder::DecodeBlock4(int32_t offset) void InterplayDecoder::DecodeBlock5(int32_t offset) { // copy a block from the previous frame using an expanded range; need 2 more bytes - int8_t x = fr.ReadUInt8(); - int8_t y = fr.ReadUInt8(); + int8_t x = *ChunkPtr++; + int8_t y = *ChunkPtr++; uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset; uint8_t* pSrc = GetPreviousFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride); @@ -662,8 +825,8 @@ void InterplayDecoder::DecodeBlock7(int32_t offset) uint32_t flags = 0; uint8_t P[2]; - P[0] = fr.ReadUInt8(); - P[1] = fr.ReadUInt8(); + P[0] = *ChunkPtr++; + P[1] = *ChunkPtr++; // 2-color encoding if (P[0] <= P[1]) @@ -671,7 +834,7 @@ void InterplayDecoder::DecodeBlock7(int32_t offset) // need 8 more bytes from the stream for (int y = 0; y < 8; y++) { - flags = fr.ReadUInt8() | 0x100; + flags = (*ChunkPtr++) | 0x100; for (; flags != 1; flags >>= 1) { *pBuffer++ = P[flags & 1]; } @@ -681,7 +844,8 @@ void InterplayDecoder::DecodeBlock7(int32_t offset) else { // need 2 more bytes from the stream - flags = fr.ReadUInt16(); + flags = LE_16(ChunkPtr); + ChunkPtr += 2; for (int y = 0; y < 8; y += 2) { @@ -704,8 +868,8 @@ void InterplayDecoder::DecodeBlock8(int32_t offset) uint8_t P[4]; // 2-color encoding for each 4x4 quadrant, or 2-color encoding on either top and bottom or left and right halves - P[0] = fr.ReadUInt8(); - P[1] = fr.ReadUInt8(); + P[0] = *ChunkPtr++; + P[1] = *ChunkPtr++; if (P[0] <= P[1]) { @@ -715,10 +879,11 @@ void InterplayDecoder::DecodeBlock8(int32_t offset) if (!(y & 3)) { if (y) { - P[0] = fr.ReadUInt8(); - P[1] = fr.ReadUInt8(); + P[0] = *ChunkPtr++; + P[1] = *ChunkPtr++; } - flags = fr.ReadUInt16(); + flags = LE_16(ChunkPtr); + ChunkPtr += 2; } for (int x = 0; x < 4; x++, flags >>= 1) { @@ -732,9 +897,10 @@ void InterplayDecoder::DecodeBlock8(int32_t offset) } else { - flags = fr.ReadUInt32(); - P[2] = fr.ReadUInt8(); - P[3] = fr.ReadUInt8(); + flags = LE_32(ChunkPtr); + ChunkPtr += 4; + P[2] = *ChunkPtr++; + P[3] = *ChunkPtr++; if (P[2] <= P[3]) { @@ -752,7 +918,8 @@ void InterplayDecoder::DecodeBlock8(int32_t offset) pBuffer -= 8 * videoStride - 4; P[0] = P[2]; P[1] = P[3]; - flags = fr.ReadUInt32(); + flags = LE_32(ChunkPtr); + ChunkPtr += 4; } } } @@ -764,7 +931,8 @@ void InterplayDecoder::DecodeBlock8(int32_t offset) if (y == 4) { P[0] = P[2]; P[1] = P[3]; - flags = fr.ReadUInt32(); + flags = LE_32(ChunkPtr); + ChunkPtr += 4; } for (int x = 0; x < 8; x++, flags >>= 1) @@ -781,7 +949,8 @@ void InterplayDecoder::DecodeBlock9(int32_t offset) uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t P[4]; - fr.Read(P, 4); + memcpy(P, ChunkPtr, 4); + ChunkPtr += 4; // 4-color encoding if (P[0] <= P[1]) @@ -792,7 +961,8 @@ void InterplayDecoder::DecodeBlock9(int32_t offset) for (int y = 0; y < 8; y++) { // get the next set of 8 2-bit flags - int flags = fr.ReadUInt16(); + int flags = LE_16(ChunkPtr); + ChunkPtr += 2; for (int x = 0; x < 8; x++, flags >>= 2) { *pBuffer++ = P[flags & 0x03]; @@ -804,7 +974,8 @@ void InterplayDecoder::DecodeBlock9(int32_t offset) else { // 1 of 4 colors for each 2x2 block, need 4 more bytes - uint32_t flags = fr.ReadUInt32(); + uint32_t flags = LE_32(ChunkPtr); + ChunkPtr += 4; for (int y = 0; y < 8; y += 2) { @@ -823,7 +994,8 @@ void InterplayDecoder::DecodeBlock9(int32_t offset) else { // 1 of 4 colors for each 2x1 or 1x2 block, need 8 more bytes - uint64_t flags = fr.ReadUInt64(); + uint64_t flags = LE_64(ChunkPtr); + ChunkPtr += 8; if (P[2] <= P[3]) { @@ -857,7 +1029,8 @@ void InterplayDecoder::DecodeBlock10(int32_t offset) uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t P[8]; - fr.Read(P, 4); + memcpy(P, ChunkPtr, 4); + ChunkPtr += 4; // 4-color encoding for each 4x4 quadrant, or 4-color encoding on either top and bottom or left and right halves if (P[0] <= P[1]) @@ -869,8 +1042,12 @@ void InterplayDecoder::DecodeBlock10(int32_t offset) { // new values for each 4x4 block if (!(y & 3)) { - if (y) fr.Read(P, 4); - flags = fr.ReadUInt32(); + if (y) { + memcpy(P, ChunkPtr, 4); + ChunkPtr += 4; + } + flags = LE_32(ChunkPtr); + ChunkPtr += 4; } for (int x = 0; x < 4; x++, flags >>= 2) { @@ -886,9 +1063,11 @@ void InterplayDecoder::DecodeBlock10(int32_t offset) { // vertical split? int vert; - uint64_t flags = fr.ReadUInt64(); + uint64_t flags = LE_64(ChunkPtr); + ChunkPtr += 8; - fr.Read(P + 4, 4); + memcpy(P + 4, ChunkPtr, 4); + ChunkPtr += 4; vert = P[4] <= P[5]; // 4-color encoding for either left and right or top and bottom halves @@ -908,7 +1087,8 @@ void InterplayDecoder::DecodeBlock10(int32_t offset) // load values for second half if (y == 7) { memcpy(P, P + 4, 4); - flags = fr.ReadUInt64(); + flags = LE_64(ChunkPtr); + ChunkPtr += 8; } } } @@ -921,7 +1101,8 @@ void InterplayDecoder::DecodeBlock11(int32_t offset) for (int y = 0; y < 8; y++) { - fr.Read(pBuffer, 8); + memcpy(pBuffer, ChunkPtr, 8); + ChunkPtr += 8; pBuffer += videoStride; } } @@ -938,7 +1119,7 @@ void InterplayDecoder::DecodeBlock12(int32_t offset) pBuffer[x] = pBuffer[x + 1] = pBuffer[x + videoStride] = - pBuffer[x + 1 + videoStride] = fr.ReadUInt8(); + pBuffer[x + 1 + videoStride] = *ChunkPtr++; } pBuffer += videoStride * 2; } @@ -954,8 +1135,8 @@ void InterplayDecoder::DecodeBlock13(int32_t offset) { if (!(y & 3)) { - P[0] = fr.ReadUInt8(); - P[1] = fr.ReadUInt8(); + P[0] = *ChunkPtr++; + P[1] = *ChunkPtr++; } memset(pBuffer, P[0], 4); @@ -968,7 +1149,7 @@ void InterplayDecoder::DecodeBlock14(int32_t offset) { // 1-color encoding : the whole block is 1 solid color uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; - uint8_t pix = fr.ReadUInt8(); + uint8_t pix = *ChunkPtr++; for (int y = 0; y < 8; y++) { @@ -983,8 +1164,8 @@ void InterplayDecoder::DecodeBlock15(int32_t offset) uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t P[2]; - P[0] = fr.ReadUInt8(); - P[1] = fr.ReadUInt8(); + P[0] = *ChunkPtr++; + P[1] = *ChunkPtr++; for (int y = 0; y < 8; y++) { diff --git a/source/common/cutscenes/playmve.h b/source/common/cutscenes/playmve.h index ebee8fd29..9cb299038 100644 --- a/source/common/cutscenes/playmve.h +++ b/source/common/cutscenes/playmve.h @@ -44,6 +44,11 @@ #pragma once +#include +#include +#include +#include + #include "files.h" #include "animtexture.h" #include "s_music.h" @@ -103,26 +108,49 @@ public: bool Open(FileReader &fr); void Close(); + bool RunFrame(uint64_t clock); - struct AudioData - { - int hFx; - int nChannels; - uint16_t nSampleRate; - uint8_t nBitDepth; + bool FillSamples(void *buff, int len); - int16_t samples[6000 * kAudioBlocks]; // must be a multiple of the stream buffer size - int nWrite; - int nRead; - }; - - AudioData audio; - AnimTextures animtex; + bool HasAudio() const noexcept { return bAudioEnabled; } + int NumChannels() const noexcept { return audio.nChannels; } + int GetSampleRate() const noexcept { return audio.nSampleRate; } + void DisableAudio(); AnimTextures& animTex() { return animtex; } private: + 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; + }; + + struct AudioData + { + int nChannels = 0; + uint16_t nSampleRate = 0; + uint8_t nBitDepth = 0; + bool bCompressed = false; + + std::unique_ptr samples; + int nWrite = 0; + int nRead = 0; + + std::deque Packets; + }; + AudioData audio; + struct DecodeMap { uint8_t* pData; @@ -156,24 +184,30 @@ private: void DecodeBlock14(int32_t offset); void DecodeBlock15(int32_t offset); + std::mutex PacketMutex; FileReader fr; - bool bIsPlaying, bAudioStarted; + bool bIsPlaying, bAudioEnabled; uint32_t nTimerRate, nTimerDiv; uint32_t nWidth, nHeight, nFrame; double nFps; uint64_t nFrameDuration; + std::vector ChunkData; + int ProcessNextChunk(); + + std::deque VideoPackets; uint8_t* pVideoBuffers[2]; uint32_t nCurrentVideoBuffer, nPreviousVideoBuffer; int32_t videoStride; + const uint8_t *ChunkPtr = nullptr; DecodeMap decodeMap; + AnimTextures animtex; Palette palette[256]; uint64_t nNextFrameTime = 0; - SoundStream* stream = nullptr; }; #endif diff --git a/source/common/thirdparty/libsmackerdec/include/BitReader.h b/source/common/thirdparty/libsmackerdec/include/BitReader.h index 4863e42a8..ebc60a560 100644 --- a/source/common/thirdparty/libsmackerdec/include/BitReader.h +++ b/source/common/thirdparty/libsmackerdec/include/BitReader.h @@ -29,8 +29,8 @@ namespace SmackerCommon { class BitReader { public: - BitReader(SmackerCommon::FileStream &file, uint32_t size); - ~BitReader(); + BitReader(const uint8_t *data, uint32_t size) : bitData(data), totalSize(size) { } + ~BitReader() = default; uint32_t GetBit(); uint32_t GetBits(uint32_t n); void SkipBits(uint32_t n); @@ -39,15 +39,10 @@ class BitReader uint32_t GetPosition(); private: - uint32_t totalSize; - uint32_t currentOffset; - uint32_t bytesRead; - - SmackerCommon::FileStream *file; - - TArray Cache; - - void FillCache(); + const uint8_t *bitData = nullptr; + uint32_t totalSize = 0; + uint32_t currentOffset = 0; + uint32_t bytesRead = 0; }; } // close namespace SmackerCommon diff --git a/source/common/thirdparty/libsmackerdec/include/SmackerDecoder.h b/source/common/thirdparty/libsmackerdec/include/SmackerDecoder.h index f35fdc0fc..e9a8b1e27 100644 --- a/source/common/thirdparty/libsmackerdec/include/SmackerDecoder.h +++ b/source/common/thirdparty/libsmackerdec/include/SmackerDecoder.h @@ -48,6 +48,9 @@ #include #include "FileStream.h" #include "BitReader.h" +#include +#include +#include #include // exportable interface @@ -61,7 +64,6 @@ struct SmackerAudioInfo { uint32_t sampleRate; uint8_t nChannels; - uint8_t bitsPerSample; uint32_t idealBufferSize; }; @@ -71,6 +73,7 @@ void Smacker_Close (SmackerHandle &handle); uint32_t Smacker_GetNumAudioTracks (SmackerHandle &handle); SmackerAudioInfo Smacker_GetAudioTrackDetails (SmackerHandle &handle, uint32_t trackIndex); uint32_t Smacker_GetAudioData (SmackerHandle &handle, uint32_t trackIndex, int16_t *data); +void Smacker_DisableAudioTrack (SmackerHandle &handle, uint32_t trackIndex); uint32_t Smacker_GetNumFrames (SmackerHandle &handle); void Smacker_GetFrameSize (SmackerHandle &handle, uint32_t &width, uint32_t &height); uint32_t Smacker_GetCurrentFrameNum (SmackerHandle &handle); @@ -86,6 +89,12 @@ const int kMaxAudioTracks = 7; struct HuffContext; struct DBCtx; +struct SmackerPacket +{ + size_t size = 0; + std::unique_ptr data; +}; + struct SmackerAudioTrack { uint32_t sizeInBytes; @@ -99,6 +108,7 @@ struct SmackerAudioTrack uint32_t bufferSize; uint32_t bytesReadThisFrame; + std::deque packetData; }; class SmackerDecoder @@ -114,8 +124,10 @@ class SmackerDecoder void GetPalette(uint8_t *palette); void GetFrame(uint8_t *frame); + uint32_t GetNumAudioTracks(); SmackerAudioInfo GetAudioTrackDetails(uint32_t trackIndex); uint32_t GetAudioData(uint32_t trackIndex, int16_t *audioBuffer); + void DisableAudioTrack(uint32_t trackIndex); uint32_t GetNumFrames(); uint32_t GetCurrentFrameNum(); float GetFrameRate(); @@ -125,6 +137,7 @@ class SmackerDecoder private: SmackerCommon::FileStream file; char signature[4]; + std::mutex fileMutex; // video related members uint32_t nFrames; @@ -135,6 +148,10 @@ class SmackerDecoder bool isVer4; + uint32_t currentReadFrame; + std::vector packetData; + + std::deque framePacketData; SmackerAudioTrack audioTracks[kMaxAudioTracks]; uint32_t treeSize; @@ -160,9 +177,9 @@ class SmackerDecoder int DecodeBigTree(SmackerCommon::BitReader &bits, HuffContext *hc, DBCtx *ctx); int GetCode(SmackerCommon::BitReader &bits, std::vector &recode, int *last); int ReadPacket(); - int DecodeFrame(uint32_t frameSize); + int DecodeFrame(const uint8_t *dataPtr, uint32_t frameSize); void GetFrameSize(uint32_t &width, uint32_t &height); - int DecodeAudio(uint32_t size, SmackerAudioTrack &track); + int DecodeAudio(const uint8_t *dataPtr, uint32_t size, SmackerAudioTrack &track); }; #endif diff --git a/source/common/thirdparty/libsmackerdec/src/BitReader.cpp b/source/common/thirdparty/libsmackerdec/src/BitReader.cpp index ea5169d3f..604848ec6 100644 --- a/source/common/thirdparty/libsmackerdec/src/BitReader.cpp +++ b/source/common/thirdparty/libsmackerdec/src/BitReader.cpp @@ -22,25 +22,6 @@ namespace SmackerCommon { -BitReader::BitReader(SmackerCommon::FileStream &file, uint32_t size) -{ - this->file = &file; - this->totalSize = size; - this->currentOffset = 0; - this->bytesRead = 0; - - this->Cache.Resize(size); - file.ReadBytes(this->Cache.Data(), size); -} - -BitReader::~BitReader() -{ -} - -void BitReader::FillCache() -{ -} - uint32_t BitReader::GetSize() { return totalSize * 8; @@ -53,7 +34,7 @@ uint32_t BitReader::GetPosition() uint32_t BitReader::GetBit() { - uint32_t ret = (Cache[currentOffset>>3]>>(currentOffset&7))&1; + uint32_t ret = (bitData[currentOffset>>3]>>(currentOffset&7))&1; currentOffset++; return ret; } diff --git a/source/common/thirdparty/libsmackerdec/src/SmackerDecoder.cpp b/source/common/thirdparty/libsmackerdec/src/SmackerDecoder.cpp index 08c9323fa..ff69f3b2c 100644 --- a/source/common/thirdparty/libsmackerdec/src/SmackerDecoder.cpp +++ b/source/common/thirdparty/libsmackerdec/src/SmackerDecoder.cpp @@ -49,6 +49,7 @@ #include "limits.h" #include #include +#include std::vector classInstances; @@ -93,10 +94,9 @@ void Smacker_Close(SmackerHandle &handle) handle.isValid = false; } -uint32_t Smacker_GetNumAudioTracks(SmackerHandle &) +uint32_t Smacker_GetNumAudioTracks(SmackerHandle &handle) { - // TODO: fixme - return 1; + return classInstances[handle.instanceIndex]->GetNumAudioTracks(); } SmackerAudioInfo Smacker_GetAudioTrackDetails(SmackerHandle &handle, uint32_t trackIndex) @@ -115,6 +115,18 @@ uint32_t Smacker_GetAudioData(SmackerHandle &handle, uint32_t trackIndex, int16_ return classInstances[handle.instanceIndex]->GetAudioData(trackIndex, data); } +/* Disables an audio track if it's enabled. + * + * When getting video or audio data, encoded data is stored for other tracks so + * they can be read and decoded later. This function prevents a build-up of + * encoded audio data for the specified track if such data isn't going to be + * decoded and read by the caller. + */ +void Smacker_DisableAudioTrack(SmackerHandle &handle, uint32_t trackIndex) +{ + classInstances[handle.instanceIndex]->DisableAudioTrack(trackIndex); +} + uint32_t Smacker_GetNumFrames(SmackerHandle &handle) { return classInstances[handle.instanceIndex]->GetNumFrames(); @@ -165,6 +177,7 @@ void Smacker_GotoFrame(SmackerHandle &handle, uint32_t frameNum) SmackerDecoder::SmackerDecoder() { isVer4 = false; + currentReadFrame = 0; currentFrame = 0; picture = 0; nextPos = 0; @@ -223,6 +236,7 @@ static const uint8_t smk_pal[64] = { enum SAudFlags { SMK_AUD_PACKED = 0x80000000, + SMK_AUD_PRESENT = 0x40000000, SMK_AUD_16BITS = 0x20000000, SMK_AUD_STEREO = 0x10000000, SMK_AUD_BINKAUD = 0x08000000, @@ -372,6 +386,10 @@ bool SmackerDecoder::Open(const char *fileName) frameFlags[i] = file.ReadByte(); } + auto maxFrameSize = std::max_element(frameSizes.begin(), frameSizes.end()); + if (maxFrameSize != frameSizes.end()) + packetData.reserve(*maxFrameSize); + // handle possible audio streams for (int i = 0; i < kMaxAudioTracks; i++) { @@ -379,6 +397,11 @@ bool SmackerDecoder::Open(const char *fileName) audioTracks[i].bufferSize = 0; audioTracks[i].bytesReadThisFrame = 0; + // Disable non-consecutive enabled tracks. Not sure how to otherwise report + // them properly for Smacker_GetNumAudioTracks. + if (i > 0 && !(audioTracks[i-1].flags & SMK_AUD_PRESENT)) + audioTracks[i].flags &= ~SMK_AUD_PRESENT; + if (audioTracks[i].flags & 0xFFFFFF) { /* @@ -426,13 +449,18 @@ bool SmackerDecoder::Open(const char *fileName) { if (frameFlag & 1) { - // skip size - file.Skip(4); - + uint32_t size = file.ReadUint32LE(); uint32_t unpackedSize = file.ReadUint32LE(); + // If the track isn't 16-bit, double the buffer size for converting 8-bit to 16-bit. + if (!(audioTracks[i].flags & SMK_AUD_16BITS)) + unpackedSize *= 2; + audioTracks[i].bufferSize = unpackedSize; audioTracks[i].buffer = new uint8_t[unpackedSize]; + + // skip size + file.Skip(size - 8); } frameFlag >>= 1; } @@ -633,7 +661,10 @@ int SmackerDecoder::DecodeHeaderTree(SmackerCommon::BitReader &bits, std::vector // static int decode_header_trees(SmackVContext *smk) { bool SmackerDecoder::DecodeHeaderTrees() { - SmackerCommon::BitReader bits(file, treeSize); + auto treeData = std::make_unique(treeSize); + file.ReadBytes(treeData.get(), treeSize); + + SmackerCommon::BitReader bits(treeData.get(), treeSize); if (!bits.GetBit()) { @@ -683,58 +714,53 @@ bool SmackerDecoder::DecodeHeaderTrees() DecodeHeaderTree(bits, type_tbl, type_last, typeSize); } - /* FIXME - we don't seems to read/use EVERY bit we 'load' into the bit reader - * and as my bitreader reads from the file rather than a buffer read from file - * of size 'treeSize', I need to make sure I consume the remaining bits (and thus increment - * the file read position to where the code expects it to be when this function returns (ie - * 'treeSize' number of bytes must be read - */ - uint32_t left = bits.GetSize() - bits.GetPosition(); - bits.SkipBits(left); - return true; } void SmackerDecoder::GetNextFrame() { - ReadPacket(); -} - -int SmackerDecoder::ReadPacket() -{ - // test-remove if (currentFrame >= nFrames) - return 1; + return; - // seek to next frame position - file.Seek(nextPos, SmackerCommon::FileStream::kSeekStart); + std::unique_lock flock(fileMutex); + while (framePacketData.empty()) + { + if (ReadPacket() > 0) + return; + } + SmackerPacket pkt = std::move(framePacketData.front()); + framePacketData.pop_front(); + flock.unlock(); - uint32_t frameSize = frameSizes[currentFrame] & (~3); + uint32_t frameSize = (uint32_t)pkt.size; uint8_t frameFlag = frameFlags[currentFrame]; + const uint8_t *packetDataPtr = pkt.data.get(); + // handle palette change if (frameFlag & kSMKpal) { - int size, sz, t, off, j, pos; + int size, sz, t, off, j; uint8_t *pal = palette; uint8_t oldpal[768]; + const uint8_t *dataEnd; memcpy(oldpal, pal, 768); - size = file.ReadByte(); + size = *(packetDataPtr++); size = size * 4 - 1; frameSize -= size; frameSize--; sz = 0; - pos = file.GetPosition() + size; + dataEnd = packetDataPtr + size; while (sz < 256) { - t = file.ReadByte(); + t = *(packetDataPtr++); if (t & 0x80){ /* skip palette entries */ sz += (t & 0x7F) + 1; pal += ((t & 0x7F) + 1) * 3; } else if (t & 0x40){ /* copy with offset */ - off = file.ReadByte() * 3; + off = *(packetDataPtr++) * 3; j = (t & 0x3F) + 1; while (j-- && sz < 256) { *pal++ = oldpal[off + 0]; @@ -745,47 +771,92 @@ int SmackerDecoder::ReadPacket() } } else { /* new entries */ *pal++ = smk_pal[t]; - *pal++ = smk_pal[file.ReadByte() & 0x3F]; - *pal++ = smk_pal[file.ReadByte() & 0x3F]; + *pal++ = smk_pal[*(packetDataPtr++) & 0x3F]; + *pal++ = smk_pal[*(packetDataPtr++) & 0x3F]; sz++; } } - - file.Seek(pos, SmackerCommon::FileStream::kSeekStart); + + packetDataPtr = dataEnd; + } + + if (frameSize > 0) + DecodeFrame(packetDataPtr, frameSize); + + currentFrame++; +} + +int SmackerDecoder::ReadPacket() +{ + // test-remove + if (currentReadFrame >= nFrames) + return 1; + + // seek to next frame position + file.Seek(nextPos, SmackerCommon::FileStream::kSeekStart); + + uint32_t frameSize = frameSizes[currentReadFrame] & (~3); + uint8_t frameFlag = frameFlags[currentReadFrame]; + + packetData.resize(frameSize); + file.ReadBytes(packetData.data(), frameSize); + + const uint8_t *packetDataPtr = packetData.data(); + + // skip pal data for later, after getting audio data + if (frameFlag & kSMKpal) + { + int size = (*packetDataPtr * 4); + packetDataPtr += size; + frameSize -= size; } frameFlag >>= 1; - - // check for and handle audio - for (int i = 0; i < kMaxAudioTracks; i++) + for (int i = 0; i < kMaxAudioTracks; i++) { - audioTracks[i].bytesReadThisFrame = 0; - - if (frameFlag & 1) + if (frameFlag & 1) { - uint32_t size = file.ReadUint32LE() - 4; + uint32_t size = *(packetDataPtr++); + size |= *(packetDataPtr++) << 8; + size |= *(packetDataPtr++) << 16; + size |= *(packetDataPtr++) << 24; frameSize -= size; - frameSize -= 4; + size -= 4; - DecodeAudio(size, audioTracks[i]); + if (audioTracks[i].flags & SMK_AUD_PRESENT) + { + SmackerPacket pkt; + pkt.size = size; + pkt.data = std::make_unique(size); + memcpy(pkt.data.get(), packetDataPtr, size); + audioTracks[i].packetData.emplace_back(std::move(pkt)); + } + packetDataPtr += size; } frameFlag >>= 1; } - if (frameSize == 0) { - return -1; + SmackerPacket pkt; + frameFlag = frameFlags[currentReadFrame]; + if (frameSize != 0 || (frameFlag & kSMKpal)) + { + int palsize = (frameFlag & kSMKpal) ? packetData[0] * 4 : 0; + int totalSize = frameSize + palsize; + pkt.size = totalSize; + pkt.data = std::make_unique(totalSize); + memcpy(pkt.data.get(), packetData.data(), palsize); + memcpy(pkt.data.get()+palsize, packetDataPtr, frameSize); } + framePacketData.emplace_back(std::move(pkt)); - DecodeFrame(frameSize); - - currentFrame++; + ++currentReadFrame; nextPos = file.GetPosition(); return 0; } -int SmackerDecoder::DecodeFrame(uint32_t frameSize) +int SmackerDecoder::DecodeFrame(const uint8_t *dataPtr, uint32_t frameSize) { last_reset(mmap_tbl, mmap_last); last_reset(mclr_tbl, mclr_last); @@ -805,7 +876,8 @@ int SmackerDecoder::DecodeFrame(uint32_t frameSize) stride = frameWidth; - SmackerCommon::BitReader bits(file, frameSize); + SmackerCommon::BitReader bits(dataPtr, frameSize); + dataPtr += frameSize; while (blk < blocks) { @@ -935,24 +1007,14 @@ int SmackerDecoder::DecodeFrame(uint32_t frameSize) } } - /* FIXME - we don't seems to read/use EVERY bit we 'load' into the bit reader - * and as my bitreader reads from the file rather than a buffer read from file - * of size 'frameSize', I need to make sure I consume the remaining bits (and thus increment - * the file read position to where the code expects it to be when this function returns (ie - * 'frameSize' number of bytes must be read - */ - uint32_t left = bits.GetSize() - bits.GetPosition(); - bits.SkipBits(left); - return 0; } /** * Decode Smacker audio data */ -int SmackerDecoder::DecodeAudio(uint32_t size, SmackerAudioTrack &track) +int SmackerDecoder::DecodeAudio(const uint8_t *dataPtr, uint32_t size, SmackerAudioTrack &track) { - HuffContext h[4]; SmackerCommon::VLCtable vlc[4]; int val; int i, res; @@ -961,19 +1023,15 @@ int SmackerDecoder::DecodeAudio(uint32_t size, SmackerAudioTrack &track) int pred[2] = {0, 0}; int16_t *samples = reinterpret_cast(track.buffer); - int8_t *samples8 = reinterpret_cast(track.buffer); - int buf_size = track.bufferSize; + SmackerCommon::BitReader bits(dataPtr, size); - if (buf_size <= 4) { + unpackedSize = bits.GetBits(32); + if (unpackedSize <= 4) { Printf("SmackerDecoder::DecodeAudio() - Packet is too small\n"); return -1; } - SmackerCommon::BitReader bits(file, size); - - unpackedSize = bits.GetBits(32); - if (!bits.GetBit()) { // no sound data return 1; @@ -987,7 +1045,7 @@ int SmackerDecoder::DecodeAudio(uint32_t size, SmackerAudioTrack &track) return -1; } - memset(h, 0, sizeof(HuffContext) * 4); + HuffContext h[4]; // Initialize for (i = 0; i < (1 << (sampleBits + stereo)); i++) { @@ -1045,7 +1103,7 @@ int SmackerDecoder::DecodeAudio(uint32_t size, SmackerAudioTrack &track) for (i = stereo; i >= 0; i--) pred[i] = bits.GetBits(8); for (i = 0; i <= stereo; i++) - *samples8++ = pred[i]; + *samples++ = (pred[i]-128) << 8; for (; i < unpackedSize; i++) { if (i & stereo){ if (VLC_GetSize(vlc[1])) @@ -1053,23 +1111,21 @@ int SmackerDecoder::DecodeAudio(uint32_t size, SmackerAudioTrack &track) else res = 0; pred[1] += (int8_t)h[1].values[res]; - *samples8++ = pred[1]; + *samples++ = (pred[1]-128) << 8; } else { if (VLC_GetSize(vlc[0])) res = VLC_GetCodeBits(bits, vlc[0]); else res = 0; pred[0] += (int8_t)h[0].values[res]; - *samples8++ = pred[0]; + *samples++ = (pred[0]-128) << 8; } } + unpackedSize *= 2; } track.bytesReadThisFrame = unpackedSize; - uint32_t left = bits.GetSize() - bits.GetPosition(); - bits.SkipBits(left); - return 0; } @@ -1113,11 +1169,32 @@ void SmackerDecoder::GotoFrame(uint32_t frameNum) // file.Seek(firstFrameFilePos, SmackerCommon::FileStream::kSeekStart); + currentReadFrame = 0; currentFrame = 0; nextPos = firstFrameFilePos; - for (unsigned i = 0; i < frameNum + 1; i++) + framePacketData.clear(); + for (unsigned j = 0; j < kMaxAudioTracks; j++) + audioTracks[j].packetData.clear(); + + for (unsigned i = 0; i < frameNum; i++) + { GetNextFrame(); + for (unsigned j = 0; j < kMaxAudioTracks; j++) + audioTracks[j].packetData.clear(); + } + + GetNextFrame(); +} + +uint32_t SmackerDecoder::GetNumAudioTracks() +{ + for(uint32_t i = 0;i < kMaxAudioTracks;++i) + { + if (!(audioTracks[i].flags & SMK_AUD_PRESENT)) + return i; + } + return kMaxAudioTracks; } SmackerAudioInfo SmackerDecoder::GetAudioTrackDetails(uint32_t trackIndex) @@ -1127,7 +1204,6 @@ SmackerAudioInfo SmackerDecoder::GetAudioTrackDetails(uint32_t trackIndex) info.sampleRate = track->sampleRate; info.nChannels = track->nChannels; - info.bitsPerSample = track->bitsPerSample; // audio buffer size in bytes info.idealBufferSize = track->bufferSize; @@ -1143,9 +1219,37 @@ uint32_t SmackerDecoder::GetAudioData(uint32_t trackIndex, int16_t *audioBuffer) SmackerAudioTrack *track = &audioTracks[trackIndex]; + std::unique_lock flock(fileMutex); + if (!(track->flags & SMK_AUD_PRESENT)) + return 0; + + while (track->packetData.empty()) + { + if (ReadPacket() > 0) + return 0; + } + SmackerPacket pkt = std::move(track->packetData.front()); + track->packetData.pop_front(); + flock.unlock(); + + track->bytesReadThisFrame = 0; + + const uint8_t *packetDataPtr = pkt.data.get(); + uint32_t size = (uint32_t)pkt.size; + + DecodeAudio(packetDataPtr, size, *track); + if (track->bytesReadThisFrame) { memcpy(audioBuffer, track->buffer, std::min(track->bufferSize, track->bytesReadThisFrame)); } return track->bytesReadThisFrame; } + +void SmackerDecoder::DisableAudioTrack(uint32_t trackIndex) +{ + SmackerAudioTrack *track = &audioTracks[trackIndex]; + std::unique_lock flock(fileMutex); + track->flags &= ~SMK_AUD_PRESENT; + track->packetData.clear(); +}