| /* | |
| * 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 = 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); | |
| } |