mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-02-02 13:51:52 +00:00
Make a common class to help with movie audio streams
This commit is contained in:
parent
d11e2ef1ac
commit
5e465a65e2
1 changed files with 121 additions and 83 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue