// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Components/SynthComponent.h" #include "Containers/Array.h" #include "HAL/CriticalSection.h" #include "MediaSampleQueue.h" #include "Misc/Timespan.h" #include "Templates/Atomic.h" #include "Templates/SharedPointer.h" #include "UObject/ObjectMacros.h" #include "UObject/ScriptMacros.h" #include "DSP/SpectrumAnalyzer.h" #include "DSP/BufferVectorOperations.h" #include "DSP/EnvelopeFollower.h" #include "Sound/SoundClass.h" #include "Sound/SoundGenerator.h" #include "MediaAudioResampler.h" #include "MediaSoundComponent.generated.h" class FMediaAudioResampler; class FMediaPlayerFacade; class FMediaSoundComponentClockSink; class IMediaAudioSample; class IMediaPlayer; class UMediaPlayer; /** * Available media sound channel types. */ UENUM() enum class EMediaSoundChannels { /** Mono (1 channel). */ Mono, /** Stereo (2 channels). */ Stereo, /** Surround sound (7.1 channels; for UI). */ Surround }; UENUM(BlueprintType) enum class EMediaSoundComponentFFTSize : uint8 { Min_64, Small_256, Medium_512, Large_1024, }; USTRUCT(BlueprintType) struct FMediaSoundComponentSpectralData { GENERATED_USTRUCT_BODY() // The frequency hz of the spectrum value UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpectralData") float FrequencyHz = 0.0f; // The magnitude of the spectrum at this frequency UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpectralData") float Magnitude = 0.0f; }; // Class implements an ISoundGenerator to feed decoded audio to audio renderering async tasks class FMediaSoundGenerator : public ISoundGenerator { public: struct FSoundGeneratorParams { int32 SampleRate = 0; int32 NumChannels = 0; TSharedPtr SampleQueue; uint32 PreviousSampleQueueFlushCount = 0; bool bSpectralAnalysisEnabled = false; bool bEnvelopeFollowingEnabled = false; int32 EnvelopeFollowerAttackTime = 0; int32 EnvelopeFollowerReleaseTime = 0; Audio::FSpectrumAnalyzerSettings SpectrumAnalyzerSettings; TArray FrequenciesToAnalyze; float CachedRate = 0.0f; FTimespan CachedTime; FTimespan LastPlaySampleTime; }; FMediaSoundGenerator(FSoundGeneratorParams& InParams); virtual ~FMediaSoundGenerator(); virtual void OnEndGenerate() override; virtual int32 OnGenerateAudio(float* OutAudio, int32 NumSamples) override; void SetCachedData(float InCachedRate, const FTimespan& InCachedTime); void SetLastPlaySampleTime(const FTimespan& InLastPlaySampleTime); void SetEnableSpectralAnalysis(bool bInSpectralAnlaysisEnabled); void SetEnableEnvelopeFollowing(bool bInEnvelopeFollowingEnabled); void SetSpectrumAnalyzerSettings(Audio::FSpectrumAnalyzerSettings::EFFTSize InFFTSize, const TArray& InFrequenciesToAnalyze); void SetEnvelopeFollowingSettings(int32 InAttackTimeMsec, int32 InReleaseTimeMsec); void SetSampleQueue(TSharedPtr& InSampleQueue); TArray GetSpectralData() const; TArray GetNormalizedSpectralData() const; float GetCurrentEnvelopeValue() const { return CurrentEnvelopeValue; } FTimespan GetLastPlayTime() const { return LastPlaySampleTime.Load(); } private: FSoundGeneratorParams Params; /** The audio resampler. */ FMediaAudioResampler Resampler; /** Scratch buffer to mix in source audio to from decoder */ Audio::AlignedFloatBuffer AudioScratchBuffer; /** Spectrum analyzer used for analyzing audio in media. */ mutable Audio::FAsyncSpectrumAnalyzer SpectrumAnalyzer; Audio::FEnvelopeFollower EnvelopeFollower; TAtomic CachedRate; TAtomic CachedTime; TAtomic LastPlaySampleTime; float CurrentEnvelopeValue = 0.0f; bool bEnvelopeFollowerSettingsChanged = false; mutable FCriticalSection AnalysisCritSect; mutable FCriticalSection SampleQueueCritSect; }; /** * Implements a sound component for playing a media player's audio output. */ UCLASS(ClassGroup=Media, editinlinenew, meta=(BlueprintSpawnableComponent), MinimalAPI) class UMediaSoundComponent : public USynthComponent { GENERATED_BODY() public: /** Media sound channel type. */ UPROPERTY(EditAnywhere, Category="Media") EMediaSoundChannels Channels; /** Dynamically adjust the sample rate if audio and media clock desynchronize. */ UPROPERTY(EditAnywhere, Category="Media", AdvancedDisplay) bool DynamicRateAdjustment; /** * Factor for calculating the sample rate adjustment. * * If dynamic rate adjustment is enabled, this number is multiplied with the drift * between the audio and media clock (in 100ns ticks) to determine the adjustment. * that is to be multiplied into the current playrate. */ UPROPERTY(EditAnywhere, Category="Media", AdvancedDisplay) float RateAdjustmentFactor; /** * The allowed range of dynamic rate adjustment. * * If dynamic rate adjustment is enabled, and the necessary adjustment * falls outside of this range, audio samples will be dropped. */ UPROPERTY(EditAnywhere, Category="Media", AdvancedDisplay) FFloatRange RateAdjustmentRange; public: /** * Create and initialize a new instance. * * @param ObjectInitializer Initialization parameters. */ MEDIAASSETS_API UMediaSoundComponent(const FObjectInitializer& ObjectInitializer); /** Virtual destructor. */ MEDIAASSETS_API ~UMediaSoundComponent(); public: /** * Get the attenuation settings based on the current component settings. * * @param OutAttenuationSettings Will contain the attenuation settings, if available. * @return true if attenuation settings were returned, false if attenuation is disabled. */ UFUNCTION(BlueprintCallable, Category="Media|MediaSoundComponent", meta=(DisplayName="Get Attenuation Settings To Apply", ScriptName="GetAttenuationSettingsToApply")) MEDIAASSETS_API bool BP_GetAttenuationSettingsToApply(FSoundAttenuationSettings& OutAttenuationSettings); /** * Get the media player that provides the audio samples. * * @return The component's media player, or nullptr if not set. * @see SetMediaPlayer */ UFUNCTION(BlueprintCallable, Category="Media|MediaSoundComponent") MEDIAASSETS_API UMediaPlayer* GetMediaPlayer() const; virtual USoundClass* GetSoundClass() override { if (SoundClass) { return SoundClass; } if (const UAudioSettings* AudioSettings = GetDefault()) { if (USoundClass* DefaultSoundClass = AudioSettings->GetDefaultMediaSoundClass()) { return DefaultSoundClass; } if (USoundClass* DefaultSoundClass = AudioSettings->GetDefaultSoundClass()) { return DefaultSoundClass; } } return nullptr; } /** * Set the media player that provides the audio samples. * * @param NewMediaPlayer The player to set. * @see GetMediaPlayer */ UFUNCTION(BlueprintCallable, Category="Media|MediaSoundComponent") MEDIAASSETS_API void SetMediaPlayer(UMediaPlayer* NewMediaPlayer); /** Turns on spectral analysis of the audio generated in the media sound component. */ UFUNCTION(BlueprintCallable, Category = "Media|MediaSoundComponent") MEDIAASSETS_API void SetEnableSpectralAnalysis(bool bInSpectralAnalysisEnabled); /** Sets the settings to use for spectral analysis. */ UFUNCTION(BlueprintCallable, Category = "Media|MediaSoundComponent") MEDIAASSETS_API void SetSpectralAnalysisSettings(TArray InFrequenciesToAnalyze, EMediaSoundComponentFFTSize InFFTSize = EMediaSoundComponentFFTSize::Medium_512); /** Retrieves the spectral data if spectral analysis is enabled. */ UFUNCTION(BlueprintCallable, Category = "TimeSynth") MEDIAASSETS_API TArray GetSpectralData(); /** Retrieves and normalizes the spectral data if spectral analysis is enabled. */ UFUNCTION(BlueprintCallable, Category = "TimeSynth") MEDIAASSETS_API TArray GetNormalizedSpectralData(); /** Turns on amplitude envelope following the audio in the media sound component. */ UFUNCTION(BlueprintCallable, Category = "Media|MediaSoundComponent") MEDIAASSETS_API void SetEnableEnvelopeFollowing(bool bInEnvelopeFollowing); /** Sets the envelope attack and release times (in ms). */ UFUNCTION(BlueprintCallable, Category = "Media|MediaSoundComponent") MEDIAASSETS_API void SetEnvelopeFollowingsettings(int32 AttackTimeMsec, int32 ReleaseTimeMsec); /** Retrieves the current amplitude envelope. */ UFUNCTION(BlueprintCallable, Category = "TimeSynth") MEDIAASSETS_API float GetEnvelopeValue() const; public: /** Adds a clock sink so this can be ticked without the world. */ MEDIAASSETS_API void AddClockSink(); /** Removes the clock sink. */ MEDIAASSETS_API void RemoveClockSink(); MEDIAASSETS_API void UpdatePlayer(); #if WITH_EDITOR /** * Set the component's default media player property. * * @param NewMediaPlayer The player to set. * @see SetMediaPlayer */ MEDIAASSETS_API void SetDefaultMediaPlayer(UMediaPlayer* NewMediaPlayer); #endif public: //~ TAttenuatedComponentVisualizer interface MEDIAASSETS_API void CollectAttenuationShapesForVisualization(TMultiMap& ShapeDetailsMap) const; public: //~ UActorComponent interface MEDIAASSETS_API virtual void OnRegister() override; MEDIAASSETS_API virtual void OnUnregister() override; MEDIAASSETS_API virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; public: //~ USceneComponent interface MEDIAASSETS_API virtual void Activate(bool bReset = false) override; MEDIAASSETS_API virtual void Deactivate() override; public: //~ UObject interface MEDIAASSETS_API virtual void PostLoad() override; #if WITH_EDITOR MEDIAASSETS_API virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endif protected: /** * Get the attenuation settings based on the current component settings. * * @return Attenuation settings, or nullptr if attenuation is disabled. */ MEDIAASSETS_API const FSoundAttenuationSettings* GetSelectedAttenuationSettings() const; protected: //~ USynthComponent interface MEDIAASSETS_API virtual bool Init(int32& SampleRate) override; MEDIAASSETS_API virtual ISoundGeneratorPtr CreateSoundGenerator(const FSoundGeneratorInitParams& InParams) override; protected: /** * The media player asset associated with this component. * * This property is meant for design-time convenience. To change the * associated media player at run-time, use the SetMediaPlayer method. * * @see SetMediaPlayer */ UPROPERTY(EditAnywhere, Category="Media") TObjectPtr MediaPlayer; private: /** The player's current play rate (cached for use on audio thread). */ float CachedRate; /** The player's current time (cached for use on audio thread). */ FTimespan CachedTime; /** Critical section for synchronizing access to PlayerFacadePtr. */ FCriticalSection CriticalSection; /** The player that is currently associated with this component. */ TWeakObjectPtr CurrentPlayer; /** The player facade that's currently providing texture samples. */ TWeakPtr CurrentPlayerFacade; /** Adjusts the output sample rate to synchronize audio and media clock. */ float RateAdjustment; /** Audio sample queue. */ TSharedPtr SampleQueue; /* Time of last sample played. */ FTimespan LastPlaySampleTime; /** Which frequencies to analyze. */ TArray FrequenciesToAnalyze; /** Spectrum analyzer used for analyzing audio in media. */ Audio::FSpectrumAnalyzerSettings SpectrumAnalyzerSettings; int32 EnvelopeFollowerAttackTime; int32 EnvelopeFollowerReleaseTime; /** Whether or not spectral analysis is enabled. */ bool bSpectralAnalysisEnabled; /** Whether or not envelope following is enabled. */ bool bEnvelopeFollowingEnabled; /** Holds our clock sink if available. */ TSharedPtr ClockSink; /** Instance of our media sound generator. This is a non-uobject that is used to feed sink audio to a sound source on the audio render thread (or async task). */ ISoundGeneratorPtr MediaSoundGenerator; };