vgmstream/src/coding/mpeg_custom_utils_awc.c
bnnm 7d9e5a93e4 Make MPEG custom decoder use their own data buffer instead sharing one
In rare cases (ex. EALayer3) mpg123 may need several passes to feed one
stream's data buffer, and meanwhile other streams would overwrite it
2017-12-01 20:02:30 +01:00

185 lines
7.2 KiB
C

#include "mpeg_decoder.h"
#ifdef VGM_USE_MPEG
/**
* AWC music uses blocks (sfx doesn't), the fun part being each channel has different num_samples/frames
* per block, so it's unsuitable for the normal "blocked" layout and parsed here instead.
* Channel data is separate within the block (first all frames of ch0, then ch1, etc), padded, and sometimes
* the last few frames of a channel are repeated in the new block (marked with the "discard samples" field).
*/
/* block header size, algined/padded to 0x800 */
static size_t get_block_header_size(STREAMFILE *streamFile, off_t offset, mpeg_codec_data *data) {
size_t header_size = 0;
int i;
int entries = data->config.channels;
int32_t (*read_32bit)(off_t,STREAMFILE*) = data->config.big_endian ? read_32bitBE : read_32bitLE;
for (i = 0; i < entries; i++) {
header_size += 0x18;
header_size += read_32bit(offset + 0x18*i + 0x04, streamFile) * 0x04; /* entries in the table */
}
if (header_size % 0x800) /* padded */
header_size += 0x800 - (header_size % 0x800);
return header_size;
}
/* find data that repeats in the beginning of a new block at the end of last block */
static size_t get_repeated_data_size(STREAMFILE *streamFile, off_t new_offset, off_t last_offset) {
uint8_t new_frame[0x1000];/* buffer to avoid fseek back and forth */
mpeg_frame_info info;
off_t off;
int i;
/* read block first frame */
if ( !mpeg_get_frame_info(streamFile, new_offset, &info))
goto fail;
if (info.frame_size > 0x1000)
goto fail;
if (read_streamfile(new_frame,new_offset, info.frame_size,streamFile) != info.frame_size)
goto fail;
/* find the frame in last bytes of prev block */
off = last_offset - 0x4000; /* typical max is 5-10 frames of ~0x200, no way to know exact size */
while (off < last_offset) {
/* compare frame vs prev block data */
for (i = 0; i < info.frame_size; i++) {
if ((uint8_t)read_8bit(off+i,streamFile) != new_frame[i])
break;
}
/* frame fully compared? */
if (i == info.frame_size)
return last_offset - off;
else
off += i+1;
}
fail:
VGM_LOG("AWC: can't find repeat size, new=0x%08lx, last=0x%08lx\n", new_offset, last_offset);
return 0; /* keep on truckin' */
}
/* init config and validate */
int mpeg_custom_setup_init_awc(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
mpeg_frame_info info;
int is_music;
/* start_offset can point to a block header that always starts with 0 (music) or normal data (sfx) */
is_music = read_32bitBE(start_offset, streamFile) == 0x00000000;
if (is_music)
start_offset += get_block_header_size(streamFile, start_offset, data);
/* get frame info at offset */
if ( !mpeg_get_frame_info(streamFile, start_offset, &info))
goto fail;
switch(info.layer) {
case 1: *coding_type = coding_MPEG_layer1; break;
case 2: *coding_type = coding_MPEG_layer2; break;
case 3: *coding_type = coding_MPEG_layer3; break;
default: goto fail;
}
data->channels_per_frame = info.channels;
data->samples_per_frame = info.frame_samples;
/* extra checks */
if (is_music) {
if (data->config.chunk_size <= 0)
goto fail; /* needs block size */
}
/* no big encoder delay added (for sfx they can start in less than ~300 samples) */
return 1;
fail:
return 0;
}
/* writes data to the buffer and moves offsets, parsing AWC blocks */
int mpeg_custom_parse_frame_awc(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
mpeg_custom_stream *ms = data->streams[num_stream];
mpeg_frame_info info;
size_t current_data_size = 0, data_offset;
size_t file_size = get_streamfile_size(stream->streamfile);
int i;
/* blocked layout used for music */
if (data->config.chunk_size) {
off_t last_offset = stream->offset; /* when block end needs to be known */
/* block ended for this channel, move to next block start */
if (ms->current_size_count > 0 && ms->current_size_count == ms->current_size_target) {
//mpg123_open_feed(ms->m); //todo reset maybe needed?
data_offset = stream->offset - stream->channel_start_offset; /* ignoring header */
data_offset -= data_offset % data->config.chunk_size; /* start of current block */
stream->offset = stream->channel_start_offset + data_offset + data->config.chunk_size;
ms->current_size_count = 0;
ms->current_size_target = 0;
}
/* just in case, shouldn't happen */
if (stream->offset >= file_size) {
goto fail;
}
/* block starts for this channel, point to mpeg data */
if (ms->current_size_count == 0) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = data->config.big_endian ? read_32bitBE : read_32bitLE;
off_t channel_offset = 0;
/* block has a header with base info per channel and table per channel (see blocked_awc.c) */
ms->decode_to_discard = read_32bit(stream->offset + 0x18*num_stream + 0x08, stream->streamfile);
ms->current_size_target = read_32bit(stream->offset + 0x18*num_stream + 0x14, stream->streamfile);
for (i = 0; i < num_stream; i++) { /* num_stream serves as channel */
size_t channel_size = read_32bit(stream->offset + 0x18*i + 0x14, stream->streamfile);
if (channel_size % 0x10) /* 32b aligned */
channel_size += 0x10 - channel_size % 0x10;
channel_offset += channel_size;
}
//;VGM_ASSERT(ms->decode_to_discard > 0, "AWC: s%i discard of %x found at chunk %lx\n", num_stream, ms->decode_to_discard, stream->offset);
stream->offset += channel_offset + get_block_header_size(stream->streamfile, stream->offset, data);
/* A new block may repeat frame bytes from prev block, and decode_to_discard has the number of repeated samples.
* However in RDR PS3 (not GTA5?) the value can be off (ie. discards 1152 but the repeat decodes to ~1152*4).
* I can't figure out why, so just find and skip the repeat data manually (probably better for mpg123 too) */
if (ms->decode_to_discard) {
size_t repeat = get_repeated_data_size(stream->streamfile, stream->offset, last_offset);
if (repeat > 0)
ms->decode_to_discard = 0;
stream->offset += repeat;
ms->current_size_target -= repeat;
}
}
}
/* update frame */
if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) )
goto fail;
current_data_size = info.frame_size;
ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset, current_data_size, stream->streamfile);
stream->offset += current_data_size;
ms->current_size_count += current_data_size;
return 1;
fail:
return 0;
}
#endif