proxy: began work towards creating proxy from video

This commit is contained in:
itsmattkc
2020-05-03 17:09:52 +10:00
parent e4c3b6bf7b
commit 6754feb2f6
16 changed files with 388 additions and 81 deletions
+1 -1
View File
@@ -343,7 +343,7 @@ bool Decoder::HasConformedVersion(const AudioRenderingParams &params)
return index_already_matches;
}
void Decoder::SignalIndexProgress(const int64_t &ts)
void Decoder::SignalProcessingProgress(const int64_t &ts)
{
if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) {
emit IndexProgress(qRound(100.0 * static_cast<double>(ts) / static_cast<double>(stream()->duration())));
+1 -1
View File
@@ -245,7 +245,7 @@ signals:
void IndexProgress(int);
protected:
void SignalIndexProgress(const int64_t& ts);
void SignalProcessingProgress(const int64_t& ts);
/**
* @brief Returns the filename for the index
+135 -55
View File
@@ -27,6 +27,7 @@ extern "C" {
#include <libavutil/pixdesc.h>
}
#include <OpenImageIO/imagebuf.h>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
@@ -588,8 +589,6 @@ void FFmpegDecoder::Error(const QString &s)
bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider)
{
return false;
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(stream());
QString frame_index_file = GetIndexFilename().append('d').append(QString::number(divider));
@@ -597,32 +596,113 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider)
if (QFileInfo::exists(frame_index_file)) {
// A proxy of this type already exists so we can do nothing
video_stream->append_proxy(divider);
} else {
// Iterate each frame and transcode it to EXR
FFmpegDecoderInstance instance(stream()->footage()->filename().toUtf8(), stream()->index());
int ret;
AVPacket* pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
while (true) {
ret = instance.GetFrame(pkt, frame);
if (ret < 0) {
if (ret == AVERROR_EOF) {
}
}
}
av_frame_free(&frame);
av_packet_free(&pkt);
video_stream->set_proxy(divider);
return true;
}
// Iterate each frame and transcode it to EXR
FFmpegDecoderInstance instance(stream()->footage()->filename().toUtf8(), stream()->index());
int ret;
AVPixelFormat src_fmt = static_cast<AVPixelFormat>(instance.stream()->codecpar->format);
AVPixelFormat ideal_fmt = FFmpegCommon::GetCompatiblePixelFormat(src_fmt);
PixelFormat::Format native_fmt = GetNativePixelFormat(ideal_fmt);
int divided_width = instance.stream()->codecpar->width;
int divided_height = instance.stream()->codecpar->height;
SwsContext* scaler = sws_getContext(instance.stream()->codecpar->width,
instance.stream()->codecpar->height,
src_fmt,
divided_width,
divided_height,
ideal_fmt,
SWS_FAST_BILINEAR,
nullptr,
nullptr,
0);
AVPacket* pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
QVector<int64_t> frame_index;
QByteArray converted_buffer(PixelFormat::GetBufferSize(native_fmt,
divided_width,
divided_height),
Qt::Uninitialized);
uint8_t* converted_data = reinterpret_cast<uint8_t*>(converted_buffer.data());
int converted_linesize = PixelFormat::GetBufferSize(native_fmt,
divided_width,
1);
bool succeeded = false;
while (true) {
if (cancelled && *cancelled) {
break;
}
ret = instance.GetFrame(pkt, frame);
// Handle errors
if (ret < 0) {
if (ret == AVERROR_EOF) {
succeeded = true;
} else {
char err_str[50];
av_strerror(ret, err_str, 50);
qWarning() << "Failed to proxy:" << ret << err_str;
}
break;
}
sws_scale(scaler,
frame->data,
frame->linesize,
0,
frame->height,
&converted_data,
&converted_linesize);
QString dst_fn = GetIndexFilename()
.append(QString::number(frame->pts))
.append(QStringLiteral(".tiff"));
std::string dst_std_fn = dst_fn.toStdString();
auto out = OIIO::ImageOutput::create(dst_std_fn);
if (out) {
out->open(dst_std_fn,
OIIO::ImageSpec(divided_width,
divided_height,
PixelFormat::ChannelCount(native_fmt),
PixelFormat::GetOIIOTypeDesc(native_fmt)));
out->write_image(PixelFormat::GetOIIOTypeDesc(native_fmt), converted_data);
out->close();
#if OIIO_VERSION < 10903
OIIO::ImageOutput::destroy(out);
#endif
}
frame_index.append(frame->pts);
SignalProcessingProgress(frame->pts);
}
sws_freeContext(scaler);
av_frame_free(&frame);
av_packet_free(&pkt);
return succeeded;
}
bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderingParams &p)
@@ -694,39 +774,39 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderi
} else {
char err_str[50];
av_strerror(ret, err_str, 50);
qWarning() << "Failed to index:" << ret << err_str;
qWarning() << "Failed to conform:" << ret << err_str;
}
break;
} else {
// Allocate buffers
int nb_samples = swr_get_out_samples(resampler, frame->nb_samples);
char* data = new char[p.samples_to_bytes(nb_samples)];
// Resample audio to our destination parameters
nb_samples = swr_convert(resampler,
reinterpret_cast<uint8_t**>(&data),
nb_samples,
const_cast<const uint8_t**>(frame->data),
frame->nb_samples);
if (nb_samples < 0) {
char err_str[50];
av_strerror(nb_samples, err_str, 50);
qWarning() << "libswresample failed with error:" << nb_samples << err_str;
break;
}
// Write packed WAV data to the disk cache
wave_out.write(data, p.samples_to_bytes(nb_samples));
// If we allocated an output for the resampler, delete it here
if (data != reinterpret_cast<char*>(frame->data[0])) {
delete [] data;
}
SignalIndexProgress(frame->pts);
}
// Allocate buffers
int nb_samples = swr_get_out_samples(resampler, frame->nb_samples);
char* data = new char[p.samples_to_bytes(nb_samples)];
// Resample audio to our destination parameters
nb_samples = swr_convert(resampler,
reinterpret_cast<uint8_t**>(&data),
nb_samples,
const_cast<const uint8_t**>(frame->data),
frame->nb_samples);
if (nb_samples < 0) {
char err_str[50];
av_strerror(nb_samples, err_str, 50);
qWarning() << "libswresample failed with error:" << nb_samples << err_str;
break;
}
// Write packed WAV data to the disk cache
wave_out.write(data, p.samples_to_bytes(nb_samples));
// If we allocated an output for the resampler, delete it here
if (data != reinterpret_cast<char*>(frame->data[0])) {
delete [] data;
}
SignalProcessingProgress(frame->pts);
}
wave_out.close();
+14 -3
View File
@@ -304,11 +304,11 @@ void Footage::ClearStreams()
streams_.clear();
}
bool Footage::HasStreamsOfType(const Stream::Type type)
bool Footage::HasStreamsOfType(const Stream::Type &type) const
{
// Return true if any streams are video streams
for (int i=0;i<streams_.size();i++) {
if (streams_.at(i)->type() == type) {
foreach (StreamPtr stream, streams_) {
if (stream->type() == type) {
return true;
}
}
@@ -316,6 +316,17 @@ bool Footage::HasStreamsOfType(const Stream::Type type)
return false;
}
StreamPtr Footage::get_first_stream_of_type(const Stream::Type &type) const
{
foreach (StreamPtr stream, streams_) {
if (stream->type() == type) {
return stream;
}
}
return nullptr;
}
void Footage::UpdateTooltip()
{
switch (status_) {
+9 -7
View File
@@ -206,12 +206,6 @@ public:
quint64 get_enabled_stream_flags() const;
private:
/**
* @brief Internal function to delete all Stream children and empty the array
*/
void ClearStreams();
/**
* @brief Check if this footage has streams of a certain type
*
@@ -219,7 +213,15 @@ private:
*
* The stream type to check for
*/
bool HasStreamsOfType(const Stream::Type type);
bool HasStreamsOfType(const Stream::Type& type) const;
StreamPtr get_first_stream_of_type(const Stream::Type& type) const;
private:
/**
* @brief Internal function to delete all Stream children and empty the array
*/
void ClearStreams();
/**
* @brief Update the icon based on the Footage status
-2
View File
@@ -102,8 +102,6 @@ protected:
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const;
signals:
void IndexChanged();
void ParametersChanged();
private:
+28 -5
View File
@@ -30,7 +30,9 @@ const int64_t VideoStream::kEndTimestamp = AV_NOPTS_VALUE;
VideoStream::VideoStream() :
start_time_(0),
is_image_sequence_(false)
is_image_sequence_(false),
is_generating_proxy_(false),
using_proxy_(0)
{
set_type(kVideo);
}
@@ -73,18 +75,39 @@ void VideoStream::set_image_sequence(bool e)
is_image_sequence_ = e;
}
bool VideoStream::has_proxy(const int &divider)
bool VideoStream::is_generating_proxy()
{
QMutexLocker locker(proxy_access_lock());
return proxies_.contains(divider);
return is_generating_proxy_;
}
void VideoStream::append_proxy(const int &divider)
bool VideoStream::try_start_proxy()
{
QMutexLocker locker(proxy_access_lock());
proxies_.append(divider);
if (is_generating_proxy_) {
return false;
}
is_generating_proxy_ = true;
return true;
}
int VideoStream::using_proxy()
{
QMutexLocker locker(proxy_access_lock());
return using_proxy_;
}
void VideoStream::set_proxy(const int &divider)
{
QMutexLocker locker(proxy_access_lock());
using_proxy_ = divider;
is_generating_proxy_ = false;
}
/*
+7 -3
View File
@@ -61,8 +61,10 @@ public:
bool save_frame_index(const QString& s);
*/
bool has_proxy(const int& divider);
void append_proxy(const int& divider);
bool is_generating_proxy();
bool try_start_proxy();
int using_proxy();
void set_proxy(const int& divider);
private:
rational frame_rate_;
@@ -73,7 +75,9 @@ private:
bool is_image_sequence_;
QVector<int> proxies_;
bool is_generating_proxy_;
int using_proxy_;
};
@@ -65,6 +65,8 @@ void AudioRenderBackend::DisconnectViewer(ViewerOutput *node)
{
disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
conform_wait_info_.clear();
}
bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash)
+1
View File
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(conform)
add_subdirectory(proxy)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
+6 -4
View File
@@ -34,7 +34,7 @@ ConformTask::ConformTask(AudioStreamPtr stream, const AudioRenderingParams& para
void ConformTask::Action()
{
if (stream_->footage()->decoder().isEmpty()) {
emit Failed(QStringLiteral("Stream has no decoder"));
emit Failed(tr("Failed to find decoder to conform audio stream"));
} else {
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
@@ -42,9 +42,11 @@ void ConformTask::Action()
connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged);
decoder->ConformAudio(&IsCancelled(), params_);
emit Succeeded();
if (decoder->ConformAudio(&IsCancelled(), params_)) {
emit Succeeded();
} else {
emit Failed(QStringLiteral("Failed to conform audio"));
}
}
}
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/proxy/proxy.h
task/proxy/proxy.cpp
PARENT_SCOPE
)
+60
View File
@@ -0,0 +1,60 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "proxy.h"
#include "codec/decoder.h"
OLIVE_NAMESPACE_ENTER
ProxyTask::ProxyTask(VideoStreamPtr stream, int divider) :
stream_(stream),
divider_(divider)
{
if (divider_ == 1) {
SetTitle(tr("Generating full resolution proxy %1:%2").arg(stream_->footage()->filename(),
QString::number(stream_->index())));
} else {
SetTitle(tr("Generating 1/%1 resolution proxy %2:%3").arg(QString::number(divider),
stream_->footage()->filename(),
QString::number(stream_->index())));
}
}
void ProxyTask::Action()
{
if (stream_->footage()->decoder().isEmpty()) {
emit Failed(tr("Failed to find decoder to conform audio stream"));
} else {
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
decoder->set_stream(stream_);
connect(decoder.get(), &Decoder::IndexProgress, this, &ProxyTask::ProgressChanged);
if (decoder->ProxyVideo(&IsCancelled(), divider_)) {
emit Succeeded();
} else {
emit Failed(QStringLiteral("Failed to generate proxy"));
}
}
}
OLIVE_NAMESPACE_EXIT
+46
View File
@@ -0,0 +1,46 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef PROXYTASK_H
#define PROXYTASK_H
#include "project/item/footage/videostream.h"
#include "task/task.h"
OLIVE_NAMESPACE_ENTER
class ProxyTask : public Task
{
public:
ProxyTask(VideoStreamPtr stream, int divider);
protected:
virtual void Action() override;
private:
VideoStreamPtr stream_;
int divider_;
};
OLIVE_NAMESPACE_EXIT
#endif // PROXYTASK_H
@@ -31,6 +31,8 @@
#include "core.h"
#include "dialog/footageproperties/footageproperties.h"
#include "dialog/sequence/sequence.h"
#include "task/proxy/proxy.h"
#include "task/taskmanager.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
#include "window/mainwindow/mainwindow.h"
@@ -284,6 +286,36 @@ void ProjectExplorer::ShowContextMenu()
connect(reveal_action, &QAction::triggered, this, &ProjectExplorer::RevealSelectedFootage);
menu.addSeparator();
Footage* f = static_cast<Footage*>(context_menu_item_);
if (f->HasStreamsOfType(Stream::kVideo)) {
Menu* proxy_menu = new Menu(tr("Proxy"), &menu);
menu.addMenu(proxy_menu);
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(f->get_first_stream_of_type(Stream::kVideo));
if (video_stream->is_generating_proxy()) {
// Prevent multiple proxy actions from occurring at once
QAction* cant_proxy_action = proxy_menu->addAction(tr("Proxy being generated..."));
cant_proxy_action->setEnabled(false);
} else {
proxy_menu->addAction(tr("(None)"))->setData(0);
proxy_menu->addSeparator();
proxy_menu->addAction(tr("Full"))->setData(1);
proxy_menu->addAction(tr("1/2"))->setData(2);
proxy_menu->addAction(tr("1/4"))->setData(4);
proxy_menu->addAction(tr("1/8"))->setData(8);
connect(proxy_menu, &Menu::triggered, this, &ProjectExplorer::ContextMenuStartProxy);
}
menu.addSeparator();
}
}
QAction* properties_action = menu.addAction(tr("P&roperties"));
@@ -347,6 +379,28 @@ void ProjectExplorer::OpenContextMenuItemInNewWindow()
Core::instance()->main_window()->FolderOpen(project(), context_menu_item_, true);
}
void ProjectExplorer::ContextMenuStartProxy(QAction *a)
{
// Find video stream
VideoStreamPtr video_stream = nullptr;
foreach (StreamPtr s, static_cast<Footage*>(context_menu_item_)->streams()) {
if (s->type() == Stream::kVideo) {
video_stream = std::static_pointer_cast<VideoStream>(s);
break;
}
}
if (!video_stream) {
return;
}
if (video_stream->try_start_proxy()) {
ProxyTask* proxy_task = new ProxyTask(video_stream, a->data().toInt());
TaskManager::instance()->AddTask(proxy_task);
}
}
Project *ProjectExplorer::project() const
{
return model_.project();
@@ -171,6 +171,8 @@ private slots:
void OpenContextMenuItemInNewWindow();
void ContextMenuStartProxy(QAction* a);
};
OLIVE_NAMESPACE_EXIT