vgmstream/src/meta/bik.c

126 lines
4.8 KiB
C

#include "meta.h"
#include "../coding/coding.h"
#include "../util.h"
#ifdef VGM_USE_FFMPEG
static uint32_t bik_get_num_samples(STREAMFILE *streamFile, int bits_per_sample);
/* BIK 1/2 - RAD Game Tools movies (audio/video format) */
VGMSTREAM * init_vgmstream_bik(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
ffmpeg_codec_data *data = NULL;
/* check extension, case insensitive (bika = manually demuxed audio) */
if (!check_extensions(streamFile,"bik,bika,bik2,bik2a,bk2,bk2a")) goto fail;
/* check header "BIK" (bik1) or "KB2" (bik2) (followed by version-char) */
if ((read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x42494B00 &&
(read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x4B423200 ) goto fail;
/* FFmpeg can parse BIK audio, but can't get the number of samples, which vgmstream needs.
* The only way to get them is to read all frame headers */
data = init_ffmpeg_offset(streamFile, 0x0, get_streamfile_size(streamFile));
if (!data) goto fail;
vgmstream = allocate_vgmstream(data->channels, 0); /* alloc FFmpeg first to get channel count */
if (!vgmstream) goto fail;
vgmstream->codec_data = data;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_FFmpeg;
vgmstream->sample_rate = data->sampleRate;
/* manually get num_samples since data->totalSamples is always 0 */
vgmstream->num_samples = bik_get_num_samples(streamFile, data->bitsPerSample);
if (vgmstream->num_samples == 0)
goto fail;
return vgmstream;
fail:
free_ffmpeg(data);
if (vgmstream) {
vgmstream->codec_data = NULL;
close_vgmstream(vgmstream);
}
return NULL;
}
/**
* Gets the number of samples in a BIK file by reading all frames' headers,
* as they are not in the main header. The header for BIK1 and 2 is the same.
* (a ~3 min movie needs ~6000-7000 frames = fseeks, should be fast enough)
*
* Needs bits per sample to calculate PCM samples, since most bink audio seems to use 32, actually.
*/
static uint32_t bik_get_num_samples(STREAMFILE *streamFile, int bits_per_sample) {
uint32_t *offsets = NULL;
uint32_t num_samples_b = 0;
off_t cur_offset;
size_t filesize;
int i, j, num_frames, num_tracks;
int target_stream = 0;
filesize = get_streamfile_size(streamFile);
num_frames = read_32bitLE(0x08,streamFile);
if (num_frames<=0) goto fail;
/* multichannel audio is usually N tracks of stereo/mono, no way to know channel layout */
num_tracks = read_32bitLE(0x28,streamFile);
if (num_tracks<=0 || num_tracks > 255) goto fail;
/* find the frame index table, which is after 3 audio headers of size 4 for each track */
cur_offset = 0x2c + num_tracks*4 * 3;
/* read offsets in a buffer, to avoid fseeking to the table back and forth
* the number of frames can be highly variable so we'll alloc */
offsets = malloc(sizeof(uint32_t) * num_frames);
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;
/* multistream support just for fun (FFmpeg should select the same target stream)
* (num_samples for other streams seem erratic though) */
if (target_stream > num_tracks) goto fail;
if (target_stream == 0) target_stream = 1;
VGM_ASSERT(num_tracks > 1, "BIK: multiple streams found (%i entries)\n", num_tracks);
/* read each frame header and sum all samples
* a frame has N audio packets with header (one per track) + video packet */
for (i=0; i < num_frames; i++) {
cur_offset = offsets[i];
/* read audio packet headers */
for (j=0; j < num_tracks; j++) {
uint32_t ap_size, samples_b;
ap_size = read_32bitLE(cur_offset+0x00,streamFile); /* not counting this int */
samples_b = read_32bitLE(cur_offset+0x04,streamFile); /* decoded samples in bytes */
if (ap_size==0) break; /* no audio in this frame */
if (j == target_stream-1) { /* target samples found, read next frame */
num_samples_b += samples_b;
break;
} else { /* check next audio packet */
cur_offset += 4 + ap_size; /* todo sometimes ap_size doesn't include itself (+4), others it does? */
}
}
}
free(offsets);
return num_samples_b / (bits_per_sample / 8);
fail:
free(offsets);
return 0;
}
#endif