From 9fb50b2fe64f189bc930395ec5d1b2e07b9f55d4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 16:06:50 -0700 Subject: [PATCH 01/19] markerpropertiesdialog: update dialog title --- app/dialog/markerproperties/markerpropertiesdialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp index 007625f8f..7e81c2887 100644 --- a/app/dialog/markerproperties/markerpropertiesdialog.cpp +++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp @@ -117,6 +117,8 @@ MarkerPropertiesDialog::MarkerPropertiesDialog(const std::vectoraddWidget(buttons, row, 0, 1, 2); + + setWindowTitle(tr("Edit Markers")); } void MarkerPropertiesDialog::accept() From 818ac12fb75da708e96dafa45fab058dd7b6ab4f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 16:07:24 -0700 Subject: [PATCH 02/19] viewer: set actual filename for audio recording --- app/widget/viewer/viewer.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index acc7b45c4..9caee2a3e 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -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,6 +1166,23 @@ void ViewerWidget::Play(bool in_to_out_only) in_to_out_only = false; } } else if (record_armed_) { + 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(Config::Current()[QStringLiteral("AudioRecordingFormat")].toInt()))) + ); + if (AudioManager::instance()->StartRecording(recording_filename_, GetConnectedNode()->GetAudioParams())) { recording_ = true; controls_->SetPauseButtonRecordingState(true); @@ -1175,8 +1191,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); From 6e4cb02ac232a71e9964d413435b7a0278abcd04 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 16:07:35 -0700 Subject: [PATCH 03/19] seekablewidget: reimplement 0 limit on seek --- app/widget/timeruler/seekablewidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index b5334fe83..5af1f030a 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -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; From 026cc9f0649ac73a4b0474a2d93fb617f3abfb4f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 16:07:43 -0700 Subject: [PATCH 04/19] config: add default recording format --- app/config/config.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/config/config.cpp b/app/config/config.cpp index 501cc4f35..e5de5fcc8 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -27,6 +27,7 @@ #include #include +#include "codec/exportformat.h" #include "common/autoscroll.h" #include "common/filefunctions.h" #include "common/xmlutils.h" @@ -120,6 +121,8 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString()); + SetEntryInternal(QStringLiteral("AudioRecordingFormat"), NodeValue::kInt, ExportFormat::kFormatWAV); + SetEntryInternal(QStringLiteral("DiskCacheBehind"), NodeValue::kRational, QVariant::fromValue(rational(0))); SetEntryInternal(QStringLiteral("DiskCacheAhead"), NodeValue::kRational, QVariant::fromValue(rational(60))); From ef515579df7ce172f969dd73cc188e4b18191df0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 17:05:11 -0700 Subject: [PATCH 05/19] completed audio recording implementation --- app/audio/audiomanager.cpp | 12 ++-- app/audio/audiomanager.h | 2 +- app/codec/ffmpeg/ffmpegencoder.cpp | 27 ++++++-- app/codec/ffmpeg/ffmpegencoder.h | 2 +- app/config/config.cpp | 5 ++ app/config/config.h | 2 + app/dialog/export/export.cpp | 5 +- app/dialog/export/exportaudiotab.cpp | 10 +-- app/dialog/export/exportaudiotab.h | 20 +++++- app/dialog/export/exportformatcombobox.cpp | 8 ++- .../preferences/tabs/preferencesaudiotab.cpp | 33 +++++++--- .../preferences/tabs/preferencesaudiotab.h | 6 ++ app/render/audioparams.cpp | 30 +++++++++ app/render/audioparams.h | 2 + app/widget/standardcombos/CMakeLists.txt | 1 + .../standardcombos/sampleformatcombobox.h | 64 +++++++++++++++++++ app/widget/standardcombos/standardcombos.h | 1 + app/widget/timelinewidget/tool/record.cpp | 2 +- app/widget/viewer/viewer.cpp | 9 ++- 19 files changed, 204 insertions(+), 37 deletions(-) create mode 100644 app/widget/standardcombos/sampleformatcombobox.h diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 9794b77c5..174f37fb4 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -184,25 +184,21 @@ void AudioManager::HardReset() Pa_Initialize(); } -bool AudioManager::StartRecording(const QString &filename, const AudioParams ¶ms) +bool AudioManager::StartRecording(const EncodingParams ¶ms) { 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; } diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 62fd0e920..bf71e2223 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -74,7 +74,7 @@ public: void HardReset(); - bool StartRecording(const QString &filename, const AudioParams ¶ms); + bool StartRecording(const EncodingParams ¶ms); void StopRecording(); diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 9b38505b6..01985e0d0 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -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++) { @@ -579,7 +579,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 +653,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) { @@ -842,7 +842,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 +872,24 @@ 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: + break; + case AudioParams::kFormatUnsigned8: + return avcodec_find_encoder(AV_CODEC_ID_PCM_U8); + case AudioParams::kFormatSigned16: + return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE); + case AudioParams::kFormatSigned32: + return avcodec_find_encoder(AV_CODEC_ID_PCM_S32LE); + case AudioParams::kFormatSigned64: + return avcodec_find_encoder(AV_CODEC_ID_PCM_S64LE); + case AudioParams::kFormatFloat32: + return avcodec_find_encoder(AV_CODEC_ID_PCM_F32LE); + case AudioParams::kFormatFloat64: + return avcodec_find_encoder(AV_CODEC_ID_PCM_F64LE); + } + break; case ExportCodec::kCodecFLAC: return avcodec_find_encoder(AV_CODEC_ID_FLAC); case ExportCodec::kCodecOpus: diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 82e7040da..1bbc1ac54 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -80,7 +80,7 @@ private: bool InitializeResampleContext(const AudioParams &audio, bool planar); - static const AVCodec *GetEncoder(ExportCodec::Codec c); + static const AVCodec *GetEncoder(ExportCodec::Codec c, AudioParams::Format aformat); AVFormatContext* fmt_ctx_; diff --git a/app/config/config.cpp b/app/config/config.cpp index e5de5fcc8..f52b0c175 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -122,6 +122,11 @@ void Config::SetDefaults() 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::kFormatSigned16); + SetEntryInternal(QStringLiteral("AudioRecordingBitRate"), NodeValue::kInt, 320); SetEntryInternal(QStringLiteral("DiskCacheBehind"), NodeValue::kRational, QVariant::fromValue(rational(0))); SetEntryInternal(QStringLiteral("DiskCacheAhead"), NodeValue::kRational, QVariant::fromValue(rational(60))); diff --git a/app/config/config.h b/app/config/config.h index 11f466cc3..b5c18e063 100644 --- a/app/config/config.h +++ b/app/config/config.h @@ -30,6 +30,8 @@ namespace olive { +#define OLIVE_CONFIG(x) Config::Current()[QStringLiteral(x)] + class Config { public: static Config& Current(); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index ba5280897..c7a467d5c 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -211,6 +211,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : video_tab_->pixel_format_field()->SetPixelFormat(static_cast(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()->SetSampleFormat(ap.format()); audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout()); video_aspect_ratio_ = static_cast(vp.width()) / static_cast(vp.height()); @@ -515,7 +516,7 @@ ExportParams ExportDialog::GenerateParams() const AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(), 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(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); diff --git a/app/dialog/export/exportaudiotab.cpp b/app/dialog/export/exportaudiotab.cpp index 0db6dd0cd..70ef8d19a 100644 --- a/app/dialog/export/exportaudiotab.cpp +++ b/app/dialog/export/exportaudiotab.cpp @@ -59,7 +59,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 +70,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(320); bit_rate_slider_->SetFormat(tr("%1 kbps")); layout->addWidget(bit_rate_slider_, row, 1); @@ -79,9 +81,9 @@ int ExportAudioTab::SetFormat(ExportFormat::Format format) { QList acodecs = ExportFormat::GetAudioCodecs(format); setEnabled(!acodecs.isEmpty()); - codec_combobox()->clear(); + codec_combobox_->clear(); foreach (ExportCodec::Codec acodec, acodecs) { - codec_combobox()->addItem(ExportCodec::GetCodecName(acodec), acodec); + codec_combobox_->addItem(ExportCodec::GetCodecName(acodec), acodec); } return acodecs.size(); } diff --git a/app/dialog/export/exportaudiotab.h b/app/dialog/export/exportaudiotab.h index 4e29e3d48..241db8e8b 100644 --- a/app/dialog/export/exportaudiotab.h +++ b/app/dialog/export/exportaudiotab.h @@ -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(codec_combobox_->currentData().toInt()); + } + + void SetCodec(ExportCodec::Codec c) + { + for (int i=0; icount(); 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_; @@ -64,6 +79,7 @@ private: QComboBox* codec_combobox_; SampleRateComboBox* sample_rate_combobox_; ChannelLayoutComboBox* channel_layout_combobox_; + SampleFormatComboBox *sample_format_combobox_; IntegerSlider* bit_rate_slider_; }; diff --git a/app/dialog/export/exportformatcombobox.cpp b/app/dialog/export/exportformatcombobox.cpp index 9f1b1927f..c2ca8c453 100644 --- a/app/dialog/export/exportformatcombobox.cpp +++ b/app/dialog/export/exportformatcombobox.cpp @@ -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; diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index e8371cf9f..8a8b99a83 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -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,22 @@ 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(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_->SetCodec(static_cast(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(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); + + record_options_->SetFormat(record_format_combo_->GetFormat()); } QHBoxLayout* refresh_layout = new QHBoxLayout(); @@ -134,12 +140,19 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command) PaDeviceIndex input_device = audio_input_devices_->currentData().value(); // 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() diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.h b/app/dialog/preferences/tabs/preferencesaudiotab.h index 38b6138e3..8fccd4276 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.h +++ b/app/dialog/preferences/tabs/preferencesaudiotab.h @@ -25,6 +25,8 @@ #include #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(); diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index 24246429b..f90b70aa9 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -244,4 +244,34 @@ QString AudioParams::ChannelLayoutToString(const uint64_t &layout) } } +QString AudioParams::FormatToString(const Format &f) +{ + switch (f) { + case kFormatUnsigned8: + return QCoreApplication::translate("AudioParams", "Unsigned 8-bit"); + break; + case kFormatSigned16: + return QCoreApplication::translate("AudioParams", "Signed 16-bit"); + break; + case kFormatSigned32: + return QCoreApplication::translate("AudioParams", "Signed 32-bit"); + break; + case kFormatSigned64: + return QCoreApplication::translate("AudioParams", "Signed 64-bit"); + break; + case kFormatFloat32: + return QCoreApplication::translate("AudioParams", "Float 32-bit"); + break; + case kFormatFloat64: + return QCoreApplication::translate("AudioParams", "Float 64-bit"); + break; + + case kFormatInvalid: + case kFormatCount: + break; + } + + return QCoreApplication::translate("AudioParams", "Unknown (0x%1)").arg(f, 1, 16); +} + } diff --git a/app/render/audioparams.h b/app/render/audioparams.h index bf2e6f08b..cd0fd94b9 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -200,6 +200,8 @@ public: */ static QString ChannelLayoutToString(const uint64_t &layout); + static QString FormatToString(const Format &f); + private: void set_default_footage_parameters() { diff --git a/app/widget/standardcombos/CMakeLists.txt b/app/widget/standardcombos/CMakeLists.txt index 3784cb136..992a0ef7b 100644 --- a/app/widget/standardcombos/CMakeLists.txt +++ b/app/widget/standardcombos/CMakeLists.txt @@ -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 diff --git a/app/widget/standardcombos/sampleformatcombobox.h b/app/widget/standardcombos/sampleformatcombobox.h new file mode 100644 index 000000000..a1e610f7e --- /dev/null +++ b/app/widget/standardcombos/sampleformatcombobox.h @@ -0,0 +1,64 @@ +/*** + + 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 . + +***/ + +#ifndef SAMPLEFORMATCOMBOBOX_H +#define SAMPLEFORMATCOMBOBOX_H + +#include + +#include "render/audioparams.h" + +namespace olive { + +class SampleFormatComboBox : public QComboBox +{ + Q_OBJECT +public: + SampleFormatComboBox(QWidget* parent = nullptr) : + QComboBox(parent) + { + // Set up preview formats + for (int i=0;i(i); + + this->addItem(AudioParams::FormatToString(smp_fmt), smp_fmt); + } + } + + AudioParams::Format GetSampleFormat() const + { + return static_cast(this->currentData().toInt()); + } + + void SetSampleFormat(AudioParams::Format fmt) + { + for (int i=0; icount(); i++) { + if (this->itemData(i).toInt() == fmt) { + this->setCurrentIndex(i); + break; + } + } + } + +}; + +} + +#endif // SAMPLEFORMATCOMBOBOX_H diff --git a/app/widget/standardcombos/standardcombos.h b/app/widget/standardcombos/standardcombos.h index 1fe140116..acfffc893 100644 --- a/app/widget/standardcombos/standardcombos.h +++ b/app/widget/standardcombos/standardcombos.h @@ -26,6 +26,7 @@ #include "interlacedcombobox.h" #include "pixelaspectratiocombobox.h" #include "pixelformatcombobox.h" +#include "sampleformatcombobox.h" #include "sampleratecombobox.h" #include "videodividercombobox.h" diff --git a/app/widget/timelinewidget/tool/record.cpp b/app/widget/timelinewidget/tool/record.cpp index c8a7fc001..46125902f 100644 --- a/app/widget/timelinewidget/tool/record.cpp +++ b/app/widget/timelinewidget/tool/record.cpp @@ -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; } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 9caee2a3e..f8b25247a 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1183,7 +1183,14 @@ void ViewerWidget::Play(bool in_to_out_only) ExportFormat::GetExtension(static_cast(Config::Current()[QStringLiteral("AudioRecordingFormat")].toInt()))) ); - if (AudioManager::instance()->StartRecording(recording_filename_, GetConnectedNode()->GetAudioParams())) { + AudioParams ap(OLIVE_CONFIG("AudioRecordingSampleRate").toInt(), OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong(), static_cast(OLIVE_CONFIG("AudioRecordingSampleFormat").toInt())); + + EncodingParams encode_param; + encode_param.EnableAudio(ap, static_cast(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_)); From 4eba69c305ce5a1efc0c5706c676dd3c46ae4d98 Mon Sep 17 00:00:00 2001 From: Bennett Date: Mon, 2 May 2022 17:25:23 -0700 Subject: [PATCH 06/19] Return timestamp of 0 if 'time' is NaN; would otherwise cause signed integer overflow in qRound64 --- app/common/timecodefunctions.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index b80f8b237..2ac854af2 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -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: From 5351e634b03d0b0aaaf4e508aa9ebe77f6d7dd48 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 07:10:38 -0700 Subject: [PATCH 07/19] nodeparamviewwidgetbridge: force spacing between sliders Fixes #1910 --- app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 29c35657b..55e92cebc 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -26,6 +26,7 @@ #include #include +#include "common/qtutils.h" #include "core.h" #include "node/node.h" #include "node/project/sequence/sequence.h" @@ -394,6 +395,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); } From 028d2fd60cf1c3cb6f9fe031c78ee7a7a9ef32e0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 11:19:21 -0700 Subject: [PATCH 08/19] nodes: optimize job infrastructure Rework traversing so that jobs are only resolved when they are used. This should provide moderate optimize to the traversing process. --- app/node/audio/pan/pan.cpp | 6 +- app/node/block/clip/clip.cpp | 2 +- app/node/block/transition/transition.cpp | 2 +- .../cornerpin/cornerpindistortnode.cpp | 2 +- app/node/distort/crop/cropdistortnode.cpp | 2 +- app/node/distort/flip/flipdistortnode.cpp | 2 +- .../transform/transformdistortnode.cpp | 2 +- app/node/effect/opacity/opacityeffect.cpp | 2 +- app/node/filter/blur/blur.cpp | 4 +- app/node/filter/mosaic/mosaicfilternode.cpp | 2 +- app/node/filter/stroke/stroke.cpp | 2 +- app/node/generator/noise/noise.cpp | 2 +- app/node/generator/polygon/polygon.cpp | 2 +- app/node/generator/shape/shapenode.cpp | 2 +- app/node/generator/solid/solid.cpp | 2 +- app/node/generator/text/textv1.cpp | 2 +- app/node/generator/text/textv2.cpp | 2 +- .../colordifferencekey/colordifferencekey.cpp | 2 +- app/node/keying/despill/despill.cpp | 2 +- app/node/math/math/mathbase.cpp | 6 +- app/node/math/merge/merge.cpp | 2 +- app/node/project/footage/footage.cpp | 8 +- app/node/traverser.cpp | 102 +++++++----------- app/node/traverser.h | 2 +- app/node/value.cpp | 12 --- app/node/value.h | 41 +------ app/render/opengl/openglrenderer.cpp | 4 - app/widget/nodeparamview/nodeparamview.cpp | 5 +- app/widget/nodeparamview/nodeparamview.h | 2 + .../nodeparamviewwidgetbridge.cpp | 12 --- app/widget/nodetableview/nodetableview.cpp | 4 - 31 files changed, 78 insertions(+), 166 deletions(-) diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index 5e6d87a39..482bf075f 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -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); } } diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index bb5d7188d..a7ce383b2 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -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); } diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index ddcea3911..97f97a31b 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -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 diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index febb41061..57129bf3f 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -101,7 +101,7 @@ void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g && job.GetValue(kTopRightInput).data().value().isNull() && job.GetValue(kBottomRightInput).data().value().isNull() && job.GetValue(kBottomLeftInput).data().value().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); } diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index 18274109d..5fc6dd56a 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -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); } diff --git a/app/node/distort/flip/flipdistortnode.cpp b/app/node/distort/flip/flipdistortnode.cpp index 21a835e0c..4589b4542 100644 --- a/app/node/distort/flip/flipdistortnode.cpp +++ b/app/node/distort/flip/flipdistortnode.cpp @@ -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)); diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index e60d901f2..d2eb794ea 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -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; } diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index b4dcba646..6027c2ab3 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -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)); diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 1865afbca..0452c4fba 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -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 diff --git a/app/node/filter/mosaic/mosaicfilternode.cpp b/app/node/filter/mosaic/mosaicfilternode.cpp index e6f5f0545..c198868c5 100644 --- a/app/node/filter/mosaic/mosaicfilternode.cpp +++ b/app/node/filter/mosaic/mosaicfilternode.cpp @@ -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)); } diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index 8883a9047..d0360cc10 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -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)); } diff --git a/app/node/generator/noise/noise.cpp b/app/node/generator/noise/noise.cpp index 7ed7eb2b9..64ea2266a 100644 --- a/app/node/generator/noise/noise.cpp +++ b/app/node/generator/noise/noise.cpp @@ -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); } } diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index c96d5b932..cfccc83c2 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -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 diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp index 2d9848c1a..3da5f51a3 100644 --- a/app/node/generator/shape/shapenode.cpp +++ b/app/node/generator/shape/shapenode.cpp @@ -76,7 +76,7 @@ void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); - table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index 0327af098..713a096a7 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -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 diff --git a/app/node/generator/text/textv1.cpp b/app/node/generator/text/textv1.cpp index 45dd1fad7..9a850d4d4 100644 --- a/app/node/generator/text/textv1.cpp +++ b/app/node/generator/text/textv1.cpp @@ -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); } } diff --git a/app/node/generator/text/textv2.cpp b/app/node/generator/text/textv2.cpp index b976a58ff..12eb94b06 100644 --- a/app/node/generator/text/textv2.cpp +++ b/app/node/generator/text/textv2.cpp @@ -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); } } diff --git a/app/node/keying/colordifferencekey/colordifferencekey.cpp b/app/node/keying/colordifferencekey/colordifferencekey.cpp index 5770d0f55..39595e039 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.cpp +++ b/app/node/keying/colordifferencekey/colordifferencekey.cpp @@ -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); } } diff --git a/app/node/keying/despill/despill.cpp b/app/node/keying/despill/despill.cpp index 344b1f869..e72124f31 100644 --- a/app/node/keying/despill/despill.cpp +++ b/app/node/keying/despill/despill.cpp @@ -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); } } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 1876416c2..d21e0e613 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -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; } diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index 5940358aa..1f1c9be42 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -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); } } } diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index de87caa4f..d3c82304f 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -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()); } } } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 62dca9c79..d7a28d239 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -52,6 +52,8 @@ NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node row.insert(it.key(), value); } + PreProcessRow(node, 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(); @@ -355,9 +353,8 @@ 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::PreProcessRow(const Node *node, const TimeRange &range, NodeValueRow &row) { - bool got_cached_frame = false; QByteArray cached_node_hash; // Convert footage to image/sample buffers @@ -374,80 +371,53 @@ void NodeTraverser::PostProcessTable(const Node *node, const Node::ValueHint &hi } }*/ - // Strip out any jobs or footage - QList footage_jobs_to_run; - QList shader_jobs_to_run; - QList sample_jobs_to_run; - QList generate_jobs_to_run; + // 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(); - for (int i=0; i* take_this_value_list = nullptr; + if (val.type() == NodeValue::kTexture || val.type() == NodeValue::kSamples) { + const QVariant &v = val.data(); - 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; - } + if (v.canConvert()) { - if (take_this_value_list) { - take_this_value_list->append(output_params.TakeAt(i)); - i--; - } - } + qDebug() << "Running shader for" << val.source(); + val.set_data(QVariant::fromValue(ProcessShader(val.source(), range, v.value()))); - 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(); + } else if (v.canConvert()) { - 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()); + val.set_data(QVariant::fromValue(ProcessFrameGeneration(val.source(), v.value()))); - if (footage_time.isNaN()) { - // Push dummy texture - output_params.Push(NodeValue::kTexture, QVariant::fromValue(CreateDummyTexture(job.video_params())), node, v.array(), v.tag()); - } else { - output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessVideoFootage(job, footage_time)), node, v.array(), v.tag()); + } else if (v.canConvert()) { + + FootageJob job = v.value(); + + 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()); + + if (footage_time.isNaN()) { + // Push dummy texture + val.set_data(QVariant::fromValue(CreateDummyTexture(job.video_params()))); + } else { + val.set_data(QVariant::fromValue(ProcessVideoFootage(job, footage_time))); + } + } else if (job.type() == Track::kAudio) { + val.set_data(QVariant::fromValue(ProcessAudioFootage(job, range))); } + + } else if (v.canConvert()) { + + val.set_data(QVariant::fromValue(ProcessSamples(node, range, v.value()))); + } - } - // Run shaders - foreach (const NodeValue& v, shader_jobs_to_run) { - output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessShader(node, range, v.data().value())), 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())), 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(); - - if (job.type() == Track::kAudio) { - output_params.Push(NodeValue::kSamples, QVariant::fromValue(ProcessAudioFootage(job, range)), node, v.array(), v.tag()); - } - } - - // 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())), node, v.array(), v.tag()); - } - - if (CanCacheFrames() && node->GetCacheTextures() && !got_cached_frame) { + /*if (CanCacheFrames() && node->GetCacheTextures() && !got_cached_frame) { // Save cached texture SaveCachedTexture(cached_node_hash, output_params.Get(NodeValue::kTexture).value()); - } + }*/ } TexturePtr NodeTraverser::CreateDummyTexture(const VideoParams &p) diff --git a/app/node/traverser.h b/app/node/traverser.h index 839c8561b..3a15455da 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -109,7 +109,7 @@ protected: } private: - void PostProcessTable(const Node *node, const Node::ValueHint &hint, const TimeRange &range, NodeValueTable &output_params); + void PreProcessRow(const Node *node, const TimeRange &range, NodeValueRow &row); TexturePtr CreateDummyTexture(const VideoParams &p); diff --git a/app/node/value.cpp b/app/node/value.cpp index d3fa7373b..cdafe2151 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -135,12 +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 kDataTypeCount: break; } @@ -354,10 +350,6 @@ QString NodeValue::GetPrettyDataTypeName(Type type) case kAudioParams: return QCoreApplication::translate("NodeValue", "Audio Parameters"); - case kFootageJob: - case kShaderJob: - case kSampleJob: - case kGenerateJob: case kDataTypeCount: break; } @@ -406,10 +398,6 @@ QString NodeValue::GetDataTypeName(Type type) return QStringLiteral("vparam"); case kAudioParams: return QStringLiteral("aparam"); - case kFootageJob: - case kShaderJob: - case kSampleJob: - case kGenerateJob: case kDataTypeCount: break; } diff --git a/app/node/value.h b/app/node/value.h index 12fb91a33..d894a7d35 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -172,42 +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, - /** * End of list */ @@ -244,6 +208,11 @@ public: return data_; } + void set_data(const QVariant& data) + { + data_ = data; + } + const QString& tag() const { return tag_; diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index f270bb568..628ed8f15 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -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::kDataTypeCount: diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 8e3beb82e..49f470da8 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -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_(true) { // 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; } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index c0078b83e..f04072e52 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -143,6 +143,8 @@ private: QVector contexts_; QVector current_contexts_; + bool show_all_nodes_; + private slots: void UpdateGlobalScrollBar(); diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 55e92cebc..e030d1b15 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -87,10 +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::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kDataTypeCount: @@ -241,10 +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::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kDataTypeCount: @@ -422,10 +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::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kDataTypeCount: diff --git a/app/widget/nodetableview/nodetableview.cpp b/app/widget/nodetableview/nodetableview.cpp index 9ebee87b4..174b3df3e 100644 --- a/app/widget/nodetableview/nodetableview.cpp +++ b/app/widget/nodetableview/nodetableview.cpp @@ -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: From 6e7e6f81f9168d70de22f00ba739cf1b6ed2a60d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 11:23:03 -0700 Subject: [PATCH 09/19] traverser: remove debug line --- app/node/traverser.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index d7a28d239..a620c3fa8 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -381,7 +381,6 @@ void NodeTraverser::PreProcessRow(const Node *node, const TimeRange &range, Node if (v.canConvert()) { - qDebug() << "Running shader for" << val.source(); val.set_data(QVariant::fromValue(ProcessShader(val.source(), range, v.value()))); } else if (v.canConvert()) { From b290c4d00709838153a913e7007cb3c6bec308c3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 13:41:27 -0700 Subject: [PATCH 10/19] audio: extended support for planar/packed formats --- app/audio/audiomanager.cpp | 20 ++- app/audio/packedprocessor.cpp | 4 +- app/audio/planarprocessor.cpp | 4 +- app/codec/encoder.cpp | 8 +- app/codec/encoder.h | 1 + app/codec/exportcodec.cpp | 29 ++++ app/codec/exportcodec.h | 8 +- app/codec/exportformat.cpp | 31 +++- app/codec/exportformat.h | 5 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 2 +- app/codec/ffmpeg/ffmpegencoder.cpp | 62 ++++++-- app/codec/ffmpeg/ffmpegencoder.h | 6 +- app/common/ffmpegutils.cpp | 56 +++++--- app/common/ffmpegutils.h | 2 +- app/config/config.cpp | 2 +- app/dialog/export/export.cpp | 8 +- app/dialog/export/exportaudiotab.cpp | 31 +++- app/dialog/export/exportaudiotab.h | 8 ++ .../preferences/tabs/preferencesaudiotab.cpp | 3 +- app/render/audioparams.cpp | 132 ++++++++++++++---- app/render/audioparams.h | 41 +++++- .../standardcombos/sampleformatcombobox.h | 33 ++++- 22 files changed, 393 insertions(+), 103 deletions(-) diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 174f37fb4..f58449848 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -77,7 +77,7 @@ int InputCallback(const void *input, void *output, unsigned long frameCount, con s->set_sample_count(frameCount); s->set_audio_params(f->params().audio_params()); - f->WriteAudioData(f->params().audio_params(), false, reinterpret_cast(&input), frameCount); + f->WriteAudioData(f->params().audio_params(), reinterpret_cast(&input), frameCount); return paContinue; } @@ -115,16 +115,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; diff --git a/app/audio/packedprocessor.cpp b/app/audio/packedprocessor.cpp index f748da5c2..91fd25a5c 100644 --- a/app/audio/packedprocessor.cpp +++ b/app/audio/packedprocessor.cpp @@ -42,10 +42,10 @@ bool PackedProcessor::Open(const AudioParams ¶ms) 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); diff --git a/app/audio/planarprocessor.cpp b/app/audio/planarprocessor.cpp index abd2be85b..0d5222b76 100644 --- a/app/audio/planarprocessor.cpp +++ b/app/audio/planarprocessor.cpp @@ -42,10 +42,10 @@ bool PlanarProcessor::Open(const AudioParams ¶ms) 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); diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index eec326526..06b36c3bb 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -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 Encoder::GetSampleFormatsForCodec(ExportCodec::Codec c) const +{ + return std::vector(); +} + } diff --git a/app/codec/encoder.h b/app/codec/encoder.h index ace7bf7a0..102b0430e 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -155,6 +155,7 @@ public: static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms); virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const; + virtual std::vector GetSampleFormatsForCodec(ExportCodec::Codec c) const; const EncodingParams& params() const; diff --git a/app/codec/exportcodec.cpp b/app/codec/exportcodec.cpp index 254a9989c..e1c76ad4c 100644 --- a/app/codec/exportcodec.cpp +++ b/app/codec/exportcodec.cpp @@ -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; +} + } diff --git a/app/codec/exportcodec.h b/app/codec/exportcodec.h index f4d17dfa0..a57abc294 100644 --- a/app/codec/exportcodec.h +++ b/app/codec/exportcodec.h @@ -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); + }; } diff --git a/app/codec/exportformat.cpp b/app/codec/exportformat.cpp index 59561d50b..14f3e8446 100644 --- a/app/codec/exportformat.cpp +++ b/app/codec/exportformat.cpp @@ -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 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 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 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 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 ExportFormat::GetSampleFormatsForCodec(Format format, ExportCodec::Codec c) +{ + std::vector f; + Encoder *e = Encoder::CreateFromFormat(format, EncodingParams()); + + if (e) { + f = e->GetSampleFormatsForCodec(c); + delete e; + } + + + return f; +} + } diff --git a/app/codec/exportformat.h b/app/codec/exportformat.h index 36be9db58..acc603c8c 100644 --- a/app/codec/exportformat.h +++ b/app/codec/exportformat.h @@ -33,10 +33,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 +49,7 @@ public: kFormatOgg, kFormatWebM, kFormatSRT, + kFormatMPEG4Audio, kFormatCount }; @@ -59,6 +61,7 @@ public: static QList GetSubtitleCodecs(ExportFormat::Format f); static QStringList GetPixelFormatsForCodec(Format f, ExportCodec::Codec c); + static std::vector GetSampleFormatsForCodec(Format f, ExportCodec::Codec c); }; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 14bcf0e1e..04f889a34 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -455,7 +455,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &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(instance_.avstream()->codecpar->format), diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 01985e0d0..19db89732 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -62,6 +62,38 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const return pix_fmts; } +std::vector FFmpegEncoder::GetSampleFormatsForCodec(ExportCodec::Codec c) const +{ + std::vector 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(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; iaudio_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(input_data), input_sample_count); + result = WriteAudioData(audio->audio_params(), const_cast(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; } @@ -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(audio.channel_layout()), - FFmpegUtils::GetFFmpegSampleFormat(audio.format(), planar), + FFmpegUtils::GetFFmpegSampleFormat(audio.format()), audio.sample_rate(), 0, nullptr); @@ -875,18 +907,24 @@ const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c, AudioParams::Form 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::kFormatUnsigned8: + case AudioParams::kFormatUnsigned8Packed: return avcodec_find_encoder(AV_CODEC_ID_PCM_U8); - case AudioParams::kFormatSigned16: + case AudioParams::kFormatSigned16Packed: return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE); - case AudioParams::kFormatSigned32: + case AudioParams::kFormatSigned32Packed: return avcodec_find_encoder(AV_CODEC_ID_PCM_S32LE); - case AudioParams::kFormatSigned64: + case AudioParams::kFormatSigned64Packed: return avcodec_find_encoder(AV_CODEC_ID_PCM_S64LE); - case AudioParams::kFormatFloat32: + case AudioParams::kFormatFloat32Packed: return avcodec_find_encoder(AV_CODEC_ID_PCM_F32LE); - case AudioParams::kFormatFloat64: + case AudioParams::kFormatFloat64Packed: return avcodec_find_encoder(AV_CODEC_ID_PCM_F64LE); } break; diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 1bbc1ac54..a38955f3f 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -41,13 +41,15 @@ public: virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const override; + virtual std::vector 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,7 +80,7 @@ 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, AudioParams::Format aformat); diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index 6bc8b037f..90c84e620 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -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; diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index 2262dc299..8208e56ad 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -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); }; } diff --git a/app/config/config.cpp b/app/config/config.cpp index f52b0c175..d114b0a10 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -125,7 +125,7 @@ void Config::SetDefaults() 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::kFormatSigned16); + SetEntryInternal(QStringLiteral("AudioRecordingSampleFormat"), NodeValue::kInt, AudioParams::kFormatSigned16Packed); SetEntryInternal(QStringLiteral("AudioRecordingBitRate"), NodeValue::kInt, 320); SetEntryInternal(QStringLiteral("DiskCacheBehind"), NodeValue::kRational, QVariant::fromValue(rational(0))); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index c7a467d5c..8723634e2 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -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,7 +211,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : video_tab_->pixel_format_field()->SetPixelFormat(static_cast(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()->SetSampleFormat(ap.format()); + audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false); audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout()); video_aspect_ratio_ = static_cast(vp.width()) / static_cast(vp.height()); @@ -514,7 +514,7 @@ 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(), audio_tab_->sample_format_combobox()->GetSampleFormat()); diff --git a/app/dialog/export/exportaudiotab.cpp b/app/dialog/export/exportaudiotab.cpp index 70ef8d19a..860052a7c 100644 --- a/app/dialog/export/exportaudiotab.cpp +++ b/app/dialog/export/exportaudiotab.cpp @@ -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(&QComboBox::currentIndexChanged), this, &ExportAudioTab::UpdateSampleFormats); + connect(codec_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &ExportAudioTab::UpdateBitRateEnabled); layout->addWidget(codec_combobox_, row, 1); row++; @@ -70,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(320); + bit_rate_slider_->SetValue(kDefaultBitRate); bit_rate_slider_->SetFormat(tr("%1 kbps")); layout->addWidget(bit_rate_slider_, row, 1); @@ -81,11 +85,36 @@ int ExportAudioTab::SetFormat(ExportFormat::Format format) { QList acodecs = ExportFormat::GetAudioCodecs(format); setEnabled(!acodecs.isEmpty()); + codec_combobox_->blockSignals(true); codec_combobox_->clear(); foreach (ExportCodec::Codec acodec, acodecs) { 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 ); + } +} + } diff --git a/app/dialog/export/exportaudiotab.h b/app/dialog/export/exportaudiotab.h index 241db8e8b..9cd8faac2 100644 --- a/app/dialog/export/exportaudiotab.h +++ b/app/dialog/export/exportaudiotab.h @@ -76,12 +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(); + }; } diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index 8a8b99a83..c8760ae8a 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -103,6 +103,7 @@ PreferencesAudioTab::PreferencesAudioTab() fmt_layout->addWidget(record_format_combo_); record_options_ = new ExportAudioTab(); + record_options_->SetFormat(record_format_combo_->GetFormat()); record_options_->SetCodec(static_cast(OLIVE_CONFIG("AudioRecordingCodec").toInt())); record_options_->sample_rate_combobox()->SetSampleRate(OLIVE_CONFIG("AudioRecordingSampleRate").toInt()); record_options_->channel_layout_combobox()->SetChannelLayout(OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong()); @@ -111,8 +112,6 @@ PreferencesAudioTab::PreferencesAudioTab() recording_layout->addWidget(record_options_); connect(record_format_combo_, &ExportFormatComboBox::FormatChanged, record_options_, &ExportAudioTab::SetFormat); - - record_options_->SetFormat(record_format_combo_->GetFormat()); } QHBoxLayout* refresh_layout = new QHBoxLayout(); diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index f90b70aa9..3b5db0230 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -52,7 +52,7 @@ const QVector 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: @@ -247,24 +253,30 @@ QString AudioParams::ChannelLayoutToString(const uint64_t &layout) QString AudioParams::FormatToString(const Format &f) { switch (f) { - case kFormatUnsigned8: - return QCoreApplication::translate("AudioParams", "Unsigned 8-bit"); - break; - case kFormatSigned16: - return QCoreApplication::translate("AudioParams", "Signed 16-bit"); - break; - case kFormatSigned32: - return QCoreApplication::translate("AudioParams", "Signed 32-bit"); - break; - case kFormatSigned64: - return QCoreApplication::translate("AudioParams", "Signed 64-bit"); - break; - case kFormatFloat32: - return QCoreApplication::translate("AudioParams", "Float 32-bit"); - break; - case kFormatFloat64: - return QCoreApplication::translate("AudioParams", "Float 64-bit"); - break; + 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: @@ -274,4 +286,74 @@ QString AudioParams::FormatToString(const Format &f) 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; +} + } diff --git a/app/render/audioparams.h b/app/render/audioparams.h index cd0fd94b9..9213fd872 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -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; @@ -202,6 +226,9 @@ public: 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() { diff --git a/app/widget/standardcombos/sampleformatcombobox.h b/app/widget/standardcombos/sampleformatcombobox.h index a1e610f7e..d30418f17 100644 --- a/app/widget/standardcombos/sampleformatcombobox.h +++ b/app/widget/standardcombos/sampleformatcombobox.h @@ -32,13 +32,28 @@ class SampleFormatComboBox : public QComboBox Q_OBJECT public: SampleFormatComboBox(QWidget* parent = nullptr) : - QComboBox(parent) + QComboBox(parent), + attempt_to_restore_format_(true) { - // Set up preview formats - for (int i=0;i(i); + } - this->addItem(AudioParams::FormatToString(smp_fmt), smp_fmt); + void SetAttemptToRestoreFormat(bool e) { attempt_to_restore_format_ = e; } + + void SetAvailableFormats(const std::vector &formats) + { + AudioParams::Format tmp; + + if (attempt_to_restore_format_) { + tmp = GetSampleFormat(); + } + + clear(); + foreach (const AudioParams::Format &of, formats) { + AddFormatItem(of); + } + + if (attempt_to_restore_format_) { + SetSampleFormat(tmp); } } @@ -57,6 +72,14 @@ public: } } +private: + void AddFormatItem(AudioParams::Format f) + { + this->addItem(AudioParams::FormatToString(f), f); + } + + bool attempt_to_restore_format_; + }; } From 22e2b33e0660204db150f71808d48d6c7f25c50d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 13:44:00 -0700 Subject: [PATCH 11/19] code: added missing include --- app/codec/exportformat.h | 1 + 1 file changed, 1 insertion(+) diff --git a/app/codec/exportformat.h b/app/codec/exportformat.h index acc603c8c..fcba9666e 100644 --- a/app/codec/exportformat.h +++ b/app/codec/exportformat.h @@ -26,6 +26,7 @@ #include "common/define.h" #include "exportcodec.h" +#include "render/audioparams.h" namespace olive { From 7fbe7118664cbd1799732463d2c122a77a4e9675 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 13:53:53 -0700 Subject: [PATCH 12/19] audio: correctly report packed format to encoder --- app/audio/audiomanager.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index f58449848..878944505 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -73,11 +73,10 @@ int InputCallback(const void *input, void *output, unsigned long frameCount, con { FFmpegEncoder *f = static_cast(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(), reinterpret_cast(&input), frameCount); + f->WriteAudioData(our_params, reinterpret_cast(&input), frameCount); return paContinue; } From 82bd6a103ce06b3ff5a8ab5d9ea2cbc2c1c4e5c7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 14:23:41 -0700 Subject: [PATCH 13/19] timeline: implement reveal in project action --- app/panel/project/project.h | 5 ++ app/panel/timeline/timeline.cpp | 3 +- app/panel/timeline/timeline.h | 2 + .../projectexplorer/projectexplorer.cpp | 53 ++++++++++++++++--- app/widget/projectexplorer/projectexplorer.h | 4 ++ app/widget/timelinewidget/timelinewidget.cpp | 17 ++++++ app/widget/timelinewidget/timelinewidget.h | 4 ++ app/window/mainwindow/mainwindow.cpp | 10 ++++ app/window/mainwindow/mainwindow.h | 2 + 9 files changed, 92 insertions(+), 8 deletions(-) diff --git a/app/panel/project/project.h b/app/panel/project/project.h index 82715aaf4..d2bc30341 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -52,6 +52,11 @@ public: ProjectViewModel* model() const; + bool SelectItem(Node *n) + { + return explorer_->SelectItem(n); + } + virtual void SelectAll() override; virtual void DeselectAll() override; diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index e4fd4e274..0eb45f0a4 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -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() diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 9e40b232d..34b926bfd 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -114,6 +114,8 @@ signals: void RequestCaptureStart(const TimeRange &time, const Track::Reference &track); + void RevealViewerInProject(ViewerOutput *r); + }; } diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 4482ac8d7..5242a2f47 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -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(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(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(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; +} + } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 83010ea94..fa145112b 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -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 */ diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 58629b2c2..eba333b85 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1071,6 +1071,14 @@ void TimelineWidget::ShowContextMenu() menu.addSeparator(); + if (ClipBlock *clip = dynamic_cast(selected.first())) { + if (clip->connected_viewer()) { + QAction *reveal_in_project = menu.addAction(tr("Reveal in Project")); + reveal_in_project->setData(reinterpret_cast(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(sender()); + + ViewerOutput *item_to_reveal = reinterpret_cast(a->data().value()); + + emit RevealViewerInProject(item_to_reveal); +} + void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) { ghost_items_.append(ghost); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index c23e7093f..a0b9afaaa 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -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(); + }; } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 5a5fc6693..6e02ffc07 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -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); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index e027e08e6..364d3f7a7 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -195,6 +195,8 @@ private slots: void ShowWelcomeDialog(); + void RevealViewerInProject(ViewerOutput *r); + }; } From d6670dfe471b4c09d7217936d6f26452195a4d9e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 14:23:53 -0700 Subject: [PATCH 14/19] nodeparamview: correct bool mistake --- app/widget/nodeparamview/nodeparamview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 49f470da8..f812c121e 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -40,7 +40,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : last_scroll_val_(0), focused_node_(nullptr), time_target_(nullptr), - show_all_nodes_(true) + show_all_nodes_(false) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); From 5d06c58a29d54aef60a84dea9e5bfbe082615b58 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 14:25:29 -0700 Subject: [PATCH 15/19] text: update code for new job system --- app/node/generator/text/textv3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index d5e791855..fc3d5e5bf 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -89,7 +89,7 @@ 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); + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } From 4249b82cfc6b7b6a0b27c2c52f49e079c07f0796 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 14:35:43 -0700 Subject: [PATCH 16/19] suppress pointless gcc warning --- app/widget/standardcombos/sampleformatcombobox.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/standardcombos/sampleformatcombobox.h b/app/widget/standardcombos/sampleformatcombobox.h index d30418f17..a48e9016a 100644 --- a/app/widget/standardcombos/sampleformatcombobox.h +++ b/app/widget/standardcombos/sampleformatcombobox.h @@ -41,7 +41,7 @@ public: void SetAvailableFormats(const std::vector &formats) { - AudioParams::Format tmp; + AudioParams::Format tmp = AudioParams::kFormatInvalid; if (attempt_to_restore_format_) { tmp = GetSampleFormat(); From 0e490b94e54da9e24be4c6883479e40a06e760a0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 15:03:14 -0700 Subject: [PATCH 17/19] nodes: implemented recursive jobs --- app/node/generator/shape/shapenode.cpp | 24 ++++++++++++++++++---- app/node/generator/shape/shapenodebase.cpp | 20 +++++++++++++++--- app/node/generator/shape/shapenodebase.h | 10 ++++++--- app/node/generator/text/textv3.cpp | 18 +++++++++++++++- app/node/traverser.cpp | 14 ++++++++----- app/node/traverser.h | 2 +- app/render/job/acceleratedjob.h | 6 ++---- 7 files changed, 73 insertions(+), 21 deletions(-) diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp index 3da5f51a3..a0aeb7b03 100644 --- a/app/node/generator/shape/shapenode.cpp +++ b/app/node/generator/shape/shapenode.cpp @@ -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::kTexture, 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); + } } } diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index 15a262f29..aa0625957 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -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(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(sender()); diff --git a/app/node/generator/shape/shapenodebase.h b/app/node/generator/shape/shapenodebase.h index d7529eff0..4079ad3c3 100644 --- a/app/node/generator/shape/shapenodebase.h +++ b/app/node/generator/shape/shapenodebase.h @@ -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 diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index fc3d5e5bf..af36e74b6 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -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::kTexture, 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]); } } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index a620c3fa8..1d80fca63 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -52,7 +52,7 @@ NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node row.insert(it.key(), value); } - PreProcessRow(node, range, row); + PreProcessRow(range, row); return row; } @@ -353,7 +353,7 @@ QVector2D NodeTraverser::GenerateResolution() const return QVector2D(video_params_.square_pixel_width(), video_params_.height()); } -void NodeTraverser::PreProcessRow(const Node *node, const TimeRange &range, NodeValueRow &row) +void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row) { QByteArray cached_node_hash; @@ -381,11 +381,15 @@ void NodeTraverser::PreProcessRow(const Node *node, const TimeRange &range, Node if (v.canConvert()) { - val.set_data(QVariant::fromValue(ProcessShader(val.source(), range, v.value()))); + ShaderJob job = v.value(); + PreProcessRow(range, job.GetValues()); + val.set_data(QVariant::fromValue(ProcessShader(val.source(), range, job))); } else if (v.canConvert()) { - val.set_data(QVariant::fromValue(ProcessFrameGeneration(val.source(), v.value()))); + GenerateJob job = v.value(); + PreProcessRow(range, job.GetValues()); + val.set_data(QVariant::fromValue(ProcessFrameGeneration(val.source(), job))); } else if (v.canConvert()) { @@ -406,7 +410,7 @@ void NodeTraverser::PreProcessRow(const Node *node, const TimeRange &range, Node } else if (v.canConvert()) { - val.set_data(QVariant::fromValue(ProcessSamples(node, range, v.value()))); + val.set_data(QVariant::fromValue(ProcessSamples(val.source(), range, v.value()))); } diff --git a/app/node/traverser.h b/app/node/traverser.h index 3a15455da..6919f61a9 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -109,7 +109,7 @@ protected: } private: - void PreProcessRow(const Node *node, const TimeRange &range, NodeValueRow &row); + void PreProcessRow(const TimeRange &range, NodeValueRow &row); TexturePtr CreateDummyTexture(const VideoParams &p); diff --git a/app/render/job/acceleratedjob.h b/app/render/job/acceleratedjob.h index a40e5a14a..534f4a528 100644 --- a/app/render/job/acceleratedjob.h +++ b/app/render/job/acceleratedjob.h @@ -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_; From 6512774b860d5150e0553212d7af7159f034cfcc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 17:10:03 -0700 Subject: [PATCH 18/19] code: conform traverser buffer creation Helps ensure parity between traverser and render processor --- app/codec/decoder.cpp | 25 ++++--- app/codec/decoder.h | 13 +--- app/codec/samplebuffer.h | 4 + app/node/hashtraverser.cpp | 30 +++----- app/node/hashtraverser.h | 10 +-- app/node/traverser.cpp | 130 +++++++++++---------------------- app/node/traverser.h | 38 ++++++++-- app/render/renderprocessor.cpp | 128 +++++++++----------------------- app/render/renderprocessor.h | 20 +++-- 9 files changed, 155 insertions(+), 243 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 7d33a0e3f..1145a6967 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -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 ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode) +Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBufferPtr dest, const TimeRange &range, const AudioParams ¶ms, 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 &filenames, const Audi return false; } -SampleBufferPtr Decoder::RetrieveAudioFromConform(const QVector &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) +bool Decoder::RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector &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 &confor input.close(); - return sample_buffer; + return true; } - return nullptr; + return false; } void Decoder::UpdateLastAccessed() diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 0e30ce455..2234753d9 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -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 &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); + bool RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; diff --git a/app/codec/samplebuffer.h b/app/codec/samplebuffer.h index 0f5aa928b..d7a3a49e4 100644 --- a/app/codec/samplebuffer.h +++ b/app/codec/samplebuffer.h @@ -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) { diff --git a/app/node/hashtraverser.cpp b/app/node/hashtraverser.cpp index c4b7fa9a5..78edf681b 100644 --- a/app/node/hashtraverser.cpp +++ b/app/node/hashtraverser.cpp @@ -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,25 +95,19 @@ 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()); } -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) diff --git a/app/node/hashtraverser.h b/app/node/hashtraverser.h index 4eb0eb5cd..707d89b9f 100644 --- a/app/node/hashtraverser.h +++ b/app/node/hashtraverser.h @@ -33,15 +33,15 @@ public: QByteArray GetHash(const Node *node, const Node::ValueHint &hint, const VideoParams ¶ms, 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 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); diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 1d80fca63..9a0fb034d 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -287,67 +287,6 @@ 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); -} - -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()); @@ -357,20 +296,6 @@ void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row) { QByteArray cached_node_hash; - // 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()); - - TexturePtr cached_frame = GetCachedTexture(cached_node_hash); - if (cached_frame) { - output_params.Push(NodeValue::kTexture, QVariant::fromValue(cached_frame), node); - - // No more to do here - got_cached_frame = true; - } - }*/ - // Resolve any jobs for (auto it=row.begin(); it!=row.end(); it++) { // Jobs will almost always be submitted with one of these types @@ -382,45 +307,74 @@ void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row) if (v.canConvert()) { ShaderJob job = v.value(); - PreProcessRow(range, job.GetValues()); - val.set_data(QVariant::fromValue(ProcessShader(val.source(), range, job))); + + VideoParams tex_params = GetCacheVideoParams(); + tex_params.set_channel_count(GetChannelCountFromJob(job)); + + TexturePtr tex = CreateTexture(tex_params); + + ProcessShader(tex, val.source(), range, job); + + val.set_data(QVariant::fromValue(tex)); } else if (v.canConvert()) { GenerateJob job = v.value(); - PreProcessRow(range, job.GetValues()); - val.set_data(QVariant::fromValue(ProcessFrameGeneration(val.source(), job))); + + 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()) { FootageJob job = v.value(); 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 - val.set_data(QVariant::fromValue(CreateDummyTexture(job.video_params()))); + tex = CreateDummyTexture(job.video_params()); } else { - val.set_data(QVariant::fromValue(ProcessVideoFootage(job, footage_time))); + 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) { - val.set_data(QVariant::fromValue(ProcessAudioFootage(job, range))); + + SampleBufferPtr buffer = CreateSampleBuffer(GetCacheAudioParams(), range.length()); + ProcessAudioFootage(buffer, job, range); + val.set_data(QVariant::fromValue(buffer)); + } } else if (v.canConvert()) { - val.set_data(QVariant::fromValue(ProcessSamples(val.source(), range, v.value()))); + SampleJob job = v.value(); + 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)); } } } - - /*if (CanCacheFrames() && node->GetCacheTextures() && !got_cached_frame) { - // Save cached texture - SaveCachedTexture(cached_node_hash, output_params.Get(NodeValue::kTexture).value()); - }*/ } TexturePtr NodeTraverser::CreateDummyTexture(const VideoParams &p) diff --git a/app/node/traverser.h b/app/node/traverser.h index 6919f61a9..21cba7c1d 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -65,6 +65,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: @@ -72,19 +82,31 @@ 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 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 ¶ms, int sample_count) + { + // Return dummy by default + return SampleBuffer::Create(); + } + + SampleBufferPtr CreateSampleBuffer(const AudioParams ¶ms, const rational &length) + { + return CreateSampleBuffer(params, params.time_to_samples(length)); + } virtual bool CanCacheFrames() { @@ -115,6 +137,8 @@ private: VideoParams video_params_; + AudioParams audio_params_; + const QAtomicInt *cancel_; }; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 2c0080889..1d04c6771 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -128,10 +128,12 @@ void RenderProcessor::Run() SetCancelPointer(&ticket_->IsCancelled()); + SetCacheVideoParams(ticket_->property("vparam").value()); + SetCacheAudioParams(ticket_->property("aparam").value()); + switch (type) { case RenderManager::kTypeVideo: { - SetCacheVideoParams(ticket_->property("vparam").value()); rational time = ticket_->property("time").value(); rational frame_length = GetCacheVideoParams().frame_rate_as_time_base(); @@ -256,7 +258,7 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim { if (track->type() == Track::kAudio) { - const AudioParams& audio_params = ticket_->property("aparam").value(); + const AudioParams& audio_params = GetCacheAudioParams(); QVector active_blocks = track->BlocksAtTimeRange(range); @@ -370,11 +372,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::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 @@ -442,12 +444,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()); @@ -464,39 +460,32 @@ TexturePtr RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const render_ctx_->BlitColorManaged(processor, unmanaged_texture, alpha_assoc, - value.get()); - - return value; + 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(); + const AudioParams& audio_params = GetCacheAudioParams(); - Decoder::RetrieveAudioData status = decoder->RetrieveAudio(input_time, audio_params, - stream.cache_path(), - stream.loop_mode(), - static_cast(ticket_->property("mode").toInt())); + Decoder::RetrieveAudioStatus status = decoder->RetrieveAudio(destination, + input_time, audio_params, + stream.cache_path(), + stream.loop_mode(), + static_cast(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) @@ -512,7 +501,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; } } @@ -520,24 +509,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(); + const AudioParams& audio_params = GetCacheAudioParams(); for (int i=0;isample_count();i++) { // Calculate the exact rational time at this sample @@ -554,42 +538,34 @@ SampleBufferPtr RenderProcessor::ProcessSamples(const Node *node, const TimeRang node->ProcessSamples(value_db, job.samples(), - output_buffer, + destination, i); } - - return output_buffer; } -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(ticket_->property("colormanager")); ColorProcessorPtr cp = ColorProcessor::Create(color_manager, job.GetColorspace(), color_manager->GetReferenceColorSpace()); - render_ctx_->BlitColorManaged(cp, texture, Renderer::kAlphaAssociated, dest.get()); - texture = dest; + render_ctx_->BlitColorManaged(cp, mid, Renderer::kAlphaAssociated, destination.get()); } - - return texture; } bool RenderProcessor::CanCacheFrames() @@ -597,42 +573,4 @@ bool RenderProcessor::CanCacheFrames() return ticket_->property("type").value() == 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(); - 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"; - } - }*/ -} - } diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 213b2df16..d1172cf85 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -44,21 +44,27 @@ 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 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 ¶ms, 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); From 329dfb79138126659c9a7f8f72467f2877a64c21 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 3 May 2022 18:43:12 -0700 Subject: [PATCH 19/19] renderer: resolve jobs at the end --- app/node/traverser.cpp | 151 +++++++++++++++++---------------- app/node/traverser.h | 2 + app/render/renderprocessor.cpp | 10 ++- 3 files changed, 88 insertions(+), 75 deletions(-) diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 9a0fb034d..cf04d8e72 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -292,6 +292,83 @@ QVector2D NodeTraverser::GenerateResolution() const return QVector2D(video_params_.square_pixel_width(), video_params_.height()); } +void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) +{ + if (val.type() == NodeValue::kTexture || val.type() == NodeValue::kSamples) { + const QVariant &v = val.data(); + + if (v.canConvert()) { + + ShaderJob job = v.value(); + + VideoParams tex_params = GetCacheVideoParams(); + tex_params.set_channel_count(GetChannelCountFromJob(job)); + + TexturePtr tex = CreateTexture(tex_params); + + ProcessShader(tex, val.source(), range, job); + + val.set_data(QVariant::fromValue(tex)); + + } else if (v.canConvert()) { + + GenerateJob job = v.value(); + + 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()) { + + FootageJob job = v.value(); + + 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 + tex = CreateDummyTexture(job.video_params()); + } else { + 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 job = v.value(); + 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)); + + } + + } +} + void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row) { QByteArray cached_node_hash; @@ -301,79 +378,7 @@ void NodeTraverser::PreProcessRow(const TimeRange &range, NodeValueRow &row) // Jobs will almost always be submitted with one of these types NodeValue &val = it.value(); - if (val.type() == NodeValue::kTexture || val.type() == NodeValue::kSamples) { - const QVariant &v = val.data(); - - if (v.canConvert()) { - - ShaderJob job = v.value(); - - VideoParams tex_params = GetCacheVideoParams(); - tex_params.set_channel_count(GetChannelCountFromJob(job)); - - TexturePtr tex = CreateTexture(tex_params); - - ProcessShader(tex, val.source(), range, job); - - val.set_data(QVariant::fromValue(tex)); - - } else if (v.canConvert()) { - - GenerateJob job = v.value(); - - 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()) { - - FootageJob job = v.value(); - - 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 - tex = CreateDummyTexture(job.video_params()); - } else { - 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 job = v.value(); - 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)); - - } - - } + ResolveJobs(val, range); } } diff --git a/app/node/traverser.h b/app/node/traverser.h index 21cba7c1d..9a48916a7 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -130,6 +130,8 @@ protected: cancel_ = cancel; } + void ResolveJobs(NodeValue &value, const TimeRange &range); + private: void PreProcessRow(const TimeRange &range, NodeValueRow &row); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 1d04c6771..8b7dbb0e7 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -50,12 +50,18 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational { ViewerOutput* viewer = Node::ValueToPtr(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(); + NodeValue tex_val = table.GetWithMeta(NodeValue::kTexture); + + ResolveJobs(tex_val, range); + + return tex_val.data().value(); } FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time)