Fix multichannel Wwise Opus

This commit is contained in:
bnnm 2020-11-22 19:00:55 +01:00
parent 450281dafd
commit be8eeb22d4
3 changed files with 60 additions and 15 deletions

View File

@ -551,7 +551,7 @@ ffmpeg_codec_data* init_ffmpeg_ue4_opus(STREAMFILE* sf, off_t start_offset, size
ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg);
size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf);

View File

@ -490,11 +490,11 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
if (mapping_family > 0) {
int i;
/* internal mono/stereo streams (N mono/stereo streams form M channels) */
/* internal mono/stereo streams (N mono/stereo streams that make M channels) */
put_u8(buf+0x13, cfg->stream_count);
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled in 4 streams */
put_u8(buf+0x14, cfg->coupled_count);
/* mapping bits per channel? */
/* mapping per channel (order of channels, ex: 0x000104050203) */
for (i = 0; i < cfg->channels; i++) {
put_u8(buf+0x15+i, cfg->channel_mapping[i]);
}
@ -753,8 +753,8 @@ ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int ta
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
return init_ffmpeg_custom_opus(sf, start_offset, data_size, channels, skip, sample_rate, OPUS_FSB);
}
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip) {
return init_ffmpeg_custom_table_opus(sf, table_offset, table_count, data_offset, data_size, channels, skip, 0, OPUS_WWISE);
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg) {
return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, cfg, OPUS_WWISE);
}
static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset) {

View File

@ -40,6 +40,7 @@ typedef struct {
int block_align;
int average_bps;
int bits_per_sample;
uint8_t channel_type;
uint32_t channel_layout;
size_t extra_size;
@ -462,7 +463,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
break;
}
case OPUS: { /* alt to Vorbis [Girl Cafe Gun (Mobile)] */
case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile)] */
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
/* extra: size 0x12 */
@ -484,18 +485,26 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
break;
}
case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */
int skip, table_count;
case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */
int mapping;
opus_config cfg = {0};
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
if (!ww.seek_offset) goto fail;
/* extra: size 0x10 */
cfg.channels = ww.channels;
cfg.table_offset = ww.seek_offset;
/* extra: size 0x10 (though last 2 fields are beyond, AK plz) */
/* 0x12: samples per frame */
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */
skip = read_u16(ww.fmt_offset + 0x20, sf);
/* 0x22: 1? (though extra size is declared as 0x10 so this is outsize, AK plz */
cfg.table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */
cfg.skip = read_u16(ww.fmt_offset + 0x20, sf);
/* 0x22: codec version */
mapping = read_u8(ww.fmt_offset + 0x23, sf);
if (read_u8(ww.fmt_offset + 0x22, sf) != 1)
goto fail;
/* OPUS is VBR so this is very approximate percent, meh */
if (ww.truncated) {
@ -504,8 +513,42 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
ww.data_size = ww.file_size - start_offset;
}
/* AK does some wonky implicit config for multichannel */
if (mapping == 1 && ww.channel_type == 1) { /* only allowed values ATM, set when >2ch */
static const int8_t mapping_matrix[8][8] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 1, 0, 0, 0, 0, 0, 0, },
{ 0, 2, 1, 0, 0, 0, 0, 0, },
{ 0, 1, 2, 3, 0, 0, 0, 0, },
{ 0, 4, 1, 2, 3, 0, 0, 0, },
{ 0, 4, 1, 2, 3, 5, 0, 0, },
{ 0, 6, 1, 2, 3, 4, 5, 0, },
{ 0, 6, 1, 2, 3, 4, 5, 7, },
};
int i;
/* find coupled OPUS streams (internal streams using 2ch) */
switch(ww.channel_layout) {
case mapping_7POINT1_surround: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */
case mapping_5POINT1_surround: /* 2ch+2ch+1ch+1ch, 4 streams */
case mapping_QUAD_side: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */
case mapping_2POINT1_xiph: /* 2ch+1ch, 2 streams */
case mapping_STEREO: cfg.coupled_count = 1; break; /* 2ch, 1 stream */
default: cfg.coupled_count = 0; break; /* 1ch, 1 stream */
//TODO: AK OPUS doesn't seem to handles others mappings, though AK's .h imply they exist (uses 0 coupleds?)
}
/* total number internal OPUS streams (should be >0) */
cfg.stream_count = ww.channels - cfg.coupled_count;
/* channel assignments */
for (i = 0; i < ww.channels; i++) {
cfg.channel_mapping[i] = mapping_matrix[ww.channels - 1][i];
}
}
/* Wwise Opus saves all frame sizes in the seek table */
vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.seek_offset, table_count, ww.data_offset, ww.data_size, ww.channels, skip);
vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.data_offset, ww.data_size, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
@ -648,6 +691,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
* - HEVAG: very off
* - XMA2: exact file size
* - some RIFX have LE size
* Value is ignored by AK's parser (set to -1).
* (later we'll validate "data" which fortunately is correct)
*/
if (read_u32(0x04,sf) + 0x04 + 0x04 != ww->file_size) {
@ -743,6 +787,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
* - 4b: eConfigType (0=none, 1=standard, 2=ambisonic)
* - 19b: uChannelMask */
if ((ww->channel_layout & 0xFF) == ww->channels) {
ww->channel_type = (ww->channel_layout >> 8) & 0x0F;
ww->channel_layout = (ww->channel_layout >> 12);
}
}