Add mixing/priority/fixes to Audacious

This commit is contained in:
bnnm 2019-10-06 23:31:32 +02:00
parent 421cefbdd7
commit 028ff2f3d0
5 changed files with 371 additions and 273 deletions

View File

@ -93,11 +93,11 @@ automatically. You need to manually refresh it by selecting songs and doing
### Audacious plugin ### Audacious plugin
*Installation*: needs to be manually built. Instructions can be found in the BUILD *Installation*: needs to be manually built. Instructions can be found in the BUILD
document. document in vgmstream's source code.
### vgmstream123 ### vgmstream123
*Installation*: needs to be manually built. Instructions can be found in the *Installation*: needs to be manually built. Instructions can be found in the BUILD
BUILD document in vgmstream's source code. document in vgmstream's source code.
Usage: `vgmstream123 [options] INFILE ...` Usage: `vgmstream123 [options] INFILE ...`
@ -148,15 +148,18 @@ them playable through vgmstream.
- .wav to .lwav (standard WAV) - .wav to .lwav (standard WAV)
- .wma to .lwma (standard WMA) - .wma to .lwma (standard WMA)
- .(any) to .vgmstream (FFmpeg formats or TXTH) - .(any) to .vgmstream (FFmpeg formats or TXTH)
Command line tools don't have this restriction and will accept the original Command line tools don't have this restriction and will accept the original
filename. filename.
The main advantage to rename them is that vgmstream may use the file's The main advantage to rename them is that vgmstream may use the file's
internal loop info, or apply subtle fixes, but is also limited in some ways internal loop info, or apply subtle fixes, but is also limited in some ways
(like standard/player's tagging). (like standard/player's tagging). .vgmstream is a catch-all extension that
may work as a last resort to make a file playable.
.vgmstream is a catch-all extension that may work as a last resort to make Some plugins have options that allow any extension (common or unknown) to be
a file playable. played, making renaming is unnecessary (may need to adjust plugin priority in
player's options).
When extracting from a bigfile, sometimes internal files don't have an actual When extracting from a bigfile, sometimes internal files don't have an actual
name+extension. Those should be renamed to its proper/common extension, as the name+extension. Those should be renamed to its proper/common extension, as the
@ -214,7 +217,7 @@ a companion file:
The key file can be ".(ext)key" (for the whole folder), or "(name).(ext)key" The key file can be ".(ext)key" (for the whole folder), or "(name).(ext)key"
(for a single file). The format is made up to suit vgmstream. (for a single file). The format is made up to suit vgmstream.
### Artificial/generic headers ### Artificial files
In some cases a file only has raw data, while important header info (codec type, In some cases a file only has raw data, while important header info (codec type,
sample rate, channels, etc) is stored in the .exe or other hard to locate places. sample rate, channels, etc) is stored in the .exe or other hard to locate places.
@ -225,18 +228,29 @@ The resulting file must be (name).genh. Contains static header data.
Programs like VGMToolbox can help to create GENH. Programs like VGMToolbox can help to create GENH.
**TXTH**: a text header placed in an external file. The TXTH must be named **TXTH**: a text header placed in an external file. The TXTH must be named
".txth" or ".(ext).txth" (for the whole folder), or "(name.ext).txth" (for a `.txth` or `.(ext).txth` (for the whole folder), or `(name.ext).txth` (for a
single file). Contains dynamic text commands to read data from the original single file). Contains dynamic text commands to read data from the original
file, or static values. file, or static values.
**TXTP**: a text playing configurator. Can contain a list of filenames to *TXTH* is recomended over *GENH* as it's far easier to create and has many
play as one (ex. "intro.vag" "loop.vag"), list of separate channel files more functions.
to join as a single multichannel file, subsong index (ex. bgm.sxd#10),
per-file configurations like number of loops, and many other features.
For files that already play, sometimes they are used by the game in various
complex and non-standard ways, like playing multiple small songs as a single
one, or using some channels as a section of the song. For those cases we
can use create a *TXTP* file.
**TXTP**: a text player configurator named `(name).txtp`. Text inside can
contain a list of filenames to play as one (ex. `intro.vag(line)loop.vag`),
list of separate channel files to join as a single multichannel file,
subsong index (ex. `bgm.sxd#10`), per-file configurations like number of
loops, remove unneeded channels, and many other features.
Creation of those files is meant for advanced users, docs can be found in Creation of those files is meant for advanced users, docs can be found in
vgmstream source. vgmstream source.
### Plugin conflicts ### Plugin conflicts
Since vgmstream supports a huge amount of formats it's possibly that some of Since vgmstream supports a huge amount of formats it's possibly that some of
them are also supported in other plugins, and this sometimes causes conflicts. them are also supported in other plugins, and this sometimes causes conflicts.
@ -244,9 +258,18 @@ If a file that should isn't playing or looping, first make sure vgmstream is
really opening it (should show "VGMSTREAM" somewhere in the file info), and really opening it (should show "VGMSTREAM" somewhere in the file info), and
try to remove a few other plugins. try to remove a few other plugins.
foobar's ffmpeg plugin and foo_adpcm are known to cause issues, but in foobar's FFmpeg plugin and foo_adpcm are known to cause issues, but in
recent versions (1.4.x) you can configure plugin priority. recent versions (1.4.x) you can configure plugin priority.
In Audacious, vgmstream is set with slightly higher priority than FFmpeg,
since it steals many formats that you normally want to loop (like .adx).
However other plugins may set themselves higher, stealing formats instead.
If current Audacious version doesn't let to change plugin priority you may
need to disable some plugins (requires restart) or set priority on compile
time. Particularly, mpg123 plugin may steal formats that aren't even MP3,
making impossible for vgmstream to play it properly.
### Channel issues ### Channel issues
Some games layer a huge number of channels, that are disabled or downmixed Some games layer a huge number of channels, that are disabled or downmixed
during gameplay. The player may be unable to play those files (for example during gameplay. The player may be unable to play those files (for example
@ -275,17 +298,17 @@ filename1
filename2 filename2
``` ```
Accepted tags depend on the player (foobar: any; winamp: see ATF config), Accepted tags depend on the player (foobar: any; winamp: see ATF config),
typically ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc, lower or uppercase, typically *ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc*, lower or uppercase,
separated by one or multiple spaces. Repeated tags overwrite previous separated by one or multiple spaces. Repeated tags overwrite previous
(ex.- may define @COMPOSER for multiple tracks). It only reads up to current (ex.- may define *@COMPOSER* for multiple tracks). It only reads up to current
_filename_ though, so any @TAG below would be ignored. *filename* though, so any *@TAG* below would be ignored.
Playlist formatting should follow player's config. ASCII or UTF-8 tags work. Playlist formatting should follow player's config. ASCII or UTF-8 tags work.
GLOBAL_COMMANDs currently can be: *GLOBAL_COMMAND*s currently can be:
- AUTOTRACK: sets %TRACK% tag automatically (1..N as files are encountered - *AUTOTRACK*: sets *%TRACK* tag automatically (1..N as files are encountered
in the tag file). in the tag file).
- AUTOALBUM: sets %ALBUM% tag automatically using the containing dir as album. - *AUTOALBUM*: sets *%ALBUM* tag automatically using the containing dir as album.
Note that since you can use global tags don't need to put all files inside. Note that since you can use global tags don't need to put all files inside.
This would be a perfectly valid *!tags.m3u*: This would be a perfectly valid *!tags.m3u*:
@ -306,7 +329,7 @@ enabled in preferences):
If your player isn't picking tags make sure vgmstream is detecting the song If your player isn't picking tags make sure vgmstream is detecting the song
(as other plugins can steal its extensions, see above), .m3u is properly (as other plugins can steal its extensions, see above), .m3u is properly
named and that filenames inside match the song filename. For Winamp you need named and that filenames inside match the song filename. For Winamp you need
to make sure options > titles > advanced title formatting checkbox is set and to make sure *options > titles > advanced title formatting* checkbox is set and
the format defined. the format defined.
## Virtual TXTP files ## Virtual TXTP files

View File

@ -16,6 +16,7 @@
extern "C" { extern "C" {
#include "../src/vgmstream.h" #include "../src/vgmstream.h"
#include "../src/plugins.h"
} }
#include "plugin.h" #include "plugin.h"
#include "vfs.h" #include "vfs.h"
@ -31,20 +32,24 @@ extern "C" {
#define CFG_ID "vgmstream" // ID for storing in audacious #define CFG_ID "vgmstream" // ID for storing in audacious
#define MIN_BUFFER_SIZE 576 #define MIN_BUFFER_SIZE 576
/* internal state */ /* global state */
VgmstreamPlugin aud_plugin_instance; /*EXPORT*/ VgmstreamPlugin aud_plugin_instance;
Settings vgmstream_cfg; audacious_settings settings;
VGMSTREAM *vgmstream = NULL; VGMSTREAM *vgmstream = NULL; //todo make local?
const char *const VgmstreamPlugin::defaults[] = { const char *const VgmstreamPlugin::defaults[] = {
"loop_forever", "1", "loop_forever", "FALSE",
"loop_count", "2", "loop_count", "2", //maybe double?
"fade_length", "3", "fade_length", "10.0",
"fade_delay", "3", "fade_delay", "0.0",
"downmix_channels", "8",
"exts_unknown_on", "FALSE",
"exts_common_on", "FALSE",
NULL NULL
}; };
// N_(...) for i18n but not much point here
const char VgmstreamPlugin::about[] = const char VgmstreamPlugin::about[] =
"vgmstream plugin " VERSION " " __DATE__ "\n" "vgmstream plugin " VERSION " " __DATE__ "\n"
"by hcs, FastElbja, manakoAT, bxaimc, snakemeat, soneek, kode54, bnnm and many others\n" "by hcs, FastElbja, manakoAT, bxaimc, snakemeat, soneek, kode54, bnnm and many others\n"
@ -58,70 +63,99 @@ const char VgmstreamPlugin::about[] =
"https://sourceforge.net/projects/vgmstream/ (original)"; "https://sourceforge.net/projects/vgmstream/ (original)";
const PreferencesWidget VgmstreamPlugin::widgets[] = { const PreferencesWidget VgmstreamPlugin::widgets[] = {
WidgetLabel(N_("<b>vgmstream Config</b>")), WidgetLabel(N_("<b>vgmstream config</b>")),
WidgetCheck(N_("Loop Forever:"), WidgetBool(vgmstream_cfg.loop_forever)), WidgetCheck(N_("Loop forever:"), WidgetBool(settings.loop_forever)),
WidgetSpin(N_("Loop Count:"), WidgetInt(vgmstream_cfg.loop_count), {1, 20, 1}), WidgetSpin(N_("Loop count:"), WidgetInt(settings.loop_count), {1, 20, 1}),
WidgetSpin(N_("Fade Length:"), WidgetFloat(vgmstream_cfg.fade_length), {0, 60, 0.1}), WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_length), {0, 60, 0.1}),
WidgetSpin(N_("Fade Delay:"), WidgetFloat(vgmstream_cfg.fade_delay), {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}),
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)
//WidgetCheck(N_("Enable common exts"), WidgetBool(settings.exts_common_on)),
}; };
void vgmstream_cfg_load() { void vgmstream_settings_load() {
debugMessage("cfg_load called"); AUDINFO("load settings\n");
aud_config_set_defaults(CFG_ID, VgmstreamPlugin::defaults); aud_config_set_defaults(CFG_ID, VgmstreamPlugin::defaults);
vgmstream_cfg.loop_forever = aud_get_bool(CFG_ID, "loop_forever"); settings.loop_forever = aud_get_bool(CFG_ID, "loop_forever");
vgmstream_cfg.loop_count = aud_get_int(CFG_ID, "loop_count"); settings.loop_count = aud_get_int(CFG_ID, "loop_count");
vgmstream_cfg.fade_length = aud_get_double(CFG_ID, "fade_length"); settings.fade_length = aud_get_double(CFG_ID, "fade_length");
vgmstream_cfg.fade_delay = aud_get_double(CFG_ID, "fade_delay"); 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");
settings.exts_common_on = aud_get_bool(CFG_ID, "exts_common_on");
} }
void vgmstream_cfg_save() { void vgmstream_settings_save() {
debugMessage("cfg_save called"); AUDINFO("save settings\n");
aud_set_bool(CFG_ID, "loop_forever", vgmstream_cfg.loop_forever); aud_set_bool(CFG_ID, "loop_forever", settings.loop_forever);
aud_set_int(CFG_ID, "loop_count", vgmstream_cfg.loop_count); aud_set_int(CFG_ID, "loop_count", settings.loop_count);
aud_set_double(CFG_ID, "fade_length", vgmstream_cfg.fade_length); aud_set_double(CFG_ID, "fade_length", settings.fade_length);
aud_set_double(CFG_ID, "fade_delay", vgmstream_cfg.fade_delay); 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);
aud_set_bool(CFG_ID, "exts_common_on", settings.exts_common_on);
} }
const PluginPreferences VgmstreamPlugin::prefs = { const PluginPreferences VgmstreamPlugin::prefs = {
{widgets}, vgmstream_cfg_load, vgmstream_cfg_save {widgets}, vgmstream_settings_load, vgmstream_settings_save
}; };
// validate extension // validate extension (thread safe)
bool VgmstreamPlugin::is_our_file(const char *filename, VFSFile &file) { bool VgmstreamPlugin::is_our_file(const char *filename, VFSFile &file) {
const char * ext = strrchr(filename,'.'); AUDDBG("test file=%s\n", filename);
if (ext==NULL)
ext = filename+strlen(filename); /* point to null, i.e. an empty string for the extension */
else
ext = ext+1; /* skip the dot */
size_t ext_list_len = 0; vgmstream_ctx_valid_cfg cfg = {0};
const char ** ext_list = vgmstream_get_formats(&ext_list_len);
for (int i=0; i < ext_list_len; i++) { cfg.accept_unknown = settings.exts_unknown_on;
if (!strcasecmp(ext, ext_list[i])) cfg.accept_common = settings.exts_common_on;
return true; return vgmstream_ctx_is_valid(filename, &cfg) > 0 ? true : false;
}
return false;
} }
// called on startup (main thread)
bool VgmstreamPlugin::init() { bool VgmstreamPlugin::init() {
debugMessage("init"); AUDINFO("plugin start\n");
vgmstream_cfg_load(); vgmstream_settings_load();
debugMessage("after load cfg");
return true; return true;
} }
// called on stop (main thread)
void VgmstreamPlugin::cleanup() { void VgmstreamPlugin::cleanup() {
debugMessage("cleanup"); AUDINFO("plugin end\n");
vgmstream_cfg_save(); vgmstream_settings_save();
} }
// called every time the user adds a new file to playlist #if 0
bool read_data(const char * filename, Tuple & tuple) { static int get_filename_subtune(const char * filename) {
int subtune;
int pos = strstr("?"))
if (pos <= 0)
return -1;
if (sscanf(filename + pos, "%i", &subtune) != 1)
return -1;
return subtune;
}
#endif
// internal helper, called every time user adds a new file to playlist
static bool read_info(const char * filename, Tuple & tuple) {
AUDINFO("read file=%s\n", filename);
#if 0
//todo subsongs:
// in theory just set FlagSubtunes in plugin.h, and set_subtunes below
// Audacious will call "play" and this(?) with filename?N where N=subtune
// and you just load subtune N, but I can't get it working
int subtune;
string basename;
int subtune = get_filename_subtune(basename, &subtune);
//must use basename to open streamfile
#endif
STREAMFILE *streamfile = open_vfs(filename); STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) return false; if (!streamfile) return false;
@ -132,16 +166,47 @@ bool read_data(const char * filename, Tuple & tuple) {
return false; return false;
} }
tuple.set_filename(filename); //may leak string??? #if 0
int bitrate = get_vgmstream_average_bitrate(infostream); int total_subtunes = infostream->num_streams;
tuple.set_int(Tuple::Bitrate, bitrate);
int ms = get_vgmstream_play_samples(vgmstream_cfg.loop_count, vgmstream_cfg.fade_length, vgmstream_cfg.fade_delay, infostream); //somehow max was changed to short in recent versions, though
// some formats can exceed this
if (total_subtunes > 32767)
total_subtunes = 32767;
if (infostream->num_streams > 1 && subtune <= 0) {
//set nullptr to leave subsong index linear (must add +1 to subtune)
tuple.set_subtunes(total_subtunes, nullptr);
return true; //must return?
}
streamfile->stream_index = (subtune + 1);
#endif
//todo apply_config
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; ms = ms* 1000LL / infostream->sample_rate;
// 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, ms);
tuple.set_str(Tuple::Codec, "vgmstream codec");//doesn't show? //todo here we could call describe_vgmstream() and get substring to add tags and stuff
// here we could call describe_vgmstream() and get substring to add tags and stuff tuple.set_str(Tuple::Codec, "vgmstream codec");
//tuple.set_int(Tuple::Subtune, subtune);
//tuple.set_int(Tuple::NumSubtunes, subtune); //done un set_subtunes
//todo tags (see tuple.h)
//tuple.set_int (Tuple::Track, ...);
//tuple.set_str (Tuple::Artist, ...);
//tuple.set_str (Tuple::Album, ...);
close_streamfile(streamfile); close_streamfile(streamfile);
close_vgmstream(infostream); close_vgmstream(infostream);
@ -149,118 +214,31 @@ bool read_data(const char * filename, Tuple & tuple) {
return true; return true;
} }
//for Audacious <= 3.7 (unused otherwise) // 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) {
debugMessage("read_tuple");
Tuple tuple; Tuple tuple;
read_data(filename, tuple); read_info(filename, tuple);
return tuple; return tuple;
} }
//for Audacious >= 3.8 (unused otherwise) // thread safe (for Audacious >= 3.8, unused otherwise)
bool VgmstreamPlugin::read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image) { bool VgmstreamPlugin::read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image) {
debugMessage("read_tag"); return read_info(filename, tuple);
return read_data(filename, tuple);
} }
bool VgmstreamPlugin::play(const char *filename, VFSFile &file) { // internal util to seek during play
debugMessage("start play"); static void seek_helper(int seek_value, int &current_sample_pos, int input_channels) {
AUDINFO("seeking\n");
int current_sample_pos = 0;
int bitrate;
// just in case
if (vgmstream)
close_vgmstream(vgmstream);
STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) {
printf("failed opening %s\n", filename);
return false;
}
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
close_streamfile(streamfile);
if (!vgmstream || vgmstream->channels <= 0) {
printf("Error::Channels are zero or couldn't init plugin\n");
if (vgmstream)
close_vgmstream(vgmstream);
vgmstream = NULL;
return false;
}
short buffer[MIN_BUFFER_SIZE * vgmstream->channels];
int max_buffer_samples = sizeof(buffer) / sizeof(buffer[0]) / vgmstream->channels;
int stream_samples_amount = get_vgmstream_play_samples(
vgmstream_cfg.loop_count, vgmstream_cfg.fade_length,
vgmstream_cfg.fade_delay, vgmstream);
bitrate = get_vgmstream_average_bitrate(vgmstream);
set_stream_bitrate(bitrate);
open_audio(FMT_S16_LE, vgmstream->sample_rate, vgmstream->channels);
int fade_samples = vgmstream_cfg.fade_length * vgmstream->sample_rate;
while (!check_stop()) {
int toget = max_buffer_samples;
int seek_value = check_seek();
if (seek_value > 0)
seek(seek_value, current_sample_pos);
// If we haven't configured the plugin to play forever
// or the current song is not loopable.
if (!vgmstream_cfg.loop_forever || !vgmstream->loop_flag) {
if (current_sample_pos >= stream_samples_amount)
break;
if (current_sample_pos + toget > stream_samples_amount)
toget = stream_samples_amount - current_sample_pos;
}
render_vgmstream(buffer, toget, vgmstream);
if (vgmstream->loop_flag && fade_samples > 0 &&
!vgmstream_cfg.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 < vgmstream->channels; k++)
buffer[j * vgmstream->channels + k] *= fadedness;
}
}
}
}
write_audio(buffer, toget * sizeof(short) * vgmstream->channels);
current_sample_pos += toget;
}
debugMessage("finished");
if (vgmstream)
close_vgmstream(vgmstream);
vgmstream = NULL;
return true;
}
void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
debugMessage("seeking");
// compute from ms to samples // compute from ms to samples
int seek_needed_samples = (long long)seek_value * vgmstream->sample_rate / 1000L; int seek_needed_samples = (long long)seek_value * vgmstream->sample_rate / 1000L;
short buffer[MIN_BUFFER_SIZE * vgmstream->channels]; short buffer[MIN_BUFFER_SIZE * input_channels];
int max_buffer_samples = sizeof(buffer) / sizeof(buffer[0]) / vgmstream->channels; int max_buffer_samples = MIN_BUFFER_SIZE;
int samples_to_do = 0; int samples_to_do = 0;
if (seek_needed_samples < current_sample_pos) { if (seek_needed_samples < current_sample_pos) {
// go back in time, reopen file // go back in time, reopen file
debugMessage("reopen file to seek backward"); AUDINFO("resetting file to seek backwards\n");
reset_vgmstream(vgmstream); reset_vgmstream(vgmstream);
current_sample_pos = 0; current_sample_pos = 0;
samples_to_do = seek_needed_samples; samples_to_do = seek_needed_samples;
@ -271,7 +249,7 @@ void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
// do the actual seeking // do the actual seeking
if (samples_to_do >= 0) { if (samples_to_do >= 0) {
debugMessage("render forward"); AUDINFO("rendering forward\n");
// render till seeked sample // render till seeked sample
while (samples_to_do > 0) { while (samples_to_do > 0) {
@ -280,21 +258,97 @@ void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
samples_to_do -= seek_samples; samples_to_do -= seek_samples;
render_vgmstream(buffer, seek_samples, vgmstream); render_vgmstream(buffer, seek_samples, vgmstream);
} }
debugMessage("after render vgmstream");
} }
} }
void debugMessage(const char *str) { // called on play (play thread)
#ifdef DEBUG bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
timeval curTime; AUDINFO("play file=%s\n", filename);
gettimeofday(&curTime, NULL);
int milli = curTime.tv_usec / 1000;
char buffer[80]; // just in case
strftime(buffer, 80, "%H:%M:%S", localtime(&curTime.tv_sec)); if (vgmstream)
close_vgmstream(vgmstream);
char currentTime[84] = ""; STREAMFILE *streamfile = open_vfs(filename);
sprintf(currentTime, "%s:%d", buffer, milli); if (!streamfile) {
printf("[%s] Debug: %s\n", currentTime, str); AUDERR("failed opening file %s\n", filename);
#endif return false;
}
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
close_streamfile(streamfile);
if (!vgmstream) {
AUDINFO("filename %s is not a valid format\n", filename);
close_vgmstream(vgmstream);
vgmstream = NULL;
return false;
}
int bitrate = get_vgmstream_average_bitrate(vgmstream);
set_stream_bitrate(bitrate);
//todo apply config
int input_channels = vgmstream->channels;
int output_channels = vgmstream->channels;
/* enable after all config but before outbuf */
vgmstream_mixing_autodownmix(vgmstream, settings.downmix_channels);
vgmstream_mixing_enable(vgmstream, MIN_BUFFER_SIZE, &input_channels, &output_channels);
//FMT_S8 / FMT_S16_NE / FMT_S24_NE / FMT_S32_NE / FMT_FLOAT
open_audio(FMT_S16_LE, vgmstream->sample_rate, output_channels);
// 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;
while (!check_stop()) {
int toget = max_buffer_samples;
// handle seek request
int seek_value = check_seek();
if (seek_value >= 0)
seek_helper(seek_value, current_sample_pos, input_channels);
// check stream finished
if (!settings.loop_forever || !vgmstream->loop_flag) {
if (current_sample_pos >= stream_samples_amount)
break;
if (current_sample_pos + toget > stream_samples_amount)
toget = stream_samples_amount - current_sample_pos;
}
render_vgmstream(buffer, toget, 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;
}
AUDINFO("play finished\n");
close_vgmstream(vgmstream);
vgmstream = NULL;
return true;
} }

View File

@ -7,6 +7,11 @@
#include <libaudcore/preferences.h> #include <libaudcore/preferences.h>
#include <libaudcore/runtime.h> #include <libaudcore/runtime.h>
#ifndef AUDACIOUS_VGMSTREAM_PRIORITY
/* set higher than FFmpeg's 10 */
#define AUDACIOUS_VGMSTREAM_PRIORITY 9
#endif
class VgmstreamPlugin : public InputPlugin { class VgmstreamPlugin : public InputPlugin {
public: public:
//static const char *const exts[]; //static const char *const exts[];
@ -19,12 +24,11 @@ public:
N_("vgmstream Decoder"), N_("vgmstream"), about, &prefs, N_("vgmstream Decoder"), N_("vgmstream"), about, &prefs,
}; };
// accepted exts are validated at runtime in is_our_file now, this is to set a static list constexpr VgmstreamPlugin() : InputPlugin (info,
//static constexpr auto iinfo = InputInfo().with_exts(exts); InputInfo() //InputInfo(FlagSubtunes) // allow subsongs
//constexpr VgmstreamPlugin() : InputPlugin(info, iinfo) {} .with_priority(AUDACIOUS_VGMSTREAM_PRIORITY) // where 0=default(?), 10=lowest
//constexpr VgmstreamPlugin() : InputPlugin (info, InputInfo().with_exts(exts)) {} //.with_exts(exts)) {} //accepted exts are validated at runtime now
) {}
constexpr VgmstreamPlugin() : InputPlugin (info, InputInfo()) {}
bool init(); bool init();
void cleanup(); void cleanup();
@ -33,12 +37,9 @@ public:
bool read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image); 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);
private:
void seek(int seek_value, int &current_sample_pos);
}; };
// reminder of usage, probably no more need // static extension list, not sure of advantages (uses is_our_file)
//const char *const VgmstreamPlugin::exts[] = { "ext1", "ext2", ..., NULL } //const char *const VgmstreamPlugin::exts[] = { "ext1", "ext2", ..., NULL }
@ -47,12 +48,14 @@ typedef struct {
int loop_count; int loop_count;
double fade_length; double fade_length;
double fade_delay; double fade_delay;
} Settings; int downmix_channels;
bool exts_unknown_on;
bool exts_common_on;
} audacious_settings;
extern Settings vgmstream_cfg; extern audacious_settings settings;
void debugMessage(const char *str); void vgmstream_settings_load();
void vgmstream_cfg_load(); void vgmstream_settings_save();
void vgmstream_cfg_save();
#endif #endif

View File

@ -7,54 +7,53 @@
#include "plugin.h" #include "plugin.h"
#include "vfs.h" #include "vfs.h"
typedef struct _VFSSTREAMFILE { typedef struct {
STREAMFILE sf; STREAMFILE sf;
VFSFile *vfsFile; VFSFile *vfsFile;
off_t offset; off_t offset;
char name[32768]; char name[32768];
} VFSSTREAMFILE; } VFS_STREAMFILE;
static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path); static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path);
static size_t read_vfs(VFSSTREAMFILE *streamfile, uint8_t *dest, off_t offset, static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length) {
size_t length) { size_t bytes_read;
size_t sz;
if (!dest || length <= 0 || offset < 0)
return 0;
// if the offsets don't match, then we need to perform a seek // if the offsets don't match, then we need to perform a seek
if (streamfile->offset != offset) { if (streamfile->offset != offset) {
streamfile->vfsFile->fseek(offset, VFS_SEEK_SET); int ok = streamfile->vfsFile->fseek(offset, VFS_SEEK_SET);
if (ok != 0) return 0;
streamfile->offset = offset; streamfile->offset = offset;
} }
sz = streamfile->vfsFile->fread(dest, 1, length); bytes_read = streamfile->vfsFile->fread(dest, 1, length);
// increment our current offset streamfile->offset += bytes_read;
streamfile->offset += sz;
return sz; return bytes_read;
} }
static void close_vfs(VFSSTREAMFILE *streamfile) { static void close_vfs(VFS_STREAMFILE *streamfile) {
debugMessage("close_vfs");
delete streamfile->vfsFile; //fcloses the internal file too delete streamfile->vfsFile; //fcloses the internal file too
free(streamfile); free(streamfile);
} }
static size_t get_size_vfs(VFSSTREAMFILE *streamfile) { static size_t get_size_vfs(VFS_STREAMFILE *streamfile) {
return streamfile->vfsFile->fsize(); return streamfile->vfsFile->fsize();
} }
static size_t get_offset_vfs(VFSSTREAMFILE *streamfile) { static size_t get_offset_vfs(VFS_STREAMFILE *streamfile) {
return streamfile->offset; return streamfile->offset;
} }
static void get_name_vfs(VFSSTREAMFILE *streamfile, char *buffer, static void get_name_vfs(VFS_STREAMFILE *streamfile, char *buffer, size_t length) {
size_t length) {
strncpy(buffer, streamfile->name, length); strncpy(buffer, streamfile->name, length);
buffer[length - 1] = '\0'; buffer[length - 1] = '\0';
} }
static STREAMFILE *open_vfs_impl(VFSSTREAMFILE *streamfile, static STREAMFILE *open_vfs_impl(VFS_STREAMFILE *streamfile, const char *const filename, size_t buffersize) {
const char *const filename,
size_t buffersize) {
if (!filename) if (!filename)
return NULL; return NULL;
@ -62,12 +61,12 @@ static STREAMFILE *open_vfs_impl(VFSSTREAMFILE *streamfile,
} }
STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path) { STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path) {
VFSSTREAMFILE *streamfile = (VFSSTREAMFILE *)malloc(sizeof(VFSSTREAMFILE)); VFS_STREAMFILE *streamfile = (VFS_STREAMFILE *)malloc(sizeof(VFS_STREAMFILE));
if (!streamfile) if (!streamfile)
return NULL; return NULL;
// success, set our pointers // success, set our pointers
memset(streamfile, 0, sizeof(VFSSTREAMFILE)); memset(streamfile, 0, sizeof(VFS_STREAMFILE));
streamfile->sf.read = (size_t (*)(STREAMFILE *, uint8_t *, off_t, size_t))read_vfs; streamfile->sf.read = (size_t (*)(STREAMFILE *, uint8_t *, off_t, size_t))read_vfs;
streamfile->sf.get_size = (size_t (*)(STREAMFILE *))get_size_vfs; streamfile->sf.get_size = (size_t (*)(STREAMFILE *))get_size_vfs;

View File

@ -32,7 +32,7 @@ If you wish to use CMake, see [CMAKE.md](CMAKE.md).
**With GCC**: use the *./Makefile* in the root folder, see inside for options. For compilation flags check the *Makefile* in each folder. **With GCC**: use the *./Makefile* in the root folder, see inside for options. For compilation flags check the *Makefile* in each folder.
You may need to manually rebuild if you change a *.h* file (use *make clean*). You may need to manually rebuild if you change a *.h* file (use *make clean*).
In Linux, Makefiles can be used to cross-compile with the MingW headers, but may not be updated to generate native code at the moment. It should be fixable with some effort. Autotools should build it as vgmstream-cli instead (see the Audacious section). Some Linux distributions like Arch Linux include pre-patched vgmstream with most libraries, you may want that instead (see: https://aur.archlinux.org/packages/vgmstream-kode54-git/). In Linux, Makefiles can be used to cross-compile with the MingW headers, but may not be updated to generate native code at the moment. It should be fixable with some effort. Autotools should build and install it as `vgmstream-cli` instead (see the Audacious section). Some Linux distributions like Arch Linux include pre-patched vgmstream with most libraries, you may want that instead (see: https://aur.archlinux.org/packages/vgmstream-kode54-git/).
Windows CMD example: Windows CMD example:
``` ```
@ -99,10 +99,14 @@ 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). 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)
Terminal example, assuming a Ubuntu-based Linux distribution: Terminal example, assuming a Ubuntu-based Linux distribution:
``` ```
# build requirements # build setup
# default requirements
sudo apt-get update sudo apt-get update
sudo apt-get install gcc g++ make sudo apt-get install gcc g++ make
sudo apt-get install autoconf automake libtool sudo apt-get install autoconf automake libtool
@ -113,10 +117,15 @@ sudo apt-get install libmpg123-dev libvorbis-dev
sudo apt-get install audacious sudo apt-get install audacious
sudo apt-get install audacious-dev libglib2.0-dev libgtk2.0-dev libpango1.0-dev sudo apt-get install audacious-dev libglib2.0-dev libgtk2.0-dev libpango1.0-dev
# if you want vgmstream123 do this too
sudo apt-get install libao-dev
# check Audacious version >= 3.5 # check Audacious version >= 3.5
pkg-config --modversion audacious pkg-config --modversion audacious
```
```
# vgmstream build
# build
git clone https://github.com/kode54/vgmstream git clone https://github.com/kode54/vgmstream
cd vgmstream cd vgmstream
@ -124,13 +133,21 @@ cd vgmstream
./configure ./configure
make -f Makefile.autotools make -f Makefile.autotools
# copy to audacious plugins and update global libvgmstream.so.0 refs # copy to audacious plugins (note that this will also install "libvgmstream",
# vgmstream-cli and vgmstream123, so they can be invoked from the terminal)
sudo make -f Makefile.autotools install sudo make -f Makefile.autotools install
# update global libvgmstream.so.0 refs
sudo ldconfig sudo ldconfig
# start audacious in verbose mode to check if it was installed correctly # start audacious in verbose mode to check if it was installed correctly
audacious -V audacious -V
# if all goes well no "ERROR (something) referencing libvgmstream should show
# in the terminal log, then go to menu services > plugins > input tab and check
# vgmstream is there (you can start audacious normally next time)
```
```
# uninstall if needed # uninstall if needed
sudo make -f Makefile.autotools uninstall sudo make -f Makefile.autotools uninstall
@ -141,11 +158,13 @@ find . -name ".deps" -type d -exec rm -r "{}" \;
## WARNING, removes *all* untracked files not in .gitignore ## WARNING, removes *all* untracked files not in .gitignore
git clean -fd 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.
### vgmstream123 player ### vgmstream123 player
Should be buildable with Autotools, much like the Audacious plugin, though requires libao (libao-dev). Should be buildable with Autotools by following the same steps as listen in the Audacious section (requires libao-dev).
Windows builds are possible with libao.dll and includes, but some features are disabled. Windows builds are possible with `libao.dll` and `libao` includes (found elsewhere) through the `Makefile`, but some features are disabled.
libao is licensed under the GPL v2 or later. libao is licensed under the GPL v2 or later.