23 constexpr
float kInv255 = 1.0f / 255.0f;
24 constexpr
float kVectorscopeUMax = 0.43600f;
25 constexpr
float kVectorscopeVMax = 0.61500f;
27 static int clamp_int(
int value,
int min_value,
int max_value) {
28 return std::max(min_value, std::min(max_value, value));
31 static int byte_bin(
float value) {
32 return clamp_int(
static_cast<int>(std::round(value * 255.0f)), 0, 255);
35 static const std::array<float, 256>& inv_alpha_lut() {
36 static const std::array<float, 256> lut = [] {
37 std::array<float, 256> values{};
39 for (
int i = 1; i < 256; ++i)
40 values[i] = 255.0f /
static_cast<float>(i);
46 static Json::Value json_array_from_vector(
const std::vector<int>& values) {
47 Json::Value array(Json::arrayValue);
48 for (
size_t i = 0; i < values.size(); ++i)
49 array.append(values[i]);
53 static Json::Value json_array_from_vector(
const std::vector<uint32_t>& values) {
54 Json::Value array(Json::arrayValue);
55 for (
size_t i = 0; i < values.size(); ++i)
56 array.append(Json::Value::UInt(values[i]));
60 static Json::Value json_array_from_vector(
const std::vector<float>& values) {
61 Json::Value array(Json::arrayValue);
62 for (
size_t i = 0; i < values.size(); ++i)
63 array.append(values[i]);
70 waveform_columns(256),
72 vectorscope_size(256),
79 waveform_column_map_width(0),
80 waveform_column_map_columns(0),
85 FrameScope::FrameScope(std::shared_ptr<Frame> new_frame,
int new_waveform_columns,
int new_audio_buckets,
int new_vectorscope_size)
87 waveform_columns(std::max(1, new_waveform_columns)),
88 audio_buckets(std::max(1, new_audio_buckets)),
89 vectorscope_size(std::max(1, new_vectorscope_size)),
96 waveform_column_map_width(0),
97 waveform_column_map_columns(0),
102 void FrameScope::reset() {
108 void FrameScope::reset_video() {
109 video_present =
false;
114 clipped_highlights = 0;
118 ensure_video_buffers();
119 std::fill(histogram_luma.begin(), histogram_luma.end(), 0u);
120 std::fill(histogram_red.begin(), histogram_red.end(), 0u);
121 std::fill(histogram_green.begin(), histogram_green.end(), 0u);
122 std::fill(histogram_blue.begin(), histogram_blue.end(), 0u);
123 std::fill(waveform_luma.begin(), waveform_luma.end(), 0u);
124 std::fill(waveform_red.begin(), waveform_red.end(), 0u);
125 std::fill(waveform_green.begin(), waveform_green.end(), 0u);
126 std::fill(waveform_blue.begin(), waveform_blue.end(), 0u);
127 std::fill(vectorscope.begin(), vectorscope.end(), 0u);
131 void FrameScope::reset_audio() {
132 audio_present =
false;
135 audio_sample_rate = 0;
138 audio_clipped_samples.clear();
139 audio_waveform_min.clear();
140 audio_waveform_max.clear();
144 void FrameScope::ensure_video_buffers() {
145 histogram_luma.resize(256);
146 histogram_red.resize(256);
147 histogram_green.resize(256);
148 histogram_blue.resize(256);
149 waveform_luma.resize(
static_cast<size_t>(waveform_columns) *
static_cast<size_t>(waveform_bins));
150 waveform_red.resize(
static_cast<size_t>(waveform_columns) *
static_cast<size_t>(waveform_bins));
151 waveform_green.resize(
static_cast<size_t>(waveform_columns) *
static_cast<size_t>(waveform_bins));
152 waveform_blue.resize(
static_cast<size_t>(waveform_columns) *
static_cast<size_t>(waveform_bins));
153 vectorscope.resize(
static_cast<size_t>(vectorscope_size) *
static_cast<size_t>(vectorscope_size));
156 void FrameScope::ensure_audio_buffers() {
157 audio_peak.assign(
static_cast<size_t>(audio_channels), 0.0f);
158 audio_rms.assign(
static_cast<size_t>(audio_channels), 0.0f);
159 audio_clipped_samples.assign(
static_cast<size_t>(audio_channels), 0u);
160 audio_waveform_min.assign(
static_cast<size_t>(audio_channels), std::vector<float>(
static_cast<size_t>(audio_buckets), 0.0f));
161 audio_waveform_max.assign(
static_cast<size_t>(audio_channels), std::vector<float>(
static_cast<size_t>(audio_buckets), 0.0f));
164 void FrameScope::rebuild_waveform_column_map(
int width) {
165 if (width == waveform_column_map_width && waveform_columns == waveform_column_map_columns &&
166 static_cast<int>(waveform_column_map.size()) == width)
169 waveform_column_map.resize(
static_cast<size_t>(width));
170 const int waveform_column_limit = waveform_columns - 1;
171 for (
int x = 0; x < width; ++x)
172 waveform_column_map[
static_cast<size_t>(x)] = clamp_int((x * waveform_columns) / std::max(1, width), 0, waveform_column_limit);
174 waveform_column_map_width = width;
175 waveform_column_map_columns = waveform_columns;
184 waveform_columns = std::max(1, columns);
191 audio_buckets = std::max(1, buckets);
198 vectorscope_size = std::max(1, size);
205 roi_x = std::max(0.0f, std::min(1.0f, x));
206 roi_y = std::max(0.0f, std::min(1.0f, y));
207 roi_width = std::max(0.0f, std::min(1.0f - roi_x, width));
208 roi_height = std::max(0.0f, std::min(1.0f - roi_y, height));
209 roi_enabled = roi_width > 0.0f && roi_height > 0.0f;
226 void FrameScope::analyze() {
236 void FrameScope::analyze_video() {
239 std::shared_ptr<QImage> image = frame->GetImage();
240 if (!image || image->isNull())
243 video_present =
true;
244 const int width = image->width();
245 const int height = image->height();
251 start_x = clamp_int(
static_cast<int>(std::floor(roi_x * width)), 0, width - 1);
252 start_y = clamp_int(
static_cast<int>(std::floor(roi_y * height)), 0, height - 1);
253 end_x = clamp_int(
static_cast<int>(std::ceil((roi_x + roi_width) * width)), start_x + 1, width);
254 end_y = clamp_int(
static_cast<int>(std::ceil((roi_y + roi_height) * height)), start_y + 1, height);
256 video_width = std::max(1, end_x - start_x);
257 video_height = std::max(1, end_y - start_y);
258 ensure_video_buffers();
260 double luma_sum = 0.0;
261 double pixel_total = 0.0;
263 clipped_highlights = 0;
268 const int bytes_per_line = image->bytesPerLine();
269 const unsigned char* bits = image->constBits();
270 const auto& inv_alpha = inv_alpha_lut();
271 rebuild_waveform_column_map(video_width);
272 const float vectorscope_center =
static_cast<float>(vectorscope_size - 1) * 0.5f;
273 const float vectorscope_scale = vectorscope_center;
275 for (
int y = start_y; y < end_y; ++y) {
276 const unsigned char* row = bits + (
static_cast<size_t>(y) * bytes_per_line);
277 for (
int x = start_x; x < end_x; ++x) {
278 const unsigned char* pixel = row + (
static_cast<size_t>(x) * 4);
279 const int red = pixel[0];
280 const int green = pixel[1];
281 const int blue = pixel[2];
282 const int alpha = pixel[3];
290 redf = red * kInv255;
291 greenf = green * kInv255;
292 bluef = blue * kInv255;
294 const float unpremultiply = inv_alpha[alpha];
295 redf = std::min(1.0f, (red * unpremultiply) * kInv255);
296 greenf = std::min(1.0f, (green * unpremultiply) * kInv255);
297 bluef = std::min(1.0f, (blue * unpremultiply) * kInv255);
299 const float luma = (0.299f * redf) + (0.587f * greenf) + (0.114f * bluef);
301 const int luma_idx = byte_bin(luma);
302 const int red_idx = byte_bin(redf);
303 const int green_idx = byte_bin(greenf);
304 const int blue_idx = byte_bin(bluef);
305 const int roi_column = x - start_x;
306 const size_t waveform_offset =
static_cast<size_t>(waveform_column_map[
static_cast<size_t>(roi_column)]) *
static_cast<size_t>(waveform_bins);
307 const float u = -0.14713f * redf - 0.28886f * greenf + 0.43600f * bluef;
308 const float v = 0.61500f * redf - 0.51499f * greenf - 0.10001f * bluef;
309 const float normalized_u = u / kVectorscopeUMax;
310 const float normalized_v = v / kVectorscopeVMax;
311 const int vector_x = clamp_int(
static_cast<int>(std::round(vectorscope_center + (normalized_u * vectorscope_scale))), 0, vectorscope_size - 1);
312 const int vector_y = clamp_int(
static_cast<int>(std::round(vectorscope_center - (normalized_v * vectorscope_scale))), 0, vectorscope_size - 1);
313 const size_t vector_offset = (
static_cast<size_t>(vector_y) *
static_cast<size_t>(vectorscope_size)) +
static_cast<size_t>(vector_x);
315 histogram_luma[luma_idx]++;
316 histogram_red[red_idx]++;
317 histogram_green[green_idx]++;
318 histogram_blue[blue_idx]++;
319 waveform_luma[waveform_offset + luma_idx]++;
320 waveform_red[waveform_offset + red_idx]++;
321 waveform_green[waveform_offset + green_idx]++;
322 waveform_blue[waveform_offset + blue_idx]++;
323 vectorscope[vector_offset]++;
330 clipped_highlights++;
333 if (green_idx >= 253)
340 avg_luma = pixel_total > 0.0 ? (luma_sum / pixel_total) : 0.0;
343 void FrameScope::analyze_audio() {
344 if (!frame->has_audio_data || !frame->audio)
347 const int channels = frame->GetAudioChannelsCount();
348 const int samples = frame->GetAudioSamplesCount();
349 if (channels <= 0 || samples <= 0)
352 audio_present =
true;
353 audio_channels = channels;
354 audio_samples = samples;
355 audio_sample_rate = frame->SampleRate();
356 ensure_audio_buffers();
357 std::vector<double> rms_sums(
static_cast<size_t>(channels), 0.0);
359 for (
int channel = 0; channel < channels; ++channel) {
360 float* channel_samples = frame->GetAudioSamples(channel);
361 if (!channel_samples)
364 std::fill(audio_waveform_min[channel].begin(), audio_waveform_min[channel].end(), 1.0f);
365 std::fill(audio_waveform_max[channel].begin(), audio_waveform_max[channel].end(), -1.0f);
367 for (
int sample = 0; sample < samples; ++sample) {
368 const float value = channel_samples[sample];
369 const float abs_value = std::abs(value);
370 const int bucket = clamp_int((sample * audio_buckets) / std::max(1, samples), 0, audio_buckets - 1);
372 audio_peak[channel] = std::max(audio_peak[channel], abs_value);
373 rms_sums[channel] +=
static_cast<double>(value) *
static_cast<double>(value);
374 if (abs_value >= 0.999f)
375 audio_clipped_samples[channel]++;
377 audio_waveform_min[channel][bucket] = std::min(audio_waveform_min[channel][bucket], value);
378 audio_waveform_max[channel][bucket] = std::max(audio_waveform_max[channel][bucket], value);
381 for (
int bucket = 0; bucket < audio_buckets; ++bucket) {
382 if (audio_waveform_min[channel][bucket] > audio_waveform_max[channel][bucket]) {
383 audio_waveform_min[channel][bucket] = 0.0f;
384 audio_waveform_max[channel][bucket] = 0.0f;
389 for (
int channel = 0; channel < channels; ++channel) {
390 audio_rms[channel] = samples > 0 ?
static_cast<float>(std::sqrt(rms_sums[channel] /
static_cast<double>(samples))) : 0.0f;
394 void FrameScope::rebuild_json()
const {
395 scope_data = Json::Value(Json::objectValue);
396 scope_data[
"version"] = 1;
398 Json::Value video(Json::objectValue);
399 video[
"present"] = video_present;
401 video[
"width"] = video_width;
402 video[
"height"] = video_height;
404 video[
"summary"] = Json::Value(Json::objectValue);
405 video[
"summary"][
"avg_luma"] = avg_luma;
406 video[
"summary"][
"clipped_shadows"] = clipped_shadows;
407 video[
"summary"][
"clipped_highlights"] = clipped_highlights;
408 video[
"summary"][
"clipped_red"] = clipped_red;
409 video[
"summary"][
"clipped_green"] = clipped_green;
410 video[
"summary"][
"clipped_blue"] = clipped_blue;
412 video[
"histogram"] = Json::Value(Json::objectValue);
413 video[
"histogram"][
"luma"] = json_array_from_vector(histogram_luma);
414 video[
"histogram"][
"red"] = json_array_from_vector(histogram_red);
415 video[
"histogram"][
"green"] = json_array_from_vector(histogram_green);
416 video[
"histogram"][
"blue"] = json_array_from_vector(histogram_blue);
418 video[
"waveform"] = Json::Value(Json::objectValue);
419 video[
"waveform"][
"columns"] = waveform_columns;
420 video[
"waveform"][
"bins"] = waveform_bins;
421 video[
"waveform"][
"luma"] = json_array_from_vector(waveform_luma);
422 video[
"waveform"][
"red"] = json_array_from_vector(waveform_red);
423 video[
"waveform"][
"green"] = json_array_from_vector(waveform_green);
424 video[
"waveform"][
"blue"] = json_array_from_vector(waveform_blue);
426 video[
"vectorscope"] = Json::Value(Json::objectValue);
427 video[
"vectorscope"][
"size"] = vectorscope_size;
428 video[
"vectorscope"][
"density"] = json_array_from_vector(vectorscope);
430 scope_data[
"video"] = video;
432 Json::Value audio(Json::objectValue);
433 audio[
"present"] = audio_present;
435 audio[
"channels"] = audio_channels;
436 audio[
"samples"] = audio_samples;
437 audio[
"sample_rate"] = audio_sample_rate;
439 audio[
"summary"] = Json::Value(Json::objectValue);
440 audio[
"summary"][
"peak"] = json_array_from_vector(audio_peak);
441 audio[
"summary"][
"rms"] = json_array_from_vector(audio_rms);
442 audio[
"summary"][
"clipped_samples"] = json_array_from_vector(audio_clipped_samples);
444 audio[
"waveform"] = Json::Value(Json::objectValue);
445 audio[
"waveform"][
"buckets"] = audio_buckets;
446 audio[
"waveform"][
"min"] = Json::Value(Json::arrayValue);
447 audio[
"waveform"][
"max"] = Json::Value(Json::arrayValue);
448 for (
int channel = 0; channel < audio_channels; ++channel) {
449 audio[
"waveform"][
"min"].append(json_array_from_vector(audio_waveform_min[
static_cast<size_t>(channel)]));
450 audio[
"waveform"][
"max"].append(json_array_from_vector(audio_waveform_max[
static_cast<size_t>(channel)]));
453 scope_data[
"audio"] = audio;
466 return scope_data.toStyledString();
469 std::vector<int> FrameScope::copy_to_int_vector(
const std::vector<uint32_t>& values) {
470 std::vector<int> copy(values.size(), 0);
471 const uint32_t max_int =
static_cast<uint32_t
>(std::numeric_limits<int>::max());
472 for (
size_t i = 0; i < values.size(); ++i)
473 copy[i] =
static_cast<int>(std::min(values[i], max_int));
478 if (channel < 0 || channel >=
static_cast<int>(audio_waveform_min.size()))
479 return std::vector<float>();
480 return audio_waveform_min[
static_cast<size_t>(channel)];
484 if (channel < 0 || channel >=
static_cast<int>(audio_waveform_max.size()))
485 return std::vector<float>();
486 return audio_waveform_max[
static_cast<size_t>(channel)];