diff --git a/Makefile b/Makefile index a081c73b..c7b737b0 100644 --- a/Makefile +++ b/Makefile @@ -300,4 +300,4 @@ clean: $(MAKE) -C xmplay clean $(MAKE) -C ext_libs clean -.PHONY: clean buildfullrelease buildrelease sourceball bin vgmstream-cli vgmstream_cli vgmstream123 winamp xmplay version +.PHONY: clean buildfullrelease buildrelease sourceball bin vgmstream-cli vgmstream_cli vgmstream123 api_example winamp xmplay version diff --git a/cli/Makefile b/cli/Makefile index ead48746..066d43f2 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -59,6 +59,6 @@ $(TARGET_EXT_LIBS): $(MAKE) -C ../ext_libs $@ clean: - $(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_123) + $(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_API) .PHONY: clean vgmstream_cli libvgmstream.a $(TARGET_EXT_LIBS) diff --git a/cli/api_example.c b/cli/api_example.c index d4215b88..acb6cd0e 100644 --- a/cli/api_example.c +++ b/cli/api_example.c @@ -1,112 +1,410 @@ -#if 0 -#include -#include -#include "../src/api.h" - - -static void usage(const char* progname) { - fprintf(stderr, "API test v%08x\n" - "Usage: %s \n" - , libvgmstream_get_version() - , progname - ); -} - -static FILE* get_output_file(const char* filename) { - char out_filename[0x7FFF]; - snprintf(out_filename, sizeof(out_filename), "%s.pcm", filename); - - FILE* outfile = fopen(out_filename, "wb"); - if (!outfile) { - fprintf(stderr, "failed to open %s for output\n", out_filename); - } - return outfile; -} - -static libvgmstream_streamfile_t* get_streamfile(const char* filename) { - return NULL; -} - -/* simplistic example of vgmstream's API - * for something a bit more featured see vgmstream-cli - */ -int main(int argc, char** argv) { - int err; - FILE* outfile = NULL; - const char* infile; - - if (argc != 2) { - usage(argv[0]); - return EXIT_FAILURE; - } - - infile = argv[1]; - - - // main init - libvgmstream_t* lib = libvgmstream_init(); - if (!lib) return EXIT_FAILURE; - - - // set default config - libvgmstream_config_t cfg = { - .loop_count = 2.0, - .fade_time = 10.0, - }; - libvgmstream_setup(lib, &cfg); - - - // open target file - libvgmstream_options_t options = { - .sf = get_streamfile(infile) - }; - err = libvgmstream_open(lib, &options); - if (err < 0) goto fail; - - - // external SF is not needed after _open - libvgmstream_streamfile_close(options.sf); - - - // output file - outfile = get_output_file(infile); - if (!outfile) goto fail; - - - // play file and do something with decoded samples - while (true) { - int pos; - - if (lib->decoder->done) - break; - - // get current samples - err = libvgmstream_play(lib); - if (err < 0) goto fail; - - fwrite(lib->decoder->buf, sizeof(uint8_t), lib->decoder->buf_bytes, outfile); - - pos = (int)libvgmstream_play_position(lib); - printf("\rpos: %d", pos); - fflush(stdout); - } - printf("\n"); - - // close current streamfile before opening new ones, optional - //libvgmstream_close(lib); - - // process done - libvgmstream_free(lib); - fclose(outfile); - - printf("done\n"); - return EXIT_SUCCESS; -fail: - // process failed - libvgmstream_free(lib); - fclose(outfile); - - printf("failed!\n"); - return EXIT_FAILURE; -} -#endif +#include "../src/api.h" +#if LIBVGMSTREAM_ENABLE +#include +#include +#include + +#include "../src/base/api_internal.h" + + +static void usage(const char* progname) { + fprintf(stderr, "Usage: %s \n" + , progname + ); +} + +static FILE* get_output_file(const char* filename) { + char out_filename[0x7FFF]; + snprintf(out_filename, sizeof(out_filename), "%s.pcm", filename); + + FILE* outfile = fopen(out_filename, "wb"); + if (!outfile) { + fprintf(stderr, "failed to open %s for output\n", out_filename); + } + return outfile; +} + +static libvgmstream_streamfile_t* get_streamfile(const char* filename) { + return libvgmstream_streamfile_from_filename(filename); +} + +static int api_example(const char* infile) { +VGM_STEP(); + int err; + FILE* outfile = NULL; + + bool fill_test; + int pcm16_samples; + int pcm16_bytes; + short* pcm16 = NULL; + + + // main init + libvgmstream_t* lib = libvgmstream_init(); + if (!lib) return EXIT_FAILURE; + + + // set default config + libvgmstream_config_t cfg = { + //.loop_count = 1.0, + //.fade_time = 10.0, + .ignore_loop = true, + }; + libvgmstream_setup(lib, &cfg); + + + // open target file + libvgmstream_options_t opt = { + .libsf = get_streamfile(infile) + }; + err = libvgmstream_open(lib, &opt); + // external SF is not needed after _open + libvgmstream_streamfile_close(opt.libsf); + + if (err < 0) { + printf("not a valid file\n"); + goto fail; + } + + // output file + outfile = get_output_file(infile); + if (!outfile) goto fail; + + { + char title[128]; + libvgmstream_title_t cfg = { + .filename = infile, + .remove_extension = true, + }; + libvgmstream_get_title(lib, &cfg, title, sizeof(title)); + + printf("- format title:\n"); + printf("%s\n", title); + } + + { + char describe[1024]; + libvgmstream_format_describe(lib, describe, sizeof(describe)); + + printf("- format describe:\n"); + printf("%s\n", describe); + } + + printf("- format info\n"); + 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("\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; + + // play file and do something with decoded samples + while (true) { + //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); + if (err < 0) goto fail; + + buf = pcm16; + buf_bytes = err * sizeof(short) * lib->format->channels; + } + else { + err = libvgmstream_play(lib); + if (err < 0) goto fail; + + buf = lib->decoder->buf; + buf_bytes = lib->decoder->buf_bytes; + } + + fwrite(buf, sizeof(uint8_t), buf_bytes, outfile); + + //pos = (int)libvgmstream_get_play_position(lib); + //printf("\r- decode pos: %d", pos); + //fflush(stdout); + } + printf("\n"); + + // close current streamfile before opening new ones, optional + //libvgmstream_close(lib); + + // process done + libvgmstream_free(lib); + fclose(outfile); + free(pcm16); + + printf("done\n"); + return EXIT_SUCCESS; +fail: + // process failed + libvgmstream_free(lib); + fclose(outfile); + free(pcm16); + + printf("failed!\n"); + return EXIT_FAILURE; +} + + +static void test_lib_is_valid() { + VGM_STEP(); + + bool expected, result; + + expected = true; + result = libvgmstream_is_valid("test.adx", NULL); + assert(expected == result); + + expected = true; + result = libvgmstream_is_valid("extensionless", NULL); + assert(expected == result); + + libvgmstream_valid_t cfg = { + .reject_extensionless = true + }; + expected = false; + result = libvgmstream_is_valid("extensionless", &cfg); + assert(expected == result); +} + +static void test_lib_extensions() { + VGM_STEP(); + + const char** exts; + size_t size = 0; + + size = 0; + exts = libvgmstream_get_extensions(&size); + assert(exts != NULL && size > 100); + + exts = libvgmstream_get_extensions(NULL); + assert(exts == NULL); + + size = 0; + exts = libvgmstream_get_common_extensions(&size); + assert(exts != NULL && size > 1 && size < 100); + + exts = libvgmstream_get_common_extensions(NULL); + assert(exts == NULL); +} + +static libvgmstream_streamfile_t* test_libsf_open() { + VGM_STEP(); + + libvgmstream_streamfile_t* libsf = NULL; + + libsf = libvgmstream_streamfile_from_filename("api.bin_wrong"); + assert(libsf == NULL); + + libsf = libvgmstream_streamfile_from_filename("api.bin"); + assert(libsf != NULL); + + return libsf; +} + + +static void test_libsf_read(libvgmstream_streamfile_t* libsf) { + VGM_STEP(); + + int read; + uint8_t buf[0x20]; + + read = libsf->read(libsf->user_data, buf, sizeof(buf)); + assert(read == sizeof(buf)); + for (int i = 0; i < sizeof(buf); i++) { + assert(buf[i] == ((i + 0x00) & 0xFF)); + } + + read = libsf->read(libsf->user_data, buf, sizeof(buf)); + assert(read == sizeof(buf)); + for (int i = 0; i < sizeof(buf); i++) { + assert(buf[i] == ((i + 0x20) & 0xFF)); + } +} + +static void test_libsf_seek_read(libvgmstream_streamfile_t* libsf) { + VGM_STEP(); + + int read, res; + uint8_t buf[0x20]; + + res = libsf->seek(libsf->user_data, 0x19BC0, 0); + assert(res == 0); + + read = libsf->read(libsf->user_data, buf, sizeof(buf)); + assert(read == sizeof(buf)); + for (int i = 0; i < sizeof(buf); i++) { + assert(buf[i] == ((i + 0xC0) & 0xFF)); + } + + res = libsf->seek(libsf->user_data, 0x7FFFFF, 0); + assert(res == 0); + + read = libsf->read(libsf->user_data, buf, sizeof(buf)); + assert(read == 0); +} + +static void test_libsf_size(libvgmstream_streamfile_t* libsf) { + VGM_STEP(); + + int64_t size = libsf->get_size(libsf->user_data); + assert(size == 0x20000); +} + +static void test_libsf_name(libvgmstream_streamfile_t* libsf) { + VGM_STEP(); + + char name[128]; + libsf->get_name(libsf->user_data, name, sizeof(name)); + assert(strcmp(name, "api.bin") == 0); +} + +static void test_libsf_reopen(libvgmstream_streamfile_t* libsf) { + VGM_STEP(); + + uint8_t buf[0x20]; + int read; + + libvgmstream_streamfile_t* newsf = NULL; + + newsf = libsf->open(libsf->user_data, "api2.bin_wrong"); + assert(newsf == NULL); + + newsf = libsf->open(libsf->user_data, "api2.bin"); + assert(newsf != NULL); + + read = newsf->read(newsf->user_data, buf, sizeof(buf)); + assert(read == sizeof(buf)); + assert(buf[0x10] == 0x10); + + newsf->close(newsf); +} + +static void test_libsf_apisf(libvgmstream_streamfile_t* libsf) { + VGM_STEP(); + + STREAMFILE* sf = open_api_streamfile(libsf); + assert(sf != NULL); + + int read; + uint8_t buf[0x20]; + + read = read_streamfile(buf, 0xF0, sizeof(buf), sf); + assert(read == sizeof(buf)); + for (int i = 0; i < sizeof(buf); i++) { + assert(buf[i] == ((i + 0xF0) & 0xFF)); + } + + size_t size = get_streamfile_size(sf); + assert(size == 0x20000); + + close_streamfile(sf); + +} + + +static void test_lib_streamfile() { + VGM_STEP(); + + libvgmstream_streamfile_t* libsf = test_libsf_open(); + test_libsf_read(libsf); + test_libsf_seek_read(libsf); + test_libsf_size(libsf); + test_libsf_name(libsf); + test_libsf_reopen(libsf); + test_libsf_apisf(libsf); + + // close + libsf->close(libsf); +} + +static void test_lib_tags() { + VGM_STEP(); + + libvgmstream_streamfile_t* libsf = NULL; + libvgmstream_tags_t* tags = NULL; + bool more = false; + + libsf = libvgmstream_streamfile_from_filename("sample_!tags.m3u"); + assert(libsf != NULL); + + tags = libvgmstream_tags_init(libsf); + assert(tags != NULL); + + + libvgmstream_tags_find(tags, "filename1.adx"); + more = libvgmstream_tags_next_tag(tags); + assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0); + more = libvgmstream_tags_next_tag(tags); + assert(more && strcmp(tags->key, "TITLE") == 0 && strcmp(tags->val, "filename1 title") == 0); + more = libvgmstream_tags_next_tag(tags); + assert(!more); + + + libvgmstream_tags_find(tags, "filename2.adx"); + more = libvgmstream_tags_next_tag(tags); + assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0); + more = libvgmstream_tags_next_tag(tags); + assert(more && strcmp(tags->key, "TITLE") == 0 && strcmp(tags->val, "filename2 title") == 0); + more = libvgmstream_tags_next_tag(tags); + assert(!more); + + + libvgmstream_tags_find(tags, "filename3.adx"); + more = libvgmstream_tags_next_tag(tags); + assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0); + more = libvgmstream_tags_next_tag(tags); + assert(!more); + + + libvgmstream_tags_find(tags, "filename_incorrect.adx"); + more = libvgmstream_tags_next_tag(tags); + assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0); + more = libvgmstream_tags_next_tag(tags); + assert(!more); + + libvgmstream_tags_free(tags); + libvgmstream_streamfile_close(libsf); +} + + +/* simplistic example of vgmstream's API + * for something a bit more featured see vgmstream-cli + */ +int main(int argc, char** argv) { + printf("API v%08x test\n", libvgmstream_get_version()); + + libvgmstream_log_t cfg = { + .stdout_callback = true + }; + libvgmstream_set_log(&cfg); + + test_lib_is_valid(); + test_lib_extensions(); + test_lib_streamfile(); + test_lib_tags(); + + if (argc != 2) { + usage(argv[0]); + return EXIT_FAILURE; + } + + api_example(argv[1]); + + return 0; +} +#endif diff --git a/src/api.h b/src/api.h index 33808782..e2d6ef68 100644 --- a/src/api.h +++ b/src/api.h @@ -2,14 +2,34 @@ #define _API_H_ #include "base/plugins.h" //TODO: to be removed -#if 0 +//#define LIBVGMSTREAM_ENABLE 1 +#if LIBVGMSTREAM_ENABLE /* vgmstream's public API - * basic usage (also see api_example.c): - * - libvgmstream_get_version() // if needed + * + * 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 (since complex formats need those features). 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 to consider 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_get_version() // just in case * - libvgmstream_init(...) // base context - * - libvgmstream_setup(...) // standard config - * - libvgmstream_open(...) // check detected format + * - 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 @@ -33,14 +53,22 @@ #endif -/* Current API version. Only refers to the API itself, as changes related to formats/etc don't alter it. - * vgmstream's features are mostly stable, while this API may change from time to time. - * May change as well when related (such as api_streamfile.h) are changed. */ +/* 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 -#include "api_main.h" +/* 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); + + +#include "api_decode.h" +#include "api_helpers.h" #include "api_streamfile.h" #include "api_tags.h" diff --git a/src/api_decode.h b/src/api_decode.h new file mode 100644 index 00000000..3d0d6f11 --- /dev/null +++ b/src/api_decode.h @@ -0,0 +1,202 @@ +#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 diff --git a/src/api_helpers.h b/src/api_helpers.h new file mode 100644 index 00000000..9d070ba1 --- /dev/null +++ b/src/api_helpers.h @@ -0,0 +1,81 @@ +#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 diff --git a/src/api_main.c b/src/api_main.c deleted file mode 100644 index 16c2a3e0..00000000 --- a/src/api_main.c +++ /dev/null @@ -1,156 +0,0 @@ -#if 0 -#include "api.h" -#include "vgmstream.h" - -#define LIBVGMSTREAM_ERROR_GENERIC -1 -#define LIBVGMSTREAM_OK 0 - - -LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void) { - return (LIBVGMSTREAM_API_VERSION_MAJOR << 24) | (LIBVGMSTREAM_API_VERSION_MINOR << 16) | (LIBVGMSTREAM_API_VERSION_PATCH << 0); -} - - -/* vgmstream context/handle */ -typedef struct { - - libvgmstream_config_t cfg; // current config - libvgmstream_format_t fmt; // format config - libvgmstream_decoder_t dec; // decoder config -} libvgmstream_internal_t; - - - -/* base init */ -LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void) { - libvgmstream_t* lib = NULL; - libvgmstream_internal_t* priv = NULL; - - lib = calloc(1, sizeof(libvgmstream_t)); - if (!lib) goto fail; - - lib->priv = calloc(1, sizeof(libvgmstream_internal_t)); - if (!lib->priv) goto fail; - - priv = lib->priv; - - //TODO only setup on decode? (but may less error prone if set) - lib->format = &priv->fmt; - lib->decoder = &priv->dec; - - return lib; -fail: - libvgmstream_free(lib); - return NULL; -} - -/* base free */ -LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib) { - if (!lib) - return; - - //TODO close stuff in priv - //if (lib->priv) { - // libvgmstream_internal_t* priv = lib->priv; - //} - - free(lib->priv); - free(lib); -} - -/* current play config */ -LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg) { - if (!lib) - return; - if (!cfg) - return; - libvgmstream_internal_t* priv = lib->priv; - - priv->cfg = *cfg; - //TODO -} - -/* open new file */ -LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* opt) { - if (!lib) - return LIBVGMSTREAM_ERROR_GENERIC; - //if (!opt || !opt->sf || opt->subsong < 0) - // return LIBVGMSTREAM_ERROR_GENERIC; - - // close loaded song if any - libvgmstream_close(lib); - - //format_internal_id - - // TODO open new vgmstream, save in priv - - return LIBVGMSTREAM_OK; -} - -/* query new vgmstream, without closing current one */ -LIBVGMSTREAM_API int libvgmstream_open_info(libvgmstream_t* lib, libvgmstream_options_t* opt, libvgmstream_format_t* fmt) { - //TODO - return LIBVGMSTREAM_ERROR_GENERIC; -} - -/* close current vgmstream */ -LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib) { - //TODO close current vgmstream -} - - -/* decodes samples */ -LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib) { - // TODO - - //if (!lib->decoder) - // ... - - lib->decoder->done = true; - - return LIBVGMSTREAM_OK; -} - -/* vgmstream current decode position */ -LIBVGMSTREAM_API int64_t libvgmstream_play_position(libvgmstream_t* lib) { - // TODO - - return 0; -} - -/* vgmstream seek */ -LIBVGMSTREAM_API int libvgmstream_seek(libvgmstream_t* lib, int64_t sample) { - return LIBVGMSTREAM_ERROR_GENERIC; -} - - -LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_format_t* format, char* dst, int dst_size) { - return LIBVGMSTREAM_ERROR_GENERIC; -} - -LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg) { - //TODO - - return false; -} - - -LIBVGMSTREAM_API void libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len) { - //TODO -} - - -LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* log) { - -} - -/* -LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_get_file(const char* filename) { - STREAMFILE* sf = open_stdio_streamfile(filename); - //TODO make STREAMFILE compatible with libvgmstream_streamfile_t - // would need bridge functions (since defs aren't compatible), not ideal since we are calling functions over functions - // but basically not noticeable performance-wise -} -*/ - -#endif diff --git a/src/api_main.h b/src/api_main.h deleted file mode 100644 index 9273f784..00000000 --- a/src/api_main.h +++ /dev/null @@ -1,235 +0,0 @@ -#if 0 -#ifndef _API_MAIN_H_ -#define _API_MAIN_H_ -#include "api.h" -#include "api_streamfile.h" - -/* vgmstream's main (decode) API. - */ - -/* 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); - -// interleaved: buf[0]=ch0, buf[1]=ch1, buf[2]=ch0, buf[3]=ch0, ... -enum { - LIBVGMSTREAM_SAMPLE_PCM16 = 0x01, - LIBVGMSTREAM_SAMPLE_FLOAT = 0x02, -}; - -typedef struct { - /* main (always set) */ - const int channels; // output channels - const int sample_rate; // output sample rate - const int sample_type; // size of resulting samples - - /* extra info (may be 0 if not known or not relevant) */ - const uint32_t channel_layout; // standard bitflags - const int input_channels; // original file's channels before downmixing (if any) - - const int interleave; // when file is interleaved - const int frame_size; // when file has some configurable frame size - - const int subsong_index; // 0 = none, N = loaded subsong N - const int subsong_count; // 0 = format has no subsongs, N = has N subsongs (1 = format has subsongs and only 1) - - /* sample info (may not be used depending on config) */ - const int64_t sample_count; // file's max samples (not final play duration) - const int64_t loop_start; // loop start sample - const int64_t loop_end; // loop end sample - const bool loop_flag; // if file loops; note that false + defined loops means looping was forcefully disabled - - const int64_t play_time; // samples after all calculations (after applying loop/fade/etc config) - // ** may not be 100% accurate in some cases (must check decoder's 'done' flag) - // ** if loop_forever is set this value is provided for reference based on non-forever config - - const bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior (do not use to export) - - const int stream_bitrate; // average bitrate of the subsong (slightly bloated vs codec_bitrate: incorrect in rare cases) - //const int codec_bitrate; // average bitrate of the codec data (not possible/slow to calculate in most cases) - - const 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, do not store - - /* descriptions */ - const char codec[256]; - const char layout[256]; - const char metadata[256]; - -} 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 */ - 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 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_delay; // fade delay after target loops - double fade_time; // fade period after target loops - - int max_channels; // automatic downmixing if vgmstream's channels are higher than max_channels - // ** for players that can only handle N channels, but this type of downmixing is very simplistic and not recommended - - int force_format; // forces output buffer to be remixed into some LIBVGMSTREAM_SAMPLE_x format - - //bool disable_config_override; // ignore forced (TXTP) config - -} 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* sf; // 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 = unknown/first - // ** to check if a file has subsongs, _open first + check total_subsongs (then _open 2nd, 3rd, etc) - - void* external_buf; // set a custom-sized sample buf instead of the default provided buf - // ** must be at least as big as channels * sample-size * buf_samples - // ** libvgmstream decoder->buf will set this buf - // ** slower than using the provided buf (needs copying around), mainly if you have fixed sample constraints - int external_buf_samples; // max samples the custom buffer may hold - - int format_internal_id; // force a format (for example when loading new subsong) - - 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); - -/* 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 - * - the only way for vgmstream to know a file's metadata is getting 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* open_options, libvgmstream_format_t* format); - -/* closes current song; may still use libvgmstream to open other songs - */ -LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib); - - -/* decodes next batch of samples (lib->decoder->* will be updated) - * - returns < 0 on error - */ -LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib); - -/* Gets current position within the song. - * - return < 0 on error (file not ready) */ -LIBVGMSTREAM_API int64_t libvgmstream_play_position(libvgmstream_t* lib); - -/* Seeks to absolute position. Will clamp incorrect values. - * - return < 0 on error (ignores seek attempt) - * - on play_forever one may locally seek to any position, but there is an internal limit */ -LIBVGMSTREAM_API int libvgmstream_seek(libvgmstream_t* lib, int64_t sample); - - -/* Writes a description of the format into dst. Will always be null-terminated. - * - returns < 0 if file was truncated, though will still succeed. */ -LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_format_t* format, char* dst, int dst_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); - - -//TODO are all these options necessary? -typedef struct { - bool force_title; - bool subsong_range; - bool remove_extension; - bool remove_archive; - const char* filename; -} libvgmstream_title_t; - -/* get a simple title for plugins, derived from internal stream name if available - * - valid after _open */ -LIBVGMSTREAM_API void libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len); - - -enum { - LIBVGMSTREAM_LOG_LEVEL_ALL = 0, - LIBVGMSTREAM_LOG_LEVEL_DEBUG = 20, - LIBVGMSTREAM_LOG_LEVEL_INFO = 30, - LIBVGMSTREAM_LOG_LEVEL_NONE = 100, -}; - -typedef struct { - int level; // log level - void (*callback)(int level, const char* str); // log callback - bool stdout_callback; // use log callback -} 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* log); - -#endif -#endif diff --git a/src/api_streamfile.h b/src/api_streamfile.h index b5c89cbf..c581a542 100644 --- a/src/api_streamfile.h +++ b/src/api_streamfile.h @@ -1,51 +1,71 @@ -#if 0 #ifndef _API_STREAMFILE_H_ #define _API_STREAMFILE_H_ +#include "api.h" +#if LIBVGMSTREAM_ENABLE /* vgmstream's IO API, defined as a "streamfile" (SF). * - * Unlike more typical IO, vgmstream has particular needs that roughly assume there is some underlying filesystem (as typical of games): - * - reading from arbitrary offsets: header not found in the beginning of a stream, rewinding during looping, etc - * - opening other streamfiles: reopening a copy of current SF, formats with split header + data, decryption files, etc - * - filename: opening similarly named companion files, heuristics when file's data is not enough, etc + * 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 use case can't satisfy those constraints, it may still be possible to create a streamfile that just simulates part of it. - * For example, loading data into memory, returning a fake filename, and only handling "open" that reopens itself (same filename), - * while returning default/incorrect values on non-handled operations. Simpler, non-looped formats probably work fine just being read linearly. + * 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. */ -#include "api.h" -// TODO: pass opaque instead? +enum { + LIBVGMSTREAM_STREAMFILE_SEEK_SET = 0, + LIBVGMSTREAM_STREAMFILE_SEEK_CUR = 1, + LIBVGMSTREAM_STREAMFILE_SEEK_END = 2, + //LIBVGMSTREAM_STREAMFILE_SEEK_GET_OFFSET = 3, + //LIBVGMSTREAM_STREAMFILE_SEEK_GET_SIZE = 5, +}; + typedef struct libvgmstream_streamfile_t { - /* user data */ - void* opaque; + //uint32_t flags; // info flags for vgmstream + void* user_data; // any internal structure - /* read 'length' data at 'offset' to 'dst' (implicit seek) */ - size_t (*read)(struct libvgmstream_streamfile_t* sf, uint8_t* dst, int64_t offset, size_t length); + /* read 'length' data at internal offset to 'dst' (implicit seek if needed) + * - assumes 0 = failure/EOF + */ + int (*read)(void* user_data, uint8_t* dst, int dst_size); - /* get max offset */ - size_t (*get_size)(struct libvgmstream_streamfile_t* sf); //TODO return int64_t? + /* seek to offset + * - note that due to how vgmstream works this is a fairly common operation (to be optimized later) + */ + int64_t (*seek)(void* user_data, int64_t offset, int whence); - /* copy current filename to name buf */ - void (*get_name)(struct libvgmstream_streamfile_t* sf, char* name, size_t name_size); + /* get max offset + */ + int64_t (*get_size)(void* user_data); - /* open another streamfile from filename (which may be some internal path/protocol) */ - struct libvgmstream_streamfile_t* (*open)(struct libvgmstream_streamfile_t* sf, const char* const filename, size_t buf_size); + /* copy current filename to name buf + */ + void (*get_name)(void* user_data, char* name, int name_size); //TODO return char*? - /* free current STREAMFILE */ - void (*close)(struct libvgmstream_streamfile_t* sf); + /* 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 + */ + struct libvgmstream_streamfile_t* (*open)(void* user_data, const char* filename); + + /* free current SF (needed for copied streamfiles) */ + void (*close)(struct libvgmstream_streamfile_t* libsf); } libvgmstream_streamfile_t; + /* helper */ -static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* sf) { - if (!sf) +static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* libsf) { + if (!libsf || !libsf->close) return; - sf->close(sf); + libsf->close(libsf); } -//LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_get_file(const char* filename); + +LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_from_filename(const char* filename); #endif #endif diff --git a/src/api_tags.h b/src/api_tags.h index 97ded596..e0c00699 100644 --- a/src/api_tags.h +++ b/src/api_tags.h @@ -1,37 +1,42 @@ -#if 0 #ifndef _API_TAGS_H_ #define _API_TAGS_H_ #include "api.h" -#include "api_streamfile.h" +#if LIBVGMSTREAM_ENABLE /* vgmstream's !tags.m3u API. - * Doesn't need a main libvgmstream lib as tags aren't tied to loaded songs. + * 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. - * - may be reused for different files for cache purposed - */ -LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(); - -/* Restarts tagfile for a new filename. Must be called first before extracting tags. + * - libsf should point to a !tags.m3u file * - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process. */ -LIBVGMSTREAM_API void libvgmstream_tags_reset(libvgmstream_tags_t* tags, libvgmstream_streamfile_t* sf, const char* target_filename); +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 0 if no more tags are found (meant to be called repeatedly until 0) + * - 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 int libvgmstream_tags_next_tag(libvgmstream_tags_t* tags); +LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags); + /* Closes tags. */ LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags); diff --git a/src/base/api_decode_base.c b/src/base/api_decode_base.c new file mode 100644 index 00000000..7cfd53b2 --- /dev/null +++ b/src/base/api_decode_base.c @@ -0,0 +1,84 @@ +#include "api_internal.h" +#if LIBVGMSTREAM_ENABLE + +#define INTERNAL_BUF_SAMPLES 1024 + + +LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void) { + return (LIBVGMSTREAM_API_VERSION_MAJOR << 24) | (LIBVGMSTREAM_API_VERSION_MINOR << 16) | (LIBVGMSTREAM_API_VERSION_PATCH << 0); +} + + +LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void) { + libvgmstream_t* lib = NULL; + libvgmstream_priv_t* priv = NULL; + + lib = calloc(1, sizeof(libvgmstream_t)); + if (!lib) goto fail; + + lib->priv = calloc(1, sizeof(libvgmstream_priv_t)); + if (!lib->priv) goto fail; + + priv = lib->priv; + + //TODO only setup on decode? (but may less error prone if always set) + lib->format = &priv->fmt; + lib->decoder = &priv->dec; + + priv->cfg.loop_count = 1; //TODO: loop 0 means no loop (improve detection) + + return lib; +fail: + libvgmstream_free(lib); + return NULL; +} + + +LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib) { + if (!lib) + return; + + libvgmstream_priv_t* priv = lib->priv; + if (priv) { + close_vgmstream(priv->vgmstream); + free(priv->buf.data); + } + + free(priv); + free(lib); +} + + +LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg) { + if (!lib || !lib->priv) + return; + + libvgmstream_priv_t* priv = lib->priv; + if (!cfg) { + memset(&priv->cfg , 0, sizeof(libvgmstream_config_t)); + priv->cfg.loop_count = 1; //TODO: loop 0 means no loop (improve detection) + } + else { + priv->cfg = *cfg; + } + + //TODO validate, etc +} + + +void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf) { + //memset(&priv->cfg, 0, sizeof(libvgmstream_config_t)); //config is always valid + memset(&priv->fmt, 0, sizeof(libvgmstream_format_t)); + memset(&priv->dec, 0, sizeof(libvgmstream_decoder_t)); + //memset(&priv->pos, 0, sizeof(libvgmstream_priv_position_t)); //position info is updated on open + + if (reset_buf) { + free(priv->buf.data); + memset(&priv->buf, 0, sizeof(libvgmstream_priv_buf_t)); + } + + priv->pos.current = 0; + priv->decode_done = false; +} + +#endif diff --git a/src/base/api_decode_open.c b/src/base/api_decode_open.c new file mode 100644 index 00000000..4a0d526f --- /dev/null +++ b/src/base/api_decode_open.c @@ -0,0 +1,138 @@ +#include "api_internal.h" +#if LIBVGMSTREAM_ENABLE + +#define INTERNAL_BUF_SAMPLES 1024 + + +static void load_vgmstream(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) { + STREAMFILE* sf_api = open_api_streamfile(opt->libsf); + if (!sf_api) + return; + + //TODO: handle format_internal_id + + sf_api->stream_index = opt->subsong; + priv->vgmstream = init_vgmstream_from_STREAMFILE(sf_api); + close_streamfile(sf_api); +} + +static void apply_config(libvgmstream_priv_t* priv) { + libvgmstream_config_t* cfg = &priv->cfg; + + vgmstream_cfg_t vcfg = { + .disable_config_override = cfg->disable_config_override, + .allow_play_forever = cfg->allow_play_forever, + + .play_forever = cfg->play_forever, + .ignore_loop = cfg->ignore_loop, + .force_loop = cfg->force_loop, + .really_force_loop = cfg->really_force_loop, + .ignore_fade = cfg->ignore_fade, + + .loop_count = cfg->loop_count, + .fade_time = cfg->fade_time, + .fade_delay = cfg->fade_delay, + }; + + if (!vcfg.allow_play_forever) + vcfg.play_forever = 0; + + vgmstream_apply_config(priv->vgmstream, &vcfg); +} + +static void prepare_mixing(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) { + /* enable after config but before outbuf */ + if (priv->cfg.auto_downmix_channels) { + vgmstream_mixing_autodownmix(priv->vgmstream, priv->cfg.auto_downmix_channels); + } + else if (opt && opt->stereo_track >= 1) { + vgmstream_mixing_stereo_only(priv->vgmstream, opt->stereo_track - 1); + } + + vgmstream_mixing_enable(priv->vgmstream, INTERNAL_BUF_SAMPLES, NULL /*&input_channels*/, NULL /*&output_channels*/); +} + +static void update_position(libvgmstream_priv_t* priv) { + libvgmstream_priv_position_t* pos = &priv->pos; + VGMSTREAM* v = priv->vgmstream; + + pos->play_forever = vgmstream_get_play_forever(v); + pos->play_samples = vgmstream_get_samples(v); + pos->current = 0; +} + +static void update_format_info(libvgmstream_priv_t* priv) { + libvgmstream_format_t* fmt = &priv->fmt; + VGMSTREAM* v = priv->vgmstream; + + fmt->sample_size = 0x02; + fmt->sample_type = LIBVGMSTREAM_SAMPLE_PCM16; + fmt->sample_rate = v->sample_rate; + + fmt->subsong_index = v->stream_index; + fmt->subsong_count = v->num_streams; + + fmt->channels = v->channels; + fmt->input_channels = 0; + vgmstream_mixing_enable(v, 0, &fmt->input_channels, &fmt->channels); + fmt->channel_layout = v->channel_layout; + + fmt->sample_count = v->num_samples; + fmt->loop_start = v->loop_start_sample; + fmt->loop_end = v->loop_end_sample; + fmt->loop_flag = v->loop_flag; + + fmt->play_forever = priv->pos.play_forever; + fmt->play_samples = priv->pos.play_samples; + + fmt->stream_bitrate = get_vgmstream_average_bitrate(v); + + get_vgmstream_coding_description(v, fmt->codec_name, sizeof(fmt->codec_name)); + get_vgmstream_layout_description(v, fmt->layout_name, sizeof(fmt->layout_name)); + get_vgmstream_meta_description(v, fmt->meta_name, sizeof(fmt->meta_name)); + + 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) { + if (!lib ||!lib->priv) + return LIBVGMSTREAM_ERROR_GENERIC; + if (!opt || !opt->libsf || opt->subsong < 0) + return LIBVGMSTREAM_ERROR_GENERIC; + + // close loaded song if any + reset + libvgmstream_close(lib); + + libvgmstream_priv_t* priv = lib->priv; + + load_vgmstream(priv, opt); + if (!priv->vgmstream) + return LIBVGMSTREAM_ERROR_GENERIC; + apply_config(priv); + prepare_mixing(priv, opt); + update_position(priv); + + update_format_info(priv); + + + return LIBVGMSTREAM_OK; +} + + +LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib) { + if (!lib || !lib->priv) + return; + + libvgmstream_priv_t* priv = lib->priv; + + close_vgmstream(priv->vgmstream); + priv->vgmstream = NULL; + + libvgmstream_priv_reset(priv, true); +} + +#endif diff --git a/src/base/api_decode_play.c b/src/base/api_decode_play.c new file mode 100644 index 00000000..d7339008 --- /dev/null +++ b/src/base/api_decode_play.c @@ -0,0 +1,142 @@ +#include "api_internal.h" +#if LIBVGMSTREAM_ENABLE + +#define INTERNAL_BUF_SAMPLES 1024 +//TODO: use internal + +static void reset_buf(libvgmstream_priv_t* priv) { + /* state reset */ + priv->buf.samples = 0; + priv->buf.bytes = 0; + priv->buf.consumed = 0; + + if (priv->buf.initialized) + return; + int output_channels = priv->vgmstream->channels; + int input_channels = priv->vgmstream->channels; + vgmstream_mixing_enable(priv->vgmstream, 0, &input_channels, &output_channels); //query + + /* static config */ + priv->buf.channels = input_channels; + if (priv->buf.channels < output_channels) + priv->buf.channels = output_channels; + + priv->buf.sample_size = sizeof(sample_t); + priv->buf.max_samples = INTERNAL_BUF_SAMPLES; + priv->buf.max_bytes = priv->buf.max_samples * priv->buf.sample_size * priv->buf.channels; + priv->buf.data = malloc(priv->buf.max_bytes); + + priv->buf.initialized = true; +} + +static void update_buf(libvgmstream_priv_t* priv, int samples_done) { + priv->buf.samples = samples_done; + priv->buf.bytes = samples_done * priv->buf.sample_size * priv->buf.channels; + //priv->buf.consumed = 0; //external + + if (!priv->pos.play_forever) { + priv->decode_done = (priv->pos.current >= priv->pos.play_samples); + priv->pos.current += samples_done; + } +} + + +// update decoder info based on last render, though at the moment it's all fixed +static void update_decoder_info(libvgmstream_priv_t* priv, int samples_done) { + + // output copy + priv->dec.buf = priv->buf.data; + priv->dec.buf_bytes = priv->buf.bytes; + priv->dec.buf_samples = priv->buf.samples; + priv->dec.done = priv->decode_done; +} + +LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib) { + if (!lib || !lib->priv) + return LIBVGMSTREAM_ERROR_GENERIC; + + libvgmstream_priv_t* priv = lib->priv; + if (priv->decode_done) + return LIBVGMSTREAM_ERROR_GENERIC; + + reset_buf(priv); + if (!priv->buf.data) + return LIBVGMSTREAM_ERROR_GENERIC; + + int to_get = priv->buf.max_samples; + if (!priv->pos.play_forever && to_get + priv->pos.current > priv->pos.play_samples) + to_get = priv->pos.play_samples - priv->pos.current; + + int decoded = render_vgmstream(priv->buf.data, to_get, priv->vgmstream); + update_buf(priv, decoded); + update_decoder_info(priv, decoded); + + return LIBVGMSTREAM_OK; +} + + +/* _play decodes a single frame, while this copies partially that frame until frame is over */ +LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples) { + if (!lib || !lib->priv || !buf || !buf_samples) + return LIBVGMSTREAM_ERROR_GENERIC; + + libvgmstream_priv_t* priv = lib->priv; + if (priv->decode_done) + return LIBVGMSTREAM_ERROR_GENERIC; + + if (priv->buf.consumed >= priv->buf.samples) { + int err = libvgmstream_play(lib); + if (err < 0) return err; + } + + int copy_samples = priv->buf.samples; + if (copy_samples > buf_samples) + copy_samples = buf_samples; + int copy_bytes = priv->buf.sample_size * priv->buf.channels * copy_samples; + int skip_bytes = priv->buf.sample_size * priv->buf.channels * priv->buf.consumed; + + memcpy(buf, ((uint8_t*)priv->buf.data) + skip_bytes, copy_bytes); + priv->buf.consumed += copy_samples; + + return copy_samples; +} + + +LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib) { + if (!lib || !lib->priv) + return LIBVGMSTREAM_ERROR_GENERIC; + + libvgmstream_priv_t* priv = lib->priv; + if (!priv->vgmstream) + return LIBVGMSTREAM_ERROR_GENERIC; + + return priv->vgmstream->pstate.play_position; +} + + +LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample) { + if (!lib || !lib->priv) + return; + + libvgmstream_priv_t* priv = lib->priv; + if (!priv->vgmstream) + return; + + seek_vgmstream(priv->vgmstream, sample); + + priv->pos.current = priv->vgmstream->pstate.play_position; +} + + +LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib) { + if (!lib || !lib->priv) + return; + + libvgmstream_priv_t* priv = lib->priv; + if (priv->vgmstream) { + reset_vgmstream(priv->vgmstream); + } + libvgmstream_priv_reset(priv, false); +} + +#endif diff --git a/src/base/api_helpers.c b/src/base/api_helpers.c new file mode 100644 index 00000000..9bc83ab0 --- /dev/null +++ b/src/base/api_helpers.c @@ -0,0 +1,91 @@ +#include "api_internal.h" +#if LIBVGMSTREAM_ENABLE + + +static int get_internal_log_level(libvgmstream_loglevel_t level) { + switch(level) { + case LIBVGMSTREAM_LOG_LEVEL_ALL: return VGM_LOG_LEVEL_ALL; + case LIBVGMSTREAM_LOG_LEVEL_DEBUG: return VGM_LOG_LEVEL_DEBUG; + case LIBVGMSTREAM_LOG_LEVEL_INFO: return VGM_LOG_LEVEL_INFO; + case LIBVGMSTREAM_LOG_LEVEL_NONE: + default: + return 0; + } +} + +LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg) { + if (!cfg) + return; + + int ilevel = get_internal_log_level(cfg->level); + if (cfg->stdout_callback) { + //vgmstream_set_log_stdout(ilevel); + vgm_log_set_callback(NULL, ilevel, 1, NULL); + } + else { + //vgmstream_set_log_callback(ilevel, cfg->callback); + vgm_log_set_callback(NULL, ilevel, 0, cfg->callback); + } +} + + +LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size) { + return vgmstream_get_formats(size); +} + +LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size) { + return vgmstream_get_common_formats(size); +} + + +LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size) { + if (!lib || !lib->priv) + return LIBVGMSTREAM_ERROR_GENERIC; + + libvgmstream_priv_t* priv = lib->priv; + if (!priv->vgmstream) + return LIBVGMSTREAM_ERROR_GENERIC; + + describe_vgmstream(priv->vgmstream, dst, dst_size); + return LIBVGMSTREAM_OK; //TODO return truncated chars +} + + +LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg) { + if (!filename) + return false; + + if (!cfg) + return vgmstream_ctx_is_valid(filename, NULL); + + vgmstream_ctx_valid_cfg icfg = { + .is_extension = cfg->is_extension, + .skip_standard = cfg->skip_default, + .reject_extensionless = cfg->reject_extensionless, + .accept_unknown = cfg->accept_unknown, + .accept_common = cfg->accept_common + }; + return vgmstream_ctx_is_valid(filename, &icfg); +} + + +LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len) { + if (!buf || !buf_len || !cfg) + return LIBVGMSTREAM_ERROR_GENERIC; + + buf[0] = '\0'; + if (!lib || !lib->priv) + return LIBVGMSTREAM_ERROR_GENERIC; + + libvgmstream_priv_t* priv = lib->priv; + vgmstream_title_t icfg = { + .force_title = cfg->force_title, + .subsong_range = cfg->subsong_range, + .remove_extension = cfg->remove_extension, + .remove_archive = cfg->remove_archive, + }; + vgmstream_get_title(buf, buf_len, cfg->filename, priv->vgmstream, &icfg); + return LIBVGMSTREAM_OK; +} + +#endif diff --git a/src/base/api_internal.h b/src/base/api_internal.h new file mode 100644 index 00000000..573b1a95 --- /dev/null +++ b/src/base/api_internal.h @@ -0,0 +1,62 @@ +#ifndef _API_INTERNAL_H_ +#define _API_INTERNAL_H_ +#include "../api.h" +#include "../vgmstream.h" +#include "plugins.h" + +#if LIBVGMSTREAM_ENABLE + +#define LIBVGMSTREAM_OK 0 +#define LIBVGMSTREAM_ERROR_GENERIC -1 +#define LIBVGMSTREAM_ERROR_DONE -2 + +/* self-note: various API functions are just bridges to internal stuff. + * Rather than changing the internal stuff to handle API structs/etc, + * leave internals untouched for a while so external plugins/users may adapt. + * (all the bridging around may be a tiiiiny bit slower but in this day and age potatos are pretty powerful) */ + +typedef struct { + bool initialized; + void* data; + + /* config */ + int channels; + int max_bytes; + int max_samples; + int sample_size; + + /* state */ + int samples; + int bytes; + int consumed; + +} libvgmstream_priv_buf_t; + +typedef struct { + int64_t play_forever; + int64_t play_samples; + int64_t current; +} libvgmstream_priv_position_t; + +/* vgmstream context/handle */ +typedef struct { + libvgmstream_format_t fmt; // externally exposed + libvgmstream_decoder_t dec; // externally exposed + + libvgmstream_config_t cfg; // internal copy + + VGMSTREAM* vgmstream; + + libvgmstream_priv_buf_t buf; + libvgmstream_priv_position_t pos; + + bool decode_done; +} libvgmstream_priv_t; + + +void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf); + +STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf); + +#endif +#endif diff --git a/src/base/api_libsf.c b/src/base/api_libsf.c new file mode 100644 index 00000000..ec1f816a --- /dev/null +++ b/src/base/api_libsf.c @@ -0,0 +1,147 @@ +#include "api_internal.h" +#if LIBVGMSTREAM_ENABLE + +static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf); + +/* libvgmstream_streamfile_t for external use, as a default implementation calling some internal SF */ + +typedef struct { + int64_t offset; + int64_t size; + STREAMFILE* inner_sf; +} libsf_data_t; + +static int libsf_read(void* user_data, uint8_t* dst, int dst_size) { + libsf_data_t* data = user_data; + if (!data || !dst) + return 0; + + int bytes = data->inner_sf->read(data->inner_sf, dst, data->offset, dst_size); + data->offset += bytes; + + return bytes; +} + +static int64_t libsf_seek(void* user_data, int64_t offset, int whence) { + libsf_data_t* data = user_data; + if (!data) + return -1; + + switch (whence) { + case LIBVGMSTREAM_STREAMFILE_SEEK_SET: /* absolute */ + break; + case LIBVGMSTREAM_STREAMFILE_SEEK_CUR: /* relative to current */ + offset += data->offset; + break; + case LIBVGMSTREAM_STREAMFILE_SEEK_END: /* relative to file end (should be negative) */ + offset += data->size; + break; + default: + break; + } + + /* clamp offset like fseek */ + if (offset > data->size) + offset = data->size; + else if (offset < 0) + offset = 0; + + /* main seek */ + data->offset = offset; + return 0; +} + +static int64_t libsf_get_size(void* user_data) { + libsf_data_t* data = user_data; + if (!data) + return 0; + return data->size; +} + +static void libsf_get_name(void* user_data, char* name, int name_size) { + if (!name || !name_size) + return; + name[0] = '\0'; + + libsf_data_t* data = user_data; + if (!data) + return; + + data->inner_sf->get_name(data->inner_sf, name, name_size); /* default */ +} + +struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filename) { + libsf_data_t* data = user_data; + if (!data || !data->inner_sf) + return NULL; + + STREAMFILE* sf = data->inner_sf->open(data->inner_sf, filename, 0); + if (!sf) + return NULL; + + libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf); + if (!libsf) { + close_streamfile(sf); + return NULL; + } + + return libsf; +} + +static void libsf_close(struct libvgmstream_streamfile_t* libsf) { + if (!libsf) + return; + + libsf_data_t* data = libsf->user_data; + if (data && data->inner_sf) { + data->inner_sf->close(data->inner_sf); + } + free(data); + free(libsf); +} + +static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf) { + if (!sf) + return NULL; + + libvgmstream_streamfile_t* libsf = NULL; + libsf_data_t* data = NULL; + + libsf = calloc(1, sizeof(libvgmstream_streamfile_t)); + if (!libsf) goto fail; + + libsf->read = libsf_read; + libsf->seek = libsf_seek; + libsf->get_size = libsf_get_size; + libsf->get_name = libsf_get_name; + libsf->open = libsf_open; + libsf->close = libsf_close; + + libsf->user_data = calloc(1, sizeof(libsf_data_t)); + if (!libsf->user_data) goto fail; + + data = libsf->user_data; + data->inner_sf = sf; + data->size = get_streamfile_size(sf); + + return libsf; +fail: + libsf_close(libsf); + return NULL; +} + + +LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_from_filename(const char* filename) { + STREAMFILE* sf = open_stdio_streamfile(filename); + if (!sf) + return NULL; + + libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf); + if (!libsf) { + close_streamfile(sf); + return NULL; + } + + return libsf; +} +#endif diff --git a/src/base/api_tags.c b/src/base/api_tags.c new file mode 100644 index 00000000..48a9b0bc --- /dev/null +++ b/src/base/api_tags.c @@ -0,0 +1,69 @@ +#include "api_internal.h" +#if LIBVGMSTREAM_ENABLE + +typedef struct { + VGMSTREAM_TAGS* vtags; + libvgmstream_streamfile_t* libsf; + STREAMFILE* sf_tags; +} libvgmstream_tags_priv_t; + +LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf) { + if (!libsf) + return NULL; + + libvgmstream_tags_t* tags = NULL; + libvgmstream_tags_priv_t* priv = NULL; + + tags = calloc(1, sizeof(libvgmstream_tags_t)); + if (!tags) goto fail; + + tags->priv = calloc(1, sizeof(libvgmstream_tags_priv_t)); + if (!tags->priv) goto fail; + + priv = tags->priv; + priv->vtags = vgmstream_tags_init(&tags->key, &tags->val); + if (!priv->vtags) goto fail; + + priv->sf_tags = open_api_streamfile(libsf); + if (!priv->sf_tags) goto fail; + + return tags; +fail: + libvgmstream_tags_free(tags); + return NULL; +} + + +LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename) { + if (!tags || !tags->priv || !target_filename) + return; + //TODO: handle NULL filename? + libvgmstream_tags_priv_t* priv = tags->priv; + vgmstream_tags_reset(priv->vtags, target_filename); +} + + +LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags) { + if (!tags) + return false; + + libvgmstream_tags_priv_t* priv = tags->priv; + + return vgmstream_tags_next_tag(priv->vtags, priv->sf_tags); +} + + +LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags) { + if (!tags) + return; + + libvgmstream_tags_priv_t* priv = tags->priv; + if (priv) { + vgmstream_tags_close(priv->vtags); + close_streamfile(priv->sf_tags); + } + free(tags->priv); + free(tags); +} + +#endif diff --git a/src/base/streamfile_api.c b/src/base/streamfile_api.c new file mode 100644 index 00000000..31b8d32b --- /dev/null +++ b/src/base/streamfile_api.c @@ -0,0 +1,85 @@ +#include "api_internal.h" +#if LIBVGMSTREAM_ENABLE +/* STREAMFILE for internal use, that bridges calls to external libvgmstream_streamfile_t */ + + +static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf); + + +typedef struct { + STREAMFILE vt; + + libvgmstream_streamfile_t* libsf; + bool external_libsf; //TODO: improve +} API_STREAMFILE; + +static size_t api_read(API_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) { + void* user_data = sf->libsf->user_data; + + sf->libsf->seek(sf->libsf->user_data, offset, LIBVGMSTREAM_STREAMFILE_SEEK_SET); + return sf->libsf->read(user_data, dst, length); +} + +static size_t api_get_size(API_STREAMFILE* sf) { + void* user_data = sf->libsf->user_data; + + return sf->libsf->get_size(user_data); +} + +static offv_t api_get_offset(API_STREAMFILE* sf) { + return 0; //sf->libsf->get_offset(sf->inner_sf); /* default */ +} + +static void api_get_name(API_STREAMFILE* sf, char* name, size_t name_size) { + void* user_data = sf->libsf->user_data; + + sf->libsf->get_name(user_data, name, name_size); +} + +static STREAMFILE* api_open(API_STREAMFILE* sf, const char* filename, size_t buf_size) { + libvgmstream_streamfile_t* libsf = sf->libsf->open(sf->libsf->user_data, filename); + STREAMFILE* new_sf = open_api_streamfile_internal(libsf, false); + + if (!new_sf) { + libvgmstream_streamfile_close(libsf); + } + + return new_sf; +} + +static void api_close(API_STREAMFILE* sf) { + if (sf && !sf->external_libsf) { + sf->libsf->close(sf->libsf); + } + free(sf); +} + +static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf) { + API_STREAMFILE* this_sf = NULL; + + if (!libsf) + return NULL; + + this_sf = calloc(1, sizeof(API_STREAMFILE)); + if (!this_sf) return NULL; + + /* set callbacks and internals */ + this_sf->vt.read = (void*)api_read; + this_sf->vt.get_size = (void*)api_get_size; + this_sf->vt.get_offset = (void*)api_get_offset; + this_sf->vt.get_name = (void*)api_get_name; + this_sf->vt.open = (void*)api_open; + this_sf->vt.close = (void*)api_close; + //this_sf->vt.stream_index = sf->stream_index; + + this_sf->libsf = libsf; + this_sf->external_libsf = external_libsf; + + return &this_sf->vt; +} + +STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf) { + return open_api_streamfile_internal(libsf, true); +} + +#endif