AudioThumbnail, reloading the low res thumbnail


#1

I must be missing something on how to reload the thumbnail. The reloaded thumbnail looks pixelized when compared to the thumbnail before saving. The thumbnail is created with sourceSamplesPerThumbnailSample = 128. How come the 1st image has better resolution the the 2nd reloaded one ?

1st, original.
FileInputSource * inputSource = new FileInputSource(inFile);
mAudioThumbnail->setSource(inputSource);

2nd, reloaded.
FileInputStream* inFileStream = new FileInputStream(jatFile); // jatFile is the saved thumbnail file
mAudioThumbnail->loadFrom (*inFileStream);
delete inFileStream;

Thanks,
Eric


#2

If you load it from a thumbnail, all it’s got to show you is that thumbnail data, so if you zoom right in, that’s all you’ll get. But if you point it at a wave file, then when you zoom right in it can go back to the file and load the detail when you need it.


#3

Makes sense. I’ve changed the reloading code (2nd) to:
FileInputSource * inputSource = new FileInputSource(inFile);
mAudioThumbnail->setSource(inputSource);
FileInputStream* inFileStream = new FileInputStream(jatFile); // jatFile is the saved thumbnail file
mAudioThumbnail->loadFrom (*inFileStream);
delete inFileStream;

And I get access violations from the thumbnail class code.
Setting the source after loadFrom(), re-creates the thumbnail from scratch, which is no good.


#4

yes… if you pass it a reference to an object which you then immediately delete, then no big surprise that it would crash!


#5

yes indeed. thanks jules.


#6

Spoke too soon…
It still crashes occasionally in the thumbnail class code. Seems to happen more often if i use longer files. I’ve used 90 and 15 min. WAVs.
Note that if I don’t setSource(), then I don’t get crashes, but it’s back to square one.

1st crash log:

MyApp.exe!juce::AudioThumbnail::isFullyLoaded() Line 340 + 0x32 bytes C++
MyApp.exe!juce::AudioThumbnail::useTimeSlice() Line 144 + 0xb bytes C++
MyApp.exe!juce::TimeSliceThread::run() Line 127 + 0x1b bytes C++
MyApp.exe!juce::Thread::threadEntryPoint(juce::Thread * const thread=0x022ce6d8) Line 69 + 0xf bytes C++
MyApp.exe!juce::juce_threadEntryPoint(void * userData=0x022ce6d8) Line 92 + 0x9 bytes C++
MyApp.exe!juce::threadEntryProc(void * userData=0x022ce6d8) Line 104 + 0x9 bytes C++
MyApp.exe!_callthreadstartex() Line 348 + 0xf bytes C
MyApp.exe!_threadstartex(void * ptd=0x022ce7b8) Line 331 C

juce_AudioThumbnail.cpp, line 340
jassert (d != 0);

  •   data	{data=0x00000000 <Bad Ptr> size=0 }	juce::MemoryBlock
    

1st crash log:

MyApp.exe!juce::AudioThumbnail::generateSection(juce::AudioFormatReader & reader={…}, __int64 startSample=0, int numSamples=65536) Line 313 + 0x3a bytes C++
MyApp.exe!juce::AudioThumbnail::readNextBlockFromAudioFile(juce::AudioFormatReader & reader={…}) Line 256 C++
MyApp.exe!juce::AudioThumbnail::useTimeSlice() Line 140 C++
MyApp.exe!juce::TimeSliceThread::run() Line 127 + 0x1b bytes C++
MyApp.exe!juce::Thread::threadEntryPoint(juce::Thread * const thread=0x05a258e8) Line 69 + 0xf bytes C++
MyApp.exe!juce::juce_threadEntryPoint(void * userData=0x05a258e8) Line 92 + 0x9 bytes C++
MyApp.exe!juce::threadEntryProc(void * userData=0x05a258e8) Line 104 + 0x9 bytes C++
MyApp.exe!_callthreadstartex() Line 348 + 0xf bytes C
MyApp.exe!_threadstartex(void * ptd=0x05a24000) Line 331 C

juce_AudioThumbnail.cpp, line 340
l [n] = (char) jlimit (-128.0f, 127.0f, lowestLeft * 127.0f);

  •   l	0x07a70074 <Bad Ptr>	char *
    

First-chance exception at 0x0078fb38 in MyApp.exe: 0xC0000005: Access violation writing location 0x07ae008a.


#7

You’re probably doing some other dangling-pointer type mistake - this doesn’t sound like a bug in the thumbnail class to me.


#8

I guess ur using the same AudioThumbnail object to load from FileInputSource as well as loading from FileInputStream.

The Reader in the AudioThumbnail is Still active and useTimeSlice() method of the AudioThumbnail is still using the Reader.

Just delete the AudioThumbnail object and recreate it and test it…


#9

Hi Jules,

I’m working with Eric on the project that the original post refers to. First, a couple of questions:

(1) When a thumbnail is built using setSource(), then its data used to paint a waveform view, is that data different (more detailed) than it would be if we subsequently called saveTo() and then loadFrom()? We were originally assuming it would be the same, and that a thumbnail loaded from a saved file would still show detail when zoomed in.

(2) Is it “legal” to call setSource(aValidSource) followed by loadFrom()? This is what causes a crash (see below). It isn’t clear from the documentation, and after trying, the two seem mutually exclusive (provided aValidSource is indeed a valid source, not 0).

Let me explain what we’re trying to do in the hopes it helps with context.

In our application, we have a waveform view component (obviously). We want to store thumbnail data persistently to a file so the view can quickly be restored between user sessions.

Currently in our code, when the user opens audio file “foo.wav” for the first time, a thumbnail is computed and saved to a file “foo.jat”. We store the full path of foo.wav in a separate session document.
Given we have in our code an AudioThumbnail object called ‘thumbnail’:

When opening a new file, we call thumbnail.setSource(). This churns through the audio file, computing the thumbnail, taking time to display in the view. When it’s finished computing, we call thumbnail.saveTo() to save it to the .jat file. The waveform view now displays a nice, detailed waveform which we can zoom into at sample level.

When we close and reopen the session, we create a FileInputSource object “wavSource” from foo.wav and a FileInputStream object “jatStream” from foo.jat. We then call thumbnail.setSource(wavSource), followed by thumbnail.loadFrom(jatStream). This causes a crash in the thumb cache worker thread, in AudioThumbnail::generateSection(), line 313.

However, if instead of calling thumbnail.setSource(wavSource), we call thumbnail.setSource(0), we get no crash. The waveform view paints thumbnail data loaded from the .jat file, but when we zoom in near sample level, we get no detail (per the topic of this post). If we call setSource(wavSource) after loadFrom(jatStream), the thumbnail object seems to disregard the data loaded from jatStream and starts recomputing the thumbnail all over.

A work-around that I have been experimenting with is this:

  1. When audio file is opened for the first time, call AudioThumbnail::setSource(). Save the thumbnail data to .jat file. Save session file (disregarding waveform zoom setting for now).

  2. When session file is re-opened, call AudioThumbnail::setSource(0), followed by AudioThumbnail::loadFrom(jatFile). Paint the waveform view using data from the .jat file, showing the waveform fully zoomed out.

  3. Upon zooming in to a point at which greater detail is needed, then call setSource().

This works, but seems like extra code we have to write whereas we would have expected JUCE to take care of managing it automatically. Any help is appreciated!

Thanks,
Bill

P.S. in response to Godwin’s post, we tried destroying and recreating the thumbnail object, but still got the crash.


#10

If u are using loadFrom in the different session ( Next launch ) then u need not to delete the AudioThumbnail and recreate it.

But if u r doing something like this

FileInputSource * inputSource = new FileInputSource(inFile); mAudioThumbnail->setSource(inputSource); FileInputStream* inFileStream = new FileInputStream(jatFile); // jatFile is the saved thumbnail file mAudioThumbnail->loadFrom (*inFileStream); delete inFileStream;

then u definitely need to delete mAudioThumbnail object.

Saving Thumbanil.

AudioThumbnail (const int sourceSamplesPerThumbnailSample, AudioFormatManager &formatManagerToUse, AudioThumbnailCache &cacheToUse)
“sourceSamplesPerThumbnailSample” should be “1” while saving the thumbnail so that all samples from the source file is converted to low resolution( char data ) data and stored in internal buffer.
And when u save the thumbnail file it will save all the samples. ( as far as I know, I never saved and loaded the thumb file ).

When u load this file again u need to not to call setsource for the audio thumbnail. just LoadFrom is more than enough.( all the samples are there in the thumb file).

Regards,
Godwin


#11

It does sound like it’s not working correctly - the idea is that it does all this for you. You should be able to:

  1. call setSource to give it the file
  2. call loadFrom to reload your last thumbnail, if you have one
  3. when you’re finished with the thumbnail, you’d check isFullyLoaded(), and if it’s true, you’d know it’s ok to save the thumbnail file.

The only other thing you might want to do would be to test whether the file has been changed since you saved the thumbnail, e.g. by date-stamping the thumbnail file with the date of the original and checking it before you re-load. If it’s different, just delete it and let the thumbnail re-build itself.

But I have to admit that whenever I’ve used this class myself, I’ve never actually saved thumbnails, so that bit may be not very well tested!

Having a very quick look, I’d say it’s probably crashing because the loadFrom method isn’t thread-safe. It might be enough just to add:

void AudioThumbnail::loadFrom (InputStream& input) { const ScopedLock sl (readerLock);

Let me know if that doesn’t help and I’ll investigate properly!

No! Please ignore this advice! It’d just create a thumbnail that’s half as big as your wave file and try to load it into memory! The whole point is that you use a value like 512, so you have a tiny thumbnail, and this low-res data is used when you’re zoomed out. Then, when you zoom in far enough, it starts reading the original audio file directly for its high-res data.


#12

Wat if i dont want to use the original wav file for reading the data while zooming to sample level.

and

Is this in terms of samples or in terms of file size.


#13

[quote=“Godwin”]Wat if i dont want to use the original wav file for reading the data while zooming to sample level.

and

Is this in terms of samples or in terms of file size.[/quote]

Actually, I was wrong.

It stores 2 bytes per sample, so it’d actually be the same size as an uncompressed wav file! It’d be no better than just loading the whole damn file into memory and reading it directly!


#14

Ok…
So we need to load first from the saved file… and after file is fully loaded we have to setsource for AudioThumbnail …
is It??


#15

No, like I said above, you’d set the source first, and then load the thumbnail. Setting the source clears out any thumbnail data.


#16

Jules, adding the ScopedLock where you indicated appears to fix the problem. No more crashes and thumbnail behaving as expected. Thank you.


#17

Great - thanks for spotting the problem!


#18