mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-19 00:04:04 +01:00
commit
b61908f3af
10
README.md
10
README.md
@ -74,12 +74,12 @@ for /L %A in (5,1,10) do test.exe -s %A -o file_%A.wav file.fsb
|
||||
```
|
||||
|
||||
Output filename in `-o` may use multiple wildcards:
|
||||
- `:s`: sets current subsong (or 0 if format doesn't have subsongs)
|
||||
- `:0Ns`: same, but left pads subsong with up to `N` zeroes
|
||||
- `:n`: internal stream name, or input filename if format doesn't have name
|
||||
- `:f`: input filename
|
||||
- `?s`: sets current subsong (or 0 if format doesn't have subsongs)
|
||||
- `?0Ns`: same, but left pads subsong with up to `N` zeroes
|
||||
- `?n`: internal stream name, or input filename if format doesn't have name
|
||||
- `?f`: input filename
|
||||
|
||||
For example `test.exe -s 2 -o :04s_:n.wav file.fsb` could generate `0002_song1.wav`
|
||||
For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.wav`
|
||||
|
||||
|
||||
### in_vgmstream
|
||||
|
@ -34,8 +34,7 @@ extern "C" {
|
||||
|
||||
/* global state */
|
||||
/*EXPORT*/ VgmstreamPlugin aud_plugin_instance;
|
||||
audacious_settings settings;
|
||||
VGMSTREAM *vgmstream = NULL; //todo make local?
|
||||
audacious_settings_t settings;
|
||||
|
||||
/* Audacious will first send the file to a plugin based on this static extension list. If none
|
||||
* accepts it'll try again all plugins, ordered by priority, until one accepts the file. Problem is,
|
||||
@ -50,10 +49,11 @@ const char *const VgmstreamPlugin::exts[] = {
|
||||
|
||||
const char *const VgmstreamPlugin::defaults[] = {
|
||||
"loop_forever", "FALSE",
|
||||
"loop_count", "2", //maybe double?
|
||||
"ignore_loop", "FALSE",
|
||||
"loop_count", "2.0",
|
||||
"fade_length", "10.0",
|
||||
"fade_delay", "0.0",
|
||||
"downmix_channels", "8",
|
||||
"downmix_channels", "2",
|
||||
"exts_unknown_on", "FALSE",
|
||||
"exts_common_on", "FALSE",
|
||||
NULL
|
||||
@ -72,13 +72,15 @@ const char VgmstreamPlugin::about[] =
|
||||
"https://github.com/kode54/vgmstream/\n"
|
||||
"https://sourceforge.net/projects/vgmstream/ (original)";
|
||||
|
||||
/* widget config: {min, max, step} */
|
||||
const PreferencesWidget VgmstreamPlugin::widgets[] = {
|
||||
WidgetLabel(N_("<b>vgmstream config</b>")),
|
||||
WidgetCheck(N_("Loop forever:"), WidgetBool(settings.loop_forever)),
|
||||
WidgetSpin(N_("Loop count:"), WidgetInt(settings.loop_count), {1, 20, 1}),
|
||||
WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_length), {0, 60, 0.1}),
|
||||
WidgetCheck(N_("Ignore loop:"), WidgetBool(settings.ignore_loop)),
|
||||
WidgetSpin(N_("Loop count:"), WidgetFloat(settings.loop_count), {1, 100, 1.0}),
|
||||
WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_time), {0, 60, 0.1}),
|
||||
WidgetSpin(N_("Fade delay:"), WidgetFloat(settings.fade_delay), {0, 60, 0.1}),
|
||||
WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {1, 20, 1}),
|
||||
WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {0, 8, 1}),
|
||||
WidgetCheck(N_("Enable unknown exts"), WidgetBool(settings.exts_unknown_on)),
|
||||
// Audacious 3.6 will only match one plugin so this option has no actual use
|
||||
// (ex. a fake .flac only gets to the FLAC plugin and never to vgmstream, even on error)
|
||||
@ -89,8 +91,9 @@ void vgmstream_settings_load() {
|
||||
AUDINFO("load settings\n");
|
||||
aud_config_set_defaults(CFG_ID, VgmstreamPlugin::defaults);
|
||||
settings.loop_forever = aud_get_bool(CFG_ID, "loop_forever");
|
||||
settings.loop_count = aud_get_int(CFG_ID, "loop_count");
|
||||
settings.fade_length = aud_get_double(CFG_ID, "fade_length");
|
||||
settings.ignore_loop = aud_get_bool(CFG_ID, "ignore_loop");
|
||||
settings.loop_count = aud_get_double(CFG_ID, "loop_count");
|
||||
settings.fade_time = aud_get_double(CFG_ID, "fade_length");
|
||||
settings.fade_delay = aud_get_double(CFG_ID, "fade_delay");
|
||||
settings.downmix_channels = aud_get_int(CFG_ID, "downmix_channels");
|
||||
settings.exts_unknown_on = aud_get_bool(CFG_ID, "exts_unknown_on");
|
||||
@ -100,8 +103,9 @@ void vgmstream_settings_load() {
|
||||
void vgmstream_settings_save() {
|
||||
AUDINFO("save settings\n");
|
||||
aud_set_bool(CFG_ID, "loop_forever", settings.loop_forever);
|
||||
aud_set_int(CFG_ID, "loop_count", settings.loop_count);
|
||||
aud_set_double(CFG_ID, "fade_length", settings.fade_length);
|
||||
aud_set_bool(CFG_ID, "ignore_loop", settings.ignore_loop);
|
||||
aud_set_double(CFG_ID, "loop_count", settings.loop_count);
|
||||
aud_set_double(CFG_ID, "fade_length", settings.fade_time);
|
||||
aud_set_double(CFG_ID, "fade_delay", settings.fade_delay);
|
||||
aud_set_int(CFG_ID, "downmix_channels", settings.downmix_channels);
|
||||
aud_set_bool(CFG_ID, "exts_unknown_on", settings.exts_unknown_on);
|
||||
@ -113,7 +117,7 @@ const PluginPreferences VgmstreamPlugin::prefs = {
|
||||
};
|
||||
|
||||
// validate extension (thread safe)
|
||||
bool VgmstreamPlugin::is_our_file(const char *filename, VFSFile &file) {
|
||||
bool VgmstreamPlugin::is_our_file(const char * filename, VFSFile & file) {
|
||||
AUDDBG("test file=%s\n", filename);
|
||||
|
||||
vgmstream_ctx_valid_cfg cfg = {0};
|
||||
@ -140,7 +144,7 @@ void VgmstreamPlugin::cleanup() {
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int get_filename_subtune(const char * filename) {
|
||||
static int get_filename_subtune(const char* filename) {
|
||||
int subtune;
|
||||
|
||||
int pos = strstr("?"))
|
||||
@ -152,8 +156,21 @@ static int get_filename_subtune(const char * filename) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static void apply_config(VGMSTREAM* vgmstream, audacious_settings_t* settings) {
|
||||
vgmstream_cfg_t vcfg = {0};
|
||||
|
||||
vcfg.allow_play_forever = 1;
|
||||
vcfg.play_forever = settings->loop_forever;
|
||||
vcfg.loop_count = settings->loop_count;
|
||||
vcfg.fade_time = settings->fade_time;
|
||||
vcfg.fade_delay = settings->fade_delay;
|
||||
vcfg.ignore_loop = settings->ignore_loop;
|
||||
|
||||
vgmstream_apply_config(vgmstream, &vcfg);
|
||||
}
|
||||
|
||||
// internal helper, called every time user adds a new file to playlist
|
||||
static bool read_info(const char * filename, Tuple & tuple) {
|
||||
static bool read_info(const char* filename, Tuple & tuple) {
|
||||
AUDINFO("read file=%s\n", filename);
|
||||
|
||||
#if 0
|
||||
@ -167,12 +184,12 @@ static bool read_info(const char * filename, Tuple & tuple) {
|
||||
//must use basename to open streamfile
|
||||
#endif
|
||||
|
||||
STREAMFILE *streamfile = open_vfs(filename);
|
||||
if (!streamfile) return false;
|
||||
STREAMFILE* sf = open_vfs(filename);
|
||||
if (!sf) return false;
|
||||
|
||||
VGMSTREAM *infostream = init_vgmstream_from_STREAMFILE(streamfile);
|
||||
VGMSTREAM* infostream = init_vgmstream_from_STREAMFILE(sf);
|
||||
if (!infostream) {
|
||||
close_streamfile(streamfile);
|
||||
close_streamfile(sf);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -189,24 +206,26 @@ static bool read_info(const char * filename, Tuple & tuple) {
|
||||
return true; //must return?
|
||||
}
|
||||
|
||||
streamfile->stream_index = (subtune + 1);
|
||||
sf->stream_index = (subtune + 1);
|
||||
#endif
|
||||
|
||||
|
||||
//todo apply_config
|
||||
apply_config(infostream, &settings);
|
||||
|
||||
int output_channels = infostream->channels;
|
||||
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
|
||||
vgmstream_mixing_enable(infostream, 0, NULL, &output_channels);
|
||||
|
||||
int bitrate = get_vgmstream_average_bitrate(infostream);
|
||||
int ms = get_vgmstream_play_samples(settings.loop_count, settings.fade_length, settings.fade_delay, infostream);
|
||||
ms = ms* 1000LL / infostream->sample_rate;
|
||||
int length_samples = vgmstream_get_samples(infostream);
|
||||
int length_ms = length_samples * 1000LL / infostream->sample_rate;
|
||||
|
||||
//todo: set_format may throw std::bad_alloc if output_channels isn't supported (only 2?)
|
||||
// short form, not sure if better way
|
||||
tuple.set_format("vgmstream codec", output_channels, infostream->sample_rate, bitrate);
|
||||
tuple.set_filename(filename); //used?
|
||||
tuple.set_int(Tuple::Bitrate, bitrate); //in kb/s
|
||||
tuple.set_int(Tuple::Length, ms);
|
||||
tuple.set_int(Tuple::Length, length_ms);
|
||||
|
||||
//todo here we could call describe_vgmstream() and get substring to add tags and stuff
|
||||
tuple.set_str(Tuple::Codec, "vgmstream codec");
|
||||
@ -218,14 +237,14 @@ static bool read_info(const char * filename, Tuple & tuple) {
|
||||
//tuple.set_str (Tuple::Artist, ...);
|
||||
//tuple.set_str (Tuple::Album, ...);
|
||||
|
||||
close_streamfile(streamfile);
|
||||
close_streamfile(sf);
|
||||
close_vgmstream(infostream);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// thread safe (for Audacious <= 3.7, unused otherwise)
|
||||
Tuple VgmstreamPlugin::read_tuple(const char *filename, VFSFile &file) {
|
||||
Tuple VgmstreamPlugin::read_tuple(const char * filename, VFSFile & file) {
|
||||
Tuple tuple;
|
||||
read_info(filename, tuple);
|
||||
return tuple;
|
||||
@ -237,61 +256,33 @@ bool VgmstreamPlugin::read_tag(const char * filename, VFSFile & file, Tuple & tu
|
||||
}
|
||||
|
||||
// internal util to seek during play
|
||||
static void seek_helper(int seek_value, int ¤t_sample_pos, int input_channels) {
|
||||
static void do_seek(VGMSTREAM* vgmstream, int seek_ms, int& current_sample_pos) {
|
||||
AUDINFO("seeking\n");
|
||||
|
||||
// compute from ms to samples
|
||||
int seek_needed_samples = (long long)seek_value * vgmstream->sample_rate / 1000L;
|
||||
short buffer[MIN_BUFFER_SIZE * input_channels];
|
||||
int max_buffer_samples = MIN_BUFFER_SIZE;
|
||||
int seek_sample = (long long)seek_ms * vgmstream->sample_rate / 1000L;
|
||||
|
||||
int samples_to_do = 0;
|
||||
if (seek_needed_samples < current_sample_pos) {
|
||||
// go back in time, reopen file
|
||||
AUDINFO("resetting file to seek backwards\n");
|
||||
reset_vgmstream(vgmstream);
|
||||
current_sample_pos = 0;
|
||||
samples_to_do = seek_needed_samples;
|
||||
} else if (current_sample_pos < seek_needed_samples) {
|
||||
// go forward in time
|
||||
samples_to_do = seek_needed_samples - current_sample_pos;
|
||||
}
|
||||
seek_vgmstream(vgmstream, seek_sample);
|
||||
|
||||
// do the actual seeking
|
||||
if (samples_to_do >= 0) {
|
||||
AUDINFO("rendering forward\n");
|
||||
|
||||
// render till seeked sample
|
||||
while (samples_to_do > 0) {
|
||||
int seek_samples = std::min(max_buffer_samples, samples_to_do);
|
||||
current_sample_pos += seek_samples;
|
||||
samples_to_do -= seek_samples;
|
||||
render_vgmstream(buffer, seek_samples, vgmstream);
|
||||
}
|
||||
}
|
||||
current_sample_pos = seek_sample;
|
||||
}
|
||||
|
||||
// called on play (play thread)
|
||||
bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
|
||||
bool VgmstreamPlugin::play(const char * filename, VFSFile & file) {
|
||||
AUDINFO("play file=%s\n", filename);
|
||||
|
||||
// just in case
|
||||
if (vgmstream)
|
||||
close_vgmstream(vgmstream);
|
||||
|
||||
STREAMFILE *streamfile = open_vfs(filename);
|
||||
if (!streamfile) {
|
||||
STREAMFILE* sf = open_vfs(filename);
|
||||
if (!sf) {
|
||||
AUDERR("failed opening file %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
|
||||
close_streamfile(streamfile);
|
||||
VGMSTREAM* vgmstream = init_vgmstream_from_STREAMFILE(sf);
|
||||
close_streamfile(sf);
|
||||
|
||||
if (!vgmstream) {
|
||||
AUDINFO("filename %s is not a valid format\n", filename);
|
||||
close_vgmstream(vgmstream);
|
||||
vgmstream = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -300,6 +291,9 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
|
||||
|
||||
//todo apply config
|
||||
|
||||
|
||||
apply_config(vgmstream, &settings);
|
||||
|
||||
int input_channels = vgmstream->channels;
|
||||
int output_channels = vgmstream->channels;
|
||||
/* enable after all config but before outbuf */
|
||||
@ -312,53 +306,38 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
|
||||
// play
|
||||
short buffer[MIN_BUFFER_SIZE * input_channels];
|
||||
int max_buffer_samples = MIN_BUFFER_SIZE;
|
||||
int stream_samples_amount = get_vgmstream_play_samples(
|
||||
settings.loop_count, settings.fade_length,
|
||||
settings.fade_delay, vgmstream);
|
||||
int fade_samples = settings.fade_length * vgmstream->sample_rate;
|
||||
int current_sample_pos = 0;
|
||||
|
||||
int play_forever = vgmstream_get_play_forever(vgmstream);
|
||||
int length_samples = vgmstream_get_samples(vgmstream);
|
||||
int decode_pos_samples = 0;
|
||||
|
||||
while (!check_stop()) {
|
||||
int toget = max_buffer_samples;
|
||||
int to_do = max_buffer_samples;
|
||||
|
||||
// handle seek request
|
||||
int seek_value = check_seek();
|
||||
if (seek_value >= 0)
|
||||
seek_helper(seek_value, current_sample_pos, input_channels);
|
||||
if (seek_value >= 0) {
|
||||
do_seek(vgmstream, seek_value, decode_pos_samples);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check stream finished
|
||||
if (!settings.loop_forever || !vgmstream->loop_flag) {
|
||||
if (current_sample_pos >= stream_samples_amount)
|
||||
if (!play_forever) {
|
||||
if (decode_pos_samples >= length_samples)
|
||||
break;
|
||||
if (current_sample_pos + toget > stream_samples_amount)
|
||||
toget = stream_samples_amount - current_sample_pos;
|
||||
|
||||
if (decode_pos_samples + to_do > length_samples)
|
||||
to_do = length_samples - decode_pos_samples;
|
||||
}
|
||||
|
||||
render_vgmstream(buffer, toget, vgmstream);
|
||||
render_vgmstream(buffer, to_do, vgmstream);
|
||||
|
||||
if (vgmstream->loop_flag && fade_samples > 0 &&
|
||||
!settings.loop_forever) {
|
||||
int samples_into_fade =
|
||||
current_sample_pos - (stream_samples_amount - fade_samples);
|
||||
if (samples_into_fade + toget > 0) {
|
||||
for (int j = 0; j < toget; j++, samples_into_fade++) {
|
||||
if (samples_into_fade > 0) {
|
||||
double fadedness =
|
||||
(double)(fade_samples - samples_into_fade) / fade_samples;
|
||||
for (int k = 0; k < output_channels; k++)
|
||||
buffer[j * output_channels + k] *= fadedness;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_audio(buffer, toget * sizeof(short) * output_channels);
|
||||
current_sample_pos += toget;
|
||||
write_audio(buffer, to_do * sizeof(short) * output_channels);
|
||||
decode_pos_samples += to_do;
|
||||
}
|
||||
|
||||
AUDINFO("play finished\n");
|
||||
|
||||
close_vgmstream(vgmstream);
|
||||
vgmstream = NULL;
|
||||
return true;
|
||||
}
|
||||
|
@ -35,25 +35,26 @@ public:
|
||||
|
||||
bool init();
|
||||
void cleanup();
|
||||
bool is_our_file(const char *filename, VFSFile &file);
|
||||
Tuple read_tuple(const char *filename, VFSFile &file);
|
||||
bool is_our_file(const char * filename, VFSFile & file);
|
||||
Tuple read_tuple(const char * filename, VFSFile & file);
|
||||
bool read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image);
|
||||
bool play(const char *filename, VFSFile &file);
|
||||
bool play(const char * filename, VFSFile & file);
|
||||
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool loop_forever;
|
||||
int loop_count;
|
||||
double fade_length;
|
||||
bool ignore_loop;
|
||||
double loop_count;
|
||||
double fade_time;
|
||||
double fade_delay;
|
||||
int downmix_channels;
|
||||
bool exts_unknown_on;
|
||||
bool exts_common_on;
|
||||
} audacious_settings;
|
||||
} audacious_settings_t;
|
||||
|
||||
extern audacious_settings settings;
|
||||
extern audacious_settings_t settings;
|
||||
|
||||
void vgmstream_settings_load();
|
||||
void vgmstream_settings_save();
|
||||
|
@ -37,7 +37,7 @@ static void usage(const char* name, int is_full) {
|
||||
"Usage: %s [-o <outfile.wav>] [options] <infile>\n"
|
||||
"Options:\n"
|
||||
" -o <outfile.wav>: name of output .wav file, default <infile>.wav\n"
|
||||
" <outfile> wildcards can be :s=subsong, :n=stream name, :f=infile\n"
|
||||
" <outfile> wildcards can be ?s=subsong, ?n=stream name, ?f=infile\n"
|
||||
" -l loop count: loop count, default 2.0\n"
|
||||
" -f fade time: fade time in seconds after N loops, default 10.0\n"
|
||||
" -d fade delay: fade delay in seconds, default 0.0\n"
|
||||
@ -55,18 +55,20 @@ static void usage(const char* name, int is_full) {
|
||||
" -x: decode and print adxencd command line to encode as ADX\n"
|
||||
" -g: decode and print oggenc command line to encode as OGG\n"
|
||||
" -b: decode and print batch variable commands\n"
|
||||
" -h: print extra commands\n"
|
||||
" -h: print extra commands (for testing)\n"
|
||||
, name);
|
||||
if (is_full) {
|
||||
fprintf(stderr,
|
||||
" -v: validate extensions (for extension testing)\n"
|
||||
" -r: output a second file after resetting (for reset testing)\n"
|
||||
" -k N: seeks to N samples before decoding (for seek testing)\n"
|
||||
" -K N: seeks to N samples before decoding again (for seek testing)\n"
|
||||
" -t file: print tags found in file (for tag testing)\n"
|
||||
" -O: decode but don't write to file (for performance testing)\n"
|
||||
);
|
||||
}
|
||||
if (!is_full)
|
||||
return;
|
||||
fprintf(stderr,
|
||||
" -v: validate extensions (for extension testing)\n"
|
||||
" -r: output a second file after resetting (for reset testing)\n"
|
||||
" -k N: seeks to N samples before decoding (for seek testing)\n"
|
||||
" -K N: seeks again to N samples before decoding (for seek testing)\n"
|
||||
" -t file: print tags found in file (for tag testing)\n"
|
||||
" -D <max channels>: downmix to <max channels> (for plugin downmix testing)\n"
|
||||
" -O: decode but don't write to file (for performance testing)\n"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -98,6 +100,7 @@ typedef struct {
|
||||
int seek_samples1;
|
||||
int seek_samples2;
|
||||
int decode_only;
|
||||
int downmix_channels;
|
||||
|
||||
/* not quite config but eh */
|
||||
int lwav_loop_start;
|
||||
@ -119,7 +122,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
|
||||
opterr = 0;
|
||||
|
||||
/* read config */
|
||||
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:K:hOv")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:K:hOvD:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'o':
|
||||
cfg->outfilename = optarg;
|
||||
@ -194,6 +197,9 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
|
||||
case 'v':
|
||||
cfg->validate_extensions = 1;
|
||||
break;
|
||||
case 'D':
|
||||
cfg->downmix_channels = atoi(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv[0], 1);
|
||||
goto fail;
|
||||
@ -306,8 +312,8 @@ static void apply_config(VGMSTREAM* vgmstream, cli_config* cfg) {
|
||||
}
|
||||
|
||||
vcfg.play_forever = cfg->play_forever;
|
||||
vcfg.fade_period = cfg->fade_time;
|
||||
vcfg.loop_times = cfg->loop_count;
|
||||
vcfg.fade_time = cfg->fade_time;
|
||||
vcfg.loop_count = cfg->loop_count;
|
||||
vcfg.fade_delay = cfg->fade_delay;
|
||||
|
||||
vcfg.ignore_loop = cfg->ignore_loop;
|
||||
@ -356,8 +362,8 @@ static void clean_filename(char* dst, int clean_paths) {
|
||||
}
|
||||
}
|
||||
|
||||
/* replaces a filename with ":n" (stream name), ":f" (infilename) or ":s" (subsong) wildcards
|
||||
* (":" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */
|
||||
/* replaces a filename with "?n" (stream name), "?f" (infilename) or "?s" (subsong) wildcards
|
||||
* ("?" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */
|
||||
void replace_filename(char* dst, size_t dstsize, const char* outfilename, const char* infilename, VGMSTREAM* vgmstream) {
|
||||
int subsong;
|
||||
char stream_name[PATH_LIMIT];
|
||||
@ -390,29 +396,33 @@ void replace_filename(char* dst, size_t dstsize, const char* outfilename, const
|
||||
|
||||
/* do controlled replaces of each wildcard (in theory could appear N times) */
|
||||
do {
|
||||
char* pos = strchr(buf, ':');
|
||||
char* pos = strchr(buf, '?');
|
||||
if (!pos)
|
||||
break;
|
||||
|
||||
/* use buf as format and copy formatted result to tmp (assuming sprintf's format must not overlap with dst) */
|
||||
pos[0] = '%';
|
||||
if (pos[1] == 'n') {
|
||||
pos[0] = '%';
|
||||
pos[1] = 's'; /* use %s */
|
||||
snprintf(tmp, sizeof(tmp), buf, stream_name);
|
||||
}
|
||||
else if (pos[1] == 'f') {
|
||||
pos[0] = '%';
|
||||
pos[1] = 's'; /* use %s */
|
||||
snprintf(tmp, sizeof(tmp), buf, infilename);
|
||||
}
|
||||
else if (pos[1] == 's') {
|
||||
pos[0] = '%';
|
||||
pos[1] = 'i'; /* use %i */
|
||||
snprintf(tmp, sizeof(tmp), buf, subsong);
|
||||
}
|
||||
else if ((pos[1] == '0' && pos[2] >= '1' && pos[2] <= '9' && pos[3] == 's')) {
|
||||
pos[0] = '%';
|
||||
pos[3] = 'i'; /* use %0Ni */
|
||||
snprintf(tmp, sizeof(tmp), buf, subsong);
|
||||
}
|
||||
else {
|
||||
/* not recognized */
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -503,6 +513,8 @@ int main(int argc, char** argv) {
|
||||
input_channels = vgmstream->channels;
|
||||
|
||||
/* enable after config but before outbuf */
|
||||
if (cfg.downmix_channels)
|
||||
vgmstream_mixing_autodownmix(vgmstream, cfg.downmix_channels);
|
||||
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels);
|
||||
|
||||
/* get final play config */
|
||||
@ -529,7 +541,7 @@ int main(int argc, char** argv) {
|
||||
/* maybe should avoid overwriting with this auto-name, for the unlikely
|
||||
* case of file header-body pairs (file.ext+file.ext.wav) */
|
||||
}
|
||||
else if (strchr(cfg.outfilename, ':') != NULL) {
|
||||
else if (strchr(cfg.outfilename, '?') != NULL) {
|
||||
/* special substitution */
|
||||
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg.outfilename, cfg.infilename, vgmstream);
|
||||
cfg.outfilename = outfilename_temp;
|
||||
|
@ -35,6 +35,12 @@ PKG_CHECK_MODULES(MPG123, [libmpg123], have_libmpg123=yes,
|
||||
)
|
||||
AM_CONDITIONAL(HAVE_LIBMPG123, test "$have_libmpg123" = yes)
|
||||
|
||||
#have_ffmpeg=no
|
||||
#PKG_CHECK_MODULES(FFMPEG, [libavformat libavcodec libavutil libswresample], have_ffmpeg=yes,
|
||||
# [AC_MSG_WARN([Cannot find ffmpeg - will not enable FFmpeg formats])]
|
||||
#)
|
||||
#AM_CONDITIONAL(HAVE_FFMPEG, test "$have_ffmpeg" = yes)
|
||||
|
||||
have_audacious=no
|
||||
PKG_CHECK_MODULES(AUDACIOUS, [audacious >= 3.5.0], have_audacious=yes,
|
||||
[AC_MSG_WARN([Cannot find audacious >= 3.5.0 correctly installed - will not build Audacious plugin])]
|
||||
@ -55,6 +61,7 @@ then
|
||||
CFLAGS="$CFLAGS -Wall -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-but-set-variable"
|
||||
fi
|
||||
|
||||
#plugindir=`pkg-config audacious --define-variable=prefix="$prefix" --variable=plugin_dir`
|
||||
plugindir=`pkg-config audacious --variable=plugin_dir`
|
||||
AC_SUBST(plugindir)
|
||||
|
||||
|
29
doc/BUILD.md
29
doc/BUILD.md
@ -91,7 +91,7 @@ msbuild fb2k/foo_input_vgmstream.vcxproj ^
|
||||
```
|
||||
|
||||
### Audacious plugin
|
||||
Requires the dev version of Audacious (and dependencies), automake/autoconf, and gcc/make (C++11). It must be compiled and installed into Audacious, where it should appear in the plugin list as "vgmstream".
|
||||
Requires the dev version of Audacious (and dependencies), automake/autoconf or cmake, and gcc/make (C++11). It must be compiled and installed into Audacious, where it should appear in the plugin list as "vgmstream".
|
||||
|
||||
The plugin needs Audacious 3.5 or higher. New Audacious releases can break plugin compatibility so it may not work with the latest version unless adapted first.
|
||||
|
||||
@ -99,7 +99,9 @@ libvorbis and libmpg123 will be used if found, while FFmpeg and other external l
|
||||
|
||||
Windows builds aren't supported at the moment (should be possible but there are complex dependency chains).
|
||||
|
||||
Take note of other plugins stealing extensions (see README). To change Audacious's default priority for vgmstream you can make with CFLAG `AUDACIOUS_VGMSTREAM_PRIORITY n` (where `N` is number with 10=lowest)
|
||||
If you get errors during the build phase we probably forgot some `#ifdef` needed for Audacious, notify and should be quickly fixed.
|
||||
|
||||
Take note of other plugins stealing extensions (see README). To change Audacious's default priority for vgmstream you can make with CFLAG `AUDACIOUS_VGMSTREAM_PRIORITY n` (where `N` is a number where 10=lowest)
|
||||
|
||||
|
||||
Terminal example, assuming a Ubuntu-based Linux distribution:
|
||||
@ -113,22 +115,22 @@ sudo apt-get install autoconf automake libtool
|
||||
sudo apt-get install git
|
||||
# vgmstream dependencies
|
||||
sudo apt-get install libmpg123-dev libvorbis-dev
|
||||
#sudo apt-get install libavformat-dev libavcodec-dev libavutil-dev libswresample-dev
|
||||
# Audacious player and dependencies
|
||||
sudo apt-get install audacious
|
||||
sudo apt-get install audacious-dev libglib2.0-dev libgtk2.0-dev libpango1.0-dev
|
||||
|
||||
# if you want vgmstream123 do this too
|
||||
# vgmstream123 dependencies (optional)
|
||||
sudo apt-get install libao-dev
|
||||
|
||||
# check Audacious version >= 3.5
|
||||
pkg-config --modversion audacious
|
||||
```
|
||||
```
|
||||
# vgmstream build
|
||||
|
||||
git clone https://github.com/kode54/vgmstream
|
||||
# base vgmstream build
|
||||
git clone https://github.com/losnoco/vgmstream
|
||||
cd vgmstream
|
||||
|
||||
# main vgmstream build (if you get errors here please report)
|
||||
./bootstrap
|
||||
./configure
|
||||
make -f Makefile.autotools
|
||||
@ -158,8 +160,19 @@ find . -name ".deps" -type d -exec rm -r "{}" \;
|
||||
## WARNING, removes *all* untracked files not in .gitignore
|
||||
git clean -fd
|
||||
```
|
||||
To update vgmstream it's probably easiest to remove the `vgmstream` folder and start again from "vgmstream build" step, since updates often require a full rebuild anyway.
|
||||
To update vgmstream it's probably easiest to remove the `vgmstream` folder and start again from *base vgmstream build* step, since updates often require a full rebuild anyway.
|
||||
|
||||
Instead of autotools you can try building with CMake. Some older distros may not work though (CMake version needs to recognize FILTER command). You may need to install resulting artifacts manually.
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libmpg123-dev libvorbis-dev
|
||||
sudo apt-get install -y libavformat-dev libavcodec-dev libavutil-dev libswresample-dev
|
||||
sudo apt-get install -y libao-dev audacious-dev
|
||||
sudo apt-get install -y cmake
|
||||
|
||||
cmake .
|
||||
make
|
||||
```
|
||||
|
||||
### vgmstream123 player
|
||||
Should be buildable with Autotools by following the same steps as listen in the Audacious section (requires libao-dev).
|
||||
|
@ -486,8 +486,8 @@ void input_vgmstream::apply_config(VGMSTREAM* vgmstream) {
|
||||
|
||||
vcfg.allow_play_forever = 1;
|
||||
vcfg.play_forever = loop_forever;
|
||||
vcfg.loop_times = loop_count;
|
||||
vcfg.fade_period = fade_seconds;
|
||||
vcfg.loop_count = loop_count;
|
||||
vcfg.fade_time = fade_seconds;
|
||||
vcfg.fade_delay = fade_delay_seconds;
|
||||
vcfg.ignore_loop = ignore_loop;
|
||||
|
||||
|
@ -23,7 +23,13 @@ AM_CFLAGS += -DVGM_USE_VORBIS
|
||||
libvgmstream_la_LIBADD += $(VORBISFILE_LIBS) $(VORBIS_LIBS)
|
||||
endif
|
||||
endif
|
||||
|
||||
if HAVE_LIBMPG123
|
||||
AM_CFLAGS += -DVGM_USE_MPEG
|
||||
libvgmstream_la_LIBADD += $(MPG123_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_FFMPEG
|
||||
AM_CFLAGS += -DVGM_USE_FFMPEG
|
||||
libvgmstream_la_LIBADD += $(FFMPEG_LIBS)
|
||||
endif
|
||||
|
@ -17,6 +17,11 @@ if HAVE_VORBISFILE
|
||||
AM_CFLAGS += -DVGM_USE_VORBIS
|
||||
endif
|
||||
endif
|
||||
|
||||
if HAVE_LIBMPG123
|
||||
AM_CFLAGS += -DVGM_USE_MPEG
|
||||
endif
|
||||
|
||||
if HAVE_FFMPEG
|
||||
AM_CFLAGS += -DVGM_USE_FFMPEG
|
||||
endif
|
||||
|
@ -605,19 +605,6 @@ size_t aac_get_samples(STREAMFILE* sf, off_t start_offset, size_t bytes);
|
||||
size_t mpeg_get_samples(STREAMFILE* sf, off_t start_offset, size_t bytes);
|
||||
|
||||
|
||||
/* An internal struct to pass around and simulate a bitstream. */
|
||||
typedef enum { BITSTREAM_MSF, BITSTREAM_VORBIS } vgm_bitstream_t;
|
||||
typedef struct {
|
||||
uint8_t* buf; /* buffer to read/write*/
|
||||
size_t bufsize; /* max size of the buffer */
|
||||
off_t b_off; /* current offset in bits inside the buffer */
|
||||
vgm_bitstream_t mode; /* read/write modes */
|
||||
} vgm_bitstream;
|
||||
|
||||
int r_bits(vgm_bitstream* ib, int num_bits, uint32_t* value);
|
||||
int w_bits(vgm_bitstream* ob, int num_bits, uint32_t value);
|
||||
|
||||
|
||||
/* helper to pass a wrapped, clamped, fake extension-ed, SF to another meta */
|
||||
STREAMFILE* setup_subfile_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size, const char* extension);
|
||||
|
||||
|
@ -1012,152 +1012,6 @@ size_t aac_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes)
|
||||
return frames * samples_per_frame;
|
||||
}
|
||||
|
||||
|
||||
/* ******************************************** */
|
||||
/* BITSTREAM */
|
||||
/* ******************************************** */
|
||||
|
||||
|
||||
/* Read bits (max 32) from buf and update the bit offset. Vorbis packs values in LSB order and byte by byte.
|
||||
* (ex. from 2 bytes 00100111 00000001 we can could read 4b=0111 and 6b=010010, 6b=remainder (second value is split into the 2nd byte) */
|
||||
static int r_bits_vorbis(vgm_bitstream * ib, int num_bits, uint32_t * value) {
|
||||
off_t off, pos;
|
||||
int i, bit_buf, bit_val;
|
||||
if (num_bits == 0) return 1;
|
||||
if (num_bits > 32 || num_bits < 0 || ib->b_off + num_bits > ib->bufsize*8) goto fail;
|
||||
|
||||
*value = 0; /* set all bits to 0 */
|
||||
off = ib->b_off / 8; /* byte offset */
|
||||
pos = ib->b_off % 8; /* bit sub-offset */
|
||||
for (i = 0; i < num_bits; i++) {
|
||||
bit_buf = (1U << pos) & 0xFF; /* bit check for buf */
|
||||
bit_val = (1U << i); /* bit to set in value */
|
||||
|
||||
if (ib->buf[off] & bit_buf) /* is bit in buf set? */
|
||||
*value |= bit_val; /* set bit */
|
||||
|
||||
pos++; /* new byte starts */
|
||||
if (pos%8 == 0) {
|
||||
pos = 0;
|
||||
off++;
|
||||
}
|
||||
}
|
||||
|
||||
ib->b_off += num_bits;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write bits (max 32) to buf and update the bit offset. Vorbis packs values in LSB order and byte by byte.
|
||||
* (ex. writing 1101011010 from b_off 2 we get 01101011 00001101 (value split, and 11 in the first byte skipped)*/
|
||||
static int w_bits_vorbis(vgm_bitstream * ob, int num_bits, uint32_t value) {
|
||||
off_t off, pos;
|
||||
int i, bit_val, bit_buf;
|
||||
if (num_bits == 0) return 1;
|
||||
if (num_bits > 32 || num_bits < 0 || ob->b_off + num_bits > ob->bufsize*8) goto fail;
|
||||
|
||||
|
||||
off = ob->b_off / 8; /* byte offset */
|
||||
pos = ob->b_off % 8; /* bit sub-offset */
|
||||
for (i = 0; i < num_bits; i++) {
|
||||
bit_val = (1U << i); /* bit check for value */
|
||||
bit_buf = (1U << pos) & 0xFF; /* bit to set in buf */
|
||||
|
||||
if (value & bit_val) /* is bit in val set? */
|
||||
ob->buf[off] |= bit_buf; /* set bit */
|
||||
else
|
||||
ob->buf[off] &= ~bit_buf; /* unset bit */
|
||||
|
||||
pos++; /* new byte starts */
|
||||
if (pos%8 == 0) {
|
||||
pos = 0;
|
||||
off++;
|
||||
}
|
||||
}
|
||||
|
||||
ob->b_off += num_bits;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Read bits (max 32) from buf and update the bit offset. Order is BE (MSF). */
|
||||
static int r_bits_msf(vgm_bitstream * ib, int num_bits, uint32_t * value) {
|
||||
off_t off, pos;
|
||||
int i, bit_buf, bit_val;
|
||||
if (num_bits == 0) return 1;
|
||||
if (num_bits > 32 || num_bits < 0 || ib->b_off + num_bits > ib->bufsize*8) goto fail;
|
||||
|
||||
*value = 0; /* set all bits to 0 */
|
||||
off = ib->b_off / 8; /* byte offset */
|
||||
pos = ib->b_off % 8; /* bit sub-offset */
|
||||
for (i = 0; i < num_bits; i++) {
|
||||
bit_buf = (1U << (8-1-pos)) & 0xFF; /* bit check for buf */
|
||||
bit_val = (1U << (num_bits-1-i)); /* bit to set in value */
|
||||
|
||||
if (ib->buf[off] & bit_buf) /* is bit in buf set? */
|
||||
*value |= bit_val; /* set bit */
|
||||
|
||||
pos++;
|
||||
if (pos%8 == 0) { /* new byte starts */
|
||||
pos = 0;
|
||||
off++;
|
||||
}
|
||||
}
|
||||
|
||||
ib->b_off += num_bits;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write bits (max 32) to buf and update the bit offset. Order is BE (MSF). */
|
||||
static int w_bits_msf(vgm_bitstream * ob, int num_bits, uint32_t value) {
|
||||
off_t off, pos;
|
||||
int i, bit_val, bit_buf;
|
||||
if (num_bits == 0) return 1;
|
||||
if (num_bits > 32 || num_bits < 0 || ob->b_off + num_bits > ob->bufsize*8) goto fail;
|
||||
|
||||
|
||||
off = ob->b_off / 8; /* byte offset */
|
||||
pos = ob->b_off % 8; /* bit sub-offset */
|
||||
for (i = 0; i < num_bits; i++) {
|
||||
bit_val = (1U << (num_bits-1-i)); /* bit check for value */
|
||||
bit_buf = (1U << (8-1-pos)) & 0xFF; /* bit to set in buf */
|
||||
|
||||
if (value & bit_val) /* is bit in val set? */
|
||||
ob->buf[off] |= bit_buf; /* set bit */
|
||||
else
|
||||
ob->buf[off] &= ~bit_buf; /* unset bit */
|
||||
|
||||
pos++;
|
||||
if (pos%8 == 0) { /* new byte starts */
|
||||
pos = 0;
|
||||
off++;
|
||||
}
|
||||
}
|
||||
|
||||
ob->b_off += num_bits;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int r_bits(vgm_bitstream * ib, int num_bits, uint32_t * value) {
|
||||
if (ib->mode == BITSTREAM_VORBIS)
|
||||
return r_bits_vorbis(ib,num_bits,value);
|
||||
else
|
||||
return r_bits_msf(ib,num_bits,value);
|
||||
}
|
||||
int w_bits(vgm_bitstream * ob, int num_bits, uint32_t value) {
|
||||
if (ob->mode == BITSTREAM_VORBIS)
|
||||
return w_bits_vorbis(ob,num_bits,value);
|
||||
else
|
||||
return w_bits_msf(ob,num_bits,value);
|
||||
}
|
||||
|
||||
/* ******************************************** */
|
||||
/* CUSTOM STREAMFILES */
|
||||
/* ******************************************** */
|
||||
|
@ -8,48 +8,59 @@ static const int32_t l5_scales[32] = {
|
||||
0x00130B82, 0x00182B83, 0x001EAC92, 0x0026EDB2, 0x00316777, 0x003EB2E6, 0x004F9232, 0x0064FBD1
|
||||
};
|
||||
|
||||
void decode_l5_555(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
int i=first_sample;
|
||||
int32_t sample_count;
|
||||
void decode_l5_555(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x12] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
uint16_t header;
|
||||
uint8_t coef_index;
|
||||
|
||||
int framesin = first_sample/32;
|
||||
|
||||
uint16_t header = (uint16_t)read_16bitLE(framesin*0x12+stream->offset,stream->streamfile);
|
||||
int32_t pos_scale = l5_scales[(header>>5)&0x1f];
|
||||
int32_t neg_scale = l5_scales[header&0x1f];
|
||||
|
||||
int coef_index = (header >> 10) & 0x1f;
|
||||
int16_t hist1 = stream->adpcm_history1_16;
|
||||
int16_t hist2 = stream->adpcm_history2_16;
|
||||
int16_t hist3 = stream->adpcm_history3_16;
|
||||
int32_t coef1 = stream->adpcm_coef_3by32[coef_index*3];
|
||||
int32_t coef2 = stream->adpcm_coef_3by32[coef_index*3+1];
|
||||
int32_t coef3 = stream->adpcm_coef_3by32[coef_index*3+2];
|
||||
/*printf("offset: %x\nscale: %d\nindex: %d (%lf,%lf)\nhist: %d %d\n",
|
||||
(unsigned)stream->offset,scale,coef_index,coef1/2048.0,coef2/2048.0,hist1,hist2);*/
|
||||
int32_t coef1, coef2, coef3;
|
||||
int32_t pos_scale, neg_scale;
|
||||
|
||||
first_sample = first_sample%32;
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x12;
|
||||
samples_per_frame = (bytes_per_frame - 0x02) * 2; /* always 32 */
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
first_sample = first_sample % samples_per_frame;
|
||||
|
||||
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
|
||||
int sample_byte = read_8bit(framesin*0x12+stream->offset+2+i/2,stream->streamfile);
|
||||
int nibble = (i&1?
|
||||
get_low_nibble_signed(sample_byte):
|
||||
get_high_nibble_signed(sample_byte));
|
||||
int32_t prediction =
|
||||
-(hist1 * coef1 + hist2 * coef2 + hist3 * coef3);
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
header = get_u32le(frame);
|
||||
coef_index = (header >> 10) & 0x1f;
|
||||
pos_scale = l5_scales[(header >> 5) & 0x1f];
|
||||
neg_scale = l5_scales[(header >> 0) & 0x1f];
|
||||
|
||||
if (nibble >= 0)
|
||||
{
|
||||
outbuf[sample_count] = clamp16((prediction + nibble * pos_scale) >> 12);
|
||||
}
|
||||
coef1 = stream->adpcm_coef_3by32[coef_index * 3 + 0];
|
||||
coef2 = stream->adpcm_coef_3by32[coef_index * 3 + 1];
|
||||
coef3 = stream->adpcm_coef_3by32[coef_index * 3 + 2];
|
||||
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
int32_t prediction, sample = 0;
|
||||
uint8_t nibbles = frame[0x02 + i/2];
|
||||
|
||||
sample = (i&1) ?
|
||||
get_low_nibble_signed(nibbles):
|
||||
get_high_nibble_signed(nibbles);
|
||||
prediction = -(hist1 * coef1 + hist2 * coef2 + hist3 * coef3);
|
||||
|
||||
if (sample >= 0)
|
||||
sample = (prediction + sample * pos_scale) >> 12;
|
||||
else
|
||||
{
|
||||
outbuf[sample_count] = clamp16((prediction + nibble * neg_scale) >> 12);
|
||||
}
|
||||
sample = (prediction + sample * neg_scale) >> 12;
|
||||
sample = clamp16(sample);
|
||||
|
||||
outbuf[sample_count] = sample;
|
||||
sample_count += channelspacing;
|
||||
|
||||
hist3 = hist2;
|
||||
hist2 = hist1;
|
||||
hist1 = outbuf[sample_count];
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_16 = hist1;
|
||||
|
91
src/coding/mpeg_bitreader.h
Normal file
91
src/coding/mpeg_bitreader.h
Normal file
@ -0,0 +1,91 @@
|
||||
#ifndef _MPEG_BITREADER_H
|
||||
#define _MPEG_BITREADER_H
|
||||
|
||||
/* Simple bitreader for MPEG/standard bit format.
|
||||
* Kept in .h since it's slightly faster (compiler can optimize statics better) */
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t* buf; /* buffer to read/write */
|
||||
size_t bufsize; /* max size of the buffer */
|
||||
uint32_t b_off; /* current offset in bits inside the buffer */
|
||||
} bitstream_t;
|
||||
|
||||
/* convenience util */
|
||||
static void init_bitstream(bitstream_t* b, uint8_t* buf, size_t bufsize) {
|
||||
b->buf = buf;
|
||||
b->bufsize = bufsize;
|
||||
b->b_off = 0;
|
||||
}
|
||||
|
||||
/* Read bits (max 32) from buf and update the bit offset. Order is BE (MSF). */
|
||||
static int rb_bits(bitstream_t* ib, uint32_t bits, uint32_t* value) {
|
||||
uint32_t shift, pos, val;
|
||||
int i, bit_buf, bit_val;
|
||||
|
||||
if (bits > 32 || ib->b_off + bits > ib->bufsize * 8)
|
||||
goto fail;
|
||||
|
||||
pos = ib->b_off / 8; /* byte offset */
|
||||
shift = ib->b_off % 8; /* bit sub-offset */
|
||||
|
||||
val = 0;
|
||||
for (i = 0; i < bits; i++) {
|
||||
bit_buf = (1U << (8-1-shift)) & 0xFF; /* bit check for buf */
|
||||
bit_val = (1U << (bits-1-i)); /* bit to set in value */
|
||||
|
||||
if (ib->buf[pos] & bit_buf) /* is bit in buf set? */
|
||||
val |= bit_val; /* set bit */
|
||||
|
||||
shift++;
|
||||
if (shift % 8 == 0) { /* new byte starts */
|
||||
shift = 0;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
*value = val;
|
||||
ib->b_off += bits;
|
||||
return 1;
|
||||
fail:
|
||||
VGM_LOG("BITREADER: read fail\n");
|
||||
*value = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef BITSTREAM_READ_ONLY
|
||||
/* Write bits (max 32) to buf and update the bit offset. Order is BE (MSF). */
|
||||
static int wb_bits(bitstream_t* ob, uint32_t bits, uint32_t value) {
|
||||
uint32_t shift, pos;
|
||||
int i, bit_val, bit_buf;
|
||||
|
||||
if (bits > 32 || ob->b_off + bits > ob->bufsize * 8)
|
||||
goto fail;
|
||||
|
||||
pos = ob->b_off / 8; /* byte offset */
|
||||
shift = ob->b_off % 8; /* bit sub-offset */
|
||||
|
||||
for (i = 0; i < bits; i++) {
|
||||
bit_val = (1U << (bits-1-i)); /* bit check for value */
|
||||
bit_buf = (1U << (8-1-shift)) & 0xFF; /* bit to set in buf */
|
||||
|
||||
if (value & bit_val) /* is bit in val set? */
|
||||
ob->buf[pos] |= bit_buf; /* set bit */
|
||||
else
|
||||
ob->buf[pos] &= ~bit_buf; /* unset bit */
|
||||
|
||||
shift++;
|
||||
if (shift % 8 == 0) { /* new byte starts */
|
||||
shift = 0;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
ob->b_off += bits;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,4 +1,5 @@
|
||||
#include "mpeg_decoder.h"
|
||||
#include "mpeg_bitreader.h"
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
|
||||
@ -27,9 +28,9 @@
|
||||
|
||||
/* helper to pass around */
|
||||
typedef struct {
|
||||
STREAMFILE *sf;
|
||||
STREAMFILE* sf;
|
||||
off_t offset;
|
||||
vgm_bitstream is;
|
||||
bitstream_t is;
|
||||
uint8_t buf[EALAYER3_MAX_EA_FRAME_SIZE];
|
||||
int leftover_bits;
|
||||
} ealayer3_buffer_t;
|
||||
@ -82,21 +83,21 @@ typedef struct {
|
||||
} ealayer3_frame_t;
|
||||
|
||||
|
||||
static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b);
|
||||
static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream *os);
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
|
||||
static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset);
|
||||
static int ealayer3_parse_frame(mpeg_codec_data* data, int num_stream, ealayer3_buffer_t* ib, ealayer3_frame_t* eaf);
|
||||
static int ealayer3_parse_frame_v1(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf, int channels_per_frame, int is_v1b);
|
||||
static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf);
|
||||
static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf);
|
||||
static int ealayer3_rebuild_mpeg_frame(bitstream_t* is_0, ealayer3_frame_t* eaf_0, bitstream_t* is_1, ealayer3_frame_t* eaf_1, bitstream_t* os);
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, ealayer3_frame_t* eaf);
|
||||
static int ealayer3_skip_data(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, int at_start);
|
||||
static int ealayer3_is_empty_frame_v2p(STREAMFILE* sf, off_t offset);
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/* init codec from an EALayer3 frame */
|
||||
int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamfile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||
int mpeg_custom_setup_init_ealayer3(STREAMFILE* streamfile, off_t start_offset, mpeg_codec_data* data, coding_t *coding_type) {
|
||||
int ok;
|
||||
ealayer3_buffer_t ib = {0};
|
||||
ealayer3_frame_t eaf;
|
||||
@ -130,7 +131,7 @@ fail:
|
||||
}
|
||||
|
||||
/* writes data to the buffer and moves offsets, transforming EALayer3 frames */
|
||||
int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
int ok, granule_found;
|
||||
ealayer3_buffer_t ib_0 = {0}, ib_1 = {0};
|
||||
@ -148,7 +149,7 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *
|
||||
|
||||
|
||||
/* read first frame/granule, or PCM-only frame (found alone at the end of SCHl streams) */
|
||||
{
|
||||
{
|
||||
//;VGM_LOG("s%i: get granule0 at %lx\n", num_stream,stream->offset);
|
||||
if (!ealayer3_skip_data(stream, data, num_stream, 1))
|
||||
goto fail;
|
||||
@ -168,15 +169,15 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *
|
||||
|
||||
if (!ealayer3_skip_data(stream, data, num_stream, 0))
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* In EAL3 V2P sometimes there is a new SNS/SPS block between granules. Instead of trying to fix it here
|
||||
* or in blocked layout (too complex/patchy), SNS/SPS uses a custom streamfile that simply removes all
|
||||
* block headers, so this parser only sees raw EALayer3 data. It also discards samples, which confuses
|
||||
* blocked layout calculations
|
||||
*
|
||||
* Similarly (as V2P decodes and writes 1 granule at a time) stream can end in granule0. Since mpg123
|
||||
* decodes in pairs we detect and feed it a fake end granule1, to get the last granule0's 576 samples. */
|
||||
/* In EAL3 V2P sometimes there is a new SNS/SPS block between granules. Instead of trying to fix it here
|
||||
* or in blocked layout (too complex/patchy), SNS/SPS uses a custom streamfile that simply removes all
|
||||
* block headers, so this parser only sees raw EALayer3 data. It also discards samples, which confuses
|
||||
* blocked layout calculations
|
||||
*
|
||||
* Similarly (as V2P decodes and writes 1 granule at a time) stream can end in granule0. Since mpg123
|
||||
* decodes in pairs we detect and feed it a fake end granule1, to get the last granule0's 576 samples. */
|
||||
|
||||
granule_found = 0;
|
||||
/* get second frame/granule (MPEG1 only) if first granule was found */
|
||||
@ -219,10 +220,9 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *
|
||||
|
||||
/* rebuild EALayer3 frame to MPEG frame */
|
||||
{
|
||||
vgm_bitstream os = {0};
|
||||
bitstream_t os = {0};
|
||||
|
||||
os.buf = ms->buffer;
|
||||
os.bufsize = ms->buffer_size;
|
||||
init_bitstream(&os, ms->buffer, ms->buffer_size);
|
||||
|
||||
ok = ealayer3_rebuild_mpeg_frame(&ib_0.is, &eaf_0, &ib_1.is, &eaf_1, &os);
|
||||
if (!ok) goto fail;
|
||||
@ -243,7 +243,7 @@ fail:
|
||||
/* Read at most N bits from streamfile. This makes more smaller reads (not good) but
|
||||
* allows exact frame size reading (good), as reading over a frame then reading back
|
||||
* is expensive in EALayer3 since it uses custom streamfiles. */
|
||||
static void fill_buf(ealayer3_buffer_t *ib, int bits) {
|
||||
static void fill_buf(ealayer3_buffer_t* ib, int bits) {
|
||||
size_t read_size, bytes_size;
|
||||
int mod;
|
||||
|
||||
@ -273,7 +273,7 @@ static void fill_buf(ealayer3_buffer_t *ib, int bits) {
|
||||
ib->leftover_bits = (mod > 0 ? 8 - mod : 0);
|
||||
}
|
||||
|
||||
static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) {
|
||||
static int ealayer3_parse_frame(mpeg_codec_data* data, int num_stream, ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) {
|
||||
int ok;
|
||||
|
||||
/* We must pass this from state, as not all EA-frames have channel info.
|
||||
@ -302,14 +302,14 @@ fail:
|
||||
|
||||
|
||||
/* read V1"a" (in SCHl) and V1"b" (in SNS) EALayer3 frame */
|
||||
static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b) {
|
||||
static int ealayer3_parse_frame_v1(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf, int channels_per_frame, int is_v1b) {
|
||||
int ok;
|
||||
vgm_bitstream *is = &ib->is;
|
||||
bitstream_t* is = &ib->is;
|
||||
|
||||
|
||||
/* read EA-frame V1 header */
|
||||
fill_buf(ib, 8);
|
||||
r_bits(is, 8,&eaf->v1_pcm_flag);
|
||||
rb_bits(is, 8,&eaf->v1_pcm_flag);
|
||||
|
||||
eaf->pre_size = 1; /* 8b */
|
||||
|
||||
@ -331,15 +331,15 @@ static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf,
|
||||
/* check PCM block */
|
||||
if (eaf->v1_pcm_flag == 0xEE) {
|
||||
fill_buf(ib, 32);
|
||||
r_bits(is, 16,&eaf->v1_offset_samples); /* PCM block offset in the buffer */
|
||||
r_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */
|
||||
rb_bits(is, 16,&eaf->v1_offset_samples); /* PCM block offset in the buffer */
|
||||
rb_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */
|
||||
|
||||
eaf->pre_size += 2+2; /* 16b+16b */
|
||||
eaf->pcm_size = (2*eaf->v1_pcm_samples * channels_per_frame);
|
||||
|
||||
if (is_v1b) { /* extra 32b in v1b */
|
||||
fill_buf(ib, 32);
|
||||
r_bits(is, 32,&eaf->v1_pcm_unknown);
|
||||
rb_bits(is, 32,&eaf->v1_pcm_unknown);
|
||||
|
||||
eaf->pre_size += 4; /* 32b */
|
||||
|
||||
@ -356,26 +356,26 @@ fail:
|
||||
|
||||
/* read V2"PCM" and V2"Spike" EALayer3 frame (exactly the same but V2P seems to have bigger
|
||||
* PCM blocks and maybe smaller frames) */
|
||||
static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) {
|
||||
static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) {
|
||||
int ok;
|
||||
vgm_bitstream *is = &ib->is;
|
||||
bitstream_t* is = &ib->is;
|
||||
|
||||
|
||||
/* read EA-frame V2 header */
|
||||
fill_buf(ib, 16);
|
||||
r_bits(is, 1,&eaf->v2_extended_flag);
|
||||
r_bits(is, 1,&eaf->v2_stereo_flag);
|
||||
r_bits(is, 2,&eaf->v2_reserved);
|
||||
r_bits(is, 12,&eaf->v2_frame_size);
|
||||
rb_bits(is, 1,&eaf->v2_extended_flag);
|
||||
rb_bits(is, 1,&eaf->v2_stereo_flag);
|
||||
rb_bits(is, 2,&eaf->v2_reserved);
|
||||
rb_bits(is, 12,&eaf->v2_frame_size);
|
||||
|
||||
eaf->pre_size = 2; /* 16b */
|
||||
|
||||
if (eaf->v2_extended_flag) {
|
||||
fill_buf(ib, 32);
|
||||
r_bits(is, 2,&eaf->v2_offset_mode);
|
||||
r_bits(is, 10,&eaf->v2_offset_samples);
|
||||
r_bits(is, 10,&eaf->v2_pcm_samples);
|
||||
r_bits(is, 10,&eaf->v2_common_size);
|
||||
rb_bits(is, 2,&eaf->v2_offset_mode);
|
||||
rb_bits(is, 10,&eaf->v2_offset_samples);
|
||||
rb_bits(is, 10,&eaf->v2_pcm_samples);
|
||||
rb_bits(is, 10,&eaf->v2_common_size);
|
||||
|
||||
eaf->pre_size += 4; /* 32b */
|
||||
}
|
||||
@ -406,7 +406,7 @@ fail:
|
||||
|
||||
|
||||
/* parses an EALayer3 frame (common part) */
|
||||
static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) {
|
||||
static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) {
|
||||
/* index tables */
|
||||
static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
|
||||
static const int sample_rates[4][4] = { /* [version_index][sample rate index] */
|
||||
@ -417,17 +417,17 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *
|
||||
};
|
||||
static const int channels[4] = { 2,2,2, 1 }; /* [channel_mode] */
|
||||
|
||||
vgm_bitstream *is = &ib->is;
|
||||
bitstream_t* is = &ib->is;
|
||||
off_t start_b_off = is->b_off;
|
||||
int i, fill_bits, others_2_bits;
|
||||
|
||||
|
||||
/* read main header */
|
||||
fill_buf(ib, 8);
|
||||
r_bits(is, 2,&eaf->version_index);
|
||||
r_bits(is, 2,&eaf->sample_rate_index);
|
||||
r_bits(is, 2,&eaf->channel_mode);
|
||||
r_bits(is, 2,&eaf->mode_extension);
|
||||
rb_bits(is, 2,&eaf->version_index);
|
||||
rb_bits(is, 2,&eaf->sample_rate_index);
|
||||
rb_bits(is, 2,&eaf->channel_mode);
|
||||
rb_bits(is, 2,&eaf->mode_extension);
|
||||
|
||||
|
||||
/* check empty frame */
|
||||
@ -455,7 +455,7 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *
|
||||
|
||||
/* read side info */
|
||||
fill_buf(ib, 1);
|
||||
r_bits(is, 1,&eaf->granule_index);
|
||||
rb_bits(is, 1,&eaf->granule_index);
|
||||
|
||||
fill_bits = (eaf->mpeg1 && eaf->granule_index == 1) ? 4 * eaf->channels : 0;
|
||||
fill_bits = fill_bits + (12 + 32 + others_2_bits) * eaf->channels;
|
||||
@ -463,15 +463,15 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *
|
||||
|
||||
if (eaf->mpeg1 && eaf->granule_index == 1) {
|
||||
for (i = 0; i < eaf->channels; i++) {
|
||||
r_bits(is, 4,&eaf->scfsi[i]);
|
||||
rb_bits(is, 4,&eaf->scfsi[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < eaf->channels; i++) {
|
||||
r_bits(is, 12,&eaf->main_data_size[i]);
|
||||
rb_bits(is, 12,&eaf->main_data_size[i]);
|
||||
/* divided in 47b=32+15 (MPEG1) or 51b=32+19 (MPEG2), arbitrarily */
|
||||
r_bits(is, 32,&eaf->others_1[i]);
|
||||
r_bits(is, others_2_bits,&eaf->others_2[i]);
|
||||
rb_bits(is, 32,&eaf->others_1[i]);
|
||||
rb_bits(is, others_2_bits,&eaf->others_2[i]);
|
||||
}
|
||||
|
||||
|
||||
@ -496,7 +496,7 @@ fail:
|
||||
|
||||
|
||||
/* converts an EALAYER3 frame to a standard MPEG frame from pre-parsed info */
|
||||
static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream* os) {
|
||||
static int ealayer3_rebuild_mpeg_frame(bitstream_t* is_0, ealayer3_frame_t* eaf_0, bitstream_t* is_1, ealayer3_frame_t* eaf_1, bitstream_t* os) {
|
||||
uint32_t c = 0;
|
||||
int i, j;
|
||||
int expected_bitrate_index, expected_frame_size;
|
||||
@ -537,55 +537,55 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *ea
|
||||
#endif
|
||||
|
||||
/* write MPEG1/2 frame header */
|
||||
w_bits(os, 11, 0x7FF); /* sync */
|
||||
w_bits(os, 2, eaf_0->version_index);
|
||||
w_bits(os, 2, 0x01); /* layer III index */
|
||||
w_bits(os, 1, 1); /* "no CRC" flag */
|
||||
w_bits(os, 4, expected_bitrate_index);
|
||||
w_bits(os, 2, eaf_0->sample_rate_index);
|
||||
w_bits(os, 1, 0); /* padding */
|
||||
w_bits(os, 1, 0); /* private */
|
||||
w_bits(os, 2, eaf_0->channel_mode);
|
||||
w_bits(os, 2, eaf_0->mode_extension);
|
||||
w_bits(os, 1, 1); /* copyrighted */
|
||||
w_bits(os, 1, 1); /* original */
|
||||
w_bits(os, 2, 0); /* emphasis */
|
||||
wb_bits(os, 11, 0x7FF); /* sync */
|
||||
wb_bits(os, 2, eaf_0->version_index);
|
||||
wb_bits(os, 2, 0x01); /* layer III index */
|
||||
wb_bits(os, 1, 1); /* "no CRC" flag */
|
||||
wb_bits(os, 4, expected_bitrate_index);
|
||||
wb_bits(os, 2, eaf_0->sample_rate_index);
|
||||
wb_bits(os, 1, 0); /* padding */
|
||||
wb_bits(os, 1, 0); /* private */
|
||||
wb_bits(os, 2, eaf_0->channel_mode);
|
||||
wb_bits(os, 2, eaf_0->mode_extension);
|
||||
wb_bits(os, 1, 1); /* copyrighted */
|
||||
wb_bits(os, 1, 1); /* original */
|
||||
wb_bits(os, 2, 0); /* emphasis */
|
||||
|
||||
if (eaf_0->mpeg1) {
|
||||
int private_bits = (eaf_0->channels==1 ? 5 : 3);
|
||||
|
||||
/* write MPEG1 side info */
|
||||
w_bits(os, 9, 0); /* main data start (no bit reservoir) */
|
||||
w_bits(os, private_bits, 0);
|
||||
wb_bits(os, 9, 0); /* main data start (no bit reservoir) */
|
||||
wb_bits(os, private_bits, 0);
|
||||
|
||||
for (i = 0; i < eaf_1->channels; i++) {
|
||||
w_bits(os, 4, eaf_1->scfsi[i]); /* saved in granule1 only */
|
||||
wb_bits(os, 4, eaf_1->scfsi[i]); /* saved in granule1 only */
|
||||
}
|
||||
for (i = 0; i < eaf_0->channels; i++) { /* granule0 */
|
||||
w_bits(os, 12, eaf_0->main_data_size[i]);
|
||||
w_bits(os, 32, eaf_0->others_1[i]);
|
||||
w_bits(os, 47-32, eaf_0->others_2[i]);
|
||||
wb_bits(os, 12, eaf_0->main_data_size[i]);
|
||||
wb_bits(os, 32, eaf_0->others_1[i]);
|
||||
wb_bits(os, 47-32, eaf_0->others_2[i]);
|
||||
}
|
||||
for (i = 0; i < eaf_1->channels; i++) { /* granule1 */
|
||||
w_bits(os, 12, eaf_1->main_data_size[i]);
|
||||
w_bits(os, 32, eaf_1->others_1[i]);
|
||||
w_bits(os, 47-32, eaf_1->others_2[i]);
|
||||
wb_bits(os, 12, eaf_1->main_data_size[i]);
|
||||
wb_bits(os, 32, eaf_1->others_1[i]);
|
||||
wb_bits(os, 47-32, eaf_1->others_2[i]);
|
||||
}
|
||||
|
||||
/* write MPEG1 main data */
|
||||
is_0->b_off = eaf_0->data_offset_b;
|
||||
for (i = 0; i < eaf_0->channels; i++) { /* granule0 */
|
||||
for (j = 0; j < eaf_0->main_data_size[i]; j++) {
|
||||
r_bits(is_0, 1, &c);
|
||||
w_bits(os, 1, c);
|
||||
rb_bits(is_0, 1, &c);
|
||||
wb_bits(os, 1, c);
|
||||
}
|
||||
}
|
||||
|
||||
is_1->b_off = eaf_1->data_offset_b;
|
||||
for (i = 0; i < eaf_1->channels; i++) { /* granule1 */
|
||||
for (j = 0; j < eaf_1->main_data_size[i]; j++) {
|
||||
r_bits(is_1, 1, &c);
|
||||
w_bits(os, 1, c);
|
||||
rb_bits(is_1, 1, &c);
|
||||
wb_bits(os, 1, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -593,21 +593,21 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *ea
|
||||
int private_bits = (eaf_0->channels==1 ? 1 : 2);
|
||||
|
||||
/* write MPEG2 side info */
|
||||
w_bits(os, 8, 0); /* main data start (no bit reservoir) */
|
||||
w_bits(os, private_bits, 0);
|
||||
wb_bits(os, 8, 0); /* main data start (no bit reservoir) */
|
||||
wb_bits(os, private_bits, 0);
|
||||
|
||||
for (i = 0; i < eaf_0->channels; i++) {
|
||||
w_bits(os, 12, eaf_0->main_data_size[i]);
|
||||
w_bits(os, 32, eaf_0->others_1[i]);
|
||||
w_bits(os, 51-32, eaf_0->others_2[i]);
|
||||
wb_bits(os, 12, eaf_0->main_data_size[i]);
|
||||
wb_bits(os, 32, eaf_0->others_1[i]);
|
||||
wb_bits(os, 51-32, eaf_0->others_2[i]);
|
||||
}
|
||||
|
||||
/* write MPEG2 main data */
|
||||
is_0->b_off = eaf_0->data_offset_b;
|
||||
for (i = 0; i < eaf_0->channels; i++) {
|
||||
for (j = 0; j < eaf_0->main_data_size[i]; j++) {
|
||||
r_bits(is_0, 1, &c);
|
||||
w_bits(os, 1, c);
|
||||
rb_bits(is_0, 1, &c);
|
||||
wb_bits(os, 1, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -615,7 +615,7 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *ea
|
||||
/* align to closest 8b */
|
||||
if (os->b_off % 8) {
|
||||
int align_bits = 8 - (os->b_off % 8);
|
||||
w_bits(os, align_bits, 0);
|
||||
wb_bits(os, align_bits, 0);
|
||||
}
|
||||
|
||||
|
||||
@ -636,7 +636,7 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_number, int channels_per_frame, int is_packed, STREAMFILE *sf) {
|
||||
static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_number, int channels_per_frame, int is_packed, STREAMFILE* sf) {
|
||||
int i, ch;
|
||||
uint8_t pcm_block[1152 * 2 * 2]; /* assumed max: 1 MPEG frame samples * 16b * max channels */
|
||||
size_t pcm_size = pcm_number * 2 * channels_per_frame;
|
||||
@ -687,7 +687,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n
|
||||
|
||||
/* write PCM block directly to sample buffer and setup decode discard (EALayer3 seems to use this as a prefetch of sorts).
|
||||
* Seems to alter decoded sample buffer to handle encoder delay/padding in a twisted way. */
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf) {
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, ealayer3_frame_t* eaf) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
int channels_per_frame = ms->channels_per_frame;
|
||||
size_t bytes_filled;
|
||||
@ -817,7 +817,7 @@ fail:
|
||||
*
|
||||
* EALayer3 v1 in SCHl uses external offsets and 1ch multichannel instead.
|
||||
*/
|
||||
static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
|
||||
static int ealayer3_skip_data(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, int at_start) {
|
||||
int ok, i;
|
||||
ealayer3_buffer_t ib = {0};
|
||||
ealayer3_frame_t eaf;
|
||||
@ -845,7 +845,7 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset) {
|
||||
static int ealayer3_is_empty_frame_v2p(STREAMFILE* sf, off_t offset) {
|
||||
/* V2P frame header should contain a valid frame size (lower 12b) */
|
||||
uint16_t v2_header = read_u16be(offset, sf);
|
||||
return (v2_header % 0xFFF) == 0 || v2_header == 0xFFFF;
|
||||
|
103
src/coding/vorbis_bitreader.h
Normal file
103
src/coding/vorbis_bitreader.h
Normal file
@ -0,0 +1,103 @@
|
||||
#ifndef _VORBIS_BITREADER_H
|
||||
#define _VORBIS_BITREADER_H
|
||||
|
||||
/* Simple bitreader for Vorbis' bit format.
|
||||
* Kept in .h since it's slightly faster (compiler can optimize statics better) */
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t* buf; /* buffer to read/write */
|
||||
size_t bufsize; /* max size of the buffer */
|
||||
uint32_t b_off; /* current offset in bits inside the buffer */
|
||||
} bitstream_t;
|
||||
|
||||
/* convenience util */
|
||||
static void init_bitstream(bitstream_t* b, uint8_t* buf, size_t bufsize) {
|
||||
b->buf = buf;
|
||||
b->bufsize = bufsize;
|
||||
b->b_off = 0;
|
||||
}
|
||||
|
||||
/* same as (1 << bits) - 1, but that seems to trigger some nasty UB when bits = 32
|
||||
* (though in theory (1 << 32) = 0, 0 - 1 = UINT_MAX, but gives 0 compiling in some cases, but not always) */
|
||||
static const uint32_t MASK_TABLE[33] = {
|
||||
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
|
||||
0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, 0x0001ffff,
|
||||
0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff,
|
||||
0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff
|
||||
};
|
||||
|
||||
/* Read bits (max 32) from buf and update the bit offset. Vorbis packs values in LSB order and byte by byte.
|
||||
* (ex. from 2 bytes 00100111 00000001 we can could read 4b=0111 and 6b=010010, 6b=remainder (second value is split into the 2nd byte) */
|
||||
static int rv_bits(bitstream_t* ib, uint32_t bits, uint32_t* value) {
|
||||
uint32_t shift, mask, pos, val;
|
||||
|
||||
if (bits > 32 || ib->b_off + bits > ib->bufsize * 8)
|
||||
goto fail;
|
||||
|
||||
pos = ib->b_off / 8; /* byte offset */
|
||||
shift = ib->b_off % 8; /* bit sub-offset */
|
||||
mask = MASK_TABLE[bits]; /* to remove upper in highest byte */
|
||||
|
||||
val = ib->buf[pos+0] >> shift;
|
||||
if (bits + shift > 8) {
|
||||
val |= ib->buf[pos+1] << (8u - shift);
|
||||
if (bits + shift > 16) {
|
||||
val |= ib->buf[pos+2] << (16u - shift);
|
||||
if (bits + shift > 24) {
|
||||
val |= ib->buf[pos+3] << (24u - shift);
|
||||
if (bits + shift > 32) {
|
||||
val |= ib->buf[pos+4] << (32u - shift); /* upper bits are lost (shifting over 32) */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*value = (val & mask);
|
||||
|
||||
ib->b_off += bits;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
VGM_LOG("BITREADER: read fail\n");
|
||||
*value = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef BITSTREAM_READ_ONLY
|
||||
/* Write bits (max 32) to buf and update the bit offset. Vorbis packs values in LSB order and byte by byte.
|
||||
* (ex. writing 1101011010 from b_off 2 we get 01101011 00001101 (value split, and 11 in the first byte skipped)*/
|
||||
static int wv_bits(bitstream_t* ob, uint32_t bits, uint32_t value) {
|
||||
uint32_t shift, mask, pos;
|
||||
|
||||
if (bits > 32 || ob->b_off + bits > ob->bufsize*8)
|
||||
goto fail;
|
||||
|
||||
pos = ob->b_off / 8; /* byte offset */
|
||||
shift = ob->b_off % 8; /* bit sub-offset */
|
||||
mask = (1 << shift) - 1; /* to keep lower bits in lowest byte */
|
||||
|
||||
ob->buf[pos+0] = (value << shift) | (ob->buf[pos+0] & mask);
|
||||
if (bits + shift > 8) {
|
||||
ob->buf[pos+1] = value >> (8 - shift);
|
||||
if (bits + shift > 16) {
|
||||
ob->buf[pos+2] = value >> (16 - shift);
|
||||
if (bits + shift > 24) {
|
||||
ob->buf[pos+3] = value >> (24 - shift);
|
||||
if (bits + shift > 32) {
|
||||
/* upper bits are set to 0 (shifting unsigned) but shouldn't matter */
|
||||
ob->buf[pos+4] = value >> (32 - shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ob->b_off += bits;
|
||||
return 1;
|
||||
fail:
|
||||
VGM_LOG("BITREADER: write fail\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,230 +1,228 @@
|
||||
#include <math.h>
|
||||
#include "coding.h"
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
#define VORBIS_DEFAULT_BUFFER_SIZE 0x8000 /* should be at least the size of the setup header, ~0x2000 */
|
||||
|
||||
static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm);
|
||||
|
||||
/**
|
||||
* Inits a vorbis stream of some custom variety.
|
||||
*
|
||||
* Normally Vorbis packets are stored in .ogg, which is divided into OggS pages/packets, and the first packets contain necessary
|
||||
* Vorbis setup. For custom vorbis the OggS layer is replaced/optimized, the setup can be modified or stored elsewhere
|
||||
* (i.e.- in the .exe) and raw Vorbis packets may be modified as well, presumably to shave off some kb and/or obfuscate.
|
||||
* We'll manually read/modify the data and decode it with libvorbis calls.
|
||||
*
|
||||
* Reference: https://www.xiph.org/vorbis/doc/libvorbis/overview.html
|
||||
*/
|
||||
vorbis_custom_codec_data * init_vorbis_custom(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_t type, vorbis_custom_config * config) {
|
||||
vorbis_custom_codec_data * data = NULL;
|
||||
int ok;
|
||||
|
||||
/* init stuff */
|
||||
data = calloc(1,sizeof(vorbis_custom_codec_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->buffer_size = VORBIS_DEFAULT_BUFFER_SIZE;
|
||||
data->buffer = calloc(sizeof(uint8_t), data->buffer_size);
|
||||
if (!data->buffer) goto fail;
|
||||
|
||||
/* keep around to decode too */
|
||||
data->type = type;
|
||||
memcpy(&data->config, config, sizeof(vorbis_custom_config));
|
||||
|
||||
|
||||
/* init vorbis stream state, using 3 fake Ogg setup packets (info, comments, setup/codebooks)
|
||||
* libvorbis expects parsed Ogg pages, but we'll fake them with our raw data instead */
|
||||
vorbis_info_init(&data->vi);
|
||||
vorbis_comment_init(&data->vc);
|
||||
|
||||
data->op.packet = data->buffer;
|
||||
data->op.b_o_s = 1; /* fake headers start */
|
||||
|
||||
/* init header */
|
||||
switch(data->type) {
|
||||
case VORBIS_FSB: ok = vorbis_custom_setup_init_fsb(streamFile, start_offset, data); break;
|
||||
case VORBIS_WWISE: ok = vorbis_custom_setup_init_wwise(streamFile, start_offset, data); break;
|
||||
case VORBIS_OGL: ok = vorbis_custom_setup_init_ogl(streamFile, start_offset, data); break;
|
||||
case VORBIS_SK: ok = vorbis_custom_setup_init_sk(streamFile, start_offset, data); break;
|
||||
case VORBIS_VID1: ok = vorbis_custom_setup_init_vid1(streamFile, start_offset, data); break;
|
||||
default: goto fail;
|
||||
}
|
||||
if(!ok) goto fail;
|
||||
|
||||
data->op.b_o_s = 0; /* end of fake headers */
|
||||
|
||||
/* init vorbis global and block state */
|
||||
if (vorbis_synthesis_init(&data->vd,&data->vi) != 0) goto fail;
|
||||
if (vorbis_block_init(&data->vd,&data->vb) != 0) goto fail;
|
||||
|
||||
|
||||
/* write output */
|
||||
config->data_start_offset = data->config.data_start_offset;
|
||||
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
VGM_LOG("VORBIS: init fail at around 0x%x\n", (uint32_t)start_offset);
|
||||
free_vorbis_custom(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Decodes Vorbis packets into a libvorbis sample buffer, and copies them to outbuf */
|
||||
void decode_vorbis_custom(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) {
|
||||
VGMSTREAMCHANNEL *stream = &vgmstream->ch[0];
|
||||
vorbis_custom_codec_data * data = vgmstream->codec_data;
|
||||
size_t stream_size = get_streamfile_size(stream->streamfile);
|
||||
//data->op.packet = data->buffer;/* implicit from init */
|
||||
int samples_done = 0;
|
||||
|
||||
while (samples_done < samples_to_do) {
|
||||
|
||||
/* extra EOF check for edge cases */
|
||||
if (stream->offset >= stream_size) {
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (data->samples_full) { /* read more samples */
|
||||
int samples_to_get;
|
||||
float **pcm;
|
||||
|
||||
/* get PCM samples from libvorbis buffers */
|
||||
samples_to_get = vorbis_synthesis_pcmout(&data->vd, &pcm);
|
||||
if (!samples_to_get) {
|
||||
data->samples_full = 0; /* request more if empty*/
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data->samples_to_discard) {
|
||||
/* discard samples for looping */
|
||||
if (samples_to_get > data->samples_to_discard)
|
||||
samples_to_get = data->samples_to_discard;
|
||||
data->samples_to_discard -= samples_to_get;
|
||||
}
|
||||
else {
|
||||
/* get max samples and convert from Vorbis float pcm to 16bit pcm */
|
||||
if (samples_to_get > samples_to_do - samples_done)
|
||||
samples_to_get = samples_to_do - samples_done;
|
||||
pcm_convert_float_to_16(data->vi.channels, outbuf + samples_done * channels, samples_to_get, pcm);
|
||||
samples_done += samples_to_get;
|
||||
}
|
||||
|
||||
/* mark consumed samples from the buffer
|
||||
* (non-consumed samples are returned in next vorbis_synthesis_pcmout calls) */
|
||||
vorbis_synthesis_read(&data->vd, samples_to_get);
|
||||
}
|
||||
else { /* read more data */
|
||||
int ok, rc;
|
||||
|
||||
/* not actually needed, but feels nicer */
|
||||
data->op.granulepos += samples_to_do; /* can be changed next if desired */
|
||||
data->op.packetno++;
|
||||
|
||||
/* read/transform data into the ogg_packet buffer and advance offsets */
|
||||
switch(data->type) {
|
||||
case VORBIS_FSB: ok = vorbis_custom_parse_packet_fsb(stream, data); break;
|
||||
case VORBIS_WWISE: ok = vorbis_custom_parse_packet_wwise(stream, data); break;
|
||||
case VORBIS_OGL: ok = vorbis_custom_parse_packet_ogl(stream, data); break;
|
||||
case VORBIS_SK: ok = vorbis_custom_parse_packet_sk(stream, data); break;
|
||||
case VORBIS_VID1: ok = vorbis_custom_parse_packet_vid1(stream, data); break;
|
||||
default: goto decode_fail;
|
||||
}
|
||||
if(!ok) {
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
|
||||
/* parse the fake ogg packet into a logical vorbis block */
|
||||
rc = vorbis_synthesis(&data->vb,&data->op);
|
||||
if (rc == OV_ENOTAUDIO) {
|
||||
VGM_LOG("Vorbis: not an audio packet (size=0x%x) @ %x\n",(size_t)data->op.bytes,(uint32_t)stream->offset);
|
||||
//VGM_LOGB(data->op.packet, (size_t)data->op.bytes,0);
|
||||
continue; /* rarely happens, seems ok? */
|
||||
} else if (rc != 0) goto decode_fail;
|
||||
|
||||
/* finally decode the logical block into samples */
|
||||
rc = vorbis_synthesis_blockin(&data->vd,&data->vb);
|
||||
if (rc != 0) goto decode_fail; /* ? */
|
||||
|
||||
|
||||
data->samples_full = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
decode_fail:
|
||||
/* on error just put some 0 samples */
|
||||
VGM_LOG("VORBIS: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * channels * sizeof(sample));
|
||||
}
|
||||
|
||||
/* converts from internal Vorbis format to standard PCM (mostly from Xiph's decoder_example.c) */
|
||||
static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm) {
|
||||
int ch, s;
|
||||
sample_t *ptr;
|
||||
float *channel;
|
||||
|
||||
/* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc)
|
||||
* to 16 bit signed PCM ints (host order) and interleave + fix clipping */
|
||||
for (ch = 0; ch < channels; ch++) {
|
||||
/* channels should be in standard order unlike Ogg Vorbis (at least in FSB) */
|
||||
ptr = outbuf + ch;
|
||||
channel = pcm[ch];
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
int val = (int)floor(channel[s] * 32767.0f + 0.5f);
|
||||
if (val > 32767) val = 32767;
|
||||
else if (val < -32768) val = -32768;
|
||||
|
||||
*ptr = val;
|
||||
ptr += channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ********************************************** */
|
||||
|
||||
void free_vorbis_custom(vorbis_custom_codec_data * data) {
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
/* internal decoder cleanup */
|
||||
vorbis_block_clear(&data->vb);
|
||||
vorbis_dsp_clear(&data->vd);
|
||||
vorbis_comment_clear(&data->vc);
|
||||
vorbis_info_clear(&data->vi);
|
||||
|
||||
free(data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void reset_vorbis_custom(VGMSTREAM *vgmstream) {
|
||||
vorbis_custom_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
/* Seeking is provided by the Ogg layer, so with custom vorbis we'd need seek tables instead.
|
||||
* To avoid having to parse different formats we'll just discard until the expected sample */
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = 0;
|
||||
}
|
||||
|
||||
void seek_vorbis_custom(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
vorbis_custom_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
/* Seeking is provided by the Ogg layer, so with custom vorbis we'd need seek tables instead.
|
||||
* To avoid having to parse different formats we'll just discard until the expected sample */
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = num_sample;
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include "coding.h"
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
#define VORBIS_DEFAULT_BUFFER_SIZE 0x8000 /* should be at least the size of the setup header, ~0x2000 */
|
||||
|
||||
static void pcm_convert_float_to_16(sample_t* outbuf, int samples_to_do, float** pcm, int channels);
|
||||
|
||||
/**
|
||||
* Inits a vorbis stream of some custom variety.
|
||||
*
|
||||
* Normally Vorbis packets are stored in .ogg, which is divided into OggS pages/packets, and the first packets contain necessary
|
||||
* Vorbis setup. For custom vorbis the OggS layer is replaced/optimized, the setup can be modified or stored elsewhere
|
||||
* (i.e.- in the .exe) and raw Vorbis packets may be modified as well, presumably to shave off some kb and/or obfuscate.
|
||||
* We'll manually read/modify the data and decode it with libvorbis calls.
|
||||
*
|
||||
* Reference: https://www.xiph.org/vorbis/doc/libvorbis/overview.html
|
||||
*/
|
||||
vorbis_custom_codec_data* init_vorbis_custom(STREAMFILE* sf, off_t start_offset, vorbis_custom_t type, vorbis_custom_config* config) {
|
||||
vorbis_custom_codec_data* data = NULL;
|
||||
int ok;
|
||||
|
||||
/* init stuff */
|
||||
data = calloc(1,sizeof(vorbis_custom_codec_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->buffer_size = VORBIS_DEFAULT_BUFFER_SIZE;
|
||||
data->buffer = calloc(sizeof(uint8_t), data->buffer_size);
|
||||
if (!data->buffer) goto fail;
|
||||
|
||||
/* keep around to decode too */
|
||||
data->type = type;
|
||||
memcpy(&data->config, config, sizeof(vorbis_custom_config));
|
||||
|
||||
|
||||
/* init vorbis stream state, using 3 fake Ogg setup packets (info, comments, setup/codebooks)
|
||||
* libvorbis expects parsed Ogg pages, but we'll fake them with our raw data instead */
|
||||
vorbis_info_init(&data->vi);
|
||||
vorbis_comment_init(&data->vc);
|
||||
|
||||
data->op.packet = data->buffer;
|
||||
data->op.b_o_s = 1; /* fake headers start */
|
||||
|
||||
/* init header */
|
||||
switch(data->type) {
|
||||
case VORBIS_FSB: ok = vorbis_custom_setup_init_fsb(sf, start_offset, data); break;
|
||||
case VORBIS_WWISE: ok = vorbis_custom_setup_init_wwise(sf, start_offset, data); break;
|
||||
case VORBIS_OGL: ok = vorbis_custom_setup_init_ogl(sf, start_offset, data); break;
|
||||
case VORBIS_SK: ok = vorbis_custom_setup_init_sk(sf, start_offset, data); break;
|
||||
case VORBIS_VID1: ok = vorbis_custom_setup_init_vid1(sf, start_offset, data); break;
|
||||
default: goto fail;
|
||||
}
|
||||
if(!ok) goto fail;
|
||||
|
||||
data->op.b_o_s = 0; /* end of fake headers */
|
||||
|
||||
/* init vorbis global and block state */
|
||||
if (vorbis_synthesis_init(&data->vd,&data->vi) != 0) goto fail;
|
||||
if (vorbis_block_init(&data->vd,&data->vb) != 0) goto fail;
|
||||
|
||||
|
||||
/* write output */
|
||||
config->data_start_offset = data->config.data_start_offset;
|
||||
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
VGM_LOG("VORBIS: init fail at around 0x%x\n", (uint32_t)start_offset);
|
||||
free_vorbis_custom(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Decodes Vorbis packets into a libvorbis sample buffer, and copies them to outbuf */
|
||||
void decode_vorbis_custom(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do, int channels) {
|
||||
VGMSTREAMCHANNEL *stream = &vgmstream->ch[0];
|
||||
vorbis_custom_codec_data* data = vgmstream->codec_data;
|
||||
size_t stream_size = get_streamfile_size(stream->streamfile);
|
||||
//data->op.packet = data->buffer;/* implicit from init */
|
||||
int samples_done = 0;
|
||||
|
||||
while (samples_done < samples_to_do) {
|
||||
|
||||
/* extra EOF check for edge cases */
|
||||
if (stream->offset >= stream_size) {
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (data->samples_full) { /* read more samples */
|
||||
int samples_to_get;
|
||||
float **pcm;
|
||||
|
||||
/* get PCM samples from libvorbis buffers */
|
||||
samples_to_get = vorbis_synthesis_pcmout(&data->vd, &pcm);
|
||||
if (!samples_to_get) {
|
||||
data->samples_full = 0; /* request more if empty*/
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data->samples_to_discard) {
|
||||
/* discard samples for looping */
|
||||
if (samples_to_get > data->samples_to_discard)
|
||||
samples_to_get = data->samples_to_discard;
|
||||
data->samples_to_discard -= samples_to_get;
|
||||
}
|
||||
else {
|
||||
/* get max samples and convert from Vorbis float pcm to 16bit pcm */
|
||||
if (samples_to_get > samples_to_do - samples_done)
|
||||
samples_to_get = samples_to_do - samples_done;
|
||||
pcm_convert_float_to_16(outbuf + samples_done * channels, samples_to_get, pcm, data->vi.channels);
|
||||
samples_done += samples_to_get;
|
||||
}
|
||||
|
||||
/* mark consumed samples from the buffer
|
||||
* (non-consumed samples are returned in next vorbis_synthesis_pcmout calls) */
|
||||
vorbis_synthesis_read(&data->vd, samples_to_get);
|
||||
}
|
||||
else { /* read more data */
|
||||
int ok, rc;
|
||||
|
||||
/* not actually needed, but feels nicer */
|
||||
data->op.granulepos += samples_to_do; /* can be changed next if desired */
|
||||
data->op.packetno++;
|
||||
|
||||
/* read/transform data into the ogg_packet buffer and advance offsets */
|
||||
switch(data->type) {
|
||||
case VORBIS_FSB: ok = vorbis_custom_parse_packet_fsb(stream, data); break;
|
||||
case VORBIS_WWISE: ok = vorbis_custom_parse_packet_wwise(stream, data); break;
|
||||
case VORBIS_OGL: ok = vorbis_custom_parse_packet_ogl(stream, data); break;
|
||||
case VORBIS_SK: ok = vorbis_custom_parse_packet_sk(stream, data); break;
|
||||
case VORBIS_VID1: ok = vorbis_custom_parse_packet_vid1(stream, data); break;
|
||||
default: goto decode_fail;
|
||||
}
|
||||
if(!ok) {
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
|
||||
/* parse the fake ogg packet into a logical vorbis block */
|
||||
rc = vorbis_synthesis(&data->vb,&data->op);
|
||||
if (rc == OV_ENOTAUDIO) {
|
||||
VGM_LOG("Vorbis: not an audio packet (size=0x%x) @ %x\n",(size_t)data->op.bytes,(uint32_t)stream->offset);
|
||||
//VGM_LOGB(data->op.packet, (size_t)data->op.bytes,0);
|
||||
continue; /* rarely happens, seems ok? */
|
||||
} else if (rc != 0) goto decode_fail;
|
||||
|
||||
/* finally decode the logical block into samples */
|
||||
rc = vorbis_synthesis_blockin(&data->vd,&data->vb);
|
||||
if (rc != 0) goto decode_fail; /* ? */
|
||||
|
||||
|
||||
data->samples_full = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
decode_fail:
|
||||
/* on error just put some 0 samples */
|
||||
VGM_LOG("VORBIS: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * channels * sizeof(sample));
|
||||
}
|
||||
|
||||
/* converts from internal Vorbis format to standard PCM (mostly from Xiph's decoder_example.c) */
|
||||
static void pcm_convert_float_to_16(sample_t* outbuf, int samples_to_do, float** pcm, int channels) {
|
||||
int ch, s;
|
||||
sample_t* ptr;
|
||||
float* channel;
|
||||
|
||||
/* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc)
|
||||
* to 16 bit signed PCM ints (host order) and interleave + fix clipping */
|
||||
for (ch = 0; ch < channels; ch++) {
|
||||
/* channels should be in standard order unlike Ogg Vorbis (at least in FSB) */
|
||||
ptr = outbuf + ch;
|
||||
channel = pcm[ch];
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
int val = (int)floor(channel[s] * 32767.0f + 0.5f);
|
||||
if (val > 32767) val = 32767;
|
||||
else if (val < -32768) val = -32768;
|
||||
|
||||
*ptr = val;
|
||||
ptr += channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ********************************************** */
|
||||
|
||||
void free_vorbis_custom(vorbis_custom_codec_data* data) {
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
/* internal decoder cleanup */
|
||||
vorbis_block_clear(&data->vb);
|
||||
vorbis_dsp_clear(&data->vd);
|
||||
vorbis_comment_clear(&data->vc);
|
||||
vorbis_info_clear(&data->vi);
|
||||
|
||||
free(data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void reset_vorbis_custom(VGMSTREAM* vgmstream) {
|
||||
vorbis_custom_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = 0;
|
||||
}
|
||||
|
||||
void seek_vorbis_custom(VGMSTREAM* vgmstream, int32_t num_sample) {
|
||||
vorbis_custom_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
/* Seeking is provided by the Ogg layer, so with custom vorbis we'd need seek tables instead.
|
||||
* To avoid having to parse different formats we'll just discard until the expected sample */
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = num_sample;
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,54 +1,54 @@
|
||||
#ifndef _VORBIS_CUSTOM_DECODER_H_
|
||||
#define _VORBIS_CUSTOM_DECODER_H_
|
||||
|
||||
#include "../vgmstream.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* used by vorbis_custom_decoder.c, but scattered in other .c files */
|
||||
#ifdef VGM_USE_VORBIS
|
||||
|
||||
/* custom Vorbis without Ogg layer */
|
||||
struct vorbis_custom_codec_data {
|
||||
vorbis_info vi; /* stream settings */
|
||||
vorbis_comment vc; /* stream comments */
|
||||
vorbis_dsp_state vd; /* decoder global state */
|
||||
vorbis_block vb; /* decoder local state */
|
||||
ogg_packet op; /* fake packet for internal use */
|
||||
|
||||
uint8_t * buffer; /* internal raw data buffer */
|
||||
size_t buffer_size;
|
||||
|
||||
size_t samples_to_discard; /* for looping purposes */
|
||||
int samples_full; /* flag, samples available in vorbis buffers */
|
||||
|
||||
vorbis_custom_t type; /* Vorbis subtype */
|
||||
vorbis_custom_config config; /* config depending on the mode */
|
||||
|
||||
/* Wwise Vorbis: saved data to reconstruct modified packets */
|
||||
uint8_t mode_blockflag[64+1]; /* max 6b+1; flags 'n stuff */
|
||||
int mode_bits; /* bits to store mode_number */
|
||||
uint8_t prev_blockflag; /* blockflag in the last decoded packet */
|
||||
/* Ogg-style Vorbis: packet within a page */
|
||||
int current_packet;
|
||||
/* reference for page/blocks */
|
||||
off_t block_offset;
|
||||
size_t block_size;
|
||||
|
||||
int prev_block_samples; /* count for optimization */
|
||||
};
|
||||
|
||||
|
||||
int vorbis_custom_setup_init_fsb(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_wwise(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_ogl(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_sk(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_vid1(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
|
||||
int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_wwise(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
#endif/* VGM_USE_VORBIS */
|
||||
|
||||
#endif/*_VORBIS_CUSTOM_DECODER_H_ */
|
||||
#ifndef _VORBIS_CUSTOM_DECODER_H_
|
||||
#define _VORBIS_CUSTOM_DECODER_H_
|
||||
|
||||
#include "../vgmstream.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* used by vorbis_custom_decoder.c, but scattered in other .c files */
|
||||
#ifdef VGM_USE_VORBIS
|
||||
|
||||
/* custom Vorbis without Ogg layer */
|
||||
struct vorbis_custom_codec_data {
|
||||
vorbis_info vi; /* stream settings */
|
||||
vorbis_comment vc; /* stream comments */
|
||||
vorbis_dsp_state vd; /* decoder global state */
|
||||
vorbis_block vb; /* decoder local state */
|
||||
ogg_packet op; /* fake packet for internal use */
|
||||
|
||||
uint8_t* buffer; /* internal raw data buffer */
|
||||
size_t buffer_size;
|
||||
|
||||
size_t samples_to_discard; /* for looping purposes */
|
||||
int samples_full; /* flag, samples available in vorbis buffers */
|
||||
|
||||
vorbis_custom_t type; /* Vorbis subtype */
|
||||
vorbis_custom_config config; /* config depending on the mode */
|
||||
|
||||
/* Wwise Vorbis: saved data to reconstruct modified packets */
|
||||
uint8_t mode_blockflag[64+1]; /* max 6b+1; flags 'n stuff */
|
||||
int mode_bits; /* bits to store mode_number */
|
||||
uint8_t prev_blockflag; /* blockflag in the last decoded packet */
|
||||
/* Ogg-style Vorbis: packet within a page */
|
||||
int current_packet;
|
||||
/* reference for page/blocks */
|
||||
off_t block_offset;
|
||||
size_t block_size;
|
||||
|
||||
int prev_block_samples; /* count for optimization */
|
||||
};
|
||||
|
||||
|
||||
int vorbis_custom_setup_init_fsb(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_wwise(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_ogl(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_sk(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_setup_init_vid1(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data);
|
||||
|
||||
int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_wwise(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data);
|
||||
#endif/* VGM_USE_VORBIS */
|
||||
|
||||
#endif/*_VORBIS_CUSTOM_DECODER_H_ */
|
||||
|
@ -1,269 +1,269 @@
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
#define FSB_VORBIS_USE_PRECOMPILED_FVS 1 /* if enabled vgmstream weights ~600kb more but doesn't need external .fvs packets */
|
||||
#if FSB_VORBIS_USE_PRECOMPILED_FVS
|
||||
#include "vorbis_custom_data_fsb.h"
|
||||
#endif
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* DEFS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int build_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long);
|
||||
static int build_header_comment(uint8_t * buf, size_t bufsize);
|
||||
static int build_header_setup(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile);
|
||||
|
||||
static int load_fvs_file_single(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile);
|
||||
static int load_fvs_file_multi(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile);
|
||||
static int load_fvs_array(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile);
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* FSB references an external setup packet by the setup_id, and packets have mini headers with the size.
|
||||
*
|
||||
* Format info from python-fsb5 (https://github.com/HearthSim/python-fsb5) and
|
||||
* fsb-vorbis-extractor (https://github.com/tmiasko/fsb-vorbis-extractor).
|
||||
*/
|
||||
int vorbis_custom_setup_init_fsb(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) {
|
||||
vorbis_custom_config cfg = data->config;
|
||||
|
||||
data->op.bytes = build_header_identification(data->buffer, data->buffer_size, cfg.channels, cfg.sample_rate, 256, 2048); /* FSB default block sizes */
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
|
||||
data->op.bytes = build_header_comment(data->buffer, data->buffer_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
|
||||
data->op.bytes = build_header_setup(data->buffer, data->buffer_size, cfg.setup_id, streamFile);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) {
|
||||
size_t bytes;
|
||||
|
||||
/* get next packet size from the FSB 16b header (doesn't count this 16b) */
|
||||
data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile);
|
||||
stream->offset += 2;
|
||||
if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */
|
||||
|
||||
/* read raw block */
|
||||
bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile);
|
||||
stream->offset += data->op.bytes;
|
||||
if (bytes != data->op.bytes) goto fail; /* wrong packet? */
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* INTERNAL HELPERS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int build_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long) {
|
||||
int bytes = 0x1e;
|
||||
uint8_t blocksizes, exp_blocksize_0, exp_blocksize_1;
|
||||
|
||||
if (bytes > bufsize) return 0;
|
||||
|
||||
/* guetto log2 for allowed blocksizes (2-exp), could be improved */
|
||||
switch(blocksize_long) {
|
||||
case 64: exp_blocksize_0 = 6; break;
|
||||
case 128: exp_blocksize_0 = 7; break;
|
||||
case 256: exp_blocksize_0 = 8; break;
|
||||
case 512: exp_blocksize_0 = 9; break;
|
||||
case 1024: exp_blocksize_0 = 10; break;
|
||||
case 2048: exp_blocksize_0 = 11; break;
|
||||
case 4096: exp_blocksize_0 = 12; break;
|
||||
case 8192: exp_blocksize_0 = 13; break;
|
||||
default: return 0;
|
||||
}
|
||||
switch(blocksize_short) {
|
||||
case 64: exp_blocksize_1 = 6; break;
|
||||
case 128: exp_blocksize_1 = 7; break;
|
||||
case 256: exp_blocksize_1 = 8; break;
|
||||
case 512: exp_blocksize_1 = 9; break;
|
||||
case 1024: exp_blocksize_1 = 10; break;
|
||||
case 2048: exp_blocksize_1 = 11; break;
|
||||
case 4096: exp_blocksize_1 = 12; break;
|
||||
case 8192: exp_blocksize_1 = 13; break;
|
||||
default: return 0;
|
||||
}
|
||||
blocksizes = (exp_blocksize_0 << 4) | (exp_blocksize_1);
|
||||
|
||||
put_8bit (buf+0x00, 0x01); /* packet_type (id) */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */
|
||||
put_8bit (buf+0x0b, channels); /* audio_channels */
|
||||
put_32bitLE(buf+0x0c, sample_rate); /* audio_sample_rate */
|
||||
put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */
|
||||
put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */
|
||||
put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */
|
||||
put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */
|
||||
put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int build_header_comment(uint8_t * buf, size_t bufsize) {
|
||||
int bytes = 0x19;
|
||||
|
||||
if (bytes > bufsize) return 0;
|
||||
|
||||
put_8bit (buf+0x00, 0x03); /* packet_type (comments) */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
put_32bitLE(buf+0x07, 0x09); /* vendor_length */
|
||||
memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */
|
||||
put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */
|
||||
put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int build_header_setup(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) {
|
||||
int bytes;
|
||||
|
||||
/* try to locate from the precompiled list */
|
||||
bytes = load_fvs_array(buf, bufsize, setup_id, streamFile);
|
||||
if (bytes)
|
||||
return bytes;
|
||||
|
||||
/* try to load from external files */
|
||||
bytes = load_fvs_file_single(buf, bufsize, setup_id, streamFile);
|
||||
if (bytes)
|
||||
return bytes;
|
||||
|
||||
bytes = load_fvs_file_multi(buf, bufsize, setup_id, streamFile);
|
||||
if (bytes)
|
||||
return bytes;
|
||||
|
||||
/* not found */
|
||||
VGM_LOG("FSB Vorbis: setup_id %08x not found\n", setup_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_fvs_file_single(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) {
|
||||
STREAMFILE * streamFileSetup = NULL;
|
||||
|
||||
{
|
||||
char setupname[PATH_LIMIT];
|
||||
char pathname[PATH_LIMIT];
|
||||
char *path;
|
||||
|
||||
/* read "(dir/).fvs_{setup_id}" */
|
||||
streamFile->get_name(streamFile,pathname,sizeof(pathname));
|
||||
path = strrchr(pathname,DIR_SEPARATOR);
|
||||
if (path)
|
||||
*(path+1) = '\0';
|
||||
else
|
||||
pathname[0] = '\0';
|
||||
|
||||
snprintf(setupname,PATH_LIMIT,"%s.fvs_%08x", pathname, setup_id);
|
||||
streamFileSetup = streamFile->open(streamFile,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (streamFileSetup) {
|
||||
/* file found, get contents into the buffer */
|
||||
size_t bytes = streamFileSetup->get_size(streamFileSetup);
|
||||
if (bytes > bufsize) goto fail;
|
||||
|
||||
if (read_streamfile(buf, 0, bytes, streamFileSetup) != bytes)
|
||||
goto fail;
|
||||
|
||||
streamFileSetup->close(streamFileSetup);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (streamFileSetup) streamFileSetup->close(streamFileSetup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_fvs_file_multi(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) {
|
||||
STREAMFILE * streamFileSetup = NULL;
|
||||
|
||||
{
|
||||
char setupname[PATH_LIMIT];
|
||||
char pathname[PATH_LIMIT];
|
||||
char *path;
|
||||
|
||||
/* read "(dir/).fvs" */
|
||||
streamFile->get_name(streamFile,pathname,sizeof(pathname));
|
||||
path = strrchr(pathname,DIR_SEPARATOR);
|
||||
if (path)
|
||||
*(path+1) = '\0';
|
||||
else
|
||||
pathname[0] = '\0';
|
||||
|
||||
snprintf(setupname,PATH_LIMIT,"%s.fvs", pathname);
|
||||
streamFileSetup = streamFile->open(streamFile,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (streamFileSetup) {
|
||||
/* file found: read mini-header (format by bnnm, feel free to change) and locate FVS */
|
||||
int entries, i;
|
||||
uint32_t offset = 0, size = 0;
|
||||
|
||||
if (read_32bitBE(0x0, streamFileSetup) != 0x56465653) goto fail; /* "VFVS" */
|
||||
entries = read_32bitLE(0x08, streamFileSetup); /* 0x04=v0, 0x0c-0x20: reserved */
|
||||
if (entries <= 0) goto fail;
|
||||
|
||||
for (i=0; i < entries; i++) { /* entry = id, offset, size, reserved */
|
||||
if ((uint32_t)read_32bitLE(0x20 + i*0x10, streamFileSetup) == setup_id) {
|
||||
offset = read_32bitLE(0x24 + i*0x10, streamFileSetup);
|
||||
size = read_32bitLE(0x28 + i*0x10, streamFileSetup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!size || !offset || size > bufsize) goto fail;
|
||||
|
||||
/* read into buf */
|
||||
if (read_streamfile(buf, offset, size, streamFileSetup) != size)
|
||||
goto fail;
|
||||
|
||||
streamFileSetup->close(streamFileSetup);
|
||||
return size;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (streamFileSetup) streamFileSetup->close(streamFileSetup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_fvs_array(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) {
|
||||
#if FSB_VORBIS_USE_PRECOMPILED_FVS
|
||||
int i, list_length;
|
||||
|
||||
list_length = sizeof(fvs_list) / sizeof(fvs_info);
|
||||
for (i=0; i < list_length; i++) {
|
||||
if (fvs_list[i].id == setup_id) {
|
||||
if (fvs_list[i].size > bufsize) goto fail;
|
||||
/* found: copy data as-is */
|
||||
memcpy(buf,fvs_list[i].setup, fvs_list[i].size);
|
||||
return fvs_list[i].size;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
#define FSB_VORBIS_USE_PRECOMPILED_FVS 1 /* if enabled vgmstream weights ~600kb more but doesn't need external .fvs packets */
|
||||
#if FSB_VORBIS_USE_PRECOMPILED_FVS
|
||||
#include "vorbis_custom_data_fsb.h"
|
||||
#endif
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* DEFS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int build_header_identification(uint8_t* buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long);
|
||||
static int build_header_comment(uint8_t* buf, size_t bufsize);
|
||||
static int build_header_setup(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf);
|
||||
|
||||
static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf);
|
||||
static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf);
|
||||
static int load_fvs_array(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf);
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* FSB references an external setup packet by the setup_id, and packets have mini headers with the size.
|
||||
*
|
||||
* Format info from python-fsb5 (https://github.com/HearthSim/python-fsb5) and
|
||||
* fsb-vorbis-extractor (https://github.com/tmiasko/fsb-vorbis-extractor).
|
||||
*/
|
||||
int vorbis_custom_setup_init_fsb(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) {
|
||||
vorbis_custom_config cfg = data->config;
|
||||
|
||||
data->op.bytes = build_header_identification(data->buffer, data->buffer_size, cfg.channels, cfg.sample_rate, 256, 2048); /* FSB default block sizes */
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
|
||||
data->op.bytes = build_header_comment(data->buffer, data->buffer_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
|
||||
data->op.bytes = build_header_setup(data->buffer, data->buffer_size, cfg.setup_id, sf);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) {
|
||||
size_t bytes;
|
||||
|
||||
/* get next packet size from the FSB 16b header (doesn't count this 16b) */
|
||||
data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile);
|
||||
stream->offset += 2;
|
||||
if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */
|
||||
|
||||
/* read raw block */
|
||||
bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile);
|
||||
stream->offset += data->op.bytes;
|
||||
if (bytes != data->op.bytes) goto fail; /* wrong packet? */
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* INTERNAL HELPERS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int build_header_identification(uint8_t* buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long) {
|
||||
int bytes = 0x1e;
|
||||
uint8_t blocksizes, exp_blocksize_0, exp_blocksize_1;
|
||||
|
||||
if (bytes > bufsize) return 0;
|
||||
|
||||
/* guetto log2 for allowed blocksizes (2-exp), could be improved */
|
||||
switch(blocksize_long) {
|
||||
case 64: exp_blocksize_0 = 6; break;
|
||||
case 128: exp_blocksize_0 = 7; break;
|
||||
case 256: exp_blocksize_0 = 8; break;
|
||||
case 512: exp_blocksize_0 = 9; break;
|
||||
case 1024: exp_blocksize_0 = 10; break;
|
||||
case 2048: exp_blocksize_0 = 11; break;
|
||||
case 4096: exp_blocksize_0 = 12; break;
|
||||
case 8192: exp_blocksize_0 = 13; break;
|
||||
default: return 0;
|
||||
}
|
||||
switch(blocksize_short) {
|
||||
case 64: exp_blocksize_1 = 6; break;
|
||||
case 128: exp_blocksize_1 = 7; break;
|
||||
case 256: exp_blocksize_1 = 8; break;
|
||||
case 512: exp_blocksize_1 = 9; break;
|
||||
case 1024: exp_blocksize_1 = 10; break;
|
||||
case 2048: exp_blocksize_1 = 11; break;
|
||||
case 4096: exp_blocksize_1 = 12; break;
|
||||
case 8192: exp_blocksize_1 = 13; break;
|
||||
default: return 0;
|
||||
}
|
||||
blocksizes = (exp_blocksize_0 << 4) | (exp_blocksize_1);
|
||||
|
||||
put_8bit (buf+0x00, 0x01); /* packet_type (id) */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */
|
||||
put_8bit (buf+0x0b, channels); /* audio_channels */
|
||||
put_32bitLE(buf+0x0c, sample_rate); /* audio_sample_rate */
|
||||
put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */
|
||||
put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */
|
||||
put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */
|
||||
put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */
|
||||
put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int build_header_comment(uint8_t* buf, size_t bufsize) {
|
||||
int bytes = 0x19;
|
||||
|
||||
if (bytes > bufsize) return 0;
|
||||
|
||||
put_8bit (buf+0x00, 0x03); /* packet_type (comments) */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
put_32bitLE(buf+0x07, 0x09); /* vendor_length */
|
||||
memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */
|
||||
put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */
|
||||
put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int build_header_setup(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) {
|
||||
int bytes;
|
||||
|
||||
/* try to locate from the precompiled list */
|
||||
bytes = load_fvs_array(buf, bufsize, setup_id, sf);
|
||||
if (bytes)
|
||||
return bytes;
|
||||
|
||||
/* try to load from external files */
|
||||
bytes = load_fvs_file_single(buf, bufsize, setup_id, sf);
|
||||
if (bytes)
|
||||
return bytes;
|
||||
|
||||
bytes = load_fvs_file_multi(buf, bufsize, setup_id, sf);
|
||||
if (bytes)
|
||||
return bytes;
|
||||
|
||||
/* not found */
|
||||
VGM_LOG("FSB Vorbis: setup_id %08x not found\n", setup_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) {
|
||||
STREAMFILE* sf_setup = NULL;
|
||||
|
||||
{
|
||||
char setupname[PATH_LIMIT];
|
||||
char pathname[PATH_LIMIT];
|
||||
char *path;
|
||||
|
||||
/* read "(dir/).fvs_{setup_id}" */
|
||||
sf->get_name(sf,pathname,sizeof(pathname));
|
||||
path = strrchr(pathname,DIR_SEPARATOR);
|
||||
if (path)
|
||||
*(path+1) = '\0';
|
||||
else
|
||||
pathname[0] = '\0';
|
||||
|
||||
snprintf(setupname,PATH_LIMIT,"%s.fvs_%08x", pathname, setup_id);
|
||||
sf_setup = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (sf_setup) {
|
||||
/* file found, get contents into the buffer */
|
||||
size_t bytes = sf_setup->get_size(sf_setup);
|
||||
if (bytes > bufsize) goto fail;
|
||||
|
||||
if (read_streamfile(buf, 0, bytes, sf_setup) != bytes)
|
||||
goto fail;
|
||||
|
||||
sf_setup->close(sf_setup);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (sf_setup) sf_setup->close(sf_setup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) {
|
||||
STREAMFILE* sf_setup = NULL;
|
||||
|
||||
{
|
||||
char setupname[PATH_LIMIT];
|
||||
char pathname[PATH_LIMIT];
|
||||
char* path;
|
||||
|
||||
/* read "(dir/).fvs" */
|
||||
sf->get_name(sf,pathname,sizeof(pathname));
|
||||
path = strrchr(pathname,DIR_SEPARATOR);
|
||||
if (path)
|
||||
*(path+1) = '\0';
|
||||
else
|
||||
pathname[0] = '\0';
|
||||
|
||||
snprintf(setupname,PATH_LIMIT,"%s.fvs", pathname);
|
||||
sf_setup = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (sf_setup) {
|
||||
/* file found: read mini-header (format by bnnm, feel free to change) and locate FVS */
|
||||
int entries, i;
|
||||
uint32_t offset = 0, size = 0;
|
||||
|
||||
if (read_32bitBE(0x0, sf_setup) != 0x56465653) goto fail; /* "VFVS" */
|
||||
entries = read_32bitLE(0x08, sf_setup); /* 0x04=v0, 0x0c-0x20: reserved */
|
||||
if (entries <= 0) goto fail;
|
||||
|
||||
for (i=0; i < entries; i++) { /* entry = id, offset, size, reserved */
|
||||
if ((uint32_t)read_32bitLE(0x20 + i*0x10, sf_setup) == setup_id) {
|
||||
offset = read_32bitLE(0x24 + i*0x10, sf_setup);
|
||||
size = read_32bitLE(0x28 + i*0x10, sf_setup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!size || !offset || size > bufsize) goto fail;
|
||||
|
||||
/* read into buf */
|
||||
if (read_streamfile(buf, offset, size, sf_setup) != size)
|
||||
goto fail;
|
||||
|
||||
sf_setup->close(sf_setup);
|
||||
return size;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (sf_setup) sf_setup->close(sf_setup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_fvs_array(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) {
|
||||
#if FSB_VORBIS_USE_PRECOMPILED_FVS
|
||||
int i, list_length;
|
||||
|
||||
list_length = sizeof(fvs_list) / sizeof(fvs_info);
|
||||
for (i=0; i < list_length; i++) {
|
||||
if (fvs_list[i].id == setup_id) {
|
||||
if (fvs_list[i].size > bufsize) goto fail;
|
||||
/* found: copy data as-is */
|
||||
memcpy(buf,fvs_list[i].setup, fvs_list[i].size);
|
||||
return fvs_list[i].size;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,71 +1,71 @@
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* OGL removes the Ogg layer and uses 16b packet headers, that have the size of the next packet, but
|
||||
* the lower 2b need to be removed (usually 00 but 01 for the id packet, not sure about the meaning).
|
||||
*/
|
||||
int vorbis_custom_setup_init_ogl(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) {
|
||||
off_t offset = start_offset;
|
||||
size_t packet_size;
|
||||
|
||||
/* read 3 packets with triad (id/comment/setup), each with an OGL header */
|
||||
|
||||
/* normal identificacion packet */
|
||||
packet_size = (uint16_t)read_16bitLE(offset, streamFile) >> 2;
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, streamFile);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
offset += 2+packet_size;
|
||||
|
||||
/* normal comment packet */
|
||||
packet_size = (uint16_t)read_16bitLE(offset, streamFile) >> 2;
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, streamFile);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
offset += 2+packet_size;
|
||||
|
||||
/* normal setup packet */
|
||||
packet_size = (uint16_t)read_16bitLE(offset, streamFile) >> 2;
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, streamFile);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
offset += 2+packet_size;
|
||||
|
||||
/* data starts after triad */
|
||||
data->config.data_start_offset = offset;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) {
|
||||
size_t bytes;
|
||||
|
||||
/* get next packet size from the OGL 16b header (upper 14b) */
|
||||
data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile) >> 2;
|
||||
stream->offset += 2;
|
||||
if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */
|
||||
|
||||
/* read raw block */
|
||||
bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile);
|
||||
stream->offset += data->op.bytes;
|
||||
if (bytes != data->op.bytes) goto fail; /* wrong packet? */
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* OGL removes the Ogg layer and uses 16b packet headers, that have the size of the next packet, but
|
||||
* the lower 2b need to be removed (usually 00 but 01 for the id packet, not sure about the meaning).
|
||||
*/
|
||||
int vorbis_custom_setup_init_ogl(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) {
|
||||
off_t offset = start_offset;
|
||||
size_t packet_size;
|
||||
|
||||
/* read 3 packets with triad (id/comment/setup), each with an OGL header */
|
||||
|
||||
/* normal identificacion packet */
|
||||
packet_size = (uint16_t)read_16bitLE(offset, sf) >> 2;
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, sf);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
offset += 2+packet_size;
|
||||
|
||||
/* normal comment packet */
|
||||
packet_size = (uint16_t)read_16bitLE(offset, sf) >> 2;
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, sf);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
offset += 2+packet_size;
|
||||
|
||||
/* normal setup packet */
|
||||
packet_size = (uint16_t)read_16bitLE(offset, sf) >> 2;
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, sf);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
offset += 2+packet_size;
|
||||
|
||||
/* data starts after triad */
|
||||
data->config.data_start_offset = offset;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) {
|
||||
size_t bytes;
|
||||
|
||||
/* get next packet size from the OGL 16b header (upper 14b) */
|
||||
data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile) >> 2;
|
||||
stream->offset += 2;
|
||||
if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */
|
||||
|
||||
/* read raw block */
|
||||
bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile);
|
||||
stream->offset += data->op.bytes;
|
||||
if (bytes != data->op.bytes) goto fail; /* wrong packet? */
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,187 +1,187 @@
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* DEFS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int get_page_info(STREAMFILE *streamFile, off_t page_offset, off_t *out_packet_offset, size_t *out_packet_size, int *out_page_packets, int target_packet);
|
||||
static int build_header(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile, off_t packet_offset, size_t packet_size);
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* SK just replaces the id 0x4F676753 ("OggS") by 0x11534B10 (\11"SK"\10), and the word "vorbis" by "SK"
|
||||
* in init packets (for obfuscation, surely). So essentially we are parsing regular Ogg here.
|
||||
*
|
||||
* A simpler way to implement this would be in ogg_vorbis_file with read callbacks (pretend this is proof of concept).
|
||||
*/
|
||||
int vorbis_custom_setup_init_sk(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) {
|
||||
off_t offset = start_offset;
|
||||
off_t id_offset = 0, comment_offset = 0, setup_offset = 0;
|
||||
size_t id_size = 0, comment_size = 0, setup_size = 0;
|
||||
int page_packets;
|
||||
|
||||
/* rebuild header packets, they are standard except the "vorbis" keyword is replaced by "SK" */
|
||||
|
||||
/* first page has the id packet */
|
||||
if (!get_page_info(streamFile, offset, &id_offset, &id_size, &page_packets, 0)) goto fail;
|
||||
if (page_packets != 1) goto fail;
|
||||
offset = id_offset + id_size;
|
||||
|
||||
/* second page has the comment and setup packets */
|
||||
if (!get_page_info(streamFile, offset, &comment_offset, &comment_size, &page_packets, 0)) goto fail;
|
||||
if (page_packets != 2) goto fail;
|
||||
if (!get_page_info(streamFile, offset, &setup_offset, &setup_size, &page_packets, 1)) goto fail;
|
||||
if (page_packets != 2) goto fail;
|
||||
offset = comment_offset + comment_size + setup_size;
|
||||
|
||||
|
||||
/* init with all offsets found */
|
||||
data->op.bytes = build_header(data->buffer, data->buffer_size, streamFile, id_offset, id_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
|
||||
data->op.bytes = build_header(data->buffer, data->buffer_size, streamFile, comment_offset, comment_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
|
||||
data->op.bytes = build_header(data->buffer, data->buffer_size, streamFile, setup_offset, setup_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
|
||||
/* data starts after triad */
|
||||
data->config.data_start_offset = offset;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) {
|
||||
off_t packet_offset = 0;
|
||||
size_t packet_size = 0;
|
||||
int page_packets;
|
||||
int res;
|
||||
|
||||
/* read OggS/SK page and get current packet */
|
||||
res = get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, data->current_packet);
|
||||
data->current_packet++;
|
||||
if (!res || packet_size > data->buffer_size) goto fail;
|
||||
|
||||
/* read raw block */
|
||||
data->op.bytes = read_streamfile(data->buffer, packet_offset, packet_size, stream->streamfile);
|
||||
if (data->op.bytes != packet_size) goto fail; /* wrong packet? */
|
||||
|
||||
/* go next page when processed all packets in page */
|
||||
if (data->current_packet >= page_packets) {
|
||||
if (!get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, -1)) goto fail;
|
||||
stream->offset = packet_offset + packet_size;
|
||||
data->current_packet = 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* INTERNAL HELPERS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* Get packet info from an Ogg page, from segment/packet N (-1 = all segments)
|
||||
*
|
||||
* Page format:
|
||||
* 0x00(4): capture pattern ("OggS")
|
||||
* 0x01(1): stream structure version
|
||||
* 0x05(1): header type flag
|
||||
* 0x06(8): absolute granule position
|
||||
* 0x0e(4): stream serial number
|
||||
* 0x12(4): page sequence number
|
||||
* 0x16(4): page checksum
|
||||
* 0x1a(1): page segments (total bytes in segment table)
|
||||
* 0x1b(n): segment table (N bytes, 1 packet is sum of sizes until != 0xFF)
|
||||
* 0x--(n): data
|
||||
* Reference: https://xiph.org/ogg/doc/framing.html
|
||||
*/
|
||||
static int get_page_info(STREAMFILE *streamFile, off_t page_offset, off_t *out_packet_offset, size_t *out_packet_size, int *out_page_packets, int target_packet) {
|
||||
off_t table_offset, current_packet_offset, target_packet_offset = 0;
|
||||
size_t total_packets_size = 0, current_packet_size = 0, target_packet_size = 0;
|
||||
int page_packets = 0;
|
||||
uint8_t segments;
|
||||
int i;
|
||||
|
||||
|
||||
if (read_32bitBE(page_offset+0x00, streamFile) != 0x11534B10) /* \11"SK"\10 */
|
||||
goto fail; /* not a valid page */
|
||||
/* No point on validating other stuff, but they look legal enough (CRC too it seems) */
|
||||
|
||||
segments = (uint8_t)read_8bit(page_offset+0x1a, streamFile);
|
||||
|
||||
table_offset = page_offset + 0x1b;
|
||||
current_packet_offset = page_offset + 0x1b + segments; /* first packet starts after segments */
|
||||
|
||||
/* process segments */
|
||||
for (i = 0; i < segments; i++) {
|
||||
uint8_t segment_size = (uint8_t)read_8bit(table_offset, streamFile);
|
||||
total_packets_size += segment_size;
|
||||
current_packet_size += segment_size;
|
||||
table_offset += 0x01;
|
||||
|
||||
if (segment_size != 0xFF) { /* packet complete */
|
||||
page_packets++;
|
||||
|
||||
if (target_packet+1 == page_packets) {
|
||||
target_packet_offset = current_packet_offset;
|
||||
target_packet_size = current_packet_size;
|
||||
}
|
||||
|
||||
/* keep reading to fill page_packets */
|
||||
current_packet_offset += current_packet_size; /* move to next packet */
|
||||
current_packet_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* < 0 is accepted and returns first offset and all packets sizes */
|
||||
if (target_packet+1 > page_packets) goto fail;
|
||||
if (target_packet < 0) {
|
||||
target_packet_offset = page_offset + 0x1b + segments; /* first */
|
||||
target_packet_size = total_packets_size;
|
||||
}
|
||||
|
||||
if (out_packet_offset) *out_packet_offset = target_packet_offset;
|
||||
if (out_packet_size) *out_packet_size = target_packet_size;
|
||||
if (out_page_packets) *out_page_packets = page_packets;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
//VGM_LOG("SK Vorbis: failed to read page @ 0x%08lx\n", page_offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* rebuild a "SK" header packet to a "vorbis" one */
|
||||
static int build_header(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile, off_t packet_offset, size_t packet_size) {
|
||||
int bytes;
|
||||
|
||||
if (0x07+packet_size-0x03 > bufsize) return 0;
|
||||
|
||||
put_8bit (buf+0x00, read_8bit(packet_offset,streamFile)); /* packet_type */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
bytes = read_streamfile(buf+0x07,packet_offset+0x03, packet_size-0x03,streamFile); /* copy rest (all except id+"SK") */
|
||||
if (packet_size-0x03 != bytes)
|
||||
return 0;
|
||||
|
||||
return 0x07+packet_size-0x03;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* DEFS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int get_page_info(STREAMFILE* sf, off_t page_offset, off_t* p_packet_offset, size_t* p_packet_size, int* p_page_packets, int target_packet);
|
||||
static int build_header(uint8_t* buf, size_t bufsize, STREAMFILE* sf, off_t packet_offset, size_t packet_size);
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* SK just replaces the id 0x4F676753 ("OggS") by 0x11534B10 (\11"SK"\10), and the word "vorbis" by "SK"
|
||||
* in init packets (for obfuscation, surely). So essentially we are parsing regular Ogg here.
|
||||
*
|
||||
* A simpler way to implement this would be in ogg_vorbis_file with read callbacks (pretend this is proof of concept).
|
||||
*/
|
||||
int vorbis_custom_setup_init_sk(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) {
|
||||
off_t offset = start_offset;
|
||||
off_t id_offset = 0, comment_offset = 0, setup_offset = 0;
|
||||
size_t id_size = 0, comment_size = 0, setup_size = 0;
|
||||
int page_packets;
|
||||
|
||||
/* rebuild header packets, they are standard except the "vorbis" keyword is replaced by "SK" */
|
||||
|
||||
/* first page has the id packet */
|
||||
if (!get_page_info(sf, offset, &id_offset, &id_size, &page_packets, 0)) goto fail;
|
||||
if (page_packets != 1) goto fail;
|
||||
offset = id_offset + id_size;
|
||||
|
||||
/* second page has the comment and setup packets */
|
||||
if (!get_page_info(sf, offset, &comment_offset, &comment_size, &page_packets, 0)) goto fail;
|
||||
if (page_packets != 2) goto fail;
|
||||
if (!get_page_info(sf, offset, &setup_offset, &setup_size, &page_packets, 1)) goto fail;
|
||||
if (page_packets != 2) goto fail;
|
||||
offset = comment_offset + comment_size + setup_size;
|
||||
|
||||
|
||||
/* init with all offsets found */
|
||||
data->op.bytes = build_header(data->buffer, data->buffer_size, sf, id_offset, id_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
|
||||
data->op.bytes = build_header(data->buffer, data->buffer_size, sf, comment_offset, comment_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
|
||||
data->op.bytes = build_header(data->buffer, data->buffer_size, sf, setup_offset, setup_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
|
||||
/* data starts after triad */
|
||||
data->config.data_start_offset = offset;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) {
|
||||
off_t packet_offset = 0;
|
||||
size_t packet_size = 0;
|
||||
int page_packets;
|
||||
int res;
|
||||
|
||||
/* read OggS/SK page and get current packet */
|
||||
res = get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, data->current_packet);
|
||||
data->current_packet++;
|
||||
if (!res || packet_size > data->buffer_size) goto fail;
|
||||
|
||||
/* read raw block */
|
||||
data->op.bytes = read_streamfile(data->buffer, packet_offset, packet_size, stream->streamfile);
|
||||
if (data->op.bytes != packet_size) goto fail; /* wrong packet? */
|
||||
|
||||
/* go next page when processed all packets in page */
|
||||
if (data->current_packet >= page_packets) {
|
||||
if (!get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, -1)) goto fail;
|
||||
stream->offset = packet_offset + packet_size;
|
||||
data->current_packet = 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* INTERNAL HELPERS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/**
|
||||
* Get packet info from an Ogg page, from segment/packet N (-1 = all segments)
|
||||
*
|
||||
* Page format:
|
||||
* 0x00(4): capture pattern ("OggS")
|
||||
* 0x01(1): stream structure version
|
||||
* 0x05(1): header type flag
|
||||
* 0x06(8): absolute granule position
|
||||
* 0x0e(4): stream serial number
|
||||
* 0x12(4): page sequence number
|
||||
* 0x16(4): page checksum
|
||||
* 0x1a(1): page segments (total bytes in segment table)
|
||||
* 0x1b(n): segment table (N bytes, 1 packet is sum of sizes until != 0xFF)
|
||||
* 0x--(n): data
|
||||
* Reference: https://xiph.org/ogg/doc/framing.html
|
||||
*/
|
||||
static int get_page_info(STREAMFILE* sf, off_t page_offset, off_t* p_packet_offset, size_t* p_packet_size, int* p_page_packets, int target_packet) {
|
||||
off_t table_offset, current_packet_offset, target_packet_offset = 0;
|
||||
size_t total_packets_size = 0, current_packet_size = 0, target_packet_size = 0;
|
||||
int page_packets = 0;
|
||||
uint8_t segments;
|
||||
int i;
|
||||
|
||||
|
||||
if (read_32bitBE(page_offset+0x00, sf) != 0x11534B10) /* \11"SK"\10 */
|
||||
goto fail; /* not a valid page */
|
||||
/* No point on validating other stuff, but they look legal enough (CRC too it seems) */
|
||||
|
||||
segments = (uint8_t)read_8bit(page_offset+0x1a, sf);
|
||||
|
||||
table_offset = page_offset + 0x1b;
|
||||
current_packet_offset = page_offset + 0x1b + segments; /* first packet starts after segments */
|
||||
|
||||
/* process segments */
|
||||
for (i = 0; i < segments; i++) {
|
||||
uint8_t segment_size = (uint8_t)read_8bit(table_offset, sf);
|
||||
total_packets_size += segment_size;
|
||||
current_packet_size += segment_size;
|
||||
table_offset += 0x01;
|
||||
|
||||
if (segment_size != 0xFF) { /* packet complete */
|
||||
page_packets++;
|
||||
|
||||
if (target_packet+1 == page_packets) {
|
||||
target_packet_offset = current_packet_offset;
|
||||
target_packet_size = current_packet_size;
|
||||
}
|
||||
|
||||
/* keep reading to fill page_packets */
|
||||
current_packet_offset += current_packet_size; /* move to next packet */
|
||||
current_packet_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* < 0 is accepted and returns first offset and all packets sizes */
|
||||
if (target_packet+1 > page_packets) goto fail;
|
||||
if (target_packet < 0) {
|
||||
target_packet_offset = page_offset + 0x1b + segments; /* first */
|
||||
target_packet_size = total_packets_size;
|
||||
}
|
||||
|
||||
if (p_packet_offset) *p_packet_offset = target_packet_offset;
|
||||
if (p_packet_size) *p_packet_size = target_packet_size;
|
||||
if (p_page_packets) *p_page_packets = page_packets;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
//VGM_LOG("SK Vorbis: failed to read page @ 0x%08lx\n", page_offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* rebuild a "SK" header packet to a "vorbis" one */
|
||||
static int build_header(uint8_t* buf, size_t bufsize, STREAMFILE* sf, off_t packet_offset, size_t packet_size) {
|
||||
int bytes;
|
||||
|
||||
if (0x07+packet_size-0x03 > bufsize) return 0;
|
||||
|
||||
put_8bit (buf+0x00, read_8bit(packet_offset,sf)); /* packet_type */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
bytes = read_streamfile(buf+0x07,packet_offset+0x03, packet_size-0x03,sf); /* copy rest (all except id+"SK") */
|
||||
if (packet_size-0x03 != bytes)
|
||||
return 0;
|
||||
|
||||
return 0x07+packet_size-0x03;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,8 @@
|
||||
#include "vorbis_custom_decoder.h"
|
||||
|
||||
#define BITSTREAM_READ_ONLY /* config */
|
||||
#include "vorbis_bitreader.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
@ -8,8 +11,8 @@
|
||||
/* DEFS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int get_packet_header(STREAMFILE *streamFile, off_t *offset, size_t *size);
|
||||
static int build_header_comment(uint8_t * buf, size_t bufsize);
|
||||
static int get_packet_header(STREAMFILE* sf, off_t* offset, size_t* size);
|
||||
static int build_header_comment(uint8_t* buf, size_t bufsize);
|
||||
|
||||
|
||||
/* **************************************************************************** */
|
||||
@ -21,16 +24,16 @@ static int build_header_comment(uint8_t * buf, size_t bufsize);
|
||||
*
|
||||
* Info from hcs's vid1_2ogg: https://github.com/hcs64/vgm_ripping/tree/master/demux/vid1_2ogg
|
||||
*/
|
||||
int vorbis_custom_setup_init_vid1(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) {
|
||||
int vorbis_custom_setup_init_vid1(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) {
|
||||
off_t offset = start_offset;
|
||||
size_t packet_size = 0;
|
||||
|
||||
/* read header packets (id/setup), each with an VID1 header */
|
||||
|
||||
/* normal identificacion packet */
|
||||
get_packet_header(streamFile, &offset, &packet_size);
|
||||
get_packet_header(sf, &offset, &packet_size);
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset,packet_size, streamFile);
|
||||
data->op.bytes = read_streamfile(data->buffer,offset,packet_size, sf);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
offset += packet_size;
|
||||
|
||||
@ -40,9 +43,9 @@ int vorbis_custom_setup_init_vid1(STREAMFILE *streamFile, off_t start_offset, vo
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
|
||||
/* normal setup packet */
|
||||
get_packet_header(streamFile, &offset, &packet_size);
|
||||
get_packet_header(sf, &offset, &packet_size);
|
||||
if (packet_size > data->buffer_size) goto fail;
|
||||
data->op.bytes = read_streamfile(data->buffer,offset,packet_size, streamFile);
|
||||
data->op.bytes = read_streamfile(data->buffer,offset,packet_size, sf);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
offset += packet_size;
|
||||
|
||||
@ -53,7 +56,7 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) {
|
||||
int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) {
|
||||
size_t bytes;
|
||||
|
||||
|
||||
@ -100,7 +103,7 @@ fail:
|
||||
/* INTERNAL HELPERS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int build_header_comment(uint8_t * buf, size_t bufsize) {
|
||||
static int build_header_comment(uint8_t* buf, size_t bufsize) {
|
||||
int bytes = 0x19;
|
||||
|
||||
if (bytes > bufsize) return 0;
|
||||
@ -116,26 +119,24 @@ static int build_header_comment(uint8_t * buf, size_t bufsize) {
|
||||
}
|
||||
|
||||
/* read header in Vorbis bitpacking format */
|
||||
static int get_packet_header(STREAMFILE *streamFile, off_t *offset, size_t *size) {
|
||||
static int get_packet_header(STREAMFILE* sf, off_t* offset, size_t* size) {
|
||||
uint8_t ibuf[0x04]; /* header buffer */
|
||||
size_t ibufsize = 0x04; /* header ~max */
|
||||
vgm_bitstream ib = {0};
|
||||
bitstream_t ib = {0};
|
||||
uint32_t size_bits;
|
||||
|
||||
|
||||
if (read_streamfile(ibuf,(*offset),ibufsize, streamFile) != ibufsize)
|
||||
if (read_streamfile(ibuf,(*offset),ibufsize, sf) != ibufsize)
|
||||
goto fail;
|
||||
ib.buf = ibuf;
|
||||
ib.bufsize = ibufsize;
|
||||
ib.b_off = 0;
|
||||
ib.mode = BITSTREAM_VORBIS;
|
||||
|
||||
init_bitstream(&ib, ibuf, ibufsize);
|
||||
|
||||
/* read using Vorbis weird LSF */
|
||||
r_bits(&ib, 4,&size_bits);
|
||||
r_bits(&ib, (size_bits+1),(uint32_t*)size);
|
||||
rv_bits(&ib, 4,&size_bits);
|
||||
rv_bits(&ib, (size_bits+1),(uint32_t*)size);
|
||||
|
||||
/* special meaning, seen in silent frames */
|
||||
if (size_bits == 0 && *size == 0 && (uint8_t)read_8bit(*offset, streamFile)==0x80) {
|
||||
if (size_bits == 0 && *size == 0 && (uint8_t)read_8bit(*offset, sf) == 0x80) {
|
||||
*size = 0x01;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
16
src/decode.c
16
src/decode.c
@ -1,6 +1,6 @@
|
||||
#include "vgmstream.h"
|
||||
#include "decode.h"
|
||||
//#include "layout/layout.h"
|
||||
#include "layout/layout.h"
|
||||
#include "coding/coding.h"
|
||||
#include "mixing.h"
|
||||
#include "plugins.h"
|
||||
@ -1423,7 +1423,7 @@ int vgmstream_do_loop(VGMSTREAM* vgmstream) {
|
||||
}
|
||||
}
|
||||
|
||||
/* prepare certain codecs' internal state for looping */
|
||||
/* loop codecs */
|
||||
seek_codec(vgmstream);
|
||||
|
||||
/* restore! */
|
||||
@ -1436,6 +1436,18 @@ int vgmstream_do_loop(VGMSTREAM* vgmstream) {
|
||||
vgmstream->next_block_offset = vgmstream->loop_next_block_offset;
|
||||
//vgmstream->pstate = vgmstream->lstate; /* play state is applied over loops */
|
||||
|
||||
/* loop layouts (after restore, in case layout needs state manipulations) */
|
||||
switch(vgmstream->layout_type) {
|
||||
case layout_segmented:
|
||||
loop_layout_segmented(vgmstream, vgmstream->loop_current_sample);
|
||||
break;
|
||||
case layout_layered:
|
||||
loop_layout_layered(vgmstream, vgmstream->loop_current_sample);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 1; /* looped */
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,11 @@ if HAVE_VORBISFILE
|
||||
AM_CFLAGS += -DVGM_USE_VORBIS
|
||||
endif
|
||||
endif
|
||||
|
||||
if HAVE_LIBMPG123
|
||||
AM_CFLAGS += -DVGM_USE_MPEG
|
||||
endif
|
||||
|
||||
if HAVE_FFMPEG
|
||||
AM_CFLAGS += -DVGM_USE_FFMPEG
|
||||
endif
|
||||
|
@ -9,54 +9,34 @@
|
||||
|
||||
|
||||
/* Decodes samples for layered streams.
|
||||
* Similar to interleave layout, but decodec samples are mixed from complete vgmstreams, each
|
||||
* with custom codecs and different number of channels, creating a single super-vgmstream.
|
||||
* Similar to flat layout, but decoded vgmstream are mixed into a final buffer, each vgmstream
|
||||
* may have different codecs and number of channels, creating a single super-vgmstream.
|
||||
* Usually combined with custom streamfiles to handle data interleaved in weird ways. */
|
||||
void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
int samples_written = 0, loop_samples_skip = 0;
|
||||
int samples_written = 0;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
int samples_per_frame, samples_this_block;
|
||||
|
||||
samples_per_frame = VGMSTREAM_LAYER_SAMPLE_BUFFER;
|
||||
samples_this_block = vgmstream->num_samples; /* do all samples if possible */
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
int samples_to_do;
|
||||
int samples_this_block = VGMSTREAM_LAYER_SAMPLE_BUFFER;
|
||||
int layer, ch = 0;
|
||||
int layer, ch;
|
||||
|
||||
|
||||
if (data->external_looping) {
|
||||
/* normally each layer handles its own looping internally, except when using config
|
||||
* were each layer is treated as a solid part, so loop is applied externally */
|
||||
|
||||
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
reset_vgmstream(data->layers[layer]);
|
||||
//todo per-layer seeking instead of layout looping
|
||||
}
|
||||
|
||||
loop_samples_skip = vgmstream->loop_start_sample;
|
||||
vgmstream->current_sample = 0;
|
||||
vgmstream->samples_into_block = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_to_do = get_vgmstream_samples_to_do(vgmstream->num_samples, samples_this_block, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
|
||||
/* looping: discard until actual start */
|
||||
if (loop_samples_skip > 0) {
|
||||
if (samples_to_do > loop_samples_skip)
|
||||
samples_to_do = loop_samples_skip;
|
||||
}
|
||||
}
|
||||
else {
|
||||
samples_to_do = samples_this_block;
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
|
||||
/* handle looping (loop_layout has been called below) */
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_to_do = get_vgmstream_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
|
||||
|
||||
/* decode all layers */
|
||||
ch = 0;
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
int s, layer_ch, layer_channels;
|
||||
|
||||
@ -68,10 +48,6 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
||||
samples_to_do,
|
||||
data->layers[layer]);
|
||||
|
||||
if (loop_samples_skip > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* mix layer samples to main samples */
|
||||
for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) {
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
@ -84,25 +60,40 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
||||
}
|
||||
}
|
||||
|
||||
if (loop_samples_skip > 0) {
|
||||
loop_samples_skip -= samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_written += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
int layer;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
if (data->external_looping) {
|
||||
samples_written += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
/* looping is applied over resulting decode, as each layer is its own "solid" block with
|
||||
* config and needs 'external' seeking */
|
||||
seek_vgmstream(data->layers[layer], loop_sample);
|
||||
}
|
||||
else {
|
||||
samples_written += samples_to_do;
|
||||
vgmstream->current_sample = data->layers[0]->current_sample;
|
||||
vgmstream->samples_into_block = 0; /* handled in each layer */
|
||||
vgmstream->loop_count = data->layers[0]->loop_count;
|
||||
/* looping is aplied as internal loops. normally each layer does it automatically, but
|
||||
* just calls do_loop manually to behave a bit more controlled, and so that manual
|
||||
* calls to do_loop work (used in seek_vgmstream) */
|
||||
if (data->layers[layer]->loop_flag) { /* mixing looping and non-looping layers is allowed */
|
||||
data->layers[layer]->current_sample = data->layers[layer]->loop_end_sample; /* forces do loop */
|
||||
vgmstream_do_loop(data->layers[layer]); /* guaranteed to work should loop_layout be called */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* could always call seek_vgmstream, but it's not optimized to loop non-config vgmstreams ATM */
|
||||
|
||||
vgmstream->current_sample = loop_sample;
|
||||
vgmstream->samples_into_block = loop_sample;
|
||||
}
|
||||
|
||||
|
||||
|
@ -59,6 +59,7 @@ segmented_layout_data* init_layout_segmented(int segment_count);
|
||||
int setup_layout_segmented(segmented_layout_data* data);
|
||||
void free_layout_segmented(segmented_layout_data* data);
|
||||
void reset_layout_segmented(segmented_layout_data* data);
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment);
|
||||
|
||||
void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
@ -66,6 +67,7 @@ layered_layout_data* init_layout_layered(int layer_count);
|
||||
int setup_layout_layered(layered_layout_data* data);
|
||||
void free_layout_layered(layered_layout_data* data);
|
||||
void reset_layout_layered(layered_layout_data* data);
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data);
|
||||
|
||||
#endif
|
||||
|
@ -12,11 +12,10 @@
|
||||
* Chains together sequential vgmstreams, for data divided into separate sections or files
|
||||
* (like one part for intro and other for loop segments, which may even use different codecs). */
|
||||
void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
int samples_written = 0, loop_samples_skip = 0;
|
||||
int samples_written = 0, samples_this_block;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
int use_internal_buffer = 0;
|
||||
|
||||
|
||||
/* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */
|
||||
if (vgmstream->channels != data->input_channels) {
|
||||
use_internal_buffer = 1;
|
||||
@ -27,81 +26,49 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
return;
|
||||
}
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
|
||||
//VGM_LOG("segment decode start: cur=%i, this=%i, into=%i\n", data->current_segment, samples_this_block, vgmstream->samples_into_block);
|
||||
while (samples_written < sample_count) {
|
||||
int samples_to_do;
|
||||
int samples_this_segment = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
|
||||
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
|
||||
int segment, loop_segment, total_samples;
|
||||
/* handle looping (loop_layout has been called below, changes segments/state) */
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* handle looping by finding loop segment and loop_start inside that segment */
|
||||
loop_segment = 0;
|
||||
total_samples = 0;
|
||||
while (total_samples < vgmstream->num_samples) {
|
||||
int32_t segment_samples = vgmstream_get_samples(data->segments[loop_segment]);
|
||||
/* detect segment change and restart (after loop, but before decode, to allow looping to kick in) */
|
||||
if (vgmstream->samples_into_block == samples_this_block) {
|
||||
data->current_segment++;
|
||||
|
||||
if (vgmstream->loop_current_sample >= total_samples &&
|
||||
vgmstream->loop_current_sample < total_samples + segment_samples) {
|
||||
loop_samples_skip = vgmstream->loop_current_sample - total_samples;
|
||||
break; /* loop_start falls within loop_segment's samples */
|
||||
}
|
||||
total_samples += segment_samples;
|
||||
loop_segment++;
|
||||
/* could happen on last segment trying to decode more samples */
|
||||
if (data->current_segment >= data->segment_count) {
|
||||
VGM_LOG("SEGMENTED: wrong next segment\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (loop_segment == data->segment_count) {
|
||||
VGM_LOG("SEGMENTED: can't find loop segment\n");
|
||||
loop_segment = 0;
|
||||
}
|
||||
|
||||
data->current_segment = loop_segment;
|
||||
|
||||
/* loops can span multiple segments */
|
||||
for (segment = loop_segment; segment < data->segment_count; segment++) {
|
||||
reset_vgmstream(data->segments[segment]);
|
||||
}
|
||||
/* in case of looping spanning multiple segments */
|
||||
reset_vgmstream(data->segments[data->current_segment]);
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
vgmstream->samples_into_block = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_to_do = get_vgmstream_samples_to_do(samples_this_segment, sample_count, vgmstream);
|
||||
|
||||
samples_to_do = get_vgmstream_samples_to_do(samples_this_block, sample_count, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */
|
||||
samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER;
|
||||
|
||||
/* looping: discard until actual start */
|
||||
if (loop_samples_skip > 0) {
|
||||
if (samples_to_do > loop_samples_skip)
|
||||
samples_to_do = loop_samples_skip;
|
||||
}
|
||||
|
||||
/* detect segment change and restart */
|
||||
if (samples_to_do == 0) {
|
||||
data->current_segment++;
|
||||
/* could happen on last segment trying to decode more samples */
|
||||
if (data->current_segment >= data->segment_count) {
|
||||
break;
|
||||
}
|
||||
reset_vgmstream(data->segments[data->current_segment]);
|
||||
vgmstream->samples_into_block = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
render_vgmstream(
|
||||
use_internal_buffer ?
|
||||
data->buffer :
|
||||
&outbuf[samples_written * data->output_channels],
|
||||
data->buffer : &outbuf[samples_written * data->output_channels],
|
||||
samples_to_do,
|
||||
data->segments[data->current_segment]);
|
||||
|
||||
if (loop_samples_skip > 0) {
|
||||
loop_samples_skip -= samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (use_internal_buffer) {
|
||||
int s;
|
||||
for (s = 0; s < samples_to_do * data->output_channels; s++) {
|
||||
@ -115,6 +82,33 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
}
|
||||
}
|
||||
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
int segment, total_samples;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
segment = 0;
|
||||
total_samples = 0;
|
||||
while (total_samples < vgmstream->num_samples) {
|
||||
int32_t segment_samples = vgmstream_get_samples(data->segments[segment]);
|
||||
|
||||
/* find if loop falls within segment's samples */
|
||||
if (loop_sample >= total_samples && loop_sample < total_samples + segment_samples) {
|
||||
int32_t loop_relative = loop_sample - total_samples;
|
||||
|
||||
seek_vgmstream(data->segments[segment], loop_relative);
|
||||
data->current_segment = segment;
|
||||
vgmstream->samples_into_block = loop_relative;
|
||||
break;
|
||||
}
|
||||
total_samples += segment_samples;
|
||||
segment++;
|
||||
}
|
||||
|
||||
if (segment == data->segment_count) {
|
||||
VGM_LOG("SEGMENTED: can't find loop segment\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
segmented_layout_data* init_layout_segmented(int segment_count) {
|
||||
segmented_layout_data* data = NULL;
|
||||
|
@ -2002,6 +2002,10 @@
|
||||
RelativePath=".\coding\g72x_state.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\mpeg_bitreader.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\mpeg_decoder.h"
|
||||
>
|
||||
@ -2010,6 +2014,10 @@
|
||||
RelativePath=".\coding\nwa_decoder.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\vorbis_bitreader.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\vorbis_custom_decoder.h"
|
||||
>
|
||||
|
@ -84,7 +84,9 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\ext_includes\clHCA.h" />
|
||||
<ClInclude Include="coding\mpeg_bitreader.h" />
|
||||
<ClInclude Include="coding\mpeg_decoder.h" />
|
||||
<ClInclude Include="coding\vorbis_bitreader.h" />
|
||||
<ClInclude Include="coding\vorbis_custom_data_fsb.h" />
|
||||
<ClInclude Include="coding\vorbis_custom_data_wwise.h" />
|
||||
<ClInclude Include="coding\vorbis_custom_decoder.h" />
|
||||
|
@ -242,9 +242,15 @@
|
||||
<ClInclude Include="..\ext_includes\clHCA.h">
|
||||
<Filter>ext_libs\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="coding\mpeg_bitreader.h">
|
||||
<Filter>coding\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="coding\mpeg_decoder.h">
|
||||
<Filter>coding\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="coding\vorbis_bitreader.h">
|
||||
<Filter>coding\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="coding\vorbis_custom_data_fsb.h">
|
||||
<Filter>coding\Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
@ -17,6 +17,11 @@ if HAVE_VORBISFILE
|
||||
AM_CFLAGS += -DVGM_USE_VORBIS
|
||||
endif
|
||||
endif
|
||||
|
||||
if HAVE_LIBMPG123
|
||||
AM_CFLAGS += -DVGM_USE_MPEG
|
||||
endif
|
||||
|
||||
if HAVE_FFMPEG
|
||||
AM_CFLAGS += -DVGM_USE_FFMPEG
|
||||
endif
|
||||
|
@ -129,11 +129,11 @@ static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
|
||||
def->really_force_loop = vcfg->really_force_loop;
|
||||
def->ignore_fade = vcfg->ignore_fade;
|
||||
|
||||
def->loop_count = vcfg->loop_times; //todo loop times
|
||||
def->loop_count = vcfg->loop_count;
|
||||
def->loop_count_set = 1;
|
||||
def->fade_delay = vcfg->fade_delay;
|
||||
def->fade_delay_set = 1;
|
||||
def->fade_time = vcfg->fade_period; //todo loop period
|
||||
def->fade_time = vcfg->fade_time;
|
||||
def->fade_time_set = 1;
|
||||
}
|
||||
|
||||
|
@ -53,9 +53,9 @@ typedef struct {
|
||||
int ignore_fade; /* don't fade after N loops */
|
||||
|
||||
/* song processing */
|
||||
double loop_times; /* target loops */
|
||||
double loop_count; /* target loops */
|
||||
double fade_delay; /* fade delay after target loops */
|
||||
double fade_period; /* fade time after target loops */
|
||||
double fade_time; /* fade period after target loops */
|
||||
|
||||
//int downmix; /* max number of channels allowed (0=disable downmix) */
|
||||
|
||||
|
16
src/render.c
16
src/render.c
@ -513,14 +513,24 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
|
||||
|
||||
|
||||
/* will decode and loop until seek sample, but slower */
|
||||
//todo apply same loop logic as below, or pretend we have play_forever + settings?
|
||||
if (!vgmstream->config_enabled) {
|
||||
//todo same but ignore play duration or play_position
|
||||
//;VGM_LOG("SEEK: simple seek=%i, cur=%i\n", seek_sample, vgmstream->current_sample);
|
||||
if (seek_sample < vgmstream->current_sample) {
|
||||
decode_samples = seek_sample;
|
||||
reset_vgmstream(vgmstream);
|
||||
}
|
||||
else {
|
||||
decode_samples = seek_sample - vgmstream->current_sample;
|
||||
}
|
||||
|
||||
seek_force_decode(vgmstream, decode_samples);
|
||||
return;
|
||||
}
|
||||
|
||||
//todo optimize layout looping with seek_vgmstream
|
||||
//todo could improve performance bit if hit_loop wasn't lost when calling reset
|
||||
//todo wrong seek with ignore fade
|
||||
//todo wrong seek with ignore fade, also for layered layers (pass count to force loop + layers)
|
||||
|
||||
|
||||
/* seeking to requested sample normally means decoding and discarding up to that point (from
|
||||
|
@ -936,8 +936,8 @@ static void apply_config(VGMSTREAM* vgmstream, winamp_settings_t* settings) {
|
||||
|
||||
vcfg.allow_play_forever = 1;
|
||||
vcfg.play_forever = settings->loop_forever;
|
||||
vcfg.loop_times = settings->loop_count;
|
||||
vcfg.fade_period = settings->fade_time;
|
||||
vcfg.loop_count = settings->loop_count;
|
||||
vcfg.fade_time = settings->fade_time;
|
||||
vcfg.fade_delay = settings->fade_delay;
|
||||
vcfg.ignore_loop = settings->ignore_loop;
|
||||
|
||||
|
@ -173,8 +173,8 @@ static void apply_config(VGMSTREAM* vgmstream) {
|
||||
|
||||
vcfg.allow_play_forever = 0;
|
||||
//vcfg.play_forever = loop_forever;
|
||||
vcfg.loop_times = loop_count;
|
||||
vcfg.fade_period = fade_seconds;
|
||||
vcfg.loop_count = loop_count;
|
||||
vcfg.fade_time = fade_seconds;
|
||||
vcfg.fade_delay = fade_delay;
|
||||
vcfg.ignore_loop = ignore_loop;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user