updated exporter to work with new frame flow

This commit is contained in:
itsmattkc
2020-01-08 02:27:47 +11:00
parent c8baf4aa3e
commit b8aabb7e01
15 changed files with 232 additions and 43 deletions
+1 -1
View File
@@ -96,7 +96,7 @@ void Encoder::Open()
}
}
void Encoder::Write(FramePtr frame)
void Encoder::WriteFrame(FramePtr frame)
{
if (open_) {
WriteInternal(frame);
+3 -1
View File
@@ -61,7 +61,7 @@ public:
public slots:
void Open();
void Write(FramePtr frame);
void WriteFrame(FramePtr frame);
virtual void WriteAudio(const AudioRenderingParams& pcm_info, const QString& pcm_filename) = 0;
void Close();
@@ -71,6 +71,8 @@ signals:
void Closed();
void AudioComplete();
protected:
virtual bool OpenInternal() = 0;
virtual void WriteInternal(FramePtr frame) = 0;
+2
View File
@@ -89,6 +89,8 @@ void FFmpegEncoder::WriteAudio(const AudioRenderingParams &pcm_info, const QStri
pcm.close();
}
emit AudioComplete();
}
bool FFmpegEncoder::OpenInternal()
+89 -10
View File
@@ -20,7 +20,9 @@ Exporter::Exporter(ViewerOutput* viewer,
color_processor_(color_processor),
encoder_(encoder),
export_status_(false),
export_msg_(tr("Export hasn't started yet"))
export_msg_(tr("Export hasn't started yet")),
video_done_(false),
audio_done_(false)
{
connect(this, &Exporter::ExportEnded, this, &Exporter::deleteLater);
}
@@ -53,20 +55,16 @@ void Exporter::StartExporting()
video_params_.time_base(),
video_params_.format(),
video_params_.mode()));
video_backend_->SetExportMode(true);
audio_backend_->SetViewerNode(viewer_node_);
audio_backend_->SetParameters(audio_params_);
// Connect to renderers
connect(video_backend_, &VideoRenderBackend::CachedFrameReady, this, &Exporter::FrameRendered);
connect(audio_backend_, &AudioRenderBackend::QueueComplete, this, &Exporter::AudioRendered);
// Create renderers
waiting_for_frame_ = 0;
// Open encoder and wait for result
connect(encoder_, &Encoder::OpenSucceeded, this, &Exporter::EncoderOpenedSuccessfully, Qt::QueuedConnection);
connect(encoder_, &Encoder::OpenFailed, this, &Exporter::EncoderOpenFailed, Qt::QueuedConnection);
connect(encoder_, &Encoder::AudioComplete, this, &Exporter::AudioEncodeComplete, Qt::QueuedConnection);
connect(encoder_, &Encoder::Closed, encoder_, &Encoder::deleteLater, Qt::QueuedConnection);
QMetaObject::invokeMethod(encoder_,
@@ -81,6 +79,10 @@ void Exporter::SetExportMessage(const QString &s)
void Exporter::ExportSucceeded()
{
if (!audio_done_ || !video_done_) {
return;
}
Cleanup();
video_backend_->deleteLater();
@@ -99,10 +101,8 @@ void Exporter::ExportFailed()
emit ExportEnded();
}
void Exporter::FrameRendered(const rational &time, QVariant value)
void Exporter::EncodeFrame(const rational &time, QVariant value)
{
qDebug() << "Received" << time.toDouble() << "- waiting for" << waiting_for_frame_.toDouble();
if (time == waiting_for_frame_) {
bool get_cached = false;
@@ -132,7 +132,7 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
// Encode (may require re-associating alpha?)
QMetaObject::invokeMethod(encoder_,
"Write",
"WriteFrame",
Qt::QueuedConnection,
Q_ARG(FramePtr, frame));
@@ -145,6 +145,8 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
} while (cached_frames_.contains(waiting_for_frame_));
if (waiting_for_frame_ >= viewer_node_->Length()) {
video_done_ = true;
ExportSucceeded();
}
} else {
@@ -152,6 +154,23 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
}
}
void Exporter::FrameRendered(const rational &time, QVariant value)
{
qDebug() << "Received" << time.toDouble() << "- waiting for" << waiting_for_frame_.toDouble();
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
QByteArray map_hash = time_hash_map.value(time);
EncodeFrame(time, value);
QList<rational> matching_times = matched_frames_.value(map_hash);
foreach (const rational& t, matching_times) {
qDebug() << " Also matches" << t;
EncodeFrame(t, value);
}
}
void Exporter::AudioRendered()
{
// Retrieve the audio filename
@@ -167,14 +186,28 @@ void Exporter::AudioRendered()
audio_backend_->deleteLater();
}
void Exporter::AudioEncodeComplete()
{
audio_done_ = true;
ExportSucceeded();
}
void Exporter::EncoderOpenedSuccessfully()
{
// Invalidate caches
if (encoder_->params().video_enabled()) {
// First we generate the hashes so we know exactly how many frames we need
video_backend_->SetOperatingMode(VideoRenderWorker::kHashOnly);
connect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
video_backend_->InvalidateCache(0, viewer_node_->Length());
}
if (encoder_->params().audio_enabled()) {
// We set the audio backend to render the full sequence to the disk
connect(audio_backend_, &AudioRenderBackend::QueueComplete, this, &Exporter::AudioRendered);
audio_backend_->InvalidateCache(0, viewer_node_->Length());
}
}
@@ -190,3 +223,49 @@ void Exporter::EncoderClosed()
emit ProgressChanged(100);
emit ExportEnded();
}
void Exporter::VideoHashesComplete()
{
// We've got our hashes, time to kick off actual rendering
disconnect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
// Determine what frames will be hashed
TimeRangeList ranges;
ranges.append(TimeRange(0, viewer_node_->Length()));
QMap<rational, QByteArray>::const_iterator i;
QMap<rational, QByteArray>::iterator j;
// Copy time hash map
QMap<rational, QByteArray> time_hash_map = video_backend_->frame_cache()->time_hash_map();
// Check for any times that share duplicate hashes
for (i=time_hash_map.begin();i!=time_hash_map.end();i++) {
j = time_hash_map.begin();
while (j != time_hash_map.end()) {
if (i != j && i.value() == j.value()) {
// Remove the time range from this and
ranges.RemoveTimeRange(TimeRange(j.key(), j.key() + video_backend_->params().time_base()));
QList<rational> times_with_this_hash = matched_frames_.value(i.value());
times_with_this_hash.append(j.key());
matched_frames_.insert(i.value(), times_with_this_hash);
j = time_hash_map.erase(j);
} else {
j++;
}
}
}
// Set video backend to render mode but NOT hash or download
video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly);
video_backend_->SetOnlySignalLastFrameRequested(false);
connect(video_backend_, &VideoRenderBackend::CachedFrameReady, this, &Exporter::FrameRendered);
foreach (const TimeRange& range, ranges) {
video_backend_->InvalidateCache(range.in(), range.out());
}
}
+12
View File
@@ -61,6 +61,8 @@ private:
void ExportFailed();
void EncodeFrame(const rational &time, QVariant value);
ColorProcessorPtr color_processor_;
Encoder* encoder_;
@@ -73,17 +75,27 @@ private:
QHash<rational, QVariant> cached_frames_;
QHash< QByteArray, QList<rational> > matched_frames_;
bool video_done_;
bool audio_done_;
private slots:
void FrameRendered(const rational& time, QVariant value);
void AudioRendered();
void AudioEncodeComplete();
void EncoderOpenedSuccessfully();
void EncoderOpenFailed();
void EncoderClosed();
void VideoHashesComplete();
};
#endif // EXPORTER_H
+2 -4
View File
@@ -141,7 +141,7 @@ void OpenGLBackend::DecompileInternal()
shader_cache_.Clear();
}
void OpenGLBackend::EmitCachedFrameReady(const QList<rational> &times, const QVariant &value, qint64 job_time)
void OpenGLBackend::EmitCachedFrameReady(const rational &time, const QVariant &value, qint64 job_time)
{
OpenGLTextureCache::ReferencePtr ref = value.value<OpenGLTextureCache::ReferencePtr>();
OpenGLTexturePtr tex;
@@ -152,9 +152,7 @@ void OpenGLBackend::EmitCachedFrameReady(const QList<rational> &times, const QVa
tex = nullptr;
}
foreach (const rational& t, times) {
emit CachedFrameReady(t, QVariant::fromValue(tex), job_time);
}
emit CachedFrameReady(time, QVariant::fromValue(tex), job_time);
}
OpenGLTexturePtr OpenGLBackend::CopyTexture(OpenGLTexturePtr input)
+1 -1
View File
@@ -28,7 +28,7 @@ protected:
virtual void DecompileInternal() override;
virtual void EmitCachedFrameReady(const QList<rational> &times, const QVariant& value, qint64 job_time) override;
virtual void EmitCachedFrameReady(const rational &time, const QVariant& value, qint64 job_time) override;
private:
OpenGLTexturePtr CopyTexture(OpenGLTexturePtr input);
+3
View File
@@ -237,7 +237,10 @@ void RenderBackend::CacheNext()
}
if (cache_queue_.isEmpty()) {
if (AllProcessorsAreAvailable()) {
emit QueueComplete();
}
return;
}
+1 -2
View File
@@ -89,6 +89,7 @@ protected:
void QueueValueUpdate();
bool AllProcessorsAreAvailable() const;
bool WorkerIsBusy(RenderWorker* worker) const;
void SetWorkerBusyState(RenderWorker* worker, bool busy);
@@ -104,8 +105,6 @@ protected slots:
void QueueRecompile();
private:
bool AllProcessorsAreAvailable() const;
/**
* @brief Internal list of RenderProcessThreads
*/
+34 -7
View File
@@ -33,7 +33,8 @@
VideoRenderBackend::VideoRenderBackend(QObject *parent) :
RenderBackend(parent),
export_mode_(false)
operating_mode_(VideoRenderWorker::kHashRenderCache),
only_signal_last_frame_requested_(true)
{
}
@@ -71,12 +72,15 @@ const VideoRenderingParams &VideoRenderBackend::params() const
void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
{
if (!AllProcessorsAreAvailable()) {
qCritical() << "Attempted to set parameters on a backend whose workers are still busy";
return;
}
// Set new parameters
params_ = params;
// Set params on all processors
// FIXME: Undefined behavior if the processors are currently working, this may need to be delayed like the
// recompile signal
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetParameters(params_);
}
@@ -85,9 +89,23 @@ void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
RegenerateCacheID();
}
void VideoRenderBackend::SetExportMode(bool enabled)
void VideoRenderBackend::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
{
export_mode_ = enabled;
if (!AllProcessorsAreAvailable()) {
qCritical() << "Attempted to set operating mode on a backend whose workers are still busy";
return;
}
operating_mode_ = mode;
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetOperatingMode(operating_mode_);
}
}
void VideoRenderBackend::SetOnlySignalLastFrameRequested(bool enabled)
{
only_signal_last_frame_requested_ = enabled;
}
bool VideoRenderBackend::IsRendered(const rational &time) const
@@ -121,6 +139,8 @@ void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor)
{
VideoRenderWorker* video_processor = static_cast<VideoRenderWorker*>(processor);
video_processor->SetOperatingMode(operating_mode_);
connect(video_processor, &VideoRenderWorker::CompletedFrame, this, &VideoRenderBackend::ThreadCompletedFrame);
connect(video_processor, &VideoRenderWorker::HashAlreadyBeingCached, this, &VideoRenderBackend::ThreadSkippedFrame);
connect(video_processor, &VideoRenderWorker::CompletedDownload, this, &VideoRenderBackend::ThreadCompletedDownload);
@@ -206,8 +226,15 @@ TimeRange VideoRenderBackend::PopNextFrameFromQueue()
void VideoRenderBackend::ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value)
{
if (last_time_requested_ == path.in() || frame_cache_.TimeToHash(last_time_requested_) == hash) {
EmitCachedFrameReady({last_time_requested_}, value, job_time);
if (!only_signal_last_frame_requested_ || last_time_requested_ == path.in() || frame_cache_.TimeToHash(last_time_requested_) == hash) {
EmitCachedFrameReady(path.in(), value, job_time);
}
if (!(operating_mode_ & VideoRenderWorker::kDownloadOnly)) {
// If we're not downloading, the worker is done here
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
CacheNext();
}
}
+12 -7
View File
@@ -29,6 +29,7 @@
#include "render/pixelformat.h"
#include "render/rendermodes.h"
#include "videorenderframecache.h"
#include "videorenderworker.h"
/**
* @brief A multithreaded OpenGL based renderer for node systems
@@ -53,10 +54,16 @@ public:
*/
void SetParameters(const VideoRenderingParams &params);
void SetExportMode(bool enabled);
void SetOperatingMode(const VideoRenderWorker::OperatingMode& mode);
void SetOnlySignalLastFrameRequested(bool enabled);
bool IsRendered(const rational& time) const;
VideoRenderFrameCache* frame_cache();
const VideoRenderingParams& params() const;
protected:
/**
* @brief Allocate and start the multithreaded backend
@@ -85,10 +92,6 @@ protected:
virtual TimeRange PopNextFrameFromQueue() override;
VideoRenderFrameCache* frame_cache();
const VideoRenderingParams& params() const;
/**
* @brief Internal function for generating the cache ID
*/
@@ -98,9 +101,9 @@ protected:
virtual void ConnectWorkerToThis(RenderWorker* processor) override;
virtual void EmitCachedFrameReady(const QList<rational> &times, const QVariant& value, qint64 job_time) = 0;
virtual void EmitCachedFrameReady(const rational &time, const QVariant& value, qint64 job_time) = 0;
bool export_mode_;
VideoRenderWorker::OperatingMode operating_mode_;
signals:
void CachedFrameReady(const rational& time, QVariant value, qint64 job_time);
@@ -121,6 +124,8 @@ private:
rational last_time_requested_;
bool only_signal_last_frame_requested_;
private slots:
void ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value);
void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash);
@@ -109,6 +109,11 @@ QList<rational> VideoRenderFrameCache::FramesWithHash(const QByteArray &hash)
return times;
}
const QMap<rational, QByteArray> &VideoRenderFrameCache::time_hash_map() const
{
return time_hash_map_;
}
QString VideoRenderFrameCache::CachePathName(const QByteArray &hash) const
{
QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_);
@@ -45,6 +45,8 @@ public:
QList<rational> FramesWithHash(const QByteArray& hash);
const QMap<rational, QByteArray>& time_hash_map() const;
private:
QMap<rational, QByteArray> time_hash_map_;
+27 -5
View File
@@ -8,7 +8,8 @@
VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) :
RenderWorker(parent),
frame_cache_(frame_cache)
frame_cache_(frame_cache),
operating_mode_(kHashRenderCache)
{
}
@@ -22,16 +23,27 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
{
// Get hash of node graph
// We use SHA-1 for speed (benchmarks show it's the fastest hash available to us)
QByteArray hash;
if (operating_mode_ & kHashOnly) {
QCryptographicHash hasher(QCryptographicHash::Sha1);
HashNodeRecursively(&hasher, path.node(), path.in());
QByteArray hash = hasher.result();
hash = hasher.result();
}
NodeValueTable value;
if (frame_cache_->HasHash(hash)) {
if (!(operating_mode_ & kRenderOnly)) {
// Emit only the hash
emit CompletedDownload(path, job_time, hash);
} else if ((operating_mode_ & kHashOnly) && frame_cache_->HasHash(hash)) {
// We've already cached this hash, no need to continue
emit HashAlreadyExists(path, job_time, hash);
} else if (frame_cache_->TryCache(path.in(), hash)) {
} else if (!(operating_mode_ & kHashOnly) || frame_cache_->TryCache(path.in(), hash)) {
// This hash is available for us to cache, start traversing graph
value = ProcessNode(path);
@@ -42,17 +54,22 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
emit CompletedFrame(path, job_time, hash, texture);
// If we actually have a texture, download it into the disk cache
if (!texture.isNull()) {
if ((operating_mode_ & kDownloadOnly) && !texture.isNull()) {
Download(path, hash, texture, frame_cache_->CachePathName(hash));
}
frame_cache_->RemoveHashFromCurrentlyCaching(hash);
if (operating_mode_ & kDownloadOnly) {
// Signal that this job is complete
emit CompletedDownload(path, job_time, hash);
}
} else {
// Another thread must be caching this already, nothing to be done
emit HashAlreadyBeingCached(path, job_time, hash);
}
return value;
@@ -161,6 +178,11 @@ void VideoRenderWorker::SetParameters(const VideoRenderingParams &video_params)
ParametersChangedEvent();
}
void VideoRenderWorker::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
{
operating_mode_ = mode;
}
bool VideoRenderWorker::InitInternal()
{
download_buffer_.resize(PixelService::GetBufferSize(video_params().format(), video_params().effective_width(), video_params().effective_height()));
+33
View File
@@ -12,10 +12,41 @@
class VideoRenderWorker : public RenderWorker {
Q_OBJECT
public:
/**
* @brief VideoRenderWorker uses hashes to recognize frames that are identical to others in the render queue
*
* This mode can modify the behavior of the worker, either to disable hash verification or only generate hashes and
* not render. These are only used in the context of exporting.
*
* These are also flags that can be or'd together, mostly for the convenience of checking which functionalities are
* disabled and enabled.
*/
enum OperatingMode {
/// Generate hashes but don't render or download anything
kHashOnly = 0x1,
/// Render but don't download or hash
kRenderOnly = 0x2,
/// Enable download (NEVER USE THIS ON ITS OWN, this is only here for checking flags, download-only mode makes no sense)
kDownloadOnly = 0x4,
/// Use hash verification and render, but don't cache any frames to disk
kHashAndRenderOnly = 0x3,
/// Render and download, but don't use hash verification
kRenderAndCacheOnly = 0x6,
/// Render and use hashes to identify exact matches (default)
kHashRenderCache = 0x7
};
VideoRenderWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr);
void SetParameters(const VideoRenderingParams& video_params);
void SetOperatingMode(const OperatingMode& mode);
signals:
void CompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value);
@@ -57,6 +88,8 @@ private:
QByteArray download_buffer_;
OperatingMode operating_mode_;
private slots:
};