implemented core audio recording support

This commit is contained in:
itsmattkc
2022-05-02 14:00:03 -07:00
parent a34fb2145d
commit 9101f5dc49
33 changed files with 610 additions and 111 deletions
+77 -8
View File
@@ -26,6 +26,7 @@
#include <QApplication>
#include "audio/packedprocessor.h"
#include "config/config.h"
namespace olive {
@@ -68,6 +69,19 @@ int OutputCallback(const void *input, void *output, unsigned long frameCount, co
return paContinue;
}
int InputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
{
FFmpegEncoder *f = static_cast<FFmpegEncoder*>(userData);
SampleBufferPtr s = SampleBuffer::Create();
s->set_sample_count(frameCount);
s->set_audio_params(f->params().audio_params());
f->WriteAudioData(f->params().audio_params(), false, reinterpret_cast<const uint8_t**>(&input), frameCount);
return paContinue;
}
void AudioManager::PushToOutput(const AudioParams &params, const QByteArray &samples)
{
if (output_device_ == paNoDevice) {
@@ -79,13 +93,7 @@ void AudioManager::PushToOutput(const AudioParams &params, const QByteArray &sam
CloseOutputStream();
PaStreamParameters p;
p.channelCount = output_params_.channel_count();
p.device = output_device_;
p.hostApiSpecificStreamInfo = nullptr;
p.sampleFormat = GetPortAudioSampleFormat(output_params_.format());
p.suggestedLatency = Pa_GetDeviceInfo(output_device_)->defaultLowOutputLatency;
PaStreamParameters p = GetPortAudioParams(params, output_device_);
Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
@@ -176,6 +184,52 @@ void AudioManager::HardReset()
Pa_Initialize();
}
bool AudioManager::StartRecording(const QString &filename, const AudioParams &params)
{
if (input_device_ == paNoDevice) {
return false;
}
EncodingParams encode_param;
encode_param.EnableAudio(params, ExportCodec::kCodecMP3);
encode_param.SetFilename(filename);
input_encoder_ = new FFmpegEncoder(encode_param);
if (!input_encoder_->Open()) {
qCritical() << "Failed to open encoder for recording";
return false;
}
PaStreamParameters p = GetPortAudioParams(params, input_device_);
if (Pa_OpenStream(&input_stream_, &p, nullptr, params.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) {
if (Pa_StartStream(input_stream_) == paNoError) {
return true;
}
}
StopRecording();
return false;
}
void AudioManager::StopRecording()
{
if (input_stream_) {
if (Pa_IsStreamActive(input_stream_)) {
Pa_StopStream(input_stream_);
}
Pa_CloseStream(input_stream_);
input_stream_ = nullptr;
}
if (input_encoder_) {
input_encoder_->Close();
delete input_encoder_;
input_encoder_ = nullptr;
}
}
PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
{
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
@@ -199,8 +253,23 @@ PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_de
return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice();
}
PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams &params, PaDeviceIndex device)
{
PaStreamParameters p;
p.channelCount = params.channel_count();
p.device = device;
p.hostApiSpecificStreamInfo = nullptr;
p.sampleFormat = GetPortAudioSampleFormat(params.format());
p.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency;
return p;
}
AudioManager::AudioManager() :
output_stream_(nullptr)
output_stream_(nullptr),
input_stream_(nullptr),
input_encoder_(nullptr)
{
#ifdef PA_HAS_JACK
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
+9
View File
@@ -28,6 +28,7 @@
#include "audiovisualwaveform.h"
#include "common/define.h"
#include "codec/ffmpeg/ffmpegencoder.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
#include "render/previewaudiodevice.h"
@@ -73,9 +74,15 @@ public:
void HardReset();
bool StartRecording(const QString &filename, const AudioParams &params);
void StopRecording();
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device);
static PaStreamParameters GetPortAudioParams(const AudioParams &p, PaDeviceIndex device);
signals:
void OutputNotify();
@@ -96,6 +103,8 @@ private:
PreviewAudioDevice *output_buffer_;
PaDeviceIndex input_device_;
PaStream *input_stream_;
FFmpegEncoder *input_encoder_;
};
+23 -14
View File
@@ -236,11 +236,6 @@ fail:
bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
{
if (!InitializeResampleContext(audio)) {
qCritical() << "Failed to initialize resample context";
return false;
}
bool result = true;
// Create input buffer
@@ -258,6 +253,25 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
}
}
result = WriteAudioData(audio->audio_params(), true, const_cast<const uint8_t**>(input_data), input_sample_count);
if (input_data) {
av_freep(&input_data[0]);
av_freep(&input_data);
}
return result;
}
bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **input_data, int input_sample_count)
{
if (!InitializeResampleContext(audio_params, planar)) {
qCritical() << "Failed to initialize resample context";
return false;
}
bool result = true;
// Create output buffer
int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400;
uint8_t** output_data = nullptr;
@@ -308,11 +322,6 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
av_freep(&output_data);
}
if (input_data) {
av_freep(&input_data[0]);
av_freep(&input_data);
}
return result;
}
@@ -774,7 +783,7 @@ void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream)
av_packet_free(&pkt);
}
bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio, bool planar)
{
if (audio_resample_ctx_) {
return true;
@@ -785,9 +794,9 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
static_cast<int64_t>(audio_codec_ctx_->channel_layout),
audio_codec_ctx_->sample_fmt,
audio_codec_ctx_->sample_rate,
static_cast<int64_t>(audio->audio_params().channel_layout()),
FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true),
audio->audio_params().sample_rate(),
static_cast<int64_t>(audio.channel_layout()),
FFmpegUtils::GetFFmpegSampleFormat(audio.format(), planar),
audio.sample_rate(),
0,
nullptr);
if (!audio_resample_ctx_) {
+3 -1
View File
@@ -47,6 +47,8 @@ public:
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
bool WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **data, int input_sample_count);
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
virtual void Close() override;
@@ -76,7 +78,7 @@ private:
void FlushEncoders();
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
bool InitializeResampleContext(SampleBufferPtr audio);
bool InitializeResampleContext(const AudioParams &audio, bool planar);
static const AVCodec *GetEncoder(ExportCodec::Codec c);
+3 -3
View File
@@ -245,14 +245,14 @@ UndoStack *Core::undo_stack()
return &undo_stack_;
}
void Core::ImportFiles(const QStringList &urls, ProjectViewModel* model, Folder* parent)
void Core::ImportFiles(const QStringList &urls, Folder* parent)
{
if (urls.isEmpty()) {
QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import"));
return;
}
ProjectImportTask* pim = new ProjectImportTask(model, parent, urls);
ProjectImportTask* pim = new ProjectImportTask(parent, urls);
if (!pim->GetFileCount()) {
// No files to import
@@ -364,7 +364,7 @@ void Core::DialogImportShow()
// Get the selected folder in this panel
Folder* folder = active_project_panel->GetSelectedFolder();
ImportFiles(files, active_project_panel->model(), folder);
ImportFiles(files, folder);
}
}
+1 -1
View File
@@ -182,7 +182,7 @@ public:
*
* @param urls
*/
void ImportFiles(const QStringList& urls, ProjectViewModel *model, Folder *parent);
void ImportFiles(const QStringList& urls, Folder *parent);
/**
* @brief Get the currently active tool
+2
View File
@@ -24,6 +24,8 @@ set(OLIVE_SOURCES
dialog/export/exportadvancedvideodialog.h
dialog/export/exportaudiotab.cpp
dialog/export/exportaudiotab.h
dialog/export/exportformatcombobox.cpp
dialog/export/exportformatcombobox.h
dialog/export/exportsubtitlestab.cpp
dialog/export/exportsubtitlestab.h
dialog/export/exportvideotab.cpp
+8 -46
View File
@@ -118,7 +118,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
row++;
preferences_layout->addWidget(new QLabel(tr("Format:")), row, 0);
format_combobox_ = new QComboBox();
format_combobox_ = new ExportFormatComboBox();
preferences_layout->addWidget(format_combobox_, row, 1, 1, 3);
row++;
@@ -193,33 +193,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
// Set default filename
SetDefaultFilename();
// Populate combobox formats
for (int i=0; i<ExportFormat::kFormatCount; i++) {
QString format_name = ExportFormat::GetName(static_cast<ExportFormat::Format>(i));
bool inserted = false;
for (int j=0; j<format_combobox_->count(); j++) {
if (format_combobox_->itemText(j) > format_name) {
format_combobox_->insertItem(j, format_name, i);
inserted = true;
break;
}
}
if (!inserted) {
format_combobox_->addItem(format_name, i);
}
}
// Set defaults
previously_selected_format_ = ExportFormat::kFormatMPEG4;
SetCurrentFormat(ExportFormat::kFormatMPEG4);
connect(format_combobox_,
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&ExportDialog::FormatChanged);
FormatChanged(format_combobox_->currentIndex());
format_combobox_->SetFormat(ExportFormat::kFormatMPEG4);
connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged);
FormatChanged(format_combobox_->GetFormat());
VideoParams vp = viewer_node_->GetVideoParams();
AudioParams ap = viewer_node_->GetAudioParams();
@@ -273,11 +251,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace());
}
ExportFormat::Format ExportDialog::GetSelectedFormat() const
{
return static_cast<ExportFormat::Format>(format_combobox_->currentData().toInt());
}
rational ExportDialog::GetSelectedTimebase() const
{
return video_tab_->GetSelectedFrameRate().flipped();
@@ -293,7 +266,7 @@ void ExportDialog::StartExport()
// Validate if the entered filename contains the correct extension (the extension is necessary
// for both FFmpeg and OIIO to determine the output format)
QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(GetSelectedFormat()));
QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(format_combobox_->GetFormat()));
QString proposed_filename = filename_edit_->text().trimmed();
// If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export.
@@ -428,7 +401,7 @@ void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title
void ExportDialog::BrowseFilename()
{
ExportFormat::Format f = GetSelectedFormat();
ExportFormat::Format f = format_combobox_->GetFormat();
QString browsed_fn = QFileDialog::getSaveFileName(this,
"",
@@ -444,11 +417,10 @@ void ExportDialog::BrowseFilename()
}
}
void ExportDialog::FormatChanged(int index)
void ExportDialog::FormatChanged(ExportFormat::Format current_format)
{
QString current_filename = filename_edit_->text().trimmed();
QString previously_selected_ext = ExportFormat::GetExtension(previously_selected_format_);
ExportFormat::Format current_format = static_cast<ExportFormat::Format>(format_combobox_->itemData(index).toInt());
QString currently_selected_ext = ExportFormat::GetExtension(current_format);
// If the previous extension was added, remove it
@@ -546,7 +518,7 @@ ExportParams ExportDialog::GenerateParams() const
AudioParams::kInternalFormat);
ExportParams params;
params.set_encoder(Encoder::GetTypeFromFormat(GetSelectedFormat()));
params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat()));
params.SetFilename(filename_edit_->text().trimmed());
params.SetExportLength(viewer_node_->GetLength());
@@ -594,16 +566,6 @@ ExportParams ExportDialog::GenerateParams() const
return params;
}
void ExportDialog::SetCurrentFormat(ExportFormat::Format format)
{
for (int i=0; i<format_combobox_->count(); i++) {
if (format_combobox_->itemData(i).toInt() == format) {
format_combobox_->setCurrentIndex(i);
break;
}
}
}
rational ExportDialog::GetExportLength() const
{
if (range_combobox_->currentIndex() == kRangeInToOut) {
+3 -6
View File
@@ -29,6 +29,7 @@
#include "codec/exportcodec.h"
#include "codec/exportformat.h"
#include "dialog/export/exportformatcombobox.h"
#include "exportaudiotab.h"
#include "exportsubtitlestab.h"
#include "exportvideotab.h"
@@ -43,8 +44,6 @@ class ExportDialog : public QDialog
public:
ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr);
ExportFormat::Format GetSelectedFormat() const;
rational GetSelectedTimebase() const;
protected:
@@ -58,8 +57,6 @@ private:
ExportParams GenerateParams() const;
void SetCurrentFormat(ExportFormat::Format format);
ViewerOutput* viewer_node_;
ExportFormat::Format previously_selected_format_;
@@ -82,7 +79,7 @@ private:
ViewerWidget* preview_viewer_;
QLineEdit* filename_edit_;
QComboBox* format_combobox_;
ExportFormatComboBox* format_combobox_;
ExportVideoTab* video_tab_;
ExportAudioTab* audio_tab_;
@@ -98,7 +95,7 @@ private:
private slots:
void BrowseFilename();
void FormatChanged(int index);
void FormatChanged(ExportFormat::Format current_format);
void ResolutionChanged();
+3 -2
View File
@@ -37,8 +37,6 @@ class ExportAudioTab : public QWidget
public:
ExportAudioTab(QWidget* parent = nullptr);
int SetFormat(ExportFormat::Format format);
QComboBox* codec_combobox() const
{
return codec_combobox_;
@@ -59,6 +57,9 @@ public:
return bit_rate_slider_;
}
public slots:
int SetFormat(ExportFormat::Format format);
private:
QComboBox* codec_combobox_;
SampleRateComboBox* sample_rate_combobox_;
@@ -0,0 +1,83 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "exportformatcombobox.h"
namespace olive {
ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) :
QComboBox(parent)
{
// Populate combobox formats
for (int i=0; i<ExportFormat::kFormatCount; i++) {
ExportFormat::Format f = static_cast<ExportFormat::Format>(i);
switch (mode) {
case kShowAllFormats:
break;
case kShowAudioOnly:
if (!ExportFormat::GetVideoCodecs(f).isEmpty()) {
continue;
}
break;
case kShowVideoOnly:
if (!ExportFormat::GetAudioCodecs(f).isEmpty()) {
continue;
}
break;
}
QString format_name = ExportFormat::GetName(f);
bool inserted = false;
// Sort formats alphabetically
for (int j=0; j<count(); j++) {
if (itemText(j) > format_name) {
insertItem(j, format_name, i);
inserted = true;
break;
}
}
if (!inserted) {
addItem(format_name, i);
}
}
connect(this, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ExportFormatComboBox::HandleIndexChange);
}
void ExportFormatComboBox::SetFormat(ExportFormat::Format fmt)
{
for (int i=0; i<count(); i++) {
if (itemData(i).toInt() == fmt) {
setCurrentIndex(i);
break;
}
}
}
void ExportFormatComboBox::HandleIndexChange(int index)
{
emit FormatChanged(static_cast<ExportFormat::Format>(itemData(index).toInt()));
}
}
+63
View File
@@ -0,0 +1,63 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef EXPORTFORMATCOMBOBOX_H
#define EXPORTFORMATCOMBOBOX_H
#include <QComboBox>
#include "codec/exportformat.h"
namespace olive {
class ExportFormatComboBox : public QComboBox
{
Q_OBJECT
public:
enum Mode {
kShowAllFormats,
kShowAudioOnly,
kShowVideoOnly
};
ExportFormatComboBox(Mode mode, QWidget *parent = nullptr);
ExportFormatComboBox(QWidget *parent = nullptr) :
ExportFormatComboBox(kShowAllFormats, parent)
{}
ExportFormat::Format GetFormat() const
{
return static_cast<ExportFormat::Format>(currentData().toInt());
}
signals:
void FormatChanged(ExportFormat::Format fmt);
public slots:
void SetFormat(ExportFormat::Format fmt);
private slots:
void HandleIndexChange(int index);
};
}
#endif // EXPORTFORMATCOMBOBOX_H
@@ -26,6 +26,8 @@
#include "audio/audiomanager.h"
#include "config/config.h"
#include "dialog/export/exportaudiotab.h"
#include "dialog/export/exportformatcombobox.h"
namespace olive {
@@ -87,12 +89,24 @@ PreferencesAudioTab::PreferencesAudioTab()
row++;
input_layout->addWidget(new QLabel(tr("Recording Mode:"), this), row, 0);
QGroupBox *recording_group = new QGroupBox(tr("Recording"));
input_layout->addWidget(recording_group, row, 0, 1, 2);
recording_combobox_ = new QComboBox();
recording_combobox_->addItem(tr("Mono"));
recording_combobox_->addItem(tr("Stereo"));
input_layout->addWidget(recording_combobox_, row, 1);
QVBoxLayout *recording_layout = new QVBoxLayout(recording_group);
QHBoxLayout *fmt_layout = new QHBoxLayout();
recording_layout->addLayout(fmt_layout);
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);
ExportAudioTab *audio_recording_options = new ExportAudioTab();
recording_layout->addWidget(audio_recording_options);
connect(fmt_combo, &ExportFormatComboBox::FormatChanged, audio_recording_options, &ExportAudioTab::SetFormat);
}
QHBoxLayout* refresh_layout = new QHBoxLayout();
+1 -1
View File
@@ -405,7 +405,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
}
// Trigger an import
Core::instance()->ImportFiles(urls, this, static_cast<Folder*>(drop_item));
Core::instance()->ImportFiles(urls, static_cast<Folder*>(drop_item));
return true;
}
@@ -19,6 +19,7 @@
***/
#include "sequenceviewer.h"
#include "panel/timeline/timeline.h"
namespace olive {
@@ -29,6 +30,12 @@ SequenceViewerPanel::SequenceViewerPanel(QWidget *parent) :
Retranslate();
}
void SequenceViewerPanel::StartCapture(const TimeRange &time, const Track::Reference &track)
{
TimelinePanel *tp = static_cast<TimelinePanel *>(sender());
static_cast<ViewerWidget*>(GetTimeBasedWidget())->StartCapture(tp->timeline_widget(), time, track);
}
void SequenceViewerPanel::Retranslate()
{
ViewerPanel::Retranslate();
@@ -31,6 +31,9 @@ class SequenceViewerPanel : public ViewerPanel
public:
SequenceViewerPanel(QWidget* parent);
public slots:
void StartCapture(const TimeRange &time, const Track::Reference &track);
protected:
virtual void Retranslate() override;
+1
View File
@@ -34,6 +34,7 @@ TimelinePanel::TimelinePanel(QWidget *parent) :
Retranslate();
connect(tw, &TimelineWidget::BlockSelectionChanged, this, &TimelinePanel::BlockSelectionChanged);
connect(tw, &TimelineWidget::RequestCaptureStart, this, &TimelinePanel::RequestCaptureStart );
}
void TimelinePanel::SplitAtPlayhead()
+2
View File
@@ -112,6 +112,8 @@ protected:
signals:
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
};
}
+5 -3
View File
@@ -30,9 +30,8 @@
namespace olive {
ProjectImportTask::ProjectImportTask(ProjectViewModel *model, Folder *folder, const QStringList &filenames) :
ProjectImportTask::ProjectImportTask(Folder *folder, const QStringList &filenames) :
command_(nullptr),
model_(model),
folder_(folder)
{
foreach (const QString& f, filenames) {
@@ -117,6 +116,9 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
// Create undoable command that adds the items to the model
AddItemToFolder(folder, footage, parent_command);
// Add to vector
imported_footage_.push_back(footage);
} else {
// Add to list so we can tell the user about it later
invalid_files_.append(file_info.absoluteFilePath());
@@ -220,7 +222,7 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i
void ProjectImportTask::AddItemToFolder(Folder *folder, Node *item, MultiUndoCommand *command)
{
// Create undoable command that adds the items to the model
Project* project = model_->project();
Project* project = folder->project();
NodeAddCommand* nac = new NodeAddCommand(project, item);
nac->PushToThread(project->thread());
+5 -3
View File
@@ -34,7 +34,7 @@ class ProjectImportTask : public Task
{
Q_OBJECT
public:
ProjectImportTask(ProjectViewModel* model, Folder* folder, const QStringList& filenames);
ProjectImportTask(Folder* folder, const QStringList& filenames);
const int& GetFileCount() const;
@@ -53,6 +53,8 @@ public:
return !invalid_files_.isEmpty();
}
const QVector<Footage*> &GetImportedFootage() const { return imported_footage_; }
protected:
virtual bool Run() override;
@@ -71,8 +73,6 @@ private:
MultiUndoCommand* command_;
ProjectViewModel* model_;
Folder* folder_;
QFileInfoList filenames_;
@@ -83,6 +83,8 @@ private:
QList<QString> image_sequence_ignore_files_;
QVector<Footage*> imported_footage_;
};
}
@@ -156,6 +156,10 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
SetAudioVideoDragButtonsVisible(false);
connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &PlaybackControls::TimecodeChanged);
play_blink_timer_ = new QTimer(this);
play_blink_timer_->setInterval(500);
connect(play_blink_timer_, &QTimer::timeout, this, &PlaybackControls::PlayBlink);
}
void PlaybackControls::SetTimecodeEnabled(bool enabled)
@@ -231,10 +235,20 @@ void PlaybackControls::UpdateIcons()
audio_drag_btn_->setIcon(icon::Audio);
}
void PlaybackControls::SetButtonRecordingState(QPushButton *btn, bool on)
{
btn->setStyleSheet(on ? QStringLiteral("background: red;") : QString());
}
void PlaybackControls::TimecodeChanged()
{
// Update end time
SetEndTime(end_time_);
}
void PlaybackControls::PlayBlink()
{
SetButtonRecordingState(play_btn_, play_btn_->styleSheet().isEmpty());
}
}
@@ -61,6 +61,23 @@ public slots:
void ShowPlayButton();
void StartPlayBlink()
{
play_blink_timer_->start();
SetButtonRecordingState(play_btn_, true);
}
void StopPlayBlink()
{
play_blink_timer_->stop();
SetButtonRecordingState(play_btn_, false);
}
void SetPauseButtonRecordingState(bool on)
{
SetButtonRecordingState(pause_btn_, on);
}
signals:
/**
* @brief Signal emitted when "Go to Start" is clicked
@@ -108,6 +125,8 @@ protected:
private:
void UpdateIcons();
static void SetButtonRecordingState(QPushButton *btn, bool on);
QWidget* lower_left_container_;
QWidget* lower_right_container_;
@@ -129,9 +148,13 @@ private:
QStackedWidget* playpause_stack_;
QTimer *play_blink_timer_;
private slots:
void TimecodeChanged();
void PlayBlink();
};
}
@@ -32,6 +32,7 @@
#include "dialog/speedduration/speeddurationdialog.h"
#include "node/block/transition/transition.h"
#include "node/project/serializer/serializer.h"
#include "task/project/import/import.h"
#include "tool/add.h"
#include "tool/beam.h"
#include "tool/edit.h"
@@ -788,6 +789,35 @@ void TimelineWidget::ShowSpeedDurationDialogForSelectedClips()
}
}
void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track)
{
ProjectImportTask task(GetConnectedNode()->project()->root(), {filename});
task.Start();
MultiUndoCommand *import_command = task.GetCommand();
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
if (task.GetImportedFootage().empty()) {
qCritical() << "Failed to import recorded audio file" << filename;
} else {
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index());
}
}
void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord)
{
foreach (TimelineAndTrackView* tview, views_) {
tview->view()->EnableRecordingOverlay(coord);
}
}
void TimelineWidget::DisableRecordingOverlay()
{
foreach (TimelineAndTrackView* tview, views_) {
tview->view()->DisableRecordingOverlay();
}
}
void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command)
{
for (int i=0;i<Track::kCount;i++) {
@@ -99,6 +99,12 @@ public:
void ShowSpeedDurationDialogForSelectedClips();
void RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track);
void EnableRecordingOverlay(const TimelineCoordinate &coord);
void DisableRecordingOverlay();
/**
* @brief Timelines should always be connected to sequences
*/
@@ -261,6 +267,8 @@ public:
signals:
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
+4 -4
View File
@@ -173,7 +173,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event)
}
}
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert)
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, int track_offset)
{
DraggedFootageData refs;
@@ -181,10 +181,10 @@ void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational
refs.append({f, f->GetEnabledStreamsAsReferences()});
}
PlaceAt(refs, start, insert);
PlaceAt(refs, start, insert, track_offset);
}
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert)
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset)
{
dragged_footage_ = footage;
@@ -192,7 +192,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star
return;
}
PrepGhosts(start, 0);
PrepGhosts(start, track_offset);
DropGhosts(insert);
}
+2 -2
View File
@@ -37,8 +37,8 @@ public:
using DraggedFootageData = QVector<QPair<ViewerOutput*, QVector<Track::Reference> > >;
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert);
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert);
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, int track_offset = 0);
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0);
enum DropWithoutSequenceBehavior {
kDWSAsk,
+78 -1
View File
@@ -1,11 +1,88 @@
#include "record.h"
#include "widget/timelinewidget/timelinewidget.h"
namespace olive {
RecordTool::RecordTool(TimelineWidget *parent) :
BeamTool(parent)
BeamTool(parent),
ghost_(nullptr)
{
}
void RecordTool::MousePress(TimelineViewMouseEvent *event)
{
const Track::Reference& track = event->GetTrack();
// Check if track is locked
Track* t = parent()->GetTrackFromReference(track);
if (t && t->IsLocked()) {
return;
}
if (t->type() != Track::kAudio) {
// We only support audio tracks here
return;
}
drag_start_point_ = ValidatedCoordinate(event->GetCoordinates(true)).GetFrame();
ghost_ = new TimelineViewGhostItem();
ghost_->SetIn(drag_start_point_);
ghost_->SetOut(drag_start_point_);
ghost_->SetTrack(track);
parent()->AddGhost(ghost_);
snap_points_.push_back(drag_start_point_);
}
void RecordTool::MouseMove(TimelineViewMouseEvent *event)
{
if (!ghost_) {
return;
}
// Calculate movement
rational movement = event->GetFrame() - drag_start_point_;
// Validation: Ensure in point never goes below 0
if (movement < -ghost_->GetIn()) {
movement = -ghost_->GetIn();
}
// Snap movement
bool snapped;
if (Core::instance()->snapping()) {
snapped = parent()->SnapPoint(snap_points_, &movement);
} else {
snapped = false;
}
// Make adjustment
if (!movement) {
ghost_->SetInAdjustment(0);
ghost_->SetOutAdjustment(0);
} else if (movement > 0) {
ghost_->SetInAdjustment(0);
ghost_->SetOutAdjustment(movement);
} else if (movement < 0) {
ghost_->SetInAdjustment(movement);
ghost_->SetOutAdjustment(0);
}
Q_UNUSED(snapped)
}
void RecordTool::MouseRelease(TimelineViewMouseEvent *event)
{
if (ghost_) {
emit parent()->RequestCaptureStart(TimeRange(ghost_->GetAdjustedIn(), ghost_->GetAdjustedOut()), ghost_->GetTrack());
parent()->ClearGhosts();
snap_points_.clear();
ghost_ = nullptr;
}
}
}
+12
View File
@@ -29,6 +29,18 @@ class RecordTool : public BeamTool
{
public:
RecordTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
protected:
void MouseMoveInternal(const rational& cursor_frame, bool outwards);
TimelineViewGhostItem* ghost_;
rational drag_start_point_;
};
}
@@ -348,6 +348,16 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
track_y + GetTrackHeight(track_index));
}
// Draw recording overlay
if (recording_overlay_ && recording_coord_.GetTrack().type() == connected_track_list_->type()) {
painter->setPen(QPen(Qt::red, 2));
painter->setBrush(QColor(255, 128, 128));
int x = TimeToScene(recording_coord_.GetFrame());
painter->drawRect(x, GetTrackY(recording_coord_.GetTrack().index()),
TimeToScene(GetTime()) - x, GetTrackHeight(recording_coord_.GetTrack().index()));
}
// Draw standard TimelineViewBase things (such as playhead)
super::drawForeground(painter, rect);
}
@@ -364,6 +374,7 @@ void TimelineView::ToolChangedEvent(Tool::Item tool)
case Tool::kAdd:
case Tool::kTransition:
case Tool::kZoom:
case Tool::kRecord :
setCursor(Qt::CrossCursor);
break;
case Tool::kTrackSelect:
@@ -745,6 +756,19 @@ void TimelineView::SetTransitionOverlay(ClipBlock *out, ClipBlock *in)
}
}
void TimelineView::EnableRecordingOverlay(const TimelineCoordinate &coord)
{
recording_overlay_ = true;
recording_coord_ = coord;
viewport()->update();
}
void TimelineView::DisableRecordingOverlay()
{
recording_overlay_ = false;
viewport()->update();
}
int TimelineView::SceneToTrack(double y)
{
int track = -1;
@@ -56,6 +56,8 @@ public:
void SetBeamCursor(const TimelineCoordinate& coord);
void SetTransitionOverlay(ClipBlock *out, ClipBlock *in);
void EnableRecordingOverlay(const TimelineCoordinate &coord);
void DisableRecordingOverlay();
void SetSelectionList(QHash<Track::Reference, TimeRangeList>* s)
{
@@ -157,6 +159,9 @@ private:
QMap<TimelineMarker*, QRectF> clip_marker_rects_;
bool recording_overlay_;
TimelineCoordinate recording_coord_;
private slots:
void TrackListChanged();
+74 -11
View File
@@ -65,7 +65,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
color_menu_enabled_(true),
time_changed_from_timer_(false),
prequeuing_video_(false),
prequeuing_audio_(0)
prequeuing_audio_(0),
record_armed_(false),
recording_(false)
{
// Set up main layout
QVBoxLayout* layout = new QVBoxLayout(this);
@@ -158,6 +160,10 @@ void ViewerWidget::TimeChangedEvent(const rational &time)
PauseInternal();
}
if (record_armed_) {
DisarmRecording();
}
controls_->SetTime(time);
waveform_view_->SetTime(time);
@@ -377,6 +383,17 @@ void ViewerWidget::SetGizmos(Node *node)
display_widget_->SetGizmos(node);
}
void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track)
{
SetTimeAndSignal(time.in());
ArmForRecording();
recording_filename_ = QStringLiteral("/home/matt/Desktop/ass.mp3");
recording_callback_ = source;
recording_range_ = time;
recording_track_ = track;
}
FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QByteArray& hash, const rational& time)
{
FramePtr frame = FrameHashCache::LoadCacheFrame(cache_path, hash);
@@ -428,6 +445,18 @@ void ViewerWidget::DecrementPrequeuedAudio()
}
}
void ViewerWidget::ArmForRecording()
{
controls_->StartPlayBlink();
record_armed_ = true;
}
void ViewerWidget::DisarmRecording()
{
controls_->StopPlayBlink();
record_armed_ = false;
}
void ViewerWidget::QueueNextAudioBuffer()
{
rational queue_end = audio_playback_queue_time_ + (kAudioPlaybackInterval * playback_speed_);
@@ -595,13 +624,20 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
viewer->auto_cacher_.SetAudioPaused(true);
}
// Disarm recording if armed
if (record_armed_) {
DisarmRecording();
}
// If the playhead is beyond the end, restart at 0
rational last_frame = GetConnectedNode()->GetLength() - timebase();
if (!in_to_out_only && GetTime() >= last_frame) {
if (speed > 0) {
SetTimeAndSignal(0);
} else {
SetTimeAndSignal(last_frame);
if (!recording_) {
rational last_frame = GetConnectedNode()->GetLength() - timebase();
if (!in_to_out_only && GetTime() >= last_frame) {
if (speed > 0) {
SetTimeAndSignal(0);
} else {
SetTimeAndSignal(last_frame);
}
}
}
@@ -657,6 +693,15 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
void ViewerWidget::PauseInternal()
{
if (recording_) {
AudioManager::instance()->StopRecording();
recording_ = false;
controls_->SetPauseButtonRecordingState(false);
recording_callback_->DisableRecordingOverlay();
recording_callback_->RecordingCallback(recording_filename_, recording_range_, recording_track_);
}
if (IsPlaying()) {
playback_speed_ = 0;
controls_->ShowPlayButton();
@@ -1121,6 +1166,17 @@ void ViewerWidget::Play(bool in_to_out_only)
} else {
in_to_out_only = false;
}
} else if (record_armed_) {
if (AudioManager::instance()->StartRecording(recording_filename_, GetConnectedNode()->GetAudioParams())) {
recording_ = true;
controls_->SetPauseButtonRecordingState(true);
recording_callback_->EnableRecordingOverlay(TimelineCoordinate(recording_range_.in(), recording_track_));
} else {
QMessageBox::critical(this, tr("Audio Recording"), tr("Failed to start audio recording"));
return;
}
DisarmRecording();
}
PlayInternal(1, in_to_out_only);
@@ -1205,7 +1261,13 @@ void ViewerWidget::PlaybackTimerUpdate()
rational min_time, max_time;
if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) {
if (recording_ && recording_range_.out() != recording_range_.in()) {
// Limit recording range if applicable
min_time = recording_range_.in();
max_time = recording_range_.out();
} else if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) {
// If "play in to out" is enabled or we're looping AND we have a workarea, only play the workarea
min_time = GetConnectedNode()->GetTimelinePoints()->workarea()->in();
@@ -1229,8 +1291,9 @@ void ViewerWidget::PlaybackTimerUpdate()
bool end_of_line = false;
bool play_after_pause = false;
if ((playback_speed_ < 0 && current_time <= min_time)
|| (playback_speed_ > 0 && current_time >= max_time)) {
if ((!recording_ || recording_range_.out() != recording_range_.in())
&& ((playback_speed_ < 0 && current_time <= min_time)
|| (playback_speed_ > 0 && current_time >= max_time))) {
// Determine which timestamp we tripped
rational tripped_time;
@@ -1245,7 +1308,7 @@ void ViewerWidget::PlaybackTimerUpdate()
// or restart playback
end_of_line = true;
if (Config::Current()[QStringLiteral("Loop")].toBool()) {
if (Config::Current()[QStringLiteral("Loop")].toBool() && !recording_) {
// If we're looping, jump to the other side of the workarea and continue
time_to_set = (tripped_time == min_time) ? max_time : min_time;
+14
View File
@@ -41,6 +41,7 @@
#include "viewerwindow.h"
#include "widget/playbackcontrols/playbackcontrols.h"
#include "widget/timebased/timebasedwidget.h"
#include "widget/timelinewidget/timelinewidget.h"
namespace olive {
@@ -87,6 +88,8 @@ public:
void SetGizmos(Node* node);
void StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track);
public slots:
void Play(bool in_to_out_only);
@@ -205,6 +208,10 @@ private:
void DecrementPrequeuedAudio();
void ArmForRecording();
void DisarmRecording();
QStackedWidget* stack_;
ViewerSizer* sizer_;
@@ -255,6 +262,13 @@ private:
static QVector<ViewerWidget*> instances_;
bool record_armed_;
bool recording_;
TimelineWidget *recording_callback_;
TimeRange recording_range_;
Track::Reference recording_track_;
QString recording_filename_;
private slots:
void PlaybackTimerUpdate();
+1
View File
@@ -555,6 +555,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel()
connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime);
connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime);
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(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);