This is, surprisingly, the most useful for VA-API decodes; they can
have long latency at 1080p, and Futatabi's dropping scheme sometimes
caused massive unfairness. Our system doesn't pipeline all that
nicely, so just having multiple threads was the simplest solution.
The risk is that we now access VA-API from multiple threads, which
has a tendency to tickle bugs, but we'll see.
Of course, for CPU decoding, you will also benefit.
-struct PendingDecode {
- JPEGFrameView *destination;
-
- // For actual decodes (only if frame below is nullptr).
- FrameOnDisk primary, secondary;
- float fade_alpha; // Irrelevant if secondary.stream_idx == -1.
-
- // Already-decoded frames are also sent through PendingDecode,
- // so that they get drawn in the right order. If frame is nullptr,
- // it's a real decode.
- shared_ptr<Frame> frame;
-};
-
// There can be multiple JPEGFrameView instances, so make all the metrics static.
once_flag jpeg_metrics_inited;
atomic<int64_t> metric_jpeg_cache_used_bytes{ 0 }; // Same value as cache_bytes_used.
// There can be multiple JPEGFrameView instances, so make all the metrics static.
once_flag jpeg_metrics_inited;
atomic<int64_t> metric_jpeg_cache_used_bytes{ 0 }; // Same value as cache_bytes_used.
-thread JPEGFrameView::jpeg_decoder_thread;
mutex cache_mu;
map<FrameOnDisk, LRUFrame, FrameOnDiskLexicalOrder> cache; // Under cache_mu.
size_t cache_bytes_used = 0; // Under cache_mu.
mutex cache_mu;
map<FrameOnDisk, LRUFrame, FrameOnDiskLexicalOrder> cache; // Under cache_mu.
size_t cache_bytes_used = 0; // Under cache_mu.
-condition_variable any_pending_decodes;
-deque<PendingDecode> pending_decodes; // Under cache_mu.
atomic<size_t> event_counter{ 0 };
extern QGLWidget *global_share_widget;
extern atomic<bool> should_quit;
atomic<size_t> event_counter{ 0 };
extern QGLWidget *global_share_widget;
extern atomic<bool> should_quit;
CacheMissBehavior cache_miss_behavior = DECODE_IF_NOT_IN_CACHE;
{
unique_lock<mutex> lock(cache_mu); // TODO: Perhaps under another lock?
CacheMissBehavior cache_miss_behavior = DECODE_IF_NOT_IN_CACHE;
{
unique_lock<mutex> lock(cache_mu); // TODO: Perhaps under another lock?
- any_pending_decodes.wait(lock, [] {
+ any_pending_decodes.wait(lock, [this] {
return !pending_decodes.empty() || should_quit.load();
});
if (should_quit.load())
return !pending_decodes.empty() || should_quit.load();
});
if (should_quit.load())
decode = pending_decodes.front();
pending_decodes.pop_front();
decode = pending_decodes.front();
pending_decodes.pop_front();
- size_t num_pending = 0;
- for (const PendingDecode &other_decode : pending_decodes) {
- if (other_decode.destination == decode.destination) {
- ++num_pending;
- }
- }
- if (num_pending > 3) {
+ if (pending_decodes.size() > 3) {
cache_miss_behavior = RETURN_NULLPTR_IF_NOT_IN_CACHE;
}
}
if (decode.frame != nullptr) {
// Already decoded, so just show it.
cache_miss_behavior = RETURN_NULLPTR_IF_NOT_IN_CACHE;
}
}
if (decode.frame != nullptr) {
// Already decoded, so just show it.
- decode.destination->setDecodedFrame(decode.frame, nullptr, 1.0f);
+ setDecodedFrame(decode.frame, nullptr, 1.0f);
- shared_ptr<Frame> frame = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &decode.destination->frame_reader, &found_in_cache);
+ shared_ptr<Frame> frame = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &frame_reader, &found_in_cache);
if (frame == nullptr) {
assert(cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE);
if (frame == nullptr) {
assert(cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE);
}
// TODO: Could we get jitter between non-interpolated and interpolated frames here?
}
// TODO: Could we get jitter between non-interpolated and interpolated frames here?
- decode.destination->setDecodedFrame(primary_frame, secondary_frame, decode.fade_alpha);
+ setDecodedFrame(primary_frame, secondary_frame, decode.fade_alpha);
-void JPEGFrameView::shutdown()
+JPEGFrameView::~JPEGFrameView()
{
any_pending_decodes.notify_all();
jpeg_decoder_thread.join();
{
any_pending_decodes.notify_all();
jpeg_decoder_thread.join();
decode.primary = frame;
decode.secondary = secondary_frame;
decode.fade_alpha = fade_alpha;
decode.primary = frame;
decode.secondary = secondary_frame;
decode.fade_alpha = fade_alpha;
- decode.destination = this;
pending_decodes.push_back(decode);
any_pending_decodes.notify_all();
}
pending_decodes.push_back(decode);
any_pending_decodes.notify_all();
}
lock_guard<mutex> lock(cache_mu);
PendingDecode decode;
decode.frame = std::move(frame);
lock_guard<mutex> lock(cache_mu);
PendingDecode decode;
decode.frame = std::move(frame);
- decode.destination = this;
pending_decodes.push_back(decode);
any_pending_decodes.notify_all();
}
pending_decodes.push_back(decode);
any_pending_decodes.notify_all();
}
-ResourcePool *resource_pool = nullptr;
-
void JPEGFrameView::initializeGL()
{
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
check_error();
void JPEGFrameView::initializeGL()
{
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
check_error();
- static once_flag once;
- call_once(once, [] {
- resource_pool = new ResourcePool;
- jpeg_decoder_thread = std::thread(jpeg_decoder_thread_func);
- });
+ resource_pool = new ResourcePool;
+ jpeg_decoder_thread = std::thread(&JPEGFrameView::jpeg_decoder_thread_func, this);
ycbcr_converter.reset(new YCbCrConverter(YCbCrConverter::OUTPUT_TO_RGBA, resource_pool));
ycbcr_converter.reset(new YCbCrConverter(YCbCrConverter::OUTPUT_TO_RGBA, resource_pool));
#include <movit/mix_effect.h>
#include <movit/ycbcr_input.h>
#include <stdint.h>
#include <movit/mix_effect.h>
#include <movit/ycbcr_input.h>
#include <stdint.h>
+#include <condition_variable>
+#include <deque>
#include <thread>
enum CacheMissBehavior {
#include <thread>
enum CacheMissBehavior {
public:
JPEGFrameView(QWidget *parent);
public:
JPEGFrameView(QWidget *parent);
void setFrame(unsigned stream_idx, FrameOnDisk frame, FrameOnDisk secondary_frame = {}, float fade_alpha = 0.0f);
void setFrame(std::shared_ptr<Frame> frame);
void setFrame(unsigned stream_idx, FrameOnDisk frame, FrameOnDisk secondary_frame = {}, float fade_alpha = 0.0f);
void setFrame(std::shared_ptr<Frame> frame);
void setDecodedFrame(std::shared_ptr<Frame> frame, std::shared_ptr<Frame> secondary_frame, float fade_alpha);
void set_overlay(const std::string &text); // Blank for none.
void setDecodedFrame(std::shared_ptr<Frame> frame, std::shared_ptr<Frame> secondary_frame, float fade_alpha);
void set_overlay(const std::string &text); // Blank for none.
- static void shutdown();
-
void paintGL() override;
private:
void paintGL() override;
private:
- static void jpeg_decoder_thread_func();
+ void jpeg_decoder_thread_func();
FrameReader frame_reader;
FrameReader frame_reader;
- static std::thread jpeg_decoder_thread;
+ std::thread jpeg_decoder_thread;
+ movit::ResourcePool *resource_pool = nullptr;
+
+ struct PendingDecode {
+ // For actual decodes (only if frame below is nullptr).
+ FrameOnDisk primary, secondary;
+ float fade_alpha; // Irrelevant if secondary.stream_idx == -1.
+
+ // Already-decoded frames are also sent through PendingDecode,
+ // so that they get drawn in the right order. If frame is nullptr,
+ // it's a real decode.
+ std::shared_ptr<Frame> frame;
+ };
+
+ std::condition_variable any_pending_decodes;
+ std::deque<PendingDecode> pending_decodes; // Under cache_mu.
};
#endif // !defined(_JPEG_FRAME_VIEW_H)
};
#endif // !defined(_JPEG_FRAME_VIEW_H)
should_quit = true;
record_thread.join();
should_quit = true;
record_thread.join();
- JPEGFrameView::shutdown();