| /* |
| * Copyright (c) 2021, Alliance for Open Media. All rights reserved |
| * |
| * This source code is subject to the terms of the BSD 3-Clause Clear License |
| * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear |
| * License was not distributed with this source code in the LICENSE file, you |
| * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the |
| * Alliance for Open Media Patent License 1.0 was not distributed with this |
| * source code in the PATENTS file, you can obtain it at |
| * aomedia.org/license/patent-license/. |
| */ |
| |
| #include "stats/rate_hist.h" |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <math.h> |
| |
| #define RATE_BINS 100 |
| #define HIST_BAR_MAX 40 |
| |
| struct hist_bucket { |
| int low; |
| int high; |
| int count; |
| }; |
| |
| struct rate_hist { |
| int64_t *pts; |
| int *sz; |
| int samples; |
| int frames; |
| struct hist_bucket bucket[RATE_BINS]; |
| int total; |
| }; |
| |
| struct rate_hist *init_rate_histogram(const aom_codec_enc_cfg_t *cfg, |
| const aom_rational_t *fps) { |
| int i; |
| struct rate_hist *hist = malloc(sizeof(*hist)); |
| |
| // Determine the number of samples in the buffer. Use the file's framerate |
| // to determine the number of frames in rc_buf_sz milliseconds, with an |
| // adjustment (5/4) to account for alt-refs |
| hist->samples = cfg->rc_buf_sz * 5 / 4 * fps->num / fps->den / 1000; |
| |
| // prevent division by zero |
| if (hist->samples == 0) hist->samples = 1; |
| |
| hist->frames = 0; |
| hist->total = 0; |
| |
| hist->pts = calloc(hist->samples, sizeof(*hist->pts)); |
| hist->sz = calloc(hist->samples, sizeof(*hist->sz)); |
| for (i = 0; i < RATE_BINS; i++) { |
| hist->bucket[i].low = INT_MAX; |
| hist->bucket[i].high = 0; |
| hist->bucket[i].count = 0; |
| } |
| |
| return hist; |
| } |
| |
| void destroy_rate_histogram(struct rate_hist *hist) { |
| if (hist) { |
| free(hist->pts); |
| free(hist->sz); |
| free(hist); |
| } |
| } |
| |
| void update_rate_histogram(struct rate_hist *hist, |
| const aom_codec_enc_cfg_t *cfg, |
| const aom_codec_cx_pkt_t *pkt) { |
| int i; |
| int64_t then = 0; |
| int64_t avg_bitrate = 0; |
| int64_t sum_sz = 0; |
| const int64_t now = pkt->data.frame.pts * 1000 * |
| (uint64_t)cfg->g_timebase.num / |
| (uint64_t)cfg->g_timebase.den; |
| |
| int idx = hist->frames++ % hist->samples; |
| hist->pts[idx] = now; |
| hist->sz[idx] = (int)pkt->data.frame.sz; |
| |
| if (now < cfg->rc_buf_initial_sz) return; |
| |
| if (!cfg->rc_target_bitrate) return; |
| |
| then = now; |
| |
| /* Sum the size over the past rc_buf_sz ms */ |
| for (i = hist->frames; i > 0 && hist->frames - i < hist->samples; i--) { |
| const int i_idx = (i - 1) % hist->samples; |
| |
| then = hist->pts[i_idx]; |
| if (now - then > cfg->rc_buf_sz) break; |
| sum_sz += hist->sz[i_idx]; |
| } |
| |
| if (now == then) return; |
| |
| avg_bitrate = sum_sz * 8 * 1000 / (now - then); |
| idx = (int)(avg_bitrate * (RATE_BINS / 2) / (cfg->rc_target_bitrate * 1000)); |
| if (idx < 0) idx = 0; |
| if (idx > RATE_BINS - 1) idx = RATE_BINS - 1; |
| if (hist->bucket[idx].low > avg_bitrate) |
| hist->bucket[idx].low = (int)avg_bitrate; |
| if (hist->bucket[idx].high < avg_bitrate) |
| hist->bucket[idx].high = (int)avg_bitrate; |
| hist->bucket[idx].count++; |
| hist->total++; |
| } |
| |
| static int merge_hist_buckets(struct hist_bucket *bucket, int max_buckets, |
| int *num_buckets) { |
| int small_bucket = 0, merge_bucket = INT_MAX, big_bucket = 0; |
| int buckets = *num_buckets; |
| int i; |
| |
| /* Find the extrema for this list of buckets */ |
| big_bucket = small_bucket = 0; |
| for (i = 0; i < buckets; i++) { |
| if (bucket[i].count < bucket[small_bucket].count) small_bucket = i; |
| if (bucket[i].count > bucket[big_bucket].count) big_bucket = i; |
| } |
| |
| /* If we have too many buckets, merge the smallest with an adjacent |
| * bucket. |
| */ |
| while (buckets > max_buckets) { |
| int last_bucket = buckets - 1; |
| |
| /* merge the small bucket with an adjacent one. */ |
| if (small_bucket == 0) |
| merge_bucket = 1; |
| else if (small_bucket == last_bucket) |
| merge_bucket = last_bucket - 1; |
| else if (bucket[small_bucket - 1].count < bucket[small_bucket + 1].count) |
| merge_bucket = small_bucket - 1; |
| else |
| merge_bucket = small_bucket + 1; |
| |
| assert(abs(merge_bucket - small_bucket) <= 1); |
| assert(small_bucket < buckets); |
| assert(big_bucket < buckets); |
| assert(merge_bucket < buckets); |
| |
| if (merge_bucket < small_bucket) { |
| bucket[merge_bucket].high = bucket[small_bucket].high; |
| bucket[merge_bucket].count += bucket[small_bucket].count; |
| } else { |
| bucket[small_bucket].high = bucket[merge_bucket].high; |
| bucket[small_bucket].count += bucket[merge_bucket].count; |
| merge_bucket = small_bucket; |
| } |
| |
| assert(bucket[merge_bucket].low != bucket[merge_bucket].high); |
| |
| buckets--; |
| |
| /* Remove the merge_bucket from the list, and find the new small |
| * and big buckets while we're at it |
| */ |
| big_bucket = small_bucket = 0; |
| for (i = 0; i < buckets; i++) { |
| if (i > merge_bucket) bucket[i] = bucket[i + 1]; |
| |
| if (bucket[i].count < bucket[small_bucket].count) small_bucket = i; |
| if (bucket[i].count > bucket[big_bucket].count) big_bucket = i; |
| } |
| } |
| |
| *num_buckets = buckets; |
| return bucket[big_bucket].count; |
| } |
| |
| static void show_histogram(const struct hist_bucket *bucket, int buckets, |
| int total, int scale) { |
| const char *pat1, *pat2; |
| int i; |
| |
| switch ((int)(log(bucket[buckets - 1].high) / log(10)) + 1) { |
| case 1: |
| case 2: |
| pat1 = "%4d %2s: "; |
| pat2 = "%4d-%2d: "; |
| break; |
| case 3: |
| pat1 = "%5d %3s: "; |
| pat2 = "%5d-%3d: "; |
| break; |
| case 4: |
| pat1 = "%6d %4s: "; |
| pat2 = "%6d-%4d: "; |
| break; |
| case 5: |
| pat1 = "%7d %5s: "; |
| pat2 = "%7d-%5d: "; |
| break; |
| case 6: |
| pat1 = "%8d %6s: "; |
| pat2 = "%8d-%6d: "; |
| break; |
| case 7: |
| pat1 = "%9d %7s: "; |
| pat2 = "%9d-%7d: "; |
| break; |
| default: |
| pat1 = "%12d %10s: "; |
| pat2 = "%12d-%10d: "; |
| break; |
| } |
| |
| for (i = 0; i < buckets; i++) { |
| int len; |
| int j; |
| float pct; |
| |
| pct = (float)(100.0 * bucket[i].count / total); |
| len = HIST_BAR_MAX * bucket[i].count / scale; |
| if (len < 1) len = 1; |
| assert(len <= HIST_BAR_MAX); |
| |
| if (bucket[i].low == bucket[i].high) |
| fprintf(stderr, pat1, bucket[i].low, ""); |
| else |
| fprintf(stderr, pat2, bucket[i].low, bucket[i].high); |
| |
| for (j = 0; j < HIST_BAR_MAX; j++) fprintf(stderr, j < len ? "=" : " "); |
| fprintf(stderr, "\t%5d (%6.2f%%)\n", bucket[i].count, pct); |
| } |
| } |
| |
| void show_q_histogram(const int counts[64], int max_buckets) { |
| struct hist_bucket bucket[64]; |
| int buckets = 0; |
| int total = 0; |
| int scale; |
| int i; |
| |
| for (i = 0; i < 64; i++) { |
| if (counts[i]) { |
| bucket[buckets].low = bucket[buckets].high = i; |
| bucket[buckets].count = counts[i]; |
| buckets++; |
| total += counts[i]; |
| } |
| } |
| |
| fprintf(stderr, "\nQuantizer Selection:\n"); |
| scale = merge_hist_buckets(bucket, max_buckets, &buckets); |
| show_histogram(bucket, buckets, total, scale); |
| } |
| |
| void show_rate_histogram(struct rate_hist *hist, const aom_codec_enc_cfg_t *cfg, |
| int max_buckets) { |
| int i, scale; |
| int buckets = 0; |
| |
| for (i = 0; i < RATE_BINS; i++) { |
| if (hist->bucket[i].low == INT_MAX) continue; |
| hist->bucket[buckets++] = hist->bucket[i]; |
| } |
| |
| fprintf(stderr, "\nRate (over %dms window):\n", cfg->rc_buf_sz); |
| scale = merge_hist_buckets(hist->bucket, max_buckets, &buckets); |
| show_histogram(hist->bucket, buckets, hist->total, scale); |
| } |