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.
This commit is contained in:
itsmattkc
2019-12-23 01:45:27 +11:00
parent a8beae64ae
commit 3a7b3ffac6
19 changed files with 377 additions and 159 deletions
+40 -4
View File
@@ -3,8 +3,8 @@
#include "ffmpeg/ffmpegencoder.h"
Encoder::Encoder(const EncodingParams &params) :
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<FFmpegEncoder>(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();
}
+23 -10
View File
@@ -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 &params);
static Encoder *CreateFromID(const QString& id, const EncodingParams &params);
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
+160 -92
View File
@@ -1,5 +1,7 @@
#include "ffmpegencoder.h"
#include <QFile>
#include "ffmpegcommon.h"
#include "render/pixelservice.h"
@@ -15,12 +17,82 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams &params) :
{
}
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<int>(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<const uint8_t**>(&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<const uint8_t**>(&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<const uint8_t**>(&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;
}
+11 -4
View File
@@ -11,12 +11,17 @@ extern "C" {
class FFmpegEncoder : public Encoder
{
Q_OBJECT
public:
FFmpegEncoder(const EncodingParams &params);
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);
+2
View File
@@ -140,4 +140,6 @@ private:
};
Q_DECLARE_METATYPE(FramePtr)
#endif // FRAME_H
+2
View File
@@ -341,6 +341,8 @@ void Core::DeclareTypesForQt()
qRegisterMetaType<OpenGLTexturePtr>();
qRegisterMetaType<OpenGLTextureCache::ReferencePtr>();
qRegisterMetaType<NodeValueTable>();
qRegisterMetaType<FramePtr>();
qRegisterMetaType<AudioRenderingParams>();
}
void Core::StartGUI(bool full_screen)
+17 -10
View File
@@ -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<kFormatCount;i++) {
switch (static_cast<Format>(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;
+7 -2
View File
@@ -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));
+1
View File
@@ -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);
+4 -1
View File
@@ -6,9 +6,10 @@
class ExportFormat {
public:
ExportFormat(const QString& name, const QString& extension, const QList<int>& vcodecs, const QList<int>& acodecs) :
ExportFormat(const QString& name, const QString& extension, const QString& encoder, const QList<int>& vcodecs, const QList<int>& 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<int>& video_codecs() const {return video_codecs_;}
const QList<int>& audio_codecs() const {return audio_codecs_;}
private:
QString name_;
QString extension_;
QString encoder_;
QList<int> video_codecs_;
QList<int> audio_codecs_;
+2
View File
@@ -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)
+2
View File
@@ -46,4 +46,6 @@ private:
SampleFormat format_;
};
Q_DECLARE_METATYPE(AudioRenderingParams)
#endif // AUDIOPARAMS_H
+2 -2
View File
@@ -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:
+72 -20
View File
@@ -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();
}
+14 -4
View File
@@ -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
+7 -7
View File
@@ -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<OpenGLTexture>();
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,
+3 -2
View File
@@ -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;
+5 -1
View File
@@ -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;
}
+3
View File
@@ -36,6 +36,9 @@ public slots:
void Decompile();
signals:
void QueueComplete();
protected:
void RegenerateCacheID();