use IDs for multiple decoders

This commit is contained in:
itsmattkc
2019-08-08 15:38:58 +10:00
parent 8730da4b43
commit f3b60369aa
9 changed files with 203 additions and 53 deletions
+23 -1
View File
@@ -48,13 +48,35 @@ class Decoder : public QObject
{
Q_OBJECT
public:
Decoder();
Decoder(Stream* fs);
// Necessary for subclassing, it's empty
virtual ~Decoder();
/**
* @brief Deleted copy constructor
*/
Decoder(const Decoder& other) = delete;
/**
* @brief Deleted move constructor
*/
Decoder(Decoder&& other) = delete;
/**
* @brief Deleted copy assignment
*/
Decoder& operator=(const Decoder& other) = delete;
/**
* @brief Deleted move assignment
*/
Decoder& operator=(Decoder&& other) = delete;
virtual QString id() = 0;
const Stream* stream();
void set_stream(const Stream *fs);
+46 -19
View File
@@ -44,6 +44,11 @@ FFmpegDecoder::FFmpegDecoder() :
{
}
FFmpegDecoder::~FFmpegDecoder()
{
Close();
}
bool FFmpegDecoder::Open()
{
if (open_) {
@@ -192,27 +197,16 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt
// Convert timecode to AVStream timebase
int64_t target_ts = qFloor(timecode.toDouble() * rational(avstream_->time_base).flipped().toDouble());
// Index now if we haven't already
if (frame_index_.isEmpty() && !LoadFrameIndex()) {
Index();
// Find closest actual timebase in the file
target_ts = GetClosestTimestampInIndex(target_ts);
if (target_ts < 0) {
Error(tr("Index failed to produce a valid timestamp"));
return nullptr;
}
// Use index to find closest frame in file
for (int i=1;i<frame_index_.size();i++) {
if (frame_index_.at(i) > target_ts) {
target_ts = frame_index_.at(i - 1);
break;
}
}
int ret = 0;
// Allocate and init a packet for reading encoded data
AVPacket pkt;
av_init_packet(&pkt);
// Cache FFmpeg error code returns
ret = 0;
int ret = 0;
// Set up seeking loop
int64_t seek_ts = target_ts;
@@ -225,7 +219,16 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt
if (frame_->pts > target_ts || frame_->pts == AV_NOPTS_VALUE) {
avcodec_flush_buffers(codec_ctx_);
av_seek_frame(fmt_ctx_, avstream_->index, seek_ts, AVSEEK_FLAG_BACKWARD);
seek_ts -= second_ts;
// FFmpeg doesn't always seek correctly, if we have to seek again we wrangle it into seeking back far enough
// If we already tried seeking to 0 though, there's nothing we can do so we error here
if (seek_ts == 0) {
Error(tr("FFmpeg failed to seek to the correct location"));
return nullptr;
}
seek_ts = qMax(0L, seek_ts - second_ts);
}
ret = GetFrame();
@@ -307,6 +310,11 @@ void FFmpegDecoder::Close()
open_ = false;
}
QString FFmpegDecoder::id()
{
return "ffmpeg";
}
bool FFmpegDecoder::Probe(Footage *f)
{
if (open_) {
@@ -564,3 +572,22 @@ AVPixelFormat FFmpegDecoder::GetCompatiblePixelFormat(const AVPixelFormat &pix_f
1,
nullptr);
}
int64_t FFmpegDecoder::GetClosestTimestampInIndex(const int64_t &ts)
{
// Index now if we haven't already
if (frame_index_.isEmpty() && !LoadFrameIndex()) {
Index();
}
// Use index to find closest frame in file
for (int i=1;i<frame_index_.size();i++) {
if (frame_index_.at(i) == ts) {
return ts;
} else if (frame_index_.at(i) > ts) {
return frame_index_.at(i - 1);
}
}
return -1;
}
+8
View File
@@ -37,14 +37,20 @@ extern "C" {
class FFmpegDecoder : public Decoder
{
public:
// Constructor
FFmpegDecoder();
// Destructor
virtual ~FFmpegDecoder() override;
virtual bool Probe(Footage *f) override;
virtual bool Open() override;
virtual FramePtr Retrieve(const rational &timecode, const rational &length = 0) override;
virtual void Close() override;
virtual QString id() override;
private:
/**
* @brief Handle an error
@@ -118,6 +124,8 @@ private:
*/
AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt);
int64_t GetClosestTimestampInIndex(const int64_t& ts);
AVFormatContext* fmt_ctx_;
AVCodecContext* codec_ctx_;
AVStream* avstream_;
+10
View File
@@ -94,8 +94,18 @@ public:
*/
const uint8_t* const_data();
/**
* @brief Allocate memory buffer to store data based on parameters
*
* For video frames, the width(), height(), and format() must be set for this function to work.
*
* If a memory buffer has been previously allocated without destroying, this function will destroy it.
*/
void allocate();
/**
* @brief Destroy a memory buffer allocated with allocate()
*/
void destroy();
private:
+63 -13
View File
@@ -26,6 +26,22 @@
#include "decoder/ffmpeg/ffmpegdecoder.h"
QVector<Decoder*> ReceiveListOfAllDecoders() {
QVector<Decoder*> decoders;
decoders.append(new FFmpegDecoder());
return decoders;
}
void FreeListOfDecoders(const QVector<Decoder*>& decoders, Decoder* except = nullptr) {
foreach (Decoder* d, decoders) {
if (except == nullptr || except != d) {
delete d;
}
}
}
bool olive::ProbeMedia(Footage *f)
{
// Check for a valid filename
@@ -43,27 +59,61 @@ bool olive::ProbeMedia(Footage *f)
// Reset Footage state for probing
f->Clear();
// Create decoder instance
FFmpegDecoder ff_dec;
// Create list to iterate through
QList<Decoder*> decoder_list;
decoder_list.append(&ff_dec);
QVector<Decoder*> decoder_list = ReceiveListOfAllDecoders();
Decoder* found_decoder = nullptr;
// Pass Footage through each Decoder's probe function
for (int i=0;i<decoder_list.size();i++) {
if (decoder_list.at(i)->Probe(f)) {
// FIXME Some way of "attaching" the Footage to the Decoder without having to iterate through Decoders again at
// render time?
Decoder* decoder = decoder_list.at(i);
f->set_status(Footage::kReady);
return true;
if (decoder->Probe(f)) {
// FIXME: Cache the results so we don't have to probe if this media is added a second time
found_decoder = decoder;
break;
}
}
// We aren't able to use this Footage
f->set_status(Footage::kInvalid);
if (found_decoder == nullptr) {
// We aren't able to use this Footage
f->set_status(Footage::kInvalid);
f->set_decoder(QString());
} else {
// We found a Decoder, so we can set this media as valid
f->set_status(Footage::kReady);
return false;
// Attach the successful Decoder to this Footage object
f->set_decoder(found_decoder->id());
}
FreeListOfDecoders(decoder_list);
return (found_decoder != nullptr);
}
Decoder* olive::CreateDecoderFromID(const QString &id)
{
if (id.isEmpty()) {
return nullptr;
}
// Create list to iterate through
QVector<Decoder*> decoder_list = ReceiveListOfAllDecoders();
Decoder* found_decoder = nullptr;
foreach (Decoder* d, decoder_list) {
if (d->id() == id) {
found_decoder = d;
break;
}
}
FreeListOfDecoders(decoder_list, found_decoder);
return found_decoder;
}
+3
View File
@@ -21,6 +21,7 @@
#ifndef PROBESERVER_H
#define PROBESERVER_H
#include "decoder/decoder.h"
#include "project/item/footage/footage.h"
namespace olive {
@@ -46,6 +47,8 @@ namespace olive {
*/
bool ProbeMedia(Footage* f);
Decoder* CreateDecoderFromID(const QString& id);
}
#endif // PROBESERVER_H
+21 -20
View File
@@ -82,26 +82,6 @@ public:
*/
virtual QString Description();
/**
* @brief Add a parameter to this node
*
* The Node takes ownership of this parameter.
*
* This can be either an output or an input at any time. Parameters will always appear in the order they're added.
*/
void AddParameter(NodeParam* param);
/**
* @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and
* requires re-rendering
*
* Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function.
* Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break
* the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can
* call this function with transformed time and relay the signal that way.
*/
virtual void InvalidateCache(const rational& start_range, const rational& end_range);
/**
* @brief Return the parameter at a given index
*/
@@ -134,6 +114,27 @@ public:
*/
static T* ValueToPtr(const QVariant& ptr);
protected:
/**
* @brief Add a parameter to this node
*
* The Node takes ownership of this parameter.
*
* This can be either an output or an input at any time. Parameters will always appear in the order they're added.
*/
void AddParameter(NodeParam* param);
/**
* @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and
* requires re-rendering
*
* Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function.
* Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break
* the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can
* call this function with transformed time and relay the signal that way.
*/
virtual void InvalidateCache(const rational& start_range, const rational& end_range);
public slots:
/**
* @brief The main processing function
+10
View File
@@ -101,6 +101,16 @@ Item::Type Footage::type() const
return kFootage;
}
const QString &Footage::decoder()
{
return decoder_;
}
void Footage::set_decoder(const QString &id)
{
decoder_ = id;
}
void Footage::ClearStreams()
{
if (streams_.empty()) {
+19
View File
@@ -185,6 +185,20 @@ public:
*/
virtual Type type() const override;
/**
* @brief Get the Decoder ID set when this Footage was probed
*
* @return
*
* A decoder ID
*/
const QString& decoder();
/**
* @brief Used by decoders when they Probe to attach itself to this Footage
*/
void set_decoder(const QString& id);
private:
/**
* @brief Internal function to delete all Stream children and empty the array
@@ -236,6 +250,11 @@ private:
*/
Status status_;
/**
* @brief Internal attached decoder ID
*/
QString decoder_;
};
using FootagePtr = std::shared_ptr<Footage>;