mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 00:20:47 +01:00
api: add beta api
This commit is contained in:
parent
00a8ee7875
commit
70695617a8
2
Makefile
2
Makefile
@ -300,4 +300,4 @@ clean:
|
|||||||
$(MAKE) -C xmplay clean
|
$(MAKE) -C xmplay clean
|
||||||
$(MAKE) -C ext_libs 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
|
||||||
|
@ -59,6 +59,6 @@ $(TARGET_EXT_LIBS):
|
|||||||
$(MAKE) -C ../ext_libs $@
|
$(MAKE) -C ../ext_libs $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_123)
|
$(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_API)
|
||||||
|
|
||||||
.PHONY: clean vgmstream_cli libvgmstream.a $(TARGET_EXT_LIBS)
|
.PHONY: clean vgmstream_cli libvgmstream.a $(TARGET_EXT_LIBS)
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
#if 0
|
#include "../src/api.h"
|
||||||
|
#if LIBVGMSTREAM_ENABLE
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "../src/api.h"
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "../src/base/api_internal.h"
|
||||||
|
|
||||||
|
|
||||||
static void usage(const char* progname) {
|
static void usage(const char* progname) {
|
||||||
fprintf(stderr, "API test v%08x\n"
|
fprintf(stderr, "Usage: %s <infile>\n"
|
||||||
"Usage: %s <infile>\n"
|
|
||||||
, libvgmstream_get_version()
|
|
||||||
, progname
|
, progname
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -24,23 +25,18 @@ static FILE* get_output_file(const char* filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static libvgmstream_streamfile_t* get_streamfile(const char* filename) {
|
static libvgmstream_streamfile_t* get_streamfile(const char* filename) {
|
||||||
return NULL;
|
return libvgmstream_streamfile_from_filename(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* simplistic example of vgmstream's API
|
static int api_example(const char* infile) {
|
||||||
* for something a bit more featured see vgmstream-cli
|
VGM_STEP();
|
||||||
*/
|
|
||||||
int main(int argc, char** argv) {
|
|
||||||
int err;
|
int err;
|
||||||
FILE* outfile = NULL;
|
FILE* outfile = NULL;
|
||||||
const char* infile;
|
|
||||||
|
|
||||||
if (argc != 2) {
|
bool fill_test;
|
||||||
usage(argv[0]);
|
int pcm16_samples;
|
||||||
return EXIT_FAILURE;
|
int pcm16_bytes;
|
||||||
}
|
short* pcm16 = NULL;
|
||||||
|
|
||||||
infile = argv[1];
|
|
||||||
|
|
||||||
|
|
||||||
// main init
|
// main init
|
||||||
@ -50,45 +46,96 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
// set default config
|
// set default config
|
||||||
libvgmstream_config_t cfg = {
|
libvgmstream_config_t cfg = {
|
||||||
.loop_count = 2.0,
|
//.loop_count = 1.0,
|
||||||
.fade_time = 10.0,
|
//.fade_time = 10.0,
|
||||||
|
.ignore_loop = true,
|
||||||
};
|
};
|
||||||
libvgmstream_setup(lib, &cfg);
|
libvgmstream_setup(lib, &cfg);
|
||||||
|
|
||||||
|
|
||||||
// open target file
|
// open target file
|
||||||
libvgmstream_options_t options = {
|
libvgmstream_options_t opt = {
|
||||||
.sf = get_streamfile(infile)
|
.libsf = get_streamfile(infile)
|
||||||
};
|
};
|
||||||
err = libvgmstream_open(lib, &options);
|
err = libvgmstream_open(lib, &opt);
|
||||||
if (err < 0) goto fail;
|
|
||||||
|
|
||||||
|
|
||||||
// external SF is not needed after _open
|
// external SF is not needed after _open
|
||||||
libvgmstream_streamfile_close(options.sf);
|
libvgmstream_streamfile_close(opt.libsf);
|
||||||
|
|
||||||
|
if (err < 0) {
|
||||||
|
printf("not a valid file\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
// output file
|
// output file
|
||||||
outfile = get_output_file(infile);
|
outfile = get_output_file(infile);
|
||||||
if (!outfile) goto fail;
|
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
|
// play file and do something with decoded samples
|
||||||
while (true) {
|
while (true) {
|
||||||
int pos;
|
//int pos;
|
||||||
|
void* buf;
|
||||||
|
int buf_bytes = 0;
|
||||||
|
|
||||||
if (lib->decoder->done)
|
if (lib->decoder->done)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// get current samples
|
// 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);
|
err = libvgmstream_play(lib);
|
||||||
if (err < 0) goto fail;
|
if (err < 0) goto fail;
|
||||||
|
|
||||||
fwrite(lib->decoder->buf, sizeof(uint8_t), lib->decoder->buf_bytes, outfile);
|
buf = lib->decoder->buf;
|
||||||
|
buf_bytes = lib->decoder->buf_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
pos = (int)libvgmstream_play_position(lib);
|
fwrite(buf, sizeof(uint8_t), buf_bytes, outfile);
|
||||||
printf("\rpos: %d", pos);
|
|
||||||
fflush(stdout);
|
//pos = (int)libvgmstream_get_play_position(lib);
|
||||||
|
//printf("\r- decode pos: %d", pos);
|
||||||
|
//fflush(stdout);
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
@ -98,6 +145,7 @@ int main(int argc, char** argv) {
|
|||||||
// process done
|
// process done
|
||||||
libvgmstream_free(lib);
|
libvgmstream_free(lib);
|
||||||
fclose(outfile);
|
fclose(outfile);
|
||||||
|
free(pcm16);
|
||||||
|
|
||||||
printf("done\n");
|
printf("done\n");
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
@ -105,8 +153,258 @@ fail:
|
|||||||
// process failed
|
// process failed
|
||||||
libvgmstream_free(lib);
|
libvgmstream_free(lib);
|
||||||
fclose(outfile);
|
fclose(outfile);
|
||||||
|
free(pcm16);
|
||||||
|
|
||||||
printf("failed!\n");
|
printf("failed!\n");
|
||||||
return EXIT_FAILURE;
|
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
|
#endif
|
||||||
|
46
src/api.h
46
src/api.h
@ -2,14 +2,34 @@
|
|||||||
#define _API_H_
|
#define _API_H_
|
||||||
#include "base/plugins.h" //TODO: to be removed
|
#include "base/plugins.h" //TODO: to be removed
|
||||||
|
|
||||||
#if 0
|
//#define LIBVGMSTREAM_ENABLE 1
|
||||||
|
#if LIBVGMSTREAM_ENABLE
|
||||||
|
|
||||||
/* vgmstream's public API
|
/* 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_init(...) // base context
|
||||||
* - libvgmstream_setup(...) // standard config
|
* - libvgmstream_setup(...) // config if needed
|
||||||
* - libvgmstream_open(...) // check detected format
|
* - libvgmstream_open(...) // setup format
|
||||||
* - libvgmstream_play(...) // main decode
|
* - libvgmstream_play(...) // main decode
|
||||||
* - output samples + repeat libvgmstream_play until stream is done
|
* - output samples + repeat libvgmstream_play until stream is done
|
||||||
* - libvgmstream_free(...) // cleanup
|
* - libvgmstream_free(...) // cleanup
|
||||||
@ -33,14 +53,22 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* Current API version. Only refers to the API itself, as changes related to formats/etc don't alter it.
|
/* Current API version.
|
||||||
* vgmstream's features are mostly stable, while this API may change from time to time.
|
* - only refers to the API itself, as changes related to formats/etc don't alter this (since they are usually additive)
|
||||||
* May change as well when related (such as api_streamfile.h) are changed. */
|
* - 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_MAJOR 1 // breaking API/ABI changes
|
||||||
#define LIBVGMSTREAM_API_VERSION_MINOR 0 // compatible API/ABI changes
|
#define LIBVGMSTREAM_API_VERSION_MINOR 0 // compatible API/ABI changes
|
||||||
#define LIBVGMSTREAM_API_VERSION_PATCH 0 // fixes
|
#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_streamfile.h"
|
||||||
#include "api_tags.h"
|
#include "api_tags.h"
|
||||||
|
|
||||||
|
202
src/api_decode.h
Normal file
202
src/api_decode.h
Normal file
@ -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
|
81
src/api_helpers.h
Normal file
81
src/api_helpers.h
Normal file
@ -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
|
156
src/api_main.c
156
src/api_main.c
@ -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
|
|
235
src/api_main.h
235
src/api_main.h
@ -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
|
|
@ -1,51 +1,71 @@
|
|||||||
#if 0
|
|
||||||
#ifndef _API_STREAMFILE_H_
|
#ifndef _API_STREAMFILE_H_
|
||||||
#define _API_STREAMFILE_H_
|
#define _API_STREAMFILE_H_
|
||||||
|
#include "api.h"
|
||||||
|
#if LIBVGMSTREAM_ENABLE
|
||||||
|
|
||||||
/* vgmstream's IO API, defined as a "streamfile" (SF).
|
/* 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):
|
* Compared to typical IO, vgmstream has some extra needs that roughly assume there is an underlying filesystem (as usual in games):
|
||||||
* - reading from arbitrary offsets: header not found in the beginning of a stream, rewinding during looping, etc
|
* - 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 current SF, formats with split header + data, decryption files, etc
|
* - opening other streamfiles: reopening a copy of the 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
|
* - 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.
|
* 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, loading data into memory, returning a fake filename, and only handling "open" that reopens itself (same filename),
|
* For example, returning a fake filename, and only handling "open" that reopens itself (same filename), while returning default/incorrect
|
||||||
* while returning default/incorrect values on non-handled operations. Simpler, non-looped formats probably work fine just being read linearly.
|
* 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 {
|
typedef struct libvgmstream_streamfile_t {
|
||||||
/* user data */
|
//uint32_t flags; // info flags for vgmstream
|
||||||
void* opaque;
|
void* user_data; // any internal structure
|
||||||
|
|
||||||
/* read 'length' data at 'offset' to 'dst' (implicit seek) */
|
/* read 'length' data at internal offset to 'dst' (implicit seek if needed)
|
||||||
size_t (*read)(struct libvgmstream_streamfile_t* sf, uint8_t* dst, int64_t offset, size_t length);
|
* - assumes 0 = failure/EOF
|
||||||
|
*/
|
||||||
|
int (*read)(void* user_data, uint8_t* dst, int dst_size);
|
||||||
|
|
||||||
/* get max offset */
|
/* seek to offset
|
||||||
size_t (*get_size)(struct libvgmstream_streamfile_t* sf); //TODO return int64_t?
|
* - 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 */
|
/* get max offset
|
||||||
void (*get_name)(struct libvgmstream_streamfile_t* sf, char* name, size_t name_size);
|
*/
|
||||||
|
int64_t (*get_size)(void* user_data);
|
||||||
|
|
||||||
/* open another streamfile from filename (which may be some internal path/protocol) */
|
/* copy current filename to name buf
|
||||||
struct libvgmstream_streamfile_t* (*open)(struct libvgmstream_streamfile_t* sf, const char* const filename, size_t buf_size);
|
*/
|
||||||
|
void (*get_name)(void* user_data, char* name, int name_size); //TODO return char*?
|
||||||
|
|
||||||
/* free current STREAMFILE */
|
/* open another streamfile from filename (may be some path/protocol, or same as current get_name = reopen)
|
||||||
void (*close)(struct libvgmstream_streamfile_t* sf);
|
* - 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;
|
} libvgmstream_streamfile_t;
|
||||||
|
|
||||||
|
|
||||||
/* helper */
|
/* helper */
|
||||||
static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* sf) {
|
static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* libsf) {
|
||||||
if (!sf)
|
if (!libsf || !libsf->close)
|
||||||
return;
|
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
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,37 +1,42 @@
|
|||||||
#if 0
|
|
||||||
#ifndef _API_TAGS_H_
|
#ifndef _API_TAGS_H_
|
||||||
#define _API_TAGS_H_
|
#define _API_TAGS_H_
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
#include "api_streamfile.h"
|
#if LIBVGMSTREAM_ENABLE
|
||||||
|
|
||||||
/* vgmstream's !tags.m3u API.
|
/* 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 */
|
/* tag state */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void* priv; // internal data
|
void* priv; // internal data
|
||||||
|
|
||||||
const char* key; // current key
|
const char* key; // current key
|
||||||
const char* val; // current value
|
const char* val; // current value
|
||||||
|
|
||||||
} libvgmstream_tags_t;
|
} libvgmstream_tags_t;
|
||||||
|
|
||||||
/* Initializes tags.
|
/* Initializes tags.
|
||||||
* - may be reused for different files for cache purposed
|
* - libsf should point to a !tags.m3u file
|
||||||
*/
|
|
||||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init();
|
|
||||||
|
|
||||||
/* Restarts tagfile for a new filename. Must be called first before extracting tags.
|
|
||||||
* - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process.
|
* - 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.
|
/* 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
|
* - 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. */
|
/* Closes tags. */
|
||||||
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags);
|
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags);
|
||||||
|
84
src/base/api_decode_base.c
Normal file
84
src/base/api_decode_base.c
Normal file
@ -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
|
138
src/base/api_decode_open.c
Normal file
138
src/base/api_decode_open.c
Normal file
@ -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
|
142
src/base/api_decode_play.c
Normal file
142
src/base/api_decode_play.c
Normal file
@ -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
|
91
src/base/api_helpers.c
Normal file
91
src/base/api_helpers.c
Normal file
@ -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
|
62
src/base/api_internal.h
Normal file
62
src/base/api_internal.h
Normal file
@ -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
|
147
src/base/api_libsf.c
Normal file
147
src/base/api_libsf.c
Normal file
@ -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
|
69
src/base/api_tags.c
Normal file
69
src/base/api_tags.c
Normal file
@ -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
|
85
src/base/streamfile_api.c
Normal file
85
src/base/streamfile_api.c
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user