Merge branch 'olive-editor:master' into av1

This commit is contained in:
jazztickets
2022-05-31 12:08:03 -06:00
committed by GitHub
7 changed files with 240 additions and 51 deletions
+117 -17
View File
@@ -52,12 +52,15 @@ extern "C" {
namespace olive { namespace olive {
QVariant Yuv2RgbShader;
FFmpegDecoder::FFmpegDecoder() : FFmpegDecoder::FFmpegDecoder() :
filter_graph_(nullptr), filter_graph_(nullptr),
buffersrc_ctx_(nullptr), buffersrc_ctx_(nullptr),
buffersink_ctx_(nullptr), buffersink_ctx_(nullptr),
input_fmt_(AV_PIX_FMT_NONE), input_fmt_(AV_PIX_FMT_NONE),
native_pix_fmt_(VideoParams::kFormatInvalid), native_internal_pix_fmt_(VideoParams::kFormatInvalid),
native_output_pix_fmt_(VideoParams::kFormatInvalid),
working_frame_(nullptr), working_frame_(nullptr),
working_packet_(nullptr), working_packet_(nullptr),
is_working_(false), is_working_(false),
@@ -142,28 +145,116 @@ bool FFmpegDecoder::OpenInternal()
TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams &params, const QAtomicInt *cancelled) TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams &params, const QAtomicInt *cancelled)
{ {
if (AVFramePtr f = RetrieveFrame(timecode, cancelled)) { if (AVFramePtr f = RetrieveFrame(timecode, cancelled)) {
if (InitScaler(f.get(), params)) { if (cancelled && *cancelled) {
int r; return nullptr;
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, f.get(), AV_BUFFERSRC_FLAG_KEEP_REF); }
if (r < 0) {
return nullptr;
}
r = av_buffersink_get_frame(buffersink_ctx_, working_frame_);
if (r < 0) {
return nullptr;
}
if (InitScaler(f.get(), params)) {
VideoParams vp(instance_.avstream()->codecpar->width, VideoParams vp(instance_.avstream()->codecpar->width,
instance_.avstream()->codecpar->height, instance_.avstream()->codecpar->height,
native_pix_fmt_, native_output_pix_fmt_,
native_channel_count_, native_channel_count_,
av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr), av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr),
VideoParams::kInterlaceNone, VideoParams::kInterlaceNone,
params.divider); params.divider);
TexturePtr tex = renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel()); TexturePtr tex = nullptr;
const bool hwscale = true;
av_frame_unref(working_frame_); // Attempt to use GLSL shader for faster YUV to RGB conversion
if (hwscale) {
AVPixelFormat src_fmt = AVPixelFormat(f.get()->format);
if (src_fmt == AV_PIX_FMT_YUV420P
|| src_fmt == AV_PIX_FMT_YUV422P
|| src_fmt == AV_PIX_FMT_YUV444P
|| src_fmt == AV_PIX_FMT_YUV420P10LE
|| src_fmt == AV_PIX_FMT_YUV422P10LE
|| src_fmt == AV_PIX_FMT_YUV444P10LE
|| src_fmt == AV_PIX_FMT_YUV420P12LE
|| src_fmt == AV_PIX_FMT_YUV422P12LE
|| src_fmt == AV_PIX_FMT_YUV444P12LE) {
if (Yuv2RgbShader.isNull()) {
// Compile shader
Yuv2RgbShader = renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag"))));
}
if (!Yuv2RgbShader.isNull()) {
int px_size;
int bits_per_pixel;
switch (src_fmt) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUV444P:
default:
px_size = 1;
bits_per_pixel = 8;
break;
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV444P10LE:
px_size = 2;
bits_per_pixel = 10;
break;
case AV_PIX_FMT_YUV420P12LE:
case AV_PIX_FMT_YUV422P12LE:
case AV_PIX_FMT_YUV444P12LE:
px_size = 2;
bits_per_pixel = 12;
break;
}
VideoParams plane_params = vp;
plane_params.set_channel_count(1);
plane_params.set_divider(1);
plane_params.set_format(native_internal_pix_fmt_);
TexturePtr y_plane = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size);
if (src_fmt == AV_PIX_FMT_YUV420P
|| src_fmt == AV_PIX_FMT_YUV422P
|| src_fmt == AV_PIX_FMT_YUV420P10LE
|| src_fmt == AV_PIX_FMT_YUV422P10LE
|| src_fmt == AV_PIX_FMT_YUV420P12LE
|| src_fmt == AV_PIX_FMT_YUV422P12LE) {
plane_params.set_width(plane_params.width()/2);
}
if (src_fmt == AV_PIX_FMT_YUV420P
|| src_fmt == AV_PIX_FMT_YUV420P10LE
|| src_fmt == AV_PIX_FMT_YUV420P12LE) {
plane_params.set_height(plane_params.height()/2);
}
TexturePtr u_plane = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size);
TexturePtr v_plane = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size);
ShaderJob job;
job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane)));
job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane)));
job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane)));
job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel));
tex = renderer->CreateTexture(vp);
renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false);
}
}
}
if (!tex) {
// Fallback to software pixel format conversion
int r;
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, f.get(), AV_BUFFERSRC_FLAG_KEEP_REF);
if (r < 0) {
return nullptr;
}
r = av_buffersink_get_frame(buffersink_ctx_, working_frame_);
if (r < 0) {
return nullptr;
}
tex = renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel());
av_frame_unref(working_frame_);
}
return tex; return tex;
} }
@@ -190,7 +281,8 @@ void FFmpegDecoder::CloseInternal()
instance_.Close(); instance_.Close();
input_fmt_ = AV_PIX_FMT_NONE; input_fmt_ = AV_PIX_FMT_NONE;
native_pix_fmt_ = VideoParams::kFormatInvalid; native_internal_pix_fmt_ = VideoParams::kFormatInvalid;
native_output_pix_fmt_ = VideoParams::kFormatInvalid;
} }
QString FFmpegDecoder::id() const QString FFmpegDecoder::id() const
@@ -702,6 +794,10 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *
// Pull from the decoder // Pull from the decoder
ret = instance_.GetFrame(working_packet_, filtered.get()); ret = instance_.GetFrame(working_packet_, filtered.get());
if (cancelled && *cancelled) {
break;
}
// Handle any errors that aren't EOF (EOF is handled later on) // Handle any errors that aren't EOF (EOF is handled later on)
if (ret < 0 && ret != AVERROR_EOF) { if (ret < 0 && ret != AVERROR_EOF) {
qCritical() << "Failed to retrieve frame:" << ret; qCritical() << "Failed to retrieve frame:" << ret;
@@ -804,10 +900,14 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params
// Determine which Olive native pixel format we retrieved // Determine which Olive native pixel format we retrieved
// Note that FFmpeg doesn't support float formats // Note that FFmpeg doesn't support float formats
native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt); native_output_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt);
native_channel_count_ = GetNativeChannelCount(ideal_pix_fmt); native_channel_count_ = GetNativeChannelCount(ideal_pix_fmt);
if (native_pix_fmt_ == VideoParams::kFormatInvalid AVPixelFormat ideal_internal_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(input_fmt_));
native_internal_pix_fmt_ = GetNativePixelFormat(ideal_internal_pix_fmt);
if (native_output_pix_fmt_ == VideoParams::kFormatInvalid
|| native_internal_pix_fmt_ == VideoParams::kFormatInvalid
|| native_channel_count_ == 0) { || native_channel_count_ == 0) {
qCritical() << "Failed to find valid native pixel format for" << ideal_pix_fmt; qCritical() << "Failed to find valid native pixel format for" << ideal_pix_fmt;
return false; return false;
+2 -1
View File
@@ -160,7 +160,8 @@ private:
AVFilterContext* buffersrc_ctx_; AVFilterContext* buffersrc_ctx_;
AVFilterContext* buffersink_ctx_; AVFilterContext* buffersink_ctx_;
AVPixelFormat input_fmt_; AVPixelFormat input_fmt_;
VideoParams::Format native_pix_fmt_; VideoParams::Format native_internal_pix_fmt_;
VideoParams::Format native_output_pix_fmt_;
int native_channel_count_; int native_channel_count_;
AVFrame *working_frame_; AVFrame *working_frame_;
+35
View File
@@ -0,0 +1,35 @@
uniform sampler2D y_channel;
uniform sampler2D u_channel;
uniform sampler2D v_channel;
uniform int bits_per_pixel;
in vec2 ove_texcoord;
out vec4 frag_color;
void main() {
vec4 rgba;
vec3 yuv;
yuv.r = texture(y_channel, ove_texcoord).r;
yuv.g = texture(u_channel, ove_texcoord).r;
yuv.b = texture(v_channel, ove_texcoord).r;
if (bits_per_pixel == 10) {
yuv *= 64.0;
} else if (bits_per_pixel == 12) {
yuv *= 16.0;
}
yuv.r = 1.1643 * (yuv.r - 0.0625);
yuv.g = yuv.g - 0.5;
yuv.b = yuv.b - 0.5;
rgba.r = yuv.r + 1.5958 * yuv.b;
rgba.g = yuv.r - 0.39173 * yuv.g - 0.81290 * yuv.b;
rgba.b = yuv.r + 2.017 * yuv.g;
rgba.a = 1.0;
frag_color = rgba;
}
+36 -19
View File
@@ -29,52 +29,67 @@ ThreadPool::ThreadPool(unsigned threads, QObject *parent) :
threads = std::thread::hardware_concurrency(); threads = std::thread::hardware_concurrency();
} }
available_count_ = threads;
for (unsigned i = 0; i < threads; i += 1) { for (unsigned i = 0; i < threads; i += 1) {
worker_threads_.emplace_back(std::bind(&ThreadPool::thread_exec, this)); worker_threads_.emplace_back(std::bind(&ThreadPool::thread_exec, this, &tasks_, &task_mutex_, &cond_));
} }
// Make single reserved thread for high priority tasks (usually audio) so they don't get stuck
// behind a lot of slow tasks
high_thread_ = std::thread(std::thread(std::bind(&ThreadPool::thread_exec, this, &high_tasks_, &high_mutex_, &high_cond_)));
} }
void ThreadPool::AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority) void ThreadPool::AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority)
{ {
std::lock_guard<std::mutex> lock(task_mutex_);
if (priority == RenderTicketPriority::kHigh) { if (priority == RenderTicketPriority::kHigh) {
tasks_.emplace_front(std::move(ticket)); std::lock_guard<std::mutex> lock(high_mutex_);
high_tasks_.emplace_back(std::move(ticket));
high_cond_.notify_one();
} else { } else {
std::lock_guard<std::mutex> lock(task_mutex_);
tasks_.emplace_back(std::move(ticket)); tasks_.emplace_back(std::move(ticket));
cond_.notify_one();
} }
cond_.notify_one();
} }
bool ThreadPool::RemoveTicket(RenderTicketPtr ticket) bool ThreadPool::RemoveTicket(RenderTicketPtr ticket)
{ {
std::lock_guard<std::mutex> lock(task_mutex_); {
std::lock_guard<std::mutex> lock(task_mutex_);
const auto it = std::find(tasks_.begin(), tasks_.end(), ticket); const auto it = std::find(tasks_.begin(), tasks_.end(), ticket);
if (it == tasks_.end()) { if (it != tasks_.end()) {
return false; tasks_.erase(it);
return true;
}
} }
tasks_.erase(it); {
return true; std::lock_guard<std::mutex> lock(high_mutex_);
const auto it = std::find(high_tasks_.begin(), high_tasks_.end(), ticket);
if (it != high_tasks_.end()) {
high_tasks_.erase(it);
return true;
}
}
return false;
} }
void ThreadPool::thread_exec() void ThreadPool::thread_exec(std::deque<TaskType> *queue, std::mutex *mutex, std::condition_variable *cond)
{ {
while (true) { while (true) {
TaskType task; TaskType task;
{ {
std::unique_lock<std::mutex> lock(task_mutex_); std::unique_lock<std::mutex> lock(*mutex);
cond_.wait(lock, [this]{ return this->end_threadp_ || !this->tasks_.empty(); }); cond->wait(lock, [this, queue]{ return this->end_threadp_ || !queue->empty(); });
if (this->end_threadp_ && this->tasks_.empty()) { if (this->end_threadp_ && queue->empty()) {
break; break;
} }
task = std::move(tasks_.front()); task = std::move(queue->front());
tasks_.pop_front(); queue->pop_front();
} }
RunTicket(task); RunTicket(task);
@@ -85,10 +100,12 @@ ThreadPool::~ThreadPool()
{ {
end_threadp_ = true; end_threadp_ = true;
cond_.notify_all(); cond_.notify_all();
high_cond_.notify_all();
for (auto &e : worker_threads_) { for (auto &e : worker_threads_) {
e.join(); e.join();
} }
high_thread_.join();
} }
} }
+8 -1
View File
@@ -49,13 +49,20 @@ public:
virtual ~ThreadPool() override; virtual ~ThreadPool() override;
private: private:
void thread_exec(); void thread_exec(std::deque<TaskType> *queue, std::mutex *mutex, std::condition_variable *cond);
std::vector<std::thread> worker_threads_; std::vector<std::thread> worker_threads_;
std::deque<TaskType> tasks_; std::deque<TaskType> tasks_;
std::mutex task_mutex_; std::mutex task_mutex_;
std::condition_variable cond_; std::condition_variable cond_;
std::thread high_thread_;
std::deque<TaskType> high_tasks_;
std::mutex high_mutex_;
std::condition_variable high_cond_;
std::atomic_bool end_threadp_{false}; std::atomic_bool end_threadp_{false};
std::atomic_int available_count_;
}; };
+40 -12
View File
@@ -60,6 +60,8 @@ QVector<ViewerWidget*> ViewerWidget::instances_;
// changing values. 1/4 second seems to be a good middleground. // changing values. 1/4 second seems to be a good middleground.
const rational ViewerWidget::kAudioPlaybackInterval = rational(1, 4); const rational ViewerWidget::kAudioPlaybackInterval = rational(1, 4);
const rational kVideoPlaybackInterval = rational(1, 2);
ViewerWidget::ViewerWidget(QWidget *parent) : ViewerWidget::ViewerWidget(QWidget *parent) :
super(false, true, parent), super(false, true, parent),
playback_speed_(0), playback_speed_(0),
@@ -69,6 +71,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
prequeuing_audio_(0), prequeuing_audio_(0),
record_armed_(false), record_armed_(false),
recording_(false), recording_(false),
first_requeue_watcher_(nullptr),
enable_audio_scrubbing_(true) enable_audio_scrubbing_(true)
{ {
// Set up main layout // Set up main layout
@@ -635,12 +638,20 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing()
void ViewerWidget::QueueStarved() void ViewerWidget::QueueStarved()
{ {
static const int kMaximumWaitTime = 250; static const int kMaximumWaitTimeMs = 250;
static const rational kMaximumWaitTime(kMaximumWaitTimeMs, 1000);
qint64 now = QDateTime::currentMSecsSinceEpoch(); qint64 now = QDateTime::currentMSecsSinceEpoch();
if (!queue_starved_start_) { if (!queue_starved_start_) {
queue_starved_start_ = now; queue_starved_start_ = now;
} else if (now > queue_starved_start_ + kMaximumWaitTime) { } else if (now > queue_starved_start_ + kMaximumWaitTimeMs) {
if (first_requeue_watcher_) {
if (GetTime() + kMaximumWaitTime < first_requeue_watcher_->property("time").value<rational>()) {
// We still have time
return;
}
}
ForceRequeueFromCurrentTime(); ForceRequeueFromCurrentTime();
queue_starved_start_ = 0; queue_starved_start_ = 0;
} }
@@ -653,11 +664,19 @@ void ViewerWidget::QueueNoLongerStarved()
void ViewerWidget::ForceRequeueFromCurrentTime() void ViewerWidget::ForceRequeueFromCurrentTime()
{ {
// Allow half a second for requeue to complete
static const rational kRequeueWaitTime(1);
ClearVideoAutoCacherQueue(); ClearVideoAutoCacherQueue();
queue_watchers_.clear();
int queue = DeterminePlaybackQueueSize(); int queue = DeterminePlaybackQueueSize();
playback_queue_next_frame_ = GetTimestamp() + playback_speed_; playback_queue_next_frame_ = GetTimestamp() + playback_speed_ * Timecode::time_to_timestamp(kRequeueWaitTime, timebase(), Timecode::kFloor);;
for (int i=queue_watchers_.size(); i<queue; i++) { first_requeue_watcher_ = nullptr;
RequestNextFrameForQueue(); for (int i=0; i<queue; i++) {
RenderTicketWatcher *watcher = RequestNextFrameForQueue();
if (!first_requeue_watcher_) {
first_requeue_watcher_ = watcher;
}
} }
} }
@@ -899,8 +918,10 @@ void ViewerWidget::SetDisplayImage(QVariant frame)
} }
} }
void ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority priority, bool increment) RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority priority, bool increment)
{ {
RenderTicketWatcher *watcher = nullptr;
rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_, rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_,
timebase()); timebase());
@@ -909,12 +930,14 @@ void ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority priority, bool
playback_queue_next_frame_ += playback_speed_; playback_queue_next_frame_ += playback_speed_;
} }
RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher = new RenderTicketWatcher();
watcher->setProperty("time", QVariant::fromValue(next_time)); watcher->setProperty("time", QVariant::fromValue(next_time));
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue);
queue_watchers_.append(watcher); queue_watchers_.append(watcher);
watcher->SetTicket(GetFrame(next_time, priority)); watcher->SetTicket(GetFrame(next_time, priority));
} }
return watcher;
} }
RenderTicketPtr ViewerWidget::GetFrame(const rational &t, RenderTicketPriority priority) RenderTicketPtr ViewerWidget::GetFrame(const rational &t, RenderTicketPriority priority)
@@ -986,7 +1009,7 @@ int ViewerWidget::DeterminePlaybackQueueSize()
int remaining_frames = (end_ts - GetTimestamp()) / playback_speed_; int remaining_frames = (end_ts - GetTimestamp()) / playback_speed_;
// Generate maximum queue // Generate maximum queue
int max_frames = qCeil(kAudioPlaybackInterval.toDouble() / timebase().toDouble()); int max_frames = qCeil(kVideoPlaybackInterval.toDouble() / timebase().toDouble());
return qMin(max_frames, remaining_frames); return qMin(max_frames, remaining_frames);
} }
@@ -1095,6 +1118,10 @@ void ViewerWidget::RendererGeneratedFrameForQueue()
} }
} }
if (first_requeue_watcher_ == watcher) {
first_requeue_watcher_ = nullptr;
}
delete watcher; delete watcher;
} }
@@ -1465,10 +1492,11 @@ void ViewerWidget::PlaybackTimerUpdate()
} }
if (IsPlaying()) { if (IsPlaying()) {
int count = 0; while (queue_watchers_.size() < DeterminePlaybackQueueSize()) {
for (int i=display_widget_->queue()->size(); i<DeterminePlaybackQueueSize(); i++) { if (!RequestNextFrameForQueue()) {
RequestNextFrameForQueue(); // Prevent infinite loop
count++; break;
}
} }
} }
+2 -1
View File
@@ -195,7 +195,7 @@ private:
void SetDisplayImage(QVariant frame); void SetDisplayImage(QVariant frame);
void RequestNextFrameForQueue(RenderTicketPriority priority = RenderTicketPriority::kNormal, bool increment = true); RenderTicketWatcher *RequestNextFrameForQueue(RenderTicketPriority priority = RenderTicketPriority::kNormal, bool increment = true);
RenderTicketPtr GetFrame(const rational& t, RenderTicketPriority priority); RenderTicketPtr GetFrame(const rational& t, RenderTicketPriority priority);
@@ -280,6 +280,7 @@ private:
QString recording_filename_; QString recording_filename_;
qint64 queue_starved_start_; qint64 queue_starved_start_;
RenderTicketWatcher *first_requeue_watcher_;
bool enable_audio_scrubbing_; bool enable_audio_scrubbing_;