cog/Frameworks/vgmstream/vgmstream/src/coding/hca_decoder.c

285 lines
9.8 KiB
C
Raw Normal View History

#include "coding.h"
#include "hca_decoder_clhca.h"
2016-06-28 07:33:58 +00:00
struct hca_codec_data {
STREAMFILE* streamfile;
clHCA_stInfo info;
signed short* sample_buffer;
size_t samples_filled;
size_t samples_consumed;
size_t samples_to_discard;
void* data_buffer;
unsigned int current_block;
void* handle;
};
/* init a HCA stream; STREAMFILE will be duplicated for internal use. */
hca_codec_data* init_hca(STREAMFILE* sf) {
uint8_t header_buffer[0x2000]; /* hca header buffer data (probable max ~0x400) */
hca_codec_data* data = NULL; /* vgmstream HCA context */
int header_size;
int status;
/* test header */
if (read_streamfile(header_buffer, 0x00, 0x08, sf) != 0x08)
goto fail;
header_size = clHCA_isOurFile(header_buffer, 0x08);
if (header_size < 0 || header_size > 0x1000)
goto fail;
if (read_streamfile(header_buffer, 0x00, header_size, sf) != header_size)
goto fail;
/* init vgmstream context */
data = calloc(1, sizeof(hca_codec_data));
if (!data) goto fail;
/* init library handle */
data->handle = calloc(1, clHCA_sizeof());
clHCA_clear(data->handle);
status = clHCA_DecodeHeader(data->handle, header_buffer, header_size); /* parse header */
if (status < 0) {
VGM_LOG("HCA: unsupported header found, %i\n", status);
goto fail;
}
status = clHCA_getInfo(data->handle, &data->info); /* extract header info */
if (status < 0) goto fail;
data->data_buffer = malloc(data->info.blockSize);
if (!data->data_buffer) goto fail;
data->sample_buffer = malloc(sizeof(signed short) * data->info.channelCount * data->info.samplesPerBlock);
if (!data->sample_buffer) goto fail;
/* load streamfile for reads */
data->streamfile = reopen_streamfile(sf, 0);
if (!data->streamfile) goto fail;
/* set initial values */
reset_hca(data);
return data;
fail:
free_hca(data);
return NULL;
}
void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
int samples_done = 0;
const unsigned int channels = data->info.channelCount;
const unsigned int blockSize = data->info.blockSize;
while (samples_done < samples_to_do) {
if (data->samples_filled) {
int samples_to_get = data->samples_filled;
if (data->samples_to_discard) {
/* discard samples for looping */
if (samples_to_get > data->samples_to_discard)
samples_to_get = data->samples_to_discard;
data->samples_to_discard -= samples_to_get;
}
else {
/* get max samples and copy */
if (samples_to_get > samples_to_do - samples_done)
samples_to_get = samples_to_do - samples_done;
memcpy(outbuf + samples_done*channels,
data->sample_buffer + data->samples_consumed*channels,
samples_to_get*channels * sizeof(sample));
samples_done += samples_to_get;
}
/* mark consumed samples */
data->samples_consumed += samples_to_get;
data->samples_filled -= samples_to_get;
2016-06-28 07:33:58 +00:00
}
else {
off_t offset = data->info.headerSize + data->current_block * blockSize;
int status;
size_t bytes;
/* EOF/error */
if (data->current_block >= data->info.blockCount) {
memset(outbuf, 0, (samples_to_do - samples_done) * channels * sizeof(sample));
break;
}
/* read frame */
bytes = read_streamfile(data->data_buffer, offset, blockSize, data->streamfile);
if (bytes != blockSize) {
VGM_LOG("HCA: read %x vs expected %x bytes at %x\n", bytes, blockSize, (uint32_t)offset);
break;
}
data->current_block++;
/* decode frame */
status = clHCA_DecodeBlock(data->handle, (void*)(data->data_buffer), blockSize);
if (status < 0) {
VGM_LOG("HCA: decode fail at %x, code=%i\n", (uint32_t)offset, status);
break;
}
/* extract samples */
clHCA_ReadSamples16(data->handle, data->sample_buffer);
data->samples_consumed = 0;
data->samples_filled += data->info.samplesPerBlock;
}
}
}
void reset_hca(hca_codec_data* data) {
if (!data) return;
clHCA_DecodeReset(data->handle);
data->current_block = 0;
data->samples_filled = 0;
data->samples_consumed = 0;
data->samples_to_discard = data->info.encoderDelay;
}
void loop_hca(hca_codec_data* data, int32_t num_sample) {
if (!data) return;
/* manually calc loop values if not set (should only happen with installed/forced looping,
* as actual files usually pad encoder delay so earliest loopStartBlock becomes 1-2,
* probably for decoding cleanup so this may not be as exact) */
if (data->info.loopStartBlock == 0 && data->info.loopStartDelay == 0) {
int target_sample = num_sample + data->info.encoderDelay;
data->info.loopStartBlock = target_sample / data->info.samplesPerBlock;
data->info.loopStartDelay = target_sample - (data->info.loopStartBlock * data->info.samplesPerBlock);
}
data->current_block = data->info.loopStartBlock;
data->samples_filled = 0;
data->samples_consumed = 0;
data->samples_to_discard = data->info.loopStartDelay;
}
void free_hca(hca_codec_data* data) {
if (!data) return;
close_streamfile(data->streamfile);
clHCA_done(data->handle);
free(data->handle);
free(data->data_buffer);
free(data->sample_buffer);
free(data);
}
/* ************************************************************************* */
/* Test a single HCA key and assign an score for comparison. Multiple keys could potentially result
* in "playable" results (mostly silent with random clips), so it also checks the resulting PCM.
* Currently wrong keys should be detected during decoding+un-xor test, so this score may not
* be needed anymore, but keep around in case CRI breaks those tests in the future. */
/* arbitrary scale to simplify score comparisons */
#define HCA_KEY_SCORE_SCALE 10
/* ignores beginning frames (~10 is not uncommon, Dragalia Lost vocal layers have lots) */
#define HCA_KEY_MAX_SKIP_BLANKS 1200
/* 5~15 should be enough, but almost silent or badly mastered files may need tweaks
* (ex. newer Tales of the Rays files clip a lot and need +6 as some keys give almost-ok results) */
#define HCA_KEY_MIN_TEST_FRAMES 7
#define HCA_KEY_MAX_TEST_FRAMES 12
/* score of 10~30 isn't uncommon in a single frame, too many frames over that is unlikely */
#define HCA_KEY_MAX_FRAME_SCORE 150
#define HCA_KEY_MAX_TOTAL_SCORE (HCA_KEY_MAX_TEST_FRAMES * 50*HCA_KEY_SCORE_SCALE)
/* Test a number of frames if key decrypts correctly.
* Returns score: <0: error/wrong, 0: unknown/silent file, >0: good (the closest to 1 the better). */
int test_hca_key(hca_codec_data* data, unsigned long long keycode) {
size_t test_frames = 0, current_frame = 0, blank_frames = 0;
int total_score = 0, found_regular_frame = 0;
const unsigned int blockSize = data->info.blockSize;
/* Due to the potentially large number of keys this must be tuned for speed.
* Buffered IO seems fast enough (not very different reading a large block once vs frame by frame).
* clHCA_TestBlock could be optimized a bit more. */
clHCA_SetKey(data->handle, keycode);
/* Test up to N non-blank frames or until total frames. */
/* A final score of 0 (=silent) is only possible for short files with all blank frames */
while (test_frames < HCA_KEY_MAX_TEST_FRAMES && current_frame < data->info.blockCount) {
off_t offset = data->info.headerSize + current_frame * blockSize;
int score;
size_t bytes;
/* read and test frame */
bytes = read_streamfile(data->data_buffer, offset, blockSize, data->streamfile);
if (bytes != blockSize) {
/* normally this shouldn't happen, but pre-fetch ACB stop with frames in half, so just keep score */
//total_score = -1;
break;
}
score = clHCA_TestBlock(data->handle, (void*)(data->data_buffer), blockSize);
if (score < 0 || score > HCA_KEY_MAX_FRAME_SCORE) {
total_score = -1;
break;
}
current_frame++;
/* ignore silent frames at the beginning, up to a point */
if (score == 0 && blank_frames < HCA_KEY_MAX_SKIP_BLANKS && !found_regular_frame) {
blank_frames++;
continue;
}
found_regular_frame = 1;
test_frames++;
/* scale values to make scores of perfect frames more detectable */
switch(score) {
case 1: score = 1; break;
case 0: score = 3*HCA_KEY_SCORE_SCALE; break; /* blanks after non-blacks aren't very trustable */
default: score = score*HCA_KEY_SCORE_SCALE;
}
total_score += score;
/* don't bother checking more frames, other keys will get better scores */
if (total_score > HCA_KEY_MAX_TOTAL_SCORE)
break;
}
//;VGM_LOG("HCA KEY: blanks=%i, tests=%i, score=%i\n", blank_frames, test_frames, total_score);
/* signal best possible score (many perfect frames and few blank frames) */
if (test_frames > HCA_KEY_MIN_TEST_FRAMES && total_score > 0 && total_score <= test_frames) {
total_score = 1;
}
clHCA_DecodeReset(data->handle);
return total_score;
}
void hca_set_encryption_key(hca_codec_data* data, uint64_t keycode) {
clHCA_SetKey(data->handle, (unsigned long long)keycode);
}
clHCA_stInfo* hca_get_info(hca_codec_data* data) {
return &data->info;
}
STREAMFILE* hca_get_streamfile(hca_codec_data* data) {
if (!data) return NULL;
return data->streamfile;
}