| /* | |
| * Copyright (c) 2014 The WebM project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include <assert.h> | |
| #include <stdlib.h> | |
| #include <limits.h> | |
| #include <stdio.h> | |
| #include <math.h> | |
| #include "./rate_hist.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 vpx_codec_enc_cfg_t *cfg, | |
| const vpx_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 vpx_codec_enc_cfg_t *cfg, | |
| const vpx_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 vpx_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); | |
| } |