gtreshchev / RuntimeAudioImporter

Runtime Audio Importer plugin for Unreal Engine. Importing audio of various formats at runtime.
MIT License
308 stars 67 forks source link

Memory Leak issue [ UE 5.0.2 ] #23

Closed Dark583 closed 2 years ago

Dark583 commented 2 years ago

Made a video to showcase a memory leak type issue. This is a huge issue for us to keep using the plugin... Any help is greatly appreciated . Basically Sounds aren't being cleared from the RAM and every new song you load increase the RAM by crazy amounts...See end of video for an example of the problem.

https://youtu.be/NAP7CAyo8UI

gtreshchev commented 2 years ago

The video seems to have been cropped so I can't see much, but I seem to understand your problem. I recently did a memory refactoring that was supposed to fix all possible bugs with its allocation and cleanup and after that I didn't notice memory leaks, but I could miss something. I'll look into it when I have time.

In any case, feel free to add any additional data or make a pull request if you know something.

Caffiendish commented 2 years ago

https://github.com/EpicGames/UnrealEngine/blob/5.0/Engine/Source/Runtime/Engine/Classes/Kismet/GameplayStatics.h#L701

~~"Stop when destroyed" is related to the audio component, and it stops playing audio when what the audio component is attached to, is destroyed. It's not "destroy when stopped".~~

~~As for why the audio is never cleaned up, you're right, it IS never destroyed. It won't be, since you're feeding the audio to an audio component, which is attached to your player pawn, which isn't destroyed. So your audio component isn't consumed by the unending hunger of the garbage collection, which means it doesn't throw away the audio it has.~~

Then, every time you press =, you make a new audio component, import the track again, which starts the process all over.

Here's some more information on the garbage collection

I had a quick look at the code though, everything I've seen either gets put into an object that will automatically get cleaned on destruction, or is explicitly cleaned up.

tl;dr: Working as intended.

Edit: Wow, I'm clearly the kind of person who would struggle with slight of hand tricks: that circling of "Stop when Attached to Destroyed" absolutely got me, didn't even notice "Auto Destroy".

Caffiendish commented 2 years ago

I loaded 100 instances of an MP3 by itself, and whilst UE5 got REALLY fat (>16gb) for a minute or so, eventually GC came by and brought me down to a normal amount of ram usage (sub 2gb).

Amusingly, I didn't save a reference to my importer, and tried to push to 400 instances of an MP3, which got my importer thrown away, mid operation! 😅 Upon saving my reference, I got 400 imports, and they're in the process of being tossed into the fiery pits of mount GC: image

Back to sub 1gb ram usage, from >20gb.

Seems like it's working normally?

I'd verify that your AudioComponent is actually getting destroyed, @Dark583. Bear in mind that GC can take a little while to start, if you're not reaching critical mass on ram usage.

Dark583 commented 2 years ago

See in my examples i waited 2-5 minutes and the ram just stayed where it was. I do infact get that warning that the audio is being unloaded but! There seems to be a fatal issue out of the bug that if you run a memory report on audio via the console command it shows all the imported audio still there i had to manually call

GetCurSoundInfo().SpeakerTrack->ConditionalBeginDestroy(); to foricefully remove it from the audio memory report. How i generated a memory report -au.debug.audiomemreport : shows all the currently loaded audio

I will record it for you guys now give me 10 mins

Dark583 commented 2 years ago

Here is the issue . It never clears and the GC refuses to do anything in a test project or our project. I did test it with play sound at location ill test that again in my test project but im pretty sure it didn't clear either. Will verify... Attached the actual plugin files im using from the latest version available on the Unreal Games Launcher .

There was a typo in the video SoundClass not soundcue https://youtu.be/uFA9udCBGS8 RuntimeAudioImporter.zip A9udCBGS8

I even tried deleting the AmbientSound everytime a new sound is played as seen in the video above ^. Regardless though it occurs even if its the same track or a new track/song is played. To be clear i can see the ambient sound disappearing and being created again with this method to see if it works everytime a new file is played.


void ASpeakerSystem::PlaySound(FSpeakerData InSound)
{
    //ImportAudioFromFile
    if (InSound.SpeakerTrack == nullptr)
    {
        if (!EnsureGameInstanceRef())
        {
            UE_LOG(LogTemp, Error, TEXT("%s InSound.SpeakerTrack == nullptr"), *GetName());
            return;
        }
        SetCurSoundInfo(InSound);
        GameInstanceRef->CurrentRunTimeAudio->ImportAudioFromFile(InSound.TrackName, EAudioFormat::Auto);
        GameInstanceRef->CurrentRunTimeAudio->OnResult.AddUniqueDynamic(this, &ASpeakerSystem::TrackLoaded);
    }
    else
    {
        if (CurrentAudioComponent != nullptr)
        {
            BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, false);
            //GetCurSoundInfo().SpeakerTrack->ConditionalBeginDestroy();
            CurrentAudioComponent->Destroy(true, true);
        }
        SetCurSoundInfo(InSound);
        GetCurSoundInfo().SpeakerTrack->RewindPlaybackTime(0.f);
        FTransform SpawnTM(GetActorRotation(), GetActorLocation());
        CurrentAudioComponent = Cast<AAmbientSound>(UGameplayStatics::BeginDeferredActorSpawnFromClass(GetWorld(), AAmbientSound::StaticClass(), SpawnTM));
        CurrentAudioComponent->GetAudioComponent()->bOverrideSubtitlePriority = false;
        CurrentAudioComponent->GetAudioComponent()->bIsUISound = false;
        CurrentAudioComponent->GetAudioComponent()->bOverridePriority = true;
        CurrentAudioComponent->GetAudioComponent()->bSuppressSubtitles = true;

        CurrentAudioComponent->GetAudioComponent()->bAllowSpatialization = true;

        CurrentAudioComponent->GetAudioComponent()->AttenuationSettings = AttenuationSettings;
        CurrentAudioComponent->GetAudioComponent()->bAutoManageAttachment = false;
        CurrentAudioComponent->GetAudioComponent()->AutoAttachLocationRule = EAttachmentRule::SnapToTarget;
        CurrentAudioComponent->GetAudioComponent()->AutoAttachRotationRule = EAttachmentRule::SnapToTarget;
        CurrentAudioComponent->GetAudioComponent()->AutoAttachScaleRule = EAttachmentRule::SnapToTarget;
        CurrentAudioComponent->GetAudioComponent()->SoundClassOverride = SoundClass;
        CurrentAudioComponent->GetAudioComponent()->SetSound(GetCurSoundInfo().SpeakerTrack);
        CurrentAudioComponent->GetAudioComponent()->bAutoDestroy = true;

        UGameplayStatics::FinishSpawningActor(CurrentAudioComponent, SpawnTM);

        CurrentAudioComponent->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale, FName(""));

        if (!bPlayingMusic)
        {
            bPlayingMusic = true;
            SetSpeakerActive(bPlayingMusic && !GetPaused());
        }
        BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, true);
    }
}

void ASpeakerSystem::TrackLoaded(class URuntimeAudioImporterLibrary* RuntimeAudioImporterObjectRef, UImportedSoundWave* SoundWaveRef, ETranscodingStatus Status)
{
    if (SoundWaveRef != nullptr)
    {
        if (Status == ETranscodingStatus::AudioDoesNotExist)
        {

        }
        else if (Status == ETranscodingStatus::FailedToReadAudioDataArray)
        {

        }
        else if (Status == ETranscodingStatus::InvalidAudioFormat)
        {

        }
        else if (Status == ETranscodingStatus::LoadFileToArrayError)
        {

        }
        else if (Status == ETranscodingStatus::SoundWaveDeclarationError)
        {

        }
        else if (Status == ETranscodingStatus::SuccessfulImport)
        {
            CurSoundInfo.SpeakerTrack = SoundWaveRef;
            SetCurSoundInfo(CurSoundInfo);
            PlaySound(CurSoundInfo);
        }
    }
}

void ASpeakerSystem::BindTrackDelegates(UImportedSoundWave* Track, bool Bind)
{
    if (Track == nullptr)
    {
        return;
    }

    if (Bind)
    {
        if (!Track->OnAudioPlaybackFinished.Contains(this, "CurrentTrackFinished"))
        {
            Track->OnAudioPlaybackFinished.AddDynamic(this, &ASpeakerSystem::CurrentTrackFinished);
        }
    }
    else
    {
        if (Track->OnAudioPlaybackFinished.Contains(this, "CurrentTrackFinished"))
        {
            Track->OnAudioPlaybackFinished.RemoveDynamic(this, &ASpeakerSystem::CurrentTrackFinished);
        }
    }
}

void ASpeakerSystem::CurrentTrackFinished()
{
    if ((!bPlayingMusic) || GetCurSoundInfo().TrackName == "NULL")
    {
        return;
    }

    if (DelayBetweenTrackSwitch > 0.f)
    {
        GetWorldTimerManager().SetTimer(SelectNextTrackTimer, this, 
            &ASpeakerSystem::SelectNextTrack, DelayBetweenTrackSwitch, false);
        return;
    }

    SelectNextTrack();
} 
gtreshchev commented 2 years ago

I have tried different formats and ways of releasing audio data, but was unable to reproduce your bug and everything worked as intended.

You can help me figure out your problem if you try the following:

Caffiendish commented 2 years ago

Ah, I didn't try playing the audio files.

The issue seems to be that the audio components themselves are never getting destroyed, my quick PlaySound2D tests led to the destructor for the imported MP3 never getting called.

I don't get a reference from that, so I made some AudioComponents, and attached them to root, as you did, and whilst playback stops, the AudioComponent never changes IsPlaying status to false, so your AutoDestroy is never called.

The audio file definitely cleans up properly if it's ever asked to, though.

Edit: Sorry, I would have posted this a lot earlier, but I've fallen down the rabbit hole of "Okay, but HOW does the AudioComponent know when the audio has finished..?". Do not recommend it, real bad time.

Dark583 commented 2 years ago

Should i still test PlaySound2D if @Caffiendish is saying it doesn't work for destroying them @gtreshchev . How do i properly play music with this plugin if i cant use it with audio components how can i utilize sound classes and attenuation + attach them to physical objects in the world?

Caffiendish commented 2 years ago

No UE component is going to work, because there's some absolute shenanigans regarding how the audio components are told that the audio has stopped playing. I'm still trying to work out exactly how it works, but it's pretty convoluted. @gtreshchev This is maybe one to pose directly to the UE team, or more knowledgeable people on the sub reddit/Forums.

Workaround: You CAN bind to the "OnAudioPlaybackFinished" and "OnAudioPlaybackFinishedNative" events, and manually destroy everything though. You should only need to destroy your sound component directly, but you should definitely check the log to see if the imported sound gets GC'd.

Edit: To be clear, you can use the UE components, they'll work, but auto destroying isn't going to happen; the basic UE audio formats do SOMETHING to notify the container/sound device, but it's not clear (to me, at least) what they're doing, to replicate it in the imported audio class.

Caffiendish commented 2 years ago

No, it's not complex at all, it's because I'm an idiot, and started at the most complex end of this; the AudioComponent, then never looked for an FActiveSound reference in the base classes. 🤦

You have to set the ActiveSound.bFinished to true, which requires overriding some methods.

Dark583 commented 2 years ago

@Caffiendish Why doesn't this line fix the problem with the Destroy to the Ambient Sound or am i misunderstanding you?

if (CurrentAudioComponent != nullptr) { BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, false); //GetCurSoundInfo().SpeakerTrack->ConditionalBeginDestroy(); CurrentAudioComponent->Destroy(true, true); }

Dark583 commented 2 years ago

Will that completely destory the reference and allow it to be GCed to restore the RAM?

Caffiendish commented 2 years ago

Will that completely destory the reference and allow it to be GCed to restore the RAM?

Ah, no, the audio asset is only referenced by a pointer, so the actual asset is stored elsewhere, the AudioComponent won't mark it for GC.

This should be resolvable now that it's clear that the ActiveSound.bFinished handles all of this for us, I'm going to try and work out a solution for it shortly.

Dark583 commented 2 years ago

Thank you! Do let me know when a fix is up so i can help test it!

Caffiendish commented 2 years ago

I've gotten the audio component to recognise that it's no longer playing audio, and so it gets collected, but the ImportedSoundWave passed to it doesn't seem to be destroyed. 😕

Dark583 commented 2 years ago

Is there something in SoundWaveProcedural we can use? That is so annoying! Question In combination with ConditionalBeginDestroy destory on the ImportedSoundWave does that help? In the mem report for audio it did delete it from that list. In combination with your change do they have an effect?

gtreshchev commented 2 years ago

I'll try to figure it out soon, please wait

Caffiendish commented 2 years ago

I came to the conclusion that the SoundWaveProcedural class isn't a good fit to inherit from, since we're not actually generating sound, we're setting a buffer, and forgetting it. That's pretty much just a normal SoundWave. Not only that, but the ImportSoundWave can only be played by a single audio source, it doesn't easily clean up...

Definitely feels like we should be using SoundWave as the base class. I've been poking around the engine, and the internet, and I think that might be doable. I was going to try it out before mentioning it, but I didn't realise @gtreshchev was working on this too, so I thought I'd mention it, so as to not waste your time on a problem that might not need solving.

Caffiendish commented 2 years ago

https://github.com/Geromatic/Midi-Unreal is worth a look, they made a procedural class based on SoundWave, but I think, as long as we override the Serialize method in the same way, and put data into the correct buffer (all the buffers seem to be public as well), it looks like it might be possible with minimal change?

gtreshchev commented 2 years ago

@Caffiendish Regardless of which class to inherit from, in order for the engine to call GeneratePCMData, it must be designated as procedural. Otherwise it will try to use engine buffers which do not have a single buffer and in general their behavior is not well defined for dynamic sound wave loading. Btw, this also has the same problems with cleaning audio data as in a procedural one. However, you can use CompressSoundWave if it is important for you to fill these buffers.

gtreshchev commented 2 years ago

@Dark583 My GC references parsing didn't show anything specific. After playing the sound wave, this object is referenced by UUnrealEdEngine in the editor and by UGameEngine in the game. But how it is added there, I have not yet figured out.

For now, I recommend clearing the audio data via the ReleaseMemory function after the sound wave is not needed, but this will definitely need to be resolved completely. I don't have much time, unfortunately, but I will gradually investigate it.

Caffiendish commented 2 years ago

Perhaps I'm unclear on how to use the CompressSoundWave functionality, since I get an exception with UE5, when trying to play the resulting SoundWave:

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000004

UnrealEditor_BinkAudioDecoder!FBinkAudioInfo::Decode() [D:\build++UE5\Sync\Engine\Source\Runtime\BinkAudioDecoder\Module\Private\BinkAudioInfo.cpp:439] UnrealEditor_Engine!IStreamedCompressedInfo::ReadCompressedData() [D:\build++UE5\Sync\Engine\Source\Runtime\Engine\Private\AudioDecompress.cpp:86] UnrealEditor_AudioMixer!Audio::FAsyncDecodeWorker::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\AudioMixer\Private\AudioMixerSourceDecode.cpp:162] UnrealEditor_AudioMixer!FAsyncTask::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:304] UnrealEditor_AudioMixer!FAsyncTask::DoThreadedWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:328] UnrealEditor_Core!FQueuedThread::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\HAL\ThreadingBase.cpp:1313] UnrealEditor_Core!FRunnableThreadWin::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\Windows\WindowsRunnableThread.cpp:146]

gtreshchev commented 2 years ago

Are you filling any buffers like Compressed for example?

Caffiendish commented 2 years ago

That was all three, actually.

Here's them singularly: Compressed:

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000004

UnrealEditor_BinkAudioDecoder!FBinkAudioInfo::Decode() [D:\build++UE5\Sync\Engine\Source\Runtime\BinkAudioDecoder\Module\Private\BinkAudioInfo.cpp:439] UnrealEditor_Engine!IStreamedCompressedInfo::ReadCompressedData() [D:\build++UE5\Sync\Engine\Source\Runtime\Engine\Private\AudioDecompress.cpp:86] UnrealEditor_AudioMixer!Audio::FAsyncDecodeWorker::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\AudioMixer\Private\AudioMixerSourceDecode.cpp:162] UnrealEditor_AudioMixer!FAsyncTask::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:304] UnrealEditor_AudioMixer!FAsyncTask::DoThreadedWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:328] UnrealEditor_Core!FQueuedThread::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\HAL\ThreadingBase.cpp:1313] UnrealEditor_Core!FRunnableThreadWin::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\Windows\WindowsRunnableThread.cpp:146]

PCM Buffer:

Unhandled Exception: EXCEPTION_STACK_OVERFLOW

UnrealEditor_BinkAudioDecoder!__chkstk() [d:\a01_work\6\s\src\vctools\crt\vcstartup\src\misc\amd64\chkstk.asm:109] UnrealEditor_BinkAudioDecoder!FBinkAudioInfo::Decode() [D:\build++UE5\Sync\Engine\Source\Runtime\BinkAudioDecoder\Module\Private\BinkAudioInfo.cpp:431] UnrealEditor_Engine!IStreamedCompressedInfo::ReadCompressedData() [D:\build++UE5\Sync\Engine\Source\Runtime\Engine\Private\AudioDecompress.cpp:86] UnrealEditor_AudioMixer!Audio::FAsyncDecodeWorker::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\AudioMixer\Private\AudioMixerSourceDecode.cpp:162] UnrealEditor_AudioMixer!FAsyncTask::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:304] UnrealEditor_AudioMixer!FAsyncTask::DoThreadedWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:328] UnrealEditor_Core!FQueuedThread::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\HAL\ThreadingBase.cpp:1313] UnrealEditor_Core!FRunnableThreadWin::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\Windows\WindowsRunnableThread.cpp:146]

RawWave Buffer:

Unhandled Exception: EXCEPTION_STACK_OVERFLOW

UnrealEditor_BinkAudioDecoder!__chkstk() [d:\a01_work\6\s\src\vctools\crt\vcstartup\src\misc\amd64\chkstk.asm:109] UnrealEditor_BinkAudioDecoder!FBinkAudioInfo::Decode() [D:\build++UE5\Sync\Engine\Source\Runtime\BinkAudioDecoder\Module\Private\BinkAudioInfo.cpp:431] UnrealEditor_Engine!IStreamedCompressedInfo::ReadCompressedData() [D:\build++UE5\Sync\Engine\Source\Runtime\Engine\Private\AudioDecompress.cpp:86] UnrealEditor_AudioMixer!Audio::FAsyncDecodeWorker::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\AudioMixer\Private\AudioMixerSourceDecode.cpp:162] UnrealEditor_AudioMixer!FAsyncTask::DoWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:304] UnrealEditor_AudioMixer!FAsyncTask::DoThreadedWork() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:328] UnrealEditor_Core!FQueuedThread::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\HAL\ThreadingBase.cpp:1313] UnrealEditor_Core!FRunnableThreadWin::Run() [D:\build++UE5\Sync\Engine\Source\Runtime\Core\Private\Windows\WindowsRunnableThread.cpp:146]

Caffiendish commented 2 years ago

Probably related to this though: https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Engine/Private/SoundWave.cpp#L591

Caffiendish commented 2 years ago

If you change line 84 of RuntimeAudioCompressor.cpp to FAudioThread::RunCommandOnAudioThread([this,ImportedSoundWaveRef, RegularSoundWaveRef, Quality, bFillPCMBuffer, bFillRAWWaveBuffer, bFillCompressedBuffer, CompressedSoundWaveInfo]() and add in RegularSoundWaveRef->SetSoundAssetCompressionType(ESoundAssetCompressionType::PCM);

Audio plays with the PCM Buffer (not tried any of the others yet), but this isn't much of a solution, since we can only set that in the editor: https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Engine/Private/SoundWave.cpp#L928 (And no, it definitely doesn't work without that being set, I've checked)

gtreshchev commented 2 years ago

@Caffiendish It doesn't work in version 5.0.2 either, last time I tested it on version 5.0.0 and it worked. This is pretty much dependent on what code the epics are changing, so sound wave compression is not stable.

Caffiendish commented 2 years ago

I'm using 5.0.2 as well, perhaps an issue needs to be raised on the UE github issue tracker about all these issues, the fact that procedural sounds never go away, the privatisation of all the useful methods/properties, etc?

Dark583 commented 2 years ago

@Dark583 My GC references parsing didn't show anything specific. After playing the sound wave, this object is referenced by UUnrealEdEngine in the editor and by UGameEngine in the game. But how it is added there, I have not yet figured out.

For now, I recommend clearing the audio data via the ReleaseMemory function after the sound wave is not needed, but this will definitely need to be resolved completely. I don't have much time, unfortunately, but I will gradually investigate it.

Whenever i call ReleaseMemory my engine crashes. How do i properly set the stage for a call? Do i need to call Stop and destroy the actor that was playing it?

I used to call it here

if (CurrentAudioComponent != nullptr) { BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, false); //GetCurSoundInfo().SpeakerTrack->ReleaseMemory(); CurrentAudioComponent->Destroy(true, true); }

Which resulted in a crash after playing a few music tracks

Caffiendish commented 2 years ago

You'll need to stop the audio component I think, otherwise the engine will keep calling the Parse and GeneratePCM methods. I'd destroy the audio component first, then call ReleaseMemory.

Dark583 commented 2 years ago

Does there need to be time between the calls? Or i can do them right after each other?

Caffiendish commented 2 years ago

Does there need to be time between the calls? Or i can do them right after each other?

I'd do them one after the other, you're just looking to stop/destroy the audio component, then destroy the track. Maybe call stop, call destroy on the component (with a check for validity), then release memory?

FWIW, you should only need to stop the audio component if you want to keep it, then destroy the audio track.

Dark583 commented 2 years ago

Just got this update from the Launcher what does the new change for ReleaseMemory do?

image image

gtreshchev commented 2 years ago

I'm using 5.0.2 as well, perhaps an issue needs to be raised on the UE ~github~ issue tracker about all these issues, the fact that procedural sounds never go away, the privatisation of all the useful methods/properties, etc?

We may create an issue on the issue tracker, but it may take them some time for them to investigate and fix it

gtreshchev commented 2 years ago

Just got this update from the Launcher what does the new change for ReleaseMemory do?

image image

This is a small fix to make sure buffer clearing only happens once. For now, this is a temporary solution (as is the need to use ReleaseMemory instead of GC in general). At least to be able to clean the sound wave manually correctly.

gtreshchev commented 2 years ago

@Dark583 Yes, exactly as suggested by @Caffiendish should be done. Just to make sure the sound wave isn't used anywhere else.

Dark583 commented 2 years ago

Here is the error i get attempting ReleaseMemory image image


void ASpeakerSystem::PlaySound(FSpeakerData InSound)
{
    //ImportAudioFromFile
    if (InSound.SpeakerTrack == nullptr)
    {
        if (!EnsureGameInstanceRef())
        {
            UE_LOG(LogTemp, Error, TEXT("%s InSound.SpeakerTrack == nullptr"), *GetName());
            return;
        }
        SetCurSoundInfo(InSound);
        GameInstanceRef->CurrentRunTimeAudio->ImportAudioFromFile(InSound.TrackName, EAudioFormat::Auto);
        GameInstanceRef->CurrentRunTimeAudio->OnResult.AddUniqueDynamic(this, &ASpeakerSystem::TrackLoaded);
    }
    else
    {
        if (CurrentAudioComponent != nullptr)
        {
            BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, false);
            CurrentAudioComponent->Stop();
            CurrentAudioComponent->Destroy(true, true);
            if (GetCurSoundInfo().SpeakerTrack !=  nullptr)
            {
                GetCurSoundInfo().SpeakerTrack->ReleaseMemory();
            }

        }
        SetCurSoundInfo(InSound);
        GetCurSoundInfo().SpeakerTrack->RewindPlaybackTime(0.f);
        FTransform SpawnTM(GetActorRotation(), GetActorLocation());
        CurrentAudioComponent = Cast<AAmbientSound>(UGameplayStatics::BeginDeferredActorSpawnFromClass(GetWorld(), AAmbientSound::StaticClass(), SpawnTM));
        CurrentAudioComponent->GetAudioComponent()->bOverrideSubtitlePriority = false;
        CurrentAudioComponent->GetAudioComponent()->bIsUISound = false;
        CurrentAudioComponent->GetAudioComponent()->bOverridePriority = true;
        CurrentAudioComponent->GetAudioComponent()->bSuppressSubtitles = true;

        CurrentAudioComponent->GetAudioComponent()->bAllowSpatialization = true;
        CurrentAudioComponent->GetAudioComponent()->bOverrideAttenuation = true;

        CurrentAudioComponent->GetAudioComponent()->AttenuationSettings = AttenuationSettings;
        CurrentAudioComponent->GetAudioComponent()->bAutoManageAttachment = false;
        CurrentAudioComponent->GetAudioComponent()->AutoAttachLocationRule = EAttachmentRule::SnapToTarget;
        CurrentAudioComponent->GetAudioComponent()->AutoAttachRotationRule = EAttachmentRule::SnapToTarget;
        CurrentAudioComponent->GetAudioComponent()->AutoAttachScaleRule = EAttachmentRule::SnapToTarget;
        CurrentAudioComponent->GetAudioComponent()->SoundClassOverride = SoundClass;
        CurrentAudioComponent->GetAudioComponent()->SetSound(GetCurSoundInfo().SpeakerTrack);
        CurrentAudioComponent->GetAudioComponent()->bAutoDestroy = true;

        UGameplayStatics::FinishSpawningActor(CurrentAudioComponent, SpawnTM);

        CurrentAudioComponent->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale, FName(""));

        if (!bPlayingMusic)
        {
            bPlayingMusic = true;
            SetSpeakerActive(bPlayingMusic && !GetPaused());
        }
        BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, true);
    }
}
gtreshchev commented 2 years ago

Here is the error i get attempting ReleaseMemory image image


void ASpeakerSystem::PlaySound(FSpeakerData InSound)
{
  //ImportAudioFromFile
  if (InSound.SpeakerTrack == nullptr)
  {
      if (!EnsureGameInstanceRef())
      {
          UE_LOG(LogTemp, Error, TEXT("%s InSound.SpeakerTrack == nullptr"), *GetName());
          return;
      }
      SetCurSoundInfo(InSound);
      GameInstanceRef->CurrentRunTimeAudio->ImportAudioFromFile(InSound.TrackName, EAudioFormat::Auto);
      GameInstanceRef->CurrentRunTimeAudio->OnResult.AddUniqueDynamic(this, &ASpeakerSystem::TrackLoaded);
  }
  else
  {
      if (CurrentAudioComponent != nullptr)
      {
          BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, false);
          CurrentAudioComponent->Stop();
          CurrentAudioComponent->Destroy(true, true);
          if (GetCurSoundInfo().SpeakerTrack !=  nullptr)
          {
              GetCurSoundInfo().SpeakerTrack->ReleaseMemory();
          }

      }
      SetCurSoundInfo(InSound);
      GetCurSoundInfo().SpeakerTrack->RewindPlaybackTime(0.f);
      FTransform SpawnTM(GetActorRotation(), GetActorLocation());
      CurrentAudioComponent = Cast<AAmbientSound>(UGameplayStatics::BeginDeferredActorSpawnFromClass(GetWorld(), AAmbientSound::StaticClass(), SpawnTM));
      CurrentAudioComponent->GetAudioComponent()->bOverrideSubtitlePriority = false;
      CurrentAudioComponent->GetAudioComponent()->bIsUISound = false;
      CurrentAudioComponent->GetAudioComponent()->bOverridePriority = true;
      CurrentAudioComponent->GetAudioComponent()->bSuppressSubtitles = true;

      CurrentAudioComponent->GetAudioComponent()->bAllowSpatialization = true;
      CurrentAudioComponent->GetAudioComponent()->bOverrideAttenuation = true;

      CurrentAudioComponent->GetAudioComponent()->AttenuationSettings = AttenuationSettings;
      CurrentAudioComponent->GetAudioComponent()->bAutoManageAttachment = false;
      CurrentAudioComponent->GetAudioComponent()->AutoAttachLocationRule = EAttachmentRule::SnapToTarget;
      CurrentAudioComponent->GetAudioComponent()->AutoAttachRotationRule = EAttachmentRule::SnapToTarget;
      CurrentAudioComponent->GetAudioComponent()->AutoAttachScaleRule = EAttachmentRule::SnapToTarget;
      CurrentAudioComponent->GetAudioComponent()->SoundClassOverride = SoundClass;
      CurrentAudioComponent->GetAudioComponent()->SetSound(GetCurSoundInfo().SpeakerTrack);
      CurrentAudioComponent->GetAudioComponent()->bAutoDestroy = true;

      UGameplayStatics::FinishSpawningActor(CurrentAudioComponent, SpawnTM);

      CurrentAudioComponent->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale, FName(""));

      if (!bPlayingMusic)
      {
          bPlayingMusic = true;
          SetSpeakerActive(bPlayingMusic && !GetPaused());
      }
      BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, true);
  }
}

The sound wave is still using PCM data, as you can see by calling "OnGeneratePCM", so it's still being used. Make sure that no component is playing the sound wave at that time.

Dark583 commented 2 years ago

Thats the odd part? Do i need to set a timer and wait 0.2 seconds or something ? After i Stop/Destroy and then call release

gtreshchev commented 2 years ago

@Dark583 Yes, try clearing the data with a short delay of a few seconds.

Dark583 commented 2 years ago

@gtreshchev Attempting this just a moment!

Dark583 commented 2 years ago

it seems to work now and clear the memory i had it set to 1.0 initially for the timer how low would you recommend?

Caffiendish commented 2 years ago

You called Stop, but then call Destroy without checking if your audio component still exists. Are you still using AutoDestroy = True?

You could also try the hacky BS stop gap I came up with here: https://github.com/Caffiendish/RuntimeAudioImporter/commit/c7dca7bcad743014aa9c5dae2a397cc12eb6a10e So when you submit a Stop request, it, you know, stops.

Caffiendish commented 2 years ago

Or you could use the delegate: https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h#L429

Dark583 commented 2 years ago

Is what your suggesting better then this? We have a music player in our game so music can change at any time.

void ASpeakerSystem::PlaySound(FSpeakerData InSound)
{
    //ImportAudioFromFile
    if (InSound.SpeakerTrack == nullptr)
    {
        if (!EnsureGameInstanceRef())
        {
            UE_LOG(LogTemp, Error, TEXT("%s InSound.SpeakerTrack == nullptr"), *GetName());
            return;
        }
        SetCurSoundInfo(InSound);
        GameInstanceRef->CurrentRunTimeAudio->ImportAudioFromFile(InSound.TrackName, EAudioFormat::Auto);
        GameInstanceRef->CurrentRunTimeAudio->OnResult.AddUniqueDynamic(this, &ASpeakerSystem::TrackLoaded);
    }
    else
    {
        if (CurrentAudioComponent != nullptr)
        {
            BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, false);
            CurrentAudioComponent->Stop();
            CurrentAudioComponent->Destroy(true, true); 
        }
        if (!GetWorldTimerManager().IsTimerActive(DelayedPlaySoundTimer))
        {
            GetWorldTimerManager().SetTimer(DelayedPlaySoundTimer, this, &ASpeakerSystem::DelayedPlaySound, 1.0f, false);
        }
        else
        {
            GetWorldTimerManager().ClearTimer(DelayedPlaySoundTimer);
            GetWorldTimerManager().SetTimer(DelayedPlaySoundTimer, this, &ASpeakerSystem::DelayedPlaySound, 1.0f, false);
        }
    }
    PendingSound = InSound;
}

void ASpeakerSystem::DelayedPlaySound()
{
    if (GetCurSoundInfo().SpeakerTrack != nullptr)
    {
        GetCurSoundInfo().SpeakerTrack->ReleaseMemory();
    }
    SetCurSoundInfo(PendingSound);
    GetCurSoundInfo().SpeakerTrack->RewindPlaybackTime(0.f);
    FTransform SpawnTM(GetActorRotation(), GetActorLocation());
    CurrentAudioComponent = Cast<AAmbientSound>(UGameplayStatics::BeginDeferredActorSpawnFromClass(GetWorld(), AAmbientSound::StaticClass(), SpawnTM));
    CurrentAudioComponent->GetAudioComponent()->bOverrideSubtitlePriority = false;
    CurrentAudioComponent->GetAudioComponent()->bIsUISound = false;
    CurrentAudioComponent->GetAudioComponent()->bOverridePriority = true;
    CurrentAudioComponent->GetAudioComponent()->bSuppressSubtitles = true;

    CurrentAudioComponent->GetAudioComponent()->bAllowSpatialization = true;
    CurrentAudioComponent->GetAudioComponent()->bOverrideAttenuation = true;

    CurrentAudioComponent->GetAudioComponent()->AttenuationSettings = AttenuationSettings;
    CurrentAudioComponent->GetAudioComponent()->bAutoManageAttachment = false;
    CurrentAudioComponent->GetAudioComponent()->AutoAttachLocationRule = EAttachmentRule::SnapToTarget;
    CurrentAudioComponent->GetAudioComponent()->AutoAttachRotationRule = EAttachmentRule::SnapToTarget;
    CurrentAudioComponent->GetAudioComponent()->AutoAttachScaleRule = EAttachmentRule::SnapToTarget;
    CurrentAudioComponent->GetAudioComponent()->SoundClassOverride = SoundClass;
    CurrentAudioComponent->GetAudioComponent()->SetSound(GetCurSoundInfo().SpeakerTrack);
    CurrentAudioComponent->GetAudioComponent()->bAutoDestroy = true;

    UGameplayStatics::FinishSpawningActor(CurrentAudioComponent, SpawnTM);

    CurrentAudioComponent->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale, FName(""));

    if (!bPlayingMusic)
    {
        bPlayingMusic = true;
        SetSpeakerActive(bPlayingMusic && !GetPaused());
    }
    BindTrackDelegates(GetCurSoundInfo().SpeakerTrack, true);
}
Caffiendish commented 2 years ago

Why do you delete the Audio Component if you're probably going to spawn another, by the way? You can reuse it with this method (or delete it, safely):

Since the Audio Component never actually stops playing currently, we can just use that as our "delete imported song" trigger, by binding to the OnAudioFinished delegate. Just call Stop, then in the function the delegate calls, you delete the song, and if you really want to, the Audio Component as well, there should be no order requirement at that point, since the parse method shouldn't be called again.

There's no need to destroy the Audio Component, we were just saying that if you wanted to, this would be the time to do it (since you were trying to auto destroy them before), but you could always just feed it a new ImportedSoundWave. In fact, you should be able to feed it a new ImportedSoundWave, then delete the previous one.

Caffiendish commented 2 years ago

If you're going to swap out tracks, you could use the delegate OnAudioPlayStateChanged instead perhaps.

I'd imagine that changing the audio track will require a status change, and that would allow you to delete your previous track, and then you can save a reference to your new track, from your audio component.

Caffiendish commented 2 years ago

@gtreshchev I posted a topic, if you've got anything to add to it: https://forums.unrealengine.com/t/runtime-audio-importing/587288

gtreshchev commented 2 years ago

@Caffiendish Thanks for all the comments, I'll look into it over the weekend when I have time.

I think it's possible to override USoundWave::Parse and try to fix this (or at least make it possible to loop the sound wave or do other things as needed).

fishcatcher commented 2 years ago

I think there's a leak with static void TranscodeRAWData(TArray<uint8> RAWData_From, TArray<uint8>& RAWData_To) method DataTo is allocated and passed to RAWData_To. The TArray RAWData_To will copy the content of DataTo which then leaving it dangling.