RF64 / oddity in WavAudioFormat?


#1

Hello

I’m currently updating juce_WavAudioFormat to support RF64 for > 4GB files, and as reading the following code lines (juce_WavAudioFormat.cpp), I’m wondering if it’s normal that ‘smpl’ chunk size is not taken into account for Master Riff header size??

532         const int bytesPerFrame = numChannels * bitsPerSample / 8;
533         output->writeInt (chunkName ("RIFF"));
534         output->writeInt ((int) (lengthInSamples * bytesPerFrame
535                                    + ((bwavChunk.getSize() > 0) ? (44 + bwavChunk.getSize()) : 36)))

#2

Well, that bit of code is writing the RIFF chunk size, so it looks correct to me… (Unless I’m misunderstanding what you mean?)


#3

Well, I’m not aware of how the smpl chunk works but I would have expected it to be a sub chunk of the RIFF chunk and thus the RIFF chunk size to include the size of smpl chunk…


#4

But it does include the size of the smpl chunk… That’s what lengthInSamples is used for (?)


#5

…errr… to me lengthInSamples is for ‘data’ chunk…
by ‘smpl’ chunk I meant the chunk define by this code:

struct SMPLChunk
{
    struct SampleLoop
    {
        uint32 identifier;
        uint32 type;
        uint32 start;
        uint32 end;
        uint32 fraction;
        uint32 playCount;
    } PACKED;

    uint32 manufacturer;
    uint32 product;
    uint32 samplePeriod;
    uint32 midiUnityNote;
    uint32 midiPitchFraction;
    uint32 smpteFormat;
    uint32 smpteOffset;
    uint32 numSampleLoops;
    uint32 samplerData;
    SampleLoop loops[1];
}

#6

Ah, I see - sorry, for some reason I was convinced that ‘smpl’ was the name of the big chunk containing the sample data.

Good point! I guess it needs a tweak like this:

output->writeInt ((int) (lengthInSamples * bytesPerFrame + ((bwavChunk.getSize() > 0) ? (44 + bwavChunk.getSize()) : 36) + (smplChunk.getSize() > 0 ? smplChunk.getSize() + 8 : 0)));


#7

ok makes more sense now :slight_smile:
Now I just finished updating and validating my modifications on WavAudioFormat for RF64 (being able to write and read > 4GB wav files, following the MBWF/RF64 specifications http://tech.ebu.ch/docs/tech/tech3306-2009.pdf). Basically, the writer now creates fully compliant RIFF Wave file as far as the amount of audio data is small enough, and turns the header into an RF64 wave file on the fly when reaching the 32 bit limit.
Changes:

  • writer automatically switch to RF64 format when needed (audioDataChunkSize > 0xffffffff), using a ‘JUNK’ chunk for space reservation necessary to store eventual ‘ds64’ chunk as the file grows above the 4GB limit
  • reader can handle both RF64 and normal RIFF WAVE file
  • written format now use the WAVEFORMATEX structure rather than WAVEFORMAT

Would you be interested in integrating these changes in JUCE ?


#8

Sure, that’s very kind of you - I’d love to take a look!


#9

Stupid question but if Juce uses int for sample indexes (for example, PositionableAudioSource::getNextReadPosition) isn’t it possible that a large-file WAV cannot have every sample individually addressed by a 32-bit signed integer?


#10

true, but still allows to read about up to ~2+ hours 48k/24bit/stereo files (or even longer if resolution is higher) through PositionableAudioSource… which is more than the maximum length of a standard one disc album…


#11

The AudioFormatReader already uses 64-bit values for addressing sample numbers, so it can read/write large files with no problem - it’s just PositionableAudioSource that doesn’t take advantage of this, but that could be tweaked.


#12

I wish Juce had a typedef for a sample index either signed int32 or signed int64 and I could just use that in my app


#13

Thanks for updating WavAudioFormat with these changes. I saw you removed the bool IsRF64() method from the reader/writer. Well I think this can still be useful for the reader, my experience being for example that it let me filtering the RF64 wav files and avoiding trying to tag them with TagLib (TagLib::RIFF parsing’s 32 bit indexing causes the ID3 Tag to be written over a part of the data chunk instead of being appended…).


#14

Thanks! I did quite a bit of rewriting, hope I didn’t break anything!

I couldn’t really see why you added the isRF64 method, because it was in an internal class, not visible to any other code…?


#15

true, sorry, I had moved WavAudioFormatReader and writer definition and declaration to the header file in between…


#16

I was just debugging an issue where WavAudioFormat wouldn’t read a regular wav file that is larget than 2gb - maybe it was introduced with the change discussed here.

        else if (firstChunkType == chunkName ("RIFF"))
        {
            len = (uint64)(input->readInt());
            end = input->getPosition() + len;
        }

As far as I understand the Wav/RIFF format, the length is supposed to be an unsigned 32 bit value and so it should allow up to 4gb file sizes. But casting the signed integer returned by readInt() directly to uint64 leads to a wrong value for the file length (and then “end” becomes negative because it’s a signed int64).

Casting the length to uint32 first seems to fix this, but I have not yet properly tested it. I’d like to know first if this is a regression bug or there are indeed other reason why the WavAudioFormat won’t work with regular wav files between 2gb and 4gb size.


#17

Ah, thanks, yes - that certainly looks like a signedness conversion problem…


#18

any chance this fix can merged back in the master branch? we’d like to do a maintenance release based on the pre-quake version, and this is the only patch we currently need to apply now that you backported the other plugin fixes