Recovering a WAV file's length


#1

I'm attempting to add some handling for crash recovery of a wav file that was being written to using the WavAudioFormatWriter.  I believe the file will have a correct header minus the actual data length.  So I'm looking for the ability to open a wav file and read in the *actual* data length and update the header.  I've been looking through the code and have done some forum seaches but haven't been able to to locate anything.  It doesn't appear to be very complicated but it would be nice to use something tied to the WavAudioFormatReader/Writer.  Does anyone know if I missing something, or will have to roll my own?

Thanks!


#2

You'd have to roll your own, the current implementation doesn't handle that.

If you felt like doing it in a way that might benefit everyone, probably the best plan would not be to write a recovery mechanism, but rather to tweak the WavAudioFormatWriter so that it flushes the header at intervals, e.g. every few seconds, so that even if it crashes, you'd get a file that's mostly complete. (Actually, that'd probably be easier than writing something to recover a broken file too)


#3

Yeah that's a good idea.  I've tested out a quick implementation which seems to work alright.  I don't know if you'd be concerned about updating the entire header each time.  Also this seems easily doable enough for WAV and AIFF, but I'm not up on FLAC, OGG and LAME.  As for configuration, it might work well enough to have an extra parameter in createWriterFor which would be an int64 specifying the update frequency in samples.  That could be paired up with modifications to the constructors or with a virtual setter on the writer.  The formats that don't support it would assert false.

 

//In WaveAudioFormatWriter::write
...
       
        if (headerUpdateFrequencyInSamples > 0)
        {
            headerUpdateFrequencyCount += numSamples;
            
            if (headerUpdateFrequencyCount >= headerUpdateFrequencyInSamples)
            {
                headerUpdateFrequencyCount = 0;
                //writeHeader seeks to the header position.  capture current position so we can set it back.
                int64 currentPosition = output->getPosition();
                writeHeader();
                output->setPosition (currentPosition);
            }
            
        }

...

 


#4

I suppose int's (vs int64) would be more approriate for the frequency values.


#5

Actually, thinking about it, a good design would probably be to add a base class method flush(), which could return true if the format supports it. Then people could call it manually at whatever interval makes sense in their app.


#6

I see, yeah it would be cleaner and very flexible.  The OutputStreams have a void flush so using a bool return in this case might introduce some inconsistency between similar apis, but perhaps it's not a big issue.  Would there be an issue in manually calling flush from the write thread when using a ThreadedWriter?


#7

Why would there be an issue with calling it from the write thread? That's the only place it could be called from (?)


#8

Well to be honest I'm not quite certain what the proposed flush method would do.  Would it write just the header information, or would it work like a typical flush and happen to include header information?  My point about the ThreadedWriter is that it's write method just write's to it's fifo, so manually calling flush on a ThreadedWriter would require special handling to signal the TimeSliceThread that we want the underlying writer to flush.


#9

Presumably it'd just call the code that currently happens in the destructor to write the header, then would set the position back to the end of the file to carry on writing. Obviously flush() could only be called from the same thread that calls write(), but the ThreadedWriter could easily have something added to make it flush after a specified interval. I wouldn't suggest adding a flush method to ThreadedWriter, only to the AudioFormatWriter itself.


#10

I'm not sure that adding the interval code to the ThreadedWriter would be best.  The ThreadedWriter really should only be concerned with managing the fifo.  The fact that ThreadedWriter has an IncomingDataReceiver disagrees with that I suppose but I'm not sure that it would be great to add more in that regard.

I would see the header information and the format's ability to update at intervals being a concern of the writer itself.  How about having flush() and flushAtInterval(int numSamples) both added to AudioFormatWriter?  It could then be called manually, or on interval regardless of whether a ThreadedWriter is being used or not.

I'd also suggest having a separate method to query the format's flushing capability.  Otherwise you are forced to invoke the flush just to determine the format's ability to do so, which may not be desirable at the given time.

So something like the following:

bool AudioFormatWriter::allowsFlushing()

void AudioFormatWriter::flush()

void AudioFormatWriter::flushAtInterval(int numSamples)


#11

Hi Jules, I see that you added the flush implementation to the wav writer.  I'm wondering about the padding code:


        if ((bytesWritten & 1) != 0) // pad to an even length
        {
            ++bytesWritten;
            output->writeByte (0);
        }

What if someone writes an odd number of bytes then updates the header and continues writing.  Wouldn't this insert a 0 into the audio mid stream?  Or is an odd byte count something that would never happen?


#12

It may write a zero byte at the end, but will then seek back to the original position and overwrite it.


#13

Ah I see..  thanks and sorry I missed that!


#14

For what it's worth it looks like that bytesWritten value will be made incorrect due to the increment in the padding code (when executed mid stream).  As you mentioned the position gets set back but the bytesWritten will be 1 greater than it should be.  It doesn't look like it's super important but figured it might be worth mentioning.


#15

Also in case your interested, I've implemented some flush interval code for ThreadedWriter (I've attached the code).  Essentially adds a method to ThreadedWriter to set the flush interval in samples which passes it on to the ThreadedWriter::Buffer class which then implements the code in writePendingData.

EDIT: fixed the cpp file upload.


#16

Thanks!


#17

Thanks for getting the code in but it looks like numToDo isn't the right value to use for decrementing the flush counter as it's always a quarter of the total fifo size.  I believe a const "samplesRead" value derived from size1 and size2 would be the correct value.

        //replace numToDo usage with this.
        const int samplesRead = size1 + size2;

        fifo.finishedRead (samplesRead);

        if (samplesPerFlush > 0)
        {
            flushSampleCounter -= samplesRead;

            if (flushSampleCounter <= 0)
            {
                flushSampleCounter = samplesPerFlush;
                writer->flush();
            }
        }

 

 


#18

Ah, silly me! Thanks,  will fix that..