diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 69a3b0012..275148494 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -14,6 +14,7 @@ #include "ui/viewerwidget.h" #include "project/sequence.h" #include "io/exportthread.h" +#include "playback/playback.h" extern "C" { #include @@ -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; } diff --git a/effects/effect.cpp b/effects/effect.cpp index 202458a3f..a56c961e5 100644 --- a/effects/effect.cpp +++ b/effects/effect.cpp @@ -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; } diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 445bd0047..248b98da7 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -24,7 +24,30 @@ extern "C" { #include #include -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(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(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; } diff --git a/io/exportthread.h b/io/exportthread.h index 31f768af2..8e6d6887e 100644 --- a/io/exportthread.h +++ b/io/exportthread.h @@ -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 diff --git a/playback/playback.cpp b/playback/playback.cpp index f4a7f9b88..a9ca87cb4 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -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; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index d54078960..0822d4e0b 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -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= 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); } diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 450cbcb32..677de4b3d 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -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);