began work on exr proxy task
This commit is contained in:
@@ -319,7 +319,11 @@ QString Decoder::GetConformedFilename(const AudioRenderingParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
void Decoder::Index(const QAtomicInt *)
|
||||
void Decoder::ProxyVideo(const QAtomicInt *, int )
|
||||
{
|
||||
}
|
||||
|
||||
void Decoder::ProxyAudio(const QAtomicInt *)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
+23
-18
@@ -216,37 +216,41 @@ public:
|
||||
static DecoderPtr CreateFromID(const QString& id);
|
||||
|
||||
/**
|
||||
* @brief Conform an audio stream to match certain parameters (audio only)
|
||||
* @brief AUDIO ONLY: Conform an audio stream to match certain parameters
|
||||
*
|
||||
* Resamples and converts the currently open audio to match the params. If the audio doesn't need conforming (e.g.
|
||||
* audio params already match or a conformed match already exists), this function will return immediately. Otherwise
|
||||
* it will block the calling thread until the conform is complete. This function should therefore only be called
|
||||
* from a background render thread.
|
||||
* Resamples and converts the currently open audio to match the params. If the audio doesn't need
|
||||
* conforming (e.g. audio params already match or a conformed match already exists), this function
|
||||
* will return immediately. Otherwise it will block the calling thread until the conform is
|
||||
* complete. This function should therefore only be called from a background render thread.
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled);
|
||||
|
||||
/**
|
||||
* @brief Create an index for this media
|
||||
*
|
||||
* Indexes are used to improve speed and reliability of imported media. Calling Retrieve() will automatically check
|
||||
* for an index and create one if it doesn't exist.
|
||||
*
|
||||
* Indexing is slow so it's recommended to do it in a background thread. Index() must be called while the Decoder is
|
||||
* open, and does not automatically call Open() and Close() the Decoder. The caller must call thse manually.
|
||||
* @brief VIDEO ONLY: Produce a compressed EXR proxy with the specified divider
|
||||
*/
|
||||
virtual void Index(const QAtomicInt* cancelled);
|
||||
virtual void ProxyVideo(const QAtomicInt* cancelled, int divider);
|
||||
|
||||
/**
|
||||
* @brief AUDIO ONLY: Returns whether a cached transcode of this audio matching the specified params already exists
|
||||
* @brief AUDIO ONLY: Produces a complete PCM extraction of the audio stream
|
||||
*
|
||||
* Internally, our render engine only deals with PCM since it provides the least headaches and
|
||||
* modern computers have the processing power to do it.
|
||||
*/
|
||||
virtual void ProxyAudio(const QAtomicInt* cancelled);
|
||||
|
||||
/**
|
||||
* @brief AUDIO ONLY: Returns whether a transcode of this audio matching the specified params
|
||||
* already exists
|
||||
*/
|
||||
bool HasConformedVersion(const AudioRenderingParams& params);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if available
|
||||
* @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if
|
||||
* available
|
||||
*/
|
||||
void IndexProgress(int);
|
||||
|
||||
@@ -256,7 +260,8 @@ protected:
|
||||
/**
|
||||
* @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.
|
||||
* Retrieves the absolute filename of the index file for this stream. Decoder must be open for
|
||||
* this to work correctly.
|
||||
*/
|
||||
virtual QString GetIndexFilename() = 0;
|
||||
|
||||
|
||||
+156
-154
@@ -623,24 +623,170 @@ void FFmpegDecoder::Error(const QString &s)
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
void FFmpegDecoder::Index(const QAtomicInt* cancelled)
|
||||
void FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider)
|
||||
{
|
||||
// Iterate through each video frame transcode each frame to compressed EXR
|
||||
QMutexLocker locker(stream()->index_process_lock());
|
||||
|
||||
if (stream()->type() == Stream::kAudio) {
|
||||
QByteArray fn_bytes = stream()->footage()->filename().toUtf8();
|
||||
|
||||
if (QFileInfo::exists(GetIndexFilename())) {
|
||||
WaveInput input(GetIndexFilename());
|
||||
if (input.open()) {
|
||||
std::static_pointer_cast<AudioStream>(stream())->set_index_done(true);
|
||||
std::static_pointer_cast<AudioStream>(stream())->set_index_length(input.params().bytes_to_time(input.data_length()));
|
||||
FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index());
|
||||
|
||||
input.close();
|
||||
|
||||
}
|
||||
|
||||
void FFmpegDecoder::ProxyAudio(const QAtomicInt *cancelled)
|
||||
{
|
||||
// Iterate through each audio frame and extract the PCM data
|
||||
QMutexLocker locker(stream()->index_process_lock());
|
||||
|
||||
if (QFileInfo::exists(GetIndexFilename())) {
|
||||
WaveInput input(GetIndexFilename());
|
||||
if (input.open()) {
|
||||
std::static_pointer_cast<AudioStream>(stream())->set_index_done(true);
|
||||
std::static_pointer_cast<AudioStream>(stream())->set_index_length(input.params().bytes_to_time(input.data_length()));
|
||||
|
||||
input.close();
|
||||
}
|
||||
} else {
|
||||
QByteArray fn_bytes = stream()->footage()->filename().toUtf8();
|
||||
|
||||
FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index());
|
||||
|
||||
uint64_t channel_layout = index_instance.stream()->codecpar->channel_layout;
|
||||
if (!channel_layout) {
|
||||
if (!index_instance.stream()->codecpar->channels) {
|
||||
// No channel data - we can't do anything with this
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
UnconditionalAudioIndex(cancelled);
|
||||
|
||||
channel_layout = static_cast<uint64_t>(av_get_default_channel_layout(index_instance.stream()->codecpar->channels));
|
||||
}
|
||||
|
||||
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream());
|
||||
|
||||
// This should be unnecessary, but just in case...
|
||||
audio_stream->clear_index();
|
||||
|
||||
SwrContext* resampler = nullptr;
|
||||
AVSampleFormat src_sample_fmt = static_cast<AVSampleFormat>(index_instance.stream()->codecpar->format);
|
||||
AVSampleFormat dst_sample_fmt;
|
||||
|
||||
// We don't use planar types internally, so if this is a planar format convert it now
|
||||
if (av_sample_fmt_is_planar(src_sample_fmt)) {
|
||||
dst_sample_fmt = av_get_packed_sample_fmt(src_sample_fmt);
|
||||
|
||||
resampler = swr_alloc_set_opts(nullptr,
|
||||
static_cast<int64_t>(index_instance.stream()->codecpar->channel_layout),
|
||||
dst_sample_fmt,
|
||||
index_instance.stream()->codecpar->sample_rate,
|
||||
static_cast<int64_t>(index_instance.stream()->codecpar->channel_layout),
|
||||
src_sample_fmt,
|
||||
index_instance.stream()->codecpar->sample_rate,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
swr_init(resampler);
|
||||
} else {
|
||||
dst_sample_fmt = src_sample_fmt;
|
||||
}
|
||||
|
||||
AudioRenderingParams wave_params(index_instance.stream()->codecpar->sample_rate,
|
||||
channel_layout,
|
||||
FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt));
|
||||
WaveOutput wave_out(GetIndexFilename(), wave_params);
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
int ret;
|
||||
|
||||
if (wave_out.open()) {
|
||||
bool success = false;
|
||||
|
||||
while (true) {
|
||||
// Check if we have a `cancelled` ptr and its value
|
||||
if (cancelled && *cancelled) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = index_instance.GetFrame(pkt, frame);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
success = true;
|
||||
} else {
|
||||
char err_str[50];
|
||||
av_strerror(ret, err_str, 50);
|
||||
qWarning() << "Failed to index:" << ret << err_str;
|
||||
}
|
||||
break;
|
||||
|
||||
} else {
|
||||
char* data;
|
||||
int nb_samples;
|
||||
|
||||
if (resampler) {
|
||||
|
||||
nb_samples = swr_get_out_samples(resampler, frame->nb_samples);
|
||||
|
||||
data = new char[wave_params.samples_to_bytes(nb_samples)];
|
||||
|
||||
// We must need to resample this (mainly just convert from planar to packed if necessary)
|
||||
nb_samples = swr_convert(resampler,
|
||||
reinterpret_cast<uint8_t**>(&data),
|
||||
nb_samples,
|
||||
const_cast<const uint8_t**>(frame->data),
|
||||
frame->nb_samples);
|
||||
|
||||
if (nb_samples < 0) {
|
||||
char err_str[50];
|
||||
av_strerror(nb_samples, err_str, 50);
|
||||
qWarning() << "libswresample failed with error:" << nb_samples << err_str;
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// No resampling required, we can write directly from the frame buffer
|
||||
data = reinterpret_cast<char*>(frame->data[0]);
|
||||
nb_samples = frame->nb_samples;
|
||||
|
||||
}
|
||||
|
||||
// Write packed WAV data to the disk cache
|
||||
wave_out.write(data, wave_params.samples_to_bytes(nb_samples));
|
||||
|
||||
audio_stream->set_index_length(wave_params.bytes_to_time(wave_out.data_length()));
|
||||
|
||||
// If we allocated an output for the resampler, delete it here
|
||||
if (data != reinterpret_cast<char*>(frame->data[0])) {
|
||||
delete [] data;
|
||||
}
|
||||
|
||||
SignalIndexProgress(frame->pts);
|
||||
}
|
||||
}
|
||||
|
||||
wave_out.close();
|
||||
|
||||
if (success) {
|
||||
audio_stream->set_index_done(true);
|
||||
} else {
|
||||
// Audio index didn't complete, delete it
|
||||
QFile(GetIndexFilename()).remove();
|
||||
audio_stream->clear_index();
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to open WAVE output for indexing";
|
||||
}
|
||||
|
||||
if (resampler != nullptr) {
|
||||
swr_free(&resampler);
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,150 +801,6 @@ int FFmpegDecoder::GetScaledDimension(int dim, int divider)
|
||||
return dim / divider;
|
||||
}
|
||||
|
||||
void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled)
|
||||
{
|
||||
// Iterate through each audio frame and extract the PCM data
|
||||
|
||||
QByteArray fn_bytes = stream()->footage()->filename().toUtf8();
|
||||
|
||||
FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index());
|
||||
|
||||
uint64_t channel_layout = index_instance.stream()->codecpar->channel_layout;
|
||||
if (!channel_layout) {
|
||||
if (!index_instance.stream()->codecpar->channels) {
|
||||
// No channel data - we can't do anything with this
|
||||
return;
|
||||
}
|
||||
|
||||
channel_layout = static_cast<uint64_t>(av_get_default_channel_layout(index_instance.stream()->codecpar->channels));
|
||||
}
|
||||
|
||||
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream());
|
||||
|
||||
// This should be unnecessary, but just in case...
|
||||
audio_stream->clear_index();
|
||||
|
||||
SwrContext* resampler = nullptr;
|
||||
AVSampleFormat src_sample_fmt = static_cast<AVSampleFormat>(index_instance.stream()->codecpar->format);
|
||||
AVSampleFormat dst_sample_fmt;
|
||||
|
||||
// We don't use planar types internally, so if this is a planar format convert it now
|
||||
if (av_sample_fmt_is_planar(src_sample_fmt)) {
|
||||
dst_sample_fmt = av_get_packed_sample_fmt(src_sample_fmt);
|
||||
|
||||
resampler = swr_alloc_set_opts(nullptr,
|
||||
static_cast<int64_t>(index_instance.stream()->codecpar->channel_layout),
|
||||
dst_sample_fmt,
|
||||
index_instance.stream()->codecpar->sample_rate,
|
||||
static_cast<int64_t>(index_instance.stream()->codecpar->channel_layout),
|
||||
src_sample_fmt,
|
||||
index_instance.stream()->codecpar->sample_rate,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
swr_init(resampler);
|
||||
} else {
|
||||
dst_sample_fmt = src_sample_fmt;
|
||||
}
|
||||
|
||||
AudioRenderingParams wave_params(index_instance.stream()->codecpar->sample_rate,
|
||||
channel_layout,
|
||||
FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt));
|
||||
WaveOutput wave_out(GetIndexFilename(), wave_params);
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
int ret;
|
||||
|
||||
if (wave_out.open()) {
|
||||
bool success = false;
|
||||
|
||||
while (true) {
|
||||
// Check if we have a `cancelled` ptr and its value
|
||||
if (cancelled && *cancelled) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = index_instance.GetFrame(pkt, frame);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
success = true;
|
||||
} else {
|
||||
char err_str[50];
|
||||
av_strerror(ret, err_str, 50);
|
||||
qWarning() << "Failed to index:" << ret << err_str;
|
||||
}
|
||||
break;
|
||||
|
||||
} else {
|
||||
char* data;
|
||||
int nb_samples;
|
||||
|
||||
if (resampler) {
|
||||
|
||||
nb_samples = swr_get_out_samples(resampler, frame->nb_samples);
|
||||
|
||||
data = new char[wave_params.samples_to_bytes(nb_samples)];
|
||||
|
||||
// We must need to resample this (mainly just convert from planar to packed if necessary)
|
||||
nb_samples = swr_convert(resampler,
|
||||
reinterpret_cast<uint8_t**>(&data),
|
||||
nb_samples,
|
||||
const_cast<const uint8_t**>(frame->data),
|
||||
frame->nb_samples);
|
||||
|
||||
if (nb_samples < 0) {
|
||||
char err_str[50];
|
||||
av_strerror(nb_samples, err_str, 50);
|
||||
qWarning() << "libswresample failed with error:" << nb_samples << err_str;
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// No resampling required, we can write directly from the frame buffer
|
||||
data = reinterpret_cast<char*>(frame->data[0]);
|
||||
nb_samples = frame->nb_samples;
|
||||
|
||||
}
|
||||
|
||||
// Write packed WAV data to the disk cache
|
||||
wave_out.write(data, wave_params.samples_to_bytes(nb_samples));
|
||||
|
||||
audio_stream->set_index_length(wave_params.bytes_to_time(wave_out.data_length()));
|
||||
|
||||
// If we allocated an output for the resampler, delete it here
|
||||
if (data != reinterpret_cast<char*>(frame->data[0])) {
|
||||
delete [] data;
|
||||
}
|
||||
|
||||
SignalIndexProgress(frame->pts);
|
||||
}
|
||||
}
|
||||
|
||||
wave_out.close();
|
||||
|
||||
if (success) {
|
||||
audio_stream->set_index_done(true);
|
||||
} else {
|
||||
// Audio index didn't complete, delete it
|
||||
QFile(GetIndexFilename()).remove();
|
||||
audio_stream->clear_index();
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to open WAVE output for indexing";
|
||||
}
|
||||
|
||||
if (resampler != nullptr) {
|
||||
swr_free(&resampler);
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
|
||||
int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame)
|
||||
{
|
||||
bool eof = false;
|
||||
|
||||
@@ -144,7 +144,8 @@ public:
|
||||
virtual bool SupportsVideo() override;
|
||||
virtual bool SupportsAudio() override;
|
||||
|
||||
virtual void Index(const QAtomicInt *cancelled) override;
|
||||
virtual void ProxyVideo(const QAtomicInt* cancelled, int divider) override;
|
||||
virtual void ProxyAudio(const QAtomicInt* cancelled) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -168,8 +169,6 @@ private:
|
||||
|
||||
virtual QString GetIndexFilename() override;
|
||||
|
||||
void UnconditionalAudioIndex(const QAtomicInt* cancelled);
|
||||
|
||||
void ClearResources();
|
||||
|
||||
void InitScaler(int divider);
|
||||
|
||||
Reference in New Issue
Block a user