Merge branch 'master' into pr/1875

This commit is contained in:
itsmattkc
2022-05-03 18:51:59 -07:00
84 changed files with 1005 additions and 574 deletions
+19 -18
View File
@@ -73,11 +73,10 @@ int InputCallback(const void *input, void *output, unsigned long frameCount, con
{
FFmpegEncoder *f = static_cast<FFmpegEncoder*>(userData);
SampleBufferPtr s = SampleBuffer::Create();
s->set_sample_count(frameCount);
s->set_audio_params(f->params().audio_params());
AudioParams our_params = f->params().audio_params();
our_params.set_format(AudioParams::GetPackedEquivalent(f->params().audio_params().format()));
f->WriteAudioData(f->params().audio_params(), false, reinterpret_cast<const uint8_t**>(&input), frameCount);
f->WriteAudioData(our_params, reinterpret_cast<const uint8_t**>(&input), frameCount);
return paContinue;
}
@@ -115,16 +114,22 @@ void AudioManager::ClearBufferedOutput()
PaSampleFormat AudioManager::GetPortAudioSampleFormat(AudioParams::Format fmt)
{
switch (fmt) {
case AudioParams::kFormatUnsigned8:
case AudioParams::kFormatUnsigned8Packed:
case AudioParams::kFormatUnsigned8Planar:
return paUInt8;
case AudioParams::kFormatSigned16:
case AudioParams::kFormatSigned16Packed:
case AudioParams::kFormatSigned16Planar:
return paInt16;
case AudioParams::kFormatSigned32:
case AudioParams::kFormatSigned32Packed:
case AudioParams::kFormatSigned32Planar:
return paInt32;
case AudioParams::kFormatFloat32:
case AudioParams::kFormatFloat32Packed:
case AudioParams::kFormatFloat32Planar:
return paFloat32;
case AudioParams::kFormatSigned64:
case AudioParams::kFormatFloat64:
case AudioParams::kFormatSigned64Packed:
case AudioParams::kFormatSigned64Planar:
case AudioParams::kFormatFloat64Packed:
case AudioParams::kFormatFloat64Planar:
case AudioParams::kFormatInvalid:
case AudioParams::kFormatCount:
break;
@@ -184,25 +189,21 @@ void AudioManager::HardReset()
Pa_Initialize();
}
bool AudioManager::StartRecording(const QString &filename, const AudioParams &params)
bool AudioManager::StartRecording(const EncodingParams &params)
{
if (input_device_ == paNoDevice) {
return false;
}
EncodingParams encode_param;
encode_param.EnableAudio(params, ExportCodec::kCodecMP3);
encode_param.SetFilename(filename);
input_encoder_ = new FFmpegEncoder(encode_param);
input_encoder_ = new FFmpegEncoder(params);
if (!input_encoder_->Open()) {
qCritical() << "Failed to open encoder for recording";
return false;
}
PaStreamParameters p = GetPortAudioParams(params, input_device_);
PaStreamParameters p = GetPortAudioParams(params.audio_params(), input_device_);
if (Pa_OpenStream(&input_stream_, &p, nullptr, params.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) {
if (Pa_OpenStream(&input_stream_, &p, nullptr, params.audio_params().sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) {
if (Pa_StartStream(input_stream_) == paNoError) {
return true;
}
+1 -1
View File
@@ -74,7 +74,7 @@ public:
void HardReset();
bool StartRecording(const QString &filename, const AudioParams &params);
bool StartRecording(const EncodingParams &params);
void StopRecording();
+2 -2
View File
@@ -42,10 +42,10 @@ bool PackedProcessor::Open(const AudioParams &params)
swr_ctx_ = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
FFmpegUtils::GetFFmpegSampleFormat(AudioParams::GetPackedEquivalent(params.format())),
params.sample_rate(),
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
FFmpegUtils::GetFFmpegSampleFormat(params.format()),
params.sample_rate(),
0,
nullptr);
+2 -2
View File
@@ -42,10 +42,10 @@ bool PlanarProcessor::Open(const AudioParams &params)
swr_ctx_ = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
FFmpegUtils::GetFFmpegSampleFormat(AudioParams::GetPlanarEquivalent(params.format())),
params.sample_rate(),
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
FFmpegUtils::GetFFmpegSampleFormat(params.format()),
params.sample_rate(),
0,
nullptr);
+13 -12
View File
@@ -109,7 +109,7 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const RetrieveVideoPar
return RetrieveVideoInternal(timecode, divider, cancelled);
}
Decoder::RetrieveAudioData Decoder::RetrieveAudio(const TimeRange &range, const AudioParams &params, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode)
Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBufferPtr dest, const TimeRange &range, const AudioParams &params, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode)
{
QMutexLocker locker(&mutex_);
@@ -117,24 +117,27 @@ Decoder::RetrieveAudioData Decoder::RetrieveAudio(const TimeRange &range, const
if (!stream_.IsValid()) {
qCritical() << "Can't retrieve audio on a closed decoder";
return {kInvalid, nullptr, nullptr};
return kInvalid;
}
if (!SupportsAudio()) {
qCritical() << "Decoder doesn't support audio";
return {kInvalid, nullptr, nullptr};
return kInvalid;
}
// Get conform state from ConformManager
ConformManager::Conform conform = ConformManager::instance()->GetConformState(id(), cache_path, stream_, params, (mode == RenderMode::kOnline));
if (conform.state == ConformManager::kConformGenerating) {
return {kWaitingForConform, nullptr, conform.task};
// If we need the task, it's available in `conform.task`
return kWaitingForConform;
}
// See if we got the conform
SampleBufferPtr out_buffer = RetrieveAudioFromConform(conform.filenames, range, loop_mode, params);
return {kOK, out_buffer, nullptr};
if (RetrieveAudioFromConform(dest, conform.filenames, range, loop_mode, params)) {
return kOK;
} else {
return kUnknownError;
}
}
qint64 Decoder::GetLastAccessedTime()
@@ -269,12 +272,10 @@ bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const Audi
return false;
}
SampleBufferPtr Decoder::RetrieveAudioFromConform(const QVector<QString> &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params)
bool Decoder::RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector<QString> &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params)
{
PlanarFileDevice input;
if (input.open(conform_filenames, QFile::ReadOnly)) {
SampleBufferPtr sample_buffer = SampleBuffer::CreateAllocated(input_params, range.length());
qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count();
qint64 write_index = 0;
@@ -313,10 +314,10 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QVector<QString> &confor
input.close();
return sample_buffer;
return true;
}
return nullptr;
return false;
}
void Decoder::UpdateLastAccessed()
+4 -9
View File
@@ -189,13 +189,8 @@ public:
enum RetrieveAudioStatus {
kInvalid = -1,
kOK,
kWaitingForConform
};
struct RetrieveAudioData {
RetrieveAudioStatus status;
SampleBufferPtr samples;
Task *task;
kWaitingForConform,
kUnknownError
};
/**
@@ -206,7 +201,7 @@ public:
*
* This function is thread safe and can only run while the decoder is open. \see Open()
*/
RetrieveAudioData RetrieveAudio(const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode);
RetrieveAudioStatus RetrieveAudio(SampleBufferPtr dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode);
/**
* @brief Determine the last time this decoder instance was used in any way
@@ -312,7 +307,7 @@ signals:
private:
void UpdateLastAccessed();
SampleBufferPtr RetrieveAudioFromConform(const QVector<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams &params);
bool RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams &params);
CodecStream stream_;
+7 -1
View File
@@ -320,7 +320,8 @@ Encoder::Type Encoder::GetTypeFromFormat(ExportFormat::Format f)
case ExportFormat::kFormatDNxHD:
case ExportFormat::kFormatMatroska:
case ExportFormat::kFormatQuickTime:
case ExportFormat::kFormatMPEG4:
case ExportFormat::kFormatMPEG4Video:
case ExportFormat::kFormatMPEG4Audio:
case ExportFormat::kFormatWAV:
case ExportFormat::kFormatAIFF:
case ExportFormat::kFormatMP3:
@@ -350,4 +351,9 @@ QStringList Encoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
return QStringList();
}
std::vector<AudioParams::Format> Encoder::GetSampleFormatsForCodec(ExportCodec::Codec c) const
{
return std::vector<AudioParams::Format>();
}
}
+1
View File
@@ -155,6 +155,7 @@ public:
static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams &params);
virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const;
virtual std::vector<AudioParams::Format> GetSampleFormatsForCodec(ExportCodec::Codec c) const;
const EncodingParams& params() const;
+29
View File
@@ -103,4 +103,33 @@ bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
return false;
}
bool ExportCodec::IsCodecLossless(Codec c)
{
switch (c) {
case kCodecPCM:
case kCodecFLAC:
return true;
case kCodecDNxHD:
case kCodecH264:
case kCodecH264rgb:
case kCodecH265:
case kCodecProRes:
case kCodecCineform:
case kCodecMP2:
case kCodecMP3:
case kCodecAAC:
case kCodecVorbis:
case kCodecOpus:
case kCodecVP9:
case kCodecSRT:
case kCodecOpenEXR:
case kCodecPNG:
case kCodecTIFF:
case kCodecCount:
break;
}
return false;
}
}
+3 -5
View File
@@ -33,8 +33,8 @@ class ExportCodec : public QObject
{
Q_OBJECT
public:
// Only append to this list (never insert) because indexes are used in serialized files
enum Codec {
// Video codecs
kCodecDNxHD,
kCodecH264,
kCodecH264rgb,
@@ -45,8 +45,6 @@ public:
kCodecCineform,
kCodecTIFF,
kCodecVP9,
// Audio codecs
kCodecMP2,
kCodecMP3,
kCodecAAC,
@@ -54,8 +52,6 @@ public:
kCodecOpus,
kCodecVorbis,
kCodecFLAC,
// Subtitle codecs
kCodecSRT,
kCodecCount
@@ -65,6 +61,8 @@ public:
static bool IsCodecAStillImage(Codec c);
static bool IsCodecLossless(Codec c);
};
}
+26 -5
View File
@@ -31,8 +31,10 @@ QString ExportFormat::GetName(olive::ExportFormat::Format f)
return tr("DNxHD");
case kFormatMatroska:
return tr("Matroska Video");
case kFormatMPEG4:
case kFormatMPEG4Video:
return tr("MPEG-4 Video");
case kFormatMPEG4Audio:
return tr("MPEG-4 Audio");
case kFormatOpenEXR:
return tr("OpenEXR");
case kFormatPNG:
@@ -70,8 +72,10 @@ QString ExportFormat::GetExtension(ExportFormat::Format f)
return QStringLiteral("mxf");
case kFormatMatroska:
return QStringLiteral("mkv");
case kFormatMPEG4:
case kFormatMPEG4Video:
return QStringLiteral("mp4");
case kFormatMPEG4Audio:
return QStringLiteral("m4a");
case kFormatOpenEXR:
return QStringLiteral("exr");
case kFormatPNG:
@@ -108,7 +112,7 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
return {ExportCodec::kCodecDNxHD};
case kFormatMatroska:
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecVP9};
case kFormatMPEG4:
case kFormatMPEG4Video:
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265};
case kFormatOpenEXR:
return {ExportCodec::kCodecOpenEXR};
@@ -122,6 +126,7 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
return {ExportCodec::kCodecVP9};
case kFormatOgg:
case kFormatWAV:
case kFormatMPEG4Audio:
case kFormatAIFF:
case kFormatMP3:
case kFormatFLAC:
@@ -141,7 +146,8 @@ QList<ExportCodec::Codec> ExportFormat::GetAudioCodecs(ExportFormat::Format f)
return {ExportCodec::kCodecPCM};
case kFormatMatroska:
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus, ExportCodec::kCodecFLAC};
case kFormatMPEG4:
case kFormatMPEG4Video:
case kFormatMPEG4Audio:
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3};
case kFormatQuickTime:
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM};
@@ -177,7 +183,8 @@ QList<ExportCodec::Codec> ExportFormat::GetSubtitleCodecs(Format f)
{
switch (f) {
case kFormatDNxHD:
case kFormatMPEG4:
case kFormatMPEG4Video:
case kFormatMPEG4Audio:
case kFormatOpenEXR:
case kFormatQuickTime:
case kFormatPNG:
@@ -211,4 +218,18 @@ QStringList ExportFormat::GetPixelFormatsForCodec(ExportFormat::Format f, Export
return list;
}
std::vector<AudioParams::Format> ExportFormat::GetSampleFormatsForCodec(Format format, ExportCodec::Codec c)
{
std::vector<AudioParams::Format> f;
Encoder *e = Encoder::CreateFromFormat(format, EncodingParams());
if (e) {
f = e->GetSampleFormatsForCodec(c);
delete e;
}
return f;
}
}
+5 -1
View File
@@ -26,6 +26,7 @@
#include "common/define.h"
#include "exportcodec.h"
#include "render/audioparams.h"
namespace olive {
@@ -33,10 +34,11 @@ class ExportFormat : public QObject
{
Q_OBJECT
public:
// Only append to this list (never insert) because indexes are used in serialized files
enum Format {
kFormatDNxHD,
kFormatMatroska,
kFormatMPEG4,
kFormatMPEG4Video,
kFormatOpenEXR,
kFormatQuickTime,
kFormatPNG,
@@ -48,6 +50,7 @@ public:
kFormatOgg,
kFormatWebM,
kFormatSRT,
kFormatMPEG4Audio,
kFormatCount
};
@@ -59,6 +62,7 @@ public:
static QList<ExportCodec::Codec> GetSubtitleCodecs(ExportFormat::Format f);
static QStringList GetPixelFormatsForCodec(Format f, ExportCodec::Codec c);
static std::vector<AudioParams::Format> GetSampleFormatsForCodec(Format f, ExportCodec::Codec c);
};
+1 -1
View File
@@ -455,7 +455,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector<QString> &filenames, cons
// Create resampling context
SwrContext* resampler = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
FFmpegUtils::GetFFmpegSampleFormat(params.format()),
params.sample_rate(),
channel_layout,
static_cast<AVSampleFormat>(instance_.avstream()->codecpar->format),
+66 -11
View File
@@ -50,7 +50,7 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
{
QStringList pix_fmts;
const AVCodec* codec_info = GetEncoder(c);
const AVCodec* codec_info = GetEncoder(c, AudioParams::kFormatInvalid);
if (codec_info) {
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
@@ -62,6 +62,38 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
return pix_fmts;
}
std::vector<AudioParams::Format> FFmpegEncoder::GetSampleFormatsForCodec(ExportCodec::Codec c) const
{
std::vector<AudioParams::Format> f;
if (c == ExportCodec::kCodecPCM) {
// FFmpeg lists these as separate codecs so we need custom functionality here
// We list signed 16 first because ExportDialog will always use the first element by default
// (beacuse first element is the "default" in FFmpeg)
f = {
AudioParams::kFormatSigned16Packed,
AudioParams::kFormatUnsigned8Packed,
AudioParams::kFormatSigned32Packed,
AudioParams::kFormatSigned64Packed,
AudioParams::kFormatFloat32Packed,
AudioParams::kFormatFloat64Packed
};
} else {
const AVCodec* codec_info = GetEncoder(c, AudioParams::kFormatInvalid);
if (codec_info && codec_info->sample_fmts) {
for (int i=0; codec_info->sample_fmts[i]!=-1; i++) {
AudioParams::Format this_format = FFmpegUtils::GetNativeSampleFormat(static_cast<AVSampleFormat>(codec_info->sample_fmts[i]));
if (this_format != AudioParams::kFormatInvalid) {
f.push_back(this_format);
}
}
}
}
return f;
}
bool FFmpegEncoder::Open()
{
if (open_) {
@@ -246,14 +278,14 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
int input_linesize;
av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio->audio_params().channel_count(),
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true), 0);
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format()), 0);
for (int i=0; i<audio->audio_params().channel_count(); i++) {
memcpy(input_data[i], audio->data(i), input_sample_count * audio->audio_params().bytes_per_sample_per_channel());
}
}
result = WriteAudioData(audio->audio_params(), true, const_cast<const uint8_t**>(input_data), input_sample_count);
result = WriteAudioData(audio->audio_params(), const_cast<const uint8_t**>(input_data), input_sample_count);
if (input_data) {
av_freep(&input_data[0]);
@@ -263,9 +295,9 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
return result;
}
bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **input_data, int input_sample_count)
bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, const uint8_t **input_data, int input_sample_count)
{
if (!InitializeResampleContext(audio_params, planar)) {
if (!InitializeResampleContext(audio_params)) {
qCritical() << "Failed to initialize resample context";
return false;
}
@@ -579,7 +611,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
}
// Find encoder
const AVCodec* encoder = GetEncoder(codec);
const AVCodec* encoder = GetEncoder(codec, params().audio_params().format());
if (!encoder) {
SetError(tr("Failed to find codec for 0x%1").arg(codec, 16));
return false;
@@ -653,7 +685,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
codec_ctx->sample_rate = params().audio_params().sample_rate();
codec_ctx->channel_layout = params().audio_params().channel_layout();
codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);
codec_ctx->sample_fmt = encoder->sample_fmts[0];
codec_ctx->sample_fmt = FFmpegUtils::GetFFmpegSampleFormat(params().audio_params().format());
codec_ctx->time_base = {1, codec_ctx->sample_rate};
if (params().audio_bit_rate() > 0) {
@@ -783,7 +815,7 @@ void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream)
av_packet_free(&pkt);
}
bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio, bool planar)
bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio)
{
if (audio_resample_ctx_) {
return true;
@@ -795,7 +827,7 @@ bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio, bool pla
audio_codec_ctx_->sample_fmt,
audio_codec_ctx_->sample_rate,
static_cast<int64_t>(audio.channel_layout()),
FFmpegUtils::GetFFmpegSampleFormat(audio.format(), planar),
FFmpegUtils::GetFFmpegSampleFormat(audio.format()),
audio.sample_rate(),
0,
nullptr);
@@ -842,7 +874,7 @@ bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio, bool pla
return true;
}
const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c, AudioParams::Format aformat)
{
switch (c) {
case ExportCodec::kCodecH264:
@@ -872,7 +904,30 @@ const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
case ExportCodec::kCodecAAC:
return avcodec_find_encoder(AV_CODEC_ID_AAC);
case ExportCodec::kCodecPCM:
return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
switch (aformat) {
case AudioParams::kFormatInvalid:
case AudioParams::kFormatCount:
case AudioParams::kFormatUnsigned8Planar:
case AudioParams::kFormatSigned16Planar:
case AudioParams::kFormatSigned32Planar:
case AudioParams::kFormatSigned64Planar:
case AudioParams::kFormatFloat32Planar:
case AudioParams::kFormatFloat64Planar:
break;
case AudioParams::kFormatUnsigned8Packed:
return avcodec_find_encoder(AV_CODEC_ID_PCM_U8);
case AudioParams::kFormatSigned16Packed:
return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
case AudioParams::kFormatSigned32Packed:
return avcodec_find_encoder(AV_CODEC_ID_PCM_S32LE);
case AudioParams::kFormatSigned64Packed:
return avcodec_find_encoder(AV_CODEC_ID_PCM_S64LE);
case AudioParams::kFormatFloat32Packed:
return avcodec_find_encoder(AV_CODEC_ID_PCM_F32LE);
case AudioParams::kFormatFloat64Packed:
return avcodec_find_encoder(AV_CODEC_ID_PCM_F64LE);
}
break;
case ExportCodec::kCodecFLAC:
return avcodec_find_encoder(AV_CODEC_ID_FLAC);
case ExportCodec::kCodecOpus:
+5 -3
View File
@@ -41,13 +41,15 @@ public:
virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const override;
virtual std::vector<AudioParams::Format> GetSampleFormatsForCodec(ExportCodec::Codec c) const override;
virtual bool Open() override;
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
bool WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **data, int input_sample_count);
bool WriteAudioData(const AudioParams &audio_params, const uint8_t **data, int input_sample_count);
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
@@ -78,9 +80,9 @@ private:
void FlushEncoders();
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
bool InitializeResampleContext(const AudioParams &audio, bool planar);
bool InitializeResampleContext(const AudioParams &audio);
static const AVCodec *GetEncoder(ExportCodec::Codec c);
static const AVCodec *GetEncoder(ExportCodec::Codec c, AudioParams::Format aformat);
AVFormatContext* fmt_ctx_;
+4
View File
@@ -54,6 +54,10 @@ public:
const int &sample_count() const;
void set_sample_count(const int &sample_count);
void set_sample_count(const rational &length)
{
set_sample_count(audio_params_.time_to_samples(length));
}
float* data(int channel)
{
+37 -19
View File
@@ -42,23 +42,29 @@ AudioParams::Format FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp
{
switch (smp_fmt) {
case AV_SAMPLE_FMT_U8:
return AudioParams::kFormatUnsigned8;
return AudioParams::kFormatUnsigned8Packed;
case AV_SAMPLE_FMT_S16:
return AudioParams::kFormatSigned16;
return AudioParams::kFormatSigned16Packed;
case AV_SAMPLE_FMT_S32:
return AudioParams::kFormatSigned32;
return AudioParams::kFormatSigned32Packed;
case AV_SAMPLE_FMT_S64:
return AudioParams::kFormatSigned64;
return AudioParams::kFormatSigned64Packed;
case AV_SAMPLE_FMT_FLT:
return AudioParams::kFormatFloat32;
return AudioParams::kFormatFloat32Packed;
case AV_SAMPLE_FMT_DBL:
return AudioParams::kFormatFloat64;
return AudioParams::kFormatFloat64Packed;
case AV_SAMPLE_FMT_U8P :
return AudioParams::kFormatUnsigned8Planar;
case AV_SAMPLE_FMT_S16P:
return AudioParams::kFormatSigned16Planar;
case AV_SAMPLE_FMT_S32P:
return AudioParams::kFormatSigned32Planar;
case AV_SAMPLE_FMT_S64P:
return AudioParams::kFormatSigned64Planar;
case AV_SAMPLE_FMT_FLTP:
return AudioParams::kFormatFloat32Planar;
case AV_SAMPLE_FMT_DBLP:
return AudioParams::kFormatFloat64Planar;
case AV_SAMPLE_FMT_NONE:
case AV_SAMPLE_FMT_NB:
break;
@@ -67,21 +73,33 @@ AudioParams::Format FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp
return AudioParams::kFormatInvalid;
}
AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt, bool planar)
AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt)
{
switch (smp_fmt) {
case AudioParams::kFormatUnsigned8:
return planar ? AV_SAMPLE_FMT_U8P : AV_SAMPLE_FMT_U8;
case AudioParams::kFormatSigned16:
return planar ? AV_SAMPLE_FMT_S16P : AV_SAMPLE_FMT_S16;
case AudioParams::kFormatSigned32:
return planar ? AV_SAMPLE_FMT_S32P : AV_SAMPLE_FMT_S32;
case AudioParams::kFormatSigned64:
return planar ? AV_SAMPLE_FMT_S64P : AV_SAMPLE_FMT_S64;
case AudioParams::kFormatFloat32:
return planar ? AV_SAMPLE_FMT_FLTP : AV_SAMPLE_FMT_FLT;
case AudioParams::kFormatFloat64:
return planar ? AV_SAMPLE_FMT_DBLP : AV_SAMPLE_FMT_DBL;
case AudioParams::kFormatUnsigned8Packed:
return AV_SAMPLE_FMT_U8;
case AudioParams::kFormatSigned16Packed:
return AV_SAMPLE_FMT_S16;
case AudioParams::kFormatSigned32Packed:
return AV_SAMPLE_FMT_S32;
case AudioParams::kFormatSigned64Packed:
return AV_SAMPLE_FMT_S64;
case AudioParams::kFormatFloat32Packed:
return AV_SAMPLE_FMT_FLT;
case AudioParams::kFormatFloat64Packed:
return AV_SAMPLE_FMT_DBL;
case AudioParams::kFormatUnsigned8Planar:
return AV_SAMPLE_FMT_U8P;
case AudioParams::kFormatSigned16Planar:
return AV_SAMPLE_FMT_S16P;
case AudioParams::kFormatSigned32Planar:
return AV_SAMPLE_FMT_S32P;
case AudioParams::kFormatSigned64Planar:
return AV_SAMPLE_FMT_S64P;
case AudioParams::kFormatFloat32Planar:
return AV_SAMPLE_FMT_FLTP;
case AudioParams::kFormatFloat64Planar:
return AV_SAMPLE_FMT_DBLP;
case AudioParams::kFormatInvalid:
case AudioParams::kFormatCount:
break;
+1 -1
View File
@@ -56,7 +56,7 @@ public:
/**
* @brief Returns an FFmpeg sample format type for a given native type
*/
static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt, bool planar = false);
static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt);
};
}
+5 -1
View File
@@ -287,7 +287,11 @@ int64_t Timecode::time_to_timestamp(const rational &time, const rational &timeba
int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase, Rounding floor)
{
double d = time * timebase.flipped().toDouble();
const double d = time * timebase.flipped().toDouble();
if (std::isnan(d)) {
return 0;
}
switch (floor) {
case kRound:
+8
View File
@@ -27,6 +27,7 @@
#include <QStandardPaths>
#include <QXmlStreamWriter>
#include "codec/exportformat.h"
#include "common/autoscroll.h"
#include "common/filefunctions.h"
#include "common/xmlutils.h"
@@ -120,6 +121,13 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString());
SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString());
SetEntryInternal(QStringLiteral("AudioRecordingFormat"), NodeValue::kInt, ExportFormat::kFormatWAV);
SetEntryInternal(QStringLiteral("AudioRecordingCodec"), NodeValue::kInt, ExportCodec::kCodecPCM);
SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"), NodeValue::kInt, 48000);
SetEntryInternal(QStringLiteral("AudioRecordingChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO);
SetEntryInternal(QStringLiteral("AudioRecordingSampleFormat"), NodeValue::kInt, AudioParams::kFormatSigned16Packed);
SetEntryInternal(QStringLiteral("AudioRecordingBitRate"), NodeValue::kInt, 320);
SetEntryInternal(QStringLiteral("DiskCacheBehind"), NodeValue::kRational, QVariant::fromValue(rational(0)));
SetEntryInternal(QStringLiteral("DiskCacheAhead"), NodeValue::kRational, QVariant::fromValue(rational(60)));
+2
View File
@@ -30,6 +30,8 @@
namespace olive {
#define OLIVE_CONFIG(x) Config::Current()[QStringLiteral(x)]
class Config {
public:
static Config& Current();
+6 -5
View File
@@ -194,8 +194,8 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
SetDefaultFilename();
// Set defaults
previously_selected_format_ = ExportFormat::kFormatMPEG4;
format_combobox_->SetFormat(ExportFormat::kFormatMPEG4);
previously_selected_format_ = ExportFormat::kFormatMPEG4Video;
format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video);
connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged);
FormatChanged(format_combobox_->GetFormat());
@@ -211,6 +211,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
video_tab_->pixel_format_field()->SetPixelFormat(static_cast<VideoParams::Format>(Config::Current()[QStringLiteral("OnlinePixelFormat")].toInt()));
video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing());
audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate());
audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false);
audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout());
video_aspect_ratio_ = static_cast<double>(vp.width()) / static_cast<double>(vp.height());
@@ -513,9 +514,9 @@ ExportParams ExportDialog::GenerateParams() const
video_tab_->interlaced_combobox()->GetInterlaceMode(),
1);
AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(),
AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->GetSampleRate(),
audio_tab_->channel_layout_combobox()->GetChannelLayout(),
AudioParams::kInternalFormat);
audio_tab_->sample_format_combobox()->GetSampleFormat());
ExportParams params;
params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat()));
@@ -553,7 +554,7 @@ ExportParams ExportDialog::GenerateParams() const
}
if (audio_enabled_->isChecked()) {
ExportCodec::Codec audio_codec = static_cast<ExportCodec::Codec>(audio_tab_->codec_combobox()->currentData().toInt());
ExportCodec::Codec audio_codec = audio_tab_->GetCodec();
params.EnableAudio(audio_render_params, audio_codec);
params.set_audio_bit_rate(audio_tab_->bit_rate_slider()->GetValue() * 1000);
+35 -4
View File
@@ -27,6 +27,8 @@
namespace olive {
const int ExportAudioTab::kDefaultBitRate = 320;
ExportAudioTab::ExportAudioTab(QWidget* parent) :
QWidget(parent)
{
@@ -40,6 +42,8 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) :
layout->addWidget(new QLabel(tr("Codec:")), row, 0);
codec_combobox_ = new QComboBox();
connect(codec_combobox_, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ExportAudioTab::UpdateSampleFormats);
connect(codec_combobox_, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ExportAudioTab::UpdateBitRateEnabled);
layout->addWidget(codec_combobox_, row, 1);
row++;
@@ -59,7 +63,9 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) :
row++;
layout->addWidget(new QLabel(tr("Format:")), row, 0);
layout->addWidget(new QComboBox(), row, 1);
sample_format_combobox_ = new SampleFormatComboBox();
layout->addWidget(sample_format_combobox_, row, 1);
row++;
@@ -68,7 +74,7 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) :
bit_rate_slider_ = new IntegerSlider();
bit_rate_slider_->SetMinimum(32);
bit_rate_slider_->SetMaximum(320);
bit_rate_slider_->SetValue(256);
bit_rate_slider_->SetValue(kDefaultBitRate);
bit_rate_slider_->SetFormat(tr("%1 kbps"));
layout->addWidget(bit_rate_slider_, row, 1);
@@ -79,11 +85,36 @@ int ExportAudioTab::SetFormat(ExportFormat::Format format)
{
QList<ExportCodec::Codec> acodecs = ExportFormat::GetAudioCodecs(format);
setEnabled(!acodecs.isEmpty());
codec_combobox()->clear();
codec_combobox_->blockSignals(true);
codec_combobox_->clear();
foreach (ExportCodec::Codec acodec, acodecs) {
codec_combobox()->addItem(ExportCodec::GetCodecName(acodec), acodec);
codec_combobox_->addItem(ExportCodec::GetCodecName(acodec), acodec);
}
codec_combobox_->blockSignals(false);
fmt_ = format;
UpdateSampleFormats();
UpdateBitRateEnabled();
return acodecs.size();
}
void ExportAudioTab::UpdateSampleFormats()
{
auto fmts = ExportFormat::GetSampleFormatsForCodec(fmt_, GetCodec());
sample_format_combobox_->SetAvailableFormats(fmts);
}
void ExportAudioTab::UpdateBitRateEnabled()
{
bool uses_bitrate = !ExportCodec::IsCodecLossless(GetCodec());
bit_rate_slider_->setEnabled(uses_bitrate);
if (!uses_bitrate) {
bit_rate_slider_->SetTristate();
} else {
bit_rate_slider_->SetValue(kDefaultBitRate );
}
}
}
+26 -2
View File
@@ -37,9 +37,19 @@ class ExportAudioTab : public QWidget
public:
ExportAudioTab(QWidget* parent = nullptr);
QComboBox* codec_combobox() const
ExportCodec::Codec GetCodec() const
{
return codec_combobox_;
return static_cast<ExportCodec::Codec>(codec_combobox_->currentData().toInt());
}
void SetCodec(ExportCodec::Codec c)
{
for (int i=0; i<codec_combobox_->count(); i++) {
if (codec_combobox_->itemData(i) == c) {
codec_combobox_->setCurrentIndex(i);
break;
}
}
}
SampleRateComboBox* sample_rate_combobox() const
@@ -47,6 +57,11 @@ public:
return sample_rate_combobox_;
}
SampleFormatComboBox* sample_format_combobox() const
{
return sample_format_combobox_;
}
ChannelLayoutComboBox* channel_layout_combobox() const
{
return channel_layout_combobox_;
@@ -61,11 +76,20 @@ public slots:
int SetFormat(ExportFormat::Format format);
private:
ExportFormat::Format fmt_;
QComboBox* codec_combobox_;
SampleRateComboBox* sample_rate_combobox_;
ChannelLayoutComboBox* channel_layout_combobox_;
SampleFormatComboBox *sample_format_combobox_;
IntegerSlider* bit_rate_slider_;
static const int kDefaultBitRate;
private slots:
void UpdateSampleFormats();
void UpdateBitRateEnabled();
};
}
+6 -2
View File
@@ -33,12 +33,16 @@ ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) :
case kShowAllFormats:
break;
case kShowAudioOnly:
if (!ExportFormat::GetVideoCodecs(f).isEmpty()) {
if (!ExportFormat::GetVideoCodecs(f).isEmpty()
|| !ExportFormat::GetSubtitleCodecs(f).isEmpty()
|| ExportFormat::GetAudioCodecs(f).isEmpty()) {
continue;
}
break;
case kShowVideoOnly:
if (!ExportFormat::GetAudioCodecs(f).isEmpty()) {
if (ExportFormat::GetVideoCodecs(f).isEmpty()
|| !ExportFormat::GetSubtitleCodecs(f).isEmpty()
|| !ExportFormat::GetAudioCodecs(f).isEmpty()) {
continue;
}
break;
@@ -117,6 +117,8 @@ MarkerPropertiesDialog::MarkerPropertiesDialog(const std::vector<TimelineMarker
connect(buttons, &QDialogButtonBox::accepted, this, &MarkerPropertiesDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &MarkerPropertiesDialog::reject);
layout->addWidget(buttons, row, 0, 1, 2);
setWindowTitle(tr("Edit Markers"));
}
void MarkerPropertiesDialog::accept()
@@ -26,8 +26,6 @@
#include "audio/audiomanager.h"
#include "config/config.h"
#include "dialog/export/exportaudiotab.h"
#include "dialog/export/exportformatcombobox.h"
namespace olive {
@@ -99,14 +97,21 @@ PreferencesAudioTab::PreferencesAudioTab()
fmt_layout->addWidget(new QLabel(tr("Format:")));
ExportFormatComboBox *fmt_combo = new ExportFormatComboBox(ExportFormatComboBox::kShowAudioOnly);
fmt_combo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
fmt_layout->addWidget(fmt_combo);
record_format_combo_ = new ExportFormatComboBox(ExportFormatComboBox::kShowAudioOnly);
record_format_combo_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
record_format_combo_->SetFormat(static_cast<ExportFormat::Format>(OLIVE_CONFIG("AudioRecordingFormat").toInt()));
fmt_layout->addWidget(record_format_combo_);
ExportAudioTab *audio_recording_options = new ExportAudioTab();
recording_layout->addWidget(audio_recording_options);
record_options_ = new ExportAudioTab();
record_options_->SetFormat(record_format_combo_->GetFormat());
record_options_->SetCodec(static_cast<ExportCodec::Codec>(OLIVE_CONFIG("AudioRecordingCodec").toInt()));
record_options_->sample_rate_combobox()->SetSampleRate(OLIVE_CONFIG("AudioRecordingSampleRate").toInt());
record_options_->channel_layout_combobox()->SetChannelLayout(OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong());
record_options_->bit_rate_slider()->SetValue(OLIVE_CONFIG("AudioRecordingBitRate").toInt());
record_options_->sample_format_combobox()->SetSampleFormat(static_cast<AudioParams::Format>(OLIVE_CONFIG("AudioRecordingSampleFormat").toInt()));
recording_layout->addWidget(record_options_);
connect(fmt_combo, &ExportFormatComboBox::FormatChanged, audio_recording_options, &ExportAudioTab::SetFormat);
connect(record_format_combo_, &ExportFormatComboBox::FormatChanged, record_options_, &ExportAudioTab::SetFormat);
}
QHBoxLayout* refresh_layout = new QHBoxLayout();
@@ -134,12 +139,19 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command)
PaDeviceIndex input_device = audio_input_devices_->currentData().value<PaDeviceIndex>();
// Get device names, which seem to be the closest thing we have to a "unique identifier" for them
Config::Current()[QStringLiteral("AudioOutput")] = audio_output_devices_->currentText();
Config::Current()[QStringLiteral("AudioInput")] = audio_input_devices_->currentText();
OLIVE_CONFIG("AudioOutput") = audio_output_devices_->currentText();
OLIVE_CONFIG("AudioInput") = audio_input_devices_->currentText();
// Set devices to be used from now on
AudioManager::instance()->SetOutputDevice(output_device);
AudioManager::instance()->SetInputDevice(input_device);
OLIVE_CONFIG("AudioRecordingFormat") = record_format_combo_->GetFormat();
OLIVE_CONFIG("AudioRecordingCodec") = record_options_->GetCodec();
OLIVE_CONFIG("AudioRecordingSampleRate") = record_options_->sample_rate_combobox()->GetSampleRate();
OLIVE_CONFIG("AudioRecordingChannelLayout") = QVariant::fromValue(record_options_->channel_layout_combobox()->GetChannelLayout());
OLIVE_CONFIG("AudioRecordingBitRate") = QVariant::fromValue(record_options_->bit_rate_slider()->GetValue());
OLIVE_CONFIG("AudioRecordingSampleFormat") = record_options_->sample_format_combobox()->GetSampleFormat();
}
void PreferencesAudioTab::RefreshBackends()
@@ -25,6 +25,8 @@
#include <QPushButton>
#include "dialog/configbase/configdialogbase.h"
#include "dialog/export/exportaudiotab.h"
#include "dialog/export/exportformatcombobox.h"
namespace olive {
@@ -59,6 +61,10 @@ private:
*/
QPushButton* refresh_devices_btn_;
ExportFormatComboBox *record_format_combo_;
ExportAudioTab *record_options_;
private slots:
void RefreshBackends();
+1 -5
View File
@@ -95,11 +95,7 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
}
}
if (push_job) {
table->Push(NodeValue::kSampleJob, QVariant::fromValue(job), this);
} else {
table->Push(NodeValue::kSamples, QVariant::fromValue(job.samples()), this);
}
table->Push(NodeValue::kSamples, push_job ? QVariant::fromValue(job) : QVariant::fromValue(job.samples()), this);
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ ClipBlock::ClipBlock() :
AddInput(kMaintainAudioPitchInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable));
SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
//SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
SetEffectInput(kBufferIn);
}
+1 -1
View File
@@ -202,7 +202,7 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global
ShaderJobEvent(value, job);
job_type = NodeValue::kShaderJob;
job_type = NodeValue::kTexture;
push_job = QVariant::fromValue(job);
} else if (data_type == NodeValue::kSamples) {
// This must be an audio transition
@@ -101,7 +101,7 @@ void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
&& job.GetValue(kTopRightInput).data().value<QVector2D>().isNull() &&
job.GetValue(kBottomRightInput).data().value<QVector2D>().isNull() &&
job.GetValue(kBottomLeftInput).data().value<QVector2D>().isNull())) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
} else {
table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this);
}
+1 -1
View File
@@ -87,7 +87,7 @@ void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global
|| !qIsNull(job.GetValue(kRightInput).data().toDouble())
|| !qIsNull(job.GetValue(kTopInput).data().toDouble())
|| !qIsNull(job.GetValue(kBottomInput).data().toDouble())) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
} else {
table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this);
}
+1 -1
View File
@@ -90,7 +90,7 @@ void FlipDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global
if (!job.GetValue(kTextureInput).data().isNull()) {
// Only run shader if at least one of flip or flop are selected
if (job.GetValue(kHorizontalInput).data().toBool() || job.GetValue(kVerticalInput).data().toBool()) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
} else {
// If we're not flipping or flopping just push the texture
table->Push(job.GetValue(kTextureInput));
@@ -107,7 +107,7 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
// end up with gaps in the screen that will require an alpha channel.
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
pushed_job = true;
}
+1 -1
View File
@@ -53,7 +53,7 @@ void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals,
if (!job.GetValue(kTextureInput).data().isNull()) {
if (!qFuzzyCompare(job.GetValue(kValueInput).data().toDouble(), 1.0)) {
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
} else {
// 1.0 float is a no-op, so just push the texture
table->Push(job.GetValue(kTextureInput));
+2 -2
View File
@@ -35,7 +35,7 @@ BlurFilterNode::BlurFilterNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
AddInput(kMethodInput, NodeValue::kCombo, 0);
AddInput(kMethodInput, NodeValue::kCombo, 1); // Default to gaussian
AddInput(kRadiusInput, NodeValue::kFloat, 10.0);
SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0);
@@ -118,7 +118,7 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
}
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
} else {
// If we're not performing the blur job, just push the texture
+1 -1
View File
@@ -66,7 +66,7 @@ void MosaicFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa
if (texture
&& job.GetValue(kHorizInput).data().toInt() != texture->width()
&& job.GetValue(kVertInput).data().toInt() != texture->height()) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
} else {
table->Push(job.GetValue(kTextureInput));
}
+1 -1
View File
@@ -99,7 +99,7 @@ void StrokeFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa
if (!job.GetValue(kTextureInput).data().isNull()) {
if (job.GetValue(kRadiusInput).data().toDouble() > 0.0
&& job.GetValue(kOpacityInput).data().toDouble() > 0.0) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
} else {
table->Push(job.GetValue(kTextureInput));
}
+1 -1
View File
@@ -80,7 +80,7 @@ void NoiseGeneratorNode::Value(const NodeValueRow &value, const NodeGlobals &glo
job.InsertValue(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this));
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
}
+1 -1
View File
@@ -102,7 +102,7 @@ void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globa
job.SetRequestedFormat(VideoParams::kFormatFloat32);
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) const
+20 -4
View File
@@ -63,9 +63,11 @@ void ShapeNode::Retranslate()
ShaderCode ShapeNode::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/shape.frag")));
if (shader_id == QStringLiteral("shape")) {
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/shape.frag")));
} else {
return super::GetShaderCode(shader_id);
}
}
void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
@@ -75,8 +77,22 @@ void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod
job.InsertValue(value);
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
job.SetShaderID(QStringLiteral("shape"));
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
if (!value[kBaseInput].data().isNull()) {
// Push as merge node
ShaderJob merge;
merge.SetShaderID(QStringLiteral("mrg"));
merge.InsertValue(MergeNode::kBaseIn, value[kBaseInput]);
merge.InsertValue(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, QVariant::fromValue(job), this));
merge.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this);
} else {
// Just push generate job
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
}
}
+17 -3
View File
@@ -30,12 +30,14 @@ namespace olive {
#define super Node
QString ShapeNodeBase::kPositionInput = QStringLiteral("pos_in");
QString ShapeNodeBase::kSizeInput = QStringLiteral("size_in");
QString ShapeNodeBase::kColorInput = QStringLiteral("color_in");
const QString ShapeNodeBase::kBaseInput = QStringLiteral("base_in");
const QString ShapeNodeBase::kPositionInput = QStringLiteral("pos_in");
const QString ShapeNodeBase::kSizeInput = QStringLiteral("size_in");
const QString ShapeNodeBase::kColorInput = QStringLiteral("color_in");
ShapeNodeBase::ShapeNodeBase(bool create_color_input)
{
AddInput(kBaseInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0));
AddInput(kSizeInput, NodeValue::kVec2, QVector2D(100, 100));
SetInputProperty(kSizeInput, QStringLiteral("min"), QVector2D(0, 0));
@@ -58,6 +60,9 @@ ShapeNodeBase::ShapeNodeBase(bool create_color_input)
for (int i=0; i<kGizmoScaleCount; i++) {
point_gizmo_[i] = AddDraggableGizmo<PointGizmo>(pos_n_sz, PointGizmo::kAbsolute);
}
SetEffectInput(kBaseInput);
SetFlags(kVideoEffect);
}
void ShapeNodeBase::Retranslate()
@@ -102,6 +107,15 @@ void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlob
poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt));
}
ShaderCode ShapeNodeBase::GetShaderCode(const QString &shader_id) const
{
if (shader_id == QStringLiteral("mrg")) {
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag"));
}
return ShaderCode();
}
void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
+7 -3
View File
@@ -24,6 +24,7 @@
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/inputdragger.h"
#include "node/math/merge/merge.h"
#include "node/node.h"
namespace olive {
@@ -40,9 +41,12 @@ public:
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
static QString kPositionInput;
static QString kSizeInput;
static QString kColorInput;
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
static const QString kBaseInput;
static const QString kPositionInput;
static const QString kSizeInput;
static const QString kColorInput;
protected:
PolygonGizmo *poly_gizmo() const
+1 -1
View File
@@ -70,7 +70,7 @@ void SolidGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals
{
ShaderJob job;
job.InsertValue(value);
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
ShaderCode SolidGenerator::GetShaderCode(const QString &shader_id) const
+1 -1
View File
@@ -95,7 +95,7 @@ void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &global
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
if (!job.GetValue(kTextInput).data().toString().isEmpty()) {
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
}
+1 -1
View File
@@ -99,7 +99,7 @@ void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &global
job.SetRequestedFormat(VideoParams::kFormatFloat32);
if (!job.GetValue(kTextInput).data().toString().isEmpty()) {
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
}
+17 -1
View File
@@ -76,6 +76,7 @@ void TextGeneratorV3::Retranslate()
super::Retranslate();
SetInputName(kTextInput, tr("Text"));
SetInputName(kBaseInput, tr("Base"));
}
void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
@@ -89,7 +90,22 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global
job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace());
if (!job.GetValue(kTextInput).data().toString().isEmpty()) {
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
if (!value[kBaseInput].data().isNull()) {
// Push as merge node
ShaderJob merge;
merge.SetShaderID(QStringLiteral("mrg"));
merge.InsertValue(MergeNode::kBaseIn, value[kBaseInput]);
merge.InsertValue(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, QVariant::fromValue(job), this));
merge.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this);
} else {
// Just push generate job
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
} else if (!value[kBaseInput].data().isNull()) {
table->Push(value[kBaseInput]);
}
}
+12 -24
View File
@@ -58,7 +58,7 @@ QByteArray HashTraverser::GetHash(const Node *node, const Node::ValueHint &hint,
return hash_.result();
}
TexturePtr HashTraverser::ProcessVideoFootage(const FootageJob &stream, const rational &input_time)
void HashTraverser::ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time)
{
Hash(FileFunctions::GetUniqueFileIdentifier(stream.filename()));
Hash(stream.loop_mode());
@@ -69,24 +69,20 @@ TexturePtr HashTraverser::ProcessVideoFootage(const FootageJob &stream, const ra
Hash(stream.video_params().video_type() == VideoParams::kVideoTypeStill ? 0 : input_time);
Hash(stream.video_params().video_type());
TexturePtr texture = super::ProcessVideoFootage(stream, input_time);
texture_ids_.insert(texture.get(), hash_.result());
return texture;
texture_ids_.insert(destination.get(), hash_.result());
}
SampleBufferPtr HashTraverser::ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time)
void HashTraverser::ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time)
{
Hash(FileFunctions::GetUniqueFileIdentifier(stream.filename()));
Hash(stream.loop_mode());
Hash(stream.audio_params().stream_index());
Hash(input_time);
SampleBufferPtr buf = super::ProcessAudioFootage(stream, input_time);
texture_ids_.insert(buf.get(), hash_.result());
return buf;
texture_ids_.insert(destination.get(), hash_.result());
}
TexturePtr HashTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job)
void HashTraverser::ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob &job)
{
HashGenerateJob(node, &job);
@@ -99,33 +95,25 @@ TexturePtr HashTraverser::ProcessShader(const Node *node, const TimeRange &range
Hash(it.value());
}
TexturePtr texture = super::ProcessShader(node, range, job);
texture_ids_.insert(texture.get(), hash_.result());
return texture;
texture_ids_.insert(destination.get(), hash_.result());
}
TexturePtr HashTraverser::ProcessColorTransform(const Node *node, const ColorTransformJob &job)
void HashTraverser::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job)
{
Hash(job.GetColorProcessor()->id());
TexturePtr texture = super::ProcessColorTransform(node, job);
texture_ids_.insert(texture.get(), hash_.result());
return texture;
texture_ids_.insert(destination.get(), hash_.result());
}
SampleBufferPtr HashTraverser::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job)
void HashTraverser::ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job)
{
SampleBufferPtr buf = super::ProcessSamples(node, range, job);
texture_ids_.insert(buf.get(), hash_.result());
return buf;
texture_ids_.insert(destination.get(), hash_.result());
}
TexturePtr HashTraverser::ProcessFrameGeneration(const Node *node, const GenerateJob &job)
void HashTraverser::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob &job)
{
HashGenerateJob(node, &job);
TexturePtr texture = super::ProcessFrameGeneration(node, job);
texture_ids_.insert(texture.get(), hash_.result());
return texture;
texture_ids_.insert(destination.get(), hash_.result());
}
void HashTraverser::HashGenerateJob(const Node *node, const GenerateJob *job)
+6 -6
View File
@@ -33,17 +33,17 @@ public:
QByteArray GetHash(const Node *node, const Node::ValueHint &hint, const VideoParams &params, const TimeRange &range);
protected:
virtual TexturePtr ProcessVideoFootage(const FootageJob &stream, const rational &input_time) override;
virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) override;
virtual SampleBufferPtr ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time) override;
virtual void ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time) override;
virtual TexturePtr ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override;
virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job) override;
virtual TexturePtr ProcessColorTransform(const Node *node, const ColorTransformJob& job) override;
virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job) override;
virtual SampleBufferPtr ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override;
virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job) override;
virtual TexturePtr ProcessFrameGeneration(const Node *node, const GenerateJob& job) override;
virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override;
private:
void HashGenerateJob(const Node *node, const GenerateJob *job);
@@ -102,7 +102,7 @@ void ColorDifferenceKeyNode::Value(const NodeValueRow &value, const NodeGlobals
// If there's no texture, no need to run an operation
if (!job.GetValue(kTextureInput).data().isNull()) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ void DespillNode::Value(const NodeValueRow &value, const NodeGlobals &globals, N
// If there's no texture, no need to run an operation
if (!job.GetValue(kTextureInput).data().isNull()) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
}
+3 -3
View File
@@ -382,7 +382,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt
output->Push(texture_val);
} else {
// Push shader job
output->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
output->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
break;
}
@@ -413,10 +413,10 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt
output->Push(NodeValue::kSamples, QVariant::fromValue(job.samples()), this);
} else {
output->Push(NodeValue::kSampleJob, QVariant::fromValue(job), this);
output->Push(NodeValue::kSamples, QVariant::fromValue(job), this);
}
} else {
output->Push(NodeValue::kSampleJob, QVariant::fromValue(job), this);
output->Push(NodeValue::kSamples, QVariant::fromValue(job), this);
}
break;
}
+1 -1
View File
@@ -101,7 +101,7 @@ void MergeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOff);
}
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
}
}
}
+7 -1
View File
@@ -341,6 +341,8 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
Track::Reference ref = GetReferenceFromRealIndex(i);
FootageJob job(decoder_, filename(), ref.type(), GetLength(), loop_mode);
NodeValue::Type type;
if (ref.type() == Track::kVideo) {
VideoParams vp = GetVideoParams(ref.index());
@@ -348,13 +350,17 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
vp.set_colorspace(GetColorspaceToUse(vp));
job.set_video_params(vp);
type = NodeValue::kTexture;
} else {
AudioParams ap = GetAudioParams(ref.index());
job.set_audio_params(ap);
job.set_cache_path(project()->cache_path());
type = NodeValue::kSamples;
}
table->Push(NodeValue::kFootageJob, QVariant::fromValue(job), this, false, ref.ToString());
table->Push(type, QVariant::fromValue(job), this, false, ref.ToString());
}
}
}
+78 -147
View File
@@ -52,6 +52,8 @@ NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node
row.insert(it.key(), value);
}
PreProcessRow(range, row);
return row;
}
@@ -258,7 +260,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint
if (is_enabled) {
NodeValueRow row = GenerateRow(&database, n, range);
//qDebug() << "FIXME: Implement pre-process of row";
// Generate output table
NodeValueTable table = database.Merge();
@@ -266,9 +267,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint
// By this point, the node should have all the inputs it needs to render correctly
n->Value(row, GenerateGlobals(video_params_, range), &table);
// Post-process table
PostProcessTable(n, hint, range, table);
return table;
} else {
return database.Merge();
@@ -289,179 +287,112 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR
return table;
}
TexturePtr NodeTraverser::ProcessVideoFootage(const FootageJob &stream, const rational &input_time)
{
Q_UNUSED(input_time)
// Create dummy texture with footage params
return CreateDummyTexture(stream.video_params());
}
SampleBufferPtr NodeTraverser::ProcessAudioFootage(const FootageJob& stream, const TimeRange &input_time)
{
Q_UNUSED(stream)
Q_UNUSED(input_time)
return SampleBuffer::Create();
}
TexturePtr NodeTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job)
{
Q_UNUSED(node)
Q_UNUSED(range)
Q_UNUSED(job)
// Create dummy texture with sequence params
VideoParams tex_params = video_params_;
tex_params.set_channel_count(GetChannelCountFromJob(job));
return CreateDummyTexture(tex_params);
}
TexturePtr NodeTraverser::ProcessColorTransform(const Node *node, const ColorTransformJob &job)
{
Q_UNUSED(node)
return CreateDummyTexture(job.GetInputTexture()->params());
}
SampleBufferPtr NodeTraverser::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job)
{
Q_UNUSED(node)
Q_UNUSED(range)
Q_UNUSED(job)
return SampleBuffer::Create();
}
TexturePtr NodeTraverser::ProcessFrameGeneration(const Node *node, const GenerateJob &job)
{
Q_UNUSED(node)
Q_UNUSED(job)
// Create dummy texture with sequence params
VideoParams tex_params = video_params_;
tex_params.set_channel_count(GetChannelCountFromJob(job));
return CreateDummyTexture(tex_params);
}
void NodeTraverser::SaveCachedTexture(const QByteArray &hash, TexturePtr texture)
{
Q_UNUSED(hash)
Q_UNUSED(texture)
}
TexturePtr NodeTraverser::GetCachedTexture(const QByteArray& hash)
{
Q_UNUSED(hash)
return nullptr;
}
QVector2D NodeTraverser::GenerateResolution() const
{
return QVector2D(video_params_.square_pixel_width(), video_params_.height());
}
void NodeTraverser::PostProcessTable(const Node *node, const Node::ValueHint &hint, const TimeRange &range, NodeValueTable &output_params)
void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range)
{
bool got_cached_frame = false;
QByteArray cached_node_hash;
if (val.type() == NodeValue::kTexture || val.type() == NodeValue::kSamples) {
const QVariant &v = val.data();
// Convert footage to image/sample buffers
/*if (CanCacheFrames() && node->GetCacheTextures()) {
// This node is set to cache the result, see if we can retrieved a previously cached version
cached_node_hash = RenderManager::Hash(node, hint, GetCacheVideoParams(), range.in());
if (v.canConvert<ShaderJob>()) {
TexturePtr cached_frame = GetCachedTexture(cached_node_hash);
if (cached_frame) {
output_params.Push(NodeValue::kTexture, QVariant::fromValue(cached_frame), node);
ShaderJob job = v.value<ShaderJob>();
// No more to do here
got_cached_frame = true;
}
}*/
VideoParams tex_params = GetCacheVideoParams();
tex_params.set_channel_count(GetChannelCountFromJob(job));
// Strip out any jobs or footage
QList<NodeValue> footage_jobs_to_run;
QList<NodeValue> shader_jobs_to_run;
QList<NodeValue> sample_jobs_to_run;
QList<NodeValue> generate_jobs_to_run;
QList<NodeValue> color_transform_jobs_to_run;
TexturePtr tex = CreateTexture(tex_params);
for (int i=0; i<output_params.Count(); i++) {
const NodeValue& v = output_params.at(i);
QList<NodeValue>* take_this_value_list = nullptr;
ProcessShader(tex, val.source(), range, job);
if (v.type() == NodeValue::kFootageJob) {
take_this_value_list = &footage_jobs_to_run;
} else if (v.type() == NodeValue::kShaderJob) {
take_this_value_list = &shader_jobs_to_run;
} else if (v.type() == NodeValue::kSampleJob) {
take_this_value_list = &sample_jobs_to_run;
} else if (v.type() == NodeValue::kGenerateJob) {
take_this_value_list = &generate_jobs_to_run;
} else if (v.type() == NodeValue::kColorTransformJob) {
take_this_value_list = &color_transform_jobs_to_run;
}
val.set_data(QVariant::fromValue(tex));
if (take_this_value_list) {
take_this_value_list->append(output_params.TakeAt(i));
i--;
}
}
} else if (v.canConvert<GenerateJob>()) {
if (!got_cached_frame) {
// Retrieve video frames
foreach (const NodeValue& v, footage_jobs_to_run) {
// Assume this is a VideoStream, we did a type check earlier in the function
FootageJob job = v.data().value<FootageJob>();
GenerateJob job = v.value<GenerateJob>();
VideoParams tex_params = GetCacheVideoParams();
tex_params.set_channel_count(GetChannelCountFromJob(job));
if (job.GetRequestedFormat() != VideoParams::kFormatInvalid) {
tex_params.set_format(job.GetRequestedFormat());
}
TexturePtr tex = CreateTexture(tex_params);
ProcessFrameGeneration(tex, val.source(), job);
val.set_data(QVariant::fromValue(tex));
} else if (v.canConvert<ColorTransformJob>()) {
ColorTransformJob job = v.value<ColorTransformJob>();
VideoParams src_params = job.GetInputTexture()->params();
src_params.set_channel_count(GetChannelCountFromJob(job));
TexturePtr dest = CreateTexture(src_params);
ProcessColorTransform(dest, val.source(), job);
val.set_data(QVariant::fromValue(dest));
} else if (v.canConvert<FootageJob>()) {
FootageJob job = v.value<FootageJob>();
if (job.type() == Track::kVideo) {
rational footage_time = Footage::AdjustTimeByLoopMode(range.in(), job.loop_mode(), job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base());
TexturePtr tex;
if (footage_time.isNaN()) {
// Push dummy texture
output_params.Push(NodeValue::kTexture, QVariant::fromValue(CreateDummyTexture(job.video_params())), node, v.array(), v.tag());
tex = CreateDummyTexture(job.video_params());
} else {
output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessVideoFootage(job, footage_time)), node, v.array(), v.tag());
VideoParams managed_params = job.video_params();
managed_params.set_format(GetCacheVideoParams().format());
tex = CreateTexture(job.video_params());
ProcessVideoFootage(tex, job, footage_time);
}
val.set_data(QVariant::fromValue(tex));
} else if (job.type() == Track::kAudio) {
SampleBufferPtr buffer = CreateSampleBuffer(GetCacheAudioParams(), range.length());
ProcessAudioFootage(buffer, job, range);
val.set_data(QVariant::fromValue(buffer));
}
} else if (v.canConvert<SampleJob>()) {
SampleJob job = v.value<SampleJob>();
SampleBufferPtr output_buffer = CreateSampleBuffer(job.samples()->audio_params(), job.samples()->sample_count());
ProcessSamples(output_buffer, val.source(), range, job);
val.set_data(QVariant::fromValue(output_buffer));
}
// Run shaders
foreach (const NodeValue& v, shader_jobs_to_run) {
output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessShader(node, range, v.data().value<ShaderJob>())), node, v.array(), v.tag());
}
// Run color transforms
foreach (const NodeValue& v, color_transform_jobs_to_run) {
output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessColorTransform(node, v.data().value<ColorTransformJob>())), node, v.array(), v.tag());
}
// Run generate jobs
foreach (const NodeValue& v, generate_jobs_to_run) {
output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessFrameGeneration(node, v.data().value<GenerateJob>())), node, v.array(), v.tag());
}
}
}
// Retrieve audio samples
foreach (const NodeValue& v, footage_jobs_to_run) {
// Assume this is an AudioStream, we did a type check earlier in the function
FootageJob job = v.data().value<FootageJob>();
void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row)
{
QByteArray cached_node_hash;
if (job.type() == Track::kAudio) {
output_params.Push(NodeValue::kSamples, QVariant::fromValue(ProcessAudioFootage(job, range)), node, v.array(), v.tag());
}
}
// Resolve any jobs
for (auto it=row.begin(); it!=row.end(); it++) {
// Jobs will almost always be submitted with one of these types
NodeValue &val = it.value();
// Run any accelerated shader jobs
foreach (const NodeValue& v, sample_jobs_to_run) {
output_params.Push(NodeValue::kSamples, QVariant::fromValue(ProcessSamples(node, range, v.data().value<SampleJob>())), node, v.array(), v.tag());
}
if (CanCacheFrames() && node->GetCacheTextures() && !got_cached_frame) {
// Save cached texture
SaveCachedTexture(cached_node_hash, output_params.Get(NodeValue::kTexture).value<TexturePtr>());
ResolveJobs(val, range);
}
}
+35 -9
View File
@@ -66,6 +66,16 @@ public:
video_params_ = params;
}
const AudioParams& GetCacheAudioParams() const
{
return audio_params_;
}
void SetCacheAudioParams(const AudioParams& params)
{
audio_params_ = params;
}
static int GetChannelCountFromJob(const GenerateJob& job);
protected:
@@ -73,21 +83,33 @@ protected:
virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange& range);
virtual TexturePtr ProcessVideoFootage(const FootageJob &stream, const rational &input_time);
virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time){}
virtual SampleBufferPtr ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time);
virtual void ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time){}
virtual TexturePtr ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job);
virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job){}
virtual TexturePtr ProcessColorTransform(const Node *node, const ColorTransformJob& job);
virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job){}
virtual SampleBufferPtr ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job);
virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job){}
virtual TexturePtr ProcessFrameGeneration(const Node *node, const GenerateJob& job);
virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job){}
virtual TexturePtr GetCachedTexture(const QByteArray& hash);
virtual TexturePtr CreateTexture(const VideoParams &p)
{
return CreateDummyTexture(p);
}
virtual void SaveCachedTexture(const QByteArray& hash, TexturePtr texture);
virtual SampleBufferPtr CreateSampleBuffer(const AudioParams &params, int sample_count)
{
// Return dummy by default
return SampleBuffer::Create();
}
SampleBufferPtr CreateSampleBuffer(const AudioParams &params, const rational &length)
{
return CreateSampleBuffer(params, params.time_to_samples(length));
}
virtual bool CanCacheFrames()
{
@@ -111,13 +133,17 @@ protected:
cancel_ = cancel;
}
void ResolveJobs(NodeValue &value, const TimeRange &range);
private:
void PostProcessTable(const Node *node, const Node::ValueHint &hint, const TimeRange &range, NodeValueTable &output_params);
void PreProcessRow(const TimeRange &range, NodeValueRow &row);
TexturePtr CreateDummyTexture(const VideoParams &p);
VideoParams video_params_;
AudioParams audio_params_;
const QAtomicInt *cancel_;
};
-15
View File
@@ -135,13 +135,8 @@ QByteArray NodeValue::ValueToBytes(NodeValue::Type type, const QVariant &value)
// These types have no persistent input
case kNone:
case kFootageJob:
case kTexture:
case kSamples:
case kShaderJob:
case kSampleJob:
case kGenerateJob:
case kColorTransformJob:
case kDataTypeCount:
break;
}
@@ -355,11 +350,6 @@ QString NodeValue::GetPrettyDataTypeName(Type type)
case kAudioParams:
return QCoreApplication::translate("NodeValue", "Audio Parameters");
case kFootageJob:
case kShaderJob:
case kSampleJob:
case kGenerateJob:
case kColorTransformJob:
case kDataTypeCount:
break;
}
@@ -408,11 +398,6 @@ QString NodeValue::GetDataTypeName(Type type)
return QStringLiteral("vparam");
case kAudioParams:
return QStringLiteral("aparam");
case kFootageJob:
case kShaderJob:
case kSampleJob:
case kGenerateJob:
case kColorTransformJob:
case kDataTypeCount:
break;
}
+5 -45
View File
@@ -172,51 +172,6 @@ public:
*/
kAudioParams,
/**
* Job type
*
* An internal type used to indicate to the renderer that a footage job needs to
* run. This value will usually be taken from a table and a kTexture or kSamples value will be
* pushed to take its place.
*/
kFootageJob,
/**
* Job type
*
* An internal type used to indicate to the renderer that an accelerated shader job needs to
* run. This value will usually be taken from a table and a kTexture value will be pushed to
* take its place.
*/
kShaderJob,
/**
* Job type
*
* An internal type used to indicate to the renderer that an accelerated sample job needs to
* take place. This value will usually be taken from a table and a kSamples value will be
* pushed to take its place.
*/
kSampleJob,
/**
* Job type
*
* An internal type used to indicate to the renderer that an accelerated sample job needs to
* take place. This value will usually be taken from a table and a kSamples value will be
* pushed to take its place.
*/
kGenerateJob,
/**
* Job type
*
* An internal type used to indicate to the renderer that an accelerated color transform job
* needs to take place. This value will usually be taken from a table and a kTexture value will
* be pushed to take its place.
*/
kColorTransformJob,
/**
* End of list
*/
@@ -253,6 +208,11 @@ public:
return data_;
}
void set_data(const QVariant& data)
{
data_ = data;
}
const QString& tag() const
{
return tag_;
+5
View File
@@ -52,6 +52,11 @@ public:
ProjectViewModel* model() const;
bool SelectItem(Node *n)
{
return explorer_->SelectItem(n);
}
virtual void SelectAll() override;
virtual void DeselectAll() override;
+2 -1
View File
@@ -34,7 +34,8 @@ TimelinePanel::TimelinePanel(QWidget *parent) :
Retranslate();
connect(tw, &TimelineWidget::BlockSelectionChanged, this, &TimelinePanel::BlockSelectionChanged);
connect(tw, &TimelineWidget::RequestCaptureStart, this, &TimelinePanel::RequestCaptureStart );
connect(tw, &TimelineWidget::RequestCaptureStart, this, &TimelinePanel::RequestCaptureStart);
connect(tw, &TimelineWidget::RevealViewerInProject, this, &TimelinePanel::RevealViewerInProject);
}
void TimelinePanel::SplitAtPlayhead()
+2
View File
@@ -114,6 +114,8 @@ signals:
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
void RevealViewerInProject(ViewerOutput *r);
};
}
+119 -7
View File
@@ -52,7 +52,7 @@ const QVector<uint64_t> AudioParams::kSupportedChannelLayouts = {
AV_CH_LAYOUT_7POINT1
};
const AudioParams::Format AudioParams::kInternalFormat = AudioParams::kFormatFloat32;
const AudioParams::Format AudioParams::kInternalFormat = AudioParams::kFormatFloat32Planar;
bool AudioParams::operator==(const AudioParams &other) const
{
@@ -144,15 +144,21 @@ int AudioParams::channel_count() const
int AudioParams::bytes_per_sample_per_channel() const
{
switch (format_) {
case kFormatUnsigned8:
case kFormatUnsigned8Packed:
case kFormatUnsigned8Planar:
return 1;
case kFormatSigned16:
case kFormatSigned16Packed:
case kFormatSigned16Planar:
return 2;
case kFormatSigned32:
case kFormatFloat32:
case kFormatSigned32Packed:
case kFormatSigned32Planar:
case kFormatFloat32Packed:
case kFormatFloat32Planar:
return 4;
case kFormatSigned64:
case kFormatFloat64:
case kFormatSigned64Packed:
case kFormatSigned64Planar:
case kFormatFloat64Packed:
case kFormatFloat64Planar:
return 8;
case kFormatInvalid:
case kFormatCount:
@@ -244,4 +250,110 @@ QString AudioParams::ChannelLayoutToString(const uint64_t &layout)
}
}
QString AudioParams::FormatToString(const Format &f)
{
switch (f) {
case kFormatUnsigned8Packed:
return QCoreApplication::translate("AudioParams", "Unsigned 8-bit (Packed)");
case kFormatSigned16Packed:
return QCoreApplication::translate("AudioParams", "Signed 16-bit (Packed)");
case kFormatSigned32Packed:
return QCoreApplication::translate("AudioParams", "Signed 32-bit (Packed)");
case kFormatSigned64Packed:
return QCoreApplication::translate("AudioParams", "Signed 64-bit (Packed)");
case kFormatFloat32Packed:
return QCoreApplication::translate("AudioParams", "Float 32-bit (Packed)");
case kFormatFloat64Packed:
return QCoreApplication::translate("AudioParams", "Float 64-bit (Packed)");
case kFormatUnsigned8Planar:
return QCoreApplication::translate("AudioParams", "Unsigned 8-bit (Planar)");
case kFormatSigned16Planar:
return QCoreApplication::translate("AudioParams", "Signed 16-bit (Planar)");
case kFormatSigned32Planar:
return QCoreApplication::translate("AudioParams", "Signed 32-bit (Planar)");
case kFormatSigned64Planar:
return QCoreApplication::translate("AudioParams", "Signed 64-bit (Planar)");
case kFormatFloat32Planar:
return QCoreApplication::translate("AudioParams", "Float 32-bit (Planar)");
case kFormatFloat64Planar:
return QCoreApplication::translate("AudioParams", "Float 64-bit (Planar)");
case kFormatInvalid:
case kFormatCount:
break;
}
return QCoreApplication::translate("AudioParams", "Unknown (0x%1)").arg(f, 1, 16);
}
AudioParams::Format AudioParams::GetPackedEquivalent(Format fmt)
{
switch (fmt) {
// For packed input, just return input
case kFormatUnsigned8Packed:
case kFormatSigned16Packed:
case kFormatSigned32Packed:
case kFormatSigned64Packed:
case kFormatFloat32Packed:
case kFormatFloat64Packed:
return fmt;
// Convert to packed
case kFormatUnsigned8Planar:
return kFormatUnsigned8Packed;
case kFormatSigned16Planar:
return kFormatSigned16Packed;
case kFormatSigned32Planar:
return kFormatSigned32Packed;
case kFormatSigned64Planar:
return kFormatSigned64Packed;
case kFormatFloat32Planar:
return kFormatFloat32Packed;
case kFormatFloat64Planar:
return kFormatFloat64Packed;
case kFormatInvalid:
case kFormatCount:
break;
}
return kFormatInvalid;
}
AudioParams::Format AudioParams::GetPlanarEquivalent(Format fmt)
{
switch (fmt) {
// Convert to planar
case kFormatUnsigned8Packed:
return kFormatUnsigned8Planar;
case kFormatSigned16Packed:
return kFormatSigned16Planar;
case kFormatSigned32Packed:
return kFormatSigned32Planar;
case kFormatSigned64Packed:
return kFormatSigned64Planar;
case kFormatFloat32Packed:
return kFormatFloat32Planar;
case kFormatFloat64Packed:
return kFormatFloat64Planar;
// For planar input, just return input
case kFormatUnsigned8Planar:
case kFormatSigned16Planar:
case kFormatSigned32Planar:
case kFormatSigned64Planar:
case kFormatFloat32Planar:
case kFormatFloat64Planar:
return fmt;
case kFormatInvalid:
case kFormatCount:
break;
}
return kFormatInvalid;
}
}
+36 -7
View File
@@ -35,30 +35,54 @@ namespace olive {
class AudioParams {
public:
// Only append to this list (never insert) because indexes are used in serialized files
enum Format {
/// Invalid
kFormatInvalid = -1,
/// 8-bit unsigned integer
kFormatUnsigned8,
kFormatUnsigned8Planar,
/// 16-bit signed integer
kFormatSigned16,
kFormatSigned16Planar,
/// 32-bit signed integer
kFormatSigned32,
kFormatSigned32Planar,
/// 64-bit signed integer
kFormatSigned64,
kFormatSigned64Planar,
/// 32-bit float
kFormatFloat32,
kFormatFloat32Planar,
/// 64-bit float
kFormatFloat64,
kFormatFloat64Planar,
/// 8-bit unsigned integer
kFormatUnsigned8Packed,
/// 16-bit signed integer
kFormatSigned16Packed,
/// 32-bit signed integer
kFormatSigned32Packed,
/// 64-bit signed integer
kFormatSigned64Packed,
/// 32-bit float
kFormatFloat32Packed,
/// 64-bit float
kFormatFloat64Packed,
/// Total format count
kFormatCount
kFormatCount,
kPlanarStart = kFormatUnsigned8Planar,
kPackedStart = kFormatUnsigned8Packed,
kPlanarEnd = kPackedStart,
kPackedEnd = kFormatCount
};
static const Format kInternalFormat;
@@ -200,6 +224,11 @@ public:
*/
static QString ChannelLayoutToString(const uint64_t &layout);
static QString FormatToString(const Format &f);
static AudioParams::Format GetPackedEquivalent(AudioParams::Format fmt);
static AudioParams::Format GetPlanarEquivalent(AudioParams::Format fmt);
private:
void set_default_footage_parameters()
{
+2 -4
View File
@@ -56,10 +56,8 @@ public:
#endif
}
const NodeValueRow &GetValues() const
{
return value_map_;
}
const NodeValueRow &GetValues() const { return value_map_; }
NodeValueRow &GetValues() { return value_map_; }
private:
NodeValueRow value_map_;
-4
View File
@@ -518,10 +518,6 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
case NodeValue::kFile:
case NodeValue::kVideoParams:
case NodeValue::kAudioParams:
case NodeValue::kShaderJob:
case NodeValue::kSampleJob:
case NodeValue::kGenerateJob:
case NodeValue::kFootageJob:
case NodeValue::kBezier:
case NodeValue::kNone:
case NodeValue::kColorTransformJob:
+45 -109
View File
@@ -50,12 +50,18 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational
{
ViewerOutput* viewer = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"));
TimeRange range = TimeRange(time, time + frame_length);
NodeValueTable table;
if (Node *texture_output = viewer->GetConnectedTextureOutput()) {
table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kTextureInput), TimeRange(time, time + frame_length));
table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kTextureInput), range);
}
return table.Get(NodeValue::kTexture).value<TexturePtr>();
NodeValue tex_val = table.GetWithMeta(NodeValue::kTexture);
ResolveJobs(tex_val, range);
return tex_val.data().value<TexturePtr>();
}
FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time)
@@ -133,10 +139,12 @@ void RenderProcessor::Run()
SetCancelPointer(&ticket_->IsCancelled());
SetCacheVideoParams(ticket_->property("vparam").value<VideoParams>());
SetCacheAudioParams(ticket_->property("aparam").value<AudioParams>());
switch (type) {
case RenderManager::kTypeVideo:
{
SetCacheVideoParams(ticket_->property("vparam").value<VideoParams>());
rational time = ticket_->property("time").value<rational>();
rational frame_length = GetCacheVideoParams().frame_rate_as_time_base();
@@ -261,7 +269,7 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim
{
if (track->type() == Track::kAudio) {
const AudioParams& audio_params = ticket_->property("aparam").value<AudioParams>();
const AudioParams& audio_params = GetCacheAudioParams();
QVector<Block*> active_blocks = track->BlocksAtTimeRange(range);
@@ -375,11 +383,11 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim
}
}
TexturePtr RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const rational &input_time)
void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time)
{
if (ticket_->property("type").value<RenderManager::TicketType>() != RenderManager::kTypeVideo) {
// Video cannot contribute to audio, so we do nothing here
return super::ProcessVideoFootage(stream, input_time);
return;
}
// Check the still frame cache. On large frames such as high resolution still images, uploading
@@ -447,12 +455,6 @@ TexturePtr RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const
// We convert to our rendering pixel format, since that will always be float-based which
// is necessary for correct color conversion
VideoParams managed_params = frame->video_params();
managed_params.set_format(render_params.format());
managed_params.set_pixel_aspect_ratio(stream_data.pixel_aspect_ratio());
managed_params.set_interlacing(stream_data.interlacing());
TexturePtr value = render_ctx_->CreateTexture(managed_params);
ColorProcessorPtr processor = ColorProcessor::Create(color_manager,
using_colorspace,
color_manager->GetReferenceColorSpace());
@@ -471,39 +473,32 @@ TexturePtr RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const
job.SetInputAlphaAssociation(kAlphaUnassociated);
}
render_ctx_->BlitColorManaged(job, value.get());
return value;
render_ctx_->BlitColorManaged(job, destination.get());
}
}
}
return super::ProcessVideoFootage(stream, input_time);
}
SampleBufferPtr RenderProcessor::ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time)
void RenderProcessor::ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time)
{
DecoderPtr decoder = ResolveDecoderFromInput(stream.decoder(), Decoder::CodecStream(stream.filename(), stream.audio_params().stream_index()));
if (decoder) {
const AudioParams& audio_params = ticket_->property("aparam").value<AudioParams>();
const AudioParams& audio_params = GetCacheAudioParams();
Decoder::RetrieveAudioData status = decoder->RetrieveAudio(input_time, audio_params,
stream.cache_path(),
stream.loop_mode(),
static_cast<RenderMode::Mode>(ticket_->property("mode").toInt()));
Decoder::RetrieveAudioStatus status = decoder->RetrieveAudio(destination,
input_time, audio_params,
stream.cache_path(),
stream.loop_mode(),
static_cast<RenderMode::Mode>(ticket_->property("mode").toInt()));
if (status.status == Decoder::kOK && status.samples) {
return status.samples;
} else if (status.status == Decoder::kWaitingForConform) {
if (status == Decoder::kWaitingForConform) {
ticket_->setProperty("incomplete", true);
}
}
return super::ProcessAudioFootage(stream, input_time);
}
TexturePtr RenderProcessor::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job)
void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob &job)
{
Q_UNUSED(range)
@@ -524,7 +519,7 @@ TexturePtr RenderProcessor::ProcessShader(const Node *node, const TimeRange &ran
if (shader.isNull()) {
// Couldn't find or build the shader required
return super::ProcessShader(node, range, job);
return;
}
}
@@ -532,24 +527,19 @@ TexturePtr RenderProcessor::ProcessShader(const Node *node, const TimeRange &ran
tex_params.set_channel_count(GetChannelCountFromJob(job));
TexturePtr destination = render_ctx_->CreateTexture(tex_params);
// Run shader
render_ctx_->BlitToTexture(shader, job, destination.get());
return destination;
}
SampleBufferPtr RenderProcessor::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job)
void RenderProcessor::ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job)
{
if (!job.samples() || !job.samples()->is_allocated()) {
return super::ProcessSamples(node, range, job);
return;
}
SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(job.samples()->audio_params(), job.samples()->sample_count());
NodeValueRow value_db;
const AudioParams& audio_params = ticket_->property("aparam").value<AudioParams>();
const AudioParams& audio_params = GetCacheAudioParams();
for (int i=0;i<job.samples()->sample_count();i++) {
// Calculate the exact rational time at this sample
@@ -566,62 +556,46 @@ SampleBufferPtr RenderProcessor::ProcessSamples(const Node *node, const TimeRang
node->ProcessSamples(value_db,
job.samples(),
output_buffer,
destination,
i);
}
return output_buffer;
}
TexturePtr RenderProcessor::ProcessColorTransform(const Node *node, const ColorTransformJob &job)
void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job)
{
TexturePtr src = job.GetInputTexture();
VideoParams src_params = src->params();
src_params.set_channel_count(GetChannelCountFromJob(job));
TexturePtr dest = render_ctx_->CreateTexture(src_params);
render_ctx_->BlitColorManaged(job, dest.get());
return dest;
render_ctx_->BlitColorManaged(job, destination.get());
}
TexturePtr RenderProcessor::ProcessFrameGeneration(const Node *node, const GenerateJob &job)
void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob &job)
{
FramePtr frame = Frame::Create();
VideoParams frame_params = GetCacheVideoParams();
frame_params.set_channel_count(GetChannelCountFromJob(job));
if (job.GetRequestedFormat() != VideoParams::kFormatInvalid) {
frame_params.set_format(job.GetRequestedFormat());
}
frame->set_video_params(frame_params);
frame->set_video_params(destination->params());
frame->allocate();
node->GenerateFrame(frame, job);
TexturePtr texture = render_ctx_->CreateTexture(frame->video_params(),
frame->data(),
frame->linesize_pixels());
if (!job.GetColorspace().isEmpty()) {
if (job.GetColorspace().isEmpty()) {
// Just upload frame data straight to frame
destination->Upload(frame->data(), frame->linesize_pixels());
} else {
// Convert to reference space
TexturePtr dest = render_ctx_->CreateTexture(GetCacheVideoParams());
// Upload to middle texture
TexturePtr mid = render_ctx_->CreateTexture(GetCacheVideoParams());
mid->Upload(frame->data(), frame->linesize_pixels());
ColorManager* color_manager = Node::ValueToPtr<ColorManager>(ticket_->property("colormanager"));
ColorProcessorPtr cp = ColorProcessor::Create(color_manager, job.GetColorspace(), color_manager->GetReferenceColorSpace());
ColorTransformJob ctj;
ctj.SetColorProcessor(cp);
ctj.SetInputTexture(texture);
ctj.SetInputTexture(mid);
ctj.SetInputAlphaAssociation(kAlphaAssociated);
render_ctx_->BlitColorManaged(ctj, dest.get());
texture = dest;
render_ctx_->BlitColorManaged(ctj, destination.get());
}
return texture;
}
bool RenderProcessor::CanCacheFrames()
@@ -629,42 +603,4 @@ bool RenderProcessor::CanCacheFrames()
return ticket_->property("type").value<RenderManager::TicketType>() == RenderManager::kTypeVideo;
}
TexturePtr RenderProcessor::GetCachedTexture(const QByteArray& hash)
{
QString cache_dir = ticket_->property("cache").toString();
if (cache_dir.isEmpty()) {
return nullptr;
}
FramePtr f = FrameHashCache::LoadCacheFrame(cache_dir, hash);
if (f) {
TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels());
return texture;
}
return nullptr;
}
void RenderProcessor::SaveCachedTexture(const QByteArray &hash, TexturePtr tex_var)
{
// FIXME: Temporarily disabled because I don't know how to ensure that the frame saved here is
// not the main frame. If it is, it'll be saved twice which will waste a lot of cycles.
// At least disabled, the frame will still save, and if nothing else alters the hash, it
// will pick up automatically from GetCachedTexture.
/*if (!tex_var.isNull()) {
QString cache_dir = ticket_->property("cache").toString();
if (!cache_dir.isEmpty()) {
TexturePtr texture = tex_var.value<TexturePtr>();
FramePtr frame = Frame::Create();
frame->set_video_params(texture->params());
frame->allocate();
render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels());
FrameHashCache::SaveCacheFrame(cache_dir, hash, frame);
qDebug() << "Saved mid-render frame to cache";
}
}*/
}
}
+14 -8
View File
@@ -44,23 +44,29 @@ public:
protected:
virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange &range) override;
virtual TexturePtr ProcessVideoFootage(const FootageJob &stream, const rational &input_time) override;
virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) override;
virtual SampleBufferPtr ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time) override;
virtual void ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time) override;
virtual TexturePtr ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override;
virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job) override;
virtual SampleBufferPtr ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override;
virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job) override;
virtual TexturePtr ProcessColorTransform(const Node *node, const ColorTransformJob& job) override;
virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job) override;
virtual TexturePtr ProcessFrameGeneration(const Node *node, const GenerateJob& job) override;
virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override;
virtual bool CanCacheFrames() override;
virtual TexturePtr GetCachedTexture(const QByteArray &hash) override;
virtual TexturePtr CreateTexture(const VideoParams &p) override
{
return render_ctx_->CreateTexture(p);
}
virtual void SaveCachedTexture(const QByteArray& hash, TexturePtr texture) override;
virtual SampleBufferPtr CreateSampleBuffer(const AudioParams &params, int sample_count) override
{
return SampleBuffer::CreateAllocated(params, sample_count);
}
private:
RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader);
+3 -2
View File
@@ -39,7 +39,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
super(true, false, parent),
last_scroll_val_(0),
focused_node_(nullptr),
time_target_(nullptr)
time_target_(nullptr),
show_all_nodes_(false)
{
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
QHBoxLayout* layout = new QHBoxLayout(this);
@@ -453,7 +454,7 @@ void NodeParamView::RemoveContext(Node *ctx)
void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context)
{
if ((n->GetFlags() & Node::kDontShowInParamView) && !IsGroupMode()) {
if ((n->GetFlags() & Node::kDontShowInParamView) && !IsGroupMode() && !show_all_nodes_) {
return;
}
+2
View File
@@ -143,6 +143,8 @@ private:
QVector<Node*> contexts_;
QVector<Node*> current_contexts_;
bool show_all_nodes_;
private slots:
void UpdateGlobalScrollBar();
@@ -26,6 +26,7 @@
#include <QVector3D>
#include <QVector4D>
#include "common/qtutils.h"
#include "core.h"
#include "node/node.h"
#include "node/project/sequence/sequence.h"
@@ -86,11 +87,6 @@ void NodeParamViewWidgetBridge::CreateWidgets()
case NodeValue::kTexture:
case NodeValue::kMatrix:
case NodeValue::kSamples:
case NodeValue::kFootageJob:
case NodeValue::kShaderJob:
case NodeValue::kSampleJob:
case NodeValue::kGenerateJob:
case NodeValue::kColorTransformJob:
case NodeValue::kVideoParams:
case NodeValue::kAudioParams:
case NodeValue::kDataTypeCount:
@@ -241,11 +237,6 @@ void NodeParamViewWidgetBridge::WidgetCallback()
case NodeValue::kTexture:
case NodeValue::kMatrix:
case NodeValue::kSamples:
case NodeValue::kFootageJob:
case NodeValue::kShaderJob:
case NodeValue::kSampleJob:
case NodeValue::kGenerateJob:
case NodeValue::kColorTransformJob:
case NodeValue::kVideoParams:
case NodeValue::kAudioParams:
case NodeValue::kDataTypeCount:
@@ -396,6 +387,10 @@ void NodeParamViewWidgetBridge::CreateSliders(int count)
T* fs = new T();
fs->SliderBase::SetDefaultValue(GetInnerInput().GetSplitDefaultValueForTrack(i));
fs->SetLadderElementCount(2);
// HACK: Force some spacing between sliders
fs->setContentsMargins(0, 0, QtUtils::QFontMetricsWidth(fs->fontMetrics(), QStringLiteral(" ")), 0);
widgets_.append(fs);
connect(fs, &T::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
}
@@ -419,11 +414,6 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
case NodeValue::kTexture:
case NodeValue::kMatrix:
case NodeValue::kSamples:
case NodeValue::kFootageJob:
case NodeValue::kShaderJob:
case NodeValue::kSampleJob:
case NodeValue::kGenerateJob:
case NodeValue::kColorTransformJob:
case NodeValue::kVideoParams:
case NodeValue::kAudioParams:
case NodeValue::kDataTypeCount:
@@ -139,10 +139,6 @@ void NodeTableView::SetTime(const rational &time)
switch (value.type()) {
case NodeValue::kVideoParams:
case NodeValue::kAudioParams:
case NodeValue::kFootageJob:
case NodeValue::kShaderJob:
case NodeValue::kSampleJob:
case NodeValue::kGenerateJob:
// These types have no string representation
break;
case NodeValue::kTexture:
+46 -7
View File
@@ -97,6 +97,8 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) :
connect(tree_view_, &ProjectExplorerTreeView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu);
connect(list_view_, &ProjectExplorerListView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu);
connect(icon_view_, &ProjectExplorerIconView::customContextMenuRequested, this, &ProjectExplorer::ShowContextMenu);
UpdateNavBarText();
}
const ProjectToolbar::ViewType &ProjectExplorer::view_type() const
@@ -151,13 +153,7 @@ void ProjectExplorer::BrowseToFolder(const QModelIndex &index)
list_view_->setRootIndex(index);
// Set navbar text to folder's name
if (index.isValid()) {
Folder* f = static_cast<Folder*>(sort_model_.mapToSource(index).internalPointer());
nav_bar_->set_text(f->GetLabel());
} else {
// Or set it to an empty string if the index is valid (which means we're browsing to the root directory)
nav_bar_->set_text(QString());
}
UpdateNavBarText();
// Set directory up enabled button based on whether we're in root or not
nav_bar_->set_dir_up_enabled(index.isValid());
@@ -246,6 +242,21 @@ QString ProjectExplorer::GetHumanReadableNodeName(Node *node)
}
}
void ProjectExplorer::UpdateNavBarText()
{
QString absolute;
Folder* f = static_cast<Folder*>(sort_model_.mapToSource(list_view_->rootIndex()).internalPointer());
while (f && f != project()->root()) {
absolute.prepend(QStringLiteral("%1 / ").arg(f->GetLabel()));
f = f->folder();
}
absolute.prepend(QStringLiteral("/ "));
nav_bar_->set_text(absolute);
}
QAbstractItemView *ProjectExplorer::CurrentView() const
{
return static_cast<QAbstractItemView*>(stacked_widget_->currentWidget());
@@ -667,4 +678,32 @@ void ProjectExplorer::DeleteSelected()
}
}
bool ProjectExplorer::SelectItem(Node *n)
{
DeselectAll();
QModelIndex index = model_.CreateIndexFromItem(n);
if (index.isValid()) {
index = sort_model_.mapFromSource(index);
QModelIndex parent = index.parent();
if (view_type() == ProjectToolbar::TreeView) {
// Expand all folders until this index is visible
while (parent.isValid()) {
tree_view_->expand(parent);
parent = parent.parent();
}
} else {
BrowseToFolder(parent);
}
CurrentView()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
return true;
}
return false;
}
}
@@ -85,6 +85,8 @@ public:
void DeleteSelected();
bool SelectItem(Node *n);
public slots:
void set_view_type(ProjectToolbar::ViewType type);
@@ -138,6 +140,8 @@ private:
static QString GetHumanReadableNodeName(Node* node);
void UpdateNavBarText();
/**
* @brief Get the currently active QAbstractItemView
*/
+1
View File
@@ -21,6 +21,7 @@ set(OLIVE_SOURCES
widget/standardcombos/interlacedcombobox.h
widget/standardcombos/pixelaspectratiocombobox.h
widget/standardcombos/pixelformatcombobox.h
widget/standardcombos/sampleformatcombobox.h
widget/standardcombos/sampleratecombobox.h
widget/standardcombos/standardcombos.h
widget/standardcombos/videodividercombobox.h
@@ -0,0 +1,87 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 SAMPLEFORMATCOMBOBOX_H
#define SAMPLEFORMATCOMBOBOX_H
#include <QComboBox>
#include "render/audioparams.h"
namespace olive {
class SampleFormatComboBox : public QComboBox
{
Q_OBJECT
public:
SampleFormatComboBox(QWidget* parent = nullptr) :
QComboBox(parent),
attempt_to_restore_format_(true)
{
}
void SetAttemptToRestoreFormat(bool e) { attempt_to_restore_format_ = e; }
void SetAvailableFormats(const std::vector<AudioParams::Format> &formats)
{
AudioParams::Format tmp = AudioParams::kFormatInvalid;
if (attempt_to_restore_format_) {
tmp = GetSampleFormat();
}
clear();
foreach (const AudioParams::Format &of, formats) {
AddFormatItem(of);
}
if (attempt_to_restore_format_) {
SetSampleFormat(tmp);
}
}
AudioParams::Format GetSampleFormat() const
{
return static_cast<AudioParams::Format>(this->currentData().toInt());
}
void SetSampleFormat(AudioParams::Format fmt)
{
for (int i=0; i<this->count(); i++) {
if (this->itemData(i).toInt() == fmt) {
this->setCurrentIndex(i);
break;
}
}
}
private:
void AddFormatItem(AudioParams::Format f)
{
this->addItem(AudioParams::FormatToString(f), f);
}
bool attempt_to_restore_format_;
};
}
#endif // SAMPLEFORMATCOMBOBOX_H
@@ -26,6 +26,7 @@
#include "interlacedcombobox.h"
#include "pixelaspectratiocombobox.h"
#include "pixelformatcombobox.h"
#include "sampleformatcombobox.h"
#include "sampleratecombobox.h"
#include "videodividercombobox.h"
@@ -1071,6 +1071,14 @@ void TimelineWidget::ShowContextMenu()
menu.addSeparator();
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(selected.first())) {
if (clip->connected_viewer()) {
QAction *reveal_in_project = menu.addAction(tr("Reveal in Project"));
reveal_in_project->setData(reinterpret_cast<quintptr>(clip->connected_viewer()));
connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject);
}
}
QAction* properties_action = menu.addAction(tr("Properties"));
connect(properties_action, &QAction::triggered, this, &TimelineWidget::ShowSpeedDurationDialogForSelectedClips);
}
@@ -1215,6 +1223,15 @@ void TimelineWidget::SignalBlockSelectionChange()
signal_block_change_timer_->start();
}
void TimelineWidget::RevealInProject()
{
QAction *a = static_cast<QAction*>(sender());
ViewerOutput *item_to_reveal = reinterpret_cast<ViewerOutput*>(a->data().value<quintptr>());
emit RevealViewerInProject(item_to_reveal);
}
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
{
ghost_items_.append(ghost);
@@ -269,6 +269,8 @@ signals:
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
void RevealViewerInProject(ViewerOutput *r);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
@@ -410,6 +412,8 @@ private slots:
void SignalBlockSelectionChange();
void RevealInProject();
};
}
+1 -1
View File
@@ -21,7 +21,7 @@ void RecordTool::MousePress(TimelineViewMouseEvent *event)
return;
}
if (t->type() != Track::kAudio) {
if (t && t->type() != Track::kAudio) {
// We only support audio tracks here
return;
}
+1 -1
View File
@@ -252,7 +252,7 @@ void SeekableWidget::SeekToScenePoint(qreal scene)
return;
}
rational playhead_time = SceneToTime(scene);
rational playhead_time = qMax(rational(0), SceneToTime(scene));
if (Core::instance()->snapping() && GetSnapService()) {
rational movement;
+25 -4
View File
@@ -388,7 +388,6 @@ void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, c
SetTimeAndSignal(time.in());
ArmForRecording();
recording_filename_ = QStringLiteral("/home/matt/Desktop/ass.mp3");
recording_callback_ = source;
recording_range_ = time;
recording_track_ = track;
@@ -1167,7 +1166,31 @@ void ViewerWidget::Play(bool in_to_out_only)
in_to_out_only = false;
}
} else if (record_armed_) {
if (AudioManager::instance()->StartRecording(recording_filename_, GetConnectedNode()->GetAudioParams())) {
DisarmRecording();
if (GetConnectedNode()->project()->filename().isEmpty()) {
QMessageBox::critical(this, tr("Audio Recording"), tr("Project must be saved before you can record audio."));
return;
}
QDir audio_path(QFileInfo(GetConnectedNode()->project()->filename()).dir().filePath(tr("audio")));
if (!audio_path.exists()) {
audio_path.mkpath(QStringLiteral("."));
}
recording_filename_ = audio_path.filePath(QStringLiteral("%1.%2").arg(
QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss"),
ExportFormat::GetExtension(static_cast<ExportFormat::Format>(Config::Current()[QStringLiteral("AudioRecordingFormat")].toInt())))
);
AudioParams ap(OLIVE_CONFIG("AudioRecordingSampleRate").toInt(), OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong(), static_cast<AudioParams::Format>(OLIVE_CONFIG("AudioRecordingSampleFormat").toInt()));
EncodingParams encode_param;
encode_param.EnableAudio(ap, static_cast<ExportCodec::Codec>(OLIVE_CONFIG("AudioRecordingCodec").toInt()));
encode_param.SetFilename(recording_filename_);
encode_param.set_audio_bit_rate(OLIVE_CONFIG("AudioRecordingBitRate").toInt() * 1000);
if (AudioManager::instance()->StartRecording(encode_param)) {
recording_ = true;
controls_->SetPauseButtonRecordingState(true);
recording_callback_->EnableRecordingOverlay(TimelineCoordinate(recording_range_.in(), recording_track_));
@@ -1175,8 +1198,6 @@ void ViewerWidget::Play(bool in_to_out_only)
QMessageBox::critical(this, tr("Audio Recording"), tr("Failed to start audio recording"));
return;
}
DisarmRecording();
}
PlayInternal(1, in_to_out_only);
+10
View File
@@ -479,6 +479,15 @@ void MainWindow::ShowWelcomeDialog()
}
}
void MainWindow::RevealViewerInProject(ViewerOutput *r)
{
foreach (ProjectPanel *p, project_panels_) {
if (p->project() == r->project() && p->SelectItem(r)) {
break;
}
}
}
#ifdef Q_OS_LINUX
void MainWindow::ShowNouveauWarning()
{
@@ -557,6 +566,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel()
connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture);
connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged);
connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject);
connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime);
+2
View File
@@ -195,6 +195,8 @@ private slots:
void ShowWelcomeDialog();
void RevealViewerInProject(ViewerOutput *r);
};
}