VSE: Implement partial waveform data loading #110837
No reviewers
Labels
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#110837
Loading…
Reference in New Issue
No description provided.
Delete Branch "Yup_Lucas/blender:Yup_Lucas/vse-partial-waveform-loads"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The waveform drawing is not totally functional yet, but the loads work correctly.
How the implementation works
When we want to draw a sound's waveform, we need to first read the sound samples and prepare the data so that the "waves" can be drawn.
You can visualise the sound data as a contiguous array of sample data:
In the main branch, the waveform data is loaded all at once, even if it is not needed to draw the current frame. This is problematic with large files as it requires a lot of data to be read before anything is drawn to the screen.
To address this issue, we determine what portion of the sound strip is visible in the sequencer and read only the data needed for that portion.
To keep track of what data is loaded and/or needs to be loaded to render a given sound strip, we use a segment tree to keep track of each segment:
Each node in the keeps track of whether the data it is responsible for is already loaded.
Only leaf nodes (the ones without any children in the tree) are loaded.
This takes advantage of the multi-threading introduced in #108877. If more than one segment needs to be loaded in order to render the strip's waveform, the segments get loaded in parallel.
Since no two segments overlap, they do not need to coordinate when writing to the waveform data buffer.
Each segment is only processed once thanks to tags in each segment tree node. If a node is either loaded or loading, no task is created to load its data.
Note: Traversing the segment tree is not a big deal. Since it is parameterised on the total number of seconds in the audio source, even if the audio was 5 hours long we would only need to traverse ~17 (log2(5 * 3600) nodes (in the worst case) to figure out if a segment is loaded or not.
Example: Display a segment with only partial data loaded
We want to render the waveform data for the node marked in yellow:
Since the left subtree is already loaded, we only need load the right subtree:

Once the right subtree is loaded, the yellow node is marked as loaded and ready for display:

Notes
The previous AUD_readSound function normalised the data read by default.
That function was replaced with AUD_readSoundAt.
AUD_readSoundAt does not normalise the data.
Instead, whenever we compute the waveform drawing data, the data normalises the sample using the current maximum sample value across all the loaded samples.
I considered always normalising and then renormalising the data when a new. maximum was found, but decided against it due to potentially losing precision due to the various floating point divisions and multiplications.
Another option would have been to keep a shadow version of the audio data that would be overridden when a new maximum was found. But that would double the memory requirements.
Demo
Here's a demo showcasing how the partial waveform load behaves: https://youtu.be/Ujg-EvFpVgg
WIP: VSE: Implement partial waveform data loadingto VSE: Implement partial waveform data loadingSo far I was only able to quickly check functionality, unfortunately with this patch it double locks
sound->spinlock
and blender hangs.When I commentedout the lock where hang occurs -
clear_sound_waveform_loading_tag()
it does not load all waveforms.I can do some debugging on monday.
hey @iss , can you send me the repro steps so I can figure out what is wrong with the patch? 🤔
@ -285,0 +296,4 @@
if (!(segment->tags & loaded_or_loading)) {
if (!segment->left && !segment->right) {
segment->tags |= SOUND_TAGS_WAVEFORM_LOADING;
sequencer_preview_add_sound_segment(C, seq, segment);
I think the issue with the double locking that @iss reported is here.
waveform_job_start_all_needed_segements
is called inside the spin lock lock.If in sequencer_preview_add_segment the PreviewJob is stopped, we'll get double locking because of the call to
clear_sound_waveform_loading_tag
(this function tries to acquire the lock).Thinking about this now, we probably don't need that lock to be used.
We do an atomic load of the tags in the
waveform_job_start_all_needed_segments
, and in theclear_sound_waveform_loading_tag
andBKE_sound_read_waveform_segment
functions do an atomic write to set the tags correctly.TODO for myself is to update
clear_sound_waveform_loading_tag
to clear the segment tag, not the sound itself. We are not loading entire sound files anymore.Unfortunately I have used file, that I cannot share. It had large number (~100) of shorter sound clips, with some longer ones.
I will try to produce .blend file with sound files that you can copy, to reproduce.
I have tried to comment out locks, but it didn't work either and haven't had time to look into it in bit more detail.
I was able to replicate the problem on my machine by putting a really long sleep in the
preview_startjob
function insequencer_preview
:** Why did the deadlock happen? **
When a segment was submitted to the PreviewJob, the sound's spin lock was acquired.
If the PreviewJob was terminating and a new segment was submitted, we cleared the sound's loading bit.
The function that cleared the bit acquired the same spin lock and the code deadlocked.
** Why didn't the waveforms load after removing the locks? **
The problem was in the function that cleared the sound loading bit.
Since we now load segments instead of the entire sound, the loading bit is set in each segment node (in the segment tree).
When we hit the case where the PreviewJob was finishing, we cleared the wrong bit and bailed.
From the sequencer_draw.cc code's point of view, the sound segment was still tagged as loading and was never resubmitted.
I changed the code to get rid of the locking and updated the function that clears the sound segment bit.
I added the
sleep(10)
call to the code after I made these changes and now waveform loading works properly when we hit the case where the PreviewJob is finishing.@iss if you get a chance, can you re-do your tests using the set up you have there?
I'll try to create my own test file with many strips to see how it goes.
I'll also run another round of tests on my windows PC to see if I find anything else :)
I have tested latest patch and it works. It does load waveforms a bit faster, but it seems, that it loads whole strip anyway. So I am confused, why it does load things faster, when doing more work?
To load only visible part seems to be simple mistake, but some strips aren't drawn correctly. It is even faster though (up to 8x with some files).
One thing that I observed is, that with this patch I get noticably longer delay when opening a .blend file. Will have to double check whether this is result of this patch or somehow behavior is different with buildbot. If the delay is caused by the patch, then waveform drawing wouldn't be that much better.
Are the strips completely visible when you enable waveform drawing?
If they are, the code loads all of the data since that's what it needs to do the drawing.
One of the "future work" points I added to the report is that we can make the loading adapt to the zoom levels.
If you're totally zoomed out and seeing the whole strip, we could sample the audio data instead of reading all of it. You're not gonna have much resolution around the audio anyways.
Each segment is pushed individually to the PreviewJob queue.
They get loaded in parallel and that's why you see better performance there.
@iss can you send me a screenshot to see what's up with the loading?
Not sure what's going wrong there with the strip.
This doesn't look right though.
SEQ_time_strip_length_get
returns the number of frames. So the unit here is frames(frame_start - x1_aligned)
and(frame_end - x1_aligned)
are in pixels.We need to convert the result of
SEQ_time_strip_length_get
from frames to pixels for the division to make sense.Isn't the original
strip_length
variable the size in terms of pixels that specific strip would have?Strips are completely visible, but they are cut up, so only smaller part of content is visible.
I wouldn't implement adaptive resolution, because this increases code complexity, when I don't think it is necessary. In addition it may cause UI to feel bit busy seeing waveforms constantly redrawing. This patch already feels very fast, so I don't even think it's necessary.
Sure, but if it loads waveform of entire content, it is same amount of data, that has to be processed, so now it is segmented in additions, which I would expect to be same at best and bit slower at worst. Could be some very low level stuff I don't understand (hardware caches / prefetching).
frame_start
,frame_end
andx1_aligned
are in timeline frames. So you want length factor, but in strip time domain.Then it draws waveforms correctly, but it still seems to load waveform for whole content - if I resize the strip it doesn't run job again and shows waveform.
From memory, the purpose of
x1_aligned
is to align first sample that should be drawn with pixels, so when you pan timeline, it doesnt pick random start samples and doesn't feel glitchy. Not great naming there...I need to profile
BKE_sound_init_waveform
next time and have a closer look at the file opening delay I have mentioned. That is something I wouldn't like to introduce.I did look into
BKE_sound_init_waveform()
impact on performance. To do this with higher accuraccy, I have forced initialization for all strips in a loop.I have tested this with 2 files:
So there is rather large delay when .blend file is opened, that effectively negates effects of this patch. Also I would rather loading to be slower than lock UI for such significant amount of time. That said, I don't see reason, why this initialization has to be done in main thread.
DEFAULT_MAX_SEGMENT_SIZE
seems to be pretty conservative already, so don't think changing this value would help a lot.@iss sound good, I'll move the initialisation to a background thread.
It does make sense to have different segments be initialised in the background
Checkout
From your project repository, check out a new branch and test the changes.