From 3a7b3ffac6fa7b8cb9dbcee0f0a2db89efbd6651 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 23 Dec 2019 01:45:27 +1100 Subject: [PATCH] implemented audio exporting The encoder was moved to its own thread and will transcode the PCM from the audio renderer into the chosen codec while the video frames are still received. The implementation isn't perfect and could use some cleaning up, but it is functional at the moment. --- app/codec/encoder.cpp | 44 +++- app/codec/encoder.h | 33 ++- app/codec/ffmpeg/ffmpegencoder.cpp | 252 ++++++++++++------- app/codec/ffmpeg/ffmpegencoder.h | 15 +- app/codec/frame.h | 2 + app/core.cpp | 2 + app/dialog/export/export.cpp | 27 +- app/dialog/export/exportaudiotab.cpp | 9 +- app/dialog/export/exportaudiotab.h | 1 + app/dialog/export/exportformat.h | 5 +- app/main.cpp | 2 + app/render/audioparams.h | 2 + app/render/backend/audiorenderbackend.h | 4 +- app/render/backend/exporter.cpp | 92 +++++-- app/render/backend/exporter.h | 18 +- app/render/backend/opengl/openglexporter.cpp | 14 +- app/render/backend/opengl/openglexporter.h | 5 +- app/render/backend/renderbackend.cpp | 6 +- app/render/backend/renderbackend.h | 3 + 19 files changed, 377 insertions(+), 159 deletions(-) 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();