- movieplayer update from GZDoom.

This commit is contained in:
Christoph Oelckers 2022-10-14 20:18:13 +02:00
parent 5a580c145e
commit 09b31afd40
7 changed files with 1060 additions and 589 deletions

View file

@ -47,6 +47,10 @@
#include "filesystem.h"
#include "vm.h"
#include "printf.h"
#include <atomic>
#include <cmath>
#include <zmusic.h>
#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<int64_t>(pos.samplesplayed / double(SampleRate) * 1'000'000'000.0);
postime = std::max<int64_t>(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<uint64_t>(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<MvePlayer*>(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<int> animSnd;
ZMusic_MusicStream MusicStream = nullptr;
MovieAudioTrack AudioTrack;
unsigned width, height;
TArray<uint8_t> Pic;
TArray<uint8_t> 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<VpxPlayer*>(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<uint8_t> pFrame;
TArray<uint8_t> audioBuffer;
TArray<int16_t> audioBuffer;
int nFrames;
bool fullscreenScale;
uint64_t nFrameNs;
int nFrame = 0;
const TArray<int> 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<SmkPlayer*>(userdata)->StreamCallback(stream, buff, len); }
SmkPlayer(const char *fn, TArray<int>& 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<int>& 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);

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,11 @@
#pragma once
#include <deque>
#include <memory>
#include <mutex>
#include <vector>
#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<uint8_t[]> pData;
};
struct VideoPacket
{
uint16_t nPalStart=0, nPalCount=0;
uint32_t nDecodeMapSize = 0;
uint32_t nVideoDataSize = 0;
bool bSendFlag = false;
std::unique_ptr<uint8_t[]> pData;
};
struct AudioData
{
int nChannels = 0;
uint16_t nSampleRate = 0;
uint8_t nBitDepth = 0;
bool bCompressed = false;
std::unique_ptr<int16_t[]> samples;
int nWrite = 0;
int nRead = 0;
std::deque<AudioPacket> 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<uint8_t> ChunkData;
int ProcessNextChunk();
std::deque<VideoPacket> 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

View file

@ -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<uint8_t> Cache;
void FillCache();
const uint8_t *bitData = nullptr;
uint32_t totalSize = 0;
uint32_t currentOffset = 0;
uint32_t bytesRead = 0;
};
} // close namespace SmackerCommon

View file

@ -48,6 +48,9 @@
#include <stdint.h>
#include "FileStream.h"
#include "BitReader.h"
#include <deque>
#include <memory>
#include <mutex>
#include <vector>
// 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<uint8_t[]> data;
};
struct SmackerAudioTrack
{
uint32_t sizeInBytes;
@ -99,6 +108,7 @@ struct SmackerAudioTrack
uint32_t bufferSize;
uint32_t bytesReadThisFrame;
std::deque<SmackerPacket> 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<uint8_t> packetData;
std::deque<SmackerPacket> 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<int> &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

View file

@ -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;
}

View file

@ -49,6 +49,7 @@
#include "limits.h"
#include <assert.h>
#include <algorithm>
#include <memory>
std::vector<class SmackerDecoder*> 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<uint8_t[]>(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<uint8_t[]>(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<uint8_t[]>(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<int16_t*>(track.buffer);
int8_t *samples8 = reinterpret_cast<int8_t*>(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();
}