How to get imported file bytes from USoundWave ?

Hi,

I am trying to send an HTTP request at runtime with an audio file. The request takes a wav audio byte as parameter (multipart/form-data).

If I have access to the original wav file, I can read it as bytes array then send it directly using this tutorial. However, I would prefer not to use the original file as it may not exist anymore.

Audio files are imported in Unreal as USoundWave. I am using USoundWave::GetImportedSoundWaveData() to get the audio bytes. It seems that the header byte part is NOT included in the returned bytes, which is required for me…

Do you know how to get or create the wav header file with my USoundWave ? Or, do you know any other method that could work (i tried using xdbxdbx’s answer but sadly it didn’t work) ?

void Test(USoundWave* Audio, ...)
{
        TArray<uint8> AudioBytes;
	uint32 SR;
	uint16 Channels;
	if(Audio->GetImportedSoundWaveData(AudioBytes, SR, Channels))
	{
		UE_LOG(LogTemp, Warning, TEXT("PCM data size is %d"), AudioBytes.Num());
	}
        ....
}

To view the uploaded file, I created a small REST API server using FastAPI. I printed the size of the file as well as the beginning and the end bytes.

This is what I have by sending the file directly (not through Unreal, i used Postman):

size is 46124
begin bytes are b'RIFF$\xb4\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00"V\x00\x00D\xac\x00\x00\x02\x00\x10\x00data\x00\xb4\x00\x00\x00\x00\x00\x00\x00\x00'
end bytes are b"\x86\x00\x86\x00\x86\x00\x82\x00\x82\x00{\x00\x7f\x00\x82\x00\x7f\x00\x7f\x00{\x00{\x00w\x00w\x00w\x00w\x00w\x00t\x00t\x00w\x00t\x00l\x00p\x00p\x00i\x00i\x00i\x00i\x00e\x00e\x00i\x00e\x00a\x00a\x00a\x00^\x00^\x00Z\x00W\x00W\x00W\x00S\x00L\x00L\x00L\x00D\x00D\x00A\x00A\x00=\x00=\x00:\x00:\x006\x002\x002\x00/\x00/\x00/\x00/\x00'\x00'\x00'\x00$\x00'\x00 \x00 \x00\x1d\x00\x1d\x00\x1d\x00\x1d\x00\x19\x00\x19\x00\x15\x00\x15\x00\x15\x00\x12\x00\x12\x00\x0e\x00\x0e\x00\n\x00\n\x00\n\x00\x07\x00\x07\x00\x03\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

And this is what I have by sending the file through Unreal:

size is 46080
begin bytes are b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
end bytes are b"\x86\x00\x86\x00\x86\x00\x82\x00\x82\x00{\x00\x7f\x00\x82\x00\x7f\x00\x7f\x00{\x00{\x00w\x00w\x00w\x00w\x00w\x00t\x00t\x00w\x00t\x00l\x00p\x00p\x00i\x00i\x00i\x00i\x00e\x00e\x00i\x00e\x00a\x00a\x00a\x00^\x00^\x00Z\x00W\x00W\x00W\x00S\x00L\x00L\x00L\x00D\x00D\x00A\x00A\x00=\x00=\x00:\x00:\x006\x002\x002\x00/\x00/\x00/\x00/\x00'\x00'\x00'\x00$\x00'\x00 \x00 \x00\x1d\x00\x1d\x00\x1d\x00\x1d\x00\x19\x00\x19\x00\x15\x00\x15\x00\x15\x00\x12\x00\x12\x00\x0e\x00\x0e\x00\n\x00\n\x00\n\x00\x07\x00\x07\x00\x03\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

We can see that the header part of the file
RIFF$\xb4\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00"V\x00\x00D\xac\x00\x00\x02\x00\x10\x00data\x00\xb4 ...
is missing when I send the bytes through Unreal. How do I get/create it ?

Thanks for your help !

inside of sound.cpp you have

 bool USoundWave::GetImportedSoundWaveData(TArray<uint8>& OutRawPCMData, uint32& OutSampleRate, TArray<EAudioSpeakers>& OutChannelOrder) const {

there is a fragment:

// Copy the raw PCM data and the header info that was parsed
...			OutRawPCMData.AddUninitialized(WaveInfo.SampleDataSize);
			FMemory::Memcpy(OutRawPCMData.GetData(), WaveInfo.SampleDataStart, WaveInfo.SampleDataSize);
...
}

Which would suggest that the parsed header should be copied back. Could it be cut off before being passed into the function?

You should probably be sending a pure byte array from your source.
Not all of this is pure byte code:

RIFF$\xb4\x00\x00WAVEfmt\x10\

I’m guessing that’s why it’s being stripped out.

Thanks for your answer.

I have imported my wav file using the ‘Import’ button in the Content browser of UE. When the game start, I directly call my function with the imported Soundwave.

My guess is that the header is read by the function but is not returned as a bytes array. Instead, the samplerate and the channels values are extracted from the audio header and are given directly.

I am trying to recreate the audio wav header using this format by myself (using the information provided by the USoundWave::GetImportedSoundWaveData() function.

I solved the problem by recreating the header myself using SampleRate and the Channels amount. It seems to work for basic PCM-16bit depth wave files, which is enough for me. Here is the code to do so (functions are static because it’s in a BP static library):

→ Please note that the function GetImportedSoundWaveData() is only available in editor mode, meaning that the code below will not work on packaged projects.

.h :

static TArray<uint8> SoundWaveToBytes(const USoundWave* Audio);
static TArray<uint8> FStringToBytes(const FString& String);
static TArray<uint8> uint32ToBytes(const uint32 Value, bool UseLittleEndian = true);
static TArray<uint8> uint16ToBytes(const uint16 Value, bool UseLittleEndian = true);

.cpp :

TArray<uint8> SoundWaveToBytes(const USoundWave* Audio)
{
	TArray<uint8> BytesArr;
	if (IsValid(Audio))
	{
		TArray<uint8> AudioBytes;
		TArray<uint8> AudioHeader;
		uint32 SampleRate;
		uint16 BitDepth = 16; // How to retrieve information directly from the SoundWave ??
		uint16 Channels;

		if (Audio->GetImportedSoundWaveData(AudioBytes, SampleRate, Channels))
		{
			// Please see: https://docs.fileformat.com/audio/wav/							   Bytes
			AudioHeader.Append(FStringToBytes("RIFF"));								// 1-4
			AudioHeader.Append(uint32ToBytes(AudioBytes.Num() + 36));					// 5-8
			AudioHeader.Append(FStringToBytes("WAVEfmt "));							// 9-16
			AudioHeader.Append(uint32ToBytes(16));										// 17-20
			AudioHeader.Append(uint16ToBytes(1));									// 21-22
			AudioHeader.Append(uint16ToBytes(Channels));								// 23-24
			AudioHeader.Append(uint32ToBytes(SampleRate));									// 25-28
			AudioHeader.Append(uint32ToBytes(SampleRate * BitDepth * Channels / 8));	// 29-32
			AudioHeader.Append(uint16ToBytes(BitDepth * Channels / 8));			// 33-34
			AudioHeader.Append(uint16ToBytes(BitDepth));								// 35-36
			AudioHeader.Append(FStringToBytes("data"));								// 37-40
			AudioHeader.Append(uint32ToBytes(AudioBytes.Num()));						// 41-44

			BytesArr.Append(AudioHeader);
			BytesArr.Append(AudioBytes);
		}
	}
	return BytesArr;
}



TArray<uint8> FStringToBytes(const FString& String)
{
	TArray<uint8> OutBytes;

	// Handle empty strings
	if (String.Len() > 0)
	{
		FTCHARToUTF8 Converted(*String); // Convert to UTF8
		OutBytes.Append(reinterpret_cast<const uint8*>(Converted.Get()), Converted.Length());
	}

	return OutBytes;
}

TArray<uint8> uint32ToBytes(const uint32 Value, bool UseLittleEndian)
{
	TArray<uint8> OutBytes;
	if (UseLittleEndian)
	{
		OutBytes.Add(Value >> 0 & 0xFF);
		OutBytes.Add(Value >> 8 & 0xFF);
		OutBytes.Add(Value >> 16 & 0xFF);
		OutBytes.Add(Value >> 24 & 0xFF);
	}
	else
	{
		OutBytes.Add(Value >> 24 & 0xFF);
		OutBytes.Add(Value >> 16 & 0xFF);
		OutBytes.Add(Value >> 8 & 0xFF);
		OutBytes.Add(Value >> 0 & 0xFF);
	}
	return OutBytes;
}

TArray<uint8> uint16ToBytes(const uint16 Value, bool UseLittleEndian)
{
	TArray<uint8> OutBytes;
	if (UseLittleEndian)
	{
		OutBytes.Add(Value >> 0 & 0xFF);
		OutBytes.Add(Value >> 8 & 0xFF);
	}
	else
	{
		OutBytes.Add(Value >> 8 & 0xFF);
		OutBytes.Add(Value >> 0 & 0xFF);
	}
	return OutBytes;
}

Usage :

USoundWave* Audio = ...;
TArray<uint8> AudioBytes = SoundWaveToBytes(Audio);

When I send the audio bytes in the HTTP POST multipart/form-data request (see this) the file is now readable by the server!

2 Likes

Very helpful. It works in UE5.1
Have your found any solution to replace “GetImportedSoundWaveData” function, which makes the code can only work in Editor mode. T_T

1 Like

Any updates on “GetImportedSoundWaveData” function ?

@DSJind Hi, I have published a plugin named microphonerecorder, maybe this is what you want.