Start recording output and stop recording output?

Hello, I found these 2 nodes and I’m trying to get it to output my ingame audio as it says in the docs:

FROM DOCS “Start recording audio. By leaving the Submix To Record field blank, you can record the master output of the game.”

FROM DOCS “Stop recording audio. Path can be absolute, or relative (to the /Saved/BouncedWavFiles folder). By leaving the Submix To Record field blank, you can record the master output of the game.”

I have audio that is triggered by events in game and i can’t seem to get it to record my audio. here is my BP below. anyone have an idea how to get it recording and saving the wav file to my drive?

1 Like

@Minus_Kelvin got any ideas why its not working?

@frostic, the Delay function actually delays the execution on the rest of the execution graph, so you aren’t actually calling PlaySoundAtLocation until after FinishRecordingOutput has been called. So if you’re trying to record that sound, it may be too late.

Otherwise, I would simplify your path just in case there’s some path error and it’s not saving to the correct location.

the play sound at location was to play after the recording was done just so i knew when i could stop and check if it saved my audio.
the sounds are played by collisions of drum sticks, and i’m trying to capture that as a wav file so people can share their drumming i added the delay because i figured
since its event driven sounds by the drums it would record for 30 seconds. i’m guessing that expected duration is like the delay before FinishRecordingOutput happens?
I’ll try without that delay. I have used c:\ and f:\ and lots of different folder locations. i’m thinking that delay is making it 60 seconds since my
expected duration is 30 and the delay is 30. i just thought it needed delayed before the FinishRecordingOutput was called.
hope this works

@dan.reynolds SCRATCH ALL THAT LOL. I got it working. it can’t be done in line… lol that broke it.
now if i set the D to start and F to finish it works.

so basically it needs to be started by 1 event and ended by another event.

Hmmmm, I would not expect that to be a problem. Delay nodes are like timer delegates.

The expected duration pre-allocates a scratch buffer.

well whatever it is its working and your messages helped me to get it working using a widget. thanks for your help. may this post help someone else Cheers <3

@dan.reynolds so everything works in editor but my packaged game will not record. it throws this error :

LogAudioMixer: Error: Output recording is an audio mixer only feature. Please run the game with -audiomixer to enable this feature.

But this game is an oculus game, there is no way I can possible have oculus users to go into folders and add this because it runs directly from the exe not a shortcut…
how in the world OR Why in the world is this setup this way? why can’t i enable it in the project settings?
this makes no sense to me. please tell me there is a way to do this.

@dan.reynolds

Got it fixed, I went in my engine/config/Windows folder and copied that folder and put it into my project/config folder and now the published beta records audio and is working awesome!

@dan.reynolds Thankyou for your help :slight_smile: you are awesome!

Hah, yeah, you will want to indicate using the AudioMixer within your Engine ini config files to use it for packaging.

@frostic @dan.reynolds hey, I think im having a problem where I saved my recording as a soundwave, and then play it back on another level. this works in the editor but not in build. do you guys have any idea what i missed?

I had to export to wav file then load it up using a c++ script to load/play it.

in your project click add c++ file. select the type called blueprint function library Name it AudioFunctions click create class.

then it will open up visual studio 2017

in there you should see AudioFunctions.h and AudioFunctions.cpp
these 2 functions will load a wav file from a folder/filename and allow it to be played… below is the code used to load/play the wav

inside your AudioFunctions.h delete all code in it and add this code:
before you delete the code make a copy of the line that says the folowing. yours will be different because the name of your game will be there instead of the words EDITME

UCLASS()
class EDITME_API UAudioFunctions : public UBlueprintFunctionLibrary




// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "AudioFunctions.generated.h"

/**
 *
 */
UCLASS()
class EDITME_API UAudioFunctions : public UBlueprintFunctionLibrary  //* EDIT THIS LINE TO MATCH THE ONE FOR YOUR GAME (EDITME_API)
{
    GENERATED_BODY()

    //* Declare blueprint functions here?
        UFUNCTION(BlueprintCallable, Category = "AudioFunctions", meta = (DisplayName = "Get Sound Wave from Wave file", Keywords = "Get Sound Wave from Wave file"))
        static class USoundWave* GetSoundFromWaveFile(const FString& filePath, bool& Success);


    /** Obtain all files in a provided directory, with optional extension filter. All files are returned if Ext is left blank. Returns false if operation could not occur. */
        UFUNCTION(BlueprintPure, Category = "AudioFunctions")
        static bool GetALLFilesInFolder(TArray<FString>& Files, FString RootFolderFullPath, FString Ext);

};



then inside your AudioFunctions.cpp delete all code and replace that with all of this code:




// Fill out your copyright notice in the Description page of Project Settings.
#include "AudioFunctions.h"
#include "Sound/SoundWave.h"
#include "Misc/FileHelper.h"
#include "Kismet/GameplayStatics.h"


class USoundWave* UAudioFunctions::GetSoundFromWaveFile(const FString& filePath, bool& Success)
{
    if (filePath == "") { Success = false; return nullptr; }

    USoundWave* sw = NewObject<USoundWave>(USoundWave::StaticClass());
    if (!sw) { Success = false; return nullptr; }

    TArray < uint8 > rawFile;

    FFileHelper::LoadFileToArray(rawFile, filePath.GetCharArray().GetData());
    FWaveModInfo WaveInfo;

    if (WaveInfo.ReadWaveInfo(rawFile.GetData(), rawFile.Num()))
    {

        // - catching not supported bit depth
        if (*WaveInfo.pBitsPerSample != 16) { Success = false;  return nullptr; }

        sw->InvalidateCompressedData();

        sw->RawData.Lock(LOCK_READ_WRITE);
        void* LockedData = sw->RawData.Realloc(rawFile.Num());
        FMemory::Memcpy(LockedData, rawFile.GetData(), rawFile.Num());
        sw->RawData.Unlock();

        int32 DurationDiv = *WaveInfo.pChannels * *WaveInfo.pBitsPerSample * *WaveInfo.pSamplesPerSec;
        if (DurationDiv)
        {
            sw->Duration = *WaveInfo.pWaveDataSize * 8.0f / DurationDiv;
        }
        else
        {
            sw->Duration = 0.0f;
        }
        sw->SetSampleRate(*WaveInfo.pSamplesPerSec);
        sw->NumChannels = *WaveInfo.pChannels;
        sw->RawPCMDataSize = WaveInfo.SampleDataSize;
        sw->SoundGroup = ESoundGroup::SOUNDGROUP_Default;

    }
    else {
        Success = false;
        return nullptr;
    }

// - Baking PCM Data from file into SoundWave memory
const int32 NumSamples = sw->RawPCMDataSize / sizeof(Audio::FResamplerResults);

sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
FMemory::Memcpy(sw->RawPCMData, WaveInfo.SampleDataStart, NumSamples * sizeof(Audio::FResamplerResults));

if (!sw) { Success = false; return nullptr; }

Success = true;
return sw;
}
//~~~~~~
// File IO
//~~~~~~
bool UAudioFunctions::GetALLFilesInFolder(TArray<FString>& Files, FString RootFolderFullPath, FString Ext)
{
    if (RootFolderFullPath.Len() < 1) return false;

    FPaths::NormalizeDirectoryName(RootFolderFullPath);

    IFileManager& FileManager = IFileManager::Get();

    if (!Ext.Contains(TEXT("*")))
    {
        if (Ext == "")
        {
            Ext = "*.*";
        }
        else
        {
            Ext = (Ext.Left(1) == ".") ? "*" + Ext : "*." + Ext;
        }
    }

    FString FinalPath = RootFolderFullPath + "/" + Ext;

    FileManager.FindFiles(Files, *FinalPath, true, false);
    return true;
}



Changes in code for 4.24.3 highlighted in green

after you add the code click on your project name ------> over there
and click build/build solution. once it is done
you should have 2 new BP nodes.

GetALLFilesInFolder <---- can be used to load an array with wave files to a STRING
GetSoundFromWaveFile<---- Gets the Usoundwave from the folder/name STRING

Inside a widget(not needed) I added the following nodes to load all files in the folder
into an array on EVENT CONSTRUCT I call the GetFilesInFolder EVENT and put the array in a combo box to display all the wav files to the player
so they can choose what one to play. then on the button PLAY(button_134) i get the projectpath, + {path}Recordings
this is the folder in the same path as my content folder config, intermediate and saved etc. in there i have a folder named Recordings. you can name it whatever you want.
whatever name you name the folder make sure you change the name in the format text to match your folder name. this it the path data. the combo box will get the wav file NAME as a string
so you need to feed the PATH and FILENAME into the format text using the append STRING. then use the GetSoundFromWaveFile to convert the STRING by loading up the actual file from the STRING into a UsoundWav format for it to be hooked up to the play sound at location(if looping needed) use Spawn sound at location (if you don’t need Looping)
Click Compile/saveall etc. Save everything then build for windows 64 and it should be able to load/play the file.

THEN for recording i used this code:

Once you are done and you compile your game into a folder go into the windowsnoeditor folder
you will see an engine folder
an exe of your game and another folder with the name of your game

Open the folder with the same name as your game and Create the folder in there where the files will be loaded from.
mine is a folder named Recordings. then after you record something, when you look in that folder you will see the file
with a name like 01_22_20_01_55.WAV this is the recorded file.

2 Likes

WOW, what a glorious piece of art. Thanks Matt!!!

Don’t thank me. it was @Jaytheway that helped me with the c++ code, the blueprints was help that @dan.reynolds helped me figure out to do recording.
all i did was figure out how to make it all work together lol and i barrowed code from victory plugin to load all files from a folder. thanks to RAMA for that code!

Thank you to all contributors! I still think taking the time and compiling it all into one concise post for others is a beautiful thing to appreciate.

To anyone getting an error here is the c++ fix to address the issue

DefaultUSoundWavSampleType seems to have been removed in 4.24.3

so find your comment // - Baking PCM Data from file into SoundWave memory
and exchange that with:

FResamplerResults

// - Baking PCM Data from file into SoundWave memory
const int32 NumSamples = sw->RawPCMDataSize / sizeof(Audio::FResamplerResults);

sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
FMemory::Memcpy(sw->RawPCMData, WaveInfo.SampleDataStart, NumSamples * sizeof(Audio::FResamplerResults));

if (!sw) { Success = false; return nullptr; }

Success = true;
return sw;
}

Fantastic code, and I’ve been using it for a while now, but it no longer works in 5.1.

In the soundwave class, this:

FByteBulkData RawData;

has changed to this:

UE::Serialization::FEditorBulkData RawData;

As a result, the functions ‘lock’, ‘unlock’ and ‘realloc’ no longer exist. I can’t find any examples of how to use ‘EditorBulkData’. I am also confused by the name, why call it EDITORbulkdata? Surely it is used in pakcaged builds too? The changes seem to be related to “virtual assets”.

Anyone know if FEditorBulkData has direct equivalents to the old lock, unlock and realloc functions?

3 Likes

Hi, did you find equivalents for old lock and unlock with EditorBulkData?