decoder: move conform function to base class

There's no reason the Conform() function can't live in the base class since its
functionality isn't necessarily specific to FFmpeg.
This commit is contained in:
itsmattkc
2020-02-19 12:28:37 +11:00
parent 53551246da
commit 08e6c6ffc0
6 changed files with 166 additions and 159 deletions
+139 -4
View File
@@ -24,8 +24,11 @@
#include <QDebug>
#include <QFileInfo>
#include "codec/ffmpeg/ffmpegcommon.h"
#include "codec/ffmpeg/ffmpegdecoder.h"
#include "codec/oiio/oiiodecoder.h"
#include "codec/waveinput.h"
#include "codec/waveoutput.h"
#include "render/indexmanager.h"
#include "task/index/index.h"
#include "task/taskmanager.h"
@@ -172,12 +175,144 @@ DecoderPtr Decoder::CreateFromID(const QString &id)
void Decoder::Conform(const AudioRenderingParams &params, const QAtomicInt* cancelled)
{
Q_UNUSED(params)
qCritical() << "Conform called on an audio decoder that does not have a handler for it:" << id();
abort();
if (stream()->type() != Stream::kAudio) {
// Nothing to be done
return;
}
Index(cancelled);
// Get indexed WAV file
WaveInput input(GetIndexFilename());
// FIXME: No handling if input failed to open/is corrupt
if (input.open()) {
// If the parameters are equal, nothing to be done
// FIXME: Technically we only need to conform if the SAMPLE RATE is not equal. Format and channel layout conversion
// could be done on the fly so we could perhaps conform less often at some point.
if (input.params() == params) {
input.close();
return;
}
// Otherwise, let's start converting the format
// Generate destination filename for this conversion to see if it exists
QString conformed_fn = GetConformedFilename(params);
if (QFileInfo::exists(conformed_fn)) {
// We must have already conformed this format
input.close();
return;
}
// Set up resampler
SwrContext* resampler = swr_alloc_set_opts(nullptr,
static_cast<int64_t>(params.channel_layout()),
FFmpegCommon::GetFFmpegSampleFormat(params.format()),
params.sample_rate(),
static_cast<int64_t>(input.params().channel_layout()),
FFmpegCommon::GetFFmpegSampleFormat(input.params().format()),
input.params().sample_rate(),
0,
nullptr);
swr_init(resampler);
WaveOutput conformed_output(conformed_fn, params);
if (!conformed_output.open()) {
qWarning() << "Failed to open conformed output:" << conformed_fn;
input.close();
return;
}
// Convert one second of audio at a time
int input_buffer_sz = input.params().time_to_bytes(1);
while (!input.at_end()) {
if (cancelled && *cancelled) {
break;
}
// Read up to one second of audio from WAV file
QByteArray read_samples = input.read(input_buffer_sz);
// Determine how many samples this is
int in_sample_count = input.params().bytes_to_samples(read_samples.size());
ConformInternal(resampler, &conformed_output, read_samples.data(), in_sample_count);
}
// Flush resampler
ConformInternal(resampler, &conformed_output, nullptr, 0);
// Clean up
swr_free(&resampler);
conformed_output.close();
input.close();
// If we cancelled, the conform didn't finish so remove it
if (cancelled && *cancelled) {
QFile(conformed_fn).remove();
}
} else {
qWarning() << "Failed to conform file:" << stream()->footage()->filename();
}
}
void Decoder::Index(const QAtomicInt *cancelled)
void Decoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const char* in_data, int in_sample_count)
{
// Determine how many samples the output will be
int out_sample_count = swr_get_out_samples(resampler, in_sample_count);
// Allocate array for the amount of samples we'll need
QByteArray out_samples;
out_samples.resize(output->params().samples_to_bytes(out_sample_count));
char* out_data = out_samples.data();
// Convert samples
int convert_count = swr_convert(resampler,
reinterpret_cast<uint8_t**>(&out_data),
out_sample_count,
reinterpret_cast<const uint8_t**>(&in_data),
in_sample_count);
if (convert_count != out_sample_count) {
out_samples.resize(output->params().samples_to_bytes(convert_count));
}
output->write(out_samples);
}
QString Decoder::GetConformedFilename(const AudioRenderingParams &params)
{
QString index_fn = GetIndexFilename();
WaveInput input(GetIndexFilename());
// FIXME: No handling if input failed to open/is corrupt
if (input.open()) {
// If the parameters are equal, nothing to be done
AudioRenderingParams index_params = input.params();
input.close();
if (index_params == params) {
// Source file matches perfectly, no conform required
return index_fn;
}
}
index_fn.append('.');
index_fn.append(QString::number(params.sample_rate()));
index_fn.append('.');
index_fn.append(QString::number(params.format()));
index_fn.append('.');
index_fn.append(QString::number(params.channel_layout()));
return index_fn;
}
void Decoder::Index(const QAtomicInt *)
{
}
+19 -1
View File
@@ -25,6 +25,7 @@
#include <stdint.h>
#include "codec/frame.h"
#include "codec/waveoutput.h"
#include "common/constructors.h"
#include "common/rational.h"
#include "project/item/footage/footage.h"
@@ -32,6 +33,8 @@
class Decoder;
using DecoderPtr = std::shared_ptr<Decoder>;
struct SwrContext;
/**
* @brief A decoder's is the main class for bringing external media into Olive
*
@@ -221,7 +224,7 @@ public:
* All audio decoders must override this. It's not pure since video decoders don't need to use this, but default
* behavior will abort since it should never be called.
*/
virtual void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled);
void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled);
/**
* @brief Create an index for this media
@@ -243,10 +246,25 @@ signals:
protected:
void SignalIndexProgress(const int64_t& ts);
/**
* @brief Returns the filename for the index
*
* Retrieves the absolute filename of the index file for this stream. Decoder must be open for this to work correctly.
*/
virtual QString GetIndexFilename() = 0;
/**
* @brief Get the destination filename of an audio stream conformed to a set of parameters
*/
QString GetConformedFilename(const AudioRenderingParams &params);
bool open_;
private:
void ConformInternal(SwrContext *resampler, WaveOutput *output, const char *in_data, int in_sample_count);
StreamPtr stream_;
};
Q_DECLARE_METATYPE(Decoder::RetrieveState)
-139
View File
@@ -416,93 +416,6 @@ QString FFmpegDecoder::id()
return "ffmpeg";
}
void FFmpegDecoder::Conform(const AudioRenderingParams &params, const QAtomicInt* cancelled)
{
if (avstream_->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
// Nothing to be done
return;
}
Index(cancelled);
// Get indexed WAV file
WaveInput input(GetIndexFilename());
// FIXME: No handling if input failed to open/is corrupt
if (input.open()) {
// If the parameters are equal, nothing to be done
// FIXME: Technically we only need to conform if the SAMPLE RATE is not equal. Format and channel layout conversion
// could be done on the fly so we could perhaps conform less often at some point.
if (input.params() == params) {
input.close();
return;
}
// Otherwise, let's start converting the format
// Generate destination filename for this conversion to see if it exists
QString conformed_fn = GetConformedFilename(params);
if (QFileInfo::exists(conformed_fn)) {
// We must have already conformed this format
input.close();
return;
}
// Set up resampler
SwrContext* resampler = swr_alloc_set_opts(nullptr,
static_cast<int64_t>(params.channel_layout()),
FFmpegCommon::GetFFmpegSampleFormat(params.format()),
params.sample_rate(),
static_cast<int64_t>(input.params().channel_layout()),
FFmpegCommon::GetFFmpegSampleFormat(input.params().format()),
input.params().sample_rate(),
0,
nullptr);
swr_init(resampler);
WaveOutput conformed_output(conformed_fn, params);
if (!conformed_output.open()) {
qWarning() << "Failed to open conformed output:" << conformed_fn;
input.close();
return;
}
// Convert one second of audio at a time
int input_buffer_sz = input.params().time_to_bytes(1);
while (!input.at_end()) {
if (cancelled && *cancelled) {
break;
}
// Read up to one second of audio from WAV file
QByteArray read_samples = input.read(input_buffer_sz);
// Determine how many samples this is
int in_sample_count = input.params().bytes_to_samples(read_samples.size());
ConformInternal(resampler, &conformed_output, read_samples.data(), in_sample_count);
}
// Flush resampler
ConformInternal(resampler, &conformed_output, nullptr, 0);
// Clean up
swr_free(&resampler);
conformed_output.close();
input.close();
// If we cancelled, the conform didn't finish so remove it
if (cancelled && *cancelled) {
QFile(conformed_fn).remove();
}
} else {
qWarning() << "Failed to conform file:" << stream()->footage()->filename();
}
}
bool FFmpegDecoder::SupportsVideo()
{
return true;
@@ -518,31 +431,6 @@ void FFmpegDecoder::SetMultithreading(bool e)
multithreading_ = e;
}
void FFmpegDecoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const char* in_data, int in_sample_count)
{
// Determine how many samples the output will be
int out_sample_count = swr_get_out_samples(resampler, in_sample_count);
// Allocate array for the amount of samples we'll need
QByteArray out_samples;
out_samples.resize(output->params().samples_to_bytes(out_sample_count));
char* out_data = out_samples.data();
// Convert samples
int convert_count = swr_convert(resampler,
reinterpret_cast<uint8_t**>(&out_data),
out_sample_count,
reinterpret_cast<const uint8_t**>(&in_data),
in_sample_count);
if (convert_count != out_sample_count) {
out_samples.resize(output->params().samples_to_bytes(convert_count));
}
output->write(out_samples);
}
bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
{
if (open_) {
@@ -742,33 +630,6 @@ QString FFmpegDecoder::GetIndexFilename()
.append(QString::number(avstream_->index));
}
QString FFmpegDecoder::GetConformedFilename(const AudioRenderingParams &params)
{
QString index_fn = GetIndexFilename();
WaveInput input(GetIndexFilename());
// FIXME: No handling if input failed to open/is corrupt
if (input.open()) {
// If the parameters are equal, nothing to be done
AudioRenderingParams index_params = input.params();
input.close();
if (index_params == params) {
// Source file matches perfectly, no conform required
return index_fn;
}
}
index_fn.append('.');
index_fn.append(QString::number(params.sample_rate()));
index_fn.append('.');
index_fn.append(QString::number(params.format()));
index_fn.append('.');
index_fn.append(QString::number(params.channel_layout()));
return index_fn;
}
void FFmpegDecoder::UnconditionalAudioIndex(AVPacket *pkt, AVFrame *frame, const QAtomicInt* cancelled)
{
// Iterate through each audio frame and extract the PCM data
+1 -15
View File
@@ -57,8 +57,6 @@ public:
virtual QString id() override;
virtual void Conform(const AudioRenderingParams& params, const QAtomicInt *cancelled) override;
virtual bool SupportsVideo() override;
virtual bool SupportsAudio() override;
@@ -67,8 +65,6 @@ public:
virtual void Index(const QAtomicInt *cancelled) override;
private:
void ConformInternal(SwrContext *resampler, WaveOutput *output, const char *in_data, int in_sample_count);
/**
* @brief Handle an error
*
@@ -97,17 +93,7 @@ private:
*/
int GetFrame(AVPacket* pkt, AVFrame* frame);
/**
* @brief Returns the filename for the index
*
* Retrieves the absolute filename of the index file for this stream. Decoder must be open for this to work correctly.
*/
QString GetIndexFilename();
/**
* @brief Get the destination filename of an audio stream conformed to a set of parameters
*/
QString GetConformedFilename(const AudioRenderingParams &params);
virtual QString GetIndexFilename() override;
void UnconditionalAudioIndex(AVPacket* pkt, AVFrame* frame, const QAtomicInt* cancelled);
void UnconditionalVideoIndex(AVPacket* pkt, AVFrame* frame, const QAtomicInt* cancelled);
+5
View File
@@ -187,3 +187,8 @@ bool OIIODecoder::SupportsVideo()
{
return true;
}
QString OIIODecoder::GetIndexFilename()
{
return QString();
}
+2
View File
@@ -44,6 +44,8 @@ public:
virtual bool SupportsVideo() override;
virtual QString GetIndexFilename() override;
private:
#if OIIO_VERSION < 10903
OIIO::ImageInput* image_;