| /* |
| * Copyright (c) 2016, Alliance for Open Media. All rights reserved |
| * |
| * This source code is subject to the terms of the BSD 2 Clause License and |
| * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
| * was not distributed with this source code in the LICENSE file, you can |
| * obtain it at www.aomedia.org/license/software. 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 www.aomedia.org/license/patent. |
| */ |
| |
| #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 = calloc(1, sizeof(*hist)); |
| |
| if (hist == NULL || cfg == NULL || fps == NULL || fps->num == 0 || |
| fps->den == 0) { |
| destroy_rate_histogram(hist); |
| return NULL; |
| } |
| |
| // 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; |
| |
| if (hist == NULL || cfg == NULL || pkt == NULL) return; |
| |
| 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; |
| int i; |
| |
| assert(bucket != NULL); |
| assert(num_buckets != NULL); |
| |
| buckets = *num_buckets; |
| |
| /* 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) { |
| int width1, width2; |
| int i; |
| |
| if (!buckets) return; |
| assert(bucket != NULL); |
| assert(buckets > 0); |
| |
| switch ((int)(log(bucket[buckets - 1].high) / log(10)) + 1) { |
| case 1: |
| case 2: |
| width1 = 4; |
| width2 = 2; |
| break; |
| case 3: |
| width1 = 5; |
| width2 = 3; |
| break; |
| case 4: |
| width1 = 6; |
| width2 = 4; |
| break; |
| case 5: |
| width1 = 7; |
| width2 = 5; |
| break; |
| case 6: |
| width1 = 8; |
| width2 = 6; |
| break; |
| case 7: |
| width1 = 9; |
| width2 = 7; |
| break; |
| default: |
| width1 = 12; |
| width2 = 10; |
| 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, "%*d %*s: ", width1, bucket[i].low, width2, ""); |
| else |
| fprintf(stderr, "%*d-%*d: ", width1, bucket[i].low, width2, |
| 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; |
| |
| if (hist == NULL || cfg == NULL) return; |
| |
| 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); |
| } |