Added new CacheDisk class, which caches frames to the hard drive, dramatically speeding up preview speeds, at the expense of IO operations. New unittests for caching framework. Fixed a few bugs with Frame constructor, which was causing invalid # width & height. Integrated JSON into the cache framework, to quickly share the state of the cache (including ranges of cached frame numbers). Fixed a bug where some Timeline frames could have no audio samples.

This commit is contained in:
Jonathan Thomas
2016-09-07 00:40:01 -05:00
parent 89fb86453e
commit c53c9364f3
19 changed files with 1109 additions and 63 deletions

View File

@@ -31,6 +31,7 @@
#include <tr1/memory>
#include "Frame.h"
#include "Exceptions.h"
#include "Json.h"
namespace openshot {
@@ -44,7 +45,8 @@ namespace openshot {
class CacheBase
{
protected:
int64 max_bytes; ///< This is the max number of bytes to cache (0 = no limit)
string cache_type; ///< This is a friendly type name of the derived cache instance
long long int max_bytes; ///< This is the max number of bytes to cache (0 = no limit)
/// Section lock for multiple threads
CriticalSection *cacheCriticalSection;
@@ -56,7 +58,7 @@ namespace openshot {
/// @brief Constructor that sets the max bytes to cache
/// @param max_bytes The maximum bytes to allow in the cache. Once exceeded, the cache will purge the oldest frames.
CacheBase(int64 max_bytes);
CacheBase(long long int max_bytes);
/// @brief Add a Frame to the cache
/// @param frame The openshot::Frame object needing to be cached.
@@ -73,7 +75,7 @@ namespace openshot {
virtual tr1::shared_ptr<Frame> GetFrame(long int frame_number) = 0;
/// Gets the maximum bytes value
virtual int64 GetBytes() = 0;
virtual long long int GetBytes() = 0;
/// Get the smallest frame number
virtual tr1::shared_ptr<Frame> GetSmallestFrame() = 0;
@@ -82,12 +84,17 @@ namespace openshot {
/// @param frame_number The frame number of the cached frame
virtual void Remove(long int frame_number) = 0;
/// @brief Remove a range of frames
/// @param start_frame_number The starting frame number of the cached frame
/// @param end_frame_number The ending frame number of the cached frame
virtual void Remove(long int start_frame_number, long int end_frame_number) = 0;
/// Gets the maximum bytes value
int64 GetMaxBytes() { return max_bytes; };
long long int GetMaxBytes() { return max_bytes; };
/// @brief Set maximum bytes to a different amount
/// @param number_of_bytes The maximum bytes to allow in the cache. Once exceeded, the cache will purge the oldest frames.
void SetMaxBytes(int64 number_of_bytes) { max_bytes = number_of_bytes; };
void SetMaxBytes(long long int number_of_bytes) { max_bytes = number_of_bytes; };
/// @brief Set maximum bytes to a different amount based on a ReaderInfo struct
/// @param number_of_frames The maximum number of frames to hold in cache
@@ -97,6 +104,11 @@ namespace openshot {
/// @param channels The number of audio channels in the frame
void SetMaxBytesFromInfo(long int number_of_frames, int width, int height, int sample_rate, int channels);
/// Get and Set JSON methods
virtual string Json() = 0; ///< Generate JSON string of this object
virtual void SetJson(string value) throw(InvalidJSON) = 0; ///< Load JSON string into this object
virtual Json::Value JsonValue() = 0; ///< Generate Json::JsonValue for this object
virtual void SetJsonValue(Json::Value root) = 0; ///< Load Json::JsonValue into this object
};

136
include/CacheDisk.h Normal file
View File

@@ -0,0 +1,136 @@
/**
* @file
* @brief Header file for CacheDisk class
* @author Jonathan Thomas <jonathan@openshot.org>
*
* @section LICENSE
*
* Copyright (c) 2008-2014 OpenShot Studios, LLC
* <http://www.openshotstudios.com/>. This file is part of
* OpenShot Library (libopenshot), an open-source project dedicated to
* delivering high quality video editing and animation solutions to the
* world. For more information visit <http://www.openshot.org/>.
*
* OpenShot Library (libopenshot) is free software: you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* OpenShot Library (libopenshot) is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENSHOT_CACHE_DISK_H
#define OPENSHOT_CACHE_DISK_H
#include <map>
#include <deque>
#include <tr1/memory>
#include "CacheBase.h"
#include "Frame.h"
#include "Exceptions.h"
#include <QDir>
#include <QString>
#include <QTextStream>
namespace openshot {
/**
* @brief This class is a disk-based cache manager for Frame objects.
*
* It is used by the Timeline class, if enabled, to cache video and audio frames to disk, to cut down on CPU
* and memory utilization. This will thrash a user's disk, but save their memory and CPU. It's a trade off that
* sometimes makes perfect sense. You can also set the max number of bytes to cache.
*/
class CacheDisk : public CacheBase {
private:
QDir path; ///< This is the folder path of the cache directory
map<long int, long int> frames; ///< This map holds the frame number and Frame objects
deque<long int> frame_numbers; ///< This queue holds a sequential list of cached Frame numbers
string image_format;
float image_quality;
float image_scale;
long long int frame_size_bytes; ///< The size of the cached frame in bytes
bool needs_range_processing; ///< Something has changed, and the range data needs to be re-calculated
Json::Value ranges; ///< JSON ranges of frame numbers
vector<long int> ordered_frame_numbers; ///< Ordered list of frame numbers used by cache
map<long int, long int> frame_ranges; ///< This map holds the ranges of frames, useful for quickly displaying the contents of the cache
long int range_version; ///< The version of the JSON range data (incremented with each change)
/// Clean up cached frames that exceed the max number of bytes
void CleanUp();
/// Init path directory
void InitPath(string cache_path);
/// Calculate ranges of frames
void CalculateRanges();
public:
/// @brief Default constructor, no max bytes
/// @param cache_path The folder path of the cache directory (empty string = /tmp/preview-cache/)
/// @param format The image format for disk caching (ppm, jpg, png)
/// @param quality The quality of the image (1.0=highest quality/slowest speed, 0.0=worst quality/fastest speed)
/// @param scale The scale factor for the preview images (1.0 = original size, 0.5=half size, 0.25=quarter size, etc...)
CacheDisk(string cache_path, string format, float quality, float scale);
/// @brief Constructor that sets the max bytes to cache
/// @param cache_path The folder path of the cache directory (empty string = /tmp/preview-cache/)
/// @param format The image format for disk caching (ppm, jpg, png)
/// @param quality The quality of the image (1.0=highest quality/slowest speed, 0.0=worst quality/fastest speed)
/// @param scale The scale factor for the preview images (1.0 = original size, 0.5=half size, 0.25=quarter size, etc...)
/// @param max_bytes The maximum bytes to allow in the cache. Once exceeded, the cache will purge the oldest frames.
CacheDisk(string cache_path, string format, float quality, float scale, long long int max_bytes);
// Default destructor
~CacheDisk();
/// @brief Add a Frame to the cache
/// @param frame The openshot::Frame object needing to be cached.
void Add(tr1::shared_ptr<Frame> frame);
/// Clear the cache of all frames
void Clear();
/// Count the frames in the queue
long int Count();
/// @brief Get a frame from the cache
/// @param frame_number The frame number of the cached frame
tr1::shared_ptr<Frame> GetFrame(long int frame_number);
/// Gets the maximum bytes value
long long int GetBytes();
/// Get the smallest frame number
tr1::shared_ptr<Frame> GetSmallestFrame();
/// @brief Move frame to front of queue (so it lasts longer)
/// @param frame_number The frame number of the cached frame
void MoveToFront(long int frame_number);
/// @brief Remove a specific frame
/// @param frame_number The frame number of the cached frame
void Remove(long int frame_number);
/// @brief Remove a range of frames
/// @param start_frame_number The starting frame number of the cached frame
/// @param end_frame_number The ending frame number of the cached frame
void Remove(long int start_frame_number, long int end_frame_number);
/// Get and Set JSON methods
string Json(); ///< Generate JSON string of this object
void SetJson(string value) throw(InvalidJSON); ///< Load JSON string into this object
Json::Value JsonValue(); ///< Generate Json::JsonValue for this object
void SetJsonValue(Json::Value root) throw(InvalidFile, ReaderClosed); ///< Load Json::JsonValue into this object
};
}
#endif

View File

@@ -1,6 +1,6 @@
/**
* @file
* @brief Header file for Cache class
* @brief Header file for CacheMemory class
* @author Jonathan Thomas <jonathan@openshot.org>
*
* @section LICENSE
@@ -25,8 +25,8 @@
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENSHOT_CACHE_H
#define OPENSHOT_CACHE_H
#ifndef OPENSHOT_CACHE_MEMORY_H
#define OPENSHOT_CACHE_MEMORY_H
#include <map>
#include <deque>
@@ -38,7 +38,7 @@
namespace openshot {
/**
* @brief This class is a cache manager for Frame objects.
* @brief This class is a memory-based cache manager for Frame objects.
*
* It is used by FileReaders (such as FFmpegReader) to cache recently accessed frames. Due to the
* high cost of decoding streams, once a frame is decoded, converted to RGB, and a Frame object is created,
@@ -50,9 +50,17 @@ namespace openshot {
map<long int, tr1::shared_ptr<Frame> > frames; ///< This map holds the frame number and Frame objects
deque<long int> frame_numbers; ///< This queue holds a sequential list of cached Frame numbers
bool needs_range_processing; ///< Something has changed, and the range data needs to be re-calculated
Json::Value ranges; ///< JSON ranges of frame numbers
vector<long int> ordered_frame_numbers; ///< Ordered list of frame numbers used by cache
map<long int, long int> frame_ranges; ///< This map holds the ranges of frames, useful for quickly displaying the contents of the cache
long int range_version; ///< The version of the JSON range data (incremented with each change)
/// Clean up cached frames that exceed the max number of bytes
void CleanUp();
/// Calculate ranges of frames
void CalculateRanges();
public:
/// Default constructor, no max bytes
@@ -60,7 +68,7 @@ namespace openshot {
/// @brief Constructor that sets the max bytes to cache
/// @param max_bytes The maximum bytes to allow in the cache. Once exceeded, the cache will purge the oldest frames.
CacheMemory(int64 max_bytes);
CacheMemory(long long int max_bytes);
// Default destructor
~CacheMemory();
@@ -80,7 +88,7 @@ namespace openshot {
tr1::shared_ptr<Frame> GetFrame(long int frame_number);
/// Gets the maximum bytes value
int64 GetBytes();
long long int GetBytes();
/// Get the smallest frame number
tr1::shared_ptr<Frame> GetSmallestFrame();
@@ -93,6 +101,16 @@ namespace openshot {
/// @param frame_number The frame number of the cached frame
void Remove(long int frame_number);
/// @brief Remove a range of frames
/// @param start_frame_number The starting frame number of the cached frame
/// @param end_frame_number The ending frame number of the cached frame
void Remove(long int start_frame_number, long int end_frame_number);
/// Get and Set JSON methods
string Json(); ///< Generate JSON string of this object
void SetJson(string value) throw(InvalidJSON); ///< Load JSON string into this object
Json::Value JsonValue(); ///< Generate Json::JsonValue for this object
void SetJsonValue(Json::Value root) throw(InvalidFile, ReaderClosed); ///< Load Json::JsonValue into this object
};
}

View File

@@ -158,10 +158,10 @@ namespace openshot
~Frame();
/// Add (or replace) pixel data to the frame (based on a solid color)
void AddColor(int width, int height, string color);
void AddColor(int new_width, int new_height, string color);
/// Add (or replace) pixel data to the frame
void AddImage(int width, int height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_);
void AddImage(int new_width, int new_height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_);
/// Add (or replace) pixel data to the frame
void AddImage(tr1::shared_ptr<QImage> new_image);

View File

@@ -99,6 +99,7 @@
#include "AudioBufferSource.h"
#include "AudioReaderSource.h"
#include "AudioResampler.h"
#include "CacheDisk.h"
#include "CacheMemory.h"
#include "ChunkReader.h"
#include "ChunkWriter.h"

View File

@@ -61,10 +61,10 @@ namespace openshot
/// Get Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
int getSpeed() const { return speed; }
/// Play the audio
/// Play the video
void Play();
/// Seek the audio thread
/// Seek the reader to a particular frame number
void Seek(int new_position);
/// Set the currently displaying frame number

View File

@@ -117,7 +117,7 @@ namespace openshot
void DrawFrameOnScene(string path, long _graphics_scene_address);
/// Get the cache object used by this reader (note: not all readers use cache)
virtual CacheMemory* GetCache() = 0;
virtual CacheBase* GetCache() = 0;
/// This method is required for all derived classes of ReaderBase, and returns the
/// openshot::Frame object, which contains the image and audio information for that

View File

@@ -32,6 +32,8 @@
#include <tr1/memory>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include "CacheBase.h"
#include "CacheDisk.h"
#include "CacheMemory.h"
#include "Color.h"
#include "Clip.h"
@@ -147,7 +149,7 @@ namespace openshot {
list<Clip*> closing_clips; ///<List of clips that need to be closed
map<Clip*, Clip*> open_clips; ///<List of 'opened' clips on this timeline
list<EffectBase*> effects; ///<List of clips on this timeline
CacheMemory final_cache; ///<Final cache of timeline frames
CacheBase *final_cache; ///<Final cache of timeline frames
/// Process a new layer of video or audio
void add_layer(tr1::shared_ptr<Frame> new_frame, Clip* source_clip, long int clip_frame_number, long int timeline_frame_number, bool is_top_clip);
@@ -228,7 +230,10 @@ namespace openshot {
list<EffectBase*> Effects() { return effects; };
/// Get the cache object used by this reader
CacheMemory* GetCache() { return &final_cache; };
CacheBase* GetCache() { return final_cache; };
/// Get the cache object used by this reader
void SetCache(CacheBase* new_cache);
/// Get an openshot::Frame object for a specific frame number of this timeline.
///

View File

@@ -188,6 +188,7 @@ SET ( OPENSHOT_SOURCE_FILES
AudioReaderSource.cpp
AudioResampler.cpp
CacheBase.cpp
CacheDisk.cpp
CacheMemory.cpp
ChunkReader.cpp
ChunkWriter.cpp

View File

@@ -37,7 +37,7 @@ CacheBase::CacheBase() : max_bytes(0) {
};
// Constructor that sets the max frames to cache
CacheBase::CacheBase(int64 max_bytes) : max_bytes(max_bytes) {
CacheBase::CacheBase(long long int max_bytes) : max_bytes(max_bytes) {
// Init the critical section
cacheCriticalSection = new CriticalSection();
};
@@ -46,7 +46,27 @@ CacheBase::CacheBase(int64 max_bytes) : max_bytes(max_bytes) {
void CacheBase::SetMaxBytesFromInfo(long int number_of_frames, int width, int height, int sample_rate, int channels)
{
// n frames X height X width X 4 colors of chars X audio channels X 4 byte floats
int64 bytes = number_of_frames * (height * width * 4 + (sample_rate * channels * 4));
long long int bytes = number_of_frames * (height * width * 4 + (sample_rate * channels * 4));
SetMaxBytes(bytes);
}
// Generate Json::JsonValue for this object
Json::Value CacheBase::JsonValue() {
// Create root json object
Json::Value root;
stringstream max_bytes_stream;
max_bytes_stream << max_bytes;
root["max_bytes"] = max_bytes_stream.str();
// return JsonValue
return root;
}
// Load Json::JsonValue into this object
void CacheBase::SetJsonValue(Json::Value root) {
// Set data from Json (if key is found)
if (!root["max_bytes"].isNull())
max_bytes = atoll(root["max_bytes"].asString().c_str());
}

529
src/CacheDisk.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -32,12 +32,18 @@ using namespace openshot;
// Default constructor, no max bytes
CacheMemory::CacheMemory() : CacheBase(0) {
// Set cache type name
cache_type = "CacheMemory";
range_version = 0;
needs_range_processing = false;
};
// Constructor that sets the max bytes to cache
CacheMemory::CacheMemory(int64 max_bytes) : CacheBase(max_bytes) {
CacheMemory::CacheMemory(long long int max_bytes) : CacheBase(max_bytes) {
// Set cache type name
cache_type = "CacheMemory";
range_version = 0;
needs_range_processing = false;
};
// Default destructor
@@ -45,12 +51,79 @@ CacheMemory::~CacheMemory()
{
frames.clear();
frame_numbers.clear();
ordered_frame_numbers.clear();
// remove critical section
delete cacheCriticalSection;
cacheCriticalSection = NULL;
}
// Calculate ranges of frames
void CacheMemory::CalculateRanges() {
// Only calculate when something has changed
if (needs_range_processing) {
// Create a scoped lock, to protect the cache from multiple threads
const GenericScopedLock<CriticalSection> lock(*cacheCriticalSection);
// Sort ordered frame #s, and calculate JSON ranges
std::sort(ordered_frame_numbers.begin(), ordered_frame_numbers.end());
// Clear existing JSON variable
ranges.clear();
ranges = Json::Value(Json::arrayValue);
// Increment range version
range_version++;
vector<long int>::iterator itr_ordered;
long int starting_frame = *ordered_frame_numbers.begin();
long int ending_frame = *ordered_frame_numbers.begin();
// Loop through all known frames (in sequential order)
for (itr_ordered = ordered_frame_numbers.begin(); itr_ordered != ordered_frame_numbers.end(); ++itr_ordered) {
long int frame_number = *itr_ordered;
if (frame_number - ending_frame > 1) {
// End of range detected
Json::Value range;
// Add JSON object with start/end attributes
// Use strings, since long ints are supported in JSON
stringstream start_str;
start_str << starting_frame;
stringstream end_str;
end_str << ending_frame;
range["start"] = start_str.str();
range["end"] = end_str.str();
ranges.append(range);
// Set new starting range
starting_frame = frame_number;
}
// Set current frame as end of range, and keep looping
ending_frame = frame_number;
}
// APPEND FINAL VALUE
Json::Value range;
// Add JSON object with start/end attributes
// Use strings, since long ints are supported in JSON
stringstream start_str;
start_str << starting_frame;
stringstream end_str;
end_str << ending_frame;
range["start"] = start_str.str();
range["end"] = end_str.str();
ranges.append(range);
// Reset needs_range_processing
needs_range_processing = false;
}
}
// Add a Frame to the cache
void CacheMemory::Add(tr1::shared_ptr<Frame> frame)
{
@@ -68,6 +141,8 @@ void CacheMemory::Add(tr1::shared_ptr<Frame> frame)
// Add frame to queue and map
frames[frame_number] = frame;
frame_numbers.push_front(frame_number);
ordered_frame_numbers.push_back(frame_number);
needs_range_processing = true;
// Clean up old frames
CleanUp();
@@ -113,12 +188,12 @@ tr1::shared_ptr<Frame> CacheMemory::GetSmallestFrame()
}
// Gets the maximum bytes value
int64 CacheMemory::GetBytes()
long long int CacheMemory::GetBytes()
{
// Create a scoped lock, to protect the cache from multiple threads
const GenericScopedLock<CriticalSection> lock(*cacheCriticalSection);
int64 total_bytes = 0;
long long int total_bytes = 0;
// Loop through frames, and calculate total bytes
deque<long int>::reverse_iterator itr;
@@ -132,24 +207,43 @@ int64 CacheMemory::GetBytes()
// Remove a specific frame
void CacheMemory::Remove(long int frame_number)
{
Remove(frame_number, frame_number);
}
// Remove range of frames
void CacheMemory::Remove(long int start_frame_number, long int end_frame_number)
{
// Create a scoped lock, to protect the cache from multiple threads
const GenericScopedLock<CriticalSection> lock(*cacheCriticalSection);
// Loop through frame numbers
deque<long int>::iterator itr;
for(itr = frame_numbers.begin(); itr != frame_numbers.end(); ++itr)
deque<long int>::iterator itr = frame_numbers.begin();
while (itr != frame_numbers.end())
{
if (*itr == frame_number)
if (*itr >= start_frame_number && *itr <= end_frame_number)
{
// erase frame number
frame_numbers.erase(itr);
break;
}
itr = frame_numbers.erase(itr++);
}else
++itr;
}
// Remove frame from map. If frame_number doesn't exist, frames.erase returns zero.
frames.erase(frame_number);
// Loop through ordered frame numbers
vector<long int>::iterator itr_ordered = ordered_frame_numbers.begin();
while (itr_ordered != ordered_frame_numbers.end())
{
if (*itr_ordered >= start_frame_number && *itr_ordered <= end_frame_number)
{
// erase frame number
frames.erase(*itr_ordered);
itr_ordered = ordered_frame_numbers.erase(itr_ordered++);
}else
++itr_ordered;
}
// Needs range processing (since cache has changed)
needs_range_processing = true;
}
// Move frame to front of queue (so it lasts longer)
@@ -161,7 +255,7 @@ void CacheMemory::MoveToFront(long int frame_number)
// Does frame exists in cache?
/* FIXME if the frame number isn't present, the loop will do nothing, so why protect it?
* Is it to save time by avoiding a loop?
* Do we really need to optmize the case where we've been given a nonexisting frame_number? */
* Do we really need to optimize the case where we've been given a nonexisting frame_number? */
if (frames.count(frame_number))
{
// Loop through frame numbers
@@ -189,6 +283,7 @@ void CacheMemory::Clear()
frames.clear();
frame_numbers.clear();
ordered_frame_numbers.clear();
}
// Count the frames in the queue
@@ -220,3 +315,67 @@ void CacheMemory::CleanUp()
}
}
}
// Generate JSON string of this object
string CacheMemory::Json() {
// Return formatted string
return JsonValue().toStyledString();
}
// Generate Json::JsonValue for this object
Json::Value CacheMemory::JsonValue() {
// Proccess range data (if anything has changed)
CalculateRanges();
// Create root json object
Json::Value root = CacheBase::JsonValue(); // get parent properties
root["type"] = cache_type;
root["ranges"] = ranges;
Json::Value version;
stringstream range_version_str;
range_version_str << range_version;
root["version"] = range_version_str.str();
// return JsonValue
return root;
}
// Load JSON string into this object
void CacheMemory::SetJson(string value) throw(InvalidJSON) {
// Parse JSON string into JSON objects
Json::Value root;
Json::Reader reader;
bool success = reader.parse( value, root );
if (!success)
// Raise exception
throw InvalidJSON("JSON could not be parsed (or is invalid)", "");
try
{
// Set all values that match
SetJsonValue(root);
}
catch (exception e)
{
// Error parsing JSON (or missing keys)
throw InvalidJSON("JSON is invalid (missing keys or invalid data types)", "");
}
}
// Load Json::JsonValue into this object
void CacheMemory::SetJsonValue(Json::Value root) throw(InvalidFile, ReaderClosed) {
// Close timeline before we do anything (this also removes all open and closing clips)
Clear();
// Set parent data
CacheBase::SetJsonValue(root);
if (!root["type"].isNull())
cache_type = root["type"].asString();
}

View File

@@ -670,11 +670,11 @@ int Frame::constrain(int color_value)
}
// Add (or replace) pixel data to the frame (based on a solid color)
void Frame::AddColor(int width, int height, string color)
void Frame::AddColor(int new_width, int new_height, string color)
{
// Create new image object, and fill with pixel data
const GenericScopedLock<CriticalSection> lock(addingImageSection);
image = tr1::shared_ptr<QImage>(new QImage(width, height, QImage::Format_RGBA8888));
image = tr1::shared_ptr<QImage>(new QImage(new_width, new_height, QImage::Format_RGBA8888));
// Fill with solid color
image->fill(QColor(QString::fromStdString(color)));
@@ -686,18 +686,18 @@ void Frame::AddColor(int width, int height, string color)
}
// Add (or replace) pixel data to the frame
void Frame::AddImage(int width, int height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_)
void Frame::AddImage(int new_width, int new_height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_)
{
// Create new buffer
const GenericScopedLock<CriticalSection> lock(addingImageSection);
int buffer_size = width * height * bytes_per_pixel;
int buffer_size = new_width * new_height * bytes_per_pixel;
qbuffer = new unsigned char[buffer_size]();
// Copy buffer data
memcpy((unsigned char*)qbuffer, pixels_, buffer_size);
// Create new image object, and fill with pixel data
image = tr1::shared_ptr<QImage>(new QImage(qbuffer, width, height, width * bytes_per_pixel, type, (QImageCleanupFunction) &openshot::Frame::cleanUpBuffer, (void*) qbuffer));
image = tr1::shared_ptr<QImage>(new QImage(qbuffer, new_width, new_height, new_width * bytes_per_pixel, type, (QImageCleanupFunction) &openshot::Frame::cleanUpBuffer, (void*) qbuffer));
// Always convert to RGBA8888 (if different)
if (image->format() != QImage::Format_RGBA8888)

View File

@@ -56,13 +56,13 @@ namespace openshot
current_display_frame = current_frame_number;
}
// Seek the audio thread
// Seek the reader to a particular frame number
void VideoCacheThread::Seek(int new_position)
{
position = new_position;
}
// Play the audio
// Play the video
void VideoCacheThread::Play() {
// Start playing
is_playing = true;

View File

@@ -57,7 +57,8 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha
info.video_length = info.fps.ToFloat() * info.duration;
// Init cache
final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels);
final_cache = new CacheMemory();
final_cache->SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels);
}
// Add an openshot::Clip to the timeline
@@ -125,7 +126,7 @@ void Timeline::apply_mapper_to_clip(Clip* clip)
void Timeline::ApplyMapperToClips()
{
// Clear all cached frames
final_cache.Clear();
final_cache->Clear();
// Loop through all clips
list<Clip*>::iterator clip_itr;
@@ -582,7 +583,7 @@ void Timeline::Close()
is_open = false;
// Clear cache
final_cache.Clear();
final_cache->Clear();
}
// Open the reader (and start consuming resources)
@@ -609,7 +610,7 @@ tr1::shared_ptr<Frame> Timeline::GetFrame(long int requested_frame) throw(Reader
requested_frame = 1;
// Check cache
tr1::shared_ptr<Frame> frame = final_cache.GetFrame(requested_frame);
tr1::shared_ptr<Frame> frame = final_cache->GetFrame(requested_frame);
if (frame) {
// Debug output
ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Cached frame found)", "requested_frame", requested_frame, "", -1, "", -1, "", -1, "", -1, "", -1);
@@ -623,7 +624,7 @@ tr1::shared_ptr<Frame> Timeline::GetFrame(long int requested_frame) throw(Reader
const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
// Check cache again (due to locking)
frame = final_cache.GetFrame(requested_frame);
frame = final_cache->GetFrame(requested_frame);
if (frame) {
// Debug output
ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Cached frame found on 2nd look)", "requested_frame", requested_frame, "", -1, "", -1, "", -1, "", -1, "", -1);
@@ -683,6 +684,7 @@ tr1::shared_ptr<Frame> Timeline::GetFrame(long int requested_frame) throw(Reader
// Create blank frame (which will become the requested frame)
tr1::shared_ptr<Frame> new_frame(tr1::shared_ptr<Frame>(new Frame(frame_number, info.width, info.height, "#000000", samples_in_frame, info.channels)));
new_frame->AddAudioSilence(samples_in_frame);
new_frame->SampleRate(info.sample_rate);
new_frame->ChannelsLayout(info.channel_layout);
@@ -748,7 +750,7 @@ tr1::shared_ptr<Frame> Timeline::GetFrame(long int requested_frame) throw(Reader
ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Add frame to cache)", "frame_number", frame_number, "info.width", info.width, "info.height", info.height, "", -1, "", -1, "", -1);
// Add final frame to cache
final_cache.Add(new_frame);
final_cache->Add(new_frame);
} // end frame loop
} // end parallel
@@ -757,7 +759,7 @@ tr1::shared_ptr<Frame> Timeline::GetFrame(long int requested_frame) throw(Reader
ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (end parallel region)", "requested_frame", requested_frame, "omp_get_thread_num()", omp_get_thread_num(), "", -1, "", -1, "", -1, "", -1);
// Return frame (or blank frame)
return final_cache.GetFrame(requested_frame);
return final_cache->GetFrame(requested_frame);
}
}
@@ -810,6 +812,12 @@ vector<Clip*> Timeline::find_intersecting_clips(long int requested_frame, int nu
return matching_clips;
}
// Get the cache object used by this reader
void Timeline::SetCache(CacheBase* new_cache) {
// Set new cache
final_cache = new_cache;
}
// Generate JSON string of this object
string Timeline::Json() {
@@ -943,9 +951,6 @@ void Timeline::SetJsonValue(Json::Value root) throw(InvalidFile, ReaderClosed) {
// Apply a special formatted JSON object, which represents a change to the timeline (insert, update, delete)
void Timeline::ApplyJsonDiff(string value) throw(InvalidJSON, InvalidJSONKey) {
// Clear internal cache (since things are about to change)
final_cache.Clear();
// Parse JSON string into JSON objects
Json::Value root;
Json::Reader reader;
@@ -982,9 +987,6 @@ void Timeline::ApplyJsonDiff(string value) throw(InvalidJSON, InvalidJSONKey) {
// Error parsing JSON (or missing keys)
throw InvalidJSON("JSON is invalid (missing keys or invalid data types)", "");
}
// Adjust cache (in case something changed)
final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels);
}
// Apply JSON diff to clips
@@ -1053,6 +1055,11 @@ void Timeline::apply_json_to_clips(Json::Value change) throw(InvalidJSONKey) {
}
}
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int new_starting_frame = change["value"]["position"].asDouble() * info.fps.ToDouble();
long int new_ending_frame = (change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble();
final_cache->Remove(new_starting_frame - 1, new_ending_frame + 1);
// Determine type of change operation
if (change_type == "insert") {
@@ -1064,14 +1071,30 @@ void Timeline::apply_json_to_clips(Json::Value change) throw(InvalidJSONKey) {
} else if (change_type == "update") {
// Update existing clip
if (existing_clip)
existing_clip->SetJsonValue(change["value"]); // Update clip properties from JSON
if (existing_clip) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_clip->Position() * info.fps.ToDouble();
long int old_ending_frame = (existing_clip->Position() + existing_clip->End() - existing_clip->Start()) * info.fps.ToDouble();
final_cache->Remove(old_starting_frame - 1, old_ending_frame + 1);
// Update clip properties from JSON
existing_clip->SetJsonValue(change["value"]);
}
} else if (change_type == "delete") {
// Remove existing clip
if (existing_clip)
RemoveClip(existing_clip); // Remove clip from timeline
if (existing_clip) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_clip->Position() * info.fps.ToDouble();
long int old_ending_frame = (existing_clip->Position() + existing_clip->End() - existing_clip->Start()) * info.fps.ToDouble();
final_cache->Remove(old_starting_frame - 1, old_ending_frame + 1);
// Remove clip from timeline
RemoveClip(existing_clip);
}
}
@@ -1124,6 +1147,11 @@ void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_ef
// Get key and type of change
string change_type = change["type"].asString();
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int new_starting_frame = change["value"]["position"].asDouble() * info.fps.ToDouble();
long int new_ending_frame = (change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble();
final_cache->Remove(new_starting_frame - 1, new_ending_frame + 1);
// Determine type of change operation
if (change_type == "insert") {
@@ -1145,14 +1173,30 @@ void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_ef
} else if (change_type == "update") {
// Update existing effect
if (existing_effect)
existing_effect->SetJsonValue(change["value"]); // Update effect properties from JSON
if (existing_effect) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_effect->Position() * info.fps.ToDouble();
long int old_ending_frame = (existing_effect->Position() + existing_effect->End() - existing_effect->Start()) * info.fps.ToDouble();
final_cache->Remove(old_starting_frame - 1, old_ending_frame + 1);
// Update effect properties from JSON
existing_effect->SetJsonValue(change["value"]);
}
} else if (change_type == "delete") {
// Remove existing effect
if (existing_effect)
RemoveEffect(existing_effect); // Remove effect from timeline
if (existing_effect) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_effect->Position() * info.fps.ToDouble();
long int old_ending_frame = (existing_effect->Position() + existing_effect->End() - existing_effect->Start()) * info.fps.ToDouble();
final_cache->Remove(old_starting_frame - 1, old_ending_frame + 1);
// Remove effect from timeline
RemoveEffect(existing_effect);
}
}
}
@@ -1167,6 +1211,9 @@ void Timeline::apply_json_to_timeline(Json::Value change) throw(InvalidJSONKey)
if (change["key"].size() >= 2)
sub_key = change["key"][(uint)1].asString();
// Clear entire cache
final_cache->Clear();
// Determine type of change operation
if (change_type == "insert" || change_type == "update") {

View File

@@ -56,6 +56,7 @@
#include "../../../include/ReaderBase.h"
#include "../../../include/WriterBase.h"
#include "../../../include/CacheBase.h"
#include "../../../include/CacheDisk.h"
#include "../../../include/CacheMemory.h"
#include "../../../include/ChannelLayouts.h"
#include "../../../include/ChunkReader.h"
@@ -117,6 +118,7 @@
%include "../../../include/ReaderBase.h"
%include "../../../include/WriterBase.h"
%include "../../../include/CacheBase.h"
%include "../../../include/CacheDisk.h"
%include "../../../include/CacheMemory.h"
%include "../../../include/ChannelLayouts.h"
%include "../../../include/ChunkReader.h"

View File

@@ -62,6 +62,7 @@ namespace tr1
#include "../../../include/ReaderBase.h"
#include "../../../include/WriterBase.h"
#include "../../../include/CacheBase.h"
#include "../../../include/CacheDisk.h"
#include "../../../include/CacheMemory.h"
#include "../../../include/ChannelLayouts.h"
#include "../../../include/ChunkReader.h"
@@ -112,6 +113,7 @@ namespace tr1
%include "../../../include/ReaderBase.h"
%include "../../../include/WriterBase.h"
%include "../../../include/CacheBase.h"
%include "../../../include/CacheDisk.h"
%include "../../../include/CacheMemory.h"
%include "../../../include/ChannelLayouts.h"
%include "../../../include/ChunkReader.h"

View File

@@ -27,6 +27,7 @@
#include "UnitTest++.h"
#include "../include/OpenShot.h"
#include "../include/Json.h"
using namespace std;
using namespace openshot;
@@ -270,3 +271,116 @@ TEST(Cache_Set_Max_Bytes)
c.SetMaxBytes(4 * 1024);
CHECK_EQUAL(4 * 1024, c.GetMaxBytes());
}
TEST(CacheDisk_Set_Max_Bytes)
{
// Create cache object (using platform /temp/ directory)
CacheDisk c("", "PPM", 1.0, 0.25);
// Add frames to disk cache
for (int i = 0; i < 20; i++)
{
// Add blank frame to the cache
tr1::shared_ptr<Frame> f(new Frame());
f->number = i;
// Add some picture data
f->AddColor(1280, 720, "Blue");
f->ResizeAudio(2, 500, 44100, LAYOUT_STEREO);
f->AddAudioSilence(500);
c.Add(f);
}
CHECK_EQUAL(0, c.GetMaxBytes()); // Cache defaults max frames to -1, unlimited frames
// Set max frames
c.SetMaxBytes(8 * 1024);
CHECK_EQUAL(8 * 1024, c.GetMaxBytes());
// Set max frames
c.SetMaxBytes(4 * 1024);
CHECK_EQUAL(4 * 1024, c.GetMaxBytes());
// Read frames from disk cache
tr1::shared_ptr<Frame> f = c.GetFrame(5);
CHECK_EQUAL(320, f->GetWidth());
CHECK_EQUAL(180, f->GetHeight());
CHECK_EQUAL(2, f->GetAudioChannelsCount());
CHECK_EQUAL(500, f->GetAudioSamplesCount());
CHECK_EQUAL(LAYOUT_STEREO, f->ChannelsLayout());
CHECK_EQUAL(44100, f->SampleRate());
}
TEST(CacheDisk_JSON)
{
// Create cache object (using platform /temp/ directory)
CacheDisk c("", "PPM", 1.0, 0.25);
// Add some frames (out of order)
tr1::shared_ptr<Frame> f3(new Frame(3, 1280, 720, "Blue", 500, 2));
c.Add(f3);
CHECK_EQUAL(1, c.JsonValue()["ranges"].size());
CHECK_EQUAL("1", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f1(new Frame(1, 1280, 720, "Blue", 500, 2));
c.Add(f1);
CHECK_EQUAL(2, c.JsonValue()["ranges"].size());
CHECK_EQUAL("2", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f2(new Frame(2, 1280, 720, "Blue", 500, 2));
c.Add(f2);
CHECK_EQUAL(1, c.JsonValue()["ranges"].size());
CHECK_EQUAL("3", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f5(new Frame(5, 1280, 720, "Blue", 500, 2));
c.Add(f5);
CHECK_EQUAL(2, c.JsonValue()["ranges"].size());
CHECK_EQUAL("4", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f4(new Frame(4, 1280, 720, "Blue", 500, 2));
c.Add(f4);
CHECK_EQUAL(1, c.JsonValue()["ranges"].size());
CHECK_EQUAL("5", c.JsonValue()["version"].asString());
}
TEST(CacheMemory_JSON)
{
// Create memory cache object
CacheMemory c;
// Add some frames (out of order)
tr1::shared_ptr<Frame> f3(new Frame(3, 1280, 720, "Blue", 500, 2));
c.Add(f3);
CHECK_EQUAL(1, c.JsonValue()["ranges"].size());
CHECK_EQUAL("1", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f1(new Frame(1, 1280, 720, "Blue", 500, 2));
c.Add(f1);
CHECK_EQUAL(2, c.JsonValue()["ranges"].size());
CHECK_EQUAL("2", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f2(new Frame(2, 1280, 720, "Blue", 500, 2));
c.Add(f2);
CHECK_EQUAL(1, c.JsonValue()["ranges"].size());
CHECK_EQUAL("3", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f5(new Frame(5, 1280, 720, "Blue", 500, 2));
c.Add(f5);
CHECK_EQUAL(2, c.JsonValue()["ranges"].size());
CHECK_EQUAL("4", c.JsonValue()["version"].asString());
// Add some frames (out of order)
tr1::shared_ptr<Frame> f4(new Frame(4, 1280, 720, "Blue", 500, 2));
c.Add(f4);
CHECK_EQUAL(1, c.JsonValue()["ranges"].size());
CHECK_EQUAL("5", c.JsonValue()["version"].asString());
}

View File

@@ -40,7 +40,7 @@ TEST(ReaderBase_Derived_Class)
{
public:
TestReader() { };
CacheMemory* GetCache() { return NULL; };
CacheBase* GetCache() { return NULL; };
tr1::shared_ptr<Frame> GetFrame(long int number) { tr1::shared_ptr<Frame> f(new Frame()); return f; }
void Close() { };
void Open() { };