Add multichannel Switch Opus

This commit is contained in:
bnnm 2019-07-23 22:47:40 +02:00
parent b570337e7d
commit eac44c37c9
4 changed files with 124 additions and 63 deletions

View File

@ -293,7 +293,19 @@ void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples);
uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data);
void ffmpeg_set_channel_remapping(ffmpeg_codec_data * data, int *channels_remap);
typedef struct {
int channels;
int skip;
int sample_rate;
/* multichannel-only */
int coupled_count;
int stream_count;
int channel_mapping[8];
} opus_config;
/* ffmpeg_decoder_custom_opus.c (helper-things) */
ffmpeg_codec_data * init_ffmpeg_switch_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config* cfg);
ffmpeg_codec_data * init_ffmpeg_switch_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
ffmpeg_codec_data * init_ffmpeg_ue4_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
ffmpeg_codec_data * init_ffmpeg_ea_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);

View File

@ -13,7 +13,7 @@
* https://github.com/hcs64/ww2ogg
*/
static size_t make_oggs_first(uint8_t * buf, int buf_size, int channels, int skip, int sample_rate);
static size_t make_oggs_first(uint8_t * buf, int buf_size, opus_config *cfg);
static size_t make_oggs_page(uint8_t * buf, int buf_size, size_t data_size, int page_sequence, int granule);
static size_t opus_get_packet_samples(const uint8_t * buf, int len);
static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile);
@ -230,7 +230,7 @@ static size_t opus_io_size(STREAMFILE *streamfile, opus_io_data* data) {
/* Prepares custom IO for custom Opus, that is converted to Ogg Opus on the fly */
static STREAMFILE* setup_opus_streamfile(STREAMFILE *streamFile, int channels, int skip, int sample_rate, off_t stream_offset, size_t stream_size, opus_type_t type) {
static STREAMFILE* setup_opus_streamfile(STREAMFILE *streamFile, opus_config *cfg, off_t stream_offset, size_t stream_size, opus_type_t type) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
opus_io_data io_data = {0};
size_t io_data_size = sizeof(opus_io_data);
@ -239,7 +239,7 @@ static STREAMFILE* setup_opus_streamfile(STREAMFILE *streamFile, int channels, i
io_data.stream_offset = stream_offset;
io_data.stream_size = stream_size;
io_data.physical_offset = stream_offset;
io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), channels, skip, sample_rate);
io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg);
if (!io_data.head_size) goto fail;
io_data.sequence = 2;
io_data.logical_size = opus_io_size(streamFile, &io_data); /* force init */
@ -405,26 +405,16 @@ fail:
return 0;
}
static size_t make_opus_header(uint8_t * buf, int buf_size, int channels, int skip, int sample_rate) {
static size_t make_opus_header(uint8_t * buf, int buf_size, opus_config *cfg) {
size_t header_size = 0x13;
int mapping_family = 0; /* channel config: 0=standard (single stream mono/stereo), 1=vorbis, 255: not defined */
#if 0
int stream_count = 1; /* number of internal mono/stereo streams (N mono/stereo streams form M channels) */
int coupled_count = channels - 1; /* number of stereo streams (packet has which one is mono or stereo) */
int mapping_family = 0;
/* special multichannel config */
if (channels > 2) {
header_size += 0x01+0x01+channels;
switch(type) {
case ...:
...
break;
default:
goto fail;
}
if (cfg->channels > 2) {
/* channel config: 0=standard (single stream mono/stereo), 1=vorbis, 255: not defined */
mapping_family = 1;
header_size += 0x01+0x01+cfg->channels;
}
#endif
if (header_size > buf_size) {
VGM_LOG("OPUS: buffer can't hold header\n");
@ -434,23 +424,24 @@ static size_t make_opus_header(uint8_t * buf, int buf_size, int channels, int sk
put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */
put_32bitBE(buf+0x04, 0x48656164); /* "Head" header magic */
put_8bit (buf+0x08, 1); /* version */
put_8bit (buf+0x09, channels);
put_16bitLE(buf+0x0A, skip);
put_32bitLE(buf+0x0c, sample_rate);
put_8bit (buf+0x09, cfg->channels);
put_16bitLE(buf+0x0A, cfg->skip);
put_32bitLE(buf+0x0c, cfg->sample_rate);
put_16bitLE(buf+0x10, 0); /* output gain */
put_8bit (buf+0x12, mapping_family);
#if 0
if (mapping_family > 0) {
int 0;
int i;
put_8bit(buf+0x13, stream_count);
put_8bit(buf+0x14, coupled_count);
for (i = 0; i < channels; i++) {
put_8bit(buf+0x15+i, i); /* channel mapping (may need to change per family) */
/* internal mono/stereo streams (N mono/stereo streams form M channels) */
put_8bit(buf+0x13, cfg->stream_count);
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */
put_8bit(buf+0x14, cfg->coupled_count);
/* mapping bits per channel? */
for (i = 0; i < cfg->channels; i++) {
put_8bit(buf+0x15+i, cfg->channel_mapping[i]);
}
}
#endif
return header_size;
fail:
@ -485,7 +476,7 @@ fail:
return 0;
}
static size_t make_oggs_first(uint8_t * buf, int buf_size, int channels, int skip, int sample_rate) {
static size_t make_oggs_first(uint8_t * buf, int buf_size, opus_config *cfg) {
int buf_done = 0;
size_t bytes;
@ -493,7 +484,7 @@ static size_t make_oggs_first(uint8_t * buf, int buf_size, int channels, int ski
goto fail;
/* make header */
bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, channels, skip, sample_rate);
bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, cfg);
make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 0, 0);
buf_done += 0x1c + bytes;
@ -613,11 +604,11 @@ size_t ea_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) {
/* ******************************************************* */
static ffmpeg_codec_data * init_ffmpeg_custom_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) {
static ffmpeg_codec_data * init_ffmpeg_custom_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config *cfg, opus_type_t type) {
ffmpeg_codec_data * ffmpeg_data = NULL;
STREAMFILE *temp_streamFile = NULL;
temp_streamFile = setup_opus_streamfile(streamFile, channels, skip, sample_rate, start_offset, data_size, type);
temp_streamFile = setup_opus_streamfile(streamFile, cfg, start_offset, data_size, type);
if (!temp_streamFile) goto fail;
ffmpeg_data = init_ffmpeg_offset(temp_streamFile, 0x00,get_streamfile_size(temp_streamFile));
@ -637,7 +628,18 @@ fail:
close_streamfile(temp_streamFile);
return NULL;
}
static ffmpeg_codec_data * init_ffmpeg_custom_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) {
opus_config cfg = {0};
cfg.channels = channels;
cfg.skip = skip;
cfg.sample_rate = sample_rate;
return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, &cfg, type);
}
ffmpeg_codec_data * init_ffmpeg_switch_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config* cfg) {
return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, cfg, OPUS_SWITCH);
}
ffmpeg_codec_data * init_ffmpeg_switch_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_SWITCH);
}

View File

@ -32,53 +32,79 @@ VGMSTREAM * init_vgmstream_ktss(STREAMFILE *streamFile) {
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
vgmstream->num_samples = read_32bitLE(0x30, streamFile);
vgmstream->sample_rate = read_32bitLE(0x2c, streamFile);
vgmstream->num_samples = read_32bitLE(0x30, streamFile);
vgmstream->loop_start_sample = read_32bitLE(0x34, streamFile);
vgmstream->loop_end_sample = vgmstream->loop_start_sample + loop_length;
vgmstream->meta_type = meta_KTSS;
start_offset = read_32bitLE(0x24, streamFile) + 0x20;
switch (codec_id) {
case 0x2: /* DSP ADPCM - Hyrule Warriors, Fire Emblem Warriors, and other Koei Tecmo games */
/* check type details */
version = read_8bit(0x22, streamFile);
if (version == 1) {
coef_start_offset = 0x40;
coef_spacing = 0x2e;
}
else if (version == 3) { // Fire Emblem Warriors (Switch)
coef_start_offset = 0x5c;
coef_spacing = 0x60;
}
else
goto fail;
case 0x2: /* DSP ADPCM - Hyrule Warriors, Fire Emblem Warriors, and other Koei Tecmo games */
/* check type details */
version = read_8bit(0x22, streamFile);
if (version == 1) {
coef_start_offset = 0x40;
coef_spacing = 0x2e;
}
else if (version == 3) { // Fire Emblem Warriors (Switch)
coef_start_offset = 0x5c;
coef_spacing = 0x60;
}
else
goto fail;
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x8;
dsp_read_coefs_le(vgmstream, streamFile, coef_start_offset, coef_spacing);
break;
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x8;
dsp_read_coefs_le(vgmstream, streamFile, coef_start_offset, coef_spacing);
break;
#ifdef VGM_USE_FFMPEG
case 0x9: /* Opus - Dead or Alive Xtreme 3: Scarlet */
data_size = read_32bitLE(0x44, streamFile);
{
vgmstream->codec_data = init_ffmpeg_switch_opus(streamFile, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate);
case 0x9: { /* Opus - Dead or Alive Xtreme 3: Scarlet, Fire Emblem: Three Houses */
opus_config cfg = {0};
data_size = read_32bitLE(0x44, streamFile);
cfg.channels = vgmstream->channels;
cfg.skip = read_32bitLE(0x58, streamFile);
cfg.sample_rate = vgmstream->sample_rate; /* also at 0x54 */
/* this info seems always included even for stereo streams */
if (vgmstream->channels <= 8) {
int i;
cfg.stream_count = read_8bit(0x5a,streamFile);
cfg.coupled_count = read_8bit(0x5b,streamFile);
for (i = 0; i < vgmstream->channels; i++) {
cfg.channel_mapping[i] = read_8bit(0x5c + i,streamFile);
}
}
vgmstream->codec_data = init_ffmpeg_switch_opus_config(streamFile, start_offset, data_size, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
/* apparently KTSS doesn't need standard Opus reordering, so we undo their thing */
switch(vgmstream->channels) {
case 6: {
/* FL FC FR BL LFE BR > FL FR FC LFE BL BR */
int channel_remap[] = { 0, 2, 2, 3, 3, 5 };
ffmpeg_set_channel_remapping(vgmstream->codec_data, channel_remap);
break;
}
default:
break;
}
if (vgmstream->num_samples == 0) {
vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip;
}
break;
}
break;
default:
goto fail;
#endif
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream, streamFile, start_offset))

View File

@ -9,7 +9,7 @@ static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type,
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag = 0, channel_count;
off_t data_offset;
off_t data_offset, multichannel_offset = 0;
size_t data_size, skip = 0;
@ -22,6 +22,11 @@ static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type,
skip = read_16bitLE(offset + 0x1c, streamFile);
/* 0x1e: ? (seen in Lego Movie 2 (Switch)) */
/* recent >2ch info [Clannad (Switch)] */
if ((uint32_t)read_32bitLE(offset + 0x20, streamFile) == 0x80000005) {
multichannel_offset = offset + 0x20;
}
if ((uint32_t)read_32bitLE(data_offset, streamFile) != 0x80000004)
goto fail;
@ -45,10 +50,26 @@ static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type,
#ifdef VGM_USE_FFMPEG
{
vgmstream->codec_data = init_ffmpeg_switch_opus(streamFile, start_offset,data_size, vgmstream->channels, skip, vgmstream->sample_rate);
opus_config cfg = {0};
cfg.channels = vgmstream->channels;
cfg.skip = skip;
cfg.sample_rate = vgmstream->sample_rate;
if (multichannel_offset && vgmstream->channels <= 8) {
int i;
cfg.stream_count = read_8bit(multichannel_offset + 0x08,streamFile);
cfg.coupled_count = read_8bit(multichannel_offset + 0x09,streamFile);
for (i = 0; i < vgmstream->channels; i++) {
cfg.channel_mapping[i] = read_8bit(multichannel_offset + 0x0a + i,streamFile);
}
}
vgmstream->codec_data = init_ffmpeg_switch_opus_config(streamFile, start_offset,data_size, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
if (vgmstream->num_samples == 0) {
vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip;