Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clip Refactor (move Timeline Drawing code to Clip) #571

Merged
merged 41 commits into from Oct 27, 2020

Conversation

jonoomph
Copy link
Member

This will likely be absorbed into another PR before I'm done. But I wanted to add some visibility to the work.

- Making Clip a proper Reader (so it can be used directly, instead of a Timeline)
…()->MAX_HEIGHT out of the Cilp class. GetFrame() now has an overload which specifies the width, height, and samples needed. Otherwise, it returns the Clip image based on the source reader (width, height, num samples).
…need to replace the Crop functionality with a more robust cropping tool. Also, updated Timeline to use the MaxWidth/MaxHeight settings when calling the clip (since those are set when the screen is resized).
@jonoomph jonoomph changed the title WIP: Clip Refactor (move Timeline code to Clip) WIP: Clip Refactor (move Timeline Drawing code to Clip) Sep 20, 2020
@codecov
Copy link

codecov bot commented Sep 20, 2020

Codecov Report

Merging #571 into develop will increase coverage by 1.59%.
The diff coverage is 68.24%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #571      +/-   ##
===========================================
+ Coverage    49.45%   51.05%   +1.59%     
===========================================
  Files          129      130       +1     
  Lines        10261    10443     +182     
===========================================
+ Hits          5075     5332     +257     
+ Misses        5186     5111      -75     
Impacted Files Coverage Δ
src/ChunkReader.cpp 0.00% <ø> (ø)
src/ChunkWriter.cpp 0.00% <ø> (ø)
src/EffectBase.h 100.00% <ø> (ø)
src/FrameMapper.h 100.00% <ø> (+25.00%) ⬆️
src/Qt/PlayerDemo.cpp 0.00% <ø> (ø)
src/Qt/VideoCacheThread.cpp 0.00% <0.00%> (ø)
src/Qt/VideoCacheThread.h 0.00% <ø> (ø)
src/QtHtmlReader.cpp 0.00% <0.00%> (ø)
src/QtTextReader.cpp 0.00% <0.00%> (ø)
src/ReaderBase.h 100.00% <ø> (+33.33%) ⬆️
... and 46 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 07a447c...1723484. Read the comment docs.

jonoomph and others added 25 commits October 4, 2020 16:59
…w a Clip access to the parent timeline instance (if available), and thus, certain properties (preview size, timeline FPS, etc...). This allows for a simpler rendering of Clip keyframes (during the Clip::GetFrame method), and a simpler Timeline class, that can change the preview window size dynamically and no longer requires a Singleton Settings class.

 - Also removed "crop" from Clip class, as it was never implmeneted correctly, and we have a fully functional "crop" effect when needed
 - Added caching to Clip class, to optimize previewing of cached frames (much faster than previous)
# Conflicts:
#	include/Clip.h
#	include/ReaderBase.h
#	include/Timeline.h
#	src/Clip.cpp
#	src/FFmpegReader.cpp
#	src/QtImageReader.cpp
#	src/ReaderBase.cpp
…hen exporting to different fps

The FrameMapper class now receives the updated clip position and returns the correct amount of samples for a given frame number
Implemented position remapper inside FrameMapper to fix audio noise when exporting to different fps
…ich still seems to benefit from performance, but keeps the byte order the same as before. win win
…ll colors (since we have switched to a premulitplied alpha format)
# Conflicts:
#	src/CacheDisk.cpp
#	src/Clip.cpp
#	src/Frame.cpp
#	src/QtHtmlReader.cpp
#	src/QtImageReader.cpp
#	src/QtTextReader.cpp
#	src/effects/Bars.cpp
#	src/effects/Crop.cpp
- Making Clip a proper Reader (so it can be used directly, instead of a Timeline)
…()->MAX_HEIGHT out of the Cilp class. GetFrame() now has an overload which specifies the width, height, and samples needed. Otherwise, it returns the Clip image based on the source reader (width, height, num samples).
…need to replace the Crop functionality with a more robust cropping tool. Also, updated Timeline to use the MaxWidth/MaxHeight settings when calling the clip (since those are set when the screen is resized).
…w a Clip access to the parent timeline instance (if available), and thus, certain properties (preview size, timeline FPS, etc...). This allows for a simpler rendering of Clip keyframes (during the Clip::GetFrame method), and a simpler Timeline class, that can change the preview window size dynamically and no longer requires a Singleton Settings class.

 - Also removed "crop" from Clip class, as it was never implmeneted correctly, and we have a fully functional "crop" effect when needed
 - Added caching to Clip class, to optimize previewing of cached frames (much faster than previous)
…hen exporting to different fps

The FrameMapper class now receives the updated clip position and returns the correct amount of samples for a given frame number
…ich still seems to benefit from performance, but keeps the byte order the same as before. win win
@ferdnyc
Copy link
Contributor

ferdnyc commented Oct 19, 2020

Still cleaning up the conflict-resolution here, I should have this back to passing within the hour.

@ferdnyc
Copy link
Contributor

ferdnyc commented Oct 19, 2020

Hrm. This could become an issue, if anyone ever decides to actually use the Ruby bindings...

[ 79%] Swig compile openshot.i for ruby
src/Clip.h:96: Warning 802: Warning for openshot::Clip, base openshot::ReaderBase
ignored. Multiple inheritance is not supported in Ruby.
src/Timeline.h:166: Warning 802: Warning for openshot::Timeline,
base openshot::ReaderBase ignored. Multiple inheritance is not supported in Ruby.
[ 79%] Built target rbopenshot_swig_compilation

@jonoomph
Copy link
Member Author

 Multiple inheritance is not supported in Ruby.

I did notice this. Hmmm, I wonder how this will actually manifest at runtime. It apparently still compiles the swig module for Ruby, but somehow with missing multiple inheritance? Sounds like it fail to build the Ruby binding I'm missing something.

src/Clip.cpp Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
Comment on lines +1209 to +1214
// Calculate ratio of source size to project size
// Even with no scaling, previews need to be adjusted correctly
// (otherwise NONE scaling draws the frame image outside of the preview)
float source_width_ratio = source_size.width() / float(width);
float source_height_ratio = source_size.height() / float(height);
source_size.scale(width * source_width_ratio, height * source_height_ratio, Qt::KeepAspectRatio);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly equivalent to:

source_size.scale(source_size.width(), source_size.height(), Qt::KeepAspectRatio);

The two widths and heights in the ratios and in the arguments cancel each other out. So, it's a complete no-op.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(By canceling, I mean:)

width * source_width_ratio
= width * ( source_size.width() / float(width) )
= source_size.width()

src/FrameMapper.cpp Outdated Show resolved Hide resolved
@ferdnyc
Copy link
Contributor

ferdnyc commented Oct 19, 2020

 Multiple inheritance is not supported in Ruby.

I did notice this. Hmmm, I wonder how this will actually manifest at runtime. It apparently still compiles the swig module for Ruby, but somehow with missing multiple inheritance? Sounds like it fail to build the Ruby binding I'm missing something.

Oh, it builds just fine, but what comes out... ain't right. For instance, the ruby Openshot::Clip class doesn't have an info member anymore, or a DisplayInfo() method, because it no longer inherits from ReaderBase. It has the methods defined in ClipBase, like Position() and Start() and etc., but that's it:

$ cd build/bindings/ruby
$ irb -I . -ropenshot
irb(main):002:0> c = Openshot::Clip.new('test.mp4')
irb(main):003:0> c.Open()
=> nil
irb(main):004:0> c.IsOpen()
=> true
irb(main):005:0> c.Position()
=> 0.0
irb(main):006:0> c.Duration()
=> 3.3366665840148926
irb(main):007:0> c.DisplayInfo()
Traceback (most recent call last):
        4: from /usr/bin/irb:23:in `<main>'
        3: from /usr/bin/irb:23:in `load'
        2: from /home/ferd/.gem/ruby/gems/irb-1.2.4/exe/irb:11:in `<top (required)>'
        1: from (irb):7
NoMethodError (undefined method `DisplayInfo' for #<Openshot::Clip:0x00005618a4aafee8>)
Did you mean?  display
irb(main):008:0> c.info
Traceback (most recent call last):
        5: from /usr/bin/irb:23:in `<main>'
        4: from /usr/bin/irb:23:in `load'
        3: from /home/ferd/.gem/ruby/gems/irb-1.2.4/exe/irb:11:in `<top (required)>'
        2: from (irb):7
        1: from (irb):8:in `rescue in irb_binding'
NoMethodError (undefined method `info' for #<Openshot::Clip:0x00005618a4aafee8>)
irb(main):009:0> 

src/Clip.h Outdated Show resolved Hide resolved
src/Clip.h Outdated Show resolved Hide resolved
src/Clip.h Outdated Show resolved Hide resolved
src/ClipBase.h Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
src/Clip.cpp Outdated Show resolved Hide resolved
jonoomph and others added 3 commits October 20, 2020 12:39
Adding suggestions from @ferdnyc

Co-authored-by: Frank Dana <ferdnyc@gmail.com>
Applying some awesome improvements from @ferdnyc, thx!

Co-authored-by: Frank Dana <ferdnyc@gmail.com>
- Removing transformed == true boolean (Qt should be smart enough to optimize for blank transforms)
- Fixing regression from TimelineBase import
@jonoomph
Copy link
Member Author

@ferdnyc Regarding Ruby, I don't have an easy solution for this one. I tried a ton of different approaches to access the parent timeline of a clip. But since the Timeline class depends on the Clip, the Clip interface can't also depend on the Timeline. Creating a base class of the Timeline to expose a few of the parameters needed seemed to make the most sense. However, simple forward declarations don't work, since we are also access some members of the class. I had to revert the one suggestion because it fails to compile.

In summary, I've tried a bunch of things, and what we have now seems the simplest (and only) solution I could come up with, but I'm open to ideas.

@ferdnyc
Copy link
Contributor

ferdnyc commented Oct 20, 2020

@jonoomph

However, simple forward declarations don't work, since we are also access some members of the class. I had to revert the one suggestion because it fails to compile.

Hm, really? In the header? I missed that, somehow.

(In the implementation, sure. Part of what I was suggesting was that we add #include "TimelineBase.h" to the ClipBase.cpp file, instead of the header. That way it would have access to the class definition. I just couldn't actually submit that as a suggestion via the Github review, because ClipBase.cpp wasn't part of this PR yet.)

Well, anyway, that's largely housekeeping. But on this...

In summary, I've tried a bunch of things, and what we have now seems the simplest (and only) solution I could come up with, but I'm open to ideas.

Yeah, I've got nothin' either, when it comes to the multiple inheritance problem. (Which the forward declaration suggestion wouldn't solve anyway, that was just about the source-file dependency tree.)

I did have one awful thought last night, but I hated myself immediately for it so I didn't bring it up:

...What if both TimelineBase and ClipBase were derived from ReaderBase?

It could possibly solve the Ruby problem (at the expense of, you know, sanity), as it would eliminate the multiple inheritance. (It seems like it would, anyway. Not 100% sure. Like I said, I felt dirty as soon as I thought it, so I kind of pushed it way down and tried not to think about it.)

@jonoomph
Copy link
Member Author

...What if both TimelineBase and ClipBase were derived from ReaderBase?

That is not a terrible idea. In effect, both Clip and Timeline are readers now. We would just need to move some things around. But I'll keep that a separate idea from this PR. But I do agree it would solve the problem! Nice

@ferdnyc
Copy link
Contributor

ferdnyc commented Oct 20, 2020

@jonoomph

Heh. libopenshot 0.3.0, the "It's Readers all the way down" edition. 😆

…no gaps/pops, and offset FrameMapper clips don't use the wrong # of audio samples
…m, and FFmpegReaders with a Clip/Timeline associated with them.
…f cache vs display frame #. Also found a mutex that was needed, to prevent crashing when the video thread calls timeline::GetFrame at certain times... colliding with another thread (and independent of OpenMP).
@jonoomph jonoomph merged commit 8b4d568 into develop Oct 27, 2020
@jonoomph jonoomph deleted the clip-refactor-keyframes branch October 27, 2020 07:04
@jonoomph
Copy link
Member Author

Finally merging this branch, as I have other branches which depend on this one. If this causes any huge issues, we can open up another PR. 🤞

@jonoomph
Copy link
Member Author

Fingers crossed emoji is right next to the middle finger one... lol. That is dangerous!

@jonoomph jonoomph changed the title WIP: Clip Refactor (move Timeline Drawing code to Clip) Clip Refactor (move Timeline Drawing code to Clip) Oct 29, 2020
@jonoomph
Copy link
Member Author

@ferdnyc You might be right about the reverse playback. I will look into that. In theory, the direction of the player should drive the direction of the clip caching. Yes, it is a bit scorched earth (and was somewhat experimental), but it also solves some problems (as scorching the earth tends to do). We can always revert it, if the problems outweigh the fixes.

This was really designed to solve 1 thing:

  • Jumping the playhead around creating dozens of pockets of cached frames... which have audio alignment issues, since we have cleared internal buffers and Seeked around... causing tiny mis-alignments that are unique to the preview experience.

But in my testing (including the changes to video caching), I found things much more responsive and continuous in OpenShot. So I thought, dang, maybe this is better. But maybe it's not. 😬

@ferdnyc
Copy link
Contributor

ferdnyc commented Oct 29, 2020

@ferdnyc You might be right about the reverse playback. I will look into that. In theory, the direction of the player should drive the direction of the clip caching.

Yeah, but sorta-not-really. You can't escape the basic reality that encoded video is tuned to be read primarily in one direction — except for a few specialty transport formats or full-frame raw video streams (@ gigabytes-per-second of bandwidth), video wants to be read "in, like... forwards".

I think what the reverse player would have to do, to cache most effectively, is figure out the "frame chunk size" that's going to be produced by the reader (based on the processor count, etc.) and then keep jumping that many frames "ahead of" the "end" of the cache (really, jumping before the beginning of the cache), stuffing N new frames onto the front in one go, and then jumping again. Like a video-frame-hoarding Scott Bakula.

But in my testing (including the changes to video caching), I found things much more responsive and continuous in OpenShot. So I thought, dang, maybe this is better. But maybe it's not.

I wouldn't be surprised if it is, for the most degenerate case where OpenShot is easily able to keep up with the input. Those are the times when you really don't need a cache, and a small readahead buffer is plenty. But as soon as the readahead starts falling behind and can't keep up with the playhead, then everything goes to hell fast. That's true even with the cache, to some extent (unfortunately), but at least with a cache you can give it a run to fill the buffer, then back up and play from the cache at reasonable speed.

@ferdnyc
Copy link
Contributor

ferdnyc commented Oct 29, 2020

I think what the reverse player would have to do, to cache most effectively, is figure out the "frame chunk size" that's going to be produced by the reader (based on the processor count, etc.) and then keep jumping that many frames "ahead of" the "end" of the cache (really, jumping before the beginning of the cache), stuffing N new frames onto the front in one go, and then jumping again. Like a video-frame-hoarding Scott Bakula.

...And even doing that, there are going to be a ton of situations where it takes the next leap and ends up smack in the middle of the Kennedy assassination — sorry, no, that's Quantum Leap again — smack in the middle of a bunch of i-frames that really, really don't want to be read starting from the middle, and it's going to have to (very quickly!) adjust in one direction or the other and then figure out how to deal with the break in its preferred stride. That kind of thing is where timestamping and frame ordering (a) become total hell, but also (b) are absolutely critical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants