// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "AudioDevice.h" #include "Sound/SoundWave.h" #include "DSP/SinOsc.h" #include "DSP/ParamInterpolator.h" #include "Misc/ScopeLock.h" class FAudioDevice; #define AUDIO_SOURCE_DECODER_DEBUG 0 namespace Audio { class FMixerBuffer; class FMixerSourceBuffer; struct FDecodingSoundSourceHandle { FDecodingSoundSourceHandle() : Id(INDEX_NONE) {} int32 Id; FName SoundWaveName; }; struct FSourceDecodeInit { FSourceDecodeInit() : SoundWave(nullptr) , SeekTime(0.0f) , PitchScale(1.0f) , VolumeScale(1.0f) , bForceSyncDecode(false) {} TObjectPtr SoundWave; float SeekTime; float PitchScale; float VolumeScale; bool bForceSyncDecode; FDecodingSoundSourceHandle Handle; }; class FDecodingSoundSource { public: FDecodingSoundSource(FAudioDevice* AudioDevice, const FSourceDecodeInit& InitData); ~FDecodingSoundSource(); // Called before we initialize bool PreInit(int32 OutputSampleRate); // Queries if we're ready to initialize bool IsReadyToInit(); // Initializes the decoding source void Init(); // Returns if we've been initialized bool IsInitialized() const { return bInitialized; } // If the sound source finished playing all its source. Will only return true for non-looping sources. bool IsFinished() const { return !bInitialized || SourceInfo.bIsLastBuffer; } // Sets the pitch scale void SetPitchScale(float InPitchScale, uint32 NumFrames = 512); // Sets the volume scale void SetVolumeScale(float InVolumeScale, uint32 NumFrames = 512); // Sets the ForceSyncDecode flag. (Decodes for this soundwave will not happen in an async task if true) void SetForceSyncDecode(bool bShouldForceSyncDecode); // Get audio buffer bool GetAudioBuffer(const int32 InNumFrames, const int32 InNumChannels, FAlignedFloatBuffer& OutAudioBuffer); // Return the underlying sound wave USoundWave* GetSoundWave() { return SoundWave; } TObjectPtr& GetSoundWavePtr() { return SoundWave; } private: void ReadFrame(); void GetAudioBufferInternal(const int32 InNumFrames, const int32 InNumChannels, FAlignedFloatBuffer& OutAudioBuffer); // Handle to the decoding source FDecodingSoundSourceHandle Handle; FDeviceId AudioDeviceID; // The sound wave object with which this sound is generating TObjectPtr SoundWave; // Mixer buffer object which is a convenience wrapper around some buffer initialization and management FMixerBuffer* MixerBuffer; // Critical Section around mixer source buffer access FCriticalSection MixerSourceBufferCritSec; // Object which handles bulk of decoding operations TSharedPtr MixerSourceBuffer; // Scratch buffer used for upmixing and downmixing the audio FAlignedFloatBuffer ScratchBuffer; // Sample rate of the source int32 SampleRate; // Current seek time float SeekTime; // If we've initialized FThreadSafeBool bInitialized; // force the decoding of this sound to be synchronous bool bForceSyncDecode{false}; // Object used for source generation from decoded buffers struct FSourceInfo { // Number of channels of source file int32 NumSourceChannels; // Total number of frames of source file uint32 TotalNumFrames; // Total number of frames played (or read from decoded buffers) so far. Will always be less than TotalNumFrames uint32 NumFramesRead; // Total number of frames generated (could be larger or smaller than number of frames read) uint32 NumFramesGenerated; // The current frame alpha (how far we are between current and next frame) float CurrentFrameAlpha; // The current frame index int32 CurrentFrameIndex; // Number of frames of current decoded chunk int32 CurrentAudioChunkNumFrames; // The pitch scale to use to account for sample rate differences of source to output sample rate float BasePitchScale; float PitchScale; // The pitch param object, allows easy pitch interpolation FParam PitchParam; // The frame count (from frames generated) to reset the pitch param uint32 PitchResetFrame; // The volume param object, allows easy volume interpolation FParam VolumeParam; // The frame count (from frames generated) to reset the volume param uint32 VolumeResetFrame; // Buffer to store current decoded audio frame TArray CurrentFrameValues; // Buffer to store next decoded audio frame TArray NextFrameValues; // The current decoded PCM buffer we are reading from TSharedPtr CurrentPCMBuffer; // If this sound is done (has decoded all data) bool bIsDone; // If this sound hasn't yet started rendering audio bool bHasStarted; // If this is the last decoded buffer bool bIsLastBuffer; FSourceInfo() : NumSourceChannels(0) , TotalNumFrames(0) , NumFramesRead(0) , NumFramesGenerated(0) , CurrentFrameAlpha(0.0f) , CurrentFrameIndex(0) , CurrentAudioChunkNumFrames(0) , BasePitchScale(1.0f) , PitchScale(1.0f) , PitchResetFrame(0) , VolumeResetFrame(0) , bIsDone(false) , bHasStarted(false) , bIsLastBuffer(false) {} }; FSourceInfo SourceInfo; #if AUDIO_SOURCE_DECODER_DEBUG FSineOsc SineTone[2]; #endif }; typedef TSharedPtr FDecodingSoundSourcePtr; class FSoundSourceDecoder : public FGCObject { public: AUDIOMIXER_API FSoundSourceDecoder(); AUDIOMIXER_API virtual ~FSoundSourceDecoder(); //~ Begin FGCObject AUDIOMIXER_API virtual void AddReferencedObjects(FReferenceCollector & Collector) override; virtual FString GetReferencerName() const override { return TEXT("Audio::FSoundSourceDecoder"); } //~ End FGCObject // Initialize the source decoder at the given output sample rate // Sources will automatically sample rate convert to match this output AUDIOMIXER_API void Init(FAudioDevice* InAudioDevice, int32 SampleRate); // Creates a new decoding sound source handle AUDIOMIXER_API FDecodingSoundSourceHandle CreateSourceHandle(USoundWave* InSoundWave); // Called from the audio thread AUDIOMIXER_API void Update(); // Called from the audio render thread AUDIOMIXER_API void UpdateRenderThread(); // Initialize a decoding instance of this sound wave object. Call only from game thread. AUDIOMIXER_API bool InitDecodingSource(const FSourceDecodeInit& InitData); // Removes the decoding source from the decoder AUDIOMIXER_API void RemoveDecodingSource(const FDecodingSoundSourceHandle& Handle); // Resets internal state of decoder AUDIOMIXER_API void Reset(); // Sets the source pitch scale AUDIOMIXER_API void SetSourcePitchScale(const FDecodingSoundSourceHandle& Handle, float InPitchScale); // Sets the source volume scale AUDIOMIXER_API void SetSourceVolumeScale(const FDecodingSoundSourceHandle& Handle, float InVolumeScale); // Get a decoded buffer for the given decoding sound wave handle. Call only from audio render thread or audio render thread task. AUDIOMIXER_API bool GetSourceBuffer(const FDecodingSoundSourceHandle& InHandle, const int32 NumOutFrames, const int32 NumOutChannels, FAlignedFloatBuffer& OutAudioBuffer); // Queries if the decoding source is finished AUDIOMIXER_API bool IsFinished(const FDecodingSoundSourceHandle& InHandle) const; AUDIOMIXER_API bool IsInitialized(const FDecodingSoundSourceHandle& InHandle) const; private: // Sends a command to the audio render thread from audio thread AUDIOMIXER_API void EnqueueDecoderCommand(TFunction Command); AUDIOMIXER_API void PumpDecoderCommandQueue(); AUDIOMIXER_API bool InitDecodingSourceInternal(const FSourceDecodeInit& InitData); int32 AudioThreadId; FAudioDevice* AudioDevice; int32 SampleRate; mutable FCriticalSection DecodingSourcesCritSec; TMap InitializingDecodingSources; TMap DecodingSources; TMap PrecachingSources; TQueue> CommandQueue; }; }