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:
+40
-4
@@ -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<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
@@ -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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "ffmpegencoder.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#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<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,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);
|
||||
|
||||
@@ -140,4 +140,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(FramePtr)
|
||||
|
||||
#endif // FRAME_H
|
||||
|
||||
@@ -341,6 +341,8 @@ void Core::DeclareTypesForQt()
|
||||
qRegisterMetaType<OpenGLTexturePtr>();
|
||||
qRegisterMetaType<OpenGLTextureCache::ReferencePtr>();
|
||||
qRegisterMetaType<NodeValueTable>();
|
||||
qRegisterMetaType<FramePtr>();
|
||||
qRegisterMetaType<AudioRenderingParams>();
|
||||
}
|
||||
|
||||
void Core::StartGUI(bool full_screen)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -46,4 +46,6 @@ private:
|
||||
SampleFormat format_;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(AudioRenderingParams)
|
||||
|
||||
#endif // AUDIOPARAMS_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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ public slots:
|
||||
|
||||
void Decompile();
|
||||
|
||||
signals:
|
||||
void QueueComplete();
|
||||
|
||||
protected:
|
||||
void RegenerateCacheID();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user