merged fxrestructure

This commit is contained in:
itsmattkc
2019-03-16 20:14:36 +11:00
289 changed files with 8975 additions and 6556 deletions
+8 -6
View File
@@ -20,16 +20,16 @@
#include "audio.h"
#include "oliveglobal.h"
#include "global/global.h"
#include "project/sequence.h"
#include "timeline/sequence.h"
#include "panels/panels.h"
#include "io/config.h"
#include "global/config.h"
#include "ui/audiomonitor.h"
#include "rendering/renderfunctions.h"
#include "debug.h"
#include "global/debug.h"
#include <QApplication>
#include <QAudioOutput>
@@ -50,9 +50,11 @@ bool audio_scrub = false;
QMutex audio_write_lock;
QAudioInput* audio_input = nullptr;
QFile output_recording;
bool audio_rendering = false;
bool recording = false;
bool audio_rendering = false;
int audio_rendering_rate = 0;
qint8 audio_ibuffer[audio_ibuffer_size];
qint64 audio_ibuffer_read = 0;
long audio_ibuffer_frame = 0;
@@ -152,7 +154,7 @@ void clear_audio_ibuffer() {
}
int current_audio_freq() {
return audio_rendering ? olive::ActiveSequence->audio_frequency : audio_output->format().sampleRate();
return audio_rendering ? audio_rendering_rate : audio_output->format().sampleRate();
}
qint64 get_buffer_offset_from_frame(double framerate, long frame) {
+12 -11
View File
@@ -29,22 +29,22 @@
#include <QAudioOutput>
#include <QComboBox>
#include "project/sequence.h"
#include "timeline/sequence.h"
class AudioSenderThread : public QThread {
Q_OBJECT
Q_OBJECT
public:
AudioSenderThread();
void run();
void stop();
QWaitCondition cond;
bool close;
QMutex lock;
AudioSenderThread();
void run();
void stop();
QWaitCondition cond;
bool close;
QMutex lock;
public slots:
void notifyReceiver();
void notifyReceiver();
private:
QVector<qint16> samples;
int send_audio_to_output(qint64 offset, int max);
QVector<qint16> samples;
int send_audio_to_output(qint64 offset, int max);
};
double log_volume(double linear);
@@ -62,6 +62,7 @@ extern double audio_ibuffer_timecode;
extern bool audio_scrub;
extern bool recording;
extern bool audio_rendering;
extern int audio_rendering_rate;
void clear_audio_ibuffer();
int current_audio_freq();
+22 -13
View File
@@ -20,8 +20,14 @@
#include "cacher.h"
#define __STDC_FORMAT_MACROS
#ifndef __STDC_FORMAT_MACROS
// For some reason the Windows AppVeyor build fails to find PRIx64 without this definition and including
// <inttypes.h> Maybe something to do with the GCC version being used? Either way, that's why it's here.
#define __STDC_FORMAT_MACROS 1
#endif
#include <inttypes.h>
#include <QOpenGLFramebufferObject>
#include <QtMath>
#include <QAudioOutput>
@@ -31,8 +37,8 @@
#include "rendering/audio.h"
#include "rendering/renderfunctions.h"
#include "panels/panels.h"
#include "io/config.h"
#include "debug.h"
#include "global/config.h"
#include "global/debug.h"
// Enable verbose audio messages - good for debugging reversed audio
//#define AUDIOWARNINGS
@@ -50,8 +56,10 @@ void apply_audio_effects(Clip* clip, double timecode_start, AVFrame* frame, int
timecode_end = timecode_start + bytes_to_seconds(nb_bytes, frame->channels, frame->sample_rate);
for (int j=0;j<clip->effects.size();j++) {
EffectPtr e = clip->effects.at(j);
if (e->is_enabled()) e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2);
Effect* e = clip->effects.at(j).get();
if (e->IsEnabled()) {
e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2);
}
}
if (clip->opening_transition != nullptr) {
if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
@@ -174,6 +182,7 @@ void Cacher::CacheAudioWorker() {
// retrieve frame
bool new_frame = false;
while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_bytes) && nb_bytes > 0) {
// no more audio left in frame, get a new one
if (!reached_end) {
int loop = 0;
@@ -181,7 +190,8 @@ void Cacher::CacheAudioWorker() {
if (reverse_audio && !audio_just_reset) {
avcodec_flush_buffers(codecCtx);
reached_end = false;
int64_t backtrack_seek = qMax(reverse_target_ - static_cast<int64_t>(av_q2d(av_inv_q(stream->time_base))), static_cast<int64_t>(0));
int64_t backtrack_seek = qMax(reverse_target_ - static_cast<int64_t>(av_q2d(av_inv_q(stream->time_base))),
static_cast<int64_t>(0));
av_seek_frame(formatCtx, stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD);
#ifdef AUDIOWARNINGS
if (backtrack_seek == 0) {
@@ -333,7 +343,10 @@ void Cacher::CacheAudioWorker() {
if (audio_just_reset) {
// get precise sample offset for the elected clip_in from this audio frame
double target_sts = playhead_to_clip_seconds(clip, audio_target_frame);
double frame_sts = ((frame->pts - stream->start_time) * timebase);
int64_t stream_start = qMax(static_cast<int64_t>(0), stream->start_time);
double frame_sts = ((frame->pts - stream_start) * timebase);
int nb_samples = qRound64((target_sts - frame_sts)*current_audio_freq());
frame_sample_index_ = nb_samples * 4;
#ifdef AUDIOWARNINGS
@@ -381,10 +394,6 @@ void Cacher::CacheAudioWorker() {
if (new_frame) {
apply_audio_effects(clip, bytes_to_seconds(audio_buffer_write, 2, current_audio_freq()) + audio_ibuffer_timecode + ((double)clip->clip_in(true)/clip->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests_);
}
} else {
// shouldn't ever get here
qCritical() << "Tried to cache a non-footage/tone clip";
return;
}
// mix audio into internal buffer
@@ -833,7 +842,7 @@ void Cacher::OpenWorker() {
}
} else if (clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
// opens file resource for FFmpeg and prepares Clip struct for playback
FootagePtr m = clip->media()->to_footage();
Footage* m = clip->media()->to_footage();
// byte array for retriving raw bytes from QString URL
QByteArray ba;
@@ -951,7 +960,7 @@ void Cacher::OpenWorker() {
// set up cache
queue_.append(av_frame_alloc());
// if (clip->reverse) {
if (true) {
AVFrame* reverse_frame = av_frame_alloc();
+558
View File
@@ -0,0 +1,558 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "exportthread.h"
#include "global/global.h"
#include "timeline/sequence.h"
#include "panels/panels.h"
#include "ui/viewerwidget.h"
#include "rendering/renderthread.h"
#include "rendering/renderfunctions.h"
#include "rendering/audio.h"
#include "ui/mainwindow.h"
#include "global/debug.h"
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
}
#include <QApplication>
#include <QOffscreenSurface>
#include <QOpenGLFramebufferObject>
#include <QOpenGLPaintDevice>
#include <QPainter>
ExportThread::ExportThread(const ExportParams &iparams,
const VideoCodecParams& ivparams,
QObject *parent) :
QThread(parent)
{
params = iparams;
vcodec_params = ivparams;
continueEncode = true;
surface.create();
fmt_ctx = nullptr;
video_stream = nullptr;
vcodec = nullptr;
vcodec_ctx = nullptr;
video_frame = nullptr;
sws_ctx = nullptr;
audio_stream = nullptr;
acodec = nullptr;
audio_frame = nullptr;
swr_frame = nullptr;
acodec_ctx = nullptr;
swr_ctx = nullptr;
vpkt_alloc = false;
apkt_alloc = false;
}
bool ExportThread::encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream, bool rescale) {
ret = avcodec_send_frame(codec_ctx, frame);
if (ret < 0) {
qCritical() << "Failed to send frame to encoder." << ret;
export_error = tr("failed to send frame to encoder (%1)").arg(QString::number(ret));
return false;
}
while (ret >= 0) {
ret = avcodec_receive_packet(codec_ctx, packet);
if (ret == AVERROR(EAGAIN)) {
return true;
} else if (ret < 0) {
if (ret != AVERROR_EOF) {
qCritical() << "Failed to receive packet from encoder." << ret;
export_error = tr("failed to receive packet from encoder (%1)").arg(QString::number(ret));
}
return false;
}
packet->stream_index = stream->index;
if (rescale) av_packet_rescale_ts(packet, codec_ctx->time_base, stream->time_base);
av_interleaved_write_frame(ofmt_ctx, packet);
av_packet_unref(packet);
}
return true;
}
bool ExportThread::setupVideo() {
// if video is disabled, no setup necessary
if (!params.video_enabled) return true;
// find video encoder
vcodec = avcodec_find_encoder(static_cast<enum AVCodecID>(params.video_codec));
if (!vcodec) {
qCritical() << "Could not find video encoder";
export_error = tr("could not video encoder for %1").arg(QString::number(params.video_codec));
return false;
}
// create video stream
video_stream = avformat_new_stream(fmt_ctx, vcodec);
video_stream->id = 0;
if (!video_stream) {
qCritical() << "Could not allocate video stream";
export_error = tr("could not allocate video stream");
return false;
}
// allocate context
// vcodec_ctx = video_stream->codec;
vcodec_ctx = avcodec_alloc_context3(vcodec);
if (!vcodec_ctx) {
qCritical() << "Could not allocate video encoding context";
export_error = tr("could not allocate video encoding context");
return false;
}
// setup context
vcodec_ctx->codec_id = static_cast<enum AVCodecID>(params.video_codec);
vcodec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
vcodec_ctx->width = params.video_width;
vcodec_ctx->height = params.video_height;
vcodec_ctx->sample_aspect_ratio = {1, 1};
vcodec_ctx->pix_fmt = static_cast<AVPixelFormat>(vcodec_params.pix_fmt);
vcodec_ctx->framerate = av_d2q(params.video_frame_rate, INT_MAX);
if (params.video_compression_type == COMPRESSION_TYPE_CBR) {
vcodec_ctx->bit_rate = qRound(params.video_bitrate * 1000000);
}
vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate);
video_stream->time_base = vcodec_ctx->time_base;
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
vcodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
switch (vcodec_ctx->codec_id) {
/// H.264 specific settings
case AV_CODEC_ID_H264:
case AV_CODEC_ID_H265:
switch (params.video_compression_type) {
case COMPRESSION_TYPE_CFR:
av_opt_set(vcodec_ctx->priv_data, "crf", QString::number(static_cast<int>(params.video_bitrate)).toUtf8(), AV_OPT_SEARCH_CHILDREN);
break;
}
break;
}
AVDictionary* opts = nullptr;
if (vcodec_params.threads == 0) {
av_dict_set(&opts, "threads", "auto", 0);
} else {
av_dict_set(&opts, "threads", QString::number(vcodec_params.threads).toUtf8(), 0);
}
ret = avcodec_open2(vcodec_ctx, vcodec, &opts);
if (ret < 0) {
qCritical() << "Could not open output video encoder." << ret;
export_error = tr("could not open output video encoder (%1)").arg(QString::number(ret));
return false;
}
// copy video encoder parameters to output stream
ret = avcodec_parameters_from_context(video_stream->codecpar, vcodec_ctx);
if (ret < 0) {
qCritical() << "Could not copy video encoder parameters to output stream." << ret;
export_error = tr("could not copy video encoder parameters to output stream (%1)").arg(QString::number(ret));
return false;
}
// create AVFrame
video_frame = av_frame_alloc();
av_frame_make_writable(video_frame);
video_frame->format = AV_PIX_FMT_RGBA;
video_frame->width = olive::ActiveSequence->width;
video_frame->height = olive::ActiveSequence->height;
av_frame_get_buffer(video_frame, 0);
av_init_packet(&video_pkt);
sws_ctx = sws_getContext(
olive::ActiveSequence->width,
olive::ActiveSequence->height,
AV_PIX_FMT_RGBA,
params.video_width,
params.video_height,
vcodec_ctx->pix_fmt,
SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr
);
return true;
}
bool ExportThread::setupAudio() {
// if audio is disabled, no setup necessary
if (!params.audio_enabled) return true;
// find encoder
acodec = avcodec_find_encoder(static_cast<AVCodecID>(params.audio_codec));
if (!acodec) {
qCritical() << "Could not find audio encoder";
export_error = tr("could not audio encoder for %1").arg(QString::number(params.audio_codec));
return false;
}
// allocate audio stream
audio_stream = avformat_new_stream(fmt_ctx, acodec);
audio_stream->id = 1;
if (!audio_stream) {
qCritical() << "Could not allocate audio stream";
export_error = tr("could not allocate audio stream");
return false;
}
// allocate context
// acodec_ctx = audio_stream->codec;
acodec_ctx = avcodec_alloc_context3(acodec);
if (!acodec_ctx) {
qCritical() << "Could not find allocate audio encoding context";
export_error = tr("could not allocate audio encoding context");
return false;
}
// set sample rate to use for project
audio_rendering_rate = params.audio_sampling_rate;
// setup context
acodec_ctx->codec_id = static_cast<AVCodecID>(params.audio_codec);
acodec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
acodec_ctx->sample_rate = params.audio_sampling_rate;
acodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is what the user sets the output audio to)
acodec_ctx->channels = av_get_channel_layout_nb_channels(acodec_ctx->channel_layout);
acodec_ctx->sample_fmt = acodec->sample_fmts[0];
acodec_ctx->bit_rate = params.audio_bitrate * 1000;
acodec_ctx->time_base.num = 1;
acodec_ctx->time_base.den = params.audio_sampling_rate;
audio_stream->time_base = acodec_ctx->time_base;
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
acodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
// open encoder
ret = avcodec_open2(acodec_ctx, acodec, nullptr);
if (ret < 0) {
qCritical() << "Could not open output audio encoder." << ret;
export_error = tr("could not open output audio encoder (%1)").arg(QString::number(ret));
return false;
}
// copy params to output stream
ret = avcodec_parameters_from_context(audio_stream->codecpar, acodec_ctx);
if (ret < 0) {
qCritical() << "Could not copy audio encoder parameters to output stream." << ret;
export_error = tr("could not copy audio encoder parameters to output stream (%1)").arg(QString::number(ret));
return false;
}
// init audio resampler context
swr_ctx = swr_alloc_set_opts(
nullptr,
acodec_ctx->channel_layout,
acodec_ctx->sample_fmt,
acodec_ctx->sample_rate,
olive::ActiveSequence->audio_layout,
AV_SAMPLE_FMT_S16,
acodec_ctx->sample_rate,
0,
nullptr
);
swr_init(swr_ctx);
// initialize raw audio frame
audio_frame = av_frame_alloc();
audio_frame->sample_rate = acodec_ctx->sample_rate;
audio_frame->nb_samples = acodec_ctx->frame_size;
if (audio_frame->nb_samples == 0) {
// FIXME: Magic number. I don't know what to put here and truthfully I don't even know if it matters.
audio_frame->nb_samples = 256;
}
audio_frame->format = AV_SAMPLE_FMT_S16;
audio_frame->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is whatever format they're held in the internal buffer)
audio_frame->channels = av_get_channel_layout_nb_channels(audio_frame->channel_layout);
av_frame_make_writable(audio_frame);
ret = av_frame_get_buffer(audio_frame, 0);
if (ret < 0) {
qCritical() << "Could not allocate audio buffer." << ret;
export_error = tr("could not allocate audio buffer (%1)").arg(QString::number(ret));
return false;
}
aframe_bytes = av_samples_get_buffer_size(nullptr, audio_frame->channels, audio_frame->nb_samples, static_cast<AVSampleFormat>(audio_frame->format), 0);
av_init_packet(&audio_pkt);
// init converted audio frame
swr_frame = av_frame_alloc();
swr_frame->channel_layout = acodec_ctx->channel_layout;
swr_frame->channels = acodec_ctx->channels;
swr_frame->sample_rate = acodec_ctx->sample_rate;
swr_frame->format = acodec_ctx->sample_fmt;
swr_frame->nb_samples = acodec_ctx->frame_size;
av_frame_get_buffer(swr_frame, 0);
av_frame_make_writable(swr_frame);
return true;
}
bool ExportThread::setupContainer() {
avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, c_filename);
if (!fmt_ctx) {
qCritical() << "Could not create output context";
export_error = tr("could not create output format context");
return false;
}
//av_dump_format(fmt_ctx, 0, c_filename, 1);
ret = avio_open(&fmt_ctx->pb, c_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
qCritical() << "Could not open output file." << ret;
export_error = tr("could not open output file (%1)").arg(QString::number(ret));
return false;
}
return true;
}
void ExportThread::run() {
panel_sequence_viewer->pause();
panel_sequence_viewer->seek(params.start_frame);
// copy filename
QByteArray ba = params.filename.toUtf8();
c_filename = new char[ba.size()+1];
strcpy(c_filename, ba.data());
continueEncode = setupContainer();
if (params.video_enabled && continueEncode) continueEncode = setupVideo();
if (params.audio_enabled && continueEncode) continueEncode = setupAudio();
if (continueEncode) {
ret = avformat_write_header(fmt_ctx, nullptr);
if (ret < 0) {
qCritical() << "Could not write output file header." << ret;
export_error = tr("could not write output file header (%1)").arg(QString::number(ret));
continueEncode = false;
}
}
long file_audio_samples = 0;
qint64 start_time, frame_time, avg_time, eta, total_time = 0;
long remaining_frames, frame_count = 1;
RenderThread* renderer = panel_sequence_viewer->viewer_widget->get_renderer();
disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint()));
connect(renderer, SIGNAL(ready()), this, SLOT(wake()));
mutex.lock();
while (olive::ActiveSequence->playhead <= params.end_frame && continueEncode) {
start_time = QDateTime::currentMSecsSinceEpoch();
if (params.audio_enabled) {
compose_audio(nullptr, olive::ActiveSequence.get(), 1, true);
}
if (params.video_enabled) {
do {
// TODO optimize by rendering the next frame while encoding the last
renderer->start_render(nullptr, olive::ActiveSequence.get(), nullptr, video_frame->data[0], video_frame->linesize[0]/4);
waitCond.wait(&mutex);
if (!continueEncode) break;
} while (renderer->did_texture_fail());
if (!continueEncode) break;
}
// encode last frame while rendering next frame
double timecode_secs = double(olive::ActiveSequence->playhead - params.start_frame) / olive::ActiveSequence->frame_rate;
if (params.video_enabled) {
// create sws_frame for converting pixel format
//
// - I'm not sure why, but we have to alloc/free sws_frame every frame, or it breaks GIF exporting.
// - (i.e. GIFs get stuck on the first frame)
// - The same problem/solution can be seen here: https://stackoverflow.com/a/38997739
// - Perhaps this is the intended way to use swscale, but it seems inefficient.
// - Anyway, here we are.
//
sws_frame = av_frame_alloc();
sws_frame->format = vcodec_ctx->pix_fmt;
sws_frame->width = params.video_width;
sws_frame->height = params.video_height;
av_frame_get_buffer(sws_frame, 0);
// convert pixel format to format expected by the encoder
sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize);
sws_frame->pts = qRound(timecode_secs/av_q2d(video_stream->time_base));
// send converted frame to encoder
if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream, false)) continueEncode = false;
av_frame_free(&sws_frame);
}
if (params.audio_enabled) {
// do we need to encode more audio samples?
while (continueEncode && file_audio_samples <= (timecode_secs*params.audio_sampling_rate)) {
// copy samples from audio buffer to AVFrame
int adjusted_read = audio_ibuffer_read%audio_ibuffer_size;
int copylen = qMin(aframe_bytes, audio_ibuffer_size-adjusted_read);
memcpy(audio_frame->data[0], audio_ibuffer+adjusted_read, copylen);
memset(audio_ibuffer+adjusted_read, 0, copylen);
audio_ibuffer_read += copylen;
if (copylen < aframe_bytes) {
// copy remainder
int remainder_len = aframe_bytes-copylen;
memcpy(audio_frame->data[0]+copylen, audio_ibuffer, remainder_len);
memset(audio_ibuffer, 0, remainder_len);
audio_ibuffer_read += remainder_len;
}
// convert to export sample format
swr_convert_frame(swr_ctx, swr_frame, audio_frame);
swr_frame->pts = file_audio_samples;
// send to encoder
if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream, true)) continueEncode = false;
file_audio_samples += swr_frame->nb_samples;
}
}
// generating encoding statistics (time it took to encode this frame/estimated remaining time)
frame_time = (QDateTime::currentMSecsSinceEpoch()-start_time);
total_time += frame_time;
remaining_frames = (params.end_frame - olive::ActiveSequence->playhead);
avg_time = (total_time/frame_count);
eta = (remaining_frames*avg_time);
emit progress_changed(qRound((double(olive::ActiveSequence->playhead - params.start_frame) / double(params.end_frame - params.start_frame)) * 100.0), eta);
olive::ActiveSequence->playhead++;
frame_count++;
}
disconnect(renderer, SIGNAL(ready()), this, SLOT(wake()));
connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint()));
mutex.unlock();
if (continueEncode) {
if (params.video_enabled) vpkt_alloc = true;
if (params.audio_enabled) apkt_alloc = true;
}
olive::Global->set_rendering_state(false);
if (params.audio_enabled && continueEncode) {
// flush swresample
do {
swr_convert_frame(swr_ctx, swr_frame, nullptr);
if (swr_frame->nb_samples == 0) break;
swr_frame->pts = file_audio_samples;
if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream, true)) continueEncode = false;
file_audio_samples += swr_frame->nb_samples;
} while (swr_frame->nb_samples > 0);
}
bool continueVideo = true;
bool continueAudio = true;
if (continueEncode) {
// flush remaining packets
while (continueVideo && continueAudio) {
if (continueVideo && params.video_enabled) continueVideo = encode(fmt_ctx, vcodec_ctx, nullptr, &video_pkt, video_stream, false);
if (continueAudio && params.audio_enabled) continueAudio = encode(fmt_ctx, acodec_ctx, nullptr, &audio_pkt, audio_stream, true);
}
ret = av_write_trailer(fmt_ctx);
if (ret < 0) {
qCritical() << "Could not write output file trailer." << ret;
export_error = tr("could not write output file trailer (%1)").arg(QString::number(ret));
continueEncode = false;
}
if (continueEncode) {
emit progress_changed(100, 0);
}
}
avio_closep(&fmt_ctx->pb);
if (vpkt_alloc) av_packet_unref(&video_pkt);
if (video_frame != nullptr) av_frame_free(&video_frame);
if (vcodec_ctx != nullptr) {
avcodec_close(vcodec_ctx);
avcodec_free_context(&vcodec_ctx);
}
if (apkt_alloc) av_packet_unref(&audio_pkt);
if (audio_frame != nullptr) av_frame_free(&audio_frame);
if (acodec_ctx != nullptr) {
avcodec_close(acodec_ctx);
avcodec_free_context(&acodec_ctx);
}
avformat_free_context(fmt_ctx);
if (sws_ctx != nullptr) {
sws_freeContext(sws_ctx);
}
if (swr_ctx != nullptr) {
av_frame_free(&swr_frame);
swr_free(&swr_ctx);
}
delete [] c_filename;
}
const QString &ExportThread::getError() {
return export_error;
}
void ExportThread::wake() {
mutex.lock();
waitCond.wakeAll();
mutex.unlock();
}
+126
View File
@@ -0,0 +1,126 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef EXPORTTHREAD_H
#define EXPORTTHREAD_H
#include <QThread>
#include <QOffscreenSurface>
#include <QMutex>
#include <QWaitCondition>
struct AVFormatContext;
struct AVCodecContext;
struct AVFrame;
struct AVPacket;
struct AVStream;
struct AVCodec;
struct SwsContext;
struct SwrContext;
extern "C" {
#include <libavcodec/avcodec.h>
}
#define COMPRESSION_TYPE_CBR 0
#define COMPRESSION_TYPE_CFR 1
#define COMPRESSION_TYPE_TARGETSIZE 2
#define COMPRESSION_TYPE_TARGETBR 3
// structs that store parameters passed from the export dialogs to this thread
struct ExportParams {
// export parameters
QString filename;
bool video_enabled;
int video_codec;
int video_width;
int video_height;
double video_frame_rate;
int video_compression_type;
double video_bitrate;
bool audio_enabled;
int audio_codec;
int audio_sampling_rate;
int audio_bitrate;
long start_frame;
long end_frame;
};
struct VideoCodecParams {
int pix_fmt;
int threads;
};
class ExportThread : public QThread {
Q_OBJECT
public:
ExportThread(const ExportParams& iparams, const VideoCodecParams& ivparams, QObject* parent = nullptr);
void run();
const QString& getError();
QOffscreenSurface surface;
bool continueEncode;
signals:
void progress_changed(int value, qint64 remaining_ms);
public slots:
void wake();
private:
bool encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream, bool rescale);
bool setupVideo();
bool setupAudio();
bool setupContainer();
// params imported from dialogs
ExportParams params;
VideoCodecParams vcodec_params;
AVFormatContext* fmt_ctx;
AVStream* video_stream;
AVCodec* vcodec;
AVCodecContext* vcodec_ctx;
AVFrame* video_frame;
AVFrame* sws_frame;
SwsContext* sws_ctx;
AVStream* audio_stream;
AVCodec* acodec;
AVFrame* audio_frame;
AVFrame* swr_frame;
AVCodecContext* acodec_ctx;
AVPacket video_pkt;
AVPacket audio_pkt;
SwrContext* swr_ctx;
bool vpkt_alloc;
bool apkt_alloc;
int aframe_bytes;
int ret;
char* c_filename;
QMutex mutex;
QWaitCondition waitCond;
QString export_error;
};
#endif // EXPORTTHREAD_H
+38 -30
View File
@@ -34,25 +34,23 @@ extern "C" {
namespace OCIO = OCIO_NAMESPACE;
#endif
#include "project/clip.h"
#include "project/sequence.h"
#include "timeline/clip.h"
#include "timeline/sequence.h"
#include "project/media.h"
#include "project/effect.h"
#include "effects/effect.h"
#include "project/footage.h"
#include "project/transition.h"
#include "effects/transition.h"
#include "ui/collapsiblewidget.h"
#include "rendering/audio.h"
#include "io/math.h"
#include "io/config.h"
#include "global/math.h"
#include "global/config.h"
#include "panels/timeline.h"
#include "panels/viewer.h"
const int kMaximumRetryCount = 10;
void full_blit() {
glPushMatrix();
glLoadIdentity();
@@ -114,12 +112,12 @@ void process_effect(Clip* c,
bool& fbo_switcher,
bool& texture_failed,
int data) {
if (e->is_enabled()) {
if (e->enable_coords) {
if (e->IsEnabled()) {
if (e->Flags() & Effect::CoordsFlag) {
e->process_coords(timecode, coords, data);
}
bool can_process_shaders = (e->enable_shader && olive::CurrentRuntimeConfig.shaders_are_enabled);
if (can_process_shaders || e->enable_superimpose) {
bool can_process_shaders = ((e->Flags() & Effect::ShaderFlag) && olive::CurrentRuntimeConfig.shaders_are_enabled);
if (can_process_shaders || (e->Flags() & Effect::SuperimposeFlag)) {
e->startEffect();
if (can_process_shaders && e->is_glsl_linked()) {
for (int i=0;i<e->getIterations();i++) {
@@ -128,7 +126,7 @@ void process_effect(Clip* c,
fbo_switcher = !fbo_switcher;
}
}
if (e->enable_superimpose) {
if (e->Flags() & Effect::SuperimposeFlag) {
GLuint superimpose_texture = e->process_superimpose(timecode);
if (superimpose_texture == 0) {
@@ -159,12 +157,12 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
GLuint final_fbo = params.main_buffer;
SequencePtr s = params.seq;
Sequence* s = params.seq;
long playhead = s->playhead;
if (!params.nests.isEmpty()) {
for (int i=0;i<params.nests.size();i++) {
s = params.nests.at(i)->media()->to_sequence();
s = params.nests.at(i)->media()->to_sequence().get();
playhead += params.nests.at(i)->clip_in(true) - params.nests.at(i)->timeline_in(true);
playhead = rescale_frame_number(playhead, params.nests.at(i)->sequence->frame_rate, s->frame_rate);
}
@@ -194,7 +192,7 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
// is the clip a "footage" clip?
if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
FootagePtr m = c->media()->to_footage();
Footage* m = c->media()->to_footage();
// does the clip have a valid media source?
if (!m->invalid && !(c->track() >= 0 && !is_audio_device_set())) {
@@ -410,14 +408,6 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
coords.blendmode = -1;
coords.opacity = 1.0;
// if auto-scale is enabled, auto-scale the clip
if (c->autoscaled() && (video_width != s->width && video_height != s->height)) {
float width_multiplier = float(s->width) / float(video_width);
float height_multiplier = float(s->height) / float(video_height);
float scale_multiplier = qMin(width_multiplier, height_multiplier);
glScalef(scale_multiplier, scale_multiplier, 1);
}
// == EFFECT CODE START ==
// get current sequence time in seconds (used for effects)
@@ -428,10 +418,7 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
Effect* e = c->effects.at(j).get();
process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone);
if (e == params.gizmos) {
e->gizmo_draw(timecode, coords); // set correct gizmo coords
e->gizmo_world_to_screen(); // convert gizmo coords to screen coords
}
}
// if the clip has an opening transition, process that now
@@ -452,6 +439,25 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
// == EFFECT CODE END ==
// Check whether the parent clip is auto-scaledc
if (c->autoscaled()
&& (video_width != s->width
&& video_height != s->height)) {
float width_multiplier = float(s->width) / float(video_width);
float height_multiplier = float(s->height) / float(video_height);
float scale_multiplier = qMin(width_multiplier, height_multiplier);
glScalef(scale_multiplier, scale_multiplier, 1);
}
// Configure effect gizmos if they exist
if (params.gizmos != nullptr) {
params.gizmos->gizmo_draw(timecode, coords); // set correct gizmo coords
params.gizmos->gizmo_world_to_screen(); // convert gizmo coords to screen coords
}
if (textureID > 0) {
// set viewport to sequence size
params.ctx->functions()->glViewport(0, 0, s->width, s->height);
@@ -626,6 +632,7 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
}
}
/*
// visually update all the keyframe values
if (c->sequence == params.seq) { // only if you can currently see them
double ts = (playhead - c->timeline_in(true) + c->clip_in(true))/s->frame_rate;
@@ -639,6 +646,7 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
}
}
}
*/
}
} else {
params.texture_failed = true;
@@ -667,7 +675,7 @@ GLuint compose_sequence(ComposeSequenceParams &params) {
return 0;
}
void compose_audio(Viewer* viewer, SequencePtr seq, int playback_speed, bool wait_for_mutexes) {
void compose_audio(Viewer* viewer, Sequence* seq, int playback_speed, bool wait_for_mutexes) {
ComposeSequenceParams params;
params.viewer = viewer;
params.ctx = nullptr;
@@ -716,7 +724,7 @@ int64_t playhead_to_timestamp(Clip* c, long playhead) {
return seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead));
}
void close_active_clips(SequencePtr s) {
void close_active_clips(Sequence* s) {
if (s != nullptr) {
for (int i=0;i<s->clips.size();i++) {
Clip* c = s->clips.at(i).get();
+196 -187
View File
@@ -25,9 +25,8 @@
#include <QVector>
#include <QOpenGLShaderProgram>
#include "project/sequence.h"
#include "project/effect.h"
#include "timeline/sequence.h"
#include "effects/effect.h"
#include "panels/viewer.h"
/**
@@ -37,173 +36,183 @@
*/
struct ComposeSequenceParams {
/**
* @brief Reference to the Viewer class that's calling compose_sequence()
*
* Primarily used for calling Viewer::play_wake() when appropriate.
*/
Viewer* viewer;
/**
* @brief Reference to the Viewer class that's calling compose_sequence()
*
* Primarily used for calling Viewer::play_wake() when appropriate.
*/
Viewer* viewer;
/**
* @brief The OpenGL context to use while rendering.
*
* For video rendering, this must be a valid OpenGL context. For audio, this variable is never accessed.
*
* \see ComposeSequenceParams::video
*/
QOpenGLContext* ctx;
/**
* @brief The OpenGL context to use while rendering.
*
* For video rendering, this must be a valid OpenGL context. For audio, this variable is never accessed.
*
* \see ComposeSequenceParams::video
*/
QOpenGLContext* ctx;
/**
* @brief The sequence to compose
*
* In addition to clips, sequences also contain the playhead position so compose_sequence() knows which frame
* to render.
*/
SequencePtr seq;
/**
* @brief The sequence to compose
*
* In addition to clips, sequences also contain the playhead position so compose_sequence() knows which frame
* to render.
*/
Sequence* seq;
/**
* @brief Array to store the nested sequence hierarchy
*
* Should be left empty. This array gets passed around compose_sequence() as it calls itself recursively to
* handle nested sequences.
*/
QVector<Clip*> nests;
/**
* @brief Array to store the nested sequence hierarchy
*
* Should be left empty. This array gets passed around compose_sequence() as it calls itself recursively to
* handle nested sequences.
*/
QVector<Clip*> nests;
/**
* @brief Set compose mode to video or audio
*
* **TRUE** if this function should render video, **FALSE** if this function should render audio.
*/
bool video;
/**
* @brief Set compose mode to video or audio
*
* **TRUE** if this function should render video, **FALSE** if this function should render audio.
*/
bool video;
/**
* @brief Set to the Effect whose gizmos were chosen to be drawn on screen
*
* The currently active Effect that compose_sequence() will update the gizmos of.
*/
Effect* gizmos;
/**
* @brief Set to the Effect whose gizmos were chosen to be drawn on screen
*
* The currently active Effect that compose_sequence() will update the gizmos of.
*/
Effect* gizmos;
/**
* @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown.
*
* A footage item or shader may not be ready at the time this frame is drawn. If compose_sequence() couldn't draw
* any of the clips in the scene, this variable is set to **TRUE** indicating that the image rendered is a
* "best effort", but not the actual image.
*
* This variable should be checked after compose_sequence() and a repaint should be triggered if it's **TRUE**.
*
* \note This variable is probably bad design and is a relic of an earlier rendering backend. There may be a better
* way to communicate this information.
*
* Additionally, since
* compose_sequence() for video will now always run in a separate thread anyway, there's no real issue with
* stalling it to wait for footage to complete opening or whatever may be lagging behind. A possible side effect
* of this though is that the preview may become less responsive if it's stuck trying to render one frame. With
* the current system, the preview may show incomplete frames occasionally but at least it will show something.
* This may be preferable. See ComposeSequenceParams::single_threaded for a similar function that could be
* removed.
*/
bool texture_failed;
/**
* @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown.
*
* A footage item or shader may not be ready at the time this frame is drawn. If compose_sequence() couldn't draw
* any of the clips in the scene, this variable is set to **TRUE** indicating that the image rendered is a
* "best effort", but not the actual image.
*
* This variable should be checked after compose_sequence() and a repaint should be triggered if it's **TRUE**.
*
* \note This variable is probably bad design and is a relic of an earlier rendering backend. There may be a better
* way to communicate this information.
*
* Additionally, since
* compose_sequence() for video will now always run in a separate thread anyway, there's no real issue with
* stalling it to wait for footage to complete opening or whatever may be lagging behind. A possible side effect
* of this though is that the preview may become less responsive if it's stuck trying to render one frame. With
* the current system, the preview may show incomplete frames occasionally but at least it will show something.
* This may be preferable. See ComposeSequenceParams::single_threaded for a similar function that could be
* removed.
*/
bool texture_failed;
/**
* @brief Run all cachers in the same thread that compose_sequence() is in
*
* Standard behavior is that all clips cache frames in their own thread and signals are sent between
* compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling
* the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a
* technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used
* when rendering as timing isn't as important as creating output frames as quickly as possible.
*
* \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes
* it easier to synchronize everything, export performance could probably benefit from keeping them in separate
* threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could
* be removed.
*/
bool wait_for_mutexes;
/**
* @brief Run all cachers in the same thread that compose_sequence() is in
*
* Standard behavior is that all clips cache frames in their own thread and signals are sent between
* compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling
* the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a
* technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used
* when rendering as timing isn't as important as creating output frames as quickly as possible.
*
* \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes
* it easier to synchronize everything, export performance could probably benefit from keeping them in separate
* threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could
* be removed.
*/
bool wait_for_mutexes;
/**
* @brief Set the current playback speed (adjusted with Shuttle Left/Right)
*
* Only used for audio rendering to determine how many samples to skip in order to play audio at the correct speed.
*
* \see ComposeSequenceParams::video
*/
int playback_speed;
/**
* @brief Set the current playback speed (adjusted with Shuttle Left/Right)
*
* Only used for audio rendering to determine how many samples to skip in order to play audio at the correct speed.
*
* \see ComposeSequenceParams::video
*/
int playback_speed;
/**
* @brief Blending mode shader
*
* Used only for video rendering. Never accessed with audio rendering.
*
* A program containing the current active
* blending mode shader that can be bound during rendering. Must be compiled and linked beforehand. See
* RenderThread::blend_mode_program for how this is properly set up.
*
* \see ComposeSequenceParams::video
*/
QOpenGLShaderProgram* blend_mode_program;
/**
* @brief Blending mode shader
*
* Used only for video rendering. Never accessed with audio rendering.
*
* A program containing the current active
* blending mode shader that can be bound during rendering. Must be compiled and linked beforehand. See
* RenderThread::blend_mode_program for how this is properly set up.
*
* \see ComposeSequenceParams::video
*/
QOpenGLShaderProgram* blend_mode_program;
/**
* @brief The OpenGL framebuffer object that the final texture to be shown is rendered to.
*
* Used only for video rendering. Never accessed with audio rendering.
*
* When compose_sequence() is rendering the final image, this framebuffer will be bound.
*/
GLuint main_buffer;
/**
* @brief Premultiply alpha shader
*
* Used only for video rendering. Never accessed with audio rendering.
*
* compose_sequence()'s internal composition
* expects premultipled alpha, but it will pre-emptively multiply any footage that is not set as already
* premultiplied (see Footage::alpha_is_premultiplied) using this shader. Must be compiled and linked beforehand.
* See RenderThread::premultiply_program for how this is properly set up.
*/
QOpenGLShaderProgram* premultiply_program;
/**
* @brief The attachment to the framebuffer in main_buffer
*
* Used only for video rendering. Never accessed with audio rendering.
*
* The OpenGL texture attached to the framebuffer referenced by main_buffer.
*/
GLuint main_attachment;
/**
* @brief The OpenGL framebuffer object that the final texture to be shown is rendered to.
*
* Used only for video rendering. Never accessed with audio rendering.
*
* When compose_sequence() is rendering the final image, this framebuffer will be bound.
*/
GLuint main_buffer;
/**
* @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer
*
* In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging"
* between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose.
*/
GLuint backend_buffer1;
/**
* @brief The attachment to the framebuffer in main_buffer
*
* Used only for video rendering. Never accessed with audio rendering.
*
* The OpenGL texture attached to the framebuffer referenced by main_buffer.
*/
GLuint main_attachment;
/**
* @brief Backend OpenGL framebuffer 1's texture attachment
*
* The texture that ComposeSequenceParams::backend_buffer1 renders to. Bound and drawn to
* ComposeSequenceParams::backend_buffer2 to "ping-pong" between them and various shaders.
*/
GLuint backend_attachment1;
/**
* @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer
*
* In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging"
* between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose.
*/
GLuint backend_buffer1;
/**
* @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer
*
* In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging"
* between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose.
*/
GLuint backend_buffer2;
/**
* @brief Backend OpenGL framebuffer 1's texture attachment
*
* The texture that ComposeSequenceParams::backend_buffer1 renders to. Bound and drawn to
* ComposeSequenceParams::backend_buffer2 to "ping-pong" between them and various shaders.
*/
GLuint backend_attachment1;
/**
* @brief Backend OpenGL framebuffer 2's texture attachment
*
* The texture that ComposeSequenceParams::backend_buffer2 renders to. Bound and drawn to
* ComposeSequenceParams::backend_buffer1 to "ping-pong" between them and various shaders.
*/
GLuint backend_attachment2;
/**
* @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer
*
* In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging"
* between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose.
*/
GLuint backend_buffer2;
#ifndef NO_OCIO
/**
* @brief OpenGL shader containing OpenColorIO shader information
*/
QOpenGLShaderProgram* ocio_shader;
/**
* @brief Backend OpenGL framebuffer 2's texture attachment
*
* The texture that ComposeSequenceParams::backend_buffer2 renders to. Bound and drawn to
* ComposeSequenceParams::backend_buffer1 to "ping-pong" between them and various shaders.
*/
GLuint backend_attachment2;
/**
* @brief OpenGL texture containing LUT obtained form OpenColorIO
*/
GLuint ocio_lut_texture;
#endif
/**
* @brief OpenGL shader containing OpenColorIO shader information
*/
QOpenGLShaderProgram* ocio_shader;
/**
* @brief OpenGL texture containing LUT obtained form OpenColorIO
*/
GLuint ocio_lut_texture;
};
/**
@@ -231,29 +240,29 @@ struct ComposeSequenceParams {
GLuint compose_sequence(ComposeSequenceParams &params);
/**
* @brief Convenience wrapper function for compose_sequence() to render audio
*
* Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for
* video rendering. For audio rendering, this function is easier to handle and will correctly set up
* compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object.
*
* @param viewer
*
* The Viewer object calling this function
*
* @param seq
*
* The Sequence whose audio to render.
*
* @param playback_speed
*
* The current playback speed (controlled by Shuttle Left/Right)
*
* @param
*
* Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**.
*/
void compose_audio(Viewer* viewer, SequencePtr seq, int playback_speed, bool wait_for_mutexes);
* @brief Convenience wrapper function for compose_sequence() to render audio
*
* Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for
* video rendering. For audio rendering, this function is easier to handle and will correctly set up
* compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object.
*
* @param viewer
*
* The Viewer object calling this function
*
* @param seq
*
* The Sequence whose audio to render.
*
* @param playback_speed
*
* The current playback speed (controlled by Shuttle Left/Right)
*
* @param
*
* Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**.
*/
void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_for_mutexes);
/**
* @brief Rescale a frame number between two frame rates
@@ -380,17 +389,17 @@ int64_t seconds_to_timestamp(Clip* c, double seconds);
int64_t playhead_to_timestamp(Clip *c, long playhead);
/**
* @brief Close all open clips in a Sequence
*
* Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a
* result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that
* Sequence too.
*
* @param s
*
* The Sequence to close all clips on.
*/
void close_active_clips(SequencePtr s);
* @brief Close all open clips in a Sequence
*
* Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a
* result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that
* Sequence too.
*
* @param s
*
* The Sequence to close all clips on.
*/
void close_active_clips(Sequence* s);
void UpdateOCIOGLState(const ComposeSequenceParams &params);
+11 -4
View File
@@ -32,9 +32,9 @@ namespace OCIO = OCIO_NAMESPACE;
#endif
#include "rendering/renderfunctions.h"
#include "project/sequence.h"
#include "project/effectloaders.h"
#include "io/config.h"
#include "timeline/sequence.h"
#include "effects/effectloaders.h"
#include "global/config.h"
RenderThread::RenderThread() :
gizmos(nullptr),
@@ -46,6 +46,8 @@ RenderThread::RenderThread() :
tex_height(-1),
queued(false),
texture_failed(false),
ocio_lut_texture(0),
ocio_shader(nullptr),
running(true),
front_buffer_switcher(false)
{
@@ -352,7 +354,12 @@ void RenderThread::paint() {
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void RenderThread::start_render(QOpenGLContext *share, SequencePtr s, const QString& save, GLvoid* pixels, int pixel_linesize, int idivider) {
void RenderThread::start_render(QOpenGLContext *share,
Sequence* s,
const QString& save,
GLvoid* pixels,
int pixel_linesize,
int idivider) {
Q_UNUSED(idivider);
seq = s;
+7 -4
View File
@@ -30,15 +30,17 @@
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions_2_0>
#include "project/sequence.h"
#include "project/effect.h"
#include "timeline/sequence.h"
#include "effects/effect.h"
#include "rendering/framebufferobject.h"
#ifndef NO_OCIO
// copied from source code to OCIODisplay
const int OCIO_LUT3D_EDGE_SIZE = 32;
// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE
const int OCIO_NUM_3D_ENTRIES = 98304;
#endif
class RenderThread : public QThread {
Q_OBJECT
@@ -53,7 +55,7 @@ public:
Effect* gizmos;
void paint();
void start_render(QOpenGLContext* share,
SequencePtr s,
Sequence *s,
const QString &save = nullptr,
GLvoid *pixels = nullptr,
int pixel_linesize = 0,
@@ -106,7 +108,8 @@ private:
FramebufferObject back_buffer_1;
FramebufferObject back_buffer_2;
SequencePtr seq;
Sequence* seq;
int divider;
int tex_width;
int tex_height;