Files
UnrealEngineUWP/Engine/Source/Runtime/AudioMixer/Private/Quartz/AudioMixerQuantizedCommands.cpp
maxwell hayes 6c451ce002 Converting Quartz audio frame counting to be double instead of integer - the latter causes clock drift over time and causes issue when synchronizing with external sources or long audio
#jira UE-147371
#rb Aaron.McLeran
#preflight 624600f5e434babd8af9bcc4

#ROBOMERGE-AUTHOR: maxwell.hayes
#ROBOMERGE-SOURCE: CL 19578173 via CL 19579759 via CL 19579771 via CL 19579784
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v938-19570697)

[CL 19580686 by maxwell hayes in ue5-main branch]
2022-03-31 19:27:04 -04:00

251 lines
7.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Quartz/AudioMixerQuantizedCommands.h"
#include "AudioMixerSourceManager.h"
namespace Audio
{
FQuantizedPlayCommand::FQuantizedPlayCommand()
{
}
TSharedPtr<IQuartzQuantizedCommand> FQuantizedPlayCommand::GetDeepCopyOfDerivedObject() const
{
TSharedPtr<FQuantizedPlayCommand> NewCopy = MakeShared<FQuantizedPlayCommand>();
NewCopy->OwningClockPtr = OwningClockPtr;
NewCopy->SourceID = SourceID;
return NewCopy;
}
void FQuantizedPlayCommand::OnQueuedCustom(const FQuartzQuantizedCommandInitInfo& InCommandInitInfo)
{
OwningClockPtr = InCommandInitInfo.OwningClockPointer;
SourceID = InCommandInitInfo.SourceID;
bIsCanceled = false;
// access source manager through owning clock (via clock manager)
FMixerSourceManager* SourceManager = OwningClockPtr->GetSourceManager();
if (SourceManager)
{
SourceManager->PauseSoundForQuantizationCommand(SourceID);
}
else
{
// cancel ourselves (no source manager may mean we are running without an audio device)
if (ensure(OwningClockPtr))
{
OwningClockPtr->CancelQuantizedCommand(TSharedPtr<IQuartzQuantizedCommand>(this));
}
}
}
// TODO: think about playback progress of a sound source
// TODO: AudioComponent "waiting to play" state (cancel-able)
void FQuantizedPlayCommand::OnFinalCallbackCustom(int32 InNumFramesLeft)
{
// Access source manager through owning clock (via clock manager)
check(OwningClockPtr && OwningClockPtr->GetSourceManager());
// This was canceled before the active sound hit the source manager.
// Calling CancelCustom() make sure we stop the associated sound.
if (bIsCanceled)
{
CancelCustom();
return;
}
// access source manager through owning clock (via clock manager)
// Owning Clock Ptr may be nullptr if this command was canceled.
if (OwningClockPtr)
{
FMixerSourceManager* SourceManager = OwningClockPtr->GetSourceManager();
if (SourceManager)
{
SourceManager->SetSubBufferDelayForSound(SourceID, InNumFramesLeft);
SourceManager->UnPauseSoundForQuantizationCommand(SourceID);
}
else
{
// cancel ourselves (no source manager may mean we are running without an audio device)
OwningClockPtr->CancelQuantizedCommand(TSharedPtr<IQuartzQuantizedCommand>(this));
}
}
}
void FQuantizedPlayCommand::CancelCustom()
{
bIsCanceled = true;
if (OwningClockPtr)
{
FMixerSourceManager* SourceManager = OwningClockPtr->GetSourceManager();
FMixerDevice* MixerDevice = OwningClockPtr->GetMixerDevice();
if (MixerDevice && SourceManager && MixerDevice->IsAudioRenderingThread())
{
// if we don't UnPause first, this function will be called by FMixerSourceManager::StopInternal()
SourceManager->UnPauseSoundForQuantizationCommand(SourceID); // (avoid infinite recursion)
SourceManager->CancelQuantizedSound(SourceID);
}
}
}
static const FName PlayCommandName("Play Command");
FName FQuantizedPlayCommand::GetCommandName() const
{
return PlayCommandName;
}
void FQuantizedQueueCommand::SetQueueCommand(const FAudioComponentCommandInfo& InAudioComponentData)
{
AudioComponentData = InAudioComponentData;
}
TSharedPtr<IQuartzQuantizedCommand> FQuantizedQueueCommand::GetDeepCopyOfDerivedObject() const
{
return MakeShared<FQuantizedQueueCommand>(*this);
}
void FQuantizedQueueCommand::OnQueuedCustom(const FQuartzQuantizedCommandInitInfo& InCommandInitInfo)
{
OwningClockPtr = InCommandInitInfo.OwningClockPointer;
}
int32 FQuantizedQueueCommand::OverrideFramesUntilExec(int32 NumFramesUntilExec)
{
// Calculate the amount of time before taking up a voice slot
int32 NumFramesBeforeVoiceSlot = NumFramesUntilExec - static_cast<int32>(OwningClockPtr->GetTickRate().GetFramesPerDuration(AudioComponentData.AnticapatoryBoundary.Quantization));
//If NumFramesBeforeVoiceSlot is less than 0, change the boundary back to the original, and mark this command as having 0 frames till exec
if (NumFramesBeforeVoiceSlot < 0)
{
return 0;
}
return NumFramesBeforeVoiceSlot;
}
void FQuantizedQueueCommand::OnFinalCallbackCustom(int32 InNumFramesLeft)
{
if (OwningClockPtr && AudioComponentData.ComponentCommandPtr.IsValid())
{
FName ClockName = OwningClockPtr->GetName();
Audio::FQuartzQueueCommandData CommandData(AudioComponentData, ClockName);
AudioComponentData.ComponentCommandPtr->PushEvent(CommandData);
}
}
static const FName QueueCommandName("Queue Command");
FName FQuantizedQueueCommand::GetCommandName() const
{
return QueueCommandName;
}
TSharedPtr<IQuartzQuantizedCommand> FQuantizedTickRateChange::GetDeepCopyOfDerivedObject() const
{
TSharedPtr<FQuantizedTickRateChange> NewCopy = MakeShared<FQuantizedTickRateChange>();
NewCopy->OwningClockPtr = OwningClockPtr;
NewCopy->TickRate = TickRate;
return NewCopy;
}
void FQuantizedTickRateChange::OnQueuedCustom(const FQuartzQuantizedCommandInitInfo& InCommandInitInfo)
{
OwningClockPtr = InCommandInitInfo.OwningClockPointer;
}
void FQuantizedTickRateChange::OnFinalCallbackCustom(int32 InNumFramesLeft)
{
OwningClockPtr->ChangeTickRate(TickRate, InNumFramesLeft);
}
static const FName TickRateChangeCommandName("Tick Rate Change Command");
FName FQuantizedTickRateChange::GetCommandName() const
{
return TickRateChangeCommandName;
}
TSharedPtr<IQuartzQuantizedCommand> FQuantizedTransportReset::GetDeepCopyOfDerivedObject() const
{
TSharedPtr<FQuantizedTransportReset> NewCopy = MakeShared<FQuantizedTransportReset>();
NewCopy->OwningClockPtr = OwningClockPtr;
return NewCopy;
}
void FQuantizedTransportReset::OnQueuedCustom(const FQuartzQuantizedCommandInitInfo& InCommandInitInfo)
{
OwningClockPtr = InCommandInitInfo.OwningClockPointer;
}
void FQuantizedTransportReset::OnFinalCallbackCustom(int32 InNumFramesLeft)
{
OwningClockPtr->ResetTransport();
}
static const FName TransportResetCommandName("Transport Reset Command");
FName FQuantizedTransportReset::GetCommandName() const
{
return TransportResetCommandName;
}
TSharedPtr<IQuartzQuantizedCommand> FQuantizedOtherClockStart::GetDeepCopyOfDerivedObject() const
{
TSharedPtr<FQuantizedOtherClockStart> NewCopy = MakeShared<FQuantizedOtherClockStart>();
NewCopy->OwningClockPtr = OwningClockPtr;
NewCopy->NameOfClockToStart = NameOfClockToStart;
return NewCopy;
}
void FQuantizedOtherClockStart::OnQueuedCustom(const FQuartzQuantizedCommandInitInfo& InCommandInitInfo)
{
OwningClockPtr = InCommandInitInfo.OwningClockPointer;
check(OwningClockPtr.IsValid());
NameOfClockToStart = InCommandInitInfo.OtherClockName;
}
void FQuantizedOtherClockStart::OnFinalCallbackCustom(int32 InNumFramesLeft)
{
if (!ensureMsgf(OwningClockPtr.IsValid(), TEXT("Quantized Other Clock Start is early exiting (invalid/missing Owning Clock Pointer)")))
{
return;
}
// get access to the clock manager
FQuartzClockManager* ClockManager = OwningClockPtr->GetClockManager();
bool bShouldStart = ClockManager && !ClockManager->IsClockRunning(NameOfClockToStart);
if (bShouldStart)
{
// ...start the clock
ClockManager->ResumeClock(NameOfClockToStart, InNumFramesLeft);
if (ClockManager->HasClockBeenTickedThisUpdate(NameOfClockToStart))
{
ClockManager->UpdateClock(NameOfClockToStart, ClockManager->GetLastUpdateSizeInFrames());
}
}
}
static const FName StartOtherClockName("Start Other Clock Command");
FName FQuantizedOtherClockStart::GetCommandName() const
{
return StartOtherClockName;
}
} // namespace Audio