OGG file importer - How to work with the Buffer?

Hey guys,

I spend some time on writing an importer for .ogg files. For this I made my own USoundFactory and implemented the function FactoryCreateBinary.
Now I ran into a few problems with the data.

The importing itself works so far, but the sound itself is not working as I want it to work.
The USoundFactory does this with the Buffer that gets passed in the FactoryCreateBinary function:



Sound->RawData.Lock( LOCK_READ_WRITE );
void* LockedData = Sound->RawData.Realloc( BufferEnd-Buffer );		
FMemory::Memcpy( LockedData, Buffer, BufferEnd-Buffer ); 
Sound->RawData.Unlock();


So it’s basically copying the Buffer into the RawData of the USoundWave. RawData is of type FByteBulkData.
Naw RawData is commented to be

, so I can’t really use that.
In fact, if I do it the same way, I just can’t an imported USoundWave object that does not play anything.

Here is what I did, and what results in sound actually playing:



FByteBulkData* BulkData = &Sound->CompressedFormatData.GetFormat(FName("OGG"));
BulkData->Lock(LOCK_READ_WRITE);
FMemory::Memmove(BulkData->Realloc(RawOggData.Num()), RawOggData.GetData(), RawOggData.Num());
BulkData->Unlock();


RawOggData is just a TArray in which I copied the Buffer:



// Array that holds the Raw Ogg Data
TArray<uint8> RawOggData;
RawOggData.Empty(BufferEnd - Buffer);
RawOggData.AddUninitialized(BufferEnd - Buffer);
// Copy over the Buffer into the Raw Ogg Data array
FMemory::Memcpy(RawOggData.GetData(), Buffer, RawOggData.Num());


So I put the Buffer into the CompressedFormatData, which is a TMap of FByteBulkData. Key is OGG here.

While this works in the Editor Process in which I imported the file, the file does not play anthing anymore after I closed and re-opened the editor.

Now I assume that the Editor clears the CompressedFormatData? What I can do with the Buffer/SampleData to store it?
I looked into different functions but I can’t figure it out. One thing I came to was using:


OggInfo.ExpandFile(RawPCMData.GetData(), &QualityInfo);

With OggInfo being of type FVorbisAudioInfo, RawPCMData being a TArray again and QualityInfo being of type FSoundQualityInfo.
Before I call this I called


OggInfo.ReadCompressedInfo(RawOggData.GetData(), RawOggData.Num(), &QualityInfo)

.

This, if I understood it correctly, should have filled the QualityInfo as well as the VorbisData Pointer that is inside of the OggInfo.

But ExpandFile is freezing the Editor. In Debug mode it breaks at checking if BytesRead is < 0. I don’t even get how that can fail as BytesRead is of type long.

I feel like I’m running in a circle. Would really love some help here. Is it even possible to import .ogg files and “save” them properly for Editor restarts and even packaging?

USoundFactory::FactoryCreateBinary looks like one hell of a function to override.

Since it looks like you’ve got the raw pcm at some point, perhaps you could just take the **FileType\b], Buffer and BufferEnd like below?


UObject* USoundOggFactory::FactoryCreateBinary
(
	UClass*				Class,
	UObject*			InParent,
	FName				Name,
	EObjectFlags		Flags,
	UObject*			Context,
	const TCHAR*,//		FileType,
	const uint8*&		Buffer,
	const uint8*			BufferEnd,
	FFeedbackContext*	Warn
){
  TArray<uint8> Wav{};
  ConvertOggToPcmThenAddWavFileHeaders(Buffer,BufferEnd,Wav);
  const uint8 *WavBuffer = Wav.GetData();
  const uint8 *const WavBufferEnd = WavBuffer + Buffer.Num();
  return Super::FactoryCreateBinary(
	Class,
	InParent,
	Name,
	Flags,
	Context,
	TEXT("WAV"),
	WavBuffer,WavBufferEnd,
	Warn);
}

The problem with this is, that I don’t know how to do the ConvertOggToPCMThenAddWavFileHeaders().
Because I only have the functions I already described and the “ExpandFile” function is freezing the Editor, while the ReadCompressedData() function didn’t really give me anything.
Its description is:

so I would assume I could use it, but I’m not 100% sure. Will try though.

What do you mean with the Wav Header? I haven’t understood the actual difference between Ogg and Wav yet (in terms how they are build up).
Would I fill the Wav header from the data I got from ReadCompressedInfo? So basically from the FSoundQualityInfo?
Because the Header is just another chunk of bytes after all and I don’t know what part of the Ogg file is used for that.

Hey eXi,

Sorry it sounded like you had audio playing at some point, where the issue was only with having the asset persist between editor launches. I’ve only written one factory type, for my own custom extended music module code (.xm) and it doesn’t use USoundFactory and full disclosure: I’ve only been using UE4 for a little over a month.

What I was stating is that if you’re able to get the ogg to raw 16 bit PCM, it would be fairly simple to append a wav file header to the pcm data. The WAV format is somewhat of a modular one in that WAV files are able to define different codecs based upon the header within the file. So thinking that FactoryCreateBinary is being sent a buffer containing the exact bytes of the file, you could take the ogg, expand it to acquire the pcm (raw wave data in 16 bit signed integers, interleaved if stereo) from the ogg file and prefix it with the file header of wav that accurately describes the uncompressed pcm (such as if its stereo or mono), then the Super::FactoryCreateBinary would read that to setup its internals as if it were a wav file on disk (in theory).

I may be incorrect but it seems that unreal basically reads from the actual file input once, saves the path to the original file if you want to reimport it at a later time where the cooking process actually deals with deciding what should be compressed or not. So I’d think the goal would be to get the input buffer containing the ogg data, then convert that to wav in memory then send that to Super while you declare the file type is WAV.

So yea, it sounds like if UE4 does have ogg expanding functions in it that are for whatever reason failing, you could try utilizing public domain Ogg Vorbis decoder ( stb/stb_vorbis.c at master · nothings/stb · GitHub ) to get the pcm data. That way you’ll at least be able to determine if it is a bug with UE or not.

Once you got the pcm in a 16bit signed integer format, you would just prefix the data with a basic wav file header. I found this on google but you may be able to find a better resource: Microsoft WAVE soundfile format

Basically, you just insert those descriptions in ahead of the pcm data, that should leave you with a compatible WAV file (in memory) that you can pass to the super.

If i’m missing / misunderstanding anything, let me know. I’m interested to hear about it as I plan on writing various factories as well. Thanks

Also, on your note about the differences between ogg and wav. PCM (pulse code modulation) is ultimately what hits the speakers, unreal basically requires that the pcm be in sixteen bit signed integers (shorts) which is traditionally standard. when the integer is SHORT_MIN the speaker is all the way in, and SHORT_MAX out (or vicsa versa), the actual movement/acceleration of the speaker moving in and out is what produces the sound though. When speaking of the most basic type of WAV file, it has this raw pcm data immediately after the file header for the wav, where formats like OGG and MP3 use methods to have less variation in the samples, then use compression on top of that similar to zip.

Thanks for your help so far, highly appreciated!

So in theory, if I convert the OGG stuff to raw 16 bit PCM, I would just need to add 44 bytes of Header data to it, so it’s “representing” a WAV and then simply use the WAV implementation?
And yes, I have sound, but only if a save the OGG data directly in the FByteBulkData TMap entry for OGG in the CompressedFormatData variable.
Not really ideal as it seems to get cleared on restart.

It doesn’t seem that straight forward to me how to create that header. I mean, the page describes pretty well what each X bytes represent and how the header is build up, as well as given an example,
but that is in hex and using the endian thingy. No idea how to properly do that in code.

I assume I gather all the data that I need and then, one by one, convert them first to numbers (if for example characters like RIFF are used) and then convert them to byte with big and little endian in mind?
I’m pretty sure I break all the possible things when doing that, but I’ll try. Maybe I’m lucky :X

Will also try to find an already existing implementation of that.

Thats right and yes in theory it should work based on just review of the USoundFactory source. The only problematic looking parts of the source is the checks for TEXT(“WAV”) possibly, somehow overminding the reimport process but it doesn’t look immediately prone to issue as long as it still uses your factory for reimport. It would be pretty easy to take the same steps your doing right now and just make a modified version of the Super that checks for OGG or whatever else.

Basically, think of the header for the wav file as a struct of size 44. in the table on that site the offset (first left number) would be the offset of the member in the struct.

I took a look around for a C implementation of the header and found this NameBright - Coming Soon . though i’d caution you that theres likely way more information on that site than you need to accomplish this.

using unreal typedefs the header would look something like this…



struct FWaveHeader {
    // Riff Wave Header
    uint8 chunkId[4];
    uint32 chunkSize;
    uint8 format[4];

    // Format Subchunk
    uint8 subChunk1Id[4];
    uint32  subChunk1Size;
    uint16 audioFormat;
    uint16 numChannels;
    uint32 sampleRate;
    uint32 byteRate;
    uint16 blockAlign;
    uint16 bitsPerSample;
    //uint16 extraParamSize;

    // Data Subchunk
    uint8 subChunk2Id[4];
    uint32  subChunk2Size;

};


1 Like

So I created me a Wave Header and filled it similar to another page I used.


FWaveHeader MyWaveHeader = CreateWaveHeader(QualityInfo); // QualityInfo holds NumChannels etc already extracted from the OGG file
WaveHeader.Empty(44); // TArray<uint8>
WaveHeader.AddUninitialized(44);
FMemory::Memcpy(WaveHeader.GetData(), &MyWaveHeader, 44);

The struct looks like the one you posted. I tripple checked the values I took (most of them were already filled in despite the chunksize,
but I’m 99% sure that I calculated that correctly as I took the values that the pages tell me to use :confused: “NumSamples * NumChannels * BitsPerSample/8”)

I then used ReadCompressedData to

Then I went ahead and simply appended the TArray<uint8> RawPCMData to my TArray<uint8> WaveHeader.
Now I tried using ReadWaveInfo from FWaveModInfo with the new appended array to check if that runs into any error.
It sadly gets a warning:

But it also cuts it down then, so I ignored it for now.
Then I copied the appended array over to the RawData of the USoundWave, but that doesn’t seem to work, as the I still can’t play it and the output log on import gives me this:

I’m kinda lost at what this could cause. It seems like I really don’t understand how OGG, WAV, uint8* Buffers and all that stuff work.
In theory it seems so simple but I can’t even tell if the data I use can be used like that. I would assume that the ReadCompressedData function of FVorbisAudioInfo should give me the RawPCM Data,
but then again, it might already fail here.

Hm, traced the error back to another ReadWaveHeader call that weirdly enough now fails on the AudioFormat check.
Everything else works, but the AudioFormat is suddenly not PCM anymore. I give up for today >.>

Hey eXi, i took a look at FWaveModInfo::ReadWaveInfo. I’ll try to put together something quickly to produce proper headers.

1 Like