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" #include "ffmpeg/ffmpegencoder.h"
Encoder::Encoder(const EncodingParams &params) : 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_; 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_; AudioRenderingParams audio_params_;
}; };
class Encoder class Encoder : public QObject
{ {
Q_OBJECT
public: public:
Encoder(const EncodingParams& params); 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 * @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 * 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; 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: private:
EncodingParams params_; EncodingParams params_;
bool open_;
}; };
#endif // ENCODER_H #endif // ENCODER_H
+160 -92
View File
@@ -1,5 +1,7 @@
#include "ffmpegencoder.h" #include "ffmpegencoder.h"
#include <QFile>
#include "ffmpegcommon.h" #include "ffmpegcommon.h"
#include "render/pixelservice.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_) { QFile pcm(pcm_filename);
return true; 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; int error_code;
// Convert QString to C string that FFmpeg expects // Convert QString to C string that FFmpeg expects
@@ -32,7 +104,7 @@ bool FFmpegEncoder::Open()
// Check error code // Check error code
if (error_code < 0) { if (error_code < 0) {
FFmpegError(error_code); FFmpegError("Failed to allocate output context", error_code);
return false; return false;
} }
@@ -42,8 +114,6 @@ bool FFmpegEncoder::Open()
return false; return false;
} }
// This is the format we will expect frames received in Write() to be in // This is the format we will expect frames received in Write() to be in
olive::PixelFormat native_pixel_fmt = params().video_params().format(); olive::PixelFormat native_pixel_fmt = params().video_params().format();
@@ -81,112 +151,70 @@ bool FFmpegEncoder::Open()
// Open output file for writing // Open output file for writing
error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE); error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE);
if (error_code < 0) { if (error_code < 0) {
FFmpegError(error_code); FFmpegError("Failed to open IO context", error_code);
return false; return false;
} }
// Write header // Write header
error_code = avformat_write_header(fmt_ctx_, nullptr); error_code = avformat_write_header(fmt_ctx_, nullptr);
if (error_code < 0) { if (error_code < 0) {
FFmpegError(error_code); FFmpegError("Failed to write format header", error_code);
return false; return false;
} }
open_ = true;
return true; return true;
} }
void FFmpegEncoder::Write(FramePtr frame) void FFmpegEncoder::WriteInternal(FramePtr frame)
{ {
AVFrame* encoded_frame = av_frame_alloc(); AVFrame* encoded_frame = av_frame_alloc();
AVPacket* pkt = av_packet_alloc();
int error_code; int error_code;
const char* input_data;
int input_linesize;
AVCodecContext* codec_ctx; // Frame must be video
AVStream* stream; encoded_frame->width = frame->width();
encoded_frame->height = frame->height();
encoded_frame->format = video_codec_ctx_->pix_fmt;
if (frame->width() > 0) { error_code = av_frame_get_buffer(encoded_frame, 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);
if (error_code < 0) { if (error_code < 0) {
FFmpegError(error_code); FFmpegError("Failed to create AVFrame buffer", error_code);
goto fail; goto fail;
} }
// Retrieve packets from encoder // We may need to convert this frame to a frame that swscale will understand
while (error_code >= 0) { if (frame->format() != video_conversion_fmt_) {
error_code = avcodec_receive_packet(codec_ctx, pkt); frame = PixelService::ConvertPixelFormat(frame, video_conversion_fmt_);
// 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);
} }
// 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: fail:
av_packet_free(&pkt);
av_frame_free(&encoded_frame); av_frame_free(&encoded_frame);
} }
void FFmpegEncoder::Close() void FFmpegEncoder::CloseInternal()
{ {
if (open_) { if (IsOpen()) {
// Flush encoders // Flush encoders
FlushEncoders(); 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]; char err[128];
av_strerror(error_code, err, 1024); av_strerror(error_code, err, 128);
Error(QStringLiteral("Error encoding %1 - %2 %3").arg(params().filename(), Error(QStringLiteral("%1 for %2 - %3 %4").arg(context,
QString::number(error_code), params().filename(),
err)); 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) 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 // Try to open encoder
error_code = avcodec_open2(codec_ctx, codec, &codec_opts); error_code = avcodec_open2(codec_ctx, codec, &codec_opts);
if (error_code < 0) { if (error_code < 0) {
FFmpegError(error_code); FFmpegError("Failed to open encoder", error_code);
return false; return false;
} }
// Copy context settings to codecpar object // Copy context settings to codecpar object
error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx); error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
if (error_code < 0) { if (error_code < 0) {
FFmpegError(error_code); FFmpegError("Failed to copy codec parameters to stream", error_code);
return false; return false;
} }
+11 -4
View File
@@ -11,12 +11,17 @@ extern "C" {
class FFmpegEncoder : public Encoder class FFmpegEncoder : public Encoder
{ {
Q_OBJECT
public: public:
FFmpegEncoder(const EncodingParams &params); FFmpegEncoder(const EncodingParams &params);
virtual bool Open() override; public slots:
virtual void Write(FramePtr frame) override; virtual void WriteAudio(const AudioRenderingParams& pcm_info, const QString& pcm_filename) override;
virtual void Close() override;
protected:
virtual bool OpenInternal() override;
virtual void WriteInternal(FramePtr frame) override;
virtual void CloseInternal() override;
private: private:
/** /**
@@ -36,7 +41,9 @@ private:
* *
* @param error_code * @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 InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const QString& codec);
bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, AVCodec* 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 #endif // FRAME_H
+2
View File
@@ -341,6 +341,8 @@ void Core::DeclareTypesForQt()
qRegisterMetaType<OpenGLTexturePtr>(); qRegisterMetaType<OpenGLTexturePtr>();
qRegisterMetaType<OpenGLTextureCache::ReferencePtr>(); qRegisterMetaType<OpenGLTextureCache::ReferencePtr>();
qRegisterMetaType<NodeValueTable>(); qRegisterMetaType<NodeValueTable>();
qRegisterMetaType<FramePtr>();
qRegisterMetaType<AudioRenderingParams>();
} }
void Core::StartGUI(bool full_screen) void Core::StartGUI(bool full_screen)
+17 -10
View File
@@ -181,6 +181,11 @@ void ExportDialog::accept()
// FIXME: Hardcoded pixel format // FIXME: Hardcoded pixel format
VideoRenderingParams video_render_params(dest_width, dest_height, video_tab_->frame_rate().flipped(), olive::PIX_FMT_RGBA32F, olive::kOnline); 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(), ColorProcessorPtr color_processor = ColorProcessor::Create(color_manager_->GetConfig(),
OCIO::ROLE_SCENE_LINEAR, OCIO::ROLE_SCENE_LINEAR,
video_tab_->CurrentOCIODisplay(), video_tab_->CurrentOCIODisplay(),
@@ -192,13 +197,15 @@ void ExportDialog::accept()
encoding_params.SetFilename(filename_edit_->text()); // FIXME: Validate extension encoding_params.SetFilename(filename_edit_->text()); // FIXME: Validate extension
const ExportCodec& video_codec = codecs_.at(video_tab_->codec_combobox()->currentData().toInt()); 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, encoding_params.EnableVideo(video_render_params,
video_codec.id()); 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, audio_render_params, transform, color_processor, encoder);
OpenGLExporter* exporter = new OpenGLExporter(viewer_node_, video_render_params, transform, color_processor, encoder);
connect(exporter, &Exporter::ExportEnded, this, &ExportDialog::ExporterIsDone); connect(exporter, &Exporter::ExportEnded, this, &ExportDialog::ExporterIsDone);
connect(exporter, &Exporter::ProgressChanged, progress_bar_, &QProgressBar::setValue); connect(exporter, &Exporter::ProgressChanged, progress_bar_, &QProgressBar::setValue);
@@ -326,25 +333,25 @@ void ExportDialog::SetUpFormats()
for (int i=0;i<kFormatCount;i++) { for (int i=0;i<kFormatCount;i++) {
switch (static_cast<Format>(i)) { switch (static_cast<Format>(i)) {
case kFormatDNxHD: case kFormatDNxHD:
formats_.append(ExportFormat(tr("DNxHD"), "mxf", {kCodecDNxHD}, {kCodecPCM})); formats_.append(ExportFormat(tr("DNxHD"), "mxf", "ffmpeg", {kCodecDNxHD}, {kCodecPCM}));
break; break;
case kFormatMatroska: 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; break;
case kFormatMPEG4: 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; break;
case kFormatOpenEXR: case kFormatOpenEXR:
formats_.append(ExportFormat(tr("OpenEXR"), "exr", {kCodecOpenEXR}, {})); formats_.append(ExportFormat(tr("OpenEXR"), "exr", "oiio", {kCodecOpenEXR}, {}));
break; break;
case kFormatQuickTime: 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; break;
case kFormatPNG: case kFormatPNG:
formats_.append(ExportFormat(tr("PNG"), "png", {kCodecPNG}, {})); formats_.append(ExportFormat(tr("PNG"), "png", "oiio", {kCodecPNG}, {}));
break; break;
case kFormatTIFF: case kFormatTIFF:
formats_.append(ExportFormat(tr("TIFF"), "tiff", {kCodecTIFF}, {})); formats_.append(ExportFormat(tr("TIFF"), "tiff", "oiio", {kCodecTIFF}, {}));
break; break;
case kFormatCount: case kFormatCount:
break; break;
+7 -2
View File
@@ -27,7 +27,7 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) :
sample_rate_combobox_ = new QComboBox(); sample_rate_combobox_ = new QComboBox();
sample_rates_ = Core::SupportedSampleRates(); sample_rates_ = Core::SupportedSampleRates();
foreach (const int& sr, sample_rates_) { 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); layout->addWidget(sample_rate_combobox_, row, 1);
@@ -38,7 +38,7 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) :
channel_layout_combobox_ = new QComboBox(); channel_layout_combobox_ = new QComboBox();
channel_layouts_ = Core::SupportedChannelLayouts(); channel_layouts_ = Core::SupportedChannelLayouts();
foreach (const uint64_t& layout, channel_layouts_) { 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); layout->addWidget(channel_layout_combobox_, row, 1);
@@ -60,6 +60,11 @@ QComboBox *ExportAudioTab::sample_rate_combobox() const
return sample_rate_combobox_; return sample_rate_combobox_;
} }
QComboBox *ExportAudioTab::channel_layout_combobox() const
{
return channel_layout_combobox_;
}
void ExportAudioTab::set_sample_rate(int rate) void ExportAudioTab::set_sample_rate(int rate)
{ {
sample_rate_combobox_->setCurrentIndex(sample_rates_.indexOf(rate)); sample_rate_combobox_->setCurrentIndex(sample_rates_.indexOf(rate));
+1
View File
@@ -11,6 +11,7 @@ public:
QComboBox* codec_combobox() const; QComboBox* codec_combobox() const;
QComboBox* sample_rate_combobox() const; QComboBox* sample_rate_combobox() const;
QComboBox* channel_layout_combobox() const;
void set_sample_rate(int rate); void set_sample_rate(int rate);
void set_channel_layout(uint64_t layout); void set_channel_layout(uint64_t layout);
+4 -1
View File
@@ -6,9 +6,10 @@
class ExportFormat { class ExportFormat {
public: 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), name_(name),
extension_(extension), extension_(extension),
encoder_(encoder),
video_codecs_(vcodecs), video_codecs_(vcodecs),
audio_codecs_(acodecs) audio_codecs_(acodecs)
{ {
@@ -16,12 +17,14 @@ public:
const QString& name() const {return name_;} const QString& name() const {return name_;}
const QString& extension() const {return extension_;} const QString& extension() const {return extension_;}
const QString& encoder() const {return encoder_;}
const QList<int>& video_codecs() const {return video_codecs_;} const QList<int>& video_codecs() const {return video_codecs_;}
const QList<int>& audio_codecs() const {return audio_codecs_;} const QList<int>& audio_codecs() const {return audio_codecs_;}
private: private:
QString name_; QString name_;
QString extension_; QString extension_;
QString encoder_;
QList<int> video_codecs_; QList<int> video_codecs_;
QList<int> audio_codecs_; QList<int> audio_codecs_;
+2
View File
@@ -38,6 +38,8 @@ extern "C" {
#include "common/debug.h" #include "common/debug.h"
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
av_log_set_level(AV_LOG_QUIET);
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
// Set OpenGL display profile (3.2 Core) // Set OpenGL display profile (3.2 Core)
+2
View File
@@ -46,4 +46,6 @@ private:
SampleFormat format_; SampleFormat format_;
}; };
Q_DECLARE_METATYPE(AudioRenderingParams)
#endif // AUDIOPARAMS_H #endif // AUDIOPARAMS_H
+2 -2
View File
@@ -22,6 +22,8 @@ public:
const AudioRenderingParams& params(); const AudioRenderingParams& params();
QString CachePathName();
public slots: public slots:
virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; virtual void InvalidateCache(const rational &start_range, const rational &end_range) override;
@@ -37,8 +39,6 @@ protected:
virtual NodeInput* GetDependentInput() override; virtual NodeInput* GetDependentInput() override;
QString CachePathName();
virtual bool CanRender() override; virtual bool CanRender() override;
private: private:
+72 -20
View File
@@ -3,12 +3,19 @@
#include "render/colormanager.h" #include "render/colormanager.h"
#include "render/pixelservice.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), QObject(parent),
video_backend_(nullptr), video_backend_(nullptr),
audio_backend_(nullptr), audio_backend_(nullptr),
viewer_node_(viewer), viewer_node_(viewer),
params_(params), video_params_(video_params),
audio_params_(audio_params),
transform_(transform), transform_(transform),
color_processor_(color_processor), color_processor_(color_processor),
encoder_(encoder), encoder_(encoder),
@@ -35,6 +42,7 @@ void Exporter::StartExporting()
// Create renderers // Create renderers
if (!Initialize()) { if (!Initialize()) {
SetExportMessage("Failed to initialize exporter");
ExportFailed(); ExportFailed();
return; return;
} }
@@ -42,26 +50,28 @@ void Exporter::StartExporting()
video_backend_->SetViewerNode(viewer_node_); video_backend_->SetViewerNode(viewer_node_);
video_backend_->SetParameters(VideoRenderingParams(viewer_node_->video_params().width(), video_backend_->SetParameters(VideoRenderingParams(viewer_node_->video_params().width(),
viewer_node_->video_params().height(), viewer_node_->video_params().height(),
params_.time_base(), video_params_.time_base(),
olive::PIX_FMT_RGBA32F, video_params_.format(),
olive::kOnline)); video_params_.mode()));
video_backend_->SetExportMode(true); video_backend_->SetExportMode(true);
video_backend_->Compile(); audio_backend_->SetViewerNode(viewer_node_);
//audio_backend_->SetViewerNode(viewer_node_); audio_backend_->SetParameters(audio_params_);
//audio_renderer_->SetParameters(AudioRenderingParams(viewer_node_->audio_params(), SAMPLE_FMT_FLT));
// Connect to renderers // Connect to renderers
connect(video_backend_, SIGNAL(CachedFrameReady(const rational&, QVariant)), this, SLOT(FrameRendered(const rational&, QVariant))); connect(video_backend_, SIGNAL(CachedFrameReady(const rational&, QVariant)), this, SLOT(FrameRendered(const rational&, QVariant)));
connect(audio_backend_, SIGNAL(QueueComplete()), this, SLOT(AudioRendered()));
// Create renderers // Create renderers
waiting_for_frame_ = 0; waiting_for_frame_ = 0;
// Open encoder // Open encoder and wait for result
encoder_->Open(); 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 QMetaObject::invokeMethod(encoder_,
video_backend_->InvalidateCache(0, viewer_node_->Length()); "Open",
//viewer_node_->InvalidateCache(0, viewer_node_->Length(), viewer_node_->samples_input()); Qt::QueuedConnection);
} }
void Exporter::SetExportMessage(const QString &s) void Exporter::SetExportMessage(const QString &s)
@@ -74,14 +84,14 @@ void Exporter::ExportSucceeded()
Cleanup(); Cleanup();
video_backend_->deleteLater(); video_backend_->deleteLater();
audio_backend_->deleteLater();
export_status_ = true; export_status_ = true;
encoder_->Close(); connect(encoder_, SIGNAL(Closed()), this, SLOT(EncoderClosed()));
emit ProgressChanged(100); QMetaObject::invokeMethod(encoder_,
emit ExportEnded(); "Close",
Qt::QueuedConnection);
} }
void Exporter::ExportFailed() void Exporter::ExportFailed()
@@ -121,9 +131,12 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
frame->set_timestamp(waiting_for_frame_); frame->set_timestamp(waiting_for_frame_);
// Encode (may require re-associating alpha?) // 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 // Calculate progress
int progress = qRound(100.0 * (waiting_for_frame_.toDouble() / viewer_node_->Length().toDouble())); 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_)); } while (cached_frames_.contains(waiting_for_frame_));
if (waiting_for_frame_ >= viewer_node_->Length()) { if (waiting_for_frame_ >= viewer_node_->Length()) {
ExportSucceeded(); S ExportSucceeded();
} }
} else { } else {
cached_frames_.insert(time, value); 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 Q_OBJECT
public: public:
Exporter(ViewerOutput* viewer, Exporter(ViewerOutput* viewer,
const VideoRenderingParams& params, const VideoRenderingParams& video_params,
const AudioRenderingParams& audio_params,
const QMatrix4x4& transform, const QMatrix4x4& transform,
ColorProcessorPtr color_processor, ColorProcessorPtr color_processor,
EncoderPtr encoder, Encoder* encoder,
QObject* parent = nullptr); QObject* parent = nullptr);
bool GetExportStatus() const; bool GetExportStatus() const;
@@ -49,7 +50,8 @@ protected:
ViewerOutput* viewer_node_; ViewerOutput* viewer_node_;
// Export parameters // Export parameters
VideoRenderingParams params_; VideoRenderingParams video_params_;
AudioRenderingParams audio_params_;
// Export transform // Export transform
QMatrix4x4 transform_; QMatrix4x4 transform_;
@@ -61,7 +63,7 @@ private:
ColorProcessorPtr color_processor_; ColorProcessorPtr color_processor_;
EncoderPtr encoder_; Encoder* encoder_;
bool export_status_; bool export_status_;
@@ -74,6 +76,14 @@ private:
private slots: private slots:
void FrameRendered(const rational& time, QVariant value); void FrameRendered(const rational& time, QVariant value);
void AudioRendered();
void EncoderOpenedSuccessfully();
void EncoderOpenFailed();
void EncoderClosed();
}; };
#endif // EXPORTER_H #endif // EXPORTER_H
+7 -7
View File
@@ -3,8 +3,8 @@
#include "render/backend/opengl/functions.h" #include "render/backend/opengl/functions.h"
#include "render/pixelservice.h" #include "render/pixelservice.h"
OpenGLExporter::OpenGLExporter(ViewerOutput* viewer, const VideoRenderingParams& params, const QMatrix4x4 &transform, ColorProcessorPtr color_processor, EncoderPtr encoder, QObject* 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, params, transform, color_processor, encoder, parent) Exporter(viewer, video_params, audio_params, transform, color_processor, encoder, parent)
{ {
} }
@@ -24,7 +24,7 @@ bool OpenGLExporter::Initialize()
buffer_.Create(ctx); buffer_.Create(ctx);
texture_ = std::make_shared<OpenGLTexture>(); 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(); pipeline_ = OpenGLShader::CreateDefault();
@@ -40,9 +40,9 @@ void OpenGLExporter::Cleanup()
FramePtr OpenGLExporter::TextureToFrame(const QVariant& texture) FramePtr OpenGLExporter::TextureToFrame(const QVariant& texture)
{ {
FramePtr frame = Frame::Create(); FramePtr frame = Frame::Create();
frame->set_width(params_.width()); frame->set_width(video_params_.width());
frame->set_height(params_.height()); frame->set_height(video_params_.height());
frame->set_format(params_.format()); frame->set_format(video_params_.format());
frame->allocate(); frame->allocate();
// Blit for transform if the width/height are different // Blit for transform if the width/height are different
@@ -65,7 +65,7 @@ FramePtr OpenGLExporter::TextureToFrame(const QVariant& texture)
buffer_.Attach(texture_); buffer_.Attach(texture_);
buffer_.Bind(); buffer_.Bind();
PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(params_.format()); PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(video_params_.format());
f->glReadPixels(0, f->glReadPixels(0,
0, 0,
+3 -2
View File
@@ -11,10 +11,11 @@ class OpenGLExporter : public Exporter
{ {
public: public:
OpenGLExporter(ViewerOutput* viewer, OpenGLExporter(ViewerOutput* viewer,
const VideoRenderingParams& params, const VideoRenderingParams& video_params,
const AudioRenderingParams& audio_params,
const QMatrix4x4& transform, const QMatrix4x4& transform,
ColorProcessorPtr color_processor, ColorProcessorPtr color_processor,
EncoderPtr encoder, Encoder* encoder,
QObject* parent = nullptr); QObject* parent = nullptr);
virtual ~OpenGLExporter() override; virtual ~OpenGLExporter() override;
+5 -1
View File
@@ -210,12 +210,16 @@ void RenderBackend::DisconnectViewer(ViewerOutput *node)
void RenderBackend::CacheNext() void RenderBackend::CacheNext()
{ {
if (!Init() if (!Init()
|| cache_queue_.isEmpty()
|| !ViewerIsConnected() || !ViewerIsConnected()
|| !CanRender()) { || !CanRender()) {
return; return;
} }
if (cache_queue_.isEmpty()) {
emit QueueComplete();
return;
}
if ((input_update_queued_ || recompile_queued_) && !AllProcessorsAreAvailable()) { if ((input_update_queued_ || recompile_queued_) && !AllProcessorsAreAvailable()) {
return; return;
} }
+3
View File
@@ -36,6 +36,9 @@ public slots:
void Decompile(); void Decompile();
signals:
void QueueComplete();
protected: protected:
void RegenerateCacheID(); void RegenerateCacheID();