fixed rendering with new playback system
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "ui/viewerwidget.h"
|
||||
#include "project/sequence.h"
|
||||
#include "io/exportthread.h"
|
||||
#include "playback/playback.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
@@ -459,19 +460,16 @@ void ExportDialog::on_pushButton_clicked()
|
||||
}
|
||||
}
|
||||
|
||||
et = new ExportThread();
|
||||
|
||||
et->surface.create();
|
||||
et = new ExportThread();
|
||||
|
||||
connect(et, SIGNAL(finished()), et, SLOT(deleteLater()));
|
||||
connect(et, SIGNAL(finished()), this, SLOT(render_thread_finished()));
|
||||
connect(et, SIGNAL(progress_changed(int)), this, SLOT(update_progress_bar(int)));
|
||||
|
||||
panel_viewer->viewer_widget->multithreaded = false;
|
||||
panel_viewer->viewer_widget->enable_paint = false;
|
||||
closeActiveClips(sequence, true);
|
||||
|
||||
panel_viewer->viewer_widget->context()->doneCurrent();
|
||||
panel_viewer->viewer_widget->context()->moveToThread(et);
|
||||
panel_viewer->viewer_widget->force_audio = true;
|
||||
|
||||
prep_ui_for_render(true);
|
||||
|
||||
@@ -499,7 +497,7 @@ void ExportDialog::on_pushButton_clicked()
|
||||
}
|
||||
|
||||
et->ed = this;
|
||||
cancelled = false;
|
||||
cancelled = false;
|
||||
|
||||
et->start();
|
||||
}
|
||||
@@ -510,6 +508,6 @@ void ExportDialog::update_progress_bar(int value) {
|
||||
}
|
||||
|
||||
void ExportDialog::on_renderCancel_clicked() {
|
||||
et->fail = true;
|
||||
et->continueEncode = false;
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
@@ -303,6 +303,7 @@ void Effect::setIterations(int i) {
|
||||
|
||||
Effect* Effect::copy(Clip* c) {
|
||||
Effect* copy = create_effect(id, c);
|
||||
copy->set_enabled(is_enabled());
|
||||
copy_field_keyframes(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
+361
-336
@@ -24,7 +24,30 @@ extern "C" {
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QPainter>
|
||||
|
||||
bool ExportThread::encode(AVFormatContext* fmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream) {
|
||||
AVFormatContext* fmt_ctx = NULL;
|
||||
AVStream* video_stream;
|
||||
AVCodec* vcodec;
|
||||
AVCodecContext* vcodec_ctx;
|
||||
AVFrame* video_frame;
|
||||
AVFrame* sws_frame;
|
||||
SwsContext* sws_ctx = NULL;
|
||||
AVStream* audio_stream;
|
||||
AVCodec* acodec;
|
||||
AVFrame* audio_frame;
|
||||
AVFrame* swr_frame;
|
||||
AVCodecContext* acodec_ctx;
|
||||
AVPacket video_pkt;
|
||||
AVPacket audio_pkt;
|
||||
SwrContext* swr_ctx = NULL;
|
||||
int aframe_bytes;
|
||||
int ret;
|
||||
char* c_filename;
|
||||
|
||||
ExportThread::ExportThread() : continueEncode(true) {
|
||||
surface.create();
|
||||
}
|
||||
|
||||
bool ExportThread::encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream) {
|
||||
int ret = avcodec_send_frame(codec_ctx, frame);
|
||||
if (ret < 0/* && ret != AVERROR(EAGAIN) && frame != NULL*/) {
|
||||
qDebug() << "[ERROR] Failed to send frame to encoder." << ret;
|
||||
@@ -42,374 +65,376 @@ bool ExportThread::encode(AVFormatContext* fmt_ctx, AVCodecContext* codec_ctx, A
|
||||
return false;
|
||||
} else {
|
||||
packet->stream_index = stream->index;
|
||||
av_interleaved_write_frame(fmt_ctx, packet);
|
||||
av_interleaved_write_frame(ofmt_ctx, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportThread::run() {
|
||||
panel_timeline->pause();
|
||||
// av_log_set_level(AV_LOG_DEBUG);
|
||||
bool ExportThread::setupVideo() {
|
||||
// if video is disabled, no setup necessary
|
||||
if (!video_enabled) return true;
|
||||
|
||||
if (!panel_viewer->viewer_widget->context()->makeCurrent(&surface)) {
|
||||
qDebug() << "[ERROR] Make current failed";
|
||||
ed->export_error = "could not make OpenGL context current";
|
||||
return;
|
||||
// find video encoder
|
||||
vcodec = avcodec_find_encoder((enum AVCodecID) video_codec);
|
||||
if (!vcodec) {
|
||||
qDebug() << "[ERROR] Could not find video encoder";
|
||||
ed->export_error = "could not video encoder for " + QString::number(video_codec);
|
||||
return false;
|
||||
}
|
||||
|
||||
AVFormatContext* fmt_ctx = NULL;
|
||||
// create video stream
|
||||
video_stream = avformat_new_stream(fmt_ctx, vcodec);
|
||||
video_stream->id = 0;
|
||||
if (!video_stream) {
|
||||
qDebug() << "[ERROR] Could not allocate video stream";
|
||||
ed->export_error = "could not allocate video stream";
|
||||
return false;
|
||||
}
|
||||
|
||||
// allocate context
|
||||
vcodec_ctx = avcodec_alloc_context3(vcodec);
|
||||
if (!vcodec_ctx) {
|
||||
qDebug() << "[ERROR] Could not allocate video encoding context";
|
||||
ed->export_error = "could not allocate video encoding context";
|
||||
return false;
|
||||
}
|
||||
|
||||
// setup context
|
||||
vcodec_ctx->codec_id = (enum AVCodecID) video_codec;
|
||||
vcodec_ctx->width = video_width;
|
||||
vcodec_ctx->height = video_height;
|
||||
vcodec_ctx->sample_aspect_ratio = av_d2q(video_width/video_height, INT_MAX);
|
||||
vcodec_ctx->pix_fmt = vcodec->pix_fmts[0]; // maybe be breakable code
|
||||
vcodec_ctx->framerate = av_d2q(video_frame_rate, INT_MAX);
|
||||
vcodec_ctx->bit_rate = video_bitrate * 1000000;
|
||||
vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate);
|
||||
|
||||
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
vcodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
// open encoder
|
||||
vcodec_ctx->gop_size = 12; // ? does this help?
|
||||
ret = avcodec_open2(vcodec_ctx, vcodec, NULL);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not open output video encoder." << ret;
|
||||
ed->export_error = "could not open output video encoder (" + 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) {
|
||||
qDebug() << "[ERROR] Could not copy video encoder parameters to output stream." << ret;
|
||||
ed->export_error = "could not copy video encoder parameters to output stream (" + 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 = sequence->width;
|
||||
video_frame->height = sequence->height;
|
||||
av_frame_get_buffer(video_frame, 0);
|
||||
|
||||
av_init_packet(&video_pkt);
|
||||
|
||||
sws_ctx = sws_getContext(
|
||||
sequence->width,
|
||||
sequence->height,
|
||||
AV_PIX_FMT_RGBA,
|
||||
video_width,
|
||||
video_height,
|
||||
vcodec_ctx->pix_fmt,
|
||||
SWS_FAST_BILINEAR,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
sws_frame = av_frame_alloc();
|
||||
sws_frame->format = vcodec_ctx->pix_fmt;
|
||||
sws_frame->width = video_width;
|
||||
sws_frame->height = video_height;
|
||||
av_frame_get_buffer(sws_frame, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExportThread::setupAudio() {
|
||||
// if audio is disabled, no setup necessary
|
||||
if (!audio_enabled) return true;
|
||||
|
||||
// find encoder
|
||||
acodec = avcodec_find_encoder((enum AVCodecID) audio_codec);
|
||||
if (!acodec) {
|
||||
qDebug() << "[ERROR] Could not find audio encoder";
|
||||
ed->export_error = "could not audio encoder for " + QString::number(audio_codec);
|
||||
return false;
|
||||
}
|
||||
|
||||
// allocate audio stream
|
||||
audio_stream = avformat_new_stream(fmt_ctx, acodec);
|
||||
audio_stream->id = 1;
|
||||
if (!audio_stream) {
|
||||
qDebug() << "[ERROR] Could not allocate audio stream";
|
||||
ed->export_error = "could not allocate audio stream";
|
||||
return false;
|
||||
}
|
||||
|
||||
// allocate context
|
||||
acodec_ctx = avcodec_alloc_context3(acodec);
|
||||
if (!acodec_ctx) {
|
||||
qDebug() << "[ERROR] Could not find allocate audio encoding context";
|
||||
ed->export_error = "could not allocate audio encoding context";
|
||||
return false;
|
||||
}
|
||||
|
||||
// setup context
|
||||
acodec_ctx->sample_rate = 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 = audio_bitrate * 1000;
|
||||
acodec_ctx->time_base.num = 1;
|
||||
acodec_ctx->time_base.den = audio_sampling_rate;
|
||||
|
||||
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
acodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
// open encoder
|
||||
ret = avcodec_open2(acodec_ctx, acodec, NULL);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not open output audio encoder." << ret;
|
||||
ed->export_error = "could not open output audio encoder (" + QString::number(ret) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy params to output stream
|
||||
ret = avcodec_parameters_from_context(audio_stream->codecpar, acodec_ctx);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not copy audio encoder parameters to output stream." << ret;
|
||||
ed->export_error = "could not copy audio encoder parameters to output stream (" + QString::number(ret) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
// init audio resampler context
|
||||
swr_ctx = swr_alloc_set_opts(
|
||||
NULL,
|
||||
acodec_ctx->channel_layout,
|
||||
acodec_ctx->sample_fmt,
|
||||
acodec_ctx->sample_rate,
|
||||
sequence->audio_layout,
|
||||
AV_SAMPLE_FMT_S16,
|
||||
sequence->audio_frequency,
|
||||
0,
|
||||
NULL
|
||||
);
|
||||
swr_init(swr_ctx);
|
||||
|
||||
// initialize raw audio frame
|
||||
audio_frame = av_frame_alloc();
|
||||
audio_frame->sample_rate = sequence->audio_frequency;
|
||||
audio_frame->nb_samples = acodec_ctx->frame_size;
|
||||
if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 2048; // should possibly be smaller?
|
||||
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) {
|
||||
qDebug() << "[ERROR] Could not allocate audio buffer." << ret;
|
||||
ed->export_error = "could not allocate audio buffer (" + QString::number(ret) + ")";
|
||||
return false;
|
||||
}
|
||||
aframe_bytes = av_samples_get_buffer_size(NULL, audio_frame->channels, audio_frame->nb_samples, static_cast<AVSampleFormat>(audio_frame->format), 0);
|
||||
|
||||
// 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;
|
||||
av_frame_make_writable(swr_frame);
|
||||
|
||||
av_init_packet(&audio_pkt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExportThread::setupContainer() {
|
||||
avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, c_filename);
|
||||
if (!fmt_ctx) {
|
||||
qDebug() << "[ERROR] Could not create output context";
|
||||
ed->export_error = "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) {
|
||||
qDebug() << "[ERROR] Could not open output file." << ret;
|
||||
ed->export_error = "could not open output file (" + QString::number(ret) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportThread::run() {
|
||||
panel_timeline->pause();
|
||||
|
||||
if (!panel_viewer->viewer_widget->context()->makeCurrent(&surface)) {
|
||||
qDebug() << "[ERROR] Make current failed";
|
||||
ed->export_error = "could not make OpenGL context current";
|
||||
return;
|
||||
}
|
||||
|
||||
// copy filename
|
||||
QByteArray ba = filename.toLatin1();
|
||||
char* c_filename = new char[ba.size()+1];
|
||||
c_filename = new char[ba.size()+1];
|
||||
strcpy(c_filename, ba.data());
|
||||
|
||||
AVOutputFormat* ofmt = av_guess_format(NULL, c_filename, NULL);
|
||||
avformat_alloc_output_context2(&fmt_ctx, ofmt, NULL, c_filename);
|
||||
if (!fmt_ctx) {
|
||||
qDebug() << "[ERROR] Could not create output context";
|
||||
ed->export_error = "could not create output format context";
|
||||
} else {
|
||||
AVStream* video_stream;
|
||||
AVCodec* vcodec;
|
||||
AVCodecContext* vcodec_ctx;
|
||||
AVFrame* video_frame;
|
||||
AVFrame* sws_frame;
|
||||
SwsContext* sws_ctx = NULL;
|
||||
AVStream* audio_stream;
|
||||
AVCodec* acodec;
|
||||
AVFrame* audio_frame;
|
||||
AVFrame* swr_frame;
|
||||
AVCodecContext* acodec_ctx;
|
||||
AVPacket video_pkt;
|
||||
AVPacket audio_pkt;
|
||||
SwrContext* swr_ctx = NULL;
|
||||
int aframe_bytes;
|
||||
int ret;
|
||||
continueEncode = setupContainer();
|
||||
|
||||
fail = false;
|
||||
if (continueEncode) continueEncode = setupVideo();
|
||||
|
||||
if (video_enabled) {
|
||||
vcodec = avcodec_find_encoder((enum AVCodecID) video_codec);
|
||||
if (!vcodec) {
|
||||
qDebug() << "[ERROR] Could not find video encoder";
|
||||
ed->export_error = "could not video encoder for " + QString::number(video_codec);
|
||||
fail = true;
|
||||
} else {
|
||||
video_stream = avformat_new_stream(fmt_ctx, vcodec);
|
||||
video_stream->id = 0;
|
||||
if (continueEncode) continueEncode = setupAudio();
|
||||
|
||||
if (!video_stream) {
|
||||
qDebug() << "[ERROR] Could not allocate video stream";
|
||||
ed->export_error = "could not allocate video stream";
|
||||
fail = true;
|
||||
} else {
|
||||
vcodec_ctx = avcodec_alloc_context3(vcodec);
|
||||
|
||||
if (!vcodec_ctx) {
|
||||
qDebug() << "[ERROR] Could not allocate video encoding context";
|
||||
ed->export_error = "could not allocate video encoding context";
|
||||
fail = true;
|
||||
} else {
|
||||
vcodec_ctx->codec_id = (enum AVCodecID) video_codec;
|
||||
vcodec_ctx->width = video_width;
|
||||
vcodec_ctx->height = video_height;
|
||||
vcodec_ctx->sample_aspect_ratio = av_d2q(video_width/video_height, INT_MAX);
|
||||
vcodec_ctx->pix_fmt = vcodec->pix_fmts[0]; // maybe be breakable code
|
||||
vcodec_ctx->framerate = av_d2q(video_frame_rate, INT_MAX);
|
||||
vcodec_ctx->bit_rate = video_bitrate * 1000000;
|
||||
vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate);
|
||||
|
||||
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
vcodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
vcodec_ctx->gop_size = 12; // ? does this help?
|
||||
|
||||
ret = avcodec_open2(vcodec_ctx, vcodec, NULL);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not open output video encoder." << ret;
|
||||
ed->export_error = "could not open output video encoder (" + QString::number(ret) + ")";
|
||||
fail = true;
|
||||
} else {
|
||||
ret = avcodec_parameters_from_context(video_stream->codecpar, vcodec_ctx);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not copy video encoder parameters to output stream." << ret;
|
||||
ed->export_error = "could not copy video encoder parameters to output stream (" + QString::number(ret) + ")";
|
||||
fail = true;
|
||||
} else {
|
||||
// initialize raw video frame
|
||||
video_frame = av_frame_alloc();
|
||||
av_frame_make_writable(video_frame);
|
||||
video_frame->format = AV_PIX_FMT_RGBA;
|
||||
video_frame->width = video_width;
|
||||
video_frame->height = video_height;
|
||||
av_frame_get_buffer(video_frame, 0);
|
||||
|
||||
av_init_packet(&video_pkt);
|
||||
|
||||
sws_ctx = sws_getContext(
|
||||
video_width,
|
||||
video_height,
|
||||
AV_PIX_FMT_RGBA,
|
||||
video_width,
|
||||
video_height,
|
||||
vcodec_ctx->pix_fmt,
|
||||
SWS_FAST_BILINEAR,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
sws_frame = av_frame_alloc();
|
||||
sws_frame->format = vcodec_ctx->pix_fmt;
|
||||
sws_frame->width = video_frame->width;
|
||||
sws_frame->height = video_frame->height;
|
||||
av_frame_get_buffer(sws_frame, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (continueEncode) {
|
||||
ret = avformat_write_header(fmt_ctx, NULL);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not write output file header." << ret;
|
||||
ed->export_error = "could not write output file header (" + QString::number(ret) + ")";
|
||||
continueEncode = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio_enabled && !fail) {
|
||||
acodec = avcodec_find_encoder((enum AVCodecID) audio_codec);
|
||||
if (!acodec) {
|
||||
qDebug() << "[ERROR] Could not find audio encoder";
|
||||
ed->export_error = "could not audio encoder for " + QString::number(audio_codec);
|
||||
fail = true;
|
||||
} else {
|
||||
audio_stream = avformat_new_stream(fmt_ctx, acodec);
|
||||
audio_stream->id = 1;
|
||||
if (!audio_stream) {
|
||||
qDebug() << "[ERROR] Could not allocate audio stream";
|
||||
ed->export_error = "could not allocate audio stream";
|
||||
fail = true;
|
||||
} else {
|
||||
acodec_ctx = avcodec_alloc_context3(acodec);
|
||||
if (!acodec_ctx) {
|
||||
qDebug() << "[ERROR] Could not find allocate audio encoding context";
|
||||
ed->export_error = "could not allocate audio encoding context";
|
||||
fail = true;
|
||||
} else {
|
||||
acodec_ctx->sample_rate = 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 = audio_bitrate * 1000;
|
||||
acodec_ctx->time_base.num = 1;
|
||||
acodec_ctx->time_base.den = audio_sampling_rate;
|
||||
panel_timeline->seek(start_frame);
|
||||
|
||||
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
acodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
QOpenGLFramebufferObject fbo(sequence->width, sequence->height, QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_RECTANGLE);
|
||||
fbo.bind();
|
||||
|
||||
ret = avcodec_open2(acodec_ctx, acodec, NULL);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not open output audio encoder." << ret;
|
||||
ed->export_error = "could not open output audio encoder (" + QString::number(ret) + ")";
|
||||
fail = true;
|
||||
} else {
|
||||
ret = avcodec_parameters_from_context(audio_stream->codecpar, acodec_ctx);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not copy audio encoder parameters to output stream." << ret;
|
||||
ed->export_error = "could not copy audio encoder parameters to output stream (" + QString::number(ret) + ")";
|
||||
fail = true;
|
||||
} else {
|
||||
// init audio resampler context
|
||||
swr_ctx = swr_alloc_set_opts(
|
||||
NULL,
|
||||
acodec_ctx->channel_layout,
|
||||
acodec_ctx->sample_fmt,
|
||||
acodec_ctx->sample_rate,
|
||||
sequence->audio_layout,
|
||||
AV_SAMPLE_FMT_S16,
|
||||
sequence->audio_frequency,
|
||||
0,
|
||||
NULL
|
||||
);
|
||||
swr_init(swr_ctx);
|
||||
panel_viewer->viewer_widget->rendering = true;
|
||||
panel_viewer->viewer_widget->fbo = &fbo;
|
||||
|
||||
// initialize raw audio frame
|
||||
audio_frame = av_frame_alloc();
|
||||
audio_frame->sample_rate = sequence->audio_frequency;
|
||||
audio_frame->nb_samples = acodec_ctx->frame_size;
|
||||
if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 2048; // should possibly be smaller?
|
||||
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);
|
||||
ret = av_frame_get_buffer(audio_frame, 0);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not allocate audio buffer." << ret;
|
||||
ed->export_error = "could not allocate audio buffer (" + QString::number(ret) + ")";
|
||||
fail = true;
|
||||
} else {
|
||||
// av_samples_alloc(&audio_frame->data[0], NULL, av_get_channel_layout_nb_channels(audio_frame->channel_layout), audio_frame->nb_samples, acodec_ctx->sample_fmt, 0);
|
||||
av_frame_make_writable(audio_frame);
|
||||
aframe_bytes = av_samples_get_buffer_size(NULL, audio_frame->channels, audio_frame->nb_samples, static_cast<AVSampleFormat>(audio_frame->format), 0);
|
||||
long file_audio_samples = 0;
|
||||
|
||||
// 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;
|
||||
av_frame_make_writable(swr_frame);
|
||||
while (panel_timeline->playhead < end_frame && continueEncode) {
|
||||
panel_viewer->viewer_widget->paintGL();
|
||||
|
||||
av_init_packet(&audio_pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
double timecode_secs = (double) (panel_timeline->playhead-start_frame) / sequence->frame_rate;
|
||||
if (video_enabled) {
|
||||
// get image from opengl
|
||||
glReadPixels(0, 0, sequence->width, sequence->height, GL_RGBA, GL_UNSIGNED_BYTE, video_frame->data[0]);
|
||||
|
||||
if (!fail) {
|
||||
av_dump_format(fmt_ctx, 0, c_filename, 1);
|
||||
// change pixel format
|
||||
sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize);
|
||||
sws_frame->pts = round(timecode_secs/av_q2d(video_stream->time_base));
|
||||
|
||||
ret = avio_open(&fmt_ctx->pb, c_filename, AVIO_FLAG_WRITE);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not open output file." << ret;
|
||||
ed->export_error = "could not open output file (" + QString::number(ret) + ")";
|
||||
} else {
|
||||
ret = avformat_write_header(fmt_ctx, NULL);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not write output file header." << ret;
|
||||
ed->export_error = "could not write output file header (" + QString::number(ret) + ")";
|
||||
} else {
|
||||
panel_timeline->seek(start_frame);
|
||||
// send to encoder
|
||||
if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) continueEncode = false;
|
||||
}
|
||||
if (audio_enabled) {
|
||||
// do we need to encode more audio samples?
|
||||
while (continueEncode && file_audio_samples <= (timecode_secs*audio_sampling_rate)) {
|
||||
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;
|
||||
|
||||
QOpenGLFramebufferObject fbo(video_width, video_height, QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_RECTANGLE);
|
||||
fbo.bind();
|
||||
QOpenGLPaintDevice fbo_dev(video_width, video_height);
|
||||
QPainter painter(&fbo_dev);
|
||||
painter.beginNativePainting();
|
||||
|
||||
panel_viewer->viewer_widget->initializeGL();
|
||||
panel_viewer->viewer_widget->flip = true;
|
||||
|
||||
long file_audio_samples = 0;
|
||||
|
||||
while (panel_timeline->playhead < end_frame && !fail) {
|
||||
panel_viewer->viewer_widget->paintGL();
|
||||
|
||||
double timecode_secs = (double) (panel_timeline->playhead-start_frame) / sequence->frame_rate;
|
||||
if (video_enabled) {
|
||||
// get image from opengl
|
||||
glReadPixels(0, 0, video_width, video_height, GL_RGBA, GL_UNSIGNED_BYTE, video_frame->data[0]);
|
||||
|
||||
// change pixel format
|
||||
sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize);
|
||||
sws_frame->pts = round(timecode_secs/av_q2d(video_stream->time_base));
|
||||
|
||||
// send to encoder
|
||||
if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) fail = true;
|
||||
}
|
||||
if (audio_enabled) {
|
||||
// do we need to encode more audio samples?
|
||||
while (!fail && file_audio_samples <= (timecode_secs*audio_sampling_rate)) {
|
||||
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)) fail = true;
|
||||
|
||||
file_audio_samples += swr_frame->nb_samples;
|
||||
}
|
||||
}
|
||||
emit progress_changed(((double) (panel_timeline->playhead-start_frame) / (double) (end_frame-start_frame)) * 100);
|
||||
panel_timeline->playhead++;
|
||||
}
|
||||
|
||||
panel_viewer->viewer_widget->flip = false;
|
||||
|
||||
painter.endNativePainting();
|
||||
fbo.release();
|
||||
|
||||
if (!fail) {
|
||||
// flush remaining packets
|
||||
if (video_enabled) {
|
||||
while (encode(fmt_ctx, vcodec_ctx, NULL, &video_pkt, video_stream)) {}
|
||||
}
|
||||
if (audio_enabled) {
|
||||
// flush swresample
|
||||
do {
|
||||
swr_convert_frame(swr_ctx, swr_frame, NULL);
|
||||
swr_frame->pts = file_audio_samples;
|
||||
if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) fail = true;
|
||||
file_audio_samples += swr_frame->nb_samples;
|
||||
} while (swr_frame->nb_samples > 0);
|
||||
|
||||
// flush encoder
|
||||
while (encode(fmt_ctx, acodec_ctx, NULL, &audio_pkt, audio_stream)) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fail) {
|
||||
ret = av_write_trailer(fmt_ctx);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not write output file trailer." << ret;
|
||||
ed->export_error = "could not write output file trailer (" + QString::number(ret) + ")";
|
||||
}
|
||||
|
||||
emit progress_changed(100);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
avio_closep(&fmt_ctx->pb);
|
||||
// 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)) continueEncode = false;
|
||||
|
||||
file_audio_samples += swr_frame->nb_samples;
|
||||
}
|
||||
|
||||
if (video_enabled) {
|
||||
avcodec_close(vcodec_ctx);
|
||||
av_packet_unref(&video_pkt);
|
||||
av_frame_free(&video_frame);
|
||||
avcodec_free_context(&vcodec_ctx);
|
||||
}
|
||||
|
||||
if (audio_enabled) {
|
||||
avcodec_close(acodec_ctx);
|
||||
av_packet_unref(&audio_pkt);
|
||||
av_frame_free(&audio_frame);
|
||||
avcodec_free_context(&acodec_ctx);
|
||||
}
|
||||
|
||||
avformat_free_context(fmt_ctx);
|
||||
|
||||
// qDebug() << "Render took: %ih %im %is\n", render_time/3600000, render_time/60000, render_time/1000;
|
||||
|
||||
if (sws_ctx != NULL) {
|
||||
sws_freeContext(sws_ctx);
|
||||
av_frame_free(&sws_frame);
|
||||
}
|
||||
if (swr_ctx != NULL) {
|
||||
swr_free(&swr_ctx);
|
||||
av_frame_free(&swr_frame);
|
||||
}
|
||||
}
|
||||
emit progress_changed(((double) (panel_timeline->playhead-start_frame) / (double) (end_frame-start_frame)) * 100);
|
||||
panel_timeline->playhead++;
|
||||
}
|
||||
|
||||
panel_viewer->viewer_widget->fbo = NULL;
|
||||
panel_viewer->viewer_widget->rendering = false;
|
||||
|
||||
fbo.release();
|
||||
|
||||
if (continueEncode) {
|
||||
// flush remaining packets
|
||||
if (video_enabled) {
|
||||
while (encode(fmt_ctx, vcodec_ctx, NULL, &video_pkt, video_stream)) {}
|
||||
}
|
||||
if (audio_enabled) {
|
||||
// flush swresample
|
||||
do {
|
||||
swr_convert_frame(swr_ctx, swr_frame, NULL);
|
||||
swr_frame->pts = file_audio_samples;
|
||||
if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) continueEncode = false;
|
||||
file_audio_samples += swr_frame->nb_samples;
|
||||
} while (swr_frame->nb_samples > 0);
|
||||
|
||||
// flush encoder
|
||||
while (encode(fmt_ctx, acodec_ctx, NULL, &audio_pkt, audio_stream)) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (continueEncode) {
|
||||
ret = av_write_trailer(fmt_ctx);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[ERROR] Could not write output file trailer." << ret;
|
||||
ed->export_error = "could not write output file trailer (" + QString::number(ret) + ")";
|
||||
continueEncode = false;
|
||||
}
|
||||
|
||||
emit progress_changed(100);
|
||||
}
|
||||
|
||||
avio_closep(&fmt_ctx->pb);
|
||||
|
||||
if (video_enabled) {
|
||||
avcodec_close(vcodec_ctx);
|
||||
av_packet_unref(&video_pkt);
|
||||
av_frame_free(&video_frame);
|
||||
avcodec_free_context(&vcodec_ctx);
|
||||
}
|
||||
|
||||
if (audio_enabled) {
|
||||
avcodec_close(acodec_ctx);
|
||||
av_packet_unref(&audio_pkt);
|
||||
av_frame_free(&audio_frame);
|
||||
avcodec_free_context(&acodec_ctx);
|
||||
}
|
||||
|
||||
avformat_free_context(fmt_ctx);
|
||||
|
||||
if (sws_ctx != NULL) {
|
||||
sws_freeContext(sws_ctx);
|
||||
av_frame_free(&sws_frame);
|
||||
}
|
||||
if (swr_ctx != NULL) {
|
||||
swr_free(&swr_ctx);
|
||||
av_frame_free(&swr_frame);
|
||||
}
|
||||
|
||||
delete [] c_filename;
|
||||
|
||||
panel_viewer->viewer_widget->context()->doneCurrent();
|
||||
panel_viewer->viewer_widget->context()->moveToThread(qApp->thread());
|
||||
panel_viewer->viewer_widget->multithreaded = true;
|
||||
panel_viewer->viewer_widget->force_audio = false;
|
||||
panel_viewer->viewer_widget->enable_paint = true;
|
||||
}
|
||||
|
||||
+7
-3
@@ -14,6 +14,7 @@ struct AVStream;
|
||||
class ExportThread : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportThread();
|
||||
void run();
|
||||
|
||||
// export parameters
|
||||
@@ -33,13 +34,16 @@ public:
|
||||
|
||||
QOffscreenSurface surface;
|
||||
|
||||
ExportDialog* ed;
|
||||
ExportDialog* ed;
|
||||
|
||||
bool fail;
|
||||
bool continueEncode;
|
||||
signals:
|
||||
void progress_changed(int value);
|
||||
private:
|
||||
bool encode(AVFormatContext* fmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream);
|
||||
bool encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream);
|
||||
bool setupVideo();
|
||||
bool setupAudio();
|
||||
bool setupContainer();
|
||||
};
|
||||
|
||||
#endif // EXPORTTHREAD_H
|
||||
|
||||
@@ -28,10 +28,9 @@ bool texture_failed = false;
|
||||
|
||||
void open_clip(Clip* clip, bool multithreaded) {
|
||||
if (clip->media_type == MEDIA_TYPE_FOOTAGE) {
|
||||
clip->multithreaded = multithreaded;
|
||||
if (multithreaded) {
|
||||
if (clip->open_lock.tryLock()) {
|
||||
clip->multithreaded = true;
|
||||
|
||||
// maybe keep cacher instance in memory while clip exists for performance?
|
||||
clip->cacher = new Cacher(clip);
|
||||
QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater()));
|
||||
@@ -39,7 +38,6 @@ void open_clip(Clip* clip, bool multithreaded) {
|
||||
clip->cacher->start(QThread::LowPriority);
|
||||
}
|
||||
} else {
|
||||
clip->multithreaded = false;
|
||||
clip->finished_opening = false;
|
||||
clip->open = true;
|
||||
|
||||
|
||||
+40
-32
@@ -27,10 +27,8 @@ extern "C" {
|
||||
|
||||
ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
QOpenGLWidget(parent),
|
||||
multithreaded(true),
|
||||
force_audio(false),
|
||||
enable_paint(true),
|
||||
flip(false)
|
||||
rendering(false),
|
||||
fbo(NULL)
|
||||
{
|
||||
QSurfaceFormat format;
|
||||
format.setDepthBufferSize(24);
|
||||
@@ -54,7 +52,6 @@ void ViewerWidget::retry() {
|
||||
|
||||
void ViewerWidget::initializeGL() {
|
||||
connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(deleteFunction()), Qt::DirectConnection);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
|
||||
retry_timer.start();
|
||||
}
|
||||
@@ -64,7 +61,7 @@ void ViewerWidget::initializeGL() {
|
||||
//}
|
||||
|
||||
void ViewerWidget::paintEvent(QPaintEvent *e) {
|
||||
if (enable_paint) {
|
||||
if (!rendering) {
|
||||
makeCurrent();
|
||||
QOpenGLWidget::paintEvent(e);
|
||||
}
|
||||
@@ -74,7 +71,9 @@ GLuint ViewerWidget::draw_clip(Clip* clip, GLuint texture) {
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
glOrtho(0, 1, 0, 1, -1, 1);
|
||||
|
||||
clip->fbo->bind();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0, 0); // top left
|
||||
@@ -87,7 +86,10 @@ GLuint ViewerWidget::draw_clip(Clip* clip, GLuint texture) {
|
||||
glVertex2f(0, 1); // bottom left
|
||||
glEnd();
|
||||
glBindTexture(GL_TEXTURE_2D, NULL);
|
||||
|
||||
clip->fbo->release();
|
||||
if (fbo != NULL) fbo->bind();
|
||||
|
||||
glPopMatrix();
|
||||
return clip->fbo->takeTexture();
|
||||
}
|
||||
@@ -121,9 +123,8 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
// if thread is already working, we don't want to touch this,
|
||||
// but we also don't want to hang the UI thread
|
||||
if (!c->open) {
|
||||
open_clip(c, multithreaded);
|
||||
open_clip(c, !rendering);
|
||||
}
|
||||
|
||||
clip_is_active = true;
|
||||
} else if (c->open) {
|
||||
close_clip(c);
|
||||
@@ -135,7 +136,7 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
break;
|
||||
case MEDIA_TYPE_SEQUENCE:
|
||||
if (is_clip_active(c, playhead)) {
|
||||
if (!c->open) open_clip(c, multithreaded);
|
||||
if (!c->open) open_clip(c, !rendering);
|
||||
clip_is_active = true;
|
||||
} else if (c->open) {
|
||||
close_clip(c);
|
||||
@@ -159,6 +160,14 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
}
|
||||
}
|
||||
|
||||
int half_width = s->width/2;
|
||||
int half_height = s->height/2;
|
||||
if (rendering || nest != NULL) half_height = -half_height;
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glColor4f(1.0, 1.0, 1.0, 1.0);
|
||||
glLoadIdentity();
|
||||
glOrtho(-half_width, half_width, half_height, -half_height, -1, 1);
|
||||
|
||||
for (int i=0;i<current_clips.size();i++) {
|
||||
Clip* c = current_clips.at(i);
|
||||
|
||||
@@ -188,15 +197,10 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
texture_failed = true;
|
||||
} else if (playhead >= c->timeline_in) {
|
||||
// start preparing cache
|
||||
if (c->fbo == NULL) c->fbo = new QOpenGLFramebufferObject(video_width, video_height);
|
||||
if (c->fbo == NULL) {
|
||||
c->fbo = new QOpenGLFramebufferObject(video_width, video_height);
|
||||
}
|
||||
|
||||
int half_width = c->sequence->width/2;
|
||||
int half_height = c->sequence->height/2;
|
||||
if (flip || nest != NULL) half_height = -half_height;
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glColor4f(1.0, 1.0, 1.0, 1.0);
|
||||
glLoadIdentity();
|
||||
glOrtho(-half_width, half_width, half_height, -half_height, -1, 1);
|
||||
glViewport(0, 0, video_width, video_height);
|
||||
|
||||
GLuint composite_texture = draw_clip(c, textureID);
|
||||
@@ -244,6 +248,8 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
if (nest->fbo == NULL) nest->fbo = new QOpenGLFramebufferObject(s->width, s->height);
|
||||
nest->fbo->bind();
|
||||
glViewport(0, 0, s->width, s->height);
|
||||
} else if (rendering) {
|
||||
glViewport(0, 0, s->width, s->height);
|
||||
} else {
|
||||
glViewport(0, 0, width(), height());
|
||||
}
|
||||
@@ -268,6 +274,7 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
|
||||
if (nest != NULL) {
|
||||
nest->fbo->release();
|
||||
if (fbo != NULL) fbo->bind();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -288,33 +295,34 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
}
|
||||
|
||||
void ViewerWidget::paintGL() {
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
bool loop = false;
|
||||
do {
|
||||
loop = false;
|
||||
|
||||
bool loop = true;
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
while (loop) {
|
||||
loop = false;
|
||||
texture_failed = false;
|
||||
|
||||
if (multithreaded) retry_timer.stop();
|
||||
if (!rendering) retry_timer.stop();
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// compose video preview
|
||||
compose_sequence(NULL, (panel_timeline->playing || force_audio));
|
||||
compose_sequence(NULL, (panel_timeline->playing || rendering));
|
||||
|
||||
if (texture_failed) {
|
||||
if (multithreaded) {
|
||||
retry_timer.start();
|
||||
if (rendering) {
|
||||
qDebug() << "[INFO] Texture failed - looping";
|
||||
loop = true;
|
||||
} else {
|
||||
qDebug() << "[INFO] Texture failed - looping";
|
||||
loop = true;
|
||||
retry_timer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
} while (loop);
|
||||
}
|
||||
|
||||
+4
-4
@@ -11,6 +11,7 @@
|
||||
|
||||
struct Clip;
|
||||
struct Sequence;
|
||||
class QOpenGLFramebufferObject;
|
||||
|
||||
class ViewerWidget : public QOpenGLWidget
|
||||
{
|
||||
@@ -18,12 +19,11 @@ class ViewerWidget : public QOpenGLWidget
|
||||
public:
|
||||
ViewerWidget(QWidget *parent = 0);
|
||||
|
||||
bool multithreaded;
|
||||
bool force_audio;
|
||||
bool enable_paint;
|
||||
bool flip;
|
||||
bool rendering;
|
||||
void paintGL();
|
||||
void initializeGL();
|
||||
|
||||
QOpenGLFramebufferObject* fbo;
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e);
|
||||
// void resizeGL(int w, int h);
|
||||
|
||||
Reference in New Issue
Block a user