diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index 624d48e0..091fac00 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -210,6 +210,8 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal if (get_description_tag(temp,description,"stream count: ")) p_info.info_set("stream_count",temp); if (get_description_tag(temp,description,"stream index: ")) p_info.info_set("stream_index",temp); if (get_description_tag(temp,description,"stream name: ")) p_info.info_set("stream_name",temp); + + if (get_description_tag(temp,description,"channel mask: ")) p_info.info_set("channel_mask",temp); } t_filestats input_vgmstream::get_file_stats(abort_callback & p_abort) { @@ -293,12 +295,16 @@ bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort) /* copy back to global buffer... in case of multithreading stuff? */ memcpy(sample_buffer,temp_buffer, samples_to_do*downmix_channels*sizeof(short)); + unsigned channel_config = audio_chunk::g_guess_channel_config(downmix_channels); bytes = (samples_to_do*downmix_channels * sizeof(sample_buffer[0])); - p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, downmix_channels, 16, audio_chunk::g_guess_channel_config(downmix_channels)); + p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, downmix_channels, 16, channel_config); } else { + unsigned channel_config = vgmstream->channel_layout; + if (!channel_config) + channel_config = audio_chunk::g_guess_channel_config(vgmstream->channels); bytes = (samples_to_do*vgmstream->channels * sizeof(sample_buffer[0])); - p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, vgmstream->channels, 16, audio_chunk::g_guess_channel_config(vgmstream->channels)); + p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, vgmstream->channels, 16, channel_config); } diff --git a/src/coding/coding.h b/src/coding/coding.h index 0391640c..02c79753 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -215,7 +215,7 @@ void free_vorbis_custom(vorbis_custom_codec_data *data); /* mpeg_decoder */ mpeg_codec_data *init_mpeg(STREAMFILE *streamfile, off_t start_offset, coding_t *coding_type, int channels); mpeg_codec_data *init_mpeg_custom(STREAMFILE *streamFile, off_t start_offset, coding_t *coding_type, int channels, mpeg_custom_t custom_type, mpeg_custom_config *config); -void decode_mpeg(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, int channels); +void decode_mpeg(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels); void reset_mpeg(VGMSTREAM *vgmstream); void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample); void free_mpeg(mpeg_codec_data *data); @@ -289,6 +289,7 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample); void free_ffmpeg(ffmpeg_codec_data *data); void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples); +uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data); /* ffmpeg_decoder_custom_opus.c (helper-things) */ ffmpeg_codec_data * init_ffmpeg_switch_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.c b/src/coding/ffmpeg_decoder.c index 90a6c15e..cc1cac8e 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -808,4 +808,10 @@ void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples) { data->skipSamples = skip_samples; } +/* returns channel layout if set */ +uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data) { + if (!data || !data->codecCtx) return 0; + return (uint32_t)data->codecCtx->channel_layout; /* uint64 but there ain't so many speaker mappings */ +} + #endif diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index b0e9004a..95593267 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -66,6 +66,8 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, if (vgmstream->num_samples <= 0) goto fail; + vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); + return vgmstream; fail: diff --git a/src/meta/mp4.c b/src/meta/mp4.c index 03b0077e..5471fe0d 100644 --- a/src/meta/mp4.c +++ b/src/meta/mp4.c @@ -216,12 +216,13 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) { vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_MP4; - vgmstream->num_samples = ffmpeg_data->totalSamples; vgmstream->sample_rate = ffmpeg_data->sampleRate; - vgmstream->channels = ffmpeg_data->channels; + vgmstream->num_samples = ffmpeg_data->totalSamples; vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; + vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); + return vgmstream; fail: @@ -233,11 +234,7 @@ fail: return NULL; } -/** - * Almost the same as streamfile.c's find_chunk but for "atom" chunks, which have chunk_size first because Apple. - * - * returns 0 on failure - */ +/* Almost the same as streamfile.c's find_chunk but for "atom" chunks, which have chunk_size first because Apple, returns 0 on failure */ static int find_atom_be(STREAMFILE *streamFile, uint32_t atom_id, off_t start_offset, off_t *out_atom_offset, size_t *out_atom_size) { size_t filesize; off_t current_atom = start_offset; diff --git a/src/meta/ogg_opus.c b/src/meta/ogg_opus.c index 41a5ca73..dd1d3409 100644 --- a/src/meta/ogg_opus.c +++ b/src/meta/ogg_opus.c @@ -111,6 +111,7 @@ VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) { 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); /* FFmpeg+libopus handles skip samples ok, FFmpeg+opus doesn't */ } #else diff --git a/src/meta/ogg_vorbis.c b/src/meta/ogg_vorbis.c index b9342421..4e5bd8b2 100644 --- a/src/meta/ogg_vorbis.c +++ b/src/meta/ogg_vorbis.c @@ -275,6 +275,18 @@ static void lse_ff_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, } } +static const uint32_t xiph_mappings[] = { + 0, + mapping_MONO, + mapping_STEREO, + mapping_2POINT1_xiph, + mapping_QUAD, + mapping_5POINT0_xiph, + mapping_5POINT1, + mapping_7POINT0, + mapping_7POINT1, +}; + /* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { @@ -634,6 +646,10 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb vgmstream->layout_type = layout_none; vgmstream->meta_type = ovmi->meta_type; + if (vgmstream->channels <= 8) { + vgmstream->channel_layout = xiph_mappings[vgmstream->channels]; + } + return vgmstream; fail: diff --git a/src/meta/wwise.c b/src/meta/wwise.c index 0a17ccd4..233b3e05 100644 --- a/src/meta/wwise.c +++ b/src/meta/wwise.c @@ -29,6 +29,7 @@ typedef struct { int block_align; int average_bps; int bits_per_sample; + uint32_t channel_layout; size_t extra_size; int32_t num_samples; @@ -104,11 +105,12 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { if (ww.fmt_size < 0x12) goto fail; ww.format = (uint16_t)read_16bit(ww.fmt_offset+0x00,streamFile); - if (ww.format == 0x0165) { /* XMA2WAVEFORMAT (always "fmt"+"XMA2", unlike .xma that may only have "XMA2") */ + if (ww.format == 0x0165) { /* pseudo-XMA2WAVEFORMAT (always "fmt"+"XMA2", unlike .xma that may only have "XMA2") */ if (!find_chunk(streamFile, 0x584D4132,first_offset,0, &ww.chunk_offset,NULL, ww.big_endian, 0)) goto fail; xma2_parse_xma2_chunk(streamFile, ww.chunk_offset,&ww.channels,&ww.sample_rate, &ww.loop_flag, &ww.num_samples, &ww.loop_start_sample, &ww.loop_end_sample); - } else { /* WAVEFORMATEX */ + } + else { /* pseudo-WAVEFORMATEX */ ww.channels = read_16bit(ww.fmt_offset+0x02,streamFile); ww.sample_rate = read_32bit(ww.fmt_offset+0x04,streamFile); ww.average_bps = read_32bit(ww.fmt_offset+0x08,streamFile);/* bytes per sec */ @@ -116,9 +118,10 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { ww.bits_per_sample = (uint16_t)read_16bit(ww.fmt_offset+0x0e,streamFile); if (ww.fmt_size > 0x10 && ww.format != 0x0165 && ww.format != 0x0166) /* ignore XMAWAVEFORMAT */ ww.extra_size = (uint16_t)read_16bit(ww.fmt_offset+0x10,streamFile); - /* channel bitmask, see AkSpeakerConfig.h (ex. 1ch uses FRONT_CENTER 0x4, 2ch FRONT_LEFT 0x1 | FRONT_RIGHT 0x2, etc) */ - //if (ww.extra_size >= 6) - // ww.channel_config = read_32bit(ww.fmt_offset+0x14,streamFile); + if (ww.extra_size >= 0x06) { /* mostly WAVEFORMATEXTENSIBLE's bitmask, see AkSpeakerConfig.h */ + /* always present (actual RIFFs only have it in WAVEFORMATEXTENSIBLE) */ + ww.channel_layout = read_32bit(ww.fmt_offset+0x14,streamFile); + } } /* find loop info */ @@ -192,6 +195,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { goto fail; } + start_offset = ww.data_offset; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(ww.channels,ww.loop_flag); @@ -200,10 +204,9 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { vgmstream->sample_rate = ww.sample_rate; vgmstream->loop_start_sample = ww.loop_start_sample; vgmstream->loop_end_sample = ww.loop_end_sample; + vgmstream->channel_layout = ww.channel_layout; vgmstream->meta_type = meta_WWISE_RIFF; - start_offset = ww.data_offset; - switch(ww.codec) { case PCM: /* common */ /* normally riff.c has priority but it's needed when .wem is used */ diff --git a/src/vgmstream.c b/src/vgmstream.c index b6532596..0bc780be 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -2288,6 +2288,36 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { vgmstream->channels); concatn(length,desc,temp); + if (vgmstream->channel_layout) { + int cl = vgmstream->channel_layout; + + snprintf(temp,TEMPSIZE, + "channel mask: 0x%x /", + vgmstream->channel_layout); + concatn(length,desc,temp); + + if (cl & speaker_FL) concatn(length,desc," FL"); + if (cl & speaker_FR) concatn(length,desc," FR"); + if (cl & speaker_FC) concatn(length,desc," FC"); + if (cl & speaker_LFE) concatn(length,desc," LFE"); + if (cl & speaker_BL) concatn(length,desc," BL"); + if (cl & speaker_BR) concatn(length,desc," BR"); + if (cl & speaker_FLC) concatn(length,desc," FLC"); + if (cl & speaker_FRC) concatn(length,desc," FRC"); + if (cl & speaker_BC) concatn(length,desc," BC"); + if (cl & speaker_SL) concatn(length,desc," SL"); + if (cl & speaker_SR) concatn(length,desc," SR"); + if (cl & speaker_TC) concatn(length,desc," TC"); + if (cl & speaker_TFL) concatn(length,desc," TFL"); + if (cl & speaker_TFC) concatn(length,desc," TFC"); + if (cl & speaker_TFR) concatn(length,desc," TFR"); + if (cl & speaker_TBL) concatn(length,desc," TBL"); + if (cl & speaker_TBC) concatn(length,desc," TBC"); + if (cl & speaker_TBR) concatn(length,desc," TBR"); + + concatn(length,desc,"\n"); + } + if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) { snprintf(temp,TEMPSIZE, "looping: %s\n" diff --git a/src/vgmstream.h b/src/vgmstream.h index 817f2c51..80e21d19 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -727,6 +727,48 @@ typedef enum { } meta_t; +/* standard WAVEFORMATEXTENSIBLE speaker positions */ +typedef enum { + speaker_FL = (1 << 0), /* front left */ + speaker_FR = (1 << 1), /* front right */ + speaker_FC = (1 << 2), /* front center */ + speaker_LFE = (1 << 3), /* low frequency effects */ + speaker_BL = (1 << 4), /* back left */ + speaker_BR = (1 << 5), /* back right */ + speaker_FLC = (1 << 6), /* front left center */ + speaker_FRC = (1 << 7), /* front right center */ + speaker_BC = (1 << 8), /* back center */ + speaker_SL = (1 << 9), /* side left */ + speaker_SR = (1 << 10), /* side right */ + + speaker_TC = (1 << 11), /* top center*/ + speaker_TFL = (1 << 12), /* top front left */ + speaker_TFC = (1 << 13), /* top front center */ + speaker_TFR = (1 << 14), /* top front right */ + speaker_TBL = (1 << 15), /* top back left */ + speaker_TBC = (1 << 16), /* top back center */ + speaker_TBR = (1 << 17), /* top back left */ + +} speaker_t; + +/* typical mappings that metas may use to set channel_layout (but plugin must actually use it) + * (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR) */ +typedef enum { + mapping_MONO = speaker_FC, + mapping_STEREO = speaker_FL | speaker_FR, + mapping_2POINT1 = speaker_FL | speaker_FR | speaker_LFE, + mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC, + mapping_QUAD = speaker_FL | speaker_FR | speaker_BL | speaker_BR, + mapping_QUAD_surround = speaker_FL | speaker_FR | speaker_FC | speaker_BC, + mapping_5POINT0 = speaker_FL | speaker_FR | speaker_LFE | speaker_BL | speaker_BR, + mapping_5POINT0_xiph = speaker_FL | speaker_FR | speaker_FC | speaker_BL | speaker_BR, + mapping_5POINT1 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR, + mapping_5POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_SL | speaker_SR, + mapping_7POINT0 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BC | speaker_FLC | speaker_FRC, + mapping_7POINT1 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR | speaker_FLC | speaker_FRC, + mapping_7POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR | speaker_SL | speaker_SR, +} mapping_t; + #ifdef VGMSTREAM_MIXING /* mixing info */ typedef enum { @@ -832,6 +874,9 @@ typedef struct { size_t stream_size; /* info to properly calculate bitrate in case of subsongs */ char stream_name[STREAM_NAME_SIZE]; /* name of the current stream (info), if the file stores it and it's filled */ + /* mapping config (info for plugins) */ + uint32_t channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */ + /* other config */ int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ #ifndef VGMSTREAM_MIXING