2017-07-15 11:49:28 +02:00
|
|
|
#include "meta.h"
|
|
|
|
#include "../coding/coding.h"
|
|
|
|
#include "../layout/layout.h"
|
|
|
|
|
2017-11-23 23:03:55 +01:00
|
|
|
#define TXT_LINE_MAX 0x2000
|
2017-08-12 11:11:08 +02:00
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
/* known TXTH types */
|
|
|
|
typedef enum {
|
2019-01-12 13:02:08 +01:00
|
|
|
PSX = 0, /* PS-ADPCM */
|
|
|
|
XBOX = 1, /* XBOX IMA ADPCM */
|
|
|
|
NGC_DTK = 2, /* NGC ADP/DTK ADPCM */
|
|
|
|
PCM16BE = 3, /* 16-bit big endian PCM */
|
|
|
|
PCM16LE = 4, /* 16-bit little endian PCM */
|
|
|
|
PCM8 = 5, /* 8-bit PCM */
|
|
|
|
SDX2 = 6, /* SDX2 (3D0 games) */
|
|
|
|
DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */
|
|
|
|
MPEG = 8, /* MPEG (MP3) */
|
|
|
|
IMA = 9, /* IMA ADPCM (low nibble first) */
|
|
|
|
AICA = 10, /* AICA ADPCM (Dreamcast games) */
|
|
|
|
MSADPCM = 11, /* MS ADPCM (Windows games) */
|
|
|
|
NGC_DSP = 12, /* NGC DSP (Nintendo games) */
|
|
|
|
PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */
|
|
|
|
PSX_bf = 14, /* PS-ADPCM with bad flags */
|
|
|
|
MS_IMA = 15, /* Microsoft IMA ADPCM */
|
|
|
|
PCM8_U = 16, /* 8-bit unsigned PCM */
|
|
|
|
APPLE_IMA4 = 17, /* Apple Quicktime 4-bit IMA ADPCM */
|
|
|
|
ATRAC3 = 18, /* Raw ATRAC3 */
|
|
|
|
ATRAC3PLUS = 19, /* Raw ATRAC3PLUS */
|
|
|
|
XMA1 = 20, /* Raw XMA1 */
|
|
|
|
XMA2 = 21, /* Raw XMA2 */
|
|
|
|
FFMPEG = 22, /* Any headered FFmpeg format */
|
|
|
|
AC3 = 23, /* AC3/SPDIF */
|
|
|
|
PCFX = 24, /* PC-FX ADPCM */
|
|
|
|
PCM4 = 25, /* 4-bit signed PCM (3rd and 4th gen games) */
|
|
|
|
PCM4_U = 26, /* 4-bit unsigned PCM (3rd and 4th gen games) */
|
|
|
|
OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */
|
2017-07-15 11:49:28 +02:00
|
|
|
} txth_type;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
txth_type codec;
|
2017-11-10 19:34:36 +01:00
|
|
|
uint32_t codec_mode;
|
2018-11-24 15:25:11 +01:00
|
|
|
|
|
|
|
uint32_t value_mul;
|
|
|
|
uint32_t value_div;
|
|
|
|
uint32_t value_add;
|
|
|
|
uint32_t value_sub;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
uint32_t id_value;
|
|
|
|
uint32_t id_offset;
|
|
|
|
|
2018-11-24 15:25:11 +01:00
|
|
|
uint32_t interleave;
|
2019-01-20 00:17:06 +01:00
|
|
|
uint32_t interleave_last;
|
2017-07-15 11:49:28 +02:00
|
|
|
uint32_t channels;
|
|
|
|
uint32_t sample_rate;
|
|
|
|
|
|
|
|
uint32_t data_size;
|
|
|
|
int data_size_set;
|
|
|
|
uint32_t start_offset;
|
|
|
|
|
2018-08-13 23:02:31 +02:00
|
|
|
int sample_type;
|
2017-07-15 11:49:28 +02:00
|
|
|
uint32_t num_samples;
|
|
|
|
uint32_t loop_start_sample;
|
|
|
|
uint32_t loop_end_sample;
|
|
|
|
uint32_t loop_adjust;
|
|
|
|
int skip_samples_set;
|
|
|
|
uint32_t skip_samples;
|
|
|
|
|
|
|
|
uint32_t loop_flag;
|
|
|
|
int loop_flag_set;
|
2018-08-22 21:04:16 +02:00
|
|
|
int loop_flag_auto;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
uint32_t coef_offset;
|
|
|
|
uint32_t coef_spacing;
|
|
|
|
uint32_t coef_big_endian;
|
2017-11-10 19:34:36 +01:00
|
|
|
uint32_t coef_mode;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
2018-05-12 13:53:43 +02:00
|
|
|
int num_samples_data_size;
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
int target_subsong;
|
|
|
|
uint32_t subsong_count;
|
|
|
|
uint32_t subsong_offset;
|
|
|
|
|
2018-12-27 16:51:27 +01:00
|
|
|
uint32_t name_offset_set;
|
|
|
|
uint32_t name_offset;
|
|
|
|
uint32_t name_size;
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
/* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */
|
|
|
|
STREAMFILE *streamFile;
|
|
|
|
int streamfile_is_txth;
|
|
|
|
|
|
|
|
/* configurable STREAMFILEs and if we opened it (thus must close it later) */
|
|
|
|
STREAMFILE *streamText;
|
|
|
|
STREAMFILE *streamHead;
|
|
|
|
STREAMFILE *streamBody;
|
|
|
|
int streamtext_opened;
|
|
|
|
int streamhead_opened;
|
|
|
|
int streambody_opened;
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
} txth_header;
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
|
2017-11-10 19:34:36 +01:00
|
|
|
static STREAMFILE * open_txth(STREAMFILE * streamFile);
|
2018-11-24 01:45:04 +01:00
|
|
|
static int parse_txth(txth_header * txth);
|
|
|
|
static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val);
|
|
|
|
static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value);
|
2017-07-15 11:49:28 +02:00
|
|
|
static int get_bytes_to_samples(txth_header * txth, uint32_t bytes);
|
|
|
|
|
|
|
|
|
|
|
|
/* TXTH - an artificial "generic" header for headerless streams.
|
|
|
|
* Similar to GENH, but with a single separate .txth file in the dir and text-based. */
|
|
|
|
VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
|
|
|
VGMSTREAM * vgmstream = NULL;
|
2017-11-10 19:34:36 +01:00
|
|
|
txth_header txth = {0};
|
2017-07-15 11:49:28 +02:00
|
|
|
coding_t coding;
|
|
|
|
int i, j;
|
|
|
|
|
2018-04-15 00:50:05 +02:00
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
/* accept .txth (should set body_file or will fail later) */
|
|
|
|
if (check_extensions(streamFile, "txth")) {
|
|
|
|
txth.streamFile = streamFile;
|
|
|
|
txth.streamfile_is_txth = 1;
|
2018-04-15 00:50:05 +02:00
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
txth.streamText = streamFile;
|
|
|
|
txth.streamHead = NULL;
|
|
|
|
txth.streamBody = NULL;
|
|
|
|
txth.streamtext_opened = 0;
|
|
|
|
txth.streamhead_opened = 0;
|
|
|
|
txth.streambody_opened = 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* accept base file (no need for ID or ext checks --if a companion .TXTH exists all is good)
|
|
|
|
* (player still needs to accept the streamfile's ext, so at worst rename to .vgmstream) */
|
|
|
|
STREAMFILE * streamText = open_txth(streamFile);
|
|
|
|
if (!streamText) goto fail;
|
|
|
|
|
|
|
|
txth.streamFile = streamFile;
|
|
|
|
txth.streamfile_is_txth = 0;
|
|
|
|
|
|
|
|
txth.streamText = streamText;
|
|
|
|
txth.streamHead = streamFile;
|
|
|
|
txth.streamBody = streamFile;
|
|
|
|
txth.streamtext_opened = 1;
|
|
|
|
txth.streamhead_opened = 0;
|
|
|
|
txth.streambody_opened = 0;
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
/* process the text file */
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_txth(&txth))
|
2017-07-15 11:49:28 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
|
|
/* type to coding conversion */
|
|
|
|
switch (txth.codec) {
|
|
|
|
case PSX: coding = coding_PSX; break;
|
2018-02-17 12:30:14 +01:00
|
|
|
case XBOX: coding = coding_XBOX_IMA; break;
|
2017-07-15 11:49:28 +02:00
|
|
|
case NGC_DTK: coding = coding_NGC_DTK; break;
|
|
|
|
case PCM16BE: coding = coding_PCM16BE; break;
|
|
|
|
case PCM16LE: coding = coding_PCM16LE; break;
|
|
|
|
case PCM8: coding = coding_PCM8; break;
|
|
|
|
case SDX2: coding = coding_SDX2; break;
|
|
|
|
case DVI_IMA: coding = coding_DVI_IMA; break;
|
|
|
|
#ifdef VGM_USE_MPEG
|
2017-07-29 13:05:23 +02:00
|
|
|
case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */
|
2017-07-15 11:49:28 +02:00
|
|
|
#endif
|
|
|
|
case IMA: coding = coding_IMA; break;
|
2018-03-28 00:30:59 +02:00
|
|
|
case AICA: coding = coding_AICA; break;
|
2017-07-15 11:49:28 +02:00
|
|
|
case MSADPCM: coding = coding_MSADPCM; break;
|
|
|
|
case NGC_DSP: coding = coding_NGC_DSP; break;
|
|
|
|
case PCM8_U_int: coding = coding_PCM8_U_int; break;
|
|
|
|
case PSX_bf: coding = coding_PSX_badflags; break;
|
|
|
|
case MS_IMA: coding = coding_MS_IMA; break;
|
|
|
|
case PCM8_U: coding = coding_PCM8_U; break;
|
|
|
|
case APPLE_IMA4: coding = coding_APPLE_IMA4; break;
|
|
|
|
#ifdef VGM_USE_FFMPEG
|
|
|
|
case ATRAC3:
|
|
|
|
case ATRAC3PLUS:
|
|
|
|
case XMA1:
|
|
|
|
case XMA2:
|
2017-11-10 19:34:36 +01:00
|
|
|
case AC3:
|
2017-07-15 11:49:28 +02:00
|
|
|
case FFMPEG: coding = coding_FFmpeg; break;
|
|
|
|
#endif
|
2018-12-27 16:14:59 +01:00
|
|
|
case PCFX: coding = coding_PCFX; break;
|
2019-01-09 20:30:15 +01:00
|
|
|
case PCM4: coding = coding_PCM4; break;
|
|
|
|
case PCM4_U: coding = coding_PCM4_U; break;
|
2019-01-12 13:02:08 +01:00
|
|
|
case OKI16: coding = coding_OKI16; break;
|
2017-07-15 11:49:28 +02:00
|
|
|
default:
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2018-11-24 15:25:11 +01:00
|
|
|
|
2018-08-22 21:04:16 +02:00
|
|
|
/* try to autodetect PS-ADPCM loop data */
|
|
|
|
if (txth.loop_flag_auto && coding == coding_PSX) {
|
2018-11-24 15:25:11 +01:00
|
|
|
txth.loop_flag = ps_find_loop_offsets(txth.streamBody, txth.start_offset, txth.data_size, txth.channels, txth.interleave,
|
2018-08-22 21:04:16 +02:00
|
|
|
(int32_t*)&txth.loop_start_sample, (int32_t*)&txth.loop_end_sample);
|
|
|
|
}
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
/* build the VGMSTREAM */
|
|
|
|
vgmstream = allocate_vgmstream(txth.channels,txth.loop_flag);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
|
|
|
vgmstream->sample_rate = txth.sample_rate;
|
|
|
|
vgmstream->num_samples = txth.num_samples;
|
|
|
|
vgmstream->loop_start_sample = txth.loop_start_sample;
|
|
|
|
vgmstream->loop_end_sample = txth.loop_end_sample;
|
2018-11-24 15:25:11 +01:00
|
|
|
vgmstream->num_streams = txth.subsong_count;
|
|
|
|
vgmstream->stream_size = txth.data_size;
|
2018-12-27 16:51:27 +01:00
|
|
|
if (txth.name_offset_set) {
|
|
|
|
size_t name_size = txth.name_size ? txth.name_size + 1 : STREAM_NAME_SIZE;
|
|
|
|
read_string(vgmstream->stream_name,name_size, txth.name_offset,txth.streamHead);
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
/* codec specific (taken from GENH with minimal changes) */
|
|
|
|
switch (coding) {
|
|
|
|
case coding_PCM8_U_int:
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
break;
|
|
|
|
case coding_PCM16LE:
|
|
|
|
case coding_PCM16BE:
|
|
|
|
case coding_PCM8:
|
|
|
|
case coding_PCM8_U:
|
2019-01-09 20:30:15 +01:00
|
|
|
case coding_PCM4:
|
|
|
|
case coding_PCM4_U:
|
2017-07-15 11:49:28 +02:00
|
|
|
case coding_SDX2:
|
|
|
|
case coding_PSX:
|
|
|
|
case coding_PSX_badflags:
|
|
|
|
case coding_DVI_IMA:
|
|
|
|
case coding_IMA:
|
2018-03-28 00:30:59 +02:00
|
|
|
case coding_AICA:
|
2017-07-15 11:49:28 +02:00
|
|
|
case coding_APPLE_IMA4:
|
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
2019-01-20 00:17:06 +01:00
|
|
|
vgmstream->interleave_last_block_size = txth.interleave_last;
|
2017-07-15 11:49:28 +02:00
|
|
|
if (vgmstream->channels > 1)
|
|
|
|
{
|
|
|
|
if (coding == coding_SDX2) {
|
|
|
|
coding = coding_SDX2_int;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vgmstream->interleave_block_size==0xffffffff || vgmstream->interleave_block_size == 0) {
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
if (coding == coding_DVI_IMA)
|
|
|
|
coding = coding_DVI_IMA_int;
|
|
|
|
if (coding == coding_IMA)
|
|
|
|
coding = coding_IMA_int;
|
2018-03-28 00:30:59 +02:00
|
|
|
if (coding == coding_AICA)
|
|
|
|
coding = coding_AICA_int;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* to avoid endless loops */
|
|
|
|
if (!txth.interleave && (
|
|
|
|
coding == coding_PSX ||
|
|
|
|
coding == coding_PSX_badflags ||
|
|
|
|
coding == coding_IMA_int ||
|
|
|
|
coding == coding_DVI_IMA_int ||
|
2018-03-28 00:30:59 +02:00
|
|
|
coding == coding_SDX2_int ||
|
|
|
|
coding == coding_AICA_int) ) {
|
2017-07-15 11:49:28 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* setup adpcm */
|
2018-03-28 00:30:59 +02:00
|
|
|
if (coding == coding_AICA || coding == coding_AICA_int) {
|
2017-07-15 11:49:28 +02:00
|
|
|
int i;
|
|
|
|
for (i=0;i<vgmstream->channels;i++) {
|
|
|
|
vgmstream->ch[i].adpcm_step_index = 0x7f;
|
|
|
|
}
|
|
|
|
}
|
2019-01-09 20:30:15 +01:00
|
|
|
|
|
|
|
if (coding == coding_PCM4 || coding == coding_PCM4_U) {
|
|
|
|
/* high nibble or low nibble first */
|
|
|
|
vgmstream->codec_config = txth.codec_mode;
|
|
|
|
}
|
2018-12-27 16:14:59 +01:00
|
|
|
break;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
2018-12-27 16:14:59 +01:00
|
|
|
case coding_PCFX:
|
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
2019-01-20 00:17:06 +01:00
|
|
|
vgmstream->interleave_last_block_size = txth.interleave_last;
|
2018-12-27 16:14:59 +01:00
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
if (txth.codec_mode >= 0 && txth.codec_mode <= 3)
|
|
|
|
vgmstream->codec_config = txth.codec_mode;
|
2017-07-15 11:49:28 +02:00
|
|
|
break;
|
2018-12-27 16:14:59 +01:00
|
|
|
|
2019-01-12 13:02:08 +01:00
|
|
|
case coding_OKI16:
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
break;
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
case coding_MS_IMA:
|
|
|
|
if (!txth.interleave) goto fail; /* creates garbage */
|
|
|
|
|
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
break;
|
|
|
|
case coding_MSADPCM:
|
|
|
|
if (vgmstream->channels > 2) goto fail;
|
|
|
|
if (!txth.interleave) goto fail; /* creates garbage */
|
|
|
|
|
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
break;
|
2018-02-17 12:30:14 +01:00
|
|
|
case coding_XBOX_IMA:
|
2018-09-04 17:11:08 +02:00
|
|
|
if (txth.codec_mode == 1) { /* mono interleave */
|
2018-03-03 01:09:44 +01:00
|
|
|
coding = coding_XBOX_IMA_int;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
2019-01-20 00:17:06 +01:00
|
|
|
vgmstream->interleave_last_block_size = txth.interleave_last;
|
2018-03-03 01:09:44 +01:00
|
|
|
}
|
2018-09-04 17:11:08 +02:00
|
|
|
else { /* 1ch mono, or stereo interleave */
|
|
|
|
vgmstream->layout_type = txth.interleave ? layout_interleave : layout_none;
|
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
2019-01-20 00:17:06 +01:00
|
|
|
vgmstream->interleave_last_block_size = txth.interleave_last;
|
2018-09-04 17:11:08 +02:00
|
|
|
if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0)
|
|
|
|
goto fail; /* only 2ch+..+2ch layout is known */
|
2018-03-03 01:09:44 +01:00
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
break;
|
|
|
|
case coding_NGC_DTK:
|
|
|
|
if (vgmstream->channels != 2) goto fail;
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
break;
|
|
|
|
case coding_NGC_DSP:
|
|
|
|
if (txth.channels > 1 && txth.codec_mode == 0) {
|
|
|
|
if (!txth.interleave) goto fail;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
2019-01-20 00:17:06 +01:00
|
|
|
vgmstream->interleave_last_block_size = txth.interleave_last;
|
2017-07-15 11:49:28 +02:00
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
|
|
|
} else if (txth.channels > 1 && txth.codec_mode == 1) {
|
|
|
|
if (!txth.interleave) goto fail;
|
2017-12-06 21:04:34 +01:00
|
|
|
coding = coding_NGC_DSP_subint;
|
|
|
|
vgmstream->layout_type = layout_none;
|
2017-07-15 11:49:28 +02:00
|
|
|
vgmstream->interleave_block_size = txth.interleave;
|
|
|
|
} else if (txth.channels == 1 || txth.codec_mode == 2) {
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
} else {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get coefs */
|
|
|
|
for (i=0;i<vgmstream->channels;i++) {
|
|
|
|
int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE;
|
|
|
|
|
|
|
|
/* normal/split coefs */
|
2018-12-01 02:41:56 +01:00
|
|
|
if (txth.coef_mode == 0) { /* normal mode */
|
|
|
|
for (j = 0; j < 16; j++) {
|
|
|
|
vgmstream->ch[i].adpcm_coef[j] = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead);
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
2017-11-10 19:34:36 +01:00
|
|
|
}
|
2018-12-01 02:41:56 +01:00
|
|
|
else { /* split coefs */
|
2017-07-15 11:49:28 +02:00
|
|
|
goto fail; //IDK what is this
|
|
|
|
/*
|
2018-12-01 02:41:56 +01:00
|
|
|
for (j = 0; j < 8; j++) {
|
|
|
|
vgmstream->ch[i].adpcm_coef[j*2] = read_16bit(genh.coef_offset + i*genh.coef_spacing + j*2, txth.streamHead);
|
|
|
|
vgmstream->ch[i].adpcm_coef[j*2+1] = read_16bit(genh.coef_split_offset + i*genh.coef_split_spacing + j*2, txth.streamHead);
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
#ifdef VGM_USE_MPEG
|
2017-07-29 13:05:23 +02:00
|
|
|
case coding_MPEG_layer3:
|
|
|
|
vgmstream->layout_type = layout_none;
|
2018-11-24 01:45:04 +01:00
|
|
|
vgmstream->codec_data = init_mpeg(txth.streamBody, txth.start_offset, &coding, vgmstream->channels);
|
2017-07-15 11:49:28 +02:00
|
|
|
if (!vgmstream->codec_data) goto fail;
|
|
|
|
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
#ifdef VGM_USE_FFMPEG
|
|
|
|
case coding_FFmpeg: {
|
|
|
|
ffmpeg_codec_data *ffmpeg_data = NULL;
|
|
|
|
|
2017-11-10 19:34:36 +01:00
|
|
|
if (txth.codec == FFMPEG || txth.codec == AC3) {
|
2017-07-15 11:49:28 +02:00
|
|
|
/* default FFmpeg */
|
2018-11-24 01:45:04 +01:00
|
|
|
ffmpeg_data = init_ffmpeg_offset(txth.streamBody, txth.start_offset,txth.data_size);
|
2017-07-15 11:49:28 +02:00
|
|
|
if ( !ffmpeg_data ) goto fail;
|
2017-09-24 22:28:35 +02:00
|
|
|
|
|
|
|
if (vgmstream->num_samples == 0)
|
|
|
|
vgmstream->num_samples = ffmpeg_data->totalSamples; /* sometimes works */
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* fake header FFmpeg */
|
|
|
|
uint8_t buf[200];
|
|
|
|
int32_t bytes;
|
|
|
|
|
|
|
|
if (txth.codec == ATRAC3) {
|
|
|
|
int block_size = txth.interleave;
|
|
|
|
int joint_stereo;
|
|
|
|
switch(txth.codec_mode) {
|
|
|
|
case 0: joint_stereo = vgmstream->channels > 1 && txth.interleave/vgmstream->channels==0x60 ? 1 : 0; break; /* autodetect */
|
|
|
|
case 1: joint_stereo = 1; break; /* force joint stereo */
|
|
|
|
case 2: joint_stereo = 0; break; /* force stereo */
|
|
|
|
default: goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes = ffmpeg_make_riff_atrac3(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_size, joint_stereo, txth.skip_samples);
|
|
|
|
}
|
|
|
|
else if (txth.codec == ATRAC3PLUS) {
|
|
|
|
int block_size = txth.interleave;
|
|
|
|
|
|
|
|
bytes = ffmpeg_make_riff_atrac3plus(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_size, txth.skip_samples);
|
|
|
|
}
|
|
|
|
else if (txth.codec == XMA1) {
|
|
|
|
int xma_stream_mode = txth.codec_mode == 1 ? 1 : 0;
|
|
|
|
|
|
|
|
bytes = ffmpeg_make_riff_xma1(buf, 100, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode);
|
|
|
|
}
|
|
|
|
else if (txth.codec == XMA2) {
|
2018-08-15 21:19:12 +02:00
|
|
|
int block_count, block_size;
|
|
|
|
|
|
|
|
block_size = txth.interleave ? txth.interleave : 2048;
|
|
|
|
block_count = txth.data_size / block_size;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
bytes = ffmpeg_make_riff_xma2(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size);
|
2017-07-15 11:49:28 +02:00
|
|
|
if ( !ffmpeg_data ) goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
vgmstream->codec_data = ffmpeg_data;
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
if (txth.codec == XMA1 || txth.codec == XMA2) {
|
2018-11-24 01:45:04 +01:00
|
|
|
xma_fix_raw_samples(vgmstream, txth.streamBody, txth.start_offset,txth.data_size, 0, 0,0);
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
} else if (txth.skip_samples_set) { /* force encoder delay */
|
2017-07-15 11:49:28 +02:00
|
|
|
ffmpeg_set_skip_samples(ffmpeg_data, txth.skip_samples);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef VGM_USE_FFMPEG
|
2018-08-13 23:02:31 +02:00
|
|
|
if ((txth.sample_type==1 || txth.num_samples_data_size) && (txth.codec == XMA1 || txth.codec == XMA2)) {
|
2017-07-15 11:49:28 +02:00
|
|
|
/* manually find sample offsets */
|
2018-05-12 13:53:43 +02:00
|
|
|
ms_sample_data msd = {0};
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
msd.xma_version = 1;
|
|
|
|
msd.channels = txth.channels;
|
|
|
|
msd.data_offset = txth.start_offset;
|
|
|
|
msd.data_size = txth.data_size;
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth.sample_type==1) {
|
2018-05-12 13:53:43 +02:00
|
|
|
msd.loop_flag = txth.loop_flag;
|
|
|
|
msd.loop_start_b = txth.loop_start_sample;
|
|
|
|
msd.loop_end_b = txth.loop_end_sample;
|
|
|
|
msd.loop_start_subframe = txth.loop_adjust & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */
|
|
|
|
msd.loop_end_subframe = txth.loop_adjust >> 4; /* upper 4b: subframe where the loop ends, 0..3 */
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
xma_get_samples(&msd, txth.streamBody);
|
2018-05-12 13:53:43 +02:00
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
vgmstream->num_samples = msd.num_samples;
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth.sample_type==1) {
|
2018-05-12 13:53:43 +02:00
|
|
|
vgmstream->loop_start_sample = msd.loop_start_sample;
|
|
|
|
vgmstream->loop_end_sample = msd.loop_end_sample;
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
vgmstream->coding_type = coding;
|
|
|
|
vgmstream->meta_type = meta_TXTH;
|
2018-08-23 18:00:34 +02:00
|
|
|
vgmstream->allow_dual_stereo = 1;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
if ( !vgmstream_open_stream(vgmstream,txth.streamBody,txth.start_offset) )
|
2017-07-15 11:49:28 +02:00
|
|
|
goto fail;
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
if (txth.streamtext_opened) close_streamfile(txth.streamText);
|
|
|
|
if (txth.streamhead_opened) close_streamfile(txth.streamHead);
|
|
|
|
if (txth.streambody_opened) close_streamfile(txth.streamBody);
|
2017-07-15 11:49:28 +02:00
|
|
|
return vgmstream;
|
|
|
|
|
|
|
|
fail:
|
2018-11-24 01:45:04 +01:00
|
|
|
if (txth.streamtext_opened) close_streamfile(txth.streamText);
|
|
|
|
if (txth.streamhead_opened) close_streamfile(txth.streamHead);
|
|
|
|
if (txth.streambody_opened) close_streamfile(txth.streamBody);
|
2017-07-15 11:49:28 +02:00
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-11-10 19:34:36 +01:00
|
|
|
|
|
|
|
static STREAMFILE * open_txth(STREAMFILE * streamFile) {
|
2019-01-12 01:00:07 +01:00
|
|
|
char basename[PATH_LIMIT];
|
2017-11-10 19:34:36 +01:00
|
|
|
char filename[PATH_LIMIT];
|
|
|
|
char fileext[PATH_LIMIT];
|
2019-01-12 01:00:07 +01:00
|
|
|
const char *subext;
|
2017-11-10 19:34:36 +01:00
|
|
|
STREAMFILE * streamText;
|
|
|
|
|
|
|
|
/* try "(path/)(name.ext).txth" */
|
2018-03-29 21:03:25 +02:00
|
|
|
get_streamfile_name(streamFile,filename,PATH_LIMIT);
|
2017-11-10 19:34:36 +01:00
|
|
|
strcat(filename, ".txth");
|
2018-08-04 20:42:25 +02:00
|
|
|
streamText = open_streamfile(streamFile,filename);
|
2017-11-10 19:34:36 +01:00
|
|
|
if (streamText) return streamText;
|
|
|
|
|
2019-01-12 01:00:07 +01:00
|
|
|
/* try "(path/)(.sub.ext).txth" */
|
|
|
|
get_streamfile_basename(streamFile,basename,PATH_LIMIT);
|
|
|
|
subext = filename_extension(basename);
|
|
|
|
if (subext != NULL) {
|
|
|
|
get_streamfile_path(streamFile,filename,PATH_LIMIT);
|
|
|
|
get_streamfile_ext(streamFile,fileext,PATH_LIMIT);
|
|
|
|
strcat(filename,".");
|
|
|
|
strcat(filename, subext);
|
|
|
|
strcat(filename,".");
|
|
|
|
strcat(filename, fileext);
|
|
|
|
strcat(filename, ".txth");
|
|
|
|
|
|
|
|
streamText = open_streamfile(streamFile,filename);
|
|
|
|
if (streamText) return streamText;
|
|
|
|
}
|
|
|
|
|
2017-11-10 19:34:36 +01:00
|
|
|
/* try "(path/)(.ext).txth" */
|
2018-03-29 21:03:25 +02:00
|
|
|
get_streamfile_path(streamFile,filename,PATH_LIMIT);
|
|
|
|
get_streamfile_ext(streamFile,fileext,PATH_LIMIT);
|
2017-11-10 19:34:36 +01:00
|
|
|
strcat(filename,".");
|
|
|
|
strcat(filename, fileext);
|
|
|
|
strcat(filename, ".txth");
|
2018-08-04 20:42:25 +02:00
|
|
|
streamText = open_streamfile(streamFile,filename);
|
2017-11-10 19:34:36 +01:00
|
|
|
if (streamText) return streamText;
|
|
|
|
|
|
|
|
/* try "(path/).txth" */
|
2018-03-29 21:03:25 +02:00
|
|
|
get_streamfile_path(streamFile,filename,PATH_LIMIT);
|
2017-11-10 19:34:36 +01:00
|
|
|
strcat(filename, ".txth");
|
2018-08-04 20:42:25 +02:00
|
|
|
streamText = open_streamfile(streamFile,filename);
|
2017-11-10 19:34:36 +01:00
|
|
|
if (streamText) return streamText;
|
|
|
|
|
|
|
|
/* not found */
|
2018-03-29 21:03:25 +02:00
|
|
|
return NULL;
|
2017-11-10 19:34:36 +01:00
|
|
|
}
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
/* Simple text parser of "key = value" lines.
|
|
|
|
* The code is meh and error handling not exactly the best. */
|
2018-11-24 01:45:04 +01:00
|
|
|
static int parse_txth(txth_header * txth) {
|
2017-11-23 23:03:55 +01:00
|
|
|
off_t txt_offset = 0x00;
|
2018-11-24 01:45:04 +01:00
|
|
|
off_t file_size = get_streamfile_size(txth->streamText);
|
|
|
|
|
|
|
|
/* setup txth defaults */
|
|
|
|
if (txth->streamBody)
|
|
|
|
txth->data_size = get_streamfile_size(txth->streamBody);
|
|
|
|
txth->target_subsong = txth->streamFile->stream_index;
|
|
|
|
if (txth->target_subsong == 0) txth->target_subsong = 1;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* skip BOM if needed */
|
2018-11-24 01:45:04 +01:00
|
|
|
if ((uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFFFE ||
|
|
|
|
(uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFEFF) {
|
2017-11-23 23:03:55 +01:00
|
|
|
txt_offset = 0x02;
|
2018-11-24 01:45:04 +01:00
|
|
|
}
|
|
|
|
else if (((uint32_t)read_32bitBE(0x00, txth->streamText) & 0xFFFFFF00) == 0xEFBBBF00) {
|
|
|
|
txt_offset = 0x03;
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
/* read lines */
|
2017-11-23 23:03:55 +01:00
|
|
|
while (txt_offset < file_size) {
|
|
|
|
char line[TXT_LINE_MAX] = {0};
|
|
|
|
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
|
|
|
|
int ok, bytes_read, line_done;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,txth->streamText, &line_done);
|
2017-11-23 23:03:55 +01:00
|
|
|
if (!line_done) goto fail;
|
2018-12-01 02:41:56 +01:00
|
|
|
//;VGM_LOG("TXTH: line=%s\n",line);
|
2017-11-23 23:03:55 +01:00
|
|
|
|
|
|
|
txt_offset += bytes_read;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
/* get key/val (ignores lead/trail spaces, stops at space/comment/separator) */
|
|
|
|
ok = sscanf(line, " %[^ \t#=] = %[^ \t#\r\n] ", key,val);
|
|
|
|
if (ok != 2) /* ignore line if no key=val (comment or garbage) */
|
|
|
|
continue;
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_keyval(txth->streamFile, txth, key, val)) /* read key/val */
|
2017-07-15 11:49:28 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!txth->loop_flag_set)
|
|
|
|
txth->loop_flag = txth->loop_end_sample && txth->loop_end_sample != 0xFFFFFFFF;
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!txth->streamBody)
|
|
|
|
goto fail;
|
|
|
|
|
2018-11-24 15:25:11 +01:00
|
|
|
if (txth->data_size > get_streamfile_size(txth->streamBody) - txth->start_offset || txth->data_size == 0)
|
|
|
|
txth->data_size = get_streamfile_size(txth->streamBody) - txth->start_offset;
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char * key, char * val) {
|
|
|
|
//;VGM_LOG("TXTH: key=%s, val=%s\n", key, val);
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
if (0==strcmp(key,"codec")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (0==strcmp(val,"PSX")) txth->codec = PSX;
|
|
|
|
else if (0==strcmp(val,"XBOX")) txth->codec = XBOX;
|
|
|
|
else if (0==strcmp(val,"NGC_DTK")) txth->codec = NGC_DTK;
|
|
|
|
else if (0==strcmp(val,"DTK")) txth->codec = NGC_DTK;
|
|
|
|
else if (0==strcmp(val,"PCM16BE")) txth->codec = PCM16BE;
|
|
|
|
else if (0==strcmp(val,"PCM16LE")) txth->codec = PCM16LE;
|
|
|
|
else if (0==strcmp(val,"PCM8")) txth->codec = PCM8;
|
|
|
|
else if (0==strcmp(val,"SDX2")) txth->codec = SDX2;
|
|
|
|
else if (0==strcmp(val,"DVI_IMA")) txth->codec = DVI_IMA;
|
|
|
|
else if (0==strcmp(val,"MPEG")) txth->codec = MPEG;
|
|
|
|
else if (0==strcmp(val,"IMA")) txth->codec = IMA;
|
|
|
|
else if (0==strcmp(val,"AICA")) txth->codec = AICA;
|
|
|
|
else if (0==strcmp(val,"MSADPCM")) txth->codec = MSADPCM;
|
|
|
|
else if (0==strcmp(val,"NGC_DSP")) txth->codec = NGC_DSP;
|
|
|
|
else if (0==strcmp(val,"DSP")) txth->codec = NGC_DSP;
|
|
|
|
else if (0==strcmp(val,"PCM8_U_int")) txth->codec = PCM8_U_int;
|
|
|
|
else if (0==strcmp(val,"PSX_bf")) txth->codec = PSX_bf;
|
|
|
|
else if (0==strcmp(val,"MS_IMA")) txth->codec = MS_IMA;
|
|
|
|
else if (0==strcmp(val,"PCM8_U")) txth->codec = PCM8_U;
|
|
|
|
else if (0==strcmp(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4;
|
|
|
|
else if (0==strcmp(val,"ATRAC3")) txth->codec = ATRAC3;
|
|
|
|
else if (0==strcmp(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS;
|
|
|
|
else if (0==strcmp(val,"XMA1")) txth->codec = XMA1;
|
|
|
|
else if (0==strcmp(val,"XMA2")) txth->codec = XMA2;
|
|
|
|
else if (0==strcmp(val,"FFMPEG")) txth->codec = FFMPEG;
|
|
|
|
else if (0==strcmp(val,"AC3")) txth->codec = AC3;
|
2018-12-27 16:14:59 +01:00
|
|
|
else if (0==strcmp(val,"PCFX")) txth->codec = PCFX;
|
2019-01-09 20:30:15 +01:00
|
|
|
else if (0==strcmp(val,"PCM4")) txth->codec = PCM4;
|
|
|
|
else if (0==strcmp(val,"PCM4_U")) txth->codec = PCM4_U;
|
2019-01-12 13:02:08 +01:00
|
|
|
else if (0==strcmp(val,"OKI16")) txth->codec = OKI16;
|
2017-07-15 11:49:28 +02:00
|
|
|
else goto fail;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"codec_mode")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail;
|
|
|
|
}
|
2018-11-24 15:25:11 +01:00
|
|
|
else if (0==strcmp(key,"value_mul") || 0==strcmp(key,"value_*")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->value_mul)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
2018-11-24 15:25:11 +01:00
|
|
|
else if (0==strcmp(key,"value_div") || 0==strcmp(key,"value_/")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->value_div)) goto fail;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"value_add") || 0==strcmp(key,"value_+")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->value_add)) goto fail;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"value_sub") || 0==strcmp(key,"value_-")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->value_sub)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"id_value")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"id_offset")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
if (txth->id_value != txth->id_offset) /* evaluate current ID */
|
|
|
|
goto fail;
|
|
|
|
}
|
2018-11-24 15:25:11 +01:00
|
|
|
else if (0==strcmp(key,"interleave")) {
|
|
|
|
if (0==strcmp(val,"half_size")) {
|
|
|
|
if (txth->channels == 0) goto fail;
|
|
|
|
txth->interleave = txth->data_size / txth->channels;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail;
|
|
|
|
}
|
|
|
|
}
|
2019-01-20 00:17:06 +01:00
|
|
|
else if (0==strcmp(key,"interleave_last")) {
|
|
|
|
if (0==strcmp(val,"auto")) {
|
|
|
|
if (txth->channels > 0 && txth->interleave > 0)
|
|
|
|
txth->interleave_last = (txth->data_size % (txth->interleave * txth->channels)) / txth->channels;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->interleave_last)) goto fail;
|
|
|
|
}
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
else if (0==strcmp(key,"channels")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"sample_rate")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->sample_rate)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"start_offset")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->start_offset)) goto fail;
|
|
|
|
if (!txth->data_size_set) {
|
|
|
|
txth->data_size = !txth->streamBody ? 0 :
|
|
|
|
get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"data_size")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->data_size)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
txth->data_size_set = 1;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"sample_type")) {
|
2018-08-13 23:02:31 +02:00
|
|
|
if (0==strcmp(val,"samples")) txth->sample_type = 0;
|
|
|
|
else if (0==strcmp(val,"bytes")) txth->sample_type = 1;
|
|
|
|
else if (0==strcmp(val,"blocks")) txth->sample_type = 2;
|
2017-07-15 11:49:28 +02:00
|
|
|
else goto fail;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"num_samples")) {
|
|
|
|
if (0==strcmp(val,"data_size")) {
|
|
|
|
txth->num_samples = get_bytes_to_samples(txth, txth->data_size);
|
2018-05-12 13:53:43 +02:00
|
|
|
txth->num_samples_data_size = 1;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->num_samples)) goto fail;
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==1)
|
2017-07-15 11:49:28 +02:00
|
|
|
txth->num_samples = get_bytes_to_samples(txth, txth->num_samples);
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==2)
|
|
|
|
txth->num_samples = get_bytes_to_samples(txth, txth->num_samples * (txth->interleave*txth->channels));
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"loop_start_sample")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->loop_start_sample)) goto fail;
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==1)
|
2017-07-15 11:49:28 +02:00
|
|
|
txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample);
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==2)
|
|
|
|
txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample * (txth->interleave*txth->channels));
|
2017-07-15 11:49:28 +02:00
|
|
|
if (txth->loop_adjust)
|
|
|
|
txth->loop_start_sample += txth->loop_adjust;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"loop_end_sample")) {
|
|
|
|
if (0==strcmp(val,"data_size")) {
|
|
|
|
txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size);
|
|
|
|
}
|
|
|
|
else {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->loop_end_sample)) goto fail;
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==1)
|
2017-07-15 11:49:28 +02:00
|
|
|
txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample);
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==2)
|
|
|
|
txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample * (txth->interleave*txth->channels));
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
if (txth->loop_adjust)
|
|
|
|
txth->loop_end_sample += txth->loop_adjust;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"skip_samples")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->skip_samples)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
txth->skip_samples_set = 1;
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==1)
|
2017-07-15 11:49:28 +02:00
|
|
|
txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples);
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==2)
|
|
|
|
txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples * (txth->interleave*txth->channels));
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"loop_adjust")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->loop_adjust)) goto fail;
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==1)
|
2017-07-15 11:49:28 +02:00
|
|
|
txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust);
|
2018-08-13 23:02:31 +02:00
|
|
|
if (txth->sample_type==2)
|
|
|
|
txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust * (txth->interleave*txth->channels));
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"loop_flag")) {
|
2018-08-22 21:04:16 +02:00
|
|
|
if (0==strcmp(val,"auto")) {
|
|
|
|
txth->loop_flag_auto = 1;
|
|
|
|
}
|
|
|
|
else {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->loop_flag)) goto fail;
|
2018-08-22 21:04:16 +02:00
|
|
|
txth->loop_flag_set = 1;
|
2018-11-24 15:25:11 +01:00
|
|
|
if (txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) { /* normally -1 = no loop */
|
|
|
|
txth->loop_flag = 0;
|
|
|
|
}
|
2018-08-22 21:04:16 +02:00
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"coef_offset")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->coef_offset)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"coef_spacing")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->coef_spacing)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"coef_endianness")) {
|
|
|
|
if (val[0]=='B' && val[1]=='E')
|
|
|
|
txth->coef_big_endian = 1;
|
|
|
|
else if (val[0]=='L' && val[1]=='E')
|
|
|
|
txth->coef_big_endian = 0;
|
2018-11-24 01:45:04 +01:00
|
|
|
else if (!parse_num(txth->streamHead,txth,val, &txth->coef_big_endian)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"coef_mode")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
2018-08-22 21:04:16 +02:00
|
|
|
else if (0==strcmp(key,"psx_loops")) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"subsong_count")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->subsong_count)) goto fail;
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"subsong_offset")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->subsong_offset)) goto fail;
|
|
|
|
}
|
2018-12-27 16:51:27 +01:00
|
|
|
else if (0==strcmp(key,"name_offset")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->name_offset)) goto fail;
|
|
|
|
txth->name_offset_set = 1;
|
|
|
|
/* special subsong adjustment */
|
|
|
|
if (txth->subsong_offset)
|
|
|
|
txth->name_offset = txth->name_offset + txth->subsong_offset * (txth->target_subsong - 1);
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"name_size")) {
|
|
|
|
if (!parse_num(txth->streamHead,txth,val, &txth->name_size)) goto fail;
|
|
|
|
}
|
2018-11-24 01:45:04 +01:00
|
|
|
else if (0==strcmp(key,"header_file")) {
|
|
|
|
if (txth->streamhead_opened) {
|
|
|
|
close_streamfile(txth->streamHead);
|
|
|
|
txth->streamHead = NULL;
|
|
|
|
txth->streamhead_opened = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0==strcmp(val,"null")) { /* reset */
|
|
|
|
if (!txth->streamfile_is_txth) {
|
|
|
|
txth->streamHead = txth->streamFile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
|
|
|
|
txth->streamHead = open_streamfile_by_ext(txth->streamFile, (val+2));
|
|
|
|
if (!txth->streamHead) goto fail;
|
|
|
|
txth->streamhead_opened = 1;
|
|
|
|
}
|
|
|
|
else { /* open file */
|
|
|
|
fix_dir_separators(val); /* clean paths */
|
|
|
|
|
|
|
|
txth->streamHead = open_streamfile_by_filename(txth->streamFile, val);
|
|
|
|
if (!txth->streamHead) goto fail;
|
|
|
|
txth->streamhead_opened = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (0==strcmp(key,"body_file")) {
|
|
|
|
if (txth->streambody_opened) {
|
|
|
|
close_streamfile(txth->streamBody);
|
|
|
|
txth->streamBody = NULL;
|
|
|
|
txth->streambody_opened = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0==strcmp(val,"null")) { /* reset */
|
|
|
|
if (!txth->streamfile_is_txth) {
|
|
|
|
txth->streamBody = txth->streamFile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
|
|
|
|
txth->streamBody = open_streamfile_by_ext(txth->streamFile, (val+2));
|
|
|
|
if (!txth->streamBody) goto fail;
|
|
|
|
txth->streambody_opened = 1;
|
|
|
|
}
|
|
|
|
else { /* open file */
|
|
|
|
fix_dir_separators(val); /* clean paths */
|
|
|
|
|
|
|
|
txth->streamBody = open_streamfile_by_filename(txth->streamFile, val);
|
|
|
|
if (!txth->streamBody) goto fail;
|
|
|
|
txth->streambody_opened = 1;
|
|
|
|
}
|
|
|
|
|
2018-12-01 02:41:56 +01:00
|
|
|
/* use body as header when opening a .txth directly to simplify things */
|
|
|
|
if (txth->streamfile_is_txth && !txth->streamhead_opened) {
|
|
|
|
txth->streamHead = txth->streamBody;
|
|
|
|
}
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
txth->data_size = !txth->streamBody ? 0 :
|
|
|
|
get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */
|
2018-08-22 21:04:16 +02:00
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
else {
|
|
|
|
VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) {
|
2018-11-24 15:25:11 +01:00
|
|
|
/* out_value can be these, save before modifying */
|
|
|
|
uint32_t value_mul = txth->value_mul;
|
|
|
|
uint32_t value_div = txth->value_div;
|
|
|
|
uint32_t value_add = txth->value_add;
|
|
|
|
uint32_t value_sub = txth->value_sub;
|
2018-11-24 01:45:04 +01:00
|
|
|
uint32_t subsong_offset = txth->subsong_offset;
|
2017-07-15 11:49:28 +02:00
|
|
|
|
|
|
|
if (val[0] == '@') { /* offset */
|
2018-11-24 01:45:04 +01:00
|
|
|
uint32_t offset = 0;
|
2017-07-15 11:49:28 +02:00
|
|
|
char ed1 = 'L', ed2 = 'E';
|
|
|
|
int size = 4;
|
|
|
|
int big_endian = 0;
|
|
|
|
int hex = (val[1]=='0' && val[2]=='x');
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
/* can happen when loading .txth and not setting body/head */
|
|
|
|
if (!streamFile)
|
|
|
|
goto fail;
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
/* read exactly N fields in the expected format */
|
|
|
|
if (strchr(val,':') && strchr(val,'$')) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (sscanf(val, hex ? "@%x:%c%c$%i" : "@%u:%c%c$%i", &offset, &ed1,&ed2, &size) != 4) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
} else if (strchr(val,':')) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (sscanf(val, hex ? "@%x:%c%c" : "@%u:%c%c", &offset, &ed1,&ed2) != 3) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
} else if (strchr(val,'$')) {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (sscanf(val, hex ? "@%x$%i" : "@%u$%i", &offset, &size) != 2) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
} else {
|
2018-11-24 01:45:04 +01:00
|
|
|
if (sscanf(val, hex ? "@%x" : "@%u", &offset) != 1) goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
|
|
|
|
2018-12-08 00:33:07 +01:00
|
|
|
if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile))
|
2017-07-15 11:49:28 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (ed1 == 'B' && ed2 == 'E')
|
|
|
|
big_endian = 1;
|
|
|
|
else if (!(ed1 == 'L' && ed2 == 'E'))
|
|
|
|
goto fail;
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
if (subsong_offset)
|
|
|
|
offset = offset + subsong_offset * (txth->target_subsong - 1);
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
switch(size) {
|
2018-11-24 01:45:04 +01:00
|
|
|
case 1: *out_value = read_8bit(offset,streamFile); break;
|
|
|
|
case 2: *out_value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break;
|
|
|
|
case 3: *out_value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break;
|
|
|
|
case 4: *out_value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break;
|
2017-07-15 11:49:28 +02:00
|
|
|
default: goto fail;
|
|
|
|
}
|
|
|
|
}
|
2018-11-24 15:25:11 +01:00
|
|
|
else if (val[0] >= '0' && val[0] <= '9') { /* unsigned constant */
|
2017-07-15 11:49:28 +02:00
|
|
|
int hex = (val[0]=='0' && val[1]=='x');
|
|
|
|
|
2018-11-24 01:45:04 +01:00
|
|
|
if (sscanf(val, hex ? "%x" : "%u", out_value)!=1)
|
|
|
|
goto fail;
|
2017-07-15 11:49:28 +02:00
|
|
|
}
|
2018-11-24 15:25:11 +01:00
|
|
|
else { /* known field */
|
|
|
|
if (0==strcmp(val,"interleave")) *out_value = txth->interleave;
|
2019-01-20 00:17:06 +01:00
|
|
|
if (0==strcmp(val,"interleave_last")) *out_value = txth->interleave_last;
|
2018-11-24 15:25:11 +01:00
|
|
|
else if (0==strcmp(val,"channels")) *out_value = txth->channels;
|
|
|
|
else if (0==strcmp(val,"sample_rate")) *out_value = txth->sample_rate;
|
|
|
|
else if (0==strcmp(val,"start_offset")) *out_value = txth->start_offset;
|
|
|
|
else if (0==strcmp(val,"data_size")) *out_value = txth->data_size;
|
|
|
|
else if (0==strcmp(val,"num_samples")) *out_value = txth->num_samples;
|
|
|
|
else if (0==strcmp(val,"loop_start_sample")) *out_value = txth->loop_start_sample;
|
|
|
|
else if (0==strcmp(val,"loop_end_sample")) *out_value = txth->loop_end_sample;
|
|
|
|
else if (0==strcmp(val,"subsong_count")) *out_value = txth->subsong_count;
|
|
|
|
else if (0==strcmp(val,"subsong_offset")) *out_value = txth->subsong_offset;
|
|
|
|
else goto fail;
|
|
|
|
}
|
2017-07-15 11:49:28 +02:00
|
|
|
|
2018-12-01 02:41:56 +01:00
|
|
|
/* operators, but only if current value wasn't set to 0 right before */
|
|
|
|
if (value_mul && txth->value_mul)
|
2018-11-24 15:25:11 +01:00
|
|
|
*out_value = (*out_value) * value_mul;
|
2018-12-01 02:41:56 +01:00
|
|
|
if (value_div && txth->value_div)
|
2018-11-24 15:25:11 +01:00
|
|
|
*out_value = (*out_value) / value_div;
|
2018-12-01 02:41:56 +01:00
|
|
|
if (value_add && txth->value_add)
|
2018-11-24 15:25:11 +01:00
|
|
|
*out_value = (*out_value) + value_add;
|
2018-12-01 02:41:56 +01:00
|
|
|
if (value_sub && txth->value_sub)
|
2018-11-24 15:25:11 +01:00
|
|
|
*out_value = (*out_value) - value_sub;
|
2018-11-24 01:45:04 +01:00
|
|
|
|
|
|
|
//;VGM_LOG("TXTH: val=%s, read %u (0x%x)\n", val, *out_value, *out_value);
|
2017-07-15 11:49:28 +02:00
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) {
|
2017-08-25 23:27:23 +02:00
|
|
|
if (!txth->channels)
|
|
|
|
return 0; /* div-by-zero is no fun */
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
switch(txth->codec) {
|
|
|
|
case MS_IMA:
|
2017-08-25 23:27:23 +02:00
|
|
|
if (!txth->interleave) return 0;
|
2017-07-15 11:49:28 +02:00
|
|
|
return ms_ima_bytes_to_samples(bytes, txth->interleave, txth->channels);
|
|
|
|
case XBOX:
|
2018-02-17 12:30:14 +01:00
|
|
|
return xbox_ima_bytes_to_samples(bytes, txth->channels);
|
2017-07-15 11:49:28 +02:00
|
|
|
case NGC_DSP:
|
|
|
|
return dsp_bytes_to_samples(bytes, txth->channels);
|
|
|
|
case PSX:
|
|
|
|
case PSX_bf:
|
|
|
|
return ps_bytes_to_samples(bytes, txth->channels);
|
|
|
|
case PCM16BE:
|
|
|
|
case PCM16LE:
|
|
|
|
return pcm_bytes_to_samples(bytes, txth->channels, 16);
|
|
|
|
case PCM8:
|
|
|
|
case PCM8_U_int:
|
|
|
|
case PCM8_U:
|
|
|
|
return pcm_bytes_to_samples(bytes, txth->channels, 8);
|
2019-01-09 20:30:15 +01:00
|
|
|
case PCM4:
|
|
|
|
case PCM4_U:
|
|
|
|
return pcm_bytes_to_samples(bytes, txth->channels, 4);
|
2017-07-15 11:49:28 +02:00
|
|
|
case MSADPCM:
|
2017-08-25 23:27:23 +02:00
|
|
|
if (!txth->interleave) return 0;
|
2017-07-15 11:49:28 +02:00
|
|
|
return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels);
|
|
|
|
case ATRAC3:
|
2017-08-25 23:27:23 +02:00
|
|
|
if (!txth->interleave) return 0;
|
2017-07-15 11:49:28 +02:00
|
|
|
return atrac3_bytes_to_samples(bytes, txth->interleave);
|
|
|
|
case ATRAC3PLUS:
|
2017-08-25 23:27:23 +02:00
|
|
|
if (!txth->interleave) return 0;
|
2017-07-15 11:49:28 +02:00
|
|
|
return atrac3plus_bytes_to_samples(bytes, txth->interleave);
|
|
|
|
|
|
|
|
/* XMA bytes-to-samples is done at the end as the value meanings are a bit different */
|
|
|
|
case XMA1:
|
|
|
|
case XMA2:
|
|
|
|
return bytes; /* preserve */
|
|
|
|
|
2017-11-10 19:34:36 +01:00
|
|
|
case AC3:
|
|
|
|
if (!txth->interleave) return 0;
|
|
|
|
return bytes / txth->interleave * 256 * txth->channels;
|
|
|
|
|
2017-07-15 11:49:28 +02:00
|
|
|
case IMA:
|
|
|
|
case DVI_IMA:
|
2018-05-27 17:29:23 +02:00
|
|
|
return ima_bytes_to_samples(bytes, txth->channels);
|
|
|
|
case AICA:
|
|
|
|
return aica_bytes_to_samples(bytes, txth->channels);
|
2018-12-27 16:14:59 +01:00
|
|
|
case PCFX:
|
2019-01-12 13:02:08 +01:00
|
|
|
case OKI16:
|
|
|
|
return oki_bytes_to_samples(bytes, txth->channels);
|
2018-05-27 17:29:23 +02:00
|
|
|
|
|
|
|
/* untested */
|
2017-07-15 11:49:28 +02:00
|
|
|
case SDX2:
|
|
|
|
return bytes;
|
|
|
|
case NGC_DTK:
|
2018-08-15 21:19:12 +02:00
|
|
|
return bytes / 0x20 * 28; /* always stereo */
|
2017-07-15 11:49:28 +02:00
|
|
|
case APPLE_IMA4:
|
2017-08-25 23:27:23 +02:00
|
|
|
if (!txth->interleave) return 0;
|
2017-07-15 11:49:28 +02:00
|
|
|
return (bytes / txth->interleave) * (txth->interleave - 2) * 2;
|
|
|
|
|
|
|
|
case MPEG: /* a bit complex */
|
2017-09-24 22:28:35 +02:00
|
|
|
case FFMPEG: /* too complex, try after init */
|
2017-07-15 11:49:28 +02:00
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|