giant rendering overhaul and many small fixes

This commit is contained in:
itsmattkc
2019-02-25 13:47:18 -08:00
parent b9d7efa0bc
commit c3373524b1
1429 changed files with 10234 additions and 194593 deletions
+410 -411
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
-2
View File
@@ -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));