No support for WAVE_FORMAT_EXTENSIBLE & a proposed fix

This is a bug notice and its fix. Hopefully this will make it in future versions of juce.

I noticed that there appears to be a lack of support for WAVE_FORMAT_EXTENSIBLE. By this I mean that we have a sample app that converts one WAV to another WAV changing various parts of the format (# of channels, sample rate, and bits per sample). However, when I use a WAV that uses an audio format code of WAVE_FORMAT_EXTENSIBLE the conversion finishes but with a duration of 00:00:00. I looked into this and discovered that it appears only WAVE_FORMAT_PCM (1) and WAVE_FORMAT_IEEE_FLOAT (3) are supported. However, with only a little code change, WAVE_FORMAT_EXTENSIBLE can easily be supported so long as its sub format code is either PCM or IEEE_FLOAT.

The support for only PCM and IEEE_FLOAT can be seen in the WavAudioFormatReader constructor. When reading the "fmt " WAV chunk there is code that checks the audio format in this manner:

   if (format == 3)
      usesFloatingPointData = true;
   else if (format != 1)
      bytesPerFrame = 0;

Note again that 3 refers to WAVE_FORMAT_IEEE_FLOAT and 1 refers to WAVE_FORMAT_PCM (both supported). When the format is WAVE_FORMAT_EXTENSIBLE (0xFFFE or 65534) then one should refer to the sub format for how to read the WAV. I got the code working the way I want, but I’d love it if this became standard part of juce for future updates we may grab and for other users.

There were two important changes.

First change:

I made the format variable a uint16. This is important since WAVE_FORMAT_EXTENSIBLE will be read as -2 not 65534 if the format is a signed int16 a.k.a. short. I also made it not a const since later we’ll assign it to the value of the sub format if the format is WAVE_FORMAT_EXTENSIBLE

                        uint16 format = (uint16) input->readShort();

Second change:

I added this conditional code after reading in all the other data in the "fmt " chunk:

                        if (format == 65534)    // if format is WAV_FORMAT_PCM_EXTENSIBLE
                        {
                            // Skip past the following:
                            // ---------------------------------
                            // 2 bytes  blockAlign
                            // 2 bytes  bitsPerSample
                            // 2 bytes  cbSize
                            // 2 bytes  wValidBitsPerSample    
                            // 4 bytes  dwChannelMask
                            input->skipNextBytes(12);
                            // Next is a 16 byte GUID.  The first two bytes of the GUID
                            // make up the sub format.
                            const uint16 subFormat = (uint16) input->readShort();
                            format = subFormat;
                        }

Here is the entire revised constructor for WavAudioFormatReader (although only the "fmt " chunk data part is interesting).

	WavAudioFormatReader (InputStream* const in)
		: AudioFormatReader (in, TRANS (wavFormatName)),
		  dataLength (0),
		  bwavChunkStart (0),
		  bwavSize (0)
	{
		if (input->readInt() == chunkName ("RIFF"))
		{
			const uint32 len = (uint32) input->readInt();
			const int64 end = input->getPosition() + len;
			bool hasGotType = false;
			bool hasGotData = false;

			if (input->readInt() == chunkName ("WAVE"))
			{
				while (input->getPosition() < end
						&& ! input->isExhausted())
				{
					const int chunkType = input->readInt();
					uint32 length = (uint32) input->readInt();
					const int64 chunkEnd = input->getPosition() + length + (length & 1);

					if (chunkType == chunkName ("fmt "))
					{
						// read the format chunk
						uint16 format = (uint16) input->readShort();
						const short numChans = input->readShort();
						sampleRate = input->readInt();
						const int bytesPerSec = input->readInt();

						numChannels = numChans;
						bytesPerFrame = bytesPerSec / (int)sampleRate;
						bitsPerSample = 8 * bytesPerFrame / numChans;

						if (format == 65534)    // if format is WAV_FORMAT_PCM_EXTENSIBLE
						{
							// Skip past the following:
							// ---------------------------------
							// 2 bytes  blockAlign
							// 2 bytes  bitsPerSample
							// 2 bytes  cbSize
							// 2 bytes  wValidBitsPerSample	
							// 4 bytes  dwChannelMask
							input->skipNextBytes(12);
							// Next is a 16 byte GUID.  The first two bytes of the GUID
							// make up the sub format.
							const uint16 subFormat = (uint16) input->readShort();
							format = subFormat;
						}

						if (format == 3)		// if format is WAVE_FORMAT_IEEE_FLOAT
							usesFloatingPointData = true;
						else if (format != 1)   // if format is not WAVE_FORMAT_PCM
							bytesPerFrame = 0;

						hasGotType = true;
					}
					else if (chunkType == chunkName ("data"))
					{
						// get the data chunk's position
						dataLength = length;
						dataChunkStart = input->getPosition();
						lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;

						hasGotData = true;
					}
					else if (chunkType == chunkName ("bext"))
					{
						bwavChunkStart = input->getPosition();
						bwavSize = length;

						// Broadcast-wav extension chunk..
						HeapBlock <BWAVChunk> bwav;
						bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1);
						input->read (bwav, length);
						bwav->copyTo (metadataValues);
					}
					else if (chunkType == chunkName ("smpl"))
					{
						HeapBlock <SMPLChunk> smpl;
						smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1);
						input->read (smpl, length);
						smpl->copyTo (metadataValues, length);
					}
					else if (chunkEnd <= input->getPosition())
					{
						break;
					}

					input->setPosition (chunkEnd);
				}
			}
		}
	}

Great stuff, thanks! I didn’t realise it was so simple to pull out the format from an extensible chunk! I’ll get something like that merged in asap…

Um… ok, I just took a look at this, but there’s already a bunch of code there that reads the extensible block - you seem to be quoting a really old version of the codebase there…??

Sorry about that. I’ll see if we can grab the latest and I’ll take another look. :slight_smile: