giant rendering overhaul and many small fixes
This commit is contained in:
+410
-411
@@ -27,18 +27,17 @@
|
||||
#include "panels/panels.h"
|
||||
|
||||
#include "ui/viewerwidget.h"
|
||||
#include "ui/renderthread.h"
|
||||
#include "ui/renderfunctions.h"
|
||||
#include "playback/playback.h"
|
||||
#include "playback/audio.h"
|
||||
#include "rendering/renderthread.h"
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include "rendering/audio.h"
|
||||
#include "mainwindow.h"
|
||||
#include "debug.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include <QApplication>
|
||||
@@ -50,488 +49,488 @@ extern "C" {
|
||||
ExportThread::ExportThread(const ExportParams &iparams,
|
||||
const VideoCodecParams& ivparams,
|
||||
QObject *parent) :
|
||||
QThread(parent)
|
||||
QThread(parent)
|
||||
{
|
||||
params = iparams;
|
||||
vcodec_params = ivparams;
|
||||
continueEncode = true;
|
||||
params = iparams;
|
||||
vcodec_params = ivparams;
|
||||
continueEncode = true;
|
||||
|
||||
surface.create();
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// 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);
|
||||
// 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) {
|
||||
case AV_CODEC_ID_H264:
|
||||
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;
|
||||
}
|
||||
vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate);
|
||||
video_stream->time_base = vcodec_ctx->time_base;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
vcodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
AVDictionary* opts = nullptr;
|
||||
av_dict_set(&opts, "threads", "auto", 0);
|
||||
|
||||
switch (vcodec_ctx->codec_id) {
|
||||
case AV_CODEC_ID_H264:
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
AVDictionary* opts = nullptr;
|
||||
av_dict_set(&opts, "threads", "auto", 0);
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
av_init_packet(&video_pkt);
|
||||
|
||||
// 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);
|
||||
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
|
||||
);
|
||||
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExportThread::setupAudio() {
|
||||
// if audio is disabled, no setup necessary
|
||||
if (!params.audio_enabled) return true;
|
||||
// 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;
|
||||
}
|
||||
// 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 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// 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,
|
||||
olive::ActiveSequence->audio_frequency,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
swr_init(swr_ctx);
|
||||
// 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,
|
||||
olive::ActiveSequence->audio_frequency,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
swr_init(swr_ctx);
|
||||
|
||||
// initialize raw audio frame
|
||||
audio_frame = av_frame_alloc();
|
||||
audio_frame->sample_rate = olive::ActiveSequence->audio_frequency;
|
||||
audio_frame->nb_samples = acodec_ctx->frame_size;
|
||||
if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 256; // 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) {
|
||||
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);
|
||||
// initialize raw audio frame
|
||||
audio_frame = av_frame_alloc();
|
||||
audio_frame->sample_rate = olive::ActiveSequence->audio_frequency;
|
||||
audio_frame->nb_samples = acodec_ctx->frame_size;
|
||||
if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 256; // 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) {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
av_init_packet(&audio_pkt);
|
||||
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
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);
|
||||
//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;
|
||||
}
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportThread::run() {
|
||||
panel_sequence_viewer->pause();
|
||||
panel_sequence_viewer->seek(params.start_frame);
|
||||
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());
|
||||
// copy filename
|
||||
QByteArray ba = params.filename.toUtf8();
|
||||
c_filename = new char[ba.size()+1];
|
||||
strcpy(c_filename, ba.data());
|
||||
|
||||
continueEncode = setupContainer();
|
||||
continueEncode = setupContainer();
|
||||
|
||||
if (params.video_enabled && continueEncode) continueEncode = setupVideo();
|
||||
if (params.video_enabled && continueEncode) continueEncode = setupVideo();
|
||||
|
||||
if (params.audio_enabled && continueEncode) continueEncode = setupAudio();
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
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()));
|
||||
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();
|
||||
mutex.lock();
|
||||
|
||||
while (olive::ActiveSequence->playhead <= params.end_frame && continueEncode) {
|
||||
start_time = QDateTime::currentMSecsSinceEpoch();
|
||||
while (olive::ActiveSequence->playhead <= params.end_frame && continueEncode) {
|
||||
start_time = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
if (params.audio_enabled) {
|
||||
compose_audio(nullptr, olive::ActiveSequence, 1);
|
||||
}
|
||||
if (params.video_enabled) {
|
||||
do {
|
||||
// TODO optimize by rendering the next frame while encoding the last
|
||||
renderer->start_render(nullptr, olive::ActiveSequence, nullptr, video_frame->data[0], video_frame->linesize[0]/4);
|
||||
waitCond.wait(&mutex);
|
||||
if (!continueEncode) break;
|
||||
} while (renderer->did_texture_fail());
|
||||
if (!continueEncode) break;
|
||||
}
|
||||
if (params.audio_enabled) {
|
||||
compose_audio(nullptr, olive::ActiveSequence, 1, true);
|
||||
}
|
||||
if (params.video_enabled) {
|
||||
do {
|
||||
// TODO optimize by rendering the next frame while encoding the last
|
||||
renderer->start_render(nullptr, olive::ActiveSequence, 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
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
// - 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);
|
||||
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));
|
||||
// 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;
|
||||
// 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) {
|
||||
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)) {
|
||||
// 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;
|
||||
// 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);
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
// send to encoder
|
||||
if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream, true)) continueEncode = false;
|
||||
|
||||
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);
|
||||
}
|
||||
file_audio_samples += swr_frame->nb_samples;
|
||||
}
|
||||
}
|
||||
|
||||
avformat_free_context(fmt_ctx);
|
||||
// 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);
|
||||
|
||||
if (sws_ctx != nullptr) {
|
||||
sws_freeContext(sws_ctx);
|
||||
}
|
||||
if (swr_ctx != nullptr) {
|
||||
swr_free(&swr_ctx);
|
||||
av_frame_free(&swr_frame);
|
||||
}
|
||||
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++;
|
||||
}
|
||||
|
||||
delete [] c_filename;
|
||||
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) {
|
||||
swr_free(&swr_ctx);
|
||||
av_frame_free(&swr_frame);
|
||||
}
|
||||
|
||||
delete [] c_filename;
|
||||
}
|
||||
|
||||
const QString &ExportThread::getError() {
|
||||
return export_error;
|
||||
return export_error;
|
||||
}
|
||||
|
||||
void ExportThread::wake() {
|
||||
mutex.lock();
|
||||
waitCond.wakeAll();
|
||||
mutex.unlock();
|
||||
mutex.lock();
|
||||
waitCond.wakeAll();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
+29
-22
@@ -29,7 +29,7 @@
|
||||
#include "project/projectelements.h"
|
||||
|
||||
#include "io/config.h"
|
||||
#include "playback/playback.h"
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include "io/previewgenerator.h"
|
||||
#include "effects/internal/voideffect.h"
|
||||
#include "debug.h"
|
||||
@@ -376,51 +376,56 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
|
||||
int media_id, stream_id;
|
||||
ClipPtr c(new Clip(s));
|
||||
|
||||
QColor clip_color;
|
||||
ClipSpeed speed_info = c->speed();
|
||||
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "name") {
|
||||
c->name = attr.value().toString();
|
||||
c->set_name(attr.value().toString());
|
||||
} else if (attr.name() == "enabled") {
|
||||
c->enabled = (attr.value() == "1");
|
||||
c->set_enabled(attr.value() == "1");
|
||||
} else if (attr.name() == "id") {
|
||||
c->load_id = attr.value().toInt();
|
||||
} else if (attr.name() == "clipin") {
|
||||
c->clip_in = attr.value().toLong();
|
||||
c->set_clip_in(attr.value().toLong());
|
||||
} else if (attr.name() == "in") {
|
||||
c->timeline_in = attr.value().toLong();
|
||||
c->set_timeline_in(attr.value().toLong());
|
||||
} else if (attr.name() == "out") {
|
||||
c->timeline_out = attr.value().toLong();
|
||||
c->set_timeline_out(attr.value().toLong());
|
||||
} else if (attr.name() == "track") {
|
||||
c->track = attr.value().toInt();
|
||||
c->set_track(attr.value().toInt());
|
||||
} else if (attr.name() == "r") {
|
||||
c->color_r = quint8(attr.value().toInt());
|
||||
clip_color.setRed(attr.value().toInt());
|
||||
} else if (attr.name() == "g") {
|
||||
c->color_g = quint8(attr.value().toInt());
|
||||
clip_color.setGreen(attr.value().toInt());
|
||||
} else if (attr.name() == "b") {
|
||||
c->color_b = quint8(attr.value().toInt());
|
||||
clip_color.setBlue(attr.value().toInt());
|
||||
} else if (attr.name() == "autoscale") {
|
||||
c->autoscale = (attr.value() == "1");
|
||||
c->set_autoscaled(attr.value() == "1");
|
||||
} else if (attr.name() == "media") {
|
||||
media_type = MEDIA_TYPE_FOOTAGE;
|
||||
media_id = attr.value().toInt();
|
||||
} else if (attr.name() == "stream") {
|
||||
stream_id = attr.value().toInt();
|
||||
} else if (attr.name() == "speed") {
|
||||
c->speed = attr.value().toDouble();
|
||||
speed_info.value = attr.value().toDouble();
|
||||
} else if (attr.name() == "maintainpitch") {
|
||||
c->maintain_audio_pitch = (attr.value() == "1");
|
||||
speed_info.maintain_audio_pitch = (attr.value() == "1");
|
||||
} else if (attr.name() == "reverse") {
|
||||
c->reverse = (attr.value() == "1");
|
||||
c->set_reversed(attr.value() == "1");
|
||||
} else if (attr.name() == "sequence") {
|
||||
media_type = MEDIA_TYPE_SEQUENCE;
|
||||
|
||||
// since we haven't finished loading sequences, we defer linking this until later
|
||||
c->media = nullptr;
|
||||
c->media_stream = attr.value().toInt();
|
||||
c->set_media(nullptr, attr.value().toInt());
|
||||
loaded_clips.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
c->set_color(clip_color);
|
||||
c->set_speed(speed_info);
|
||||
|
||||
// set media and media stream
|
||||
switch (media_type) {
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
@@ -428,8 +433,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
|
||||
for (int j=0;j<loaded_media_items.size();j++) {
|
||||
FootagePtr m = loaded_media_items.at(j)->to_footage();
|
||||
if (m->save_id == media_id) {
|
||||
c->media = loaded_media_items.at(j);
|
||||
c->media_stream = stream_id;
|
||||
c->set_media(loaded_media_items.at(j), stream_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -630,8 +634,9 @@ void LoadThread::run() {
|
||||
// attach nested sequence clips to their sequences
|
||||
for (int i=0;i<loaded_clips.size();i++) {
|
||||
for (int j=0;j<loaded_sequences.size();j++) {
|
||||
if (loaded_clips.at(i)->media == nullptr && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) {
|
||||
loaded_clips.at(i)->media = loaded_sequences.at(j);
|
||||
if (loaded_clips.at(i)->media() == nullptr
|
||||
&& loaded_clips.at(i)->media_stream_index() == loaded_sequences.at(j)->to_sequence()->save_id) {
|
||||
loaded_clips.at(i)->set_media(loaded_sequences.at(j), loaded_clips.at(i)->media_stream_index());
|
||||
loaded_clips.at(i)->refresh();
|
||||
break;
|
||||
}
|
||||
@@ -644,7 +649,7 @@ void LoadThread::run() {
|
||||
emit success(); // run in main thread
|
||||
|
||||
for (int i=0;i<loaded_media_items.size();i++) {
|
||||
panel_project->start_preview_generator(loaded_media_items.at(i), true);
|
||||
PreviewGenerator* pg = new PreviewGenerator(loaded_media_items.at(i));
|
||||
}
|
||||
} else {
|
||||
if (error_str.isEmpty()) {
|
||||
@@ -712,7 +717,9 @@ void LoadThread::success_func() {
|
||||
}
|
||||
|
||||
olive::MainWindow->setWindowModified(autorecovery);
|
||||
if (open_seq != nullptr) set_sequence(open_seq);
|
||||
if (open_seq != nullptr) {
|
||||
olive::Global->set_sequence(open_seq);
|
||||
}
|
||||
update_ui(false);
|
||||
}
|
||||
|
||||
|
||||
+117
-108
@@ -39,30 +39,37 @@
|
||||
|
||||
QSemaphore sem(5); // only 5 preview generators can run at one time
|
||||
|
||||
PreviewGenerator::PreviewGenerator(Media* i, FootagePtr m, bool r) :
|
||||
QThread(nullptr),
|
||||
fmt_ctx(nullptr),
|
||||
media(i),
|
||||
footage(m),
|
||||
retrieve_duration(false),
|
||||
contains_still_image(false),
|
||||
replace(r),
|
||||
cancelled(false)
|
||||
PreviewGenerator::PreviewGenerator(Media* i) :
|
||||
QThread(nullptr)
|
||||
{
|
||||
data_dir = QDir(get_data_dir().filePath("previews"));
|
||||
if (!data_dir.exists()) {
|
||||
data_dir.mkpath(".");
|
||||
fmt_ctx_ = (nullptr);
|
||||
media_ = (i);
|
||||
retrieve_duration_ = (false);
|
||||
contains_still_image_ = (false);
|
||||
cancelled_ = (false);
|
||||
footage_ = media_->to_footage();
|
||||
|
||||
footage_->preview_gen = this;
|
||||
|
||||
data_dir_ = QDir(get_data_dir().filePath("previews"));
|
||||
if (!data_dir_.exists()) {
|
||||
data_dir_.mkpath(".");
|
||||
}
|
||||
|
||||
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
|
||||
|
||||
// set up throbber animation
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_LOADING);
|
||||
|
||||
start(QThread::LowPriority);
|
||||
}
|
||||
|
||||
void PreviewGenerator::parse_media() {
|
||||
// detect video/audio streams in file
|
||||
for (int i=0;i<int(fmt_ctx->nb_streams);i++) {
|
||||
for (int i=0;i<int(fmt_ctx_->nb_streams);i++) {
|
||||
// Find the decoder for the video stream
|
||||
if (avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id) == nullptr) {
|
||||
qCritical() << "Unsupported codec in stream" << i << "of file" << footage->name;
|
||||
if (avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id) == nullptr) {
|
||||
qCritical() << "Unsupported codec in stream" << i << "of file" << footage_->name;
|
||||
} else {
|
||||
FootageStream ms;
|
||||
ms.preview_done = false;
|
||||
@@ -72,59 +79,62 @@ void PreviewGenerator::parse_media() {
|
||||
|
||||
bool append = false;
|
||||
|
||||
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
|
||||
&& fmt_ctx->streams[i]->codecpar->width > 0
|
||||
&& fmt_ctx->streams[i]->codecpar->height > 0) {
|
||||
if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
|
||||
&& fmt_ctx_->streams[i]->codecpar->width > 0
|
||||
&& fmt_ctx_->streams[i]->codecpar->height > 0) {
|
||||
|
||||
// heuristic to determine if video is a still image (if it is, we treat it differently in the playback/render process)
|
||||
if (fmt_ctx->streams[i]->avg_frame_rate.den == 0
|
||||
&& fmt_ctx->streams[i]->codecpar->codec_id != AV_CODEC_ID_DNXHD) { // silly hack but this is the only scenario i've seen this
|
||||
if (footage->url.contains('%')) {
|
||||
if (fmt_ctx_->streams[i]->avg_frame_rate.den == 0
|
||||
&& fmt_ctx_->streams[i]->codecpar->codec_id != AV_CODEC_ID_DNXHD) { // silly hack but this is the only scenario i've seen this
|
||||
if (footage_->url.contains('%')) {
|
||||
// must be an image sequence
|
||||
ms.video_frame_rate = 25;
|
||||
} else {
|
||||
ms.infinite_length = true;
|
||||
contains_still_image = true;
|
||||
contains_still_image_ = true;
|
||||
ms.video_frame_rate = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
// using ffmpeg's built-in heuristic
|
||||
ms.video_frame_rate = av_q2d(av_guess_frame_rate(fmt_ctx, fmt_ctx->streams[i], nullptr));
|
||||
ms.video_frame_rate = av_q2d(av_guess_frame_rate(fmt_ctx_, fmt_ctx_->streams[i], nullptr));
|
||||
}
|
||||
|
||||
ms.video_width = fmt_ctx->streams[i]->codecpar->width;
|
||||
ms.video_height = fmt_ctx->streams[i]->codecpar->height;
|
||||
ms.video_width = fmt_ctx_->streams[i]->codecpar->width;
|
||||
ms.video_height = fmt_ctx_->streams[i]->codecpar->height;
|
||||
|
||||
// default value, we get the true value later in generate_waveform()
|
||||
ms.video_auto_interlacing = VIDEO_PROGRESSIVE;
|
||||
ms.video_interlacing = VIDEO_PROGRESSIVE;
|
||||
|
||||
append = true;
|
||||
} else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
ms.audio_channels = fmt_ctx->streams[i]->codecpar->channels;
|
||||
ms.audio_layout = int(fmt_ctx->streams[i]->codecpar->channel_layout);
|
||||
ms.audio_frequency = fmt_ctx->streams[i]->codecpar->sample_rate;
|
||||
} else if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
ms.audio_channels = fmt_ctx_->streams[i]->codecpar->channels;
|
||||
ms.audio_layout = int(fmt_ctx_->streams[i]->codecpar->channel_layout);
|
||||
ms.audio_frequency = fmt_ctx_->streams[i]->codecpar->sample_rate;
|
||||
|
||||
append = true;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
QVector<FootageStream>& stream_list = (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ? footage->audio_tracks : footage->video_tracks;
|
||||
QVector<FootageStream>& stream_list = (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ?
|
||||
footage_->audio_tracks : footage_->video_tracks;
|
||||
|
||||
for (int j=0;j<stream_list.size();j++) {
|
||||
if (stream_list.at(j).file_index == i) {
|
||||
stream_list[j] = ms;
|
||||
append = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (append) stream_list.append(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
footage->length = fmt_ctx->duration;
|
||||
footage_->length = fmt_ctx_->duration;
|
||||
|
||||
if (fmt_ctx->duration == INT64_MIN) {
|
||||
retrieve_duration = true;
|
||||
if (fmt_ctx_->duration == INT64_MIN) {
|
||||
retrieve_duration_ = true;
|
||||
} else {
|
||||
finalize_media();
|
||||
}
|
||||
@@ -132,14 +142,14 @@ void PreviewGenerator::parse_media() {
|
||||
|
||||
bool PreviewGenerator::retrieve_preview(const QString& hash) {
|
||||
// returns true if generate_waveform must be run, false if we got all previews from cached files
|
||||
if (retrieve_duration) {
|
||||
if (retrieve_duration_) {
|
||||
//dout << "[NOTE] " << media->name << "needs to retrieve duration";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool found = true;
|
||||
for (int i=0;i<footage->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage->video_tracks[i];
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->video_tracks[i];
|
||||
QString thumb_path = get_thumbnail_path(hash, ms);
|
||||
QFile f(thumb_path);
|
||||
if (f.exists() && ms.video_preview.load(thumb_path)) {
|
||||
@@ -151,8 +161,8 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i=0;i<footage->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage->audio_tracks[i];
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->audio_tracks[i];
|
||||
QString waveform_path = get_waveform_path(hash, ms);
|
||||
QFile f(waveform_path);
|
||||
if (f.exists()) {
|
||||
@@ -172,12 +182,12 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) {
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
for (int i=0;i<footage->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage->video_tracks[i];
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->video_tracks[i];
|
||||
ms.preview_done = false;
|
||||
}
|
||||
for (int i=0;i<footage->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage->audio_tracks[i];
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->audio_tracks[i];
|
||||
ms.audio_preview.clear();
|
||||
ms.preview_done = false;
|
||||
}
|
||||
@@ -186,20 +196,20 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) {
|
||||
}
|
||||
|
||||
void PreviewGenerator::finalize_media() {
|
||||
footage->ready_lock.unlock();
|
||||
footage->ready = true;
|
||||
footage_->ready_lock.unlock();
|
||||
footage_->ready = true;
|
||||
|
||||
if (!cancelled) {
|
||||
if (footage->video_tracks.size() == 0) {
|
||||
olive::media_icon_service->SetMediaIcon(media, ICON_TYPE_AUDIO);
|
||||
} else if (contains_still_image) {
|
||||
olive::media_icon_service->SetMediaIcon(media, ICON_TYPE_IMAGE);
|
||||
if (!cancelled_) {
|
||||
if (footage_->video_tracks.size() == 0) {
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_AUDIO);
|
||||
} else if (contains_still_image_) {
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_IMAGE);
|
||||
} else {
|
||||
olive::media_icon_service->SetMediaIcon(media, ICON_TYPE_VIDEO);
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_VIDEO);
|
||||
}
|
||||
|
||||
if (olive::ActiveSequence != nullptr) {
|
||||
olive::ActiveSequence->RefreshClips(media);
|
||||
olive::ActiveSequence->RefreshClips(media_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,19 +224,19 @@ void PreviewGenerator::generate_waveform() {
|
||||
AVFrame* temp_frame = av_frame_alloc();
|
||||
|
||||
// stores codec contexts for format's streams
|
||||
AVCodecContext** codec_ctx = new AVCodecContext* [fmt_ctx->nb_streams];
|
||||
AVCodecContext** codec_ctx = new AVCodecContext* [fmt_ctx_->nb_streams];
|
||||
|
||||
// stores media lengths while scanning in case the format has no duration metadata
|
||||
int64_t* media_lengths = new int64_t[fmt_ctx->nb_streams]{0};
|
||||
int64_t* media_lengths = new int64_t[fmt_ctx_->nb_streams]{0};
|
||||
|
||||
// stores samples while scanning before they get sent to preview file
|
||||
qint16*** waveform_cache_data = new qint16** [fmt_ctx->nb_streams];
|
||||
qint16*** waveform_cache_data = new qint16** [fmt_ctx_->nb_streams];
|
||||
int waveform_cache_count = 0;
|
||||
|
||||
// defaults to false, sets to true if we find a valid stream to make a preview of
|
||||
bool create_previews = false;
|
||||
|
||||
for (unsigned int i=0;i<fmt_ctx->nb_streams;i++) {
|
||||
for (unsigned int i=0;i<fmt_ctx_->nb_streams;i++) {
|
||||
|
||||
// default to nullptr values for easier memory management later
|
||||
codec_ctx[i] = nullptr;
|
||||
@@ -234,32 +244,32 @@ void PreviewGenerator::generate_waveform() {
|
||||
|
||||
// we only generate previews for video and audio
|
||||
// and only if the thumbnail and waveform sizes are > 0
|
||||
if ((fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && olive::CurrentConfig.thumbnail_resolution > 0)
|
||||
|| (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && olive::CurrentConfig.waveform_resolution > 0)) {
|
||||
AVCodec* codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id);
|
||||
if ((fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && olive::CurrentConfig.thumbnail_resolution > 0)
|
||||
|| (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && olive::CurrentConfig.waveform_resolution > 0)) {
|
||||
AVCodec* codec = avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id);
|
||||
if (codec != nullptr) {
|
||||
|
||||
// alloc the context and load the params into it
|
||||
codec_ctx[i] = avcodec_alloc_context3(codec);
|
||||
avcodec_parameters_to_context(codec_ctx[i], fmt_ctx->streams[i]->codecpar);
|
||||
avcodec_parameters_to_context(codec_ctx[i], fmt_ctx_->streams[i]->codecpar);
|
||||
|
||||
// open the decoder
|
||||
avcodec_open2(codec_ctx[i], codec, nullptr);
|
||||
|
||||
// audio specific functions
|
||||
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
|
||||
// allocate sample cache for this stream
|
||||
waveform_cache_data[i] = new qint16* [fmt_ctx->streams[i]->codecpar->channels];
|
||||
waveform_cache_data[i] = new qint16* [fmt_ctx_->streams[i]->codecpar->channels];
|
||||
|
||||
// each channel gets a min and a max value so we allocate two ints for each one
|
||||
for (int j=0;j<fmt_ctx->streams[i]->codecpar->channels;j++) {
|
||||
for (int j=0;j<fmt_ctx_->streams[i]->codecpar->channels;j++) {
|
||||
waveform_cache_data[i][j] = new qint16[2];
|
||||
}
|
||||
|
||||
// if codec context has no defined channel layout, guess it from the channel count
|
||||
if (codec_ctx[i]->channel_layout == 0) {
|
||||
codec_ctx[i]->channel_layout = av_get_default_channel_layout(fmt_ctx->streams[i]->codecpar->channels);
|
||||
codec_ctx[i]->channel_layout = av_get_default_channel_layout(fmt_ctx_->streams[i]->codecpar->channels);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -280,16 +290,14 @@ void PreviewGenerator::generate_waveform() {
|
||||
|
||||
// get the ball rolling
|
||||
do {
|
||||
av_read_frame(fmt_ctx, packet);
|
||||
av_read_frame(fmt_ctx_, packet);
|
||||
} while (codec_ctx[packet->stream_index] == nullptr);
|
||||
avcodec_send_packet(codec_ctx[packet->stream_index], packet);
|
||||
|
||||
while (!end_of_file) {
|
||||
while (codec_ctx[packet->stream_index] == nullptr || avcodec_receive_frame(codec_ctx[packet->stream_index], temp_frame) == AVERROR(EAGAIN)) {
|
||||
av_packet_unref(packet);
|
||||
int read_ret = av_read_frame(fmt_ctx, packet);
|
||||
|
||||
//dout << "read frame for" << footage->name << footage->url << read_ret << "retrieve_duration:" << retrieve_duration << "eof:" << end_of_file << "packet pts:" << packet->pts;
|
||||
int read_ret = av_read_frame(fmt_ctx_, packet);
|
||||
|
||||
if (read_ret < 0) {
|
||||
end_of_file = true;
|
||||
@@ -306,9 +314,9 @@ void PreviewGenerator::generate_waveform() {
|
||||
}
|
||||
}
|
||||
if (!end_of_file) {
|
||||
FootageStream* s = footage->get_stream_from_file_index(fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index);
|
||||
FootageStream* s = footage_->get_stream_from_file_index(fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index);
|
||||
if (s != nullptr) {
|
||||
if (fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (!s->preview_done) {
|
||||
int dstH = olive::CurrentConfig.thumbnail_resolution;
|
||||
int dstW = qRound(dstH * (float(temp_frame->width)/float(temp_frame->height)));
|
||||
@@ -342,13 +350,13 @@ void PreviewGenerator::generate_waveform() {
|
||||
|
||||
sws_freeContext(sws_ctx);
|
||||
|
||||
if (!retrieve_duration) {
|
||||
if (!retrieve_duration_) {
|
||||
avcodec_close(codec_ctx[packet->stream_index]);
|
||||
codec_ctx[packet->stream_index] = nullptr;
|
||||
}
|
||||
}
|
||||
media_lengths[packet->stream_index]++;
|
||||
} else if (fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
} else if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
AVFrame* swr_frame = av_frame_alloc();
|
||||
swr_frame->channel_layout = temp_frame->channel_layout;
|
||||
swr_frame->sample_rate = temp_frame->sample_rate;
|
||||
@@ -419,7 +427,7 @@ void PreviewGenerator::generate_waveform() {
|
||||
|
||||
waveform_cache_count++;
|
||||
|
||||
if (cancelled) {
|
||||
if (cancelled_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -427,7 +435,7 @@ void PreviewGenerator::generate_waveform() {
|
||||
swr_free(&swr_ctx);
|
||||
av_frame_free(&swr_frame);
|
||||
|
||||
if (cancelled) {
|
||||
if (cancelled_) {
|
||||
end_of_file = true;
|
||||
break;
|
||||
}
|
||||
@@ -435,12 +443,12 @@ void PreviewGenerator::generate_waveform() {
|
||||
}
|
||||
|
||||
// check if we've got all our previews
|
||||
if (retrieve_duration) {
|
||||
if (retrieve_duration_) {
|
||||
done = false;
|
||||
} else if (footage->audio_tracks.size() == 0) {
|
||||
} else if (footage_->audio_tracks.size() == 0) {
|
||||
done = true;
|
||||
for (int i=0;i<footage->video_tracks.size();i++) {
|
||||
if (!footage->video_tracks.at(i).preview_done) {
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
if (!footage_->video_tracks.at(i).preview_done) {
|
||||
done = false;
|
||||
break;
|
||||
}
|
||||
@@ -457,7 +465,7 @@ void PreviewGenerator::generate_waveform() {
|
||||
av_frame_free(&temp_frame);
|
||||
av_packet_free(&packet);
|
||||
|
||||
for (unsigned int i=0;i<fmt_ctx->nb_streams;i++) {
|
||||
for (unsigned int i=0;i<fmt_ctx_->nb_streams;i++) {
|
||||
if (waveform_cache_data[i] != nullptr) {
|
||||
for (int j=0;j<codec_ctx[i]->channels;j++) {
|
||||
delete [] waveform_cache_data[i][j];
|
||||
@@ -472,22 +480,22 @@ void PreviewGenerator::generate_waveform() {
|
||||
}
|
||||
|
||||
// by this point, we'll have made all audio waveform previews
|
||||
for (int i=0;i<footage->audio_tracks.size();i++) {
|
||||
footage->audio_tracks[i].preview_done = true;
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
footage_->audio_tracks[i].preview_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (retrieve_duration) {
|
||||
footage->length = 0;
|
||||
if (retrieve_duration_) {
|
||||
footage_->length = 0;
|
||||
unsigned int maximum_stream = 0;
|
||||
for (unsigned int i=0;i<fmt_ctx->nb_streams;i++) {
|
||||
for (unsigned int i=0;i<fmt_ctx_->nb_streams;i++) {
|
||||
if (media_lengths[i] > media_lengths[maximum_stream]) {
|
||||
maximum_stream = i;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: length is currently retrieved as a frame count rather than a timestamp
|
||||
footage->length = qRound(double(media_lengths[maximum_stream]) / av_q2d(fmt_ctx->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE);
|
||||
footage_->length = qRound(double(media_lengths[maximum_stream]) / av_q2d(fmt_ctx_->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE);
|
||||
|
||||
finalize_media();
|
||||
}
|
||||
@@ -498,58 +506,59 @@ void PreviewGenerator::generate_waveform() {
|
||||
}
|
||||
|
||||
QString PreviewGenerator::get_thumbnail_path(const QString& hash, const FootageStream& ms) {
|
||||
return data_dir.filePath(QString("%1t%2").arg(hash, QString::number(ms.file_index)));
|
||||
return data_dir_.filePath(QString("%1t%2").arg(hash, QString::number(ms.file_index)));
|
||||
}
|
||||
|
||||
QString PreviewGenerator::get_waveform_path(const QString& hash, const FootageStream& ms) {
|
||||
return data_dir.filePath(QString("%1w%2").arg(hash, QString::number(ms.file_index)));
|
||||
return data_dir_.filePath(QString("%1w%2").arg(hash, QString::number(ms.file_index)));
|
||||
}
|
||||
|
||||
void PreviewGenerator::run() {
|
||||
Q_ASSERT(footage != nullptr);
|
||||
Q_ASSERT(media != nullptr);
|
||||
Q_ASSERT(footage_ != nullptr);
|
||||
Q_ASSERT(media_ != nullptr);
|
||||
|
||||
QByteArray ba = footage->url.toUtf8();
|
||||
const QString url = footage_->url;
|
||||
QByteArray ba = url.toUtf8();
|
||||
char* filename = new char[ba.size()+1];
|
||||
strcpy(filename, ba.data());
|
||||
|
||||
QString errorStr;
|
||||
bool error = false;
|
||||
int errCode = avformat_open_input(&fmt_ctx, filename, nullptr, nullptr);
|
||||
int errCode = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr);
|
||||
if(errCode != 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
errorStr = tr("Could not open file - %1").arg(err);
|
||||
error = true;
|
||||
} else {
|
||||
errCode = avformat_find_stream_info(fmt_ctx, nullptr);
|
||||
errCode = avformat_find_stream_info(fmt_ctx_, nullptr);
|
||||
if (errCode < 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
errorStr = tr("Could not find stream information - %1").arg(err);
|
||||
error = true;
|
||||
} else {
|
||||
av_dump_format(fmt_ctx, 0, filename, 0);
|
||||
av_dump_format(fmt_ctx_, 0, filename, 0);
|
||||
parse_media();
|
||||
|
||||
// see if we already have data for this
|
||||
QString hash = get_file_hash(footage->url);
|
||||
QString hash = get_file_hash(footage_->url);
|
||||
|
||||
if (retrieve_preview(hash)) {
|
||||
sem.acquire();
|
||||
|
||||
if (!cancelled) {
|
||||
if (!cancelled_) {
|
||||
generate_waveform();
|
||||
|
||||
if (!cancelled) {
|
||||
if (!cancelled_) {
|
||||
// save preview to file
|
||||
for (int i=0;i<footage->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage->video_tracks[i];
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->video_tracks[i];
|
||||
ms.video_preview.save(get_thumbnail_path(hash, ms), "PNG");
|
||||
//dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms);
|
||||
}
|
||||
for (int i=0;i<footage->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage->audio_tracks[i];
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->audio_tracks[i];
|
||||
QFile f(get_waveform_path(hash, ms));
|
||||
f.open(QFile::WriteOnly);
|
||||
f.write(ms.audio_preview.constData(), ms.audio_preview.size());
|
||||
@@ -562,25 +571,25 @@ void PreviewGenerator::run() {
|
||||
sem.release();
|
||||
}
|
||||
}
|
||||
avformat_close_input(&fmt_ctx);
|
||||
avformat_close_input(&fmt_ctx_);
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
if (!cancelled_) {
|
||||
if (error) {
|
||||
media->update_tooltip(errorStr);
|
||||
olive::media_icon_service->SetMediaIcon(media, ICON_TYPE_ERROR);
|
||||
footage->invalid = true;
|
||||
footage->ready_lock.unlock();
|
||||
media_->update_tooltip(errorStr);
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_ERROR);
|
||||
footage_->invalid = true;
|
||||
footage_->ready_lock.unlock();
|
||||
} else {
|
||||
media->update_tooltip();
|
||||
media_->update_tooltip();
|
||||
}
|
||||
}
|
||||
|
||||
delete [] filename;
|
||||
footage->preview_gen = nullptr;
|
||||
footage_->preview_gen = nullptr;
|
||||
}
|
||||
|
||||
void PreviewGenerator::cancel() {
|
||||
cancelled = true;
|
||||
cancelled_ = true;
|
||||
wait();
|
||||
}
|
||||
|
||||
+10
-10
@@ -39,24 +39,24 @@ class PreviewGenerator : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PreviewGenerator(Media*, FootagePtr, bool);
|
||||
PreviewGenerator(Media*);
|
||||
void run();
|
||||
void cancel();
|
||||
private:
|
||||
void parse_media();
|
||||
bool retrieve_preview(const QString &hash);
|
||||
void generate_waveform();
|
||||
void finalize_media();
|
||||
AVFormatContext* fmt_ctx;
|
||||
Media* media;
|
||||
FootagePtr footage;
|
||||
bool retrieve_duration;
|
||||
bool contains_still_image;
|
||||
bool replace;
|
||||
bool cancelled;
|
||||
QDir data_dir;
|
||||
void finalize_media();
|
||||
QString get_thumbnail_path(const QString &hash, const FootageStream &ms);
|
||||
QString get_waveform_path(const QString& hash, const FootageStream &ms);
|
||||
|
||||
AVFormatContext* fmt_ctx_;
|
||||
Media* media_;
|
||||
FootagePtr footage_;
|
||||
bool retrieve_duration_;
|
||||
bool contains_still_image_;
|
||||
bool cancelled_;
|
||||
QDir data_dir_;
|
||||
};
|
||||
|
||||
#endif // PREVIEWGENERATOR_H
|
||||
|
||||
+285
-281
@@ -21,6 +21,7 @@
|
||||
#include "proxygenerator.h"
|
||||
|
||||
#include "io/path.h"
|
||||
#include "io/previewgenerator.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QDir>
|
||||
@@ -31,9 +32,9 @@
|
||||
#include <QDebug>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
enum AVCodecID temp_enc_codec = AV_CODEC_ID_PRORES;
|
||||
@@ -41,370 +42,373 @@ enum AVCodecID temp_enc_codec = AV_CODEC_ID_PRORES;
|
||||
ProxyGenerator::ProxyGenerator() : cancelled(false) {}
|
||||
|
||||
void ProxyGenerator::transcode(const ProxyInfo& info) {
|
||||
// set progress to 0
|
||||
current_progress = 0.0;
|
||||
FootagePtr footage = info.media->to_footage();
|
||||
|
||||
// open input file
|
||||
AVFormatContext* input_fmt_ctx = nullptr;
|
||||
avformat_open_input(&input_fmt_ctx, info.footage->url.toUtf8(), nullptr, nullptr);
|
||||
// set progress to 0
|
||||
current_progress = 0.0;
|
||||
|
||||
// open output file
|
||||
AVFormatContext* output_fmt_ctx = nullptr;
|
||||
avformat_alloc_output_context2(&output_fmt_ctx, nullptr, nullptr, info.path.toUtf8());
|
||||
// open input file
|
||||
AVFormatContext* input_fmt_ctx = nullptr;
|
||||
avformat_open_input(&input_fmt_ctx, footage->url.toUtf8(), nullptr, nullptr);
|
||||
|
||||
// open output file
|
||||
AVFormatContext* output_fmt_ctx = nullptr;
|
||||
avformat_alloc_output_context2(&output_fmt_ctx, nullptr, nullptr, info.path.toUtf8());
|
||||
|
||||
// open output file writing handle
|
||||
avio_open(&output_fmt_ctx->pb, info.path.toUtf8(), AVIO_FLAG_WRITE);
|
||||
|
||||
// get stream info from input file
|
||||
avformat_find_stream_info(input_fmt_ctx, nullptr);
|
||||
|
||||
// create array of input decoders
|
||||
QVector<AVCodecContext*> input_streams;
|
||||
input_streams.resize(input_fmt_ctx->nb_streams);
|
||||
input_streams.fill(nullptr);
|
||||
|
||||
// create array of output encoders
|
||||
QVector<AVCodecContext*> output_streams;
|
||||
output_streams.resize(input_fmt_ctx->nb_streams);
|
||||
output_streams.fill(nullptr);
|
||||
|
||||
// create array of swscale contexts for pixel format conversion
|
||||
QVector<SwsContext*> sws_contexts;
|
||||
sws_contexts.resize(input_fmt_ctx->nb_streams);
|
||||
sws_contexts.fill(nullptr);
|
||||
|
||||
// open output file writing handle
|
||||
avio_open(&output_fmt_ctx->pb, info.path.toUtf8(), AVIO_FLAG_WRITE);
|
||||
|
||||
// get stream info from input file
|
||||
avformat_find_stream_info(input_fmt_ctx, nullptr);
|
||||
|
||||
// create array of input decoders
|
||||
QVector<AVCodecContext*> input_streams;
|
||||
input_streams.resize(input_fmt_ctx->nb_streams);
|
||||
input_streams.fill(nullptr);
|
||||
|
||||
// create array of output encoders
|
||||
QVector<AVCodecContext*> output_streams;
|
||||
output_streams.resize(input_fmt_ctx->nb_streams);
|
||||
output_streams.fill(nullptr);
|
||||
// loop through file to find compatible video streams
|
||||
for (int i=0;i<int(input_fmt_ctx->nb_streams);i++) {
|
||||
AVStream* in_stream = input_fmt_ctx->streams[i];
|
||||
|
||||
// create new stream in output
|
||||
AVStream* out_stream = avformat_new_stream(output_fmt_ctx, nullptr);
|
||||
out_stream->id = in_stream->id;
|
||||
|
||||
// find decoder for this codec
|
||||
AVCodec* dec_codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
|
||||
|
||||
// find encoder for chosen proxy type
|
||||
AVCodec* enc_codec = avcodec_find_encoder(temp_enc_codec);
|
||||
|
||||
// we only transcode video streams, others we just passthrough
|
||||
if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && dec_codec != nullptr) {
|
||||
|
||||
// allocate decoding context for this stream
|
||||
AVCodecContext* dec_ctx = avcodec_alloc_context3(dec_codec);
|
||||
|
||||
// copy parameters from stream to decoding context
|
||||
avcodec_parameters_to_context(dec_ctx, in_stream->codecpar);
|
||||
|
||||
// open decoder
|
||||
avcodec_open2(dec_ctx, dec_codec, nullptr);
|
||||
|
||||
// store decoding context in array
|
||||
input_streams[i] = dec_ctx;
|
||||
|
||||
// create array of swscale contexts for pixel format conversion
|
||||
QVector<SwsContext*> sws_contexts;
|
||||
sws_contexts.resize(input_fmt_ctx->nb_streams);
|
||||
sws_contexts.fill(nullptr);
|
||||
|
||||
// loop through file to find compatible video streams
|
||||
for (int i=0;i<int(input_fmt_ctx->nb_streams);i++) {
|
||||
AVStream* in_stream = input_fmt_ctx->streams[i];
|
||||
|
||||
// create new stream in output
|
||||
AVStream* out_stream = avformat_new_stream(output_fmt_ctx, nullptr);
|
||||
out_stream->id = in_stream->id;
|
||||
|
||||
// find decoder for this codec
|
||||
AVCodec* dec_codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
|
||||
// retrieve more information about this stream
|
||||
av_dump_format(input_fmt_ctx, i, footage->url.toUtf8(), 0);
|
||||
|
||||
// find encoder for chosen proxy type
|
||||
AVCodec* enc_codec = avcodec_find_encoder(temp_enc_codec);
|
||||
|
||||
// we only transcode video streams, others we just passthrough
|
||||
if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && dec_codec != nullptr) {
|
||||
|
||||
// allocate decoding context for this stream
|
||||
AVCodecContext* dec_ctx = avcodec_alloc_context3(dec_codec);
|
||||
|
||||
// copy parameters from stream to decoding context
|
||||
avcodec_parameters_to_context(dec_ctx, in_stream->codecpar);
|
||||
// allocate encoding context for this stream
|
||||
AVCodecContext* enc_ctx = avcodec_alloc_context3(enc_codec);
|
||||
|
||||
// open decoder
|
||||
avcodec_open2(dec_ctx, dec_codec, nullptr);
|
||||
// copy properties from decoding context to encoding context
|
||||
enc_ctx->codec_id = temp_enc_codec;
|
||||
enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
enc_ctx->width = qFloor(dec_ctx->width*info.size_multiplier);
|
||||
enc_ctx->height = qFloor(dec_ctx->height*info.size_multiplier);
|
||||
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
|
||||
enc_ctx->pix_fmt = enc_codec->pix_fmts[0];
|
||||
enc_ctx->framerate = dec_ctx->framerate;
|
||||
enc_ctx->time_base = in_stream->time_base;
|
||||
out_stream->time_base = in_stream->time_base;
|
||||
|
||||
// store decoding context in array
|
||||
input_streams[i] = dec_ctx;
|
||||
// if format uses global headers, add flag to enc_ctx
|
||||
if (output_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
// retrieve more information about this stream
|
||||
av_dump_format(input_fmt_ctx, i, info.footage->url.toUtf8(), 0);
|
||||
// set encoder options (mostly just multithreading)
|
||||
AVDictionary* opts = nullptr;
|
||||
av_dict_set(&opts, "threads", "auto", 0);
|
||||
|
||||
// allocate encoding context for this stream
|
||||
AVCodecContext* enc_ctx = avcodec_alloc_context3(enc_codec);
|
||||
// open encoder
|
||||
avcodec_open2(enc_ctx, enc_codec, &opts);
|
||||
|
||||
// copy properties from decoding context to encoding context
|
||||
enc_ctx->codec_id = temp_enc_codec;
|
||||
enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
enc_ctx->width = qFloor(dec_ctx->width*info.size_multiplier);
|
||||
enc_ctx->height = qFloor(dec_ctx->height*info.size_multiplier);
|
||||
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
|
||||
enc_ctx->pix_fmt = enc_codec->pix_fmts[0];
|
||||
enc_ctx->framerate = dec_ctx->framerate;
|
||||
enc_ctx->time_base = in_stream->time_base;
|
||||
// copy parameters from encoding context to stream
|
||||
avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
|
||||
|
||||
out_stream->time_base = in_stream->time_base;
|
||||
// store encoding context in array
|
||||
output_streams[i] = enc_ctx;
|
||||
|
||||
// if format uses global headers, add flag to enc_ctx
|
||||
if (output_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
// create swscontext for this stream
|
||||
SwsContext* sws_ctx = sws_getContext(
|
||||
in_stream->codecpar->width,
|
||||
in_stream->codecpar->height,
|
||||
static_cast<enum AVPixelFormat>(in_stream->codecpar->format),
|
||||
enc_ctx->width,
|
||||
enc_ctx->height,
|
||||
enc_ctx->pix_fmt,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// set encoder options (mostly just multithreading)
|
||||
AVDictionary* opts = nullptr;
|
||||
av_dict_set(&opts, "threads", "auto", 0);
|
||||
sws_contexts[i] = sws_ctx;
|
||||
} else {
|
||||
avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
|
||||
}
|
||||
}
|
||||
|
||||
// open encoder
|
||||
avcodec_open2(enc_ctx, enc_codec, &opts);
|
||||
// write video header
|
||||
avformat_write_header(output_fmt_ctx, nullptr);
|
||||
|
||||
// packet that av_read_frame will dump file packets into
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
|
||||
// frame that decoder will decode into
|
||||
AVFrame* dec_frame = av_frame_alloc();
|
||||
|
||||
// copy parameters from encoding context to stream
|
||||
avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
|
||||
// main transcoding loop
|
||||
while (!skip) {
|
||||
|
||||
// store encoding context in array
|
||||
output_streams[i] = enc_ctx;
|
||||
// cache stream index
|
||||
int stream_index = packet.stream_index;
|
||||
|
||||
// create swscontext for this stream
|
||||
SwsContext* sws_ctx = sws_getContext(
|
||||
in_stream->codecpar->width,
|
||||
in_stream->codecpar->height,
|
||||
static_cast<enum AVPixelFormat>(in_stream->codecpar->format),
|
||||
enc_ctx->width,
|
||||
enc_ctx->height,
|
||||
enc_ctx->pix_fmt,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
// retrieve frame from decoder (this will clear the last frame so we don't have to do that)
|
||||
int read_ret = -1;
|
||||
int recfr_ret = -1;
|
||||
do {
|
||||
// read from input file
|
||||
read_ret = av_read_frame(input_fmt_ctx, &packet);
|
||||
|
||||
sws_contexts[i] = sws_ctx;
|
||||
} else {
|
||||
avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
|
||||
}
|
||||
}
|
||||
// handle errors
|
||||
if (read_ret < 0) {
|
||||
|
||||
// write video header
|
||||
avformat_write_header(output_fmt_ctx, nullptr);
|
||||
// AVERROR_EOF means we've simply reached the end of the file, otherwise this is an error
|
||||
if (read_ret != AVERROR_EOF) {
|
||||
qWarning() << "Proxy generation for file" << footage->url << "ended prematurely";
|
||||
}
|
||||
|
||||
// packet that av_read_frame will dump file packets into
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
// either way, we shall abort reading
|
||||
break;
|
||||
}
|
||||
|
||||
// frame that decoder will decode into
|
||||
AVFrame* dec_frame = av_frame_alloc();
|
||||
stream_index = packet.stream_index;
|
||||
|
||||
// main transcoding loop
|
||||
while (!skip) {
|
||||
// determine whether this frame is from a stream we're transcoding
|
||||
if (input_streams.at(stream_index) == nullptr) {
|
||||
// if we didn't allocate a decoder for this earlier, we just pass it through
|
||||
|
||||
// cache stream index
|
||||
int stream_index = packet.stream_index;
|
||||
av_packet_rescale_ts(&packet, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base);
|
||||
|
||||
// retrieve frame from decoder (this will clear the last frame so we don't have to do that)
|
||||
int read_ret = -1;
|
||||
int recfr_ret = -1;
|
||||
do {
|
||||
// read from input file
|
||||
read_ret = av_read_frame(input_fmt_ctx, &packet);
|
||||
// write packet to output
|
||||
av_interleaved_write_frame(output_fmt_ctx, &packet);
|
||||
|
||||
// handle errors
|
||||
if (read_ret < 0) {
|
||||
} else {
|
||||
// we're going to transcode this packet.
|
||||
|
||||
// AVERROR_EOF means we've simply reached the end of the file, otherwise this is an error
|
||||
if (read_ret != AVERROR_EOF) {
|
||||
qWarning() << "Proxy generation for file" << info.footage->url << "ended prematurely";
|
||||
}
|
||||
// send packet to decoder
|
||||
avcodec_send_packet(input_streams.at(stream_index), &packet);
|
||||
|
||||
// either way, we shall abort reading
|
||||
break;
|
||||
}
|
||||
// use timestamp and stream duration to create a rough estimation of the progress through this file
|
||||
current_progress = qCeil((double(packet.pts)/double(input_fmt_ctx->streams[packet.stream_index]->duration))*100);
|
||||
|
||||
stream_index = packet.stream_index;
|
||||
}
|
||||
|
||||
// determine whether this frame is from a stream we're transcoding
|
||||
if (input_streams.at(stream_index) == nullptr) {
|
||||
// if we didn't allocate a decoder for this earlier, we just pass it through
|
||||
// free packet allocated by av_read_frame
|
||||
av_packet_unref(&packet);
|
||||
} while ((recfr_ret = avcodec_receive_frame(input_streams.at(packet.stream_index), dec_frame)) == AVERROR(EAGAIN) && !skip);
|
||||
|
||||
av_packet_rescale_ts(&packet, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base);
|
||||
// error/eof handling - cancel while loop
|
||||
if (read_ret < 0 || skip) {
|
||||
break;
|
||||
}
|
||||
|
||||
// write packet to output
|
||||
av_interleaved_write_frame(output_fmt_ctx, &packet);
|
||||
// free packet as we're about to use it for encoding
|
||||
av_packet_unref(&packet);
|
||||
|
||||
} else {
|
||||
// we're going to transcode this packet.
|
||||
// rescale input frame timestamp to output timestamp
|
||||
dec_frame->pts = av_rescale_q(dec_frame->pts,
|
||||
input_fmt_ctx->streams[stream_index]->time_base,
|
||||
output_fmt_ctx->streams[stream_index]->time_base);
|
||||
|
||||
// send packet to decoder
|
||||
avcodec_send_packet(input_streams.at(stream_index), &packet);
|
||||
// determine if the pix_fmt, width, and/or height is different, so if we need to convert
|
||||
bool convert_pix_fmt = (output_streams.at(stream_index)->pix_fmt != input_streams.at(stream_index)->pix_fmt
|
||||
|| output_streams.at(stream_index)->width != input_streams.at(stream_index)->width
|
||||
|| output_streams.at(stream_index)->height != input_streams.at(stream_index)->height);
|
||||
|
||||
// use timestamp and stream duration to create a rough estimation of the progress through this file
|
||||
current_progress = qCeil((double(packet.pts)/double(input_fmt_ctx->streams[packet.stream_index]->duration))*100);
|
||||
// create reference to the frame to be sent to the encoder
|
||||
AVFrame* enc_frame = dec_frame;
|
||||
|
||||
}
|
||||
if (convert_pix_fmt) {
|
||||
// create sws frame for pixel format conversion
|
||||
enc_frame = av_frame_alloc();
|
||||
enc_frame->width = output_streams.at(stream_index)->width;
|
||||
enc_frame->height = output_streams.at(stream_index)->height;
|
||||
enc_frame->format = output_streams.at(stream_index)->pix_fmt;
|
||||
av_frame_get_buffer(enc_frame, 0);
|
||||
|
||||
// free packet allocated by av_read_frame
|
||||
av_packet_unref(&packet);
|
||||
} while ((recfr_ret = avcodec_receive_frame(input_streams.at(packet.stream_index), dec_frame)) == AVERROR(EAGAIN) && !skip);
|
||||
// convert pixel format to format expected by the encoder
|
||||
sws_scale(sws_contexts.at(stream_index), dec_frame->data, dec_frame->linesize, 0, dec_frame->height, enc_frame->data, enc_frame->linesize);
|
||||
|
||||
// error/eof handling - cancel while loop
|
||||
if (read_ret < 0 || skip) {
|
||||
break;
|
||||
}
|
||||
// set same pts as dec_frame
|
||||
enc_frame->pts = dec_frame->pts;
|
||||
}
|
||||
|
||||
// free packet as we're about to use it for encoding
|
||||
av_packet_unref(&packet);
|
||||
// send frame to encoder
|
||||
avcodec_send_frame(output_streams.at(stream_index), enc_frame);
|
||||
|
||||
// rescale input frame timestamp to output timestamp
|
||||
av_rescale_q(dec_frame->pts, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base);
|
||||
if (convert_pix_fmt) {
|
||||
// free sws frame since we made one before
|
||||
av_frame_free(&enc_frame);
|
||||
}
|
||||
|
||||
// determine if the pix_fmt, width, and/or height is different, so if we need to convert
|
||||
bool convert_pix_fmt = (output_streams.at(stream_index)->pix_fmt != input_streams.at(stream_index)->pix_fmt
|
||||
|| output_streams.at(stream_index)->width != input_streams.at(stream_index)->width
|
||||
|| output_streams.at(stream_index)->height != input_streams.at(stream_index)->height);
|
||||
// return value for packet receiving
|
||||
int recret;
|
||||
|
||||
// create reference to the frame to be sent to the encoder
|
||||
AVFrame* enc_frame = dec_frame;
|
||||
// loop through receiving packets
|
||||
while ((recret = avcodec_receive_packet(output_streams.at(stream_index), &packet)) >= 0 && !skip) {
|
||||
|
||||
if (convert_pix_fmt) {
|
||||
// create sws frame for pixel format conversion
|
||||
enc_frame = av_frame_alloc();
|
||||
enc_frame->width = output_streams.at(stream_index)->width;
|
||||
enc_frame->height = output_streams.at(stream_index)->height;
|
||||
enc_frame->format = output_streams.at(stream_index)->pix_fmt;
|
||||
av_frame_get_buffer(enc_frame, 0);
|
||||
// set packet stream index to current stream index
|
||||
packet.stream_index = stream_index;
|
||||
|
||||
// convert pixel format to format expected by the encoder
|
||||
sws_scale(sws_contexts.at(stream_index), dec_frame->data, dec_frame->linesize, 0, dec_frame->height, enc_frame->data, enc_frame->linesize);
|
||||
// write frame to file
|
||||
av_interleaved_write_frame(output_fmt_ctx, &packet);
|
||||
|
||||
// set same pts as dec_frame
|
||||
enc_frame->pts = dec_frame->pts;
|
||||
}
|
||||
// unref old packet
|
||||
av_packet_unref(&packet);
|
||||
|
||||
// send frame to encoder
|
||||
avcodec_send_frame(output_streams.at(stream_index), enc_frame);
|
||||
}
|
||||
|
||||
if (convert_pix_fmt) {
|
||||
// free sws frame since we made one before
|
||||
av_frame_free(&enc_frame);
|
||||
}
|
||||
}
|
||||
|
||||
// return value for packet receiving
|
||||
int recret;
|
||||
// free dec_frame
|
||||
av_frame_free(&dec_frame);
|
||||
|
||||
// loop through receiving packets
|
||||
while ((recret = avcodec_receive_packet(output_streams.at(stream_index), &packet)) >= 0 && !skip) {
|
||||
// write video trailer
|
||||
av_write_trailer(output_fmt_ctx);
|
||||
|
||||
// set packet stream index to current stream index
|
||||
packet.stream_index = stream_index;
|
||||
// free stream contexts
|
||||
for (int i=0;i<int(input_fmt_ctx->nb_streams);i++) {
|
||||
if (input_streams[i] != nullptr) {
|
||||
// free swscale contexts
|
||||
sws_freeContext(sws_contexts[i]);
|
||||
|
||||
// write frame to file
|
||||
av_interleaved_write_frame(output_fmt_ctx, &packet);
|
||||
// free input decoding context
|
||||
avcodec_close(input_streams[i]);
|
||||
avcodec_free_context(&input_streams[i]);
|
||||
|
||||
// unref old packet
|
||||
av_packet_unref(&packet);
|
||||
// free output encoding context
|
||||
avcodec_close(output_streams[i]);
|
||||
avcodec_free_context(&output_streams[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// close output file handle
|
||||
avio_closep(&output_fmt_ctx->pb);
|
||||
|
||||
}
|
||||
// close output file
|
||||
avformat_free_context(output_fmt_ctx);
|
||||
|
||||
// free dec_frame
|
||||
av_frame_free(&dec_frame);
|
||||
// close input file
|
||||
avformat_close_input(&input_fmt_ctx);
|
||||
|
||||
// write video trailer
|
||||
av_write_trailer(output_fmt_ctx);
|
||||
// set footage to use newly generated proxy
|
||||
footage->proxy = true;
|
||||
footage->proxy_path = info.path;
|
||||
|
||||
// free stream contexts
|
||||
for (int i=0;i<int(input_fmt_ctx->nb_streams);i++) {
|
||||
if (input_streams[i] != nullptr) {
|
||||
// free swscale contexts
|
||||
sws_freeContext(sws_contexts[i]);
|
||||
|
||||
// free input decoding context
|
||||
avcodec_close(input_streams[i]);
|
||||
avcodec_free_context(&input_streams[i]);
|
||||
|
||||
// free output encoding context
|
||||
avcodec_close(output_streams[i]);
|
||||
avcodec_free_context(&output_streams[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// close output file handle
|
||||
avio_closep(&output_fmt_ctx->pb);
|
||||
|
||||
// close output file
|
||||
avformat_free_context(output_fmt_ctx);
|
||||
|
||||
// close input file
|
||||
avformat_close_input(&input_fmt_ctx);
|
||||
|
||||
// set footage to use newly generated proxy
|
||||
info.footage->proxy = true;
|
||||
info.footage->proxy_path = info.path;
|
||||
|
||||
qInfo() << "Finished creating proxy for" << info.footage->url;
|
||||
QMetaObject::invokeMethod(olive::MainWindow->statusBar(),
|
||||
"showMessage",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, tr("Finished generating proxy for \"%1\"").arg(info.footage->url)));
|
||||
qInfo() << "Finished creating proxy for" << footage->url;
|
||||
QMetaObject::invokeMethod(olive::MainWindow->statusBar(),
|
||||
"showMessage",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, tr("Finished generating proxy for \"%1\"").arg(footage->url)));
|
||||
}
|
||||
|
||||
// main proxy generating loop
|
||||
void ProxyGenerator::run() {
|
||||
// mutex used for thread safe signalling
|
||||
mutex.lock();
|
||||
// mutex used for thread safe signalling
|
||||
mutex.lock();
|
||||
|
||||
while (!cancelled) {
|
||||
// wait for queue() to be called
|
||||
waitCond.wait(&mutex);
|
||||
while (!cancelled) {
|
||||
// wait for queue() to be called
|
||||
waitCond.wait(&mutex);
|
||||
|
||||
// quit thread if cancel() was called
|
||||
if (cancelled) break;
|
||||
// quit thread if cancel() was called
|
||||
if (cancelled) break;
|
||||
|
||||
// loop through queue until the queue is empty
|
||||
while (proxy_queue.size() > 0) {
|
||||
// loop through queue until the queue is empty
|
||||
while (proxy_queue.size() > 0) {
|
||||
|
||||
// grab proxy info
|
||||
const ProxyInfo& info = proxy_queue.first();
|
||||
// grab proxy info
|
||||
const ProxyInfo& info = proxy_queue.first();
|
||||
|
||||
// create directory for info
|
||||
QFileInfo(info.path).dir().mkpath(".");
|
||||
// create directory for info
|
||||
QFileInfo(info.path).dir().mkpath(".");
|
||||
|
||||
// set skip to false
|
||||
skip = false;
|
||||
// set skip to false
|
||||
skip = false;
|
||||
|
||||
// transcode proxy
|
||||
transcode(info);
|
||||
// transcode proxy
|
||||
transcode(info);
|
||||
|
||||
// we're finished with this proxy, remove it
|
||||
proxy_queue.removeFirst();
|
||||
// we're finished with this proxy, remove it
|
||||
proxy_queue.removeFirst();
|
||||
|
||||
// quit loop if cancel() was called
|
||||
if (cancelled) break;
|
||||
// quit loop if cancel() was called
|
||||
if (cancelled) break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex.unlock();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
// called to add footage to generate proxies for
|
||||
void ProxyGenerator::queue(const ProxyInfo &info) {
|
||||
// remove any queued proxies with the same footage
|
||||
if (!proxy_queue.isEmpty()
|
||||
&& proxy_queue.first().footage == info.footage) {
|
||||
// if the thread is currently processing a proxy with the same footage, abort it
|
||||
skip = true;
|
||||
}
|
||||
// remove any queued proxies with the same footage
|
||||
if (!proxy_queue.isEmpty()
|
||||
&& proxy_queue.first().media == info.media) {
|
||||
// if the thread is currently processing a proxy with the same footage, abort it
|
||||
skip = true;
|
||||
}
|
||||
|
||||
// scan through the rest of the queue for another proxy with the same footage (start with 1 since we already processed first())
|
||||
for (int i=1;i<proxy_queue.size();i++) {
|
||||
if (proxy_queue.at(i).footage == info.footage) {
|
||||
// found a duplicate, assume the one we're queuing now overrides and delete it
|
||||
proxy_queue.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
// scan through the rest of the queue for another proxy with the same footage (start with 1 since we already processed first())
|
||||
for (int i=1;i<proxy_queue.size();i++) {
|
||||
if (proxy_queue.at(i).media == info.media) {
|
||||
// found a duplicate, assume the one we're queuing now overrides and delete it
|
||||
proxy_queue.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// add proxy info to queue
|
||||
proxy_queue.append(info);
|
||||
// add proxy info to queue
|
||||
proxy_queue.append(info);
|
||||
|
||||
// wake proxy thread loop if sleeping
|
||||
waitCond.wakeAll();
|
||||
// wake proxy thread loop if sleeping
|
||||
waitCond.wakeAll();
|
||||
}
|
||||
|
||||
// to be called from another thread to terminate the proxy generator thread and free it
|
||||
void ProxyGenerator::cancel() {
|
||||
// signal to thread to cancel
|
||||
cancelled = true;
|
||||
skip = true;
|
||||
// signal to thread to cancel
|
||||
cancelled = true;
|
||||
skip = true;
|
||||
|
||||
// if signal is sleeping, wake it to cancel correctly
|
||||
waitCond.wakeAll();
|
||||
// if signal is sleeping, wake it to cancel correctly
|
||||
waitCond.wakeAll();
|
||||
|
||||
// wait for thread to finish
|
||||
wait();
|
||||
// wait for thread to finish
|
||||
wait();
|
||||
}
|
||||
|
||||
double ProxyGenerator::get_proxy_progress(FootagePtr f) {
|
||||
if (proxy_queue.first().footage == f) {
|
||||
return current_progress;
|
||||
}
|
||||
return 0.0;
|
||||
double ProxyGenerator::get_proxy_progress(Media* m) {
|
||||
if (proxy_queue.first().media == m) {
|
||||
return current_progress;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// proxy generator is a global omnipotent entity
|
||||
ProxyGenerator proxy_generator;
|
||||
ProxyGenerator olive::proxy_generator;
|
||||
|
||||
+28
-26
@@ -26,45 +26,47 @@
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "project/footage.h"
|
||||
#include "project/media.h"
|
||||
|
||||
struct ProxyInfo {
|
||||
FootagePtr footage;
|
||||
double size_multiplier;
|
||||
int codec_type;
|
||||
QString path;
|
||||
Media* media;
|
||||
double size_multiplier;
|
||||
int codec_type;
|
||||
QString path;
|
||||
};
|
||||
|
||||
class ProxyGenerator : public QThread {
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProxyGenerator();
|
||||
void run();
|
||||
void queue(const ProxyInfo& info);
|
||||
void cancel();
|
||||
double get_proxy_progress(FootagePtr f);
|
||||
ProxyGenerator();
|
||||
void run();
|
||||
void queue(const ProxyInfo& info);
|
||||
void cancel();
|
||||
double get_proxy_progress(Media *f);
|
||||
private:
|
||||
// queue of footage to process proxies for
|
||||
QVector<ProxyInfo> proxy_queue;
|
||||
// queue of footage to process proxies for
|
||||
QVector<ProxyInfo> proxy_queue;
|
||||
|
||||
// threading objects
|
||||
QWaitCondition waitCond;
|
||||
QMutex mutex;
|
||||
// threading objects
|
||||
QWaitCondition waitCond;
|
||||
QMutex mutex;
|
||||
|
||||
// set to true if you want to permanently close ProxyGenerator
|
||||
bool cancelled;
|
||||
// set to true if you want to permanently close ProxyGenerator
|
||||
bool cancelled;
|
||||
|
||||
// set to true if you want to abort the footage currently being processed
|
||||
bool skip;
|
||||
// set to true if you want to abort the footage currently being processed
|
||||
bool skip;
|
||||
|
||||
// stores progress in percent of proxy currently being processed
|
||||
double current_progress;
|
||||
// stores progress in percent of proxy currently being processed
|
||||
double current_progress;
|
||||
|
||||
// function that performs the actual transcode
|
||||
void transcode(const ProxyInfo& info);
|
||||
// function that performs the actual transcode
|
||||
void transcode(const ProxyInfo& info);
|
||||
};
|
||||
|
||||
// proxy generator is a global omnipotent entity
|
||||
extern ProxyGenerator proxy_generator;
|
||||
namespace olive {
|
||||
// proxy generator is a global omnipotent entity
|
||||
extern ProxyGenerator proxy_generator;
|
||||
}
|
||||
|
||||
#endif // PROXYGENERATOR_H
|
||||
|
||||
@@ -30,8 +30,6 @@ QPainterWrapper painter_wrapper;
|
||||
QPainterWrapper::QPainterWrapper() {}
|
||||
|
||||
QColor get_color_from_string(const QString& s) {
|
||||
dout << s;
|
||||
|
||||
// workaround for alpha
|
||||
if (s.at(0) == '#' && s.length() == 9) {
|
||||
QColor color(s.left(7));
|
||||
|
||||
Reference in New Issue
Block a user