implement entire project in nodes
Project items are now represented directly in nodes opening up more versatility and possibilities. This is the first iteration of this and will be buggy. Need to test thoroughly.
This commit is contained in:
+40
-84
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "codec/ffmpeg/ffmpegdecoder.h"
|
||||
#include "codec/oiio/oiiodecoder.h"
|
||||
@@ -43,18 +42,19 @@ QMutex Decoder::currently_conforming_mutex_;
|
||||
QWaitCondition Decoder::currently_conforming_wait_cond_;
|
||||
QVector<Decoder::CurrentlyConforming> Decoder::currently_conforming_;
|
||||
|
||||
Decoder::Decoder() :
|
||||
stream_(nullptr)
|
||||
const rational Decoder::kAnyTimecode = RATIONAL_MIN;
|
||||
|
||||
Decoder::Decoder()
|
||||
{
|
||||
}
|
||||
|
||||
bool Decoder::Open(Stream *fs)
|
||||
bool Decoder::Open(const CodecStream &stream)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (stream_) {
|
||||
if (stream_.IsValid()) {
|
||||
// Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not.
|
||||
if (stream_ == fs) {
|
||||
if (stream_ == stream) {
|
||||
return true;
|
||||
} else {
|
||||
qWarning() << "Tried to open a decoder that was already open with another stream";
|
||||
@@ -62,27 +62,29 @@ bool Decoder::Open(Stream *fs)
|
||||
}
|
||||
} else {
|
||||
// Stream was not open, try opening it now
|
||||
if (fs == nullptr) {
|
||||
if (!stream.IsValid()) {
|
||||
// Cannot open null stream
|
||||
qCritical() << "Decoder attempted to open null stream";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fs->footage()->decoder() != id()) {
|
||||
qCritical() << "Tried to open footage in incorrect decoder";
|
||||
if (!stream.Exists()) {
|
||||
// Cannot open file that doesn't exist
|
||||
qCritical() << "Decoder attempted to open file that doesn't exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set stream
|
||||
stream_ = fs;
|
||||
stream_ = stream;
|
||||
|
||||
// Try open internal
|
||||
if (OpenInternal()) {
|
||||
return true;
|
||||
} else {
|
||||
// Unset stream
|
||||
qCritical() << "Failed to open" << stream_.filename() << "stream" << stream_.stream();
|
||||
CloseInternal();
|
||||
stream_ = nullptr;
|
||||
stream_.Reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +94,7 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (!stream_) {
|
||||
if (!stream_.IsValid()) {
|
||||
qCritical() << "Can't retrieve video on a closed decoder";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -102,19 +104,14 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stream_->type() != Stream::kVideo) {
|
||||
qCritical() << "Tried to retrieve video from a non-video stream";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return RetrieveVideoInternal(timecode, divider);
|
||||
}
|
||||
|
||||
SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QAtomicInt *cancelled)
|
||||
SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, const QAtomicInt *cancelled)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (!stream_) {
|
||||
if (!stream_.IsValid()) {
|
||||
qCritical() << "Can't retrieve audio on a closed decoder";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -124,13 +121,8 @@ SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stream_->type() != Stream::kAudio) {
|
||||
qCritical() << "Tried to retrieve audio from a non-audio stream";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Determine if we already have a conformed version
|
||||
QString conform_filename = GetConformedFilename(params);
|
||||
QString conform_filename = GetConformedFilename(cache_path, params);
|
||||
CurrentlyConforming want_conform = {stream_, params};
|
||||
|
||||
currently_conforming_mutex_.lock();
|
||||
@@ -179,9 +171,9 @@ void Decoder::Close()
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (stream_) {
|
||||
if (stream_.IsValid()) {
|
||||
CloseInternal();
|
||||
stream_ = nullptr;
|
||||
stream_.Reset();
|
||||
} else {
|
||||
qWarning() << "Tried to close a decoder that wasn't open";
|
||||
}
|
||||
@@ -191,7 +183,7 @@ void Decoder::Close()
|
||||
* DECODER STATIC PUBLIC MEMBERS
|
||||
*/
|
||||
|
||||
QVector<DecoderPtr> ReceiveListOfAllDecoders()
|
||||
QVector<DecoderPtr> Decoder::ReceiveListOfAllDecoders()
|
||||
{
|
||||
QVector<DecoderPtr> decoders;
|
||||
|
||||
@@ -203,55 +195,6 @@ QVector<DecoderPtr> ReceiveListOfAllDecoders()
|
||||
return decoders;
|
||||
}
|
||||
|
||||
Footage* Decoder::Probe(Project* project, const QString &filename, const QAtomicInt* cancelled)
|
||||
{
|
||||
// Check for a valid filename
|
||||
if (filename.isEmpty()) {
|
||||
qWarning() << "Tried to probe media with an empty filename";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check file exists
|
||||
if (!QFileInfo::exists(filename)) {
|
||||
qWarning() << "Tried to probe file that doesn't exist:" << filename;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create list to iterate through
|
||||
QVector<DecoderPtr> decoder_list = ReceiveListOfAllDecoders();
|
||||
|
||||
// Pass Footage through each Decoder's probe function
|
||||
for (int i=0;i<decoder_list.size();i++) {
|
||||
|
||||
if (cancelled && *cancelled) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DecoderPtr decoder = decoder_list.at(i);
|
||||
|
||||
Footage* footage = decoder->Probe(filename, cancelled);
|
||||
|
||||
if (footage) {
|
||||
QFileInfo file_info(filename);
|
||||
footage->set_name(file_info.fileName());
|
||||
footage->set_filename(filename);
|
||||
|
||||
footage->set_decoder(decoder->id());
|
||||
footage->set_project(project);
|
||||
footage->set_timestamp(file_info.lastModified().toMSecsSinceEpoch());
|
||||
|
||||
footage->SetValid();
|
||||
|
||||
// FIXME: Cache the results so we don't have to probe if this media is added a second time
|
||||
|
||||
return footage;
|
||||
}
|
||||
}
|
||||
|
||||
// We aren't able to use this Footage
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DecoderPtr Decoder::CreateFromID(const QString &id)
|
||||
{
|
||||
if (id.isEmpty()) {
|
||||
@@ -270,9 +213,12 @@ DecoderPtr Decoder::CreateFromID(const QString &id)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString Decoder::GetConformedFilename(const AudioParams ¶ms)
|
||||
QString Decoder::GetConformedFilename(const QString& cache_path, const AudioParams ¶ms)
|
||||
{
|
||||
QString index_fn = GetIndexFilename();
|
||||
QString index_fn = QStringLiteral("%1.%2:%3").arg(FileFunctions::GetUniqueFileIdentifier(stream_.filename()),
|
||||
QString::number(stream_.stream()));
|
||||
|
||||
index_fn = QDir(cache_path).filePath(index_fn);
|
||||
|
||||
index_fn.append('.');
|
||||
index_fn.append(QString::number(params.sample_rate()));
|
||||
@@ -284,15 +230,20 @@ QString Decoder::GetConformedFilename(const AudioParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
QString Decoder::GetIndexFilename()
|
||||
int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time)
|
||||
{
|
||||
return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index())));
|
||||
return Timecode::time_to_timestamp(time, timebase) + start_time;
|
||||
}
|
||||
|
||||
void Decoder::SignalProcessingProgress(const int64_t &ts)
|
||||
Decoder::CodecStream Decoder::GetCodecStreamFromStreamReference(const Footage::StreamReference &ref)
|
||||
{
|
||||
if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) {
|
||||
emit IndexProgress(static_cast<double>(ts) / static_cast<double>(stream()->duration()));
|
||||
return CodecStream(ref.footage()->filename(), ref.footage()->GetRealStreamIndex(ref));
|
||||
}
|
||||
|
||||
void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration)
|
||||
{
|
||||
if (duration != AV_NOPTS_VALUE && duration != 0) {
|
||||
emit IndexProgress(static_cast<double>(ts) / static_cast<double>(duration));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,4 +328,9 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filenam
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint qHash(Decoder::CodecStream stream, uint seed)
|
||||
{
|
||||
return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+71
-31
@@ -25,6 +25,7 @@ extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QWaitCondition>
|
||||
@@ -35,6 +36,7 @@ extern "C" {
|
||||
#include "codec/waveoutput.h"
|
||||
#include "common/rational.h"
|
||||
#include "project/item/footage/footage.h"
|
||||
#include "project/item/footage/stream.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -77,6 +79,57 @@ public:
|
||||
virtual bool SupportsVideo(){return false;}
|
||||
virtual bool SupportsAudio(){return false;}
|
||||
|
||||
class CodecStream
|
||||
{
|
||||
public:
|
||||
CodecStream() :
|
||||
stream_(-1)
|
||||
{
|
||||
}
|
||||
|
||||
CodecStream(const QString& filename, int stream) :
|
||||
filename_(filename),
|
||||
stream_(stream)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return !filename_.isEmpty() && stream_ >= 0;
|
||||
}
|
||||
|
||||
bool Exists() const
|
||||
{
|
||||
return QFileInfo::exists(filename_);
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
*this = CodecStream();
|
||||
}
|
||||
|
||||
bool operator==(const CodecStream& rhs) const
|
||||
{
|
||||
return filename_ == rhs.filename_ && stream_ == rhs.stream_;
|
||||
}
|
||||
|
||||
const QString& filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
|
||||
int stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
|
||||
int stream_;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open stream for decoding
|
||||
*
|
||||
@@ -86,7 +139,9 @@ public:
|
||||
* already open and the stream == the stream provided. Returns FALSE if the stream couldn't
|
||||
* be opened OR if already open and the stream is NOT the same.
|
||||
*/
|
||||
bool Open(Stream* fs);
|
||||
bool Open(const CodecStream& stream);
|
||||
|
||||
static const rational kAnyTimecode;
|
||||
|
||||
/**
|
||||
* @brief Retrieves a video frame from footage
|
||||
@@ -108,28 +163,7 @@ public:
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
SampleBufferPtr RetrieveAudio(const TimeRange& range, const AudioParams& params, const QAtomicInt *cancelled);
|
||||
|
||||
/**
|
||||
* @brief Try to probe a Footage file by passing it through all available Decoders
|
||||
*
|
||||
* This is a helper function designed to abstract the process of communicating with several Decoders from the rest of
|
||||
* the application. This function will take a Footage file and manually pass it through the available Decoders' Probe()
|
||||
* functions until one indicates that it can decode this file. That Decoder will then dump information about the file
|
||||
* into the Footage object for use throughout the program.
|
||||
*
|
||||
* Probing may be a lengthy process and it's recommended to run this in a separate thread.
|
||||
*
|
||||
* @param f
|
||||
*
|
||||
* A Footage object with a valid filename. If the Footage does not have a valid filename (e.g. is empty or file doesn't
|
||||
* exist), this function will return FALSE.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if a Decoder was successfully able to parse and probe this file. FALSE if not.
|
||||
*/
|
||||
static Footage *Probe(Project *project, const QString& filename, const QAtomicInt *cancelled);
|
||||
SampleBufferPtr RetrieveAudio(const TimeRange& range, const AudioParams& params, const QString &cache_path, const QAtomicInt *cancelled);
|
||||
|
||||
/**
|
||||
* @brief Generate a Footage object from a file
|
||||
@@ -142,7 +176,7 @@ public:
|
||||
*
|
||||
* This function is re-entrant.
|
||||
*/
|
||||
virtual Footage *Probe(const QString& filename, const QAtomicInt* cancelled) const = 0;
|
||||
virtual Streams Probe(const QString& filename, const QAtomicInt* cancelled) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Closes media/deallocates memory
|
||||
@@ -166,6 +200,10 @@ public:
|
||||
|
||||
static int64_t GetImageSequenceIndex(const QString& filename);
|
||||
|
||||
static QVector<DecoderPtr> ReceiveListOfAllDecoders();
|
||||
|
||||
static CodecStream GetCodecStreamFromStreamReference(const Footage::StreamReference& ref);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Internal open function
|
||||
@@ -199,17 +237,15 @@ protected:
|
||||
|
||||
virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled);
|
||||
|
||||
void SignalProcessingProgress(const int64_t& ts);
|
||||
void SignalProcessingProgress(int64_t ts, int64_t duration);
|
||||
|
||||
/**
|
||||
* @brief Get the destination filename of an audio stream conformed to a set of parameters
|
||||
*/
|
||||
QString GetConformedFilename(const AudioParams ¶ms);
|
||||
|
||||
QString GetIndexFilename();
|
||||
QString GetConformedFilename(const QString &cache_path, const AudioParams ¶ms);
|
||||
|
||||
struct CurrentlyConforming {
|
||||
Stream* stream;
|
||||
CodecStream stream;
|
||||
AudioParams params;
|
||||
|
||||
bool operator==(const CurrentlyConforming& rhs) const
|
||||
@@ -223,11 +259,13 @@ protected:
|
||||
*
|
||||
* This function is NOT thread safe and should therefore only be called by thread safe functions.
|
||||
*/
|
||||
Stream* stream() const
|
||||
const CodecStream& stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time);
|
||||
|
||||
static QMutex currently_conforming_mutex_;
|
||||
static QWaitCondition currently_conforming_wait_cond_;
|
||||
static QVector<CurrentlyConforming> currently_conforming_;
|
||||
@@ -242,12 +280,14 @@ signals:
|
||||
private:
|
||||
SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range);
|
||||
|
||||
Stream* stream_;
|
||||
CodecStream stream_;
|
||||
|
||||
QMutex mutex_;
|
||||
|
||||
};
|
||||
|
||||
uint qHash(Decoder::CodecStream stream, uint seed = 0);
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::Decoder::RetrieveState)
|
||||
|
||||
+105
-141
@@ -64,13 +64,13 @@ FFmpegDecoder::~FFmpegDecoder()
|
||||
|
||||
bool FFmpegDecoder::OpenInternal()
|
||||
{
|
||||
if (instance_.Open(stream()->footage()->filename().toUtf8(), stream()->index())) {
|
||||
if (instance_.Open(stream().filename().toUtf8(), stream().stream())) {
|
||||
AVStream* s = instance_.avstream();
|
||||
|
||||
// Store one second in the source's timebase
|
||||
second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base)));
|
||||
|
||||
if (stream()->type() == Stream::kVideo) {
|
||||
if (s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
// Get an Olive compatible AVPixelFormat
|
||||
ideal_pix_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(s->codecpar->format));
|
||||
|
||||
@@ -92,20 +92,18 @@ bool FFmpegDecoder::OpenInternal()
|
||||
return false;
|
||||
}
|
||||
|
||||
FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r)
|
||||
/*FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r)
|
||||
{
|
||||
// This is a still image
|
||||
VideoStream* is = static_cast<VideoStream*>(stream());
|
||||
|
||||
QString img_filename = stream()->footage()->filename();
|
||||
QString img_filename = stream().filename();
|
||||
|
||||
int64_t ts;
|
||||
|
||||
// If it's an image sequence, we'll probably need to transform the filename
|
||||
if (is->video_type() == VideoStream::kVideoTypeImageSequence) {
|
||||
ts = static_cast<VideoStream*>(stream())->get_time_in_timebase_units(timecode);
|
||||
if (stream().GetStream().video_type() == Stream::kVideoTypeImageSequence) {
|
||||
ts = stream().GetTimeInTimebaseUnits(timecode);
|
||||
|
||||
img_filename = TransformImageSequenceFileName(stream()->footage()->filename(), ts);
|
||||
img_filename = TransformImageSequenceFileName(stream().filename(), ts);
|
||||
} else {
|
||||
ts = 0;
|
||||
}
|
||||
@@ -115,19 +113,21 @@ FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int &
|
||||
FramePtr output_frame = nullptr;
|
||||
|
||||
Instance i;
|
||||
i.Open(img_filename.toUtf8(), stream()->index());
|
||||
i.Open(img_filename.toUtf8(), stream().GetRealStreamIndex());
|
||||
|
||||
int ret = i.GetFrame(pkt, frame);
|
||||
|
||||
if (ret >= 0) {
|
||||
VideoParams video_params = stream().video_params();
|
||||
|
||||
// Create frame to return
|
||||
output_frame = Frame::Create();
|
||||
output_frame->set_video_params(VideoParams(frame->width,
|
||||
frame->height,
|
||||
native_pix_fmt_,
|
||||
native_channel_count_,
|
||||
is->pixel_aspect_ratio(),
|
||||
is->interlacing(),
|
||||
video_params.pixel_aspect_ratio(),
|
||||
video_params.interlacing(),
|
||||
divider));
|
||||
output_frame->set_timestamp(timecode);
|
||||
output_frame->allocate();
|
||||
@@ -146,59 +146,48 @@ FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int &
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return output_frame;
|
||||
}
|
||||
}*/
|
||||
|
||||
FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const int ÷r)
|
||||
{
|
||||
VideoStream* vs = static_cast<VideoStream*>(stream());
|
||||
|
||||
if (scale_divider_ != divider) {
|
||||
FreeScaler();
|
||||
InitScaler(divider);
|
||||
}
|
||||
|
||||
if (vs->video_type() == VideoStream::kVideoTypeStill
|
||||
|| vs->video_type() == VideoStream::kVideoTypeImageSequence) {
|
||||
AVStream* s = instance_.avstream();
|
||||
|
||||
return RetrieveStillImage(timecode, divider);
|
||||
int divided_width = VideoParams::GetScaledDimension(s->codecpar->width, divider);
|
||||
int divided_height = VideoParams::GetScaledDimension(s->codecpar->height, divider);
|
||||
|
||||
} else {
|
||||
if (pool_.width() != divided_width || pool_.height() != divided_height) {
|
||||
// Clear all instance queues
|
||||
ClearFrameCache();
|
||||
|
||||
int64_t target_ts = vs->get_time_in_timebase_units(timecode);
|
||||
// Set new frame pool parameters
|
||||
pool_.SetParameters(divided_width, divided_height, native_pix_fmt_, native_channel_count_);
|
||||
}
|
||||
|
||||
int divided_width = VideoParams::GetScaledDimension(vs->width(), divider);
|
||||
int divided_height = VideoParams::GetScaledDimension(vs->height(), divider);
|
||||
// Retrieve frame
|
||||
FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode, divider);
|
||||
|
||||
if (pool_.width() != divided_width || pool_.height() != divided_height) {
|
||||
// Clear all instance queues
|
||||
ClearFrameCache();
|
||||
// We found the frame, we'll return a copy
|
||||
if (return_frame) {
|
||||
FramePtr copy = Frame::Create();
|
||||
copy->set_video_params(VideoParams(s->codecpar->width,
|
||||
s->codecpar->height,
|
||||
native_pix_fmt_,
|
||||
native_channel_count_,
|
||||
av_guess_sample_aspect_ratio(instance_.fmt_ctx(), s, nullptr), // May be incorrect,
|
||||
VideoParams::kInterlaceNone, // May be incorrect
|
||||
divider));
|
||||
copy->set_timestamp(timecode);
|
||||
copy->allocate();
|
||||
|
||||
// Set new frame pool parameters
|
||||
pool_.SetParameters(divided_width, divided_height, native_pix_fmt_, native_channel_count_);
|
||||
}
|
||||
|
||||
// Retrieve frame
|
||||
FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(target_ts, divider);
|
||||
|
||||
// We found the frame, we'll return a copy
|
||||
if (return_frame) {
|
||||
FramePtr copy = Frame::Create();
|
||||
copy->set_video_params(VideoParams(vs->width(),
|
||||
vs->height(),
|
||||
native_pix_fmt_,
|
||||
native_channel_count_,
|
||||
vs->pixel_aspect_ratio(),
|
||||
vs->interlacing(),
|
||||
divider));
|
||||
copy->set_timestamp(timecode);
|
||||
copy->allocate();
|
||||
|
||||
// This data will already match the frame
|
||||
memcpy(copy->data(), return_frame->data(), copy->allocated_size());
|
||||
|
||||
return copy;
|
||||
}
|
||||
// This data will already match the frame
|
||||
memcpy(copy->data(), return_frame->data(), copy->allocated_size());
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -218,19 +207,16 @@ QString FFmpegDecoder::id()
|
||||
return QStringLiteral("ffmpeg");
|
||||
}
|
||||
|
||||
Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const
|
||||
Streams FFmpegDecoder::Probe(const QString &filename, const QAtomicInt *cancelled) const
|
||||
{
|
||||
Q_UNUSED(cancelled)
|
||||
// Return value
|
||||
Streams streams;
|
||||
|
||||
// Variable for receiving errors from FFmpeg
|
||||
int error_code;
|
||||
|
||||
// Result to return
|
||||
Footage* footage = nullptr;
|
||||
|
||||
// Convert QString to a C string
|
||||
QByteArray ba = filename.toUtf8();
|
||||
const char* filename_c = ba.constData();
|
||||
QByteArray filename_c = filename.toUtf8();
|
||||
|
||||
// Open file in a format context
|
||||
AVFormatContext* fmt_ctx = nullptr;
|
||||
@@ -244,18 +230,18 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell
|
||||
|
||||
int64_t footage_duration = fmt_ctx->duration;
|
||||
|
||||
QVector<Stream*> streams(fmt_ctx->nb_streams);
|
||||
|
||||
// Dump it into the Footage object
|
||||
for (unsigned int i=0;i<fmt_ctx->nb_streams;i++) {
|
||||
|
||||
// FFmpeg AVStream
|
||||
AVStream* avstream = fmt_ctx->streams[i];
|
||||
|
||||
// Our native stream class
|
||||
Stream stream;
|
||||
|
||||
// Find decoder for this stream, if it exists we can proceed
|
||||
AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id);
|
||||
|
||||
Stream* str;
|
||||
|
||||
if (decoder
|
||||
&& (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
|
||||
|| avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) {
|
||||
@@ -274,7 +260,7 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell
|
||||
|
||||
{
|
||||
Instance instance;
|
||||
instance.Open(filename.toUtf8(), avstream->index);
|
||||
instance.Open(filename_c, avstream->index);
|
||||
|
||||
// Read first frame and retrieve some metadata
|
||||
if (instance.GetFrame(pkt, frame) >= 0) {
|
||||
@@ -332,47 +318,35 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
|
||||
VideoStream* video_stream = new VideoStream();
|
||||
|
||||
if (image_is_still) {
|
||||
video_stream->set_video_type(VideoStream::kVideoTypeStill);
|
||||
} else {
|
||||
video_stream->set_video_type(VideoStream::kVideoTypeVideo);
|
||||
|
||||
video_stream->set_frame_rate(frame_rate);
|
||||
video_stream->set_start_time(avstream->start_time);
|
||||
}
|
||||
|
||||
video_stream->set_width(avstream->codecpar->width);
|
||||
video_stream->set_height(avstream->codecpar->height);
|
||||
video_stream->set_interlacing(interlacing);
|
||||
video_stream->set_pixel_aspect_ratio(pixel_aspect_ratio);
|
||||
|
||||
AVPixelFormat compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(avstream->codecpar->format));
|
||||
video_stream->set_format(GetNativePixelFormat(compatible_pix_fmt));
|
||||
video_stream->set_channel_count(GetNativeChannelCount(compatible_pix_fmt));
|
||||
|
||||
str = video_stream;
|
||||
stream = Stream(Stream::kVideo);
|
||||
stream.set_width(avstream->codecpar->width);
|
||||
stream.set_height(avstream->codecpar->height);
|
||||
stream.set_video_type((image_is_still) ? Stream::kVideoTypeStill : Stream::kVideoTypeVideo);
|
||||
stream.set_pixel_format(GetNativePixelFormat(compatible_pix_fmt));
|
||||
stream.set_channel_count(GetNativeChannelCount(compatible_pix_fmt));
|
||||
stream.set_interlacing(interlacing);
|
||||
stream.set_pixel_aspect_ratio(pixel_aspect_ratio);
|
||||
stream.set_frame_rate(frame_rate);
|
||||
stream.set_start_time(avstream->start_time);
|
||||
|
||||
// Defaults to false, requires user intervention if incorrect
|
||||
stream.set_premultiplied_alpha(false);
|
||||
|
||||
} else {
|
||||
|
||||
// Create an audio stream object
|
||||
AudioStream* audio_stream = new AudioStream();
|
||||
|
||||
uint64_t channel_layout = avstream->codecpar->channel_layout;
|
||||
if (!channel_layout) {
|
||||
channel_layout = static_cast<uint64_t>(av_get_default_channel_layout(avstream->codecpar->channels));
|
||||
}
|
||||
|
||||
audio_stream->set_channel_layout(channel_layout);
|
||||
audio_stream->set_channels(avstream->codecpar->channels);
|
||||
audio_stream->set_sample_rate(avstream->codecpar->sample_rate);
|
||||
|
||||
if (avstream->duration == AV_NOPTS_VALUE) {
|
||||
// Loop through stream until we get the whole duration
|
||||
if (footage_duration == AV_NOPTS_VALUE) {
|
||||
Instance instance;
|
||||
instance.Open(filename.toUtf8(), avstream->index);
|
||||
instance.Open(filename_c, avstream->index);
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
@@ -396,67 +370,53 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell
|
||||
}
|
||||
}
|
||||
|
||||
str = audio_stream;
|
||||
stream = Stream(Stream::kAudio);
|
||||
stream.set_channel_layout(channel_layout);
|
||||
stream.set_channel_count(avstream->codecpar->channels);
|
||||
stream.set_sample_rate(avstream->codecpar->sample_rate);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// This is data we can't utilize at the moment, but we make a Stream object anyway to keep parity with the file
|
||||
str = new Stream();
|
||||
Stream::Type type;
|
||||
|
||||
// Set the correct codec type based on FFmpeg's result
|
||||
switch (avstream->codecpar->codec_type) {
|
||||
case AVMEDIA_TYPE_UNKNOWN:
|
||||
str->set_type(Stream::kUnknown);
|
||||
break;
|
||||
case AVMEDIA_TYPE_DATA:
|
||||
str->set_type(Stream::kData);
|
||||
type = Stream::kData;
|
||||
break;
|
||||
case AVMEDIA_TYPE_SUBTITLE:
|
||||
str->set_type(Stream::kSubtitle);
|
||||
type = Stream::kSubtitle;
|
||||
break;
|
||||
case AVMEDIA_TYPE_ATTACHMENT:
|
||||
str->set_type(Stream::kAttachment);
|
||||
type = Stream::kAttachment;
|
||||
break;
|
||||
case AVMEDIA_TYPE_UNKNOWN:
|
||||
default:
|
||||
// Fallback to an unknown stream
|
||||
str->set_type(Stream::kUnknown);
|
||||
type = Stream::kUnknown;
|
||||
break;
|
||||
}
|
||||
|
||||
stream = Stream(type);
|
||||
|
||||
}
|
||||
|
||||
str->set_index(avstream->index);
|
||||
str->set_timebase(avstream->time_base);
|
||||
str->set_duration(avstream->duration);
|
||||
stream.set_timebase(avstream->time_base);
|
||||
stream.set_duration(avstream->duration);
|
||||
|
||||
streams.append(stream);
|
||||
|
||||
streams[i] = str;
|
||||
}
|
||||
|
||||
// Check if we could pick up any streams in this file
|
||||
bool found_valid_streams = false;
|
||||
|
||||
foreach (Stream* stream, streams) {
|
||||
if (stream->type() != Stream::kUnknown) {
|
||||
found_valid_streams = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_valid_streams) {
|
||||
// We actually have footage we can return instead of nullptr
|
||||
footage = new Footage();
|
||||
|
||||
// Add streams
|
||||
footage->add_streams(streams);
|
||||
}
|
||||
}
|
||||
|
||||
// Free all memory
|
||||
avformat_close_input(&fmt_ctx);
|
||||
|
||||
return footage;
|
||||
return streams;
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::FFmpegError(int error_code)
|
||||
@@ -549,7 +509,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar
|
||||
delete [] data;
|
||||
}
|
||||
|
||||
SignalProcessingProgress(frame->pts);
|
||||
SignalProcessingProgress(frame->pts, instance_.avstream()->duration);
|
||||
}
|
||||
|
||||
wave_out.close();
|
||||
@@ -677,27 +637,30 @@ void FFmpegDecoder::ClearFrameCache()
|
||||
cache_at_zero_ = false;
|
||||
}
|
||||
|
||||
FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_ts, int divider)
|
||||
FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, int divider)
|
||||
{
|
||||
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time);
|
||||
int64_t seek_ts = target_ts;
|
||||
bool still_seeking = false;
|
||||
|
||||
// If the frame wasn't in the frame cache, see if this frame cache is too old to use
|
||||
if (cached_frames_.isEmpty()
|
||||
|| (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) {
|
||||
ClearFrameCache();
|
||||
if (time != kAnyTimecode) {
|
||||
// If the frame wasn't in the frame cache, see if this frame cache is too old to use
|
||||
if (cached_frames_.isEmpty()
|
||||
|| (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) {
|
||||
ClearFrameCache();
|
||||
|
||||
instance_.Seek(seek_ts);
|
||||
if (seek_ts == 0) {
|
||||
cache_at_zero_ = true;
|
||||
}
|
||||
instance_.Seek(seek_ts);
|
||||
if (seek_ts == 0) {
|
||||
cache_at_zero_ = true;
|
||||
}
|
||||
|
||||
still_seeking = true;
|
||||
} else {
|
||||
// Search cache for frame
|
||||
FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts);
|
||||
if (cached_frame) {
|
||||
return cached_frame;
|
||||
still_seeking = true;
|
||||
} else {
|
||||
// Search cache for frame
|
||||
FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts);
|
||||
if (cached_frame) {
|
||||
return cached_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,7 +749,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_t
|
||||
cached_frames_.append(cached);
|
||||
|
||||
// If this is a valid frame, see if this or the frame before it are the one we need
|
||||
if (cached->timestamp() == target_ts) {
|
||||
if (cached->timestamp() == target_ts || time == kAnyTimecode) {
|
||||
return_frame = cached;
|
||||
break;
|
||||
} else if (cached->timestamp() > target_ts) {
|
||||
@@ -809,13 +772,14 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_t
|
||||
|
||||
void FFmpegDecoder::InitScaler(int divider)
|
||||
{
|
||||
VideoStream* vs = static_cast<VideoStream*>(stream());
|
||||
int src_width = instance_.avstream()->codecpar->width;
|
||||
int src_height = instance_.avstream()->codecpar->height;
|
||||
|
||||
int scaled_width = VideoParams::GetScaledDimension(vs->width(), divider);
|
||||
int scaled_height = VideoParams::GetScaledDimension(vs->height(), divider);
|
||||
int scaled_width = VideoParams::GetScaledDimension(src_width, divider);
|
||||
int scaled_height = VideoParams::GetScaledDimension(src_height, divider);
|
||||
|
||||
scale_ctx_ = sws_getContext(vs->width(),
|
||||
vs->height(),
|
||||
scale_ctx_ = sws_getContext(src_width,
|
||||
src_height,
|
||||
static_cast<AVPixelFormat>(instance_.avstream()->codecpar->format),
|
||||
scaled_width,
|
||||
scaled_height,
|
||||
|
||||
@@ -35,7 +35,6 @@ extern "C" {
|
||||
#include "codec/decoder.h"
|
||||
#include "codec/waveoutput.h"
|
||||
#include "ffmpegframepool.h"
|
||||
#include "project/item/footage/videostream.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -57,7 +56,7 @@ public:
|
||||
virtual bool SupportsVideo() override{return true;}
|
||||
virtual bool SupportsAudio() override{return true;}
|
||||
|
||||
virtual Footage* Probe(const QString& filename, const QAtomicInt* cancelled) const override;
|
||||
virtual Streams Probe(const QString &filename, const QAtomicInt *cancelled) const override;
|
||||
|
||||
protected:
|
||||
virtual bool OpenInternal() override;
|
||||
@@ -122,7 +121,7 @@ private:
|
||||
void InitScaler(int divider);
|
||||
void FreeScaler();
|
||||
|
||||
FramePtr RetrieveStillImage(const rational& timecode, const int& divider);
|
||||
//FramePtr RetrieveStillImage(const rational& timecode, const int& divider);
|
||||
|
||||
static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
|
||||
static int GetNativeChannelCount(AVPixelFormat pix_fmt);
|
||||
@@ -135,7 +134,7 @@ private:
|
||||
|
||||
void ClearFrameCache();
|
||||
|
||||
FFmpegFramePool::ElementPtr RetrieveFrame(const int64_t &target_ts, int divider);
|
||||
FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time, int divider);
|
||||
|
||||
void RemoveFirstFrame();
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef FOOTAGEMETA_H
|
||||
#define FOOTAGEMETA_H
|
||||
|
||||
struct FootageData {
|
||||
struct StreamData {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif // FOOTAGEMETA_H
|
||||
@@ -51,14 +51,16 @@ QString OIIODecoder::id()
|
||||
return QStringLiteral("oiio");
|
||||
}
|
||||
|
||||
Footage *OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const
|
||||
Streams OIIODecoder::Probe(const QString &filename, const QAtomicInt* cancelled) const
|
||||
{
|
||||
Q_UNUSED(cancelled)
|
||||
|
||||
Streams streams;
|
||||
|
||||
// Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying
|
||||
// to open a file that it can't if it's given one
|
||||
if (!FileTypeIsSupported(filename)) {
|
||||
return nullptr;
|
||||
return streams;
|
||||
}
|
||||
|
||||
std::string std_filename = filename.toStdString();
|
||||
@@ -66,82 +68,46 @@ Footage *OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancelled
|
||||
auto in = OIIO::ImageInput::open(std_filename);
|
||||
|
||||
if (!in) {
|
||||
return nullptr;
|
||||
return streams;
|
||||
}
|
||||
|
||||
// Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle
|
||||
// it better
|
||||
if (!strcmp(in->format_name(), "FFmpeg movie")) {
|
||||
return nullptr;
|
||||
return streams;
|
||||
}
|
||||
|
||||
Footage* footage = new Footage();
|
||||
Stream stream(Stream::kVideo);
|
||||
|
||||
VideoStream* image_stream = new VideoStream();
|
||||
|
||||
image_stream->set_width(in->spec().width);
|
||||
image_stream->set_height(in->spec().height);
|
||||
image_stream->set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(in->spec().format.basetype)));
|
||||
image_stream->set_channel_count(in->spec().nchannels);
|
||||
image_stream->set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec()));
|
||||
image_stream->set_video_type(VideoStream::kVideoTypeStill);
|
||||
|
||||
// Images will always have just one stream
|
||||
image_stream->set_index(0);
|
||||
stream.set_width(in->spec().width);
|
||||
stream.set_height(in->spec().height);
|
||||
stream.set_pixel_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(in->spec().format.basetype)));
|
||||
stream.set_channel_count(in->spec().nchannels);
|
||||
stream.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec()));
|
||||
stream.set_video_type(Stream::kVideoTypeStill);
|
||||
|
||||
// OIIO automatically premultiplies alpha
|
||||
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
|
||||
// likely reduces the fidelity?
|
||||
image_stream->set_premultiplied_alpha(true);
|
||||
stream.set_premultiplied_alpha(true);
|
||||
|
||||
// Get stats for this image and dump them into the Footage file
|
||||
footage->add_stream(image_stream);
|
||||
streams.append(stream);
|
||||
|
||||
// If we're here, we have a successful image open
|
||||
in->close();
|
||||
|
||||
return footage;
|
||||
return streams;
|
||||
}
|
||||
|
||||
bool OIIODecoder::OpenInternal()
|
||||
{
|
||||
// If we can open the filename provided, assume everything is working (even if this is an image
|
||||
// sequence with potentially missing frame)
|
||||
if (OpenImageHandler(stream()->footage()->filename())) {
|
||||
VideoStream* video_stream = static_cast<VideoStream*>(stream());
|
||||
|
||||
if (video_stream->video_type() == VideoStream::kVideoTypeStill) {
|
||||
last_sequence_index_ = 0;
|
||||
} else {
|
||||
last_sequence_index_ = GetImageSequenceIndex(stream()->footage()->filename());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// If we can open the filename provided, assume everything is working
|
||||
return OpenImageHandler(stream().filename());
|
||||
}
|
||||
|
||||
FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const int& divider)
|
||||
{
|
||||
VideoStream* video_stream = static_cast<VideoStream*>(stream());
|
||||
|
||||
int64_t sequence_index;
|
||||
|
||||
if (video_stream->video_type() == VideoStream::kVideoTypeStill) {
|
||||
sequence_index = 0;
|
||||
} else {
|
||||
sequence_index = video_stream->get_time_in_timebase_units(timecode);
|
||||
}
|
||||
|
||||
if (last_sequence_index_ != sequence_index) {
|
||||
CloseImageHandle();
|
||||
|
||||
if (!OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), sequence_index))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
last_sequence_index_ = sequence_index;
|
||||
}
|
||||
Q_UNUSED(timecode)
|
||||
|
||||
FramePtr frame = Frame::Create();
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
|
||||
virtual bool SupportsVideo() override{return true;}
|
||||
|
||||
virtual Footage* Probe(const QString& filename, const QAtomicInt* cancelled) const override;
|
||||
virtual Streams Probe(const QString& filename, const QAtomicInt* cancelled) const override;
|
||||
|
||||
protected:
|
||||
virtual bool OpenInternal() override;
|
||||
@@ -56,8 +56,6 @@ private:
|
||||
|
||||
void CloseImageHandle();
|
||||
|
||||
int64_t last_sequence_index_;
|
||||
|
||||
VideoParams::Format pix_fmt_;
|
||||
|
||||
int channel_count_;
|
||||
|
||||
Reference in New Issue
Block a user