diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 0304e75db..0fd2cc10a 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -3,8 +3,8 @@ #include "ffmpeg/ffmpegencoder.h" Encoder::Encoder(const EncodingParams ¶ms) : - open_(false), - params_(params) + params_(params), + open_(false) { } @@ -73,7 +73,43 @@ const AudioRenderingParams &EncodingParams::audio_params() const return audio_params_; } -EncoderPtr Encoder::CreateFromID(const QString &id, const EncodingParams& params) +Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params) { - return std::make_shared(params); + return new FFmpegEncoder(params); +} + +bool Encoder::IsOpen() const +{ + return open_; +} + +void Encoder::Open() +{ + if (!open_) { + open_ = OpenInternal(); + } + + if (open_) { + emit OpenSucceeded(); + } else { + emit OpenFailed(); + } +} + +void Encoder::Write(FramePtr frame) +{ + if (open_) { + WriteInternal(frame); + } +} + +void Encoder::Close() +{ + if (open_) { + CloseInternal(); + + open_ = false; + } + + emit Closed(); } diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 3a8afe988..be07f6abc 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -42,17 +42,12 @@ private: AudioRenderingParams audio_params_; }; -class Encoder +class Encoder : public QObject { + Q_OBJECT public: Encoder(const EncodingParams& params); - DISABLE_COPY_MOVE(Encoder) - - virtual bool Open() = 0; - virtual void Write(FramePtr frame) = 0; - virtual void Close() = 0; - /** * @brief Create a Encoder instance using a Encoder ID * @@ -60,15 +55,33 @@ public: * * A Encoder instance or nullptr if a Decoder with this ID does not exist */ - static EncoderPtr CreateFromID(const QString& id, const EncodingParams ¶ms); + static Encoder *CreateFromID(const QString& id, const EncodingParams ¶ms); -protected: const EncodingParams& params() const; - bool open_; +public slots: + void Open(); + void Write(FramePtr frame); + virtual void WriteAudio(const AudioRenderingParams& pcm_info, const QString& pcm_filename) = 0; + void Close(); + +signals: + void OpenSucceeded(); + void OpenFailed(); + + void Closed(); + +protected: + virtual bool OpenInternal() = 0; + virtual void WriteInternal(FramePtr frame) = 0; + virtual void CloseInternal() = 0; + + bool IsOpen() const; private: EncodingParams params_; + + bool open_; }; #endif // ENCODER_H diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index f3caa6c28..d3c06bc09 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -1,5 +1,7 @@ #include "ffmpegencoder.h" +#include + #include "ffmpegcommon.h" #include "render/pixelservice.h" @@ -15,12 +17,82 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : { } -bool FFmpegEncoder::Open() +void FFmpegEncoder::WriteAudio(const AudioRenderingParams &pcm_info, const QString &pcm_filename) { - if (open_) { - return true; - } + QFile pcm(pcm_filename); + if (pcm.open(QFile::ReadOnly)) { + // Divide PCM stream into AVFrames + // See if the codec defines a number of samples per frame + int maximum_frame_samples = audio_codec_ctx_->frame_size; + if (!maximum_frame_samples) { + // If not, use another frame size + if (params().video_enabled()) { + // If we're encoding video, use enough samples to cover roughly one frame of video + maximum_frame_samples = params().audio_params().time_to_samples(params().video_params().time_base()); + } else { + // If no video, just use an arbitary number + maximum_frame_samples = 256; + } + } + + SwrContext* swr_ctx = swr_alloc_set_opts(nullptr, + audio_codec_ctx_->channel_layout, + audio_codec_ctx_->sample_fmt, + audio_codec_ctx_->sample_rate, + pcm_info.channel_layout(), + FFmpegCommon::GetFFmpegSampleFormat(pcm_info.format()), + pcm_info.sample_rate(), + 0, + nullptr); + + swr_init(swr_ctx); + + // Loop through PCM queueing write events + AVFrame* frame = av_frame_alloc(); + + // Set up frame + frame->channel_layout = audio_codec_ctx_->channel_layout; + frame->nb_samples = maximum_frame_samples; + frame->format = audio_codec_ctx_->sample_fmt; + + // Allocate its buffers + av_frame_get_buffer(frame, 0); + int sample_counter = 0; + + while (!pcm.atEnd()) { + int samples_needed = static_cast(frame->nb_samples + swr_get_delay(swr_ctx, pcm_info.sample_rate())); + + QByteArray input_data; + input_data = pcm.read(pcm_info.samples_to_bytes(samples_needed)); + const char* input_data_array = input_data.constData(); + samples_needed = pcm_info.bytes_to_samples(input_data.size()); + + // Use swresample to convert the data into the correct format/linesize + int converted = swr_convert(swr_ctx, + frame->data, + maximum_frame_samples, + reinterpret_cast(&input_data_array), + samples_needed); + + //qDebug() << "swr_convert returned" << converted << "samples"; + frame->pts = sample_counter; + + sample_counter += converted; + + WriteAVFrame(frame, audio_codec_ctx_, audio_stream_); + } + + av_frame_free(&frame); + + swr_free(&swr_ctx); + + pcm.close(); + } +} + +bool FFmpegEncoder::OpenInternal() +{ int error_code; // Convert QString to C string that FFmpeg expects @@ -32,7 +104,7 @@ bool FFmpegEncoder::Open() // Check error code if (error_code < 0) { - FFmpegError(error_code); + FFmpegError("Failed to allocate output context", error_code); return false; } @@ -42,8 +114,6 @@ bool FFmpegEncoder::Open() return false; } - - // This is the format we will expect frames received in Write() to be in olive::PixelFormat native_pixel_fmt = params().video_params().format(); @@ -81,112 +151,70 @@ bool FFmpegEncoder::Open() // Open output file for writing error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE); if (error_code < 0) { - FFmpegError(error_code); + FFmpegError("Failed to open IO context", error_code); return false; } // Write header error_code = avformat_write_header(fmt_ctx_, nullptr); if (error_code < 0) { - FFmpegError(error_code); + FFmpegError("Failed to write format header", error_code); return false; } - open_ = true; - return true; } -void FFmpegEncoder::Write(FramePtr frame) +void FFmpegEncoder::WriteInternal(FramePtr frame) { AVFrame* encoded_frame = av_frame_alloc(); - AVPacket* pkt = av_packet_alloc(); int error_code; + const char* input_data; + int input_linesize; - AVCodecContext* codec_ctx; - AVStream* stream; + // Frame must be video + encoded_frame->width = frame->width(); + encoded_frame->height = frame->height(); + encoded_frame->format = video_codec_ctx_->pix_fmt; - if (frame->width() > 0) { - // Frame must be video - encoded_frame->width = frame->width(); - encoded_frame->height = frame->height(); - encoded_frame->format = video_codec_ctx_->pix_fmt; - - error_code = av_frame_get_buffer(encoded_frame, 0); - if (error_code < 0) { - FFmpegError(error_code); - goto fail; - } - - // We may need to convert this frame to a frame that swscale will understand - if (frame->format() != video_conversion_fmt_) { - frame = PixelService::ConvertPixelFormat(frame, video_conversion_fmt_); - } - - // Use swscale context to convert formats/linesizes - const char* input_data = frame->const_data(); - int input_linesize = frame->width() * PixelService::BytesPerPixel(video_conversion_fmt_); - error_code = sws_scale(video_scale_ctx_, - reinterpret_cast(&input_data), - &input_linesize, - 0, - frame->height(), - encoded_frame->data, - encoded_frame->linesize); - if (error_code < 0) { - goto fail; - } - - codec_ctx = video_codec_ctx_; - stream = video_stream_; - } else { - // Frame must be audio - codec_ctx = audio_codec_ctx_; - stream = audio_stream_; - } - - encoded_frame->pts = qRound(frame->timestamp().toDouble() / av_q2d(codec_ctx->time_base)); - - // Send raw frame to the encoder - error_code = avcodec_send_frame(codec_ctx, encoded_frame); + error_code = av_frame_get_buffer(encoded_frame, 0); if (error_code < 0) { - FFmpegError(error_code); + FFmpegError("Failed to create AVFrame buffer", error_code); goto fail; } - // Retrieve packets from encoder - while (error_code >= 0) { - error_code = avcodec_receive_packet(codec_ctx, pkt); - - // EAGAIN just means the encoder wants another frame before encoding - if (error_code == AVERROR(EAGAIN)) { - break; - } else if (error_code < 0) { - FFmpegError(error_code); - goto fail; - } - - // Set packet stream index - pkt->stream_index = stream->index; - - av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); - - // Write packet to file - av_interleaved_write_frame(fmt_ctx_, pkt); - - // Unref packet in case we're getting another - av_packet_unref(pkt); + // We may need to convert this frame to a frame that swscale will understand + if (frame->format() != video_conversion_fmt_) { + frame = PixelService::ConvertPixelFormat(frame, video_conversion_fmt_); } + // Use swscale context to convert formats/linesizes + input_data = frame->const_data(); + input_linesize = frame->width() * PixelService::BytesPerPixel(video_conversion_fmt_); + error_code = sws_scale(video_scale_ctx_, + reinterpret_cast(&input_data), + &input_linesize, + 0, + frame->height(), + encoded_frame->data, + encoded_frame->linesize); + if (error_code < 0) { + FFmpegError("Failed to scale frame", error_code); + goto fail; + } + + encoded_frame->pts = qRound(frame->timestamp().toDouble() / av_q2d(video_codec_ctx_->time_base)); + + WriteAVFrame(encoded_frame, video_codec_ctx_, video_stream_); + fail: - av_packet_free(&pkt); av_frame_free(&encoded_frame); } -void FFmpegEncoder::Close() +void FFmpegEncoder::CloseInternal() { - if (open_) { + if (IsOpen()) { // Flush encoders FlushEncoders(); @@ -217,14 +245,54 @@ void FFmpegEncoder::Close() } } -void FFmpegEncoder::FFmpegError(int error_code) +void FFmpegEncoder::FFmpegError(const char* context, int error_code) { - char err[1024]; - av_strerror(error_code, err, 1024); + char err[128]; + av_strerror(error_code, err, 128); - Error(QStringLiteral("Error encoding %1 - %2 %3").arg(params().filename(), - QString::number(error_code), - err)); + Error(QStringLiteral("%1 for %2 - %3 %4").arg(context, + params().filename(), + QString::number(error_code), + err)); +} + +void FFmpegEncoder::WriteAVFrame(AVFrame *frame, AVCodecContext* codec_ctx, AVStream* stream) +{ + // Send raw frame to the encoder + int error_code = avcodec_send_frame(codec_ctx, frame); + if (error_code < 0) { + FFmpegError("Failed to send frame to encoder", error_code); + return; + } + + AVPacket* pkt = av_packet_alloc(); + + // Retrieve packets from encoder + while (error_code >= 0) { + error_code = avcodec_receive_packet(codec_ctx, pkt); + + // EAGAIN just means the encoder wants another frame before encoding + if (error_code == AVERROR(EAGAIN)) { + break; + } else if (error_code < 0) { + FFmpegError("Failed to receive packet from decoder", error_code); + goto fail; + } + + // Set packet stream index + pkt->stream_index = stream->index; + + av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); + + // Write packet to file + av_interleaved_write_frame(fmt_ctx_, pkt); + + // Unref packet in case we're getting another + av_packet_unref(pkt); + } + +fail: + av_packet_free(&pkt); } bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AVCodecContext** codec_ctx_ptr, const QString& codec) @@ -314,14 +382,14 @@ bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ct // Try to open encoder error_code = avcodec_open2(codec_ctx, codec, &codec_opts); if (error_code < 0) { - FFmpegError(error_code); + FFmpegError("Failed to open encoder", error_code); return false; } // Copy context settings to codecpar object error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx); if (error_code < 0) { - FFmpegError(error_code); + FFmpegError("Failed to copy codec parameters to stream", error_code); return false; } diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 40272a620..095c8880f 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -11,12 +11,17 @@ extern "C" { class FFmpegEncoder : public Encoder { + Q_OBJECT public: FFmpegEncoder(const EncodingParams ¶ms); - virtual bool Open() override; - virtual void Write(FramePtr frame) override; - virtual void Close() override; +public slots: + virtual void WriteAudio(const AudioRenderingParams& pcm_info, const QString& pcm_filename) override; + +protected: + virtual bool OpenInternal() override; + virtual void WriteInternal(FramePtr frame) override; + virtual void CloseInternal() override; private: /** @@ -36,7 +41,9 @@ private: * * @param error_code */ - void FFmpegError(int error_code); + void FFmpegError(const char *context, int error_code); + + void WriteAVFrame(AVFrame* frame, AVCodecContext *codec_ctx, AVStream *stream); bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const QString& codec); bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, AVCodec* codec); diff --git a/app/codec/frame.h b/app/codec/frame.h index 22f465dd2..a3f03f882 100644 --- a/app/codec/frame.h +++ b/app/codec/frame.h @@ -140,4 +140,6 @@ private: }; +Q_DECLARE_METATYPE(FramePtr) + #endif // FRAME_H diff --git a/app/core.cpp b/app/core.cpp index 82ef9a5a6..136af0803 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -341,6 +341,8 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); } void Core::StartGUI(bool full_screen) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 7dd7f9433..458655df4 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -181,6 +181,11 @@ void ExportDialog::accept() // FIXME: Hardcoded pixel format VideoRenderingParams video_render_params(dest_width, dest_height, video_tab_->frame_rate().flipped(), olive::PIX_FMT_RGBA32F, olive::kOnline); + // FIXME: Hardcoded sample format + AudioRenderingParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(), + audio_tab_->channel_layout_combobox()->currentData().toULongLong(), + SAMPLE_FMT_FLT); + ColorProcessorPtr color_processor = ColorProcessor::Create(color_manager_->GetConfig(), OCIO::ROLE_SCENE_LINEAR, video_tab_->CurrentOCIODisplay(), @@ -192,13 +197,15 @@ void ExportDialog::accept() encoding_params.SetFilename(filename_edit_->text()); // FIXME: Validate extension const ExportCodec& video_codec = codecs_.at(video_tab_->codec_combobox()->currentData().toInt()); + const ExportCodec& audio_codec = codecs_.at(audio_tab_->codec_combobox()->currentData().toInt()); encoding_params.EnableVideo(video_render_params, video_codec.id()); + encoding_params.EnableAudio(audio_render_params, + audio_codec.id()); + Encoder* encoder = Encoder::CreateFromID("ffmpeg", encoding_params); - EncoderPtr encoder = Encoder::CreateFromID("ffmpeg", encoding_params); - - OpenGLExporter* exporter = new OpenGLExporter(viewer_node_, video_render_params, transform, color_processor, encoder); + OpenGLExporter* exporter = new OpenGLExporter(viewer_node_, video_render_params, audio_render_params, transform, color_processor, encoder); connect(exporter, &Exporter::ExportEnded, this, &ExportDialog::ExporterIsDone); connect(exporter, &Exporter::ProgressChanged, progress_bar_, &QProgressBar::setValue); @@ -326,25 +333,25 @@ void ExportDialog::SetUpFormats() for (int i=0;i(i)) { case kFormatDNxHD: - formats_.append(ExportFormat(tr("DNxHD"), "mxf", {kCodecDNxHD}, {kCodecPCM})); + formats_.append(ExportFormat(tr("DNxHD"), "mxf", "ffmpeg", {kCodecDNxHD}, {kCodecPCM})); break; case kFormatMatroska: - formats_.append(ExportFormat(tr("Matroska Video"), "mkv", {kCodecH264, kCodecH265}, {kCodecAAC, kCodecMP2, kCodecMP3, kCodecPCM})); + formats_.append(ExportFormat(tr("Matroska Video"), "mkv", "ffmpeg", {kCodecH264, kCodecH265}, {kCodecAAC, kCodecMP2, kCodecMP3, kCodecPCM})); break; case kFormatMPEG4: - formats_.append(ExportFormat(tr("MPEG-4 Video"), "mp4", {kCodecH264, kCodecH265}, {kCodecAAC, kCodecMP2, kCodecMP3, kCodecPCM})); + formats_.append(ExportFormat(tr("MPEG-4 Video"), "mp4", "ffmpeg", {kCodecH264, kCodecH265}, {kCodecAAC, kCodecMP2, kCodecMP3, kCodecPCM})); break; case kFormatOpenEXR: - formats_.append(ExportFormat(tr("OpenEXR"), "exr", {kCodecOpenEXR}, {})); + formats_.append(ExportFormat(tr("OpenEXR"), "exr", "oiio", {kCodecOpenEXR}, {})); break; case kFormatQuickTime: - formats_.append(ExportFormat(tr("QuickTime"), "mov", {kCodecH264, kCodecH265, kCodecProRes}, {kCodecAAC, kCodecMP2, kCodecMP3, kCodecPCM})); + formats_.append(ExportFormat(tr("QuickTime"), "mov", "ffmpeg", {kCodecH264, kCodecH265, kCodecProRes}, {kCodecAAC, kCodecMP2, kCodecMP3, kCodecPCM})); break; case kFormatPNG: - formats_.append(ExportFormat(tr("PNG"), "png", {kCodecPNG}, {})); + formats_.append(ExportFormat(tr("PNG"), "png", "oiio", {kCodecPNG}, {})); break; case kFormatTIFF: - formats_.append(ExportFormat(tr("TIFF"), "tiff", {kCodecTIFF}, {})); + formats_.append(ExportFormat(tr("TIFF"), "tiff", "oiio", {kCodecTIFF}, {})); break; case kFormatCount: break; diff --git a/app/dialog/export/exportaudiotab.cpp b/app/dialog/export/exportaudiotab.cpp index 4e54d7851..e28de76f8 100644 --- a/app/dialog/export/exportaudiotab.cpp +++ b/app/dialog/export/exportaudiotab.cpp @@ -27,7 +27,7 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) : sample_rate_combobox_ = new QComboBox(); sample_rates_ = Core::SupportedSampleRates(); foreach (const int& sr, sample_rates_) { - sample_rate_combobox_->addItem(Core::SampleRateToString(sr)); + sample_rate_combobox_->addItem(Core::SampleRateToString(sr), sr); } layout->addWidget(sample_rate_combobox_, row, 1); @@ -38,7 +38,7 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) : channel_layout_combobox_ = new QComboBox(); channel_layouts_ = Core::SupportedChannelLayouts(); foreach (const uint64_t& layout, channel_layouts_) { - channel_layout_combobox_->addItem(Core::ChannelLayoutToString(layout)); + channel_layout_combobox_->addItem(Core::ChannelLayoutToString(layout), layout); } layout->addWidget(channel_layout_combobox_, row, 1); @@ -60,6 +60,11 @@ QComboBox *ExportAudioTab::sample_rate_combobox() const return sample_rate_combobox_; } +QComboBox *ExportAudioTab::channel_layout_combobox() const +{ + return channel_layout_combobox_; +} + void ExportAudioTab::set_sample_rate(int rate) { sample_rate_combobox_->setCurrentIndex(sample_rates_.indexOf(rate)); diff --git a/app/dialog/export/exportaudiotab.h b/app/dialog/export/exportaudiotab.h index 027316c07..e28d22fd2 100644 --- a/app/dialog/export/exportaudiotab.h +++ b/app/dialog/export/exportaudiotab.h @@ -11,6 +11,7 @@ public: QComboBox* codec_combobox() const; QComboBox* sample_rate_combobox() const; + QComboBox* channel_layout_combobox() const; void set_sample_rate(int rate); void set_channel_layout(uint64_t layout); diff --git a/app/dialog/export/exportformat.h b/app/dialog/export/exportformat.h index a7f4d577e..064e74089 100644 --- a/app/dialog/export/exportformat.h +++ b/app/dialog/export/exportformat.h @@ -6,9 +6,10 @@ class ExportFormat { public: - ExportFormat(const QString& name, const QString& extension, const QList& vcodecs, const QList& acodecs) : + ExportFormat(const QString& name, const QString& extension, const QString& encoder, const QList& vcodecs, const QList& acodecs) : name_(name), extension_(extension), + encoder_(encoder), video_codecs_(vcodecs), audio_codecs_(acodecs) { @@ -16,12 +17,14 @@ public: const QString& name() const {return name_;} const QString& extension() const {return extension_;} + const QString& encoder() const {return encoder_;} const QList& video_codecs() const {return video_codecs_;} const QList& audio_codecs() const {return audio_codecs_;} private: QString name_; QString extension_; + QString encoder_; QList video_codecs_; QList audio_codecs_; diff --git a/app/main.cpp b/app/main.cpp index 6b2e7579f..fa7d5dbf6 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -38,6 +38,8 @@ extern "C" { #include "common/debug.h" int main(int argc, char *argv[]) { + av_log_set_level(AV_LOG_QUIET); + QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // Set OpenGL display profile (3.2 Core) diff --git a/app/render/audioparams.h b/app/render/audioparams.h index fec3d1347..17dcd6998 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -46,4 +46,6 @@ private: SampleFormat format_; }; +Q_DECLARE_METATYPE(AudioRenderingParams) + #endif // AUDIOPARAMS_H diff --git a/app/render/backend/audiorenderbackend.h b/app/render/backend/audiorenderbackend.h index c27ff78ba..d73d6ccc6 100644 --- a/app/render/backend/audiorenderbackend.h +++ b/app/render/backend/audiorenderbackend.h @@ -22,6 +22,8 @@ public: const AudioRenderingParams& params(); + QString CachePathName(); + public slots: virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; @@ -37,8 +39,6 @@ protected: virtual NodeInput* GetDependentInput() override; - QString CachePathName(); - virtual bool CanRender() override; private: diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp index ce835c00d..4b07e2fc4 100644 --- a/app/render/backend/exporter.cpp +++ b/app/render/backend/exporter.cpp @@ -3,12 +3,19 @@ #include "render/colormanager.h" #include "render/pixelservice.h" -Exporter::Exporter(ViewerOutput* viewer, const VideoRenderingParams& params, const QMatrix4x4 &transform, ColorProcessorPtr color_processor, EncoderPtr encoder, QObject* parent) : +Exporter::Exporter(ViewerOutput* viewer, + const VideoRenderingParams& video_params, + const AudioRenderingParams& audio_params, + const QMatrix4x4 &transform, + ColorProcessorPtr color_processor, + Encoder *encoder, + QObject* parent) : QObject(parent), video_backend_(nullptr), audio_backend_(nullptr), viewer_node_(viewer), - params_(params), + video_params_(video_params), + audio_params_(audio_params), transform_(transform), color_processor_(color_processor), encoder_(encoder), @@ -35,6 +42,7 @@ void Exporter::StartExporting() // Create renderers if (!Initialize()) { + SetExportMessage("Failed to initialize exporter"); ExportFailed(); return; } @@ -42,26 +50,28 @@ void Exporter::StartExporting() video_backend_->SetViewerNode(viewer_node_); video_backend_->SetParameters(VideoRenderingParams(viewer_node_->video_params().width(), viewer_node_->video_params().height(), - params_.time_base(), - olive::PIX_FMT_RGBA32F, - olive::kOnline)); + video_params_.time_base(), + video_params_.format(), + video_params_.mode())); video_backend_->SetExportMode(true); - video_backend_->Compile(); - //audio_backend_->SetViewerNode(viewer_node_); - //audio_renderer_->SetParameters(AudioRenderingParams(viewer_node_->audio_params(), SAMPLE_FMT_FLT)); + audio_backend_->SetViewerNode(viewer_node_); + audio_backend_->SetParameters(audio_params_); // Connect to renderers connect(video_backend_, SIGNAL(CachedFrameReady(const rational&, QVariant)), this, SLOT(FrameRendered(const rational&, QVariant))); + connect(audio_backend_, SIGNAL(QueueComplete()), this, SLOT(AudioRendered())); // Create renderers waiting_for_frame_ = 0; - // Open encoder - encoder_->Open(); + // Open encoder and wait for result + connect(encoder_, SIGNAL(OpenSucceeded()), this, SLOT(EncoderOpenedSuccessfully()), Qt::QueuedConnection); + connect(encoder_, SIGNAL(OpenFailed()), this, SLOT(EncoderOpenFailed()), Qt::QueuedConnection); + connect(encoder_, SIGNAL(Closed()), encoder_, SLOT(deleteLater()), Qt::QueuedConnection); - // Invalidate caches - video_backend_->InvalidateCache(0, viewer_node_->Length()); - //viewer_node_->InvalidateCache(0, viewer_node_->Length(), viewer_node_->samples_input()); + QMetaObject::invokeMethod(encoder_, + "Open", + Qt::QueuedConnection); } void Exporter::SetExportMessage(const QString &s) @@ -74,14 +84,14 @@ void Exporter::ExportSucceeded() Cleanup(); video_backend_->deleteLater(); - audio_backend_->deleteLater(); export_status_ = true; - encoder_->Close(); + connect(encoder_, SIGNAL(Closed()), this, SLOT(EncoderClosed())); - emit ProgressChanged(100); - emit ExportEnded(); + QMetaObject::invokeMethod(encoder_, + "Close", + Qt::QueuedConnection); } void Exporter::ExportFailed() @@ -121,9 +131,12 @@ void Exporter::FrameRendered(const rational &time, QVariant value) frame->set_timestamp(waiting_for_frame_); // Encode (may require re-associating alpha?) - encoder_->Write(frame); + QMetaObject::invokeMethod(encoder_, + "Write", + Qt::QueuedConnection, + Q_ARG(FramePtr, frame)); - waiting_for_frame_ += params_.time_base(); + waiting_for_frame_ += video_params_.time_base(); // Calculate progress int progress = qRound(100.0 * (waiting_for_frame_.toDouble() / viewer_node_->Length().toDouble())); @@ -132,9 +145,48 @@ void Exporter::FrameRendered(const rational &time, QVariant value) } while (cached_frames_.contains(waiting_for_frame_)); if (waiting_for_frame_ >= viewer_node_->Length()) { - ExportSucceeded(); +S ExportSucceeded(); } } else { cached_frames_.insert(time, value); } } + +void Exporter::AudioRendered() +{ + // Retrieve the audio filename + QString cache_fn = audio_backend_->CachePathName(); + + QMetaObject::invokeMethod(encoder_, + "WriteAudio", + Qt::QueuedConnection, + Q_ARG(const AudioRenderingParams&, audio_backend_->params()), + Q_ARG(const QString&, cache_fn)); + + // We don't need the audio backend anymore + audio_backend_->deleteLater(); +} + +void Exporter::EncoderOpenedSuccessfully() +{ + // Invalidate caches + if (encoder_->params().video_enabled()) { + video_backend_->InvalidateCache(0, viewer_node_->Length()); + } + + if (encoder_->params().audio_enabled()) { + audio_backend_->InvalidateCache(0, viewer_node_->Length()); + } +} + +void Exporter::EncoderOpenFailed() +{ + SetExportMessage("Failed to open encoder"); + ExportFailed(); +} + +void Exporter::EncoderClosed() +{ + emit ProgressChanged(100); + emit ExportEnded(); +} diff --git a/app/render/backend/exporter.h b/app/render/backend/exporter.h index 1215c5e62..f1235be10 100644 --- a/app/render/backend/exporter.h +++ b/app/render/backend/exporter.h @@ -16,10 +16,11 @@ class Exporter : public QObject Q_OBJECT public: Exporter(ViewerOutput* viewer, - const VideoRenderingParams& params, + const VideoRenderingParams& video_params, + const AudioRenderingParams& audio_params, const QMatrix4x4& transform, ColorProcessorPtr color_processor, - EncoderPtr encoder, + Encoder* encoder, QObject* parent = nullptr); bool GetExportStatus() const; @@ -49,7 +50,8 @@ protected: ViewerOutput* viewer_node_; // Export parameters - VideoRenderingParams params_; + VideoRenderingParams video_params_; + AudioRenderingParams audio_params_; // Export transform QMatrix4x4 transform_; @@ -61,7 +63,7 @@ private: ColorProcessorPtr color_processor_; - EncoderPtr encoder_; + Encoder* encoder_; bool export_status_; @@ -74,6 +76,14 @@ private: private slots: void FrameRendered(const rational& time, QVariant value); + void AudioRendered(); + + void EncoderOpenedSuccessfully(); + + void EncoderOpenFailed(); + + void EncoderClosed(); + }; #endif // EXPORTER_H diff --git a/app/render/backend/opengl/openglexporter.cpp b/app/render/backend/opengl/openglexporter.cpp index 1da778c02..6728e9a75 100644 --- a/app/render/backend/opengl/openglexporter.cpp +++ b/app/render/backend/opengl/openglexporter.cpp @@ -3,8 +3,8 @@ #include "render/backend/opengl/functions.h" #include "render/pixelservice.h" -OpenGLExporter::OpenGLExporter(ViewerOutput* viewer, const VideoRenderingParams& params, const QMatrix4x4 &transform, ColorProcessorPtr color_processor, EncoderPtr encoder, QObject* parent) : - Exporter(viewer, params, transform, color_processor, encoder, parent) +OpenGLExporter::OpenGLExporter(ViewerOutput* viewer, const VideoRenderingParams& video_params, const AudioRenderingParams &audio_params, const QMatrix4x4 &transform, ColorProcessorPtr color_processor, Encoder *encoder, QObject* parent) : + Exporter(viewer, video_params, audio_params, transform, color_processor, encoder, parent) { } @@ -24,7 +24,7 @@ bool OpenGLExporter::Initialize() buffer_.Create(ctx); texture_ = std::make_shared(); - texture_->Create(ctx, params_.effective_width(), params_.effective_height(), params_.format()); + texture_->Create(ctx, video_params_.effective_width(), video_params_.effective_height(), video_params_.format()); pipeline_ = OpenGLShader::CreateDefault(); @@ -40,9 +40,9 @@ void OpenGLExporter::Cleanup() FramePtr OpenGLExporter::TextureToFrame(const QVariant& texture) { FramePtr frame = Frame::Create(); - frame->set_width(params_.width()); - frame->set_height(params_.height()); - frame->set_format(params_.format()); + frame->set_width(video_params_.width()); + frame->set_height(video_params_.height()); + frame->set_format(video_params_.format()); frame->allocate(); // Blit for transform if the width/height are different @@ -65,7 +65,7 @@ FramePtr OpenGLExporter::TextureToFrame(const QVariant& texture) buffer_.Attach(texture_); buffer_.Bind(); - PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(params_.format()); + PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(video_params_.format()); f->glReadPixels(0, 0, diff --git a/app/render/backend/opengl/openglexporter.h b/app/render/backend/opengl/openglexporter.h index 401cf5e04..0ccec5461 100644 --- a/app/render/backend/opengl/openglexporter.h +++ b/app/render/backend/opengl/openglexporter.h @@ -11,10 +11,11 @@ class OpenGLExporter : public Exporter { public: OpenGLExporter(ViewerOutput* viewer, - const VideoRenderingParams& params, + const VideoRenderingParams& video_params, + const AudioRenderingParams& audio_params, const QMatrix4x4& transform, ColorProcessorPtr color_processor, - EncoderPtr encoder, + Encoder* encoder, QObject* parent = nullptr); virtual ~OpenGLExporter() override; diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index f5f978f3e..7af79e36d 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -210,12 +210,16 @@ void RenderBackend::DisconnectViewer(ViewerOutput *node) void RenderBackend::CacheNext() { if (!Init() - || cache_queue_.isEmpty() || !ViewerIsConnected() || !CanRender()) { return; } + if (cache_queue_.isEmpty()) { + emit QueueComplete(); + return; + } + if ((input_update_queued_ || recompile_queued_) && !AllProcessorsAreAvailable()) { return; } diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index a14fafa14..ffafd4ab9 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -36,6 +36,9 @@ public slots: void Decompile(); +signals: + void QueueComplete(); + protected: void RegenerateCacheID();