X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=futatabi%2Fjpeg_frame_view.cpp;h=c9b8090544b8313f7cf769812580fa4e11e0fd39;hb=3974e42a86665521c477ab0c1dfc0af1a28bede1;hp=9dc2ec25125ce3880a3db8eabb95bafd7cd1824c;hpb=5e85184e5e35367d2bbcd41970c0a4f7fde8c9c5;p=nageru
diff --git a/futatabi/jpeg_frame_view.cpp b/futatabi/jpeg_frame_view.cpp
index 9dc2ec2..c9b8090 100644
--- a/futatabi/jpeg_frame_view.cpp
+++ b/futatabi/jpeg_frame_view.cpp
@@ -1,7 +1,10 @@
#include "jpeg_frame_view.h"
#include "defs.h"
+#include "flags.h"
#include "jpeg_destroyer.h"
+#include "jpeglib_error_wrapper.h"
+#include "shared/metrics.h"
#include "shared/post_to_main_thread.h"
#include "video_stream.h"
#include "ycbcr_converter.h"
@@ -30,9 +33,8 @@ using namespace std;
namespace {
// Just an arbitrary order for std::map.
-struct FrameOnDiskLexicalOrder
-{
- bool operator() (const FrameOnDisk &a, const FrameOnDisk &b) const
+struct FrameOnDiskLexicalOrder {
+ bool operator()(const FrameOnDisk &a, const FrameOnDisk &b) const
{
if (a.pts != b.pts)
return a.pts < b.pts;
@@ -70,6 +72,18 @@ struct PendingDecode {
shared_ptr frame;
};
+// There can be multiple JPEGFrameView instances, so make all the metrics static.
+once_flag jpeg_metrics_inited;
+atomic metric_jpeg_cache_used_bytes{ 0 }; // Same value as cache_bytes_used.
+atomic metric_jpeg_cache_limit_bytes{ size_t(CACHE_SIZE_MB) * 1024 * 1024 };
+atomic metric_jpeg_cache_given_up_frames{ 0 };
+atomic metric_jpeg_cache_hit_frames{ 0 };
+atomic metric_jpeg_cache_miss_frames{ 0 };
+atomic metric_jpeg_software_decode_frames{ 0 };
+atomic metric_jpeg_software_fail_frames{ 0 };
+atomic metric_jpeg_vaapi_decode_frames{ 0 };
+atomic metric_jpeg_vaapi_fail_frames{ 0 };
+
} // namespace
thread JPEGFrameView::jpeg_decoder_thread;
@@ -78,7 +92,7 @@ map cache; // Under cache_mu.
size_t cache_bytes_used = 0; // Under cache_mu.
condition_variable any_pending_decodes;
deque pending_decodes; // Under cache_mu.
-atomic event_counter{0};
+atomic event_counter{ 0 };
extern QGLWidget *global_share_widget;
extern atomic should_quit;
@@ -88,29 +102,36 @@ shared_ptr decode_jpeg(const string &jpeg)
if (vaapi_jpeg_decoding_usable) {
frame = decode_jpeg_vaapi(jpeg);
if (frame != nullptr) {
+ ++metric_jpeg_vaapi_decode_frames;
return frame;
}
fprintf(stderr, "VA-API hardware decoding failed; falling back to software.\n");
+ ++metric_jpeg_vaapi_fail_frames;
}
frame.reset(new Frame);
jpeg_decompress_struct dinfo;
- jpeg_error_mgr jerr;
- dinfo.err = jpeg_std_error(&jerr);
- jpeg_create_decompress(&dinfo);
+ JPEGWrapErrorManager error_mgr(&dinfo);
+ if (!error_mgr.run([&dinfo] { jpeg_create_decompress(&dinfo); })) {
+ return get_black_frame();
+ }
JPEGDestroyer destroy_dinfo(&dinfo);
- jpeg_mem_src(&dinfo, reinterpret_cast(jpeg.data()), jpeg.size());
- jpeg_read_header(&dinfo, true);
+ if (!error_mgr.run([&dinfo, &jpeg] {
+ jpeg_mem_src(&dinfo, reinterpret_cast(jpeg.data()), jpeg.size());
+ jpeg_read_header(&dinfo, true);
+ })) {
+ return get_black_frame();
+ }
if (dinfo.num_components != 3) {
fprintf(stderr, "Not a color JPEG. (%d components, Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n",
- dinfo.num_components,
- dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor,
- dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor,
- dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor);
- exit(1);
+ dinfo.num_components,
+ dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor,
+ dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor,
+ dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor);
+ return get_black_frame();
}
if (dinfo.comp_info[0].h_samp_factor != dinfo.max_h_samp_factor ||
dinfo.comp_info[0].v_samp_factor != dinfo.max_v_samp_factor || // Y' must not be subsampled.
@@ -119,14 +140,18 @@ shared_ptr decode_jpeg(const string &jpeg)
(dinfo.max_h_samp_factor % dinfo.comp_info[1].h_samp_factor) != 0 ||
(dinfo.max_v_samp_factor % dinfo.comp_info[1].v_samp_factor) != 0) { // No 2:3 subsampling or other weirdness.
fprintf(stderr, "Unsupported subsampling scheme. (Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n",
- dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor,
- dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor,
- dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor);
+ dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor,
+ dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor,
+ dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor);
exit(1);
}
dinfo.raw_data_out = true;
- jpeg_start_decompress(&dinfo);
+ if (!error_mgr.run([&dinfo] {
+ jpeg_start_decompress(&dinfo);
+ })) {
+ return get_black_frame();
+ }
frame->width = dinfo.output_width;
frame->height = dinfo.output_height;
@@ -150,21 +175,26 @@ shared_ptr decode_jpeg(const string &jpeg)
frame->pitch_y = luma_width_blocks * DCTSIZE;
frame->pitch_chroma = chroma_width_blocks * DCTSIZE;
- JSAMPROW yptr[v_mcu_size], cbptr[v_mcu_size], crptr[v_mcu_size];
- JSAMPARRAY data[3] = { yptr, cbptr, crptr };
- for (unsigned y = 0; y < mcu_height_blocks; ++y) {
- // NOTE: The last elements of cbptr/crptr will be unused for vertically subsampled chroma.
- for (unsigned yy = 0; yy < v_mcu_size; ++yy) {
- yptr[yy] = frame->y.get() + (y * DCTSIZE * dinfo.max_v_samp_factor + yy) * frame->pitch_y;
- cbptr[yy] = frame->cb.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma;
- crptr[yy] = frame->cr.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma;
- }
-
- jpeg_read_raw_data(&dinfo, data, v_mcu_size);
+ if (!error_mgr.run([&dinfo, &frame, v_mcu_size, mcu_height_blocks] {
+ JSAMPROW yptr[v_mcu_size], cbptr[v_mcu_size], crptr[v_mcu_size];
+ JSAMPARRAY data[3] = { yptr, cbptr, crptr };
+ for (unsigned y = 0; y < mcu_height_blocks; ++y) {
+ // NOTE: The last elements of cbptr/crptr will be unused for vertically subsampled chroma.
+ for (unsigned yy = 0; yy < v_mcu_size; ++yy) {
+ yptr[yy] = frame->y.get() + (y * DCTSIZE * dinfo.max_v_samp_factor + yy) * frame->pitch_y;
+ cbptr[yy] = frame->cb.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma;
+ crptr[yy] = frame->cr.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma;
+ }
+
+ jpeg_read_raw_data(&dinfo, data, v_mcu_size);
+ }
+
+ (void)jpeg_finish_decompress(&dinfo);
+ })) {
+ return get_black_frame();
}
- (void)jpeg_finish_decompress(&dinfo);
-
+ ++metric_jpeg_software_decode_frames;
return frame;
}
@@ -172,7 +202,8 @@ void prune_cache()
{
// Assumes cache_mu is held.
int64_t bytes_still_to_remove = cache_bytes_used - (size_t(CACHE_SIZE_MB) * 1024 * 1024) * 9 / 10;
- if (bytes_still_to_remove <= 0) return;
+ if (bytes_still_to_remove <= 0)
+ return;
vector> lru_timestamps_and_size;
for (const auto &key_and_value : cache) {
@@ -187,12 +218,14 @@ void prune_cache()
for (const pair &it : lru_timestamps_and_size) {
lru_cutoff_point = it.first;
bytes_still_to_remove -= it.second;
- if (bytes_still_to_remove <= 0) break;
+ if (bytes_still_to_remove <= 0)
+ break;
}
- for (auto it = cache.begin(); it != cache.end(); ) {
+ for (auto it = cache.begin(); it != cache.end();) {
if (it->second.last_used <= lru_cutoff_point) {
cache_bytes_used -= frame_size(*it->second.frame);
+ metric_jpeg_cache_used_bytes = cache_bytes_used;
it = cache.erase(it);
} else {
++it;
@@ -204,23 +237,28 @@ shared_ptr decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavi
{
*did_decode = false;
{
- unique_lock lock(cache_mu);
+ lock_guard lock(cache_mu);
auto it = cache.find(frame_spec);
if (it != cache.end()) {
+ ++metric_jpeg_cache_hit_frames;
it->second.last_used = event_counter++;
return it->second.frame;
}
}
if (cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE) {
+ ++metric_jpeg_cache_given_up_frames;
return nullptr;
}
+ ++metric_jpeg_cache_miss_frames;
+
*did_decode = true;
shared_ptr frame = decode_jpeg(frame_reader->read_frame(frame_spec));
- unique_lock lock(cache_mu);
+ lock_guard lock(cache_mu);
cache_bytes_used += frame_size(*frame);
+ metric_jpeg_cache_used_bytes = cache_bytes_used;
cache[frame_spec] = LRUFrame{ frame, event_counter++ };
if (cache_bytes_used > size_t(CACHE_SIZE_MB) * 1024 * 1024) {
@@ -286,7 +324,7 @@ void JPEGFrameView::jpeg_decoder_thread_func()
++num_decoded;
if (num_decoded % 1000 == 0) {
fprintf(stderr, "Decoded %zu images, dropped %zu (%.2f%% dropped)\n",
- num_decoded, num_dropped, (100.0 * num_dropped) / (num_decoded + num_dropped));
+ num_decoded, num_dropped, (100.0 * num_dropped) / (num_decoded + num_dropped));
}
}
if (subframe_idx == 0) {
@@ -314,13 +352,24 @@ void JPEGFrameView::shutdown()
JPEGFrameView::JPEGFrameView(QWidget *parent)
: QGLWidget(parent, global_share_widget)
{
+ call_once(jpeg_metrics_inited, [] {
+ global_metrics.add("jpeg_cache_used_bytes", &metric_jpeg_cache_used_bytes, Metrics::TYPE_GAUGE);
+ global_metrics.add("jpeg_cache_limit_bytes", &metric_jpeg_cache_limit_bytes, Metrics::TYPE_GAUGE);
+ global_metrics.add("jpeg_cache_frames", { { "action", "given_up" } }, &metric_jpeg_cache_given_up_frames);
+ global_metrics.add("jpeg_cache_frames", { { "action", "hit" } }, &metric_jpeg_cache_hit_frames);
+ global_metrics.add("jpeg_cache_frames", { { "action", "miss" } }, &metric_jpeg_cache_miss_frames);
+ global_metrics.add("jpeg_decode_frames", { { "decoder", "software" }, { "result", "decode" } }, &metric_jpeg_software_decode_frames);
+ global_metrics.add("jpeg_decode_frames", { { "decoder", "software" }, { "result", "fail" } }, &metric_jpeg_software_fail_frames);
+ global_metrics.add("jpeg_decode_frames", { { "decoder", "vaapi" }, { "result", "decode" } }, &metric_jpeg_vaapi_decode_frames);
+ global_metrics.add("jpeg_decode_frames", { { "decoder", "vaapi" }, { "result", "fail" } }, &metric_jpeg_vaapi_fail_frames);
+ });
}
void JPEGFrameView::setFrame(unsigned stream_idx, FrameOnDisk frame, FrameOnDisk secondary_frame, float fade_alpha)
{
current_stream_idx = stream_idx; // TODO: Does this interact with fades?
- unique_lock lock(cache_mu);
+ lock_guard lock(cache_mu);
PendingDecode decode;
decode.primary = frame;
decode.secondary = secondary_frame;
@@ -332,7 +381,7 @@ void JPEGFrameView::setFrame(unsigned stream_idx, FrameOnDisk frame, FrameOnDisk
void JPEGFrameView::setFrame(shared_ptr frame)
{
- unique_lock lock(cache_mu);
+ lock_guard lock(cache_mu);
PendingDecode decode;
decode.frame = std::move(frame);
decode.destination = this;
@@ -453,3 +502,23 @@ void JPEGFrameView::set_overlay(const string &text)
// Don't refresh immediately; we might not have an OpenGL context here.
overlay_input_needs_refresh = true;
}
+
+shared_ptr get_black_frame()
+{
+ static shared_ptr black_frame;
+ static once_flag flag;
+ call_once(flag, [] {
+ black_frame.reset(new Frame);
+ black_frame->y.reset(new uint8_t[global_flags.width * global_flags.height]);
+ black_frame->cb.reset(new uint8_t[(global_flags.width / 2) * (global_flags.height / 2)]);
+ black_frame->cr.reset(new uint8_t[(global_flags.width / 2) * (global_flags.height / 2)]);
+ black_frame->width = global_flags.width;
+ black_frame->height = global_flags.height;
+ black_frame->chroma_subsampling_x = 2;
+ black_frame->chroma_subsampling_y = 2;
+ black_frame->pitch_y = global_flags.width;
+ black_frame->pitch_chroma = global_flags.width / 2;
+ });
+ ++metric_jpeg_software_fail_frames;
+ return black_frame;
+}