Merge pull request #1569 from bnnm/api-misc5

- Fix broken .TMX files [Need for Speed Undercover (PC)]
- cleanup
This commit is contained in:
bnnm 2024-07-28 19:00:10 +02:00 committed by GitHub
commit 8160794db5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 2898 additions and 3321 deletions

View File

@ -23,7 +23,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libjansson-dev yasm libopus-dev
sudo apt-get install -y yasm libopus-dev
- name: Create build environment
# Some projects don't allow in-source building, so create a separate build directory

View File

@ -19,7 +19,7 @@ jobs:
fetch-depth: 0
- name: Install dependencies
run: brew install autoconf automake cmake pkgconfig ffmpeg jansson libao libvorbis mpg123
run: brew install autoconf automake cmake pkgconfig ffmpeg libao libvorbis mpg123
- name: Cache celt
uses: actions/cache@v4

View File

@ -49,13 +49,6 @@ jobs:
${{runner.workspace}}/embuild/dependencies/ffmpeg/bin/usr/local/lib
key: wasm-ffmpeg-${{ hashFiles('cmake/dependencies/ffmpeg.cmake') }}
- name: Cache jansson
uses: actions/cache@v4
with:
path: |
${{runner.workspace}}/embuild/dependencies/jansson/src/.libs
key: wasm-jansson-${{ hashFiles('cmake/dependencies/jansson.cmake') }}
- name: Cache atrac9
uses: actions/cache@v4
with:

View File

@ -63,9 +63,6 @@ option(USE_G719 "Use libg719_decode for support ITU-T G.719" ON)
option(USE_ATRAC9 "Use LibAtrac9 for support of ATRAC9" ON)
option(USE_CELT "Use libcelt for support of FSB CELT versions 0.6.1 and 0.11.0" ON)
option(USE_SPEEX "Use libspeex for support of SPEEX" ON)
if(NOT WIN32)
option(USE_JANSSON "Use jansson for JSON dumping" ON)
endif()
if(NOT WIN32)
set(MPEG_PATH CACHE PATH "Path to mpg123")
@ -78,7 +75,6 @@ if(NOT WIN32)
set(CELT_0061_PATH CACHE PATH "Path to CELT version 0.6.1")
set(CELT_0110_PATH CACHE PATH "Path to CELT version 0.11.0")
set(LIBAO_PATH CACHE PATH "Path to libao")
set(JANSSON_PATH CACHE PATH "Path to jansson")
endif()
# Build choices
@ -158,7 +154,6 @@ include(dependencies/g719)
include(dependencies/atrac9)
include(dependencies/celt)
include(dependencies/speex)
include(dependencies/jansson)
include(dependencies/audacious)
if(USE_G7221)
@ -273,7 +268,6 @@ message(STATUS " FSB CELT: ${USE_CELT} ${CELT_SOURCE}")
message(STATUS " SPEEX: ${USE_SPEEX} ${SPEEX_SOURCE}")
if(NOT WIN32)
message(STATUS " LIBAO: ${BUILD_V123} ${LIBAO_SOURCE}")
message(STATUS " Jansson: ${USE_JANSSON} ${JANSSON_SOURCE}")
endif()
message(STATUS "")

View File

@ -10,12 +10,6 @@ set_target_properties(vgmstream_cli PROPERTIES
# Link to the vgmstream library
target_link_libraries(vgmstream_cli libvgmstream)
# Link to Jansson, if we have it
if (USE_JANSSON)
target_compile_definitions(vgmstream_cli PRIVATE HAVE_JSON)
target_include_directories(vgmstream_cli PRIVATE ${JANSSON_INCLUDE_DIRS})
target_link_libraries(vgmstream_cli jansson)
endif()
setup_target(vgmstream_cli TRUE)

View File

@ -39,6 +39,8 @@ CFLAGS += $(LIBS_CFLAGS)
LDFLAGS += $(LIBS_LDFLAGS)
TARGET_EXT_LIBS += $(LIBS_TARGET_EXT_LIBS)
CLI_SRCS = vgmstream_cli.c vgmstream_cli_utils.c wav_utils.c
V123_SRCS = vgmstream123.c wav_utils.c
export CFLAGS LDFLAGS
@ -47,11 +49,11 @@ export CFLAGS LDFLAGS
### targets
vgmstream_cli: libvgmstream.a $(TARGET_EXT_LIBS)
$(CC) $(CFLAGS) vgmstream_cli.c vgmstream_cli_utils.c wav_utils.c $(LDFLAGS) -o $(OUTPUT_CLI)
$(CC) $(CFLAGS) $(CLI_SRCS) $(LDFLAGS) -o $(OUTPUT_CLI)
$(STRIP) $(OUTPUT_CLI)
vgmstream123: libvgmstream.a $(TARGET_EXT_LIBS)
$(CC) $(CFLAGS) $(LIBAO_INC) vgmstream123.c wav_utils.c $(LDFLAGS) $(LIBAO_LIB) -o $(OUTPUT_123)
$(CC) $(CFLAGS) $(LIBAO_INC) $(V123_SRCS) $(LDFLAGS) $(LIBAO_LIB) -o $(OUTPUT_123)
$(STRIP) $(OUTPUT_123)
api_example: libvgmstream.a $(TARGET_EXT_LIBS)

View File

@ -1,10 +1,11 @@
#include "../src/api.h"
#include "../src/libvgmstream.h"
#if LIBVGMSTREAM_ENABLE
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "../src/base/api_internal.h"
#include "../src/streamfile.h"
static void usage(const char* progname) {
@ -29,14 +30,14 @@ static libvgmstream_streamfile_t* get_streamfile(const char* filename) {
}
static int api_example(const char* infile) {
VGM_STEP();
VGM_STEP();
int err;
FILE* outfile = NULL;
bool fill_test;
int pcm16_samples;
int pcm16_bytes;
short* pcm16 = NULL;
bool fill_test = true;
int fill_pcm16_samples;
int fill_pcm16_bytes;
short* fill_pcm16 = NULL;
// main init
@ -49,6 +50,7 @@ VGM_STEP();
//.loop_count = 1.0,
//.fade_time = 10.0,
.ignore_loop = true,
.force_pcm16 = fill_test,
};
libvgmstream_setup(lib, &cfg);
@ -57,7 +59,7 @@ VGM_STEP();
libvgmstream_options_t opt = {
.libsf = get_streamfile(infile)
};
err = libvgmstream_open(lib, &opt);
err = libvgmstream_open_song(lib, &opt);
// external SF is not needed after _open
libvgmstream_streamfile_close(opt.libsf);
@ -94,33 +96,28 @@ VGM_STEP();
printf("channels: %i\n", lib->format->channels);
printf("sample rate: %i\n", lib->format->sample_rate);
printf("codec: %s\n", lib->format->codec_name);
printf("samples: %i\n", (int32_t)lib->format->sample_count);
printf("samples: %i\n", (int32_t)lib->format->stream_samples);
printf("\n");
printf("- decoding: %i\n" , (int32_t)lib->format->play_samples);
fill_test = true;
pcm16_samples = 512;
pcm16_bytes = pcm16_samples * sizeof(short) * lib->format->channels;
pcm16 = malloc(pcm16_bytes);
if (!pcm16) goto fail;
fill_pcm16_samples = 512;
fill_pcm16_bytes = fill_pcm16_samples * sizeof(short) * lib->format->channels;
fill_pcm16 = malloc(fill_pcm16_bytes);
if (!fill_pcm16) goto fail;
// play file and do something with decoded samples
while (true) {
while (!lib->decoder->done) {
//int pos;
void* buf;
int buf_bytes = 0;
if (lib->decoder->done)
break;
// get current samples
if (fill_test) {
err = libvgmstream_fill(lib, pcm16, pcm16_samples);
err = libvgmstream_fill(lib, fill_pcm16, fill_pcm16_samples);
if (err < 0) goto fail;
buf = pcm16;
buf = fill_pcm16;
buf_bytes = err * sizeof(short) * lib->format->channels;
}
else {
@ -140,12 +137,12 @@ VGM_STEP();
printf("\n");
// close current streamfile before opening new ones, optional
//libvgmstream_close(lib);
//libvgmstream_close_song(lib);
// process done
libvgmstream_free(lib);
fclose(outfile);
free(pcm16);
free(fill_pcm16);
printf("done\n");
return EXIT_SUCCESS;
@ -153,7 +150,7 @@ fail:
// process failed
libvgmstream_free(lib);
fclose(outfile);
free(pcm16);
free(fill_pcm16);
printf("failed!\n");
return EXIT_FAILURE;

View File

@ -868,7 +868,7 @@ again_opts:
verbose = 1;
break;
default:
VGM_LOG("vgmstream123: unknown opt %x", opt);
printf("vgmstream123: unknown opt %x", opt);
goto done;
}
}

View File

@ -3,6 +3,7 @@
*/
#define POSIXLY_CORRECT
#include <stdio.h>
#include <getopt.h>
#ifdef WIN32
@ -65,10 +66,8 @@ static void print_usage(const char* progname, bool is_help) {
" -c: loop forever (continuously) to stdout\n"
" -L: append a smpl chunk and create a looping wav\n"
//" -w: allow .wav in original sample format rather than downmixing to PCM16\n"
#ifdef HAVE_JSON
" -V: print version info and supported extensions as JSON\n"
" -I: print requested file info as JSON\n"
#endif
" -h: print all commands\n"
, progname);
@ -109,11 +108,7 @@ static bool parse_config(cli_config_t* cfg, int argc, char** argv) {
optind = 1; /* reset getopt's ugly globals (needed in wasm that may call same main() multiple times) */
/* read config */
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:tTk:K:hOvD:S:B:"
#ifdef HAVE_JSON
"VI"
#endif
)) != -1) {
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:tTk:K:hOvD:S:B:VI")) != -1) {
switch (opt) {
case 'o':
cfg->outfilename = optarg;
@ -228,14 +223,12 @@ static bool parse_config(cli_config_t* cfg, int argc, char** argv) {
case 'h':
print_usage(argv[0], true);
goto fail;
#ifdef HAVE_JSON
case 'V':
print_json_version(VGMSTREAM_VERSION);
goto fail;
case 'I':
cfg->print_metajson = true;
break;
#endif
case '?':
fprintf(stderr, "missing argument or unknown option -%c\n", optopt);
goto fail;
@ -458,10 +451,48 @@ static bool is_valid_extension(cli_config_t* cfg) {
return vgmstream_ctx_is_valid(cfg->infilename, &vcfg);
}
static VGMSTREAM* open_vgmstream(cli_config_t* cfg) {
STREAMFILE* sf = NULL;
VGMSTREAM* vgmstream = NULL;
sf = open_stdio_streamfile(cfg->infilename);
if (!sf) {
fprintf(stderr, "file %s not found\n", cfg->infilename);
return NULL;
}
sf->stream_index = cfg->subsong_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
if (!vgmstream) {
fprintf(stderr, "failed opening %s\n", cfg->infilename);
goto fail;
}
/* modify the VGMSTREAM if needed (before printing file info) */
apply_config(vgmstream, cfg);
/* enable after config but before outbuf */
if (cfg->downmix_channels) {
vgmstream_mixing_autodownmix(vgmstream, cfg->downmix_channels);
}
else if (cfg->stereo_track > 0) {
vgmstream_mixing_stereo_only(vgmstream, cfg->stereo_track - 1);
}
vgmstream_mixing_enable(vgmstream, cfg->sample_buffer_size, NULL, NULL);
close_streamfile(sf);
return vgmstream;
fail:
close_streamfile(sf);
close_vgmstream(vgmstream);
return NULL;
}
static bool convert_file(cli_config_t* cfg) {
VGMSTREAM* vgmstream = NULL;
char outfilename_temp[PATH_LIMIT];
char outfilename_temp[CLI_PATH_LIMIT];
int32_t len_samples;
@ -470,40 +501,14 @@ static bool convert_file(cli_config_t* cfg) {
return false;
/* open streamfile and pass subsong */
{
STREAMFILE* sf = open_stdio_streamfile(cfg->infilename);
if (!sf) {
fprintf(stderr, "file %s not found\n", cfg->infilename);
goto fail;
}
vgmstream = open_vgmstream(cfg);
if (!vgmstream) goto fail;
sf->stream_index = cfg->subsong_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);
if (!vgmstream) {
fprintf(stderr, "failed opening %s\n", cfg->infilename);
goto fail;
}
/* force load total subsongs if signalled */
if (cfg->subsong_end == -1) {
cfg->subsong_end = vgmstream->num_streams;
close_vgmstream(vgmstream);
return true;
}
/* modify the VGMSTREAM if needed (before printing file info) */
apply_config(vgmstream, cfg);
/* enable after config but before outbuf */
if (cfg->downmix_channels) {
vgmstream_mixing_autodownmix(vgmstream, cfg->downmix_channels);
}
else if (cfg->stereo_track > 0) {
vgmstream_mixing_stereo_only(vgmstream, cfg->stereo_track - 1);
}
vgmstream_mixing_enable(vgmstream, cfg->sample_buffer_size, NULL, NULL);
/* force load total subsongs if signalled */
if (cfg->subsong_end == -1) {
cfg->subsong_end = vgmstream->num_streams;
close_vgmstream(vgmstream);
return true;
}
@ -567,18 +572,14 @@ static bool convert_file(cli_config_t* cfg) {
/* prints */
#ifdef HAVE_JSON
if (!cfg->print_metajson) {
#endif
print_info(vgmstream, cfg);
print_tags(cfg);
print_title(vgmstream, cfg);
#ifdef HAVE_JSON
}
else {
print_json_info(vgmstream, cfg, VGMSTREAM_VERSION);
}
#endif
/* prints done */
if (cfg->print_metaonly) {
@ -593,7 +594,7 @@ static bool convert_file(cli_config_t* cfg) {
/* try again with reset (for testing, simulates a seek to 0 after changing internal state)
* (could simulate by seeking to last sample then to 0, too) */
if (cfg->test_reset) {
char outfilename_reset[PATH_LIMIT];
char outfilename_reset[CLI_PATH_LIMIT];
snprintf(outfilename_reset, sizeof(outfilename_reset), "%s.reset.wav", cfg->outfilename);
cfg->outfilename = outfilename_reset;
@ -653,6 +654,9 @@ int main(int argc, char** argv) {
bool res, ok;
vgmstream_set_log_stdout(VGM_LOG_LEVEL_ALL);
/* read args */
res = parse_config(&cfg, argc, argv);
if (!res) goto fail;
@ -660,8 +664,6 @@ int main(int argc, char** argv) {
res = validate_config(&cfg);
if (!res) goto fail;
vgmstream_set_log_stdout(VGM_LOG_LEVEL_ALL);
#ifdef WIN32
/* make stdout output work with windows */
if (cfg.play_sdtout) {

View File

@ -1,9 +1,10 @@
#ifndef _VGMSTREAM_CLI_H
#define _VGMSTREAM_CLI_H
#ifndef _VGMSTREAM_CLI_H_
#define _VGMSTREAM_CLI_H_
#include "../src/api.h"
#include "../src/vgmstream.h"
#define CLI_PATH_LIMIT 4096
typedef struct {
char** infilenames;
@ -42,9 +43,7 @@ typedef struct {
bool print_oggenc;
bool print_batchvar;
bool print_title;
#ifdef HAVE_JSON
bool print_metajson;
#endif
const char* tag_filename;
// debug stuff
@ -68,10 +67,8 @@ void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg);
void print_tags(cli_config_t* cfg);
void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg);
#ifdef HAVE_JSON
void print_json_version(const char* vgmstream_version);
void print_json_info(VGMSTREAM* vgm, cli_config_t* cfg, const char* vgmstream_version);
#endif
#endif

View File

@ -105,6 +105,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="vgmstream_cli.h" />
<ClInclude Include="vjson.h" />
<ClInclude Include="wav_utils.h" />
</ItemGroup>
<ItemGroup>

View File

@ -22,6 +22,9 @@
<ClCompile Include="vgmstream_cli.h">
<Filter>Header Files</Filter>
</ClCompile>
<ClCompile Include="vjson.h">
<Filter>Header Files</Filter>
</ClCompile>
<ClCompile Include="wav_utils.h">
<Filter>Header Files</Filter>
</ClCompile>

View File

@ -1,12 +1,12 @@
#include "vgmstream_cli.h"
#include <string.h>
#include <inttypes.h>
#include <stdio.h>
#include "vgmstream_cli.h"
#include "../src/api.h"
#include "../src/vgmstream.h"
//todo use <>?
#ifdef HAVE_JSON
#include "jansson/jansson.h"
#endif
#include "vjson.h"
static void clean_filename(char* dst, int clean_paths) {
@ -23,9 +23,9 @@ static void clean_filename(char* dst, int clean_paths) {
* ("?" 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, cli_config_t* cfg, VGMSTREAM* vgmstream) {
int subsong;
char stream_name[PATH_LIMIT];
char buf[PATH_LIMIT];
char tmp[PATH_LIMIT];
char stream_name[CLI_PATH_LIMIT];
char buf[CLI_PATH_LIMIT];
char tmp[CLI_PATH_LIMIT];
/* file has a "%" > temp replace for sprintf */
@ -100,29 +100,34 @@ void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* v
void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg) {
int channels = vgmstream->channels;
int64_t num_samples = vgmstream->num_samples;
bool loop_flag = vgmstream->loop_flag;
int64_t loop_start = vgmstream->loop_start_sample;
int64_t loop_end = vgmstream->loop_start_sample;
if (!cfg->play_sdtout) {
if (cfg->print_adxencd) {
printf("adxencd");
if (!cfg->print_metaonly)
printf(" \"%s\"",cfg->outfilename);
if (vgmstream->loop_flag)
printf(" -lps%d -lpe%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
printf(" \"%s\"", cfg->outfilename);
if (loop_flag)
printf(" -lps%"PRId64" -lpe%"PRId64, loop_start, loop_end);
printf("\n");
}
else if (cfg->print_oggenc) {
printf("oggenc");
if (!cfg->print_metaonly)
printf(" \"%s\"", cfg->outfilename);
if (vgmstream->loop_flag)
printf(" -c LOOPSTART=%d -c LOOPLENGTH=%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample-vgmstream->loop_start_sample);
if (loop_flag)
printf(" -c LOOPSTART=%"PRId64" -c LOOPLENGTH=%"PRId64, loop_start, loop_end - loop_start);
printf("\n");
}
else if (cfg->print_batchvar) {
if (!cfg->print_metaonly)
printf("set fname=\"%s\"\n", cfg->outfilename);
printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, channels);
if (vgmstream->loop_flag)
printf("set lstart=%d\nset lend=%d\nset loop=1\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
printf("set tsamp=%"PRId64"\nset chan=%d\n", num_samples, channels);
if (loop_flag)
printf("set lstart=%"PRId64"\nset lend=%"PRId64"\nset loop=1\n", loop_start, loop_end);
else
printf("set loop=0\n");
}
@ -183,114 +188,106 @@ void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg) {
printf("title: %s\n", title);
}
#ifdef HAVE_JSON
void print_json_version(const char* vgmstream_version) {
size_t extension_list_len;
size_t common_extension_list_len;
size_t extension_list_len = 0;
const char** extension_list;
const char** common_extension_list;
extension_list = vgmstream_get_formats(&extension_list_len);
common_extension_list = vgmstream_get_common_formats(&common_extension_list_len);
json_t* ext_list = json_array();
json_t* cext_list = json_array();
vjson_t j = {0};
for (size_t i = 0; i < extension_list_len; ++i) {
json_t* ext = json_string(extension_list[i]);
json_array_append(ext_list, ext);
}
char buf[0x4000]; // exts need ~0x1400
vjson_init(&j, buf, sizeof(buf));
for (size_t i = 0; i < common_extension_list_len; ++i) {
json_t* cext = json_string(common_extension_list[i]);
json_array_append(cext_list, cext);
}
vjson_obj_open(&j);
vjson_keystr(&j, "version", vgmstream_version);
json_t* version_string = json_string(vgmstream_version);
vjson_key(&j, "extensions");
vjson_obj_open(&j);
json_t* final_object = json_object();
json_object_set(final_object, "version", version_string);
json_decref(version_string);
vjson_key(&j, "vgm");
vjson_arr_open(&j);
extension_list = vgmstream_get_formats(&extension_list_len);
for (int i = 0; i < extension_list_len; i++) {
vjson_str(&j, extension_list[i]);
}
vjson_arr_close(&j);
json_object_set(final_object, "extensions",
json_pack("{soso}",
"vgm", ext_list,
"common", cext_list));
vjson_key(&j, "common");
vjson_arr_open(&j);
extension_list = vgmstream_get_common_formats(&extension_list_len);
for (int i = 0; i < extension_list_len; i++) {
vjson_str(&j, extension_list[i]);
}
vjson_arr_close(&j);
json_dumpf(final_object, stdout, JSON_COMPACT);
vjson_obj_close(&j);
vjson_obj_close(&j);
printf("%s\n", buf);
}
void print_json_info(VGMSTREAM* vgm, cli_config_t* cfg, const char* vgmstream_version) {
json_t* version_string = json_string(vgmstream_version);
char buf[0x1000]; // probably fine with ~0x400
vjson_t j = {0};
vjson_init(&j, buf, sizeof(buf));
vgmstream_info info;
describe_vgmstream_info(vgm, &info);
vjson_obj_open(&j);
vjson_keystr(&j, "version", vgmstream_version);
vjson_keyint(&j, "sampleRate", info.sample_rate);
vjson_keyint(&j, "channels", info.channels);
json_t* mixing_info = NULL;
vjson_key(&j, "mixingInfo");
if (info.mixing_info.input_channels > 0) {
vjson_obj_open(&j);
vjson_keyint(&j, "inputChannels", info.mixing_info.input_channels);
vjson_keyint(&j, "outputChannels", info.mixing_info.output_channels);
vjson_obj_close(&j);
}
else {
vjson_null(&j);
}
// The JSON pack format string is defined here: https://jansson.readthedocs.io/en/latest/apiref.html#building-values
vjson_keyintnull(&j, "channelLayout", info.channel_layout);
if (info.mixing_info.input_channels > 0) {
mixing_info = json_pack("{sisi}",
"inputChannels", info.mixing_info.input_channels,
"outputChannels", info.mixing_info.output_channels);
}
vjson_key(&j, "loopingInfo");
if (info.loop_info.end > info.loop_info.start) {
vjson_obj_open(&j);
vjson_keyint(&j, "start", info.loop_info.start);
vjson_keyint(&j, "end", info.loop_info.start);
vjson_obj_close(&j);
}
else {
vjson_null(&j);
}
json_t* loop_info = NULL;
vjson_key(&j, "interleaveInfo");
if (info.interleave_info.last_block > info.interleave_info.first_block) {
vjson_obj_open(&j);
vjson_keyint(&j, "firstBlock", info.interleave_info.last_block);
vjson_keyint(&j, "lastBlock", info.interleave_info.first_block);
vjson_obj_close(&j);
}
else {
vjson_null(&j);
}
if (info.loop_info.end > info.loop_info.start) {
loop_info = json_pack("{sisi}",
"start", info.loop_info.start,
"end", info.loop_info.end);
}
vjson_keyint(&j, "numberOfSamples", info.num_samples);
vjson_keystr(&j, "encoding", info.encoding);
vjson_keystr(&j, "layout", info.layout);
vjson_keyintnull(&j, "frameSize", info.frame_size);
vjson_keystr(&j, "metadataSource", info.metadata);
vjson_keyint(&j, "bitrate", info.bitrate);
json_t* interleave_info = NULL;
vjson_key(&j, "streamInfo");
vjson_obj_open(&j);
vjson_keyint(&j, "index", info.stream_info.current);
vjson_keystr(&j, "name", info.stream_info.name);
vjson_keyint(&j, "total", info.stream_info.total);
vjson_obj_close(&j);
if (info.interleave_info.last_block > info.interleave_info.first_block) {
interleave_info = json_pack("{sisi}",
"firstBlock", info.interleave_info.first_block,
"lastBlock", info.interleave_info.last_block
);
}
vjson_obj_close(&j);
json_t* stream_info = json_pack("{sisssi}",
"index", info.stream_info.current,
"name", info.stream_info.name,
"total", info.stream_info.total
);
if (info.stream_info.name[0] == '\0') {
json_object_set(stream_info, "name", json_null());
}
json_t* final_object = json_pack(
"{sssisiso?siso?so?sisssssisssiso?}",
"version", version_string,
"sampleRate", info.sample_rate,
"channels", info.channels,
"mixingInfo", mixing_info,
"channelLayout", info.channel_layout,
"loopingInfo", loop_info,
"interleaveInfo", interleave_info,
"numberOfSamples", info.num_samples,
"encoding", info.encoding,
"layout", info.layout,
"frameSize", info.frame_size,
"metadataSource", info.metadata,
"bitrate", info.bitrate,
"streamInfo", stream_info
);
if (info.frame_size == 0) {
json_object_set(final_object, "frameSize", json_null());
}
if (info.channel_layout == 0) {
json_object_set(final_object, "channelLayout", json_null());
}
json_dumpf(final_object, stdout, JSON_COMPACT);
json_decref(final_object);
printf("\n");
printf("%s\n", buf);
}
#endif

154
cli/vjson.h Normal file
View File

@ -0,0 +1,154 @@
#ifndef _VJSON_H_
#define _VJSON_H_
/* What is this crap, you may wonder? For probably non-existant use cases Jansson was added to write JSON info,
* but external libs are a pain to maintain. For now this glorified string joiner replaces it.
*
* On incorrect usage or small buf it'll create invalid JSON because who cares, try-parse-catch as usual.
*/
#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#define VJSON_STACK_MAX 16
typedef struct {
char* buf;
int buf_len;
char* bufp;
int buf_left;
bool stack[VJSON_STACK_MAX];
int stack_pos;
bool is_last_key;
} vjson_t;
static void vjson_init(vjson_t* j, char* buf, int buf_len) {
j->buf = buf;
j->buf_len = buf_len;
j->bufp = buf;
j->buf_left = buf_len;
}
static void vjson_raw(vjson_t* j, const char* str) {
if (!str)
str = "null";
int done = snprintf(j->bufp, j->buf_left, "%s", str);
j->bufp += done;
j->buf_left -= done;
}
static void vjson_comma_(vjson_t* j) {
if (j->stack[j->stack_pos] && !j->is_last_key) {
vjson_raw(j, ",");
}
j->stack[j->stack_pos] = true;
j->is_last_key = false;
}
static void vjson_open_(vjson_t* j, const char* str) {
vjson_comma_(j);
vjson_raw(j, str);
if (j->stack_pos + 1 >= VJSON_STACK_MAX)
return;
j->stack_pos++;
j->stack[j->stack_pos] = false;
}
static void vjson_close_(vjson_t* j, const char* str) {
//vjson_comma_(j);
vjson_raw(j, str);
if (j->stack_pos - 1 <= 0)
return;
j->stack[j->stack_pos] = false;
j->stack_pos--;
}
static void vjson_arr_open(vjson_t* j) {
vjson_open_(j, "[");
}
static void vjson_arr_close(vjson_t* j) {
vjson_close_(j, "]");
}
static void vjson_obj_open(vjson_t* j) {
vjson_open_(j, "{");
}
static void vjson_obj_close(vjson_t* j) {
vjson_close_(j, "}");
}
static void vjson_key(vjson_t* j, const char* key) {
vjson_comma_(j);
vjson_raw(j, "\"");
vjson_raw(j, key);
vjson_raw(j, "\":");
j->is_last_key = true;
}
static void vjson_str(vjson_t* j, const char* str) {
vjson_comma_(j);
if (!str || str[0] == '\0') {
vjson_raw(j, NULL);
}
else {
vjson_raw(j, "\"");
vjson_raw(j, str);
vjson_raw(j, "\"");
}
}
static void vjson_int(vjson_t* j, int64_t num) {
vjson_comma_(j);
char tmp[32] = {0};
snprintf(tmp, sizeof(tmp), "%" PRId64, num);
vjson_raw(j, tmp);
}
#if 0
static void vjson_dbl(vjson_t* j, double num) {
vjson_comma_(j);
char tmp[32] = {0};
snprintf(tmp, sizeof(tmp), "%f", num);
vjson_raw(j, tmp);
}
#endif
void vjson_null(vjson_t* j){
vjson_comma_(j);
vjson_raw(j, "null");
}
static void vjson_intnull(vjson_t* j, int64_t num) {
if (num == 0) {
vjson_str(j, NULL);
}
else {
vjson_int(j, num);
}
}
static void vjson_keystr(vjson_t* j, const char* key, const char* val) {
vjson_key(j, key);
vjson_str(j, val);
}
static void vjson_keyint(vjson_t* j, const char* key, int64_t val) {
vjson_key(j, key);
vjson_int(j, val);
}
static void vjson_keyintnull(vjson_t* j, const char* key, int64_t val) {
vjson_key(j, key);
vjson_intnull(j, val);
}
#endif

View File

@ -1,57 +0,0 @@
if(NOT WIN32 AND USE_JANSSON)
if(NOT JANSSON_PATH AND NOT EMSCRIPTEN)
find_package(PkgConfig REQUIRED)
pkg_check_modules(JANSSON jansson>=2.3)
if(JANSSON_FOUND)
set(JANSSON_SOURCE "(system)")
set(JANSSON_PKG libjansson)
link_directories(${JANSSON_LIBRARY_DIRS})
endif()
endif()
if(JANSSON_PATH OR EMSCRIPTEN OR NOT JANSSON_FOUND)
FetchDependency(JANSSON
DIR jansson
GIT_REPOSITORY https://github.com/akheron/jansson
GIT_TAG v2.14
)
if(JANSSON_PATH)
set(JANSSON_LINK_PATH ${JANSSON_BIN}/src/.libs/libjansson.a)
set(JANSSON_INCLUDE_DIRS ${JANSSON_PATH}/src)
if(NOT EXISTS ${JANSSON_PATH}/configure)
add_custom_target(JANSSON_AUTORECONF
COMMAND autoreconf -iv
BYPRODUCTS ${JANSSON_PATH}/configure
WORKING_DIRECTORY ${JANSSON_PATH}
)
endif()
file(MAKE_DIRECTORY ${JANSSON_BIN})
add_custom_target(JANSSON_CONFIGURE
COMMAND "${JANSSON_PATH}/configure" --enable-static --disable-shared CC="${CMAKE_C_COMPILER}" AR="${CMAKE_AR}" RANLIB="${CMAKE_RANLIB}"
DEPENDS ${JANSSON_PATH}/configure
BYPRODUCTS ${JANSSON_BIN}/Makefile
WORKING_DIRECTORY ${JANSSON_BIN}
)
add_custom_target(JANSSON_MAKE
COMMAND make
DEPENDS ${JANSSON_BIN}/Makefile
BYPRODUCTS ${JANSSON_LINK_PATH} ${JANSSON_BIN}
WORKING_DIRECTORY ${JANSSON_BIN}
)
add_library(jansson STATIC IMPORTED)
if(NOT EXISTS ${JANSSON_LINK_PATH})
add_dependencies(jansson JANSSON_MAKE)
endif()
set_target_properties(jansson PROPERTIES
IMPORTED_LOCATION ${JANSSON_LINK_PATH}
)
endif()
endif()
endif()
if(NOT USE_JANSSON)
unset(JANSSON_SOURCE)
endif()

View File

@ -62,11 +62,6 @@ PKG_CHECK_MODULES(AO, [ao >= 1.1.0], have_libao=yes,
[AC_MSG_WARN([Cannot find libao - will not build vgmstream123])])
AM_CONDITIONAL(HAVE_LIBAO, test "$have_libao" = yes)
have_jansson=no
PKG_CHECK_MODULES(JANSSON, [jansson >= 2.3], have_jansson=yes,
[AC_MSG_WARN([Cannot find jansson - will disable JSON dumping in CLI])])
AM_CONDITIONAL(HAVE_JANSSON, test "$have_jansson" = yes)
if test "_$GCC" = _yes
then
CFLAGS="$CFLAGS -Wall -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-but-set-variable"

View File

@ -84,8 +84,6 @@ sudo apt-get install -y libmpg123-dev libvorbis-dev libspeex-dev
sudo apt-get install -y libavformat-dev libavcodec-dev libavutil-dev libswresample-dev
# for vgmstream123 and audacious
sudo apt-get install -y libao-dev audacious-dev
# for JSON dumping
sudo apt-get install -y libjansson-dev
# for static builds
sudo apt-get install -y yasm libopus-dev
# actual cmake
@ -138,8 +136,6 @@ The following option is currently only available for **Windows**:
The following option is only available for **\*nix-based OSes**:
- **USE_JANSSON**: Chooses if you wish to use libjansson for support of JSON dumping capabilities. The default is `ON`.
#### Build Options
All of these options are of type BOOL and can be set to either `ON` or `OFF`. Example usage: `cmake .. -DBUILD_CLI=ON`

View File

@ -1,386 +0,0 @@
/*
* Copyright (c) 2009-2016 Petri Lehtinen <petri@digip.org>
*
* Jansson is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#ifndef JANSSON_H
#define JANSSON_H
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h> /* for size_t */
#include "jansson_config.h"
#ifdef __cplusplus
extern "C" {
#endif
/* version */
#define JANSSON_MAJOR_VERSION 2
#define JANSSON_MINOR_VERSION 13
#define JANSSON_MICRO_VERSION 1
/* Micro version is omitted if it's 0 */
#define JANSSON_VERSION "2.13.1"
/* Version as a 3-byte hex number, e.g. 0x010201 == 1.2.1. Use this
for numeric comparisons, e.g. #if JANSSON_VERSION_HEX >= ... */
#define JANSSON_VERSION_HEX \
((JANSSON_MAJOR_VERSION << 16) | (JANSSON_MINOR_VERSION << 8) | \
(JANSSON_MICRO_VERSION << 0))
/* If __atomic or __sync builtins are available the library is thread
* safe for all read-only functions plus reference counting. */
#if JSON_HAVE_ATOMIC_BUILTINS || JSON_HAVE_SYNC_BUILTINS
#define JANSSON_THREAD_SAFE_REFCOUNT 1
#endif
#if defined(__GNUC__) || defined(__clang__)
#define JANSSON_ATTRS(x) __attribute__(x)
#else
#define JANSSON_ATTRS(x)
#endif
/* types */
typedef enum {
JSON_OBJECT,
JSON_ARRAY,
JSON_STRING,
JSON_INTEGER,
JSON_REAL,
JSON_TRUE,
JSON_FALSE,
JSON_NULL
} json_type;
typedef struct json_t {
json_type type;
volatile size_t refcount;
} json_t;
#ifndef JANSSON_USING_CMAKE /* disabled if using cmake */
#if JSON_INTEGER_IS_LONG_LONG
#ifdef _WIN32
#define JSON_INTEGER_FORMAT "I64d"
#else
#define JSON_INTEGER_FORMAT "lld"
#endif
typedef long long json_int_t;
#else
#define JSON_INTEGER_FORMAT "ld"
typedef long json_int_t;
#endif /* JSON_INTEGER_IS_LONG_LONG */
#endif
#define json_typeof(json) ((json)->type)
#define json_is_object(json) ((json) && json_typeof(json) == JSON_OBJECT)
#define json_is_array(json) ((json) && json_typeof(json) == JSON_ARRAY)
#define json_is_string(json) ((json) && json_typeof(json) == JSON_STRING)
#define json_is_integer(json) ((json) && json_typeof(json) == JSON_INTEGER)
#define json_is_real(json) ((json) && json_typeof(json) == JSON_REAL)
#define json_is_number(json) (json_is_integer(json) || json_is_real(json))
#define json_is_true(json) ((json) && json_typeof(json) == JSON_TRUE)
#define json_is_false(json) ((json) && json_typeof(json) == JSON_FALSE)
#define json_boolean_value json_is_true
#define json_is_boolean(json) (json_is_true(json) || json_is_false(json))
#define json_is_null(json) ((json) && json_typeof(json) == JSON_NULL)
/* construction, destruction, reference counting */
json_t *json_object(void);
json_t *json_array(void);
json_t *json_string(const char *value);
json_t *json_stringn(const char *value, size_t len);
json_t *json_string_nocheck(const char *value);
json_t *json_stringn_nocheck(const char *value, size_t len);
json_t *json_integer(json_int_t value);
json_t *json_real(double value);
json_t *json_true(void);
json_t *json_false(void);
#define json_boolean(val) ((val) ? json_true() : json_false())
json_t *json_null(void);
/* do not call JSON_INTERNAL_INCREF or JSON_INTERNAL_DECREF directly */
#if JSON_HAVE_ATOMIC_BUILTINS
#define JSON_INTERNAL_INCREF(json) \
__atomic_add_fetch(&json->refcount, 1, __ATOMIC_ACQUIRE)
#define JSON_INTERNAL_DECREF(json) \
__atomic_sub_fetch(&json->refcount, 1, __ATOMIC_RELEASE)
#elif JSON_HAVE_SYNC_BUILTINS
#define JSON_INTERNAL_INCREF(json) __sync_add_and_fetch(&json->refcount, 1)
#define JSON_INTERNAL_DECREF(json) __sync_sub_and_fetch(&json->refcount, 1)
#else
#define JSON_INTERNAL_INCREF(json) (++json->refcount)
#define JSON_INTERNAL_DECREF(json) (--json->refcount)
#endif
static JSON_INLINE json_t *json_incref(json_t *json) {
if (json && json->refcount != (size_t)-1)
JSON_INTERNAL_INCREF(json);
return json;
}
/* do not call json_delete directly */
void json_delete(json_t *json);
static JSON_INLINE void json_decref(json_t *json) {
if (json && json->refcount != (size_t)-1 && JSON_INTERNAL_DECREF(json) == 0)
json_delete(json);
}
#if defined(__GNUC__) || defined(__clang__)
static JSON_INLINE void json_decrefp(json_t **json) {
if (json) {
json_decref(*json);
*json = NULL;
}
}
#define json_auto_t json_t __attribute__((cleanup(json_decrefp)))
#endif
/* error reporting */
#define JSON_ERROR_TEXT_LENGTH 160
#define JSON_ERROR_SOURCE_LENGTH 80
typedef struct json_error_t {
int line;
int column;
int position;
char source[JSON_ERROR_SOURCE_LENGTH];
char text[JSON_ERROR_TEXT_LENGTH];
} json_error_t;
enum json_error_code {
json_error_unknown,
json_error_out_of_memory,
json_error_stack_overflow,
json_error_cannot_open_file,
json_error_invalid_argument,
json_error_invalid_utf8,
json_error_premature_end_of_input,
json_error_end_of_input_expected,
json_error_invalid_syntax,
json_error_invalid_format,
json_error_wrong_type,
json_error_null_character,
json_error_null_value,
json_error_null_byte_in_key,
json_error_duplicate_key,
json_error_numeric_overflow,
json_error_item_not_found,
json_error_index_out_of_range
};
static JSON_INLINE enum json_error_code json_error_code(const json_error_t *e) {
return (enum json_error_code)e->text[JSON_ERROR_TEXT_LENGTH - 1];
}
/* getters, setters, manipulation */
void json_object_seed(size_t seed);
size_t json_object_size(const json_t *object);
json_t *json_object_get(const json_t *object, const char *key)
JANSSON_ATTRS((warn_unused_result));
int json_object_set_new(json_t *object, const char *key, json_t *value);
int json_object_set_new_nocheck(json_t *object, const char *key, json_t *value);
int json_object_del(json_t *object, const char *key);
int json_object_clear(json_t *object);
int json_object_update(json_t *object, json_t *other);
int json_object_update_existing(json_t *object, json_t *other);
int json_object_update_missing(json_t *object, json_t *other);
int json_object_update_recursive(json_t *object, json_t *other);
void *json_object_iter(json_t *object);
void *json_object_iter_at(json_t *object, const char *key);
void *json_object_key_to_iter(const char *key);
void *json_object_iter_next(json_t *object, void *iter);
const char *json_object_iter_key(void *iter);
json_t *json_object_iter_value(void *iter);
int json_object_iter_set_new(json_t *object, void *iter, json_t *value);
#define json_object_foreach(object, key, value) \
for (key = json_object_iter_key(json_object_iter(object)); \
key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
key = json_object_iter_key( \
json_object_iter_next(object, json_object_key_to_iter(key))))
#define json_object_foreach_safe(object, n, key, value) \
for (key = json_object_iter_key(json_object_iter(object)), \
n = json_object_iter_next(object, json_object_key_to_iter(key)); \
key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
key = json_object_iter_key(n), \
n = json_object_iter_next(object, json_object_key_to_iter(key)))
#define json_array_foreach(array, index, value) \
for (index = 0; \
index < json_array_size(array) && (value = json_array_get(array, index)); \
index++)
static JSON_INLINE int json_object_set(json_t *object, const char *key, json_t *value) {
return json_object_set_new(object, key, json_incref(value));
}
static JSON_INLINE int json_object_set_nocheck(json_t *object, const char *key,
json_t *value) {
return json_object_set_new_nocheck(object, key, json_incref(value));
}
static JSON_INLINE int json_object_iter_set(json_t *object, void *iter, json_t *value) {
return json_object_iter_set_new(object, iter, json_incref(value));
}
static JSON_INLINE int json_object_update_new(json_t *object, json_t *other) {
int ret = json_object_update(object, other);
json_decref(other);
return ret;
}
static JSON_INLINE int json_object_update_existing_new(json_t *object, json_t *other) {
int ret = json_object_update_existing(object, other);
json_decref(other);
return ret;
}
static JSON_INLINE int json_object_update_missing_new(json_t *object, json_t *other) {
int ret = json_object_update_missing(object, other);
json_decref(other);
return ret;
}
size_t json_array_size(const json_t *array);
json_t *json_array_get(const json_t *array, size_t index)
JANSSON_ATTRS((warn_unused_result));
int json_array_set_new(json_t *array, size_t index, json_t *value);
int json_array_append_new(json_t *array, json_t *value);
int json_array_insert_new(json_t *array, size_t index, json_t *value);
int json_array_remove(json_t *array, size_t index);
int json_array_clear(json_t *array);
int json_array_extend(json_t *array, json_t *other);
static JSON_INLINE int json_array_set(json_t *array, size_t ind, json_t *value) {
return json_array_set_new(array, ind, json_incref(value));
}
static JSON_INLINE int json_array_append(json_t *array, json_t *value) {
return json_array_append_new(array, json_incref(value));
}
static JSON_INLINE int json_array_insert(json_t *array, size_t ind, json_t *value) {
return json_array_insert_new(array, ind, json_incref(value));
}
const char *json_string_value(const json_t *string);
size_t json_string_length(const json_t *string);
json_int_t json_integer_value(const json_t *integer);
double json_real_value(const json_t *real);
double json_number_value(const json_t *json);
int json_string_set(json_t *string, const char *value);
int json_string_setn(json_t *string, const char *value, size_t len);
int json_string_set_nocheck(json_t *string, const char *value);
int json_string_setn_nocheck(json_t *string, const char *value, size_t len);
int json_integer_set(json_t *integer, json_int_t value);
int json_real_set(json_t *real, double value);
/* pack, unpack */
json_t *json_pack(const char *fmt, ...) JANSSON_ATTRS((warn_unused_result));
json_t *json_pack_ex(json_error_t *error, size_t flags, const char *fmt, ...)
JANSSON_ATTRS((warn_unused_result));
json_t *json_vpack_ex(json_error_t *error, size_t flags, const char *fmt, va_list ap)
JANSSON_ATTRS((warn_unused_result));
#define JSON_VALIDATE_ONLY 0x1
#define JSON_STRICT 0x2
int json_unpack(json_t *root, const char *fmt, ...);
int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...);
int json_vunpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt,
va_list ap);
/* sprintf */
json_t *json_sprintf(const char *fmt, ...)
JANSSON_ATTRS((warn_unused_result, format(printf, 1, 2)));
json_t *json_vsprintf(const char *fmt, va_list ap)
JANSSON_ATTRS((warn_unused_result, format(printf, 1, 0)));
/* equality */
int json_equal(const json_t *value1, const json_t *value2);
/* copying */
json_t *json_copy(json_t *value) JANSSON_ATTRS((warn_unused_result));
json_t *json_deep_copy(const json_t *value) JANSSON_ATTRS((warn_unused_result));
/* decoding */
#define JSON_REJECT_DUPLICATES 0x1
#define JSON_DISABLE_EOF_CHECK 0x2
#define JSON_DECODE_ANY 0x4
#define JSON_DECODE_INT_AS_REAL 0x8
#define JSON_ALLOW_NUL 0x10
typedef size_t (*json_load_callback_t)(void *buffer, size_t buflen, void *data);
json_t *json_loads(const char *input, size_t flags, json_error_t *error)
JANSSON_ATTRS((warn_unused_result));
json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error)
JANSSON_ATTRS((warn_unused_result));
json_t *json_loadf(FILE *input, size_t flags, json_error_t *error)
JANSSON_ATTRS((warn_unused_result));
json_t *json_loadfd(int input, size_t flags, json_error_t *error)
JANSSON_ATTRS((warn_unused_result));
json_t *json_load_file(const char *path, size_t flags, json_error_t *error)
JANSSON_ATTRS((warn_unused_result));
json_t *json_load_callback(json_load_callback_t callback, void *data, size_t flags,
json_error_t *error) JANSSON_ATTRS((warn_unused_result));
/* encoding */
#define JSON_MAX_INDENT 0x1F
#define JSON_INDENT(n) ((n)&JSON_MAX_INDENT)
#define JSON_COMPACT 0x20
#define JSON_ENSURE_ASCII 0x40
#define JSON_SORT_KEYS 0x80
#define JSON_PRESERVE_ORDER 0x100
#define JSON_ENCODE_ANY 0x200
#define JSON_ESCAPE_SLASH 0x400
#define JSON_REAL_PRECISION(n) (((n)&0x1F) << 11)
#define JSON_EMBED 0x10000
typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data);
char *json_dumps(const json_t *json, size_t flags) JANSSON_ATTRS((warn_unused_result));
size_t json_dumpb(const json_t *json, char *buffer, size_t size, size_t flags);
int json_dumpf(const json_t *json, FILE *output, size_t flags);
int json_dumpfd(const json_t *json, int output, size_t flags);
int json_dump_file(const json_t *json, const char *path, size_t flags);
int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data,
size_t flags);
/* custom memory allocation */
typedef void *(*json_malloc_t)(size_t);
typedef void (*json_free_t)(void *);
void json_set_alloc_funcs(json_malloc_t malloc_fn, json_free_t free_fn);
void json_get_alloc_funcs(json_malloc_t *malloc_fn, json_free_t *free_fn);
/* runtime version checking */
const char *jansson_version_str(void);
int jansson_version_cmp(int major, int minor, int micro);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,55 +0,0 @@
/*
* Copyright (c) 2010-2016 Petri Lehtinen <petri@digip.org>
*
* Jansson is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*
*
* This file specifies a part of the site-specific configuration for
* Jansson, namely those things that affect the public API in
* jansson.h.
*
* The CMake system will generate the jansson_config.h file and
* copy it to the build and install directories.
*/
#ifndef JANSSON_CONFIG_H
#define JANSSON_CONFIG_H
/* Define this so that we can disable scattered automake configuration in source files */
#ifndef JANSSON_USING_CMAKE
#define JANSSON_USING_CMAKE
#endif
/* If your compiler supports the inline keyword in C, JSON_INLINE is
defined to `inline', otherwise empty. In C++, the inline is always
supported. */
#if defined(_MSC_VER) && (_MSC_VER < 1800) && !defined(__cplusplus)
#define JSON_INLINE __inline
#else
#define JSON_INLINE inline
#endif
#define json_int_t long
#define json_strtoint strtol
#define JSON_INTEGER_FORMAT "ld"
/* If locale.h and localeconv() are available, define to 1, otherwise to 0. */
#define JSON_HAVE_LOCALECONV 1
/* If __atomic builtins are available they will be used to manage
reference counts of json_t. */
#define JSON_HAVE_ATOMIC_BUILTINS 0
/* If __atomic builtins are not available we try using __sync builtins
to manage reference counts of json_t. */
#define JSON_HAVE_SYNC_BUILTINS 0
/* Maximum recursion depth for parsing JSON input.
This limits the depth of e.g. array-within-array constructions. */
#define JSON_PARSER_MAX_DEPTH 2048
#endif

View File

@ -94,11 +94,6 @@
<Command>lib /def:libspeex-1.def /machine:x86 /out:libspeex-1.lib</Command>
<Outputs>libspeex-1.lib;libspeex-1.exp;%(Outputs)</Outputs>
</CustomBuild>
<CustomBuild Include="jansson.def">
<Message>Building library stub</Message>
<Command>lib /def:jansson.def /machine:x86 /out:jansson.lib</Command>
<Outputs>jansson.lib;jansson.exp;%(Outputs)</Outputs>
</CustomBuild>
</ItemGroup>
<ItemGroup Condition="'$(Platform)'=='x64'">
<CustomBuild Include="libmpg123-0.def">

View File

@ -1,78 +0,0 @@
EXPORTS
json_delete
json_true
json_false
json_null
json_sprintf
json_vsprintf
json_string
json_stringn
json_string_nocheck
json_stringn_nocheck
json_string_value
json_string_length
json_string_set
json_string_setn
json_string_set_nocheck
json_string_setn_nocheck
json_integer
json_integer_value
json_integer_set
json_real
json_real_value
json_real_set
json_number_value
json_array
json_array_size
json_array_get
json_array_set_new
json_array_append_new
json_array_insert_new
json_array_remove
json_array_clear
json_array_extend
json_object
json_object_size
json_object_get
json_object_set_new
json_object_set_new_nocheck
json_object_del
json_object_clear
json_object_update
json_object_update_existing
json_object_update_missing
json_object_update_recursive
json_object_iter
json_object_iter_at
json_object_iter_next
json_object_iter_key
json_object_iter_value
json_object_iter_set_new
json_object_key_to_iter
json_object_seed
json_dumps
json_dumpb
json_dumpf
json_dumpfd
json_dump_file
json_dump_callback
json_loads
json_loadb
json_loadf
json_loadfd
json_load_file
json_load_callback
json_equal
json_copy
json_deep_copy
json_pack
json_pack_ex
json_vpack_ex
json_unpack
json_unpack_ex
json_vunpack_ex
json_set_alloc_funcs
json_get_alloc_funcs
jansson_version_str
jansson_version_cmp

Binary file not shown.

View File

@ -11,7 +11,6 @@ sudo apt-get install libavformat-dev libavcodec-dev libavutil-dev libswresample-
sudo apt-get install yasm libopus-dev
# optional: for vgmstream 123 and audacious
sudo apt-get install -y libao-dev audacious-dev
#sudo apt-get install libjansson-dev
mkdir -p build
cd build

View File

@ -228,7 +228,7 @@ $fb2kFiles64 = @(
)
$fb2kFiles_remove = @(
"bin/foobar2000/jansson.dll"
"bin/foobar2000/xxxx.dll"
)
$cliPdbFiles32 = @(

View File

@ -1,59 +1,4 @@
#ifndef _API_H_
#define _API_H_
#include "base/plugins.h" //TODO: to be removed
//#define LIBVGMSTREAM_ENABLE 1
#if LIBVGMSTREAM_ENABLE
/* vgmstream's public API
*
* By default vgmstream behaves like a simple decoder (extract samples until stream end), but you can configure it
* to loop N times or even downmix. In other words, it also behaves a bit like a player.
*
* It exposes multiple options and convenience functions beyond simple decoding mainly for various plugins,
* since it was faster moving shared behavior to core rather than reimplementing every time.
*
* All this may make the API a bit twisted and coupled (sorry, tried my best), probably will improve later. Probably.
*
* Notes:
* - vgmstream may dynamically allocate stuff as needed (not too much beyond some setup buffers, but varies per format)
* - previously the only way to use vgmstream was accesing its internals. Now there is an API internals may change in the future
* - some details described in the API may not happen at the moment (they are defined for future internal changes)
* - main reason it uses the slighly long-winded libvgmstream_* names is that internals use the vgmstream_* 'namespace'
* - c-strings should be in UTF-8
* - the API is still WIP and may be slightly buggy overall due to lack of time, to be improved later
* - vgmstream's features are mostly stable, but this API may be tweaked from time to time (check API_VERSION)
*
* Basic usage (also see api_example.c):
* - libvgmstream_init(...) // base context
* - libvgmstream_setup(...) // config if needed
* - libvgmstream_open(...) // setup format
* - libvgmstream_play(...) // main decode
* - output samples + repeat libvgmstream_play until stream is done
* - libvgmstream_free(...) // cleanup
*/
#include <stdint.h>
#include <stdbool.h>
/* standard C param call and name mangling (to avoid __stdcall / .defs) */
//#define LIBVGMSTREAM_CALL __cdecl //needed?
//LIBVGMSTREAM_API (type) LIBVGMSTREAM_CALL libvgmstream_function(...);
/* define external function behavior (during compilation) */
#if defined(LIBVGMSTREAM_EXPORT)
#define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */
#elif defined(LIBVGMSTREAM_IMPORT)
#define LIBVGMSTREAM_API __declspec(dllimport) /* when importing/linking vgmstream DLL */
#else
#define LIBVGMSTREAM_API /* nothing, internal/default */
#endif
#include "api_version.h"
#include "api_decode.h"
#include "api_helpers.h"
#include "api_streamfile.h"
#include "api_tags.h"
#endif
#endif

View File

@ -1,202 +0,0 @@
#ifndef _API_DECODE_H_
#define _API_DECODE_H_
#include "api.h"
#if LIBVGMSTREAM_ENABLE
#include "api_streamfile.h"
/* vgmstream's main (decode) API.
*/
/* interleaved samples: buf[0]=ch0, buf[1]=ch1, buf[2]=ch0, buf[3]=ch0, ... */
typedef enum {
LIBVGMSTREAM_SAMPLE_PCM16 = 0x01,
LIBVGMSTREAM_SAMPLE_PCM24 = 0x02,
LIBVGMSTREAM_SAMPLE_PCM32 = 0x03,
LIBVGMSTREAM_SAMPLE_FLOAT = 0x04,
} libvgmstream_sample_t;
/* current song info, may be copied around (values are info-only) */
typedef struct {
/* main (always set) */
libvgmstream_sample_t sample_type; // output buffer's sample type
int channels; // output channels
int sample_rate; // output sample rate
int sample_size; // derived from sample_type (pcm16=2, float=4, etc)
/* extra info (may be 0 if not known or not relevant) */
uint32_t channel_layout; // standard WAVE bitflags
int subsong_index; // 0 = none, N = loaded subsong N
int subsong_count; // 0 = format has no concept of subsongs, N = has N subsongs (1 = format has subsongs, and only 1)
int input_channels; // original file's channels before downmixing (if any)
//int interleave; // when file is interleaved
//int interleave_first; // when file is interleaved
//int interleave_last; // when file is interleaved
//int frame_size; // when file has some configurable frame size
/* sample info (may not be used depending on config) */
int64_t sample_count; // file's max samples (not final play duration)
int64_t loop_start; // loop start sample
int64_t loop_end; // loop end sample
bool loop_flag; // if file loops; note that false + defined loops means looping was forcefully disabled
bool play_forever; // if file loops forever based on current config (meaning _play never stops)
int64_t play_samples; // totals after all calculations (after applying loop/fade/etc config)
// ** may not be 100% accurate in some cases (must check decoder's 'done' flag rather than this count)
// ** if play_forever is set this is still provided for reference based on non-forever config
int stream_bitrate; // average bitrate of the subsong (slightly bloated vs codec_bitrate; incorrect in rare cases)
//int codec_bitrate; // average bitrate of the codec data
// ** not possible / slow to calculate in most cases
/* descriptions */
char codec_name[128]; //
char layout_name[128]; //
char meta_name[128]; // (not internal "tag" metadata)
char stream_name[256]; // some internal name or representation, not always useful
// ** these are a bit big for a struct, but the typical use case of vgsmtream is opening a file > immediately
// query description and since libvgmstream returns its own copy it shouldn't be too much of a problem
// ** (may be separated later)
/* misc */
//bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior (do not use to export)
int format_internal_id; // may be used when reopening subfiles or similar formats without checking other possible formats first
// ** this value WILL change without warning between vgmstream versions/commits
} libvgmstream_format_t;
typedef struct {
void* buf; // current decoded buf (valid after _decode until next call; may change between calls)
int buf_samples; // current buffer samples (0 is possible in some cases, meaning current _decode can't generate samples)
int buf_bytes; // current buffer bytes (channels * sample_size * samples)
bool done; // flag when stream is done playing based on config; will still allow _play calls returning blank samples
// ** note that with play_forever this flag is never set
} libvgmstream_decoder_t;
/* vgmstream context/handle */
typedef struct {
void* priv; // internal data
/* pointers for easier ABI compatibility */
const libvgmstream_format_t* format; // current song info, updated on _open
libvgmstream_decoder_t* decoder; // updated on each _decode call
} libvgmstream_t;
/* inits the vgmstream context
* - returns NULL on error
* - call libvgmstream_free when done.
*/
LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void);
/* frees vgmstream context and any other internal stuff that may not be closed
*/
LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib);
/* configures how vgmstream behaves internally when playing a file */
typedef struct {
bool disable_config_override; // ignore forced (TXTP) config
bool allow_play_forever; // must allow manually as some cases a TXTP may set loop forever but client may not handle it (ex. wave dumpers)
bool play_forever; // keeps looping forever (file must have loop_flag set)
bool ignore_loop; // ignores loops points
bool force_loop; // enables full loops (0..samples) if file doesn't have loop points
bool really_force_loop; // forces full loops (0..samples) even if file has loop points
bool ignore_fade; // don't fade after N loops and play remaning stream (for files with outros)
double loop_count; // target loops (values like 1.5 are ok)
double fade_time; // fade period after target loops
double fade_delay; // fade delay after target loops
int auto_downmix_channels; // downmixing if vgmstream's channels are higher than value
// ** for players that can only handle N channels, but this type of downmixing is very simplistic and not recommended
bool force_pcm16; // forces output buffer to be remixed into PCM16
} libvgmstream_config_t;
/* pass default config, that will be applied to song on open
* - invalid config or complex cases (ex. some TXTP) may ignore these settings.
* - called without a song loaded (before _open or after _close), otherwise ignored.
* - without config vgmstream will decode the current stream once
*/
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg);
/* configures how vgmstream opens the format */
typedef struct {
libvgmstream_streamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream
// ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed
int subsong; // target subsong (1..N) or 0 = default/first
// ** to check if a file has subsongs, _open first + check format->total_subsongs (then _open 2nd, 3rd, etc)
int format_internal_id; // force a format (for example when loading new subsong of the same archive)
int stereo_track; // forces vgmstream to decode one 2ch+2ch+2ch... 'track' and discard other channels, where 0 = disabled, 1..N = Nth track
} libvgmstream_options_t;
/* opens file based on config and prepares it to play if supported.
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
* - will close currently loaded song if needed
*/
LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* open_options);
#if 0
/* opens file based on config and returns its file info
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
* - equivalent to _open, but doesn't update the current loaded song / format and copies to passed struct
* - may be used while current song is loaded/playing but you need to query next song with current config
* - to play a new song don't call _open_info to check the format first, just call _open + check format afterwards
* - in many cases the only way for vgmstream to get a file's format info is making it almost ready to play,
* so this isn't any faster than _open
*/
LIBVGMSTREAM_API int libvgmstream_open_info(libvgmstream_t* lib, libvgmstream_options_t* options, libvgmstream_format_t* format);
#endif
/* closes current song; may still use libvgmstream to open other songs
*/
LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib);
/* decodes next batch of samples
* - vgmstream supplies its own buffer, updated on lib->decoder->* values (may change between calls)
* - returns < 0 on error
*/
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib);
/* Same as _play, but fills some external buffer (also updates lib->decoder->* values)
* - returns < 0 on error, or N = number of filled samples.
* - buf must be at least as big as channels * sample_size * buf_samples
* - needs copying around from internal bufs so may be slightly slower; mainly for cases when you have buf constraints
*/
LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples);
/* Gets current position within the song.
* - return < 0 on error (file not ready)
*/
LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib);
/* Seeks to absolute position. Will clamp incorrect values such as seeking before/past playable length.
* - on play_forever may seek to any position
*/
LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample);
/* Reset current song
*/
LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib);
#endif
#endif

View File

@ -1,81 +0,0 @@
#ifndef _API_HELPERS_H_
#define _API_HELPERS_H_
#include "api.h"
#if LIBVGMSTREAM_ENABLE
/* vgmstream's helper stuff, for plugins.
*/
typedef enum {
LIBVGMSTREAM_LOG_LEVEL_ALL = 0,
LIBVGMSTREAM_LOG_LEVEL_DEBUG = 20,
LIBVGMSTREAM_LOG_LEVEL_INFO = 30,
LIBVGMSTREAM_LOG_LEVEL_NONE = 100,
} libvgmstream_loglevel_t;
typedef struct {
libvgmstream_loglevel_t level; // log level
void (*callback)(int level, const char* str); // log callback
bool stdout_callback; // use default log callback rather than user supplied
} libvgmstream_log_t;
/* defines a global log callback, as vgmstream sometimes communicates format issues to the user.
* - note that log is currently set globally rather than per libvgmstream_t
*/
LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg);
/* Returns a list of supported extensions (WARNING: it's pretty big), such as "adx", "dsp", etc.
* Mainly for plugins that want to know which extensions are supported.
* - returns NULL if no size is provided
*/
LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size);
/* Same as above, buf returns a list what vgmstream considers "common" formats (such as "wav", "ogg"),
* which usually one doesn't want to associate to vgmstream.
* - returns NULL if no size is provided
*/
LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size);
typedef struct {
bool is_extension; /* set if filename is just an extension */
bool skip_default; /* set if shouldn't check default formats */
bool reject_extensionless; /* set if player can't play extensionless files */
bool accept_unknown; /* set to allow any extension (for txth) */
bool accept_common; /* set to allow known-but-common extension (when player has plugin priority) */
} libvgmstream_valid_t;
/* returns if vgmstream can parse a filename by extension, to reject some files earlier
* - doesn't check file contents (that's only done on _open)
* - config may be NULL
* - mainly for plugins that fail early; libvgmstream doesn't use this
*/
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg);
typedef struct {
bool force_title; // TODO: what was this for?
bool subsong_range; // print a range of possible subsongs after title 'filename#1~N'
bool remove_extension; // remove extension from passed filename
bool remove_archive; // remove '(archive)|(subfile)' format of some plugins
const char* filename; // base file's filename
// ** note that sometimes vgmstream doesn't have/know the original name, so it's needed again here
} libvgmstream_title_t;
/* get a simple title for plugins, derived from internal stream name if available
* - valid after _open
*/
LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len);
/* Writes a description of the current song into dst. Will always be null-terminated.
* - returns < 0 if file was truncated, though will still succeed.
*/
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size);
#endif
#endif

View File

@ -1,45 +0,0 @@
#ifndef _API_TAGS_H_
#define _API_TAGS_H_
#include "api.h"
#if LIBVGMSTREAM_ENABLE
/* vgmstream's !tags.m3u API.
* Doesn't need a main libvgmstream as tags aren't tied to loaded songs.
*
* Meant to be a simple implementation; feel free to ignore and roll your own
* (or use another external tags plugin).
*/
/* tag state */
typedef struct {
void* priv; // internal data
const char* key; // current key
const char* val; // current value
} libvgmstream_tags_t;
/* Initializes tags.
* - libsf should point to a !tags.m3u file
* - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process.
*/
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf);
/* Finds tags for a new filename. Must be called first before extracting tags.
*/
LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename);
/* Extracts next valid tag in tagfile to key/val.
* - returns false if no more tags are found (meant to be called repeatedly until false)
* - key/values are trimmed of beginning/end whitespaces and values are in UTF-8
*/
LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags);
/* Closes tags. */
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags);
#endif
#endif

View File

@ -1,25 +0,0 @@
#ifndef _API_VERSION_H_
#define _API_VERSION_H_
#include "api.h"
#if LIBVGMSTREAM_ENABLE
/* Current API version.
* - only refers to the API itself, as changes related to formats/etc don't alter this (since they are usually additive)
* - vgmstream's features are mostly stable, but this API may be tweaked from time to time
*/
#define LIBVGMSTREAM_API_VERSION_MAJOR 1 // breaking API/ABI changes
#define LIBVGMSTREAM_API_VERSION_MINOR 0 // compatible API/ABI changes
#define LIBVGMSTREAM_API_VERSION_PATCH 0 // fixes
/* returns API version in hex format: 0xMMmmpppp = MM-major, mm-minor, pppp-patch
* - use when loading vgmstream as a dynamic library to ensure API/ABI compatibility
*/
LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void);
/* CHANGELOG:
*
* - 1.0.0: initial version
*/
#endif
#endif

View File

@ -9,9 +9,9 @@ static void load_vgmstream(libvgmstream_priv_t* priv, libvgmstream_options_t* op
if (!sf_api)
return;
//TODO: handle format_internal_id
//TODO: handle internal format_id
sf_api->stream_index = opt->subsong;
sf_api->stream_index = opt->subsong_index;
priv->vgmstream = init_vgmstream_from_STREAMFILE(sf_api);
close_streamfile(sf_api);
}
@ -77,7 +77,7 @@ static void update_format_info(libvgmstream_priv_t* priv) {
vgmstream_mixing_enable(v, 0, &fmt->input_channels, &fmt->channels);
fmt->channel_layout = v->channel_layout;
fmt->sample_count = v->num_samples;
fmt->stream_samples = v->num_samples;
fmt->loop_start = v->loop_start_sample;
fmt->loop_end = v->loop_end_sample;
fmt->loop_flag = v->loop_flag;
@ -85,6 +85,8 @@ static void update_format_info(libvgmstream_priv_t* priv) {
fmt->play_forever = priv->pos.play_forever;
fmt->play_samples = priv->pos.play_samples;
fmt->format_id = v->format_id;
fmt->stream_bitrate = get_vgmstream_average_bitrate(v);
get_vgmstream_coding_description(v, fmt->codec_name, sizeof(fmt->codec_name));
@ -94,18 +96,16 @@ static void update_format_info(libvgmstream_priv_t* priv) {
if (v->stream_name[0] != '\0') { //snprintf UB for NULL args
snprintf(fmt->stream_name, sizeof(fmt->stream_name), "%s", v->stream_name);
}
fmt->format_internal_id = 0; //TODO
}
LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* opt) {
LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_options_t* opt) {
if (!lib ||!lib->priv)
return LIBVGMSTREAM_ERROR_GENERIC;
if (!opt || !opt->libsf || opt->subsong < 0)
if (!opt || !opt->libsf || opt->subsong_index < 0)
return LIBVGMSTREAM_ERROR_GENERIC;
// close loaded song if any + reset
libvgmstream_close(lib);
libvgmstream_close_song(lib);
libvgmstream_priv_t* priv = lib->priv;
@ -123,7 +123,7 @@ LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options
}
LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib) {
LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib) {
if (!lib || !lib->priv)
return;

View File

@ -1,6 +1,7 @@
#ifndef _API_INTERNAL_H_
#define _API_INTERNAL_H_
#include "../api.h"
#include "../libvgmstream.h"
#include "../util/log.h"
#include "../vgmstream.h"
#include "plugins.h"

View File

@ -5,7 +5,7 @@
static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) {
static void copy_time(bool* dst_flag, int32_t* dst_time, double* dst_time_s, bool* src_flag, int32_t* src_time, double* src_time_s) {
if (!*src_flag)
return;
*dst_flag = 1;

View File

@ -188,7 +188,7 @@ void decode_seek(VGMSTREAM* vgmstream) {
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
if (vgmstream->coding_type == coding_MP4_AAC) {
seek_mp4_aac(vgmstream, vgmstream->loop_sample);
seek_mp4_aac(vgmstream, vgmstream->loop_current_sample);
}
#endif
@ -560,7 +560,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
return 0; /* ~100 (range), ~16 (DCT) */
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
case coding_MP4_AAC:
return ((mp4_aac_codec_data*)vgmstream->codec_data)->samples_per_frame;
return mp4_get_samples_per_frame(vgmstream->codec_data);
#endif
#ifdef VGM_USE_ATRAC9
case coding_ATRAC9:

View File

@ -330,8 +330,7 @@ static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* v
#endif
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
if (vgmstream->coding_type == coding_MP4_AAC) {
mp4_aac_codec_data *data = vgmstream->codec_data;
return data ? data->if_file.streamfile : NULL;
return mp4_aac_get_streamfile(vgmstream->codec_data);
}
#endif

View File

@ -2,6 +2,7 @@
#define _MIXING_H_
#include "../vgmstream.h"
#include "../util/log.h"
/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and
* outbuf must big enough to hold output_channels*samples_to_do */

View File

@ -10,6 +10,9 @@
#include <unistd.h>
#endif
// for testing purposes; generally slower since reads often aren't optimized for unbuffered IO
//#define DISABLE_BUFFER
/* Enables a minor optimization when reopening file descriptors.
* Some systems/compilers have issues though, and dupe'd FILEs may fread garbage data in rare cases,
* possibly due to underlying buffers that get shared/thrashed by dup(). Seen for example in some .HPS and Ubi
@ -101,6 +104,15 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size
if (/*!sf->infile ||*/ !dst || length <= 0 || offset < 0)
return 0;
#ifdef DISABLE_BUFFER
if (offset != sf->offset) {
fseek_v(sf->infile, offset, SEEK_SET);
}
read_total = fread(dst, sizeof(uint8_t), length, sf->infile);
sf->offset = offset + read_total;
return read_total;
#else
//;VGM_LOG("stdio: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size, sf->buf_size);
/* is the part of the requested length in the buffer? */
@ -183,6 +195,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size
sf->offset = offset; /* last fread offset */
return read_total;
#endif
}
static size_t stdio_get_size(STDIO_STREAMFILE* sf) {

View File

@ -4,6 +4,7 @@
#include "../vgmstream.h"
#include "../util/reader_sf.h"
#include "../util/reader_get_nibbles.h"
#include "../util/log.h"
//todo remove
#include "libs/clhca.h"
@ -560,10 +561,19 @@ void free_g719(g719_codec_data* data, int channels);
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
/* mp4_aac_decoder */
typedef struct mp4_aac_codec_data mp4_aac_codec_data;
mp4_aac_codec_data* init_mp4_aac(STREAMFILE* sf);
void decode_mp4_aac(mp4_aac_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels);
void reset_mp4_aac(VGMSTREAM* vgmstream);
void seek_mp4_aac(VGMSTREAM* vgmstream, int32_t num_sample);
void free_mp4_aac(mp4_aac_codec_data* data);
STREAMFILE* mp4_aac_get_streamfile(mp4_aac_codec_data* data);
int32_t mp4_aac_get_samples(mp4_aac_codec_data* data);
int32_t mp4_aac_get_samples_per_frame(mp4_aac_codec_data* data);
int mp4_aac_get_sample_rate(mp4_aac_codec_data* data);
int mp4_aac_get_channels(mp4_aac_codec_data* data);
#endif

View File

@ -1,6 +1,159 @@
#include "../vgmstream.h"
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
#ifdef VGM_USE_MP4V2
#define MP4V2_NO_STDINT_DEFS
#include <mp4v2/mp4v2.h>
#endif
#ifdef VGM_USE_FDKAAC
#include <aacdecoder_lib.h>
#endif
typedef struct {
STREAMFILE* streamfile;
uint64_t start;
uint64_t offset;
uint64_t size;
} mp4_streamfile;
struct mp4_aac_codec_data {
mp4_streamfile if_file;
MP4FileHandle h_mp4file;
MP4TrackId track_id;
unsigned long sampleId;
unsigned long numSamples;
UINT codec_init_data_size;
HANDLE_AACDECODER h_aacdecoder;
unsigned int sample_ptr;
unsigned int samples_per_frame
unsigned int samples_discard;
INT_PCM sample_buffer[( (6) * (2048)*4 )];
};
// VGM_USE_MP4V2
static void* mp4_file_open( const char* name, MP4FileMode mode ) {
char * endptr;
#ifdef _MSC_VER
unsigned __int64 ptr = _strtoui64( name, &endptr, 16 );
#else
unsigned long ptr = strtoul( name, &endptr, 16 );
#endif
return (void*) ptr;
}
static int mp4_file_seek( void* handle, int64_t pos ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
if ( pos > file->size ) pos = file->size;
pos += file->start;
file->offset = pos;
return 0;
}
static int mp4_file_get_size( void* handle, int64_t* size ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
*size = file->size;
return 0;
}
static int mp4_file_read( void* handle, void* buffer, int64_t size, int64_t* nin, int64_t maxChunkSize ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
int64_t max_size = file->size - file->offset - file->start;
if ( size > max_size ) size = max_size;
if ( size > 0 )
{
*nin = read_streamfile( (uint8_t *) buffer, file->offset, size, file->streamfile );
file->offset += *nin;
}
else
{
*nin = 0;
return 1;
}
return 0;
}
static int mp4_file_write( void* handle, const void* buffer, int64_t size, int64_t* nout, int64_t maxChunkSize ) {
return 1;
}
static int mp4_file_close( void* handle ) {
return 0;
}
MP4FileProvider mp4_file_provider = { mp4_file_open, mp4_file_seek, mp4_file_read, mp4_file_write, mp4_file_close, mp4_file_get_size };
mp4_aac_codec_data* init_mp4_aac(STREAMFILE* sf) {
char filename[PATH_LIMIT];
uint32_t start = 0;
uint32_t size = get_streamfile_size(sf);
CStreamInfo* stream_info = NULL;
uint8_t* buffer = NULL;
uint32_t buffer_size;
UINT ubuffer_size, bytes_valid;
mp4_aac_codec_data* data = calloc(1, sizeof(mp4_aac_codec_data));
if (!data) goto fail;
data->if_file.streamfile = sf;
data->if_file.start = start;
data->if_file.offset = start;
data->if_file.size = size;
/* Big ol' kludge! */
sprintf( filename, "%p", &data->if_file );
data->h_mp4file = MP4ReadProvider( filename, &mp4_file_provider );
if ( !data->h_mp4file ) goto fail;
if ( MP4GetNumberOfTracks(data->h_mp4file, MP4_AUDIO_TRACK_TYPE, '\000') != 1 ) goto fail;
data->track_id = MP4FindTrackId( data->h_mp4file, 0, MP4_AUDIO_TRACK_TYPE, '\000' );
data->h_aacdecoder = aacDecoder_Open( TT_MP4_RAW, 1 );
if ( !data->h_aacdecoder ) goto fail;
MP4GetTrackESConfiguration( data->h_mp4file, data->track_id, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size));
ubuffer_size = buffer_size;
if ( aacDecoder_ConfigRaw( data->h_aacdecoder, &buffer, &ubuffer_size ) ) goto fail;
free( buffer ); buffer = NULL;
data->sampleId = 1;
data->numSamples = MP4GetTrackNumberOfSamples( data->h_mp4file, data->track_id );
if (!MP4ReadSample(data->h_mp4file, data->track_id, data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) goto fail;
ubuffer_size = buffer_size;
bytes_valid = buffer_size;
if ( aacDecoder_Fill( data->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) goto fail;
if ( aacDecoder_DecodeFrame( data->h_aacdecoder, data->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) goto fail;
free( buffer ); buffer = NULL;
data->sample_ptr = 0;
stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
data->samples_per_frame = stream_info->frameSize;
data->samples_discard = 0;
sf->get_name( sf, filename, sizeof(filename) );
data->if_file.streamfile = sf->open(sf, filename, 0);
if (!data->if_file.streamfile) goto fail;
return data;
fail:
free( buffer ); buffer = NULL;
free_mp4_aac(data);
}
static void convert_samples(INT_PCM * src, sample * dest, int32_t count) {
int32_t i;
for ( i = 0; i < count; i++ ) {
@ -111,4 +264,38 @@ void free_mp4_aac(mp4_aac_codec_data * data) {
}
}
void mp4_aac_get_streamfile(mp4_aac_codec_data* data) {
if (!data)
return NULL;
return data->if_file.streamfile;
}
int32_t mp4_aac_get_samples(mp4_aac_codec_data* data) {
if (!data)
return 0;
return (int32_t)(data->samples_per_frame * data->numSamples);
}
int32_t mp4_aac_get_samples_per_frame(mp4_aac_codec_data* data) {
if (!data)
return 0;
return (int32_t)(data->samples_per_frame);
}
int mp4_aac_get_sample_rate(mp4_aac_codec_data* data) {
if (!data)
return 0;
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
return stream_info->sample_rate;
}
int mp4_aac_get_channels(mp4_aac_codec_data* data) {
if (!data)
return 0;
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
return stream_info->numChannels;
}
#endif

View File

@ -38,7 +38,7 @@ void decode_nwa(nwa_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
nwa_codec_data* init_nwa(STREAMFILE* sf) {
nwa_codec_data* data = NULL;
data = malloc(sizeof(nwa_codec_data));
data = calloc(1, sizeof(nwa_codec_data));
if (!data) goto fail;
data->nwa = nwalib_open(sf);

View File

@ -4,6 +4,7 @@
#include "../streamtypes.h"
#include "../vgmstream.h"
#include "../util/reader_sf.h"
#include "../util/log.h"
/* blocked layouts */
void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);

361
src/libvgmstream.h Normal file
View File

@ -0,0 +1,361 @@
/* libvgmstream: vgmstream's public API */
#ifndef _LIBVGMSTREAM_H_
#define _LIBVGMSTREAM_H_
//#define LIBVGMSTREAM_ENABLE 1
#if LIBVGMSTREAM_ENABLE
/* By default vgmstream behaves like a decoder (decode samples until stream end), but you can configure
* it to loop N times or even downmix. In other words, it also behaves a bit like a player.
* It exposes multiple convenience stuff mainly for various plugins that mostly repeat the same features.
* All this may make the API still WIP and a bit twisted, probably will improve later. Probably.
*
* Notes:
* - now there is an API internals (vgmstream.h) may change in the future
* - may dynamically allocate stuff as needed (mainly some buffers, but varies per format)
* - some details described in the API may not happen at the moment (defined for future changes)
* - uses long-winded libvgmstream_* names since internals alredy use the vgmstream_* 'namespace', #define as needed
* - c-strings should be in UTF-8
*
* Basic usage (also see api_example.c):
* - libvgmstream_init(...) // base context
* - libvgmstream_setup(...) // config if needed
* - libvgmstream_open_song(...) // setup format
* - libvgmstream_play(...) // main decode
* - output samples + repeat libvgmstream_play until stream is done
* - libvgmstream_free(...) // cleanup
*/
/*****************************************************************************/
/* DEFINES */
///* standard C param call and name mangling (to avoid __stdcall / .defs) */
//#define LIBVGMSTREAM_CALL __cdecl //needed?
//LIBVGMSTREAM_API (type) LIBVGMSTREAM_CALL libvgmstream_function(...);
/* external function behavior (for compile time) */
#if defined(LIBVGMSTREAM_EXPORT)
#define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */
#elif defined(LIBVGMSTREAM_IMPORT)
#define LIBVGMSTREAM_API __declspec(dllimport) /* when importing/linking vgmstream DLL */
#else
#define LIBVGMSTREAM_API /* nothing, internal/default */
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "libvgmstream_streamfile.h"
/*****************************************************************************/
/* VERSION */
/* Current API version, for static checks.
* - only refers to the API itself, changes related to formats/etc don't alter this
* - vgmstream's features are mostly stable, but this API may be tweaked from time to time
*/
#define LIBVGMSTREAM_API_VERSION_MAJOR 1 // breaking API/ABI changes
#define LIBVGMSTREAM_API_VERSION_MINOR 0 // compatible API/ABI changes
#define LIBVGMSTREAM_API_VERSION_PATCH 0 // fixes
/* Current API version, for dynamic checks. returns hex value: 0xMMmmpppp = MM-major, mm-minor, pppp-patch
* - use when loading vgmstream as a dynamic library to ensure API/ABI compatibility
*/
LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void);
/* CHANGELOG:
* - 1.0.0: initial version
*/
/*****************************************************************************/
/* DECODE */
/* interleaved samples: buf[0]=ch0, buf[1]=ch1, buf[2]=ch0, buf[3]=ch0, ... */
typedef enum {
LIBVGMSTREAM_SAMPLE_PCM16 = 0x01,
LIBVGMSTREAM_SAMPLE_PCM24 = 0x02,
LIBVGMSTREAM_SAMPLE_PCM32 = 0x03,
LIBVGMSTREAM_SAMPLE_FLOAT = 0x04,
} libvgmstream_sample_t;
/* current song info, may be copied around (values are info-only) */
typedef struct {
/* main (always set) */
int channels; // output channels
int sample_rate; // output sample rate
libvgmstream_sample_t sample_type; // output buffer's sample type
int sample_size; // derived from sample_type (pcm16=0x02, float=0x04, etc)
/* extra info (may be 0 if not known or not relevant) */
uint32_t channel_layout; // standard WAVE bitflags
int subsong_index; // 0 = none, N = loaded subsong N (1=first)
int subsong_count; // 0 = format has no concept of subsongs, N = has N subsongs
// ** 1 = format has subsongs, and only 1 for current file
int input_channels; // original file's channels before downmixing (if any)
//int interleave; // when file is interleaved
//int interleave_first; // when file is interleaved
//int interleave_last; // when file is interleaved
//int frame_size; // when file has some configurable frame size
/* sample info (may not be used depending on config) */
int64_t stream_samples; // file's max samples (not final play duration)
int64_t loop_start; // loop start sample
int64_t loop_end; // loop end sample
bool loop_flag; // if file loops (false + defined loops means looping was forcefully disabled)
bool play_forever; // if file loops forever based on current config (meaning _play never stops)
int64_t play_samples; // totals after all calculations (after applying loop/fade/etc config)
// ** may not be 100% accurate in some cases (check decoder's 'done' flag to stop)
// ** if play_forever is set this is still provided for reference based on non-forever config
int stream_bitrate; // average bitrate of the subsong (slightly bloated vs codec_bitrate; incorrect in rare cases)
//int codec_bitrate; // average bitrate of the codec data
// ** not possible / slow to calculate in most cases
/* descriptions */
char codec_name[128]; //
char layout_name[128]; //
char meta_name[128]; // (not internal "tag" metadata)
char stream_name[256]; // some internal name or representation, not always useful
// ** these are a bit big for a struct, but the typical use case of vgsmtream is opening a file > immediately
// query description and since libvgmstream returns its own copy it shouldn't be too much of a problem
// ** (may be separated later)
/* misc */
//bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior
int format_id; // when reopening subfiles or similar formats without checking other all possible formats
// ** this value WILL change without warning between vgmstream versions/commits
} libvgmstream_format_t;
typedef struct {
void* buf; // current decoded buf (valid after _decode until next call; may change between calls)
int buf_samples; // current buffer samples (0 is possible in some cases)
int buf_bytes; // current buffer bytes (channels * sample_size * samples)
bool done; // when stream is done based on config
// ** note that with play_forever this flag is never set
} libvgmstream_decoder_t;
/* vgmstream context/handle */
typedef struct {
void* priv; // internal data
/* pointers for easier ABI compatibility */
const libvgmstream_format_t* format; // current song info, updated on _open
libvgmstream_decoder_t* decoder; // updated on each _decode call
} libvgmstream_t;
/* Inits the vgmstream context
* - returns NULL on error
*/
LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void);
/* Frees the vgmstream context and any other internal stuff.
*/
LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib);
/* configures how vgmstream behaves internally when playing a file */
typedef struct {
bool disable_config_override; // ignore forced (TXTP) config
bool allow_play_forever; // must allow manually as some cases a TXTP may set loop forever but client may not handle it
bool play_forever; // keeps looping forever (file must have loop_flag set)
bool ignore_loop; // ignores loops points
bool force_loop; // enables full loops (0..samples) if file doesn't have loop points
bool really_force_loop; // forces full loops (0..samples) even if file has loop points
bool ignore_fade; // don't fade after N loops and play remaning stream (for files with outros)
double loop_count; // target loops (values like 1.5 are ok)
double fade_time; // fade period after target loops
double fade_delay; // fade delay after target loops
int auto_downmix_channels; // downmixing if vgmstream's channels are higher than value
// ** for players that can only handle N channels
// ** this type of downmixing is very simplistic and not recommended
bool force_pcm16; // forces output buffer to be remixed into PCM16
} libvgmstream_config_t;
/* pass default config, that will be applied to song on open
* - invalid config or complex cases (ex. some TXTP) may ignore these settings.
* - called without a song loaded (before _open or after _close), otherwise ignored.
* - without config vgmstream will decode the current stream once
*/
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg);
/* configures how vgmstream opens the format */
typedef struct {
libvgmstream_streamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream
// ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed
int subsong_index; // target subsong (1..N) or 0 = default/first
// ** to check if a file has subsongs, _open first + check format->total_subsongs (then _open 2nd, 3rd, etc)
int format_id; // force a format (for example when loading new subsong of the same archive)
int stereo_track; // forces vgmstream to decode one 2ch+2ch+2ch... 'track' and discard other channels, where 0 = disabled, 1..N = Nth track
} libvgmstream_options_t;
/* Opens file based on config and prepares it to play if supported.
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
* - will close currently loaded song if needed
*/
LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_options_t* open_options);
/* Closes current song; may still use libvgmstream to open other songs
*/
LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib);
/* Decodes next batch of samples
* - vgmstream supplies its own buffer, updated on lib->decoder->* values (may change between calls)
* - returns < 0 on error
*/
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib);
/* Same as _play, but fills some external buffer (also updates lib->decoder->* values)
* - returns < 0 on error, or N = number of filled samples.
* - buf must be at least as big as channels * sample_size * buf_samples
* - needs copying around from internal bufs so may be slightly slower; mainly for cases when you have buf constraints
*/
LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples);
/* Gets current position within the song.
* - return < 0 on error
*/
LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib);
/* Seeks to absolute position. Will clamp incorrect values such as seeking before/past playable length.
*/
LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample);
/* Reset current song
*/
LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib);
/*****************************************************************************/
/* HELPERS */
typedef enum {
LIBVGMSTREAM_LOG_LEVEL_ALL = 0,
LIBVGMSTREAM_LOG_LEVEL_DEBUG = 20,
LIBVGMSTREAM_LOG_LEVEL_INFO = 30,
LIBVGMSTREAM_LOG_LEVEL_NONE = 100,
} libvgmstream_loglevel_t;
typedef struct {
libvgmstream_loglevel_t level; // log level
void (*callback)(int level, const char* str); // log callback
bool stdout_callback; // use default log callback rather than user supplied
} libvgmstream_log_t;
/* Defines a global log callback, as vgmstream sometimes communicates format issues to the user.
* - note that log is currently set globally rather than per libvgmstream_t
*/
LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg);
/* Returns a list of supported extensions (WARNING: it's pretty big), such as "adx", "dsp", etc.
* Mainly for plugins that want to know which extensions are supported.
* - returns NULL if no size is provided
*/
LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size);
/* Same as above, buf returns a list what vgmstream considers "common" formats (such as "wav", "ogg"),
* which usually one doesn't want to associate to vgmstream.
* - returns NULL if no size is provided
*/
LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size);
typedef struct {
bool is_extension; /* set if filename is just an extension */
bool skip_default; /* set if shouldn't check default formats */
bool reject_extensionless; /* set if player can't play extensionless files */
bool accept_unknown; /* set to allow any extension (for txth) */
bool accept_common; /* set to allow known-but-common extension (when player has plugin priority) */
} libvgmstream_valid_t;
/* Returns if vgmstream can parse a filename by extension, to reject some files earlier
* - doesn't check file contents (that's only done on _open)
* - config may be NULL
* - mainly for plugins that fail early; libvgmstream doesn't use this
*/
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg);
typedef struct {
bool force_title; // TODO: what was this for?
bool subsong_range; // print a range of possible subsongs after title 'filename#1~N'
bool remove_extension; // remove extension from passed filename
bool remove_archive; // remove '(archive)|(subfile)' format of some plugins
const char* filename; // base file's filename
// ** note that sometimes vgmstream doesn't have/know the original name, so it's needed again here
} libvgmstream_title_t;
/* Get a simple title for plugins, derived from internal stream name if available
* - valid after _open
*/
LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len);
/* Writes a description of the current song into dst. Will always be null-terminated.
* - returns < 0 if file was truncated, though will still succeed.
*/
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size);
/*****************************************************************************/
/* TAGS */
/* Meant to be a simple implementation; feel free to ignore and roll your own (or use another tags plugin).
* Doesn't need a main libvgmstream as tags aren't tied to loaded songs. */
/* tag state */
typedef struct {
void* priv; // internal data
const char* key; // current key
const char* val; // current value
} libvgmstream_tags_t;
/* Initializes tags.
* - libsf should point to a !tags.m3u file
* - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process.
*/
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf);
/* Finds tags for a new filename. Must be called first before extracting tags.
*/
LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename);
/* Extracts next valid tag in tagfile to key/val.
* - returns false if no more tags are found (meant to be called repeatedly until false)
* - key/values are trimmed of beginning/end whitespaces and values are in UTF-8
*/
LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags);
/* Closes tags. */
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags);
#endif
#endif

View File

@ -81,11 +81,8 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="api.h" />
<ClInclude Include="api_decode.h" />
<ClInclude Include="api_helpers.h" />
<ClInclude Include="api_streamfile.h" />
<ClInclude Include="api_tags.h" />
<ClInclude Include="api_version.h" />
<ClInclude Include="libvgmstream.h" />
<ClInclude Include="libvgmstream_streamfile.h" />
<ClInclude Include="streamfile.h" />
<ClInclude Include="streamtypes.h" />
<ClInclude Include="util.h" />
@ -170,6 +167,7 @@
<ClInclude Include="meta\sscf_encrypted.h" />
<ClInclude Include="meta\str_wav_streamfile.h" />
<ClInclude Include="meta\txth_streamfile.h" />
<ClInclude Include="meta\txtp.h" />
<ClInclude Include="meta\ubi_bao_streamfile.h" />
<ClInclude Include="meta\ubi_ckd_cwav_streamfile.h" />
<ClInclude Include="meta\ubi_lyn_streamfile.h" />
@ -719,6 +717,8 @@
<ClCompile Include="meta\tt_ad.c" />
<ClCompile Include="meta\txth.c" />
<ClCompile Include="meta\txtp.c" />
<ClCompile Include="meta\txtp_parser.c" />
<ClCompile Include="meta\txtp_process.c" />
<ClCompile Include="meta\ubi_bao.c" />
<ClCompile Include="meta\ubi_ckd.c" />
<ClCompile Include="meta\ubi_ckd_cwav.c" />

View File

@ -77,19 +77,10 @@
<ClInclude Include="api.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="api_decode.h">
<ClInclude Include="libvgmstream.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="api_helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="api_streamfile.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="api_tags.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="api_version.h">
<ClInclude Include="libvgmstream_streamfile.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="streamfile.h">
@ -344,6 +335,9 @@
<ClInclude Include="meta\txth_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\txtp.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\ubi_bao_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
@ -1987,6 +1981,12 @@
<ClCompile Include="meta\txtp.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\txtp_parser.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\txtp_process.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ubi_bao.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

View File

@ -1,18 +1,13 @@
#ifndef _API_STREAMFILE_H_
#define _API_STREAMFILE_H_
#include "api.h"
#ifndef _LIBVGMSTREAM_STREAMFILE_H_
#define _LIBVGMSTREAM_STREAMFILE_H_
#include "libvgmstream.h"
#if LIBVGMSTREAM_ENABLE
/* vgmstream's IO API, defined as a "streamfile" (SF).
*
* Compared to typical IO, vgmstream has some extra needs that roughly assume there is an underlying filesystem (as usual in games):
* - seeking + reading from arbitrary offsets: header not in the beginning of a stream, rewinding back when looping, etc
* - opening other streamfiles: reopening a copy of the current SF, formats with split header + data, decryption files, etc
* - extracting the filename: opening similarly named companion files, basic extension sanity checks, heuristics for odd cases, etc
*
* If your IO can't fully satisfy those constraints, it may still be possible to create a streamfile that just simulates part of it.
* For example, returning a fake filename, and only handling "open" that reopens itself (same filename), while returning default/incorrect
* values for non-handled operations. Simpler formats will probably work just fine.
* vgmstream roughly assumes there is an underlying filesystem (as usual in games): seeking + reading from arbitrary offsets,
* opening companion files, filename tests, etc. If your case is too different you may still create a partial streamfile: returning
* a fake filename, only handling "open" that reopens itself (same filename), etc. Simpler formats will probably work just fine.
*/
@ -28,26 +23,26 @@ typedef struct libvgmstream_streamfile_t {
//uint32_t flags; // info flags for vgmstream
void* user_data; // any internal structure
/* read 'length' data at internal offset to 'dst' (implicit seek if needed)
* - assumes 0 = failure/EOF
/* read 'length' data at internal offset to 'dst'
* - assumes 0 = failure/EOF
*/
int (*read)(void* user_data, uint8_t* dst, int dst_size);
/* seek to offset
* - note that due to how vgmstream works this is a fairly common operation (to be optimized later)
* - note that vgmstream needs to seek + read fairly often (to be optimized later)
*/
int64_t (*seek)(void* user_data, int64_t offset, int whence);
/* get max offset
/* get max offset (typically for checks or calculations)
*/
int64_t (*get_size)(void* user_data);
/* get current filename
/* get current filename (used to open same or other streamfiles and heuristics; no need to be a real path)
*/
const char* (*get_name)(void* user_data);
/* open another streamfile from filename (may be some path/protocol, or same as current get_name = reopen)
* - vgmstream mainly opens stuff based on current get_name (relative), so there shouldn't be need to transform this path
* - vgmstream opens stuff based on current get_name (relative), so there shouldn't be need to transform this path
*/
struct libvgmstream_streamfile_t* (*open)(void* user_data, const char* filename);

View File

@ -147,7 +147,6 @@ VGMSTREAM* init_vgmstream_akb(STREAMFILE* sf) {
#ifdef VGM_USE_FFMPEG
case 0x06: { /* M4A with AAC [The World Ends with You (iPad)] */
/* init_vgmstream_akb_mp4 above has priority, but this works fine too */
vgmstream->codec_data = init_ffmpeg_offset(sf, start_offset,stream_size-start_offset);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;

View File

@ -16,7 +16,7 @@ VGMSTREAM* init_vgmstream_ea_tmx(STREAMFILE* sf) {
if (is_id32be(0x0c, sf, "0001")) {
read_u32 = read_u32be;
}
else if (is_id32le(0x0c, sf, "1000")) {
else if (is_id32le(0x0c, sf, "0001")) {
read_u32 = read_u32le;
}
else {

View File

@ -5,6 +5,7 @@
#include "../util/reader_sf.h"
#include "../util/reader_text.h"
#include "../util/sf_utils.h"
#include "../util/log.h"
typedef VGMSTREAM* (*init_vgmstream_t)(STREAMFILE* sf);
@ -162,8 +163,7 @@ VGMSTREAM* init_vgmstream_mp4_aac_ffmpeg(STREAMFILE* sf);
#endif
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
VGMSTREAM* init_vgmstream_mp4_aac(STREAMFILE* sf);
#endif
VGMSTREAM* init_vgmstream_sli_loops(STREAMFILE* sf);

View File

@ -2,159 +2,42 @@
#include "../coding/coding.h"
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
// VGM_USE_MP4V2
void* mp4_file_open( const char* name, MP4FileMode mode )
{
char * endptr;
#ifdef _MSC_VER
unsigned __int64 ptr = _strtoui64( name, &endptr, 16 );
#else
unsigned long ptr = strtoul( name, &endptr, 16 );
#endif
return (void*) ptr;
}
int mp4_file_seek( void* handle, int64_t pos )
{
mp4_streamfile * file = ( mp4_streamfile * ) handle;
if ( pos > file->size ) pos = file->size;
pos += file->start;
file->offset = pos;
return 0;
}
int mp4_file_get_size( void* handle, int64_t* size )
{
mp4_streamfile * file = ( mp4_streamfile * ) handle;
*size = file->size;
return 0;
}
int mp4_file_read( void* handle, void* buffer, int64_t size, int64_t* nin, int64_t maxChunkSize )
{
mp4_streamfile * file = ( mp4_streamfile * ) handle;
int64_t max_size = file->size - file->offset - file->start;
if ( size > max_size ) size = max_size;
if ( size > 0 )
{
*nin = read_streamfile( (uint8_t *) buffer, file->offset, size, file->streamfile );
file->offset += *nin;
}
else
{
*nin = 0;
return 1;
}
return 0;
}
int mp4_file_write( void* handle, const void* buffer, int64_t size, int64_t* nout, int64_t maxChunkSize )
{
return 1;
}
int mp4_file_close( void* handle )
{
return 0;
}
MP4FileProvider mp4_file_provider = { mp4_file_open, mp4_file_seek, mp4_file_read, mp4_file_write, mp4_file_close, mp4_file_get_size };
// VGM_USE_FDKAAC
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *sf, uint64_t start, uint64_t size);
static VGMSTREAM* init_vgmstream_mp4_aac_offset(STREAMFILE* sf, uint64_t start, uint64_t size);
VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE *sf) {
return init_vgmstream_mp4_aac_offset( sf, 0, sf->get_size(sf) );
VGMSTREAM* init_vgmstream_mp4_aac(STREAMFILE* sf) {
return init_vgmstream_mp4_aac_offset( sf, 0x00, get_streamfile_size(sf));
}
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *sf, uint64_t start, uint64_t size) {
VGMSTREAM * vgmstream = NULL;
static VGMSTREAM* init_vgmstream_mp4_aac_offset(STREAMFILE* sf, uint64_t start, uint64_t size) {
VGMSTREAM* vgmstream = NULL;
mp4_aac_codec_data* data = NULL;
char filename[PATH_LIMIT];
data = init_mp4_aac(sf);
if (!data) goto fail;
mp4_aac_codec_data * aac_file = ( mp4_aac_codec_data * ) calloc(1, sizeof(mp4_aac_codec_data));
int channels = mp4_aac_get_channels(data);
CStreamInfo * stream_info;
uint8_t * buffer = NULL;
uint32_t buffer_size;
UINT ubuffer_size, bytes_valid;
if ( !aac_file ) goto fail;
aac_file->if_file.streamfile = sf;
aac_file->if_file.start = start;
aac_file->if_file.offset = start;
aac_file->if_file.size = size;
/* Big ol' kludge! */
sprintf( filename, "%p", &aac_file->if_file );
aac_file->h_mp4file = MP4ReadProvider( filename, &mp4_file_provider );
if ( !aac_file->h_mp4file ) goto fail;
if ( MP4GetNumberOfTracks(aac_file->h_mp4file, MP4_AUDIO_TRACK_TYPE, '\000') != 1 ) goto fail;
aac_file->track_id = MP4FindTrackId( aac_file->h_mp4file, 0, MP4_AUDIO_TRACK_TYPE, '\000' );
aac_file->h_aacdecoder = aacDecoder_Open( TT_MP4_RAW, 1 );
if ( !aac_file->h_aacdecoder ) goto fail;
MP4GetTrackESConfiguration( aac_file->h_mp4file, aac_file->track_id, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size));
ubuffer_size = buffer_size;
if ( aacDecoder_ConfigRaw( aac_file->h_aacdecoder, &buffer, &ubuffer_size ) ) goto fail;
free( buffer ); buffer = NULL;
aac_file->sampleId = 1;
aac_file->numSamples = MP4GetTrackNumberOfSamples( aac_file->h_mp4file, aac_file->track_id );
if (!MP4ReadSample(aac_file->h_mp4file, aac_file->track_id, aac_file->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) goto fail;
ubuffer_size = buffer_size;
bytes_valid = buffer_size;
if ( aacDecoder_Fill( aac_file->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) goto fail;
if ( aacDecoder_DecodeFrame( aac_file->h_aacdecoder, aac_file->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) goto fail;
free( buffer ); buffer = NULL;
aac_file->sample_ptr = 0;
stream_info = aacDecoder_GetStreamInfo( aac_file->h_aacdecoder );
aac_file->samples_per_frame = stream_info->frameSize;
aac_file->samples_discard = 0;
sf->get_name( sf, filename, sizeof(filename) );
aac_file->if_file.streamfile = sf->open(sf, filename, STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!aac_file->if_file.streamfile) goto fail;
vgmstream = allocate_vgmstream( stream_info->numChannels, 1 );
vgmstream = allocate_vgmstream(channels, 0);
if (!vgmstream) goto fail;
vgmstream->loop_flag = 0;
vgmstream->codec_data = aac_file;
vgmstream->channels = stream_info->numChannels;
vgmstream->sample_rate = stream_info->sampleRate;
vgmstream->num_samples = stream_info->frameSize * aac_file->numSamples;
vgmstream->sample_rate = mp4_aac_get_sample_rate(data);
vgmstream->num_samples = mp4_aac_get_samples(data);
vgmstream->codec_data = data;
vgmstream->coding_type = coding_MP4_AAC;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_MP4;
return vgmstream;
fail:
if ( buffer ) free( buffer );
if ( aac_file ) {
if ( aac_file->h_aacdecoder ) aacDecoder_Close( aac_file->h_aacdecoder );
if ( aac_file->h_mp4file ) MP4Close( aac_file->h_mp4file, 0 );
free( aac_file );
}
free_mp4_aac(data);
if (vgmstream) {
vgmstream->codec_data = NULL;
close_vgmstream(vgmstream);
}
return NULL;
}
#endif

File diff suppressed because it is too large Load Diff

153
src/meta/txtp.h Normal file
View File

@ -0,0 +1,153 @@
#ifndef _TXTP_H_
#define _TXTP_H_
#include "meta.h"
#include "../coding/coding.h"
#include "../layout/layout.h"
#include "../base/mixing.h"
#include "../base/plugins.h"
#include "../util/text_reader.h"
#include "../util/paths.h"
#include <math.h>
#define TXTP_FILENAME_MAX 1024
#define TXTP_MIXING_MAX 512
#define TXTP_GROUP_MODE_SEGMENTED 'S'
#define TXTP_GROUP_MODE_LAYERED 'L'
#define TXTP_GROUP_MODE_RANDOM 'R'
#define TXTP_GROUP_RANDOM_ALL '-'
#define TXTP_GROUP_REPEAT 'R'
#define TXTP_POSITION_LOOPS 'L'
/* mixing info */
typedef enum {
MIX_SWAP,
MIX_ADD,
MIX_ADD_VOLUME,
MIX_VOLUME,
MIX_LIMIT,
MIX_DOWNMIX,
MIX_KILLMIX,
MIX_UPMIX,
MIX_FADE,
MACRO_VOLUME,
MACRO_TRACK,
MACRO_LAYER,
MACRO_CROSSTRACK,
MACRO_CROSSLAYER,
MACRO_DOWNMIX,
} txtp_mix_t;
typedef struct {
txtp_mix_t command;
/* common */
int ch_dst;
int ch_src;
double vol;
/* fade envelope */
double vol_start;
double vol_end;
char shape;
int32_t sample_pre;
int32_t sample_start;
int32_t sample_end;
int32_t sample_post;
double time_pre;
double time_start;
double time_end;
double time_post;
double position;
char position_type;
/* macros */
int max;
uint32_t mask;
char mode;
} txtp_mix_data_t;
typedef struct {
/* main entry */
char filename[TXTP_FILENAME_MAX];
bool silent;
/* TXTP settings (applied at the end) */
int range_start;
int range_end;
int subsong;
uint32_t channel_mask;
int mixing_count;
txtp_mix_data_t mixing[TXTP_MIXING_MAX];
play_config_t config;
int sample_rate;
int loop_install_set;
int loop_end_max;
double loop_start_second;
int32_t loop_start_sample;
double loop_end_second;
int32_t loop_end_sample;
/* flags */
int loop_anchor_start;
int loop_anchor_end;
int trim_set;
double trim_second;
int32_t trim_sample;
} txtp_entry_t;
typedef struct {
int position;
char type;
int count;
char repeat;
int selected;
txtp_entry_t entry;
} txtp_group_t;
typedef struct {
txtp_entry_t* entry;
size_t entry_count;
size_t entry_max;
txtp_group_t* group;
size_t group_count;
size_t group_max;
int group_pos; /* entry counter for groups */
VGMSTREAM** vgmstream;
size_t vgmstream_count;
uint32_t loop_start_segment;
uint32_t loop_end_segment;
bool is_loop_keep;
bool is_loop_auto;
txtp_entry_t default_entry;
int default_entry_set;
bool is_segmented;
bool is_layered;
bool is_single;
} txtp_header_t;
txtp_header_t* txtp_parse(STREAMFILE* sf);
bool txtp_process(txtp_header_t* txtp, STREAMFILE* sf);
void txtp_clean(txtp_header_t* txtp);
void txtp_add_mixing(txtp_entry_t* entry, txtp_mix_data_t* mix, txtp_mix_t command);
void txtp_copy_config(play_config_t* dst, play_config_t* src);
#endif

1080
src/meta/txtp_parser.c Normal file

File diff suppressed because it is too large Load Diff

634
src/meta/txtp_process.c Normal file
View File

@ -0,0 +1,634 @@
#include <math.h>
#include "txtp.h"
#include "../coding/coding.h"
#include "../layout/layout.h"
#include "../base/mixing.h"
#include "../base/plugins.h"
/*******************************************************************************/
/* CONFIG */
/*******************************************************************************/
static void apply_settings(VGMSTREAM* vgmstream, txtp_entry_t* current) {
/* base settings */
if (current->sample_rate > 0) {
vgmstream->sample_rate = current->sample_rate;
}
if (current->loop_install_set) {
if (current->loop_start_second > 0 || current->loop_end_second > 0) {
current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate;
current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate;
if (current->loop_end_sample > vgmstream->num_samples &&
current->loop_end_sample - vgmstream->num_samples <= 0.1 * vgmstream->sample_rate)
current->loop_end_sample = vgmstream->num_samples; /* allow some rounding leeway */
}
if (current->loop_end_max) {
current->loop_end_sample = vgmstream->num_samples;
}
vgmstream_force_loop(vgmstream, current->loop_install_set, current->loop_start_sample, current->loop_end_sample);
}
if (current->trim_set) {
if (current->trim_second != 0.0) {
/* trim sample can become 0 here when second is too small (rounded) */
current->trim_sample = (double)current->trim_second * (double)vgmstream->sample_rate;
}
if (current->trim_sample < 0) {
vgmstream->num_samples += current->trim_sample; /* trim from end (add negative) */
}
else if (current->trim_sample > 0 && vgmstream->num_samples > current->trim_sample) {
vgmstream->num_samples = current->trim_sample; /* trim to value >0 */
}
/* readjust after triming if it went over (could check for more edge cases but eh) */
if (vgmstream->loop_end_sample > vgmstream->num_samples)
vgmstream->loop_end_sample = vgmstream->num_samples;
}
/* add macro to mixing list */
if (current->channel_mask) {
int ch;
for (ch = 0; ch < vgmstream->channels; ch++) {
if (!((current->channel_mask >> ch) & 1)) {
txtp_mix_data_t mix = {0};
mix.ch_dst = ch + 1;
mix.vol = 0.0f;
txtp_add_mixing(current, &mix, MIX_VOLUME);
}
}
}
/* copy mixing list (should be done last as some mixes depend on config) */
if (current->mixing_count > 0) {
int m, position_samples;
for (m = 0; m < current->mixing_count; m++) {
txtp_mix_data_t *mix = &current->mixing[m];
switch(mix->command) {
/* base mixes */
case MIX_SWAP: mixing_push_swap(vgmstream, mix->ch_dst, mix->ch_src); break;
case MIX_ADD: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, 1.0); break;
case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, mix->vol); break;
case MIX_VOLUME: mixing_push_volume(vgmstream, mix->ch_dst, mix->vol); break;
case MIX_LIMIT: mixing_push_limit(vgmstream, mix->ch_dst, mix->vol); break;
case MIX_UPMIX: mixing_push_upmix(vgmstream, mix->ch_dst); break;
case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix->ch_dst); break;
case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix->ch_dst); break;
case MIX_FADE:
/* Convert from time to samples now that sample rate is final.
* Samples and time values may be mixed though, so it's done for every
* value (if one is 0 the other will be too, though) */
if (mix->time_pre > 0.0) mix->sample_pre = mix->time_pre * vgmstream->sample_rate;
if (mix->time_start > 0.0) mix->sample_start = mix->time_start * vgmstream->sample_rate;
if (mix->time_end > 0.0) mix->sample_end = mix->time_end * vgmstream->sample_rate;
if (mix->time_post > 0.0) mix->sample_post = mix->time_post * vgmstream->sample_rate;
/* convert special meaning too */
if (mix->time_pre < 0.0) mix->sample_pre = -1;
if (mix->time_post < 0.0) mix->sample_post = -1;
if (mix->position_type == TXTP_POSITION_LOOPS && vgmstream->loop_flag) {
int loop_pre = vgmstream->loop_start_sample;
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
position_samples = loop_pre + loop_samples * mix->position;
if (mix->sample_pre >= 0) mix->sample_pre += position_samples;
mix->sample_start += position_samples;
mix->sample_end += position_samples;
if (mix->sample_post >= 0) mix->sample_post += position_samples;
}
mixing_push_fade(vgmstream, mix->ch_dst, mix->vol_start, mix->vol_end, mix->shape,
mix->sample_pre, mix->sample_start, mix->sample_end, mix->sample_post);
break;
/* macro mixes */
case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix->vol, mix->mask); break;
case MACRO_TRACK: mixing_macro_track(vgmstream, mix->mask); break;
case MACRO_LAYER: mixing_macro_layer(vgmstream, mix->max, mix->mask, mix->mode); break;
case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix->max); break;
case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix->max, mix->mode); break;
case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix->max); break;
default:
break;
}
}
}
/* default play config (last after sample rate mods/mixing/etc) */
txtp_copy_config(&vgmstream->config, &current->config);
setup_state_vgmstream(vgmstream);
/* config is enabled in layouts or externally (for compatibility, since we don't know yet if this
* VGMSTREAM will part of a layout, or is enabled externally to not mess up plugins's calcs) */
}
/*******************************************************************************/
/* ENTRIES */
/*******************************************************************************/
static bool parse_silents(txtp_header_t* txtp) {
VGMSTREAM* v_base = NULL;
/* silents use same channels as close files */
for (int i = 0; i < txtp->vgmstream_count; i++) {
if (!txtp->entry[i].silent) {
v_base = txtp->vgmstream[i];
break;
}
}
/* actually open silents */
for (int i = 0; i < txtp->vgmstream_count; i++) {
if (!txtp->entry[i].silent)
continue;
txtp->vgmstream[i] = init_vgmstream_silence_base(v_base);
if (!txtp->vgmstream[i]) goto fail;
apply_settings(txtp->vgmstream[i], &txtp->entry[i]);
}
return true;
fail:
return false;
}
static bool is_silent(const char* fn) {
/* should also contain "." in the filename for commands with seconds ("1.0") to work */
return fn[0] == '?';
}
static bool is_absolute(const char* fn) {
return fn[0] == '/' || fn[0] == '\\' || fn[1] == ':';
}
/* open all entries and apply settings to resulting VGMSTREAMs */
static bool parse_entries(txtp_header_t* txtp, STREAMFILE* sf) {
bool has_silents = false;
if (txtp->entry_count == 0)
goto fail;
txtp->vgmstream = calloc(txtp->entry_count, sizeof(VGMSTREAM*));
if (!txtp->vgmstream) goto fail;
txtp->vgmstream_count = txtp->entry_count;
/* open all entry files first as they'll be modified by modes */
for (int i = 0; i < txtp->vgmstream_count; i++) {
STREAMFILE* temp_sf = NULL;
const char* filename = txtp->entry[i].filename;
/* silent entry ignore */
if (is_silent(filename)) {
txtp->entry[i].silent = true;
has_silents = true;
continue;
}
/* absolute paths are detected for convenience, but since it's hard to unify all OSs
* and plugins, they aren't "officially" supported nor documented, thus may or may not work */
if (is_absolute(filename))
temp_sf = open_streamfile(sf, filename); /* from path as is */
else
temp_sf = open_streamfile_by_filename(sf, filename); /* from current path */
if (!temp_sf) {
vgm_logi("TXTP: cannot open %s\n", filename);
goto fail;
}
temp_sf->stream_index = txtp->entry[i].subsong;
txtp->vgmstream[i] = init_vgmstream_from_STREAMFILE(temp_sf);
close_streamfile(temp_sf);
if (!txtp->vgmstream[i]) {
vgm_logi("TXTP: cannot parse %s#%i\n", filename, txtp->entry[i].subsong);
goto fail;
}
apply_settings(txtp->vgmstream[i], &txtp->entry[i]);
}
if (has_silents) {
if (!parse_silents(txtp))
goto fail;
}
return true;
fail:
return false;
}
/*******************************************************************************/
/* GROUPS */
/*******************************************************************************/
static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header_t* txtp, int position, int count) {
//;VGM_LOG("TXTP: compact position=%i count=%i, vgmstreams=%i\n", position, count, txtp->vgmstream_count);
/* sets and compacts vgmstream list pulling back all following entries */
txtp->vgmstream[position] = vgmstream;
for (int i = position + count; i < txtp->vgmstream_count; i++) {
//;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count);
txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i];
txtp->entry[i + 1 - count] = txtp->entry[i]; /* memcpy old settings for other groups */
}
/* list can only become smaller, no need to alloc/free/etc */
txtp->vgmstream_count = txtp->vgmstream_count + 1 - count;
//;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count);
}
static bool find_loop_anchors(txtp_header_t* txtp, int position, int count, int* p_loop_start, int* p_loop_end) {
int loop_start = 0, loop_end = 0;
int i, j;
//;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count);
for (i = position, j = 0; i < position + count; i++, j++) {
/* catch first time anchors appear only, also logic elsewhere also uses +1 */
if (txtp->entry[i].loop_anchor_start && !loop_start) {
loop_start = j + 1;
}
if (txtp->entry[i].loop_anchor_end && !loop_end) {
loop_end = j + 1;
}
}
if (loop_start) {
if (!loop_end)
loop_end = count;
*p_loop_start = loop_start;
*p_loop_end = loop_end;
//;VGM_LOG("TXTP: loop anchors %i, %i\n", loop_start, loop_end);
return true;
}
return false;
}
static bool make_group_segment(txtp_header_t* txtp, txtp_group_t* grp, int position, int count) {
VGMSTREAM* vgmstream = NULL;
segmented_layout_data* data_s = NULL;
int loop_flag = 0;
int loop_start = 0, loop_end = 0;
/* allowed for actual groups (not final "mode"), otherwise skip to optimize */
if (!grp && count == 1) {
//;VGM_LOG("TXTP: ignored single group\n");
return true;
}
if (position + count > txtp->vgmstream_count || position < 0 || count < 0) {
VGM_LOG("TXTP: ignored segment position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count);
return true;
}
/* set loops with "anchors" (this allows loop config inside groups, not just in the final group,
* which is sometimes useful when paired with random/selectable groups or loop times) */
if (find_loop_anchors(txtp, position, count, &loop_start, &loop_end)) {
loop_flag = (loop_start > 0 && loop_start <= count);
}
/* loop segment settings only make sense if this group becomes final vgmstream */
else if (position == 0 && txtp->vgmstream_count == count) {
loop_start = txtp->loop_start_segment;
loop_end = txtp->loop_end_segment;
if (loop_start && !loop_end) {
loop_end = count;
}
else if (txtp->is_loop_auto) { /* auto set to last segment */
loop_start = count;
loop_end = count;
}
loop_flag = (loop_start > 0 && loop_start <= count);
}
/* fix loop keep (do it before init'ing as loops/metadata may be disabled for segments) */
int32_t loop_start_sample = 0, loop_end_sample = 0;
if (loop_flag && txtp->is_loop_keep) {
int32_t current_samples = 0;
for (int i = 0; i < count; i++) {
if (loop_start == i+1 /*&& txtp->vgmstream[i + position]->loop_start_sample*/) {
loop_start_sample = current_samples + txtp->vgmstream[i + position]->loop_start_sample;
}
current_samples += txtp->vgmstream[i + position]->num_samples;
if (loop_end == i+1 && txtp->vgmstream[i + position]->loop_end_sample) {
loop_end_sample = current_samples - txtp->vgmstream[i + position]->num_samples + txtp->vgmstream[i + position]->loop_end_sample;
}
}
}
/* init layout */
data_s = init_layout_segmented(count);
if (!data_s) goto fail;
/* copy each subfile */
for (int i = 0; i < count; i++) {
data_s->segments[i] = txtp->vgmstream[i + position];
txtp->vgmstream[i + position] = NULL; /* will be freed by layout */
}
/* setup VGMSTREAMs */
if (!setup_layout_segmented(data_s))
goto fail;
/* build the layout VGMSTREAM */
vgmstream = allocate_segmented_vgmstream(data_s, loop_flag, loop_start - 1, loop_end - 1);
if (!vgmstream) goto fail;
/* custom meta name if all parts don't match */
for (int i = 0; i < count; i++) {
if (vgmstream->meta_type != data_s->segments[i]->meta_type) {
vgmstream->meta_type = meta_TXTP;
break;
}
}
/* fix loop keep */
if (loop_flag && txtp->is_loop_keep) {
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
}
/* set new vgmstream and reorder positions */
update_vgmstream_list(vgmstream, txtp, position, count);
/* special "whole loop" settings */
if (grp && grp->entry.loop_anchor_start == 1) {
grp->entry.config.config_set = 1;
grp->entry.config.really_force_loop = 1;
}
return true;
fail:
close_vgmstream(vgmstream);
if (!vgmstream)
free_layout_segmented(data_s);
return false;
}
static bool make_group_layer(txtp_header_t* txtp, txtp_group_t* grp, int position, int count) {
VGMSTREAM* vgmstream = NULL;
layered_layout_data* data_l = NULL;
/* allowed for actual groups (not final mode), otherwise skip to optimize */
if (!grp && count == 1) {
//;VGM_LOG("TXTP: ignored single group\n");
return true;
}
if (position + count > txtp->vgmstream_count || position < 0 || count < 0) {
VGM_LOG("TXTP: ignored layer position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count);
return 1;
}
/* init layout */
data_l = init_layout_layered(count);
if (!data_l) goto fail;
/* copy each subfile */
for (int i = 0; i < count; i++) {
data_l->layers[i] = txtp->vgmstream[i + position];
txtp->vgmstream[i + position] = NULL; /* will be freed by layout */
}
/* setup VGMSTREAMs */
if (!setup_layout_layered(data_l))
goto fail;
/* build the layout VGMSTREAM */
vgmstream = allocate_layered_vgmstream(data_l);
if (!vgmstream) goto fail;
/* custom meta name if all parts don't match */
for (int i = 0; i < count; i++) {
if (vgmstream->meta_type != data_l->layers[i]->meta_type) {
vgmstream->meta_type = meta_TXTP;
break;
}
}
/* set new vgmstream and reorder positions */
update_vgmstream_list(vgmstream, txtp, position, count);
/* special "whole loop" settings (also loop if this group becomes final vgmstream) */
if (grp && (grp->entry.loop_anchor_start == 1
|| (position == 0 && txtp->vgmstream_count == count && txtp->is_loop_auto))) {
grp->entry.config.config_set = 1;
grp->entry.config.really_force_loop = 1;
}
return true;
fail:
close_vgmstream(vgmstream);
if (!vgmstream)
free_layout_layered(data_l);
return false;
}
static int make_group_random(txtp_header_t* txtp, txtp_group_t* grp, int position, int count, int selected) {
VGMSTREAM* vgmstream = NULL;
/* allowed for actual groups (not final mode), otherwise skip to optimize */
if (!grp && count == 1) {
//;VGM_LOG("TXTP: ignored single group\n");
return true;
}
if (position + count > txtp->vgmstream_count || position < 0 || count < 0) {
VGM_LOG("TXTP: ignored random position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count);
return true;
}
/* 0=actually random for fun and testing, but undocumented since random music is kinda weird, may change anytime
* (plus foobar caches song duration unless .txtp is modifies, so it can get strange if randoms are too different) */
if (selected < 0) {
static int random_seed = 0;
srand((unsigned)txtp + random_seed++); /* whatevs */
selected = (rand() % count); /* 0..count-1 */
//;VGM_LOG("TXTP: autoselected random %i\n", selected);
}
if (selected < 0 || selected > count) {
goto fail;
}
if (selected == count) {
/* special case meaning "select all", basically for quick testing and clearer Wwise */
if (!make_group_segment(txtp, grp, position, count))
goto fail;
vgmstream = txtp->vgmstream[position];
}
else {
/* get selected and remove non-selected */
vgmstream = txtp->vgmstream[position + selected];
txtp->vgmstream[position + selected] = NULL;
for (int i = 0; i < count; i++) {
close_vgmstream(txtp->vgmstream[i + position]);
}
/* set new vgmstream and reorder positions */
update_vgmstream_list(vgmstream, txtp, position, count);
}
/* special "whole loop" settings */
if (grp && grp->entry.loop_anchor_start == 1) {
grp->entry.config.config_set = 1;
grp->entry.config.really_force_loop = 1;
}
/* force selected vgmstream to be a segment when not a group already, and
* group + vgmstream has config (AKA must loop/modify over the result) */
//todo could optimize to not generate segment in some cases?
if (grp &&
!(vgmstream->layout_type == layout_layered || vgmstream->layout_type == layout_segmented) &&
(grp->entry.config.config_set && vgmstream->config.config_set) ) {
if (!make_group_segment(txtp, grp, position, 1))
goto fail;
}
return true;
fail:
close_vgmstream(vgmstream);
return false;
}
static bool parse_groups(txtp_header_t* txtp) {
/* detect single files before grouping */
if (txtp->group_count == 0 && txtp->vgmstream_count == 1) {
txtp->is_single = true;
txtp->is_segmented = false;
txtp->is_layered = false;
}
/* group files as needed */
for (int i = 0; i < txtp->group_count; i++) {
txtp_group_t *grp = &txtp->group[i];
int pos, groups;
//;VGM_LOG("TXTP: apply group %i%c%i%c\n",txtp->group[i].position,txtp->group[i].type,txtp->group[i].count,txtp->group[i].repeat);
/* special meaning of "all files" */
if (grp->position < 0 || grp->position >= txtp->vgmstream_count)
grp->position = 0;
if (grp->count <= 0)
grp->count = txtp->vgmstream_count - grp->position;
/* repeats N groups (trailing files are not grouped) */
if (grp->repeat == TXTP_GROUP_REPEAT) {
groups = ((txtp->vgmstream_count - grp->position) / grp->count);
}
else {
groups = 1;
}
/* as groups are compacted position goes 1 by 1 */
for (pos = grp->position; pos < grp->position + groups; pos++) {
//;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups);
switch(grp->type) {
case TXTP_GROUP_MODE_LAYERED:
if (!make_group_layer(txtp, grp, pos, grp->count))
goto fail;
break;
case TXTP_GROUP_MODE_SEGMENTED:
if (!make_group_segment(txtp, grp, pos, grp->count))
goto fail;
break;
case TXTP_GROUP_MODE_RANDOM:
if (!make_group_random(txtp, grp, pos, grp->count, grp->selected))
goto fail;
break;
default:
goto fail;
}
}
/* group may also have settings (like downmixing) */
apply_settings(txtp->vgmstream[grp->position], &grp->entry);
txtp->entry[grp->position] = grp->entry; /* memcpy old settings for subgroups */
}
/* final tweaks (should be integrated with the above?) */
if (txtp->is_layered) {
if (!make_group_layer(txtp, NULL, 0, txtp->vgmstream_count))
goto fail;
}
if (txtp->is_segmented) {
if (!make_group_segment(txtp, NULL, 0, txtp->vgmstream_count))
goto fail;
}
if (txtp->is_single) {
/* special case of setting start_segment to force/overwrite looping
* (better to use #E but left for compatibility with older TXTPs) */
if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) {
//todo try look settings
//txtp->default_entry.config.config_set = 1;
//txtp->default_entry.config.really_force_loop = 1;
vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples);
}
}
/* apply default settings to the resulting file */
if (txtp->default_entry_set) {
apply_settings(txtp->vgmstream[0], &txtp->default_entry);
}
return true;
fail:
return false;
}
bool txtp_process(txtp_header_t* txtp, STREAMFILE* sf) {
bool ok;
/* process files in the .txtp */
ok = parse_entries(txtp, sf);
if (!ok) goto fail;
/* group files into layouts */
ok = parse_groups(txtp);
if (!ok) goto fail;
/* may happen if using mixed mode but some files weren't grouped */
if (txtp->vgmstream_count != 1) {
VGM_LOG("TXTP: wrong final vgmstream count %i\n", txtp->vgmstream_count);
goto fail;
}
return true;
fail:
return false;
}

View File

@ -16,7 +16,7 @@
#include "util/sf_utils.h"
static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, int format_id);
static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf);
/*****************************************************************************/
@ -39,7 +39,7 @@ VGMSTREAM* init_vgmstream_from_STREAMFILE(STREAMFILE* sf) {
}
bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf, int format_id) {
bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf) {
/* fail if there is nothing/too much to play (<=0 generates empty files, >N writes GBs of garbage) */
if (vgmstream->num_samples <= 0 || vgmstream->num_samples > VGMSTREAM_MAX_NUM_SAMPLES) {
@ -68,7 +68,7 @@ bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf, int format_id) {
/* test if candidate for dual stereo */
if (vgmstream->channels == 1 && vgmstream->allow_dual_stereo == 1) {
try_dual_file_stereo(vgmstream, sf, format_id);
try_dual_file_stereo(vgmstream, sf);
}
@ -428,7 +428,7 @@ fail:
/* See if there is a second file which may be the second channel, given an already opened mono vgmstream.
* If a suitable file is found, open it and change opened_vgmstream to a stereo vgmstream. */
static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, int format_id) {
static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf) {
/* filename search pairs for dual file stereo */
static const char* const dfs_pairs[][2] = {
{"L","R"}, /* most common in .dsp and .vag */
@ -516,7 +516,7 @@ static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, in
return;
//;VGM_LOG("DFS: match %i filename=%s\n", dfs_pair, new_filename);
init_vgmstream_t init_vgmstream_function = get_vgmstream_format_init(format_id);
init_vgmstream_t init_vgmstream_function = get_vgmstream_format_init(opened_vgmstream->format_id);
if (init_vgmstream_function == NULL)
goto fail;

View File

@ -1,8 +1,8 @@
/*
* vgmstream.h - definitions for VGMSTREAM, encapsulating a multi-channel, looped audio stream
* vgmstream.h - internal definitions for VGMSTREAM, encapsulating a multi-channel, looped audio stream
*/
#ifndef _VGMSTREAM_H
#define _VGMSTREAM_H
#ifndef _VGMSTREAM_H_
#define _VGMSTREAM_H_
/* Due mostly to licensing issues, Vorbis, MPEG, G.722.1, etc decoding is done by external libraries.
* Libs are disabled by default, defined on compile-time for builds that support it */
@ -23,29 +23,19 @@
#include "streamfile.h"
#include "vgmstream_types.h"
#include "util/log.h"
#ifdef VGM_USE_MP4V2
#define MP4V2_NO_STDINT_DEFS
#include <mp4v2/mp4v2.h>
#endif
#ifdef VGM_USE_FDKAAC
#include <aacdecoder_lib.h>
#endif
#include "coding/g72x_state.h"
typedef struct {
int config_set; /* some of the mods below are set */
bool config_set; /* some of the mods below are set */
/* modifiers */
int play_forever;
int ignore_loop;
int force_loop;
int really_force_loop;
int ignore_fade;
bool play_forever;
bool ignore_loop;
bool force_loop;
bool really_force_loop;
bool ignore_fade;
/* processing */
double loop_count;
@ -66,18 +56,18 @@ typedef struct {
double pad_end_s;
/* internal flags */
int pad_begin_set;
int trim_begin_set;
int body_time_set;
int loop_count_set;
int trim_end_set;
int fade_delay_set;
int fade_time_set;
int pad_end_set;
bool pad_begin_set;
bool trim_begin_set;
bool body_time_set;
bool loop_count_set;
bool trim_end_set;
bool fade_delay_set;
bool fade_time_set;
bool pad_end_set;
/* for lack of a better place... */
int is_txtp;
int is_mini_txtp;
bool is_txtp;
bool is_mini_txtp;
} play_config_t;
@ -165,7 +155,7 @@ typedef struct {
meta_t meta_type; /* type of metadata */
/* loop config */
int loop_flag; /* is this stream looped? */
bool loop_flag; /* is this stream looped? */
int32_t loop_start_sample; /* first sample of the loop (included in the loop) */
int32_t loop_end_sample; /* last sample of the loop (not included in the loop) */
@ -187,7 +177,7 @@ typedef struct {
/* other config */
bool allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
int format_id; /* internal format ID */
/* layout/block state */
size_t full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */
@ -267,26 +257,6 @@ typedef struct {
} layered_layout_data;
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
typedef struct {
STREAMFILE* streamfile;
uint64_t start;
uint64_t offset;
uint64_t size;
} mp4_streamfile;
typedef struct {
mp4_streamfile if_file;
MP4FileHandle h_mp4file;
MP4TrackId track_id;
unsigned long sampleId, numSamples;
UINT codec_init_data_size;
HANDLE_AACDECODER h_aacdecoder;
unsigned int sample_ptr, samples_per_frame, samples_discard;
INT_PCM sample_buffer[( (6) * (2048)*4 )];
} mp4_aac_codec_data;
#endif
// VGMStream description in structure format
typedef struct {
int sample_rate;

View File

@ -237,11 +237,8 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_kt_wiibgm,
init_vgmstream_bfstm,
init_vgmstream_mca,
#if 0
init_vgmstream_mp4_aac,
#endif
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
init_vgmstream_akb_mp4,
init_vgmstream_mp4_aac,
#endif
init_vgmstream_ktss,
init_vgmstream_hca,
@ -575,10 +572,10 @@ VGMSTREAM* detect_vgmstream_format(STREAMFILE* sf) {
if (!vgmstream)
continue;
int format_id = i + 1;
vgmstream->format_id = i + 1;
/* validate + setup vgmstream */
if (!prepare_vgmstream(vgmstream, sf, format_id)) {
if (!prepare_vgmstream(vgmstream, sf)) {
/* keep trying if wasn't valid, as simpler formats may return a vgmstream by mistake */
close_vgmstream(vgmstream);
continue;

View File

@ -4,7 +4,7 @@
#include "meta/meta.h"
#include "vgmstream.h"
bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf, int format_id);
bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf);
VGMSTREAM* detect_vgmstream_format(STREAMFILE* sf);
init_vgmstream_t get_vgmstream_format_init(int format_id);

View File

@ -47,7 +47,7 @@
<VWmpPreprocessorDefinitions>$(VCmnPreprocessorDefinitions);IN_VGMSTREAM_EXPORTS;VGM_WINAMP_UNICODE</VWmpPreprocessorDefinitions>
<!-- link .lib -->
<VCmnAdditionalDependencies32>../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;../ext_libs/libspeex-1.lib</VCmnAdditionalDependencies32>
<VCliAdditionalDependencies32>$(VCmnAdditionalDependencies32);../ext_libs/jansson.lib</VCliAdditionalDependencies32>
<VCliAdditionalDependencies32>$(VCmnAdditionalDependencies32);</VCliAdditionalDependencies32>
<VFooAdditionalDependencies32>$(VCmnAdditionalDependencies32);$(VCmnDependenciesDir)/foobar/foobar2000/shared/shared-Win32.lib</VFooAdditionalDependencies32>
<VCmnAdditionalDependencies64>../ext_libs/dll-x64/libvorbis.lib;../ext_libs/dll-x64/libmpg123-0.lib;../ext_libs/dll-x64/libg719_decode.lib;../ext_libs/dll-x64/avcodec.lib;../ext_libs/dll-x64/avformat.lib;../ext_libs/dll-x64/avutil.lib;../ext_libs/dll-x64/swresample.lib;../ext_libs/dll-x64/libatrac9.lib;../ext_libs/dll-x64/libcelt-0061.lib;../ext_libs/dll-x64/libcelt-0110.lib;../ext_libs/dll-x64/libspeex-1.lib</VCmnAdditionalDependencies64>
<VCliAdditionalDependencies64>$(VCmnAdditionalDependencies64)</VCliAdditionalDependencies64>