Make a common class to help with movie audio streams

This commit is contained in:
Chris Robinson 2022-10-02 18:13:10 -07:00 committed by Christoph Oelckers
parent d11e2ef1ac
commit 5e465a65e2

View file

@ -71,11 +71,88 @@ public:
virtual FTextureID GetTexture() = 0; virtual FTextureID GetTexture() = 0;
}; };
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
// A simple filter is used to smooth out jittery timers // A simple filter is used to smooth out jittery timers
static const double AudioAvgFilterCoeff{std::pow(0.01, 1.0/10.0)}; 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. // A threshold is in place to avoid constantly skipping due to imprecise timers.
static constexpr double AudioSyncThreshold{0.03}; static constexpr double AudioSyncThreshold{0.03};
class MovieAudioTrack
{
SoundStream *AudioStream = nullptr;
std::atomic<uint64_t> ClockTime = 0;
int64_t AudioOffset = 0;
double ClockDiffAvg = 0;
int SampleRate = 0;
int FrameSize = 0;
double FilterDelay(double diff)
{
ClockDiffAvg = ClockDiffAvg*AudioAvgFilterCoeff + diff;
diff = ClockDiffAvg*(1.0 - AudioAvgFilterCoeff);
if(diff < AudioSyncThreshold/2.0 && diff > -AudioSyncThreshold)
return 0.0;
return diff;
}
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;
}
void SetClock(uint64_t clock) noexcept
{
ClockTime.store(clock, std::memory_order_release);
}
// NOTE: The callback (and thus GetClockDiff) can be called before
// S_CreateCustomStream returns, and thus before AudioStream gets set. So the
// caller needs to pass in the SoundStream given to the callback.
double GetClockDiff(SoundStream *stream)
{
// Calculate the difference in time between the movie clock and the audio position.
auto pos = stream->GetPlayPosition();
uint64_t clock = ClockTime.load(std::memory_order_acquire);
return FilterDelay(clock / 1'000'000'000.0 -
double(int64_t(pos.samplesplayed)+AudioOffset) / SampleRate +
pos.latency.count() / 1'000'000'000.0);
}
void AdjustOffset(int adjust) noexcept
{
AudioOffset += adjust;
}
SoundStream *GetAudioStream() const noexcept { return AudioStream; }
int GetSampleRate() const noexcept { return SampleRate; }
int GetFrameSize() const noexcept { return FrameSize; }
};
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// //
@ -231,12 +308,7 @@ class VpxPlayer : public MoviePlayer
const TArray<int> animSnd; const TArray<int> animSnd;
ZMusic_MusicStream MusicStream = nullptr; ZMusic_MusicStream MusicStream = nullptr;
SoundStream *AudioStream = nullptr; MovieAudioTrack AudioTrack;
std::atomic<uint64_t> clocktime = 0;
int64_t audiooffset = 0;
double ClockDiffAvg = 0;
int samplerate = 0;
int framesize = 0;
unsigned width, height; unsigned width, height;
TArray<uint8_t> Pic; TArray<uint8_t> Pic;
@ -257,24 +329,11 @@ class VpxPlayer : public MoviePlayer
public: public:
int soundtrack = -1; int soundtrack = -1;
double FilterDelay(double diff)
{
ClockDiffAvg = ClockDiffAvg*AudioAvgFilterCoeff + diff;
diff = ClockDiffAvg*(1.0 - AudioAvgFilterCoeff);
if(diff < AudioSyncThreshold/2.0 && diff > -AudioSyncThreshold)
return 0.0;
return diff;
}
bool StreamCallback(SoundStream *stream, void *buff, int len) bool StreamCallback(SoundStream *stream, void *buff, int len)
{ {
// Calculate the difference in time between the movie clock and the audio position. const double delay = AudioTrack.GetClockDiff(stream);
auto pos = stream->GetPlayPosition(); const int samplerate = AudioTrack.GetSampleRate();
uint64_t clock = clocktime.load(std::memory_order_acquire); const int framesize = AudioTrack.GetFrameSize();
double delay = FilterDelay(clock / 1'000'000'000.0 -
double(int64_t(pos.samplesplayed)+audiooffset) / samplerate +
pos.latency.count() / 1'000'000'000.0);
if(delay > 0.0) if(delay > 0.0)
{ {
@ -284,7 +343,7 @@ public:
return false; return false;
// Offset the measured audio position to account for the skipped samples. // Offset the measured audio position to account for the skipped samples.
audiooffset += skip/framesize; AudioTrack.AdjustOffset(skip/framesize);
if(skip == len) if(skip == len)
return ZMusic_FillStream(MusicStream, buff, len); return ZMusic_FillStream(MusicStream, buff, len);
@ -312,7 +371,7 @@ public:
} }
// Offset the measured audio position to account for the duplicated samples. // Offset the measured audio position to account for the duplicated samples.
audiooffset -= dup/framesize; AudioTrack.AdjustOffset(-dup/framesize);
return true; return true;
} }
@ -485,7 +544,11 @@ public:
void Start() override void Start() override
{ {
if (soundtrack >= 0 && !AudioStream) if (SoundStream *stream = AudioTrack.GetAudioStream())
{
stream->SetPaused(false);
}
else if (soundtrack >= 0)
{ {
S_StopMusic(true); S_StopMusic(true);
FileReader reader = fileSystem.OpenFileReader(soundtrack); FileReader reader = fileSystem.OpenFileReader(soundtrack);
@ -495,31 +558,23 @@ public:
} }
if (MusicStream) if (MusicStream)
{ {
bool ok = false;
SoundStreamInfo info{}; SoundStreamInfo info{};
ZMusic_GetStreamInfo(MusicStream, &info); ZMusic_GetStreamInfo(MusicStream, &info);
// if mBufferSize == 0, the music stream is played externally (e.g. // if mBufferSize == 0, the music stream is played externally (e.g.
// Windows' MIDI synth), which we can't keep synced. Play anyway? // Windows' MIDI synth), which we can't keep synced. Play anyway?
if (info.mBufferSize > 0 && ZMusic_Start(MusicStream, 0, false)) if (info.mBufferSize > 0 && ZMusic_Start(MusicStream, 0, false))
{ {
int channels = abs(info.mNumChannels); ok = AudioTrack.Start(info.mSampleRate, abs(info.mNumChannels),
samplerate = info.mSampleRate; (info.mNumChannels < 0) ? MusicSamples16bit : MusicSamplesFloat, &StreamCallbackC, this);
framesize = channels * ((info.mNumChannels < 0) ? sizeof(int16_t) : sizeof(float));
int bufsize = 40 * info.mSampleRate / 1000 * framesize;
AudioStream = S_CreateCustomStream(bufsize, info.mSampleRate, channels,
(info.mNumChannels < 0) ? MusicSamples16bit : MusicSamplesFloat,
&StreamCallbackC, this);
} }
if (!AudioStream) if (!ok)
{ {
ZMusic_Close(MusicStream); ZMusic_Close(MusicStream);
MusicStream = nullptr; MusicStream = nullptr;
} }
} }
} }
else if (AudioStream)
{
AudioStream->SetPaused(false);
}
animtex.SetSize(AnimTexture::YUV, width, height); animtex.SetSize(AnimTexture::YUV, width, height);
} }
@ -531,7 +586,7 @@ public:
bool Frame(uint64_t clock) override bool Frame(uint64_t clock) override
{ {
clocktime.store(clock, std::memory_order_release); AudioTrack.SetClock(clock);
bool stop = false; bool stop = false;
if (clock > nextframetime) if (clock > nextframetime)
@ -576,23 +631,21 @@ public:
void Stop() override void Stop() override
{ {
if (AudioStream) if (SoundStream *stream = AudioTrack.GetAudioStream())
{ stream->SetPaused(true);
AudioStream->SetPaused(true);
}
bool nostopsound = (flags & NOSOUNDCUTOFF); bool nostopsound = (flags & NOSOUNDCUTOFF);
if (!nostopsound) soundEngine->StopAllChannels(); if (!nostopsound) soundEngine->StopAllChannels();
} }
~VpxPlayer() ~VpxPlayer()
{ {
vpx_codec_destroy(&codec); if(MusicStream)
animtex.Clean();
if(AudioStream)
{ {
S_StopCustomStream(AudioStream); AudioTrack.Finish();
ZMusic_Close(MusicStream); ZMusic_Close(MusicStream);
} }
vpx_codec_destroy(&codec);
animtex.Clean();
} }
FTextureID GetTexture() override FTextureID GetTexture() override
@ -631,34 +684,17 @@ class SmkPlayer : public MoviePlayer
int nFrame = 0; int nFrame = 0;
const TArray<int> animSnd; const TArray<int> animSnd;
FString filename; FString filename;
SoundStream* stream = nullptr; MovieAudioTrack AudioTrack;
bool hassound = false; bool hassound = false;
std::atomic<uint64_t> clocktime = 0;
int64_t audiooffset = 0;
double ClockDiffAvg = 0;
int samplerate = 0;
int framesize = 0;
public: public:
bool isvalid() { return hSMK.isValid; } bool isvalid() { return hSMK.isValid; }
double FilterDelay(double diff)
{
ClockDiffAvg = ClockDiffAvg*AudioAvgFilterCoeff + diff;
diff = ClockDiffAvg*(1.0 - AudioAvgFilterCoeff);
if(diff < AudioSyncThreshold/2.0 && diff > -AudioSyncThreshold)
return 0.0;
return diff;
}
bool StreamCallback(SoundStream* stream, void* buff, int len) bool StreamCallback(SoundStream* stream, void* buff, int len)
{ {
auto pos = stream->GetPlayPosition(); const double delay = AudioTrack.GetClockDiff(stream);
uint64_t clock = clocktime.load(std::memory_order_acquire); const int samplerate = AudioTrack.GetSampleRate();
double delay = FilterDelay(clock / 1'000'000'000.0 - const int framesize = AudioTrack.GetFrameSize();
double(int64_t(pos.samplesplayed)+audiooffset) / samplerate +
pos.latency.count() / 1'000'000'000.0);
int avail = (adata.nWrite - adata.nRead) * 2; int avail = (adata.nWrite - adata.nRead) * 2;
@ -670,7 +706,7 @@ public:
{ {
if (avail >= skip) if (avail >= skip)
{ {
audiooffset += skip / framesize; AudioTrack.AdjustOffset(skip / framesize);
if (avail > skip) if (avail > skip)
{ {
adata.nRead += skip / 2; adata.nRead += skip / 2;
@ -681,7 +717,7 @@ public:
break; break;
} }
audiooffset += avail / framesize; AudioTrack.AdjustOffset(avail / framesize);
adata.nWrite = 0; adata.nWrite = 0;
adata.nRead = 0; adata.nRead = 0;
skip -= avail; skip -= avail;
@ -709,7 +745,7 @@ public:
} }
// Offset the measured audio position to account for the duplicated samples. // Offset the measured audio position to account for the duplicated samples.
audiooffset -= dup/framesize; AudioTrack.AdjustOffset(-dup/framesize);
char *src = (char*)&audioBuffer[adata.nRead]; char *src = (char*)&audioBuffer[adata.nRead];
char *dst = (char*)buff; char *dst = (char*)buff;
@ -782,14 +818,16 @@ public:
audioBuffer.Resize(adata.inf.idealBufferSize / 2); audioBuffer.Resize(adata.inf.idealBufferSize / 2);
hassound = true; hassound = true;
} }
for (int i = 1;i < numAudioTracks;++i)
Smacker_DisableAudioTrack(hSMK, i);
numAudioTracks = 1;
} }
if (!hassound) if (!hassound)
{ {
adata.inf = {}; adata.inf = {};
Smacker_DisableAudioTrack(hSMK, 0); Smacker_DisableAudioTrack(hSMK, 0);
numAudioTracks = 0;
} }
for (int i = 1;i < numAudioTracks;++i)
Smacker_DisableAudioTrack(hSMK, i);
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -801,7 +839,8 @@ public:
void Start() override void Start() override
{ {
animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight); animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight);
if (stream) stream->SetPaused(false); if (SoundStream *stream = AudioTrack.GetAudioStream())
stream->SetPaused(false);
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -812,7 +851,7 @@ public:
bool Frame(uint64_t clock) override bool Frame(uint64_t clock) override
{ {
clocktime.store(clock, std::memory_order_release); AudioTrack.SetClock(clock);
int frame = int(clock / nFrameNs); int frame = int(clock / nFrameNs);
twod->ClearScreen(); twod->ClearScreen();
@ -824,17 +863,15 @@ public:
Smacker_GetFrame(hSMK, pFrame.Data()); Smacker_GetFrame(hSMK, pFrame.Data());
animtex.SetFrame(palette, pFrame.Data()); animtex.SetFrame(palette, pFrame.Data());
if (!stream && hassound) if (!AudioTrack.GetAudioStream() && numAudioTracks)
{ {
S_StopMusic(true); S_StopMusic(true);
samplerate = adata.inf.sampleRate; if (!AudioTrack.Start(adata.inf.sampleRate, adata.inf.nChannels, MusicSamples16bit, StreamCallbackC, this))
framesize = adata.inf.nChannels * 2; {
int bufsize = 40 * samplerate / 1000 * framesize;
stream = S_CreateCustomStream(bufsize, adata.inf.sampleRate, adata.inf.nChannels, MusicSamples16bit, StreamCallbackC, this);
if (!stream)
Smacker_DisableAudioTrack(hSMK, 0); Smacker_DisableAudioTrack(hSMK, 0);
numAudioTracks = 0;
}
} }
bool nostopsound = (flags & NOSOUNDCUTOFF); bool nostopsound = (flags & NOSOUNDCUTOFF);
@ -856,14 +893,15 @@ public:
void Stop() override void Stop() override
{ {
if (stream) stream->SetPaused(true); if (SoundStream *stream = AudioTrack.GetAudioStream())
stream->SetPaused(true);
bool nostopsound = (flags & NOSOUNDCUTOFF); bool nostopsound = (flags & NOSOUNDCUTOFF);
if (!nostopsound && !hassound) soundEngine->StopAllChannels(); if (!nostopsound && !hassound) soundEngine->StopAllChannels();
} }
~SmkPlayer() ~SmkPlayer()
{ {
if (stream) S_StopCustomStream(stream); AudioTrack.Finish();
Smacker_Close(hSMK); Smacker_Close(hSMK);
animtex.Clean(); animtex.Clean();
} }