mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-24 06:50:20 +01:00
Merge pull request #1569 from bnnm/api-misc5
- Fix broken .TMX files [Need for Speed Undercover (PC)] - cleanup
This commit is contained in:
commit
8160794db5
2
.github/workflows/cmake-lx.yml
vendored
2
.github/workflows/cmake-lx.yml
vendored
@ -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
|
||||
|
2
.github/workflows/cmake-mac.yml
vendored
2
.github/workflows/cmake-mac.yml
vendored
@ -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
|
||||
|
7
.github/workflows/cmake-wasm.yml
vendored
7
.github/workflows/cmake-wasm.yml
vendored
@ -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:
|
||||
|
@ -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 "")
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,41 +451,24 @@ static bool is_valid_extension(cli_config_t* cfg) {
|
||||
return vgmstream_ctx_is_valid(cfg->infilename, &vcfg);
|
||||
}
|
||||
|
||||
|
||||
static bool convert_file(cli_config_t* cfg) {
|
||||
static VGMSTREAM* open_vgmstream(cli_config_t* cfg) {
|
||||
STREAMFILE* sf = NULL;
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
char outfilename_temp[PATH_LIMIT];
|
||||
int32_t len_samples;
|
||||
|
||||
|
||||
/* for plugin testing */
|
||||
if (!is_valid_extension(cfg))
|
||||
return false;
|
||||
|
||||
/* open streamfile and pass subsong */
|
||||
{
|
||||
STREAMFILE* sf = open_stdio_streamfile(cfg->infilename);
|
||||
sf = open_stdio_streamfile(cfg->infilename);
|
||||
if (!sf) {
|
||||
fprintf(stderr, "file %s not found\n", cfg->infilename);
|
||||
goto fail;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -504,6 +480,35 @@ static bool convert_file(cli_config_t* cfg) {
|
||||
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[CLI_PATH_LIMIT];
|
||||
int32_t len_samples;
|
||||
|
||||
|
||||
/* for plugin testing */
|
||||
if (!is_valid_extension(cfg))
|
||||
return false;
|
||||
|
||||
/* open streamfile and pass subsong */
|
||||
vgmstream = open_vgmstream(cfg);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* 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) {
|
||||
|
@ -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
|
||||
|
@ -105,6 +105,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="vgmstream_cli.h" />
|
||||
<ClInclude Include="vjson.h" />
|
||||
<ClInclude Include="wav_utils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
vjson_t j = {0};
|
||||
|
||||
char buf[0x4000]; // exts need ~0x1400
|
||||
vjson_init(&j, buf, sizeof(buf));
|
||||
|
||||
vjson_obj_open(&j);
|
||||
vjson_keystr(&j, "version", vgmstream_version);
|
||||
|
||||
vjson_key(&j, "extensions");
|
||||
vjson_obj_open(&j);
|
||||
|
||||
vjson_key(&j, "vgm");
|
||||
vjson_arr_open(&j);
|
||||
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();
|
||||
|
||||
for (size_t i = 0; i < extension_list_len; ++i) {
|
||||
json_t* ext = json_string(extension_list[i]);
|
||||
json_array_append(ext_list, ext);
|
||||
for (int i = 0; i < extension_list_len; i++) {
|
||||
vjson_str(&j, extension_list[i]);
|
||||
}
|
||||
vjson_arr_close(&j);
|
||||
|
||||
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_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_t* version_string = json_string(vgmstream_version);
|
||||
vjson_obj_close(&j);
|
||||
vjson_obj_close(&j);
|
||||
|
||||
json_t* final_object = json_object();
|
||||
json_object_set(final_object, "version", version_string);
|
||||
json_decref(version_string);
|
||||
|
||||
json_object_set(final_object, "extensions",
|
||||
json_pack("{soso}",
|
||||
"vgm", ext_list,
|
||||
"common", cext_list));
|
||||
|
||||
json_dumpf(final_object, stdout, JSON_COMPACT);
|
||||
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);
|
||||
|
||||
json_t* mixing_info = NULL;
|
||||
|
||||
// The JSON pack format string is defined here: https://jansson.readthedocs.io/en/latest/apiref.html#building-values
|
||||
vjson_obj_open(&j);
|
||||
vjson_keystr(&j, "version", vgmstream_version);
|
||||
vjson_keyint(&j, "sampleRate", info.sample_rate);
|
||||
vjson_keyint(&j, "channels", info.channels);
|
||||
|
||||
vjson_key(&j, "mixingInfo");
|
||||
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_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);
|
||||
}
|
||||
|
||||
json_t* loop_info = NULL;
|
||||
vjson_keyintnull(&j, "channelLayout", info.channel_layout);
|
||||
|
||||
vjson_key(&j, "loopingInfo");
|
||||
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_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* interleave_info = NULL;
|
||||
|
||||
vjson_key(&j, "interleaveInfo");
|
||||
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_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);
|
||||
}
|
||||
|
||||
json_t* stream_info = json_pack("{sisssi}",
|
||||
"index", info.stream_info.current,
|
||||
"name", info.stream_info.name,
|
||||
"total", info.stream_info.total
|
||||
);
|
||||
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);
|
||||
|
||||
if (info.stream_info.name[0] == '\0') {
|
||||
json_object_set(stream_info, "name", json_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);
|
||||
|
||||
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
|
||||
);
|
||||
vjson_obj_close(&j);
|
||||
|
||||
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
154
cli/vjson.h
Normal 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
|
@ -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()
|
@ -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"
|
||||
|
@ -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`
|
||||
|
@ -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
|
@ -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
|
@ -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">
|
||||
|
@ -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.
@ -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
|
||||
|
@ -228,7 +228,7 @@ $fb2kFiles64 = @(
|
||||
)
|
||||
|
||||
$fb2kFiles_remove = @(
|
||||
"bin/foobar2000/jansson.dll"
|
||||
"bin/foobar2000/xxxx.dll"
|
||||
)
|
||||
|
||||
$cliPdbFiles32 = @(
|
||||
|
55
src/api.h
55
src/api.h
@ -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
|
||||
|
202
src/api_decode.h
202
src/api_decode.h
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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;
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
361
src/libvgmstream.h
Normal 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
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
/* 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);
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -2,158 +2,41 @@
|
||||
#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;
|
||||
}
|
||||
|
1878
src/meta/txtp.c
1878
src/meta/txtp.c
File diff suppressed because it is too large
Load Diff
153
src/meta/txtp.h
Normal file
153
src/meta/txtp.h
Normal 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
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
634
src/meta/txtp_process.c
Normal 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 = ¤t->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, ¤t->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;
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user