From eac44c37c960af3cb0a34df077b5aca09acf0236 Mon Sep 17 00:00:00 2001 From: bnnm Date: Tue, 23 Jul 2019 22:47:40 +0200 Subject: [PATCH 1/3] Add multichannel Switch Opus --- src/coding/coding.h | 12 ++++ src/coding/ffmpeg_decoder_custom_opus.c | 68 ++++++++++---------- src/meta/ktss.c | 82 ++++++++++++++++--------- src/meta/opus.c | 25 +++++++- 4 files changed, 124 insertions(+), 63 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index 532b6d85..1c5813d6 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -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); diff --git a/src/coding/ffmpeg_decoder_custom_opus.c b/src/coding/ffmpeg_decoder_custom_opus.c index 8f992b86..1e4958bb 100644 --- a/src/coding/ffmpeg_decoder_custom_opus.c +++ b/src/coding/ffmpeg_decoder_custom_opus.c @@ -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); } diff --git a/src/meta/ktss.c b/src/meta/ktss.c index 02f36a47..8b66a8f1 100644 --- a/src/meta/ktss.c +++ b/src/meta/ktss.c @@ -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)) diff --git a/src/meta/opus.c b/src/meta/opus.c index 24c1d893..9e85d170 100644 --- a/src/meta/opus.c +++ b/src/meta/opus.c @@ -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; From 3410f68866f90139b9cd609f1c4058a3e63f84f2 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 24 Jul 2019 15:46:35 +0200 Subject: [PATCH 2/3] Fix compilation without FFmpeg --- src/coding/ffmpeg_decoder_custom_opus.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/coding/ffmpeg_decoder_custom_opus.c b/src/coding/ffmpeg_decoder_custom_opus.c index 1e4958bb..ed5b57b9 100644 --- a/src/coding/ffmpeg_decoder_custom_opus.c +++ b/src/coding/ffmpeg_decoder_custom_opus.c @@ -2,11 +2,15 @@ #include "../streamfile.h" #include +#ifdef VGM_USE_FFMPEG + /** * Transmogrifies custom Opus (no Ogg layer and custom packet headers) into is Xiph Opus, creating * valid Ogg pages with single Opus packets. * Uses an intermediate buffer to make full Ogg pages, since checksums are calculated with the whole page. * + * Mostly as an experiment/demonstration, until some details are sorted out before adding actual libopus. + * * Info, CRC and stuff: * https://www.opus-codec.org/docs/ * https://tools.ietf.org/html/rfc7845.html @@ -512,8 +516,6 @@ static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile) { } -#ifdef VGM_USE_FFMPEG - static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile, opus_type_t type) { size_t num_samples = 0; off_t end_offset = offset + stream_size; @@ -601,9 +603,11 @@ size_t ea_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) { } - /* ******************************************************* */ +/* actual FFmpeg only-code starts here (the above is universal enough but no point to compile) */ +//#ifdef VGM_USE_FFMPEG + 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; From 4b9be69347777b48f6d46654e418759738064c0e Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 24 Jul 2019 15:46:47 +0200 Subject: [PATCH 3/3] Add missing math.h --- src/meta/ea_eaac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meta/ea_eaac.c b/src/meta/ea_eaac.c index 9acdef8c..6d557735 100644 --- a/src/meta/ea_eaac.c +++ b/src/meta/ea_eaac.c @@ -1,3 +1,4 @@ +#include #include "meta.h" #include "../layout/layout.h" #include "../coding/coding.h"