diff --git a/src/coding/ffmpeg_decoder.c b/src/coding/ffmpeg_decoder.c index ae17c0cf..668f2e9d 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -322,7 +322,7 @@ ffmpeg_codec_data* init_ffmpeg_header_offset_subsong(STREAMFILE* sf, uint8_t* he goto fail; if (size == 0 || start + size > get_streamfile_size(sf)) { - VGM_LOG("FFMPEG: wrong start+size found: %x + %x > %x \n", (uint32_t)start, (uint32_t)size, get_streamfile_size(sf)); + vgm_logi(size != 0, "FFMPEG: wrong start+size found: %x + %x > %x \n", (uint32_t)start, (uint32_t)size, get_streamfile_size(sf)); size = get_streamfile_size(sf) - start; } diff --git a/src/meta/bik.c b/src/meta/bik.c index 8b0bf0de..58643d9c 100644 --- a/src/meta/bik.c +++ b/src/meta/bik.c @@ -1,147 +1,154 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../util.h" - -static int bink_get_info(STREAMFILE *streamFile, int target_subsong, int * out_total_streams, size_t *out_stream_size, int * out_channel_count, int * out_sample_rate, int * out_num_samples); - -/* BINK 1/2 - RAD Game Tools movies (audio/video format) */ -VGMSTREAM * init_vgmstream_bik(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - int channel_count = 0, loop_flag = 0, sample_rate = 0, num_samples = 0; - int total_subsongs = 0, target_subsong = streamFile->stream_index; - size_t stream_size; - - /* checks */ - /* .bik/bik2/bk2: standard - * .bika = fake extension for demuxed audio */ - if (!check_extensions(streamFile,"bik,bika,bik2,bk2")) - goto fail; - - /* check header "BIK" (bink 1) or "KB2" (bink 2), followed by version-char (audio is the same for both) */ - if ((read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x42494B00 && - (read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x4B423200 ) goto fail; - - /* find target stream info and samples */ - if (!bink_get_info(streamFile, target_subsong, &total_subsongs, &stream_size, &channel_count, &sample_rate, &num_samples)) - goto fail; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->layout_type = layout_none; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->num_streams = total_subsongs; - vgmstream->stream_size = stream_size; - vgmstream->meta_type = meta_BINK; - -#ifdef VGM_USE_FFMPEG - { - /* target_subsong should be passed manually */ - - vgmstream->codec_data = init_ffmpeg_header_offset_subsong(streamFile, NULL,0, 0x0,get_streamfile_size(streamFile), target_subsong); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - } -#else - goto fail; -#endif - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/** - * Gets stream info, and number of samples in a BINK file by reading all frames' headers (as it's VBR), - * as they are not in the main header. The header for BINK1 and 2 is the same. - * (a ~3 min movie needs ~6000-7000 frames = fseeks, should be fast enough) - */ -static int bink_get_info(STREAMFILE *streamFile, int target_subsong, int * out_total_subsongs, size_t * out_stream_size, int * out_channel_count, int * out_sample_rate, int * out_num_samples) { - uint32_t *offsets = NULL; - uint32_t num_frames, num_samples_b = 0; - off_t cur_offset; - int i, j, sample_rate, channel_count; - int total_subsongs; - size_t stream_size = 0; - - size_t filesize = get_streamfile_size(streamFile); - uint32_t signature = (read_32bitBE(0x00,streamFile) & 0xffffff00); - uint8_t revision = (read_32bitBE(0x00,streamFile) & 0xFF); - - if (read_32bitLE(0x04,streamFile)+ 0x08 != filesize) - goto fail; - - num_frames = (uint32_t)read_32bitLE(0x08,streamFile); - if (num_frames == 0 || num_frames > 0x100000) goto fail; /* something must be off (avoids big allocs below) */ - - /* multichannel/multilanguage audio is usually N streams of stereo/mono, no way to know channel layout */ - total_subsongs = read_32bitLE(0x28,streamFile); - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1 || total_subsongs > 255) goto fail; - - /* find stream info and position in offset table */ - cur_offset = 0x2c; - if ((signature == 0x42494B00 && (revision == 0x6b)) || /* k */ - (signature == 0x4B423200 && (revision == 0x69 || revision == 0x6a || revision == 0x6b))) /* i,j,k */ - cur_offset += 0x04; /* unknown v2 header field */ - cur_offset += 0x04*total_subsongs; /* skip streams max packet bytes */ - sample_rate = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x00,streamFile); - channel_count = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x02,streamFile) & 0x2000 ? 2 : 1; /* stereo flag */ - cur_offset += 0x04*total_subsongs; /* skip streams info */ - cur_offset += 0x04*total_subsongs; /* skip streams ids */ - - - /* read frame offsets in a buffer, to avoid fseeking to the table back and forth */ - offsets = malloc(sizeof(uint32_t) * num_frames); - if (!offsets) goto fail; - - for (i=0; i < num_frames; i++) { - offsets[i] = read_32bitLE(cur_offset,streamFile) & 0xFFFFFFFE; /* mask first bit (= keyframe) */ - cur_offset += 0x4; - - if (offsets[i] > filesize) goto fail; - } - /* after the last index is the file size, validate just in case */ - if (read_32bitLE(cur_offset,streamFile) != filesize) goto fail; - - /* read each frame header and sum all samples - * a frame has N audio packets with a header (one per stream) + video packet */ - for (i=0; i < num_frames; i++) { - cur_offset = offsets[i]; - - /* read audio packet headers per stream */ - for (j=0; j < total_subsongs; j++) { - uint32_t ap_size = read_32bitLE(cur_offset+0x00,streamFile); /* not counting this int */ - - if (j == target_subsong-1) { - stream_size += 0x04 + ap_size; - if (ap_size > 0) - num_samples_b += read_32bitLE(cur_offset+0x04,streamFile); /* decoded samples in bytes */ - break; /* next frame */ - } - else { /* next stream packet or frame */ - cur_offset += 4 + ap_size; //todo sometimes ap_size doesn't include itself (+4), others it does? - } - } - } - - free(offsets); - - - if (out_total_subsongs) *out_total_subsongs = total_subsongs; - if (out_stream_size) *out_stream_size = stream_size; - if (out_sample_rate) *out_sample_rate = sample_rate; - if (out_channel_count) *out_channel_count = channel_count; - //todo returns a few more samples (~48) than binkconv.exe? - if (out_num_samples) *out_num_samples = num_samples_b / (2 * channel_count); - - return 1; - -fail: - free(offsets); - return 0; -} +#include "meta.h" +#include "../coding/coding.h" +#include "../util.h" + +static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples); + +/* BINK 1/2 - RAD Game Tools movies (audio/video format) */ +VGMSTREAM* init_vgmstream_bik(STREAMFILE* sf) { + VGMSTREAM * vgmstream = NULL; + int channels = 0, loop_flag = 0, sample_rate = 0, num_samples = 0; + int total_subsongs = 0, target_subsong = sf->stream_index; + size_t stream_size; + + /* checks */ + /* .bik/bik2/bk2: standard + * .bika: fake extension for demuxed audio */ + if (!check_extensions(sf,"bik,bik2,bk2,bika")) + goto fail; + + /* check bink1/2 header, followed by version-char (audio is the same) */ + if ((read_32bitBE(0x00,sf) & 0xffffff00) != get_id32be("BIK\0") && + (read_32bitBE(0x00,sf) & 0xffffff00) != get_id32be("KB2\0")) + goto fail; + + /* find target stream info and samples */ + if (!bink_get_info(sf, target_subsong, &total_subsongs, &stream_size, &channels, &sample_rate, &num_samples)) + goto fail; + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->layout_type = layout_none; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + vgmstream->meta_type = meta_BINK; + +#ifdef VGM_USE_FFMPEG + { + /* target_subsong should be passed manually */ + vgmstream->codec_data = init_ffmpeg_header_offset_subsong(sf, NULL,0, 0x0,0, target_subsong); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + } +#else + goto fail; +#endif + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/** + * Gets stream info, and number of samples in a BINK file by reading all frames' headers (as it's VBR), + * as they are not in the main header. The header for BINK1 and 2 is the same. + * (a ~3 min movie needs ~6000-7000 frames = fseeks, should be fast enough) + */ +static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples) { + uint32_t* offsets = NULL; + uint32_t num_frames, num_samples_b = 0; + off_t cur_offset; + int i, j, sample_rate, channels; + int total_subsongs; + size_t stream_size = 0; + + size_t filesize = get_streamfile_size(sf); + uint32_t signature = (read_32bitBE(0x00,sf) & 0xffffff00); + uint8_t revision = (read_32bitBE(0x00,sf) & 0xFF); + + + if (read_32bitLE(0x04,sf) + 0x08 != filesize) + goto fail; + + num_frames = (uint32_t)read_32bitLE(0x08,sf); + if (num_frames == 0 || num_frames > 0x100000) goto fail; /* something must be off (avoids big allocs below) */ + + /* multichannel/multilanguage audio is usually N streams of stereo/mono, no way to know channel layout */ + total_subsongs = read_32bitLE(0x28,sf); + if (total_subsongs < 1) { + vgm_logi("BIK: no audio in movie (ignore)\n"); + goto fail; + } + + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs > 255) goto fail; + + + /* find stream info and position in offset table */ + cur_offset = 0x2c; + if ((signature == 0x42494B00 && (revision == 0x6b)) || /* k */ + (signature == 0x4B423200 && (revision == 0x69 || revision == 0x6a || revision == 0x6b))) /* i,j,k */ + cur_offset += 0x04; /* unknown v2 header field */ + cur_offset += 0x04*total_subsongs; /* skip streams max packet bytes */ + sample_rate = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x00,sf); + channels = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x02,sf) & 0x2000 ? 2 : 1; /* stereo flag */ + cur_offset += 0x04*total_subsongs; /* skip streams info */ + cur_offset += 0x04*total_subsongs; /* skip streams ids */ + + + /* read frame offsets in a buffer, to avoid fseeking to the table back and forth */ + offsets = malloc(sizeof(uint32_t) * num_frames); + if (!offsets) goto fail; + + for (i=0; i < num_frames; i++) { + offsets[i] = read_32bitLE(cur_offset,sf) & 0xFFFFFFFE; /* mask first bit (= keyframe) */ + cur_offset += 0x4; + + if (offsets[i] > filesize) goto fail; + } + /* after the last index is the file size, validate just in case */ + if (read_32bitLE(cur_offset,sf) != filesize) goto fail; + + /* read each frame header and sum all samples + * a frame has N audio packets with a header (one per stream) + video packet */ + for (i=0; i < num_frames; i++) { + cur_offset = offsets[i]; + + /* read audio packet headers per stream */ + for (j=0; j < total_subsongs; j++) { + uint32_t ap_size = read_32bitLE(cur_offset+0x00,sf); /* not counting this int */ + + if (j == target_subsong-1) { + stream_size += 0x04 + ap_size; + if (ap_size > 0) + num_samples_b += read_32bitLE(cur_offset+0x04,sf); /* decoded samples in bytes */ + break; /* next frame */ + } + else { /* next stream packet or frame */ + cur_offset += 4 + ap_size; //todo sometimes ap_size doesn't include itself (+4), others it does? + } + } + } + + free(offsets); + + + if (p_total_subsongs) *p_total_subsongs = total_subsongs; + if (p_stream_size) *p_stream_size = stream_size; + if (p_sample_rate) *p_sample_rate = sample_rate; + if (p_channels) *p_channels = channels; + //todo returns a few more samples (~48) than binkconv.exe? + if (p_num_samples) *p_num_samples = num_samples_b / (2 * channels); + + return 1; + +fail: + free(offsets); + return 0; +} diff --git a/src/meta/bkhd.c b/src/meta/bkhd.c index a31044b2..459e9147 100644 --- a/src/meta/bkhd.c +++ b/src/meta/bkhd.c @@ -1,5 +1,6 @@ #include "meta.h" #include "../coding/coding.h" +#include "../util/chunks.h" /* BKHD - Wwise soundbank container */ @@ -19,9 +20,9 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) { if (!check_extensions(sf,"bnk")) goto fail; - if (read_u32be(0x00, sf) == 0x414B424B) /* "AKBK" [Shadowrun (X360)] */ + if (is_id32be(0x00, sf, "AKBK")) /* [Shadowrun (X360)] */ base_offset = 0x0c; - if (read_u32be(base_offset + 0x00, sf) != 0x424B4844) /* "BKHD" */ + if (!is_id32be(base_offset + 0x00, sf, "BKHD")) goto fail; big_endian = guess_endianness32bit(base_offset + 0x04, sf); read_u32 = big_endian ? read_u32be : read_u32le; @@ -77,16 +78,41 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) { subfile_size = read_u32(offset + 0x14, sf); } else { - off_t didx_offset, data_offset, offset; - size_t didx_size; - if (!find_chunk(sf, 0x44494458, 0x00,0, &didx_offset, &didx_size, big_endian, 0)) /* "DIDX" */ - goto fail; - if (!find_chunk(sf, 0x44415441, 0x00,0, &data_offset, NULL, big_endian, 0)) /* "DATA" */ + enum { + CHUNK_DIDX = 0x44494458, /* "DIDX" */ + CHUNK_DATA = 0x44415441, /* "DATA" */ + }; + off_t didx_offset = 0, data_offset = 0, didx_size = 0, offset; + chunk_t rc = {0}; + + rc.be_size = big_endian; + rc.current = 0x00; + while (next_chunk(&rc, sf)) { + switch(rc.type) { + + case CHUNK_DIDX: + didx_offset = rc.offset; + didx_size = rc.size; + break; + + case CHUNK_DATA: + data_offset = rc.offset; + break; + + default: + break; + } + } + if (!didx_offset || !data_offset) goto fail; total_subsongs = didx_size / 0x0c; + if (total_subsongs < 1) { + vgm_logi("BKHD: bank has no subsongs (ignore)\n"); + goto fail; + } if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + if (target_subsong > total_subsongs) goto fail; offset = didx_offset + (target_subsong - 1) * 0x0c; subfile_id = read_u32(offset + 0x00, sf); diff --git a/src/meta/smk.c b/src/meta/smk.c index 72113b99..e5b91735 100644 --- a/src/meta/smk.c +++ b/src/meta/smk.c @@ -1,187 +1,192 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../util.h" - -static int smacker_get_info(STREAMFILE *streamFile, int target_subsong, int * out_total_streams, size_t *out_stream_size, int * out_channel_count, int * out_sample_rate, int * out_num_samples); - -/* SMK - RAD Game Tools Smacker movies (audio/video format) */ -VGMSTREAM * init_vgmstream_smk(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - int channel_count = 0, loop_flag = 0, sample_rate = 0, num_samples = 0; - int total_subsongs = 0, target_subsong = streamFile->stream_index; - size_t stream_size; - - - /* checks */ - if (!check_extensions(streamFile,"smk")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x534D4B32 && /* "SMK2" */ - read_32bitBE(0x00,streamFile) != 0x534D4B34) /* "SMK4" */ - goto fail; - - /* find target stream info */ - if (!smacker_get_info(streamFile, target_subsong, &total_subsongs, &stream_size, &channel_count, &sample_rate, &num_samples)) - goto fail; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->layout_type = layout_none; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->num_streams = total_subsongs; - vgmstream->stream_size = stream_size; - vgmstream->meta_type = meta_SMACKER; - - { -#ifdef VGM_USE_FFMPEG - /* target_subsong should be passed manually */ - vgmstream->codec_data = init_ffmpeg_header_offset_subsong(streamFile, NULL,0, 0x0,get_streamfile_size(streamFile), target_subsong); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; -#else - goto fail; -#endif - } - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -typedef enum { - SMK_AUDIO_PACKED = (1<<7), - SMK_AUDIO_PRESENT = (1<<6), - SMK_AUDIO_16BITS = (1<<5), - SMK_AUDIO_STEREO = (1<<4), - SMK_AUDIO_BINK_RDFT = (1<<3), - SMK_AUDIO_BINK_DCT = (1<<2), - //SMK_AUD_UNUSED1 = (1<<1), - //SMK_AUD_UNUSED0 = (1<<0), -} smk_audio_flag; - -//todo test multilang streams and codecs other than SMACKAUD -/* Gets stream info, and number of samples in a file by reading frames - * info: https://wiki.multimedia.cx/index.php/Smacker */ -static int smacker_get_info(STREAMFILE *sf, int target_subsong, int * out_total_subsongs, size_t * out_stream_size, int * out_channel_count, int * out_sample_rate, int * out_num_samples) { - STREAMFILE *sf_index = NULL; - uint32_t flags, total_frames, trees_sizes; - off_t size_offset, type_offset, data_offset; - int i, j, sample_rate = 0, channel_count = 0, num_samples = 0; - int total_subsongs, target_stream = 0; - size_t stream_size = 0; - uint8_t stream_flags = 0; - - - /* rough format: - * - header (id, frames, video info/config, audio info) - * - frame sizes table - * - frame info table - * - huffman trees - * - frame data - */ - - /* read header */ - total_frames = read_u32le(0x0c,sf); - if (total_frames <= 0 || total_frames > 0x100000) goto fail; /* something must be off */ - - flags = read_u32le(0x14,sf); - if (flags & 1) /* extra "ring frame" */ - total_frames += 1; - - trees_sizes = read_u32le(0x34,sf); - - if (target_subsong == 0) target_subsong = 1; - total_subsongs = 0; - for (i = 0; i < 7; i++) { /* up to 7 audio (multilang?) streams */ - uint32_t audio_info = read_u32le(0x48 + 0x04*i,sf); - uint8_t audio_flags = (audio_info >> 24) & 0xFF; - int audio_rate = audio_info & 0x00FFFFFF; - - if (audio_flags & SMK_AUDIO_PRESENT) { - total_subsongs++; - - if (target_subsong == total_subsongs) { - target_stream = i; - sample_rate = audio_rate & 0x00FFFFFF; - channel_count = (audio_flags & SMK_AUDIO_STEREO) ? 2 : 1; - stream_flags = audio_flags; - } - } - } - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; - if (sample_rate == 0 || channel_count == 0) goto fail; - - /* read size and type tables into buffer */ - sf_index = reopen_streamfile(sf, total_frames*0x04 + total_frames*0x01); - if (!sf_index) goto fail; - - /* read frames and sum all samples, since some codecs are VBR */ - size_offset = 0x68; - type_offset = size_offset + total_frames*0x04; - data_offset = type_offset + total_frames*0x01 + trees_sizes; - for (i=0; i < total_frames; i++) { - uint32_t frame_size = read_u32le(size_offset,sf_index) & 0xFFFFFFFC; /* last 2 bits are keyframe flags */ - uint8_t frame_type = read_u8 (type_offset,sf_index); /* 0: has palette, 1..7: has stream N) */ - off_t offset = data_offset; - - /* skip palette */ - if (frame_type & (1<<0)) { - uint8_t palette_size = read_u8(offset,sf); - offset += palette_size * 4; - } - - /* read target audio packet and ignore rest (though probably all streams are the same) */ - for (j = 0; j < 7; j++) { - uint32_t audio_size; - - /* check if stream N exists in this frame (supposedly streams can be go in separate frames) */ - if ( !(frame_type & (1<<(j+1))) ) - continue; - - audio_size = read_u32le(offset,sf); - - if (j == target_stream) { - - /* resulting PCM bytes to samples */ - if (stream_flags & SMK_AUDIO_PACKED) { /* Smacker and maybe Bink codecs */ - uint32_t unpacked_size = read_u32le(offset+0x04,sf); - num_samples += unpacked_size / (0x02 * channel_count); - } - else if (stream_flags & SMK_AUDIO_16BITS) { /* PCM16 */ - num_samples += (audio_size - 0x04) / (0x02 * channel_count); - } - else { /* PCM8 */ - num_samples += (audio_size - 0x04) / (0x01 * channel_count); - } - } - - stream_size += audio_size; - offset += audio_size; - } - - /* rest is video packet (size = offset - data_offset) */ - - size_offset += 0x04; - type_offset += 0x01; - data_offset += frame_size; - } - - if (out_total_subsongs) *out_total_subsongs = total_subsongs; - if (out_stream_size) *out_stream_size = stream_size; - if (out_sample_rate) *out_sample_rate = sample_rate; - if (out_channel_count) *out_channel_count = channel_count; - if (out_num_samples) *out_num_samples = num_samples; - - close_streamfile(sf_index); - return 1; - -fail: - close_streamfile(sf_index); - return 0; -} +#include "meta.h" +#include "../coding/coding.h" +#include "../util.h" + +static int smacker_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples); + +/* SMK - RAD Game Tools older Smacker movies (audio/video format) */ +VGMSTREAM* init_vgmstream_smk(STREAMFILE *sf) { + VGMSTREAM* vgmstream = NULL; + int channels = 0, loop_flag = 0, sample_rate = 0, num_samples = 0; + int total_subsongs = 0, target_subsong = sf->stream_index; + size_t stream_size; + + + /* checks */ + if (!check_extensions(sf,"smk")) + goto fail; + + if (!is_id32be(0x00,sf, "SMK2") && + !is_id32be(0x00,sf, "SMK4")) + goto fail; + + /* find target stream info */ + if (!smacker_get_info(sf, target_subsong, &total_subsongs, &stream_size, &channels, &sample_rate, &num_samples)) + goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_SMACKER; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + + { +#ifdef VGM_USE_FFMPEG + /* target_subsong should be passed manually */ + vgmstream->codec_data = init_ffmpeg_header_offset_subsong(sf, NULL,0, 0x00, 0, target_subsong); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; +#else + goto fail; +#endif + } + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +typedef enum { + SMK_AUDIO_PACKED = (1<<7), + SMK_AUDIO_PRESENT = (1<<6), + SMK_AUDIO_16BITS = (1<<5), + SMK_AUDIO_STEREO = (1<<4), + SMK_AUDIO_BINK_RDFT = (1<<3), + SMK_AUDIO_BINK_DCT = (1<<2), + //SMK_AUD_UNUSED1 = (1<<1), + //SMK_AUD_UNUSED0 = (1<<0), +} smk_audio_flag; + +//todo test multilang streams and codecs other than SMACKAUD +/* Gets stream info, and number of samples in a file by reading frames + * info: https://wiki.multimedia.cx/index.php/Smacker */ +static int smacker_get_info(STREAMFILE* sf, int target_subsong, int* out_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples) { + STREAMFILE* sf_index = NULL; + uint32_t flags, total_frames, trees_sizes; + off_t size_offset, type_offset, data_offset; + int i, j, sample_rate = 0, channels = 0, num_samples = 0; + int total_subsongs, target_stream = 0; + size_t stream_size = 0; + uint8_t stream_flags = 0; + + + /* rough format: + * - header (id, frames, video info/config, audio info) + * - frame sizes table + * - frame info table + * - huffman trees + * - frame data + */ + + /* read header */ + total_frames = read_u32le(0x0c,sf); + if (total_frames <= 0 || total_frames > 0x100000) goto fail; /* something must be off */ + + flags = read_u32le(0x14,sf); + if (flags & 1) /* extra "ring frame" */ + total_frames += 1; + + trees_sizes = read_u32le(0x34,sf); + + if (target_subsong == 0) target_subsong = 1; + total_subsongs = 0; + for (i = 0; i < 7; i++) { /* up to 7 audio (multilang?) streams */ + uint32_t audio_info = read_u32le(0x48 + 0x04*i,sf); + uint8_t audio_flags = (audio_info >> 24) & 0xFF; + int audio_rate = audio_info & 0x00FFFFFF; + + if (audio_flags & SMK_AUDIO_PRESENT) { + total_subsongs++; + + if (target_subsong == total_subsongs) { + target_stream = i; + sample_rate = audio_rate & 0x00FFFFFF; + channels = (audio_flags & SMK_AUDIO_STEREO) ? 2 : 1; + stream_flags = audio_flags; + } + } + } + if (total_subsongs < 1) { + vgm_logi("SMK: no audio in movie (ignore)\n"); + goto fail; + } + if (target_subsong < 0 || target_subsong > total_subsongs) goto fail; + if (sample_rate == 0 || channels == 0) goto fail; + + /* read size and type tables into buffer */ + sf_index = reopen_streamfile(sf, total_frames*0x04 + total_frames*0x01); + if (!sf_index) goto fail; + + /* read frames and sum all samples, since some codecs are VBR */ + size_offset = 0x68; + type_offset = size_offset + total_frames*0x04; + data_offset = type_offset + total_frames*0x01 + trees_sizes; + for (i = 0; i < total_frames; i++) { + uint32_t frame_size = read_u32le(size_offset,sf_index) & 0xFFFFFFFC; /* last 2 bits are keyframe flags */ + uint8_t frame_type = read_u8 (type_offset,sf_index); /* 0: has palette, 1..7: has stream N) */ + off_t offset = data_offset; + + /* skip palette */ + if (frame_type & (1<<0)) { + uint8_t palette_size = read_u8(offset,sf); + offset += palette_size * 4; + } + + /* read target audio packet and ignore rest (though probably all streams are the same) */ + for (j = 0; j < 7; j++) { + uint32_t audio_size; + + /* check if stream N exists in this frame (supposedly streams can be go in separate frames) */ + if ( !(frame_type & (1<<(j+1))) ) + continue; + + audio_size = read_u32le(offset,sf); + + if (j == target_stream) { + + /* resulting PCM bytes to samples */ + if (stream_flags & SMK_AUDIO_PACKED) { /* Smacker and maybe Bink codecs */ + uint32_t unpacked_size = read_u32le(offset+0x04,sf); + num_samples += unpacked_size / (0x02 * channels); + } + else if (stream_flags & SMK_AUDIO_16BITS) { /* PCM16 */ + num_samples += (audio_size - 0x04) / (0x02 * channels); + } + else { /* PCM8 */ + num_samples += (audio_size - 0x04) / (0x01 * channels); + } + } + + stream_size += audio_size; + offset += audio_size; + } + + /* rest is video packet (size = offset - data_offset) */ + + size_offset += 0x04; + type_offset += 0x01; + data_offset += frame_size; + } + + if (out_total_subsongs) *out_total_subsongs = total_subsongs; + if (p_stream_size) *p_stream_size = stream_size; + if (p_sample_rate) *p_sample_rate = sample_rate; + if (p_channels) *p_channels = channels; + if (p_num_samples) *p_num_samples = num_samples; + + close_streamfile(sf_index); + return 1; + +fail: + close_streamfile(sf_index); + return 0; +} diff --git a/src/render.c b/src/render.c index a3ad90a4..15310132 100644 --- a/src/render.c +++ b/src/render.c @@ -518,7 +518,7 @@ static void seek_force_loop(VGMSTREAM* vgmstream, int loop_count) { return; /* pretend decoder reached loop end so state is set to loop start */ - vgmstream->loop_count = loop_count - 1; /* seeking to first loop musy become ++ > 0 */ + vgmstream->loop_count = loop_count - 1; /* seeking to first loop must become ++ > 0 */ vgmstream->current_sample = vgmstream->loop_end_sample; vgmstream_do_loop(vgmstream); }