Improve bitrate calculations for complex .txtp

This commit is contained in:
bnnm 2021-04-10 21:59:06 +02:00
parent 84200c4cb9
commit 157c50f2e1

View File

@ -5,6 +5,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include <ctype.h>
#include "vgmstream.h" #include "vgmstream.h"
#include "meta/meta.h" #include "meta/meta.h"
#include "layout/layout.h" #include "layout/layout.h"
@ -1330,6 +1331,33 @@ fail:
return; return;
} }
/*******************************************************************************/
/* BITRATE */
/*******************************************************************************/
#define BITRATE_FILES_MAX 128 /* arbitrary max, but +100 segments have been observed */
typedef struct {
uint32_t hash[BITRATE_FILES_MAX]; /* already used streamfiles */
int subsong[BITRATE_FILES_MAX]; /* subsongs of those streamfiles (could be incorporated to the hash?) */
int count;
int count_max;
} bitrate_info_t;
static uint32_t hash_sf(STREAMFILE* sf) {
int i;
char path[PATH_LIMIT];
uint32_t hash = 2166136261;
get_streamfile_name(sf, path, sizeof(path));
/* our favorite garbo hash a.k.a FNV-1 32b */
for (i = 0; i < strlen(path); i++) {
char c = tolower(path[i]);
hash = (hash * 16777619) ^ (uint8_t)c;
}
return hash;
}
/* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */ /* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */
static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* vgmstream, int channel) { static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* vgmstream, int channel) {
@ -1368,18 +1396,18 @@ static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* v
return vgmstream->ch[channel].streamfile; return vgmstream->ch[channel].streamfile;
} }
static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int length_samples) { static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int32_t length_samples) {
if (sample_rate == 0 || length_samples == 0) return 0; if (sample_rate == 0 || length_samples == 0) return 0;
if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */ if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */
return (int)((int64_t)size * 8 * sample_rate / length_samples); return (int)((int64_t)size * 8 * sample_rate / length_samples);
} }
static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* streamfile, int sample_rate, int length_samples) { static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* sf, int sample_rate, int32_t length_samples) {
if (streamfile == NULL) return 0; if (sf == NULL) return 0;
return get_vgmstream_file_bitrate_from_size(get_streamfile_size(streamfile), sample_rate, length_samples); return get_vgmstream_file_bitrate_from_size(get_streamfile_size(sf), sample_rate, length_samples);
} }
static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, STREAMFILE** streamfile_pointers, int *pointers_count, int pointers_max) { static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br) {
int sub, ch; int i, ch;
int bitrate = 0; int bitrate = 0;
/* Recursively get bitrate and fill the list of streamfiles if needed (to filter), /* Recursively get bitrate and fill the list of streamfiles if needed (to filter),
@ -1387,59 +1415,66 @@ static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, STREAMFILE** st
* *
* Because of how data, layers and segments can be combined it's possible to * Because of how data, layers and segments can be combined it's possible to
* fool this in various ways; metas should report stream_size in complex cases * fool this in various ways; metas should report stream_size in complex cases
* to get accurate bitrates (particularly for subsongs). */ * to get accurate bitrates (particularly for subsongs). An edge case is when
* segments use only a few samples from a full file (like Wwise transitions), bitrates
* become a bit high since its hard to detect only part of the file is needed. */
if (vgmstream->stream_size) { if (vgmstream->layout_type == layout_segmented) {
bitrate = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
}
else if (vgmstream->layout_type == layout_segmented) {
segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data; segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data;
for (sub = 0; sub < data->segment_count; sub++) { for (i = 0; i < data->segment_count; i++) {
bitrate += get_vgmstream_file_bitrate_main(data->segments[sub], streamfile_pointers, pointers_count, pointers_max); bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br);
} }
bitrate = bitrate / data->segment_count;
} }
else if (vgmstream->layout_type == layout_layered) { else if (vgmstream->layout_type == layout_layered) {
layered_layout_data *data = vgmstream->layout_data; layered_layout_data *data = vgmstream->layout_data;
for (sub = 0; sub < data->layer_count; sub++) { for (i = 0; i < data->layer_count; i++) {
bitrate += get_vgmstream_file_bitrate_main(data->layers[sub], streamfile_pointers, pointers_count, pointers_max); bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br);
} }
bitrate = bitrate / data->layer_count;
} }
else { else {
/* Add channel bitrate if streamfile hasn't been used before (comparing files /* Add channel bitrate if streamfile hasn't been used before, so bitrate doesn't count repeats
* by absolute paths), so bitrate doesn't multiply when the same STREAMFILE is * (like same STREAMFILE reopened per channel, also considering SFs may be wrapped). */
* reopened per channel, also skipping repeated pointers. */
char path_current[PATH_LIMIT];
char path_compare[PATH_LIMIT];
int is_unique = 1;
for (ch = 0; ch < vgmstream->channels; ch++) { for (ch = 0; ch < vgmstream->channels; ch++) {
STREAMFILE* sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch); uint32_t hash_cur;
if (!sf_cur) continue; int subsong_cur;
get_streamfile_name(sf_cur, path_current, sizeof(path_current)); STREAMFILE* sf_cur;
int is_unique = 1; /* default to "no other SFs exist" */
for (sub = 0; sub < *pointers_count; sub++) { /* compares paths (hashes for faster compares) + subsongs (same file + different subsong = "different" file) */
STREAMFILE* sf_cmp = streamfile_pointers[sub]; sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch);
if (!sf_cmp) continue; if (!sf_cur) continue;
if (sf_cur == sf_cmp) {
is_unique = 0; hash_cur = hash_sf(sf_cur);
break; subsong_cur = vgmstream->stream_index;
}
get_streamfile_name(sf_cmp, path_compare, sizeof(path_compare)); for (i = 0; i < br->count; i++) {
if (strcmp(path_current, path_compare) == 0) { uint32_t hash_cmp = br->hash[i];
int subsong_cmp = br->subsong[i];
if (hash_cur == hash_cmp && subsong_cur == subsong_cmp) {
is_unique = 0; is_unique = 0;
break; break;
} }
} }
if (is_unique) { if (is_unique) {
if (*pointers_count >= pointers_max) goto fail; if (br->count >= br->count_max) goto fail;
streamfile_pointers[*pointers_count] = sf_cur;
(*pointers_count)++;
br->hash[br->count] = hash_cur;
br->subsong[br->count] = subsong_cur;
br->count++;
if (vgmstream->stream_size) {
/* stream_size applies to both channels but should add once and detect repeats (for current subsong) */
bitrate += get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
}
else {
bitrate += get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples); bitrate += get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples);
} }
break;
}
} }
} }
@ -1453,11 +1488,10 @@ fail:
* it counts extra data like block headers and padding. While this can be surprising * it counts extra data like block headers and padding. While this can be surprising
* sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */ * sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */
int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) { int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) {
const size_t pointers_max = 128; /* arbitrary max, but +100 segments have been observed */ bitrate_info_t br = {0};
STREAMFILE* streamfile_pointers[128]; /* list already used streamfiles */ br.count_max = BITRATE_FILES_MAX;
int pointers_count = 0;
return get_vgmstream_file_bitrate_main(vgmstream, streamfile_pointers, &pointers_count, pointers_max); return get_vgmstream_file_bitrate_main(vgmstream, &br);
} }